Fork me on GitHub
Symfony2cheatsheet

Finally, a Symfony2 guide to make your work easier.

v 2.1.*

CONTROLLER back to top

Here are some useful shortcuts and functions regarding the request in Symfony 2 controllers.

REQUEST and RESPONSE objects

$request->query->get('foo'); //gets foo GET var.
$request->request->get('bar'); //gets POST var.
$request->getMethod();

$request->server->get('HTTP_HOST'); //server variables.
$request->getPathInfo(); //gets the URI.
$request->files->get('file'); //files posted in a form.

$request->headers->get('content-type');
$request->cookies->get('PHPSESSID'); //cookies

$request->getLanguages();
$request->getPreferedLanguage(array('es','fr'));
$request->isXmlHttpRequest();

Redirecting in a controller:

$this->redirect($this->generateUrl("homepage"));

Rendering text from a controller:

return new Response('<html>…</html>');

Forwarding:

return $this->forward('Bundle:Controller:Action');

Redirect to 404 not found:

throw $this->createNotFoundException(message);

Working with the session

You can manage session attributes with:

$session = $this->getRequest()->getSession();

or the shortcut version

$this->get('session');

and to work with the data:

$session->get('foo','default value');
$session->set('foo','bar');

Flash messages

Flash messages only last one request and they are stored in a FlashBag:

$this->get('session')->getFlashBag()->add('notice','message');

To iterate trough all flash messages in a template you can use:

{% for flashMessage in app.session.flashbag.get('notice') %}
    <div class="flash notice">
        {{ flashMessage }}
    </div>
{% endfor %}

Finally, here is an example of a controller class with Request and Response object in use.

namespace Symfony\CheatSheetBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class DefaultController extends Controller
{
    public function indexAction()
    {
        return $this->render('SymfonyCheatSheetBundle:Default:index.html.twig');
    }

    public function contactAction(Request $request)
    {
        //get request variables.
        //do something, call service, go to database, create form, send emails, etc...
        return $this->render('SymfonyCheatSheetBundle:Default:feedback.html.twig', array([template vars]));
    }
}

ROUTING back to top

Routing in Symfony 2 is even easier than in Symfony 1.x. Here is an example of the most complex routing you can get in Symfony 2.

article_show:
    pattern: /{_locale}/article-details/{page}.{_format}
    defaults: {_controller:Bundle:Controller:Action, _format: html, page:1}
    requirements:
        _locale: en|fr
        _format: html|rss
        page: \d+
        _scheme: http|https

Also you can prefix imported routes and give a group of routes a prepend text:

#app/config/routing.yml
    acme_hello:
    resource: "@AcmeHelloBundle/Resources/config/routing.yml"
    prefix: /admin

Working with annotations

You can use annotations in your controller by enabling annotations in your routing.yml and in your config.yml

#config.yml
sensio_framework_extra:
    router:  { annotations: true }
    request: { converters: true }
    view:    { annotations: true }
    cache:   { annotations: true }
#routing.yml
acmedemo_main:
    resource: "@AcmeDemoWebBundle/Controller"
    prefix:   /
    type: annotation

In your controller:

/**
* @Route("/{_locale}/", name="localizedHomepage")
* @Method("POST")
* @param $name
* @Template("AcmeDemoWebBundle:Web:index.html.twig")
* @Cache(expires="+30 days")
*/
public function localizedHomepageAction($name)
{
    return array('name' => $name);
}

TEMPLATING and TWIG back to top

Including partials and components

{% include "Bundle:Controller:action" %}
in Symfony 2.1:
{% render "Bundle:Controller:action" with {"max" : 3} %}
in Symfony 2.2:
{{ render(controller("Bundle:Controller:action", {max :3})) }}

Links

<a href="{{ path('homepage') }}">Home<a/> //relative
<a href="{{ url('homepage') }}">Home<a/> //absolute
<a href="{{ path('show', {'id':article.id}) }}">Home</a>

Assets

<img src="{{ 'uploads/'~foto.url }}"/>

Debug variables in a template

{{ dump(article) }}
            

Global TWIG variables

app.security
app.user
app.request
app.request.get('foo') //get
app.request.request.get('foo') //post
app.session
app.environment
app.debug

TWIG TAGS

block

When a template uses inheritance and if you want to print a block multiple times, use the block function:

<title>{% block title %}{% endblock %}</title>
<h1>{{ block('title') }}</h1>
{% block body %}{% endblock %}

parent

When a template uses inheritance, it's possible to render the contents of the parent block when overriding a block by using the parent function:

{% extends "base.html" %}

{% block sidebar %}
<h3>Table Of Contents</h3>
...
{{ parent() }}
{% endblock %}

for

Loop over each item in a sequence. For example, to display a list of users provided in a variable called users:

<h1>Members</h1>
<ul>
{% for user in users %}
<li>{{ user.username|e }}</li>
{% endfor %}
</ul>
The loop variable

Inside of a for loop block you can access some special variables:

Variable Description
loop.index The current iteration of the loop. (1 indexed)
loop.index0 The current iteration of the loop. (0 indexed)
loop.revindex The number of iterations from the end of the loop (1 indexed)
loop.revindex0 The number of iterations from the end of the loop (0 indexed)
loop.first True if first iteration
loop.last True if last iteration
loop.length The number of items in the sequence
loop.parent The parent context
{% for user in users %}
{{ loop.index }} - {{ user.username }}
{% endfor %}

if

The if statement in Twig is comparable with the if statements of PHP.

In the simplest form you can use it to test if an expression evaluates to true:

{% if online == false %}
<p>Our website is in maintenance mode. Please, come back later.</p>
{% endif %}

raw

Everything inside raw tags won't be parsed.

{% raw %}
This variable {{foo}} won't be parsed as twig var.
{% endraw. %}

set

Inside code blocks you can also assign values to variables. Assignments use the set tag and can have multiple targets:

{% set foo = 'foo' %}
{% set foo = [1, 2] %}
{% set foo = {'foo': 'bar'} %}
{% set foo = 'foo' ~ 'bar' %}
{% set foo, bar = 'foo', 'bar' %}

filter

Filter sections allow you to apply regular Twig filters on a block of template data. Just wrap the code in the special filter section:

{% filter upper %}
This text becomes uppercase
{% endfilter %}

You can also chain filters:

{% filter lower|escape %}
<strong>SOME TEXT</strong>
{% endfilter %}

macro

Macros are comparable with functions in regular programming languages. They are useful to put often used HTML idioms into reusable elements to not repeat yourself.

Here is a small example of a macro that renders a form element:

{% macro input(name, value, type, size) %}
<input type="{{ type|default('text') }}" name="{{ name }}" value="{{ value|e }}" size="{{ size|default(20) }}" />
{% endmacro %}

Macros differs from native PHP functions in a few ways:

  • Default argument values are defined by using the default filter in the macro body;
  • Arguments of a macro are always optional.

But as PHP functions, macros don't have access to the current template variables.

Macros can be defined in any template, and need to be "imported" before being used (see the documentation for the import tag for more information):

{% import "forms.html" as forms %}

The above import call imports the "forms.html" file (which can contain only macros, or a template and some macros), and import the functions as items of the forms variable.

The macro can then be called at will:

<p>{{ forms.input('username') }}</p>
<p>{{ forms.input('password', null, 'password') }}</p>

If macros are defined and used in the same template, you can use the special _self variable to import them:

{% import _self as forms %}
<p>{{ forms.input('username') }}</p>

TWIG FILTERS

date

{{ post.published_at|date("m/d/Y") }}
{{ post.published_at|date("m/d/Y", "Europe/Paris") }}

date_modify

{{ post.published_at|date_modify("+1 day")|date("m/d/Y") }}

format

{{ "I like %s and %s."|format(foo, "bar") }}

replace

{{ "I like %this% and %that%."|replace({'%this%': foo, '%that%': "bar"}

number_format

{{ 200.35|number_format }}
{{ 9800.333|number_format(2, '.', ',') }}

url_encode

{{ data|url_encode() }}

json_encode

{{ data|json_encode() }}

convert_encoding

{{ data|convert_encoding('UTF-8', 'iso-2022-jp') }}

title

{{ 'my first car'|title }}
{# outputs 'My First Car' #}

capitalize

{{ 'my first car'|capitalize }}

nl2br

{{ "I like Twig.\nYou will like it too."|nl2br }}
    {# outputs

    I like Twig.<br />
    You will like it too.

    #}

raw

{{ var|raw }} {# var won't be escaped #}

trim

{{ ' I like Twig.'|trim('.') }}

upper

{{ 'welcome'|upper }}

lower

{{ 'WELCOME'|lower }}

striptags

{% some_html|striptags %}

join

{{ [1, 2, 3]|join('|') }}
{# returns 1|2|3 #}

split

{{ "one,two,three"|split(',') }}
{# returns ['one', 'two', 'three'] #}

reverse

{% for use in users|reverse %}
    ...
{% endfor %}

abs

{{ number|abs }}

length

{% if users|length > 10 %}
    ...
{% endif %}

sort

{% for use in users|sort %}
    ...
{% endfor %}

default

{{ var|default('var is not defined') }}

keys

{% for key in array|keys %}
    ...
{% endfor %}

escape

{{ user.username|e }}
{# is equivalent to #}
{{ user.username|e('html') }}
{{ user.username|e('css') }}
{{ user.username|e('js') }}

DOCTRINE back to top

Skipper & Doctrine ORM

Symfony comes with Doctrine ORM framework. Doctrine has extensive documentation, but you can start using it right away without a need to dive too deep. Skipper can make your life with Doctrine and other ORM frameworks much easier.

Skipper is a multiplatform tool for modeling ORM in a very comfortable way. You can continuously export your model and even edit exported classes without losing the changes on consequent exports. What’s also great about Skipper is that it can construct a model from an existing project. And it can do so in a very colourful and well-arranged way :-).

Start by downloading Skipper and installing on your computer. Skipper works natively on Mac OS X, Windows and Linux.

  • 1. Create a new Skipper project

    Pick your project name and select MVC Framework Symfony2, ORM Framework Doctrine2 and set a Project Path. Project Path should be the Symfony2 project root.

  • 1a. Import existing project (Optional)

    Insert existing project name and select MVC Framework Symfony2, ORM Framework Doctrine2 and set a Project Path. Project Path should be the Symfony2 project root.

    Select all the entities you want to import:

  • 2. Set export format

    When creating a new project you need to set the export format. Double click on the module title as shown by the red arrow. Module editor will be opened and you can set export format (Doctrine2PHP for Doctrine2 annotations, Doctrine2XML/Doctrine2YML for XML/YML format). You should also set relative path, so the class are exported correctly to your project structure.

  • 3. Export project

    Press “Export to ORM” and a table summarizing exported changes to the model will be displayed.

Working with the model

Your first model is now exported. Click Export to ORM button whenever you want to update your schema definitions.

Create new Entities

Select tool Entity from the top ribbon (or press Ctrl + T) and click in your model to place the new entity. Add required fields. You can navigate with keyboard shortcuts:

  • Insert: to add a new field
  • Ctrl+Up arrow: to move field up
  • Ctrl+Down arrow: to move field down
  • Tab: to skip to the next edit value
  • Ctrl+Insert: to insert a field above currently highlighted row

Create new Association

Select tool Association from the top ribbon (or press Ctrl + R) and select the entities you want to create association between. By filling either one or both aliases you can set the Association as an unidirectional or bidirectional.

Create new Many-Many Association

Select tool ManyToMany from the top ribbon (or press Ctrl + M) and select the entities you want to create association between. Again you need to fill aliases and additionally also the MN Entity name. If you don’t want to type it manually, you can use the naming tool (red arrow) which follows the usual naming conventions.

Set Doctrine ORM properties

You can set ORM properties using the property window in the lower bottom corner of Skipper. Property window shows you available properties based on the object type (Entity/Relation/Field).

Generate doctrine schema

If you have selected XML or YML format you need to generate the PHP classes generate:doctrine:entities [bundleNamespace]. This will automatically create all entity and repository classes for us. Easy, isn’t it? If you have been exporting to PHP annotations you’re ready to go.

Wide possibilities for customization

Another great thing about Skipper is a wide range of areas where it is possible to customize the behaviour of the application. For example you can configure list of external commands and executed it directly from the app:

Also you can extend shipped ORM properties with your own ones, you can add new datatypes, structures, etc. These configurations can be shared between all projects or add only to specific one. This topic is nicely described in Skipper documentation.

Persistint object into the database

Now that you have a mapped Product entity and corresponding product table, you're ready to persist data to the database. From inside a controller, this is pretty easy. Add the following method to the DefaultController of the bundle:

// src/Acme/StoreBundle/Controller/DefaultController.php

        use Acme\StoreBundle\Entity\Product;
        use Symfony\Component\HttpFoundation\Response;

        public function createAction()
        {
        $product = new Product();
        $product->setName('A Foo Bar');
        $product->setPrice('19.99');
        $product->setDescription('Lorem ipsum dolor');

        $em = $this->getDoctrine()->getManager();
        $em->persist($product); //marks object to be saved in the next transaction.
        $em->flush(); //performs all saves and transactions.

        return new Response('Created product id '.$product->getId());
        }
    

PHP UNIT TESTING back to top

Symfony2 works with PHPUnit 3.5.11 or later, though version 3.6.4 is needed to test the Symfony core code itself.

# specify the configuration directory on the command line
    $ phpunit -c app/

Unit tests

Writing Symfony2 unit tests is no different than writing standard PHPUnit unit tests. Suppose, for example, that you have an incredibly simple class called Calculator in the Utility/ directory of your bundle:

// src/Acme/DemoBundle/Tests/Utility/CalculatorTest.php
namespace Acme\DemoBundle\Tests\Utility;

use Acme\DemoBundle\Utility\Calculator;

class CalculatorTest extends \PHPUnit_Framework_TestCase
{
    public function testAdd()
    {
        $calc = new Calculator();
        $result = $calc->add(30, 12);

        // assert that our calculator added the numbers correctly!
        $this->assertEquals(42, $result);
    }
}

By convention, the Tests/ sub-directory should replicate the directory of your bundle. So, if you're testing a class in your bundle's Utility/ directory, put the test in the Tests/Utility/ directory.

Running tests for a given file or directory is also very easy:

# run all tests in the Utility directory
$ phpunit -c app src/Acme/DemoBundle/Tests/Utility/

# run tests for the Calculator class
$ phpunit -c app src/Acme/DemoBundle/Tests/Utility/CalculatorTest.php

# run all tests for the entire Bundle
$ phpunit -c app src/Acme/DemoBundle/

Functional Tests

They are no different from unit tests as far as PHPUnit is concerned, but they have a very specific workflow:

  • Make a request;
  • Test the response;
  • Click on a link or submit a form;
  • Test the response;
  • Rinse and repeat.

Symfony 2 provides a simple functional test for its DemoController as follows:

// src/Acme/DemoBundle/Tests/Controller/DemoControllerTest.php
namespace Acme\DemoBundle\Tests\Controller;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class DemoControllerTest extends WebTestCase
{
    public function testIndex()
    {
        $client = static::createClient();
        $crawler = $client->request('GET', '/demo/hello/Fabien');
        $this->assertGreaterThan(0, $crawler->filter('html:contains("Hello Fabien")')->count());
    }
}

The createClient() method returns a client, which is like a browser that you'll use to crawl your site:

The request() method returns a Crawler object which can be used to select elements in the Response, click on links, and submit forms.

  • Click on a link:

    $link = $crawler->filter('a:contains("Greet")')->eq(1)->link();
    $crawler = $client->click($link);
    
  • Submit a form:

    $form = $crawler->selectButton('submit')->form();
    
    // set some values
    $form['name'] = 'Lucas';
    $form['form_name[subject]'] = 'Hey there!';
    
    // submit the form
    $crawler = $client->submit($form);
    
  • Assertions:

    Assert that the response matches a given CSS selector.

     $this->assertGreaterThan(0, $crawler->filter('h1')->count());
    

    Check content text

     $this->assertRegExp('/Hello Fabien/', $client->getResponse()->getContent());
    

    Assert that there is more than one h2 tag with the class "subtitle"

     $this->assertGreaterThan(0, $crawler->filter('h2.subtitle')->count());
    

    Assert that there are exactly 4 h2 tags on the page

     $this->assertCount(4, $crawler->filter('h2'));
    

    Assert that the "Content-Type" header is "application/json

     $this->assertTrue($client->getResponse()->headers->contains('Content-Type','application/json'));
    

    Assert that the response content matches a regexp

    $this->assertRegExp('/foo/', $client->getResponse()->getContent());
    

    Assert that the response status code is 2xx

    $this->assertTrue($client->getResponse()->isSuccessful());
    

    Assert that the response status code is 404

    $this->assertTrue($client->getResponse()->isNotFound());
    

    Assert a specific 200 status code

    $this->assertEquals(200, $client->getResponse()->getStatusCode());
    

    Assert that the response is a redirect to /demo/contact

    $this->assertTrue($client->getResponse()->isRedirect('/demo/contact'));
    

    or simply check that the response is a redirect to any URL

    $this->assertTrue($client->getResponse()->isRedirect());
    

    Directly submit a form (but using the Crawler is easier!)

    $client->request('POST', '/submit', array('name' => 'Fabien'));
    

    Form submission with a file upload

    use Symfony\Component\HttpFoundation\File\UploadedFile;
    
        $photo = new UploadedFile(
        '/path/to/photo.jpg',
        'photo.jpg',
        'image/jpeg',
        123
        );
        // or
        $photo = array(
        'tmp_name' => '/path/to/photo.jpg',
        'name' => 'photo.jpg',
        'type' => 'image/jpeg',
        'size' => 123,
        'error' => UPLOAD_ERR_OK
        );
        $client->request(
        'POST',
        '/submit',
        array('name' => 'Fabien'),
        array('photo' => $photo)
        );
    

    Perform a DELETE requests, and pass HTTP headers

    $client->request(
        'DELETE',
        '/post/12',
        array(),
        array(),
        array('PHP_AUTH_USER' => 'username', 'PHP_AUTH_PW' => 'pa$$word')
        );
    

    browsing

    $client->back();
    $client->forward();
    $client->reload();
    

    Clears all cookies and the history

    $client->restart();
    

    redirecting

    $crawler = $client->followRedirect();
    $client->followRedirects();
    

    The Request() method:

    request(
        $method,
        $uri,
        array $parameters = array(),
        array $files = array(),
        array $server = array(),
        $content = null,
        $changeHistory = true
    )
    

    The server array is the raw values that you'd expect to normally find in the PHP $_SERVER4 superglobal. For example, to set the Content-Type and Referer HTTP headers, you'd pass the following:

    $client->request(
        'GET',
        '/demo/hello/Fabien',
        array(),
        array(),
        array(
            'CONTENT_TYPE' => 'application/json',
            'HTTP_REFERER' => '/foo/bar',
            )
        );
    

    Accessing Internal Objects

    If you use the client to test your application, you might want to access the client's internal objects:

    $history = $client->getHistory();
    $cookieJar = $client->getCookieJar();
    

    You can also get the objects related to the latest request:

    $request = $client->getRequest();
    $response = $client->getResponse();
    $crawler = $client->getCrawler();
    

    If your requests are not insulated, you can also access the Container and the Kernel:

    $container = $client->getContainer();
    $kernel = $client->getKernel();
    $profile = $client->getProfile();
    

    The Crawler

    A Crawler instance is returned each time you make a request with the Client. It allows you to traverse HTML documents, select nodes, find links and forms.

    Like jQuery, the Crawler has methods to traverse the DOM of an HTML/XML document.

    $newCrawler = $crawler->filter('input[type=submit]')
        ->last()
        ->parents()
        ->first();
    

    Many other methods are also available:

    • filter('h1.title') Nodes that match the CSS selector
    • filterXpath('h1') Nodes that match the XPath expression
    • eq(1) Node for the specified index
    • first() First node
    • last() Last node
    • siblings() Siblings
    • nextAll() All following siblings
    • previousAll() All preceding siblings
    • parents() Returns the parent nodes
    • children() Returns children nodes
    • reduce($lambda) Nodes for which the callable does not return false

    Extracting information

    // Returns the attribute value for the first node
    $crawler->attr('class');
    
    // Returns the node value for the first node
    $crawler->text();
    
    // Extracts an array of attributes for all nodes (_text returns the node value)
    // returns an array for each element in crawler, each with the value and href
    $info = $crawler->extract(array('_text', 'href'));
    
    // Executes a lambda for each node and return an array of results
    $data = $crawler->each(function ($node, $i)
    {
        return $node->attr('href');
    });
    
    //Selecting links
    $crawler->selectLink('Click here');
    $link = $crawler->selectLink('Click here')->link();
    $client->click($link);
    

    Forms

    // Selecting buttons.
    $buttonCrawlerNode = $crawler->selectButton('submit');
    
    // You can override values by:
    
    $form = $buttonCrawlerNode->form(array(
        'name' => 'Fabien',
        'my_form[subject]' => 'Symfony rocks!',
    ));
    
    //Simulate methods
    $form = $buttonCrawlerNode->form(array(), 'DELETE');
    
    //Submit form.
    $client->submit($form);
    
    //Submit with arguments.
    $client->submit($form, array(
        'name' => 'Fabien',
        'my_form[subject]' => 'Symfony rocks!',
    ));
    
    // Using arrays.
    $form['name'] = 'Fabien';
    $form['my_form[subject]'] = 'Symfony rocks!';
    
    // Select an option or a radio
    $form['country']->select('France');
    
    // Tick a checkbox
    $form['like_symfony']->tick();
    
    // Upload a file
    $form['photo']->upload('/path/to/lucas.jpg');
    

    Test environment configuration

    The swiftmailer is configured to not actually deliver emails in the test environment. You can see this under the swiftmailer configuration option:

    # app/config/config_test.yml
    swiftmailer:
        disable_delivery: true
    

    You can also use a different environment entirely, or override the default debug mode (true) by passing each as options to the createClient() method:

    custom environment

    $client = static::createClient(array(
        'environment' => 'my_test_env',
        'debug' => false,
        ));
    

    custom user agent

    $client = static::createClient(array(), array(
        'HTTP_HOST' => 'en.example.com',
        'HTTP_USER_AGENT' => 'MySuperBrowser/1.0',
    ));
    

    override HTTP headers

    $client->request('GET', '/', array(), array(), array(
        'HTTP_HOST' => 'en.example.com',
        'HTTP_USER_AGENT' => 'MySuperBrowser/1.0',
    ));
    

    VALIDATION back to top

    Symfony2 ships with a Validator component that makes this task easy and transparent.

    # src/Acme/BlogBundle/Resources/config/validation.yml
    Acme\BlogBundle\Entity\Author:
    properties:
        name:
            - NotBlank: ~
    

    Protected and private properties can also be validated, as well as "getter" methods (see validatorconstraint- targets).

    Using the validator Service

    // ...
    use Symfony\Component\HttpFoundation\Response;
    use Acme\BlogBundle\Entity\Author;
    
    public function indexAction()
    {
        $author = new Author();
    
        // ... do something to the $author object
        $validator = $this->get('validator');
        $errors = $validator->validate($author);
    
        if (count($errors) > 0) {
            return new Response(print_r($errors, true));
        } else {
            return new Response('The author is valid! Yes!');
        }
    }
    

    Inside the template, you can output the list of errors exactly as needed:

    {# src/Acme/BlogBundle/Resources/views/Author/validate.html.twig #}
    <h3>The author has the following errors</h3>
    <ul>
    {% for error in errors %}
    <li>{{ error.message }}</li>
    {% endfor %}
    </ul>
    

    Validation and Forms

    The Symfony2 validator is enabled by default, but you must explicitly enable annotations if you're using the annotation method to specify your constraints:

    # app/config/config.yml
    framework:
        validation: { enable_annotations: true }
    

    The good thing about annotations is that you write down all your entities validation in the entities .php in each entity PHPDOC so everything is in the same place.

    Constraints

    The validator is designed to validate objects against constraints (i.e. rules). In order to validate an object, simply map one or more constraints to its class and then pass it to the validator service.

    Basic

    YAML Annotation
    NotBlank @Assert\NotBlank()
    Blank @Assert\Blank()
    NotNull @Assert\NotNull()
    Null @Assert\Null()
    True @Assert\True(message = "The token is invalid")
    False @Assert\False()
    Type @Assert\Type(type="integer", message="The value {{ value }} is not a valid {{ type }}.")

    String

    YAML Annotation
    Email @Assert\Email(message = "The email '{{ value }}' is not a valid email.", checkMX = true, checkHost = true)
    MinLength Assert\MinLength(limit=3, message="Your name must have at least {{ limit }} characters.")
    MaxLength @Assert\MaxLength(100)
    Length @Assert\Length( min = "2",max = "50", minMessage = "msg", maxMessage = "msg" )
    Url @Assert\Url(message="msg1", protocolos=array('http','https')
    Regex @Assert\Regex("/^\w+/") => options (pattern, match, message)
    Ip @Assert\Ip

    Number

    YAML Annotation
    Max @Assert\Max(limit=5, message="msg1")
    Min @Assert\Min(limit=5, message="msg1")
    Range @Assert\Range(min = "120", max = "180",minMessage = "msg",maxMessage = "msg")

    Date

    YAML Annotation
    Date @Assert\Date()
    DateTime @Assert\DateTime()
    Time @Assert\Time()

    Collection

    YAML Annotation
    Choice @Assert\Choice(choices = {"male", "female"}, message = "Choose a valid gender.")
    Collection http://symfony.com/doc/current/reference/constraints/Collection.html
    Count @Assert\Count(min = "1", max = "5", minMessage = "msg", maxMessage = "msg" )
    UniqueEntity @ORM\Column(name="email", type="string", length=255, unique=true) (Suppose you have an AcmeUserBundle bundle with a User entity that has an email field. You can use the UniqueEntity constraint to guarantee that the email field remains unique between all of the constraints in your user table)
    Language @Assert\Language (Validates that it is a valid language code)
    Locale @Assert\Locale (Validates a valid Locale code (ej : ISO639-1)
    Country @Assert\Country (Valid two letter country code)

    File

    YAML Annotation
    File Assert\File(maxSize = "1024k",mimeTypes = {"application/pdf", "application/x-pdf"},mimeeTypesMessage = "msg") http://symfony.com/doc/current/reference/constraints/File.html
    Image @Assert\Image(minWidth = 200, maxWidth = 400, minHeight = 200, maxHeight = 400) http://symfony.com/doc/current/reference/constraints/Image.html

    Other

    YAML Annotation
    Callback @Assert\Callback(methods={"isAuthorValid"})
    All @Assert\All({ @Assert\NotBlank @Assert\MinLength(5),}) (Aplies all constraints to each element of the Transversable object)
    UserPassword @SecurityAssert\UserPassword(message = "Wrong password") (This validates that an input value is equal to the current authenticated user's password.)
    Valid This constraint is used to enable validation on objects that are embedded as properties on an object being validated. This allows you to validate an object and all sub-objects associated with it. http://symfony.com/doc/current/reference/constraints/Valid.html

    Callback validations

    // src/Acme/BlogBundle/Entity/Author.php
    use Symfony\Component\Validator\Constraints as Assert;
    
    /**
    * @Assert\Callback(methods={"isAuthorValid"})
    */
    class Author
    {
    }
    

    If the name of a method is a simple string (e.g. isAuthorValid), that method will be called on the same object that's being validated and the ExecutionContext will be the only argument (see the above example).

    use Symfony\Component\Validator\ExecutionContext;
    
    class Author
    {
        // ...
        private $firstName;
    
        public function isAuthorValid(ExecutionContext $context)
        {
            // somehow you have an array of "fake names"
            $fakeNames = array();
    
            // check if the name is actually a fake name
            if (in_array($this->getFirstName(), $fakeNames)) {
                $context->addViolationAtSubPath('firstname', 'This name sounds totally fake!', array(), null);
            }
        }
    }
    

    You can define more complex validation in the repository class of the entity:

    // src/Acme/BlogBundle/Entity/Author.php
    use Symfony\Component\Validator\Constraints as Assert;
    
    /**
    * @Assert\Callback(methods={
    * { "Acme\BlogBundle\MyStaticValidatorClass", "isAuthorValid"}
    * })
    */
    class Author
    {
    }
    

    In this case, the static method isAuthorValid will be called on the Acme\BlogBundle\MyStaticValidatorClass class. It's passed both the original object being validated (e.g. Author) as well as the ExecutionContext:

    namespace Acme\BlogBundle;
    
    use Symfony\Component\Validator\ExecutionContext;
    use Acme\BlogBundle\Entity\Author;
    
    class MyStaticValidatorClass
    {
        static public function isAuthorValid(Author $author, ExecutionContext $context)
        {
        // ...
        }
    }
    

    Translating constraint messages

    Create a translation file under the validators catalog for the constraint messages, typically in the Resources/translations/ directory of the bundle.

     <!-- validators.es.xliff -->
    <?xml version="1.0"?>
    <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
    <file source-language="en" datatype="plaintext" original="file.ext">
    <body>
    <trans-unit id="1">
    <source>author.gender.choice</source>
    <target>Escoge un género válido.</target>
    </trans-unit>
    </body>
    </file>
    </xliff>
    

    Validation groups

    # src/Acme/BlogBundle/Resources/config/validation.yml
    Acme\BlogBundle\Entity\User:
        properties:
            email:
                - Email: { groups: [registration] }
        password:
            - NotBlank: { groups: [registration] }
            - MinLength: { limit: 7, groups: [registration] }
        city:
            - MinLength: 2
    

    With this configuration, there are two validation groups:

    • Default - contains the constraints not assigned to any other group;
    • registration - contains the constraints on the email and password fields only.

      $errors = $validator->validate($author, array('registration'));
                  

    Validating Values and Arrays

    Sometimes, you just want to validate a simple value - like to verify that a string is a valid email address.

    // add this to the top of your class
    use Symfony\Component\Validator\Constraints\Email;
    
    public function addEmailAction($email)
    {
        $emailConstraint = new Email();
    
        // all constraint "options" can be set this way
        $emailConstraint->message = 'Invalid email address';
    
        // use the validator to validate the value
        $errorList = $this->get('validator')->validateValue($email, $emailConstraint);
    
        if (count($errorList) == 0) {
            // this IS a valid email address, do something
        } else {
            // this is *not* a valid email address
            $errorMessage = $errorList[0]->getMessage()
            // ... do something with the error
        }
        // ...
    }
    

    FORMS back to top

    Creating simple forms

    // src/Acme/TaskBundle/Controller/DefaultController.php
    namespace Acme\TaskBundle\Controller;
    
    use Symfony\Bundle\FrameworkBundle\Controller\Controller;
    use Acme\TaskBundle\Entity\Task;
    use Symfony\Component\HttpFoundation\Request;
    
    class DefaultController extends Controller
    {
        public function newAction(Request $request)
        {
        // create a task and give it some dummy data for this example
        $task = $this->createForm(new Task());
        $task->setTask('Write a blog post');
        $task->setDueDate(new \DateTime('tomorrow'));
    
        $form = $this->createFormBuilder($task)
            ->add('task', 'text')
            ->add('dueDate', 'date')
            ->getForm();
    
        return $this->render('AcmeTaskBundle:Default:new.html.twig', array(
            'form' => $form->createView()
        ));
    
        }
    }
    

    Creating form classes and embedding subform

    // src/Acme/TaskBundle/Form/Type/TaskType.php
    namespace Acme\TaskBundle\Form\Type;
    
    use Symfony\Component\Form\AbstractType;
    use Symfony\Component\Form\FormBuilderInterface;
    use Symfony\Component\OptionsResolver\OptionsResolverInterface;
    
    class TaskType extends AbstractType
    {
        public function buildForm(FormBuilderInterface $builder, array $options)
        {
            $builder->add('task');
            $builder->add('dueDate', null, array('widget' => 'single_text'));
    
            //Any extra field not mapped to the object must define property_path.
            $builder->add('agree','checkbox', array('property_path' => false));
    
            //Embedding one form, you need to create first the categoryType class as usual.
            $builder->add('category', new CategoryType());
    
            //Embedding a collection of TAGS forms. You already have a tagType form.
            $builder->add('tags', 'collection', array('type' => new TagType()));
    
        }
    
        public function getName()
        {
            return 'task'; //must be unique.
        }
    
        //Symfony can guess the type but it is a good practice to always set de data_class because embedding forms is necessary.
        public function setDefaultOptions(OptionsResolverInterface $resolver)
        {
            $resolver->setDefaults(array(
                'data_class' => 'Acme\TaskBundle\Entity\Task',
                'cascade_validation' => true, //needed to validate embeed forms.
                'validation_groups' => array('registration'), //use of validation groups.
                'csrf_protection' => true,
                'csrf_field_name' => '_token',
                // a unique key to help generate the secret token
                'intention' => 'task_item',
            ));
        }
    
    }
    

    Field Type Options

    ->add('dueDate', 'date', array(
        'widget' => 'single_text',
        'label' => 'Due Date'
    ))
    

    The field data can be accessed in a controller with:

    $form->get('dueDate')->getData();
    

    If, for some reason, you don't have access to your original $task object, you can fetch it from the form:

    $task = $form->getData();
    

    Using form classes in a controller:

    $form = $this->createForm(new TaskType(), new Task());
    
    // process the form on POST
    if ($request->isMethod('POST')) {
    
    //you can access POST variables directly
    $this->get('request')->request->get('name');
    
    $form->bind($request);
    
    if ($form->isValid()) {
        $em = $this->getDoctrine()->getManager();
        $em->persist($task);
        $em->flush();
    }
    }
    
    return $this->render('AcmeTaskBundle:Task:new.html.twig', array(
        'form' => $form->createView()
    ));
    

    Groups based on Submitted Data

    The ability to specify a callback or Closure in validation_groups is new to version 2.1

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'validation_groups' => array('Acme\\AcmeBundle\\Entity\\Client', 'determineValidationGroups'),
        ));
    }
    

    This will call the static method determineValidationGroups() on the Client class after the form is bound, but before validation is executed. The Form object is passed as an argument to that method (see next example). You can also define whole logic inline by using a Closure:

    You can use inline post validation also:

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'validation_groups' => function(FormInterface $form) {
                $data = $form->getData();
                if (Entity\Client::TYPE_PERSON == $data->getType()) {
                    return array('person');
                } else {
                    return array('company');
                }
            },
        ));
    }
    

    Rendering forms in TWIG

    First of all check all Form Type References

    http://symfony.com/doc/current/reference/forms/types.html

    Text field

    Widget Widget
    text textarea
    textarea integer
    email money
    number password
    percent search

    Choice fields

    Widget Widget
    choice entity
    country language
    locale timezone

    Date and datetime fields

    Widget Widget
    date datetime
    time birthday

    Other fields

    Widget Widget
    checkbox file
    radio

    Field groups

    Widget Widget
    collection repeated

    Hidden fields

    Widget Widget
    hidden csrf

    Simple and fast

    <form action="{{ path('task_new') }}" method="post" {{ form_enctype(form) }}>
    {{ form_widget(form) }}
    <input type="submit" />
    </form>
    

    Rows

    <form action="{{ path('task_new') }}" method="post" {{ form_enctype(form) }}>
    {{ form_errors(form) }}
    
    {{ form_row(form.task) }}
    {{ form_row(form.dueDate) }}
    
    {{ form_rest(form) }}
    
    <input type="submit" />
    </form>
    

    Manual

    <form action="{{ path('task_new') }}" method="post" {{ form_enctype(form) }}>
    {{ form_errors(form) }}
    <div>
        {{ form_label(form.task,'custom label') }}
        {{ form_errors(form.task) }}
        {{ form_widget(form.task, { 'attr': {'class': 'span3'} })) }}
    </div>
    <div>
        {{ form_label(form.dueDate) }}
        {{ form_errors(form.dueDate) }}
        {{ form_widget(form.dueDate) }}
    </div>
    
    {# Render one embedded form #}
    <h3>Category</h3>
    <div class="category">
        {{ form_row(form.category.name) }}
    </div>
    
    {# Render multiple embedded forms #}
    <h3>Tags</h3>
    <ul class="tags">
        {% for tag in form.tags %}
        <li>{{ form_row(tag.name) }}</li>
        {% endfor %}
    </ul>
    
    {{ form_rest(form) }}
    
    </form>
    

    Access "name" and "id" attributes

    {{ form.task.vars.id }}
    {{ form.task.vars.full_name }}
    

    You can access current data of your form via:

    {{ form.vars.value.task }}
    

    Form Theming

    In Twig, each form "fragment" is represented by a Twig block. To customize any part of how a form renders, you just need to override the appropriate block.

    To understand how this works, let's customize the form_row fragment and add a class attribute to the div element that surrounds each row. To do this, create a new template file that will store the new markup:

    {# src/Acme/TaskBundle/Resources/views/Form/fields.html.twig #}
    {% block form_row %}
        {% spaceless %}
        <div class="form_row">
            {{ form_label(form) }}
            {{ form_errors(form) }}
            {{ form_widget(form) }}
        </div>
        {% endspaceless %}
    {% endblock form_row %}
    

    To tell the form component to use your new form_row fragment defined above, add the following to the top of the template that renders the form:

    {# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #}
    {% form_theme form 'AcmeTaskBundle:Form:fields.html.twig' %}
    {% form_theme form 'AcmeTaskBundle:Form:fields.html.twig' 'AcmeTaskBundle:Form:fields2.html.twig' %}
    <form ...>
    

    New in version 2.1: An alternate Twig syntax for form_theme has been introduced in 2.1. It accepts any valid Twig expression (the most noticeable difference is using an array when using multiple themes).

    {# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #}
    {% form_theme form with 'AcmeTaskBundle:Form:fields.html.twig' %}
    {% form_theme form with ['AcmeTaskBundle:Form:fields.html.twig', 'AcmeTaskBundle:Form:fields2.html.twig'] %}
    

    You can see all form fragments from twig followin this link: https://github.com/symfony/symfony/tree/master/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form

    If your form customizations live inside an external template, you can reference the base block by using the parent() Twig function:

    {# src/Acme/DemoBundle/Resources/views/Form/fields.html.twig #}
    {% extends 'form_div_layout.html.twig' %}
    
    {% block integer_widget %}
    <div class="integer_widget">
        {{ parent() }}
    </div>
    {% endblock %}
    

    If you'd like a certain form customization to be global to your application, you can accomplish this by making the form customizations in an external template and then importing it inside your application configuration:

    # app/config/config.yml
        twig:
            form:
                resources:
                    - 'AcmeDemoBundle:Form:fields.html.twig'
    

    See more in: http://symfony.com/doc/current/cookbook/form/form_customization.html


    SECURITY back to top

    Security is a two-step process whose goal is to prevent a user from accessing a resource that he/she should not have access to.

    Authentication

    • Login form
    • HTTP Authentication
    • HTTP digest
    • X.509 certs
    • Custom auth methods

    Authorization

    • Access control for URL
    • Secure object and methods
    • Access control lists (ACLs)

    Basic Example: HTTP Authentication

    # app/config/security.yml
    security:
        firewalls:
            secured_area:
                pattern:    ^/
                anonymous: ~
                http_basic:
                    realm: "Secured Demo Area"
    
        access_control:
            - { path: ^/admin, roles: ROLE_ADMIN }
    
        providers:
            in_memory:
                memory:
                    users:
                        ryan:  { password: ryanpass, roles: 'ROLE_USER' }
                        admin: { password: kitten, roles: 'ROLE_ADMIN' }
    
        encoders:
            Symfony\Component\Security\Core\User\User: plaintext
    

    Using a Traditional Login Form

    # app/config/security.yml
    security:
        firewalls:
            secured_area:
                pattern:    ^/
                anonymous: ~
                form_login:
                    login_path:  /login
                    check_path:  /login_check
    

    If you don't need to customize your login_path or check_path values (the values used here are the default values), you can shorten your configuration:

    form_login: ~
    

    Now we need to create the login routes:

    # app/config/routing.yml
    login:
        pattern: /login
        defaults: { _controller: AcmeSecurityBundle:Security:login }
    
    login_check:
        pattern: /login_check
    

    Next, create the controller that will display the login form:

    // src/Acme/SecurityBundle/Controller/SecurityController.php;
    namespace Acme\SecurityBundle\Controller;
    
    use Symfony\Bundle\FrameworkBundle\Controller\Controller;
    use Symfony\Component\Security\Core\SecurityContext;
    
    class SecurityController extends Controller
    {
       public function loginAction()
       {
           $request = $this->getRequest();
           $session = $request->getSession();
    
           // get the login error if there is one
           if ($request->attributes->has(SecurityContext::AUTHENTICATION_ERROR)) {
               $error = $request->attributes->get(SecurityContext::AUTHENTICATION_ERROR);
           } else {
               $error = $session->get(SecurityContext::AUTHENTICATION_ERROR);
               $session->remove(SecurityContext::AUTHENTICATION_ERROR);
           }
    
           return $this->render('AcmeSecurityBundle:Security:login.html.twig', array(
               // last username entered by the user
               'last_username' => $session->get(SecurityContext::LAST_USERNAME),
               'error'         => $error,
           ));
       }
    }
    

    Finally, create the corresponding template:

    {# src/Acme/SecurityBundle/Resources/views/Security/login.html.twig #}
    {% if error %}
        <div>{{ error.message }}</div>
    {% endif %}
    
    <form action="{{ path('login_check') }}" method="post">
        <label for="username">Username:</label>
        <input type="text" id="username" name="_username" value="{{ last_username }}" />
    
        <label for="password">Password:</label>
        <input type="password" id="password" name="_password" />
    
        {#If you want to control the URL the user is redirected to on success (more details below) #}
        <input type="hidden" name="_target_path" value="/account" />
    
        <button type="submit">login</button>
        </form>
    

    Securing Specific URL Patterns

    The most basic way to secure part of your application is to secure an entire URL pattern. You've seen this already in the first example of this chapter, where anything matching the regular expression pattern ^/admin requires the ROLE_ADMIN role.

    # app/config/security.yml
    security:
        # ...
        access_control:
            - { path: ^/admin/users, roles: ROLE_SUPER_ADMIN }
            - { path: ^/admin, roles: ROLE_ADMIN }
    

    Securing by IP

    Here is an example of how you might secure this route from outside access:

    # app/config/security.yml
    security:
    # ...
    access_control:
        - { path: ^/_internal, roles: IS_AUTHENTICATED_ANONYMOUSLY, ip: 127.0.0.1 }
    

    Securing by Channel

    # app/config/security.yml
    security:
    # ...
    access_control:
        - { path: ^/cart/checkout, roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: https }
    

    Securing a Controller

    / ...
    use Symfony\Component\Security\Core\Exception\AccessDeniedException;
    
    public function helloAction($name)
    {
        if (false === $this->get('security.context')->isGranted('ROLE_ADMIN')) {
            throw new AccessDeniedException();
        }
    
    // ...
    }
    

    You can also choose to install and use the optional JMSSecurityExtraBundle, which can secure your controller using annotations:

    // ...
    use JMS\SecurityExtraBundle\Annotation\Secure;
    
    /**
    * @Secure(roles="ROLE_ADMIN")
    */
    public function helloAction($name)
    {
    // ...
    }
    

    Users

    User Providers

    # app/config/security.yml
    security:
        # ...
        providers:
            default_provider:
                memory:
                    users:
                        ryan: { password: ryanpass, roles: 'ROLE_USER' }
                        admin: { password: kitten, roles: 'ROLE_ADMIN' }
    

    Loading Users from the Database

    Next, configure an entity user provider, and point it to your User class:

    # app/config/security.yml
    security:
    providers:
        main:
            entity: { class: Acme\UserBundle\Entity\User, property: username }
    

    Encoding the User's Password

    # app/config/security.yml
    security:
        # ...
        providers:
            in_memory:
                memory:
                    users:
                        ryan:  { password: bb87a29949f3a1ee0559f8a57357487151281386, roles: 'ROLE_USER' }
                        admin: { password: 74913f5cd5f61ec0bcfdb775414c2fb3d161b620, roles: 'ROLE_ADMIN' }
    
        encoders:
            Symfony\Component\Security\Core\User\User:
                algorithm:   sha1
                iterations: 1
                encode_as_base64: false
            Acme\UserBundle\Entity\User: sha512 //user comes from database
    

    To encode the password you can use some online functions such as functions-online.com

    Determine the hasing password in a controller

    $factory = $this->get('security.encoder_factory');
    $user = new Acme\UserBundle\Entity\User();
    
    $encoder = $factory->getEncoder($user);
    $password = $encoder->encodePassword('ryanpass', $user->getSalt());
    $user->setPassword($password);
    

    Retrieving the User Object

    After authentication, the User object of the current user can be accessed via the security.context service.

    public function indexAction()
    {
        $user = $this->get('security.context')->getToken()->getUser();
        //or
        $user = $this->getUser();
    }
    

    You can also retrieve current user in a twig template by:

    <p>Username: {{ app.user.username }}</p>
    

    Using Multiple User Providers

    # app/config/security.yml
    security:
        providers:
            chain_provider:
                chain:
                    providers: [in_memory, user_db]
            in_memory:
                memory:
                    users:
                        foo: { password: test }
            user_db:
                entity: { class: Acme\UserBundle\Entity\User, property: username }
    

    You can also configure the firewall or individual authentication mechanisms to use a specific provider. Again, unless a provider is specified explicitly, the first provider is always used:

    # app/config/security.yml
    security:
        firewalls:
            secured_area:
                # ...
                provider: user_db
                http_basic:
                    realm: "Secured Demo Area"
                    provider: in_memory
                form_login: ~
    

    For more information about user provider and firewall configuration, see the Security Configuration Reference.

    Roles

    # app/config/security.yml
    security:
        role_hierarchy:
            ROLE_ADMIN:       ROLE_USER
            ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
    

    Logging Out

    # app/config/security.yml
    security:
        firewalls:
            secured_area:
                # ...
                logout:
                    path:   /logout
                    target: /
    

    and define the route:

    # app/config/routing.yml
    logout:
        pattern: /logout
    

    Access Control in Templates

    Twig

    {% if is_granted('ROLE_ADMIN') %}
        <a href="...">Delete</a>
    {% endif %}
    

    Access Control in Controllers

    public function indexAction()
    {
        // show different content to admin users
        if ($this->get('security.context')->isGranted('ROLE_ADMIN')) {
            // Load admin content here
        }
        // load other regular content here
    }
    

    Switching users

    Sometimes, it's useful to be able to switch from one user to another without having to logout and login again (for instance when you are debugging or trying to understand a bug a user sees that you can't reproduce). This can be easily done by activating the switch_user firewall listener:

    # app/config/security.yml
    security:
        firewalls:
            main:
                # ...
                switch_user: true
    

    Switch user by:

    http://example.com/somewhere?_switch_user=thomas
    

    and back to normal user:

    http://example.com/somewhere?_switch_user=_exit
    

    Secure this behaviour:

    # app/config/security.yml
    security:
        firewalls:
            main:
                # ...
                switch_user: { role: ROLE_ADMIN, parameter: _want_to_be_this_user }
    

    HTTP Cache back to top

    For the purposes of learning how to cache with Symfony2, we'll cover the subject in four steps:

    • Step 1: A gateway cache, or reverse proxy, is an independent layer that sits in front of your application. The reverse proxy caches responses as they're returned from your application and answers requests with cached responses before they hit your application. Symfony2 provides its own reverse proxy, but any reverse proxy can be used.
    • Step 2: HTTP cache headers are used to communicate with the gateway cache and any other caches between your application and the client. Symfony2 provides sensible defaults and a powerful interface for interacting with the cache headers.
    • Step 3: HTTP expiration and validation are the two models used for determining whether cached content is fresh (can be reused from the cache) or stale (should be regenerated by the application).
    • Step 4: Edge Side Includes (ESI) allow HTTP cache to be used to cache page fragments (even nested fragments) independently. With ESI, you can even cache an entire page for 60 minutes, but an embedded sidebar for only 5 minutes.

    Caching with a Gateway Cache

    To enable caching, modify the code of a front controller to use the caching kernel:

    // web/app.php
    require_once __DIR__.'/../app/bootstrap.php.cache';
    require_once __DIR__.'/../app/AppKernel.php';
    require_once __DIR__.'/../app/AppCache.php';
    
    use Symfony\Component\HttpFoundation\Request;
    
    $kernel = new AppKernel('prod', false);
    $kernel->loadClassCache();
    // wrap the default AppKernel with the AppCache one
    $kernel = new AppCache($kernel);
    $request = Request::createFromGlobals();
    $response = $kernel->handle($request);
    $response->send();
    $kernel->terminate($request, $response);
    

    The cache kernel has a special getLog() method that returns a string representation of what happened in the cache layer. In the development environment, use it to debug and validate your cache strategy:

    error_log($kernel->getLog());
    

    Introduction to HTTP Caching

    HTTP specifies four response cache headers that we're concerned with:

    • Cache-Control
    • Expires
    • ETag
    • Last-Modified

    The Cache-Control Header

    The Cache-Control header is unique in that it contains not one, but various pieces of information about the cacheability of a response. Each piece of information is separated by a comma:

    Cache-Control: private, max-age=0, must-revalidate
    Cache-Control: max-age=3600, must-revalidate
    

    Symfony provides abstraction layer:

    $response = new Response();
    
    // mark the response as either public or private
    $response->setPublic();
    $response->setPrivate();
    
    // set the private or shared max age
    $response->setMaxAge(600);
    $response->setSharedMaxAge(600);
    
    // set a custom Cache-Control directive
    $response->headers->addCacheControlDirective('must-revalidate', true);
    

    Public vs Private Responses

    Both gateway and proxy caches are considered "shared" caches as the cached content is shared by more than one user. If a user-specific response were ever mistakenly stored by a shared cache, it might be returned later to any number of different users. Imagine if your account information were cached and then returned to every subsequent user who asked for their account page!

    public: Indicates that the response may be cached by both private and shared caches;
    private: Indicates that all or part of the response message is intended for a single user and must not be cached by a shared cache.
    

    HTTP caching only works for "safe" HTTP methods (like GET and HEAD). Being safe means that you never change the application's state on the server when serving the request (you can of course log information, cache data, etc).

    Caching Rules and Defaults

    Symfony2 automatically sets a sensible and conservative Cache-Control header when none is set by the developer by following these rules:

    • If no cache header is defined (Cache-Control, Expires, ETag or Last-Modified), Cache-Control is set to no-cache, meaning that the response will not be cached;
    • If Cache-Control is empty (but one of the other cache headers is present), its value is set to private, must-revalidate;
    • But if at least one Cache-Control directive is set, and no 'public' or private directives have been explicitly added, Symfony2 adds the private directive automatically (except when s-maxage is set).

    Expiration with Expires Header

    $date = new DateTime();
    $date->modify('+600 seconds');
    $response->setExpires($date);
    

    Expiration with the Cache-Control Header

    / Sets the number of seconds after which the response
    // should no longer be considered fresh
    $response->setMaxAge(600);
    
    // Same as above but only for shared caches
    $response->setSharedMaxAge(600);
    

    Validation the the ETag Header

    The ETag header is a string header (called the "entity-tag") that uniquely identifies one representation of the target resource. It's entirely generated and set by your application so that you can tell, for example, if the /about resource that's stored by the cache is up-to-date with what your application would return. An ETag is like a fingerprint and is used to quickly compare if two different versions of a resource are equivalent. Like fingerprints, each ETag must be unique across all representations of the same resource.

    public function indexAction()
    {
        $response = $this->render('MyBundle:Main:index.html.twig');
        $response->setETag(md5($response->getContent()));
        $response->setPublic(); // make sure the response is public/cacheable
        $response->isNotModified($this->getRequest());
    
        return $response;
    }
    

    Validation with the Last-Modified Header

    The Last-Modified header is the second form of validation. According to the HTTP specification, "The Last-Modified header field indicates the date and time at which the origin server believes the representation was last modified."

    public function showAction($articleSlug)
    {
        // ...
    
        $articleDate = new \DateTime($article->getUpdatedAt());
        $authorDate = new \DateTime($author->getUpdatedAt());
    
        $date = $authorDate > $articleDate ? $authorDate : $articleDate;
    
        $response->setLastModified($date);
        // Set response as public. Otherwise it will be private by default.
        $response->setPublic();
    
        if ($response->isNotModified($this->getRequest())) {
            return $response;
        }
    
        // do more work to populate the response will the full content
    
        return $response;
    }
    

    The main goal of any caching strategy is to lighten the load on the application. Put another way, the less you do in your application to return a 304 response, the better. The Response::isNotModified() method does exactly that by exposing a simple and efficient pattern:

    public function showAction($articleSlug)
    {
        // Get the minimum information to compute
        // the ETag or the Last-Modified value
        // (based on the Request, data is retrieved from
        // a database or a key-value store for instance)
        $article = ...;
    
        // create a Response with a ETag and/or a Last-Modified header
        $response = new Response();
        $response->setETag($article->computeETag());
        $response->setLastModified($article->getPublishedAt());
    
        // Set response as public. Otherwise it will be private by default.
        $response->setPublic();
    
        // Check that the Response is not modified for the given Request
        if ($response->isNotModified($this->getRequest())) {
        // return the 304 Response immediately
            return $response;
        } else {
            // do more work here - like retrieving more data
            $comments = ...;
    
            // or render a template with the $response you've already started
            return $this->render(
                'MyBundle:MyController:article.html.twig',
                array('article' => $article, 'comments' => $comments),
                $response
            );
        }
    }
    

    Varying the Response

    So far, we've assumed that each URI has exactly one representation of the target resource. By default, HTTP caching is done by using the URI of the resource as the cache key. If two people request the same URI of a cacheable resource, the second person will receive the cached version. Sometimes this isn't enough and different versions of the same URI need to be cached based on one or more request header values. For instance, if you compress pages when the client supports it, any given URI has two representations: one when the client supports compression, and one when it does not. This determination is done by the value of the Accept-Encoding request header.

    // set one vary header
    $response->setVary('Accept-Encoding');
    
    // set multiple vary headers
    $response->setVary(array('Accept-Encoding', 'User-Agent'));
    
    // Marks the Response stale
    $response->expire();
    
    // Force the response to return a proper 304 response with no content
    $response->setNotModified();
    

    Using Edge Side Includes

    Gateway caches are a great way to make your website perform better. But they have one limitation: they can only cache whole pages. If you can't cache whole pages or if parts of a page has "more" dynamic parts, you are out of luck. Fortunately, Symfony2 provides a solution for these cases, based on a technology called ESI, or Edge Side Includes. Akamaï wrote this specification almost 10 years ago, and it allows specific parts of a page to have a different caching strategy than the main page.

    # app/config/config.yml
    framework:
        # ...
        esi: { enabled: true }
    

    Let's suppose that we hace an static page with a dynamic tickets section:

    public function indexAction()
    {
        $response = $this->render('MyBundle:MyController:index.html.twig');
        // set the shared max age - which also marks the response as public
        $response->setSharedMaxAge(600);
        return $response;
    }
    

    Now, let's embedd the ticket content using twig render's tag.

    {% render '...:news' with {}, {'standalone': true} %}
    

    Using the standalone true tells symfony to use ESI tags. The embedded action can now specify its own caching rules, entirely independent of the master page.

    public function newsAction()
    {
    // ...
    $response->setSharedMaxAge(60);
    }
    

    For the ESI include tag to work properly, you must define the _internal route:

    # app/config/routing.yml
    _internal:
        resource: "@FrameworkBundle/Resources/config/routing/internal.xml"
        prefix: /_internal
    

    Learn more How to use Varnish to speed up my Website


    Translation back to top

    Translations are handled by a Translator service that uses the user's locale to lookup and return translated messages. Before using it, enable the Translator in your configuration:

    # app/config/config.yml
    framework:
        translator: { fallback: en }
        default_locale: en
    

    Basic translation

    $t = $this->get('translator')->trans('Symfony2 is great');
    $t = $this->get('translator')->trans('Hello %name%', array('%name%' => $name));
    

    When this code is executed, Symfony2 will attempt to translate the message "Symfony2 is great" based on the locale of the user.

    <!-- messages.fr.xliff -->
    <?xml version="1.0"?>
    <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
        <file source-language="en" datatype="plaintext" original="file.ext">
            <body>
                <trans-unit id="1">
                <source>Symfony2 is great</source>
                <target>J'aime Symfony2</target>
                </trans-unit>
                <trans-unit id="2">
                <source>Hello %name%</source>
                <target>Bonjour %name%</target>
                </trans-unit>
            </body>
        </file>
    </xliff>
    

    Each time you create a new translation resource (or install a bundle that includes a translation resource), be sure to clear your cache so that Symfony can discover the new translation resource:

    Using Real or Keyword Messages

    $t = $translator->trans('Symfony2 is great');
    $t = $translator->trans('symfony2.great');
    

    In the first method, messages are written in the language of the default locale (English in this case). That message is then used as the "id" when creating translations. In the second method, messages are actually "keywords" that convey the idea of the message. The keyword message is then used as the "id" for any translations. In this case, translations must be made for the default locale (i.e. to translate symfony2.great to Symfony2 is great).

    symfony2.is.great: Symfony2 is great
    symfony2.is.amazing: Symfony2 is amazing
    symfony2.has.bundles: Symfony2 has bundles
    user.login: Login
    

    Using Message Domains

    When translating strings that are not in the default domain (messages), you must specify the domain as the third argument of trans():

    * messages.fr.xliff
    * admin.fr.xliff
    * navigation.fr.xliff
    
    $this->get('translator')->trans('Symfony2 is great', array(), 'admin');
    

    Pluralization

    To translate pluralized messages, use the transChoice() method:

    $t = $this->get('translator')->transChoice(
        'There is one apple|There are %count% apples',
        10,
        array('%count%' => 10)
    );
    

    Translations in Templates

    Translating in Twig templates example:

    //you can set de translation domain for entire twig temples
    {% trans_default_domain "app" %}
    
    {% trans %}Hello %name%{% endtrans %}
    
    {% transchoice count %}
        {0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples
    {% endtranschoice %}
    
    //variables traduction
    {{ message|trans }}
    
    {{ message|transchoice(5) }}
    
    {{ message|trans({'%name%': 'Fabien'}, "app") }}
    
    {{ message|transchoice(5, {'%name%': 'Fabien'}, 'app') }}
    

    If you need to use the percent character (%) in a string, escape it by doubling it: {% trans %}Percent: %percent%%%{% endtrans %}

    Translating Database Content

    The translation of database content should be handled by Doctrine through the Translatable Extension

    Translating constraint messages

    # src/Acme/BlogBundle/Resources/config/validation.yml
    Acme\BlogBundle\Entity\Author:
        properties:
            name:
                - NotBlank: { message: "author.name.not_blank" }
    

    Create a translation file under the validators catalog:

    <!-- validators.en.xliff -->
    <?xml version="1.0"?>
    <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
        <file source-language="en" datatype="plaintext" original="file.ext">
            <body>
                <trans-unit id="1">
                <source>author.name.not_blank</source>
                <target>Please enter an author name.</target>
                </trans-unit>
            </body>
        </file>
    </xliff>
    

    Service Container back to top

    A Service Container (or dependency injection container) is simply a PHP object that manages the instantiation of services (i.e. objects).

    # src/Acme/HelloBundle/Resources/config/services.yml
    parameters:
        newsletter_manager.class: Acme\HelloBundle\Newsletter\NewsletterManager
        my_mailer.transport: sendmail
        my_mailer.gateways:
            - mail1
            - mail2
            - mail3
    
    services:
        my_mailer:
            class: "%my_mailer.class%"
            arguments: [%my_mailer.transport%]
        newsletter_manager:
            class: "%newsletter_manager.class%"
            arguments: [@my_mailer] //required contructor args. Use @ to refer another service.
            calls:
            - [ setMailer, [ @my_mailer ] ] //Optional dependencies.
            tags:
            - { name: twig.extension } //Twig finds all services tagged with twig.extension and automatically registers them as extensions.
    

    Now we can set our class to be a real service:

    namespace Acme\HelloBundle\Newsletter;
    
    use Symfony\Component\Templating\EngineInterface;
    
    class NewsletterManager
    {
        protected $mailer;
        protected $templating;
    
        public function __construct(\Swift_Mailer $mailer, EngineInterface $templating)
        {
            $this->mailer = $mailer;
            $this->templating = $templating;
        }
    
        // ...
    }
    

    And for this particual service the corresponding services.yml would be:

     services:
        newsletter_manager:
            class: "%newsletter_manager.class%"
            arguments: [@mailer, @templating]
    

    In YAML, the special @my_mailer syntax tells the container to look for a service named my_mailer and to pass that object into the constructor of NewsletterManager. In this case, however, the specified service my_mailer must exist. If it does not, an exception will be thrown. You can mark your dependencies as optional - this will be discussed in the next section

    Making References Optional

    services:
        newsletter_manager:
            class: "%newsletter_manager.class%"
            arguments: [@?my_mailer]
    

    Debugging services

    You can find out what services are registered with the container using the console. To show all services and the class for each service, run:

    $ php app/console container:debug
    $ php app/console container:debug --show-private
    $ php app/console container:debug my_mailer
    

    See also: