SlideShare a Scribd company logo
The all-in-one Book
       for Symfony 2.0
    generated on June 20, 2012
The all-in-one Book (2.0)
This work is licensed under the “Attribution-Share Alike 3.0 Unported” license (http://creativecommons.org/
licenses/by-sa/3.0/).
You are free to share (to copy, distribute and transmit the work), and to remix (to adapt the work) under the
following conditions:

  • Attribution: You must attribute the work in the manner specified by the author or licensor (but
    not in any way that suggests that they endorse you or your use of the work).
  • Share Alike: If you alter, transform, or build upon this work, you may distribute the resulting work
    only under the same, similar or a compatible license. For any reuse or distribution, you must make
    clear to others the license terms of this work.

The information in this book is distributed on an “as is” basis, without warranty. Although every precaution
has been taken in the preparation of this work, neither the author(s) nor SensioLabs shall have any liability to
any person or entity with respect to any loss or damage caused or alleged to be caused directly or indirectly by
the information contained in this work.
If you find typos or errors, feel free to report them by creating a ticket on the Symfony ticketing system
(http://github.com/symfony/symfony-docs/issues). Based on tickets and users feedback, this book is
continuously updated.
Contents at a Glance


The Quick Tour
The Big Picture ...................................................................................................................................9
The View ..........................................................................................................................................17
The Controller ..................................................................................................................................22
The Architecture ...............................................................................................................................27


The Book
Symfony2 and HTTP Fundamentals ..................................................................................................34
Symfony2 versus Flat PHP.................................................................................................................44
Installing and Configuring Symfony...................................................................................................56
Creating Pages in Symfony2 ..............................................................................................................60
Controller.........................................................................................................................................73
Routing ............................................................................................................................................84
Creating and using Templates ...........................................................................................................95
Databases and Doctrine .................................................................................................................. 111
Databases and Propel ...................................................................................................................... 132
Testing ........................................................................................................................................... 140
Validation....................................................................................................................................... 153
Forms ............................................................................................................................................. 161
Security .......................................................................................................................................... 181
HTTP Cache................................................................................................................................... 201
Translations.................................................................................................................................... 216
Service Container ............................................................................................................................ 228
Performance ................................................................................................................................... 239
Internals ......................................................................................................................................... 242
The Symfony2 Stable API ................................................................................................................ 251


The Cookbook
How to Create and store a Symfony2 Project in git ........................................................................... 254
How to Create and store a Symfony2 Project in Subversion .............................................................. 258
How to customize Error Pages......................................................................................................... 262
How to define Controllers as Services .............................................................................................. 264


PDF brought to you by                                                                                                   Contents at a Glance | iii
generated on June 20, 2012
How to force routes to always use HTTPS or HTTP......................................................................... 265
How to allow a "/" character in a route parameter ............................................................................ 266
How to Use Assetic for Asset Management ...................................................................................... 267
How to Minify JavaScripts and Stylesheets with YUI Compressor..................................................... 272
How to Use Assetic For Image Optimization with Twig Functions ................................................... 274
How to Apply an Assetic Filter to a Specific File Extension............................................................... 276
How to handle File Uploads with Doctrine ...................................................................................... 278
Doctrine Extensions: Timestampable, Sluggable, Translatable, etc. .................................................. 285
Registering Event Listeners and Subscribers ..................................................................................... 286
How to use Doctrine's DBAL Layer ................................................................................................. 288
How to generate Entities from an Existing Database......................................................................... 290
How to work with Multiple Entity Managers ................................................................................... 293
Registering Custom DQL Functions ................................................................................................ 295
How to customize Form Rendering ................................................................................................. 296
Using Data Transformers ................................................................................................................ 307
How to Dynamically Generate Forms Using Form Events ................................................................ 311
How to Embed a Collection of Forms .............................................................................................. 314
How to Create a Custom Form Field Type....................................................................................... 325
How to use the Virtual Form Field Option....................................................................................... 330
How to create a Custom Validation Constraint ................................................................................ 333
How to Master and Create new Environments ................................................................................. 336
How to Set External Parameters in the Service Container ................................................................. 340
How to use PdoSessionStorage to store Sessions in the Database ...................................................... 343
How to use the Apache Router ........................................................................................................ 345
How to create an Event Listener ...................................................................................................... 347
How to work with Scopes ............................................................................................................... 349
How to work with Compiler Passes in Bundles ................................................................................ 352
Bundle Structure and Best Practices ................................................................................................. 353
How to use Bundle Inheritance to Override parts of a Bundle ........................................................... 358
How to Override any Part of a Bundle ............................................................................................. 361
How to expose a Semantic Configuration for a Bundle ..................................................................... 363
How to send an Email ..................................................................................................................... 370
How to use Gmail to send Emails .................................................................................................... 372
How to Work with Emails During Development .............................................................................. 373
How to Spool Email ........................................................................................................................ 375
How to simulate HTTP Authentication in a Functional Test ............................................................ 377
How to test the Interaction of several Clients ................................................................................... 378
How to use the Profiler in a Functional Test..................................................................................... 379
How to test Doctrine Repositories ................................................................................................... 381
How to load Security Users from the Database (the Entity Provider) ................................................. 383
How to add "Remember Me" Login Functionality ............................................................................ 393
How to implement your own Voter to blacklist IP Addresses............................................................ 396
Access Control Lists (ACLs) ............................................................................................................ 399
Advanced ACL Concepts ................................................................................................................ 403
How to force HTTPS or HTTP for Different URLs ........................................................................... 407
How to customize your Form Login ................................................................................................ 408
How to secure any Service or Method in your Application................................................................ 411


iv | Contents at a Glance                                                                                            Contents at a Glance | 4
How to create a custom User Provider ............................................................................................. 415
How to create a custom Authentication Provider ............................................................................. 420
How to use Varnish to speed up my Website ................................................................................... 428
Injecting variables into all templates (i.e. Global Variables) .............................................................. 430
How to use PHP instead of Twig for Templates ............................................................................... 432
How to write a custom Twig Extension ........................................................................................... 437
How to use Monolog to write Logs.................................................................................................. 440
How to Configure Monolog to Email Errors .................................................................................... 443
How to create a Console Command ................................................................................................ 445
How to optimize your development Environment for debugging ...................................................... 448
How to extend a Class without using Inheritance............................................................................. 450
How to customize a Method Behavior without using Inheritance...................................................... 453
How to register a new Request Format and Mime Type.................................................................... 455
How to create a custom Data Collector............................................................................................ 457
How to Create a SOAP Web Service in a Symfony2 Controller ......................................................... 460
How Symfony2 differs from symfony1 ............................................................................................. 463


The Components
The ClassLoader Component .......................................................................................................... 469
The Console Component ................................................................................................................ 472
The CssSelector Component ........................................................................................................... 479
The DomCrawler Component ......................................................................................................... 481
The Dependency Injection Component............................................................................................ 487
Working with Container Parameters and Definitions ....................................................................... 491
Compiling the Container................................................................................................................. 494
Working with Tagged Services ........................................................................................................ 498
Using a Factory to Create Services ................................................................................................... 501
Managing Common Dependencies with Parent Services ................................................................... 503
The Event Dispatcher Component................................................................................................... 508
The Finder Component................................................................................................................... 515
The HttpFoundation Component.................................................................................................... 520
The Locale Component................................................................................................................... 526
The Process Component ................................................................................................................. 528
The Routing Component ................................................................................................................ 530
The Templating Component ........................................................................................................... 536
The YAML Component................................................................................................................... 539


Contributing
Reporting a Bug .............................................................................................................................. 547
Submitting a Patch .......................................................................................................................... 548
Reporting a Security Issue ............................................................................................................... 554
Running Symfony2 Tests................................................................................................................. 555
Coding Standards ........................................................................................................................... 557
Conventions ................................................................................................................................... 560
Symfony2 License ........................................................................................................................... 562


PDF brought to you by                                                                                                Contents at a Glance | v
generated on June 20, 2012
Contributing to the Documentation ................................................................................................ 563
Documentation Format................................................................................................................... 565
Translations.................................................................................................................................... 568
Symfony2 Documentation License................................................................................................... 570
IRC Meetings.................................................................................................................................. 571
Other Resources ............................................................................................................................. 573


Reference Documents
FrameworkBundle Configuration ("framework").............................................................................. 575
AsseticBundle Configuration Reference ........................................................................................... 580
Configuration Reference.................................................................................................................. 582
Security Configuration Reference..................................................................................................... 586
SwiftmailerBundle Configuration ("swiftmailer").............................................................................. 590
TwigBundle Configuration Reference .............................................................................................. 594
Configuration Reference.................................................................................................................. 596
WebProfilerBundle Configuration ................................................................................................... 598
Form Types Reference..................................................................................................................... 599
birthday Field Type......................................................................................................................... 601
checkbox Field Type ....................................................................................................................... 605
choice Field Type ............................................................................................................................ 607
collection Field Type....................................................................................................................... 611
country Field Type.......................................................................................................................... 617
csrf Field Type ................................................................................................................................ 620
date Field Type ............................................................................................................................... 622
datetime Field Type ........................................................................................................................ 626
email Field Type ............................................................................................................................. 630
entity Field Type ............................................................................................................................. 632
file Field Type ................................................................................................................................. 636
The Abstract "field" Type ................................................................................................................ 639
form Field Type .............................................................................................................................. 641
hidden Field Type ........................................................................................................................... 642
integer Field Type ........................................................................................................................... 644
language Field Type ........................................................................................................................ 647
locale Field Type............................................................................................................................. 650
money Field Type ........................................................................................................................... 653
number Field Type.......................................................................................................................... 656
password Field Type ....................................................................................................................... 659
percent Field Type .......................................................................................................................... 661
radio Field Type.............................................................................................................................. 664
repeated Field Type......................................................................................................................... 666
search Field Type ............................................................................................................................ 669
text Field Type................................................................................................................................ 671
textarea Field Type ......................................................................................................................... 673
time Field Type............................................................................................................................... 675
timezone Field Type........................................................................................................................ 679
url Field Type ................................................................................................................................. 682


vi | Contents at a Glance                                                                                                 Contents at a Glance | 6
Twig Template Form Function Reference ........................................................................................ 684
Validation Constraints Reference..................................................................................................... 686
NotBlank........................................................................................................................................ 688
Blank.............................................................................................................................................. 689
NotNull.......................................................................................................................................... 690
Null................................................................................................................................................ 691
True ............................................................................................................................................... 692
False............................................................................................................................................... 694
Type............................................................................................................................................... 696
Email.............................................................................................................................................. 698
MinLength...................................................................................................................................... 700
MaxLength ..................................................................................................................................... 702
Url.................................................................................................................................................. 704
Regex ............................................................................................................................................. 706
Ip ................................................................................................................................................... 708
Max................................................................................................................................................ 710
Min ................................................................................................................................................ 712
Date ............................................................................................................................................... 714
DateTime ....................................................................................................................................... 715
Time............................................................................................................................................... 716
Choice............................................................................................................................................ 717
Collection....................................................................................................................................... 720
UniqueEntity .................................................................................................................................. 723
Language ........................................................................................................................................ 725
Locale............................................................................................................................................. 726
Country.......................................................................................................................................... 727
File ................................................................................................................................................. 728
Image ............................................................................................................................................. 731
Callback ......................................................................................................................................... 732
Valid .............................................................................................................................................. 735
All .................................................................................................................................................. 737
The Dependency Injection Tags....................................................................................................... 739
Requirements for running Symfony2................................................................................................ 747




PDF brought to you by                                                                                                    Contents at a Glance | vii
generated on June 20, 2012
Part I
The Quick Tour
Chapter 1
                                    The Big Picture

Start using Symfony2 in 10 minutes! This chapter will walk you through some of the most important
concepts behind Symfony2 and explain how you can get started quickly by showing you a simple project
in action.
If you've used a web framework before, you should feel right at home with Symfony2. If not, welcome to
a whole new way of developing web applications!


              Want to learn why and when you need to use a framework? Read the "Symfony in 5 minutes"
              document.




Downloading Symfony2
First, check that you have installed and configured a Web server (such as Apache) with PHP 5.3.2 or
higher.
Ready? Start by downloading the "Symfony2 Standard Edition1", a Symfony distribution that is
preconfigured for the most common use cases and also contains some code that demonstrates how to use
Symfony2 (get the archive with the vendors included to get started even faster).
After unpacking the archive under your web server root directory, you should have a Symfony/ directory
that looks like this:

www/ <- your web root directory                                                                                 Listing
                                                                                                                 1-1
    Symfony/ <- the unpacked archive
        app/
             cache/
             config/
             logs/
             Resources/
        bin/
        src/
             Acme/

1. http://symfony.com/download


PDF brought to you by                                                          Chapter 1: The Big Picture | 9
generated on June 20, 2012
DemoBundle/
                                    Controller/
                                    Resources/
                                    ...
                     vendor/
                          symfony/
                          doctrine/
                          ...
                     web/
                          app.php
                          ...



                          If you downloaded the Standard Edition without vendors, simply run the following command to
                          download all of the vendor libraries:

                Listing   php bin/vendors install
                 1-2




          Checking the Configuration
          Symfony2 comes with a visual server configuration tester to help avoid some headaches that come from
          Web server or PHP misconfiguration. Use the following URL to see the diagnostics for your machine:

Listing   http://localhost/Symfony/web/config.php
 1-3

          If there are any outstanding issues listed, correct them. You might also tweak your configuration by
          following any given recommendations. When everything is fine, click on "Bypass configuration and go to
          the Welcome page" to request your first "real" Symfony2 webpage:

Listing   http://localhost/Symfony/web/app_dev.php/
 1-4

          Symfony2 should welcome and congratulate you for your hard work so far!




          PDF brought to you by                                                              Chapter 1: The Big Picture | 10
          generated on June 20, 2012
Understanding the Fundamentals
One of the main goals of a framework is to ensure Separation of Concerns2. This keeps your code
organized and allows your application to evolve easily over time by avoiding the mixing of database calls,
HTML tags, and business logic in the same script. To achieve this goal with Symfony, you'll first need to
learn a few fundamental concepts and terms.


              Want proof that using a framework is better than mixing everything in the same script? Read the
              "Symfony2 versus Flat PHP" chapter of the book.


The distribution comes with some sample code that you can use to learn more about the main Symfony2
concepts. Go to the following URL to be greeted by Symfony2 (replace Fabien with your first name):

http://localhost/Symfony/web/app_dev.php/demo/hello/Fabien                                                            Listing
                                                                                                                       1-5




What's going on here? Let's dissect the URL:

  • app_dev.php: This is a front controller. It is the unique entry point of the application and it
    responds to all user requests;
  • /demo/hello/Fabien: This is the virtual path to the resource the user wants to access.

Your responsibility as a developer is to write the code that maps the user's request (/demo/hello/Fabien)
to the resource associated with it (the Hello Fabien! HTML page).


Routing
Symfony2 routes the request to the code that handles it by trying to match the requested URL against
some configured patterns. By default, these patterns (called routes) are defined in the app/config/
routing.yml configuration file. When you're in the dev environment - indicated by the app_**dev**.php
front controller - the app/config/routing_dev.yml configuration file is also loaded. In the Standard
Edition, the routes to these "demo" pages are placed in that file:

# app/config/routing_dev.yml                                                                                          Listing
                                                                                                                       1-6
_welcome:

2. http://en.wikipedia.org/wiki/Separation_of_concerns


PDF brought to you by                                                               Chapter 1: The Big Picture | 11
generated on June 20, 2012
pattern: /
               defaults: { _controller: AcmeDemoBundle:Welcome:index }

          _demo:
              resource: "@AcmeDemoBundle/Controller/DemoController.php"
              type:     annotation
              prefix: /demo

          # ...

          The first three lines (after the comment) define the code that is executed when the user requests the "/"
          resource (i.e. the welcome page you saw earlier). When requested, the AcmeDemoBundle:Welcome:index
          controller will be executed. In the next section, you'll learn exactly what that means.


                        The Symfony2 Standard Edition uses YAML3 for its configuration files, but Symfony2 also supports
                        XML, PHP, and annotations natively. The different formats are compatible and may be used
                        interchangeably within an application. Also, the performance of your application does not depend
                        on the configuration format you choose as everything is cached on the very first request.


          Controllers
          A controller is a fancy name for a PHP function or method that handles incoming requests and returns
          responses (often HTML code). Instead of using the PHP global variables and functions (like $_GET
          or header()) to manage these HTTP messages, Symfony uses objects: Request4 and Response5. The
          simplest possible controller might create the response by hand, based on the request:

Listing   use SymfonyComponentHttpFoundationResponse;
 1-7

          $name = $request->query->get('name');

          return new Response('Hello '.$name, 200, array('Content-Type' => 'text/plain'));



                        Symfony2 embraces the HTTP Specification, which are the rules that govern all communication on
                        the Web. Read the "Symfony2 and HTTP Fundamentals" chapter of the book to learn more about
                        this and the added power that this brings.

          Symfony2 chooses the controller based on the _controller value from the routing configuration:
          AcmeDemoBundle:Welcome:index. This string is the controller logical name, and it references the
          indexAction method from the AcmeDemoBundleControllerWelcomeController class:

Listing   // src/Acme/DemoBundle/Controller/WelcomeController.php
 1-8
          namespace AcmeDemoBundleController;

          use SymfonyBundleFrameworkBundleControllerController;

          class WelcomeController extends Controller
          {
              public function indexAction()
              {
                  return $this->render('AcmeDemoBundle:Welcome:index.html.twig');
              }
          }

          3. http://www.yaml.org/
          4. http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/Request.html
          5. http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/Response.html


          PDF brought to you by                                                                Chapter 1: The Big Picture | 12
          generated on June 20, 2012
You        could  have    used     the     full    class    and     method      name     -
              AcmeDemoBundleControllerWelcomeController::indexAction - for the _controller value.
              But if you follow some simple conventions, the logical name is shorter and allows for more
              flexibility.

The WelcomeController class extends the built-in Controller class, which provides useful shortcut
methods,     like   the      render()6      method     that    loads    and    renders       a     template
(AcmeDemoBundle:Welcome:index.html.twig). The returned value is a Response object populated with
the rendered content. So, if the needs arise, the Response can be tweaked before it is sent to the browser:

public function indexAction()                                                                                                        Listing
                                                                                                                                      1-9
{
    $response = $this->render('AcmeDemoBundle:Welcome:index.txt.twig');
    $response->headers->set('Content-Type', 'text/plain');

     return $response;
}

No matter how you do it, the end goal of your controller is always to return the Response object that
should be delivered back to the user. This Response object can be populated with HTML code, represent
a client redirect, or even return the contents of a JPG image with a Content-Type header of image/jpg.


              Extending the Controller base class is optional. As a matter of fact, a controller can be a plain
              PHP function or even a PHP closure. "The Controller" chapter of the book tells you everything
              about Symfony2 controllers.

The template name, AcmeDemoBundle:Welcome:index.html.twig, is the template logical name and it
references the Resources/views/Welcome/index.html.twig file inside the AcmeDemoBundle (located at
src/Acme/DemoBundle). The bundles section below will explain why this is useful.
Now, take a look at the routing configuration again and find the _demo key:

# app/config/routing_dev.yml                                                                                                         Listing
                                                                                                                                      1-10
_demo:
    resource: "@AcmeDemoBundle/Controller/DemoController.php"
    type:     annotation
    prefix: /demo

Symfony2 can read/import the routing information from different files written in YAML, XML, PHP,
or even embedded in PHP annotations. Here, the file's logical name is @AcmeDemoBundle/Controller/
DemoController.php and refers to the src/Acme/DemoBundle/Controller/DemoController.php file.
In this file, routes are defined as annotations on action methods:

// src/Acme/DemoBundle/Controller/DemoController.php                                                                                 Listing
                                                                                                                                      1-11
use SensioBundleFrameworkExtraBundleConfigurationRoute;
use SensioBundleFrameworkExtraBundleConfigurationTemplate;

class DemoController extends Controller
{
    /**
     * @Route("/hello/{name}", name="_demo_hello")
     * @Template()
     */
    public function helloAction($name)


6. http://api.symfony.com/2.0/Symfony/Bundle/FrameworkBundle/Controller/Controller.html#render()


PDF brought to you by                                                                              Chapter 1: The Big Picture | 13
generated on June 20, 2012
{
                     return array('name' => $name);
               }

               // ...
          }

          The @Route() annotation defines a new route with a pattern of /hello/{name} that executes the
          helloAction method when matched. A string enclosed in curly brackets like {name} is called a
          placeholder. As you can see, its value can be retrieved through the $name method argument.


                        Even if annotations are not natively supported by PHP, you use them extensively in Symfony2 as a
                        convenient way to configure the framework behavior and keep the configuration next to the code.


          If you take a closer look at the controller code, you can see that instead of rendering a template
          and returning a Response object like before, it just returns an array of parameters. The @Template()
          annotation tells Symfony to render the template for you, passing in each variable of the array to
          the template. The name of the template that's rendered follows the name of the controller. So, in
          this example, the AcmeDemoBundle:Demo:hello.html.twig template is rendered (located at src/Acme/
          DemoBundle/Resources/views/Demo/hello.html.twig).


                        The @Route() and @Template() annotations are more powerful than the simple examples shown
                        in this tutorial. Learn more about "annotations in controllers" in the official documentation.



          Templates
          The controller renders the src/Acme/DemoBundle/Resources/views/Demo/hello.html.twig template
          (or AcmeDemoBundle:Demo:hello.html.twig if you use the logical name):

Listing   {# src/Acme/DemoBundle/Resources/views/Demo/hello.html.twig #}
 1-12
          {% extends "AcmeDemoBundle::layout.html.twig" %}

          {% block title "Hello " ~ name %}

          {% block content %}
              <h1>Hello {{ name }}!</h1>
          {% endblock %}

          By default, Symfony2 uses Twig7 as its template engine but you can also use traditional PHP templates if
          you choose. The next chapter will introduce how templates work in Symfony2.

          Bundles
          You might have wondered why the bundle word is used in many names we have seen so far. All the code
          you write for your application is organized in bundles. In Symfony2 speak, a bundle is a structured set
          of files (PHP files, stylesheets, JavaScripts, images, ...) that implements a single feature (a blog, a forum,
          ...) and which can be easily shared with other developers. As of now, we have manipulated one bundle,
          AcmeDemoBundle. You will learn more about bundles in the last chapter of this tutorial.




          7. http://twig.sensiolabs.org/


          PDF brought to you by                                                                Chapter 1: The Big Picture | 14
          generated on June 20, 2012
Working with Environments
Now that you have a better understanding of how Symfony2 works, take a closer look at the bottom of
any Symfony2 rendered page. You should notice a small bar with the Symfony2 logo. This is called the
"Web Debug Toolbar" and it is the developer's best friend.




But what you see initially is only the tip of the iceberg; click on the weird hexadecimal number to reveal
yet another very useful Symfony2 debugging tool: the profiler.




Of course, you won't want to show these tools when you deploy your application to production. That's
why you will find another front controller in the web/ directory (app.php), which is optimized for the
production environment:

http://localhost/Symfony/web/app.php/demo/hello/Fabien                                                             Listing
                                                                                                                    1-13

And if you use Apache with mod_rewrite enabled, you can even omit the app.php part of the URL:

http://localhost/Symfony/web/demo/hello/Fabien                                                                     Listing
                                                                                                                    1-14

Last but not least, on the production servers, you should point your web root directory to the web/
directory to secure your installation and have an even better looking URL:



PDF brought to you by                                                            Chapter 1: The Big Picture | 15
generated on June 20, 2012
Listing   http://localhost/demo/hello/Fabien
 1-15




                        Note that the three URLs above are provided here only as examples of how a URL looks like
                        when the production front controller is used (with or without mod_rewrite). If you actually try
                        them in an out of the box installation of Symfony Standard Edition you will get a 404 error
                        as AcmeDemoBundle is enabled only in dev environment and its routes imported in app/config/
                        routing_dev.yml.

          To make you application respond faster, Symfony2 maintains a cache under the app/cache/ directory.
          In the development environment (app_dev.php), this cache is flushed automatically whenever you make
          changes to any code or configuration. But that's not the case in the production environment (app.php)
          where performance is key. That's why you should always use the development environment when
          developing your application.
          Different environments of a given application differ only in their configuration. In fact, a configuration
          can inherit from another one:

Listing   # app/config/config_dev.yml
 1-16
          imports:
              - { resource: config.yml }

          web_profiler:
              toolbar: true
              intercept_redirects: false

          The dev environment (which loads the config_dev.yml configuration file) imports the global
          config.yml file and then modifies it by, in this example, enabling the web debug toolbar.



          Final Thoughts
          Congratulations! You've had your first taste of Symfony2 code. That wasn't so hard, was it? There's a lot
          more to explore, but you should already see how Symfony2 makes it really easy to implement web sites
          better and faster. If you are eager to learn more about Symfony2, dive into the next section: "The View".




          PDF brought to you by                                                               Chapter 1: The Big Picture | 16
          generated on June 20, 2012
Chapter 2
                                              The View

After reading the first part of this tutorial, you have decided that Symfony2 was worth another 10
minutes. Great choice! In this second part, you will learn more about the Symfony2 template engine,
Twig1. Twig is a flexible, fast, and secure template engine for PHP. It makes your templates more readable
and concise; it also makes them more friendly for web designers.


              Instead of Twig, you can also use PHP for your templates. Both template engines are supported by
              Symfony2.




Getting familiar with Twig

              If you want to learn Twig, we highly recommend you to read its official documentation2. This
              section is just a quick overview of the main concepts.


A Twig template is a text file that can generate any type of content (HTML, XML, CSV, LaTeX, ...). Twig
defines two kinds of delimiters:

  • {{ ... }}: Prints a variable or the result of an expression;
  • {% ... %}: Controls the logic of the template; it is used to execute for loops and if
    statements, for example.

Below is a minimal template that illustrates a few basics, using two variables page_title and
navigation, which would be passed into the template:

<!DOCTYPE html>                                                                                                      Listing
                                                                                                                      2-1
<html>
    <head>
        <title>My Webpage</title>

1. http://twig.sensiolabs.org/
2. http://twig.sensiolabs.org/documentation


PDF brought to you by                                                                     Chapter 2: The View | 17
generated on June 20, 2012
</head>
               <body>
                   <h1>{{ page_title }}</h1>

                  <ul id="navigation">
                      {% for item in navigation %}
                          <li><a href="{{ item.href }}">{{ item.caption }}</a></li>
                      {% endfor %}
                  </ul>
              </body>
          </html>



                        Comments can be included inside templates using the {# ... #} delimiter.



          To render a template in Symfony, use the render method from within a controller and pass it any
          variables needed in the template:

Listing   $this->render('AcmeDemoBundle:Demo:hello.html.twig', array(
 2-2
              'name' => $name,
          ));

          Variables passed to a template can be strings, arrays, or even objects. Twig abstracts the difference
          between them and lets you access "attributes" of a variable with the dot (.) notation:

Listing   {# array('name' => 'Fabien') #}
 2-3
          {{ name }}

          {# array('user' => array('name' => 'Fabien')) #}
          {{ user.name }}

          {# force array lookup #}
          {{ user['name'] }}

          {# array('user' => new User('Fabien')) #}
          {{ user.name }}
          {{ user.getName }}

          {# force method name lookup #}
          {{ user.name() }}
          {{ user.getName() }}

          {# pass arguments to a method #}
          {{ user.date('Y-m-d') }}



                        It's important to know that the curly braces are not part of the variable but the print statement. If
                        you access variables inside tags don't put the braces around.




          Decorating Templates
          More often than not, templates in a project share common elements, like the well-known header and
          footer. In Symfony2, we like to think about this problem differently: a template can be decorated by
          another one. This works exactly the same as PHP classes: template inheritance allows you to build a


          PDF brought to you by                                                                         Chapter 2: The View | 18
          generated on June 20, 2012
base "layout" template that contains all the common elements of your site and defines "blocks" that child
templates can override.
The hello.html.twig template inherits from layout.html.twig, thanks to the extends tag:

{# src/Acme/DemoBundle/Resources/views/Demo/hello.html.twig #}                                                   Listing
                                                                                                                  2-4
{% extends "AcmeDemoBundle::layout.html.twig" %}

{% block title "Hello " ~ name %}

{% block content %}
    <h1>Hello {{ name }}!</h1>
{% endblock %}

The AcmeDemoBundle::layout.html.twig notation sounds familiar, doesn't it? It is the same notation
used to reference a regular template. The :: part simply means that the controller element is empty, so
the corresponding file is directly stored under the Resources/views/ directory.
Now, let's have a look at a simplified layout.html.twig:

{# src/Acme/DemoBundle/Resources/views/layout.html.twig #}                                                       Listing
                                                                                                                  2-5
<div class="symfony-content">
    {% block content %}
    {% endblock %}
</div>

The {% block %} tags define blocks that child templates can fill in. All the block tag does is to tell the
template engine that a child template may override those portions of the template.
In this example, the hello.html.twig template overrides the content block, meaning that the "Hello
Fabien" text is rendered inside the div.symfony-content element.



Using Tags, Filters, and Functions
One of the best feature of Twig is its extensibility via tags, filters, and functions. Symfony2 comes
bundled with many of these built-in to ease the work of the template designer.

Including other Templates
The best way to share a snippet of code between several distinct templates is to create a new template
that can then be included from other templates.
Create an embedded.html.twig template:

{# src/Acme/DemoBundle/Resources/views/Demo/embedded.html.twig #}                                                Listing
                                                                                                                  2-6
Hello {{ name }}

And change the index.html.twig template to include it:

{# src/Acme/DemoBundle/Resources/views/Demo/hello.html.twig #}                                                   Listing
                                                                                                                  2-7
{% extends "AcmeDemoBundle::layout.html.twig" %}

{# override the body block from embedded.html.twig #}
{% block content %}
    {% include "AcmeDemoBundle:Demo:embedded.html.twig" %}
{% endblock %}




PDF brought to you by                                                                 Chapter 2: The View | 19
generated on June 20, 2012
Embedding other Controllers
          And what if you want to embed the result of another controller in a template? That's very useful when
          working with Ajax, or when the embedded template needs some variable not available in the main
          template.
          Suppose you've created a fancy action, and you want to include it inside the index template. To do this,
          use the render tag:

Listing   {# src/Acme/DemoBundle/Resources/views/Demo/index.html.twig #}
 2-8
          {% render "AcmeDemoBundle:Demo:fancy" with { 'name': name, 'color': 'green' } %}

          Here, the AcmeDemoBundle:Demo:fancy string refers to the fancy action of the Demo controller. The
          arguments (name and color) act like simulated request variables (as if the fancyAction were handling a
          whole new request) and are made available to the controller:

Listing   // src/Acme/DemoBundle/Controller/DemoController.php
 2-9

          class DemoController extends Controller
          {
              public function fancyAction($name, $color)
              {
                  // create some object, based on the $color variable
                  $object = ...;

                  return $this->render('AcmeDemoBundle:Demo:fancy.html.twig', array('name' => $name,
          'object' => $object));
              }

               // ...
          }


          Creating Links between Pages
          Speaking of web applications, creating links between pages is a must. Instead of hardcoding URLs in
          templates, the path function knows how to generate URLs based on the routing configuration. That way,
          all your URLs can be easily updated by just changing the configuration:

Listing   <a href="{{ path('_demo_hello', { 'name': 'Thomas' }) }}">Greet Thomas!</a>
 2-10

          The path function takes the route name and an array of parameters as arguments. The route name is the
          main key under which routes are referenced and the parameters are the values of the placeholders defined
          in the route pattern:

Listing   // src/Acme/DemoBundle/Controller/DemoController.php
 2-11
          use SensioBundleFrameworkExtraBundleConfigurationRoute;
          use SensioBundleFrameworkExtraBundleConfigurationTemplate;

          /**
            * @Route("/hello/{name}", name="_demo_hello")
            * @Template()
            */
          public function helloAction($name)
          {
               return array('name' => $name);
          }




          PDF brought to you by                                                               Chapter 2: The View | 20
          generated on June 20, 2012
The url function generates absolute URLs: {{ url('_demo_hello', { 'name': 'Thomas' })
              }}.



Including Assets: images, JavaScripts, and stylesheets
What would the Internet be without images, JavaScripts, and stylesheets? Symfony2 provides the asset
function to deal with them easily:

<link href="{{ asset('css/blog.css') }}" rel="stylesheet" type="text/css" />                                      Listing
                                                                                                                   2-12

<img src="{{ asset('images/logo.png') }}" />

The asset function's main purpose is to make your application more portable. Thanks to this function,
you can move the application root directory anywhere under your web root directory without changing
anything in your template's code.



Escaping Variables
Twig is configured to automatically escapes all output by default. Read Twig documentation3 to learn
more about output escaping and the Escaper extension.



Final Thoughts
Twig is simple yet powerful. Thanks to layouts, blocks, templates and action inclusions, it is very easy to
organize your templates in a logical and extensible way. However, if you're not comfortable with Twig,
you can always use PHP templates inside Symfony without any issues.
You have only been working with Symfony2 for about 20 minutes, but you can already do pretty amazing
stuff with it. That's the power of Symfony2. Learning the basics is easy, and you will soon learn that this
simplicity is hidden under a very flexible architecture.
But I'm getting ahead of myself. First, you need to learn more about the controller and that's exactly the
topic of the next part of this tutorial. Ready for another 10 minutes with Symfony2?




3. http://twig.sensiolabs.org/documentation


PDF brought to you by                                                                  Chapter 2: The View | 21
generated on June 20, 2012
Chapter 3
                                               The Controller

          Still with us after the first two parts? You are already becoming a Symfony2 addict! Without further ado,
          let's discover what controllers can do for you.



          Using Formats
          Nowadays, a web application should be able to deliver more than just HTML pages. From XML for RSS
          feeds or Web Services, to JSON for Ajax requests, there are plenty of different formats to choose from.
          Supporting those formats in Symfony2 is straightforward. Tweak the route by adding a default value of
          xml for the _format variable:

Listing   // src/Acme/DemoBundle/Controller/DemoController.php
 3-1
          use SensioBundleFrameworkExtraBundleConfigurationRoute;
          use SensioBundleFrameworkExtraBundleConfigurationTemplate;

          /**
            * @Route("/hello/{name}", defaults={"_format"="xml"}, name="_demo_hello")
            * @Template()
            */
          public function helloAction($name)
          {
               return array('name' => $name);
          }

          By using the request format (as defined by the _format value), Symfony2 automatically selects the right
          template, here hello.xml.twig:

Listing   <!-- src/Acme/DemoBundle/Resources/views/Demo/hello.xml.twig -->
 3-2
          <hello>
              <name>{{ name }}</name>
          </hello>

          That's all there is to it. For standard formats, Symfony2 will also automatically choose the best Content-
          Type header for the response. If you want to support different formats for a single action, use the
          {_format} placeholder in the route pattern instead:


          PDF brought to you by                                                             Chapter 3: The Controller | 22
          generated on June 20, 2012
// src/Acme/DemoBundle/Controller/DemoController.php                                                                Listing
                                                                                                                     3-3
use SensioBundleFrameworkExtraBundleConfigurationRoute;
use SensioBundleFrameworkExtraBundleConfigurationTemplate;

/**
  * @Route("/hello/{name}.{_format}", defaults={"_format"="html"},
requirements={"_format"="html|xml|json"}, name="_demo_hello")
  * @Template()
  */
public function helloAction($name)
{
     return array('name' => $name);
}

The controller will now be called for URLs like /demo/hello/Fabien.xml or /demo/hello/
Fabien.json.
The requirements entry defines regular expressions that placeholders must match. In this example, if
you try to request the /demo/hello/Fabien.js resource, you will get a 404 HTTP error, as it does not
match the _format requirement.



Redirecting and Forwarding
If you want to redirect the user to another page, use the redirect() method:

return $this->redirect($this->generateUrl('_demo_hello', array('name' => 'Lucas')));                                Listing
                                                                                                                     3-4

The generateUrl() is the same method as the path() function we used in templates. It takes the route
name and an array of parameters as arguments and returns the associated friendly URL.
You can also easily forward the action to another one with the forward() method. Internally, Symfony
makes a "sub-request", and returns the Response object from that sub-request:

$response = $this->forward('AcmeDemoBundle:Hello:fancy', array('name' => $name, 'color' =>                          Listing
                                                                                                                     3-5
'green'));

// do something with the response or return it directly



Getting information from the Request
Besides the values of the routing placeholders, the controller also has access to the Request object:

$request = $this->getRequest();                                                                                     Listing
                                                                                                                     3-6

$request->isXmlHttpRequest(); // is it an Ajax request?

$request->getPreferredLanguage(array('en', 'fr'));

$request->query->get('page'); // get a $_GET parameter

$request->request->get('page'); // get a $_POST parameter

In a template, you can also access the Request object via the app.request variable:

{{ app.request.query.get('page') }}                                                                                 Listing
                                                                                                                     3-7

{{ app.request.parameter('page') }}


PDF brought to you by                                                              Chapter 3: The Controller | 23
generated on June 20, 2012
Persisting Data in the Session
          Even if the HTTP protocol is stateless, Symfony2 provides a nice session object that represents the client
          (be it a real person using a browser, a bot, or a web service). Between two requests, Symfony2 stores the
          attributes in a cookie by using native PHP sessions.
          Storing and retrieving information from the session can be easily achieved from any controller:

Listing   $session = $this->getRequest()->getSession();
 3-8

          // store an attribute for reuse during a later user request
          $session->set('foo', 'bar');

          // in another controller for another request
          $foo = $session->get('foo');

          // set the user locale
          $session->setLocale('fr');

          You can also store small messages that will only be available for the very next request:

Listing   // store a message for the very next request (in a controller)
 3-9
          $session->setFlash('notice', 'Congratulations, your action succeeded!');

          // display the message back in the next request (in a template)
          {{ app.session.flash('notice') }}

          This is useful when you need to set a success message before redirecting the user to another page (which
          will then show the message).



          Securing Resources
          The Symfony Standard Edition comes with a simple security configuration that fits most common needs:

Listing   # app/config/security.yml
 3-10
          security:
              encoders:
                  SymfonyComponentSecurityCoreUserUser: plaintext

               role_hierarchy:
                   ROLE_ADMIN:       ROLE_USER
                   ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]

               providers:
                   in_memory:
                       users:
                           user: { password: userpass, roles: [ 'ROLE_USER' ] }
                           admin: { password: adminpass, roles: [ 'ROLE_ADMIN' ] }

               firewalls:
                   dev:
                        pattern: ^/(_(profiler|wdt)|css|images|js)/
                        security: false

                     login:
                         pattern: ^/demo/secured/login$
                         security: false



          PDF brought to you by                                                              Chapter 3: The Controller | 24
          generated on June 20, 2012
secured_area:
               pattern:    ^/demo/secured/
               form_login:
                   check_path: /demo/secured/login_check
                   login_path: /demo/secured/login
               logout:
                   path: /demo/secured/logout
                   target: /demo/

This configuration requires users to log in for any URL starting with /demo/secured/ and defines two
valid users: user and admin. Moreover, the admin user has a ROLE_ADMIN role, which includes the
ROLE_USER role as well (see the role_hierarchy setting).


              For readability, passwords are stored in clear text in this simple configuration, but you can use any
              hashing algorithm by tweaking the encoders section.


Going to the http://localhost/Symfony/web/app_dev.php/demo/secured/hello URL                                      will
automatically redirect you to the login form because this resource is protected by a firewall.
You can also force the action to require a given role by using the @Secure annotation on the controller:

use SensioBundleFrameworkExtraBundleConfigurationRoute;                                                               Listing
                                                                                                                           3-11
use SensioBundleFrameworkExtraBundleConfigurationTemplate;
use JMSSecurityExtraBundleAnnotationSecure;

/**
  * @Route("/hello/admin/{name}", name="_demo_secured_hello_admin")
  * @Secure(roles="ROLE_ADMIN")
  * @Template()
  */
public function helloAdminAction($name)
{
     return array('name' => $name);
}

Now, log in as user (who does not have the ROLE_ADMIN role) and from the secured hello page, click on
the "Hello resource secured" link. Symfony2 should return a 403 HTTP status code, indicating that the
user is "forbidden" from accessing that resource.


              The Symfony2 security layer is very flexible and comes with many different user providers (like
              one for the Doctrine ORM) and authentication providers (like HTTP basic, HTTP digest, or X509
              certificates). Read the "Security" chapter of the book for more information on how to use and
              configure them.



Caching Resources
As soon as your website starts to generate more traffic, you will want to avoid generating the same
resource again and again. Symfony2 uses HTTP cache headers to manage resources cache. For simple
caching strategies, use the convenient @Cache() annotation:

use SensioBundleFrameworkExtraBundleConfigurationRoute;                                                               Listing
                                                                                                                           3-12
use SensioBundleFrameworkExtraBundleConfigurationTemplate;
use SensioBundleFrameworkExtraBundleConfigurationCache;



PDF brought to you by                                                                    Chapter 3: The Controller | 25
generated on June 20, 2012
/**
  * @Route("/hello/{name}", name="_demo_hello")
  * @Template()
  * @Cache(maxage="86400")
  */
public function helloAction($name)
{
     return array('name' => $name);
}

In this example, the resource will be cached for a day. But you can also use validation instead of
expiration or a combination of both if that fits your needs better.
Resource caching is managed by the Symfony2 built-in reverse proxy. But because caching is managed
using regular HTTP cache headers, you can replace the built-in reverse proxy with Varnish or Squid and
easily scale your application.


              But what if you cannot cache whole pages? Symfony2 still has the solution via Edge Side Includes
              (ESI), which are supported natively. Learn more by reading the "HTTP Cache" chapter of the book.




Final Thoughts
That's all there is to it, and I'm not even sure we have spent the full 10 minutes. We briefly introduced
bundles in the first part, and all the features we've learned about so far are part of the core framework
bundle. But thanks to bundles, everything in Symfony2 can be extended or replaced. That's the topic of
the next part of this tutorial.




PDF brought to you by                                                                Chapter 3: The Controller | 26
generated on June 20, 2012
Chapter 4
                                  The Architecture

You are my hero! Who would have thought that you would still be here after the first three parts? Your
efforts will be well rewarded soon. The first three parts didn't look too deeply at the architecture of
the framework. Because it makes Symfony2 stand apart from the framework crowd, let's dive into the
architecture now.



Understanding the Directory Structure
The directory structure of a Symfony2 application is rather flexible, but the directory structure of the
Standard Edition distribution reflects the typical and recommended structure of a Symfony2 application:

  •   app/: The application configuration;
  •   src/: The project's PHP code;
  •   vendor/: The third-party dependencies;
  •   web/: The web root directory.


The web/ Directory
The web root directory is the home of all public and static files like images, stylesheets, and JavaScript
files. It is also where each front controller lives:

// web/app.php                                                                                                     Listing
                                                                                                                    4-1
require_once __DIR__.'/../app/bootstrap.php.cache';
require_once __DIR__.'/../app/AppKernel.php';

use SymfonyComponentHttpFoundationRequest;

$kernel = new AppKernel('prod', false);
$kernel->loadClassCache();
$kernel->handle(Request::createFromGlobals())->send();

The kernel first requires the bootstrap.php.cache file, which bootstraps the framework and registers
the autoloader (see below).
Like any front controller, app.php uses a Kernel Class, AppKernel, to bootstrap the application.


PDF brought to you by                                                           Chapter 4: The Architecture | 27
generated on June 20, 2012
The app/ Directory
          The AppKernel class is the main entry point of the application configuration and as such, it is stored in
          the app/ directory.
          This class must implement two methods:

            • registerBundles() must return an array of all bundles needed to run the application;
            • registerContainerConfiguration() loads the application configuration (more on this
              later).

          PHP autoloading can be configured via app/autoload.php:

Listing   // app/autoload.php
 4-2
          use SymfonyComponentClassLoaderUniversalClassLoader;

          $loader = new UniversalClassLoader();
          $loader->registerNamespaces(array(
              'Symfony'          => array(__DIR__.'/../vendor/symfony/src', __DIR__.'/../vendor/
          bundles'),
              'Sensio'           => __DIR__.'/../vendor/bundles',
              'JMS'              => __DIR__.'/../vendor/bundles',
              'DoctrineCommon' => __DIR__.'/../vendor/doctrine-common/lib',
              'DoctrineDBAL'   => __DIR__.'/../vendor/doctrine-dbal/lib',
              'Doctrine'         => __DIR__.'/../vendor/doctrine/lib',
              'Monolog'          => __DIR__.'/../vendor/monolog/src',
              'Assetic'          => __DIR__.'/../vendor/assetic/src',
              'Metadata'         => __DIR__.'/../vendor/metadata/src',
          ));
          $loader->registerPrefixes(array(
              'Twig_Extensions_' => __DIR__.'/../vendor/twig-extensions/lib',
              'Twig_'            => __DIR__.'/../vendor/twig/lib',
          ));

          // ...

          $loader->registerNamespaceFallbacks(array(
              __DIR__.'/../src',
          ));
          $loader->register();

          The UniversalClassLoader1 is used to autoload files that respect either the technical interoperability
          standards2 for PHP 5.3 namespaces or the PEAR naming convention3 for classes. As you can see here, all
          dependencies are stored under the vendor/ directory, but this is just a convention. You can store them
          wherever you want, globally on your server or locally in your projects.


                        If you want to learn more about the flexibility of the Symfony2 autoloader, read the "The
                        ClassLoader Component" chapter.




          Understanding the Bundle System
          This section introduces one of the greatest and most powerful features of Symfony2, the bundle system.


          1. http://api.symfony.com/2.0/Symfony/Component/ClassLoader/UniversalClassLoader.html
          2. http://symfony.com/PSR0
          3. http://pear.php.net/


          PDF brought to you by                                                                   Chapter 4: The Architecture | 28
          generated on June 20, 2012
A bundle is kind of like a plugin in other software. So why is it called a bundle and not a plugin? This
is because everything is a bundle in Symfony2, from the core framework features to the code you write
for your application. Bundles are first-class citizens in Symfony2. This gives you the flexibility to use pre-
built features packaged in third-party bundles or to distribute your own bundles. It makes it easy to pick
and choose which features to enable in your application and optimize them the way you want. And at the
end of the day, your application code is just as important as the core framework itself.

Registering a Bundle
An application is made up of bundles as defined in the registerBundles() method of the AppKernel
class. Each bundle is a directory that contains a single Bundle class that describes it:

// app/AppKernel.php                                                                                                  Listing
                                                                                                                       4-3
public function registerBundles()
{
    $bundles = array(
        new SymfonyBundleFrameworkBundleFrameworkBundle(),
        new SymfonyBundleSecurityBundleSecurityBundle(),
        new SymfonyBundleTwigBundleTwigBundle(),
        new SymfonyBundleMonologBundleMonologBundle(),
        new SymfonyBundleSwiftmailerBundleSwiftmailerBundle(),
        new SymfonyBundleDoctrineBundleDoctrineBundle(),
        new SymfonyBundleAsseticBundleAsseticBundle(),
        new SensioBundleFrameworkExtraBundleSensioFrameworkExtraBundle(),
        new JMSSecurityExtraBundleJMSSecurityExtraBundle(),
    );

     if (in_array($this->getEnvironment(), array('dev', 'test'))) {
         $bundles[] = new AcmeDemoBundleAcmeDemoBundle();
         $bundles[] = new SymfonyBundleWebProfilerBundleWebProfilerBundle();
         $bundles[] = new SensioBundleDistributionBundleSensioDistributionBundle();
         $bundles[] = new SensioBundleGeneratorBundleSensioGeneratorBundle();
     }

     return $bundles;
}

In addition to the AcmeDemoBundle that we have already talked about, notice that the kernel also
enables other bundles such as the FrameworkBundle, DoctrineBundle, SwiftmailerBundle, and
AsseticBundle bundle. They are all part of the core framework.


Configuring a Bundle
Each bundle can be customized via configuration files written in YAML, XML, or PHP. Have a look at
the default configuration:

# app/config/config.yml                                                                                               Listing
                                                                                                                       4-4
imports:
    - { resource: parameters.ini }
    - { resource: security.yml }

framework:
    secret:                  "%secret%"
    charset:                 UTF-8
    router:                  { resource: "%kernel.root_dir%/config/routing.yml" }
    form:                    true
    csrf_protection:         true
    validation:              { enable_annotations: true }
    templating:              { engines: ['twig'] } #assets_version: SomeVersionScheme

PDF brought to you by                                                              Chapter 4: The Architecture | 29
generated on June 20, 2012
session:
                   default_locale: "%locale%"
                   auto_start:     true

          # Twig Configuration
          twig:
              debug:            "%kernel.debug%"
              strict_variables: "%kernel.debug%"

          # Assetic Configuration
          assetic:
              debug:          "%kernel.debug%"
              use_controller: false
              filters:
                  cssrewrite: ~
                  # closure:
                  #     jar: "%kernel.root_dir%/java/compiler.jar"
                  # yui_css:
                  #     jar: "%kernel.root_dir%/java/yuicompressor-2.4.2.jar"

          # Doctrine Configuration
          doctrine:
              dbal:
                  driver:   "%database_driver%"
                  host:     "%database_host%"
                  dbname:   "%database_name%"
                  user:     "%database_user%"
                  password: "%database_password%"
                  charset: UTF8

               orm:
                   auto_generate_proxy_classes: "%kernel.debug%"
                   auto_mapping: true

          # Swiftmailer Configuration
          swiftmailer:
              transport: "%mailer_transport%"
              host:      "%mailer_host%"
              username: "%mailer_user%"
              password: "%mailer_password%"

          jms_security_extra:
              secure_controllers: true
              secure_all_services: false

          Each entry like framework defines the configuration for a specific bundle. For example, framework
          configures the FrameworkBundle while swiftmailer configures the SwiftmailerBundle.
          Each environment can override the default configuration by providing a specific configuration file. For
          example, the dev environment loads the config_dev.yml file, which loads the main configuration (i.e.
          config.yml) and then modifies it to add some debugging tools:

Listing   # app/config/config_dev.yml
 4-5
          imports:
              - { resource: config.yml }

          framework:
              router: { resource: "%kernel.root_dir%/config/routing_dev.yml" }
              profiler: { only_exceptions: false }

          web_profiler:


          PDF brought to you by                                                         Chapter 4: The Architecture | 30
          generated on June 20, 2012
toolbar: true
     intercept_redirects: false

monolog:
    handlers:
        main:
            type:            stream
            path:            "%kernel.logs_dir%/%kernel.environment%.log"
            level:           debug
        firephp:
            type:            firephp
            level:           info

assetic:
    use_controller: true


Extending a Bundle
In addition to being a nice way to organize and configure your code, a bundle can extend another bundle.
Bundle inheritance allows you to override any existing bundle in order to customize its controllers,
templates, or any of its files. This is where the logical names (e.g. @AcmeDemoBundle/Controller/
SecuredController.php) come in handy: they abstract where the resource is actually stored.

Logical File Names
When you want to reference a file from a bundle, use this notation: @BUNDLE_NAME/path/to/file;
Symfony2 will resolve @BUNDLE_NAME to the real path to the bundle. For instance, the logical path
@AcmeDemoBundle/Controller/DemoController.php would be converted to src/Acme/DemoBundle/
Controller/DemoController.php, because Symfony knows the location of the AcmeDemoBundle.

Logical Controller Names
For    controllers, you    need     to   reference  method    names    using   the   format
BUNDLE_NAME:CONTROLLER_NAME:ACTION_NAME. For instance, AcmeDemoBundle:Welcome:index maps to
the indexAction method from the AcmeDemoBundleControllerWelcomeController class.

Logical Template Names
For templates, the logical name AcmeDemoBundle:Welcome:index.html.twig is converted to the file
path src/Acme/DemoBundle/Resources/views/Welcome/index.html.twig. Templates become even
more interesting when you realize they don't need to be stored on the filesystem. You can easily store
them in a database table for instance.

Extending Bundles
If you follow these conventions, then you can use bundle inheritance to "override" files, controllers
or templates. For example, you can create a bundle - AcmeNewBundle - and specify that its parent is
AcmeDemoBundle. When Symfony loads the AcmeDemoBundle:Welcome:index controller, it will first look
for the WelcomeController class in AcmeNewBundle and then look inside AcmeDemoBundle. This means
that one bundle can override almost any part of another bundle!
Do you understand now why Symfony2 is so flexible? Share your bundles between applications, store
them locally or globally, your choice.




PDF brought to you by                                                          Chapter 4: The Architecture | 31
generated on June 20, 2012
Using Vendors
          Odds are that your application will depend on third-party libraries. Those should be stored in the
          vendor/ directory. This directory already contains the Symfony2 libraries, the SwiftMailer library, the
          Doctrine ORM, the Twig templating system, and some other third party libraries and bundles.



          Understanding the Cache and Logs
          Symfony2 is probably one of the fastest full-stack frameworks around. But how can it be so fast if it
          parses and interprets tens of YAML and XML files for each request? The speed is partly due to its cache
          system. The application configuration is only parsed for the very first request and then compiled down
          to plain PHP code stored in the app/cache/ directory. In the development environment, Symfony2 is
          smart enough to flush the cache when you change a file. But in the production environment, it is your
          responsibility to clear the cache when you update your code or change its configuration.
          When developing a web application, things can go wrong in many ways. The log files in the app/logs/
          directory tell you everything about the requests and help you fix the problem quickly.



          Using the Command Line Interface
          Each application comes with a command line interface tool (app/console) that helps you maintain your
          application. It provides commands that boost your productivity by automating tedious and repetitive
          tasks.
          Run it without any arguments to learn more about its capabilities:

Listing   php app/console
 4-6

          The --help option helps you discover the usage of a command:

Listing   php app/console router:debug --help
 4-7




          Final Thoughts
          Call me crazy, but after reading this part, you should be comfortable with moving things around and
          making Symfony2 work for you. Everything in Symfony2 is designed to get out of your way. So, feel free
          to rename and move directories around as you see fit.
          And that's all for the quick tour. From testing to sending emails, you still need to learn a lot to become a
          Symfony2 master. Ready to dig into these topics now? Look no further - go to the official The Book and
          pick any topic you want.




          PDF brought to you by                                                             Chapter 4: The Architecture | 32
          generated on June 20, 2012
Part II
The Book
Chapter 5
                 Symfony2 and HTTP Fundamentals

Congratulations! By learning about Symfony2, you're well on your way towards being a more productive,
well-rounded and popular web developer (actually, you're on your own for the last part). Symfony2 is
built to get back to basics: to develop tools that let you develop faster and build more robust applications,
while staying out of your way. Symfony is built on the best ideas from many technologies: the tools and
concepts you're about to learn represent the efforts of thousands of people, over many years. In other
words, you're not just learning "Symfony", you're learning the fundamentals of the web, development
best practices, and how to use many amazing new PHP libraries, inside or independent of Symfony2. So,
get ready.
True to the Symfony2 philosophy, this chapter begins by explaining the fundamental concept common
to web development: HTTP. Regardless of your background or preferred programming language, this
chapter is a must-read for everyone.



HTTP is Simple
HTTP (Hypertext Transfer Protocol to the geeks) is a text language that allows two machines to
communicate with each other. That's it! For example, when checking for the latest xkcd1 comic, the
following (approximate) conversation takes place:




1. http://xkcd.com/


PDF brought to you by                                              Chapter 5: Symfony2 and HTTP Fundamentals | 34
generated on June 20, 2012
And while the actual language used is a bit more formal, it's still dead-simple. HTTP is the term used to
describe this simple text-based language. And no matter how you develop on the web, the goal of your
server is always to understand simple text requests, and return simple text responses.
Symfony2 is built from the ground-up around that reality. Whether you realize it or not, HTTP is
something you use everyday. With Symfony2, you'll learn how to master it.

Step1: The Client sends a Request
Every conversation on the web starts with a request. The request is a text message created by a client (e.g.
a browser, an iPhone app, etc) in a special format known as HTTP. The client sends that request to a
server, and then waits for the response.
Take a look at the first part of the interaction (the request) between a browser and the xkcd web server:




In HTTP-speak, this HTTP request would actually look something like this:

GET / HTTP/1.1                                                                                                      Listing
                                                                                                                     5-1
Host: xkcd.com
Accept: text/html
User-Agent: Mozilla/5.0 (Macintosh)

This simple message communicates everything necessary about exactly which resource the client is
requesting. The first line of an HTTP request is the most important and contains two things: the URI and
the HTTP method.
The URI (e.g. /, /contact, etc) is the unique address or location that identifies the resource the client
wants. The HTTP method (e.g. GET) defines what you want to do with the resource. The HTTP methods
are the verbs of the request and define the few common ways that you can act upon the resource:


PDF brought to you by                                              Chapter 5: Symfony2 and HTTP Fundamentals | 35
generated on June 20, 2012
GET            Retrieve the resource from the server
           POST           Create a resource on the server
           PUT            Update the resource on the server
           DELETE Delete the resource from the server

          With this in mind, you can imagine what an HTTP request might look like to delete a specific blog entry,
          for example:

Listing   DELETE /blog/15 HTTP/1.1
 5-2




                        There are actually nine HTTP methods defined by the HTTP specification, but many of them are
                        not widely used or supported. In reality, many modern browsers don't support the PUT and DELETE
                        methods.

          In addition to the first line, an HTTP request invariably contains other lines of information called request
          headers. The headers can supply a wide range of information such as the requested Host, the response
          formats the client accepts (Accept) and the application the client is using to make the request (User-
          Agent). Many other headers exist and can be found on Wikipedia's List of HTTP header fields2 article.


          Step 2: The Server returns a Response
          Once a server has received the request, it knows exactly which resource the client needs (via the URI)
          and what the client wants to do with that resource (via the method). For example, in the case of a GET
          request, the server prepares the resource and returns it in an HTTP response. Consider the response from
          the xkcd web server:




          Translated into HTTP, the response sent back to the browser will look something like this:

Listing   HTTP/1.1 200 OK
 5-3
          Date: Sat, 02 Apr 2011 21:05:05 GMT
          Server: lighttpd/1.4.19
          Content-Type: text/html

          <html>
            <!-- HTML for the xkcd comic -->
          </html>




          2. http://en.wikipedia.org/wiki/List_of_HTTP_header_fields


          PDF brought to you by                                               Chapter 5: Symfony2 and HTTP Fundamentals | 36
          generated on June 20, 2012
The HTTP response contains the requested resource (the HTML content in this case), as well as other
information about the response. The first line is especially important and contains the HTTP response
status code (200 in this case). The status code communicates the overall outcome of the request back
to the client. Was the request successful? Was there an error? Different status codes exist that indicate
success, an error, or that the client needs to do something (e.g. redirect to another page). A full list can
be found on Wikipedia's List of HTTP status codes3 article.
Like the request, an HTTP response contains additional pieces of information known as HTTP headers.
For example, one important HTTP response header is Content-Type. The body of the same resource
could be returned in multiple different formats like HTML, XML, or JSON and the Content-Type header
uses Internet Media Types like text/html to tell the client which format is being returned. A list of
common media types can be found on Wikipedia's List of common media types4 article.
Many other headers exist, some of which are very powerful. For example, certain headers can be used to
create a powerful caching system.

Requests, Responses and Web Development
This request-response conversation is the fundamental process that drives all communication on the web.
And as important and powerful as this process is, it's inescapably simple.
The most important fact is this: regardless of the language you use, the type of application you build
(web, mobile, JSON API), or the development philosophy you follow, the end goal of an application is
always to understand each request and create and return the appropriate response.
Symfony is architected to match this reality.


                To learn more about the HTTP specification, read the original HTTP 1.1 RFC5 or the HTTP Bis6,
                which is an active effort to clarify the original specification. A great tool to check both the request
                and response headers while browsing is the Live HTTP Headers7 extension for Firefox.



Requests and Responses in PHP
So how do you interact with the "request" and create a "response" when using PHP? In reality, PHP
abstracts you a bit from the whole process:

<?php                                                                                                                               Listing
                                                                                                                                     5-4
$uri = $_SERVER['REQUEST_URI'];
$foo = $_GET['foo'];

header('Content-type: text/html');
echo 'The URI requested is: '.$uri;
echo 'The value of the "foo" parameter is: '.$foo;

As strange as it sounds, this small application is in fact taking information from the HTTP request and
using it to create an HTTP response. Instead of parsing the raw HTTP request message, PHP prepares
superglobal variables such as $_SERVER and $_GET that contain all the information from the request.
Similarly, instead of returning the HTTP-formatted text response, you can use the header() function to
create response headers and simply print out the actual content that will be the content portion of the
response message. PHP will create a true HTTP response and return it to the client:


3. http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
4.   http://en.wikipedia.org/wiki/Internet_media_type#List_of_common_media_types
5.   http://www.w3.org/Protocols/rfc2616/rfc2616.html
6.   http://datatracker.ietf.org/wg/httpbis/
7.   https://addons.mozilla.org/en-US/firefox/addon/live-http-headers/


PDF brought to you by                                                              Chapter 5: Symfony2 and HTTP Fundamentals | 37
generated on June 20, 2012
Listing   HTTP/1.1 200 OK
 5-5
          Date: Sat, 03 Apr 2011 02:14:33 GMT
          Server: Apache/2.2.17 (Unix)
          Content-Type: text/html

          The URI requested is: /testing?foo=symfony
          The value of the "foo" parameter is: symfony



          Requests and Responses in Symfony
          Symfony provides an alternative to the raw PHP approach via two classes that allow you to interact
          with the HTTP request and response in an easier way. The Request8 class is a simple object-oriented
          representation of the HTTP request message. With it, you have all the request information at your
          fingertips:

Listing   use SymfonyComponentHttpFoundationRequest;
 5-6

          $request = Request::createFromGlobals();

          // the URI being requested (e.g. /about) minus any query parameters
          $request->getPathInfo();

          // retrieve GET and POST variables respectively
          $request->query->get('foo');
          $request->request->get('bar', 'default value if bar does not exist');

          // retrieve SERVER variables
          $request->server->get('HTTP_HOST');

          // retrieves an instance of UploadedFile identified by foo
          $request->files->get('foo');

          // retrieve a COOKIE value
          $request->cookies->get('PHPSESSID');

          // retrieve an HTTP request header, with normalized, lowercase keys
          $request->headers->get('host');
          $request->headers->get('content_type');

          $request->getMethod();                      // GET, POST, PUT, DELETE, HEAD
          $request->getLanguages();                   // an array of languages the client accepts

          As a bonus, the Request class does a lot of work in the background that you'll never need to worry about.
          For example, the isSecure() method checks the three different values in PHP that can indicate whether
          or not the user is connecting via a secured connection (i.e. https).




          8. http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/Request.html


          PDF brought to you by                                                         Chapter 5: Symfony2 and HTTP Fundamentals | 38
          generated on June 20, 2012
ParameterBags and Request attributes
                As seen above, the $_GET and $_POST variables are accessible via the public query and request
                properties respectively. Each of these objects is a ParameterBag9 object, which has methods like
                get()10, has()11, all()12 and more. In fact, every public property used in the previous example is
                some instance of the ParameterBag.
                The Request class also has a public attributes property, which holds special data related to how
                the application works internally. For the Symfony2 framework, the attributes holds the values
                returned by the matched route, like _controller, id (if you have an {id} wildcard), and even the
                name of the matched route (_route). The attributes property exists entirely to be a place where
                you can prepare and store context-specific information about the request.

Symfony also provides a Response class: a simple PHP representation of an HTTP response message.
This allows your application to use an object-oriented interface to construct the response that needs to
be returned to the client:

use SymfonyComponentHttpFoundationResponse;                                                                                        Listing
                                                                                                                                       5-7
$response = new Response();

$response->setContent('<html><body><h1>Hello world!</h1></body></html>');
$response->setStatusCode(200);
$response->headers->set('Content-Type', 'text/html');

// prints the HTTP headers followed by the content
$response->send();

If Symfony offered nothing else, you would already have a toolkit for easily accessing request information
and an object-oriented interface for creating the response. Even as you learn the many powerful features
in Symfony, keep in mind that the goal of your application is always to interpret a request and create the
appropriate response based on your application logic.


                The Request and Response classes are part of a standalone component included with Symfony
                called HttpFoundation. This component can be used entirely independent of Symfony and also
                provides classes for handling sessions and file uploads.



The Journey from the Request to the Response
Like HTTP itself, the Request and Response objects are pretty simple. The hard part of building an
application is writing what comes in between. In other words, the real work comes in writing the code
that interprets the request information and creates the response.
Your application probably does many things, like sending emails, handling form submissions, saving
things to a database, rendering HTML pages and protecting content with security. How can you manage
all of this and still keep your code organized and maintainable?
Symfony was created to solve these problems so that you don't have to.




9.    http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/ParameterBag.html
10.    http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/ParameterBag.html#get()
11.    http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/ParameterBag.html#has()
12.    http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/ParameterBag.html#all()


PDF brought to you by                                                                Chapter 5: Symfony2 and HTTP Fundamentals | 39
generated on June 20, 2012
The Front Controller
          Traditionally, applications were built so that each "page" of a site was its own physical file:

Listing   index.php
 5-8
          contact.php
          blog.php

          There are several problems with this approach, including the inflexibility of the URLs (what if you
          wanted to change blog.php to news.php without breaking all of your links?) and the fact that each file
          must manually include some set of core files so that security, database connections and the "look" of the
          site can remain consistent.
          A much better solution is to use a front controller: a single PHP file that handles every request coming
          into your application. For example:

           /index.php                  executes index.php
           /index.php/contact executes index.php
           /index.php/blog             executes index.php



                        Using Apache's mod_rewrite (or equivalent with other web servers), the URLs can easily be
                        cleaned up to be just /, /contact and /blog.


          Now, every request is handled exactly the same. Instead of individual URLs executing different PHP
          files, the front controller is always executed, and the routing of different URLs to different parts of your
          application is done internally. This solves both problems with the original approach. Almost all modern
          web apps do this - including apps like WordPress.

          Stay Organized
          But inside your front controller, how do you know which page should be rendered and how can you
          render each in a sane way? One way or another, you'll need to check the incoming URI and execute
          different parts of your code depending on that value. This can get ugly quickly:

Listing   // index.php
 5-9

          $request = Request::createFromGlobals();
          $path = $request->getPathInfo(); // the URI path being requested

          if (in_array($path, array('', '/')) {
              $response = new Response('Welcome to the homepage.');
          } elseif ($path == '/contact') {
              $response = new Response('Contact us');
          } else {
              $response = new Response('Page not found.', 404);
          }
          $response->send();

          Solving this problem can be difficult. Fortunately it's exactly what Symfony is designed to do.

          The Symfony Application Flow
          When you let Symfony handle each request, life is much easier. Symfony follows the same simple pattern
          for every request:


          PDF brought to you by                                               Chapter 5: Symfony2 and HTTP Fundamentals | 40
          generated on June 20, 2012
Incoming requests are interpreted by the routing and passed to controller functions that return Response
objects.
Each "page" of your site is defined in a routing configuration file that maps different URLs to different
PHP functions. The job of each PHP function, called a controller, is to use information from the request -
along with many other tools Symfony makes available - to create and return a Response object. In other
words, the controller is where your code goes: it's where you interpret the request and create a response.
It's that easy! Let's review:

  • Each request executes a front controller file;
  • The routing system determines which PHP function should be executed based on information
    from the request and routing configuration you've created;
  • The correct PHP function is executed, where your code creates and returns the appropriate
    Response object.


A Symfony Request in Action
Without diving into too much detail, let's see this process in action. Suppose you want to add a
/contact page to your Symfony application. First, start by adding an entry for /contact to your routing
configuration file:

contact:                                                                                                            Listing
                                                                                                                     5-10
    pattern: /contact
    defaults: { _controller: AcmeDemoBundle:Main:contact }



              This example uses YAML to define the routing configuration. Routing configuration can also be
              written in other formats such as XML or PHP.


When someone visits the /contact page, this route is matched, and the specified controller is executed.
As you'll learn in the routing chapter, the AcmeDemoBundle:Main:contact string is a short syntax that
points to a specific PHP method contactAction inside a class called MainController:

class MainController                                                                                                Listing
                                                                                                                     5-11
{
    public function contactAction()
    {
        return new Response('<h1>Contact us!</h1>');
    }
}




PDF brought to you by                                              Chapter 5: Symfony2 and HTTP Fundamentals | 41
generated on June 20, 2012
In this very simple example, the controller simply creates a Response object with the HTML
"<h1>Contact us!</h1>". In the controller chapter, you'll learn how a controller can render templates,
allowing your "presentation" code (i.e. anything that actually writes out HTML) to live in a separate
template file. This frees up the controller to worry only about the hard stuff: interacting with the
database, handling submitted data, or sending email messages.



Symfony2: Build your App, not your Tools.
You now know that the goal of any app is to interpret each incoming request and create an appropriate
response. As an application grows, it becomes more difficult to keep your code organized and
maintainable. Invariably, the same complex tasks keep coming up over and over again: persisting things
to the database, rendering and reusing templates, handling form submissions, sending emails, validating
user input and handling security.
The good news is that none of these problems is unique. Symfony provides a framework full of tools that
allow you to build your application, not your tools. With Symfony2, nothing is imposed on you: you're
free to use the full Symfony framework, or just one piece of Symfony all by itself.

Standalone Tools: The Symfony2 Components
So what is Symfony2? First, Symfony2 is a collection of over twenty independent libraries that can be
used inside any PHP project. These libraries, called the Symfony2 Components, contain something useful
for almost any situation, regardless of how your project is developed. To name a few:

  • HttpFoundation13 - Contains the Request and Response classes, as well as other classes for
    handling sessions and file uploads;
  • Routing14 - Powerful and fast routing system that allows you to map a specific URI (e.g.
    /contact) to some information about how that request should be handled (e.g. execute the
    contactAction() method);
  • Form15 - A full-featured and flexible framework for creating forms and handling form
    submissions;
  • Validator16 A system for creating rules about data and then validating whether or not user-
    submitted data follows those rules;
  • ClassLoader17 An autoloading library that allows PHP classes to be used without needing to
    manually require the files containing those classes;
  • Templating18 A toolkit for rendering templates, handling template inheritance (i.e. a template
    is decorated with a layout) and performing other common template tasks;
  • Security19 - A powerful library for handling all types of security inside an application;
  • Translation20 A framework for translating strings in your application.

Each and every one of these components is decoupled and can be used in any PHP project, regardless of
whether or not you use the Symfony2 framework. Every part is made to be used if needed and replaced
when necessary.




13. https://github.com/symfony/HttpFoundation
14. https://github.com/symfony/Routing
15. https://github.com/symfony/Form
16. https://github.com/symfony/Validator
17. https://github.com/symfony/ClassLoader
18. https://github.com/symfony/Templating
19. https://github.com/symfony/Security
20. https://github.com/symfony/Translation


PDF brought to you by                                           Chapter 5: Symfony2 and HTTP Fundamentals | 42
generated on June 20, 2012
The Full Solution: The Symfony2 Framework
So then, what is the Symfony2 Framework? The Symfony2 Framework is a PHP library that accomplishes
two distinct tasks:
     1. Provides a selection of components (i.e. the Symfony2 Components) and third-party libraries
         (e.g. Swiftmailer for sending emails);
     2. Provides sensible configuration and a "glue" library that ties all of these pieces together.
The goal of the framework is to integrate many independent tools in order to provide a consistent
experience for the developer. Even the framework itself is a Symfony2 bundle (i.e. a plugin) that can be
configured or replaced entirely.
Symfony2 provides a powerful set of tools for rapidly developing web applications without imposing on
your application. Normal users can quickly start development by using a Symfony2 distribution, which
provides a project skeleton with sensible defaults. For more advanced users, the sky is the limit.




PDF brought to you by                                           Chapter 5: Symfony2 and HTTP Fundamentals | 43
generated on June 20, 2012
Chapter 6
                                       Symfony2 versus Flat PHP

          Why is Symfony2 better than just opening up a file and writing flat PHP?
          If you've never used a PHP framework, aren't familiar with the MVC philosophy, or just wonder what all
          the hype is around Symfony2, this chapter is for you. Instead of telling you that Symfony2 allows you to
          develop faster and better software than with flat PHP, you'll see for yourself.
          In this chapter, you'll write a simple application in flat PHP, and then refactor it to be more organized.
          You'll travel through time, seeing the decisions behind why web development has evolved over the past
          several years to where it is now.
          By the end, you'll see how Symfony2 can rescue you from mundane tasks and let you take back control
          of your code.



          A simple Blog in flat PHP
          In this chapter, you'll build the token blog application using only flat PHP. To begin, create a single page
          that displays blog entries that have been persisted to the database. Writing in flat PHP is quick and dirty:

Listing   <?php
 6-1
          // index.php

          $link = mysql_connect('localhost', 'myuser', 'mypassword');
          mysql_select_db('blog_db', $link);

          $result = mysql_query('SELECT id, title FROM post', $link);
          ?>

          <html>
              <head>
                  <title>List of Posts</title>
              </head>
              <body>
                  <h1>List of Posts</h1>
                  <ul>
                       <?php while ($row = mysql_fetch_assoc($result)): ?>
                       <li>


          PDF brought to you by                                                     Chapter 6: Symfony2 versus Flat PHP | 44
          generated on June 20, 2012
<a href="/show.php?id=<?php echo $row['id'] ?>">
                     <?php echo $row['title'] ?>
                </a>
            </li>
            <?php endwhile; ?>
        </ul>
    </body>
</html>

<?php
mysql_close($link);

That's quick to write, fast to execute, and, as your app grows, impossible to maintain. There are several
problems that need to be addressed:

  • No error-checking: What if the connection to the database fails?
  • Poor organization: If the application grows, this single file will become increasingly
    unmaintainable. Where should you put code to handle a form submission? How can you
    validate data? Where should code go for sending emails?
  • Difficult to reuse code: Since everything is in one file, there's no way to reuse any part of the
    application for other "pages" of the blog.


              Another problem not mentioned here is the fact that the database is tied to MySQL. Though not
              covered here, Symfony2 fully integrates Doctrine1, a library dedicated to database abstraction and
              mapping.

Let's get to work on solving these problems and more.

Isolating the Presentation
The code can immediately gain from separating the application "logic" from the code that prepares the
HTML "presentation":

<?php                                                                                                                    Listing
                                                                                                                          6-2
// index.php

$link = mysql_connect('localhost', 'myuser', 'mypassword');
mysql_select_db('blog_db', $link);

$result = mysql_query('SELECT id, title FROM post', $link);

$posts = array();
while ($row = mysql_fetch_assoc($result)) {
    $posts[] = $row;
}

mysql_close($link);

// include the HTML presentation code
require 'templates/list.php';

The HTML code is now stored in a separate file (templates/list.php), which is primarily an HTML file
that uses a template-like PHP syntax:

<html>                                                                                                                   Listing
                                                                                                                          6-3
    <head>

1. http://www.doctrine-project.org


PDF brought to you by                                                         Chapter 6: Symfony2 versus Flat PHP | 45
generated on June 20, 2012
<title>List of Posts</title>
              </head>
              <body>
                  <h1>List of Posts</h1>
                  <ul>
                       <?php foreach ($posts as $post): ?>
                       <li>
                            <a href="/read?id=<?php echo $post['id'] ?>">
                                 <?php echo $post['title'] ?>
                            </a>
                       </li>
                       <?php endforeach; ?>
                  </ul>
              </body>
          </html>

          By convention, the file that contains all of the application logic - index.php - is known as a "controller".
          The term controller is a word you'll hear a lot, regardless of the language or framework you use. It refers
          simply to the area of your code that processes user input and prepares the response.
          In this case, our controller prepares data from the database and then includes a template to present that
          data. With the controller isolated, you could easily change just the template file if you needed to render
          the blog entries in some other format (e.g. list.json.php for JSON format).


          Isolating the Application (Domain) Logic
          So far the application contains only one page. But what if a second page needed to use the same database
          connection, or even the same array of blog posts? Refactor the code so that the core behavior and data-
          access functions of the application are isolated in a new file called model.php:

Listing   <?php
 6-4
          // model.php

          function open_database_connection()
          {
              $link = mysql_connect('localhost', 'myuser', 'mypassword');
              mysql_select_db('blog_db', $link);

               return $link;
          }

          function close_database_connection($link)
          {
              mysql_close($link);
          }

          function get_all_posts()
          {
              $link = open_database_connection();

               $result = mysql_query('SELECT id, title FROM post', $link);
               $posts = array();
               while ($row = mysql_fetch_assoc($result)) {
                   $posts[] = $row;
               }
               close_database_connection($link);

               return $posts;
          }



          PDF brought to you by                                                     Chapter 6: Symfony2 versus Flat PHP | 46
          generated on June 20, 2012
The filename model.php is used because the logic and data access of an application is traditionally
              known as the "model" layer. In a well-organized application, the majority of the code representing
              your "business logic" should live in the model (as opposed to living in a controller). And unlike in
              this example, only a portion (or none) of the model is actually concerned with accessing a database.

The controller (index.php) is now very simple:

<?php                                                                                                                     Listing
                                                                                                                           6-5
require_once 'model.php';

$posts = get_all_posts();

require 'templates/list.php';

Now, the sole task of the controller is to get data from the model layer of the application (the model) and
to call a template to render that data. This is a very simple example of the model-view-controller pattern.

Isolating the Layout
At this point, the application has been refactored into three distinct pieces offering various advantages
and the opportunity to reuse almost everything on different pages.
The only part of the code that can't be reused is the page layout. Fix that by creating a new layout.php
file:

<!-- templates/layout.php -->                                                                                             Listing
                                                                                                                           6-6
<html>
    <head>
        <title><?php echo $title ?></title>
    </head>
    <body>
        <?php echo $content ?>
    </body>
</html>

The template (templates/list.php) can now be simplified to "extend" the layout:

<?php $title = 'List of Posts' ?>                                                                                         Listing
                                                                                                                           6-7

<?php ob_start() ?>
    <h1>List of Posts</h1>
    <ul>
         <?php foreach ($posts as $post): ?>
         <li>
              <a href="/read?id=<?php echo $post['id'] ?>">
                   <?php echo $post['title'] ?>
              </a>
         </li>
         <?php endforeach; ?>
    </ul>
<?php $content = ob_get_clean() ?>

<?php include 'layout.php' ?>

You've now introduced a methodology that allows for the reuse of the layout. Unfortunately, to
accomplish this, you're forced to use a few ugly PHP functions (ob_start(), ob_get_clean()) in the
template. Symfony2 uses a Templating component that allows this to be accomplished cleanly and
easily. You'll see it in action shortly.


PDF brought to you by                                                          Chapter 6: Symfony2 versus Flat PHP | 47
generated on June 20, 2012
Adding a Blog "show" Page
          The blog "list" page has now been refactored so that the code is better-organized and reusable. To prove
          it, add a blog "show" page, which displays an individual blog post identified by an id query parameter.
          To begin, create a new function in the model.php file that retrieves an individual blog result based on a
          given id:

Listing   // model.php
 6-8
          function get_post_by_id($id)
          {
              $link = open_database_connection();

               $id = mysql_real_escape_string($id);
               $query = 'SELECT date, title, body FROM post WHERE id = '.$id;
               $result = mysql_query($query);
               $row = mysql_fetch_assoc($result);

               close_database_connection($link);

               return $row;
          }

          Next, create a new file called show.php - the controller for this new page:

Listing   <?php
 6-9
          require_once 'model.php';

          $post = get_post_by_id($_GET['id']);

          require 'templates/show.php';

          Finally, create the new template file - templates/show.php - to render the individual blog post:

Listing   <?php $title = $post['title'] ?>
 6-10

          <?php ob_start() ?>
              <h1><?php echo $post['title'] ?></h1>

              <div class="date"><?php echo $post['date'] ?></div>
              <div class="body">
                  <?php echo $post['body'] ?>
              </div>
          <?php $content = ob_get_clean() ?>

          <?php include 'layout.php' ?>

          Creating the second page is now very easy and no code is duplicated. Still, this page introduces even
          more lingering problems that a framework can solve for you. For example, a missing or invalid id query
          parameter will cause the page to crash. It would be better if this caused a 404 page to be rendered,
          but this can't really be done easily yet. Worse, had you forgotten to clean the id parameter via the
          mysql_real_escape_string() function, your entire database would be at risk for an SQL injection
          attack.
          Another major problem is that each individual controller file must include the model.php file. What if
          each controller file suddenly needed to include an additional file or perform some other global task (e.g.
          enforce security)? As it stands now, that code would need to be added to every controller file. If you forget
          to include something in one file, hopefully it doesn't relate to security...



          PDF brought to you by                                                      Chapter 6: Symfony2 versus Flat PHP | 48
          generated on June 20, 2012
A "Front Controller" to the Rescue
The solution is to use a front controller: a single PHP file through which all requests are processed. With
a front controller, the URIs for the application change slightly, but start to become more flexible:

Without a front controller                                                                                              Listing
                                                                                                                         6-11
/index.php          => Blog post list page (index.php executed)
/show.php           => Blog post show page (show.php executed)

With index.php as the front controller
/index.php          => Blog post list page (index.php executed)
/index.php/show     => Blog post show page (index.php executed)



              The index.php portion of the URI can be removed if using Apache rewrite rules (or equivalent). In
              that case, the resulting URI of the blog show page would be simply /show.


When using a front controller, a single PHP file (index.php in this case) renders every request. For
the blog post show page, /index.php/show will actually execute the index.php file, which is now
responsible for routing requests internally based on the full URI. As you'll see, a front controller is a very
powerful tool.

Creating the Front Controller
You're about to take a big step with the application. With one file handling all requests, you can
centralize things such as security handling, configuration loading, and routing. In this application,
index.php must now be smart enough to render the blog post list page or the blog post show page based
on the requested URI:

<?php                                                                                                                   Listing
                                                                                                                         6-12
// index.php

// load and initialize any global libraries
require_once 'model.php';
require_once 'controllers.php';

// route the request internally
$uri = $_SERVER['REQUEST_URI'];
if ($uri == '/index.php') {
    list_action();
} elseif ($uri == '/index.php/show' && isset($_GET['id'])) {
    show_action($_GET['id']);
} else {
    header('Status: 404 Not Found');
    echo '<html><body><h1>Page Not Found</h1></body></html>';
}

For organization, both controllers (formerly index.php and show.php) are now PHP functions and each
has been moved into a separate file, controllers.php:

function list_action()                                                                                                  Listing
                                                                                                                         6-13
{
    $posts = get_all_posts();
    require 'templates/list.php';
}

function show_action($id)


PDF brought to you by                                                        Chapter 6: Symfony2 versus Flat PHP | 49
generated on June 20, 2012
{
               $post = get_post_by_id($id);
               require 'templates/show.php';
          }

          As a front controller, index.php has taken on an entirely new role, one that includes loading the
          core libraries and routing the application so that one of the two controllers (the list_action() and
          show_action() functions) is called. In reality, the front controller is beginning to look and act a lot like
          Symfony2's mechanism for handling and routing requests.


                        Another advantage of a front controller is flexible URLs. Notice that the URL to the blog post show
                        page could be changed from /show to /read by changing code in only one location. Before, an
                        entire file needed to be renamed. In Symfony2, URLs are even more flexible.

          By now, the application has evolved from a single PHP file into a structure that is organized and allows
          for code reuse. You should be happier, but far from satisfied. For example, the "routing" system is
          fickle, and wouldn't recognize that the list page (/index.php) should be accessible also via / (if Apache
          rewrite rules were added). Also, instead of developing the blog, a lot of time is being spent working on
          the "architecture" of the code (e.g. routing, calling controllers, templates, etc.). More time will need to
          be spent to handle form submissions, input validation, logging and security. Why should you have to
          reinvent solutions to all these routine problems?

          Add a Touch of Symfony2
          Symfony2 to the rescue. Before actually using Symfony2, you need to make sure PHP knows how to find
          the Symfony2 classes. This is accomplished via an autoloader that Symfony provides. An autoloader is a
          tool that makes it possible to start using PHP classes without explicitly including the file containing the
          class.
          First, download symfony2 and place it into a vendor/symfony/ directory. Next, create an app/
          bootstrap.php file. Use it to require the two files in the application and to configure the autoloader:

Listing   <?php
 6-14
          // bootstrap.php
          require_once 'model.php';
          require_once 'controllers.php';
          require_once 'vendor/symfony/src/Symfony/Component/ClassLoader/UniversalClassLoader.php';

          $loader = new SymfonyComponentClassLoaderUniversalClassLoader();
          $loader->registerNamespaces(array(
              'Symfony' => __DIR__.'/../vendor/symfony/src',
          ));

          $loader->register();

          This tells the autoloader where the Symfony classes are. With this, you can start using Symfony classes
          without using the require statement for the files that contain them.
          Core to Symfony's philosophy is the idea that an application's main job is to interpret each request and
          return a response. To this end, Symfony2 provides both a Request3 and a Response4 class. These classes
          are object-oriented representations of the raw HTTP request being processed and the HTTP response
          being returned. Use them to improve the blog:



          2. http://symfony.com/download
          3. http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/Request.html
          4. http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/Response.html


          PDF brought to you by                                                          Chapter 6: Symfony2 versus Flat PHP | 50
          generated on June 20, 2012
<?php                                                                                                           Listing
                                                                                                                 6-15
// index.php
require_once 'app/bootstrap.php';

use SymfonyComponentHttpFoundationRequest;
use SymfonyComponentHttpFoundationResponse;

$request = Request::createFromGlobals();

$uri = $request->getPathInfo();
if ($uri == '/') {
    $response = list_action();
} elseif ($uri == '/show' && $request->query->has('id')) {
    $response = show_action($request->query->get('id'));
} else {
    $html = '<html><body><h1>Page Not Found</h1></body></html>';
    $response = new Response($html, 404);
}

// echo the headers and send the response
$response->send();

The controllers are now responsible for returning a Response object. To make this easier, you can add
a new render_template() function, which, incidentally, acts quite a bit like the Symfony2 templating
engine:

// controllers.php                                                                                              Listing
                                                                                                                 6-16
use SymfonyComponentHttpFoundationResponse;

function list_action()
{
    $posts = get_all_posts();
    $html = render_template('templates/list.php', array('posts' => $posts));

     return new Response($html);
}

function show_action($id)
{
    $post = get_post_by_id($id);
    $html = render_template('templates/show.php', array('post' => $post));

     return new Response($html);
}

// helper function to render templates
function render_template($path, array $args)
{
    extract($args);
    ob_start();
    require $path;
    $html = ob_get_clean();

     return $html;
}

By bringing in a small part of Symfony2, the application is more flexible and reliable. The Request
provides a dependable way to access information about the HTTP request. Specifically, the
getPathInfo() method returns a cleaned URI (always returning /show and never /index.php/show).



PDF brought to you by                                                Chapter 6: Symfony2 versus Flat PHP | 51
generated on June 20, 2012
So, even if the user goes to /index.php/show, the application is intelligent enough to route the request
          through show_action().
          The Response object gives flexibility when constructing the HTTP response, allowing HTTP headers
          and content to be added via an object-oriented interface. And while the responses in this application are
          simple, this flexibility will pay dividends as your application grows.

          The Sample Application in Symfony2
          The blog has come a long way, but it still contains a lot of code for such a simple application. Along the
          way, we've also invented a simple routing system and a method using ob_start() and ob_get_clean()
          to render templates. If, for some reason, you needed to continue building this "framework" from scratch,
          you could at least use Symfony's standalone Routing5 and Templating6 components, which already solve
          these problems.
          Instead of re-solving common problems, you can let Symfony2 take care of them for you. Here's the same
          sample application, now built in Symfony2:

Listing   <?php
 6-17
          // src/Acme/BlogBundle/Controller/BlogController.php

          namespace AcmeBlogBundleController;
          use SymfonyBundleFrameworkBundleControllerController;

          class BlogController extends Controller
          {
              public function listAction()
              {
                  $posts = $this->get('doctrine')->getEntityManager()
                      ->createQuery('SELECT p FROM AcmeBlogBundle:Post p')
                      ->execute();

                     return $this->render('AcmeBlogBundle:Blog:list.html.php', array('posts' => $posts));
               }

               public function showAction($id)
               {
                   $post = $this->get('doctrine')
                       ->getEntityManager()
                       ->getRepository('AcmeBlogBundle:Post')
                       ->find($id);

                     if (!$post) {
                         // cause the 404 page not found to be displayed
                         throw $this->createNotFoundException();
                     }

                     return $this->render('AcmeBlogBundle:Blog:show.html.php', array('post' => $post));
               }
          }

          The two controllers are still lightweight. Each uses the Doctrine ORM library to retrieve objects from the
          database and the Templating component to render a template and return a Response object. The list
          template is now quite a bit simpler:

Listing   <!-- src/Acme/BlogBundle/Resources/views/Blog/list.html.php -->
 6-18
          <?php $view->extend('::layout.html.php') ?>


          5. https://github.com/symfony/Routing
          6. https://github.com/symfony/Templating


          PDF brought to you by                                                    Chapter 6: Symfony2 versus Flat PHP | 52
          generated on June 20, 2012
<?php $view['slots']->set('title', 'List of Posts') ?>

<h1>List of Posts</h1>
<ul>
     <?php foreach ($posts as $post): ?>
     <li>
          <a href="<?php echo $view['router']->generate('blog_show', array('id' =>
$post->getId())) ?>">
               <?php echo $post->getTitle() ?>
          </a>
     </li>
     <?php endforeach; ?>
</ul>

The layout is nearly identical:

<!-- app/Resources/views/layout.html.php -->                                                                            Listing
                                                                                                                         6-19
<html>
    <head>
        <title><?php echo $view['slots']->output('title', 'Default title') ?></title>
    </head>
    <body>
        <?php echo $view['slots']->output('_content') ?>
    </body>
</html>



              We'll leave the show template as an exercise, as it should be trivial to create based on the list
              template.


When Symfony2's engine (called the Kernel) boots up, it needs a map so that it knows which controllers
to execute based on the request information. A routing configuration map provides this information in a
readable format:

# app/config/routing.yml                                                                                                Listing
                                                                                                                         6-20
blog_list:
    pattern: /blog
    defaults: { _controller: AcmeBlogBundle:Blog:list }

blog_show:
    pattern: /blog/show/{id}
    defaults: { _controller: AcmeBlogBundle:Blog:show }

Now that Symfony2 is handling all the mundane tasks, the front controller is dead simple. And since it
does so little, you'll never have to touch it once it's created (and if you use a Symfony2 distribution, you
won't even need to create it!):

<?php                                                                                                                   Listing
                                                                                                                         6-21
// web/app.php
require_once __DIR__.'/../app/bootstrap.php';
require_once __DIR__.'/../app/AppKernel.php';

use SymfonyComponentHttpFoundationRequest;

$kernel = new AppKernel('prod', false);
$kernel->handle(Request::createFromGlobals())->send();




PDF brought to you by                                                        Chapter 6: Symfony2 versus Flat PHP | 53
generated on June 20, 2012
The front controller's only job is to initialize Symfony2's engine (Kernel) and pass it a Request object to
          handle. Symfony2's core then uses the routing map to determine which controller to call. Just like before,
          the controller method is responsible for returning the final Response object. There's really not much else
          to it.
          For a visual representation of how Symfony2 handles each request, see the request flow diagram.

          Where Symfony2 Delivers
          In the upcoming chapters, you'll learn more about how each piece of Symfony works and the
          recommended organization of a project. For now, let's see how migrating the blog from flat PHP to
          Symfony2 has improved life:

            • Your application now has clear and consistently organized code (though Symfony doesn't
              force you into this). This promotes reusability and allows for new developers to be productive
              in your project more quickly.
            • 100% of the code you write is for your application. You don't need to develop or maintain
              low-level utilities such as autoloading, routing, or rendering controllers.
            • Symfony2 gives you access to open source tools such as Doctrine and the Templating,
              Security, Form, Validation and Translation components (to name a few).
            • The application now enjoys fully-flexible URLs thanks to the Routing component.
            • Symfony2's HTTP-centric architecture gives you access to powerful tools such as HTTP
              caching powered by Symfony2's internal HTTP cache or more powerful tools such as
              Varnish7. This is covered in a later chapter all about caching.

          And perhaps best of all, by using Symfony2, you now have access to a whole set of high-quality open
          source tools developed by the Symfony2 community! A good selection of Symfony2 community tools
          can be found on KnpBundles.com8.



          Better templates
          If you choose to use it, Symfony2 comes standard with a templating engine called Twig9 that makes
          templates faster to write and easier to read. It means that the sample application could contain even less
          code! Take, for example, the list template written in Twig:

Listing   {# src/Acme/BlogBundle/Resources/views/Blog/list.html.twig #}
 6-22

          {% extends "::layout.html.twig" %}
          {% block title %}List of Posts{% endblock %}

          {% block body %}
              <h1>List of Posts</h1>
              <ul>
                   {% for post in posts %}
                   <li>
                        <a href="{{ path('blog_show', { 'id': post.id }) }}">
                             {{ post.title }}
                        </a>
                   </li>
                   {% endfor %}
              </ul>
          {% endblock %}



          7. http://www.varnish-cache.org
          8. http://knpbundles.com/
          9. http://twig.sensiolabs.org


          PDF brought to you by                                                     Chapter 6: Symfony2 versus Flat PHP | 54
          generated on June 20, 2012
The corresponding layout.html.twig template is also easier to write:

{# app/Resources/views/layout.html.twig #}                                                                        Listing
                                                                                                                   6-23

<html>
    <head>
        <title>{% block title %}Default title{% endblock %}</title>
    </head>
    <body>
        {% block body %}{% endblock %}
    </body>
</html>

Twig is well-supported in Symfony2. And while PHP templates will always be supported in Symfony2,
we'll continue to discuss the many advantages of Twig. For more information, see the templating chapter.



Learn more from the Cookbook
  • How to use PHP instead of Twig for Templates
  • How to define Controllers as Services




PDF brought to you by                                                  Chapter 6: Symfony2 versus Flat PHP | 55
generated on June 20, 2012
Chapter 7
                Installing and Configuring Symfony

The goal of this chapter is to get you up and running with a working application built on top of Symfony.
Fortunately, Symfony offers "distributions", which are functional Symfony "starter" projects that you can
download and begin developing in immediately.


              If you're looking for instructions on how best to create a new project and store it via source control,
              see Using Source Control.




Downloading a Symfony2 Distribution

              First, check that you have installed and configured a Web server (such as Apache) with PHP
              5.3.2 or higher. For more information on Symfony2 requirements, see the requirements reference.
              For information on configuring your specific web server document root, see the following
              documentation: Apache1 | Nginx2 .

Symfony2 packages "distributions", which are fully-functional applications that include the Symfony2
core libraries, a selection of useful bundles, a sensible directory structure and some default configuration.
When you download a Symfony2 distribution, you're downloading a functional application skeleton that
can be used immediately to begin developing your application.
Start by visiting the Symfony2 download page at http://symfony.com/download3. On this page, you'll see
the Symfony Standard Edition, which is the main Symfony2 distribution. Here, you'll need to make two
choices:

  • Download either a .tgz or .zip archive - both are equivalent, download whatever you're more
    comfortable using;



1. http://httpd.apache.org/docs/current/mod/core.html#documentroot
2. http://wiki.nginx.org/Symfony
3. http://symfony.com/download


PDF brought to you by                                                    Chapter 7: Installing and Configuring Symfony | 56
generated on June 20, 2012
• Download the distribution with or without vendors. If you have Git4 installed on your
    computer, you should download Symfony2 "without vendors", as it adds a bit more flexibility
    when including third-party/vendor libraries.

Download one of the archives somewhere under your local web server's root directory and unpack it.
From a UNIX command line, this can be done with one of the following commands (replacing ### with
your actual filename):

# for .tgz file                                                                                                       Listing
                                                                                                                       7-1
tar zxvf Symfony_Standard_Vendors_2.0.###.tgz

# for a .zip file
unzip Symfony_Standard_Vendors_2.0.###.zip

When you're finished, you should have a Symfony/ directory that looks something like this:

www/ <- your web root directory                                                                                       Listing
                                                                                                                       7-2
    Symfony/ <- the unpacked archive
        app/
             cache/
             config/
             logs/
        src/
             ...
        vendor/
             ...
        web/
             app.php
             ...


Updating Vendors
Finally, if you downloaded the archive "without vendors", install the vendors by running the following
command from the command line:

php bin/vendors install                                                                                               Listing
                                                                                                                       7-3

This command downloads all of the necessary vendor libraries - including Symfony itself - into the
vendor/ directory. For more information on how third-party vendor libraries are managed inside
Symfony2, see "Managing Vendor Libraries with bin/vendors and deps".

Configuration and Setup
At this point, all of the needed third-party libraries now live in the vendor/ directory. You also have a
default application setup in app/ and some sample code inside the src/ directory.
Symfony2 comes with a visual server configuration tester to help make sure your Web server and PHP
are configured to use Symfony. Use the following URL to check your configuration:

http://localhost/Symfony/web/config.php                                                                               Listing
                                                                                                                       7-4

If there are any issues, correct them now before moving on.




4. http://git-scm.com/


PDF brought to you by                                            Chapter 7: Installing and Configuring Symfony | 57
generated on June 20, 2012
Setting up Permissions
                           One common issue is that the app/cache and app/logs directories must be writable both by the
                           web server and the command line user. On a UNIX system, if your web server user is different from
                           your command line user, you can run the following commands just once in your project to ensure
                           that permissions will be setup properly. Change www-data to your web server user:
                           1. Using ACL on a system that supports chmod +a
                           Many systems allow you to use the chmod +a command. Try this first, and if you get an error - try
                           the next method:

                 Listing   rm -rf app/cache/*
                  7-5
                           rm -rf app/logs/*

                           sudo chmod +a "www-data allow delete,write,append,file_inherit,directory_inherit" app/
                           cache app/logs
                           sudo chmod +a "`whoami` allow delete,write,append,file_inherit,directory_inherit" app/
                           cache app/logs

                           2. Using Acl on a system that does not support chmod +a
                           Some systems don't support chmod +a, but do support another utility called setfacl. You may
                           need to enable ACL support5 on your partition and install setfacl before using it (as is the case with
                           Ubuntu), like so:

                 Listing   sudo setfacl -R -m u:www-data:rwx -m u:`whoami`:rwx app/cache app/logs
                  7-6
                           sudo setfacl -dR -m u:www-data:rwx -m u:`whoami`:rwx app/cache app/logs

                           Note that not all web servers run as the user www-data. You have to check which user the web
                           server is being run as and put it in for www-data. This can be done by checking your process list to
                           see which user is running your web server processes.
                           3. Without using ACL
                           If you don't have access to changing the ACL of the directories, you will need to change the umask
                           so that the cache and log directories will be group-writable or world-writable (depending if the
                           web server user and the command line user are in the same group or not). To achieve this, put the
                           following line at the beginning of the app/console, web/app.php and web/app_dev.php files:

                 Listing   umask(0002); // This will let the permissions be 0775
                  7-7

                           // or

                           umask(0000); // This will let the permissions be 0777

                           Note that using the ACL is recommended when you have access to them on your server because
                           changing the umask is not thread-safe.

          When everything is fine, click on "Go to the Welcome page" to request your first "real" Symfony2
          webpage:

Listing   http://localhost/Symfony/web/app_dev.php/
 7-8

          Symfony2 should welcome and congratulate you for your hard work so far!




          5. https://help.ubuntu.com/community/FilePermissionsACLs


          PDF brought to you by                                                       Chapter 7: Installing and Configuring Symfony | 58
          generated on June 20, 2012
Beginning Development
Now that you have a fully-functional Symfony2 application, you can begin development! Your
distribution may contain some sample code - check the README.rst file included with the distribution
(open it as a text file) to learn about what sample code was included with your distribution and how you
can remove it later.
If you're new to Symfony, join us in the "Creating Pages in Symfony2", where you'll learn how to create
pages, change configuration, and do everything else you'll need in your new application.



Using Source Control
If you're using a version control system like Git or Subversion, you can setup your version control
system and begin committing your project to it as normal. The Symfony Standard edition is the starting
point for your new project.
For specific instructions on how best to setup your project to be stored in git, see How to Create and store
a Symfony2 Project in git.

Ignoring the vendor/ Directory
If you've downloaded the archive without vendors, you can safely ignore the entire vendor/ directory
and not commit it to source control. With Git, this is done by creating and adding the following to a
.gitignore file:

vendor/                                                                                                                 Listing
                                                                                                                         7-9

Now, the vendor directory won't be committed to source control. This is fine (actually, it's great!) because
when someone else clones or checks out the project, he/she can simply run the php bin/vendors
install script to download all the necessary vendor libraries.




PDF brought to you by                                              Chapter 7: Installing and Configuring Symfony | 59
generated on June 20, 2012
Chapter 8
                                       Creating Pages in Symfony2

          Creating a new page in Symfony2 is a simple two-step process:

            • Create a route: A route defines the URL (e.g. /about) to your page and specifies a controller
              (which is a PHP function) that Symfony2 should execute when the URL of an incoming request
              matches the route pattern;
            • Create a controller: A controller is a PHP function that takes the incoming request and
              transforms it into the Symfony2 Response object that's returned to the user.

          This simple approach is beautiful because it matches the way that the Web works. Every interaction on
          the Web is initiated by an HTTP request. The job of your application is simply to interpret the request
          and return the appropriate HTTP response.
          Symfony2 follows this philosophy and provides you with tools and conventions to keep your application
          organized as it grows in users and complexity.
          Sounds simple enough? Let's dive in!



          The "Hello Symfony!" Page
          Let's start with a spin off of the classic "Hello World!" application. When you're finished, the user will be
          able to get a personal greeting (e.g. "Hello Symfony") by going to the following URL:

Listing   http://localhost/app_dev.php/hello/Symfony
 8-1

          Actually, you'll be able to replace Symfony with any other name to be greeted. To create the page, follow
          the simple two-step process.


                        The tutorial assumes that you've already downloaded Symfony2 and configured your webserver.
                        The above URL assumes that localhost points to the web directory of your new Symfony2 project.
                        For detailed information on this process, see the documentation on the web server you are using.
                        Here's the relevant documentation page for some web server you might be using:

                           • For Apache HTTP Server, refer to Apache's DirectoryIndex documentation1.



          PDF brought to you by                                                      Chapter 8: Creating Pages in Symfony2 | 60
          generated on June 20, 2012
• For Nginx, refer to Nginx HttpCoreModule location documentation2.




Before you begin: Create the Bundle
Before you begin, you'll need to create a bundle. In Symfony2, a bundle is like a plugin, except that all of
the code in your application will live inside a bundle.
A bundle is nothing more than a directory that houses everything related to a specific feature, including
PHP classes, configuration, and even stylesheets and Javascript files (see The Bundle System).
To create a bundle called AcmeHelloBundle (a play bundle that you'll build in this chapter), run the
following command and follow the on-screen instructions (use all of the default options):

php app/console generate:bundle --namespace=Acme/HelloBundle --format=yml                                              Listing
                                                                                                                        8-2

Behind the scenes, a directory is created for the bundle at src/Acme/HelloBundle. A line is also
automatically added to the app/AppKernel.php file so that the bundle is registered with the kernel:

// app/AppKernel.php                                                                                                   Listing
                                                                                                                        8-3
public function registerBundles()
{
    $bundles = array(
        // ...
        new AcmeHelloBundleAcmeHelloBundle(),
    );
    // ...

     return $bundles;
}

Now that you have a bundle setup, you can begin building your application inside the bundle.

Step 1: Create the Route
By default, the routing configuration file in a Symfony2 application is located at app/config/
routing.yml. Like all configuration in Symfony2, you can also choose to use XML or PHP out of the box
to configure routes.
If you look at the main routing file, you'll see that Symfony already added an entry when you generated
the AcmeHelloBundle:

# app/config/routing.yml                                                                                               Listing
                                                                                                                        8-4
AcmeHelloBundle:
    resource: "@AcmeHelloBundle/Resources/config/routing.yml"
    prefix: /

This entry is pretty basic: it tells Symfony to load routing configuration from the Resources/config/
routing.yml file that lives inside the AcmeHelloBundle. This means that you place routing configuration
directly in app/config/routing.yml or organize your routes throughout your application, and import
them from here.
Now that the routing.yml file from the bundle is being imported, add the new route that defines the
URL of the page that you're about to create:


1. http://httpd.apache.org/docs/2.0/mod/mod_dir.html
2. http://wiki.nginx.org/HttpCoreModule#location


PDF brought to you by                                                     Chapter 8: Creating Pages in Symfony2 | 61
generated on June 20, 2012
Listing   # src/Acme/HelloBundle/Resources/config/routing.yml
 8-5
          hello:
              pattern: /hello/{name}
              defaults: { _controller: AcmeHelloBundle:Hello:index }

          The routing consists of two basic pieces: the pattern, which is the URL that this route will match, and
          a defaults array, which specifies the controller that should be executed. The placeholder syntax in the
          pattern ({name}) is a wildcard. It means that /hello/Ryan, /hello/Fabien or any other similar URL will
          match this route. The {name} placeholder parameter will also be passed to the controller so that you can
          use its value to personally greet the user.


                        The routing system has many more great features for creating flexible and powerful URL structures
                        in your application. For more details, see the chapter all about Routing.



          Step 2: Create the Controller
          When a URL such as /hello/Ryan is handled by the application, the hello route is matched and the
          AcmeHelloBundle:Hello:index controller is executed by the framework. The second step of the page-
          creation process is to create that controller.
          The controller - AcmeHelloBundle:Hello:index is the logical name of the controller, and it maps to the
          indexAction method of a PHP class called AcmeHelloBundleControllerHello. Start by creating this
          file inside your AcmeHelloBundle:

Listing   // src/Acme/HelloBundle/Controller/HelloController.php
 8-6
          namespace AcmeHelloBundleController;

          use SymfonyComponentHttpFoundationResponse;

          class HelloController
          {
          }

          In reality, the controller is nothing more than a PHP method that you create and Symfony executes. This
          is where your code uses information from the request to build and prepare the resource being requested.
          Except in some advanced cases, the end product of a controller is always the same: a Symfony2 Response
          object.
          Create the indexAction method that Symfony will execute when the hello route is matched:

Listing   // src/Acme/HelloBundle/Controller/HelloController.php
 8-7

          // ...
          class HelloController
          {
              public function indexAction($name)
              {
                  return new Response('<html><body>Hello '.$name.'!</body></html>');
              }
          }

          The controller is simple: it creates a new Response object, whose first argument is the content that should
          be used in the response (a small HTML page in this example).
          Congratulations! After creating only a route and a controller, you already have a fully-functional page! If
          you've setup everything correctly, your application should greet you:



          PDF brought to you by                                                       Chapter 8: Creating Pages in Symfony2 | 62
          generated on June 20, 2012
http://localhost/app_dev.php/hello/Ryan                                                                                                 Listing
                                                                                                                                         8-8




              You can also view your app in the "prod" environment by visiting:

              http://localhost/app.php/hello/Ryan                                                                                       Listing
                                                                                                                                         8-9

              If you get an error, it's likely because you need to clear your cache by running:

              php app/console cache:clear --env=prod --no-debug                                                                         Listing
                                                                                                                                         8-10




An optional, but common, third step in the process is to create a template.


              Controllers are the main entry point for your code and a key ingredient when creating pages. Much
              more information can be found in the Controller Chapter.



Optional Step 3: Create the Template
Templates allows you to move all of the presentation (e.g. HTML code) into a separate file and reuse
different portions of the page layout. Instead of writing the HTML inside the controller, render a template
instead:


  1   // src/Acme/HelloBundle/Controller/HelloController.php
      Listing                                                                                                                          Listing
       8-11                                                                                                                             8-12
  2   namespace AcmeHelloBundleController;
  3
  4   use SymfonyBundleFrameworkBundleControllerController;
  5
  6   class HelloController extends Controller
  7   {
  8       public function indexAction($name)
  9       {
 10            return $this->render('AcmeHelloBundle:Hello:index.html.twig', array('name' =>
 11   $name));
 12
 13              // render a PHP template instead
 14              // return $this->render('AcmeHelloBundle:Hello:index.html.php', array('name' =>
 15   $name));
          }
      }




              In order to use the render() method, your controller must extend the
              SymfonyBundleFrameworkBundleControllerController class (API docs: Controller3),
              which adds shortcuts for tasks that are common inside controllers. This is done in the above
              example by adding the use statement on line 4 and then extending Controller on line 6.

The render() method creates a Response object filled with the content of the given, rendered template.
Like any other controller, you will ultimately return that Response object.




3. http://api.symfony.com/2.0/Symfony/Bundle/FrameworkBundle/Controller/Controller.html


PDF brought to you by                                                                     Chapter 8: Creating Pages in Symfony2 | 63
generated on June 20, 2012
Notice that there are two different examples for rendering the template. By default, Symfony2 supports
          two different templating languages: classic PHP templates and the succinct but powerful Twig4 templates.
          Don't be alarmed - you're free to choose either or even both in the same project.
          The controller renders the AcmeHelloBundle:Hello:index.html.twig template, which uses the
          following naming convention:

                    BundleName:ControllerName:TemplateName

          This is the logical name of the template, which is mapped to a physical location using the following
          convention.

                    /path/to/BundleName/Resources/views/ControllerName/TemplateName

          In this case, AcmeHelloBundle is the bundle name, Hello is the controller, and index.html.twig the
          template:


 Listing 1
       Listing     {# src/Acme/HelloBundle/Resources/views/Hello/index.html.twig #}
  8-13 8-14
           2       {% extends '::base.html.twig' %}
           3
           4       {% block body %}
           5           Hello {{ name }}!
           6       {% endblock %}



          Let's step through the Twig template line-by-line:

                 • line 2: The extends token defines a parent template. The template explicitly defines a layout
                   file inside of which it will be placed.
                 • line 4: The block token says that everything inside should be placed inside a block called body.
                   As you'll see, it's the responsibility of the parent template (base.html.twig) to ultimately
                   render the block called body.

          The parent template, ::base.html.twig, is missing both the BundleName and ControllerName
          portions of its name (hence the double colon (::) at the beginning). This means that the template lives
          outside of the bundles and in the app directory:

Listing   {# app/Resources/views/base.html.twig #}
 8-15
          <!DOCTYPE html>
          <html>
              <head>
                  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
                  <title>{% block title %}Welcome!{% endblock %}</title>
                  {% block stylesheets %}{% endblock %}
                  <link rel="shortcut icon" href="{{ asset('favicon.ico') }}" />
              </head>
              <body>
                  {% block body %}{% endblock %}
                  {% block javascripts %}{% endblock %}
              </body>
          </html>

          The base template file defines the HTML layout and renders the body block that you defined in the
          index.html.twig template. It also renders a title block, which you could choose to define in the
          index.html.twig template. Since you did not define the title block in the child template, it defaults to
          "Welcome!".

          4. http://twig.sensiolabs.org


          PDF brought to you by                                                       Chapter 8: Creating Pages in Symfony2 | 64
          generated on June 20, 2012
Templates are a powerful way to render and organize the content for your page. A template can render
anything, from HTML markup, to CSS code, or anything else that the controller may need to return.
In the lifecycle of handling a request, the templating engine is simply an optional tool. Recall that the
goal of each controller is to return a Response object. Templates are a powerful, but optional, tool for
creating the content for that Response object.



The Directory Structure
After just a few short sections, you already understand the philosophy behind creating and rendering
pages in Symfony2. You've also already begun to see how Symfony2 projects are structured and
organized. By the end of this section, you'll know where to find and put different types of files and why.
Though entirely flexible, by default, each Symfony application has the same basic and recommended
directory structure:

  •   app/: This directory contains the application configuration;
  •   src/: All the project PHP code is stored under this directory;
  •   vendor/: Any vendor libraries are placed here by convention;
  •   web/: This is the web root directory and contains any publicly accessible files;


The Web Directory
The web root directory is the home of all public and static files including images, stylesheets, and
JavaScript files. It is also where each front controller lives:

// web/app.php                                                                                                          Listing
                                                                                                                         8-16
require_once __DIR__.'/../app/bootstrap.php.cache';
require_once __DIR__.'/../app/AppKernel.php';

use SymfonyComponentHttpFoundationRequest;

$kernel = new AppKernel('prod', false);
$kernel->loadClassCache();
$kernel->handle(Request::createFromGlobals())->send();

The front controller file (app.php in this example) is the actual PHP file that's executed when using a
Symfony2 application and its job is to use a Kernel class, AppKernel, to bootstrap the application.


              Having a front controller means different and more flexible URLs than are used in a typical flat
              PHP application. When using a front controller, URLs are formatted in the following way:

              http://localhost/app.php/hello/Ryan                                                                       Listing
                                                                                                                         8-17

              The front controller, app.php, is executed and the "internal:" URL /hello/Ryan is routed
              internally using the routing configuration. By using Apache mod_rewrite rules, you can force the
              app.php file to be executed without needing to specify it in the URL:

              http://localhost/hello/Ryan                                                                               Listing
                                                                                                                         8-18




Though front controllers are essential in handling every request, you'll rarely need to modify or even think
about them. We'll mention them again briefly in the Environments section.




PDF brought to you by                                                      Chapter 8: Creating Pages in Symfony2 | 65
generated on June 20, 2012
The Application (app) Directory
As you saw in the front controller, the AppKernel class is the main entry point of the application and is
responsible for all configuration. As such, it is stored in the app/ directory.
This class must implement two methods that define everything that Symfony needs to know about your
application. You don't even need to worry about these methods when starting - Symfony fills them in for
you with sensible defaults.

  • registerBundles(): Returns an array of all bundles needed to run the application (see The
    Bundle System);
  • registerContainerConfiguration(): Loads the main application configuration resource file
    (see the Application Configuration section).

In day-to-day development, you'll mostly use the app/ directory to modify configuration and routing
files in the app/config/ directory (see Application Configuration). It also contains the application cache
directory (app/cache), a log directory (app/logs) and a directory for application-level resource files, such
as templates (app/Resources). You'll learn more about each of these directories in later chapters.


                Autoloading
                When Symfony is loading, a special file - app/autoload.php - is included. This file is responsible
                for configuring the autoloader, which will autoload your application files from the src/ directory
                and third-party libraries from the vendor/ directory.
                Because of the autoloader, you never need to worry about using include or require statements.
                Instead, Symfony2 uses the namespace of a class to determine its location and automatically
                includes the file on your behalf the instant you need a class.
                The autoloader is already configured to look in the src/ directory for any of your PHP classes. For
                autoloading to work, the class name and path to the file have to follow the same pattern:

      Listing   Class Name:
       8-19
                    AcmeHelloBundleControllerHelloController
                Path:
                    src/Acme/HelloBundle/Controller/HelloController.php

                Typically, the only time you'll need to worry about the app/autoload.php file is when you're
                including a new third-party library in the vendor/ directory. For more information on autoloading,
                see How to autoload Classes.


The Source (src) Directory
Put simply, the src/ directory contains all of the actual code (PHP code, templates, configuration files,
stylesheets, etc) that drives your application. When developing, the vast majority of your work will be
done inside one or more bundles that you create in this directory.
But what exactly is a bundle?



The Bundle System
A bundle is similar to a plugin in other software, but even better. The key difference is that everything
is a bundle in Symfony2, including both the core framework functionality and the code written for your
application. Bundles are first-class citizens in Symfony2. This gives you the flexibility to use pre-built




PDF brought to you by                                                          Chapter 8: Creating Pages in Symfony2 | 66
generated on June 20, 2012
features packaged in third-party bundles5 or to distribute your own bundles. It makes it easy to pick and
choose which features to enable in your application and to optimize them the way you want.


              While you'll learn the basics here, an entire cookbook entry is devoted to the organization and best
              practices of bundles.


A bundle is simply a structured set of files within a directory that implement a single feature. You might
create a BlogBundle, a ForumBundle or a bundle for user management (many of these exist already as
open source bundles). Each directory contains everything related to that feature, including PHP files,
templates, stylesheets, JavaScripts, tests and anything else. Every aspect of a feature exists in a bundle
and every feature lives in a bundle.
An application is made up of bundles as defined in the registerBundles() method of the AppKernel
class:

// app/AppKernel.php                                                                                                       Listing
                                                                                                                            8-20
public function registerBundles()
{
    $bundles = array(
        new SymfonyBundleFrameworkBundleFrameworkBundle(),
        new SymfonyBundleSecurityBundleSecurityBundle(),
        new SymfonyBundleTwigBundleTwigBundle(),
        new SymfonyBundleMonologBundleMonologBundle(),
        new SymfonyBundleSwiftmailerBundleSwiftmailerBundle(),
        new SymfonyBundleDoctrineBundleDoctrineBundle(),
        new SymfonyBundleAsseticBundleAsseticBundle(),
        new SensioBundleFrameworkExtraBundleSensioFrameworkExtraBundle(),
        new JMSSecurityExtraBundleJMSSecurityExtraBundle(),
    );

     if (in_array($this->getEnvironment(), array('dev', 'test'))) {
         $bundles[] = new AcmeDemoBundleAcmeDemoBundle();
         $bundles[] = new SymfonyBundleWebProfilerBundleWebProfilerBundle();
         $bundles[] = new SensioBundleDistributionBundleSensioDistributionBundle();
         $bundles[] = new SensioBundleGeneratorBundleSensioGeneratorBundle();
     }

     return $bundles;
}

With the registerBundles() method, you have total control over which bundles are used by your
application (including the core Symfony bundles).


              A bundle can live anywhere as long as it can be autoloaded (via the autoloader configured at app/
              autoload.php).



Creating a Bundle
The Symfony Standard Edition comes with a handy task that creates a fully-functional bundle for you.
Of course, creating a bundle by hand is pretty easy as well.
To show you how simple the bundle system is, create a new bundle called AcmeTestBundle and enable
it.


5. http://symfony2bundles.org/


PDF brought to you by                                                         Chapter 8: Creating Pages in Symfony2 | 67
generated on June 20, 2012
The Acme portion is just a dummy name that should be replaced by some "vendor" name that
                        represents you or your organization (e.g. ABCTestBundle for some company named ABC).


          Start by creating a src/Acme/TestBundle/ directory and adding a new file called AcmeTestBundle.php:

Listing   // src/Acme/TestBundle/AcmeTestBundle.php
 8-21
          namespace AcmeTestBundle;

          use SymfonyComponentHttpKernelBundleBundle;

          class AcmeTestBundle extends Bundle
          {
          }



                        The name AcmeTestBundle follows the standard Bundle naming conventions. You could also
                        choose to shorten the name of the bundle to simply TestBundle by naming this class TestBundle
                        (and naming the file TestBundle.php).

          This empty class is the only piece you need to create the new bundle. Though commonly empty, this
          class is powerful and can be used to customize the behavior of the bundle.
          Now that you've created the bundle, enable it via the AppKernel class:

Listing   // app/AppKernel.php
 8-22
          public function registerBundles()
          {
              $bundles = array(
                  // ...

                     // register your bundles
                     new AcmeTestBundleAcmeTestBundle(),
               );
               // ...

               return $bundles;
          }

          And while it doesn't do anything yet, AcmeTestBundle is now ready to be used.
          And as easy as this is, Symfony also provides a command-line interface for generating a basic bundle
          skeleton:

Listing   php app/console generate:bundle --namespace=Acme/TestBundle
 8-23

          The bundle skeleton generates with a basic controller, template and routing resource that can be
          customized. You'll learn more about Symfony2's command-line tools later.


                        Whenever creating a new bundle or using a third-party bundle, always make sure the bundle has
                        been enabled in registerBundles(). When using the generate:bundle command, this is done
                        for you.




          PDF brought to you by                                                    Chapter 8: Creating Pages in Symfony2 | 68
          generated on June 20, 2012
Bundle Directory Structure
The directory structure of a bundle is simple and flexible. By default, the bundle system follows a
set of conventions that help to keep code consistent between all Symfony2 bundles. Take a look at
AcmeHelloBundle, as it contains some of the most common elements of a bundle:

  • Controller/ contains the controllers of the bundle (e.g. HelloController.php);
  • DependencyInjection/ holds certain dependency injection extension classes, which may
    import service configuration, register compiler passes or more (this directory is not necessary);
  • Resources/config/ houses configuration, including routing configuration (e.g.
    routing.yml);
  • Resources/views/ holds templates organized by controller name (e.g. Hello/
    index.html.twig);
  • Resources/public/ contains web assets (images, stylesheets, etc) and is copied or
    symbolically linked into the project web/ directory via the assets:install console command;
  • Tests/ holds all tests for the bundle.

A bundle can be as small or large as the feature it implements. It contains only the files you need and
nothing else.
As you move through the book, you'll learn how to persist objects to a database, create and validate
forms, create translations for your application, write tests and much more. Each of these has their own
place and role within the bundle.



Application Configuration
An application consists of a collection of bundles representing all of the features and capabilities of your
application. Each bundle can be customized via configuration files written in YAML, XML or PHP. By
default, the main configuration file lives in the app/config/ directory and is called either config.yml,
config.xml or config.php depending on which format you prefer:

# app/config/config.yml                                                                                               Listing
                                                                                                                       8-24
imports:
    - { resource: parameters.ini }
    - { resource: security.yml }

framework:
    secret:          "%secret%"
    charset:         UTF-8
    router:          { resource: "%kernel.root_dir%/config/routing.yml" }
    form:            true
    csrf_protection: true
    validation:      { enable_annotations: true }
    templating:      { engines: ['twig'] } #assets_version: SomeVersionScheme
    session:
        default_locale: "%locale%"
        auto_start:     true

# Twig Configuration
twig:
    debug:            "%kernel.debug%"
    strict_variables: "%kernel.debug%"

# ...




PDF brought to you by                                                    Chapter 8: Creating Pages in Symfony2 | 69
generated on June 20, 2012
You'll learn exactly how to load each file/format in the next section Environments.



          Each top-level entry like framework or twig defines the configuration for a particular bundle. For
          example, the framework key defines the configuration for the core Symfony FrameworkBundle and
          includes configuration for the routing, templating, and other core systems.
          For now, don't worry about the specific configuration options in each section. The configuration file
          ships with sensible defaults. As you read more and explore each part of Symfony2, you'll learn about the
          specific configuration options of each feature.


                        Configuration Formats
                        Throughout the chapters, all configuration examples will be shown in all three formats (YAML,
                        XML and PHP). Each has its own advantages and disadvantages. The choice of which to use is up
                        to you:

                           • YAML: Simple, clean and readable;
                           • XML: More powerful than YAML at times and supports IDE autocompletion;
                           • PHP: Very powerful but less readable than standard configuration formats.




          Environments
          An application can run in various environments. The different environments share the same PHP code
          (apart from the front controller), but use different configuration. For instance, a dev environment will
          log warnings and errors, while a prod environment will only log errors. Some files are rebuilt on each
          request in the dev environment (for the developer's convenience), but cached in the prod environment.
          All environments live together on the same machine and execute the same application.
          A Symfony2 project generally begins with three environments (dev, test and prod), though creating new
          environments is easy. You can view your application in different environments simply by changing the
          front controller in your browser. To see the application in the dev environment, access the application
          via the development front controller:

Listing   http://localhost/app_dev.php/hello/Ryan
 8-25

          If you'd like to see how your application will behave in the production environment, call the prod front
          controller instead:

Listing   http://localhost/app.php/hello/Ryan
 8-26

          Since the prod environment is optimized for speed; the configuration, routing and Twig templates are
          compiled into flat PHP classes and cached. When viewing changes in the prod environment, you'll need
          to clear these cached files and allow them to rebuild:

Listing   php app/console cache:clear --env=prod --no-debug
 8-27




                        If you open the web/app.php file, you'll find that it's configured explicitly to use the prod
                        environment:




          PDF brought to you by                                                        Chapter 8: Creating Pages in Symfony2 | 70
          generated on June 20, 2012
$kernel = new AppKernel('prod', false);                                                                    Listing
                                                                                                                          8-28

              You can create a new front controller for a new environment by copying this file and changing prod
              to some other value.



              The test environment is used when running automated tests and cannot be accessed directly
              through the browser. See the testing chapter for more details.



Environment Configuration
The AppKernel class is responsible for actually loading the configuration file of your choice:

// app/AppKernel.php                                                                                                     Listing
                                                                                                                          8-29
public function registerContainerConfiguration(LoaderInterface $loader)
{
    $loader->load(__DIR__.'/config/config_'.$this->getEnvironment().'.yml');
}

You already know that the .yml extension can be changed to .xml or .php if you prefer to use either
XML or PHP to write your configuration. Notice also that each environment loads its own configuration
file. Consider the configuration file for the dev environment.

# app/config/config_dev.yml                                                                                              Listing
                                                                                                                          8-30
imports:
    - { resource: config.yml }

framework:
    router: { resource: "%kernel.root_dir%/config/routing_dev.yml" }
    profiler: { only_exceptions: false }

# ...

The imports key is similar to a PHP include statement and guarantees that the main configuration file
(config.yml) is loaded first. The rest of the file tweaks the default configuration for increased logging
and other settings conducive to a development environment.
Both the prod and test environments follow the same model: each environment imports the base
configuration file and then modifies its configuration values to fit the needs of the specific environment.
This is just a convention, but one that allows you to reuse most of your configuration and customize just
pieces of it between environments.



Summary
Congratulations! You've now seen every fundamental aspect of Symfony2 and have hopefully discovered
how easy and flexible it can be. And while there are a lot of features still to come, be sure to keep the
following basic points in mind:

  • creating a page is a three-step process involving a route, a controller and (optionally) a
    template.
  • each project contains just a few main directories: web/ (web assets and the front controllers),
    app/ (configuration), src/ (your bundles), and vendor/ (third-party code) (there's also a bin/
    directory that's used to help updated vendor libraries);


PDF brought to you by                                                       Chapter 8: Creating Pages in Symfony2 | 71
generated on June 20, 2012
• each feature in Symfony2 (including the Symfony2 framework core) is organized into a bundle,
    which is a structured set of files for that feature;
  • the configuration for each bundle lives in the app/config directory and can be specified in
    YAML, XML or PHP;
  • each environment is accessible via a different front controller (e.g. app.php and app_dev.php)
    and loads a different configuration file.

From here, each chapter will introduce you to more and more powerful tools and advanced concepts.
The more you know about Symfony2, the more you'll appreciate the flexibility of its architecture and the
power it gives you to rapidly develop applications.




PDF brought to you by                                                 Chapter 8: Creating Pages in Symfony2 | 72
generated on June 20, 2012
Chapter 9
                                         Controller

A controller is a PHP function you create that takes information from the HTTP request and constructs
and returns an HTTP response (as a Symfony2 Response object). The response could be an HTML page,
an XML document, a serialized JSON array, an image, a redirect, a 404 error or anything else you can
dream up. The controller contains whatever arbitrary logic your application needs to render the content
of a page.
To see how simple this is, let's look at a Symfony2 controller in action. The following controller would
render a page that simply prints Hello world!:

use SymfonyComponentHttpFoundationResponse;                                                                   Listing
                                                                                                                  9-1

public function helloAction()
{
    return new Response('Hello world!');
}

The goal of a controller is always the same: create and return a Response object. Along the way, it might
read information from the request, load a database resource, send an email, or set information on the
user's session. But in all cases, the controller will eventually return the Response object that will be
delivered back to the client.
There's no magic and no other requirements to worry about! Here are a few common examples:

  • Controller A prepares a Response object representing the content for the homepage of the site.
  • Controller B reads the slug parameter from the request to load a blog entry from the database
    and create a Response object displaying that blog. If the slug can't be found in the database,
    it creates and returns a Response object with a 404 status code.
  • Controller C handles the form submission of a contact form. It reads the form information
    from the request, saves the contact information to the database and emails the contact
    information to the webmaster. Finally, it creates a Response object that redirects the client's
    browser to the contact form "thank you" page.




PDF brought to you by                                                               Chapter 9: Controller | 73
generated on June 20, 2012
Requests, Controller, Response Lifecycle
        Every request handled by a Symfony2 project goes through the same simple lifecycle. The framework
        takes care of the repetitive tasks and ultimately executes a controller, which houses your custom
        application code:
             1. Each request is handled by a single front controller file (e.g. app.php or app_dev.php) that
                 bootstraps the application;
             2. The Router reads information from the request (e.g. the URI), finds a route that matches that
                 information, and reads the _controller parameter from the route;
             3. The controller from the matched route is executed and the code inside the controller creates
                 and returns a Response object;
             4. The HTTP headers and content of the Response object are sent back to the client.
        Creating a page is as easy as creating a controller (#3) and making a route that maps a URL to that
        controller (#2).


                       Though similarly named, a "front controller" is different from the "controllers" we'll talk about in
                       this chapter. A front controller is a short PHP file that lives in your web directory and through
                       which all requests are directed. A typical application will have a production front controller (e.g.
                       app.php) and a development front controller (e.g. app_dev.php). You'll likely never need to edit,
                       view or worry about the front controllers in your application.



        A Simple Controller
        While a controller can be any PHP callable (a function, method on an object, or a Closure), in Symfony2,
        a controller is usually a single method inside a controller object. Controllers are also called actions.


              1
Listing Listing   // src/Acme/HelloBundle/Controller/HelloController.php
 9-2 9-3
           2
           3      namespace AcmeHelloBundleController;
           4      use SymfonyComponentHttpFoundationResponse;
           5
           6      class HelloController
           7      {
           8          public function indexAction($name)
           9          {
          10            return new Response('<html><body>Hello '.$name.'!</body></html>');
          11          }
          12      }




                       Note that the controller is the indexAction method, which lives inside a controller class
                       (HelloController). Don't be confused by the naming: a controller class is simply a convenient
                       way to group several controllers/actions together. Typically, the controller class will house several
                       controllers/actions (e.g. updateAction, deleteAction, etc).

        This controller is pretty straightforward, but let's walk through it:

             • line 3: Symfony2 takes advantage of PHP 5.3 namespace functionality to namespace the entire
               controller class. The use keyword imports the Response class, which our controller must
               return.


        PDF brought to you by                                                                         Chapter 9: Controller | 74
        generated on June 20, 2012
• line 6: The class name is the concatenation of a name for the controller class (i.e. Hello)
    and the word Controller. This is a convention that provides consistency to controllers and
    allows them to be referenced only by the first part of the name (i.e. Hello) in the routing
    configuration.
  • line 8: Each action in a controller class is suffixed with Action and is referenced in the routing
    configuration by the action's name (index). In the next section, you'll create a route that maps
    a URI to this action. You'll learn how the route's placeholders ({name}) become arguments to
    the action method ($name).
  • line 10: The controller creates and returns a Response object.



Mapping a URL to a Controller
The new controller returns a simple HTML page. To actually view this page in your browser, you need
to create a route, which maps a specific URL pattern to the controller:

# app/config/routing.yml                                                                                                Listing
                                                                                                                         9-4
hello:
    pattern:      /hello/{name}
    defaults:     { _controller: AcmeHelloBundle:Hello:index }

Going to /hello/ryan now executes the HelloController::indexAction() controller and passes in
ryan for the $name variable. Creating a "page" means simply creating a controller method and associated
route.
Notice the syntax used to refer to the controller: AcmeHelloBundle:Hello:index. Symfony2 uses a
flexible string notation to refer to different controllers. This is the most common syntax and tells
Symfony2 to look for a controller class called HelloController inside a bundle named
AcmeHelloBundle. The method indexAction() is then executed.
For more details on the string format used to reference different controllers, see Controller Naming
Pattern.


              This example places the routing configuration directly in the app/config/ directory. A better way
              to organize your routes is to place each route in the bundle it belongs to. For more information on
              this, see Including External Routing Resources.



              You can learn much more about the routing system in the Routing chapter.




Route Parameters as Controller Arguments
You already know that the _controller parameter AcmeHelloBundle:Hello:index refers to a
HelloController::indexAction() method that lives inside the AcmeHelloBundle bundle. What's more
interesting is the arguments that are passed to that method:

<?php                                                                                                                   Listing
                                                                                                                         9-5
// src/Acme/HelloBundle/Controller/HelloController.php

namespace AcmeHelloBundleController;
use SymfonyBundleFrameworkBundleControllerController;

class HelloController extends Controller
{


PDF brought to you by                                                                      Chapter 9: Controller | 75
generated on June 20, 2012
public function indexAction($name)
               {
                 // ...
               }
          }

          The controller has a single argument, $name, which corresponds to the {name} parameter from the
          matched route (ryan in our example). In fact, when executing your controller, Symfony2 matches each
          argument of the controller with a parameter from the matched route. Take the following example:

Listing   # app/config/routing.yml
 9-6
          hello:
              pattern:      /hello/{first_name}/{last_name}
              defaults:     { _controller: AcmeHelloBundle:Hello:index, color: green }

          The controller for this can take several arguments:

Listing   public function indexAction($first_name, $last_name, $color)
 9-7
          {
              // ...
          }

          Notice that both placeholder variables ({first_name}, {last_name}) as well as the default color
          variable are available as arguments in the controller. When a route is matched, the placeholder variables
          are merged with the defaults to make one array that's available to your controller.
          Mapping route parameters to controller arguments is easy and flexible. Keep the following guidelines in
          mind while you develop.

              • The order of the controller arguments does not matter

                          Symfony is able to match the parameter names from the route to the variable
                          names in the controller method's signature. In other words, it realizes that the
                          {last_name} parameter matches up with the $last_name argument. The arguments
                          of the controller could be totally reordered and still work perfectly:

                Listing   public function indexAction($last_name, $color, $first_name)
                 9-8
                          {
                              // ..
                          }

              • Each required controller argument must match up with a routing parameter

                          The following would throw a RuntimeException because there is no foo parameter
                          defined in the route:

                Listing   public function indexAction($first_name, $last_name, $color, $foo)
                 9-9
                          {
                              // ..
                          }

                          Making the argument optional, however, is perfectly ok. The following example
                          would not throw an exception:

                Listing   public function indexAction($first_name, $last_name, $color, $foo = 'bar')
                 9-10
                          {
                              // ..
                          }

              • Not all routing parameters need to be arguments on your controller

          PDF brought to you by                                                                 Chapter 9: Controller | 76
          generated on June 20, 2012
If, for example, the last_name weren't important for your controller, you could omit
             it entirely:

             public function indexAction($first_name, $color)                                             Listing
                                                                                                           9-11
             {
                 // ..
             }



              Every route also has a special _route parameter, which is equal to the name of the route that was
              matched (e.g. hello). Though not usually useful, this is equally available as a controller argument.



The Request as a Controller Argument
For convenience, you can also have Symfony pass you the Request object as an argument to your
controller. This is especially convenient when you're working with forms, for example:

use SymfonyComponentHttpFoundationRequest;                                                                            Listing
                                                                                                                          9-12

public function updateAction(Request $request)
{
    $form = $this->createForm(...);

     $form->bindRequest($request);
     // ...
}



The Base Controller Class
For convenience, Symfony2 comes with a base Controller class that assists with some of the most
common controller tasks and gives your controller class access to any resource it might need. By
extending this Controller class, you can take advantage of several helper methods.
Add the use statement atop the Controller class and then modify the HelloController to extend it:

// src/Acme/HelloBundle/Controller/HelloController.php                                                                   Listing
                                                                                                                          9-13

namespace AcmeHelloBundleController;
use SymfonyBundleFrameworkBundleControllerController;
use SymfonyComponentHttpFoundationResponse;

class HelloController extends Controller
{
    public function indexAction($name)
    {
      return new Response('<html><body>Hello '.$name.'!</body></html>');
    }
}

This doesn't actually change anything about how your controller works. In the next section, you'll
learn about the helper methods that the base controller class makes available. These methods are just
shortcuts to using core Symfony2 functionality that's available to you with or without the use of the base
Controller class. A great way to see the core functionality in action is to look in the Controller1 class
itself.

1. http://api.symfony.com/2.0/Symfony/Bundle/FrameworkBundle/Controller/Controller.html


PDF brought to you by                                                                       Chapter 9: Controller | 77
generated on June 20, 2012
Extending the base class is optional in Symfony; it contains useful shortcuts but nothing
                          mandatory. You can also extend SymfonyComponentDependencyInjectionContainerAware.
                          The service container object will then be accessible via the container property.



                          You can also define your Controllers as Services.




          Common Controller Tasks
          Though a controller can do virtually anything, most controllers will perform the same basic tasks over
          and over again. These tasks, such as redirecting, forwarding, rendering templates and accessing core
          services, are very easy to manage in Symfony2.

          Redirecting
          If you want to redirect the user to another page, use the redirect() method:

Listing   public function indexAction()
 9-14
          {
              return $this->redirect($this->generateUrl('homepage'));
          }

          The generateUrl() method is just a helper function that generates the URL for a given route. For more
          information, see the Routing chapter.
          By default, the redirect() method performs a 302 (temporary) redirect. To perform a 301 (permanent)
          redirect, modify the second argument:

Listing   public function indexAction()
 9-15
          {
              return $this->redirect($this->generateUrl('homepage'), 301);
          }



                          The redirect() method is simply a shortcut that creates a Response object that specializes in
                          redirecting the user. It's equivalent to:

                Listing   use SymfonyComponentHttpFoundationRedirectResponse;
                 9-16

                          return new RedirectResponse($this->generateUrl('homepage'));




          Forwarding
          You can also easily forward to another controller internally with the forward() method. Instead of
          redirecting the user's browser, it makes an internal sub-request, and calls the specified controller. The
          forward() method returns the Response object that's returned from that controller:

Listing   public function indexAction($name)
 9-17
          {
              $response = $this->forward('AcmeHelloBundle:Hello:fancy', array(
                  'name' => $name,


          PDF brought to you by                                                                    Chapter 9: Controller | 78
          generated on June 20, 2012
'color' => 'green'
     ));

     // further modify the response or return it directly

     return $response;
}

Notice that the forward() method uses the same string representation of the controller used in the
routing configuration. In this case, the target controller class will be HelloController inside some
AcmeHelloBundle. The array passed to the method becomes the arguments on the resulting controller.
This same interface is used when embedding controllers into templates (see Embedding Controllers). The
target controller method should look something like the following:

public function fancyAction($name, $color)                                                                             Listing
                                                                                                                        9-18
{
    // ... create and return a Response object
}

And just like when creating a controller for a route, the order of the arguments to fancyAction doesn't
matter. Symfony2 matches the index key names (e.g. name) with the method argument names (e.g.
$name). If you change the order of the arguments, Symfony2 will still pass the correct value to each
variable.


              Like other base Controller methods, the forward method is just a shortcut for core Symfony2
              functionality. A forward can be accomplished directly via the http_kernel service. A forward
              returns a Response object:

              $httpKernel    = $this->container->get('http_kernel');                                                   Listing
                                                                                                                        9-19
              $response =    $httpKernel->forward('AcmeHelloBundle:Hello:fancy', array(
                  'name'     => $name,
                  'color'    => 'green',
              ));




Rendering Templates
Though not a requirement, most controllers will ultimately render a template that's responsible for
generating the HTML (or other format) for the controller. The renderView() method renders a template
and returns its content. The content from the template can be used to create a Response object:

$content = $this->renderView('AcmeHelloBundle:Hello:index.html.twig', array('name' => $name));                         Listing
                                                                                                                        9-20

return new Response($content);

This can even be done in just one step with the render() method, which returns a Response object
containing the content from the template:

return $this->render('AcmeHelloBundle:Hello:index.html.twig', array('name' => $name));                                 Listing
                                                                                                                        9-21

In both cases, the Resources/views/Hello/index.html.twig template inside the AcmeHelloBundle
will be rendered.
The Symfony templating engine is explained in great detail in the Templating chapter.




PDF brought to you by                                                                     Chapter 9: Controller | 79
generated on June 20, 2012
The renderView method is a shortcut to direct use of the templating service. The templating
                          service can also be used directly:

                Listing   $templating = $this->get('templating');
                 9-22
                          $content = $templating->render('AcmeHelloBundle:Hello:index.html.twig', array('name' =>
                          $name));




                          It is possible to render templates in deeper subdirectories as well, however be careful to avoid the
                          pitfall of making your directory structure unduly elaborate:

                Listing   $templating->render('AcmeHelloBundle:Hello/Greetings:index.html.twig', array('name' =>
                 9-23
                          $name));
                          // index.html.twig found in Resources/views/Hello/Greetings is rendered.




          Accessing other Services
          When extending the base controller class, you can access any Symfony2 service via the get() method.
          Here are several common services you might need:

Listing   $request = $this->getRequest();
 9-24

          $templating = $this->get('templating');

          $router = $this->get('router');

          $mailer = $this->get('mailer');

          There are countless other services available and you are encouraged to define your own. To list all
          available services, use the container:debug console command:

Listing   php app/console container:debug
 9-25

          For more information, see the Service Container chapter.



          Managing Errors and 404 Pages
          When things are not found, you should play well with the HTTP protocol and return a 404 response.
          To do this, you'll throw a special type of exception. If you're extending the base controller class, do the
          following:

Listing   public function indexAction()
 9-26
          {
              $product = // retrieve the object from database
              if (!$product) {
                  throw $this->createNotFoundException('The product does not exist');
              }

               return $this->render(...);
          }

          The createNotFoundException() method creates a special NotFoundHttpException object, which
          ultimately triggers a 404 HTTP response inside Symfony.


          PDF brought to you by                                                                         Chapter 9: Controller | 80
          generated on June 20, 2012
Of course, you're free to throw any Exception class in your controller - Symfony2 will automatically
return a 500 HTTP response code.

throw new Exception('Something went wrong!');                                                                    Listing
                                                                                                                   9-27

In every case, a styled error page is shown to the end user and a full debug error page is shown to the
developer (when viewing the page in debug mode). Both of these error pages can be customized. For
details, read the "How to customize Error Pages" cookbook recipe.



Managing the Session
Symfony2 provides a nice session object that you can use to store information about the user (be it a
real person using a browser, a bot, or a web service) between requests. By default, Symfony2 stores the
attributes in a cookie by using the native PHP sessions.
Storing and retrieving information from the session can be easily achieved from any controller:

$session = $this->getRequest()->getSession();                                                                     Listing
                                                                                                                   9-28

// store an attribute for reuse during a later user request
$session->set('foo', 'bar');

// in another controller for another request
$foo = $session->get('foo');

// set the user locale
$session->setLocale('fr');

These attributes will remain on the user for the remainder of that user's session.

Flash Messages
You can also store small messages that will be stored on the user's session for exactly one additional
request. This is useful when processing a form: you want to redirect and have a special message shown
on the next request. These types of messages are called "flash" messages.
For example, imagine you're processing a form submit:

public function updateAction()                                                                                    Listing
                                                                                                                   9-29
{
    $form = $this->createForm(...);

     $form->bindRequest($this->getRequest());
     if ($form->isValid()) {
         // do some sort of processing

           $this->get('session')->setFlash('notice', 'Your changes were saved!');

           return $this->redirect($this->generateUrl(...));
     }

     return $this->render(...);
}

After processing the request, the controller sets a notice flash message and then redirects. The name
(notice) isn't significant - it's just what you're using to identify the type of the message.
In the template of the next action, the following code could be used to render the notice message:


PDF brought to you by                                                                Chapter 9: Controller | 81
generated on June 20, 2012
Listing   {% if app.session.hasFlash('notice') %}
 9-30
              <div class="flash-notice">
                  {{ app.session.flash('notice') }}
              </div>
          {% endif %}

          By design, flash messages are meant to live for exactly one request (they're "gone in a flash"). They're
          designed to be used across redirects exactly as you've done in this example.



          The Response Object
          The only requirement for a controller is to return a Response object. The Response2 class is a PHP
          abstraction around the HTTP response - the text-based message filled with HTTP headers and content
          that's sent back to the client:

Listing   // create a simple Response with a 200 status code (the default)
 9-31
          $response = new Response('Hello '.$name, 200);

          // create a JSON-response with a 200 status code
          $response = new Response(json_encode(array('name' => $name)));
          $response->headers->set('Content-Type', 'application/json');



                        The headers property is a HeaderBag3 object with several useful methods for reading and mutating
                        the Response headers. The header names are normalized so that using Content-Type is equivalent
                        to content-type or even content_type.




          The Request Object
          Besides the values of the routing placeholders, the controller also has access to the Request object when
          extending the base Controller class:

Listing   $request = $this->getRequest();
 9-32

          $request->isXmlHttpRequest(); // is it an Ajax request?

          $request->getPreferredLanguage(array('en', 'fr'));

          $request->query->get('page'); // get a $_GET parameter

          $request->request->get('page'); // get a $_POST parameter

          Like the Response object, the request headers are stored in a HeaderBag object and are easily accessible.



          Final Thoughts
          Whenever you create a page, you'll ultimately need to write some code that contains the logic for that
          page. In Symfony, this is called a controller, and it's a PHP function that can do anything it needs in order
          to return the final Response object that will be returned to the user.



          2. http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/Response.html
          3. http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/HeaderBag.html


          PDF brought to you by                                                                    Chapter 9: Controller | 82
          generated on June 20, 2012
To make life easier, you can choose to extend a base Controller class, which contains shortcut methods
for many common controller tasks. For example, since you don't want to put HTML code in your
controller, you can use the render() method to render and return the content from a template.
In other chapters, you'll see how the controller can be used to persist and fetch objects from a database,
process form submissions, handle caching and more.



Learn more from the Cookbook
  • How to customize Error Pages
  • How to define Controllers as Services




PDF brought to you by                                                                Chapter 9: Controller | 83
generated on June 20, 2012
Chapter 10
                                                    Routing

          Beautiful URLs are an absolute must for any serious web application. This means leaving behind ugly
          URLs like index.php?article_id=57 in favor of something like /read/intro-to-symfony.
          Having flexibility is even more important. What if you need to change the URL of a page from /blog to
          /news? How many links should you need to hunt down and update to make the change? If you're using
          Symfony's router, the change is simple.
          The Symfony2 router lets you define creative URLs that you map to different areas of your application.
          By the end of this chapter, you'll be able to:

            •   Create complex routes that map to controllers
            •   Generate URLs inside templates and controllers
            •   Load routing resources from bundles (or anywhere else)
            •   Debug your routes



          Routing in Action
          A route is a map from a URL pattern to a controller. For example, suppose you want to match any URL
          like /blog/my-post or /blog/all-about-symfony and send it to a controller that can look up and render
          that blog entry. The route is simple:

Listing   # app/config/routing.yml
 10-1
          blog_show:
              pattern: /blog/{slug}
              defaults: { _controller: AcmeBlogBundle:Blog:show }

          The pattern defined by the blog_show route acts like /blog/* where the wildcard is given the name slug.
          For the URL /blog/my-blog-post, the slug variable gets a value of my-blog-post, which is available
          for you to use in your controller (keep reading).
          The _controller parameter is a special key that tells Symfony which controller should be executed when
          a URL matches this route. The _controller string is called the logical name. It follows a pattern that
          points to a specific PHP class and method:

Listing
 10-2




          PDF brought to you by                                                               Chapter 10: Routing | 84
          generated on June 20, 2012
// src/Acme/BlogBundle/Controller/BlogController.php

namespace AcmeBlogBundleController;
use SymfonyBundleFrameworkBundleControllerController;

class BlogController extends Controller
{
    public function showAction($slug)
    {
        $blog = // use the $slug variable to query the database

           return $this->render('AcmeBlogBundle:Blog:show.html.twig', array(
               'blog' => $blog,
           ));
     }
}

Congratulations! You've just created your first route and connected it to a controller. Now, when you
visit /blog/my-post, the showAction controller will be executed and the $slug variable will be equal to
my-post.
This is the goal of the Symfony2 router: to map the URL of a request to a controller. Along the way, you'll
learn all sorts of tricks that make mapping even the most complex URLs easy.



Routing: Under the Hood
When a request is made to your application, it contains an address to the exact "resource" that the
client is requesting. This address is called the URL, (or URI), and could be /contact, /blog/read-me, or
anything else. Take the following HTTP request for example:

GET /blog/my-blog-post                                                                                             Listing
                                                                                                                    10-3

The goal of the Symfony2 routing system is to parse this URL and determine which controller should be
executed. The whole process looks like this:
     1. The request is handled by the Symfony2 front controller (e.g. app.php);
     2. The Symfony2 core (i.e. Kernel) asks the router to inspect the request;
     3. The router matches the incoming URL to a specific route and returns information about the
         route, including the controller that should be executed;
     4. The Symfony2 Kernel executes the controller, which ultimately returns a Response object.




The routing layer is a tool that translates the incoming URL into a specific controller to execute.




PDF brought to you by                                                                   Chapter 10: Routing | 85
generated on June 20, 2012
Creating Routes
          Symfony loads all the routes for your application from a single routing configuration file. The file is
          usually app/config/routing.yml, but can be configured to be anything (including an XML or PHP file)
          via the application configuration file:

Listing   # app/config/config.yml
 10-4
          framework:
              # ...
              router:        { resource: "%kernel.root_dir%/config/routing.yml" }



                        Even though all routes are loaded from a single file, it's common practice to include additional
                        routing resources from inside the file. See the Including External Routing Resources section for more
                        information.


          Basic Route Configuration
          Defining a route is easy, and a typical application will have lots of routes. A basic route consists of just
          two parts: the pattern to match and a defaults array:

Listing   _welcome:
 10-5
              pattern: /
              defaults: { _controller: AcmeDemoBundle:Main:homepage }

          This route matches the homepage (/) and maps it to the AcmeDemoBundle:Main:homepage controller.
          The _controller string is translated by Symfony2 into an actual PHP function and executed. That
          process will be explained shortly in the Controller Naming Pattern section.

          Routing with Placeholders
          Of course the routing system supports much more interesting routes. Many routes will contain one or
          more named "wildcard" placeholders:

Listing   blog_show:
 10-6
              pattern: /blog/{slug}
              defaults: { _controller: AcmeBlogBundle:Blog:show }

          The pattern will match anything that looks like /blog/*. Even better, the value matching the {slug}
          placeholder will be available inside your controller. In other words, if the URL is /blog/hello-world,
          a $slug variable, with a value of hello-world, will be available in the controller. This can be used, for
          example, to load the blog post matching that string.
          The pattern will not, however, match simply /blog. That's because, by default, all placeholders are
          required. This can be changed by adding a placeholder value to the defaults array.


          Required and Optional Placeholders
          To make things more exciting, add a new route that displays a list of all the available blog posts for this
          imaginary blog application:

Listing   blog:
 10-7
              pattern: /blog
              defaults: { _controller: AcmeBlogBundle:Blog:index }




          PDF brought to you by                                                                         Chapter 10: Routing | 86
          generated on June 20, 2012
So far, this route is as simple as possible - it contains no placeholders and will only match the exact URL
/blog. But what if you need this route to support pagination, where /blog/2 displays the second page of
blog entries? Update the route to have a new {page} placeholder:

blog:                                                                                                             Listing
                                                                                                                   10-8
    pattern: /blog/{page}
    defaults: { _controller: AcmeBlogBundle:Blog:index }

Like the {slug} placeholder before, the value matching {page} will be available inside your controller.
Its value can be used to determine which set of blog posts to display for the given page.
But hold on! Since placeholders are required by default, this route will no longer match on simply /blog.
Instead, to see page 1 of the blog, you'd need to use the URL /blog/1! Since that's no way for a rich web
app to behave, modify the route to make the {page} parameter optional. This is done by including it in
the defaults collection:

blog:                                                                                                             Listing
                                                                                                                   10-9
    pattern: /blog/{page}
    defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 }

By adding page to the defaults key, the {page} placeholder is no longer required. The URL /blog will
match this route and the value of the page parameter will be set to 1. The URL /blog/2 will also match,
giving the page parameter a value of 2. Perfect.

 /blog        {page} = 1
 /blog/1      {page} = 1
 /blog/2      {page} = 2


Adding Requirements
Take a quick look at the routes that have been created so far:

blog:                                                                                                             Listing
                                                                                                                  10-10
    pattern: /blog/{page}
    defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 }

blog_show:
    pattern: /blog/{slug}
    defaults: { _controller: AcmeBlogBundle:Blog:show }

Can you spot the problem? Notice that both routes have patterns that match URL's that look like
/blog/*. The Symfony router will always choose the first matching route it finds. In other words, the
blog_show route will never be matched. Instead, a URL like /blog/my-blog-post will match the first
route (blog) and return a nonsense value of my-blog-post to the {page} parameter.

 URL                         route parameters
 /blog/2                     blog   {page} = 2
 /blog/my-blog-post          blog   {page} = my-blog-post

The answer to the problem is to add route requirements. The routes in this example would work perfectly
if the /blog/{page} pattern only matched URLs where the {page} portion is an integer. Fortunately,
regular expression requirements can easily be added for each parameter. For example:

blog:                                                                                                             Listing
                                                                                                                  10-11
    pattern:        /blog/{page}

PDF brought to you by                                                                  Chapter 10: Routing | 87
generated on June 20, 2012
defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 }
                 requirements:
                     page: d+

          The d+ requirement is a regular expression that says that the value of the {page} parameter must be a
          digit (i.e. a number). The blog route will still match on a URL like /blog/2 (because 2 is a number), but
          it will no longer match a URL like /blog/my-blog-post (because my-blog-post is not a number).
          As a result, a URL like /blog/my-blog-post will now properly match the blog_show route.

           URL                         route        parameters
           /blog/2                     blog         {page} = 2
           /blog/my-blog-post          blog_show {slug} = my-blog-post



                        Earlier Routes always Win
                        What this all means is that the order of the routes is very important. If the blog_show route were
                        placed above the blog route, the URL /blog/2 would match blog_show instead of blog since
                        the {slug} parameter of blog_show has no requirements. By using proper ordering and clever
                        requirements, you can accomplish just about anything.

          Since the parameter requirements are regular expressions, the complexity and flexibility of each
          requirement is entirely up to you. Suppose the homepage of your application is available in two different
          languages, based on the URL:

Listing   homepage:
10-12
              pattern: /{culture}
              defaults: { _controller: AcmeDemoBundle:Main:homepage, culture: en }
              requirements:
                  culture: en|fr

          For incoming requests, the {culture} portion of the URL is matched against the regular expression
          (en|fr).

           /       {culture} = en
           /en {culture} = en
           /fr     {culture} = fr
           /es     won't match this route


          Adding HTTP Method Requirements
          In addition to the URL, you can also match on the method of the incoming request (i.e. GET, HEAD,
          POST, PUT, DELETE). Suppose you have a contact form with two controllers - one for displaying the
          form (on a GET request) and one for processing the form when it's submitted (on a POST request). This
          can be accomplished with the following route configuration:

Listing   contact:
10-13
              pattern: /contact
              defaults: { _controller: AcmeDemoBundle:Main:contact }
              requirements:
                  _method: GET

          contact_process:


          PDF brought to you by                                                                      Chapter 10: Routing | 88
          generated on June 20, 2012
pattern: /contact
     defaults: { _controller: AcmeDemoBundle:Main:contactProcess }
     requirements:
         _method: POST

Despite the fact that these two routes have identical patterns (/contact), the first route will match only
GET requests and the second route will match only POST requests. This means that you can display the
form and submit the form via the same URL, while using distinct controllers for the two actions.


              If no _method requirement is specified, the route will match on all methods.



Like the other requirements, the _method requirement is parsed as a regular expression. To match GET or
POST requests, you can use GET|POST.


Advanced Routing Example
At this point, you have everything you need to create a powerful routing structure in Symfony. The
following is an example of just how flexible the routing system can be:

article_show:                                                                                                           Listing
                                                                                                                        10-14
  pattern: /articles/{culture}/{year}/{title}.{_format}
  defaults: { _controller: AcmeDemoBundle:Article:show, _format: html }
  requirements:
      culture: en|fr
      _format: html|rss
      year:     d+

As you've seen, this route will only match if the {culture} portion of the URL is either en or fr and if
the {year} is a number. This route also shows how you can use a period between placeholders instead
of a slash. URLs matching this route might look like:

  • /articles/en/2010/my-post
  • /articles/fr/2010/my-post.rss


              The Special _format Routing Parameter
              This example also highlights the special _format routing parameter. When using this parameter,
              the matched value becomes the "request format" of the Request object. Ultimately, the request
              format is used for such things such as setting the Content-Type of the response (e.g. a json request
              format translates into a Content-Type of application/json). It can also be used in the controller
              to render a different template for each value of _format. The _format parameter is a very powerful
              way to render the same content in different formats.


Special Routing Parameters
As you've seen, each routing parameter or default value is eventually available as an argument in the
controller method. Additionally, there are three parameters that are special: each adds a unique piece of
functionality inside your application:

  • _controller: As you've seen, this parameter is used to determine which controller is executed
    when the route is matched;
  • _format: Used to set the request format (read more);
  • _locale: Used to set the locale on the session (read more);

PDF brought to you by                                                                        Chapter 10: Routing | 89
generated on June 20, 2012
Controller Naming Pattern
          Every route must have a _controller parameter, which dictates which controller should be executed
          when that route is matched. This parameter uses a simple string pattern called the logical controller name,
          which Symfony maps to a specific PHP method and class. The pattern has three parts, each separated by
          a colon:

                bundle:controller:action

          For example, a _controller value of AcmeBlogBundle:Blog:show means:

           Bundle                      Controller Class      Method Name
           AcmeBlogBundle              BlogController        showAction

          The controller might look like this:

Listing   // src/Acme/BlogBundle/Controller/BlogController.php
10-15

          namespace AcmeBlogBundleController;
          use SymfonyBundleFrameworkBundleControllerController;

          class BlogController extends Controller
          {
              public function showAction($slug)
              {
                  // ...
              }
          }

          Notice that Symfony adds the string Controller to the class name (Blog => BlogController) and
          Action to the method name (show => showAction).
          You could also refer to this controller using its fully-qualified class name and method:
          AcmeBlogBundleControllerBlogController::showAction. But if you follow some simple
          conventions, the logical name is more concise and allows more flexibility.


                        In addition to using the logical name or the fully-qualified class name, Symfony supports a
                        third way of referring to a controller. This method uses just one colon separator (e.g.
                        service_name:indexAction) and refers to the controller as a service (see How to define Controllers
                        as Services).



          Route Parameters and Controller Arguments
          The route parameters (e.g. {slug}) are especially important because each is made available as an
          argument to the controller method:

Listing   public function showAction($slug)
10-16
          {
            // ...
          }

          In reality, the entire defaults collection is merged with the parameter values to form a single array. Each
          key of that array is available as an argument on the controller.


          PDF brought to you by                                                                      Chapter 10: Routing | 90
          generated on June 20, 2012
In other words, for each argument of your controller method, Symfony looks for a route parameter of
that name and assigns its value to that argument. In the advanced example above, any combination (in
any order) of the following variables could be used as arguments to the showAction() method:

  •   $culture
  •   $year
  •   $title
  •   $_format
  •   $_controller

Since the placeholders and defaults collection are merged together, even the $_controller variable is
available. For a more detailed discussion, see Route Parameters as Controller Arguments.


              You can also use a special $_route variable, which is set to the name of the route that was matched.




Including External Routing Resources
All routes are loaded via a single configuration file - usually app/config/routing.yml (see Creating
Routes above). Commonly, however, you'll want to load routes from other places, like a routing file that
lives inside a bundle. This can be done by "importing" that file:

# app/config/routing.yml                                                                                                Listing
                                                                                                                        10-17
acme_hello:
    resource: "@AcmeHelloBundle/Resources/config/routing.yml"



              When importing resources from YAML, the key (e.g. acme_hello) is meaningless. Just be sure that
              it's unique so no other lines override it.


The resource key loads the given routing resource. In this example the resource is the full path to a
file, where the @AcmeHelloBundle shortcut syntax resolves to the path of that bundle. The imported file
might look like this:

 # src/Acme/HelloBundle/Resources/config/routing.yml                                                                    Listing
                                                                                                                        10-18
acme_hello:
     pattern: /hello/{name}
     defaults: { _controller: AcmeHelloBundle:Hello:index }

The routes from this file are parsed and loaded in the same way as the main routing file.

Prefixing Imported Routes
You can also choose to provide a "prefix" for the imported routes. For example, suppose you want the
acme_hello route to have a final pattern of /admin/hello/{name} instead of simply /hello/{name}:

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




PDF brought to you by                                                                        Chapter 10: Routing | 91
generated on June 20, 2012
The string /admin will now be prepended to the pattern of each route loaded from the new routing
          resource.



          Visualizing & Debugging Routes
          While adding and customizing routes, it's helpful to be able to visualize and get detailed information
          about your routes. A great way to see every route in your application is via the router:debug console
          command. Execute the command by running the following from the root of your project.

Listing   php app/console router:debug
10-20

          The command will print a helpful list of all the configured routes in your application:

Listing   homepage                      ANY           /
10-21
          contact                       GET           /contact
          contact_process               POST          /contact
          article_show                  ANY           /articles/{culture}/{year}/{title}.{_format}
          blog                          ANY           /blog/{page}
          blog_show                     ANY           /blog/{slug}

          You can also get very specific information on a single route by including the route name after the
          command:

Listing   php app/console router:debug article_show
10-22




          Generating URLs
          The routing system should also be used to generate URLs. In reality, routing is a bi-directional system:
          mapping the URL to a controller+parameters and a route+parameters back to a URL. The match()1 and
          generate()2 methods form this bi-directional system. Take the blog_show example route from earlier:

Listing   $params = $router->match('/blog/my-blog-post');
10-23
          // array('slug' => 'my-blog-post', '_controller' => 'AcmeBlogBundle:Blog:show')

          $uri = $router->generate('blog_show', array('slug' => 'my-blog-post'));
          // /blog/my-blog-post

          To generate a URL, you need to specify the name of the route (e.g. blog_show) and any wildcards (e.g.
          slug = my-blog-post) used in the pattern for that route. With this information, any URL can easily be
          generated:

Listing   class MainController extends Controller
10-24
          {
              public function showAction($slug)
              {
                // ...

                   $url = $this->get('router')->generate('blog_show', array('slug' => 'my-blog-post'));
               }
          }

          In an upcoming section, you'll learn how to generate URLs from inside templates.


          1. http://api.symfony.com/2.0/Symfony/Component/Routing/Router.html#match()
          2. http://api.symfony.com/2.0/Symfony/Component/Routing/Router.html#generate()


          PDF brought to you by                                                                      Chapter 10: Routing | 92
          generated on June 20, 2012
If the frontend of your application uses AJAX requests, you might want to be able to generate URLs
              in JavaScript based on your routing configuration. By using the FOSJsRoutingBundle3, you can do
              exactly that:

              var url = Routing.generate('blog_show', { "slug": 'my-blog-post'});                                     Listing
                                                                                                                      10-25

              For more information, see the documentation for that bundle.


Generating Absolute URLs
By default, the router will generate relative URLs (e.g. /blog). To generate an absolute URL, simply pass
true to the third argument of the generate() method:

$router->generate('blog_show', array('slug' => 'my-blog-post'), true);                                                Listing
                                                                                                                      10-26
// http://www.example.com/blog/my-blog-post


              The host that's used when generating an absolute URL is the host of the current Request object.
              This is detected automatically based on server information supplied by PHP. When generating
              absolute URLs for scripts run from the command line, you'll need to manually set the desired host
              on the Request object:

              $request->headers->set('HOST', 'www.example.com');                                                      Listing
                                                                                                                      10-27




Generating URLs with Query Strings
The generate method takes an array of wildcard values to generate the URI. But if you pass extra ones,
they will be added to the URI as a query string:

$router->generate('blog', array('page' => 2, 'category' => 'Symfony'));                                               Listing
                                                                                                                      10-28
// /blog/2?category=Symfony


Generating URLs from a template
The most common place to generate a URL is from within a template when linking between pages in
your application. This is done just as before, but using a template helper function:

<a href="{{ path('blog_show', { 'slug': 'my-blog-post' }) }}">                                                        Listing
                                                                                                                      10-29
  Read this blog post.
</a>

Absolute URLs can also be generated.

<a href="{{ url('blog_show', { 'slug': 'my-blog-post' }) }}">                                                         Listing
                                                                                                                      10-30
  Read this blog post.
</a>



Summary
Routing is a system for mapping the URL of incoming requests to the controller function that should be
called to process the request. It both allows you to specify beautiful URLs and keeps the functionality of

3. https://github.com/FriendsOfSymfony/FOSJsRoutingBundle


PDF brought to you by                                                                      Chapter 10: Routing | 93
generated on June 20, 2012
your application decoupled from those URLs. Routing is a two-way mechanism, meaning that it should
also be used to generate URLs.



Learn more from the Cookbook
  • How to force routes to always use HTTPS or HTTP




PDF brought to you by                                                           Chapter 10: Routing | 94
generated on June 20, 2012
Chapter 11
                        Creating and using Templates

As you know, the controller is responsible for handling each request that comes into a Symfony2
application. In reality, the controller delegates the most of the heavy work to other places so that code
can be tested and reused. When a controller needs to generate HTML, CSS or any other content, it hands
the work off to the templating engine. In this chapter, you'll learn how to write powerful templates that
can be used to return content to the user, populate email bodies, and more. You'll learn shortcuts, clever
ways to extend templates and how to reuse template code.



Templates
A template is simply a text file that can generate any text-based format (HTML, XML, CSV, LaTeX ...).
The most familiar type of template is a PHP template - a text file parsed by PHP that contains a mix of
text and PHP code:

<!DOCTYPE html>                                                                                                      Listing
                                                                                                                      11-1
<html>
    <head>
        <title>Welcome to Symfony!</title>
    </head>
    <body>
        <h1><?php echo $page_title ?></h1>

        <ul id="navigation">
            <?php foreach ($navigation as $item): ?>
                <li>
                     <a href="<?php echo $item->getHref() ?>">
                          <?php echo $item->getCaption() ?>
                     </a>
                </li>
            <?php endforeach; ?>
        </ul>
    </body>
</html>




PDF brought to you by                                                Chapter 11: Creating and using Templates | 95
generated on June 20, 2012
But Symfony2 packages an even more powerful templating language called Twig1. Twig allows you to
          write concise, readable templates that are more friendly to web designers and, in several ways, more
          powerful than PHP templates:

Listing   <!DOCTYPE html>
 11-2
          <html>
              <head>
                  <title>Welcome to Symfony!</title>
              </head>
              <body>
                  <h1>{{ page_title }}</h1>

                  <ul id="navigation">
                      {% for item in navigation %}
                          <li><a href="{{ item.href }}">{{ item.caption }}</a></li>
                      {% endfor %}
                  </ul>
              </body>
          </html>

          Twig defines two types of special syntax:

            • {{ ... }}: "Says something": prints a variable or the result of an expression to the template;
            • {% ... %}: "Does something": a tag that controls the logic of the template; it is used to execute
              statements such as for-loops for example.


                        There is a third syntax used for creating comments: {# this is a comment #}. This syntax can
                        be used across multiple lines like the PHP-equivalent /* comment */ syntax.


          Twig also contains filters, which modify content before being rendered. The following makes the title
          variable all uppercase before rendering it:

Listing   {{ title|upper }}
 11-3


          Twig comes with a long list of tags2 and filters3 that are available by default. You can even add your own
          extensions4 to Twig as needed.


                        Registering a Twig extension is as easy as creating a new service and tagging it with
                        twig.extension tag.


          As you'll see throughout the documentation, Twig also supports functions and new functions can be
          easily added. For example, the following uses a standard for tag and the cycle function to print ten div
          tags, with alternating odd, even classes:

Listing   {% for i in 0..10 %}
 11-4
              <div class="{{ cycle(['odd', 'even'], i) }}">
                <!-- some HTML here -->
              </div>
          {% endfor %}



          1. http://twig.sensiolabs.org
          2. http://twig.sensiolabs.org/doc/tags/index.html
          3. http://twig.sensiolabs.org/doc/filters/index.html
          4. http://twig.sensiolabs.org/doc/advanced.html#creating-an-extension


          PDF brought to you by                                                   Chapter 11: Creating and using Templates | 96
          generated on June 20, 2012
Throughout this chapter, template examples will be shown in both Twig and PHP.


              Why Twig?
              Twig templates are meant to be simple and won't process PHP tags. This is by design: the Twig
              template system is meant to express presentation, not program logic. The more you use Twig, the
              more you'll appreciate and benefit from this distinction. And of course, you'll be loved by web
              designers everywhere.
              Twig can also do things that PHP can't, such as true template inheritance (Twig templates compile
              down to PHP classes that inherit from each other), whitespace control, sandboxing, and the
              inclusion of custom functions and filters that only affect templates. Twig contains little features
              that make writing templates easier and more concise. Take the following example, which combines
              a loop with a logical if statement:

              <ul>                                                                                                        Listing
                                                                                                                           11-5
                  {% for user in users %}
                      <li>{{ user.username }}</li>
                  {% else %}
                      <li>No users found</li>
                  {% endfor %}
              </ul>




Twig Template Caching
Twig is fast. Each Twig template is compiled down to a native PHP class that is rendered at runtime. The
compiled classes are located in the app/cache/{environment}/twig directory (where {environment}
is the environment, such as dev or prod) and in some cases can be useful while debugging. See
Environments for more information on environments.
When debug mode is enabled (common in the dev environment), a Twig template will be automatically
recompiled when changes are made to it. This means that during development you can happily make
changes to a Twig template and instantly see the changes without needing to worry about clearing any
cache.
When debug mode is disabled (common in the prod environment), however, you must clear the Twig
cache directory so that the Twig templates will regenerate. Remember to do this when deploying your
application.



Template Inheritance and Layouts
More often than not, templates in a project share common elements, like the header, footer, sidebar
or more. In Symfony2, we like to think about this problem differently: a template can be decorated by
another one. This works exactly the same as PHP classes: template inheritance allows you to build a base
"layout" template that contains all the common elements of your site defined as blocks (think "PHP class
with base methods"). A child template can extend the base layout and override any of its blocks (think
"PHP subclass that overrides certain methods of its parent class").
First, build a base layout file:

{# app/Resources/views/base.html.twig #}                                                                                  Listing
                                                                                                                           11-6
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title>{% block title %}Test Application{% endblock %}</title>


PDF brought to you by                                                     Chapter 11: Creating and using Templates | 97
generated on June 20, 2012
</head>
               <body>
                   <div id="sidebar">
                       {% block sidebar %}
                       <ul>
                            <li><a href="/">Home</a></li>
                            <li><a href="/blog">Blog</a></li>
                       </ul>
                       {% endblock %}
                   </div>

                  <div id="content">
                      {% block body %}{% endblock %}
                  </div>
              </body>
          </html>



                        Though the discussion about template inheritance will be in terms of Twig, the philosophy is the
                        same between Twig and PHP templates.


          This template defines the base HTML skeleton document of a simple two-column page. In this example,
          three {% block %} areas are defined (title, sidebar and body). Each block may be overridden by a child
          template or left with its default implementation. This template could also be rendered directly. In that
          case the title, sidebar and body blocks would simply retain the default values used in this template.
          A child template might look like this:

Listing   {# src/Acme/BlogBundle/Resources/views/Blog/index.html.twig #}
 11-7
          {% extends '::base.html.twig' %}

          {% block title %}My cool blog posts{% endblock %}

          {% block body %}
              {% for entry in blog_entries %}
                  <h2>{{ entry.title }}</h2>
                  <p>{{ entry.body }}</p>
              {% endfor %}
          {% endblock %}



                        The parent template is identified by a special string syntax (::base.html.twig) that indicates that
                        the template lives in the app/Resources/views directory of the project. This naming convention
                        is explained fully in Template Naming and Locations.

          The key to template inheritance is the {% extends %} tag. This tells the templating engine to first
          evaluate the base template, which sets up the layout and defines several blocks. The child template is
          then rendered, at which point the title and body blocks of the parent are replaced by those from the
          child. Depending on the value of blog_entries, the output might look like this:

Listing   <!DOCTYPE html>
 11-8
          <html>
              <head>
                  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
                  <title>My cool blog posts</title>
              </head>
              <body>
                  <div id="sidebar">


          PDF brought to you by                                                     Chapter 11: Creating and using Templates | 98
          generated on June 20, 2012
<ul>
                   <li><a href="/">Home</a></li>
                   <li><a href="/blog">Blog</a></li>
               </ul>
           </div>

           <div id="content">
               <h2>My first post</h2>
               <p>The body of the first post.</p>

            <h2>Another post</h2>
            <p>The body of the second post.</p>
        </div>
    </body>
</html>

Notice that since the child template didn't define a sidebar block, the value from the parent template is
used instead. Content within a {% block %} tag in a parent template is always used by default.
You can use as many levels of inheritance as you want. In the next section, a common three-level
inheritance model will be explained along with how templates are organized inside a Symfony2 project.
When working with template inheritance, here are some tips to keep in mind:

  • If you use {% extends %} in a template, it must be the first tag in that template.
  • The more {% block %} tags you have in your base templates, the better. Remember, child
    templates don't have to define all parent blocks, so create as many blocks in your base
    templates as you want and give each a sensible default. The more blocks your base templates
    have, the more flexible your layout will be.
  • If you find yourself duplicating content in a number of templates, it probably means you
    should move that content to a {% block %} in a parent template. In some cases, a better
    solution may be to move the content to a new template and include it (see Including other
    Templates).
  • If you need to get the content of a block from the parent template, you can use the {{
    parent() }} function. This is useful if you want to add to the contents of a parent block
    instead of completely overriding it:

             {% block sidebar %}                                                                       Listing
                                                                                                        11-9
                 <h3>Table of Contents</h3>
                 ...
                 {{ parent() }}
             {% endblock %}



Template Naming and Locations
By default, templates can live in two different locations:

  • app/Resources/views/: The applications views directory can contain application-wide base
    templates (i.e. your application's layouts) as well as templates that override bundle templates
    (see Overriding Bundle Templates);
  • path/to/bundle/Resources/views/: Each bundle houses its templates in its Resources/
    views directory (and subdirectories). The majority of templates will live inside a bundle.

Symfony2 uses a bundle:controller:template string syntax for templates. This allows for several
different types of templates, each which lives in a specific location:




PDF brought to you by                                                Chapter 11: Creating and using Templates | 99
generated on June 20, 2012
• AcmeBlogBundle:Blog:index.html.twig: This syntax is used to specify a template for a
    specific page. The three parts of the string, each separated by a colon (:), mean the following:

                • AcmeBlogBundle: (bundle) the template lives inside the AcmeBlogBundle
                  (e.g. src/Acme/BlogBundle);
                • Blog: (controller) indicates that the template lives inside the Blog
                  subdirectory of Resources/views;
                • index.html.twig: (template) the actual name of the file is
                  index.html.twig.

      Assuming that the AcmeBlogBundle lives at src/Acme/BlogBundle, the final path to the layout
      would be src/Acme/BlogBundle/Resources/views/Blog/index.html.twig.
  • AcmeBlogBundle::layout.html.twig: This syntax refers to a base template that's specific
    to the AcmeBlogBundle. Since the middle, "controller", portion is missing (e.g. Blog), the
    template lives at Resources/views/layout.html.twig inside AcmeBlogBundle.
  • ::base.html.twig: This syntax refers to an application-wide base template or layout. Notice
    that the string begins with two colons (::), meaning that both the bundle and controller
    portions are missing. This means that the template is not located in any bundle, but instead in
    the root app/Resources/views/ directory.

In the Overriding Bundle Templates section, you'll find out how each template living inside the
AcmeBlogBundle, for example, can be overridden by placing a template of the same name in the app/
Resources/AcmeBlogBundle/views/ directory. This gives the power to override templates from any
vendor bundle.


              Hopefully the template naming syntax looks familiar - it's the same naming convention used to
              refer to Controller Naming Pattern.



Template Suffix
The bundle:controller:template format of each template specifies where the template file is located.
Every template name also has two extensions that specify the format and engine for that template.

  • AcmeBlogBundle:Blog:index.html.twig - HTML format, Twig engine
  • AcmeBlogBundle:Blog:index.html.php - HTML format, PHP engine
  • AcmeBlogBundle:Blog:index.css.twig - CSS format, Twig engine

By default, any Symfony2 template can be written in either Twig or PHP, and the last part of the
extension (e.g. .twig or .php) specifies which of these two engines should be used. The first part of the
extension, (e.g. .html, .css, etc) is the final format that the template will generate. Unlike the engine,
which determines how Symfony2 parses the template, this is simply an organizational tactic used in case
the same resource needs to be rendered as HTML (index.html.twig), XML (index.xml.twig), or any
other format. For more information, read the Debugging section.


              The available "engines" can be configured and even new engines added. See Templating
              Configuration for more details.




PDF brought to you by                                                 Chapter 11: Creating and using Templates | 100
generated on June 20, 2012
Tags and Helpers
You already understand the basics of templates, how they're named and how to use template inheritance.
The hardest parts are already behind you. In this section, you'll learn about a large group of tools available
to help perform the most common template tasks such as including other templates, linking to pages and
including images.
Symfony2 comes bundled with several specialized Twig tags and functions that ease the work of the
template designer. In PHP, the templating system provides an extensible helper system that provides
useful features in a template context.
We've already seen a few built-in Twig tags ({% block %} & {% extends %}) as well as an example of a
PHP helper ($view['slots']). Let's learn a few more.


Including other Templates
You'll often want to include the same template or code fragment on several different pages. For example,
in an application with "news articles", the template code displaying an article might be used on the article
detail page, on a page displaying the most popular articles, or in a list of the latest articles.
When you need to reuse a chunk of PHP code, you typically move the code to a new PHP class or
function. The same is true for templates. By moving the reused template code into its own template, it
can be included from any other template. First, create the template that you'll need to reuse.

{# src/Acme/ArticleBundle/Resources/views/Article/articleDetails.html.twig #}                                           Listing
                                                                                                                        11-10
<h2>{{ article.title }}</h2>
<h3 class="byline">by {{ article.authorName }}</h3>

<p>
       {{ article.body }}
</p>

Including this template from any other template is simple:

{# src/Acme/ArticleBundle/Resources/Article/list.html.twig #}                                                           Listing
                                                                                                                        11-11
{% extends 'AcmeArticleBundle::layout.html.twig' %}

{% block body %}
    <h1>Recent Articles<h1>

    {% for article in articles %}
        {% include 'AcmeArticleBundle:Article:articleDetails.html.twig' with {'article':
article} %}
    {% endfor %}
{% endblock %}

The template is included using the {% include %} tag. Notice that the template name follows the same
typical convention. The articleDetails.html.twig template uses an article variable. This is passed
in by the list.html.twig template using the with command.


              The {'article': article} syntax is the standard Twig syntax for hash maps (i.e. an array with
              named keys). If we needed to pass in multiple elements, it would look like this: {'foo': foo,
              'bar': bar}.




PDF brought to you by                                                  Chapter 11: Creating and using Templates | 101
generated on June 20, 2012
Embedding Controllers
          In some cases, you need to do more than include a simple template. Suppose you have a sidebar in your
          layout that contains the three most recent articles. Retrieving the three articles may include querying the
          database or performing other heavy logic that can't be done from within a template.
          The solution is to simply embed the result of an entire controller from your template. First, create a
          controller that renders a certain number of recent articles:

Listing   // src/Acme/ArticleBundle/Controller/ArticleController.php
11-12

          class ArticleController extends Controller
          {
              public function recentArticlesAction($max = 3)
              {
                  // make a database call or other logic to get the "$max" most recent articles
                  $articles = ...;

                  return $this->render('AcmeArticleBundle:Article:recentList.html.twig',
          array('articles' => $articles));
              }
          }

          The recentList template is perfectly straightforward:

Listing   {# src/Acme/ArticleBundle/Resources/views/Article/recentList.html.twig #}
11-13
          {% for article in articles %}
              <a href="/article/{{ article.slug }}">
                   {{ article.title }}
              </a>
          {% endfor %}



                        Notice that we've cheated and hardcoded the article URL in this example (e.g. /article/*slug*).
                        This is a bad practice. In the next section, you'll learn how to do this correctly.


          To include the controller, you'll need to refer to it using the standard string syntax for controllers (i.e.
          bundle:controller:action):

Listing   {# app/Resources/views/base.html.twig #}
11-14
          ...

          <div id="sidebar">
              {% render "AcmeArticleBundle:Article:recentArticles" with {'max': 3} %}
          </div>

          Whenever you find that you need a variable or a piece of information that you don't have access to
          in a template, consider rendering a controller. Controllers are fast to execute and promote good code
          organization and reuse.

          Linking to Pages
          Creating links to other pages in your application is one of the most common jobs for a template. Instead
          of hardcoding URLs in templates, use the path Twig function (or the router helper in PHP) to generate
          URLs based on the routing configuration. Later, if you want to modify the URL of a particular page, all
          you'll need to do is change the routing configuration; the templates will automatically generate the new
          URL.


          PDF brought to you by                                                  Chapter 11: Creating and using Templates | 102
          generated on June 20, 2012
First, link to the "_welcome" page, which is accessible via the following routing configuration:

_welcome:                                                                                                               Listing
                                                                                                                        11-15
    pattern: /
    defaults: { _controller: AcmeDemoBundle:Welcome:index }

To link to the page, just use the path Twig function and refer to the route:

<a href="{{ path('_welcome') }}">Home</a>                                                                               Listing
                                                                                                                        11-16

As expected, this will generate the URL /. Let's see how this works with a more complicated route:

article_show:                                                                                                           Listing
                                                                                                                        11-17
    pattern: /article/{slug}
    defaults: { _controller: AcmeArticleBundle:Article:show }

In this case, you need to specify both the route name (article_show) and a value for the {slug}
parameter. Using this route, let's revisit the recentList template from the previous section and link to
the articles correctly:

{# src/Acme/ArticleBundle/Resources/views/Article/recentList.html.twig #}                                               Listing
                                                                                                                        11-18
{% for article in articles %}
    <a href="{{ path('article_show', { 'slug': article.slug }) }}">
         {{ article.title }}
    </a>
{% endfor %}



              You can also generate an absolute URL by using the url Twig function:

              <a href="{{ url('_welcome') }}">Home</a>                                                                  Listing
                                                                                                                        11-19

              The same can be done in PHP templates by passing a third argument to the generate() method:

              <a href="<?php echo $view['router']->generate('_welcome', array(), true) ?>">Home</a>                     Listing
                                                                                                                        11-20




Linking to Assets
Templates also commonly refer to images, Javascript, stylesheets and other assets. Of course you could
hard-code the path to these assets (e.g. /images/logo.png), but Symfony2 provides a more dynamic
option via the asset Twig function:

<img src="{{ asset('images/logo.png') }}" alt="Symfony!" />                                                             Listing
                                                                                                                        11-21

<link href="{{ asset('css/blog.css') }}" rel="stylesheet" type="text/css" />

The asset function's main purpose is to make your application more portable. If your application lives
at the root of your host (e.g. http://example.com5), then the rendered paths should be /images/logo.png.
But if your application lives in a subdirectory (e.g. http://example.com/my_app6), each asset path should
render with the subdirectory (e.g. /my_app/images/logo.png). The asset function takes care of this by
determining how your application is being used and generating the correct paths accordingly.
Additionally, if you use the asset function, Symfony can automatically append a query string to your
asset, in order to guarantee that updated static assets won't be cached when deployed. For example,


5. http://example.com
6. http://example.com/my_app


PDF brought to you by                                                  Chapter 11: Creating and using Templates | 103
generated on June 20, 2012
/images/logo.png might look like /images/logo.png?v2. For more information, see the assets_version
          configuration option.



          Including Stylesheets and Javascripts in Twig
          No site would be complete without including Javascript files and stylesheets. In Symfony, the inclusion
          of these assets is handled elegantly by taking advantage of Symfony's template inheritance.


                        This section will teach you the philosophy behind including stylesheet and Javascript assets in
                        Symfony. Symfony also packages another library, called Assetic, which follows this philosophy but
                        allows you to do much more interesting things with those assets. For more information on using
                        Assetic see How to Use Assetic for Asset Management.

          Start by adding two blocks to your base template that will hold your assets: one called stylesheets
          inside the head tag and another called javascripts just above the closing body tag. These blocks will
          contain all of the stylesheets and Javascripts that you'll need throughout your site:

Listing   {# 'app/Resources/views/base.html.twig' #}
11-22
          <html>
              <head>
                  {# ... #}

                   {% block stylesheets %}
                       <link href="{{ asset('/css/main.css') }}" type="text/css" rel="stylesheet" />
                   {% endblock %}
               </head>
               <body>
                   {# ... #}

                  {% block javascripts %}
                      <script src="{{ asset('/js/main.js') }}" type="text/javascript"></script>
                  {% endblock %}
              </body>
          </html>

          That's easy enough! But what if you need to include an extra stylesheet or Javascript from a child
          template? For example, suppose you have a contact page and you need to include a contact.css
          stylesheet just on that page. From inside that contact page's template, do the following:

Listing   {# src/Acme/DemoBundle/Resources/views/Contact/contact.html.twig #}
11-23
          {% extends '::base.html.twig' %}

          {% block stylesheets %}
              {{ parent() }}

              <link href="{{ asset('/css/contact.css') }}" type="text/css" rel="stylesheet" />
          {% endblock %}

          {# ... #}

          In the child template, you simply override the stylesheets block and put your new stylesheet tag inside
          of that block. Of course, since you want to add to the parent block's content (and not actually replace
          it), you should use the parent() Twig function to include everything from the stylesheets block of the
          base template.




          PDF brought to you by                                                   Chapter 11: Creating and using Templates | 104
          generated on June 20, 2012
You can also include assets located in your bundles' Resources/public folder. You will need to run the
php app/console assets:install target [--symlink] command, which moves (or symlinks) files
into the correct location. (target is by default "web").

<link href="{{ asset('bundles/acmedemo/css/contact.css') }}" type="text/css" rel="stylesheet"                                          Listing
                                                                                                                                       11-24
/>

The end result is a page that includes both the main.css and contact.css stylesheets.



Global Template Variables
During each request, Symfony2 will set a global template variable app in both Twig and PHP template
engines by default. The app variable is a GlobalVariables7 instance which will give you access to some
application specific variables automatically:

  •   app.security - The security context.
  •   app.user - The current user object.
  •   app.request - The request object.
  •   app.session - The session object.
  •   app.environment - The current environment (dev, prod, etc).
  •   app.debug - True if in debug mode. False otherwise.

<p>Username: {{ app.user.username }}</p>                                                                                               Listing
                                                                                                                                       11-25
{% if app.debug %}
    <p>Request method: {{ app.request.method }}</p>
    <p>Application Environment: {{ app.environment }}</p>
{% endif %}



              You can add your own global template variables. See the cookbook example on Global Variables.




Configuring and using the templating Service
The heart of the template system in Symfony2 is the templating Engine. This special object is responsible
for rendering templates and returning their content. When you render a template in a controller, for
example, you're actually using the templating engine service. For example:

return $this->render('AcmeArticleBundle:Article:index.html.twig');                                                                     Listing
                                                                                                                                       11-26

is equivalent to:

      $engine     =       $this->container->get('templating');                         $content          =        $engine-
      >render('AcmeArticleBundle:Article:index.html.twig');
      return $response = new Response($content);

The templating engine (or "service") is preconfigured to work automatically inside Symfony2. It can, of
course, be configured further in the application configuration file:

# app/config/config.yml                                                                                                                Listing
                                                                                                                                       11-27
framework:

7. http://api.symfony.com/2.0/Symfony/Bundle/FrameworkBundle/Templating/GlobalVariables.html


PDF brought to you by                                                                 Chapter 11: Creating and using Templates | 105
generated on June 20, 2012
# ...
               templating: { engines: ['twig'] }

          Several configuration options are available and are covered in the Configuration Appendix.


                        The twig engine is mandatory to use the webprofiler (as well as many third-party bundles).




          Overriding Bundle Templates
          The Symfony2 community prides itself on creating and maintaining high quality bundles (see
          KnpBundles.com8) for a large number of different features. Once you use a third-party bundle, you'll likely
          need to override and customize one or more of its templates.
          Suppose you've included the imaginary open-source AcmeBlogBundle in your project (e.g. in the src/
          Acme/BlogBundle directory). And while you're really happy with everything, you want to override the
          blog "list" page to customize the markup specifically for your application. By digging into the Blog
          controller of the AcmeBlogBundle, you find the following:

Listing   public function indexAction()
11-28
          {
              $blogs = // some logic to retrieve the blogs

               $this->render('AcmeBlogBundle:Blog:index.html.twig', array('blogs' => $blogs));
          }

          When the AcmeBlogBundle:Blog:index.html.twig is rendered, Symfony2 actually looks in two
          different locations for the template:
                1. app/Resources/AcmeBlogBundle/views/Blog/index.html.twig
                2. src/Acme/BlogBundle/Resources/views/Blog/index.html.twig
          To override the bundle template, just copy the index.html.twig template from the bundle to app/
          Resources/AcmeBlogBundle/views/Blog/index.html.twig (the app/Resources/AcmeBlogBundle
          directory won't exist, so you'll need to create it). You're now free to customize the template.
          This logic also applies to base bundle templates. Suppose also that each template in AcmeBlogBundle
          inherits from a base template called AcmeBlogBundle::layout.html.twig. Just as before, Symfony2 will
          look in the following two places for the template:
                1. app/Resources/AcmeBlogBundle/views/layout.html.twig
                2. src/Acme/BlogBundle/Resources/views/layout.html.twig
          Once again, to override the template, just copy it from the bundle to app/Resources/AcmeBlogBundle/
          views/layout.html.twig. You're now free to customize this copy as you see fit.
          If you take a step back, you'll see that Symfony2 always starts by looking in the app/Resources/
          {BUNDLE_NAME}/views/ directory for a template. If the template doesn't exist there, it continues by
          checking inside the Resources/views directory of the bundle itself. This means that all bundle templates
          can be overridden by placing them in the correct app/Resources subdirectory.


                        You can also override templates from within a bundle by using bundle inheritance. For more
                        information, see How to use Bundle Inheritance to Override parts of a Bundle.




          8. http://knpbundles.com


          PDF brought to you by                                                   Chapter 11: Creating and using Templates | 106
          generated on June 20, 2012
Overriding Core Templates
Since the Symfony2 framework itself is just a bundle, core templates can be overridden in the same way.
For example, the core TwigBundle contains a number of different "exception" and "error" templates that
can be overridden by copying each from the Resources/views/Exception directory of the TwigBundle
to, you guessed it, the app/Resources/TwigBundle/views/Exception directory.



Three-level Inheritance
One common way to use inheritance is to use a three-level approach. This method works perfectly with
the three different types of templates we've just covered:

  • Create a app/Resources/views/base.html.twig file that contains the main layout for your
    application (like in the previous example). Internally, this template is called
    ::base.html.twig;
  • Create a template for each "section" of your site. For example, an AcmeBlogBundle, would
    have a template called AcmeBlogBundle::layout.html.twig that contains only blog section-
    specific elements;

             {# src/Acme/BlogBundle/Resources/views/layout.html.twig #}                              Listing
                                                                                                     11-29
             {% extends '::base.html.twig' %}

             {% block body %}
                 <h1>Blog Application</h1>

                 {% block content %}{% endblock %}
             {% endblock %}

  • Create individual templates for each page and make each extend the appropriate section
    template. For example, the "index" page would be called something close to
    AcmeBlogBundle:Blog:index.html.twig and list the actual blog posts.

             {# src/Acme/BlogBundle/Resources/views/Blog/index.html.twig #}                          Listing
                                                                                                     11-30
             {% extends 'AcmeBlogBundle::layout.html.twig' %}

             {% block content %}
                 {% for entry in blog_entries %}
                     <h2>{{ entry.title }}</h2>
                     <p>{{ entry.body }}</p>
                 {% endfor %}
             {% endblock %}

Notice that this template extends the section template -(AcmeBlogBundle::layout.html.twig) which
in-turn extends the base application layout (::base.html.twig). This is the common three-level
inheritance model.
When building your application, you may choose to follow this method or simply make each page
template extend the base application template directly (e.g. {% extends '::base.html.twig' %}). The
three-template model is a best-practice method used by vendor bundles so that the base template for a
bundle can be easily overridden to properly extend your application's base layout.



Output Escaping
When generating HTML from a template, there is always a risk that a template variable may output
unintended HTML or dangerous client-side code. The result is that dynamic content could break the

PDF brought to you by                                             Chapter 11: Creating and using Templates | 107
generated on June 20, 2012
HTML of the resulting page or allow a malicious user to perform a Cross Site Scripting9 (XSS) attack.
          Consider this classic example:

Listing   Hello {{ name }}
11-31

          Imagine that the user enters the following code as his/her name:

Listing   <script>alert('hello!')</script>
11-32

          Without any output escaping, the resulting template will cause a JavaScript alert box to pop up:

Listing   Hello <script>alert('hello!')</script>
11-33

          And while this seems harmless, if a user can get this far, that same user should also be able to write
          JavaScript that performs malicious actions inside the secure area of an unknowing, legitimate user.
          The answer to the problem is output escaping. With output escaping on, the same template will render
          harmlessly, and literally print the script tag to the screen:

Listing   Hello &lt;script&gt;alert(&#39;helloe&#39;)&lt;/script&gt;
11-34

          The Twig and PHP templating systems approach the problem in different ways. If you're using Twig,
          output escaping is on by default and you're protected. In PHP, output escaping is not automatic, meaning
          you'll need to manually escape where necessary.

          Output Escaping in Twig
          If you're using Twig templates, then output escaping is on by default. This means that you're protected
          out-of-the-box from the unintentional consequences of user-submitted code. By default, the output
          escaping assumes that content is being escaped for HTML output.
          In some cases, you'll need to disable output escaping when you're rendering a variable that is trusted and
          contains markup that should not be escaped. Suppose that administrative users are able to write articles
          that contain HTML code. By default, Twig will escape the article body. To render it normally, add the
          raw filter: {{ article.body|raw }}.
          You can also disable output escaping inside a {% block %} area or for an entire template. For more
          information, see Output Escaping10 in the Twig documentation.

          Output Escaping in PHP
          Output escaping is not automatic when using PHP templates. This means that unless you explicitly
          choose to escape a variable, you're not protected. To use output escaping, use the special escape() view
          method:

Listing   Hello <?php echo $view->escape($name) ?>
11-35

          By default, the escape() method assumes that the variable is being rendered within an HTML context
          (and thus the variable is escaped to be safe for HTML). The second argument lets you change the context.
          For example, to output something in a JavaScript string, use the js context:

Listing   var myMsg = 'Hello <?php echo $view->escape($name, 'js') ?>';
11-36




          9. http://en.wikipedia.org/wiki/Cross-site_scripting
          10. http://twig.sensiolabs.org/doc/api.html#escaper-extension


          PDF brought to you by                                               Chapter 11: Creating and using Templates | 108
          generated on June 20, 2012
Debugging
              New in version 2.0.9: This feature is available as of Twig 1.5.x, which was first shipped with
              Symfony 2.0.9.



When using PHP, you can use var_dump() if you need to quickly find the value of a variable passed. This
is useful, for example, inside your controller. The same can be achieved when using Twig by using the
debug extension. This needs to be enabled in the config:

# app/config/config.yml                                                                                                Listing
                                                                                                                       11-37
services:
    acme_hello.twig.extension.debug:
        class:         Twig_Extension_Debug
        tags:
              - { name: 'twig.extension' }

Template parameters can then be dumped using the dump function:

{# src/Acme/ArticleBundle/Resources/views/Article/recentList.html.twig #}                                              Listing
                                                                                                                       11-38

{{ dump(articles) }}

{% for article in articles %}
    <a href="/article/{{ article.slug }}">
         {{ article.title }}
    </a>
{% endfor %}

The variables will only be dumped if Twig's debug setting (in config.yml) is true. By default this means
that the variables will be dumped in the dev environment but not the prod environment.



Template Formats
Templates are a generic way to render content in any format. And while in most cases you'll use templates
to render HTML content, a template can just as easily generate JavaScript, CSS, XML or any other format
you can dream of.
For example, the same "resource" is often rendered in several different formats. To render an article index
page in XML, simply include the format in the template name:

    • XML template name: AcmeArticleBundle:Article:index.xml.twig
    • XML template filename: index.xml.twig

In reality, this is nothing more than a naming convention and the template isn't actually rendered
differently based on its format.
In many cases, you may want to allow a single controller to render multiple different formats based on
the "request format". For that reason, a common pattern is to do the following:

public function indexAction()                                                                                          Listing
                                                                                                                       11-39
{
    $format = $this->getRequest()->getRequestFormat();

     return $this->render('AcmeBlogBundle:Blog:index.'.$format.'.twig');
}



PDF brought to you by                                                 Chapter 11: Creating and using Templates | 109
generated on June 20, 2012
The getRequestFormat on the Request object defaults to html, but can return any other format based
          on the format requested by the user. The request format is most often managed by the routing, where a
          route can be configured so that /contact sets the request format to html while /contact.xml sets the
          format to xml. For more information, see the Advanced Example in the Routing chapter.
          To create links that include the format parameter, include a _format key in the parameter hash:

Listing   <a href="{{ path('article_show', {'id': 123, '_format': 'pdf'}) }}">
11-40
               PDF Version
          </a>



          Final Thoughts
          The templating engine in Symfony is a powerful tool that can be used each time you need to generate
          presentational content in HTML, XML or any other format. And though templates are a common way to
          generate content in a controller, their use is not mandatory. The Response object returned by a controller
          can be created with or without the use of a template:

Listing   // creates a Response object whose content is the rendered template
11-41
          $response = $this->render('AcmeArticleBundle:Article:index.html.twig');

          // creates a Response object whose content is simple text
          $response = new Response('response content');

          Symfony's templating engine is very flexible and two different template renderers are available by default:
          the traditional PHP templates and the sleek and powerful Twig templates. Both support a template
          hierarchy and come packaged with a rich set of helper functions capable of performing the most common
          tasks.
          Overall, the topic of templating should be thought of as a powerful tool that's at your disposal. In some
          cases, you may not need to render a template, and in Symfony2, that's absolutely fine.



          Learn more from the Cookbook
            • How to use PHP instead of Twig for Templates
            • How to customize Error Pages
            • How to write a custom Twig Extension




          PDF brought to you by                                                Chapter 11: Creating and using Templates | 110
          generated on June 20, 2012
Chapter 12
                                 Databases and Doctrine

Let's face it, one of the most common and challenging tasks for any application involves persisting and
reading information to and from a database. Fortunately, Symfony comes integrated with Doctrine1, a
library whose sole goal is to give you powerful tools to make this easy. In this chapter, you'll learn the
basic philosophy behind Doctrine and see how easy working with a database can be.


              Doctrine is totally decoupled from Symfony and using it is optional. This chapter is all about
              the Doctrine ORM, which aims to let you map objects to a relational database (such as MySQL,
              PostgreSQL or Microsoft SQL). If you prefer to use raw database queries, this is easy, and explained
              in the "How to use Doctrine's DBAL Layer" cookbook entry.
              You can also persist data to MongoDB2 using Doctrine ODM library. For more information, read
              the "DoctrineMongoDBBundle" documentation.



A Simple Example: A Product
The easiest way to understand how Doctrine works is to see it in action. In this section, you'll configure
your database, create a Product object, persist it to the database and fetch it back out.


              Code along with the example
              If you want to follow along with the example in this chapter, create an AcmeStoreBundle via:

              php app/console generate:bundle --namespace=Acme/StoreBundle                                                Listing
                                                                                                                           12-1




1. http://www.doctrine-project.org/
2. http://www.mongodb.org/


PDF brought to you by                                                          Chapter 12: Databases and Doctrine | 111
generated on June 20, 2012
Configuring the Database
          Before you really begin, you'll need to configure your database connection information. By convention,
          this information is usually configured in an app/config/parameters.ini file:

Listing   ;app/config/parameters.ini
 12-2
          [parameters]
              database_driver   = pdo_mysql
              database_host     = localhost
              database_name     = test_project
              database_user     = root
              database_password = password



                          Defining the configuration via parameters.ini is just a convention. The parameters defined in
                          that file are referenced by the main configuration file when setting up Doctrine:

                Listing   doctrine:
                 12-3
                              dbal:
                                  driver:     %database_driver%
                                  host:       %database_host%
                                  dbname:     %database_name%
                                  user:       %database_user%
                                  password:   %database_password%

                          By separating the database information into a separate file, you can easily keep different versions
                          of the file on each server. You can also easily store database configuration (or any sensitive
                          information) outside of your project, like inside your Apache configuration, for example. For more
                          information, see How to Set External Parameters in the Service Container.

          Now that Doctrine knows about your database, you can have it create the database for you:

Listing   php app/console doctrine:database:create
 12-4



          Creating an Entity Class
          Suppose you're building an application where products need to be displayed. Without even thinking
          about Doctrine or databases, you already know that you need a Product object to represent those
          products. Create this class inside the Entity directory of your AcmeStoreBundle:

Listing   // src/Acme/StoreBundle/Entity/Product.php
 12-5
          namespace AcmeStoreBundleEntity;

          class Product
          {
              protected $name;

               protected $price;

               protected $description;
          }

          The class - often called an "entity", meaning a basic class that holds data - is simple and helps fulfill the
          business requirement of needing products in your application. This class can't be persisted to a database
          yet - it's just a simple PHP class.




          PDF brought to you by                                                           Chapter 12: Databases and Doctrine | 112
          generated on June 20, 2012
Once you learn the concepts behind Doctrine, you can have Doctrine create this entity class for
              you:

              php app/console doctrine:generate:entity --entity="AcmeStoreBundle:Product"                              Listing
                                                                                                                        12-6
              --fields="name:string(255) price:float description:text"




Add Mapping Information
Doctrine allows you to work with databases in a much more interesting way than just fetching rows of
a column-based table into an array. Instead, Doctrine allows you to persist entire objects to the database
and fetch entire objects out of the database. This works by mapping a PHP class to a database table, and
the properties of that PHP class to columns on the table:




For Doctrine to be able to do this, you just have to create "metadata", or configuration that tells Doctrine
exactly how the Product class and its properties should be mapped to the database. This metadata can
be specified in a number of different formats including YAML, XML or directly inside the Product class
via annotations:


              A bundle can accept only one metadata definition format. For example, it's not possible to mix
              YAML metadata definitions with annotated PHP entity class definitions.


// src/Acme/StoreBundle/Entity/Product.php                                                                             Listing
                                                                                                                        12-7
namespace AcmeStoreBundleEntity;

use DoctrineORMMapping as ORM;

/**
  * @ORMEntity
  * @ORMTable(name="product")
  */
class Product
{
     /**
      * @ORMId
      * @ORMColumn(type="integer")
      * @ORMGeneratedValue(strategy="AUTO")
      */
     protected $id;

     /**
      * @ORMColumn(type="string", length=100)
      */


PDF brought to you by                                                       Chapter 12: Databases and Doctrine | 113
generated on June 20, 2012
protected $name;

       /**
        * @ORMColumn(type="decimal", scale=2)
        */
       protected $price;

       /**
        * @ORMColumn(type="text")
        */
       protected $description;
}



                   The table name is optional and if omitted, will be determined automatically based on the name of
                   the entity class.


Doctrine allows you to choose from a wide variety of different field types, each with their own options.
For information on the available field types, see the Doctrine Field Types Reference section.

      You can also check out Doctrine's Basic Mapping Documentation3 for all details about mapping information.
      If you use annotations, you'll need to prepend all annotations with ORM (e.g. ORMColumn(..)), which is not
      shown in Doctrine's documentation. You'll also need to include the use DoctrineORMMapping as ORM;
      statement, which imports the ORM annotations prefix.



                   Be careful that your class name and properties aren't mapped to a protected SQL keyword (such
                   as group or user). For example, if your entity class name is Group, then, by default, your table
                   name will be group, which will cause an SQL error in some engines. See Doctrine's Reserved
                   SQL keywords documentation4 on how to properly escape these names. Alternatively, if you're
                   free to choose your database schema, simply map to a different table name or column name. See
                   Doctrine's Persistent classes5 and Property Mapping6 documentation.



                   When using another library or program (ie. Doxygen) that uses annotations, you should place the
                   @IgnoreAnnotation annotation on the class to indicate which annotations Symfony should ignore.
                   For example, to prevent the @fn annotation from throwing an exception, add the following:

         Listing   /**
          12-8
                    * @IgnoreAnnotation("fn")
                    */
                   class Product




Generating Getters and Setters
Even though Doctrine now knows how to persist a Product object to the database, the class itself isn't
really useful yet. Since Product is just a regular PHP class, you need to create getter and setter methods
(e.g. getName(), setName()) in order to access its properties (since the properties are protected).
Fortunately, Doctrine can do this for you by running:


3.   http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/reference/basic-mapping.html
4.   http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/reference/basic-mapping.html#quoting-reserved-words
5.   http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/reference/basic-mapping.html#persistent-classes
6.   http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/reference/basic-mapping.html#property-mapping


PDF brought to you by                                                                          Chapter 12: Databases and Doctrine | 114
generated on June 20, 2012
php app/console doctrine:generate:entities Acme/StoreBundle/Entity/Product                                               Listing
                                                                                                                          12-9

This command makes sure that all of the getters and setters are generated for the Product class. This is
a safe command - you can run it over and over again: it only generates getters and setters that don't exist
(i.e. it doesn't replace your existing methods).


              More about doctrine:generate:entities
              With the doctrine:generate:entities command you can:

                       • generate getters and setters,
                       • generate repository classes configured with the
                               @ORMEntity(repositoryClass="...") annotation,

                       • generate the appropriate constructor for 1:n and n:m relations.

              The doctrine:generate:entities command saves a backup of the original Product.php named
              Product.php~. In some cases, the presence of this file can cause a "Cannot redeclare class" error.
              It can be safely removed.
              Note that you don't need to use this command. Doctrine doesn't rely on code generation. Like with
              normal PHP classes, you just need to make sure that your protected/private properties have getter
              and setter methods. Since this is a common thing to do when using Doctrine, this command was
              created.

You can also generate all known entities (i.e. any PHP class with Doctrine mapping information) of a
bundle or an entire namespace:

php app/console doctrine:generate:entities AcmeStoreBundle                                                               Listing
                                                                                                                         12-10
php app/console doctrine:generate:entities Acme



              Doctrine doesn't care whether your properties are protected or private, or whether or not you
              have a getter or setter function for a property. The getters and setters are generated here only
              because you'll need them to interact with your PHP object.


Creating the Database Tables/Schema
You now have a usable Product class with mapping information so that Doctrine knows exactly how to
persist it. Of course, you don't yet have the corresponding product table in your database. Fortunately,
Doctrine can automatically create all the database tables needed for every known entity in your
application. To do this, run:

php app/console doctrine:schema:update --force                                                                           Listing
                                                                                                                         12-11




              Actually, this command is incredibly powerful. It compares what your database should look like
              (based on the mapping information of your entities) with how it actually looks, and generates the
              SQL statements needed to update the database to where it should be. In other words, if you add a
              new property with mapping metadata to Product and run this task again, it will generate the "alter
              table" statement needed to add that new column to the existing product table.
              An even better way to take advantage of this functionality is via migrations, which allow you to
              generate these SQL statements and store them in migration classes that can be run systematically
              on your production server in order to track and migrate your database schema safely and reliably.



PDF brought to you by                                                         Chapter 12: Databases and Doctrine | 115
generated on June 20, 2012
Your database now has a fully-functional product table with columns that match the metadata you've
        specified.

        Persisting Objects to 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:


              1
Listing Listing   // src/Acme/StoreBundle/Controller/DefaultController.php
12-12 12-13
           2      use AcmeStoreBundleEntityProduct;
           3      use SymfonyComponentHttpFoundationResponse;
           4      // ...
           5
           6      public function createAction()
           7      {
           8          $product = new Product();
           9          $product->setName('A Foo Bar');
          10          $product->setPrice('19.99');
          11          $product->setDescription('Lorem ipsum dolor');
          12
          13          $em = $this->getDoctrine()->getEntityManager();
          14          $em->persist($product);
          15          $em->flush();
          16
          17          return new Response('Created product id '.$product->getId());
          18      }




                       If you're following along with this example, you'll need to create a route that points to this action
                       to see it work.


        Let's walk through this example:

             • lines 8-11 In this section, you instantiate and work with the $product object like any other,
               normal PHP object;
             • line 13 This line fetches Doctrine's entity manager object, which is responsible for handling
               the process of persisting and fetching objects to and from the database;
             • line 14 The persist() method tells Doctrine to "manage" the $product object. This does not
               actually cause a query to be made to the database (yet).
             • line 15 When the flush() method is called, Doctrine looks through all of the objects that it's
               managing to see if they need to be persisted to the database. In this example, the $product
               object has not been persisted yet, so the entity manager executes an INSERT query and a row is
               created in the product table.


                       In fact, since Doctrine is aware of all your managed entities, when you call the flush() method,
                       it calculates an overall changeset and executes the most efficient query/queries possible. For
                       example, if you persist a total of 100 Product objects and then subsequently call flush(), Doctrine
                       will create a single prepared statement and re-use it for each insert. This pattern is called Unit of
                       Work, and it's used because it's fast and efficient.




        PDF brought to you by                                                            Chapter 12: Databases and Doctrine | 116
        generated on June 20, 2012
When creating or updating objects, the workflow is always the same. In the next section, you'll see
how Doctrine is smart enough to automatically issue an UPDATE query if the record already exists in the
database.


              Doctrine provides a library that allows you to programmatically load testing data into your project
              (i.e. "fixture data"). For information, see DoctrineFixturesBundle.



Fetching Objects from the Database
Fetching an object back out of the database is even easier. For example, suppose you've configured a
route to display a specific Product based on its id value:

public function showAction($id)                                                                                          Listing
                                                                                                                         12-14
{
    $product = $this->getDoctrine()
        ->getRepository('AcmeStoreBundle:Product')
        ->find($id);

     if (!$product) {
         throw $this->createNotFoundException('No product found for id '.$id);
     }

     // do something, like pass the $product object into a template
}

When you query for a particular type of object, you always use what's known as its "repository". You can
think of a repository as a PHP class whose only job is to help you fetch entities of a certain class. You can
access the repository object for an entity class via:

$repository = $this->getDoctrine()                                                                                       Listing
                                                                                                                         12-15
    ->getRepository('AcmeStoreBundle:Product');



              The AcmeStoreBundle:Product string is a shortcut you can use anywhere in Doctrine instead of
              the full class name of the entity (i.e. AcmeStoreBundleEntityProduct). As long as your entity
              lives under the Entity namespace of your bundle, this will work.

Once you have your repository, you have access to all sorts of helpful methods:

// query by the primary key (usually "id")                                                                               Listing
                                                                                                                         12-16
$product = $repository->find($id);

// dynamic method names to find based on a column value
$product = $repository->findOneById($id);
$product = $repository->findOneByName('foo');

// find *all* products
$products = $repository->findAll();

// find a group of products based on an arbitrary column value
$products = $repository->findByPrice(19.99);




PDF brought to you by                                                         Chapter 12: Databases and Doctrine | 117
generated on June 20, 2012
Of course, you can also issue complex queries, which you'll learn more about in the Querying for
                        Objects section.


          You can also take advantage of the useful findBy and findOneBy methods to easily fetch objects based
          on multiple conditions:

Listing   // query for one product matching be name and price
12-17
          $product = $repository->findOneBy(array('name' => 'foo', 'price' => 19.99));

          // query for all products matching the name, ordered by price
          $product = $repository->findBy(
              array('name' => 'foo'),
              array('price' => 'ASC')
          );



                        When you render any page, you can see how many queries were made in the bottom right corner
                        of the web debug toolbar.




                        If you click the icon, the profiler will open, showing you the exact queries that were made.


          Updating an Object
          Once you've fetched an object from Doctrine, updating it is easy. Suppose you have a route that maps a
          product id to an update action in a controller:

Listing   public function updateAction($id)
12-18
          {
              $em = $this->getDoctrine()->getEntityManager();
              $product = $em->getRepository('AcmeStoreBundle:Product')->find($id);

               if (!$product) {
                   throw $this->createNotFoundException('No product found for id '.$id);
               }

               $product->setName('New product name!');
               $em->flush();



          PDF brought to you by                                                          Chapter 12: Databases and Doctrine | 118
          generated on June 20, 2012
return $this->redirect($this->generateUrl('homepage'));
}

Updating an object involves just three steps:
    1. fetching the object from Doctrine;
    2. modifying the object;
    3. calling flush() on the entity manager
Notice that calling $em->persist($product) isn't necessary. Recall that this method simply tells
Doctrine to manage or "watch" the $product object. In this case, since you fetched the $product object
from Doctrine, it's already managed.

Deleting an Object
Deleting an object is very similar, but requires a call to the remove() method of the entity manager:

$em->remove($product);                                                                                              Listing
                                                                                                                    12-19
$em->flush();

As you might expect, the remove() method notifies Doctrine that you'd like to remove the given entity
from the database. The actual DELETE query, however, isn't actually executed until the flush() method
is called.



Querying for Objects
You've already seen how the repository object allows you to run basic queries without any work:

$repository->find($id);                                                                                             Listing
                                                                                                                    12-20

$repository->findOneByName('Foo');

Of course, Doctrine also allows you to write more complex queries using the Doctrine Query Language
(DQL). DQL is similar to SQL except that you should imagine that you're querying for one or more
objects of an entity class (e.g. Product) instead of querying for rows on a table (e.g. product).
When querying in Doctrine, you have two options: writing pure Doctrine queries or using Doctrine's
Query Builder.

Querying for Objects with DQL
Imagine that you want to query for products, but only return products that cost more than 19.99,
ordered from cheapest to most expensive. From inside a controller, do the following:

$em = $this->getDoctrine()->getEntityManager();                                                                     Listing
                                                                                                                    12-21
$query = $em->createQuery(
    'SELECT p FROM AcmeStoreBundle:Product p WHERE p.price > :price ORDER BY p.price ASC'
)->setParameter('price', '19.99');

$products = $query->getResult();

If you're comfortable with SQL, then DQL should feel very natural. The biggest difference is that you
need to think in terms of "objects" instead of rows in a database. For this reason, you select from
AcmeStoreBundle:Product and then alias it as p.
The getResult() method returns an array of results. If you're querying for just one object, you can use
the getSingleResult() method instead:



PDF brought to you by                                                    Chapter 12: Databases and Doctrine | 119
generated on June 20, 2012
Listing   $product = $query->getSingleResult();
12-22




                           The getSingleResult() method throws a DoctrineORMNoResultException exception if no
                           results are returned and a DoctrineORMNonUniqueResultException if more than one result is
                           returned. If you use this method, you may need to wrap it in a try-catch block and ensure that only
                           one result is returned (if you're querying on something that could feasibly return more than one
                           result):

                 Listing   $query = $em->createQuery('SELECT ....')
                 12-23
                               ->setMaxResults(1);

                           try {
                               $product = $query->getSingleResult();
                           } catch (DoctrineOrmNoResultException $e) {
                               $product = null;
                           }
                           // ...



          The DQL syntax is incredibly powerful, allowing you to easily join between entities (the topic of
          relations will be covered later), group, etc. For more information, see the official Doctrine Doctrine Query
          Language7 documentation.


                           Setting Parameters
                           Take note of the setParameter() method. When working with Doctrine, it's always a good idea
                           to set any external values as "placeholders", which was done in the above query:

                 Listing   ... WHERE p.price > :price ...
                 12-24

                           You can then set the value of the price placeholder by calling the setParameter() method:

                 Listing   ->setParameter('price', '19.99')
                 12-25

                           Using parameters instead of placing values directly in the query string is done to prevent SQL
                           injection attacks and should always be done. If you're using multiple parameters, you can set their
                           values at once using the setParameters() method:

                 Listing   ->setParameters(array(
                 12-26
                               'price' => '19.99',
                               'name' => 'Foo',
                           ))




          Using Doctrine's Query Builder
          Instead of writing the queries directly, you can alternatively use Doctrine's QueryBuilder to do the same
          job using a nice, object-oriented interface. If you use an IDE, you can also take advantage of auto-
          completion as you type the method names. From inside a controller:

Listing   $repository = $this->getDoctrine()
12-27
              ->getRepository('AcmeStoreBundle:Product');

          $query = $repository->createQueryBuilder('p')
              ->where('p.price > :price')

          7. http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/reference/dql-doctrine-query-language.html


          PDF brought to you by                                                                        Chapter 12: Databases and Doctrine | 120
          generated on June 20, 2012
->setParameter('price', '19.99')
     ->orderBy('p.price', 'ASC')
     ->getQuery();

$products = $query->getResult();

The QueryBuilder object contains every method necessary to build your query. By calling the
getQuery() method, the query builder returns a normal Query object, which is the same object you built
directly in the previous section.
For more information on Doctrine's Query Builder, consult Doctrine's Query Builder8 documentation.

Custom Repository Classes
In the previous sections, you began constructing and using more complex queries from inside a
controller. In order to isolate, test and reuse these queries, it's a good idea to create a custom repository
class for your entity and add methods with your query logic there.
To do this, add the name of the repository class to your mapping definition.

// src/Acme/StoreBundle/Entity/Product.php                                                                                                 Listing
                                                                                                                                           12-28
namespace AcmeStoreBundleEntity;

use DoctrineORMMapping as ORM;

/**
  * @ORMEntity(repositoryClass="AcmeStoreBundleRepositoryProductRepository")
  */
class Product
{
     //...
}

Doctrine can generate the repository class for you by running the same command used earlier to generate
the missing getter and setter methods:

php app/console doctrine:generate:entities Acme                                                                                            Listing
                                                                                                                                           12-29

Next, add a new method - findAllOrderedByName() - to the newly generated repository class. This
method will query for all of the Product entities, ordered alphabetically.

// src/Acme/StoreBundle/Repository/ProductRepository.php                                                                                   Listing
                                                                                                                                           12-30
namespace AcmeStoreBundleRepository;

use DoctrineORMEntityRepository;

class ProductRepository extends EntityRepository
{
    public function findAllOrderedByName()
    {
        return $this->getEntityManager()
            ->createQuery('SELECT p FROM AcmeStoreBundle:Product p ORDER BY p.name ASC')
            ->getResult();
    }
}




8. http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/reference/query-builder.html


PDF brought to you by                                                                           Chapter 12: Databases and Doctrine | 121
generated on June 20, 2012
The entity manager can be accessed via $this->getEntityManager() from inside the repository.



          You can use this new method just like the default finder methods of the repository:

Listing   $em = $this->getDoctrine()->getEntityManager();
12-31
          $products = $em->getRepository('AcmeStoreBundle:Product')
                      ->findAllOrderedByName();



                        When using a custom repository class, you still have access to the default finder methods such as
                        find() and findAll().




          Entity Relationships/Associations
          Suppose that the products in your application all belong to exactly one "category". In this case, you'll
          need a Category object and a way to relate a Product object to a Category object. Start by creating the
          Category entity. Since you know that you'll eventually need to persist the class through Doctrine, you
          can let Doctrine create the class for you.

Listing   php app/console doctrine:generate:entity --entity="AcmeStoreBundle:Category"
12-32
          --fields="name:string(255)"

          This task generates the Category entity for you, with an id field, a name field and the associated getter
          and setter functions.

          Relationship Mapping Metadata
          To relate the Category and Product entities, start by creating a products property on the Category class:

Listing   // src/Acme/StoreBundle/Entity/Category.php
12-33
          // ...
          use DoctrineCommonCollectionsArrayCollection;

          class Category
          {
              // ...

               /**
                * @ORMOneToMany(targetEntity="Product", mappedBy="category")
                */
               protected $products;

               public function __construct()
               {
                   $this->products = new ArrayCollection();
               }
          }

          First, since a Category object will relate to many Product objects, a products array property is added
          to hold those Product objects. Again, this isn't done because Doctrine needs it, but instead because it
          makes sense in the application for each Category to hold an array of Product objects.



          PDF brought to you by                                                        Chapter 12: Databases and Doctrine | 122
          generated on June 20, 2012
The code in the __construct() method is important because Doctrine requires the $products
              property to be an ArrayCollection object. This object looks and acts almost exactly like an array,
              but has some added flexibility. If this makes you uncomfortable, don't worry. Just imagine that it's
              an array and you'll be in good shape.



              The targetEntity value in the decorator used above can reference any entity with a valid namespace,
              not just entities defined in the same class. To relate to an entity defined in a different class or
              bundle, enter a full namespace as the targetEntity.

Next, since each Product class can relate to exactly one Category object, you'll want to add a $category
property to the Product class:

// src/Acme/StoreBundle/Entity/Product.php                                                                                Listing
                                                                                                                          12-34
// ...

class Product
{
    // ...

     /**
      * @ORMManyToOne(targetEntity="Category", inversedBy="products")
      * @ORMJoinColumn(name="category_id", referencedColumnName="id")
      */
     protected $category;
}

Finally, now that you've added a new property to both the Category and Product classes, tell Doctrine
to generate the missing getter and setter methods for you:

php app/console doctrine:generate:entities Acme                                                                           Listing
                                                                                                                          12-35

Ignore the Doctrine metadata for a moment. You now have two classes - Category and Product with a
natural one-to-many relationship. The Category class holds an array of Product objects and the Product
object can hold one Category object. In other words - you've built your classes in a way that makes sense
for your needs. The fact that the data needs to be persisted to a database is always secondary.
Now, look at the metadata above the $category property on the Product class. The information here
tells doctrine that the related class is Category and that it should store the id of the category record on
a category_id field that lives on the product table. In other words, the related Category object will be
stored on the $category property, but behind the scenes, Doctrine will persist this relationship by storing
the category's id value on a category_id column of the product table.




PDF brought to you by                                                          Chapter 12: Databases and Doctrine | 123
generated on June 20, 2012
The metadata above the $products property of the Category object is less important, and simply tells
          Doctrine to look at the Product.category property to figure out how the relationship is mapped.
          Before you continue, be sure to tell Doctrine to add the new category table, and product.category_id
          column, and new foreign key:

Listing   php app/console doctrine:schema:update --force
12-36




                        This task should only be really used during development. For a more robust method of
                        systematically updating your production database, read about Doctrine migrations.



          Saving Related Entities
          Now, let's see the code in action. Imagine you're inside a controller:

Listing   // ...
12-37
          use AcmeStoreBundleEntityCategory;
          use AcmeStoreBundleEntityProduct;

          PDF brought to you by                                                    Chapter 12: Databases and Doctrine | 124
          generated on June 20, 2012
use SymfonyComponentHttpFoundationResponse;
// ...

class DefaultController extends Controller
{
    public function createProductAction()
    {
        $category = new Category();
        $category->setName('Main Products');

           $product = new Product();
           $product->setName('Foo');
           $product->setPrice(19.99);
           // relate this product to the category
           $product->setCategory($category);

           $em = $this->getDoctrine()->getEntityManager();
           $em->persist($category);
           $em->persist($product);
           $em->flush();

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

Now, a single row is added to both the category and product tables. The product.category_id column
for the new product is set to whatever the id is of the new category. Doctrine manages the persistence of
this relationship for you.

Fetching Related Objects
When you need to fetch associated objects, your workflow looks just like it did before. First, fetch a
$product object and then access its related Category:

public function showAction($id)                                                                                    Listing
                                                                                                                   12-38
{
    $product = $this->getDoctrine()
        ->getRepository('AcmeStoreBundle:Product')
        ->find($id);

     $categoryName = $product->getCategory()->getName();

     // ...
}

In this example, you first query for a Product object based on the product's id. This issues a query for
just the product data and hydrates the $product object with that data. Later, when you call $product-
>getCategory()->getName(), Doctrine silently makes a second query to find the Category that's related
to this Product. It prepares the $category object and returns it to you.




PDF brought to you by                                                   Chapter 12: Databases and Doctrine | 125
generated on June 20, 2012
What's important is the fact that you have easy access to the product's related category, but the category
          data isn't actually retrieved until you ask for the category (i.e. it's "lazily loaded").
          You can also query in the other direction:

Listing   public function showProductAction($id)
12-39
          {
              $category = $this->getDoctrine()
                  ->getRepository('AcmeStoreBundle:Category')
                  ->find($id);

               $products = $category->getProducts();

               // ...
          }

          In this case, the same things occurs: you first query out for a single Category object, and then Doctrine
          makes a second query to retrieve the related Product objects, but only once/if you ask for them (i.e. when
          you call ->getProducts()). The $products variable is an array of all Product objects that relate to the
          given Category object via their category_id value.




          PDF brought to you by                                                    Chapter 12: Databases and Doctrine | 126
          generated on June 20, 2012
Relationships and Proxy Classes
              This "lazy loading" is possible because, when necessary, Doctrine returns a "proxy" object in place
              of the true object. Look again at the above example:

              $product = $this->getDoctrine()                                                                            Listing
                                                                                                                         12-40
                  ->getRepository('AcmeStoreBundle:Product')
                  ->find($id);

              $category = $product->getCategory();

              // prints "ProxiesAcmeStoreBundleEntityCategoryProxy"
              echo get_class($category);

              This proxy object extends the true Category object, and looks and acts exactly like it. The
              difference is that, by using a proxy object, Doctrine can delay querying for the real Category data
              until you actually need that data (e.g. until you call $category->getName()).
              The proxy classes are generated by Doctrine and stored in the cache directory. And though you'll
              probably never even notice that your $category object is actually a proxy object, it's important to
              keep in mind.
              In the next section, when you retrieve the product and category data all at once (via a join),
              Doctrine will return the true Category object, since nothing needs to be lazily loaded.


Joining to Related Records
In the above examples, two queries were made - one for the original object (e.g. a Category) and one for
the related object(s) (e.g. the Product objects).


              Remember that you can see all of the queries made during a request via the web debug toolbar.



Of course, if you know up front that you'll need to access both objects, you can avoid the second query
by issuing a join in the original query. Add the following method to the ProductRepository class:

// src/Acme/StoreBundle/Repository/ProductRepository.php                                                                 Listing
                                                                                                                         12-41

public function findOneByIdJoinedToCategory($id)
{
    $query = $this->getEntityManager()
        ->createQuery('
            SELECT p, c FROM AcmeStoreBundle:Product p
            JOIN p.category c
            WHERE p.id = :id'
        )->setParameter('id', $id);

     try {
         return $query->getSingleResult();
     } catch (DoctrineORMNoResultException $e) {
         return null;
     }
}

Now, you can use this method in your controller to query for a Product object and its related Category
with just one query:


PDF brought to you by                                                         Chapter 12: Databases and Doctrine | 127
generated on June 20, 2012
Listing   public function showAction($id)
12-42
          {
              $product = $this->getDoctrine()
                  ->getRepository('AcmeStoreBundle:Product')
                  ->findOneByIdJoinedToCategory($id);

               $category = $product->getCategory();

               // ...
          }


          More Information on Associations
          This section has been an introduction to one common type of entity relationship, the one-to-many
          relationship. For more advanced details and examples of how to use other types of relations (e.g. one-
          to-one, many-to-many), see Doctrine's Association Mapping Documentation9.


                        If you're using annotations, you'll need to prepend all annotations with ORM (e.g. ORMOneToMany),
                        which is not reflected in Doctrine's documentation. You'll also need to include the use
                        DoctrineORMMapping as ORM; statement, which imports the ORM annotations prefix.




          Configuration
          Doctrine is highly configurable, though you probably won't ever need to worry about most of its options.
          To find out more about configuring Doctrine, see the Doctrine section of the reference manual.



          Lifecycle Callbacks
          Sometimes, you need to perform an action right before or after an entity is inserted, updated, or deleted.
          These types of actions are known as "lifecycle" callbacks, as they're callback methods that you need to
          execute during different stages of the lifecycle of an entity (e.g. the entity is inserted, updated, deleted,
          etc).
          If you're using annotations for your metadata, start by enabling the lifecycle callbacks. This is not
          necessary if you're using YAML or XML for your mapping:

Listing   /**
12-43
            * @ORMEntity()
            * @ORMHasLifecycleCallbacks()
            */
          class Product
          {
               // ...
          }

          Now, you can tell Doctrine to execute a method on any of the available lifecycle events. For example,
          suppose you want to set a created date column to the current date, only when the entity is first persisted
          (i.e. inserted):

Listing   /**
12-44
           * @ORMPrePersist
           */


          9. http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/reference/association-mapping.html


          PDF brought to you by                                                                        Chapter 12: Databases and Doctrine | 128
          generated on June 20, 2012
public function setCreatedValue()
{
    $this->created = new DateTime();
}



              The above example assumes that you've created and mapped a created property (not shown here).



Now, right before the entity is first persisted, Doctrine will automatically call this method and the
created field will be set to the current date.
This can be repeated for any of the other lifecycle events, which include:

  •   preRemove
  •   postRemove
  •   prePersist
  •   postPersist
  •   preUpdate
  •   postUpdate
  •   postLoad
  •   loadClassMetadata

For more information on what these lifecycle events mean and lifecycle callbacks in general, see
Doctrine's Lifecycle Events documentation10


              Lifecycle Callbacks and Event Listeners
              Notice that the setCreatedValue() method receives no arguments. This is always the case
              for lifecycle callbacks and is intentional: lifecycle callbacks should be simple methods that are
              concerned with internally transforming data in the entity (e.g. setting a created/updated field,
              generating a slug value).
              If you need to do some heavier lifting - like perform logging or send an email - you should register
              an external class as an event listener or subscriber and give it access to whatever resources you
              need. For more information, see Registering Event Listeners and Subscribers.



Doctrine Extensions: Timestampable, Sluggable, etc.
Doctrine is quite flexible, and a number of third-party extensions are available that allow you to
easily perform repeated and common tasks on your entities. These include thing such as Sluggable,
Timestampable, Loggable, Translatable, and Tree.
For more information on how to find and use these extensions, see the cookbook article about using
common Doctrine extensions.



Doctrine Field Types Reference
Doctrine comes with a large number of field types available. Each of these maps a PHP data type to a
specific column type in whatever database you're using. The following types are supported in Doctrine:

  • Strings

10. http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/reference/events.html#lifecycle-events


PDF brought to you by                                                                        Chapter 12: Databases and Doctrine | 129
generated on June 20, 2012
• string (used for shorter strings)
                   • text (used for larger strings)

            • Numbers

                   •   integer
                   •   smallint
                   •   bigint
                   •   decimal
                   •   float

            • Dates and Times (use a DateTime11 object for these fields in PHP)

                   • date
                   • time
                   • datetime

            • Other Types

                   • boolean
                   • object (serialized and stored in a CLOB field)
                   • array (serialized and stored in a CLOB field)

          For more information, see Doctrine's Mapping Types documentation12.

          Field Options
          Each field can have a set of options applied to it. The available options include type (defaults to string),
          name, length, unique and nullable. Take a few examples:

Listing   /**
12-45
           * A string field with length 255 that cannot be null
           * (reflecting the default values for the "type", "length" and *nullable* options)
           *
           * @ORMColumn()
           */
          protected $name;

          /**
           * A string field of length 150 that persists to an "email_address" column
           * and has a unique index.
           *
           * @ORMColumn(name="email_address", unique=true, length=150)
           */
          protected $email;



                        There are a few more options not listed here. For more details, see Doctrine's Property Mapping
                        documentation13




          11. http://php.net/manual/en/class.datetime.php
          12. http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/reference/basic-mapping.html#doctrine-mapping-types
          13. http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/reference/basic-mapping.html#property-mapping


          PDF brought to you by                                                                        Chapter 12: Databases and Doctrine | 130
          generated on June 20, 2012
Console Commands
The Doctrine2 ORM integration offers several console commands under the doctrine namespace. To
view the command list you can run the console without any arguments:

php app/console                                                                                                      Listing
                                                                                                                     12-46

A list of available command will print out, many of which start with the doctrine: prefix. You can find
out more information about any of these commands (or any Symfony command) by running the help
command. For example, to get details about the doctrine:database:create task, run:

php app/console help doctrine:database:create                                                                        Listing
                                                                                                                     12-47

Some notable or interesting tasks include:

  • doctrine:ensure-production-settings - checks to see if the current environment is
    configured efficiently for production. This should always be run in the prod environment:

      php app/console doctrine:ensure-production-settings --env=prod                                       Listing
                                                                                                           12-48

  • doctrine:mapping:import - allows Doctrine to introspect an existing database and create
    mapping information. For more information, see How to generate Entities from an Existing
    Database.
  • doctrine:mapping:info - tells you all of the entities that Doctrine is aware of and whether or
    not there are any basic errors with the mapping.
  • doctrine:query:dql and doctrine:query:sql - allow you to execute DQL or SQL queries
    directly from the command line.


              To be able to load data fixtures to your database, you will need to have the
              DoctrineFixturesBundle bundle installed. To learn how to do it, read the
              "DoctrineFixturesBundle" entry of the documentation.



Summary
With Doctrine, you can focus on your objects and how they're useful in your application and worry about
database persistence second. This is because Doctrine allows you to use any PHP object to hold your data
and relies on mapping metadata information to map an object's data to a particular database table.
And even though Doctrine revolves around a simple concept, it's incredibly powerful, allowing you to
create complex queries and subscribe to events that allow you to take different actions as objects go
through their persistence lifecycle.
For more information about Doctrine, see the Doctrine section of the cookbook, which includes the
following articles:

  • DoctrineFixturesBundle
  • Doctrine Extensions: Timestampable, Sluggable, Translatable, etc.




PDF brought to you by                                                   Chapter 12: Databases and Doctrine | 131
generated on June 20, 2012
Chapter 13
                                              Databases and Propel

          Let's face it, one of the most common and challenging tasks for any application involves persisting and
          reading information to and from a database. Symfony2 does not come integrated with any ORMs but the
          Propel integration is easy. To get started, read Working With Symfony21.



          A Simple Example: A Product
          In this section, you'll configure your database, create a Product object, persist it to the database and fetch
          it back out.


                        Code along with the example
                        If you want to follow along with the example in this chapter, create an AcmeStoreBundle via: php
                        app/console generate:bundle --namespace=Acme/StoreBundle.


          Configuring the Database
          Before you can start, you'll need to configure your database connection information. By convention, this
          information is usually configured in an app/config/parameters.ini file:

Listing   ;app/config/parameters.ini
 13-1
          [parameters]
              database_driver   = mysql
              database_host     = localhost
              database_name     = test_project
              database_user     = root
              database_password = password
              database_charset = UTF8




          1. http://www.propelorm.org/cookbook/symfony2/working-with-symfony2.html#installation


          PDF brought to you by                                                                   Chapter 13: Databases and Propel | 132
          generated on June 20, 2012
Defining the configuration via parameters.ini is just a convention. The parameters defined in
              that file are referenced by the main configuration file when setting up Propel:

              propel:                                                                                                 Listing
                                                                                                                       13-2
                  dbal:
                      driver:     %database_driver%
                      user:       %database_user%
                      password:   %database_password%
                      dsn:
              %database_driver%:host=%database_host%;dbname=%database_name%;charset=%database_charset%



Now that Propel knows about your database, Symfony2 can create the database for you:

php app/console propel:database:create                                                                                Listing
                                                                                                                       13-3




              In this example, you have one configured connection, named default. If you want to configure
              more than one connection, read the PropelBundle configuration section.



Creating a Model Class
In the Propel world, ActiveRecord classes are known as models because classes generated by Propel
contain some business logic.


              For people who use Symfony2 with Doctrine2, models are equivalent to entities.



Suppose you're building an application where products need to be displayed. First, create a schema.xml
file inside the Resources/config directory of your AcmeStoreBundle:

<?xml version="1.0" encoding="UTF-8"?>                                                                                Listing
                                                                                                                       13-4
<database name="default" namespace="AcmeStoreBundleModel" defaultIdMethod="native">
    <table name="product">
        <column name="id" type="integer" required="true" primaryKey="true"
autoIncrement="true" />
        <column name="name" type="varchar" primaryString="true" size="100" />
        <column name="price" type="decimal" />
        <column name="description" type="longvarchar" />
    </table>
</database>


Building the Model
After creating your schema.xml, generate your model from it by running:

php app/console propel:model:build                                                                                    Listing
                                                                                                                       13-5

This generates each model class to quickly develop your application in the Model/ directory the
AcmeStoreBundle bundle.




PDF brought to you by                                                        Chapter 13: Databases and Propel | 133
generated on June 20, 2012
Creating the Database Tables/Schema
          Now you have a usable Product class and all you need to persist it. Of course, you don't yet have
          the corresponding product table in your database. Fortunately, Propel can automatically create all the
          database tables needed for every known model in your application. To do this, run:

Listing   php app/console propel:sql:build
 13-6

          php app/console propel:sql:insert --force

          Your database now has a fully-functional product table with columns that match the schema you've
          specified.


                        You can run the last three commands combined by using the following command: php app/
                        console propel:build --insert-sql.



          Persisting Objects to the Database
          Now that you have a Product object 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:

Listing   // src/Acme/StoreBundle/Controller/DefaultController.php
 13-7
          use AcmeStoreBundleModelProduct;
          use SymfonyComponentHttpFoundationResponse;
          // ...

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

               $product->save();

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

          In this piece of code, you instantiate and work with the $product object. When you call the save()
          method on it, you persist it to the database. No need to use other services, the object knows how to
          persist itself.


                        If you're following along with this example, you'll need to create a route that points to this action
                        to see it in action.



          Fetching Objects from the Database
          Fetching an object back from the database is even easier. For example, suppose you've configured a route
          to display a specific Product based on its id value:

Listing   use AcmeStoreBundleModelProductQuery;
 13-8




          PDF brought to you by                                                             Chapter 13: Databases and Propel | 134
          generated on June 20, 2012
public function showAction($id)
{
    $product = ProductQuery::create()
        ->findPk($id);

     if (!$product) {
         throw $this->createNotFoundException('No product found for id '.$id);
     }

     // do something, like pass the $product object into a template
}


Updating an Object
Once you've fetched an object from Propel, updating it is easy. Suppose you have a route that maps a
product id to an update action in a controller:

use AcmeStoreBundleModelProductQuery;                                                                            Listing
                                                                                                                     13-9

public function updateAction($id)
{
    $product = ProductQuery::create()
        ->findPk($id);

     if (!$product) {
         throw $this->createNotFoundException('No product found for id '.$id);
     }

     $product->setName('New product name!');
     $product->save();

     return $this->redirect($this->generateUrl('homepage'));
}

Updating an object involves just three steps:
    1. fetching the object from Propel;
    2. modifying the object;
    3. saving it.

Deleting an Object
Deleting an object is very similar, but requires a call to the delete() method on the object:

$product->delete();                                                                                                 Listing
                                                                                                                    13-10




Querying for Objects
Propel provides generated Query classes to run both basic and complex queries without any work:

AcmeStoreBundleModelProductQuery::create()->findPk($id);                                                        Listing
                                                                                                                    13-11

AcmeStoreBundleModelProductQuery::create()
    ->filterByName('Foo')
    ->findOne();

Imagine that you want to query for products which cost more than 19.99, ordered from cheapest to most
expensive. From inside a controller, do the following:


PDF brought to you by                                                      Chapter 13: Databases and Propel | 135
generated on June 20, 2012
Listing   $products = AcmeStoreBundleModelProductQuery::create()
13-12
              ->filterByPrice(array('min' => 19.99))
              ->orderByPrice()
              ->find();

          In one line, you get your products in a powerful oriented object way. No need to waste your time
          with SQL or whatever, Symfony2 offers fully object oriented programming and Propel respects the same
          philosophy by providing an awesome abstraction layer.
          If you want to reuse some queries, you can add your own methods to the ProductQuery class:

Listing   // src/Acme/StoreBundle/Model/ProductQuery.php
13-13

          class ProductQuery extends BaseProductQuery
          {
              public function filterByExpensivePrice()
              {
                  return $this
                      ->filterByPrice(array('min' => 1000))
              }
          }

          But note that Propel generates a lot of methods for you and a simple findAllOrderedByName() can be
          written without any effort:

Listing   AcmeStoreBundleModelProductQuery::create()
13-14
              ->orderByName()
              ->find();



          Relationships/Associations
          Suppose that the products in your application all belong to exactly one "category". In this case, you'll
          need a Category object and a way to relate a Product object to a Category object.
          Start by adding the category definition in your schema.xml:

Listing   <database name="default" namespace="AcmeStoreBundleModel" defaultIdMethod="native">
13-15
              <table name="product">
                  <column name="id" type="integer" required="true" primaryKey="true"
          autoIncrement="true" />
                  <column name="name" type="varchar" primaryString="true" size="100" />
                  <column name="price" type="decimal" />
                  <column name="description" type="longvarchar" />

                   <column name="category_id" type="integer" />
                   <foreign-key foreignTable="category">
                        <reference local="category_id" foreign="id" />
                   </foreign-key>
               </table>

              <table name="category">
                  <column name="id" type="integer" required="true" primaryKey="true"
          autoIncrement="true" />
                  <column name="name" type="varchar" primaryString="true" size="100" />
             </table>
          </database>

          Create the classes:



          PDF brought to you by                                                    Chapter 13: Databases and Propel | 136
          generated on June 20, 2012
php app/console propel:model:build                                                                                Listing
                                                                                                                  13-16

Assuming you have products in your database, you don't want lose them. Thanks to migrations, Propel
will be able to update your database without losing existing data.

php app/console propel:migration:generate-diff                                                                    Listing
                                                                                                                  13-17

php app/console propel:migration:migrate

Your database has been updated, you can continue to write your application.

Saving Related Objects
Now, let's see the code in action. Imagine you're inside a controller:

// ...                                                                                                            Listing
                                                                                                                  13-18
use AcmeStoreBundleModelCategory;
use AcmeStoreBundleModelProduct;
use SymfonyComponentHttpFoundationResponse;
// ...

class DefaultController extends Controller
{
    public function createProductAction()
    {
        $category = new Category();
        $category->setName('Main Products');

           $product = new Product();
           $product->setName('Foo');
           $product->setPrice(19.99);
           // relate this product to the category
           $product->setCategory($category);

           // save the whole
           $product->save();

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

Now, a single row is added to both the category and product tables. The product.category_id column
for the new product is set to whatever the id is of the new category. Propel manages the persistence of
this relationship for you.

Fetching Related Objects
When you need to fetch associated objects, your workflow looks just like it did before. First, fetch a
$product object and then access its related Category:

// ...                                                                                                            Listing
                                                                                                                  13-19
use AcmeStoreBundleModelProductQuery;

public function showAction($id)
{
    $product = ProductQuery::create()
        ->joinWithCategory()


PDF brought to you by                                                    Chapter 13: Databases and Propel | 137
generated on June 20, 2012
->findPk($id);

                  $categoryName = $product->getCategory()->getName();

                  // ...
          }

          Note, in the above example, only one query was made.

          More information on Associations
          You will find more information on relations by reading the dedicated chapter on Relationships2.



          Lifecycle Callbacks
          Sometimes, you need to perform an action right before or after an object is inserted, updated, or deleted.
          These types of actions are known as "lifecycle" callbacks or "hooks", as they're callback methods that you
          need to execute during different stages of the lifecycle of an object (e.g. the object is inserted, updated,
          deleted, etc).
          To add a hook, just add a new method to the object class:

Listing   // src/Acme/StoreBundle/Model/Product.php
13-20

          // ...

          class Product extends BaseProduct
          {
              public function preInsert(PropelPDO $con = null)
              {
                  // do something before the object is inserted
              }
          }

          Propel provides the following hooks:

              •   preInsert() code executed before insertion of a new object
              •   postInsert() code executed after insertion of a new object
              •   preUpdate() code executed before update of an existing object
              •   postUpdate() code executed after update of an existing object
              •   preSave() code executed before saving an object (new or existing)
              •   postSave() code executed after saving an object (new or existing)
              •   preDelete() code executed before deleting an object
              •   postDelete() code executed after deleting an object



          Behaviors
          All bundled behaviors in Propel are working with Symfony2. To get more information about how to use
          Propel behaviors, look at the Behaviors reference section.




          2. http://www.propelorm.org/documentation/04-relationships.html


          PDF brought to you by                                                       Chapter 13: Databases and Propel | 138
          generated on June 20, 2012
Commands
You should read the dedicated section for Propel commands in Symfony23.




3. http://www.propelorm.org/cookbook/symfony2/working-with-symfony2#commands


PDF brought to you by                                                          Chapter 13: Databases and Propel | 139
generated on June 20, 2012
Chapter 14
                                                        Testing

          Whenever you write a new line of code, you also potentially add new bugs. To build better and more
          reliable applications, you should test your code using both functional and unit tests.



          The PHPUnit Testing Framework
          Symfony2 integrates with an independent library - called PHPUnit - to give you a rich testing framework.
          This chapter won't cover PHPUnit itself, but it has its own excellent documentation1.


                        Symfony2 works with PHPUnit 3.5.11 or later.



          Each test - whether it's a unit test or a functional test - is a PHP class that should live in the Tests/
          subdirectory of your bundles. If you follow this rule, then you can run all of your application's tests with
          the following command:

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

          The -c option tells PHPUnit to look in the app/ directory for a configuration file. If you're curious about
          the PHPUnit options, check out the app/phpunit.xml.dist file.


                        Code coverage can be generated with the --coverage-html option.




          1. http://www.phpunit.de/manual/3.5/en/


          PDF brought to you by                                                                  Chapter 14: Testing | 140
          generated on June 20, 2012
Unit Tests
A unit test is usually a test against a specific PHP class. If you want to test the overall behavior of your
application, see the section about Functional 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/Utility/Calculator.php                                                                           Listing
                                                                                                                         14-2
namespace AcmeDemoBundleUtility;

class Calculator
{
    public function add($a, $b)
    {
        return $a + $b;
    }
}

To test this, create a CalculatorTest file in the Tests/Utility directory of your bundle:

// src/Acme/DemoBundle/Tests/Utility/CalculatorTest.php                                                                 Listing
                                                                                                                         14-3
namespace AcmeDemoBundleTestsUtility;

use AcmeDemoBundleUtilityCalculator;

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.


Just like in your real application - autoloading is automatically enabled via the bootstrap.php.cache file
(as configured by default in the phpunit.xml.dist file).
Running tests for a given file or directory is also very easy:

# run all tests in the Utility directory                                                                                Listing
                                                                                                                         14-4
$ 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/




PDF brought to you by                                                                       Chapter 14: Testing | 141
generated on June 20, 2012
Functional Tests
          Functional tests check the integration of the different layers of an application (from the routing to the
          views). 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.

          Your First Functional Test
          Functional tests are simple PHP files that typically live in the Tests/Controller directory of your
          bundle. If you want to test the pages handled by your DemoController class, start by creating a new
          DemoControllerTest.php file that extends a special WebTestCase class.
          For example, the Symfony2 Standard Edition provides a simple functional test for its DemoController
          (DemoControllerTest2) that reads as follows:

Listing   // src/Acme/DemoBundle/Tests/Controller/DemoControllerTest.php
 14-5
          namespace AcmeDemoBundleTestsController;

          use SymfonyBundleFrameworkBundleTestWebTestCase;

          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());
              }
          }



                           To run your functional tests, the WebTestCase class bootstraps the kernel of your application. In
                           most cases, this happens automatically. However, if your kernel is in a non-standard directory,
                           you'll need to modify your phpunit.xml.dist file to set the KERNEL_DIR environment variable to
                           the directory of your kernel:

                 Listing   <phpunit>
                  14-6
                               <!-- ... -->
                               <php>
                                   <server name="KERNEL_DIR" value="/path/to/your/app/" />
                               </php>
                               <!-- ... -->
                           </phpunit>



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



          2. https://github.com/symfony/symfony-standard/blob/master/src/Acme/DemoBundle/Tests/Controller/DemoControllerTest.php


          PDF brought to you by                                                                                        Chapter 14: Testing | 142
          generated on June 20, 2012
$crawler = $client->request('GET', '/demo/hello/Fabien');                                                                Listing
                                                                                                                          14-7

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


              The Crawler only works when the response is an XML or an HTML document. To get the raw
              content response, call $client->getResponse()->getContent().


Click on a link by first selecting it with the Crawler using either an XPath expression or a CSS selector,
then use the Client to click on it. For example, the following code finds all links with the text Greet, then
selects the second one, and ultimately clicks on it:

$link = $crawler->filter('a:contains("Greet")')->eq(1)->link();                                                          Listing
                                                                                                                          14-8

$crawler = $client->click($link);

Submitting a form is very similar; select a form button, optionally override some form values, and submit
the corresponding form:

$form = $crawler->selectButton('submit')->form();                                                                        Listing
                                                                                                                          14-9

// set some values
$form['name'] = 'Lucas';
$form['form_name[subject]'] = 'Hey there!';

// submit the form
$crawler = $client->submit($form);



              The form can also handle uploads and contains methods to fill in different types of form fields (e.g.
              select() and tick()). For details, see the Forms section below.


Now that you can easily navigate through an application, use assertions to test that it actually does what
you expect it to. Use the Crawler to make assertions on the DOM:

// Assert that the response matches a given CSS selector.                                                                Listing
                                                                                                                         14-10
$this->assertGreaterThan(0, $crawler->filter('h1')->count());

Or, test against the Response content directly if you just want to assert that the content contains some
text, or if the Response is not an XML/HTML document:

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




3. http://api.symfony.com/2.0/Symfony/Component/DomCrawler/Crawler.html


PDF brought to you by                                                                        Chapter 14: Testing | 143
generated on June 20, 2012
More about the request() method:
                 The full signature of the request() method is:

       Listing   request(
       14-12
                     $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:

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




4. http://php.net/manual/en/reserved.variables.server.php


PDF brought to you by                                                                   Chapter 14: Testing | 144
generated on June 20, 2012
Useful Assertions
              To get you started faster, here is a list of the most common and useful test assertions:

              // Assert that there is more than one h2 tag with the class "subtitle"                                      Listing
                                                                                                                          14-14
              $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());




Working with the Test Client
The Test Client simulates an HTTP client like a browser and makes requests into your Symfony2
application:

$crawler = $client->request('GET', '/hello/Fabien');                                                                      Listing
                                                                                                                          14-15

The request() method takes the HTTP method and a URL as arguments and returns a Crawler
instance.
Use the Crawler to find DOM elements in the Response. These elements can then be used to click on
links and submit forms:

$link = $crawler->selectLink('Go elsewhere...')->link();                                                                  Listing
                                                                                                                          14-16
$crawler = $client->click($link);

$form = $crawler->selectButton('validate')->form();
$crawler = $client->submit($form, array('name' => 'Fabien'));

The click() and submit() methods both return a Crawler object. These methods are the best way to
browse your application as it takes care of a lot of things for you, like detecting the HTTP method from
a form and giving you a nice API for uploading files.


              You will learn more about the Link and Form objects in the Crawler section below.




PDF brought to you by                                                                         Chapter 14: Testing | 145
generated on June 20, 2012
The request method can also be used to simulate form submissions directly or perform more complex
          requests:

Listing   // Directly submit a form (but using the Crawler is easier!)
14-17
          $client->request('POST', '/submit', array('name' => 'Fabien'));

          // Form submission with a file upload
          use SymfonyComponentHttpFoundationFileUploadedFile;

          $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')
          );

          Last but not least, you can force each request to be executed in its own PHP process to avoid any side-
          effects when working with several clients in the same script:

Listing   $client->insulate();
14-18



          Browsing
          The Client supports many operations that can be done in a real browser:

Listing   $client->back();
14-19
          $client->forward();
          $client->reload();

          // Clears all cookies and the history
          $client->restart();


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



          PDF brought to you by                                                                    Chapter 14: Testing | 146
          generated on June 20, 2012
$history   = $client->getHistory();                                                                                        Listing
                                                                                                                           14-20
$cookieJar = $client->getCookieJar();

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

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

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

$container = $client->getContainer();                                                                                      Listing
                                                                                                                           14-22
$kernel    = $client->getKernel();


Accessing the Container
It's highly recommended that a functional test only tests the Response. But under certain very rare
circumstances, you might want to access some internal objects to write assertions. In such cases, you can
access the dependency injection container:

$container = $client->getContainer();                                                                                      Listing
                                                                                                                           14-23

Be warned that this does not work if you insulate the client or if you use an HTTP layer. For a list of
services available in your application, use the container:debug console task.


              If the information you need to check is available from the profiler, use it instead.




Accessing the Profiler Data
On each request, the Symfony profiler collects and stores a lot of data about the internal handling of that
request. For example, the profiler could be used to verify that a given page executes less than a certain
number of database queries when loading.
To get the Profiler for the last request, do the following:

$profile = $client->getProfile();                                                                                          Listing
                                                                                                                           14-24

For specific details on using the profiler inside a test, see the How to use the Profiler in a Functional Test
cookbook entry.

Redirecting
When a request returns a redirect response, the client does not follow it automatically. You can examine
the response and force a redirection afterwards with the followRedirect() method:

$crawler = $client->followRedirect();                                                                                      Listing
                                                                                                                           14-25

If you want the client to automatically follow all redirects, you can force him with the
followRedirects() method:

$client->followRedirects();                                                                                                Listing
                                                                                                                           14-26




PDF brought to you by                                                                          Chapter 14: Testing | 147
generated on June 20, 2012
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.

          Traversing
          Like jQuery, the Crawler has methods to traverse the DOM of an HTML/XML document. For example,
          the following finds all input[type=submit] elements, selects the last one on the page, and then selects
          its immediate parent element:

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

          Many other methods are also available:

           Method                      Description
           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

          Since each of these methods returns a new Crawler instance, you can narrow down your node selection
          by chaining the method calls:

Listing   $crawler
14-28
              ->filter('h1')
              ->reduce(function ($node, $i)
              {
                   if (!$node->getAttribute('class')) {
                       return false;
                   }
              })
              ->first();



                        Use the count() function to get the number of nodes stored in a Crawler: count($crawler)




          PDF brought to you by                                                                  Chapter 14: Testing | 148
          generated on June 20, 2012
Extracting Information
The Crawler can extract information from the nodes:

// Returns the attribute value for the first node                                                                       Listing
                                                                                                                        14-29
$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');
});


Links
To select links, you can use the traversing methods above or the convenient selectLink() shortcut:

$crawler->selectLink('Click here');                                                                                     Listing
                                                                                                                        14-30

This selects all links that contain the given text, or clickable images for which the alt attribute contains
the given text. Like the other filtering methods, this returns another Crawler object.
Once you've selected a link, you have access to a special Link object, which has helpful methods specific
to links (such as getMethod() and getUri()). To click on the link, use the Client's click() method and
pass it a Link object:

$link = $crawler->selectLink('Click here')->link();                                                                     Listing
                                                                                                                        14-31

$client->click($link);


Forms
Just like links, you select forms with the selectButton() method:

$buttonCrawlerNode = $crawler->selectButton('submit');                                                                  Listing
                                                                                                                        14-32




              Notice that we select form buttons and not forms as a form can have several buttons; if you use the
              traversing API, keep in mind that you must look for a button.


The selectButton() method can select button tags and submit input tags. It uses several different parts
of the buttons to find them:

  • The value attribute value;
  • The id or alt attribute value for images;
  • The id or name attribute value for button tags.

Once you have a Crawler representing a button, call the form() method to get a Form instance for the
form wrapping the button node:



PDF brought to you by                                                                       Chapter 14: Testing | 149
generated on June 20, 2012
Listing   $form = $buttonCrawlerNode->form();
14-33

          When calling the form() method, you can also pass an array of field values that overrides the default
          ones:

Listing   $form = $buttonCrawlerNode->form(array(
14-34
              'name'              => 'Fabien',
              'my_form[subject]' => 'Symfony rocks!',
          ));

          And if you want to simulate a specific HTTP method for the form, pass it as a second argument:

Listing   $form = $buttonCrawlerNode->form(array(), 'DELETE');
14-35

          The Client can submit Form instances:

Listing   $client->submit($form);
14-36

          The field values can also be passed as a second argument of the submit() method:

Listing   $client->submit($form, array(
14-37
              'name'              => 'Fabien',
              'my_form[subject]' => 'Symfony rocks!',
          ));

          For more complex situations, use the Form instance as an array to set the value of each field individually:

Listing   // Change the value of a field
14-38
          $form['name'] = 'Fabien';
          $form['my_form[subject]'] = 'Symfony rocks!';

          There is also a nice API to manipulate the values of the fields according to their type:

Listing   // Select an option or a radio
14-39
          $form['country']->select('France');

          // Tick a checkbox
          $form['like_symfony']->tick();

          // Upload a file
          $form['photo']->upload('/path/to/lucas.jpg');



                        You can get the values that will be submitted by calling the getValues() method on the Form
                        object. The uploaded files are available in a separate array returned by getFiles(). The
                        getPhpValues() and getPhpFiles() methods also return the submitted values, but in the PHP
                        format (it converts the keys with square brackets notation - e.g. my_form[subject] - to PHP
                        arrays).



          Testing Configuration
          The Client used by functional tests creates a Kernel that runs in a special test environment. Since
          Symfony loads the app/config/config_test.yml in the test environment, you can tweak any of your
          application's settings specifically for testing.
          For example, by default, the swiftmailer is configured to not actually deliver emails in the test
          environment. You can see this under the swiftmailer configuration option:


          PDF brought to you by                                                                      Chapter 14: Testing | 150
          generated on June 20, 2012
# app/config/config_test.yml                                                                                            Listing
                                                                                                                        14-40
# ...

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:

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

If your application behaves according to some HTTP headers, pass them as the second argument of
createClient():

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

You can also override HTTP headers on a per request basis:

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



              The test client is available as a service in the container in the test environment (or wherever the
              framework.test option is enabled). This means you can override the service entirely if you need to.



PHPUnit Configuration
Each application has its own PHPUnit configuration, stored in the phpunit.xml.dist file. You can edit
this file to change the defaults or create a phpunit.xml file to tweak the configuration for your local
machine.


              Store the phpunit.xml.dist file in your code repository, and ignore the phpunit.xml file.



By default, only the tests stored in "standard" bundles are run by the phpunit command (standard being
tests in the src/*/Bundle/Tests or src/*/Bundle/*Bundle/Tests directories) But you can easily add
more directories. For instance, the following configuration adds the tests from the installed third-party
bundles:

<!-- hello/phpunit.xml.dist -->                                                                                         Listing
                                                                                                                        14-44
<testsuites>
    <testsuite name="Project Test Suite">
        <directory>../src/*/*Bundle/Tests</directory>
        <directory>../src/Acme/Bundle/*Bundle/Tests</directory>
    </testsuite>
</testsuites>



PDF brought to you by                                                                       Chapter 14: Testing | 151
generated on June 20, 2012
To include other directories in the code coverage, also edit the <filter> section:

Listing   <filter>
14-45
              <whitelist>
                   <directory>../src</directory>
                   <exclude>
                       <directory>../src/*/*Bundle/Resources</directory>
                       <directory>../src/*/*Bundle/Tests</directory>
                       <directory>../src/Acme/Bundle/*Bundle/Resources</directory>
                       <directory>../src/Acme/Bundle/*Bundle/Tests</directory>
                   </exclude>
              </whitelist>
          </filter>



          Learn more from the Cookbook
            • How to simulate HTTP Authentication in a Functional Test
            • How to test the Interaction of several Clients
            • How to use the Profiler in a Functional Test




          PDF brought to you by                                                                Chapter 14: Testing | 152
          generated on June 20, 2012
Chapter 15
                                            Validation

Validation is a very common task in web applications. Data entered in forms needs to be validated. Data
also needs to be validated before it is written into a database or passed to a web service.
Symfony2 ships with a Validator1 component that makes this task easy and transparent. This component
is based on the JSR303 Bean Validation specification2. What? A Java specification in PHP? You heard
right, but it's not as bad as it sounds. Let's look at how it can be used in PHP.



The Basics of Validation
The best way to understand validation is to see it in action. To start, suppose you've created a plain-old-
PHP object that you need to use somewhere in your application:

// src/Acme/BlogBundle/Entity/Author.php                                                                                 Listing
                                                                                                                          15-1
namespace AcmeBlogBundleEntity;

class Author
{
    public $name;
}

So far, this is just an ordinary class that serves some purpose inside your application. The goal of
validation is to tell you whether or not the data of an object is valid. For this to work, you'll configure a list
of rules (called constraints) that the object must follow in order to be valid. These rules can be specified
via a number of different formats (YAML, XML, annotations, or PHP).
For example, to guarantee that the $name property is not empty, add the following:

# src/Acme/BlogBundle/Resources/config/validation.yml                                                                    Listing
                                                                                                                          15-2
AcmeBlogBundleEntityAuthor:
    properties:
        name:
            - NotBlank: ~


1. https://github.com/symfony/Validator
2. http://jcp.org/en/jsr/detail?id=303


PDF brought to you by                                                                     Chapter 15: Validation | 153
generated on June 20, 2012
Protected and private properties can also be validated, as well as "getter" methods (see validator-
                        constraint-targets).



          Using the validator Service
          Next, to actually validate an Author object, use the validate method on the validator service (class
          Validator3). The job of the validator is easy: to read the constraints (i.e. rules) of a class and verify
          whether or not the data on the object satisfies those constraints. If validation fails, an array of errors is
          returned. Take this simple example from inside a controller:

Listing   use SymfonyComponentHttpFoundationResponse;
 15-3
          use AcmeBlogBundleEntityAuthor;
          // ...

          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!');
               }
          }

          If the $name property is empty, you will see the following error message:

Listing   AcmeBlogBundleAuthor.name:
 15-4
              This value should not be blank

          If you insert a value into the name property, the happy success message will appear.


                        Most of the time, you won't interact directly with the validator service or need to worry about
                        printing out the errors. Most of the time, you'll use validation indirectly when handling submitted
                        form data. For more information, see the Validation and Forms.

          You could also pass the collection of errors into a template.

Listing   if (count($errors) > 0) {
 15-5
              return $this->render('AcmeBlogBundle:Author:validate.html.twig', array(
                  'errors' => $errors,
              ));
          } else {
              // ...
          }

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

Listing
 15-6



          3. http://api.symfony.com/2.0/Symfony/Component/Validator/Validator.html


          PDF brought to you by                                                                    Chapter 15: Validation | 154
          generated on June 20, 2012
{# 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>



               Each validation error (called a "constraint violation"), is represented by a ConstraintViolation4
               object.



Validation and Forms
The validator service can be used at any time to validate any object. In reality, however, you'll
usually work with the validator indirectly when working with forms. Symfony's form library uses the
validator service internally to validate the underlying object after values have been submitted and
bound. The constraint violations on the object are converted into FieldError objects that can easily be
displayed with your form. The typical form submission workflow looks like the following from inside a
controller:

use AcmeBlogBundleEntityAuthor;                                                                                      Listing
                                                                                                                         15-7
use AcmeBlogBundleFormAuthorType;
use SymfonyComponentHttpFoundationRequest;
// ...

public function updateAction(Request $request)
{
    $author = new AcmeBlogBundleEntityAuthor();
    $form = $this->createForm(new AuthorType(), $author);

     if ($request->getMethod() == 'POST') {
         $form->bindRequest($request);

           if ($form->isValid()) {
               // the validation passed, do something with the $author object

                return $this->redirect($this->generateUrl('...'));
           }
     }

     return $this->render('BlogBundle:Author:form.html.twig', array(
         'form' => $form->createView(),
     ));
}



               This example uses an AuthorType form class, which is not shown here.



For more information, see the Forms chapter.




4. http://api.symfony.com/2.0/Symfony/Component/Validator/ConstraintViolation.html


PDF brought to you by                                                                    Chapter 15: Validation | 155
generated on June 20, 2012
Configuration
          The Symfony2 validator is enabled by default, but you must explicitly enable annotations if you're using
          the annotation method to specify your constraints:

Listing   # app/config/config.yml
 15-8
          framework:
              validation: { enable_annotations: true }



          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.
          Behind the scenes, a constraint is simply a PHP object that makes an assertive statement. In real life,
          a constraint could be: "The cake must not be burned". In Symfony2, constraints are similar: they are
          assertions that a condition is true. Given a value, a constraint will tell you whether or not that value
          adheres to the rules of the constraint.

          Supported Constraints
          Symfony2 packages a large number of the most commonly-needed constraints:

          Basic Constraints
          These are the basic constraints: use them to assert very basic things about the value of properties or the
          return value of methods on your object.

            •   NotBlank
            •   Blank
            •   NotNull
            •   Null
            •   True
            •   False
            •   Type

          String Constraints
            •   Email
            •   MinLength
            •   MaxLength
            •   Url
            •   Regex
            •   Ip

          Number Constraints
            • Max
            • Min

          Date Constraints
            • Date


          PDF brought to you by                                                              Chapter 15: Validation | 156
          generated on June 20, 2012
• DateTime
  • Time

Collection Constraints
  •   Choice
  •   Collection
  •   UniqueEntity
  •   Language
  •   Locale
  •   Country

File Constraints
  • File
  • Image

Other Constraints
  • Callback
  • All
  • Valid

You can also create your own custom constraints. This topic is covered in the "How to create a Custom
Validation Constraint" article of the cookbook.

Constraint Configuration
Some constraints, like NotBlank, are simple whereas others, like the Choice constraint, have several
configuration options available. Suppose that the Author class has another property, gender that can be
set to either "male" or "female":

# src/Acme/BlogBundle/Resources/config/validation.yml                                                               Listing
                                                                                                                     15-9
AcmeBlogBundleEntityAuthor:
    properties:
        gender:
            - Choice: { choices: [male, female], message: Choose a valid gender. }

The options of a constraint can always be passed in as an array. Some constraints, however, also allow
you to pass the value of one, "default", option in place of the array. In the case of the Choice constraint,
the choices options can be specified in this way.

# src/Acme/BlogBundle/Resources/config/validation.yml                                                               Listing
                                                                                                                    15-10
AcmeBlogBundleEntityAuthor:
    properties:
        gender:
            - Choice: [male, female]

This is purely meant to make the configuration of the most common option of a constraint shorter and
quicker.
If you're ever unsure of how to specify an option, either check the API documentation for the constraint
or play it safe by always passing in an array of options (the first method shown above).




PDF brought to you by                                                                Chapter 15: Validation | 157
generated on June 20, 2012
Translation Constraint Messages
          For information on translating the constraint messages, see Translating Constraint Messages.



          Constraint Targets
          Constraints can be applied to a class property (e.g. name) or a public getter method (e.g. getFullName).
          The first is the most common and easy to use, but the second allows you to specify more complex
          validation rules.

          Properties
          Validating class properties is the most basic validation technique. Symfony2 allows you to validate
          private, protected or public properties. The next listing shows you how to configure the $firstName
          property of an Author class to have at least 3 characters.

Listing   # src/Acme/BlogBundle/Resources/config/validation.yml
15-11
          AcmeBlogBundleEntityAuthor:
              properties:
                  firstName:
                      - NotBlank: ~
                      - MinLength: 3


          Getters
          Constraints can also be applied to the return value of a method. Symfony2 allows you to add a constraint
          to any public method whose name starts with "get" or "is". In this guide, both of these types of methods
          are referred to as "getters".
          The benefit of this technique is that it allows you to validate your object dynamically. For example,
          suppose you want to make sure that a password field doesn't match the first name of the user (for security
          reasons). You can do this by creating an isPasswordLegal method, and then asserting that this method
          must return true:

Listing   # src/Acme/BlogBundle/Resources/config/validation.yml
15-12
          AcmeBlogBundleEntityAuthor:
              getters:
                  passwordLegal:
                       - "True": { message: "The password cannot match your first name" }

          Now, create the isPasswordLegal() method, and include the logic you need:

Listing   public function isPasswordLegal()
15-13
          {
              return ($this->firstName != $this->password);
          }



                        The keen-eyed among you will have noticed that the prefix of the getter ("get" or "is") is omitted
                        in the mapping. This allows you to move the constraint to a property with the same name later (or
                        vice versa) without changing your validation logic.




          PDF brought to you by                                                                   Chapter 15: Validation | 158
          generated on June 20, 2012
Classes
Some constraints apply to the entire class being validated. For example, the Callback constraint is a
generic constraint that's applied to the class itself. When that class is validated, methods specified by that
constraint are simply executed so that each can provide more custom validation.



Validation Groups
So far, you've been able to add constraints to a class and ask whether or not that class passes all of
the defined constraints. In some cases, however, you'll need to validate an object against only some of
the constraints on that class. To do this, you can organize each constraint into one or more "validation
groups", and then apply validation against just one group of constraints.
For example, suppose you have a User class, which is used both when a user registers and when a user
updates his/her contact information later:

# src/Acme/BlogBundle/Resources/config/validation.yml                                                                  Listing
                                                                                                                       15-14
AcmeBlogBundleEntityUser:
    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.

To tell the validator to use a specific group, pass one or more group names as the second argument to the
validate() method:

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

Of course, you'll usually work with validation indirectly through the form library. For information on
how to use validation groups inside forms, see Validation Groups.



Validating Values and Arrays
So far, you've seen how you can validate entire objects. But sometimes, you just want to validate a simple
value - like to verify that a string is a valid email address. This is actually pretty easy to do. From inside a
controller, it looks like this:

// add this to the top of your class                                                                                   Listing
                                                                                                                       15-16
use SymfonyComponentValidatorConstraintsEmail;

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


PDF brought to you by                                                                   Chapter 15: Validation | 159
generated on June 20, 2012
$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
      }

      // ...
}

By calling validateValue on the validator, you can pass in a raw value and the constraint object that you
want to validate that value against. A full list of the available constraints - as well as the full class name
for each constraint - is available in the constraints reference section .
The validateValue method returns a ConstraintViolationList5 object, which acts just like an array
of errors. Each error in the collection is a ConstraintViolation6 object, which holds the error message
on its getMessage method.



Final Thoughts
The Symfony2 validator is a powerful tool that can be leveraged to guarantee that the data of any
object is "valid". The power behind validation lies in "constraints", which are rules that you can apply
to properties or getter methods of your object. And while you'll most commonly use the validation
framework indirectly when using forms, remember that it can be used anywhere to validate any object.



Learn more from the Cookbook
    • How to create a Custom Validation Constraint




5. http://api.symfony.com/2.0/Symfony/Component/Validator/ConstraintViolationList.html
6. http://api.symfony.com/2.0/Symfony/Component/Validator/ConstraintViolation.html


PDF brought to you by                                                                    Chapter 15: Validation | 160
generated on June 20, 2012
Chapter 16
                                              Forms

Dealing with HTML forms is one of the most common - and challenging - tasks for a web developer.
Symfony2 integrates a Form component that makes dealing with forms easy. In this chapter, you'll build
a complex form from the ground-up, learning the most important features of the form library along the
way.


              The Symfony form component is a standalone library that can be used outside of Symfony2
              projects. For more information, see the Symfony2 Form Component1 on Github.




Creating a Simple Form
Suppose you're building a simple todo list application that will need to display "tasks". Because your users
will need to edit and create tasks, you're going to need to build a form. But before you begin, first focus
on the generic Task class that represents and stores the data for a single task:

// src/Acme/TaskBundle/Entity/Task.php                                                                            Listing
                                                                                                                   16-1
namespace AcmeTaskBundleEntity;

class Task
{
    protected $task;

     protected $dueDate;

     public function getTask()
     {
         return $this->task;
     }
     public function setTask($task)
     {
         $this->task = $task;


1. https://github.com/symfony/Form


PDF brought to you by                                                                   Chapter 16: Forms | 161
generated on June 20, 2012
}

               public function getDueDate()
               {
                   return $this->dueDate;
               }
               public function setDueDate(DateTime $dueDate = null)
               {
                   $this->dueDate = $dueDate;
               }
          }



                             If you're coding along with this example, create the AcmeTaskBundle first by running the following
                             command (and accepting all of the default options):

                   Listing   php app/console generate:bundle --namespace=Acme/TaskBundle
                    16-2




          This class is a "plain-old-PHP-object" because, so far, it has nothing to do with Symfony or any other
          library. It's quite simply a normal PHP object that directly solves a problem inside your application (i.e.
          the need to represent a task in your application). Of course, by the end of this chapter, you'll be able to
          submit data to a Task instance (via an HTML form), validate its data, and persist it to the database.


          Building the Form
          Now that you've created a Task class, the next step is to create and render the actual HTML form. In
          Symfony2, this is done by building a form object and then rendering it in a template. For now, this can
          all be done from inside a controller:

Listing   // src/Acme/TaskBundle/Controller/DefaultController.php
 16-3
          namespace AcmeTaskBundleController;

          use SymfonyBundleFrameworkBundleControllerController;
          use AcmeTaskBundleEntityTask;
          use SymfonyComponentHttpFoundationRequest;

          class DefaultController extends Controller
          {
              public function newAction(Request $request)
              {
                  // create a task and give it some dummy data for this example
                  $task = 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(),
                        ));
               }
          }




          PDF brought to you by                                                                            Chapter 16: Forms | 162
          generated on June 20, 2012
This example shows you how to build your form directly in the controller. Later, in the "Creating
              Form Classes" section, you'll learn how to build your form in a standalone class, which is
              recommended as your form becomes reusable.

Creating a form requires relatively little code because Symfony2 form objects are built with a "form
builder". The form builder's purpose is to allow you to write simple form "recipes", and have it do all the
heavy-lifting of actually building the form.
In this example, you've added two fields to your form - task and dueDate - corresponding to the task
and dueDate properties of the Task class. You've also assigned each a "type" (e.g. text, date), which,
among other things, determines which HTML form tag(s) is rendered for that field.
Symfony2 comes with many built-in types that will be discussed shortly (see Built-in Field Types).

Rendering the Form
Now that the form has been created, the next step is to render it. This is done by passing a special form
"view" object to your template (notice the $form->createView() in the controller above) and using a set
of form helper functions:

{# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #}                                                      Listing
                                                                                                                      16-4

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

    <input type="submit" />
</form>




              This example assumes that you've created a route called task_new that points to the
              AcmeTaskBundle:Default:new controller that was created earlier.


That's it! By printing form_widget(form), each field in the form is rendered, along with a label and error
message (if there is one). As easy as this is, it's not very flexible (yet). Usually, you'll want to render
each form field individually so you can control how the form looks. You'll learn how to do that in the
"Rendering a Form in a Template" section.
Before moving on, notice how the rendered task input field has the value of the task property from the
$task object (i.e. "Write a blog post"). This is the first job of a form: to take data from an object and
translate it into a format that's suitable for being rendered in an HTML form.




PDF brought to you by                                                                      Chapter 16: Forms | 163
generated on June 20, 2012
The form system is smart enough to access the value of the protected task property via the
                         getTask() and setTask() methods on the Task class. Unless a property is public, it must have a
                         "getter" and "setter" method so that the form component can get and put data onto the property.
                         For a Boolean property, you can use an "isser" method (e.g. isPublished()) instead of a getter
                         (e.g. getPublished()).


          Handling Form Submissions
          The second job of a form is to translate user-submitted data back to the properties of an object. To
          make this happen, the submitted data from the user must be bound to the form. Add the following
          functionality to your controller:

Listing   // ...
 16-5

          public function newAction(Request $request)
          {
              // just setup a fresh $task object (remove the dummy data)
              $task = new Task();

               $form = $this->createFormBuilder($task)
                   ->add('task', 'text')
                   ->add('dueDate', 'date')
                   ->getForm();

               if ($request->getMethod() == 'POST') {
                   $form->bindRequest($request);

                     if ($form->isValid()) {
                         // perform some action, such as saving the task to the database

                          return $this->redirect($this->generateUrl('task_success'));
                     }
               }

               // ...
          }

          Now, when submitting the form, the controller binds the submitted data to the form, which translates
          that data back to the task and dueDate properties of the $task object. This all happens via the
          bindRequest() method.


                         As soon as bindRequest() is called, the submitted data is transferred to the underlying object
                         immediately. This happens regardless of whether or not the underlying data is actually valid.


          This controller follows a common pattern for handling forms, and has three possible paths:
               1. When initially loading the page in a browser, the request method is GET and the form is simply
                   created and rendered;
               2. When the user submits the form (i.e. the method is POST) with invalid data (validation is covered
                   in the next section), the form is bound and then rendered, this time displaying all validation
                   errors;
               3. When the user submits the form with valid data, the form is bound and you have the
                   opportunity to perform some actions using the $task object (e.g. persisting it to the database)
                   before redirecting the user to some other page (e.g. a "thank you" or "success" page).



          PDF brought to you by                                                                     Chapter 16: Forms | 164
          generated on June 20, 2012
Redirecting a user after a successful form submission prevents the user from being able to hit
              "refresh" and re-post the data.




Form Validation
In the previous section, you learned how a form can be submitted with valid or invalid data. In Symfony2,
validation is applied to the underlying object (e.g. Task). In other words, the question isn't whether the
"form" is valid, but whether or not the $task object is valid after the form has applied the submitted data
to it. Calling $form->isValid() is a shortcut that asks the $task object whether or not it has valid data.
Validation is done by adding a set of rules (called constraints) to a class. To see this in action, add
validation constraints so that the task field cannot be empty and the dueDate field cannot be empty and
must be a valid DateTime object.

# Acme/TaskBundle/Resources/config/validation.yml                                                                      Listing
                                                                                                                        16-6
AcmeTaskBundleEntityTask:
    properties:
        task:
            - NotBlank: ~
        dueDate:
            - NotBlank: ~
            - Type: DateTime

That's it! If you re-submit the form with invalid data, you'll see the corresponding errors printed out with
the form.


              HTML5 Validation
              As of HTML5, many browsers can natively enforce certain validation constraints on the client
              side. The most common validation is activated by rendering a required attribute on fields that
              are required. For browsers that support HTML5, this will result in a native browser message being
              displayed if the user tries to submit the form with that field blank.
              Generated forms take full advantage of this new feature by adding sensible HTML attributes
              that trigger the validation. The client-side validation, however, can be disabled by adding the
              novalidate attribute to the form tag or formnovalidate to the submit tag. This is especially useful
              when you want to test your server-side validation constraints, but are being prevented by your
              browser from, for example, submitting blank fields.

Validation is a very powerful feature of Symfony2 and has its own dedicated chapter.

Validation Groups

              If you're not using validation groups, then you can skip this section.



If your object takes advantage of validation groups, you'll need to specify which validation group(s) your
form should use:

$form = $this->createFormBuilder($users, array(                                                                        Listing
                                                                                                                        16-7
    'validation_groups' => array('registration'),


PDF brought to you by                                                                        Chapter 16: Forms | 165
generated on June 20, 2012
))->add(...)
          ;

          If you're creating form classes (a good practice), then you'll need to add the following to the
          getDefaultOptions() method:

Listing   public function getDefaultOptions(array $options)
 16-8
          {
              return array(
                  'validation_groups' => array('registration')
              );
          }

          In both of these cases, only the registration validation group will be used to validate the underlying
          object.



          Built-in Field Types
          Symfony comes standard with a large group of field types that cover all of the common form fields and
          data types you'll encounter:

          Text Fields
            •   text
            •   textarea
            •   email
            •   integer
            •   money
            •   number
            •   password
            •   percent
            •   search
            •   url

          Choice Fields
            •   choice
            •   entity
            •   country
            •   language
            •   locale
            •   timezone

          Date and Time Fields
            •   date
            •   datetime
            •   time
            •   birthday

          Other Fields
            • checkbox
            • file

          PDF brought to you by                                                              Chapter 16: Forms | 166
          generated on June 20, 2012
• radio

Field Groups
  • collection
  • repeated

Hidden Fields
  • hidden
  • csrf

Base Fields
  • field
  • form

You can also create your own custom field types. This topic is covered in the "How to Create a Custom
Form Field Type" article of the cookbook.

Field Type Options
Each field type has a number of options that can be used to configure it. For example, the dueDate field
is currently being rendered as 3 select boxes. However, the date field can be configured to be rendered as
a single text box (where the user would enter the date as a string in the box):

->add('dueDate', 'date', array('widget' => 'single_text'))                                                               Listing
                                                                                                                          16-9




Each field type has a number of different options that can be passed to it. Many of these are specific to
the field type and details can be found in the documentation for each type.


              The required option
              The most common option is the required option, which can be applied to any field. By default,
              the required option is set to true, meaning that HTML5-ready browsers will apply client-side
              validation if the field is left blank. If you don't want this behavior, either set the required option
              on your field to false or disable HTML5 validation.
              Also note that setting the required option to true will not result in server-side validation to be
              applied. In other words, if a user submits a blank value for the field (either with an old browser or
              web service, for example), it will be accepted as a valid value unless you use Symfony's NotBlank
              or NotNull validation constraint.
              In other words, the required option is "nice", but true server-side validation should always be
              used.




PDF brought to you by                                                                          Chapter 16: Forms | 167
generated on June 20, 2012
The label option
                           The label for the form field can be set using the label option, which can be applied to any field:

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

                           The label for a field can also be set in the template rendering the form, see below.



          Field Type Guessing
          Now that you've added validation metadata to the Task class, Symfony already knows a bit about your
          fields. If you allow it, Symfony can "guess" the type of your field and set it up for you. In this example,
          Symfony can guess from the validation rules that both the task field is a normal text field and the
          dueDate field is a date field:

Listing   public function newAction()
16-11
          {
              $task = new Task();

                $form = $this->createFormBuilder($task)
                    ->add('task')
                    ->add('dueDate', null, array('widget' => 'single_text'))
                    ->getForm();
          }

          The "guessing" is activated when you omit the second argument to the add() method (or if you pass null
          to it). If you pass an options array as the third argument (done for dueDate above), these options are
          applied to the guessed field.


                           If your form uses a specific validation group, the field type guesser will still consider all validation
                           constraints when guessing your field types (including constraints that are not part of the validation
                           group(s) being used).


          Field Type Options Guessing
          In addition to guessing the "type" for a field, Symfony can also try to guess the correct values of a number
          of field options.


                           When these options are set, the field will be rendered with special HTML attributes that provide
                           for HTML5 client-side validation. However, it doesn't generate the equivalent server-side
                           constraints (e.g. AssertMaxLength). And though you'll need to manually add your server-side
                           validation, these field type options can then be guessed from that information.

              • required: The required option can be guessed based off of the validation rules (i.e. is the field
                NotBlank or NotNull) or the Doctrine metadata (i.e. is the field nullable). This is very useful,
                as your client-side validation will automatically match your validation rules.




          PDF brought to you by                                                                               Chapter 16: Forms | 168
          generated on June 20, 2012
• max_length: If the field is some sort of text field, then the max_length option can be guessed
    from the validation constraints (if MaxLength or Max is used) or from the Doctrine metadata
    (via the field's length).


              These field options are only guessed if you're using Symfony to guess the field type (i.e. omit or
              pass null as the second argument to add()).


If you'd like to change one of the guessed values, you can override it by passing the option in the options
field array:

->add('task', null, array('max_length' => 4))                                                                         Listing
                                                                                                                      16-12




Rendering a Form in a Template
So far, you've seen how an entire form can be rendered with just one line of code. Of course, you'll usually
need much more flexibility when rendering:

{# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #}                                                       Listing
                                                                                                                      16-13

<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>

Let's take a look at each part:

  • form_enctype(form) - If at least one field is a file upload field, this renders the obligatory
    enctype="multipart/form-data";
  • form_errors(form) - Renders any errors global to the whole form (field-specific errors are
    displayed next to each field);
  • form_row(form.dueDate) - Renders the label, any errors, and the HTML form widget for the
    given field (e.g. dueDate) inside, by default, a div element;
  • form_rest(form) - Renders any fields that have not yet been rendered. It's usually a good idea
    to place a call to this helper at the bottom of each form (in case you forgot to output a field
    or don't want to bother manually rendering hidden fields). This helper is also useful for taking
    advantage of the automatic CSRF Protection.

The majority of the work is done by the form_row helper, which renders the label, errors and HTML
form widget of each field inside a div tag by default. In the Form Theming section, you'll learn how the
form_row output can be customized on many different levels.


              You can access the current data of your form via form.vars.value:

              {{ form.vars.value.task }}                                                                              Listing
                                                                                                                      16-14




PDF brought to you by                                                                       Chapter 16: Forms | 169
generated on June 20, 2012
Rendering each Field by Hand
          The form_row helper is great because you can very quickly render each field of your form (and the
          markup used for the "row" can be customized as well). But since life isn't always so simple, you can also
          render each field entirely by hand. The end-product of the following is the same as when you used the
          form_row helper:

Listing   {{ form_errors(form) }}
16-15

          <div>
              {{ form_label(form.task) }}
              {{ form_errors(form.task) }}
              {{ form_widget(form.task) }}
          </div>

          <div>
              {{ form_label(form.dueDate) }}
              {{ form_errors(form.dueDate) }}
              {{ form_widget(form.dueDate) }}
          </div>

          {{ form_rest(form) }}

          If the auto-generated label for a field isn't quite right, you can explicitly specify it:

Listing   {{ form_label(form.task, 'Task Description') }}
16-16

          Some field types have additional rendering options that can be passed to the widget. These options are
          documented with each type, but one common options is attr, which allows you to modify attributes on
          the form element. The following would add the task_field class to the rendered input text field:

Listing   {{ form_widget(form.task, { 'attr': {'class': 'task_field'} }) }}
16-17

          If you need to render form fields "by hand" then you can access individual values for fields such as the
          id, name and label. For example to get the id:

Listing   {{ form.task.vars.id }}
16-18

          To get the value used for the form field's name attribute you need to use the full_name value:

Listing   {{ form.task.vars.full_name }}
16-19



          Twig Template Function Reference
          If you're using Twig, a full reference of the form rendering functions is available in the reference manual.
          Read this to know everything about the helpers available and the options that can be used with each.



          Creating Form Classes
          As you've seen, a form can be created and used directly in a controller. However, a better practice is
          to build the form in a separate, standalone PHP class, which can then be reused anywhere in your
          application. Create a new class that will house the logic for building the task form:

Listing   // src/Acme/TaskBundle/Form/Type/TaskType.php
16-20

          namespace AcmeTaskBundleFormType;



          PDF brought to you by                                                                       Chapter 16: Forms | 170
          generated on June 20, 2012
use SymfonyComponentFormAbstractType;
use SymfonyComponentFormFormBuilder;

class TaskType extends AbstractType
{
    public function buildForm(FormBuilder $builder, array $options)
    {
        $builder->add('task');
        $builder->add('dueDate', null, array('widget' => 'single_text'));
    }

     public function getName()
     {
         return 'task';
     }
}

This new class contains all the directions needed to create the task form (note that the getName() method
should return a unique identifier for this form "type"). It can be used to quickly build a form object in the
controller:

// src/Acme/TaskBundle/Controller/DefaultController.php                                                                  Listing
                                                                                                                         16-21

// add this new use statement at the top of the class
use AcmeTaskBundleFormTypeTaskType;

public function newAction()
{
    $task = // ...
    $form = $this->createForm(new TaskType(), $task);

     // ...
}

Placing the form logic into its own class means that the form can be easily reused elsewhere in your
project. This is the best way to create forms, but the choice is ultimately up to you.


              Setting the data_class
              Every form needs to know the name of the class that holds the underlying data (e.g.
              AcmeTaskBundleEntityTask). Usually, this is just guessed based off of the object passed to the
              second argument to createForm (i.e. $task). Later, when you begin embedding forms, this will no
              longer be sufficient. So, while not always necessary, it's generally a good idea to explicitly specify
              the data_class option by adding the following to your form type class:

              public function getDefaultOptions(array $options)                                                          Listing
                                                                                                                         16-22
              {
                  return array(
                      'data_class' => 'AcmeTaskBundleEntityTask',
                  );
              }




              When mapping forms to objects, all fields are mapped. Any fields on the form that do not exist on
              the mapped object will cause an exception to be thrown.




PDF brought to you by                                                                          Chapter 16: Forms | 171
generated on June 20, 2012
In cases where you need extra fields in the form (for example: a "do you agree with these terms"
                          checkbox) that will not be mapped to the underlying object, you need to set the property_path
                          option to false:

                Listing   public function buildForm(FormBuilder $builder, array $options)
                16-23
                          {
                              $builder->add('task');
                              $builder->add('dueDate', null, array('property_path' => false));
                          }

                          Additionally, if there are any fields on the form that aren't included in the submitted data, those
                          fields will be explicitly set to null.
                          The field data can be accessed in a controller with:

                Listing   $form->get('dueDate')->getData();
                16-24




          Forms and Doctrine
          The goal of a form is to translate data from an object (e.g. Task) to an HTML form and then translate
          user-submitted data back to the original object. As such, the topic of persisting the Task object to the
          database is entirely unrelated to the topic of forms. But, if you've configured the Task class to be persisted
          via Doctrine (i.e. you've added mapping metadata for it), then persisting it after a form submission can be
          done when the form is valid:

Listing   if ($form->isValid()) {
16-25
              $em = $this->getDoctrine()->getEntityManager();
              $em->persist($task);
              $em->flush();

               return $this->redirect($this->generateUrl('task_success'));
          }

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

Listing   $task = $form->getData();
16-26

          For more information, see the Doctrine ORM chapter.
          The key thing to understand is that when the form is bound, the submitted data is transferred to the
          underlying object immediately. If you want to persist that data, you simply need to persist the object itself
          (which already contains the submitted data).



          Embedded Forms
          Often, you'll want to build a form that will include fields from many different objects. For example,
          a registration form may contain data belonging to a User object as well as many Address objects.
          Fortunately, this is easy and natural with the form component.

          Embedding a Single Object
          Suppose that each Task belongs to a simple Category object. Start, of course, by creating the Category
          object:


          PDF brought to you by                                                                          Chapter 16: Forms | 172
          generated on June 20, 2012
// src/Acme/TaskBundle/Entity/Category.php                                                                    Listing
                                                                                                              16-27
namespace AcmeTaskBundleEntity;

use SymfonyComponentValidatorConstraints as Assert;

class Category
{
    /**
     * @AssertNotBlank()
     */
    public $name;
}

Next, add a new category property to the Task class:

// ...                                                                                                        Listing
                                                                                                              16-28

class Task
{
    // ...

     /**
      * @AssertType(type="AcmeTaskBundleEntityCategory")
      */
     protected $category;

     // ...

     public function getCategory()
     {
         return $this->category;
     }

     public function setCategory(Category $category = null)
     {
         $this->category = $category;
     }
}

Now that your application has been updated to reflect the new requirements, create a form class so that
a Category object can be modified by the user:

// src/Acme/TaskBundle/Form/Type/CategoryType.php                                                             Listing
                                                                                                              16-29
namespace AcmeTaskBundleFormType;

use SymfonyComponentFormAbstractType;
use SymfonyComponentFormFormBuilder;

class CategoryType extends AbstractType
{
    public function buildForm(FormBuilder $builder, array $options)
    {
        $builder->add('name');
    }

     public function getDefaultOptions(array $options)
     {
         return array(
             'data_class' => 'AcmeTaskBundleEntityCategory',
         );
     }


PDF brought to you by                                                               Chapter 16: Forms | 173
generated on June 20, 2012
public function getName()
               {
                   return 'category';
               }
          }

          The end goal is to allow the Category of a Task to be modified right inside the task form itself. To
          accomplish this, add a category field to the TaskType object whose type is an instance of the new
          CategoryType class:

Listing   public function buildForm(FormBuilder $builder, array $options)
16-30
          {
              // ...

               $builder->add('category', new CategoryType());
          }

          The fields from CategoryType can now be rendered alongside those from the TaskType class. Render the
          Category fields in the same way as the original Task fields:

Listing   {# ... #}
16-31

          <h3>Category</h3>
          <div class="category">
              {{ form_row(form.category.name) }}
          </div>

          {{ form_rest(form) }}
          {# ... #}

          When the user submits the form, the submitted data for the Category fields are used to construct an
          instance of Category, which is then set on the category field of the Task instance.
          The Category instance is accessible naturally via $task->getCategory() and can be persisted to the
          database or used however you need.

          Embedding a Collection of Forms
          You can also embed a collection of forms into one form (imagine a Category form with many Product
          sub-forms). This is done by using the collection field type.
          For more information see the "How to Embed a Collection of Forms" cookbook entry and the collection
          field type reference.



          Form Theming
          Every part of how a form is rendered can be customized. You're free to change how each form "row"
          renders, change the markup used to render errors, or even customize how a textarea tag should be
          rendered. Nothing is off-limits, and different customizations can be used in different places.
          Symfony uses templates to render each and every part of a form, such as label tags, input tags, error
          messages and everything else.
          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.
          In PHP, each form "fragment" is rendered via an individual template file. To customize any part of how a
          form renders, you just need to override the existing template by creating a new one.


          PDF brought to you by                                                                Chapter 16: Forms | 174
          generated on June 20, 2012
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 #}                                                                         Listing
                                                                                                                                        16-32

{% block field_row %}
{% spaceless %}
    <div class="form_row">
        {{ form_label(form) }}
        {{ form_errors(form) }}
        {{ form_widget(form) }}
    </div>
{% endspaceless %}
{% endblock field_row %}

The field_row form fragment is used when rendering most fields via the form_row function. To tell the
form component to use your new field_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 #}                                                                         Listing
                                                                                                                                        16-33

{% form_theme form 'AcmeTaskBundle:Form:fields.html.twig' %}

{% form_theme form 'AcmeTaskBundle:Form:fields.html.twig'
'AcmeTaskBundle:Form:fields2.html.twig' %}

<form ...>

The form_theme tag (in Twig) "imports" the fragments defined in the given template and uses them when
rendering the form. In other words, when the form_row function is called later in this template, it will use
the field_row block from your custom theme (instead of the default field_row block that ships with
Symfony).
Your custom theme does not have to override all the blocks. When rendering a block which is not
overridden in your custom theme, the theming engine will fall back to the global theme (defined at the
bundle level).
If several custom themes are provided they will be searched in the listed order before falling back to the
global theme.
To customize any portion of a form, you just need to override the appropriate fragment. Knowing exactly
which block or file to override is the subject of the next section.
For a more extensive discussion, see How to customize Form Rendering.

Form Fragment Naming
In Symfony, every part of a form that is rendered - HTML form elements, errors, labels, etc - is defined in
a base theme, which is a collection of blocks in Twig and a collection of template files in PHP.
In Twig, every block needed is defined in a single template file (form_div_layout.html.twig2) that lives
inside the Twig Bridge3. Inside this file, you can see every block needed to render a form and every default
field type.
In PHP, the fragments are individual template files. By default they are located in the Resources/views/
Form directory of the framework bundle (view on GitHub4).



2. https://github.com/symfony/symfony/blob/master/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig
3. https://github.com/symfony/symfony/tree/master/src/Symfony/Bridge/Twig
4. https://github.com/symfony/symfony/tree/master/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form


PDF brought to you by                                                                                         Chapter 16: Forms | 175
generated on June 20, 2012
Each fragment name follows the same basic pattern and is broken up into two pieces, separated by a
single underscore character (_). A few examples are:

  • field_row - used by form_row to render most fields;
  • textarea_widget - used by form_widget to render a textarea field type;
  • field_errors - used by form_errors to render errors for a field;

Each fragment follows the same basic pattern: type_part. The type portion corresponds to the field type
being rendered (e.g. textarea, checkbox, date, etc) whereas the part portion corresponds to what is
being rendered (e.g. label, widget, errors, etc). By default, there are 4 possible parts of a form that can
be rendered:

 label            (e.g. field_label)         renders the field's label
 widget           (e.g. field_widget)        renders the field's HTML representation
 errors           (e.g. field_errors)        renders the field's errors
 row              (e.g. field_row)           renders the field's entire row (label, widget & errors)



              There are actually 3 other parts - rows, rest, and enctype - but you should rarely if ever need to
              worry about overriding them.


By knowing the field type (e.g. textarea) and which part you want to customize (e.g. widget), you can
construct the fragment name that needs to be overridden (e.g. textarea_widget).


Template Fragment Inheritance
In some cases, the fragment you want to customize will appear to be missing. For example, there is no
textarea_errors fragment in the default themes provided with Symfony. So how are the errors for a
textarea field rendered?
The answer is: via the field_errors fragment. When Symfony renders the errors for a textarea type, it
looks first for a textarea_errors fragment before falling back to the field_errors fragment. Each field
type has a parent type (the parent type of textarea is field), and Symfony uses the fragment for the
parent type if the base fragment doesn't exist.
So, to override the errors for only textarea fields, copy the field_errors fragment, rename it to
textarea_errors and customize it. To override the default error rendering for all fields, copy and
customize the field_errors fragment directly.


              The "parent" type of each field type is available in the form type reference for each field type.




Global Form Theming
In the above example, you used the form_theme helper (in Twig) to "import" the custom form fragments
into just that form. You can also tell Symfony to import form customizations across your entire project.

Twig
To automatically include the customized blocks from the fields.html.twig template created earlier in
all templates, modify your application configuration file:


PDF brought to you by                                                                           Chapter 16: Forms | 176
generated on June 20, 2012
# app/config/config.yml                                                                                                  Listing
                                                                                                                         16-34

twig:
    form:
        resources:
            - 'AcmeTaskBundle:Form:fields.html.twig'
    # ...

Any blocks inside the fields.html.twig template are now used globally to define form output.


              Customizing Form Output all in a Single File with Twig
              In Twig, you can also customize a form block right inside the template where that customization
              is needed:

              {% extends '::base.html.twig' %}                                                                           Listing
                                                                                                                         16-35

              {# import "_self" as the form theme #}
              {% form_theme form _self %}

              {# make the form fragment customization #}
              {% block field_row %}
                  {# custom field row output #}
              {% endblock field_row %}

              {% block content %}
                  {# ... #}

                  {{ form_row(form.task) }}
              {% endblock %}

              The {% form_theme form _self %} tag allows form blocks to be customized directly inside
              the template that will use those customizations. Use this method to quickly make form output
              customizations that will only ever be needed in a single template.


                             This {% form_theme form _self %} functionality will only work if your template extends
                             another. If your template does not, you must point form_theme to a separate template.




PHP
To automatically include the customized templates from the Acme/TaskBundle/Resources/views/Form
directory created earlier in all templates, modify your application configuration file:

# app/config/config.yml                                                                                                  Listing
                                                                                                                         16-36

framework:
    templating:
        form:
            resources:
                - 'AcmeTaskBundle:Form'
# ...

Any fragments inside the Acme/TaskBundle/Resources/views/Form directory are now used globally to
define form output.




PDF brought to you by                                                                          Chapter 16: Forms | 177
generated on June 20, 2012
CSRF Protection
          CSRF - or Cross-site request forgery5 - is a method by which a malicious user attempts to make your
          legitimate users unknowingly submit data that they don't intend to submit. Fortunately, CSRF attacks
          can be prevented by using a CSRF token inside your forms.
          The good news is that, by default, Symfony embeds and validates CSRF tokens automatically for you.
          This means that you can take advantage of the CSRF protection without doing anything. In fact, every
          form in this chapter has taken advantage of the CSRF protection!
          CSRF protection works by adding a hidden field to your form - called _token by default - that contains a
          value that only you and your user knows. This ensures that the user - not some other entity - is submitting
          the given data. Symfony automatically validates the presence and accuracy of this token.
          The _token field is a hidden field and will be automatically rendered if you include the form_rest()
          function in your template, which ensures that all un-rendered fields are output.
          The CSRF token can be customized on a form-by-form basis. For example:

Listing   class TaskType extends AbstractType
16-37
          {
              // ...

               public function getDefaultOptions(array $options)
               {
                   return array(
                       'data_class'      => 'AcmeTaskBundleEntityTask',
                       'csrf_protection' => true,
                       'csrf_field_name' => '_token',
                       // a unique key to help generate the secret token
                       'intention'       => 'task_item',
                   );
               }

               // ...
          }

          To disable CSRF protection, set the csrf_protection option to false. Customizations can also be made
          globally in your project. For more information, see the form configuration reference section.


                        The intention option is optional but greatly enhances the security of the generated token by
                        making it different for each form.




          Using a Form without a Class
          In most cases, a form is tied to an object, and the fields of the form get and store their data on the
          properties of that object. This is exactly what you've seen so far in this chapter with the Task class.
          But sometimes, you may just want to use a form without a class, and get back an array of the submitted
          data. This is actually really easy:

Listing   // make sure you've imported the Request namespace above the class
16-38
          use SymfonyComponentHttpFoundationRequest
          // ...



          5. http://en.wikipedia.org/wiki/Cross-site_request_forgery


          PDF brought to you by                                                                  Chapter 16: Forms | 178
          generated on June 20, 2012
public function contactAction(Request $request)
{
    $defaultData = array('message' => 'Type your message here');
    $form = $this->createFormBuilder($defaultData)
        ->add('name', 'text')
        ->add('email', 'email')
        ->add('message', 'textarea')
        ->getForm();

           if ($request->getMethod() == 'POST') {
               $form->bindRequest($request);

                // data is an array with "name", "email", and "message" keys
                $data = $form->getData();
           }

     // ... render the form
}

By default, a form actually assumes that you want to work with arrays of data, instead of an object. There
are exactly two ways that you can change this behavior and tie the form to an object instead:
      1. Pass an object when creating the form (as the first argument to createFormBuilder or the
         second argument to createForm);
      2. Declare the data_class option on your form.
If you don't do either of these, then the form will return the data as an array. In this example, since
$defaultData is not an object (and no data_class option is set), $form->getData() ultimately returns
an array.


               You can also access POST values (in this case "name") directly through the request object, like so:

               $this->get('request')->request->get('name');                                                             Listing
                                                                                                                        16-39

               Be advised, however, that in most cases using the getData() method is a better choice, since it
               returns the data (usually an object) after it's been transformed by the form framework.


Adding Validation
The only missing piece is validation. Usually, when you call $form->isValid(), the object is validated
by reading the constraints that you applied to that class. But without a class, how can you add constraints
to the data of your form?
The answer is to setup the constraints yourself, and pass them into your form. The overall approach is
covered a bit more in the validation chapter, but here's a short example:

// import the namespaces above your controller class                                                                    Listing
                                                                                                                        16-40
use SymfonyComponentValidatorConstraintsEmail;
use SymfonyComponentValidatorConstraintsMinLength;
use SymfonyComponentValidatorConstraintsCollection;

$collectionConstraint = new Collection(array(
    'name' => new MinLength(5),
    'email' => new Email(array('message' => 'Invalid email address')),
));

// create a form, no default values, pass in the constraint option
$form = $this->createFormBuilder(null, array(
    'validation_constraint' => $collectionConstraint,
))->add('email', 'email')


PDF brought to you by                                                                         Chapter 16: Forms | 179
generated on June 20, 2012
// ...
          ;

          Now, when you call $form->bindRequest($request), the constraints setup here are run against your form's
          data. If you're using a form class, override the getDefaultOptions method to specify the option:

Listing   namespace AcmeTaskBundleFormType;
16-41

          use     SymfonyComponentFormAbstractType;
          use     SymfonyComponentFormFormBuilder;
          use     SymfonyComponentValidatorConstraintsEmail;
          use     SymfonyComponentValidatorConstraintsMinLength;
          use     SymfonyComponentValidatorConstraintsCollection;

          class ContactType extends AbstractType
          {
              // ...

                  public function getDefaultOptions(array $options)
                  {
                      $collectionConstraint = new Collection(array(
                          'name' => new MinLength(5),
                          'email' => new Email(array('message' => 'Invalid email address')),
                      ));

                      return array('validation_constraint' => $collectionConstraint);
                  }
          }

          Now, you have the flexibility to create forms - with validation - that return an array of data, instead of
          an object. In most cases, it's better - and certainly more robust - to bind your form to an object. But for
          simple forms, this is a great approach.



          Final Thoughts
          You now know all of the building blocks necessary to build complex and functional forms for your
          application. When building forms, keep in mind that the first goal of a form is to translate data from an
          object (Task) to an HTML form so that the user can modify that data. The second goal of a form is to
          take the data submitted by the user and to re-apply it to the object.
          There's still much more to learn about the powerful world of forms, such as how to handle file uploads
          with Doctrine or how to create a form where a dynamic number of sub-forms can be added (e.g. a todo
          list where you can keep adding more fields via Javascript before submitting). See the cookbook for these
          topics. Also, be sure to lean on the field type reference documentation, which includes examples of how to
          use each field type and its options.



          Learn more from the Cookbook
              •   How to handle File Uploads with Doctrine
              •   File Field Reference
              •   Creating Custom Field Types
              •   How to customize Form Rendering
              •   How to Dynamically Generate Forms Using Form Events
              •   Using Data Transformers




          PDF brought to you by                                                                  Chapter 16: Forms | 180
          generated on June 20, 2012
Chapter 17
                                              Security

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.
In the first step of the process, the security system identifies who the user is by requiring the user to
submit some sort of identification. This is called authentication, and it means that the system is trying
to find out who you are.
Once the system knows who you are, the next step is to determine if you should have access to a given
resource. This part of the process is called authorization, and it means that the system is checking to see
if you have privileges to perform a certain action.




Since the best way to learn is to see an example, let's dive right in.


              Symfony's security component1 is available as a standalone PHP library for use inside any PHP
              project.



PDF brought to you by                                                                 Chapter 17: Security | 181
generated on June 20, 2012
Basic Example: HTTP Authentication
          The security component can be configured via your application configuration. In fact, most standard
          security setups are just a matter of using the right configuration. The following configuration tells
          Symfony to secure any URL matching /admin/* and to ask the user for credentials using basic HTTP
          authentication (i.e. the old-school username/password box):

Listing   # app/config/security.yml
 17-1
          security:
              firewalls:
                  secured_area:
                      pattern:     ^/
                      anonymous: ~
                      http_basic:
                          realm: "Secured Demo Area"

                access_control:
                    - { path: ^/admin, roles: ROLE_ADMIN }

                providers:
                    in_memory:
                        users:
                            ryan: { password: ryanpass, roles: 'ROLE_USER' }
                            admin: { password: kitten, roles: 'ROLE_ADMIN' }

                encoders:
                    SymfonyComponentSecurityCoreUserUser: plaintext



                        A standard Symfony distribution separates the security configuration into a separate file (e.g. app/
                        config/security.yml). If you don't have a separate security file, you can put the configuration
                        directly into your main config file (e.g. app/config/config.yml).

          The end result of this configuration is a fully-functional security system that looks like the following:

            •   There are two users in the system (ryan and admin);
            •   Users authenticate themselves via the basic HTTP authentication prompt;
            •   Any URL matching /admin/* is secured, and only the admin user can access it;
            •   All URLs not matching /admin/* are accessible by all users (and the user is never prompted to
                login).

          Let's look briefly at how security works and how each part of the configuration comes into play.



          How Security Works: Authentication and Authorization
          Symfony's security system works by determining who a user is (i.e. authentication) and then checking to
          see if that user should have access to a specific resource or URL.

          Firewalls (Authentication)
          When a user makes a request to a URL that's protected by a firewall, the security system is activated. The
          job of the firewall is to determine whether or not the user needs to be authenticated, and if he does, to
          send a response back to the user initiating the authentication process.


          1. https://github.com/symfony/Security


          PDF brought to you by                                                                       Chapter 17: Security | 182
          generated on June 20, 2012
A firewall is activated when the URL of an incoming request matches the configured firewall's regular
expression pattern config value. In this example, the pattern (^/) will match every incoming request.
The fact that the firewall is activated does not mean, however, that the HTTP authentication username
and password box is displayed for every URL. For example, any user can access /foo without being
prompted to authenticate.




This works first because the firewall allows anonymous users via the anonymous configuration parameter.
In other words, the firewall doesn't require the user to fully authenticate immediately. And because no
special role is needed to access /foo (under the access_control section), the request can be fulfilled
without ever asking the user to authenticate.
If you remove the anonymous key, the firewall will always make a user fully authenticate immediately.


Access Controls (Authorization)
If a user requests /admin/foo, however, the process behaves differently. This is because of the
access_control configuration section that says that any URL matching the regular expression pattern
^/admin (i.e. /admin or anything matching /admin/*) requires the ROLE_ADMIN role. Roles are the basis
for most authorization: a user can access /admin/foo only if it has the ROLE_ADMIN role.




PDF brought to you by                                                               Chapter 17: Security | 183
generated on June 20, 2012
Like before, when the user originally makes the request, the firewall doesn't ask for any identification.
However, as soon as the access control layer denies the user access (because the anonymous user doesn't
have the ROLE_ADMIN role), the firewall jumps into action and initiates the authentication process. The
authentication process depends on the authentication mechanism you're using. For example, if you're
using the form login authentication method, the user will be redirected to the login page. If you're using
HTTP authentication, the user will be sent an HTTP 401 response so that the user sees the username and
password box.
The user now has the opportunity to submit its credentials back to the application. If the credentials are
valid, the original request can be re-tried.




PDF brought to you by                                                                Chapter 17: Security | 184
generated on June 20, 2012
In this example, the user ryan successfully authenticates with the firewall. But since ryan doesn't have
the ROLE_ADMIN role, he's still denied access to /admin/foo. Ultimately, this means that the user will see
some sort of message indicating that access has been denied.


              When Symfony denies the user access, the user sees an error screen and receives a 403 HTTP status
              code (Forbidden). You can customize the access denied error screen by following the directions in
              the Error Pages cookbook entry to customize the 403 error page.

Finally, if the admin user requests /admin/foo, a similar process takes place, except now, after being
authenticated, the access control layer will let the request pass through:




PDF brought to you by                                                                     Chapter 17: Security | 185
generated on June 20, 2012
The request flow when a user requests a protected resource is straightforward, but incredibly flexible. As
you'll see later, authentication can be handled in any number of ways, including via a form login, X.509
certificate, or by authenticating the user via Twitter. Regardless of the authentication method, the request
flow is always the same:
      1. A user accesses a protected resource;
      2. The application redirects the user to the login form;
      3. The user submits its credentials (e.g. username/password);
      4. The firewall authenticates the user;
      5. The authenticated user re-tries the original request.


              The exact process actually depends a little bit on which authentication mechanism you're using.
              For example, when using form login, the user submits its credentials to one URL that processes the
              form (e.g. /login_check) and then is redirected back to the originally requested URL (e.g. /admin/
              foo). But with HTTP authentication, the user submits its credentials directly to the original URL
              (e.g. /admin/foo) and then the page is returned to the user in that same request (i.e. no redirect).
              These types of idiosyncrasies shouldn't cause you any problems, but they're good to keep in mind.



              You'll also learn later how anything can be secured in Symfony2, including specific controllers,
              objects, or even PHP methods.




Using a Traditional Login Form

              In this section, you'll learn how to create a basic login form that continues to use the hard-coded
              users that are defined in the security.yml file.



PDF brought to you by                                                                       Chapter 17: Security | 186
generated on June 20, 2012
To load users from the database, please read How to load Security Users from the Database (the
              Entity Provider). By reading that article and this section, you can create a full login form system
              that loads users from the database.

So far, you've seen how to blanket your application beneath a firewall and then protect access to certain
areas with roles. By using HTTP Authentication, you can effortlessly tap into the native username/
password box offered by all browsers. However, Symfony supports many authentication mechanisms out
of the box. For details on all of them, see the Security Configuration Reference.
In this section, you'll enhance this process by allowing the user to authenticate via a traditional HTML
login form.
First, enable form login under your firewall:

# app/config/security.yml                                                                                               Listing
                                                                                                                         17-2
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: ~                                                                                             Listing
                                                                                                                         17-3




Now, when the security system initiates the authentication process, it will redirect the user to the login
form (/login by default). Implementing this login form visually is your job. First, create two routes: one
that will display the login form (i.e. /login) and one that will handle the login form submission (i.e.
/login_check):

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



              You will not need to implement a controller for the /login_check URL as the firewall will
              automatically catch and process any form submitted to this URL. It's optional, but helpful, to
              create a route so that you can use it to generate the form submission URL in the login template
              below.

Notice that the name of the login route isn't important. What's important is that the URL of the route
(/login) matches the login_path config value, as that's where the security system will redirect users that
need to login.
Next, create the controller that will display the login form:

// src/Acme/SecurityBundle/Controller/SecurityController.php;                                                           Listing
                                                                                                                         17-5
namespace AcmeSecurityBundleController;



PDF brought to you by                                                                      Chapter 17: Security | 187
generated on June 20, 2012
use SymfonyBundleFrameworkBundleControllerController;
          use SymfonyComponentSecurityCoreSecurityContext;

          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,
                     ));
               }
          }

          Don't let this controller confuse you. As you'll see in a moment, when the user submits the form, the
          security system automatically handles the form submission for you. If the user had submitted an invalid
          username or password, this controller reads the form submission error from the security system so that it
          can be displayed back to the user.
          In other words, your job is to display the login form and any login errors that may have occurred, but the
          security system itself takes care of checking the submitted username and password and authenticating
          the user.
          Finally, create the corresponding template:

Listing   {# src/Acme/SecurityBundle/Resources/views/Security/login.html.twig #}
 17-6
          {% 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>




          PDF brought to you by                                                                Chapter 17: Security | 188
          generated on June 20, 2012
The error variable passed into the template is an instance of AuthenticationException2. It may
              contain more information - or even sensitive information - about the authentication failure, so use
              it wisely!

The form has very few requirements. First, by submitting the form to /login_check (via the
login_check route), the security system will intercept the form submission and process the form for
you automatically. Second, the security system expects the submitted fields to be called _username and
_password (these field names can be configured).
And that's it! When you submit the form, the security system will automatically check the user's
credentials and either authenticate the user or send the user back to the login form where the error can
be displayed.
Let's review the whole process:
      1. The user tries to access a resource that is protected;
      2. The firewall initiates the authentication process by redirecting the user to the login form
          (/login);
      3. The /login page renders login form via the route and controller created in this example;
      4. The user submits the login form to /login_check;
      5. The security system intercepts the request, checks the user's submitted credentials,
          authenticates the user if they are correct, and sends the user back to the login form if they are
          not.
By default, if the submitted credentials are correct, the user will be redirected to the original page that
was requested (e.g. /admin/foo). If the user originally went straight to the login page, he'll be redirected
to the homepage. This can be highly customized, allowing you to, for example, redirect the user to a
specific URL.
For more details on this and how to customize the form login process in general, see How to customize
your Form Login.




2. http://api.symfony.com/2.0/Symfony/Component/Security/Core/Exception/AuthenticationException.html


PDF brought to you by                                                                                  Chapter 17: Security | 189
generated on June 20, 2012
Avoid Common Pitfalls
                When setting up your login form, watch out for a few common pitfalls.
                1. Create the correct routes
                First, be sure that you've defined the /login and /login_check routes correctly and that they
                correspond to the login_path and check_path config values. A misconfiguration here can mean
                that you're redirected to a 404 page instead of the login page, or that submitting the login form
                does nothing (you just see the login form over and over again).
                2. Be sure the login page isn't secure
                Also, be sure that the login page does not require any roles to be viewed. For example, the following
                configuration - which requires the ROLE_ADMIN role for all URLs (including the /login URL), will
                cause a redirect loop:

      Listing   access_control:
       17-7
                    - { path: ^/, roles: ROLE_ADMIN }

                Removing the access control on the /login URL fixes the problem:

      Listing   access_control:
       17-8
                    - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
                    - { path: ^/, roles: ROLE_ADMIN }

                Also, if your firewall does not allow for anonymous users, you'll need to create a special firewall
                that allows anonymous users for the login page:

      Listing   firewalls:
       17-9
                    login_firewall:
                        pattern:      ^/login$
                        anonymous:    ~
                    secured_area:
                        pattern:      ^/
                        form_login:   ~

                3. Be sure ``/login_check`` is behind a firewall
                Next, make sure that your check_path URL (e.g. /login_check) is behind the firewall you're
                using for your form login (in this example, the single firewall matches all URLs, including
                /login_check). If /login_check doesn't match any firewall, you'll receive a Unable to find the
                controller for path "/login_check" exception.
                4. Multiple firewalls don't share security context
                If you're using multiple firewalls and you authenticate against one firewall, you will not be
                authenticated against any other firewalls automatically. Different firewalls are like different
                security systems. That's why, for most applications, having one main firewall is enough.



Authorization
The first step in security is always authentication: the process of verifying who the user is. With Symfony,
authentication can be done in any way - via a form login, basic HTTP Authentication, or even via
Facebook.
Once the user has been authenticated, authorization begins. Authorization provides a standard and
powerful way to decide if a user can access any resource (a URL, a model object, a method call, ...). This
works by assigning specific roles to each user, and then requiring different roles for different resources.
The process of authorization has two different sides:
     1. The user has a specific set of roles;

PDF brought to you by                                                                          Chapter 17: Security | 190
generated on June 20, 2012
2. A resource requires a specific role in order to be accessed.
In this section, you'll focus on how to secure different resources (e.g. URLs, method calls, etc) with
different roles. Later, you'll learn more about how roles are created and assigned to users.

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.
You can define as many URL patterns as you need - each is a regular expression.

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



              Prepending the path with ^ ensures that only URLs beginning with the pattern are matched. For
              example, a path of simply /admin (without the ^) would correctly match /admin/foo but would
              also match URLs like /foo/admin.

For each incoming request, Symfony2 tries to find a matching access control rule (the first one wins).
If the user isn't authenticated yet, the authentication process is initiated (i.e. the user is given a chance
to login). However, if the user is authenticated but doesn't have the required role, an
AccessDeniedException3 exception is thrown, which you can handle and turn into a nice "access
denied" error page for the user. See How to customize Error Pages for more information.
Since Symfony uses the first access control rule it matches, a URL like /admin/users/new will match the
first rule and require only the ROLE_SUPER_ADMIN role. Any URL like /admin/blog will match the second
rule and require ROLE_ADMIN.


Securing by IP
Certain situations may arise when you may need to restrict access to a given route based on IP. This is
particularly relevant in the case of Edge Side Includes (ESI), for example, which utilize a route named
"_internal". When ESI is used, the _internal route is required by the gateway cache to enable different
caching options for subsections within a given page. This route comes with the ^/_internal prefix by
default in the standard edition (assuming you've uncommented those lines from the routing file).
Here is an example of how you might secure this route from outside access:

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


Securing by Channel
Much like securing based on IP, requiring the use of SSL is as simple as adding a new access_control
entry:



3. http://api.symfony.com/2.0/Symfony/Component/Security/Core/Exception/AccessDeniedException.html


PDF brought to you by                                                                                Chapter 17: Security | 191
generated on June 20, 2012
Listing   # app/config/security.yml
17-12
          security:
              # ...
              access_control:
                  - { path: ^/cart/checkout, roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel:
          https }


          Securing a Controller
          Protecting your application based on URL patterns is easy, but may not be fine-grained enough in certain
          cases. When necessary, you can easily force authorization from inside a controller:

Listing   use SymfonyComponentSecurityCoreExceptionAccessDeniedException;
17-13
          // ...

          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:

Listing   use JMSSecurityExtraBundleAnnotationSecure;
17-14

          /**
            * @Secure(roles="ROLE_ADMIN")
            */
          public function helloAction($name)
          {
               // ...
          }

          For more information, see the JMSSecurityExtraBundle4 documentation. If you're using Symfony's
          Standard Distribution, this bundle is available by default. If not, you can easily download and install it.

          Securing other Services
          In fact, anything in Symfony can be protected using a strategy similar to the one seen in the previous
          section. For example, suppose you have a service (i.e. a PHP class) whose job is to send emails from one
          user to another. You can restrict use of this class - no matter where it's being used from - to users that
          have a specific role.
          For more information on how you can use the security component to secure different services and
          methods in your application, see How to secure any Service or Method in your Application.

          Access Control Lists (ACLs): Securing Individual Database Objects
          Imagine you are designing a blog system where your users can comment on your posts. Now, you want
          a user to be able to edit his own comments, but not those of other users. Also, as the admin user, you
          yourself want to be able to edit all comments.



          4. https://github.com/schmittjoh/JMSSecurityExtraBundle


          PDF brought to you by                                                                 Chapter 17: Security | 192
          generated on June 20, 2012
The security component comes with an optional access control list (ACL) system that you can use when
you need to control access to individual instances of an object in your system. Without ACL, you can
secure your system so that only certain users can edit blog comments in general. But with ACL, you can
restrict or allow access on a comment-by-comment basis.
For more information, see the cookbook article: Access Control Lists (ACLs).



Users
In the previous sections, you learned how you can protect different resources by requiring a set of roles
for a resource. In this section we'll explore the other side of authorization: users.

Where do Users come from? (User Providers)
During authentication, the user submits a set of credentials (usually a username and password). The job
of the authentication system is to match those credentials against some pool of users. So where does this
list of users come from?
In Symfony2, users can come from anywhere - a configuration file, a database table, a web service, or
anything else you can dream up. Anything that provides one or more users to the authentication system
is known as a "user provider". Symfony2 comes standard with the two most common user providers: one
that loads users from a configuration file and one that loads users from a database table.

Specifying Users in a Configuration File
The easiest way to specify your users is directly in a configuration file. In fact, you've seen this already in
the example in this chapter.

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

This user provider is called the "in-memory" user provider, since the users aren't stored anywhere in a
database. The actual user object is provided by Symfony (User5).


              Any user provider can load users directly from configuration by specifying the users configuration
              parameter and listing the users beneath it.




              If your username is completely numeric (e.g. 77) or contains a dash (e.g. user-name), you should
              use that alternative syntax when specifying users in YAML:

              users:                                                                                                   Listing
                                                                                                                       17-16
                  - { name: 77, password: pass, roles: 'ROLE_USER' }
                  - { name: user-name, password: pass, roles: 'ROLE_USER' }




5. http://api.symfony.com/2.0/Symfony/Component/Security/Core/User/User.html


PDF brought to you by                                                                     Chapter 17: Security | 193
generated on June 20, 2012
For smaller sites, this method is quick and easy to setup. For more complex systems, you'll want to load
          your users from the database.

          Loading Users from the Database
          If you'd like to load your users via the Doctrine ORM, you can easily do this by creating a User class and
          configuring the entity provider.
          With this approach, you'll first create your own User class, which will be stored in the database.

Listing   // src/Acme/UserBundle/Entity/User.php
17-17
          namespace AcmeUserBundleEntity;

          use SymfonyComponentSecurityCoreUserUserInterface;
          use DoctrineORMMapping as ORM;

          /**
            * @ORMEntity
            */
          class User implements UserInterface
          {
               /**
                * @ORMColumn(type="string", length=255)
                */
               protected $username;

               // ...
          }

          As far as the security system is concerned, the only requirement for your custom user class is that it
          implements the UserInterface6 interface. This means that your concept of a "user" can be anything, as
          long as it implements this interface.


                        The user object will be serialized and saved in the session during requests, therefore it is
                        recommended that you implement the Serializable interface7 in your user object. This is especially
                        important if your User class has a parent class with private properties.

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

Listing   # app/config/security.yml
17-18
          security:
              providers:
                  main:
                      entity: { class: AcmeUserBundleEntityUser, property: username }

          With the introduction of this new provider, the authentication system will attempt to load a User object
          from the database by using the username field of that class.


                        This example is just meant to show you the basic idea behind the entity provider. For a full
                        working example, see How to load Security Users from the Database (the Entity Provider).


          For more information on creating your own custom provider (e.g. if you needed to load users via a web
          service), see How to create a custom User Provider.


          6. http://api.symfony.com/2.0/Symfony/Component/Security/Core/User/UserInterface.html
          7. http://php.net/manual/en/class.serializable.php


          PDF brought to you by                                                                     Chapter 17: Security | 194
          generated on June 20, 2012
Encoding the User's Password
So far, for simplicity, all the examples have stored the users' passwords in plain text (whether those users
are stored in a configuration file or in a database somewhere). Of course, in a real application, you'll want
to encode your users' passwords for security reasons. This is easily accomplished by mapping your User
class to one of several built-in "encoders". For example, to store your users in memory, but obscure their
passwords via sha1, do the following:

# app/config/security.yml                                                                                            Listing
                                                                                                                     17-19
security:
    # ...
    providers:
        in_memory:
            users:
                ryan: { password: bb87a29949f3a1ee0559f8a57357487151281386, roles:
'ROLE_USER' }
                admin: { password: 74913f5cd5f61ec0bcfdb775414c2fb3d161b620, roles:
'ROLE_ADMIN' }

     encoders:
         SymfonyComponentSecurityCoreUserUser:
             algorithm:    sha1
             iterations: 1
             encode_as_base64: false

By setting the iterations to 1 and the encode_as_base64 to false, the password is simply run through
the sha1 algorithm one time and without any extra encoding. You can now calculate the hashed
password either programmatically (e.g. hash('sha1', 'ryanpass')) or via some online tool like
functions-online.com8
If you're creating your users dynamically (and storing them in a database), you can use even tougher
hashing algorithms and then rely on an actual password encoder object to help you encode passwords.
For example, suppose your User object is AcmeUserBundleEntityUser (like in the above example).
First, configure the encoder for that user:

# app/config/security.yml                                                                                            Listing
                                                                                                                     17-20
security:
    # ...

     encoders:
         AcmeUserBundleEntityUser: sha512

In this case, you're using the stronger sha512 algorithm. Also, since you've simply specified the algorithm
(sha512) as a string, the system will default to hashing your password 5000 times in a row and then
encoding it as base64. In other words, the password has been greatly obfuscated so that the hashed
password can't be decoded (i.e. you can't determine the password from the hashed password).
If you have some sort of registration form for users, you'll need to be able to determine the hashed
password so that you can set it on your user. No matter what algorithm you configure for your user
object, the hashed password can always be determined in the following way from a controller:

$factory = $this->get('security.encoder_factory');                                                                   Listing
                                                                                                                     17-21
$user = new AcmeUserBundleEntityUser();

$encoder = $factory->getEncoder($user);
$password = $encoder->encodePassword('ryanpass', $user->getSalt());
$user->setPassword($password);



8. http://www.functions-online.com/sha1.html


PDF brought to you by                                                                   Chapter 17: Security | 195
generated on June 20, 2012
Retrieving the User Object
          After authentication, the User object of the current user can be accessed via the security.context
          service. From inside a controller, this will look like:

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



                          Anonymous users are technically authenticated, meaning that the isAuthenticated() method of
                          an anonymous user object will return true. To check if your user is actually authenticated, check
                          for the IS_AUTHENTICATED_FULLY role.

          In a Twig Template this object can be accessed via the app.user key, which calls the
          GlobalVariables::getUser()9 method:

Listing   <p>Username: {{ app.user.username }}</p>
17-23



          Using Multiple User Providers
          Each authentication mechanism (e.g. HTTP Authentication, form login, etc) uses exactly one user
          provider, and will use the first declared user provider by default. But what if you want to specify a few
          users via configuration and the rest of your users in the database? This is possible by creating a new
          provider that chains the two together:

Listing   # app/config/security.yml
17-24
          security:
              providers:
                  chain_provider:
                      providers: [in_memory, user_db]
                  in_memory:
                      users:
                           foo: { password: test }
                  user_db:
                      entity: { class: AcmeUserBundleEntityUser, property: username }

          Now, all authentication mechanisms will use the chain_provider, since it's the first specified. The
          chain_provider will, in turn, try to load the user from both the in_memory and user_db providers.


                          If you have no reasons to separate your in_memory users from your user_db users, you can
                          accomplish this even more easily by combining the two sources into a single provider:

                Listing   # app/config/security.yml
                17-25
                          security:
                              providers:
                                  main_provider:
                                      users:
                                          foo: { password: test }
                                      entity: { class: AcmeUserBundleEntityUser, 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:


          9. http://api.symfony.com/2.0/Symfony/Bundle/FrameworkBundle/Templating/GlobalVariables.html#getUser()


          PDF brought to you by                                                                                    Chapter 17: Security | 196
          generated on June 20, 2012
# app/config/security.yml                                                                                           Listing
                                                                                                                    17-26
security:
    firewalls:
        secured_area:
            # ...
            provider: user_db
            http_basic:
                realm: "Secured Demo Area"
                provider: in_memory
            form_login: ~

In this example, if a user tries to login via HTTP authentication, the authentication system will use the
in_memory user provider. But if the user tries to login via the form login, the user_db provider will be
used (since it's the default for the firewall as a whole).
For more information about user provider and firewall configuration, see the Security Configuration
Reference.



Roles
The idea of a "role" is key to the authorization process. Each user is assigned a set of roles and then each
resource requires one or more roles. If the user has the required roles, access is granted. Otherwise access
is denied.
Roles are pretty simple, and are basically strings that you can invent and use as needed (though roles
are objects internally). For example, if you need to start limiting access to the blog admin section of
your website, you could protect that section using a ROLE_BLOG_ADMIN role. This role doesn't need to be
defined anywhere - you can just start using it.


              All roles must begin with the ROLE_ prefix to be managed by Symfony2. If you define your own
              roles with a dedicated Role class (more advanced), don't use the ROLE_ prefix.



Hierarchical Roles
Instead of associating many roles to users, you can define role inheritance rules by creating a role
hierarchy:

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

In the above configuration, users with ROLE_ADMIN role will also have the ROLE_USER role. The
ROLE_SUPER_ADMIN role has ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH and ROLE_USER (inherited from
ROLE_ADMIN).



Logging Out
Usually, you'll also want your users to be able to log out. Fortunately, the firewall can handle this
automatically for you when you activate the logout config parameter:

                                                                                                                    Listing
                                                                                                                    17-28




PDF brought to you by                                                                  Chapter 17: Security | 197
generated on June 20, 2012
# app/config/security.yml
          security:
              firewalls:
                  secured_area:
                      # ...
                      logout:
                          path: /logout
                          target: /
              # ...

          Once this is configured under your firewall, sending a user to /logout (or whatever you configure the
          path to be), will un-authenticate the current user. The user will then be sent to the homepage (the
          value defined by the target parameter). Both the path and target config parameters default to what's
          specified here. In other words, unless you need to customize them, you can omit them entirely and
          shorten your configuration:

Listing   logout: ~
17-29

          Note that you will not need to implement a controller for the /logout URL as the firewall takes care of
          everything. You may, however, want to create a route so that you can use it to generate the URL:

Listing   # app/config/routing.yml
17-30
          logout:
              pattern: /logout

          Once the user has been logged out, he will be redirected to whatever path is defined by the target
          parameter above (e.g. the homepage). For more information on configuring the logout, see the Security
          Configuration Reference.



          Access Control in Templates
          If you want to check if the current user has a role inside a template, use the built-in helper function:

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



                        If you use this function and are not at a URL where there is a firewall active, an exception will be
                        thrown. Again, it's almost always a good idea to have a main firewall that covers all URLs (as has
                        been shown in this chapter).



          Access Control in Controllers
          If you want to check if the current user has a role in your controller, use the isGranted method of the
          security context:

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


          PDF brought to you by                                                                       Chapter 17: Security | 198
          generated on June 20, 2012
A firewall must be active or an exception will be thrown when the isGranted method is called. See
              the note above about templates for more details.




Impersonating a User
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                                                                                              Listing
                                                                                                                       17-33
security:
    firewalls:
        main:
            # ...
            switch_user: true

To switch to another user, just add a query string with the _switch_user parameter and the username as
the value to the current URL:

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

To switch back to the original user, use the special _exit username:

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

Of course, this feature needs to be made available to a small group of users. By default, access is restricted
to users having the ROLE_ALLOWED_TO_SWITCH role. The name of this role can be modified via the role
setting. For extra security, you can also change the query parameter name via the parameter setting:

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



Stateless Authentication
By default, Symfony2 relies on a cookie (the Session) to persist the security context of the user. But if you
use certificates or HTTP authentication for instance, persistence is not needed as credentials are available
for each request. In that case, and if you don't need to store anything else between requests, you can
activate the stateless authentication (which means that no cookie will be ever created by Symfony2):

# app/config/security.yml                                                                                              Listing
                                                                                                                       17-35
security:
    firewalls:
        main:
            http_basic: ~
            stateless: true


10. http://example.com/somewhere?_switch_user=thomas
11. http://example.com/somewhere?_switch_user=_exit


PDF brought to you by                                                                     Chapter 17: Security | 199
generated on June 20, 2012
If you use a form login, Symfony2 will create a cookie even if you set stateless to true.




Final Words
Security can be a deep and complex issue to solve correctly in your application. Fortunately, Symfony's
security component follows a well-proven security model based around authentication and authorization.
Authentication, which always happens first, is handled by a firewall whose job is to determine the
identity of the user through several different methods (e.g. HTTP authentication, login form, etc). In
the cookbook, you'll find examples of other methods for handling authentication, including how to
implement a "remember me" cookie functionality.
Once a user is authenticated, the authorization layer can determine whether or not the user should have
access to a specific resource. Most commonly, roles are applied to URLs, classes or methods and if the
current user doesn't have that role, access is denied. The authorization layer, however, is much deeper,
and follows a system of "voting" so that multiple parties can determine if the current user should have
access to a given resource. Find out more about this and other topics in the cookbook.



Learn more from the Cookbook
  •   Forcing HTTP/HTTPS
  •   Blacklist users by IP address with a custom voter
  •   Access Control Lists (ACLs)
  •   How to add "Remember Me" Login Functionality




PDF brought to you by                                                                      Chapter 17: Security | 200
generated on June 20, 2012
Chapter 18
                                       HTTP Cache

The nature of rich web applications means that they're dynamic. No matter how efficient your
application, each request will always contain more overhead than serving a static file.
And for most Web applications, that's fine. Symfony2 is lightning fast, and unless you're doing some
serious heavy-lifting, each request will come back quickly without putting too much stress on your server.
But as your site grows, that overhead can become a problem. The processing that's normally performed
on every request should be done only once. This is exactly what caching aims to accomplish.



Caching on the Shoulders of Giants
The most effective way to improve performance of an application is to cache the full output of a page and
then bypass the application entirely on each subsequent request. Of course, this isn't always possible for
highly dynamic websites, or is it? In this chapter, we'll show you how the Symfony2 cache system works
and why we think this is the best possible approach.
The Symfony2 cache system is different because it relies on the simplicity and power of the HTTP cache
as defined in the HTTP specification. Instead of reinventing a caching methodology, Symfony2 embraces
the standard that defines basic communication on the Web. Once you understand the fundamental
HTTP validation and expiration caching models, you'll be ready to master the Symfony2 cache system.
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).




PDF brought to you by                                                            Chapter 18: HTTP Cache | 201
generated on June 20, 2012
• 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.

Since caching with HTTP isn't unique to Symfony, many articles already exist on the topic. If you're new
to HTTP caching, we highly recommend Ryan Tomayko's article Things Caches Do1. Another in-depth
resource is Mark Nottingham's Cache Tutorial2.



Caching with a Gateway Cache
When caching with HTTP, the cache is separated from your application entirely and sits between your
application and the client making the request.
The job of the cache is to accept requests from the client and pass them back to your application. The
cache will also receive responses back from your application and forward them on to the client. The cache
is the "middle-man" of the request-response communication between the client and your application.
Along the way, the cache will store each response that is deemed "cacheable" (See Introduction to HTTP
Caching). If the same resource is requested again, the cache sends the cached response to the client,
ignoring your application entirely.
This type of cache is known as a HTTP gateway cache and many exist such as Varnish3, Squid in reverse
proxy mode4, and the Symfony2 reverse proxy.

Types of Caches
But a gateway cache isn't the only type of cache. In fact, the HTTP cache headers sent by your application
are consumed and interpreted by up to three different types of caches:

  • Browser caches: Every browser comes with its own local cache that is mainly useful for when
    you hit "back" or for images and other assets. The browser cache is a private cache as cached
    resources aren't shared with anyone else.
  • Proxy caches: A proxy is a shared cache as many people can be behind a single one. It's usually
    installed by large corporations and ISPs to reduce latency and network traffic.
  • Gateway caches: Like a proxy, it's also a shared cache but on the server side. Installed by
    network administrators, it makes websites more scalable, reliable and performant.


              Gateway caches are sometimes referred to as reverse proxy caches, surrogate caches, or even HTTP
              accelerators.




              The significance of private versus shared caches will become more obvious as we talk about caching
              responses containing content that is specific to exactly one user (e.g. account information).


Each response from your application will likely go through one or both of the first two cache types. These
caches are outside of your control but follow the HTTP cache directions set in the response.




1. http://tomayko.com/writings/things-caches-do
2. http://www.mnot.net/cache_docs/
3. http://www.varnish-cache.org/
4. http://wiki.squid-cache.org/SquidFaq/ReverseProxy


PDF brought to you by                                                                 Chapter 18: HTTP Cache | 202
generated on June 20, 2012
Symfony2 Reverse Proxy
Symfony2 comes with a reverse proxy (also called a gateway cache) written in PHP. Enable it and
cacheable responses from your application will start to be cached right away. Installing it is just as easy.
Each new Symfony2 application comes with a pre-configured caching kernel (AppCache) that wraps the
default one (AppKernel). The caching Kernel is the reverse proxy.
To enable caching, modify the code of a front controller to use the caching kernel:

// web/app.php                                                                                                     Listing
                                                                                                                    18-1

require_once __DIR__.'/../app/bootstrap.php.cache';
require_once __DIR__.'/../app/AppKernel.php';
require_once __DIR__.'/../app/AppCache.php';

use SymfonyComponentHttpFoundationRequest;

$kernel = new AppKernel('prod', false);
$kernel->loadClassCache();
// wrap the default AppKernel with the AppCache one
$kernel = new AppCache($kernel);
$kernel->handle(Request::createFromGlobals())->send();

The caching kernel will immediately act as a reverse proxy - caching responses from your application and
returning them to the client.


              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());                                                                        Listing
                                                                                                                    18-2




The AppCache object has a sensible default configuration, but it can be finely tuned via a set of options
you can set by overriding the getOptions() method:

// app/AppCache.php                                                                                                Listing
                                                                                                                    18-3

use SymfonyBundleFrameworkBundleHttpCacheHttpCache;

class AppCache extends HttpCache
{
    protected function getOptions()
    {
        return array(
            'debug'                        =>   false,
            'default_ttl'                  =>   0,
            'private_headers'              =>   array('Authorization', 'Cookie'),
            'allow_reload'                 =>   false,
            'allow_revalidate'             =>   false,
            'stale_while_revalidate'       =>   2,
            'stale_if_error'               =>   60,
        );
    }
}




PDF brought to you by                                                               Chapter 18: HTTP Cache | 203
generated on June 20, 2012
Unless overridden in getOptions(), the debug option will be set to automatically be the debug
              value of the wrapped AppKernel.


Here is a list of the main options:

  • default_ttl: The number of seconds that a cache entry should be considered fresh when no
    explicit freshness information is provided in a response. Explicit Cache-Control or Expires
    headers override this value (default: 0);
  • private_headers: Set of request headers that trigger "private" Cache-Control behavior on
    responses that don't explicitly state whether the response is public or private via a Cache-
    Control directive. (default: Authorization and Cookie);
  • allow_reload: Specifies whether the client can force a cache reload by including a Cache-
    Control "no-cache" directive in the request. Set it to true for compliance with RFC 2616
    (default: false);
  • allow_revalidate: Specifies whether the client can force a cache revalidate by including a
    Cache-Control "max-age=0" directive in the request. Set it to true for compliance with RFC
    2616 (default: false);
  • stale_while_revalidate: Specifies the default number of seconds (the granularity is the
    second as the Response TTL precision is a second) during which the cache can immediately
    return a stale response while it revalidates it in the background (default: 2); this setting
    is overridden by the stale-while-revalidate HTTP Cache-Control extension (see RFC
    5861);
  • stale_if_error: Specifies the default number of seconds (the granularity is the second)
    during which the cache can serve a stale response when an error is encountered (default: 60).
    This setting is overridden by the stale-if-error HTTP Cache-Control extension (see RFC
    5861).

If debug is true, Symfony2 automatically adds a X-Symfony-Cache header to the response containing
useful information about cache hits and misses.


              Changing from one Reverse Proxy to Another
              The Symfony2 reverse proxy is a great tool to use when developing your website or when you
              deploy your website to a shared host where you cannot install anything beyond PHP code. But
              being written in PHP, it cannot be as fast as a proxy written in C. That's why we highly recommend
              you to use Varnish or Squid on your production servers if possible. The good news is that the
              switch from one proxy server to another is easy and transparent as no code modification is needed
              in your application. Start easy with the Symfony2 reverse proxy and upgrade later to Varnish when
              your traffic increases.
              For more information on using Varnish with Symfony2, see the How to use Varnish cookbook
              chapter.



              The performance of the Symfony2 reverse proxy is independent of the complexity of the
              application. That's because the application kernel is only booted when the request needs to be
              forwarded to it.




PDF brought to you by                                                                 Chapter 18: HTTP Cache | 204
generated on June 20, 2012
Introduction to HTTP Caching
To take advantage of the available cache layers, your application must be able to communicate which
responses are cacheable and the rules that govern when/how that cache should become stale. This is done
by setting HTTP cache headers on the response.


              Keep in mind that "HTTP" is nothing more than the language (a simple text language) that web
              clients (e.g. browsers) and web servers use to communicate with each other. When we talk about
              HTTP caching, we're talking about the part of that language that allows clients and servers to
              exchange information related to caching.

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

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

The most important and versatile header is the Cache-Control header, which is actually a collection of
various cache information.


              Each of the headers will be explained in full detail in the HTTP Expiration and Validation section.




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 an abstraction around the Cache-Control header to make its creation more
manageable:

$response = new Response();                                                                                            Listing
                                                                                                                        18-4

// 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



PDF brought to you by                                                                   Chapter 18: HTTP Cache | 205
generated on June 20, 2012
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!
To handle this situation, every response may be set to be public or private:

  • 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.

Symfony conservatively defaults each response to be private. To take advantage of shared caches (like the
Symfony2 reverse proxy), the response will need to be explicitly set as public.

Safe Methods
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). This has two very reasonable consequences:

  • You should never change the state of your application when responding to a GET or HEAD
    request. Even if you don't use a gateway cache, the presence of proxy caches mean that any
    GET or HEAD request may or may not actually hit your server.
  • Don't expect PUT, POST or DELETE methods to cache. These methods are meant to be used
    when mutating the state of your application (e.g. deleting a blog post). Caching them would
    prevent certain requests from hitting and mutating your application.

Caching Rules and Defaults
HTTP 1.1 allows caching anything by default unless there is an explicit Cache-Control header. In
practice, most caches do nothing when requests have a cookie, an authorization header, use a non-safe
method (i.e. PUT, POST, DELETE), or when responses have a redirect status code.
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).



HTTP Expiration and Validation
The HTTP specification defines two caching models:

  • With the expiration model5, you simply specify how long a response should be considered
    "fresh" by including a Cache-Control and/or an Expires header. Caches that understand
    expiration will not make the same request until the cached version reaches its expiration time
    and becomes "stale".
  • When pages are really dynamic (i.e. their representation changes often), the validation model6
    is often necessary. With this model, the cache stores the response, but asks the server on
    each request whether or not the cached response is still valid. The application uses a unique


5. http://tools.ietf.org/html/rfc2616#section-13.2
6. http://tools.ietf.org/html/rfc2616#section-13.3


PDF brought to you by                                                             Chapter 18: HTTP Cache | 206
generated on June 20, 2012
response identifier (the Etag header) and/or a timestamp (the Last-Modified header) to check
      if the page has changed since being cached.

The goal of both models is to never generate the same response twice by relying on a cache to store and
return "fresh" responses.


              Reading the HTTP Specification
              The HTTP specification defines a simple but powerful language in which clients and servers can
              communicate. As a web developer, the request-response model of the specification dominates our
              work. Unfortunately, the actual specification document - RFC 26167 - can be difficult to read.
              There is an on-going effort (HTTP Bis8) to rewrite the RFC 2616. It does not describe a new version
              of HTTP, but mostly clarifies the original HTTP specification. The organization is also improved as
              the specification is split into seven parts; everything related to HTTP caching can be found in two
              dedicated parts (P4 - Conditional Requests9 and P6 - Caching: Browser and intermediary caches).
              As a web developer, we strongly urge you to read the specification. Its clarity and power - even
              more than ten years after its creation - is invaluable. Don't be put-off by the appearance of the spec
              - its contents are much more beautiful than its cover.


Expiration
The expiration model is the more efficient and straightforward of the two caching models and should be
used whenever possible. When a response is cached with an expiration, the cache will store the response
and return it directly without hitting the application until it expires.
The expiration model can be accomplished using one of two, nearly identical, HTTP headers: Expires
or Cache-Control.


Expiration with the Expires Header
According to the HTTP specification, "the Expires header field gives the date/time after which the
response is considered stale." The Expires header can be set with the setExpires() Response method.
It takes a DateTime instance as an argument:

$date = new DateTime();                                                                                                 Listing
                                                                                                                         18-5
$date->modify('+600 seconds');

$response->setExpires($date);

The resulting HTTP header will look like this:

Expires: Thu, 01 Mar 2011 16:00:00 GMT                                                                                  Listing
                                                                                                                         18-6




              The setExpires() method automatically converts the date to the GMT timezone as required by
              the specification.


Note that in HTTP versions before 1.1 the origin server wasn't required to send the Date header.
Consequently the cache (e.g. the browser) might need to rely onto his local clock to evaluate the Expires
header making the lifetime calculation vulnerable to clock skew. Another limitation of the Expires


7. http://tools.ietf.org/html/rfc2616
8. http://tools.ietf.org/wg/httpbis/
9. http://tools.ietf.org/html/draft-ietf-httpbis-p4-conditional-12


PDF brought to you by                                                                    Chapter 18: HTTP Cache | 207
generated on June 20, 2012
header is that the specification states that "HTTP/1.1 servers should not send Expires dates more than
          one year in the future."

          Expiration with the Cache-Control Header
          Because of the Expires header limitations, most of the time, you should use the Cache-Control header
          instead. Recall that the Cache-Control header is used to specify many different cache directives. For
          expiration, there are two directives, max-age and s-maxage. The first one is used by all caches, whereas
          the second one is only taken into account by shared caches:

Listing   // Sets the number of seconds after which the response
 18-7
          // should no longer be considered fresh
          $response->setMaxAge(600);

          // Same as above but only for shared caches
          $response->setSharedMaxAge(600);

          The Cache-Control header would take on the following format (it may have additional directives):

Listing   Cache-Control: max-age=600, s-maxage=600
 18-8



          Validation
          When a resource needs to be updated as soon as a change is made to the underlying data, the expiration
          model falls short. With the expiration model, the application won't be asked to return the updated
          response until the cache finally becomes stale.
          The validation model addresses this issue. Under this model, the cache continues to store responses. The
          difference is that, for each request, the cache asks the application whether or not the cached response is
          still valid. If the cache is still valid, your application should return a 304 status code and no content. This
          tells the cache that it's ok to return the cached response.
          Under this model, you mainly save bandwidth as the representation is not sent twice to the same client
          (a 304 response is sent instead). But if you design your application carefully, you might be able to get the
          bare minimum data needed to send a 304 response and save CPU also (see below for an implementation
          example).


                        The 304 status code means "Not Modified". It's important because with this status code do not
                        contain the actual content being requested. Instead, the response is simply a light-weight set of
                        directions that tell cache that it should use its stored version.

          Like with expiration, there are two different HTTP headers that can be used to implement the validation
          model: ETag and Last-Modified.


          Validation with 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.
          Let's walk through a simple implementation that generates the ETag as the md5 of the content:

Listing   public function indexAction()
 18-9
          {
              $response = $this->render('MyBundle:Main:index.html.twig');


          PDF brought to you by                                                                 Chapter 18: HTTP Cache | 208
          generated on June 20, 2012
$response->setETag(md5($response->getContent()));
     $response->isNotModified($this->getRequest());

     return $response;
}

The Response::isNotModified() method compares the ETag sent with the Request with the one set on
the Response. If the two match, the method automatically sets the Response status code to 304.
This algorithm is simple enough and very generic, but you need to create the whole Response before
being able to compute the ETag, which is sub-optimal. In other words, it saves on bandwidth, but not
CPU cycles.
In the Optimizing your Code with Validation section, we'll show how validation can be used more
intelligently to determine the validity of a cache without doing so much work.


              Symfony2 also supports weak ETags by passing true as the second argument to the setETag()10
              method.



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." In other words, the application decides whether or not the cached
content has been updated based on whether or not it's been updated since the response was cached.
For instance, you can use the latest update date for all the objects needed to compute the resource
representation as the value for the Last-Modified header value:

public function showAction($articleSlug)                                                                                 Listing
                                                                                                                         18-10
{
    // ...

     $articleDate = new DateTime($article->getUpdatedAt());
     $authorDate = new DateTime($author->getUpdatedAt());

     $date = $authorDate > $articleDate ? $authorDate : $articleDate;

     $response->setLastModified($date);
     $response->isNotModified($this->getRequest());

     return $response;
}

The Response::isNotModified() method compares the If-Modified-Since header sent by the request
with the Last-Modified header set on the response. If they are equivalent, the Response will be set to a
304 status code.


              The If-Modified-Since request header equals the Last-Modified header of the last response sent
              to the client for the particular resource. This is how the client and server communicate with each
              other and decide whether or not the resource has been updated since it was cached.




10. http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/Response.html#setETag()


PDF brought to you by                                                                     Chapter 18: HTTP Cache | 209
generated on June 20, 2012
Optimizing your Code with Validation
          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:

Listing   public function showAction($articleSlug)
18-11
          {
              // 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());

               // 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
                     );
               }
          }

          When the Response is not modified, the isNotModified() automatically sets the response status code
          to 304, removes the content, and removes some headers that must not be present for 304 responses (see
          setNotModified()11).

          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.
          In this case, we need the cache to store both a compressed and uncompressed version of the response for
          the particular URI and return them based on the request's Accept-Encoding value. This is done by using
          the Vary response header, which is a comma-separated list of different headers whose values trigger a
          different representation of the requested resource:

Listing   Vary: Accept-Encoding, User-Agent
18-12




          11. http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/Response.html#setNotModified()


          PDF brought to you by                                                                            Chapter 18: HTTP Cache | 210
          generated on June 20, 2012
This particular Vary header would cache different versions of each resource based on the URI and
              the value of the Accept-Encoding and User-Agent request header.


The Response object offers a clean interface for managing the Vary header:

// set one vary header                                                                                              Listing
                                                                                                                    18-13
$response->setVary('Accept-Encoding');

// set multiple vary headers
$response->setVary(array('Accept-Encoding', 'User-Agent'));

The setVary() method takes a header name or an array of header names for which the response varies.


Expiration and Validation
You can of course use both validation and expiration within the same Response. As expiration wins over
validation, you can easily benefit from the best of both worlds. In other words, by using both expiration
and validation, you can instruct the cache to serve the cached content, while checking back at some
interval (the expiration) to verify that the content is still valid.

More Response Methods
The Response class provides many more methods related to the cache. Here are the most useful ones:

// Marks the Response stale                                                                                         Listing
                                                                                                                    18-14
$response->expire();

// Force the response to return a proper 304 response with no content
$response->setNotModified();

Additionally, most cache-related HTTP headers can be set via the single setCache() method:

// Set cache settings in one call                                                                                   Listing
                                                                                                                    18-15
$response->setCache(array(
    'etag'          => $etag,
    'last_modified' => $date,
    'max_age'       => 10,
    's_maxage'      => 10,
    'public'        => true,
    // 'private'    => true,
));



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 ESI12, 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.
The ESI specification describes tags you can embed in your pages to communicate with the gateway
cache. Only one tag is implemented in Symfony2, include, as this is the only useful one outside of
Akamaï context:

12. http://www.w3.org/TR/esi-lang


PDF brought to you by                                                                Chapter 18: HTTP Cache | 211
generated on June 20, 2012
Listing   <html>
18-16
              <body>
                  Some content

                     <!-- Embed the content of another page here -->
                     <esi:include src="http://..." />

                  More content
              </body>
          </html>



                        Notice from the example that each ESI tag has a fully-qualified URL. An ESI tag represents a page
                        fragment that can be fetched via the given URL.


          When a request is handled, the gateway cache fetches the entire page from its cache or requests it from
          the backend application. If the response contains one or more ESI tags, these are processed in the same
          way. In other words, the gateway cache either retrieves the included page fragment from its cache or
          requests the page fragment from the backend application again. When all the ESI tags have been resolved,
          the gateway cache merges each into the main page and sends the final content to the client.
          All of this happens transparently at the gateway cache level (i.e. outside of your application). As you'll
          see, if you choose to take advantage of ESI tags, Symfony2 makes the process of including them almost
          effortless.

          Using ESI in Symfony2
          First, to use ESI, be sure to enable it in your application configuration:

Listing   # app/config/config.yml
18-17
          framework:
              # ...
              esi: { enabled: true }

          Now, suppose we have a page that is relatively static, except for a news ticker at the bottom of the
          content. With ESI, we can cache the news ticker independent of the rest of the page.

Listing   public function indexAction()
18-18
          {
              $response = $this->render('MyBundle:MyController:index.html.twig');
              $response->setSharedMaxAge(600);

               return $response;
          }

          In this example, we've given the full-page cache a lifetime of ten minutes. Next, let's include the news
          ticker in the template by embedding an action. This is done via the render helper (See Embedding
          Controllers for more details).
          As the embedded content comes from another page (or controller for that matter), Symfony2 uses the
          standard render helper to configure ESI tags:

Listing   {% render '...:news' with {}, {'standalone': true} %}
18-19

          By setting standalone to true, you tell Symfony2 that the action should be rendered as an ESI tag. You
          might be wondering why you would want to use a helper instead of just writing the ESI tag yourself.
          That's because using a helper makes your application work even if there is no gateway cache installed.
          Let's see how it works.


          PDF brought to you by                                                                 Chapter 18: HTTP Cache | 212
          generated on June 20, 2012
When standalone is false (the default), Symfony2 merges the included page content within the main
one before sending the response to the client. But when standalone is true, and if Symfony2 detects that
it's talking to a gateway cache that supports ESI, it generates an ESI include tag. But if there is no gateway
cache or if it does not support ESI, Symfony2 will just merge the included page content within the main
one as it would have done were standalone set to false.


              Symfony2 detects if a gateway cache supports ESI via another Akamaï specification that is
              supported out of the box by the Symfony2 reverse proxy.


The embedded action can now specify its own caching rules, entirely independent of the master page.

public function newsAction()                                                                                            Listing
                                                                                                                        18-20
{
  // ...

    $response->setSharedMaxAge(60);
}

With ESI, the full page cache will be valid for 600 seconds, but the news component cache will only last
for 60 seconds.
A requirement of ESI, however, is that the embedded action be accessible via a URL so the gateway cache
can fetch it independently of the rest of the page. Of course, an action can't be accessed via a URL unless
it has a route that points to it. Symfony2 takes care of this via a generic route and controller. For the ESI
include tag to work properly, you must define the _internal route:

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



              Since this route allows all actions to be accessed via a URL, you might want to protect it by using
              the Symfony2 firewall feature (by allowing access to your reverse proxy's IP range). See the Securing
              by IP section of the Security Chapter for more information on how to do this.

One great advantage of this caching strategy is that you can make your application as dynamic as needed
and at the same time, hit the application as little as possible.


              Once you start using ESI, remember to always use the s-maxage directive instead of max-age. As
              the browser only ever receives the aggregated resource, it is not aware of the sub-components, and
              so it will obey the max-age directive and cache the entire page. And you don't want that.

The render helper supports two other useful options:

    • alt: used as the alt attribute on the ESI tag, which allows you to specify an alternative URL
      to be used if the src cannot be found;
    • ignore_errors: if set to true, an onerror attribute will be added to the ESI with a value of
      continue indicating that, in the event of a failure, the gateway cache will simply remove the
      ESI tag silently.




PDF brought to you by                                                                    Chapter 18: HTTP Cache | 213
generated on June 20, 2012
Cache Invalidation
                "There are only two hard things in Computer Science: cache invalidation and naming things."
                --Phil Karlton

          You should never need to invalidate cached data because invalidation is already taken into account
          natively in the HTTP cache models. If you use validation, you never need to invalidate anything by
          definition; and if you use expiration and need to invalidate a resource, it means that you set the expires
          date too far away in the future.


                        Since invalidation is a topic specific to each type of reverse proxy, if you don't worry about
                        invalidation, you can switch between reverse proxies without changing anything in your
                        application code.

          Actually, all reverse proxies provide ways to purge cached data, but you should avoid them as much as
          possible. The most standard way is to purge the cache for a given URL by requesting it with the special
          PURGE HTTP method.
          Here is how you can configure the Symfony2 reverse proxy to support the PURGE HTTP method:

Listing   // app/AppCache.php
18-22

          use SymfonyBundleFrameworkBundleHttpCacheHttpCache;

          class AppCache extends HttpCache
          {
              protected function invalidate(Request $request)
              {
                  if ('PURGE' !== $request->getMethod()) {
                      return parent::invalidate($request);
                  }

                     $response = new Response();
                     if (!$this->getStore()->purge($request->getUri())) {
                         $response->setStatusCode(404, 'Not purged');
                     } else {
                         $response->setStatusCode(200, 'Purged');
                     }

                     return $response;
               }
          }



                        You must protect the PURGE HTTP method somehow to avoid random people purging your cached
                        data.




          Summary
          Symfony2 was designed to follow the proven rules of the road: HTTP. Caching is no exception.
          Mastering the Symfony2 cache system means becoming familiar with the HTTP cache models and
          using them effectively. This means that, instead of relying only on Symfony2 documentation and code
          examples, you have access to a world of knowledge related to HTTP caching and gateway caches such as
          Varnish.

          PDF brought to you by                                                              Chapter 18: HTTP Cache | 214
          generated on June 20, 2012
Learn more from the Cookbook
  • How to use Varnish to speed up my Website




PDF brought to you by                           Chapter 18: HTTP Cache | 215
generated on June 20, 2012
Chapter 19
                                                           Translations

          The term "internationalization" (often abbreviated i18n1) refers to the process of abstracting strings and
          other locale-specific pieces out of your application and into a layer where they can be translated and
          converted based on the user's locale (i.e. language and country). For text, this means wrapping each with
          a function capable of translating the text (or "message") into the language of the user:

Listing   // text will *always* print out in English
 19-1
          echo 'Hello World';

          // text can be translated into the end-user's language or default to English
          echo $translator->trans('Hello World');



                        The term locale refers roughly to the user's language and country. It can be any string that
                        your application uses to manage translations and other format differences (e.g. currency format).
                        We recommended the ISO639-12 language code, an underscore (_), then the ISO3166 Alpha-23
                        country code (e.g. fr_FR for French/France).

          In this chapter, we'll learn how to prepare an application to support multiple locales and then how to
          create translations for multiple locales. Overall, the process has several common steps:
                1. Enable and configure Symfony's Translation component;
                2. Abstract strings (i.e. "messages") by wrapping them in calls to the Translator;
                3. Create translation resources for each supported locale that translate each message in the
                    application;
                4. Determine, set and manage the user's locale in the session.



          Configuration
          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:


          1. http://en.wikipedia.org/wiki/Internationalization_and_localization
          2. http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
          3. http://en.wikipedia.org/wiki/ISO_3166-1#Current_codes


          PDF brought to you by                                                                 Chapter 19: Translations | 216
          generated on June 20, 2012
# app/config/config.yml                                                                                                        Listing
                                                                                                                                19-2
framework:
    translator: { fallback: en }

The fallback option defines the fallback locale when a translation does not exist in the user's locale.


              When a translation does not exist for a locale, the translator first tries to find the translation for the
              language (fr if the locale is fr_FR for instance). If this also fails, it looks for a translation using the
              fallback locale.

The locale used in translations is the one stored in the user session.



Basic Translation
Translation of text is done through the translator service (Translator4). To translate a block of text
(called a message), use the trans()5 method. Suppose, for example, that we're translating a simple
message from inside a controller:

public function indexAction()                                                                                                  Listing
                                                                                                                                19-3
{
    $t = $this->get('translator')->trans('Symfony2 is great');

      return new Response($t);
}

When this code is executed, Symfony2 will attempt to translate the message "Symfony2 is great" based
on the locale of the user. For this to work, we need to tell Symfony2 how to translate the message via a
"translation resource", which is a collection of message translations for a given locale. This "dictionary"
of translations can be created in several different formats, XLIFF being the recommended format:

<!-- messages.fr.xliff -->                                                                                                     Listing
                                                                                                                                19-4
<?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>
         </body>
    </file>
</xliff>

Now, if the language of the user's locale is French (e.g. fr_FR or fr_BE), the message will be translated
into J'aime Symfony2.


The Translation Process
To actually translate the message, Symfony2 uses a simple process:

    • The locale of the current user, which is stored in the session, is determined;
    • A catalog of translated messages is loaded from translation resources defined for the locale
      (e.g. fr_FR). Messages from the fallback locale are also loaded and added to the catalog if

4. http://api.symfony.com/2.0/Symfony/Component/Translation/Translator.html
5. http://api.symfony.com/2.0/Symfony/Component/Translation/Translator.html#trans()


PDF brought to you by                                                                         Chapter 19: Translations | 217
generated on June 20, 2012
they don't already exist. The end result is a large "dictionary" of translations. See Message
                Catalogues for more details;
              • If the message is located in the catalog, the translation is returned. If not, the translator returns
                the original message.

          When using the trans() method, Symfony2 looks for the exact string inside the appropriate message
          catalog and returns it (if it exists).

          Message Placeholders
          Sometimes, a message containing a variable needs to be translated:

Listing   public function indexAction($name)
 19-5
          {
              $t = $this->get('translator')->trans('Hello '.$name);

                return new Response($t);
          }

          However, creating a translation for this string is impossible since the translator will try to look up
          the exact message, including the variable portions (e.g. "Hello Ryan" or "Hello Fabien"). Instead of
          writing a translation for every possible iteration of the $name variable, we can replace the variable with a
          "placeholder":

Listing   public function indexAction($name)
 19-6
          {
              $t = $this->get('translator')->trans('Hello %name%', array('%name%' => $name));

                new Response($t);
          }

          Symfony2 will now look for a translation of the raw message (Hello %name%) and then replace the
          placeholders with their values. Creating a translation is done just as before:

Listing   <!-- messages.fr.xliff -->
 19-7
          <?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>Hello %name%</source>
                           <target>Bonjour %name%</target>
                       </trans-unit>
                   </body>
              </file>
          </xliff>



                        The placeholders can take on any form as the full message is reconstructed using the PHP strtr
                        function6. However, the %var% notation is required when translating in Twig templates, and is
                        overall a sensible convention to follow.

          As we've seen, creating a translation is a two-step process:
               1. Abstract the message that needs to be translated by processing it through the Translator.
               2. Create a translation for the message in each locale that you choose to support.



          6. http://www.php.net/manual/en/function.strtr.php


          PDF brought to you by                                                                  Chapter 19: Translations | 218
          generated on June 20, 2012
The second step is done by creating message catalogues that define the translations for any number of
different locales.



Message Catalogues
When a message is translated, Symfony2 compiles a message catalogue for the user's locale and looks in
it for a translation of the message. A message catalogue is like a dictionary of translations for a specific
locale. For example, the catalogue for the fr_FR locale might contain the following translation:

      Symfony2 is Great => J'aime Symfony2

It's the responsibility of the developer (or translator) of an internationalized application to create these
translations. Translations are stored on the filesystem and discovered by Symfony, thanks to some
conventions.


              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:

              php app/console cache:clear                                                                                  Listing
                                                                                                                            19-8




Translation Locations and Naming Conventions
Symfony2 looks for message files (i.e. translations) in two locations:

  • For messages found in a bundle, the corresponding message files should live in the Resources/
    translations/ directory of the bundle;
  • To override any bundle translations, place message files in the app/Resources/translations
    directory.

The filename of the translations is also important as Symfony2 uses a convention to determine details
about the translations. Each message file must be named according to the following pattern:
domain.locale.loader:

  • domain: An optional way to organize messages into groups (e.g. admin, navigation or the
    default messages) - see Using Message Domains;
  • locale: The locale that the translations are for (e.g. en_GB, en, etc);
  • loader: How Symfony2 should load and parse the file (e.g. xliff, php or yml).

The loader can be the name of any registered loader. By default, Symfony provides the following loaders:

  • xliff: XLIFF file;
  • php: PHP file;
  • yml: YAML file.

The choice of which loader to use is entirely up to you and is a matter of taste.


              You can also store translations in a database, or any other storage by providing a custom class
              implementing the LoaderInterface7 interface.




7. http://api.symfony.com/2.0/Symfony/Component/Translation/Loader/LoaderInterface.html


PDF brought to you by                                                                     Chapter 19: Translations | 219
generated on June 20, 2012
Creating Translations
          The act of creating translation files is an important part of "localization" (often abbreviated L10n8).
          Translation files consist of a series of id-translation pairs for the given domain and locale. The source is
          the identifier for the individual translation, and can be the message in the main locale (e.g. "Symfony is
          great") of your application or a unique identifier (e.g. "symfony2.great" - see the sidebar below):

Listing   <!-- src/Acme/DemoBundle/Resources/translations/messages.fr.xliff -->
 19-9
          <?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>symfony2.great</source>
                           <target>J'aime Symfony2</target>
                       </trans-unit>
                   </body>
              </file>
          </xliff>

          Symfony2 will discover these files and use them when translating either "Symfony2 is great" or
          "symfony2.great" into a French language locale (e.g. fr_FR or fr_BE).




          8. http://en.wikipedia.org/wiki/Internationalization_and_localization


          PDF brought to you by                                                              Chapter 19: Translations | 220
          generated on June 20, 2012
Using Real or Keyword Messages
              This example illustrates the two different philosophies when creating messages to be translated:

              $t = $translator->trans('Symfony2 is great');                                                                 Listing
                                                                                                                            19-10

              $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).
              The second method is handy because the message key won't need to be changed in every
              translation file if we decide that the message should actually read "Symfony2 is really great" in the
              default locale.
              The choice of which method to use is entirely up to you, but the "keyword" format is often
              recommended.
              Additionally, the php and yaml file formats support nested ids to avoid repeating yourself if you
              use keywords instead of real text for your ids:

              symfony2:                                                                                                     Listing
                                                                                                                            19-11
                  is:
                      great: Symfony2 is great
                      amazing: Symfony2 is amazing
                  has:
                      bundles: Symfony2 has bundles
              user:
                  login: Login

              The multiple levels are flattened into single id/translation pairs by adding a dot (.) between every
              level, therefore the above examples are equivalent to the following:

              symfony2.is.great: Symfony2 is great                                                                          Listing
                                                                                                                            19-12
              symfony2.is.amazing: Symfony2 is amazing
              symfony2.has.bundles: Symfony2 has bundles
              user.login: Login




Using Message Domains
As we've seen, message files are organized into the different locales that they translate. The message
files can also be organized further into "domains". When creating message files, the domain is the first
portion of the filename. The default domain is messages. For example, suppose that, for organization,
translations were split into three different domains: messages, admin and navigation. The French
translation would have the following message files:

  • messages.fr.xliff
  • admin.fr.xliff
  • navigation.fr.xliff

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

$this->get('translator')->trans('Symfony2 is great', array(), 'admin');                                                     Listing
                                                                                                                            19-13



PDF brought to you by                                                                      Chapter 19: Translations | 221
generated on June 20, 2012
Symfony2 will now look for the message in the admin domain of the user's locale.



          Handling the User's Locale
          The locale of the current user is stored in the session and is accessible via the session service:

Listing   $locale = $this->get('session')->getLocale();
19-14

          $this->get('session')->setLocale('en_US');


          Fallback and Default Locale
          If the locale hasn't been set explicitly in the session, the fallback_locale configuration parameter will
          be used by the Translator. The parameter defaults to en (see Configuration).
          Alternatively, you can guarantee that a locale is set on the user's session by defining a default_locale
          for the session service:

Listing   # app/config/config.yml
19-15
          framework:
              session: { default_locale: en }


          The Locale and the URL
          Since the locale of the user is stored in the session, it may be tempting to use the same URL to display a
          resource in many different languages based on the user's locale. For example, http://www.example.com/
          contact could show content in English for one user and French for another user. Unfortunately, this
          violates a fundamental rule of the Web: that a particular URL returns the same resource regardless of the
          user. To further muddy the problem, which version of the content would be indexed by search engines?
          A better policy is to include the locale in the URL. This is fully-supported by the routing system using the
          special _locale parameter:

Listing   contact:
19-16
              pattern: /{_locale}/contact
              defaults: { _controller: AcmeDemoBundle:Contact:index, _locale: en }
              requirements:
                  _locale: en|fr|de

          When using the special _locale parameter in a route, the matched locale will automatically be set on the
          user's session. In other words, if a user visits the URI /fr/contact, the locale fr will automatically be set
          as the locale for the user's session.
          You can now use the user's locale to create routes to other translated pages in your application.



          Pluralization
          Message pluralization is a tough topic as the rules can be quite complex. For instance, here is the
          mathematic representation of the Russian pluralization rules:

Listing   (($number % 10 == 1) && ($number % 100 != 11)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <=
19-17
          4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2);

          As you can see, in Russian, you can have three different plural forms, each given an index of 0, 1 or 2.
          For each form, the plural is different, and so the translation is also different.


          PDF brought to you by                                                               Chapter 19: Translations | 222
          generated on June 20, 2012
When a translation has different forms due to pluralization, you can provide all the forms as a string
separated by a pipe (|):

'There is one apple|There are %count% apples'                                                                                Listing
                                                                                                                             19-18

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

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

The second argument (10 in this example), is the number of objects being described and is used to
determine which translation to use and also to populate the %count% placeholder.
Based on the given number, the translator chooses the right plural form. In English, most words have a
singular form when there is exactly one object and a plural form for all other numbers (0, 2, 3...). So, if
count is 1, the translator will use the first string (There is one apple) as the translation. Otherwise it
will use There are %count% apples.
Here is the French translation:

'Il y a %count% pomme|Il y a %count% pommes'                                                                                 Listing
                                                                                                                             19-20

Even if the string looks similar (it is made of two sub-strings separated by a pipe), the French rules are
different: the first form (no plural) is used when count is 0 or 1. So, the translator will automatically use
the first string (Il y a %count% pomme) when count is 0 or 1.
Each locale has its own set of rules, with some having as many as six different plural forms with complex
rules behind which numbers map to which plural form. The rules are quite simple for English and
French, but for Russian, you'd may want a hint to know which rule matches which string. To help
translators, you can optionally "tag" each string:

'one: There is one apple|some: There are %count% apples'                                                                     Listing
                                                                                                                             19-21

'none_or_one: Il y a %count% pomme|some: Il y a %count% pommes'

The tags are really only hints for translators and don't affect the logic used to determine which plural
form to use. The tags can be any descriptive string that ends with a colon (:). The tags also do not need
to be the same in the original message as in the translated one.

Explicit Interval Pluralization
The easiest way to pluralize a message is to let Symfony2 use internal logic to choose which string to
use based on a given number. Sometimes, you'll need more control or want a different translation for
specific cases (for 0, or when the count is negative, for example). For such cases, you can use explicit
math intervals:

'{0} There are no apples|{1} There is one apple|]1,19] There are %count% apples|[20,Inf] There                               Listing
                                                                                                                             19-22
are many apples'

The intervals follow the ISO 31-1110 notation. The above string specifies four different intervals: exactly
0, exactly 1, 2-19, and 20 and higher.
You can also mix explicit math rules and standard rules. In this case, if the count is not matched by a
specific interval, the standard rules take effect after removing the explicit rules:

9. http://api.symfony.com/2.0/Symfony/Component/Translation/Translator.html#transChoice()
10. http://en.wikipedia.org/wiki/Interval_%28mathematics%29#The_ISO_notation


PDF brought to you by                                                                       Chapter 19: Translations | 223
generated on June 20, 2012
Listing   '{0} There are no apples|[20,Inf] There are many apples|There is one apple|a_few: There are
19-23
          %count% apples'

          For example, for 1 apple, the standard rule There is one apple will be used. For 2-19 apples, the
          second standard rule There are %count% apples will be selected.
          An Interval11 can represent a finite set of numbers:

Listing   {1,2,3,4}
19-24

          Or numbers between two other numbers:

Listing   [1, +Inf[
19-25
          ]-1,2[

          The left delimiter can be [ (inclusive) or ] (exclusive). The right delimiter can be [ (exclusive) or ]
          (inclusive). Beside numbers, you can use -Inf and +Inf for the infinite.



          Translations in Templates
          Most of the time, translation occurs in templates. Symfony2 provides native support for both Twig and
          PHP templates.

          Twig Templates
          Symfony2 provides specialized Twig tags (trans and transchoice) to help with message translation of
          static blocks of text:

Listing   {% trans %}Hello %name%{% endtrans %}
19-26

          {% transchoice count %}
              {0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples
          {% endtranschoice %}

          The transchoice tag automatically gets the %count% variable from the current context and passes it to
          the translator. This mechanism only works when you use a placeholder following the %var% pattern.


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


          You can also specify the message domain and pass some additional variables:

Listing   {% trans with {'%name%': 'Fabien'} from "app" %}Hello %name%{% endtrans %}
19-27

          {% trans with {'%name%': 'Fabien'} from "app" into "fr" %}Hello %name%{% endtrans %}

          {% transchoice count with {'%name%': 'Fabien'} from "app" %}
              {0} There is no apples|{1} There is one apple|]1,Inf] There are %count% apples
          {% endtranschoice %}

          The trans and transchoice filters can be used to translate variable texts and complex expressions:

Listing   {{ message|trans }}
19-28



          11. http://api.symfony.com/2.0/Symfony/Component/Translation/Interval.html


          PDF brought to you by                                                             Chapter 19: Translations | 224
          generated on June 20, 2012
{{ message|transchoice(5) }}

{{ message|trans({'%name%': 'Fabien'}, "app") }}

{{ message|transchoice(5, {'%name%': 'Fabien'}, 'app') }}



              Using the translation tags or filters have the same effect, but with one subtle difference: automatic
              output escaping is only applied to variables translated using a filter. In other words, if you need to
              be sure that your translated variable is not output escaped, you must apply the raw filter after the
              translation filter:

              {# text translated between tags is never escaped #}                                                          Listing
                                                                                                                           19-29
              {% trans %}
                  <h3>foo</h3>
              {% endtrans %}

              {% set message = '<h3>foo</h3>' %}

              {# a variable translated via a filter is escaped by default #}
              {{ message|trans|raw }}

              {# but static strings are never escaped #}
              {{ '<h3>foo</h3>'|trans }}




PHP Templates
The translator service is accessible in PHP templates through the translator helper:

<?php echo $view['translator']->trans('Symfony2 is great') ?>                                                              Listing
                                                                                                                           19-30

<?php echo $view['translator']->transChoice(
     '{0} There is no apples|{1} There is one apple|]1,Inf[ There are %count% apples',
     10,
     array('%count%' => 10)
) ?>



Forcing the Translator Locale
When translating a message, Symfony2 uses the locale from the user's session or the fallback locale if
necessary. You can also manually specify the locale to use for translation:

$this->get('translator')->trans(                                                                                           Listing
                                                                                                                           19-31
    'Symfony2 is great',
    array(),
    'messages',
    'fr_FR',
);

$this->get('translator')->transChoice(
    '{0} There are no apples|{1} There is one apple|]1,Inf[ There are %count% apples',
    10,
    array('%count%' => 10),
    'messages',
    'fr_FR',
);



PDF brought to you by                                                                     Chapter 19: Translations | 225
generated on June 20, 2012
Translating Database Content
          The translation of database content should be handled by Doctrine through the Translatable Extension12.
          For more information, see the documentation for that library.



          Translating Constraint Messages
          The best way to understand constraint translation is to see it in action. To start, suppose you've created
          a plain-old-PHP object that you need to use somewhere in your application:

Listing   // src/Acme/BlogBundle/Entity/Author.php
19-32
          namespace AcmeBlogBundleEntity;

          class Author
          {
              public $name;
          }

          Add constraints though any of the supported methods. Set the message option to the translation source
          text. For example, to guarantee that the $name property is not empty, add the following:

Listing   # src/Acme/BlogBundle/Resources/config/validation.yml
19-33
          AcmeBlogBundleEntityAuthor:
              properties:
                  name:
                      - NotBlank: { message: "author.name.not_blank" }

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

Listing   <!-- validators.fr.xliff -->
19-34
          <?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>



          Summary
          With the Symfony2 Translation component, creating an internationalized application no longer needs to
          be a painful process and boils down to just a few basic steps:

            • Abstract messages in your application by wrapping each in either the trans()13 or
              transChoice()14 methods;



          12. https://github.com/l3pp4rd/DoctrineExtensions
          13. http://api.symfony.com/2.0/Symfony/Component/Translation/Translator.html#trans()
          14. http://api.symfony.com/2.0/Symfony/Component/Translation/Translator.html#transChoice()


          PDF brought to you by                                                                        Chapter 19: Translations | 226
          generated on June 20, 2012
• Translate each message into multiple locales by creating translation message files. Symfony2
    discovers and processes each file because its name follows a specific convention;
  • Manage the user's locale, which is stored in the session.




PDF brought to you by                                                         Chapter 19: Translations | 227
generated on June 20, 2012
Chapter 20
                                      Service Container

A modern PHP application is full of objects. One object may facilitate the delivery of email messages
while another may allow you to persist information into a database. In your application, you may create
an object that manages your product inventory, or another object that processes data from a third-party
API. The point is that a modern application does many things and is organized into many objects that
handle each task.
In this chapter, we'll talk about a special PHP object in Symfony2 that helps you instantiate, organize and
retrieve the many objects of your application. This object, called a service container, will allow you to
standardize and centralize the way objects are constructed in your application. The container makes your
life easier, is super fast, and emphasizes an architecture that promotes reusable and decoupled code. And
since all core Symfony2 classes use the container, you'll learn how to extend, configure and use any object
in Symfony2. In large part, the service container is the biggest contributor to the speed and extensibility
of Symfony2.
Finally, configuring and using the service container is easy. By the end of this chapter, you'll be
comfortable creating your own objects via the container and customizing objects from any third-party
bundle. You'll begin writing code that is more reusable, testable and decoupled, simply because the
service container makes writing good code so easy.



What is a Service?
Put simply, a Service is any PHP object that performs some sort of "global" task. It's a purposefully-generic
name used in computer science to describe an object that's created for a specific purpose (e.g. delivering
emails). Each service is used throughout your application whenever you need the specific functionality it
provides. You don't have to do anything special to make a service: simply write a PHP class with some
code that accomplishes a specific task. Congratulations, you've just created a service!


              As a rule, a PHP object is a service if it is used globally in your application. A single Mailer service
              is used globally to send email messages whereas the many Message objects that it delivers are not
              services. Similarly, a Product object is not a service, but an object that persists Product objects to
              a database is a service.




PDF brought to you by                                                                  Chapter 20: Service Container | 228
generated on June 20, 2012
So what's the big deal then? The advantage of thinking about "services" is that you begin to think about
separating each piece of functionality in your application into a series of services. Since each service does
just one job, you can easily access each service and use its functionality wherever you need it. Each service
can also be more easily tested and configured since it's separated from the other functionality in your
application. This idea is called service-oriented architecture1 and is not unique to Symfony2 or even PHP.
Structuring your application around a set of independent service classes is a well-known and trusted
object-oriented best-practice. These skills are key to being a good developer in almost any language.



What is a Service Container?
A Service Container (or dependency injection container) is simply a PHP object that manages the
instantiation of services (i.e. objects). For example, suppose we have a simple PHP class that delivers
email messages. Without a service container, we must manually create the object whenever we need it:

use AcmeHelloBundleMailer;                                                                                          Listing
                                                                                                                       20-1

$mailer = new Mailer('sendmail');
$mailer->send('ryan@foobar.net', ... );

This is easy enough. The imaginary Mailer class allows us to configure the method used to deliver the
email messages (e.g. sendmail, smtp, etc). But what if we wanted to use the mailer service somewhere
else? We certainly don't want to repeat the mailer configuration every time we need to use the Mailer
object. What if we needed to change the transport from sendmail to smtp everywhere in the
application? We'd need to hunt down every place we create a Mailer service and change it.



Creating/Configuring Services in the Container
A better answer is to let the service container create the Mailer object for you. In order for this to work,
we must teach the container how to create the Mailer service. This is done via configuration, which can
be specified in YAML, XML or PHP:

# app/config/config.yml                                                                                               Listing
                                                                                                                       20-2
services:
    my_mailer:
        class:        AcmeHelloBundleMailer
        arguments:    [sendmail]



              When Symfony2 initializes, it builds the service container using the application configuration
              (app/config/config.yml by default). The exact file that's loaded is dictated by the
              AppKernel::registerContainerConfiguration() method, which loads an environment-specific
              configuration file (e.g. config_dev.yml for the dev environment or config_prod.yml for prod).

An instance of the AcmeHelloBundleMailer object is now available via the service container. The
container is available in any traditional Symfony2 controller where you can access the services of the
container via the get() shortcut method:

class HelloController extends Controller                                                                              Listing
                                                                                                                       20-3
{
    // ...

     public function sendEmailAction()


1. http://wikipedia.org/wiki/Service-oriented_architecture


PDF brought to you by                                                           Chapter 20: Service Container | 229
generated on June 20, 2012
{
                         // ...
                         $mailer = $this->get('my_mailer');
                         $mailer->send('ryan@foobar.net', ... );
                }
          }

          When we ask for the my_mailer service from the container, the container constructs the object and
          returns it. This is another major advantage of using the service container. Namely, a service is never
          constructed until it's needed. If you define a service and never use it on a request, the service is never
          created. This saves memory and increases the speed of your application. This also means that there's very
          little or no performance hit for defining lots of services. Services that are never used are never constructed.
          As an added bonus, the Mailer service is only created once and the same instance is returned each time
          you ask for the service. This is almost always the behavior you'll need (it's more flexible and powerful),
          but we'll learn later how you can configure a service that has multiple instances.



          Service Parameters
          The creation of new services (i.e. objects) via the container is pretty straightforward. Parameters make
          defining services more organized and flexible:

Listing   # app/config/config.yml
 20-4
          parameters:
              my_mailer.class:     AcmeHelloBundleMailer
              my_mailer.transport: sendmail

          services:
              my_mailer:
                  class:                  %my_mailer.class%
                  arguments:              [%my_mailer.transport%]

          The end result is exactly the same as before - the difference is only in how we defined the service. By
          surrounding the my_mailer.class and my_mailer.transport strings in percent (%) signs, the container
          knows to look for parameters with those names. When the container is built, it looks up the value of each
          parameter and uses it in the service definition.


                              The percent sign inside a parameter or argument, as part of the string, must be escaped with
                              another percent sign:

                    Listing   <argument type="string">http://symfony.com/?foo=%%s&bar=%%d</argument>
                     20-5




          The purpose of parameters is to feed information into services. Of course there was nothing wrong with
          defining the service without using any parameters. Parameters, however, have several advantages:

              • separation and organization of all service "options" under a single parameters key;
              • parameter values can be used in multiple service definitions;
              • when creating a service in a bundle (we'll show this shortly), using parameters allows the
                service to be easily customized in your application.

          The choice of using or not using parameters is up to you. High-quality third-party bundles will always
          use parameters as they make the service stored in the container more configurable. For the services in
          your application, however, you may not need the flexibility of parameters.




          PDF brought to you by                                                               Chapter 20: Service Container | 230
          generated on June 20, 2012
Array Parameters
Parameters do not need to be flat strings, they can also be arrays. For the XML format, you need to use
the type="collection" attribute for all parameters that are arrays.

# app/config/config.yml                                                                                                      Listing
                                                                                                                              20-6
parameters:
    my_mailer.gateways:
        - mail1
        - mail2
        - mail3
    my_multilang.language_fallback:
        en:
            - en
            - fr
        fr:
            - fr
            - en



Importing other Container Configuration Resources

              In this section, we'll refer to service configuration files as resources. This is to highlight that fact
              that, while most configuration resources will be files (e.g. YAML, XML, PHP), Symfony2 is so
              flexible that configuration could be loaded from anywhere (e.g. a database or even via an external
              web service).

The service container is built using a single configuration resource (app/config/config.yml by default).
All other service configuration (including the core Symfony2 and third-party bundle configuration) must
be imported from inside this file in one way or another. This gives you absolute flexibility over the
services in your application.
External service configuration can be imported in two different ways. First, we'll talk about the method
that you'll use most commonly in your application: the imports directive. In the following section,
we'll introduce the second method, which is the flexible and preferred method for importing service
configuration from third-party bundles.

Importing Configuration with imports
So far, we've placed our my_mailer service container definition directly in the application configuration
file (e.g. app/config/config.yml). Of course, since the Mailer class itself lives inside the
AcmeHelloBundle, it makes more sense to put the my_mailer container definition inside the bundle as
well.
First, move the my_mailer container definition into a new container resource file inside
AcmeHelloBundle. If the Resources or Resources/config directories don't exist, create them.

# src/Acme/HelloBundle/Resources/config/services.yml                                                                         Listing
                                                                                                                              20-7
parameters:
    my_mailer.class:      AcmeHelloBundleMailer
    my_mailer.transport: sendmail

services:
    my_mailer:
        class:               %my_mailer.class%
        arguments:           [%my_mailer.transport%]



PDF brought to you by                                                                  Chapter 20: Service Container | 231
generated on June 20, 2012
The definition itself hasn't changed, only its location. Of course the service container doesn't know about
          the new resource file. Fortunately, we can easily import the resource file using the imports key in the
          application configuration.

Listing   # app/config/config.yml
 20-8
          imports:
              - { resource: @AcmeHelloBundle/Resources/config/services.yml }

          The imports directive allows your application to include service container configuration resources from
          any other location (most commonly from bundles). The resource location, for files, is the absolute path
          to the resource file. The special @AcmeHello syntax resolves the directory path of the AcmeHelloBundle
          bundle. This helps you specify the path to the resource without worrying later if you move the
          AcmeHelloBundle to a different directory.


          Importing Configuration via Container Extensions
          When developing in Symfony2, you'll most commonly use the imports directive to import container
          configuration from the bundles you've created specifically for your application. Third-party bundle
          container configuration, including Symfony2 core services, are usually loaded using another method
          that's more flexible and easy to configure in your application.
          Here's how it works. Internally, each bundle defines its services very much like we've seen so far. Namely,
          a bundle uses one or more configuration resource files (usually XML) to specify the parameters and
          services for that bundle. However, instead of importing each of these resources directly from your
          application configuration using the imports directive, you can simply invoke a service container extension
          inside the bundle that does the work for you. A service container extension is a PHP class created by the
          bundle author to accomplish two things:

            • import all service container resources needed to configure the services for the bundle;
            • provide semantic, straightforward configuration so that the bundle can be configured without
              interacting with the flat parameters of the bundle's service container configuration.

          In other words, a service container extension configures the services for a bundle on your behalf. And as
          we'll see in a moment, the extension provides a sensible, high-level interface for configuring the bundle.
          Take the FrameworkBundle - the core Symfony2 framework bundle - as an example. The presence of
          the following code in your application configuration invokes the service container extension inside the
          FrameworkBundle:

Listing   # app/config/config.yml
 20-9
          framework:
              secret:          xxxxxxxxxx
              charset:         UTF-8
              form:            true
              csrf_protection: true
              router:        { resource: "%kernel.root_dir%/config/routing.yml" }
              # ...

          When the configuration is parsed, the container looks for an extension that can handle the framework
          configuration directive. The extension in question, which lives in the FrameworkBundle, is invoked and
          the service configuration for the FrameworkBundle is loaded. If you remove the framework key from
          your application configuration file entirely, the core Symfony2 services won't be loaded. The point is that
          you're in control: the Symfony2 framework doesn't contain any magic or perform any actions that you
          don't have control over.
          Of course you can do much more than simply "activate" the service container extension of the
          FrameworkBundle. Each extension allows you to easily customize the bundle, without worrying about
          how the internal services are defined.


          PDF brought to you by                                                         Chapter 20: Service Container | 232
          generated on June 20, 2012
In this case, the extension allows you to customize the charset, error_handler, csrf_protection,
router configuration and much more. Internally, the FrameworkBundle uses the options specified here
to define and configure the services specific to it. The bundle takes care of creating all the necessary
parameters and services for the service container, while still allowing much of the configuration to
be easily customized. As an added bonus, most service container extensions are also smart enough to
perform validation - notifying you of options that are missing or the wrong data type.
When installing or configuring a bundle, see the bundle's documentation for how the services for the
bundle should be installed and configured. The options available for the core bundles can be found inside
the Reference Guide.


              Natively, the service container only recognizes the parameters, services, and imports directives.
              Any other directives are handled by a service container extension.


If you want to expose user friendly configuration in your own bundles, read the "How to expose a
Semantic Configuration for a Bundle" cookbook recipe.



Referencing (Injecting) Services
So far, our original my_mailer service is simple: it takes just one argument in its constructor, which is
easily configurable. As you'll see, the real power of the container is realized when you need to create a
service that depends on one or more other services in the container.
Let's start with an example. Suppose we have a new service, NewsletterManager, that helps to manage
the preparation and delivery of an email message to a collection of addresses. Of course the my_mailer
service is already really good at delivering email messages, so we'll use it inside NewsletterManager to
handle the actual delivery of the messages. This pretend class might look something like this:

namespace AcmeHelloBundleNewsletter;                                                                                  Listing
                                                                                                                        20-10

use AcmeHelloBundleMailer;

class NewsletterManager
{
    protected $mailer;

     public function __construct(Mailer $mailer)
     {
         $this->mailer = $mailer;
     }

     // ...
}

Without using the service container, we can create a new NewsletterManager fairly easily from inside a
controller:

public function sendNewsletterAction()                                                                                  Listing
                                                                                                                        20-11
{
    $mailer = $this->get('my_mailer');
    $newsletter = new AcmeHelloBundleNewsletterNewsletterManager($mailer);
    // ...
}

This approach is fine, but what if we decide later that the NewsletterManager class needs a second or
third constructor argument? What if we decide to refactor our code and rename the class? In both cases,

PDF brought to you by                                                             Chapter 20: Service Container | 233
generated on June 20, 2012
you'd need to find every place where the NewsletterManager is instantiated and modify it. Of course,
          the service container gives us a much more appealing option:

Listing   # src/Acme/HelloBundle/Resources/config/services.yml
20-12
          parameters:
              # ...
              newsletter_manager.class: AcmeHelloBundleNewsletterNewsletterManager

          services:
              my_mailer:
                  # ...
              newsletter_manager:
                  class:     %newsletter_manager.class%
                  arguments: [@my_mailer]

          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.
          Using references is a very powerful tool that allows you to create independent service classes with well-
          defined dependencies. In this example, the newsletter_manager service needs the my_mailer service in
          order to function. When you define this dependency in the service container, the container takes care of
          all the work of instantiating the objects.

          Optional Dependencies: Setter Injection
          Injecting dependencies into the constructor in this manner is an excellent way of ensuring that the
          dependency is available to use. If you have optional dependencies for a class, then "setter injection" may
          be a better option. This means injecting the dependency using a method call rather than through the
          constructor. The class would look like this:

Listing   namespace AcmeHelloBundleNewsletter;
20-13

          use AcmeHelloBundleMailer;

          class NewsletterManager
          {
              protected $mailer;

               public function setMailer(Mailer $mailer)
               {
                   $this->mailer = $mailer;
               }

               // ...
          }

          Injecting the dependency by the setter method just needs a change of syntax:

Listing   # src/Acme/HelloBundle/Resources/config/services.yml
20-14
          parameters:
              # ...
              newsletter_manager.class: AcmeHelloBundleNewsletterNewsletterManager

          services:
              my_mailer:
                  # ...
              newsletter_manager:
                  class:     %newsletter_manager.class%

          PDF brought to you by                                                          Chapter 20: Service Container | 234
          generated on June 20, 2012
calls:
               - [ setMailer, [ @my_mailer ] ]



              The approaches presented in this section are called "constructor injection" and "setter injection".
              The Symfony2 service container also supports "property injection".




Making References Optional
Sometimes, one of your services may have an optional dependency, meaning that the dependency is
not required for your service to work properly. In the example above, the my_mailer service must exist,
otherwise an exception will be thrown. By modifying the newsletter_manager service definition, you
can make this reference optional. The container will then inject it if it exists and do nothing if it doesn't:

# src/Acme/HelloBundle/Resources/config/services.yml                                                                     Listing
                                                                                                                         20-15
parameters:
    # ...

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

In YAML, the special @? syntax tells the service container that the dependency is optional. Of course, the
NewsletterManager must also be written to allow for an optional dependency:

public function __construct(Mailer $mailer = null)                                                                       Listing
                                                                                                                         20-16
{
    // ...
}



Core Symfony and Third-Party Bundle Services
Since Symfony2 and all third-party bundles configure and retrieve their services via the container, you can
easily access them or even use them in your own services. To keep things simple, Symfony2 by default
does not require that controllers be defined as services. Furthermore Symfony2 injects the entire service
container into your controller. For example, to handle the storage of information on a user's session,
Symfony2 provides a session service, which you can access inside a standard controller as follows:

public function indexAction($bar)                                                                                        Listing
                                                                                                                         20-17
{
    $session = $this->get('session');
    $session->set('foo', $bar);

     // ...
}

In Symfony2, you'll constantly use services provided by the Symfony core or other third-party bundles
to perform tasks such as rendering templates (templating), sending emails (mailer), or accessing
information on the request (request).
We can take this a step further by using these services inside services that you've created for your
application. Let's modify the NewsletterManager to use the real Symfony2 mailer service (instead of the



PDF brought to you by                                                              Chapter 20: Service Container | 235
generated on June 20, 2012
pretend my_mailer). Let's also pass the templating engine service to the NewsletterManager so that it
          can generate the email content via a template:

Listing   namespace AcmeHelloBundleNewsletter;
20-18

          use SymfonyComponentTemplatingEngineInterface;

          class NewsletterManager
          {
              protected $mailer;

               protected $templating;

               public function __construct(Swift_Mailer $mailer, EngineInterface $templating)
               {
                   $this->mailer = $mailer;
                   $this->templating = $templating;
               }

               // ...
          }

          Configuring the service container is easy:

Listing   services:
20-19
              newsletter_manager:
                  class:     %newsletter_manager.class%
                  arguments: [@mailer, @templating]

          The newsletter_manager service now has access to the core mailer and templating services. This is a
          common way to create services specific to your application that leverage the power of different services
          within the framework.


                        Be sure that swiftmailer entry appears in your application configuration. As we mentioned
                        in Importing Configuration via Container Extensions, the swiftmailer key invokes the service
                        extension from the SwiftmailerBundle, which registers the mailer service.




          Advanced Container Configuration
          As we've seen, defining services inside the container is easy, generally involving a service configuration
          key and a few parameters. However, the container has several other tools available that help to tag
          services for special functionality, create more complex services, and perform operations after the
          container is built.

          Marking Services as public / private
          When defining services, you'll usually want to be able to access these definitions within your application
          code. These services are called public. For example, the doctrine service registered with the container
          when using the DoctrineBundle is a public service as you can access it via:

Listing   $doctrine = $container->get('doctrine');
20-20

          However, there are use-cases when you don't want a service to be public. This is common when a service
          is only defined because it could be used as an argument for another service.



          PDF brought to you by                                                         Chapter 20: Service Container | 236
          generated on June 20, 2012
If you use a private service as an argument to more than one other service, this will result in two
              different instances being used as the instantiation of the private service is done inline (e.g. new
              PrivateFooBar()).

Simply said: A service will be private when you do not want to access it directly from your code.
Here is an example:

services:                                                                                                                Listing
                                                                                                                         20-21
   foo:
     class: AcmeHelloBundleFoo
     public: false

Now that the service is private, you cannot call:

$container->get('foo');                                                                                                  Listing
                                                                                                                         20-22

However, if a service has been marked as private, you can still alias it (see below) to access this service
(via the alias).


              Services are by default public.




Aliasing
When using core or third party bundles within your application, you may want to use shortcuts to access
some services. You can do so by aliasing them and, furthermore, you can even alias non-public services.

services:                                                                                                                Listing
                                                                                                                         20-23
   foo:
     class: AcmeHelloBundleFoo
   bar:
     alias: foo

This means that when using the container directly, you can access the foo service by asking for the bar
service like this:

$container->get('bar'); // Would return the foo service                                                                  Listing
                                                                                                                         20-24



Requiring files
There might be use cases when you need to include another file just before the service itself gets loaded.
To do so, you can use the file directive.

services:                                                                                                                Listing
                                                                                                                         20-25
   foo:
     class: AcmeHelloBundleFooBar
     file: %kernel.root_dir%/src/path/to/file/foo.php

Notice that symfony will internally call the PHP function require_once which means that your file will be
included only once per request.




PDF brought to you by                                                              Chapter 20: Service Container | 237
generated on June 20, 2012
Tags (tags)
          In the same way that a blog post on the Web might be tagged with things such as "Symfony" or "PHP",
          services configured in your container can also be tagged. In the service container, a tag implies that the
          service is meant to be used for a specific purpose. Take the following example:

Listing   services:
20-26
              foo.twig.extension:
                  class: AcmeHelloBundleExtensionFooExtension
                  tags:
                      - { name: twig.extension }

          The twig.extension tag is a special tag that the TwigBundle uses during configuration. By giving
          the service this twig.extension tag, the bundle knows that the foo.twig.extension service should
          be registered as a Twig extension with Twig. In other words, Twig finds all services tagged with
          twig.extension and automatically registers them as extensions.
          Tags, then, are a way to tell Symfony2 or other third-party bundles that your service should be registered
          or used in some special way by the bundle.
          The following is a list of tags available with the core Symfony2 bundles. Each of these has a different
          effect on your service and many tags require additional arguments (beyond just the name parameter).

            •   assetic.filter
            •   assetic.templating.php
            •   data_collector
            •   form.field_factory.guesser
            •   kernel.cache_warmer
            •   kernel.event_listener
            •   monolog.logger
            •   routing.loader
            •   security.listener.factory
            •   security.voter
            •   templating.helper
            •   twig.extension
            •   translation.loader
            •   validator.constraint_validator



          Learn more
            • Using a Factory to Create Services
            • Managing Common Dependencies with Parent Services
            • How to define Controllers as Services




          PDF brought to you by                                                         Chapter 20: Service Container | 238
          generated on June 20, 2012
Chapter 21
                                                Performance

Symfony2 is fast, right out of the box. Of course, if you really need speed, there are many ways that you
can make Symfony even faster. In this chapter, you'll explore many of the most common and powerful
ways to make your Symfony application even faster.



Use a Byte Code Cache (e.g. APC)
One the best (and easiest) things that you should do to improve your performance is to use a "byte code
cache". The idea of a byte code cache is to remove the need to constantly recompile the PHP source code.
There are a number of byte code caches1 available, some of which are open source. The most widely used
byte code cache is probably APC2
Using a byte code cache really has no downside, and Symfony2 has been architected to perform really
well in this type of environment.

Further Optimizations
Byte code caches usually monitor the source files for changes. This ensures that if the source of a
file changes, the byte code is recompiled automatically. This is really convenient, but obviously adds
overhead.
For this reason, some byte code caches offer an option to disable these checks. Obviously, when disabling
these checks, it will be up to the server admin to ensure that the cache is cleared whenever any source
files change. Otherwise, the updates you've made won't be seen.
For example, to disable these checks in APC, simply add apc.stat=0 to your php.ini configuration.




1. http://en.wikipedia.org/wiki/List_of_PHP_accelerators
2. http://php.net/manual/en/book.apc.php


PDF brought to you by                                                            Chapter 21: Performance | 239
generated on June 20, 2012
Use an Autoloader that caches (e.g. ApcUniversalClassLoader)
          By default, the Symfony2 standard edition uses the UniversalClassLoader in the autoloader.php3 file.
          This autoloader is easy to use, as it will automatically find any new classes that you've placed in the
          registered directories.
          Unfortunately, this comes at a cost, as the loader iterates over all configured namespaces to find a
          particular file, making file_exists calls until it finally finds the file it's looking for.
          The simplest solution is to cache the location of each class after it's located the first time. Symfony comes
          with a class - ApcUniversalClassLoader - loader that extends the UniversalClassLoader and stores
          the class locations in APC.
          To use this class loader, simply adapt your autoloader.php as follows:

Listing   // app/autoload.php
 21-1
          require __DIR__.'/../vendor/symfony/src/Symfony/Component/ClassLoader/
          ApcUniversalClassLoader.php';

          use SymfonyComponentClassLoaderApcUniversalClassLoader;

          $loader = new ApcUniversalClassLoader('some caching unique prefix');
          // ...


                        When using the APC autoloader, if you add new classes, they will be found automatically and
                        everything will work the same as before (i.e. no reason to "clear" the cache). However, if you
                        change the location of a particular namespace or prefix, you'll need to flush your APC cache.
                        Otherwise, the autoloader will still be looking at the old location for all classes inside that
                        namespace.



          Use Bootstrap Files
          To ensure optimal flexibility and code reuse, Symfony2 applications leverage a variety of classes and 3rd
          party components. But loading all of these classes from separate files on each request can result in some
          overhead. To reduce this overhead, the Symfony2 Standard Edition provides a script to generate a so-
          called bootstrap file4, consisting of multiple classes definitions in a single file. By including this file (which
          contains a copy of many of the core classes), Symfony no longer needs to include any of the source files
          containing those classes. This will reduce disc IO quite a bit.
          If you're using the Symfony2 Standard Edition, then you're probably already using the bootstrap file. To
          be sure, open your front controller (usually app.php) and check to make sure that the following line
          exists:

Listing   require_once __DIR__.'/../app/bootstrap.php.cache';
 21-2

          Note that there are two disadvantages when using a bootstrap file:

            • the file needs to be regenerated whenever any of the original sources change (i.e. when you
              update the Symfony2 source or vendor libraries);
            • when debugging, one will need to place break points inside the bootstrap file.

          If you're using Symfony2 Standard Edition, the bootstrap file is automatically rebuilt after updating the
          vendor libraries via the php bin/vendors install command.


          3. https://github.com/symfony/symfony-standard/blob/master/app/autoload.php
          4. https://github.com/sensio/SensioDistributionBundle/blob/2.0/Resources/bin/build_bootstrap.php


          PDF brought to you by                                                                              Chapter 21: Performance | 240
          generated on June 20, 2012
Bootstrap Files and Byte Code Caches
Even when using a byte code cache, performance will improve when using a bootstrap file since there
will be less files to monitor for changes. Of course if this feature is disabled in the byte code cache (e.g.
apc.stat=0 in APC), there is no longer a reason to use a bootstrap file.




PDF brought to you by                                                               Chapter 21: Performance | 241
generated on June 20, 2012
Chapter 22
                                                      Internals

Looks like you want to understand how Symfony2 works and how to extend it. That makes me very
happy! This section is an in-depth explanation of the Symfony2 internals.


              You need to read this section only if you want to understand how Symfony2 works behind the
              scene, or if you want to extend Symfony2.




Overview
The Symfony2 code is made of several independent layers. Each layer is built on top of the previous one.


              Autoloading is not managed by the framework directly; it's done independently with the help of
              the UniversalClassLoader1 class and the src/autoload.php file. Read the dedicated chapter for
              more information.


HttpFoundation Component
The deepest level is the HttpFoundation2 component. HttpFoundation provides the main objects needed
to deal with HTTP. It is an Object-Oriented abstraction of some native PHP functions and variables:

  • The Request3 class abstracts the main PHP global variables like $_GET, $_POST, $_COOKIE,
    $_FILES, and $_SERVER;
  • The Response4 class abstracts some PHP functions like header(), setcookie(), and echo;




1. http://api.symfony.com/2.0/Symfony/Component/ClassLoader/UniversalClassLoader.html
2. http://api.symfony.com/2.0/Symfony/Component/HttpFoundation.html
3. http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/Request.html
4. http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/Response.html


PDF brought to you by                                                                   Chapter 22: Internals | 242
generated on June 20, 2012
• The Session5 class and SessionStorageInterface6 interface abstract session management
    session_*() functions.


HttpKernel Component
On top of HttpFoundation is the HttpKernel7 component. HttpKernel handles the dynamic part of
HTTP; it is a thin wrapper on top of the Request and Response classes to standardize the way requests
are handled. It also provides extension points and tools that makes it the ideal starting point to create a
Web framework without too much overhead.
It also optionally adds configurability and extensibility, thanks to the Dependency Injection component
and a powerful plugin system (bundles).

    Read more about Dependency Injection and Bundles.


FrameworkBundle Bundle
The FrameworkBundle8 bundle is the bundle that ties the main components and libraries together
to make a lightweight and fast MVC framework. It comes with a sensible default configuration and
conventions to ease the learning curve.



Kernel
The HttpKernel9 class is the central class of Symfony2 and is responsible for handling client requests. Its
main goal is to "convert" a Request10 object to a Response11 object.
Every Symfony2 Kernel implements HttpKernelInterface12:

function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true)                                                             Listing
                                                                                                                                            22-1



Controllers
To convert a Request to a Response, the Kernel relies on a "Controller". A Controller can be any valid
PHP callable.
The Kernel delegates the selection of what Controller should be executed to an implementation of
ControllerResolverInterface13:

public function getController(Request $request);                                                                                           Listing
                                                                                                                                            22-2

public function getArguments(Request $request, $controller);

The getController()14 method returns the Controller (a PHP callable) associated with the given
Request. The default implementation (ControllerResolver15) looks for a _controller request attribute



5. http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/Session.html
6. http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/SessionStorage/SessionStorageInterface.html
7. http://api.symfony.com/2.0/Symfony/Component/HttpKernel.html
8. http://api.symfony.com/2.0/Symfony/Bundle/FrameworkBundle.html
9. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/HttpKernel.html
10. http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/Request.html
11. http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/Response.html
12. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/HttpKernelInterface.html
13. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/Controller/ControllerResolverInterface.html
14. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/Controller/ControllerResolverInterface.html#getController()
15. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/Controller/ControllerResolver.html


PDF brought to you by                                                                                        Chapter 22: Internals | 243
generated on June 20, 2012
that    represents   the    controller   name                                (a       "class::method"            string,         like
BundleBlogBundlePostController:indexAction).


                The default implementation uses the RouterListener16 to define the _controller Request
                attribute (see kernel.request Event).


The getArguments()17 method returns an array of arguments to pass to the Controller callable. The
default implementation automatically resolves the method arguments, based on the Request attributes.


                Matching Controller method arguments from Request attributes
                For each method argument, Symfony2 tries to get the value of a Request attribute with the same
                name. If it is not defined, the argument default value is used if defined:

      Listing   // Symfony2 will look for an 'id' attribute (mandatory)
       22-3
                // and an 'admin' one (optional)
                public function showAction($id, $admin = true)
                {
                    // ...
                }




Handling Requests
The handle() method takes a Request and always returns a Response. To convert the Request,
handle() relies on the Resolver and an ordered chain of Event notifications (see the next section for more
information about each Event):
     1. Before doing anything else, the kernel.request event is notified -- if one of the listeners returns
         a Response, it jumps to step 8 directly;
     2. The Resolver is called to determine the Controller to execute;
     3. Listeners of the kernel.controller event can now manipulate the Controller callable the way
         they want (change it, wrap it, ...);
     4. The Kernel checks that the Controller is actually a valid PHP callable;
     5. The Resolver is called to determine the arguments to pass to the Controller;
     6. The Kernel calls the Controller;
     7. If the Controller does not return a Response, listeners of the kernel.view event can convert the
         Controller return value to a Response;
     8. Listeners of the kernel.response event can manipulate the Response (content and headers);
     9. The Response is returned.
If an Exception is thrown during processing, the kernel.exception is notified and listeners are given a
chance to convert the Exception to a Response. If that works, the kernel.response event is notified; if
not, the Exception is re-thrown.
If you don't want Exceptions to be caught (for embedded requests for instance), disable the
kernel.exception event by passing false as the third argument to the handle() method.


Internal Requests
At any time during the handling of a request (the 'master' one), a sub-request can be handled. You can
pass the request type to the handle() method (its second argument):


16. http://api.symfony.com/2.0/Symfony/Bundle/FrameworkBundle/EventListener/RouterListener.html
17. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/Controller/ControllerResolverInterface.html#getArguments()


PDF brought to you by                                                                                      Chapter 22: Internals | 244
generated on June 20, 2012
• HttpKernelInterface::MASTER_REQUEST;
  • HttpKernelInterface::SUB_REQUEST.

The type is passed to all events and listeners can act accordingly (some processing must only occur on
the master request).

Events
Each event thrown by the Kernel is a subclass of KernelEvent18. This means that each event has access
to the same basic information:

  • getRequestType()         -      returns       the      type of   the       request
    (HttpKernelInterface::MASTER_REQUEST or HttpKernelInterface::SUB_REQUEST);
  • getKernel() - returns the Kernel handling the request;
  • getRequest() - returns the current Request being handled.

getRequestType()
The getRequestType() method allows listeners to know the type of the request. For instance, if a listener
must only be active for master requests, add the following code at the beginning of your listener method:

use SymfonyComponentHttpKernelHttpKernelInterface;                                                                           Listing
                                                                                                                                 22-4

if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
    // return immediately
    return;
}



              If you are not yet familiar with the Symfony2 Event Dispatcher, read the Event Dispatcher
              Component Documentation section first.



kernel.request Event
Event Class: GetResponseEvent19
The goal of this event is to either return a Response object immediately or setup variables so that a
Controller can be called after the event. Any listener can return a Response object via the setResponse()
method on the event. In this case, all other listeners won't be called.
This event is used by FrameworkBundle to populate the _controller Request attribute, via the
RouterListener20. RequestListener uses a RouterInterface21 object to match the Request and
determine the Controller name (stored in the _controller Request attribute).

kernel.controller Event
Event Class: FilterControllerEvent22
This event is not used by FrameworkBundle, but can be an entry point used to modify the controller that
should be executed:

                                                                                                                                Listing
                                                                                                                                 22-5



18. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/Event/KernelEvent.html
19. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/Event/GetResponseEvent.html
20. http://api.symfony.com/2.0/Symfony/Bundle/FrameworkBundle/EventListener/RouterListener.html
21. http://api.symfony.com/2.0/Symfony/Component/Routing/RouterInterface.html
22. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/Event/FilterControllerEvent.html


PDF brought to you by                                                                             Chapter 22: Internals | 245
generated on June 20, 2012
use SymfonyComponentHttpKernelEventFilterControllerEvent;

          public function onKernelController(FilterControllerEvent $event)
          {
              $controller = $event->getController();
              // ...

                  // the controller can be changed to any PHP callable
                  $event->setController($controller);
          }

          kernel.view Event
          Event Class: GetResponseForControllerResultEvent23
          This event is not used by FrameworkBundle, but it can be used to implement a view sub-system. This
          event is called only if the Controller does not return a Response object. The purpose of the event is to
          allow some other return value to be converted into a Response.
          The value returned by the Controller is accessible via the getControllerResult method:

Listing   use SymfonyComponentHttpKernelEventGetResponseForControllerResultEvent;
 22-6
          use SymfonyComponentHttpFoundationResponse;

          public function onKernelView(GetResponseForControllerResultEvent $event)
          {
              $val = $event->getControllerResult();
              $response = new Response();
              // some how customize the Response from the return value

                  $event->setResponse($response);
          }

          kernel.response Event
          Event Class: FilterResponseEvent24
          The purpose of this event is to allow other systems to modify or replace the Response object after its
          creation:

Listing   public function onKernelResponse(FilterResponseEvent $event)
 22-7
          {
              $response = $event->getResponse();
              // .. modify the response object
          }

          The FrameworkBundle registers several listeners:

              •   ProfilerListener25: collects data for the current request;
              •   WebDebugToolbarListener26: injects the Web Debug Toolbar;
              •   ResponseListener27: fixes the Response Content-Type based on the request format;
              •   EsiListener28: adds a Surrogate-Control HTTP header when the Response needs to be
                  parsed for ESI tags.



          23. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/Event/GetResponseForControllerResultEvent.html
          24. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/Event/FilterResponseEvent.html
          25. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/EventListener/ProfilerListener.html
          26. http://api.symfony.com/2.0/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.html
          27. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/EventListener/ResponseListener.html
          28. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/EventListener/EsiListener.html


          PDF brought to you by                                                                                        Chapter 22: Internals | 246
          generated on June 20, 2012
kernel.exception Event
Event Class: GetResponseForExceptionEvent29
FrameworkBundle registers an ExceptionListener30 that forwards the Request to a given Controller (the
value of the exception_listener.controller parameter -- must be in the class::method notation).
A listener on this event can create and set a Response object, create and set a new Exception object, or
do nothing:

use SymfonyComponentHttpKernelEventGetResponseForExceptionEvent;                                                                Listing
                                                                                                                                     22-8
use SymfonyComponentHttpFoundationResponse;

public function onKernelException(GetResponseForExceptionEvent $event)
{
    $exception = $event->getException();
    $response = new Response();
    // setup the Response object based on the caught exception
    $event->setResponse($response);

     // you can alternatively set a new Exception
     // $exception = new Exception('Some special exception');
     // $event->setException($exception);
}



The Event Dispatcher
The event dispatcher is a standalone component that is responsible for much of the underlying logic
and flow behind a Symfony request. For more information, see the Event Dispatcher Component
Documentation.



Profiler
When enabled, the Symfony2 profiler collects useful information about each request made to your
application and store them for later analysis. Use the profiler in the development environment to help you
to debug your code and enhance performance; use it in the production environment to explore problems
after the fact.
You rarely have to deal with the profiler directly as Symfony2 provides visualizer tools like the Web
Debug Toolbar and the Web Profiler. If you use the Symfony2 Standard Edition, the profiler, the web
debug toolbar, and the web profiler are all already configured with sensible settings.


              The profiler collects information for all requests (simple requests, redirects, exceptions, Ajax
              requests, ESI requests; and for all HTTP methods and all formats). It means that for a single URL,
              you can have several associated profiling data (one per external request/response pair).




29. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/Event/GetResponseForExceptionEvent.html
30. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/EventListener/ExceptionListener.html


PDF brought to you by                                                                                 Chapter 22: Internals | 247
generated on June 20, 2012
Visualizing Profiling Data

          Using the Web Debug Toolbar
          In the development environment, the web debug toolbar is available at the bottom of all pages. It displays
          a good summary of the profiling data that gives you instant access to a lot of useful information when
          something does not work as expected.
          If the summary provided by the Web Debug Toolbar is not enough, click on the token link (a string made
          of 13 random characters) to access the Web Profiler.


                        If the token is not clickable, it means that the profiler routes are not registered (see below for
                        configuration information).



          Analyzing Profiling data with the Web Profiler
          The Web Profiler is a visualization tool for profiling data that you can use in development to debug your
          code and enhance performance; but it can also be used to explore problems that occur in production. It
          exposes all information collected by the profiler in a web interface.

          Accessing the Profiling information
          You don't need to use the default visualizer to access the profiling information. But how can you retrieve
          profiling information for a specific request after the fact? When the profiler stores data about a Request,
          it also associates a token with it; this token is available in the X-Debug-Token HTTP header of the
          Response:

Listing   $profile = $container->get('profiler')->loadProfileFromResponse($response);
 22-9

          $profile = $container->get('profiler')->loadProfile($token);



                        When the profiler is enabled but not the web debug toolbar, or when you want to get the token for
                        an Ajax request, use a tool like Firebug to get the value of the X-Debug-Token HTTP header.


          Use the find() method to access tokens based on some criteria:

Listing   // get the latest 10 tokens
22-10
          $tokens = $container->get('profiler')->find('', '', 10);

          // get the latest 10 tokens for all URL containing /admin/
          $tokens = $container->get('profiler')->find('', '/admin/', 10);

          // get the latest 10 tokens for local requests
          $tokens = $container->get('profiler')->find('127.0.0.1', '', 10);

          If you want to manipulate profiling data on a different machine than the one where the information were
          generated, use the export() and import() methods:

Listing   // on the production machine
22-11
          $profile = $container->get('profiler')->loadProfile($token);
          $data = $profiler->export($profile);

          // on the development machine
          $profiler->import($data);


          PDF brought to you by                                                                     Chapter 22: Internals | 248
          generated on June 20, 2012
Configuration
The default Symfony2 configuration comes with sensible settings for the profiler, the web debug toolbar,
and the web profiler. Here is for instance the configuration for the development environment:

# load the profiler                                                                                               Listing
                                                                                                                  22-12
framework:
    profiler: { only_exceptions: false }

# enable the web profiler
web_profiler:
    toolbar: true
    intercept_redirects: true
    verbose: true

When only-exceptions is set to true, the profiler only collects data when an exception is thrown by the
application.
When intercept-redirects is set to true, the web profiler intercepts the redirects and gives you the
opportunity to look at the collected data before following the redirect.
When verbose is set to true, the Web Debug Toolbar displays a lot of information. Setting verbose to
false hides some secondary information to make the toolbar shorter.
If you enable the web profiler, you also need to mount the profiler routes:

_profiler:                                                                                                        Listing
                                                                                                                  22-13
    resource: @WebProfilerBundle/Resources/config/routing/profiler.xml
    prefix: /_profiler

As the profiler adds some overhead, you might want to enable it only under certain circumstances in the
production environment. The only-exceptions settings limits profiling to 500 pages, but what if you
want to get information when the client IP comes from a specific address, or for a limited portion of the
website? You can use a request matcher:

# enables the profiler only for request coming for the 192.168.0.0 network                                        Listing
                                                                                                                  22-14
framework:
    profiler:
        matcher: { ip: 192.168.0.0/24 }

# enables the profiler only for the /admin URLs
framework:
    profiler:
        matcher: { path: "^/admin/" }

# combine rules
framework:
    profiler:
        matcher: { ip: 192.168.0.0/24, path: "^/admin/" }

# use a custom matcher instance defined in the "custom_matcher" service
framework:
    profiler:
        matcher: { service: custom_matcher }



Learn more from the Cookbook
  • How to use the Profiler in a Functional Test
  • How to create a custom Data Collector
  • How to extend a Class without using Inheritance

PDF brought to you by                                                               Chapter 22: Internals | 249
generated on June 20, 2012
• How to customize a Method Behavior without using Inheritance




PDF brought to you by                                              Chapter 22: Internals | 250
generated on June 20, 2012
Chapter 23
                             The Symfony2 Stable API

The Symfony2 stable API is a subset of all Symfony2 published public methods (components and core
bundles) that share the following properties:

  •   The namespace and class name won't change;
  •   The method name won't change;
  •   The method signature (arguments and return value type) won't change;
  •   The semantic of what the method does won't change.

The implementation itself can change though. The only valid case for a change in the stable API is in
order to fix a security issue.
The stable API is based on a whitelist, tagged with @api. Therefore, everything not tagged explicitly is
not part of the stable API.


              Any third party bundle should also publish its own stable API.



As of Symfony 2.0, the following components have a public tagged API:

  •   BrowserKit
  •   ClassLoader
  •   Console
  •   CssSelector
  •   DependencyInjection
  •   DomCrawler
  •   EventDispatcher
  •   Finder
  •   HttpFoundation
  •   HttpKernel
  •   Locale
  •   Process
  •   Routing
  •   Templating
  •   Translation

PDF brought to you by                                                          Chapter 23: The Symfony2 Stable API | 251
generated on June 20, 2012
• Validator
  • Yaml




PDF brought to you by        Chapter 23: The Symfony2 Stable API | 252
generated on June 20, 2012
Part III
The Cookbook
Chapter 24
 How to Create and store a Symfony2 Project in
                      git


               Though this entry is specifically about git, the same generic principles will apply if you're storing
               your project in Subversion.


Once you've read through Creating Pages in Symfony2 and become familiar with using Symfony, you'll
no-doubt be ready to start your own project. In this cookbook article, you'll learn the best way to start a
new Symfony2 project that's stored using the git1 source control management system.



Initial Project Setup
To get started, you'll need to download Symfony and initialize your local git repository:
       1. Download the Symfony2 Standard Edition2 without vendors.
       2. Unzip/untar the distribution. It will create a folder called Symfony with your new project
          structure, config files, etc. Rename it to whatever you like.
       3. Create a new file called .gitignore at the root of your new project (e.g. next to the deps file)
          and paste the following into it. Files matching these patterns will be ignored by git:

    Listing   /web/bundles/
     24-1
              /app/bootstrap*
              /app/cache/*
              /app/logs/*
              /vendor/
              /app/config/parameters.ini




1. http://git-scm.com/
2. http://symfony.com/download


PDF brought to you by                                      Chapter 24: How to Create and store a Symfony2 Project in git | 254
generated on June 20, 2012
You may also want to create a .gitignore file that can be used system-wide, in which case, you can
              find more information here: Github .gitignore3 This way you can exclude files/folders often used by
              your IDE for all of your projects.

       4. Copy      app/config/parameters.ini            to   app/config/parameters.ini.dist.          The
          parameters.ini file is ignored by git (see above) so that machine-specific settings like database
          passwords aren't committed. By creating the parameters.ini.dist file, new developers can
          quickly clone the project, copy this file to parameters.ini, customize it, and start developing.
       5. Initialize your git repository:

           $ git init                                                                                                           Listing
                                                                                                                                 24-2

       6. Add all of the initial files to git:

           $ git add .                                                                                                          Listing
                                                                                                                                 24-3

       7. Create an initial commit with your started project:

           $ git commit -m "Initial commit"                                                                                     Listing
                                                                                                                                 24-4

       8. Finally, download all of the third-party vendor libraries:

           $ php bin/vendors install                                                                                            Listing
                                                                                                                                 24-5

At this point, you have a fully-functional Symfony2 project that's correctly committed to git. You can
immediately begin development, committing the new changes to your git repository.


              After execution of the command:

              $ php bin/vendors install                                                                                         Listing
                                                                                                                                 24-6

              your project will contain complete the git history of all the bundles and libraries defined in the
              deps file. It can be as much as 100 MB! If you save the current versions of all your dependencies
              with the command:

              $ php bin/vendors lock                                                                                            Listing
                                                                                                                                 24-7

              then you can remove the git history directories with the following command:

              $ find vendor -name .git -type d | xargs rm -rf                                                                   Listing
                                                                                                                                 24-8

              The command removes all .git directories contained inside the vendor directory.
              If you want to update bundles defined in deps file after this, you will have to reinstall them:

              $ php bin/vendors install --reinstall                                                                             Listing
                                                                                                                                 24-9




You can continue to follow along with the Creating Pages in Symfony2 chapter to learn more about how
to configure and develop inside your application.




3. http://help.github.com/ignore-files/


PDF brought to you by                                     Chapter 24: How to Create and store a Symfony2 Project in git | 255
generated on June 20, 2012
The Symfony2 Standard Edition comes with some example functionality. To remove the sample
                        code, follow the instructions on the Standard Edition Readme4.




          Managing Vendor Libraries with bin/vendors and deps
          How does it work?
          Every Symfony project uses a group of third-party "vendor" libraries. One way or another the goal is to
          download these files into your vendor/ directory and, ideally, to give you some sane way to manage the
          exact version you need for each.
          By default, these libraries are downloaded by running a php bin/vendors install "downloader" script.
          This script reads from the deps file at the root of your project. This is an ini-formatted script, which
          holds a list of each of the external libraries you need, the directory each should be downloaded to, and
          (optionally) the version to be downloaded. The bin/vendors script uses git to downloaded these, solely
          because these external libraries themselves tend to be stored via git. The bin/vendors script also reads
          the deps.lock file, which allows you to pin each library to an exact git commit hash.
          It's important to realize that these vendor libraries are not actually part of your repository. Instead, they're
          simply un-tracked files that are downloaded into the vendor/ directory by the bin/vendors script. But
          since all the information needed to download these files is saved in deps and deps.lock (which are
          stored) in our repository), any other developer can use our project, run php bin/vendors install, and
          download the exact same set of vendor libraries. This means that you're controlling exactly what each
          vendor library looks like, without needing to actually commit them to your repository.
          So, whenever a developer uses your project, he/she should run the php bin/vendors install script to
          ensure that all of the needed vendor libraries are downloaded.


                        Upgrading Symfony
                        Since Symfony is just a group of third-party libraries and third-party libraries are entirely controlled
                        through deps and deps.lock, upgrading Symfony means simply upgrading each of these files to
                        match their state in the latest Symfony Standard Edition.
                        Of course, if you've added new entries to deps or deps.lock, be sure to replace only the original
                        parts (i.e. be sure not to also delete any of your custom entries).



                        There is also a php bin/vendors update command, but this has nothing to do with upgrading
                        your project and you will normally not need to use it. This command is used to freeze the versions
                        of all of your vendor libraries by updating them to the version specified in deps and recording it
                        into the deps.lock file.


          Hacking vendor libraries
          Sometimes, you want a specific branch, tag, or commit of a library to be downloaded or upgraded. You
          can set that directly to the deps file :

Listing   [AcmeAwesomeBundle]
24-10
              git=http://github.com/johndoe/Acme/AwesomeBundle.git


          4. https://github.com/symfony/symfony-standard/blob/master/README.md


          PDF brought to you by                                              Chapter 24: How to Create and store a Symfony2 Project in git | 256
          generated on June 20, 2012
target=/bundles/Acme/AwesomeBundle
     version=the-awesome-version

  • The git option sets the URL of the library. It can use various protocols, like http:// as well
    as git://.
  • The target option specifies where the repository will live : plain Symfony bundles should go
    under the vendor/bundles/Acme directory, other third-party libraries usually go to vendor/
    my-awesome-library-name. The target directory defaults to this last option when not
    specified.
  • The version option allows you to set a specific revision. You can use a tag
             (version=origin/0.42) or a branch name (refs/remotes/origin/awesome-branch). It
             defaults to origin/HEAD.


Updating workflow
When you execute the php bin/vendors install, for every library, the script first checks if the install
directory exists.
If it does not (and ONLY if it does not), it runs a git clone.
Then, it does a git fetch origin and a git reset --hard the-awesome-version.
This means that the repository will only be cloned once. If you want to perform any change of the git
remote, you MUST delete the entire target directory, not only its content.

Vendors and Submodules
Instead of using the deps, bin/vendors system for managing your vendor libraries, you may instead
choose to use native git submodules5. There is nothing wrong with this approach, though the deps system
is the official way to solve this problem and git submodules can be difficult to work with at times.



Storing your Project on a Remote Server
You now have a fully-functional Symfony2 project stored in git. However, in most cases, you'll also want
to store your project on a remote server both for backup purposes, and so that other developers can
collaborate on the project.
The easiest way to store your project on a remote server is via GitHub6. Public repositories are free,
however you will need to pay a monthly fee to host private repositories.
Alternatively, you can store your git repository on any server by creating a barebones repository7 and then
pushing to it. One library that helps manage this is Gitolite8.




5. http://book.git-scm.com/5_submodules.html
6. https://github.com/
7. http://progit.org/book/ch4-4.html
8. https://github.com/sitaramc/gitolite


PDF brought to you by                                Chapter 24: How to Create and store a Symfony2 Project in git | 257
generated on June 20, 2012
Chapter 25
           How to Create and store a Symfony2 Project in
                           Subversion


                        This entry is specifically about Subversion, and based on principles found in How to Create and
                        store a Symfony2 Project in git.


          Once you've read through Creating Pages in Symfony2 and become familiar with using Symfony, you'll
          no-doubt be ready to start your own project. The preferred method to manage Symfony2 projects is using
          git1 but some prefer to use Subversion2 which is totally fine!. In this cookbook article, you'll learn how to
          manage your project using svn3 in a similar manner you would do with git4.


                        This is a method to tracking your Symfony2 project in a Subversion repository. There are several
                        ways to do and this one is simply one that works.




          The Subversion Repository
          For this article we will suppose that your repository layout follows the widespread standard structure:

Listing   myproject/
 25-1
              branches/
              tags/
              trunk/




          1. http://git-scm.com/
          2. http://subversion.apache.org/
          3. http://subversion.apache.org/
          4. http://git-scm.com/


          PDF brought to you by                            Chapter 25: How to Create and store a Symfony2 Project in Subversion | 258
          generated on June 20, 2012
Most subversion hosting should follow this standard practice. This is the recommended layout in
               Version Control with Subversion5 and the layout used by most free hosting (see Subversion hosting
               solutions).



Initial Project Setup
To get started, you'll need to download Symfony2 and get the basic Subversion setup:
       1. Download the Symfony2 Standard Edition6 with or without vendors.
       2. Unzip/untar the distribution. It will create a folder called Symfony with your new project
          structure, config files, etc. Rename it to whatever you like.
       3. Checkout the Subversion repository that will host this project. Let's say it is hosted on Google
          code7 and called myproject:

           $ svn checkout http://myproject.googlecode.com/svn/trunk myproject                                                       Listing
                                                                                                                                     25-2

       4. Copy the Symfony2 project files in the subversion folder:

           $ mv Symfony/* myproject/                                                                                                Listing
                                                                                                                                     25-3

       5. Let's now set the ignore rules. Not everything should be stored in your subversion repository.
          Some files (like the cache) are generated and others (like the database configuration) are meant
          to be customized on each machine. This makes use of the svn:ignore property, so that we can
          ignore specific files.

           $ cd myproject/                                                                                                          Listing
                                                                                                                                     25-4
           $ svn add --depth=empty app app/cache app/logs app/config web

           $   svn   propset      svn:ignore   "vendor" .
           $   svn   propset      svn:ignore   "bootstrap*" app/
           $   svn   propset      svn:ignore   "parameters.ini" app/config/
           $   svn   propset      svn:ignore   "*" app/cache/
           $   svn   propset      svn:ignore   "*" app/logs/

           $ svn propset svn:ignore "bundles" web

           $ svn ci -m "commit basic symfony ignore list (vendor, app/bootstrap*, app/config/
           parameters.ini, app/cache/*, app/logs/*, web/bundles)"

       6. The rest of the files can now be added and committed to the project:

           $ svn add --force .                                                                                                      Listing
                                                                                                                                     25-5
           $ svn ci -m "add basic Symfony Standard 2.X.Y"

       7. Copy      app/config/parameters.ini            to   app/config/parameters.ini.dist.          The
          parameters.ini file is ignored by svn (see above) so that machine-specific settings like database
          passwords aren't committed. By creating the parameters.ini.dist file, new developers can
          quickly clone the project, copy this file to parameters.ini, customize it, and start developing.
       8. Finally, download all of the third-party vendor libraries:



5. http://svnbook.red-bean.com/
6. http://symfony.com/download
7. http://code.google.com/hosting/


PDF brought to you by                                  Chapter 25: How to Create and store a Symfony2 Project in Subversion | 259
generated on June 20, 2012
Listing   $ php bin/vendors install
     25-6




               git8 has to be installed to run bin/vendors, this is the protocol used to fetch vendor libraries.
               This only means that git is used as a tool to basically help download the libraries in the vendor/
               directory.

At this point, you have a fully-functional Symfony2 project stored in your Subversion repository. The
development can start with commits in the Subversion repository.
You can continue to follow along with the Creating Pages in Symfony2 chapter to learn more about how
to configure and develop inside your application.


               The Symfony2 Standard Edition comes with some example functionality. To remove the sample
               code, follow the instructions on the Standard Edition Readme9.




Managing Vendor Libraries with bin/vendors and deps
How does it work?
Every Symfony project uses a group of third-party "vendor" libraries. One way or another the goal is to
download these files into your vendor/ directory and, ideally, to give you some sane way to manage the
exact version you need for each.
By default, these libraries are downloaded by running a php bin/vendors install "downloader" script.
This script reads from the deps file at the root of your project. This is an ini-formatted script, which
holds a list of each of the external libraries you need, the directory each should be downloaded to, and
(optionally) the version to be downloaded. The bin/vendors script uses git to downloaded these, solely
because these external libraries themselves tend to be stored via git. The bin/vendors script also reads
the deps.lock file, which allows you to pin each library to an exact git commit hash.
It's important to realize that these vendor libraries are not actually part of your repository. Instead, they're
simply un-tracked files that are downloaded into the vendor/ directory by the bin/vendors script. But
since all the information needed to download these files is saved in deps and deps.lock (which are
stored) in our repository), any other developer can use our project, run php bin/vendors install, and
download the exact same set of vendor libraries. This means that you're controlling exactly what each
vendor library looks like, without needing to actually commit them to your repository.
So, whenever a developer uses your project, he/she should run the php bin/vendors install script to
ensure that all of the needed vendor libraries are downloaded.


               Upgrading Symfony
               Since Symfony is just a group of third-party libraries and third-party libraries are entirely controlled
               through deps and deps.lock, upgrading Symfony means simply upgrading each of these files to
               match their state in the latest Symfony Standard Edition.
               Of course, if you've added new entries to deps or deps.lock, be sure to replace only the original
               parts (i.e. be sure not to also delete any of your custom entries).




8. http://git-scm.com/
9. https://github.com/symfony/symfony-standard/blob/master/README.md


PDF brought to you by                                     Chapter 25: How to Create and store a Symfony2 Project in Subversion | 260
generated on June 20, 2012
There is also a php bin/vendors update command, but this has nothing to do with upgrading
              your project and you will normally not need to use it. This command is used to freeze the versions
              of all of your vendor libraries by updating them to the version specified in deps and recording it
              into the deps.lock file.


Hacking vendor libraries
Sometimes, you want a specific branch, tag, or commit of a library to be downloaded or upgraded. You
can set that directly to the deps file :

[AcmeAwesomeBundle]                                                                                                            Listing
                                                                                                                                25-7
    git=http://github.com/johndoe/Acme/AwesomeBundle.git
    target=/bundles/Acme/AwesomeBundle
    version=the-awesome-version

  • The git option sets the URL of the library. It can use various protocols, like http:// as well
    as git://.
  • The target option specifies where the repository will live : plain Symfony bundles should go
    under the vendor/bundles/Acme directory, other third-party libraries usually go to vendor/
    my-awesome-library-name. The target directory defaults to this last option when not
    specified.
  • The version option allows you to set a specific revision. You can use a tag
             (version=origin/0.42) or a branch name (refs/remotes/origin/awesome-branch). It
             defaults to origin/HEAD.


Updating workflow
When you execute the php bin/vendors install, for every library, the script first checks if the install
directory exists.
If it does not (and ONLY if it does not), it runs a git clone.
Then, it does a git fetch origin and a git reset --hard the-awesome-version.
This means that the repository will only be cloned once. If you want to perform any change of the git
remote, you MUST delete the entire target directory, not only its content.



Subversion hosting solutions
The biggest difference between git10 and svn11 is that Subversion needs a central repository to work. You
then have several solutions:

  • Self hosting: create your own repository and access it either through the filesystem or the
    network. To help in this task you can read Version Control with Subversion.
  • Third party hosting: there are a lot of serious free hosting solutions available like GitHub12,
    Google code13, SourceForge14 or Gna15. Some of them offer git hosting as well.



10. http://git-scm.com/
11. http://subversion.apache.org/
12. http://github.com/
13. http://code.google.com/hosting/
14. http://sourceforge.net/
15. http://gna.org/


PDF brought to you by                             Chapter 25: How to Create and store a Symfony2 Project in Subversion | 261
generated on June 20, 2012
Chapter 26
                                  How to customize Error Pages

          When any exception is thrown in Symfony2, the exception is caught inside the Kernel class and
          eventually forwarded to a special controller, TwigBundle:Exception:show for handling. This controller,
          which lives inside the core TwigBundle, determines which error template to display and the status code
          that should be set for the given exception.
          Error pages can be customized in two different ways, depending on how much control you need:
               1. Customize the error templates of the different error pages (explained below);
               2. Replace the default exception controller TwigBundle::Exception:show with your own
                   controller and handle it however you want (see exception_controller in the Twig reference);


                        The customization of exception handling is actually much more powerful than what's written here.
                        An internal event, kernel.exception, is thrown which allows complete control over exception
                        handling. For more information, see kernel.exception Event.

          All of the error templates live inside TwigBundle. To override the templates, we simply rely on the
          standard method for overriding templates that live inside a bundle. For more information, see Overriding
          Bundle Templates.
          For example, to override the default error template that's shown to the end-user, create a new template
          located at app/Resources/TwigBundle/views/Exception/error.html.twig:

Listing   <!DOCTYPE html>
 26-1
          <html>
          <head>
              <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
              <title>An Error Occurred: {{ status_text }}</title>
          </head>
          <body>
              <h1>Oops! An Error Occurred</h1>
              <h2>The server returned a "{{ status_code }} {{ status_text }}".</h2>
          </body>
          </html>




          PDF brought to you by                                                  Chapter 26: How to customize Error Pages | 262
          generated on June 20, 2012
If you're not familiar with Twig, don't worry. Twig is a simple, powerful and optional templating
              engine that integrates with Symfony2. For more information about Twig see Creating and using
              Templates.

In addition to the standard HTML error page, Symfony provides a default error page for many of the
most common response formats, including JSON (error.json.twig), XML, (error.xml.twig), and
even Javascript (error.js.twig), to name a few. To override any of these templates, just create a new
file with the same name in the app/Resources/TwigBundle/views/Exception directory. This is the
standard way of overriding any template that lives inside a bundle.



Customizing the 404 Page and other Error Pages
You can also customize specific error templates according to the HTTP status code. For instance, create
a app/Resources/TwigBundle/views/Exception/error404.html.twig template to display a special
page for 404 (page not found) errors.
Symfony uses the following algorithm to determine which template to use:

  • First, it looks for a template for the given format and status code (like error404.json.twig);
  • If it does not exist, it looks for a template for the given format (like error.json.twig);
  • If it does not exist, it falls back to the HTML template (like error.html.twig).


              To see the full list of default error templates, see the Resources/views/Exception directory of
              the TwigBundle. In a standard Symfony2 installation, the TwigBundle can be found at vendor/
              symfony/src/Symfony/Bundle/TwigBundle. Often, the easiest way to customize an error page
              is to copy it from the TwigBundle into app/Resources/TwigBundle/views/Exception and then
              modify it.



              The debug-friendly exception pages shown to the developer can even be customized in the same
              way by creating templates such as exception.html.twig for the standard HTML exception page
              or exception.json.twig for the JSON exception page.




PDF brought to you by                                                   Chapter 26: How to customize Error Pages | 263
generated on June 20, 2012
Chapter 27
                        How to define Controllers as Services

          In the book, you've learned how easily a controller can be used when it extends the base Controller1
          class. While this works fine, controllers can also be specified as services.
          To refer to a controller that's defined as a service, use the single colon (:) notation. For example, suppose
          we've defined a service called my_controller and we want to forward to a method called indexAction()
          inside the service:

Listing   $this->forward('my_controller:indexAction', array('foo' => $bar));
 27-1

          You need to use the same notation when defining the route _controller value:

Listing   my_controller:
 27-2
              pattern: /
              defaults: { _controller: my_controller:indexAction }

          To use a controller in this way, it must be defined in the service container configuration. For more
          information, see the Service Container chapter.
          When using a controller defined as a service, it will most likely not extend the base Controller class.
          Instead of relying on its shortcut methods, you'll interact directly with the services that you need.
          Fortunately, this is usually pretty easy and the base Controller class itself is a great source on how to
          perform many common tasks.


                        Specifying a controller as a service takes a little bit more work. The primary advantage is that the
                        entire controller or any services passed to the controller can be modified via the service container
                        configuration. This is especially useful when developing an open-source bundle or any bundle that
                        will be used in many different projects. So, even if you don't specify your controllers as services,
                        you'll likely see this done in some open-source Symfony2 bundles.




          1. http://api.symfony.com/2.0/Symfony/Bundle/FrameworkBundle/Controller/Controller.html


          PDF brought to you by                                                          Chapter 27: How to define Controllers as Services | 264
          generated on June 20, 2012
Chapter 28
     How to force routes to always use HTTPS or
                       HTTP

Sometimes, you want to secure some routes and be sure that they are always accessed via the HTTPS
protocol. The Routing component allows you to enforce the URI scheme via the _scheme requirement:

secure:                                                                                                                    Listing
                                                                                                                            28-1
    pattern: /secure
    defaults: { _controller: AcmeDemoBundle:Main:secure }
    requirements:
        _scheme: https

The above configuration forces the secure route to always use HTTPS.
When generating the secure URL, and if the current scheme is HTTP, Symfony will automatically
generate an absolute URL with HTTPS as the scheme:

# If the current scheme is HTTPS                                                                                           Listing
                                                                                                                            28-2
{{ path('secure') }}
# generates /secure

# If the current scheme is HTTP
{{ path('secure') }}
# generates https://example.com/secure

The requirement is also enforced for incoming requests. If you try to access the /secure path with HTTP,
you will automatically be redirected to the same URL, but with the HTTPS scheme.
The above example uses https for the _scheme, but you can also force a URL to always use http.


              The Security component provides another way to enforce HTTP or HTTPs via the
              requires_channel setting. This alternative method is better suited to secure an "area" of your
              website (all URLs under /admin) or when you want to secure URLs defined in a third party bundle.




PDF brought to you by                                  Chapter 28: How to force routes to always use HTTPS or HTTP | 265
generated on June 20, 2012
Chapter 29
                      How to allow a "/" character in a route
                                   parameter

          Sometimes, you need to compose URLs with parameters that can contain a slash /. For example, take the
          classic /hello/{name} route. By default, /hello/Fabien will match this route but not /hello/Fabien/
          Kris. This is because Symfony uses this character as separator between route parts.
          This guide covers how you can modify a route so that /hello/Fabien/Kris matches the /hello/{name}
          route, where {name} equals Fabien/Kris.



          Configure the Route
          By default, the symfony routing components requires that the parameters match the following regex
          pattern: [^/]+. This means that all characters are allowed except /.
          You must explicitly allow / to be part of your parameter by specifying a more permissive regex pattern.

Listing   _hello:
 29-1
              pattern: /hello/{name}
              defaults: { _controller: AcmeDemoBundle:Demo:hello }
              requirements:
                  name: ".+"

          That's it! Now, the {name} parameter can contain the / character.




          PDF brought to you by                               Chapter 29: How to allow a "/" character in a route parameter | 266
          generated on June 20, 2012
Chapter 30
       How to Use Assetic for Asset Management

Assetic combines two major ideas: assets and filters. The assets are files such as CSS, JavaScript and image
files. The filters are things that can be applied to these files before they are served to the browser. This
allows a separation between the asset files stored in the application and the files actually presented to the
user.
Without Assetic, you just serve the files that are stored in the application directly:

<script src="{{ asset('js/script.js') }}" type="text/javascript" />                                                      Listing
                                                                                                                          30-1

But with Assetic, you can manipulate these assets however you want (or load them from anywhere) before
serving them. These means you can:

  • Minify and combine all of your CSS and JS files
  • Run all (or just some) of your CSS or JS files through some sort of compiler, such as LESS,
    SASS or CoffeeScript
  • Run image optimizations on your images



Assets
Using Assetic provides many advantages over directly serving the files. The files do not need to be stored
where they are served from and can be drawn from various sources such as from within a bundle:

{% javascripts                                                                                                           Listing
                                                                                                                          30-2
    '@AcmeFooBundle/Resources/public/js/*'
%}
<script type="text/javascript" src="{{ asset_url }}"></script>
{% endjavascripts %}



              To bring in CSS stylesheets, you can use the same methodologies seen in this entry, except with
              the stylesheets tag:

                                                                                                                         Listing
                                                                                                                          30-3




PDF brought to you by                                        Chapter 30: How to Use Assetic for Asset Management | 267
generated on June 20, 2012
{% stylesheets
                            '@AcmeFooBundle/Resources/public/css/*'
                        %}
                        <link rel="stylesheet" href="{{ asset_url }}" />
                        {% endstylesheets %}



          In this example, all of the files in the Resources/public/js/ directory of the AcmeFooBundle will be
          loaded and served from a different location. The actual rendered tag might simply look like:

Listing   <script src="/app_dev.php/js/abcd123.js"></script>
 30-4




                        This is a key point: once you let Assetic handle your assets, the files are served from a different
                        location. This can cause problems with CSS files that reference images by their relative path.
                        However, this can be fixed by using the cssrewrite filter, which updates paths in CSS files to
                        reflect their new location.


          Combining Assets
          You can also combine several files into one. This helps to reduce the number of HTTP requests, which is
          great for front end performance. It also allows you to maintain the files more easily by splitting them into
          manageable parts. This can help with re-usability as you can easily split project-specific files from those
          which can be used in other applications, but still serve them as a single file:

Listing   {% javascripts
 30-5
              '@AcmeFooBundle/Resources/public/js/*'
              '@AcmeBarBundle/Resources/public/js/form.js'
              '@AcmeBarBundle/Resources/public/js/calendar.js'
          %}
          <script src="{{ asset_url }}"></script>
          {% endjavascripts %}

          In the dev environment, each file is still served individually, so that you can debug problems more easily.
          However, in the prod environment, this will be rendered as a single script tag.


                        If you're new to Assetic and try to use your application in the prod environment (by using the
                        app.php controller), you'll likely see that all of your CSS and JS breaks. Don't worry! This is on
                        purpose. For details on using Assetic in the prod environment, see Dumping Asset Files.

          And combining files doesn't only apply to your files. You can also use Assetic to combine third party
          assets, such as jQuery, with your own into a single file:

Listing   {% javascripts
 30-6
              '@AcmeFooBundle/Resources/public/js/thirdparty/jquery.js'
              '@AcmeFooBundle/Resources/public/js/*'
          %}
          <script src="{{ asset_url }}"></script>
          {% endjavascripts %}




          PDF brought to you by                                          Chapter 30: How to Use Assetic for Asset Management | 268
          generated on June 20, 2012
Filters
Once they're managed by Assetic, you can apply filters to your assets before they are served. This includes
filters that compress the output of your assets for smaller file sizes (and better front-end optimization).
Other filters can compile JavaScript file from CoffeeScript files and process SASS into CSS. In fact, Assetic
has a long list of available filters.
Many of the filters do not do the work directly, but use existing third-party libraries to do the heavy-
lifting. This means that you'll often need to install a third-party library to use a filter. The great advantage
of using Assetic to invoke these libraries (as opposed to using them directly) is that instead of having to
run them manually after you work on the files, Assetic will take care of this for you and remove this step
altogether from your development and deployment processes.
To use a filter, you first need to specify it in the Assetic configuration. Adding a filter here doesn't mean
it's being used - it just means that it's available to use (we'll use the filter below).
For example to use the JavaScript YUI Compressor the following config should be added:

# app/config/config.yml                                                                                                   Listing
                                                                                                                           30-7
assetic:
    filters:
        yui_js:
             jar: "%kernel.root_dir%/Resources/java/yuicompressor.jar"

Now, to actually use the filter on a group of JavaScript files, add it into your template:

{% javascripts                                                                                                            Listing
                                                                                                                           30-8
    '@AcmeFooBundle/Resources/public/js/*'
    filter='yui_js'
%}
<script src="{{ asset_url }}"></script>
{% endjavascripts %}

A more detailed guide about configuring and using Assetic filters as well as details of Assetic's debug
mode can be found in How to Minify JavaScripts and Stylesheets with YUI Compressor.



Controlling the URL used
If you wish to, you can control the URLs that Assetic produces. This is done from the template and is
relative to the public document root:

{% javascripts                                                                                                            Listing
                                                                                                                           30-9
    '@AcmeFooBundle/Resources/public/js/*'
    output='js/compiled/main.js'
%}
<script src="{{ asset_url }}"></script>
{% endjavascripts %}



              Symfony also contains a method for cache busting, where the final URL generated by Assetic
              contains a query parameter that can be incremented via configuration on each deployment. For
              more information, see the assets_version configuration option.




PDF brought to you by                                         Chapter 30: How to Use Assetic for Asset Management | 269
generated on June 20, 2012
Dumping Asset Files
          In the dev environment, Assetic generates paths to CSS and JavaScript files that don't physically exist on
          your computer. But they render nonetheless because an internal Symfony controller opens the files and
          serves back the content (after running any filters).
          This kind of dynamic serving of processed assets is great because it means that you can immediately see
          the new state of any asset files you change. It's also bad, because it can be quite slow. If you're using a lot
          of filters, it might be downright frustrating.
          Fortunately, Assetic provides a way to dump your assets to real files, instead of being generated
          dynamically.

          Dumping Asset Files in the prod environment
          In the prod environment, your JS and CSS files are represented by a single tag each. In other words,
          instead of seeing each JavaScript file you're including in your source, you'll likely just see something like
          this:

Listing   <script src="/app_dev.php/js/abcd123.js"></script>
30-10

          Moreover, that file does not actually exist, nor is it dynamically rendered by Symfony (as the asset files
          are in the dev environment). This is on purpose - letting Symfony generate these files dynamically in a
          production environment is just too slow.
          Instead, each time you use your app in the prod environment (and therefore, each time you deploy), you
          should run the following task:

Listing   php app/console assetic:dump --env=prod --no-debug
30-11

          This will physically generate and write each file that you need (e.g. /js/abcd123.js). If you update any
          of your assets, you'll need to run this again to regenerate the file.

          Dumping Asset Files in the dev environment
          By default, each asset path generated in the dev environment is handled dynamically by Symfony. This
          has no disadvantage (you can see your changes immediately), except that assets can load noticeably slow.
          If you feel like your assets are loading too slowly, follow this guide.
          First, tell Symfony to stop trying to process these files dynamically. Make the following change in your
          config_dev.yml file:

Listing   # app/config/config_dev.yml
30-12
          assetic:
              use_controller: false

          Next, since Symfony is no longer generating these assets for you, you'll need to dump them manually. To
          do so, run the following:

Listing   php app/console assetic:dump
30-13

          This physically writes all of the asset files you need for your dev environment. The big disadvantage is
          that you need to run this each time you update an asset. Fortunately, by passing the --watch option, the
          command will automatically regenerate assets as they change:

Listing   php app/console assetic:dump --watch
30-14




          PDF brought to you by                                        Chapter 30: How to Use Assetic for Asset Management | 270
          generated on June 20, 2012
Since running this command in the dev environment may generate a bunch of files, it's usually a good
idea to point your generated assets files to some isolated directory (e.g. /js/compiled), to keep things
organized:

{% javascripts                                                                                                       Listing
                                                                                                                     30-15
    '@AcmeFooBundle/Resources/public/js/*'
    output='js/compiled/main.js'
%}
<script src="{{ asset_url }}"></script>
{% endjavascripts %}




PDF brought to you by                                    Chapter 30: How to Use Assetic for Asset Management | 271
generated on June 20, 2012
Chapter 31
          How to Minify JavaScripts and Stylesheets with
                         YUI Compressor

          Yahoo! provides an excellent utility for minifying JavaScripts and stylesheets so they travel over the wire
          faster, the YUI Compressor1. Thanks to Assetic, you can take advantage of this tool very easily.



          Download the YUI Compressor JAR
          The YUI Compressor is written in Java and distributed as a JAR. Download the JAR2 from the Yahoo! site
          and save it to app/Resources/java/yuicompressor.jar.



          Configure the YUI Filters
          Now you need to configure two Assetic filters in your application, one for minifying JavaScripts with the
          YUI Compressor and one for minifying stylesheets:

Listing   # app/config/config.yml
 31-1
          assetic:
              # java: "/usr/bin/java"
              filters:
                  yui_css:
                       jar: "%kernel.root_dir%/Resources/java/yuicompressor.jar"
                  yui_js:
                       jar: "%kernel.root_dir%/Resources/java/yuicompressor.jar"



                        Windows users need to remember to update config to proper java location. In Windows7 x64 bit
                        by default it's C:Program Files (x86)Javajre6binjava.exe.



          1. http://developer.yahoo.com/yui/compressor/
          2. http://yuilibrary.com/downloads/#yuicompressor


          PDF brought to you by                               Chapter 31: How to Minify JavaScripts and Stylesheets with YUI Compressor | 272
          generated on June 20, 2012
You now have access to two new Assetic filters in your application: yui_css and yui_js. These will use
the YUI Compressor to minify stylesheets and JavaScripts, respectively.



Minify your Assets
You have YUI Compressor configured now, but nothing is going to happen until you apply one of these
filters to an asset. Since your assets are a part of the view layer, this work is done in your templates:

{% javascripts '@AcmeFooBundle/Resources/public/js/*' filter='yui_js' %}                                                         Listing
                                                                                                                                  31-2
<script src="{{ asset_url }}"></script>
{% endjavascripts %}



              The above example assumes that you have a bundle called AcmeFooBundle and your JavaScript
              files are in the Resources/public/js directory under your bundle. This isn't important however -
              you can include your Javascript files no matter where they are.

With the addition of the yui_js filter to the asset tags above, you should now see minified JavaScripts
coming over the wire much faster. The same process can be repeated to minify your stylesheets.

{% stylesheets '@AcmeFooBundle/Resources/public/css/*' filter='yui_css' %}                                                       Listing
                                                                                                                                  31-3
<link rel="stylesheet" type="text/css" media="screen" href="{{ asset_url }}" />
{% endstylesheets %}



Disable Minification in Debug Mode
Minified JavaScripts and Stylesheets are very difficult to read, let alone debug. Because of this, Assetic lets
you disable a certain filter when your application is in debug mode. You can do this by prefixing the filter
name in your template with a question mark: ?. This tells Assetic to only apply this filter when debug
mode is off.

{% javascripts '@AcmeFooBundle/Resources/public/js/*' filter='?yui_js' %}                                                        Listing
                                                                                                                                  31-4
<script src="{{ asset_url }}"></script>
{% endjavascripts %}



              Instead of adding the filter to the asset tags, you can also globally enable it by adding the apply-
              to attribute to the filter configuration, for example in the yui_js filter apply_to: ".js$". To
              only have the filter applied in production, add this to the config_prod file rather than the common
              config file. For details on applying filters by file extension, see Filtering based on a File Extension.




PDF brought to you by                          Chapter 31: How to Minify JavaScripts and Stylesheets with YUI Compressor | 273
generated on June 20, 2012
Chapter 32
               How to Use Assetic For Image Optimization
                         with Twig Functions

          Amongst its many filters, Assetic has four filters which can be used for on-the-fly image optimization.
          This allows you to get the benefits of smaller file sizes without having to use an image editor to process
          each image. The results are cached and can be dumped for production so there is no performance hit for
          your end users.



          Using Jpegoptim
          Jpegoptim1 is a utility for optimizing JPEG files. To use it with Assetic, add the following to the Assetic
          config:

Listing   # app/config/config.yml
 32-1
          assetic:
              filters:
                  jpegoptim:
                       bin: path/to/jpegoptim



                        Notice that to use jpegoptim, you must have it already installed on your system. The bin option
                        points to the location of the compiled binary.


          It can now be used from a template:

Listing   {% image '@AcmeFooBundle/Resources/public/images/example.jpg'
 32-2
              filter='jpegoptim' output='/images/example.jpg'
          %}
          <img src="{{ asset_url }}" alt="Example"/>
          {% endimage %}



          1. http://www.kokkonen.net/tjko/projects.html


          PDF brought to you by                           Chapter 32: How to Use Assetic For Image Optimization with Twig Functions | 274
          generated on June 20, 2012
Removing all EXIF Data
By default, running this filter only removes some of the meta information stored in the file. Any EXIF
data and comments are not removed, but you can remove these by using the strip_all option:

# app/config/config.yml                                                                                                     Listing
                                                                                                                             32-3
assetic:
    filters:
        jpegoptim:
             bin: path/to/jpegoptim
             strip_all: true


Lowering Maximum Quality
The quality level of the JPEG is not affected by default. You can gain further file size reductions by setting
the max quality setting lower than the current level of the images. This will of course be at the expense of
image quality:

# app/config/config.yml                                                                                                     Listing
                                                                                                                             32-4
assetic:
    filters:
        jpegoptim:
             bin: path/to/jpegoptim
             max: 70



Shorter syntax: Twig Function
If you're using Twig, it's possible to achieve all of this with a shorter syntax by enabling and using a
special Twig function. Start by adding the following config:

# app/config/config.yml                                                                                                     Listing
                                                                                                                             32-5
assetic:
    filters:
        jpegoptim:
             bin: path/to/jpegoptim
    twig:
        functions:
             jpegoptim: ~

The Twig template can now be changed to the following:

<img src="{{ jpegoptim('@AcmeFooBundle/Resources/public/images/example.jpg') }}"                                            Listing
                                                                                                                             32-6
     alt="Example"/>

You can specify the output directory in the config in the following way:

# app/config/config.yml                                                                                                     Listing
                                                                                                                             32-7
assetic:
    filters:
        jpegoptim:
             bin: path/to/jpegoptim
    twig:
        functions:
             jpegoptim: { output: images/*.jpg }




PDF brought to you by                     Chapter 32: How to Use Assetic For Image Optimization with Twig Functions | 275
generated on June 20, 2012
Chapter 33
           How to Apply an Assetic Filter to a Specific File
                            Extension

          Assetic filters can be applied to individual files, groups of files or even, as you'll see here, files that have a
          specific extension. To show you how to handle each option, let's suppose that you want to use Assetic's
          CoffeeScript filter, which compiles CoffeeScript files into Javascript.
          The main configuration is just the paths to coffee and node. These default respectively to /usr/bin/
          coffee and /usr/bin/node:

Listing   # app/config/config.yml
 33-1
          assetic:
              filters:
                  coffee:
                       bin: /usr/bin/coffee
                       node: /usr/bin/node



          Filter a Single File
          You can now serve up a single CoffeeScript file as JavaScript from within your templates:

Listing   {% javascripts '@AcmeFooBundle/Resources/public/js/example.coffee'
 33-2
              filter='coffee'
          %}
          <script src="{{ asset_url }}" type="text/javascript"></script>
          {% endjavascripts %}

          This is all that's needed to compile this CoffeeScript file and server it as the compiled JavaScript.



          Filter Multiple Files
          You can also combine multiple CoffeeScript files into a single output file:



          PDF brought to you by                             Chapter 33: How to Apply an Assetic Filter to a Specific File Extension | 276
          generated on June 20, 2012
{% javascripts '@AcmeFooBundle/Resources/public/js/example.coffee'                                                              Listing
                                                                                                                                 33-3
               '@AcmeFooBundle/Resources/public/js/another.coffee'
    filter='coffee'
%}
<script src="{{ asset_url }}" type="text/javascript"></script>
{% endjavascripts %}

Both the files will now be served up as a single file compiled into regular JavaScript.



Filtering based on a File Extension
One of the great advantages of using Assetic is reducing the number of asset files to lower HTTP requests.
In order to make full use of this, it would be good to combine all your JavaScript and CoffeeScript files
together since they will ultimately all be served as JavaScript. Unfortunately just adding the JavaScript
files to the files to be combined as above will not work as the regular JavaScript files will not survive the
CoffeeScript compilation.
This problem can be avoided by using the apply_to option in the config, which allows you to specify
that a filter should always be applied to particular file extensions. In this case you can specify that the
Coffee filter is applied to all .coffee files:

# app/config/config.yml                                                                                                         Listing
                                                                                                                                 33-4
assetic:
    filters:
         coffee:
             bin: /usr/bin/coffee
             node: /usr/bin/node
             apply_to: ".coffee$"

With this, you no longer need to specify the coffee filter in the template. You can also list regular
JavaScript files, all of which will be combined and rendered as a single JavaScript file (with only the
.coffee files being run through the CoffeeScript filter):

{% javascripts '@AcmeFooBundle/Resources/public/js/example.coffee'                                                              Listing
                                                                                                                                 33-5
               '@AcmeFooBundle/Resources/public/js/another.coffee'
               '@AcmeFooBundle/Resources/public/js/regular.js'
%}
<script src="{{ asset_url }}" type="text/javascript"></script>
{% endjavascripts %}




PDF brought to you by                           Chapter 33: How to Apply an Assetic Filter to a Specific File Extension | 277
generated on June 20, 2012
Chapter 34
                  How to handle File Uploads with Doctrine

          Handling file uploads with Doctrine entities is no different than handling any other file upload. In other
          words, you're free to move the file in your controller after handling a form submission. For examples of
          how to do this, see the file type reference page.
          If you choose to, you can also integrate the file upload into your entity lifecycle (i.e. creation, update and
          removal). In this case, as your entity is created, updated, and removed from Doctrine, the file uploading
          and removal processing will take place automatically (without needing to do anything in your controller);
          To make this work, you'll need to take care of a number of details, which will be covered in this cookbook
          entry.



          Basic Setup
          First, create a simple Doctrine Entity class to work with:

Listing   // src/Acme/DemoBundle/Entity/Document.php
 34-1
          namespace AcmeDemoBundleEntity;

          use DoctrineORMMapping as ORM;
          use SymfonyComponentValidatorConstraints as Assert;

          /**
            * @ORMEntity
            */
          class Document
          {
               /**
                * @ORMId
                * @ORMColumn(type="integer")
                * @ORMGeneratedValue(strategy="AUTO")
                */
               public $id;

               /**
                * @ORMColumn(type="string", length=255)
                * @AssertNotBlank


          PDF brought to you by                                        Chapter 34: How to handle File Uploads with Doctrine | 278
          generated on June 20, 2012
*/
     public $name;

     /**
      * @ORMColumn(type="string", length=255, nullable=true)
      */
     public $path;

     public function getAbsolutePath()
     {
         return null === $this->path ? null : $this->getUploadRootDir().'/'.$this->path;
     }

     public function getWebPath()
     {
         return null === $this->path ? null : $this->getUploadDir().'/'.$this->path;
     }

     protected function getUploadRootDir()
     {
         // the absolute directory path where uploaded documents should be saved
         return __DIR__.'/../../../../web/'.$this->getUploadDir();
     }

    protected function getUploadDir()
    {
        // get rid of the __DIR__ so it doesn't screw when displaying uploaded doc/image in
the view.
        return 'uploads/documents';
    }
}

The Document entity has a name and it is associated with a file. The path property stores the relative path
to the file and is persisted to the database. The getAbsolutePath() is a convenience method that returns
the absolute path to the file while the getWebPath() is a convenience method that returns the web path,
which can be used in a template to link to the uploaded file.


              If you have not done so already, you should probably read the file type documentation first to
              understand how the basic upload process works.




              If you're using annotations to specify your validation rules (as shown in this example), be sure that
              you've enabled validation by annotation (see validation configuration).


To handle the actual file upload in the form, use a "virtual" file field. For example, if you're building
your form directly in a controller, it might look like this:

public function uploadAction()                                                                                               Listing
                                                                                                                              34-2
{
    // ...

     $form = $this->createFormBuilder($document)
         ->add('name')
         ->add('file')
         ->getForm()
     ;



PDF brought to you by                                           Chapter 34: How to handle File Uploads with Doctrine | 279
generated on June 20, 2012
// ...
          }

          Next, create this property on your Document class and add some validation rules:

Listing   // src/Acme/DemoBundle/Entity/Document.php
 34-3

          // ...
          class Document
          {
              /**
               * @AssertFile(maxSize="6000000")
               */
              public $file;

               // ...
          }



                         As you are using the File constraint, Symfony2 will automatically guess that the form field is a
                         file upload input. That's why you did not have to set it explicitly when creating the form above
                         (->add('file')).

          The following controller shows you how to handle the entire process:

Listing   use AcmeDemoBundleEntityDocument;
 34-4
          use SensioBundleFrameworkExtraBundleConfigurationTemplate;
          // ...

          /**
            * @Template()
            */
          public function uploadAction()
          {
               $document = new Document();
               $form = $this->createFormBuilder($document)
                   ->add('name')
                   ->add('file')
                   ->getForm()
               ;

               if ($this->getRequest()->getMethod() === 'POST') {
                   $form->bindRequest($this->getRequest());
                   if ($form->isValid()) {
                       $em = $this->getDoctrine()->getEntityManager();

                          $em->persist($document);
                          $em->flush();

                          $this->redirect($this->generateUrl('...'));
                     }
               }

               return array('form' => $form->createView());
          }




          PDF brought to you by                                          Chapter 34: How to handle File Uploads with Doctrine | 280
          generated on June 20, 2012
When writing the template, don't forget to set the enctype attribute:

              <h1>Upload File</h1>                                                                                                    Listing
                                                                                                                                       34-5

              <form action="#" method="post" {{ form_enctype(form) }}>
                  {{ form_widget(form) }}

                  <input type="submit" value="Upload Document" />
              </form>



The previous controller will automatically persist the Document entity with the submitted name, but it
will do nothing about the file and the path property will be blank.
An easy way to handle the file upload is to move it just before the entity is persisted and then set the path
property accordingly. Start by calling a new upload() method on the Document class, which you'll create
in a moment to handle the file upload:

if ($form->isValid()) {                                                                                                               Listing
                                                                                                                                       34-6
    $em = $this->getDoctrine()->getEntityManager();

     $document->upload();

     $em->persist($document);
     $em->flush();

     $this->redirect('...');
}

The upload() method will take advantage of the UploadedFile1 object, which is what's returned after a
file field is submitted:

public function upload()                                                                                                              Listing
                                                                                                                                       34-7
{
    // the file property can be empty if the field is not required
    if (null === $this->file) {
        return;
    }

     // we use the original file name here but you should
     // sanitize it at least to avoid any security issues

     // move takes the target directory and then the target filename to move to
     $this->file->move($this->getUploadRootDir(), $this->file->getClientOriginalName());

     // set the path property to the filename where you'ved saved the file
     $this->path = $this->file->getClientOriginalName();

     // clean up the file property as you won't need it anymore
     $this->file = null;
}




1. http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/File/UploadedFile.html


PDF brought to you by                                                    Chapter 34: How to handle File Uploads with Doctrine | 281
generated on June 20, 2012
Using Lifecycle Callbacks
          Even if this implementation works, it suffers from a major flaw: What if there is a problem when the
          entity is persisted? The file would have already moved to its final location even though the entity's path
          property didn't persist correctly.
          To avoid these issues, you should change the implementation so that the database operation and the
          moving of the file become atomic: if there is a problem persisting the entity or if the file cannot be moved,
          then nothing should happen.
          To do this, you need to move the file right as Doctrine persists the entity to the database. This can be
          accomplished by hooking into an entity lifecycle callback:

Listing   /**
 34-8
            * @ORMEntity
            * @ORMHasLifecycleCallbacks
            */
          class Document
          {
          }

          Next, refactor the Document class to take advantage of these callbacks:

Listing   use SymfonyComponentHttpFoundationFileUploadedFile;
 34-9

          /**
            * @ORMEntity
            * @ORMHasLifecycleCallbacks
            */
          class Document
          {
               /**
                 * @ORMPrePersist()
                 * @ORMPreUpdate()
                 */
               public function preUpload()
               {
                    if (null !== $this->file) {
                        // do whatever you want to generate a unique name
                        $this->path = uniqid().'.'.$this->file->guessExtension();
                    }
               }

               /**
                 * @ORMPostPersist()
                 * @ORMPostUpdate()
                 */
               public function upload()
               {
                    if (null === $this->file) {
                        return;
                    }

                     // if there is an error when moving the file, an exception will
                     // be automatically thrown by move(). This will properly prevent
                     // the entity from being persisted to the database on error
                     $this->file->move($this->getUploadRootDir(), $this->path);

                     unset($this->file);
               }



          PDF brought to you by                                       Chapter 34: How to handle File Uploads with Doctrine | 282
          generated on June 20, 2012
/**
       * @ORMPostRemove()
       */
     public function removeUpload()
     {
          if ($file = $this->getAbsolutePath()) {
              unlink($file);
          }
     }
}

The class now does everything you need: it generates a unique filename before persisting, moves the file
after persisting, and removes the file if the entity is ever deleted.
Now that the moving of the file is handled atomically by the entity, the call to $document->upload()
should be removed from the controller:

if ($form->isValid()) {                                                                                                       Listing
                                                                                                                              34-10
    $em = $this->getDoctrine()->getEntityManager();

     $em->persist($document);
     $em->flush();

     $this->redirect('...');
}



              The @ORMPrePersist() and @ORMPostPersist() event callbacks are triggered before and after
              the entity is persisted to the database. On the other hand, the @ORMPreUpdate() and
              @ORMPostUpdate() event callbacks are called when the entity is updated.



              The PreUpdate and PostUpdate callbacks are only triggered if there is a change in one of the
              entity's field that are persisted. This means that, by default, if you modify only the $file property,
              these events will not be triggered, as the property itself is not directly persisted via Doctrine. One
              solution would be to use an updated field that's persisted to Doctrine, and to modify it manually
              when changing the file.



Using the id as the filename
If you want to use the id as the name of the file, the implementation is slightly different as you need to
save the extension under the path property, instead of the actual filename:

use SymfonyComponentHttpFoundationFileUploadedFile;                                                                       Listing
                                                                                                                              34-11

/**
  * @ORMEntity
  * @ORMHasLifecycleCallbacks
  */
class Document
{
     // a property used temporarily while deleting
     private $filenameForRemove;

     /**
      * @ORMPrePersist()
      * @ORMPreUpdate()


PDF brought to you by                                            Chapter 34: How to handle File Uploads with Doctrine | 283
generated on June 20, 2012
*/
     public function preUpload()
     {
          if (null !== $this->file) {
              $this->path = $this->file->guessExtension();
          }
     }

     /**
       * @ORMPostPersist()
       * @ORMPostUpdate()
       */
     public function upload()
     {
          if (null === $this->file) {
              return;
          }

        // you must throw an exception here if the file cannot be moved
        // so that the entity is not persisted to the database
        // which the UploadedFile move() method does
        $this->file->move($this->getUploadRootDir(),
$this->id.'.'.$this->file->guessExtension());

           unset($this->file);
     }

     /**
       * @ORMPreRemove()
       */
     public function storeFilenameForRemove()
     {
          $this->filenameForRemove = $this->getAbsolutePath();
     }

     /**
       * @ORMPostRemove()
       */
     public function removeUpload()
     {
          if ($this->filenameForRemove) {
              unlink($this->filenameForRemove);
          }
     }

    public function getAbsolutePath()
    {
        return null === $this->path ? null :
$this->getUploadRootDir().'/'.$this->id.'.'.$this->path;
    }
}

You'll notice in this case that you need to do a little bit more work in order to remove the file. Before it's
removed, you must store the file path (since it depends on the id). Then, once the object has been fully
removed from the database, you can safely delete the file (in PostRemove).




PDF brought to you by                                        Chapter 34: How to handle File Uploads with Doctrine | 284
generated on June 20, 2012
Chapter 35
              Doctrine Extensions: Timestampable,
                  Sluggable, Translatable, etc.

Doctrine2 is very flexible, and the community has already created a series of useful Doctrine extensions
to help you with common entity-related tasks.
One library in particular - the DoctrineExtensions1 library - provides integration functionality for
Sluggable2, Translatable3, Timestampable4, Loggable5, Tree6 and Sortable7 behaviors.
The usage for each of these extensions is explained in that repository.
However, to install/activate each extension you must register and activate an Event Listener. To do this,
you have two options:
     1. Use the StofDoctrineExtensionsBundle8, which integrates the above library.
     2. Implement this services directly by following the documentation for integration with Symfony2:
        Install Gedmo Doctrine2 extensions in Symfony29




1. https://github.com/l3pp4rd/DoctrineExtensions
2. https://github.com/l3pp4rd/DoctrineExtensions/blob/master/doc/sluggable.md
3. https://github.com/l3pp4rd/DoctrineExtensions/blob/master/doc/translatable.md
4. https://github.com/l3pp4rd/DoctrineExtensions/blob/master/doc/timestampable.md
5. https://github.com/l3pp4rd/DoctrineExtensions/blob/master/doc/loggable.md
6. https://github.com/l3pp4rd/DoctrineExtensions/blob/master/doc/tree.md
7. https://github.com/l3pp4rd/DoctrineExtensions/blob/master/doc/sortable.md
8. https://github.com/stof/StofDoctrineExtensionsBundle
9. https://github.com/l3pp4rd/DoctrineExtensions/blob/master/doc/symfony2.md


PDF brought to you by                              Chapter 35: Doctrine Extensions: Timestampable, Sluggable, Translatable, etc. | 285
generated on June 20, 2012
Chapter 36
               Registering Event Listeners and Subscribers

          Doctrine packages a rich event system that fires events when almost anything happens inside the system.
          For you, this means that you can create arbitrary services and tell Doctrine to notify those objects
          whenever a certain action (e.g. prePersist) happens within Doctrine. This could be useful, for example,
          to create an independent search index whenever an object in your database is saved.
          Doctrine defines two types of objects that can listen to Doctrine events: listeners and subscribers. Both
          are very similar, but listeners are a bit more straightforward. For more, see The Event System1 on
          Doctrine's website.



          Configuring the Listener/Subscriber
          To register a service to act as an event listener or subscriber you just have to tag it with the appropriate
          name. Depending on your use-case, you can hook a listener into every DBAL connection and ORM entity
          manager or just into one specific DBAL connection and all the entity managers that use this connection.

Listing   doctrine:
 36-1
              dbal:
                  default_connection: default
                  connections:
                      default:
                          driver: pdo_sqlite
                          memory: true

          services:
              my.listener:
                  class: AcmeSearchBundleListenerSearchIndexer
                  tags:
                      - { name: doctrine.event_listener, event: postPersist }
              my.listener2:
                  class: AcmeSearchBundleListenerSearchIndexer2
                  tags:
                      - { name: doctrine.event_listener, event: postPersist, connection: default }
              my.subscriber:


          1. http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/reference/events.html


          PDF brought to you by                                                    Chapter 36: Registering Event Listeners and Subscribers | 286
          generated on June 20, 2012
class: AcmeSearchBundleListenerSearchIndexerSubscriber
           tags:
               - { name: doctrine.event_subscriber, connection: default }



Creating the Listener Class
In the previous example, a service my.listener was configured as a Doctrine listener on the event
postPersist. That class behind that service must have a postPersist method, which will be called
when the event is thrown:

// src/Acme/SearchBundle/Listener/SearchIndexer.php                                                                        Listing
                                                                                                                            36-2
namespace AcmeSearchBundleListener;

use DoctrineORMEventLifecycleEventArgs;
use AcmeStoreBundleEntityProduct;

class SearchIndexer
{
    public function postPersist(LifecycleEventArgs $args)
    {
        $entity = $args->getEntity();
        $entityManager = $args->getEntityManager();

           // perhaps you only want to act on some "Product" entity
           if ($entity instanceof Product) {
               // do something with the Product
           }
     }
}

In each event, you have access to a LifecycleEventArgs object, which gives you access to both the entity
object of the event and the entity manager itself.
One important thing to notice is that a listener will be listening for all entities in your application. So,
if you're interested in only handling a specific type of entity (e.g. a Product entity but not a BlogPost
entity), you should check for the class name of the entity in your method (as shown above).




PDF brought to you by                                      Chapter 36: Registering Event Listeners and Subscribers | 287
generated on June 20, 2012
Chapter 37
                             How to use Doctrine's DBAL Layer


                        This article is about Doctrine DBAL's layer. Typically, you'll work with the higher level Doctrine
                        ORM layer, which simply uses the DBAL behind the scenes to actually communicate with the
                        database. To read more about the Doctrine ORM, see "Databases and Doctrine".

          The Doctrine1 Database Abstraction Layer (DBAL) is an abstraction layer that sits on top of PDO2 and
          offers an intuitive and flexible API for communicating with the most popular relational databases. In
          other words, the DBAL library makes it easy to execute queries and perform other database actions.


                        Read the official Doctrine DBAL Documentation3 to learn all the details and capabilities of
                        Doctrine's DBAL library.


          To get started, configure the database connection parameters:

Listing   # app/config/config.yml
 37-1
          doctrine:
              dbal:
                  driver:   pdo_mysql
                  dbname:   Symfony2
                  user:     root
                  password: null
                  charset: UTF8

          For full DBAL configuration options, see Doctrine DBAL Configuration.
          You can then access the Doctrine DBAL connection by accessing the database_connection service:

Listing   class UserController extends Controller
 37-2
          {
              public function indexAction()


          1. http://www.doctrine-project.org
          2. http://www.php.net/pdo
          3. http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/index.html


          PDF brought to you by                                                             Chapter 37: How to use Doctrine's DBAL Layer | 288
          generated on June 20, 2012
{
           $conn = $this->get('database_connection');
           $users = $conn->fetchAll('SELECT * FROM users');

           // ...
     }
}



Registering Custom Mapping Types
You can register custom mapping types through Symfony's configuration. They will be added to all
configured connections. For more information on custom mapping types, read Doctrine's Custom
Mapping Types4 section of their documentation.

# app/config/config.yml                                                                                                               Listing
                                                                                                                                       37-3
doctrine:
    dbal:
        types:
            custom_first: AcmeHelloBundleTypeCustomFirst
            custom_second: AcmeHelloBundleTypeCustomSecond



Registering Custom Mapping Types in the SchemaTool
The SchemaTool is used to inspect the database to compare the schema. To achieve this task, it needs to
know which mapping type needs to be used for each database types. Registering new ones can be done
through the configuration.
Let's map the ENUM type (not suppoorted by DBAL by default) to a the string mapping type:

# app/config/config.yml                                                                                                               Listing
                                                                                                                                       37-4
doctrine:
    dbal:
        connections:
            default:
                // Other connections parameters
                mapping_types:
                     enum: string




4. http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/types.html#custom-mapping-types


PDF brought to you by                                                            Chapter 37: How to use Doctrine's DBAL Layer | 289
generated on June 20, 2012
Chapter 38
                 How to generate Entities from an Existing
                               Database

          When starting work on a brand new project that uses a database, two different situations comes
          naturally. In most cases, the database model is designed and built from scratch. Sometimes, however,
          you'll start with an existing and probably unchangeable database model. Fortunately, Doctrine comes
          with a bunch of tools to help generate model classes from your existing database.


                        As the Doctrine tools documentation1 says, reverse engineering is a one-time process to get started
                        on a project. Doctrine is able to convert approximately 70-80% of the necessary mapping
                        information based on fields, indexes and foreign key constraints. Doctrine can't discover inverse
                        associations, inheritance types, entities with foreign keys as primary keys or semantical operations
                        on associations such as cascade or lifecycle events. Some additional work on the generated entities
                        will be necessary afterwards to design each to fit your domain model specificities.

          This tutorial assumes you're using a simple blog application with the following two tables: blog_post
          and blog_comment. A comment record is linked to a post record thanks to a foreign key constraint.

Listing   CREATE TABLE `blog_post` (
 38-1
            `id` bigint(20) NOT NULL AUTO_INCREMENT,
            `title` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
            `content` longtext COLLATE utf8_unicode_ci NOT NULL,
            `created_at` datetime NOT NULL,
            PRIMARY KEY (`id`)
          ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

          CREATE TABLE `blog_comment` (
            `id` bigint(20) NOT NULL AUTO_INCREMENT,
            `post_id` bigint(20) NOT NULL,
            `author` varchar(20) COLLATE utf8_unicode_ci NOT NULL,
            `content` longtext COLLATE utf8_unicode_ci NOT NULL,
            `created_at` datetime NOT NULL,
            PRIMARY KEY (`id`),

          1. http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/reference/tools.html#reverse-engineering


          PDF brought to you by                                            Chapter 38: How to generate Entities from an Existing Database | 290
          generated on June 20, 2012
KEY `blog_comment_post_id_idx` (`post_id`),
  CONSTRAINT `blog_post_id` FOREIGN KEY (`post_id`) REFERENCES `blog_post` (`id`) ON DELETE
CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

Before diving into the recipe, be sure your database connection parameters are correctly setup in the
app/config/parameters.ini file (or wherever your database configuration is kept) and that you have
initialized a bundle that will host your future entity class. In this tutorial, we will assume that an
AcmeBlogBundle exists and is located under the src/Acme/BlogBundle folder.
The first step towards building entity classes from an existing database is to ask Doctrine to introspect
the database and generate the corresponding metadata files. Metadata files describe the entity class to
generate based on tables fields.

php app/console doctrine:mapping:convert xml ./src/Acme/BlogBundle/Resources/config/doctrine/                                  Listing
                                                                                                                                38-2
metadata/orm --from-database --force

This command line tool asks Doctrine to introspect the database and generate the XML metadata files
under the src/Acme/BlogBundle/Resources/config/doctrine/metadata/orm folder of your bundle.


              It's also possible to generate metadata class in YAML format by changing the first argument to yml.



The generated BlogPost.dcm.xml metadata file looks as follows:

<?xml version="1.0" encoding="utf-8"?>                                                                                         Listing
                                                                                                                                38-3
<doctrine-mapping>
  <entity name="BlogPost" table="blog_post">
    <change-tracking-policy>DEFERRED_IMPLICIT</change-tracking-policy>
    <id name="id" type="bigint" column="id">
      <generator strategy="IDENTITY"/>
    </id>
    <field name="title" type="string" column="title" length="100"/>
    <field name="content" type="text" column="content"/>
    <field name="isPublished" type="boolean" column="is_published"/>
    <field name="createdAt" type="datetime" column="created_at"/>
    <field name="updatedAt" type="datetime" column="updated_at"/>
    <field name="slug" type="string" column="slug" length="255"/>
    <lifecycle-callbacks/>
  </entity>
</doctrine-mapping>

Once the metadata files are generated, you can ask Doctrine to import the schema and build related entity
classes by executing the following two commands.

php app/console doctrine:mapping:import AcmeBlogBundle annotation                                                              Listing
                                                                                                                                38-4
php app/console doctrine:generate:entities AcmeBlogBundle

The first command generates entity classes with an annotations mapping, but you can of course change
the annotation argument to xml or yml. The newly created BlogComment entity class looks as follow:

<?php                                                                                                                          Listing
                                                                                                                                38-5

// src/Acme/BlogBundle/Entity/BlogComment.php
namespace AcmeBlogBundleEntity;

use DoctrineORMMapping as ORM;



PDF brought to you by                                   Chapter 38: How to generate Entities from an Existing Database | 291
generated on June 20, 2012
/**
  * AcmeBlogBundleEntityBlogComment
  *
  * @ORMTable(name="blog_comment")
  * @ORMEntity
  */
class BlogComment
{
     /**
      * @var bigint $id
      *
      * @ORMColumn(name="id", type="bigint", nullable=false)
      * @ORMId
      * @ORMGeneratedValue(strategy="IDENTITY")
      */
     private $id;

     /**
      * @var string $author
      *
      * @ORMColumn(name="author", type="string", length=100, nullable=false)
      */
     private $author;

     /**
      * @var text $content
      *
      * @ORMColumn(name="content", type="text", nullable=false)
      */
     private $content;

     /**
      * @var datetime $createdAt
      *
      * @ORMColumn(name="created_at", type="datetime", nullable=false)
      */
     private $createdAt;

     /**
      * @var BlogPost
      *
      * @ORMManyToOne(targetEntity="BlogPost")
      * @ORMJoinColumn(name="post_id", referencedColumnName="id")
      */
     private $post;
}

As you can see, Doctrine converts all table fields to pure private and annotated class properties. The most
impressive thing is that it also discovered the relationship with the BlogPost entity class based on the
foreign key constraint. Consequently, you can find a private $post property mapped with a BlogPost
entity in the BlogComment entity class.
The last command generated all getters and setters for your two BlogPost and BlogComment entity class
properties. The generated entities are now ready to be used. Have fun!




PDF brought to you by                               Chapter 38: How to generate Entities from an Existing Database | 292
generated on June 20, 2012
Chapter 39
    How to work with Multiple Entity Managers

You can use multiple entity managers in a Symfony2 application. This is necessary if you are using
different databases or even vendors with entirely different sets of entities. In other words, one entity
manager that connects to one database will handle some entities while another entity manager that
connects to another database might handle the rest.


              Using multiple entity managers is pretty easy, but more advanced and not usually required. Be sure
              you actually need multiple entity managers before adding in this layer of complexity.


The following configuration code shows how you can configure two entity managers:

doctrine:                                                                                                                  Listing
                                                                                                                            39-1
    orm:
        default_entity_manager:    default
        entity_managers:
            default:
                connection:        default
                mappings:
                     AcmeDemoBundle: ~
                     AcmeStoreBundle: ~
            customer:
                connection:        customer
                mappings:
                     AcmeCustomerBundle: ~

In this case, you've defined two entity managers and called them default and customer. The default
entity manager manages entities in the AcmeDemoBundle and AcmeStoreBundle, while the customer
entity manager manages entities in the AcmeCustomerBundle.
When working with multiple entity managers, you should be explicit about which entity manager you
want. If you do omit the entity manager's name when asking for it, the default entity manager (i.e.
default) is returned:

class UserController extends Controller                                                                                    Listing
                                                                                                                            39-2
{
    public function indexAction()

PDF brought to you by                                        Chapter 39: How to work with Multiple Entity Managers | 293
generated on June 20, 2012
{
           // both return the "default" em
           $em = $this->get('doctrine')->getEntityManager();
           $em = $this->get('doctrine')->getEntityManager('default');

           $customerEm = $this->get('doctrine')->getEntityManager('customer');
     }
}

You can now use Doctrine just as you did before - using the default entity manager to persist and fetch
entities that it manages and the customer entity manager to persist and fetch its entities.




PDF brought to you by                                  Chapter 39: How to work with Multiple Entity Managers | 294
generated on June 20, 2012
Chapter 40
                  Registering Custom DQL Functions

Doctrine allows you to specify custom DQL functions. For more information on this topic, read
Doctrine's cookbook article "DQL User Defined Functions1".
In Symfony, you can register your custom DQL functions as follows:

# app/config/config.yml                                                                                                               Listing
                                                                                                                                       40-1
doctrine:
    orm:
        # ...
        entity_managers:
            default:
                # ...
                dql:
                     string_functions:
                         test_string: AcmeHelloBundleDQLStringFunction
                         second_string: AcmeHelloBundleDQLSecondStringFunction
                     numeric_functions:
                         test_numeric: AcmeHelloBundleDQLNumericFunction
                     datetime_functions:
                         test_datetime: AcmeHelloBundleDQLDatetimeFunction




1. http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/cookbook/dql-user-defined-functions.html


PDF brought to you by                                                            Chapter 40: Registering Custom DQL Functions | 295
generated on June 20, 2012
Chapter 41
                           How to customize Form Rendering

          Symfony gives you a wide variety of ways to customize how a form is rendered. In this guide, you'll learn
          how to customize every possible part of your form with as little effort as possible whether you use Twig
          or PHP as your templating engine.



          Form Rendering Basics
          Recall that the label, error and HTML widget of a form field can easily be rendered by using the form_row
          Twig function or the row PHP helper method:

Listing   {{ form_row(form.age) }}
 41-1

          You can also render each of the three parts of the field individually:

Listing   <div>
 41-2
              {{ form_label(form.age) }}
              {{ form_errors(form.age) }}
              {{ form_widget(form.age) }}
          </div>

          In both cases, the form label, errors and HTML widget are rendered by using a set of markup that ships
          standard with Symfony. For example, both of the above templates would render:

Listing   <div>
 41-3
              <label for="form_age">Age</label>
              <ul>
                   <li>This field is required</li>
              </ul>
              <input type="number" id="form_age" name="form[age]" />
          </div>

          To quickly prototype and test a form, you can render the entire form with just one line:

Listing   {{ form_widget(form) }}
 41-4




          PDF brought to you by                                             Chapter 41: How to customize Form Rendering | 296
          generated on June 20, 2012
The remainder of this recipe will explain how every part of the form's markup can be modified at
several different levels. For more information about form rendering in general, see Rendering a Form in a
Template.



What are Form Themes?
Symfony uses form fragments - a small piece of a template that renders just one part of a form - to render
every part of a form - - field labels, errors, input text fields, select tags, etc
The fragments are defined as blocks in Twig and as template files in PHP.
A theme is nothing more than a set of fragments that you want to use when rendering a form. In other
words, if you want to customize one portion of how a form is rendered, you'll import a theme which
contains a customization of the appropriate form fragments.
Symfony comes with a default theme (form_div_layout.html.twig1 in Twig and FrameworkBundle:Form
in PHP) that defines each and every fragment needed to render every part of a form.
In the next section you will learn how to customize a theme by overriding some or all of its fragments.
For example, when the widget of a integer type field is rendered, an input number field is generated

{{ form_widget(form.age) }}                                                                                                          Listing
                                                                                                                                      41-5

renders:

<input type="number" id="form_age" name="form[age]" required="required" value="33" />                                                Listing
                                                                                                                                      41-6

Internally, Symfony uses the integer_widget fragment to render the field. This is because the field type
is integer and you're rendering its widget (as opposed to its label or errors).
In Twig that would default to the block integer_widget from the form_div_layout.html.twig2 template.
In PHP it would rather be the integer_widget.html.php file located in FrameworkBundle/Resources/
views/Form folder.
The default implementation of the integer_widget fragment looks like this:

{% block integer_widget %}                                                                                                           Listing
                                                                                                                                      41-7
    {% set type = type|default('number') %}
    {{ block('field_widget') }}
{% endblock integer_widget %}

As you can see, this fragment itself renders another fragment - field_widget:

{% block field_widget %}                                                                                                             Listing
                                                                                                                                      41-8
    {% set type = type|default('text') %}
    <input type="{{ type }}" {{ block('widget_attributes') }} value="{{ value }}" />
{% endblock field_widget %}

The point is, the fragments dictate the HTML output of each part of a form. To customize the form
output, you just need to identify and override the correct fragment. A set of these form fragment
customizations is known as a form "theme". When rendering a form, you can choose which form
theme(s) you want to apply.
In Twig a theme is a single template file and the fragments are the blocks defined in this file.
In PHP a theme is a folder and the the fragments are individual template files in this folder.



1. https://github.com/symfony/symfony/blob/master/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig
2. https://github.com/symfony/symfony/blob/master/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig


PDF brought to you by                                                            Chapter 41: How to customize Form Rendering | 297
generated on June 20, 2012
Knowing which block to customize
                        In this example, the customized fragment name is integer_widget because you want to override
                        the HTML widget for all integer field types. If you need to customize textarea fields, you would
                        customize textarea_widget.
                        As you can see, the fragment name is a combination of the field type and which part of the field is
                        being rendered (e.g. widget, label, errors, row). As such, to customize how errors are rendered
                        for just input text fields, you should customize the text_errors fragment.
                        More commonly, however, you'll want to customize how errors are displayed across all fields.
                        You can do this by customizing the field_errors fragment. This takes advantage of field type
                        inheritance. Specifically, since the text type extends from the field type, the form component
                        will first look for the type-specific fragment (e.g. text_errors) before falling back to its parent
                        fragment name if it doesn't exist (e.g. field_errors).
                        For more information on this topic, see Form Fragment Naming.



          Form Theming
          To see the power of form theming, suppose you want to wrap every input number field with a div tag.
          The key to doing this is to customize the integer_widget fragment.



          Form Theming in Twig
          When customizing the form field block in Twig, you have two options on where the customized form
          block can live:

           Method                                Pros                              Cons
           Inside the same template as the       Quick and easy                    Can't be reused in other templates
           form
           Inside a separate template            Can be reused by many             Requires an extra template to be
                                                 templates                         created

          Both methods have the same effect but are better in different situations.

          Method 1: Inside the same Template as the Form
          The easiest way to customize the integer_widget block is to customize it directly in the template that's
          actually rendering the form.

Listing   {% extends '::base.html.twig' %}
 41-9

          {% form_theme form _self %}

          {% block integer_widget %}
              <div class="integer_widget">
                  {% set type = type|default('number') %}
                  {{ block('field_widget') }}
              </div>
          {% endblock %}

          {% block content %}


          PDF brought to you by                                                Chapter 41: How to customize Form Rendering | 298
          generated on June 20, 2012
{# render the form #}

    {{ form_row(form.age) }}
{% endblock %}

By using the special {% form_theme form _self %} tag, Twig looks inside the same template for
any overridden form blocks. Assuming the form.age field is an integer type field, when its widget is
rendered, the customized integer_widget block will be used.
The disadvantage of this method is that the customized form block can't be reused when rendering other
forms in other templates. In other words, this method is most useful when making form customizations
that are specific to a single form in your application. If you want to reuse a form customization across
several (or all) forms in your application, read on to the next section.

Method 2: Inside a Separate Template
You can also choose to put the customized integer_widget form block in a separate template entirely.
The code and end-result are the same, but you can now re-use the form customization across many
templates:

{# src/Acme/DemoBundle/Resources/views/Form/fields.html.twig #}                                                    Listing
                                                                                                                   41-10

{% block integer_widget %}
    <div class="integer_widget">
        {% set type = type|default('number') %}
        {{ block('field_widget') }}
    </div>
{% endblock %}

Now that you've created the customized form block, you need to tell Symfony to use it. Inside the
template where you're actually rendering your form, tell Symfony to use the template via the form_theme
tag:

{% form_theme form 'AcmeDemoBundle:Form:fields.html.twig' %}                                                       Listing
                                                                                                                   41-11

{{ form_widget(form.age) }}

When the form.age widget is rendered, Symfony will use the integer_widget block from the new
template and the input tag will be wrapped in the div element specified in the customized block.



Form Theming in PHP
When using PHP as a templating engine, the only method to customize a fragment is to create a new
template file - this is similar to the second method used by Twig.
The template file must be named after the fragment. You must create a integer_widget.html.php file
in order to customize the integer_widget fragment.

<!-- src/Acme/DemoBundle/Resources/views/Form/integer_widget.html.php -->                                          Listing
                                                                                                                   41-12

<div class="integer_widget">
    <?php echo $view['form']->renderBlock('field_widget', array('type' => isset($type) ? $type
: "number")) ?>
</div>




PDF brought to you by                                          Chapter 41: How to customize Form Rendering | 299
generated on June 20, 2012
Now that you've created the customized form template, you need to tell Symfony to use it. Inside the
          template where you're actually rendering your form, tell Symfony to use the theme via the setTheme
          helper method:

Listing   <?php $view['form']->setTheme($form, array('AcmeDemoBundle:Form')) ;?>
41-13

          <?php $view['form']->widget($form['age']) ?>

          When the form.age widget is rendered, Symfony will use the customized integer_widget.html.php
          template and the input tag will be wrapped in the div element.



          Referencing Base Form Blocks (Twig specific)
          So far, to override a particular form block, the best method is to copy the default block from
          form_div_layout.html.twig3, paste it into a different template, and the customize it. In many cases, you
          can avoid doing this by referencing the base block when customizing it.
          This is easy to do, but varies slightly depending on if your form block customizations are in the same
          template as the form or a separate template.

          Referencing Blocks from inside the same Template as the Form
          Import the blocks by adding a use tag in the template where you're rendering the form:

Listing   {% use 'form_div_layout.html.twig' with integer_widget as base_integer_widget %}
41-14


          Now, when the blocks from form_div_layout.html.twig4 are imported, the integer_widget block is
          called base_integer_widget. This means that when you redefine the integer_widget block, you can
          reference the default markup via base_integer_widget:

Listing   {% block integer_widget %}
41-15
              <div class="integer_widget">
                  {{ block('base_integer_widget') }}
              </div>
          {% endblock %}


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

Listing   {# src/Acme/DemoBundle/Resources/views/Form/fields.html.twig #}
41-16

          {% extends 'form_div_layout.html.twig' %}

          {% block integer_widget %}
              <div class="integer_widget">
                  {{ parent() }}
              </div>
          {% endblock %}




          3. https://github.com/symfony/symfony/blob/master/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig
          4. https://github.com/symfony/symfony/blob/master/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig


          PDF brought to you by                                                            Chapter 41: How to customize Form Rendering | 300
          generated on June 20, 2012
It is not possible to reference the base block when using PHP as the templating engine. You have
              to manually copy the content from the base block to your new template file.




Making Application-wide Customizations
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:

Twig
By using the following configuration, any customized form blocks inside the
AcmeDemoBundle:Form:fields.html.twig template will be used globally when a form is rendered.

# app/config/config.yml                                                                                                Listing
                                                                                                                       41-17

twig:
    form:
        resources:
            - 'AcmeDemoBundle:Form:fields.html.twig'
    # ...

By default, Twig uses a div layout when rendering forms. Some people, however, may prefer to render
forms in a table layout. Use the form_table_layout.html.twig resource to use such a layout:

# app/config/config.yml                                                                                                Listing
                                                                                                                       41-18

twig:
    form:
        resources: ['form_table_layout.html.twig']
    # ...

If you only want to make the change in one template, add the following line to your template file rather
than adding the template as a resource:

{% form_theme form 'form_table_layout.html.twig' %}                                                                    Listing
                                                                                                                       41-19

Note that the form variable in the above code is the form view variable that you passed to your template.


PHP
By using the following configuration, any customized form fragments inside the src/Acme/DemoBundle/
Resources/views/Form folder will be used globally when a form is rendered.

# app/config/config.yml                                                                                                Listing
                                                                                                                       41-20

framework:
    templating:
        form:
            resources:
                - 'AcmeDemoBundle:Form'
    # ...




PDF brought to you by                                              Chapter 41: How to customize Form Rendering | 301
generated on June 20, 2012
By default, the PHP engine uses a div layout when rendering forms. Some people, however, may prefer to
          render forms in a table layout. Use the FrameworkBundle:FormTable resource to use such a layout:

Listing   # app/config/config.yml
41-21

          framework:
              templating:
                  form:
                      resources:
                          - 'FrameworkBundle:FormTable'

          If you only want to make the change in one template, add the following line to your template file rather
          than adding the template as a resource:

Listing   <?php $view['form']->setTheme($form, array('FrameworkBundle:FormTable')); ?>
41-22

          Note that the $form variable in the above code is the form view variable that you passed to your template.



          How to customize an Individual field
          So far, you've seen the different ways you can customize the widget output of all text field types. You
          can also customize individual fields. For example, suppose you have two text fields - first_name and
          last_name - but you only want to customize one of the fields. This can be accomplished by customizing
          a fragment whose name is a combination of the field id attribute and which part of the field is being
          customized. For example:

Listing   {% form_theme form _self %}
41-23

          {% block _product_name_widget %}
              <div class="text_widget">
                  {{ block('field_widget') }}
              </div>
          {% endblock %}

          {{ form_widget(form.name) }}

          Here, the _product_name_widget fragment defines the template to use for the field whose id is
          product_name (and name is product[name]).


                        The product portion of the field is the form name, which may be set manually or generated
                        automatically based on your form type name (e.g. ProductType equates to product). If you're not
                        sure what your form name is, just view the source of your generated form.

          You can also override the markup for an entire field row using the same method:

Listing   {% form_theme form _self %}
41-24

          {% block _product_name_row %}
              <div class="name_row">
                  {{ form_label(form) }}
                  {{ form_errors(form) }}
                  {{ form_widget(form) }}
              </div>
          {% endblock %}




          PDF brought to you by                                              Chapter 41: How to customize Form Rendering | 302
          generated on June 20, 2012
Other Common Customizations
So far, this recipe has shown you several different ways to customize a single piece of how a form is
rendered. The key is to customize a specific fragment that corresponds to the portion of the form you
want to control (see naming form blocks).
In the next sections, you'll see how you can make several common form customizations. To apply these
customizations, use one of the methods described in the Form Theming section.

Customizing Error Output

              The form component only handles how the validation errors are rendered, and not the actual
              validation error messages. The error messages themselves are determined by the validation
              constraints you apply to your objects. For more information, see the chapter on validation.

There are many different ways to customize how errors are rendered when a form is submitted with
errors. The error messages for a field are rendered when you use the form_errors helper:

{{ form_errors(form.age) }}                                                                                            Listing
                                                                                                                       41-25

By default, the errors are rendered inside an unordered list:

<ul>                                                                                                                   Listing
                                                                                                                       41-26
    <li>This field is required</li>
</ul>

To override how errors are rendered for all fields, simply copy, paste and customize the field_errors
fragment.

{% block field_errors %}                                                                                               Listing
                                                                                                                       41-27
{% spaceless %}
    {% if errors|length > 0 %}
    <ul class="error_list">
        {% for error in errors %}
            <li>{{ error.messageTemplate|trans(error.messageParameters, 'validators') }}</li>
        {% endfor %}
    </ul>
    {% endif %}
{% endspaceless %}
{% endblock field_errors %}



              See Form Theming for how to apply this customization.



You can also customize the error output for just one specific field type. For example, certain errors that
are more global to your form (i.e. not specific to just one field) are rendered separately, usually at the top
of your form:

{{ form_errors(form) }}                                                                                                Listing
                                                                                                                       41-28

To customize only the markup used for these errors, follow the same directions as above, but now call
the block form_errors (Twig) / the file form_errors.html.php (PHP). Now, when errors for the form
type are rendered, your customized fragment will be used instead of the default field_errors.



PDF brought to you by                                              Chapter 41: How to customize Form Rendering | 303
generated on June 20, 2012
Customizing the "Form Row"
          When you can manage it, the easiest way to render a form field is via the form_row function, which
          renders the label, errors and HTML widget of a field. To customize the markup used for rendering all
          form field rows, override the field_row fragment. For example, suppose you want to add a class to the
          div element around each row:

Listing   {% block field_row %}
41-29
              <div class="form_row">
                  {{ form_label(form) }}
                  {{ form_errors(form) }}
                  {{ form_widget(form) }}
              </div>
          {% endblock field_row %}



                        See Form Theming for how to apply this customization.




          Adding a "Required" Asterisk to Field Labels
          If you want to denote all of your required fields with a required asterisk (*), you can do this by
          customizing the field_label fragment.
          In Twig, if you're making the form customization inside the same template as your form, modify the use
          tag and add the following:

Listing   {% use 'form_div_layout.html.twig' with field_label as base_field_label %}
41-30

          {% block field_label %}
              {{ block('base_field_label') }}

              {% if required %}
                  <span class="required" title="This field is required">*</span>
              {% endif %}
          {% endblock %}

          In Twig, if you're making the form customization inside a separate template, use the following:

Listing   {% extends 'form_div_layout.html.twig' %}
41-31

          {% block field_label %}
              {{ parent() }}

              {% if required %}
                  <span class="required" title="This field is required">*</span>
              {% endif %}
          {% endblock %}

          When using PHP as a templating engine you have to copy the content from the original template:

Listing   <!-- field_label.html.php -->
41-32

          <!-- original content -->
          <label for="<?php echo $view->escape($id) ?>" <?php foreach($attr as $k => $v) {
          printf('%s="%s" ', $view->escape($k), $view->escape($v)); } ?>><?php echo
          $view->escape($view['translator']->trans($label)) ?></label>



          PDF brought to you by                                             Chapter 41: How to customize Form Rendering | 304
          generated on June 20, 2012
<!-- customization -->
<?php if ($required) : ?>
    <span class="required" title="This field is required">*</span>
<?php endif ?>



              See Form Theming for how to apply this customization.




Adding "help" messages
You can also customize your form widgets to have an optional "help" message.
In Twig, If you're making the form customization inside the same template as your form, modify the use
tag and add the following:

{% use 'form_div_layout.html.twig' with field_widget as base_field_widget %}                                           Listing
                                                                                                                       41-33

{% block field_widget %}
    {{ block('base_field_widget') }}

    {% if help is defined %}
        <span class="help">{{ help }}</span>
    {% endif %}
{% endblock %}

In twig, If you're making the form customization inside a separate template, use the following:

{% extends 'form_div_layout.html.twig' %}                                                                              Listing
                                                                                                                       41-34

{% block field_widget %}
    {{ parent() }}

    {% if help is defined %}
        <span class="help">{{ help }}</span>
    {% endif %}
{% endblock %}

When using PHP as a templating engine you have to copy the content from the original template:

<!-- field_widget.html.php -->                                                                                         Listing
                                                                                                                       41-35

<!-- Original content -->
<input
    type="<?php echo isset($type) ? $view->escape($type) : "text" ?>"
    value="<?php echo $view->escape($value) ?>"
    <?php echo $view['form']->renderBlock('attributes') ?>
/>

<!-- Customization -->
<?php if (isset($help)) : ?>
    <span class="help"><?php echo $view->escape($help) ?></span>
<?php endif ?>

To render a help message below a field, pass in a help variable:

{{ form_widget(form.title, { 'help': 'foobar' }) }}                                                                    Listing
                                                                                                                       41-36




PDF brought to you by                                              Chapter 41: How to customize Form Rendering | 305
generated on June 20, 2012
See Form Theming for how to apply this customization.




PDF brought to you by                                             Chapter 41: How to customize Form Rendering | 306
generated on June 20, 2012
Chapter 42
                             Using Data Transformers

You'll often find the need to transform the data the user entered in a form into something else for use
in your program. You could easily do this manually in your controller, but what if you want to use this
specific form in different places?
Say you have a one-to-one relation of Task to Issue, e.g. a Task optionally has an issue linked to it. Adding
a listbox with all possible issues can eventually lead to a really long listbox in which it is impossible to
find something. You'll rather want to add a textbox, in which the user can simply enter the number of
the issue. In the controller you can convert this issue number to an actual task, and eventually add errors
to the form if it was not found, but of course this is not really clean.
It would be better if this issue was automatically looked up and converted to an Issue object, for use in
your action. This is where Data Transformers come into play.
First, create a custom form type which has a Data Transformer attached to it, which returns the Issue
by number: the issue selector type. Eventually this will simply be a text field, as we configure the fields'
parent to be a "text" field, in which you will enter the issue number. The field will display an error if a
non existing number was entered:

// src/Acme/TaskBundle/Form/Type/IssueSelectorType.php                                                                Listing
                                                                                                                       42-1
namespace AcmeTaskBundleFormType;

use   SymfonyComponentFormAbstractType;
use   SymfonyComponentFormFormBuilder;
use   AcmeTaskBundleFormDataTransformerIssueToNumberTransformer;
use   DoctrineCommonPersistenceObjectManager;

class IssueSelectorType extends AbstractType
{
    /**
     * @var ObjectManager
     */
    private $om;

      /**
        * @param ObjectManager $om
        */
      public function __construct(ObjectManager $om)
      {


PDF brought to you by                                                     Chapter 42: Using Data Transformers | 307
generated on June 20, 2012
$this->om = $om;
               }

               public function buildForm(FormBuilder $builder, array $options)
               {
                   $transformer = new IssueToNumberTransformer($this->om);
                   $builder->appendClientTransformer($transformer);
               }

               public function getDefaultOptions(array $options)
               {
                   return array(
                       'invalid_message' => 'The selected issue does not exist',
                   );
               }

               public function getParent(array $options)
               {
                   return 'text';
               }

               public function getName()
               {
                   return 'issue_selector';
               }
          }



                             You can also use transformers without creating a new custom form type by calling
                             appendClientTransformer on any field builder:

                   Listing   use AcmeTaskBundleFormDataTransformerIssueToNumberTransformer;
                    42-2

                             class TaskType extends AbstractType
                             {
                                 public function buildForm(FormBuilder $builder, array $options)
                                 {
                                     // ...

                                     // this assumes that the entity manager was passed in as an option
                                     $entityManager = $options['em'];
                                     $transformer = new IssueToNumberTransformer($entityManager);

                                     // use a normal text field, but transform the text into an issue object
                                     $builder
                                         ->add('issue', 'text')
                                         ->appendClientTransformer($transformer)
                                     ;
                                 }

                                 // ...
                             }



          Next, we create the data transformer, which does the actual conversion:

Listing   // src/Acme/TaskBundle/Form/DataTransformer/IssueToNumberTransformer.php
 42-3

          namespace AcmeTaskBundleFormDataTransformer;

          use SymfonyComponentFormDataTransformerInterface;


          PDF brought to you by                                                        Chapter 42: Using Data Transformers | 308
          generated on June 20, 2012
use SymfonyComponentFormExceptionTransformationFailedException;
use DoctrineCommonPersistenceObjectManager;
use AcmeTaskBundleEntityIssue;

class IssueToNumberTransformer implements DataTransformerInterface
{
    /**
     * @var ObjectManager
     */
    private $om;

     /**
       * @param ObjectManager $om
       */
     public function __construct(ObjectManager $om)
     {
          $this->om = $om;
     }

     /**
       * Transforms an object (issue) to a string (number).
       *
       * @param Issue|null $issue
       * @return string
       */
     public function transform($issue)
     {
          if (null === $issue) {
              return "";
          }

           return $issue->getNumber();
     }

     /**
       * Transforms a string (number) to an object (issue).
       *
       * @param string $number
       * @return Issue|null
       * @throws TransformationFailedException if object (issue) is not found.
       */
     public function reverseTransform($number)
     {
          if (!$number) {
              return null;
          }

           $issue = $this->om
               ->getRepository('AcmeTaskBundle:Issue')
               ->findOneBy(array('number' => $number))
           ;

           if (null === $issue) {
               throw new TransformationFailedException(sprintf(
                   'An issue with number "%s" does not exist!',
                   $number
               ));
           }

           return $issue;



PDF brought to you by                                              Chapter 42: Using Data Transformers | 309
generated on June 20, 2012
}
          }

          Finally, since we've decided to create a custom form type that uses the data transformer, register the Type
          in the service container, so that the entity manager can be automatically injected:

Listing   services:
 42-4
              acme_demo.type.issue_selector:
                  class: AcmeTaskBundleFormTypeIssueSelectorType
                  arguments: ["@doctrine.orm.entity_manager"]
                  tags:
                      - { name: form.type, alias: issue_selector }

          You can now add the type to your form by its alias as follows:

Listing   // src/Acme/TaskBundle/Form/Type/TaskType.php
 42-5

          namespace AcmeTaskBundleFormType;

          use SymfonyComponentFormAbstractType;
          use SymfonyComponentFormFormBuilder;

          class TaskType extends AbstractType
          {
              public function buildForm(FormBuilder $builder, array $options)
              {
                  $builder
                      ->add('task')
                      ->add('dueDate', null, array('widget' => 'single_text'));
                      ->add('issue', 'issue_selector')
                  ;
              }

               public function getName()
               {
                   return 'task';
               }
          }

          Now it will be very easy at any random place in your application to use this selector type to select an issue
          by number. No logic has to be added to your Controller at all.
          If you want a new issue to be created when an unknown number is entered, you can instantiate it rather
          than throwing the TransformationFailedException, and even persist it to your entity manager if the task
          has no cascading options for the issue.




          PDF brought to you by                                                     Chapter 42: Using Data Transformers | 310
          generated on June 20, 2012
Chapter 43
      How to Dynamically Generate Forms Using
                  Form Events

Before jumping right into dynamic form generation, let's have a quick review of what a bare form class
looks like:

//src/Acme/DemoBundle/Form/ProductType.php                                                                                   Listing
                                                                                                                              43-1
namespace AcmeDemoBundleForm;

use SymfonyComponentFormAbstractType;
use SymfonyComponentFormFormBuilder;

class ProductType extends AbstractType
{
    public function buildForm(FormBuilder $builder, array $options)
    {
        $builder->add('name');
        $builder->add('price');
    }

     public function getName()
     {
         return 'product';
     }
}



              If this particular section of code isn't already familiar to you, you probably need to take a step back
              and first review the Forms chapter before proceeding.


Let's assume for a moment that this form utilizes an imaginary "Product" class that has only two relevant
properties ("name" and "price"). The form generated from this class will look the exact same regardless
of a new Product is being created or if an existing product is being edited (e.g. a product fetched from the
database).



PDF brought to you by                                Chapter 43: How to Dynamically Generate Forms Using Form Events | 311
generated on June 20, 2012
Suppose now, that you don't want the user to be able to change the name value once the object has been
          created. To do this, you can rely on Symfony's Event Dispatcher system to analyze the data on the object
          and modify the form based on the Product object's data. In this entry, you'll learn how to add this level
          of flexibility to your forms.



          Adding An Event Subscriber To A Form Class
          So, instead of directly adding that "name" widget via our ProductType form class, let's delegate the
          responsibility of creating that particular field to an Event Subscriber:

Listing   //src/Acme/DemoBundle/Form/ProductType.php
 43-2
          namespace AcmeDemoBundleForm

          use SymfonyComponentFormAbstractType
          use SymfonyComponentFormFormBuilder;
          use AcmeDemoBundleFormEventListenerAddNameFieldSubscriber;

          class ProductType extends AbstractType
          {
              public function buildForm(FormBuilder $builder, array $options)
              {
                  $subscriber = new AddNameFieldSubscriber($builder->getFormFactory());
                  $builder->addEventSubscriber($subscriber);
                  $builder->add('price');
              }

                public function getName()
                {
                    return 'product';
                }
          }

          The event subscriber is passed the FormFactory object in its constructor so that our new subscriber is
          capable of creating the form widget once it is notified of the dispatched event during form creation.



          Inside the Event Subscriber Class
          The goal is to create a "name" field only if the underlying Product object is new (e.g. hasn't been persisted
          to the database). Based on that, the subscriber might look like the following:

Listing   // src/Acme/DemoBundle/Form/EventListener/AddNameFieldSubscriber.php
 43-3
          namespace AcmeDemoBundleFormEventListener;

          use   SymfonyComponentFormEventDataEvent;
          use   SymfonyComponentFormFormFactoryInterface;
          use   SymfonyComponentEventDispatcherEventSubscriberInterface;
          use   SymfonyComponentFormFormEvents;

          class AddNameFieldSubscriber implements EventSubscriberInterface
          {
              private $factory;

                public function __construct(FormFactoryInterface $factory)
                {
                    $this->factory = $factory;
                }


          PDF brought to you by                            Chapter 43: How to Dynamically Generate Forms Using Form Events | 312
          generated on June 20, 2012
public static function getSubscribedEvents()
     {
         // Tells the dispatcher that we want to listen on the form.pre_set_data
         // event and that the preSetData method should be called.
         return array(FormEvents::PRE_SET_DATA => 'preSetData');
     }

     public function preSetData(DataEvent $event)
     {
         $data = $event->getData();
         $form = $event->getForm();

           //   During form creation setData() is called with null as an argument
           //   by the FormBuilder constructor. We're only concerned with when
           //   setData is called with an actual Entity object in it (whether new,
           //   or fetched with Doctrine). This if statement let's us skip right
           //   over the null condition.
           if   (null === $data) {
                 return;
           }

           // check if the product object is "new"
           if (!$data->getId()) {
               $form->add($this->factory->createNamed('text', 'name'));
           }
     }
}



                It is easy to misunderstand the purpose of the if (null === $data) segment of this event
                subscriber. To fully understand its role, you might consider also taking a look at the Form class1
                and paying special attention to where setData() is called at the end of the constructor, as well as
                the setData() method itself.

The FormEvents::PRE_SET_DATA line actually resolves to the string form.pre_set_data. The
FormEvents class2 serves an organizational purpose. It is a centralized location in which you can find all
of the various form events available.
While this example could have used the form.set_data event or even the form.post_set_data events
just as effectively, by using form.pre_set_data we guarantee that the data being retrieved from the
Event object has in no way been modified by any other subscribers or listeners. This is because
form.pre_set_data passes a DataEvent3 object instead of the FilterDataEvent4 object passed by the
form.set_data event. DataEvent5, unlike its child FilterDataEvent6, lacks a setData() method.


                You may view the full list of form events via the FormEvents class7, found in the form bundle.




1. https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Form.php
2. https://github.com/symfony/Form/blob/master/FormEvents.php
3. https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Event/DataEvent.php
4. https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Event/FilterDataEvent.php
5. https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Event/DataEvent.php
6. https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Event/FilterDataEvent.php
7. https://github.com/symfony/Form/blob/master/FormEvents.php


PDF brought to you by                                       Chapter 43: How to Dynamically Generate Forms Using Form Events | 313
generated on June 20, 2012
Chapter 44
                         How to Embed a Collection of Forms

          In this entry, you'll learn how to create a form that embeds a collection of many other forms. This could
          be useful, for example, if you had a Task class and you wanted to edit/create/remove many Tag objects
          related to that Task, right inside the same form.


                        In this entry, we'll loosely assume that you're using Doctrine as your database store. But if you're
                        not using Doctrine (e.g. Propel or just a database connection), it's all very similar. There are only a
                        few parts of this tutorial that really care about "persistence".
                        If you are using Doctrine, you'll need to add the Doctrine metadata, including the ManyToMany on
                        the Task's tags property.

          Let's start there: suppose that each Task belongs to multiple Tags objects. Start by creating a simple Task
          class:

Listing   // src/Acme/TaskBundle/Entity/Task.php
 44-1
          namespace AcmeTaskBundleEntity;

          use DoctrineCommonCollectionsArrayCollection;

          class Task
          {
              protected $description;

               protected $tags;

               public function __construct()
               {
                   $this->tags = new ArrayCollection();
               }

               public function getDescription()
               {
                   return $this->description;
               }

               public function setDescription($description)


          PDF brought to you by                                                Chapter 44: How to Embed a Collection of Forms | 314
          generated on June 20, 2012
{
           $this->description = $description;
     }

     public function getTags()
     {
         return $this->tags;
     }

     public function setTags(ArrayCollection $tags)
     {
         $this->tags = $tags;
     }
}



              The ArrayCollection is specific to Doctrine and is basically the same as using an array (but it
              must be an ArrayCollection) if you're using Doctrine.


Now, create a Tag class. As you saw above, a Task can have many Tag objects:

// src/Acme/TaskBundle/Entity/Tag.php                                                                                       Listing
                                                                                                                             44-2
namespace AcmeTaskBundleEntity;

class Tag
{
    public $name;
}



              The name property is public here, but it can just as easily be protected or private (but then it would
              need getName and setName methods).


Now let's get to the forms. Create a form class so that a Tag object can be modified by the user:

// src/Acme/TaskBundle/Form/Type/TagType.php                                                                                Listing
                                                                                                                             44-3
namespace AcmeTaskBundleFormType;

use SymfonyComponentFormAbstractType;
use SymfonyComponentFormFormBuilder;

class TagType extends AbstractType
{
    public function buildForm(FormBuilder $builder, array $options)
    {
        $builder->add('name');
    }

     public function getDefaultOptions(array $options)
     {
         return array(
             'data_class' => 'AcmeTaskBundleEntityTag',
         );
     }

     public function getName()
     {
         return 'tag';


PDF brought to you by                                                Chapter 44: How to Embed a Collection of Forms | 315
generated on June 20, 2012
}
          }

          With this, we have enough to render a tag form by itself. But since the end goal is to allow the tags of a
          Task to be modified right inside the task form itself, create a form for the Task class.
          Notice that we embed a collection of TagType forms using the collection field type:

Listing   // src/Acme/TaskBundle/Form/Type/TaskType.php
 44-4
          namespace AcmeTaskBundleFormType;

          use SymfonyComponentFormAbstractType;
          use SymfonyComponentFormFormBuilder;

          class TaskType extends AbstractType
          {
              public function buildForm(FormBuilder $builder, array $options)
              {
                  $builder->add('description');

                     $builder->add('tags', 'collection', array('type' => new TagType()));
                }

                public function getDefaultOptions(array $options)
                {
                    return array(
                        'data_class' => 'AcmeTaskBundleEntityTask',
                    );
                }

                public function getName()
                {
                    return 'task';
                }
          }

          In your controller, you'll now initialize a new instance of TaskType:

Listing   // src/Acme/TaskBundle/Controller/TaskController.php
 44-5
          namespace AcmeTaskBundleController;

          use   AcmeTaskBundleEntityTask;
          use   AcmeTaskBundleEntityTag;
          use   AcmeTaskBundleFormTypeTaskType;
          use   SymfonyComponentHttpFoundationRequest;
          use   SymfonyBundleFrameworkBundleControllerController;

          class TaskController extends Controller
          {
              public function newAction(Request $request)
              {
                  $task = new Task();

                     // dummy code - this is here just so that the Task has some tags
                     // otherwise, this isn't an interesting example
                     $tag1 = new Tag();
                     $tag1->name = 'tag1';
                     $task->getTags()->add($tag1);
                     $tag2 = new Tag();
                     $tag2->name = 'tag2';
                     $task->getTags()->add($tag2);


          PDF brought to you by                                          Chapter 44: How to Embed a Collection of Forms | 316
          generated on June 20, 2012
// end dummy code

           $form = $this->createForm(new TaskType(), $task);

           // process the form on POST
           if ('POST' === $request->getMethod()) {
               $form->bindRequest($request);
               if ($form->isValid()) {
                   // maybe do some form processing, like saving the Task and Tag objects
               }
           }

           return $this->render('AcmeTaskBundle:Task:new.html.twig', array(
               'form' => $form->createView(),
           ));
     }
}

The corresponding template is now able to render both the description field for the task form as well
as all the TagType forms for any tags that are already related to this Task. In the above controller, I added
some dummy code so that you can see this in action (since a Task has zero tags when first created).

{# src/Acme/TaskBundle/Resources/views/Task/new.html.twig #}                                                               Listing
                                                                                                                            44-6
{# ... #}

<form action="..." method="POST" {{ form_enctype(form) }}>
    {# render the task's only field: description #}
    {{ form_row(form.description) }}

     <h3>Tags</h3>
     <ul class="tags">
         {# iterate over each existing tag and render its only field: name #}
         {% for tag in form.tags %}
             <li>{{ form_row(tag.name) }}</li>
         {% endfor %}
     </ul>

    {{ form_rest(form) }}
    {# ... #}
</form>

When the user submits the form, the submitted data for the Tags fields are used to construct an
ArrayCollection of Tag objects, which is then set on the tag field of the Task instance.
The Tags collection is accessible naturally via $task->getTags() and can be persisted to the database or
used however you need.
So far, this works great, but this doesn't allow you to dynamically add new tags or delete existing tags.
So, while editing existing tags will work great, your user can't actually add any new tags yet.


              In this entry, we embed only one collection, but you are not limited to this. You can also embed
              nested collection as many level down as you like. But if you use Xdebug in your development setup,
              you may receive a Maximum function nesting level of '100' reached, aborting! error.
              This is due to the xdebug.max_nesting_level PHP setting, which defaults to 100.
              This directive limits recursion to 100 calls which may not be enough for rendering the form in
              the template if you render the whole form at once (e.g form_widget(form)). To fix this you can
              set this directive to a higher value (either via a PHP ini file or via ini_set1, for example in app/
              autoload.php) or render each form field by hand using form_row.



PDF brought to you by                                               Chapter 44: How to Embed a Collection of Forms | 317
generated on June 20, 2012
Allowing "new" tags with the "prototype"
          Allowing the user to dynamically add new tags means that we'll need to use some JavaScript. Previously
          we added two tags to our form in the controller. Now we need to let the user add as many tag forms as
          he needs directly in the browser. This will be done through a bit of JavaScript.
          The first thing we need to do is to let the form collection know that it will receive an unknown number
          of tags. So far we've added two tags and the form type expects to receive exactly two, otherwise an error
          will be thrown: This form should not contain extra fields. To make this flexible, we add the
          allow_add option to our collection field:

Listing   // src/Acme/TaskBundle/Form/Type/TaskType.php
 44-7
          // ...

          public function buildForm(FormBuilder $builder, array $options)
          {
              $builder->add('description');

               $builder->add('tags', 'collection', array(
                   'type' => new TagType(),
                   'allow_add' => true,
                   'by_reference' => false,
               ));
          }

          Note that we also added 'by_reference' => false. Normally, the form framework would modify the
          tags on a Task object without actually ever calling setTags. By setting by_reference to false, setTags will be
          called. This will be important later as you'll see.
          In addition to telling the field to accept any number of submitted objects, the allow_add also makes a
          "prototype" variable available to you. This "prototype" is a little "template" that contains all the HTML
          to be able to render any new "tag" forms. To render it, make the following change to your template:

Listing   <ul class="tags" data-prototype="{{ form_widget(form.tags.get('prototype')) | e }}">
 44-8
              ...
          </ul>



                          If you render your whole "tags" sub-form at once (e.g. form_row(form.tags)), then the prototype
                          is automatically available on the outer div as the data-prototype attribute, similar to what you
                          see above.



                          The form.tags.get('prototype') is form element that looks and feels just like the individual
                          form_widget(tag) elements inside our for loop. This means that you can call form_widget,
                          form_row, or form_label on it. You could even choose to render only one of its fields (e.g. the
                          name field):

                Listing   {{ form_widget(form.tags.get('prototype').name) | e }}
                 44-9




          On the rendered page, the result will look something like this:

Listing   <ul class="tags" data-prototype="&lt;div&gt;&lt;label class=&quot;
44-10
          required&quot;&gt;$$name$$&lt;/label&gt;&lt;div
          id=&quot;task_tags_$$name$$&quot;&gt;&lt;div&gt;&lt;label


          1. http://php.net/manual/en/function.ini-set.php


          PDF brought to you by                                               Chapter 44: How to Embed a Collection of Forms | 318
          generated on June 20, 2012
for=&quot;task_tags_$$name$$_name&quot; class=&quot; required&quot;&gt;Name&lt;/
label&gt;&lt;input type=&quot;text&quot; id=&quot;task_tags_$$name$$_name&quot;
name=&quot;task[tags][$$name$$][name]&quot; required=&quot;required&quot;
maxlength=&quot;255&quot; /&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;">

The goal of this section will be to use JavaScript to read this attribute and dynamically add new tag forms
when the user clicks a "Add a tag" link. To make things simple, we'll use jQuery and assume you have it
included somewhere on your page.
Add a script tag somewhere on your page so we can start writing some JavaScript.
First, add a link to the bottom of the "tags" list via JavaScript. Second, bind to the "click" event of that
link so we can add a new tag form (addTagForm will be show next):

// Get the div that holds the collection of tags                                                                      Listing
                                                                                                                      44-11
var collectionHolder = $('ul.tags');

// setup an "add a tag" link
var $addTagLink = $('<a href="#" class="add_tag_link">Add a tag</a>');
var $newLinkLi = $('<li></li>').append($addTagLink);

jQuery(document).ready(function() {
    // add the "add a tag" anchor and li to the tags ul
    collectionHolder.append($newLinkLi);

      $addTagLink.on('click', function(e) {
          // prevent the link from creating a "#" on the URL
          e.preventDefault();

            // add a new tag form (see next code block)
            addTagForm(collectionHolder, $newLinkLi);
      });
});

The addTagForm function's job will be to use the data-prototype attribute to dynamically add a new
form when this link is clicked. The data-prototype HTML contains the tag text input element with
a name of task[tags][$$name$$][name] and id of task_tags_$$name$$_name. The $$name is a little
"placeholder", which we'll replace with a unique, incrementing number (e.g. task[tags][3][name]).
The actual code needed to make this all work can vary quite a bit, but here's one example:

function addTagForm(collectionHolder, $newLinkLi) {                                                                   Listing
                                                                                                                      44-12
    // Get the data-prototype we explained earlier
    var prototype = collectionHolder.attr('data-prototype');

      // Replace '$$name$$' in the prototype's HTML to
      // instead be a number based on the current collection's length.
      var newForm = prototype.replace(/$$name$$/g, collectionHolder.children().length);

      // Display the form in the page in an li, before the "Add a tag" link li
      var $newFormLi = $('<li></li>').append(newForm);
      $newLinkLi.before($newFormLi);
}

Now, each time a user clicks the Add a tag link, a new sub form will appear on the page. When we
submit, any new tag forms will be converted into new Tag objects and added to the tags property of the
Task object.




PDF brought to you by                                          Chapter 44: How to Embed a Collection of Forms | 319
generated on June 20, 2012
Doctrine: Cascading Relations and saving the "Inverse" side
                 To get the new tags to save in Doctrine, you need to consider a couple more things. First, unless
                 you iterate over all of the new Tag objects and call $em->persist($tag) on each, you'll receive an
                 error from Doctrine:

                      A new entity was found through the relationship 'AcmeTaskBundleEntityTask#tags'
                      that was not configured to cascade persist operations for entity...

                 To fix this, you may choose to "cascade" the persist operation automatically from the Task object
                 to any related tags. To do this, add the cascade option to your ManyToMany metadata:

       Listing   /**
       44-13
                  * @ORMManyToMany(targetEntity="Tag", cascade={"persist"})
                  */
                 protected $tags;

                 A second potential issue deals with the Owning Side and Inverse Side2 of Doctrine relationships. In
                 this example, if the "owning" side of the relationship is "Task", then persistence will work fine as
                 the tags are properly added to the Task. However, if the owning side is on "Tag", then you'll need
                 to do a little bit more work to ensure that the correct side of the relationship is modified.
                 The trick is to make sure that the single "Task" is set on each "Tag". One easy way to do this is to
                 add some extra logic to setTags(), which is called by the form framework since by_reference is set
                 to false:

       Listing   // src/Acme/TaskBundle/Entity/Task.php
       44-14
                 // ...

                 public function setTags(ArrayCollection $tags)
                 {
                     foreach ($tags as $tag) {
                         $tag->addTask($this);
                     }

                     $this->tags = $tags;
                 }

                 Inside Tag, just make sure you have an addTask method:

       Listing   // src/Acme/TaskBundle/Entity/Tag.php
       44-15
                 // ...

                 public function addTask(Task $task)
                 {
                     if (!$this->tasks->contains($task)) {
                         $this->tasks->add($task);
                     }
                 }

                 If you have a OneToMany relationship, then the workaround is similar, except that you can simply
                 call setTask from inside setTags.




2. http://docs.doctrine-project.org/en/latest/reference/unitofwork-associations.html


PDF brought to you by                                                          Chapter 44: How to Embed a Collection of Forms | 320
generated on June 20, 2012
Allowing tags to be removed
The next step is to allow the deletion of a particular item in the collection. The solution is similar to
allowing tags to be added.
Start by adding the allow_delete option in the form Type:

// src/Acme/TaskBundle/Form/Type/TaskType.php                                                                        Listing
                                                                                                                     44-16
// ...

public function buildForm(FormBuilder $builder, array $options)
{
    $builder->add('description');

      $builder->add('tags', 'collection', array(
          'type' => new TagType(),
          'allow_add' => true,
          'allow_delete' => true,
          'by_reference' => false,
      ));
}


Templates Modifications
The allow_delete option has one consequence: if an item of a collection isn't sent on submission, the
related data is removed from the collection on the server. The solution is thus to remove the form element
from the DOM.
First, add a "delete this tag" link to each tag form:

jQuery(document).ready(function() {                                                                                  Listing
                                                                                                                     44-17
    // add a delete link to all of the existing tag form li elements
    collectionHolder.find('li').each(function() {
        addTagFormDeleteLink($(this));
    });

      // ... the rest of the block from above
});

function addTagForm() {
    // ...

      // add a delete link to the new form
      addTagFormDeleteLink($newFormLi);
}

The addTagFormDeleteLink function will look something like this:

function addTagFormDeleteLink($tagFormLi) {                                                                          Listing
                                                                                                                     44-18
    var $removeFormA = $('<a href="#">delete this tag</a>');
    $tagFormLi.append($removeFormA);

      $removeFormA.on('click', function(e) {
          // prevent the link from creating a "#" on the URL
          e.preventDefault();

            // remove the li for the tag form
            $tagFormLi.remove();
      });
}


PDF brought to you by                                         Chapter 44: How to Embed a Collection of Forms | 321
generated on June 20, 2012
When a tag form is removed from the DOM and submitted, the removed Tag object will not be included
in the collection passed to setTags. Depending on your persistence layer, this may or may not be enough
to actually remove the relationship between the removed Tag and Task object.




PDF brought to you by                                        Chapter 44: How to Embed a Collection of Forms | 322
generated on June 20, 2012
Doctrine: Ensuring the database persistence
              When removing objects in this way, you may need to do a little bit more work to ensure that the
              relationship between the Task and the removed Tag is properly removed.
              In Doctrine, you have two side of the relationship: the owning side and the inverse side. Normally
              in this case you'll have a ManyToMany relation and the deleted tags will disappear and persist
              correctly (adding new tags also works effortlessly).
              But if you have an OneToMany relation or a ManyToMany with a mappedBy on the Task entity
              (meaning Task is the "inverse" side), you'll need to do more work for the removed tags to persist
              correctly.
              In this case, you can modify the controller to remove the relationship on the removed tag. This
              assumes that you have some editAction which is handling the "update" of your Task:

              // src/Acme/TaskBundle/Controller/TaskController.php                                                        Listing
                                                                                                                          44-19
              // ...

              public function editAction($id, Request $request)
              {
                  $em = $this->getDoctrine()->getEntityManager();
                  $task = $em->getRepository('AcmeTaskBundle:Task')->find($id);

                   if (!$task) {
                       throw $this->createNotFoundException('No task found for is '.$id);
                   }

                   // Create an array of the current Tag objects in the database
                   foreach ($task->getTags() as $tag) $originalTags[] = $tag;

                   $editForm = $this->createForm(new TaskType(), $task);

                       if ('POST' === $request->getMethod()) {
                        $editForm->bindRequest($this->getRequest());

                        if ($editForm->isValid()) {

                             // filter $originalTags to contain tags no longer present
                             foreach ($task->getTags() as $tag) {
                                 foreach ($originalTags as $key => $toDel) {
                                     if ($toDel->getId() === $tag->getId()) {
                                         unset($originalTags[$key]);
                                     }
                                 }
                             }

                             // remove the relationship between the tag and the Task
                             foreach ($originalTags as $tag) {
                                 // remove the Task from the Tag
                                 $tag->getTasks()->removeElement($task);

                                 // if it were a ManyToOne relationship, remove the relationship like this
                                 // $tag->setTask(null);

                                 $em->persist($tag);

                                 // if you wanted to delete the Tag entirely, you can also do that
                                 // $em->remove($tag);
                             }

                             $em->persist($task);




PDF brought to you by                                              Chapter 44: How to Embed a Collection of Forms | 323
generated on June 20, 2012
$em->flush();

                             // redirect back to some edit page
                             return $this->redirect($this->generateUrl('task_edit', array('id' => $id)));
                        }
                   }

                   // render some form template
              }

              As you can see, adding and removing the elements correctly can be tricky. Unless you have a
              ManyToMany relationship where Task is the "owning" side, you'll need to do extra work to make
              sure that the relationship is properly updated (whether you're adding new tags or removing existing
              tags) on each Tag object itself.




PDF brought to you by                                              Chapter 44: How to Embed a Collection of Forms | 324
generated on June 20, 2012
Chapter 45
         How to Create a Custom Form Field Type

Symfony comes with a bunch of core field types available for building forms. However there are situations
where we want to create a custom form field type for a specific purpose. This recipe assumes we need a
field definition that holds a person's gender, based on the existing choice field. This section explains how
the field is defined, how we can customize its layout and finally, how we can register it for use in our
application.



Defining the Field Type
In order to create the custom field type, first we have to create the class representing the field. In our
situation the class holding the field type will be called GenderType and the file will be stored in the default
location for form fields, which is <BundleName>FormType. Make sure the field extends AbstractType1:

# src/Acme/DemoBundle/Form/Type/GenderType.php                                                                                      Listing
                                                                                                                                     45-1
namespace AcmeDemoBundleFormType;

use SymfonyComponentFormAbstractType;
use SymfonyComponentFormFormBuilder;

class GenderType extends AbstractType
{
    public function getDefaultOptions(array $options)
    {
        return array(
            'choices' => array(
                'm' => 'Male',
                'f' => 'Female',
            )
        );
    }

     public function getParent(array $options)
     {
         return 'choice';

1. http://api.symfony.com/2.0/Symfony/Component/Form/AbstractType.html


PDF brought to you by                                                    Chapter 45: How to Create a Custom Form Field Type | 325
generated on June 20, 2012
}

                public function getName()
                {
                    return 'gender';
                }
          }



                        The location of this file is not important - the FormType directory is just a convention.



          Here, the return value of the getParent function indicates that we're extending the choice field type.
          This means that, by default, we inherit all of the logic and rendering of that field type. To see some of the
          logic, check out the ChoiceType2 class. There are three methods that are particularly important:

              • buildForm() - Each field type has a buildForm method, which is where you configure and
                build any field(s). Notice that this is the same method you use to setup your forms, and it
                works the same here.
              • buildView() - This method is used to set any extra variables you'll need when rendering your
                field in a template. For example, in ChoiceType3, a multiple variable is set and used in the
                template to set (or not set) the multiple attribute on the select field. See Creating a Template
                for the Field for more details.
              • getDefaultOptions() - This defines options for your form type that can be used in
                buildForm() and buildView(). There are a lot of options common to all fields (see
                FieldType4), but you can create any others that you need here.


                        If you're creating a field that consists of many fields, then be sure to set your "parent" type as form
                        or something that extends form. Also, if you need to modify the "view" of any of your child types
                        from your parent type, use the buildViewBottomUp() method.

          The getName() method returns an identifier which should be unique in your application. This is used in
          various places, such as when customizing how your form type will be rendered.
          The goal of our field was to extend the choice type to enable selection of a gender. This is achieved by
          fixing the choices to a list of possible genders.



          Creating a Template for the Field
          Each field type is rendered by a template fragment, which is determined in part by the value of your
          getName() method. For more information, see What are Form Themes?.
          In this case, since our parent field is choice, we don't need to do any work as our custom field type
          will automatically be rendered like a choice type. But for the sake of this example, let's suppose that
          when our field is "expanded" (i.e. radio buttons or checkboxes, instead of a select field), we want to
          always render it in a ul element. In your form theme template (see above link for details), create a
          gender_widget block to handle this:

Listing   {# src/Acme/DemoBundle/Resources/views/Form/fields.html.twig #}
 45-2

          {% block gender_widget %}

          2. https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php
          3. https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php
          4. https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Extension/Core/Type/FieldType.php


          PDF brought to you by                                                      Chapter 45: How to Create a Custom Form Field Type | 326
          generated on June 20, 2012
{% spaceless %}
    {% if expanded %}
        <ul {{ block('widget_container_attributes') }}>
        {% for child in form %}
            <li>
                 {{ form_widget(child) }}
                 {{ form_label(child) }}
            </li>
        {% endfor %}
        </ul>
    {% else %}
        {# just let the choice widget render the select tag #}
        {{ block('choice_widget') }}
    {% endif %}
{% endspaceless %}
{% endblock %}



              Make sure the correct widget prefix is used. In this example the name should be gender_widget,
              according to the value returned by getName. Further, the main config file should point to the
              custom form template so that it's used when rendering all forms.

              # app/config/config.yml                                                                                   Listing
                                                                                                                         45-3

              twig:
                  form:
                      resources:
                          - 'AcmeDemoBundle:Form:fields.html.twig'




Using the Field Type
You can now use your custom field type immediately, simply by creating a new instance of the type in
one of your forms:

// src/Acme/DemoBundle/Form/Type/AuthorType.php                                                                         Listing
                                                                                                                         45-4
namespace AcmeDemoBundleFormType;

use SymfonyComponentFormAbstractType;
use SymfonyComponentFormFormBuilder;

class AuthorType extends AbstractType
{
    public function buildForm(FormBuilder $builder, array $options)
    {
        $builder->add('gender_code', new GenderType(), array(
            'empty_value' => 'Choose a gender',
        ));
    }
}

But this only works because the GenderType() is very simple. What if the gender codes were stored
in configuration or in a database? The next section explains how more complex field types solve this
problem.




PDF brought to you by                                        Chapter 45: How to Create a Custom Form Field Type | 327
generated on June 20, 2012
Creating your Field Type as a Service
          So far, this entry has assumed that you have a very simple custom field type. But if you need access to
          configuration, a database connection, or some other service, then you'll want to register your custom type
          as a service. For example, suppose that we're storing the gender parameters in configuration:

Listing   # app/config/config.yml
 45-5
          parameters:
              genders:
                  m: Male
                  f: Female

          To use the parameter, we'll define our custom field type as a service, injecting the genders parameter
          value as the first argument to its to-be-created __construct function:

Listing   # src/Acme/DemoBundle/Resources/config/services.yml
 45-6
          services:
              form.type.gender:
                  class: AcmeDemoBundleFormTypeGenderType
                  arguments:
                      - "%genders%"
                  tags:
                      - { name: form.type, alias: gender }



                        Make sure the services file is being imported. See Importing Configuration with imports for details.



          Be sure that the alias attribute of the tag corresponds with the value returned by the getName method
          defined earlier. We'll see the importance of this in a moment when we use the custom field type. But first,
          add a __construct argument to GenderType, which receives the gender configuration:

Listing   # src/Acme/DemoBundle/Form/Type/GenderType.php
 45-7
          namespace AcmeDemoBundleFormType;
          // ...

          class GenderType extends AbstractType
          {
              private $genderChoices;

               public function __construct(array $genderChoices)
               {
                   $this->genderChoices = $genderChoices;
               }

               public function getDefaultOptions(array $options)
               {
                   return array(
                       'choices' => $this->genderChoices,
                   );
               }

               // ...
          }

          Great! The GenderType is now fueled by the configuration parameters and registered as a service. And
          because we used the form.type alias in its configuration, using the field is now much easier:


          PDF brought to you by                                           Chapter 45: How to Create a Custom Form Field Type | 328
          generated on June 20, 2012
// src/Acme/DemoBundle/Form/Type/AuthorType.php                                                                        Listing
                                                                                                                        45-8
namespace AcmeDemoBundleFormType;
// ...

class AuthorType extends AbstractType
{
    public function buildForm(FormBuilder $builder, array $options)
    {
        $builder->add('gender_code', 'gender', array(
            'empty_value' => 'Choose a gender',
        ));
    }
}

Notice that instead of instantiating a new instance, we can just refer to it by the alias used in our service
configuration, gender. Have fun!




PDF brought to you by                                       Chapter 45: How to Create a Custom Form Field Type | 329
generated on June 20, 2012
Chapter 46
                   How to use the Virtual Form Field Option

          The virtual form field option can be very useful when you have some duplicated fields in different
          entities.
          For example, imagine you have two entities, a Company and a Customer:

Listing   // src/Acme/HelloBundle/Entity/Company.php
 46-1
          namespace AcmeHelloBundleEntity;

          class Company
          {
              private $name;
              private $website;

               private    $address;
               private    $zipcode;
               private    $city;
               private    $country;
          }

Listing   // src/Acme/HelloBundle/Entity/Company.php
 46-2
          namespace AcmeHelloBundleEntity;

          class Customer
          {
              private $firstName;
              private $lastName;

               private    $address;
               private    $zipcode;
               private    $city;
               private    $country;
          }

          Like you can see, each entity shares a few of the same fields: address, zipcode, city, country.
          Now, you want to build two forms: one for a Company and the second for a Customer.
          Start by creating a very simple CompanyType and CustomerType:


          PDF brought to you by                                     Chapter 46: How to use the Virtual Form Field Option | 330
          generated on June 20, 2012
// src/Acme/HelloBundle/Form/Type/CompanyType.php                                                                       Listing
                                                                                                                         46-3
namespace AcmeHelloBundleFormType;

class CompanyType extends AbstractType
{
    public function buildForm(FormBuilder $builder, array $options)
    {
        $builder
            ->add('name', 'text')
            ->add('website', 'text')
        ;
    }
}

// src/Acme/HelloBundle/Form/Type/CustomerType.php                                                                      Listing
                                                                                                                         46-4
namespace AcmeHelloBundleFormType;

class CustomerType extends AbstractType
{
    public function buildForm(FormBuilder $builder, array $options)
    {
        $builder
            ->add('firstName', 'text')
            ->add('lastName', 'text')
        ;
    }
}

Now, we have to deal with the four duplicated fields. Here is a (simple) location form type:

// src/Acme/HelloBundle/Form/Type/LocationType.php                                                                      Listing
                                                                                                                         46-5
namespace AcmeHelloBundleFormType;

class LocationType extends AbstractType
{
    public function buildForm(FormBuilder $builder, array $options)
    {
        $builder
            ->add('address', 'textarea')
            ->add('zipcode', 'text')
            ->add('city', 'text')
            ->add('country', 'text')
        ;
    }

     public function getName()
     {
         return 'location';
     }
}

We don't actually have a location field in each of our entities, so we can't directly link our LocationType
to our CompanyType or CustomerType. But we absolutely want to have a dedicated form type to deal with
location (remember, DRY!).
The virtual form field option is the solution.
We can set the option 'virtual' => true in the getDefaultOptions method of LocationType and
directly start using it in the two original form types.
Look at the result:



PDF brought to you by                                      Chapter 46: How to use the Virtual Form Field Option | 331
generated on June 20, 2012
Listing   // CompanyType
 46-6
          public function buildForm(FormBuilder $builder, array $options)
          {
              $builder->add('foo', new LocationType());
          }

Listing   // CustomerType
 46-7
          public function buildForm(FormBuilder $builder, array $options)
          {
              $builder->add('bar', new LocationType());
          }

          With the virtual option set to false (default behavior), the Form Component expect each underlying
          object to have a foo (or bar) property that is either some object or array which contains the four location
          fields. Of course, we don't have this object/array in our entities and we don't want it!
          With the virtual option set to true, the Form component skips the foo (or bar) property, and instead
          "gets" and "sets" the 4 location fields directly on the underlying object!


                        Instead of setting the virtual option inside LocationType, you can (just like with any options)
                        also pass it in as an array option to the third argument of $builder->add().




          PDF brought to you by                                        Chapter 46: How to use the Virtual Form Field Option | 332
          generated on June 20, 2012
Chapter 47
  How to create a Custom Validation Constraint

You can create a custom constraint by extending the base constraint class, Constraint1. As an example
we're going to create a simple validator that checks if a string contains only alphanumeric characters.



Creating Constraint class
First you need to create a Constraint class and extend Constraint2:

namespace AcmeDemoBundleValidatorConstraints;                                                                                      Listing
                                                                                                                                       47-1

use SymfonyComponentValidatorConstraint;

/**
  * @Annotation
  */
class ContainsAlphanumeric extends Constraint
{
     public $message = 'The string "%string%" contains an illegal character: it can only
contain letters or numbers.';
}



              The @Annotation annotation is necessary for this new constraint in order to make it available for
              use in classes via annotations. Options for your constraint are represented as public properties on
              the constraint class.




1. http://api.symfony.com/2.0/Symfony/Component/Validator/Constraint.html
2. http://api.symfony.com/2.0/Symfony/Component/Validator/Constraint.html


PDF brought to you by                                                Chapter 47: How to create a Custom Validation Constraint | 333
generated on June 20, 2012
Creating the Validator itself
          As you can see, a constraint class is fairly minimal. The actual validation is performed by a another
          "constraint validator" class. The constraint validator class is specified by the constraint's validatedBy()
          method, which includes some simple default logic:

Listing   // in the base SymfonyComponentValidatorConstraint class
 47-2
          public function validatedBy()
          {
              return get_class($this).'Validator';
          }

          In other words, if you create a custom Constraint (e.g. MyConstraint), Symfony2 will automatically
          look for another class, MyConstraintValidator when actually performing the validation.
          The validator class is also simple, and only has one required method: isValid:

Listing   namespace AcmeDemoBundleValidatorConstraints;
 47-3

          use SymfonyComponentValidatorConstraint;
          use SymfonyComponentValidatorConstraintValidator;

          class ContainsAlphanumericValidator extends ConstraintValidator
          {
              public function isValid($value, Constraint $constraint)
              {
                  if (!preg_match('/^[a-zA-Za0-9]+$/', $value, $matches)) {
                      $this->setMessage($constraint->message, array('%string%' => $value));

                          return false;
                     }

                     return true;
               }
          }



                         Don't forget to call setMessage to construct an error message when the value is invalid.




          Using the new Validator
          Using custom validators is very easy, just as the ones provided by Symfony2 itself:

Listing   # src/Acme/BlogBundle/Resources/config/validation.yml
 47-4
          AcmeDemoBundleEntityAcmeEntity:
              properties:
                  name:
                      - NotBlank: ~
                      - AcmeDemoBundleValidatorConstraintsContainsAlphanumeric: ~

          If your constraint contains options, then they should be public properties on the custom Constraint class
          you created earlier. These options can be configured like options on core Symfony constraints.




          PDF brought to you by                                        Chapter 47: How to create a Custom Validation Constraint | 334
          generated on June 20, 2012
Constraint Validators with Dependencies
If your constraint validator has dependencies, such as a database connection, it will need to be configured
as a service in the dependency injection container. This service must include the
validator.constraint_validator tag and an alias attribute:

services:                                                                                                                Listing
                                                                                                                          47-5
    validator.unique.your_validator_name:
        class: FullyQualifiedValidatorClassName
        tags:
            - { name: validator.constraint_validator, alias: alias_name }

Your constraint class should now use this alias to reference the appropriate validator:

public function validatedBy()                                                                                            Listing
                                                                                                                          47-6
{
    return 'alias_name';
}

As mentioned above, Symfony2 will automatically look for a class named after the constraint, with
Validator appended. If your constraint validator is defined as a service, it's important that you override
the validatedBy() method to return the alias used when defining your service, otherwise Symfony2
won't use the constraint validator service, and will instantiate the class instead, without any dependencies
injected.

Class Constraint Validator
Beside validating a class property, a constraint can have a class scope by providing a target:

public function getTargets()                                                                                             Listing
                                                                                                                          47-7
{
    return self::CLASS_CONSTRAINT;
}

With this, the validator isValid() method gets an object as its first argument:

class ProtocolClassValidator extends ConstraintValidator                                                                 Listing
                                                                                                                          47-8
{
    public function isValid($protocol, Constraint $constraint)
    {
        if ($protocol->getFoo() != $protocol->getBar()) {

                $propertyPath = $this->context->getPropertyPath() . 'foo';
                $this->context->setPropertyPath($propertyPath);
                $this->context->addViolation($constraint->getMessage(), array(), null);

                return false;
           }

           return true;
     }
}




PDF brought to you by                                   Chapter 47: How to create a Custom Validation Constraint | 335
generated on June 20, 2012
Chapter 48
              How to Master and Create new Environments

          Every application is the combination of code and a set of configuration that dictates how that code should
          function. The configuration may define the database being used, whether or not something should be
          cached, or how verbose logging should be. In Symfony2, the idea of "environments" is the idea that the
          same codebase can be run using multiple different configurations. For example, the dev environment
          should use configuration that makes development easy and friendly, while the prod environment should
          use a set of configuration optimized for speed.



          Different Environments, Different Configuration Files
          A typical Symfony2 application begins with three environments: dev, prod, and test. As discussed, each
          "environment" simply represents a way to execute the same codebase with different configuration. It
          should be no surprise then that each environment loads its own individual configuration file. If you're
          using the YAML configuration format, the following files are used:

              • for the dev environment: app/config/config_dev.yml
              • for the prod environment: app/config/config_prod.yml
              • for the test environment: app/config/config_test.yml

          This works via a simple standard that's used by default inside the AppKernel class:

Listing   // app/AppKernel.php
 48-1
          // ...

          class AppKernel extends Kernel
          {
              // ...

               public function registerContainerConfiguration(LoaderInterface $loader)
               {
                   $loader->load(__DIR__.'/config/config_'.$this->getEnvironment().'.yml');
               }
          }




          PDF brought to you by                                   Chapter 48: How to Master and Create new Environments | 336
          generated on June 20, 2012
As you can see, when Symfony2 is loaded, it uses the given environment to determine which
configuration file to load. This accomplishes the goal of multiple environments in an elegant, powerful
and transparent way.
Of course, in reality, each environment differs only somewhat from others. Generally, all environments
will share a large base of common configuration. Opening the "dev" configuration file, you can see how
this is accomplished easily and transparently:

imports:                                                                                                               Listing
                                                                                                                        48-2
    - { resource: config.yml }

# ...

To share common configuration, each environment's configuration file simply first imports from a central
configuration file (config.yml). The remainder of the file can then deviate from the default configuration
by overriding individual parameters. For example, by default, the web_profiler toolbar is disabled.
However, in the dev environment, the toolbar is activated by modifying the default value in the dev
configuration file:

# app/config/config_dev.yml                                                                                            Listing
                                                                                                                        48-3
imports:
    - { resource: config.yml }

web_profiler:
    toolbar: true
    # ...



Executing an Application in Different Environments
To execute the application in each environment, load up the application using either the app.php (for the
prod environment) or the app_dev.php (for the dev environment) front controller:

http://localhost/app.php     -> *prod* environment                                                                     Listing
                                                                                                                        48-4
http://localhost/app_dev.php -> *dev* environment



               The given URLs assume that your web server is configured to use the web/ directory of the
               application as its root. Read more in Installing Symfony2.


If you open up one of these files, you'll quickly see that the environment used by each is explicitly set:


 1    <?php
     Listing                                                         Listing
      48-5                                                            48-6
 2
 3    require_once __DIR__.'/../app/bootstrap_cache.php';
 4    require_once __DIR__.'/../app/AppCache.php';
 5
 6    use SymfonyComponentHttpFoundationRequest;
 7
 8    $kernel = new AppCache(new AppKernel('prod', false));
 9    $kernel->handle(Request::createFromGlobals())->send();



As you can see, the prod key specifies that this environment will run in the prod environment. A
Symfony2 application can be executed in any environment by using this code and changing the
environment string.

PDF brought to you by                                    Chapter 48: How to Master and Create new Environments | 337
generated on June 20, 2012
The test environment is used when writing functional tests and is not accessible in the browser
                          directly via a front controller. In other words, unlike the other environments, there is no
                          app_test.php front controller file.



                          Debug Mode
                          Important, but unrelated to the topic of environments is the false key on line 8 of the front
                          controller above. This specifies whether or not the application should run in "debug mode".
                          Regardless of the environment, a Symfony2 application can be run with debug mode set to true
                          or false. This affects many things in the application, such as whether or not errors should be
                          displayed or if cache files are dynamically rebuilt on each request. Though not a requirement,
                          debug mode is generally set to true for the dev and test environments and false for the prod
                          environment.
                          Internally, the value of the debug mode becomes the kernel.debug parameter used inside the
                          service container. If you look inside the application configuration file, you'll see the parameter used,
                          for example, to turn logging on or off when using the Doctrine DBAL:

                Listing   doctrine:
                 48-7
                             dbal:
                                 logging:    "%kernel.debug%"
                                 # ...




          Creating a New Environment
          By default, a Symfony2 application has three environments that handle most cases. Of course, since an
          environment is nothing more than a string that corresponds to a set of configuration, creating a new
          environment is quite easy.
          Suppose, for example, that before deployment, you need to benchmark your application. One way
          to benchmark the application is to use near-production settings, but with Symfony2's web_profiler
          enabled. This allows Symfony2 to record information about your application while benchmarking.
          The best way to accomplish this is via a new environment called, for example, benchmark. Start by
          creating a new configuration file:

Listing   # app/config/config_benchmark.yml
 48-8

          imports:
              - { resource: config_prod.yml }

          framework:
              profiler: { only_exceptions: false }

          And with this simple addition, the application now supports a new environment called benchmark.
          This new configuration file imports the configuration from the prod environment and modifies it. This
          guarantees that the new environment is identical to the prod environment, except for any changes
          explicitly made here.
          Because you'll want this environment to be accessible via a browser, you should also create a front
          controller for it. Copy the web/app.php file to web/app_benchmark.php and edit the environment to be
          benchmark:

Listing
 48-9



          PDF brought to you by                                            Chapter 48: How to Master and Create new Environments | 338
          generated on June 20, 2012
<?php

require_once __DIR__.'/../app/bootstrap.php';
require_once __DIR__.'/../app/AppKernel.php';

use SymfonyComponentHttpFoundationRequest;

$kernel = new AppKernel('benchmark', false);
$kernel->handle(Request::createFromGlobals())->send();

The new environment is now accessible via:

http://localhost/app_benchmark.php                                                                                            Listing
                                                                                                                              48-10




              Some environments, like the dev environment, are never meant to be accessed on any deployed
              server by the general public. This is because certain environments, for debugging purposes, may
              give too much information about the application or underlying infrastructure. To be sure these
              environments aren't accessible, the front controller is usually protected from external IP addresses
              via the following code at the top of the controller:

                    if (!in_array(@$_SERVER['REMOTE_ADDR'], array('127.0.0.1', '::1'))) {                           Listing
                                                                                                                    48-11
                        die('You are not allowed to access this file. Check '.basename(__FILE__).'
                    for more information.');
                    }




Environments and the Cache Directory
Symfony2 takes advantage of caching in many ways: the application configuration, routing configuration,
Twig templates and more are cached to PHP objects stored in files on the filesystem.
By default, these cached files are largely stored in the app/cache directory. However, each environment
caches its own set of files:

app/cache/dev - cache directory for the *dev* environment                                                                     Listing
                                                                                                                              48-12
app/cache/prod - cache directory for the *prod* environment

Sometimes, when debugging, it may be helpful to inspect a cached file to understand how something
is working. When doing so, remember to look in the directory of the environment you're using (most
commonly dev while developing and debugging). While it can vary, the app/cache/dev directory
includes the following:

  • appDevDebugProjectContainer.php - the cached "service container" that represents the
    cached application configuration;
  • appdevUrlGenerator.php - the PHP class generated from the routing configuration and used
    when generating URLs;
  • appdevUrlMatcher.php - the PHP class used for route matching - look here to see the
    compiled regular expression logic used to match incoming URLs to different routes;
  • twig/ - this directory contains all the cached Twig templates.



Going Further
Read the article on How to Set External Parameters in the Service Container.



PDF brought to you by                                        Chapter 48: How to Master and Create new Environments | 339
generated on June 20, 2012
Chapter 49
            How to Set External Parameters in the Service
                             Container

          In the chapter How to Master and Create new Environments, you learned how to manage your application
          configuration. At times, it may benefit your application to store certain credentials outside of your project
          code. Database configuration is one such example. The flexibility of the symfony service container allows
          you to easily do this.



          Environment Variables
          Symfony will grab any environment variable prefixed with SYMFONY__ and set it as a parameter in the
          service container. Double underscores are replaced with a period, as a period is not a valid character in
          an environment variable name.
          For example, if you're using Apache, environment variables can be set using the following VirtualHost
          configuration:

Listing   <VirtualHost *:80>
 49-1
              ServerName               Symfony2
              DocumentRoot             "/path/to/symfony_2_app/web"
              DirectoryIndex           index.php index.html
              SetEnv                   SYMFONY__DATABASE__USER user
              SetEnv                   SYMFONY__DATABASE__PASSWORD secret

              <Directory "/path/to/symfony_2_app/web">
                  AllowOverride All
                  Allow from All
              </Directory>
          </VirtualHost>



                        The example above is for an Apache configuration, using the SetEnv1 directive. However, this will
                        work for any web server which supports the setting of environment variables.



          PDF brought to you by                               Chapter 49: How to Set External Parameters in the Service Container | 340
          generated on June 20, 2012
Also, in order for your console to work (which does not use Apache), you must export these as
              shell variables. On a Unix system, you can run the following:

              export SYMFONY__DATABASE__USER=user                                                                                Listing
                                                                                                                                  49-2
              export SYMFONY__DATABASE__PASSWORD=secret



Now that you have declared an environment variable, it will be present in the PHP $_SERVER global
variable. Symfony then automatically sets all $_SERVER variables prefixed with SYMFONY__ as parameters
in the service container.
You can now reference these parameters wherever you need them.

doctrine:                                                                                                                        Listing
                                                                                                                                  49-3
    dbal:
        driver           pdo_mysql
        dbname:          symfony2_project
        user:            %database.user%
        password:        %database.password%



Constants
The container also has support for setting PHP constants as parameters. To take advantage of this feature,
map the name of your constant to a parameter key, and define the type as constant.

      <?xml version="1.0" encoding="UTF-8"?>                                                                           Listing
                                                                                                                        49-4

      <container xmlns="http://symfony.com/schema/dic/services"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      >

          <parameters>
              <parameter key="global.constant.value"
      type="constant">GLOBAL_CONSTANT</parameter>
              <parameter key="my_class.constant.value"
      type="constant">My_Class::CONSTANT_NAME</parameter>
          </parameters>
      </container>



              This only works for XML configuration. If you're not using XML, simply import an XML file to
              take advantage of this functionality:

              // app/config/config.yml                                                                                           Listing
                                                                                                                                  49-5
              imports:
                  - { resource: parameters.xml }




Miscellaneous Configuration
The imports directive can be used to pull in parameters stored elsewhere. Importing a PHP file gives
you the flexibility to add whatever is needed in the container. The following imports a file named
parameters.php.

1. http://httpd.apache.org/docs/current/env.html


PDF brought to you by                              Chapter 49: How to Set External Parameters in the Service Container | 341
generated on June 20, 2012
Listing   # app/config/config.yml
 49-6
          imports:
              - { resource: parameters.php }



                        A resource file can be one of many types. PHP, XML, YAML, INI, and closure resources are all
                        supported by the imports directive.


          In parameters.php, tell the service container the parameters that you wish to set. This is useful when
          important configuration is in a nonstandard format. The example below includes a Drupal database's
          configuration in the symfony service container.

Listing   // app/config/parameters.php
 49-7

          include_once('/path/to/drupal/sites/default/settings.php');
          $container->setParameter('drupal.database.url', $db_url);




          PDF brought to you by                            Chapter 49: How to Set External Parameters in the Service Container | 342
          generated on June 20, 2012
Chapter 50
           How to use PdoSessionStorage to store
                 Sessions in the Database

The default session storage of Symfony2 writes the session information to file(s). Most medium to large
websites use a database to store the session values instead of files, because databases are easier to use and
scale in a multi-webserver environment.
Symfony2 has a built-in solution for database session storage called PdoSessionStorage1. To use it, you
just need to change some parameters in config.yml (or the configuration format of your choice):

# app/config/config.yml                                                                                                                 Listing
                                                                                                                                         50-1
framework:
    session:
        # ...
        storage_id:     session.storage.pdo

parameters:
    pdo.db_options:
        db_table:            session
        db_id_col:           session_id
        db_data_col:         session_value
        db_time_col:         session_time

services:
    pdo:
         class: PDO
         arguments:
             dsn:      "mysql:dbname=mydatabase"
             user:     myuser
             password: mypassword

     session.storage.pdo:
         class:     SymfonyComponentHttpFoundationSessionStoragePdoSessionStorage
         arguments: [@pdo, %session.storage.options%, %pdo.db_options%]



1. http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/SessionStorage/PdoSessionStorage.html


PDF brought to you by                                Chapter 50: How to use PdoSessionStorage to store Sessions in the Database | 343
generated on June 20, 2012
•    db_table: The name of the session table in your database
            •    db_id_col: The name of the id column in your session table (VARCHAR(255) or larger)
            •    db_data_col: The name of the value column in your session table (TEXT or CLOB)
            •    db_time_col: The name of the time column in your session table (INTEGER)



          Sharing your Database Connection Information
          With the given configuration, the database connection settings are defined for the session storage
          connection only. This is OK when you use a separate database for the session data.
          But if you'd like to store the session data in the same database as the rest of your project's data, you
          can use the connection settings from the parameter.ini by referencing the database-related parameters
          defined there:

Listing   pdo:
 50-2
                 class: PDO
                 arguments:
                     - "mysql:dbname=%database_name%"
                     - %database_user%
                     - %database_password%



          Example SQL Statements
          MySQL
          The SQL statement for creating the needed database table might look like the following (MySQL):

Listing   CREATE TABLE `session` (
 50-3
              `session_id` varchar(255) NOT NULL,
              `session_value` text NOT NULL,
              `session_time` int(11) NOT NULL,
              PRIMARY KEY (`session_id`)
          ) ENGINE=InnoDB DEFAULT CHARSET=utf8;


          PostgreSQL
          For PostgreSQL, the statement should look like this:

Listing   CREATE TABLE session (
 50-4
              session_id character varying(255) NOT NULL,
              session_value text NOT NULL,
              session_time integer NOT NULL,
              CONSTRAINT session_pkey PRIMARY KEY (session_id)
          );




          PDF brought to you by                    Chapter 50: How to use PdoSessionStorage to store Sessions in the Database | 344
          generated on June 20, 2012
Chapter 51
                       How to use the Apache Router

Symfony2, while fast out of the box, also provides various ways to increase that speed with a little bit of
tweaking. One of these ways is by letting apache handle routes directly, rather than using Symfony2 for
this task.



Change Router Configuration Parameters
To dump Apache routes we must first tweak some configuration parameters to tell Symfony2 to use the
ApacheUrlMatcher instead of the default one:

# app/config/config_prod.yml                                                                                                             Listing
                                                                                                                                          51-1
parameters:
    router.options.matcher.cache_class: ~ # disable router cache
    router.options.matcher_class: SymfonyComponentRoutingMatcherApacheUrlMatcher



              Note that ApacheUrlMatcher1 extends UrlMatcher2 so even if you don't regenerate the url_rewrite
              rules, everything will work (because at the end of ApacheUrlMatcher::match() a call to
              parent::match() is done).




Generating mod_rewrite rules
To test that it's working, let's create a very basic route for demo bundle:

# app/config/routing.yml                                                                                                                 Listing
                                                                                                                                          51-2
hello:
    pattern: /hello/{name}
    defaults: { _controller: AcmeDemoBundle:Demo:hello }

Now we generate url_rewrite rules:

1. http://api.symfony.com/2.0/Symfony/Component/Routing/Matcher/ApacheUrlMatcher.html
2. http://api.symfony.com/2.0/Symfony/Component/Routing/Matcher/UrlMatcher.html


PDF brought to you by                                                                   Chapter 51: How to use the Apache Router | 345
generated on June 20, 2012
Listing   php app/console router:dump-apache -e=prod --no-debug
 51-3

          Which should roughly output the following:

Listing   # skip "real" requests
 51-4
          RewriteCond %{REQUEST_FILENAME} -f
          RewriteRule .* - [QSA,L]

          # hello
          RewriteCond %{REQUEST_URI} ^/hello/([^/]+?)$
          RewriteRule .* app.php
          [QSA,L,E=_ROUTING__route:hello,E=_ROUTING_name:%1,E=_ROUTING__controller:AcmeDemoBundle:Demo:hello]

          You can now rewrite web/.htaccess to use the new rules, so with our example it should look like this:

Listing   <IfModule mod_rewrite.c>
 51-5
              RewriteEngine On

               # skip "real" requests
               RewriteCond %{REQUEST_FILENAME} -f
               RewriteRule .* - [QSA,L]

              # hello
              RewriteCond %{REQUEST_URI} ^/hello/([^/]+?)$
              RewriteRule .* app.php
          [QSA,L,E=_ROUTING__route:hello,E=_ROUTING_name:%1,E=_ROUTING__controller:AcmeDemoBundle:Demo:hello]
          </IfModule>



                        Procedure above should be done each time you add/change a route if you want to take full
                        advantage of this setup


          That's it! You're now all set to use Apache Route rules.



          Additional tweaks
          To save a little bit of processing time, change occurrences of Request to ApacheRequest in web/app.php:

Listing   // web/app.php
 51-6

          require_once __DIR__.'/../app/bootstrap.php.cache';
          require_once __DIR__.'/../app/AppKernel.php';
          //require_once __DIR__.'/../app/AppCache.php';

          use SymfonyComponentHttpFoundationApacheRequest;

          $kernel = new AppKernel('prod', false);
          $kernel->loadClassCache();
          //$kernel = new AppCache($kernel);
          $kernel->handle(ApacheRequest::createFromGlobals())->send();




          PDF brought to you by                                              Chapter 51: How to use the Apache Router | 346
          generated on June 20, 2012
Chapter 52
                     How to create an Event Listener

Symfony has various events and hooks that can be used to trigger custom behavior in your application.
Those events are thrown by the HttpKernel component and can be viewed in the KernelEvents1 class.
To hook into an event and add your own custom logic, you have to create a service that will act as an
event listener on that event. In this entry, we will create a service that will act as an Exception Listener,
allowing us to modify how exceptions are shown by our application. The KernelEvents::EXCEPTION
event is just one of the core kernel events:

// src/Acme/DemoBundle/Listener/AcmeExceptionListener.php                                                                          Listing
                                                                                                                                    52-1
namespace AcmeDemoBundleListener;

use SymfonyComponentHttpKernelEventGetResponseForExceptionEvent;

class AcmeExceptionListener
{
    public function onKernelException(GetResponseForExceptionEvent $event)
    {
        // We get the exception object from the received event
        $exception = $event->getException();
        $message = 'My Error says: ' . $exception->getMessage();

           // Customize our response object to display our exception details
           $response->setContent($message);
           $response->setStatusCode($exception->getStatusCode());

           // Send our modified response object to the event
           $event->setResponse($response);
     }
}



              Each event receives a slightly different type of $event object. For the kernel.exception event, it
              is GetResponseForExceptionEvent2. To see what type of object each event listener receives, see
              KernelEvents3.


1. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/KernelEvents.html


PDF brought to you by                                                          Chapter 52: How to create an Event Listener | 347
generated on June 20, 2012
Now that the class is created, we just need to register it as a service and notify Symfony that it is a
          "listener" on the kernel.exception event by using a special "tag":

Listing   services:
 52-2
              kernel.listener.your_listener_name:
                  class: AcmeDemoBundleListenerAcmeExceptionListener
                  tags:
                      - { name: kernel.event_listener, event: kernel.exception, method:
          onKernelException }



                        There is an additional tag option priority that is optional and defaults to 0. This value can be
                        from -255 to 255, and the listeners will be executed in the order of their priority. This is useful
                        when you need to guarantee that one listener is executed before another.



          Request events, checking types
          A single page can make several requests (one mast request, and then multiple sub-requests), which is why
          when working with the KernelEvents::REQUEST event, you might need to check the type of the request.
          This can be easily done as follow:

Listing   // src/Acme/DemoBundle/Listener/AcmeRequestListener.php
 52-3
          namespace AcmeDemoBundleListener;

          use SymfonyComponentHttpKernelEventGetResponseEvent;
          use SymfonyComponentHttpKernelHttpKernel;

          class AcmeRequestListener
          {
              public function onKernelRequest(GetResponseEvent $event)
              {
                  if (HttpKernel::MASTER_REQUEST != $event->getRequestType()) {
                      // don't do anything if it's not the master request
                      return;
                  }

                     // your code
               }
          }



                        Two types of request are available in the HttpKernelInterface4 interface:
                        HttpKernelInterface::MASTER_REQUEST and HttpKernelInterface::SUB_REQUEST.




          2. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/Event/GetResponseForExceptionEvent.html
          3. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/KernelEvents.html
          4. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/HttpKernelInterface.html


          PDF brought to you by                                                               Chapter 52: How to create an Event Listener | 348
          generated on June 20, 2012
Chapter 53
                              How to work with Scopes

This entry is all about scopes, a somewhat advanced topic related to the Service Container. If you've ever
gotten an error mentioning "scopes" when creating services, or need to create a service that depends on
the request service, then this entry is for you.



Understanding Scopes
The scope of a service controls how long an instance of a service is used by the container. The
Dependency Injection component provides two generic scopes:

  • container (the default one): The same instance is used each time you request it from this
    container.
  • prototype: A new instance is created each time you request the service.

The FrameworkBundle also defines a third scope: request. This scope is tied to the request, meaning a
new instance is created for each subrequest and is unavailable outside the request (for instance in the
CLI).
Scopes add a constraint on the dependencies of a service: a service cannot depend on services from
a narrower scope. For example, if you create a generic my_foo service, but try to inject the request
component, you'll receive a ScopeWideningInjectionException1 when compiling the container. Read
the sidebar below for more details.




1. http://api.symfony.com/2.0/Symfony/Component/DependencyInjection/Exception/ScopeWideningInjectionException.html


PDF brought to you by                                                                     Chapter 53: How to work with Scopes | 349
generated on June 20, 2012
Scopes and Dependencies
                        Imagine you've configured a my_mailer service. You haven't configured the scope of the service, so
                        it defaults to container. In other words, everytime you ask the container for the my_mailer service,
                        you get the same object back. This is usually how you want your services to work.
                        Imagine, however, that you need the request service in your my_mailer service, maybe because
                        you're reading the URL of the current request. So, you add it as a constructor argument. Let's look
                        at why this presents a problem:

                           • When requesting my_mailer, an instance of my_mailer (let's call it MailerA) is created
                             and the request service (let's call it RequestA) is passed to it. Life is good!
                           • You've now made a subrequest in Symfony, which is a fancy way of saying that you've
                             called, for example, the {% render ... %} Twig function, which executes another
                             controller. Internally, the old request service (RequestA) is actually replaced by a new
                             request instance (RequestB). This happens in the background, and it's totally normal.
                           • In your embedded controller, you once again ask for the my_mailer service. Since your
                             service is in the container scope, the same instance (MailerA) is just re-used. But here's
                             the problem: the MailerA instance still contains the old RequestA object, which is now
                             not the correct request object to have (RequestB is now the current request service). This
                             is subtle, but the mis-match could cause major problems, which is why it's not allowed.
                              So, that's the reason why scopes exist, and how they can cause problems. Keep reading
                              to find out the common solutions.




                        A service can of course depend on a service from a wider scope without any issue.




          Setting the Scope in the Definition
          The scope of a service is set in the definition of the service:

Listing   # src/Acme/HelloBundle/Resources/config/services.yml
 53-1
          services:
              greeting_card_manager:
                  class: AcmeHelloBundleMailGreetingCardManager
                  scope: request

          If you don't specify the scope, it defaults to container, which is what you want most of the time. Unless
          your service depends on another service that's scoped to a narrower scope (most commonly, the request
          service), you probably don't need to set the scope.



          Using a Service from a narrower Scope
          If your service depends on a scoped service, the best solution is to put it in the same scope (or a narrower
          one). Usually, this means putting your new service in the request scope.
          But this is not always possible (for instance, a twig extension must be in the container scope as the Twig
          environment needs it as a dependency). In these cases, you should pass the entire container into your
          service and retrieve your dependency from the container each time we need it to be sure you have the
          right instance:


          PDF brought to you by                                                         Chapter 53: How to work with Scopes | 350
          generated on June 20, 2012
namespace AcmeHelloBundleMail;                                                                                           Listing
                                                                                                                            53-2

use SymfonyComponentDependencyInjectionContainerInterface;

class Mailer
{
    protected $container;

     public function __construct(ContainerInterface $container)
     {
         $this->container = $container;
     }

     public function sendEmail()
     {
         $request = $this->container->get('request');
         // Do something using the request here
     }
}



              Take care not to store the request in a property of the object for a future call of the service as it
              would cause the same issue described in the first section (except that Symfony cannot detect that
              you are wrong).

The service config for this class would look something like this:

# src/Acme/HelloBundle/Resources/config/services.yml                                                                       Listing
                                                                                                                            53-3
parameters:
    # ...
    my_mailer.class: AcmeHelloBundleMailMailer
services:
    my_mailer:
        class:     %my_mailer.class%
        arguments:
            - "@service_container"
        # scope: container can be omitted as it is the default



              Injecting the whole container into a service is generally not a good idea (only inject what you need).
              In some rare cases, it's necessary when you have a service in the container scope that needs a
              service in the request scope.

If you define a controller as a service then you can get the Request object without injecting the container
by having it passed in as an argument of your action method. See The Request as a Controller Argument
for details.




PDF brought to you by                                                          Chapter 53: How to work with Scopes | 351
generated on June 20, 2012
Chapter 54
              How to work with Compiler Passes in Bundles

          Compiler passes give you an opportunity to manipulate other service definitions that have been registered
          with the service container. You can read about how to create them in the components section "Compiling
          the Container". To register a compiler pass from a bundle you need to add it to the build method of the
          bundle definition class:

Listing   namespace AcmeMailerBundle;
 54-1

          use SymfonyComponentHttpKernelBundleBundle;
          use SymfonyComponentDependencyInjectionContainerBuilder;

          use AcmeMailerBundleDependencyInjectionCompilerCustomCompilerPass;

          class AcmeMailerBundle extends Bundle
          {
              public function build(ContainerBuilder $container)
              {
                  parent::build($container);

                     $container->addCompilerPass(new CustomCompilerPass());
               }
          }

          One of the most common use-cases of compiler passes is to work with tagged services (read more about
          tags in the components section "Working with Tagged Services"). If you are using custom tags in a bundle
          then by convention, tag names consist of the name of the bundle (lowercase, underscores as separators),
          followed by a dot, and finally the "real" name. For example, if you want to introduce some sort of
          "transport" tag in your AcmeMailerBundle, you should call it acme_mailer.transport.




          PDF brought to you by                                  Chapter 54: How to work with Compiler Passes in Bundles | 352
          generated on June 20, 2012
Chapter 55
                Bundle Structure and Best Practices

A bundle is a directory that has a well-defined structure and can host anything from classes to controllers
and web resources. Even if bundles are very flexible, you should follow some best practices if you want
to distribute them.



Bundle Name
A bundle is also a PHP namespace. The namespace must follow the technical interoperability standards1
for PHP 5.3 namespaces and class names: it starts with a vendor segment, followed by zero or more
category segments, and it ends with the namespace short name, which must end with a Bundle suffix.
A namespace becomes a bundle as soon as you add a bundle class to it. The bundle class name must
follow these simple rules:

  • Use only alphanumeric characters and underscores;
  • Use a CamelCased name;
  • Use a descriptive and short name (no more than 2 words);
  • Prefix the name with the concatenation of the vendor (and optionally the category
    namespaces);
  • Suffix the name with Bundle.

Here are some valid bundle namespaces and class names:

 Namespace                             Bundle Class Name
 AcmeBundleBlogBundle                AcmeBlogBundle
 AcmeBundleSocialBlogBundle AcmeSocialBlogBundle
 AcmeBlogBundle                       AcmeBlogBundle

By convention, the getName() method of the bundle class should return the class name.




1. http://symfony.com/PSR0


PDF brought to you by                                            Chapter 55: Bundle Structure and Best Practices | 353
generated on June 20, 2012
If you share your bundle publicly, you must use the bundle class name as the name of the
                        repository (AcmeBlogBundle and not BlogBundle for instance).




                        Symfony2 core Bundles do not prefix the Bundle class with Symfony and always add a Bundle
                        subnamespace; for example: FrameworkBundle2.


          Each bundle has an alias, which is the lower-cased short version of the bundle name using underscores
          (acme_hello for AcmeHelloBundle, or acme_social_blog for AcmeSocialBlogBundle for instance).
          This alias is used to enforce uniqueness within a bundle (see below for some usage examples).



          Directory Structure
          The basic directory structure of a HelloBundle bundle must read as follows:

Listing   XXX/...
 55-1
              HelloBundle/
                  HelloBundle.php
                  Controller/
                  Resources/
                      meta/
                           LICENSE
                      config/
                      doc/
                           index.rst
                      translations/
                      views/
                      public/
                  Tests/

          The XXX directory(ies) reflects the namespace structure of the bundle.
          The following files are mandatory:

            • HelloBundle.php;
            • Resources/meta/LICENSE: The full license for the code;
            • Resources/doc/index.rst: The root file for the Bundle documentation.


                        These conventions ensure that automated tools can rely on this default structure to work.



          The depth of sub-directories should be kept to the minimal for most used classes and files (2 levels at a
          maximum). More levels can be defined for non-strategic, less-used files.
          The bundle directory is read-only. If you need to write temporary files, store them under the cache/ or
          log/ directory of the host application. Tools can generate files in the bundle directory structure, but only
          if the generated files are going to be part of the repository.
          The following classes and files have specific emplacements:



          2. http://api.symfony.com/2.0/Symfony/Bundle/FrameworkBundle/FrameworkBundle.html


          PDF brought to you by                                                           Chapter 55: Bundle Structure and Best Practices | 354
          generated on June 20, 2012
Type                             Directory
 Commands                         Command/
 Controllers                      Controller/
 Service Container Extensions DependencyInjection/
 Event Listeners                  EventListener/
 Configuration                    Resources/config/
 Web Resources                    Resources/public/
 Translation files                Resources/translations/
 Templates                        Resources/views/
 Unit and Functional Tests        Tests/



Classes
The bundle directory structure is used as the namespace hierarchy. For instance, a HelloController
controller is stored in Bundle/HelloBundle/Controller/HelloController.php and the fully qualified
class name is BundleHelloBundleControllerHelloController.
All classes and files must follow the Symfony2 coding standards.
Some classes should be seen as facades and should be as short as possible, like Commands, Helpers,
Listeners, and Controllers.
Classes that connect to the Event Dispatcher should be suffixed with Listener.
Exceptions classes should be stored in an Exception sub-namespace.



Vendors
A bundle must not embed third-party PHP libraries. It should rely on the standard Symfony2 autoloading
instead.
A bundle should not embed third-party libraries written in JavaScript, CSS, or any other language.



Tests
A bundle should come with a test suite written with PHPUnit and stored under the Tests/ directory.
Tests should follow the following principles:

  • The test suite must be executable with a simple phpunit command run from a sample
    application;
  • The functional tests should only be used to test the response output and some profiling
    information if you have some;
  • The code coverage should at least covers 95% of the code base.


              A test suite must not contain AllTests.php scripts, but must rely on the existence of a
              phpunit.xml.dist file.




PDF brought to you by                                          Chapter 55: Bundle Structure and Best Practices | 355
generated on June 20, 2012
Documentation
All classes and functions must come with full PHPDoc.
Extensive documentation should also be provided in the reStructuredText format, under the Resources/
doc/ directory; the Resources/doc/index.rst file is the only mandatory file and must be the entry point
for the documentation.



Controllers
As a best practice, controllers in a bundle that's meant to be distributed to others must not extend the
Controller3 base class. They can implement ContainerAwareInterface4 or extend ContainerAware5
instead.


              If you have a look at Controller6 methods, you will see that they are only nice shortcuts to ease
              the learning curve.




Routing
If the bundle provides routes, they must be prefixed with the bundle alias. For an AcmeBlogBundle for
instance, all routes must be prefixed with acme_blog_.



Templates
If a bundle provides templates, they must use Twig. A bundle must not provide a main layout, except if
it provides a full working application.



Translation Files
If a bundle provides message translations, they must be defined in the XLIFF format; the domain should
be named after the bundle name (bundle.hello).
A bundle must not override existing messages from another bundle.



Configuration
To provide more flexibility, a bundle can provide configurable settings by using the Symfony2 built-in
mechanisms.
For simple configuration settings, rely on the default parameters entry of the Symfony2 configuration.
Symfony2 parameters are simple key/value pairs; a value being any valid PHP value. Each parameter
name should start with the bundle alias, though this is just a best-practice suggestion. The rest of the
parameter name will use a period (.) to separate different parts (e.g. acme_hello.email.from).


3. http://api.symfony.com/2.0/Symfony/Bundle/FrameworkBundle/Controller/Controller.html
4. http://api.symfony.com/2.0/Symfony/Component/DependencyInjection/ContainerAwareInterface.html
5. http://api.symfony.com/2.0/Symfony/Component/DependencyInjection/ContainerAware.html
6. http://api.symfony.com/2.0/Symfony/Bundle/FrameworkBundle/Controller/Controller.html


PDF brought to you by                                                            Chapter 55: Bundle Structure and Best Practices | 356
generated on June 20, 2012
The end user can provide values in any configuration file:

# app/config/config.yml                                                                                                        Listing
                                                                                                                                55-2
parameters:
    acme_hello.email.from: fabien@example.com

Retrieve the configuration parameters in your code from the container:

$container->getParameter('acme_hello.email.from');                                                                             Listing
                                                                                                                                55-3

Even if this mechanism is simple enough, you are highly encouraged to use the semantic configuration
described in the cookbook.


              If you are defining services, they should also be prefixed with the bundle alias.




Learn more from the Cookbook
  • How to expose a Semantic Configuration for a Bundle




PDF brought to you by                                                  Chapter 55: Bundle Structure and Best Practices | 357
generated on June 20, 2012
Chapter 56
                How to use Bundle Inheritance to Override
                            parts of a Bundle

          When working with third-party bundles, you'll probably come across a situation where you want to
          override a file in that third-party bundle with a file in one of your own bundles. Symfony gives you a very
          convenient way to override things like controllers, templates, and other files in a bundle's Resources/
          directory.
          For example, suppose that you're installing the FOSUserBundle1, but you want to override its base
          layout.html.twig template, as well as one of its controllers. Suppose also that you have your own
          AcmeUserBundle where you want the overridden files to live. Start by registering the FOSUserBundle as
          the "parent" of your bundle:

Listing   // src/Acme/UserBundle/AcmeUserBundle.php
 56-1
          namespace AcmeUserBundle;

          use SymfonyComponentHttpKernelBundleBundle;

          class AcmeUserBundle extends Bundle
          {
              public function getParent()
              {
                  return 'FOSUserBundle';
              }
          }

          By making this simple change, you can now override several parts of the FOSUserBundle simply by
          creating a file with the same name.




          1. https://github.com/friendsofsymfony/fosuserbundle


          PDF brought to you by                                  Chapter 56: How to use Bundle Inheritance to Override parts of a Bundle | 358
          generated on June 20, 2012
Overriding Controllers
Suppose you want to add some functionality to the registerAction of a RegistrationController that
lives inside FOSUserBundle. To do so, just create your own RegistrationController.php file, override
the bundle's original method, and change its functionality:

// src/Acme/UserBundle/Controller/RegistrationController.php                                                                   Listing
                                                                                                                                56-2
namespace AcmeUserBundleController;

use FOSUserBundleControllerRegistrationController as BaseController;

class RegistrationController extends BaseController
{
    public function registerAction()
    {
        $response = parent::registerAction();

           // do custom stuff

           return $response;
     }
}



              Depending on how severely you need to change the behavior, you might call
              parent::registerAction() or completely replace its logic with your own.




              Overriding controllers in this way only works if the bundle refers to the controller using the
              standard FOSUserBundle:Registration:register syntax in routes and templates. This is the
              best practice.



Overriding Resources: Templates, Routing, Validation, etc
Most resources can also be overridden, simply by creating a file in the same location as your parent
bundle.
For example, it's very common to need to override the FOSUserBundle's layout.html.twig template so
that it uses your application's base layout. Since the file lives at Resources/views/layout.html.twig in
the FOSUserBundle, you can create your own file in the same location of AcmeUserBundle. Symfony will
ignore the file that lives inside the FOSUserBundle entirely, and use your file instead.
The same goes for routing files, validation configuration and other resources.


              The overriding of resources only works when you refer to resources with the @FosUserBundle/
              Resources/config/routing/security.xml method. If you refer to resources without using the
              @BundleName shortcut, they can't be overridden in this way.



              Translation files do not work in the same way as described above. All translation files are
              accumulated into a set of "pools" (one for each) domain. Symfony loads translation files from
              bundles first (in the order that the bundles are initialized) and then from your app/Resources




PDF brought to you by                          Chapter 56: How to use Bundle Inheritance to Override parts of a Bundle | 359
generated on June 20, 2012
directory. If the same translation is specified in two resources, the translation from the resource
              that's loaded last will win.




PDF brought to you by                           Chapter 56: How to use Bundle Inheritance to Override parts of a Bundle | 360
generated on June 20, 2012
Chapter 57
              How to Override any Part of a Bundle

This document is a quick reference for how to override different parts of third-party bundles.



Templates
For information on overriding templates, see * Overriding Bundle Templates. * How to use Bundle
Inheritance to Override parts of a Bundle



Routing
Routing is never automatically imported in Symfony2. If you want to include the routes from any
bundle, then they must be manually imported from somewhere in your application (e.g. app/config/
routing.yml).
The easiest way to "override" a bundle's routing is to never import it at all. Instead of importing a third-
party bundle's routing, simply copying that routing file into your application, modify it, and import it
instead.



Controllers
Assuming the third-party bundle involved uses non-service controllers (which is almost always the case),
you can easily override controllers via bundle inheritance. For more information, see How to use Bundle
Inheritance to Override parts of a Bundle.



Services & Configuration
In progress...




PDF brought to you by                                          Chapter 57: How to Override any Part of a Bundle | 361
generated on June 20, 2012
Entities & Entity mapping
In progress...



Forms
In progress...



Validation metadata
In progress...



Translations
In progress...




PDF brought to you by        Chapter 57: How to Override any Part of a Bundle | 362
generated on June 20, 2012
Chapter 58
  How to expose a Semantic Configuration for a
                    Bundle

If you open your application configuration file (usually app/config/config.yml), you'll see a number of
different configuration "namespaces", such as framework, twig, and doctrine. Each of these configures
a specific bundle, allowing you to configure things at a high level and then let the bundle make all the
low-level, complex changes that result.
For example, the following tells the FrameworkBundle to enable the form integration, which involves the
defining of quite a few services as well as integration of other related components:

framework:                                                                                                                   Listing
                                                                                                                              58-1
    # ...
    form:                    true

When you create a bundle, you have two choices on how to handle configuration:
      1. Normal Service Configuration (easy):

                  You can specify your services in a configuration file (e.g. services.yml) that lives
                  in your bundle and then import it from your main application configuration. This is
                  really easy, quick and totally effective. If you make use of parameters, then you still
                  have the flexibility to customize your bundle from your application configuration. See
                  "Importing Configuration with imports" for more details.

      2. Exposing Semantic Configuration (advanced):

                  This is the way configuration is done with the core bundles (as described above). The
                  basic idea is that, instead of having the user override individual parameters, you let
                  the user configure just a few, specifically created options. As the bundle developer,
                  you then parse through that configuration and load services inside an "Extension"
                  class. With this method, you won't need to import any configuration resources from
                  your main application configuration: the Extension class can handle all of this.




PDF brought to you by                                Chapter 58: How to expose a Semantic Configuration for a Bundle | 363
generated on June 20, 2012
The second option - which you'll learn about in this article - is much more flexible, but also requires more
          time to setup. If you're wondering which method you should use, it's probably a good idea to start with
          method #1, and then change to #2 later if you need to.
          The second method has several specific advantages:

              • Much more powerful than simply defining parameters: a specific option value might trigger
                the creation of many service definitions;
              • Ability to have configuration hierarchy
              • Smart merging when several configuration files (e.g. config_dev.yml and config.yml)
                override each other's configuration;
              • Configuration validation (if you use a Configuration Class);
              • IDE auto-completion when you create an XSD and developers use XML.


                        Overriding bundle parameters
                        If a Bundle provides an Extension class, then you should generally not override any service
                        container parameters from that bundle. The idea is that if an Extension class is present, every
                        setting that should be configurable should be present in the configuration made available by that
                        class. In other words the extension class defines all the publicly supported configuration settings
                        for which backward compatibility will be maintained.



          Creating an Extension Class
          If you do choose to expose a semantic configuration for your bundle, you'll first need to create a new
          "Extension" class, which will handle the process. This class should live in the DependencyInjection
          directory of your bundle and its name should be constructed by replacing the Bundle suffix of the Bundle
          class name with Extension. For example, the Extension class of AcmeHelloBundle would be called
          AcmeHelloExtension:

Listing   // Acme/HelloBundle/DependencyInjection/AcmeHelloExtension.php
 58-2
          use SymfonyComponentHttpKernelDependencyInjectionExtension;
          use SymfonyComponentDependencyInjectionContainerBuilder;

          class AcmeHelloExtension extends Extension
          {
              public function load(array $configs, ContainerBuilder $container)
              {
                  // where all of the heavy logic is done
              }

                public function getXsdValidationBasePath()
                {
                    return __DIR__.'/../Resources/config/';
                }

                public function getNamespace()
                {
                    return 'http://www.example.com/symfony/schema/';
                }
          }




          PDF brought to you by                                 Chapter 58: How to expose a Semantic Configuration for a Bundle | 364
          generated on June 20, 2012
The getXsdValidationBasePath and getNamespace methods are only required if the bundle
              provides optional XSD's for the configuration.


The presence of the previous class means that you can now define an acme_hello configuration
namespace in any configuration file. The namespace acme_hello is constructed from the extension's class
name by removing the word Extension and then lowercasing and underscoring the rest of the name. In
other words, AcmeHelloExtension becomes acme_hello.
You can begin specifying configuration under this namespace immediately:

# app/config/config.yml                                                                                                      Listing
                                                                                                                              58-3
acme_hello: ~



              If you follow the naming conventions laid out above, then the load() method of your extension
              code is always called as long as your bundle is registered in the Kernel. In other words, even if
              the user does not provide any configuration (i.e. the acme_hello entry doesn't even appear), the
              load() method will be called and passed an empty $configs array. You can still provide some
              sensible defaults for your bundle if you want.



Parsing the $configs Array
Whenever a user includes the acme_hello namespace in a configuration file, the configuration under it
is added to an array of configurations and passed to the load() method of your extension (Symfony2
automatically converts XML and YAML to an array).
Take the following configuration:

# app/config/config.yml                                                                                                      Listing
                                                                                                                              58-4
acme_hello:
    foo: fooValue
    bar: barValue

The array passed to your load() method will look like this:

array(                                                                                                                       Listing
                                                                                                                              58-5
    array(
        'foo' => 'fooValue',
        'bar' => 'barValue',
    )
)

Notice that this is an array of arrays, not just a single flat array of the configuration values. This is
intentional. For example, if acme_hello appears in another configuration file - say config_dev.yml -
with different values beneath it, then the incoming array might look like this:

array(                                                                                                                       Listing
                                                                                                                              58-6
    array(
        'foo'      => 'fooValue',
        'bar'      => 'barValue',
    ),
    array(
        'foo'      => 'fooDevValue',
        'baz'      => 'newConfigEntry',



PDF brought to you by                                Chapter 58: How to expose a Semantic Configuration for a Bundle | 365
generated on June 20, 2012
),
          )

          The order of the two arrays depends on which one is set first.
          It's your job, then, to decide how these configurations should be merged together. You might, for
          example, have later values override previous values or somehow merge them together.
          Later, in the Configuration Class section, you'll learn of a truly robust way to handle this. But for now,
          you might just merge them manually:

Listing   public function load(array $configs, ContainerBuilder $container)
 58-7
          {
              $config = array();
              foreach ($configs as $subConfig) {
                  $config = array_merge($config, $subConfig);
              }

               // now use the flat $config array
          }



                        Make sure the above merging technique makes sense for your bundle. This is just an example, and
                        you should be careful to not use it blindly.




          Using the load() Method
          Within load(), the $container variable refers to a container that only knows about this namespace
          configuration (i.e. it doesn't contain service information loaded from other bundles). The goal of the
          load() method is to manipulate the container, adding and configuring any methods or services needed
          by your bundle.

          Loading External Configuration Resources
          One common thing to do is to load an external configuration file that may contain the bulk of the services
          needed by your bundle. For example, suppose you have a services.xml file that holds much of your
          bundle's service configuration:

Listing   use SymfonyComponentDependencyInjectionLoaderXmlFileLoader;
 58-8
          use SymfonyComponentConfigFileLocator;

          public function load(array $configs, ContainerBuilder $container)
          {
              // prepare your $config variable

               $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
               $loader->load('services.xml');
          }

          You might even do this conditionally, based on one of the configuration values. For example, suppose
          you only want to load a set of services if an enabled option is passed and set to true:

Listing   public function load(array $configs, ContainerBuilder $container)
 58-9
          {
              // prepare your $config variable

               $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));

          PDF brought to you by                               Chapter 58: How to expose a Semantic Configuration for a Bundle | 366
          generated on June 20, 2012
if (isset($config['enabled']) && $config['enabled']) {
         $loader->load('services.xml');
     }
}


Configuring Services and Setting Parameters
Once you've loaded some service configuration, you may need to modify the configuration based on
some of the input values. For example, suppose you have a service whose first argument is some string
"type" that it will use internally. You'd like this to be easily configured by the bundle user, so in your
service configuration file (e.g. services.xml), you define this service and use a blank parameter -
acme_hello.my_service_type - as its first argument:

<!-- src/Acme/HelloBundle/Resources/config/services.xml -->                                                              Listing
                                                                                                                         58-10
<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/
services/services-1.0.xsd">

     <parameters>
         <parameter key="acme_hello.my_service_type" />
     </parameters>

    <services>
        <service id="acme_hello.my_service" class="AcmeHelloBundleMyService">
             <argument>%acme_hello.my_service_type%</argument>
        </service>
    </services>
</container>

But why would you define an empty parameter and then pass it to your service? The answer is that you'll
set this parameter in your extension class, based on the incoming configuration values. Suppose, for
example, that you want to allow the user to define this type option under a key called my_type. Add the
following to the load() method to do this:

public function load(array $configs, ContainerBuilder $container)                                                        Listing
                                                                                                                         58-11
{
    // prepare your $config variable

     $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
     $loader->load('services.xml');

     if (!isset($config['my_type'])) {
         throw new InvalidArgumentException('The "my_type" option must be set');
     }

     $container->setParameter('acme_hello.my_service_type', $config['my_type']);
}

Now, the user can effectively configure the service by specifying the my_type configuration value:

# app/config/config.yml                                                                                                  Listing
                                                                                                                         58-12
acme_hello:
    my_type: foo
    # ...




PDF brought to you by                            Chapter 58: How to expose a Semantic Configuration for a Bundle | 367
generated on June 20, 2012
Global Parameters
          When you're configuring the container, be aware that you have the following global parameters available
          to use:

            •   kernel.name
            •   kernel.environment
            •   kernel.debug
            •   kernel.root_dir
            •   kernel.cache_dir
            •   kernel.logs_dir
            •   kernel.bundle_dirs
            •   kernel.bundles
            •   kernel.charset


                        All parameter and service names starting with a _ are reserved for the framework, and new ones
                        must not be defined by bundles.




          Validation and Merging with a Configuration Class
          So far, you've done the merging of your configuration arrays by hand and are checking for the presence
          of config values manually using the isset() PHP function. An optional Configuration system is also
          available which can help with merging, validation, default values, and format normalization.


                        Format normalization refers to the fact that certain formats - largely XML - result in slightly
                        different configuration arrays and that these arrays need to be "normalized" to match everything
                        else.

          To take advantage of this system, you'll create a Configuration class and build a tree that defines your
          configuration in that class:

Listing   // src/Acme/HelloBundle/DependencyInjection/Configuration.php
58-13
          namespace AcmeHelloBundleDependencyInjection;

          use SymfonyComponentConfigDefinitionBuilderTreeBuilder;
          use SymfonyComponentConfigDefinitionConfigurationInterface;

          class Configuration implements ConfigurationInterface
          {
              public function getConfigTreeBuilder()
              {
                  $treeBuilder = new TreeBuilder();
                  $rootNode = $treeBuilder->root('acme_hello');

                     $rootNode
                         ->children()
                             ->scalarNode('my_type')->defaultValue('bar')->end()
                         ->end()
                     ;

                     return $treeBuilder;
                }



          PDF brought to you by                                Chapter 58: How to expose a Semantic Configuration for a Bundle | 368
          generated on June 20, 2012
This is a very simple example, but you can now use this class in your load() method to merge your
configuration and force validation. If any options other than my_type are passed, the user will be notified
with an exception that an unsupported option was passed:

public function load(array $configs, ContainerBuilder $container)                                                                      Listing
                                                                                                                                       58-14
{
    $configuration = new Configuration();
    $config = $this->processConfiguration($configuration, $configs);

     // ...
}

The processConfiguration() method uses the configuration tree you've defined in the Configuration
class to validate, normalize and merge all of the configuration arrays together.
The Configuration class can be much more complicated than shown here, supporting array nodes,
"prototype" nodes, advanced validation, XML-specific normalization and advanced merging. The best
way to see this in action is to checkout out some of the core Configuration classes, such as the one from
the FrameworkBundle Configuration1 or the TwigBundle Configuration2.



Extension Conventions
When creating an extension, follow these simple conventions:

    • The extension must be stored in the DependencyInjection sub-namespace;
    • The extension must be named after the bundle name and suffixed with Extension
      (AcmeHelloExtension for AcmeHelloBundle);
    • The extension should provide an XSD schema.

If you follow these simple conventions, your extensions will be registered automatically by Symfony2. If
not, override the Bundle build()3 method in your bundle:

use AcmeHelloBundleDependencyInjectionUnconventionalExtensionClass;                                                                 Listing
                                                                                                                                       58-15

class AcmeHelloBundle extends Bundle
{
    public function build(ContainerBuilder $container)
    {
        parent::build($container);

           // register extensions that do not follow the conventions manually
           $container->registerExtension(new UnconventionalExtensionClass());
     }
}

In this case, the extension class must also implement a getAlias() method and return a unique alias
named after the bundle (e.g. acme_hello). This is required because the class name doesn't follow the
standards by ending in Extension.
Additionally, the load() method of your extension will only be called if the user specifies the acme_hello
alias in at least one configuration file. Once again, this is because the Extension class doesn't follow the
standards set out above, so nothing happens automatically.




1. https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
2. https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php
3. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/Bundle/Bundle.html#build()


PDF brought to you by                                          Chapter 58: How to expose a Semantic Configuration for a Bundle | 369
generated on June 20, 2012
Chapter 59
                                             How to send an Email

          Sending emails is a classic task for any web application and one that has special complications and
          potential pitfalls. Instead of recreating the wheel, one solution to send emails is to use the
          SwiftmailerBundle, which leverages the power of the Swiftmailer1 library.


                           Don't forget to enable the bundle in your kernel before using it:

                 Listing   public function registerBundles()
                  59-1
                           {
                               $bundles = array(
                                   // ...
                                   new SymfonyBundleSwiftmailerBundleSwiftmailerBundle(),
                               );

                               // ...
                           }




          Configuration
          Before using Swiftmailer, be sure to include its configuration. The only mandatory configuration
          parameter is transport:

Listing   # app/config/config.yml
 59-2
          swiftmailer:
              transport: smtp
              encryption: ssl
              auth_mode: login
              host:       smtp.gmail.com
              username:   your_username
              password:   your_password




          1. http://www.swiftmailer.org/


          PDF brought to you by                                                                Chapter 59: How to send an Email | 370
          generated on June 20, 2012
The majority of the Swiftmailer configuration deals with how the messages themselves should be
delivered.
The following configuration attributes are available:

    •   transport (smtp, mail, sendmail, or gmail)
    •   username
    •   password
    •   host
    •   port
    •   encryption (tls, or ssl)
    •   auth_mode (plain, login, or cram-md5)
    •   spool

          • type (how to queue the messages, only file is supported currently)
          • path (where to store the messages)

    • delivery_address (an email address where to send ALL emails)
    • disable_delivery (set to true to disable delivery completely)



Sending Emails
The Swiftmailer library works by creating, configuring and then sending Swift_Message objects. The
"mailer" is responsible for the actual delivery of the message and is accessible via the mailer service.
Overall, sending an email is pretty straightforward:

public function indexAction($name)                                                                                      Listing
                                                                                                                         59-3
{
    $message = Swift_Message::newInstance()
         ->setSubject('Hello Email')
         ->setFrom('send@example.com')
         ->setTo('recipient@example.com')
         ->setBody($this->renderView('HelloBundle:Hello:email.txt.twig', array('name' =>
$name)))
    ;
    $this->get('mailer')->send($message);

        return $this->render(...);
}

To keep things decoupled, the email body has been stored in a template and rendered with the
renderView() method.
The $message object supports many more options, such as including attachments, adding HTML
content, and much more. Fortunately, Swiftmailer covers the topic of Creating Messages2 in great detail
in its documentation.


              Several other cookbook articles are available related to sending emails in Symfony2:

                 • How to use Gmail to send Emails
                 • How to Work with Emails During Development
                 • How to Spool Email




2. http://swiftmailer.org/docs/messages.html


PDF brought to you by                                                          Chapter 59: How to send an Email | 371
generated on June 20, 2012
Chapter 60
                              How to use Gmail to send Emails

          During development, instead of using a regular SMTP server to send emails, you might find using Gmail
          easier and more practical. The Swiftmailer bundle makes it really easy.


                        Instead of using your regular Gmail account, it's of course recommended that you create a special
                        account.


          In the development configuration file, change the transport setting to gmail and set the username and
          password to the Google credentials:

Listing   # app/config/config_dev.yml
 60-1
          swiftmailer:
              transport: gmail
              username: your_gmail_username
              password: your_gmail_password

          You're done!


                        The gmail transport is simply a shortcut that uses the smtp transport and sets encryption,
                        auth_mode and host to work with Gmail.




          PDF brought to you by                                                Chapter 60: How to use Gmail to send Emails | 372
          generated on June 20, 2012
Chapter 61
How to Work with Emails During Development

When developing an application which sends email, you will often not want to actually send the email
to the specified recipient during development. If you are using the SwiftmailerBundle with Symfony2,
you can easily achieve this through configuration settings without having to make any changes to
your application's code at all. There are two main choices when it comes to handling email during
development: (a) disabling the sending of email altogether or (b) sending all email to a specific address.



Disabling Sending
You can disable sending email by setting the disable_delivery option to true. This is the default in the
test environment in the Standard distribution. If you do this in the test specific config then email will
not be sent when you run tests, but will continue to be sent in the prod and dev environments:

# app/config/config_test.yml                                                                                          Listing
                                                                                                                       61-1
swiftmailer:
    disable_delivery: true

If you'd also like to disable deliver in the dev environment, simply add this same configuration to the
config_dev.yml file.



Sending to a Specified Address
You can also choose to have all email sent to a specific address, instead of the address actually specified
when sending the message. This can be done via the delivery_address option:

# app/config/config_dev.yml                                                                                           Listing
                                                                                                                       61-2
swiftmailer:
    delivery_address: dev@example.com

Now, suppose you're sending an email to recipient@example.com.

public function indexAction($name)                                                                                    Listing
                                                                                                                       61-3
{


PDF brought to you by                                  Chapter 61: How to Work with Emails During Development | 373
generated on June 20, 2012
$message = Swift_Message::newInstance()
                   ->setSubject('Hello Email')
                   ->setFrom('send@example.com')
                   ->setTo('recipient@example.com')
                   ->setBody($this->renderView('HelloBundle:Hello:email.txt.twig', array('name' =>
          $name)))
              ;
              $this->get('mailer')->send($message);

               return $this->render(...);
          }

          In the dev environment, the email will instead be sent to dev@example.com. Swiftmailer will add an extra
          header to the email, X-Swift-To, containing the replaced address, so you can still see who it would have
          been sent to.


                        In addition to the to addresses, this will also stop the email being sent to any CC and BCC addresses
                        set for it. Swiftmailer will add additional headers to the email with the overridden addresses in
                        them. These are X-Swift-Cc and X-Swift-Bcc for the CC and BCC addresses respectively.




          Viewing from the Web Debug Toolbar
          You can view any email sent during a single response when you are in the dev environment using the
          Web Debug Toolbar. The email icon in the toolbar will show how many emails were sent. If you click it,
          a report will open showing the details of the sent emails.
          If you're sending an email and then immediately redirecting to another page, the web debug toolbar will
          not display an email icon or a report on the next page.
          Instead, you can set the intercept_redirects option to true in the config_dev.yml file, which will
          cause the redirect to stop and allow you to open the report with details of the sent emails.


                        Alternatively, you can open the profiler after the redirect and search by the submit URL used
                        on previous request (e.g. /contact/handle). The profiler's search feature allows you to load the
                        profiler information for any past requests.

Listing   # app/config/config_dev.yml
 61-4
          web_profiler:
              intercept_redirects: true




          PDF brought to you by                                       Chapter 61: How to Work with Emails During Development | 374
          generated on June 20, 2012
Chapter 62
                                 How to Spool Email

When you are using the SwiftmailerBundle to send an email from a Symfony2 application, it will
default to sending the email immediately. You may, however, want to avoid the performance hit of the
communication between Swiftmailer and the email transport, which could cause the user to wait for
the next page to load while the email is sending. This can be avoided by choosing to "spool" the emails
instead of sending them directly. This means that Swiftmailer does not attempt to send the email but
instead saves the message to somewhere such as a file. Another process can then read from the spool and
take care of sending the emails in the spool. Currently only spooling to file is supported by Swiftmailer.
In order to use the spool, use the following configuration:

# app/config/config.yml                                                                                               Listing
                                                                                                                       62-1
swiftmailer:
    # ...
    spool:
        type: file
        path: /path/to/spool



              If you want to store the spool somewhere with your project directory, remember that you can use
              the %kernel.root_dir% parameter to reference the project's root:

              path: "%kernel.root_dir%/spool"                                                                         Listing
                                                                                                                       62-2




Now, when your app sends an email, it will not actually be sent but instead added to the spool. Sending
the messages from the spool is done separately. There is a console command to send the messages in the
spool:

php app/console swiftmailer:spool:send --env=prod                                                                     Listing
                                                                                                                       62-3

It has an option to limit the number of messages to be sent:

php app/console swiftmailer:spool:send --message-limit=10 --env=prod                                                  Listing
                                                                                                                       62-4

You can also set the time limit in seconds:



PDF brought to you by                                                          Chapter 62: How to Spool Email | 375
generated on June 20, 2012
Listing   php app/console swiftmailer:spool:send --time-limit=10 --env=prod
 62-5

          Of course you will not want to run this manually in reality. Instead, the console command should be
          triggered by a cron job or scheduled task and run at a regular interval.




          PDF brought to you by                                                  Chapter 62: How to Spool Email | 376
          generated on June 20, 2012
Chapter 63
       How to simulate HTTP Authentication in a
                   Functional Test

If your application needs HTTP authentication, pass the username and password as server variables to
createClient():

$client = static::createClient(array(), array(                                                                             Listing
                                                                                                                            63-1
    'PHP_AUTH_USER' => 'username',
    'PHP_AUTH_PW'   => 'pa$$word',
));

You can also override it on a per request basis:

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

When your application is using a form_login, you can simplify your tests by allowing your test
configuration to make use of HTTP authentication. This way you can use the above to authenticate in
tests, but still have your users login via the normal form_login. The trick is to include the http_basic
key in your firewall, along with the form_login key:

# app/config/config_test.yml                                                                                               Listing
                                                                                                                            63-3
security:
    firewalls:
        your_firewall_name:
            http_basic:




PDF brought to you by                         Chapter 63: How to simulate HTTP Authentication in a Functional Test | 377
generated on June 20, 2012
Chapter 64
              How to test the Interaction of several Clients

          If you need to simulate an interaction between different Clients (think of a chat for instance), create
          several Clients:

Listing   $harry = static::createClient();
 64-1
          $sally = static::createClient();

          $harry->request('POST', '/say/sally/Hello');
          $sally->request('GET', '/messages');

          $this->assertEquals(201, $harry->getResponse()->getStatusCode());
          $this->assertRegExp('/Hello/', $sally->getResponse()->getContent());

          This works except when your code maintains a global state or if it depends on a third-party library that
          has some kind of global state. In such a case, you can insulate your clients:

Listing   $harry = static::createClient();
 64-2
          $sally = static::createClient();

          $harry->insulate();
          $sally->insulate();

          $harry->request('POST', '/say/sally/Hello');
          $sally->request('GET', '/messages');

          $this->assertEquals(201, $harry->getResponse()->getStatusCode());
          $this->assertRegExp('/Hello/', $sally->getResponse()->getContent());

          Insulated clients transparently execute their requests in a dedicated and clean PHP process, thus avoiding
          any side-effects.


                        As an insulated client is slower, you can keep one client in the main process, and insulate the other
                        ones.




          PDF brought to you by                                         Chapter 64: How to test the Interaction of several Clients | 378
          generated on June 20, 2012
Chapter 65
      How to use the Profiler in a Functional Test

It's highly recommended that a functional test only tests the Response. But if you write functional tests
that monitor your production servers, you might want to write tests on the profiling data as it gives you
a great way to check various things and enforce some metrics.
The Symfony2 Profiler gathers a lot of data for each request. Use this data to check the number of
database calls, the time spent in the framework, ... But before writing assertions, always check that the
profiler is indeed available (it is enabled by default in the test environment):

class HelloControllerTest extends WebTestCase                                                                                 Listing
                                                                                                                               65-1
{
    public function testIndex()
    {
        $client = static::createClient();
        $crawler = $client->request('GET', '/hello/Fabien');

           // Write some assertions about the Response
           // ...

           // Check that the profiler is enabled
           if ($profile = $client->getProfile()) {
               // check the number of requests
               $this->assertLessThan(10, $profile->getCollector('db')->getQueryCount());

                // check the time spent in the framework
                $this->assertLessThan(0.5, $profile->getCollector('timer')->getTime());
           }
     }
}

If a test fails because of profiling data (too many DB queries for instance), you might want to use the Web
Profiler to analyze the request after the tests finish. It's easy to achieve if you embed the token in the error
message:

$this->assertLessThan(                                                                                                        Listing
                                                                                                                               65-2
    30,
    $profile->get('db')->getQueryCount(),



PDF brought to you by                                        Chapter 65: How to use the Profiler in a Functional Test | 379
generated on June 20, 2012
sprintf('Checks that query count is less than 30 (token %s)', $profile->getToken())
);



              The profiler store can be different depending on the environment (especially if you use the SQLite
              store, which is the default configured one).




              The profiler information is available even if you insulate the client or if you use an HTTP layer for
              your tests.




              Read the API for built-in data collectors to learn more about their interfaces.




PDF brought to you by                                           Chapter 65: How to use the Profiler in a Functional Test | 380
generated on June 20, 2012
Chapter 66
                   How to test Doctrine Repositories

Unit testing Doctrine repositories in a Symfony project is not recommended. When you're dealing with
a repository, you're really dealing with something that's meant to be tested against a real database
connection.
Fortunately, you can easily test your queries against a real database, as described below.



Functional Testing
If you need to actually execute a query, you will need to boot the kernel to get a valid connection. In this
case, you'll extend the WebTestCase, which makes all of this quite easy:

// src/Acme/StoreBundle/Tests/Entity/ProductRepositoryFunctionalTest.php                                                Listing
                                                                                                                         66-1

namespace AcmeStoreBundleTestsEntity;

use SymfonyBundleFrameworkBundleTestWebTestCase;

class ProductRepositoryFunctionalTest extends WebTestCase
{
    /**
     * @var DoctrineORMEntityManager
     */
    private $em;

     public function setUp()
     {
         $kernel = static::createKernel();
         $kernel->boot();
         $this->em = $kernel->getContainer()->get('doctrine.orm.entity_manager');
     }

     public function testProductByCategoryName()
     {
         $results = $this->em
             ->getRepository('AcmeStoreBundle:Product')
             ->searchProductsByNameQuery('foo')

PDF brought to you by                                             Chapter 66: How to test Doctrine Repositories | 381
generated on June 20, 2012
->getResult()
           ;

           $this->assertCount(1, $results);
     }
}




PDF brought to you by                         Chapter 66: How to test Doctrine Repositories | 382
generated on June 20, 2012
Chapter 67
  How to load Security Users from the Database
              (the Entity Provider)

The security layer is one of the smartest tools of Symfony. It handles two things: the authentication
and the authorization processes. Although it may seem difficult to understand how it works internally,
the security system is very flexible and allows you to integrate your application with any authentication
backend, like Active Directory, an OAuth server or a database.



Introduction
This article focuses on how to authenticate users against a database table managed by a Doctrine entity
class. The content of this cookbook entry is split in three parts. The first part is about designing a
Doctrine User entity class and making it usable in the security layer of Symfony. The second part
describes how to easily authenticate a user with the Doctrine EntityUserProvider1 object bundled with
the framework and some configuration. Finally, the tutorial will demonstrate how to create a custom
EntityUserProvider2 object to retrieve users from a database with custom conditions.
This tutorial assumes there is a bootstrapped and loaded AcmeUserBundle bundle in the application
kernel.



The Data Model
For the purpose of this cookbook, the AcmeUserBundle bundle contains a User entity class with the
following fields: id, username, salt, password, email and isActive. The isActive field tells whether
or not the user account is active.
To make it shorter, the getter and setter methods for each have been removed to focus on the most
important methods that come from the UserInterface3.


1. http://api.symfony.com/2.0/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.html
2. http://api.symfony.com/2.0/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.html
3. http://api.symfony.com/2.0/Symfony/Component/Security/Core/User/UserInterface.html


PDF brought to you by                            Chapter 67: How to load Security Users from the Database (the Entity Provider) | 383
generated on June 20, 2012
Listing   // src/Acme/UserBundle/Entity/User.php
 67-1

          namespace AcmeUserBundleEntity;

          use DoctrineORMMapping as ORM;
          use SymfonyComponentSecurityCoreUserUserInterface;

          /**
            * AcmeUserBundleEntityUser
            *
            * @ORMTable(name="acme_users")
            * @ORMEntity(repositoryClass="AcmeUserBundleEntityUserRepository")
            */
          class User implements UserInterface
          {
               /**
                * @ORMColumn(type="integer")
                * @ORMId
                * @ORMGeneratedValue(strategy="AUTO")
                */
               private $id;

               /**
                * @ORMColumn(type="string", length=25, unique=true)
                */
               private $username;

               /**
                * @ORMColumn(type="string", length=32)
                */
               private $salt;

               /**
                * @ORMColumn(type="string", length=40)
                */
               private $password;

               /**
                * @ORMColumn(type="string", length=60, unique=true)
                */
               private $email;

               /**
                * @ORMColumn(name="is_active", type="boolean")
                */
               private $isActive;

               public function __construct()
               {
                   $this->isActive = true;
                   $this->salt = md5(uniqid(null, true));
               }

               /**
                 * @inheritDoc
                 */
               public function getUsername()
               {
                    return $this->username;
               }



          PDF brought to you by                Chapter 67: How to load Security Users from the Database (the Entity Provider) | 384
          generated on June 20, 2012
/**
          * @inheritDoc
          */
        public function getSalt()
        {
             return $this->salt;
        }

        /**
          * @inheritDoc
          */
        public function getPassword()
        {
             return $this->password;
        }

        /**
          * @inheritDoc
          */
        public function getRoles()
        {
             return array('ROLE_USER');
        }

        /**
          * @inheritDoc
          */
        public function eraseCredentials()
        {
        }

        /**
          * @inheritDoc
          */
        public function equals(UserInterface $user)
        {
             return $this->username === $user->getUsername();
        }
}

In order to use an instance of the AcmeUserBundle:User class in the Symfony security layer, the entity
class must implement the UserInterface4. This interface forces the class to implement the six following
methods:

    •   getUsername()
    •   getSalt()
    •   getPassword()
    •   getRoles()
    •   eraseCredentials()
    •   equals()

For more details on each of these, see UserInterface5.
To keep it simple, the equals() method just compares the username field but it's also possible to do more
checks depending on the complexity of your data model. On the other hand, the eraseCredentials()
method remains empty as we don't care about it in this tutorial.



4. http://api.symfony.com/2.0/Symfony/Component/Security/Core/User/UserInterface.html
5. http://api.symfony.com/2.0/Symfony/Component/Security/Core/User/UserInterface.html


PDF brought to you by                            Chapter 67: How to load Security Users from the Database (the Entity Provider) | 385
generated on June 20, 2012
Below is an export of my User table from MySQL. For details on how to create user records and encode
          their password, see Encoding the User's Password.

Listing   mysql> select * from user;
 67-2
          +----+----------+----------------------------------+------------------------------------------+----------------
          | id | username | salt                             | password
          | email              | is_active |
          +----+----------+----------------------------------+------------------------------------------+----------------
          | 1 | hhamon | 7308e59b97f6957fb42d66f894793079 | 09610f61637408828a35d7debee5b38a8350eebe
          | hhamon@example.com |         1 |
          | 2 | jsmith | ce617a6cca9126bf4036ca0c02e82dee | 8390105917f3a3d533815250ed7c64b4594d7ebf
          | jsmith@example.com |         1 |
          | 3 | maxime | cd01749bb995dc658fa56ed45458d807 | 9764731e5f7fb944de5fd8efad4949b995b72a3c
          | maxime@example.com |         0 |
          | 4 | donald | 6683c2bfd90c0426088402930cadd0f8 | 5c3bcec385f59edcc04490d1db95fdb8673bf612
          | donald@example.com |         1 |
          +----+----------+----------------------------------+------------------------------------------+----------------
          4 rows in set (0.00 sec)

          The database now contains four users with different usernames, emails and statuses. The next part will
          focus on how to authenticate one of these users thanks to the Doctrine entity user provider and a couple
          of lines of configuration.



          Authenticating Someone against a Database
          Authenticating a Doctrine user against the database with the Symfony security layer is a piece of cake.
          Everything resides in the configuration of the SecurityBundle stored in the app/config/security.yml
          file.
          Below is an example of configuration where the user will enter his/her username and password via
          HTTP basic authentication. That information will then be checked against our User entity records in the
          database:

Listing   # app/config/security.yml
 67-3

          security:
              encoders:
                  AcmeUserBundleEntityUser:
                      algorithm:        sha1
                      encode_as_base64: false
                      iterations:       1

               role_hierarchy:
                   ROLE_ADMIN:       ROLE_USER
                   ROLE_SUPER_ADMIN: [ ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH ]

               providers:
                   administrators:
                       entity: { class: AcmeUserBundle:User, property: username }

               firewalls:
                   admin_area:
                       pattern:    ^/admin
                       http_basic: ~

               access_control:
                   - { path: ^/admin, roles: ROLE_ADMIN }




          PDF brought to you by                 Chapter 67: How to load Security Users from the Database (the Entity Provider) | 386
          generated on June 20, 2012
The encoders section associates the sha1 password encoder to the entity class. This means that Symfony
will expect the password that's stored in the database to be encoded using this algorithm. For details on
how to create a new User object with a properly encoded password, see the Encoding the User's Password
section of the security chapter.
The providers section defines an administrators user provider. A user provider is a "source" of where
users are loaded during authentication. In this case, the entity keyword means that Symfony will use
the Doctrine entity user provider to load User entity objects from the database by using the username
unique field. In other words, this tells Symfony how to fetch the user from the database before checking
the password validity.
This code and configuration works but it's not enough to secure the application for active users. As of
now, we still can authenticate with maxime. The next section explains how to forbid non active users.



Forbid non Active Users
The easiest way to exclude non active users is to implement the AdvancedUserInterface6 interface
that takes care of checking the user's account status. The AdvancedUserInterface7 extends the
UserInterface8 interface, so you just need to switch to the new interface in the AcmeUserBundle:User
entity class to benefit from simple and advanced authentication behaviors.
The AdvancedUserInterface9 interface adds four extra methods to validate the account status:

  •   isAccountNonExpired() checks whether the user's account has expired,
  •   isAccountNonLocked() checks whether the user is locked,
  •   isCredentialsNonExpired() checks whether the user's credentials (password) has expired,
  •   isEnabled() checks whether the user is enabled.

For this example, the first three methods will return true whereas the isEnabled() method will return
the boolean value in the isActive field.

// src/Acme/UserBundle/Entity/User.php                                                                                                  Listing
                                                                                                                                         67-4

namespace AcmeBundleUserBundleEntity;

// ...
use SymfonyComponentSecurityCoreUserAdvancedUserInterface;

// ...
class User implements AdvancedUserInterface
{
    // ...
    public function isAccountNonExpired()
    {
        return true;
    }

      public function isAccountNonLocked()
      {
          return true;
      }

      public function isCredentialsNonExpired()
      {


6. http://api.symfony.com/2.0/Symfony/Component/Security/Core/User/AdvancedUserInterface.html
7. http://api.symfony.com/2.0/Symfony/Component/Security/Core/User/AdvancedUserInterface.html
8. http://api.symfony.com/2.0/Symfony/Component/Security/Core/User/UserInterface.html
9. http://api.symfony.com/2.0/Symfony/Component/Security/Core/User/AdvancedUserInterface.html


PDF brought to you by                            Chapter 67: How to load Security Users from the Database (the Entity Provider) | 387
generated on June 20, 2012
return true;
                }

                public function isEnabled()
                {
                    return $this->isActive;
                }
          }

          If we try to authenticate a maxime, the access is now forbidden as this user does not have an enabled
          account. The next session will focus on how to write a custom entity provider to authenticate a user with
          his username or his email address.



          Authenticating Someone with a Custom Entity Provider
          The next step is to allow a user to authenticate with his username or his email address as they are both
          unique in the database. Unfortunately, the native entity provider is only able to handle a single property
          to fetch the user from the database.
          To accomplish this, create a custom entity provider that looks for a user whose username or email field
          matches the submitted login username. The good news is that a Doctrine repository object can act as
          an entity user provider if it implements the UserProviderInterface10. This interface comes with three
          methods to implement: loadUserByUsername($username), refreshUser(UserInterface $user), and
          supportsClass($class). For more details, see UserProviderInterface11.
          The code below shows the implementation of the UserProviderInterface12 in the UserRepository
          class:

Listing   // src/Acme/UserBundle/Entity/UserRepository.php
 67-5

          namespace AcmeUserBundleEntity;

          use   SymfonyComponentSecurityCoreUserUserInterface;
          use   SymfonyComponentSecurityCoreUserUserProviderInterface;
          use   SymfonyComponentSecurityCoreExceptionUsernameNotFoundException;
          use   SymfonyComponentSecurityCoreExceptionUnsupportedUserException;
          use   DoctrineORMEntityRepository;
          use   DoctrineORMNoResultException;

          class UserRepository extends EntityRepository implements UserProviderInterface
          {
              public function loadUserByUsername($username)
              {
                  $q = $this
                      ->createQueryBuilder('u')
                      ->where('u.username = :username OR u.email = :email')
                      ->setParameter('username', $username)
                      ->setParameter('email', $username)
                      ->getQuery()
                  ;

                     try {
                         // The Query::getSingleResult() method throws an exception
                         // if there is no record matching the criteria.
                         $user = $q->getSingleResult();


          10. http://api.symfony.com/2.0/Symfony/Component/Security/Core/User/UserProviderInterface.html
          11. http://api.symfony.com/2.0/Symfony/Component/Security/Core/User/UserProviderInterface.html
          12. http://api.symfony.com/2.0/Symfony/Component/Security/Core/User/UserProviderInterface.html


          PDF brought to you by                            Chapter 67: How to load Security Users from the Database (the Entity Provider) | 388
          generated on June 20, 2012
} catch (NoResultException $e) {
            throw new UsernameNotFoundException(sprintf('Unable to find an active admin
AcmeUserBundle:User object identified by "%s".', $username), null, 0, $e);
        }

           return $user;
     }

    public function refreshUser(UserInterface $user)
    {
        $class = get_class($user);
        if (!$this->supportsClass($class)) {
            throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.',
$class));
        }

           return $this->loadUserByUsername($user->getUsername());
     }

    public function supportsClass($class)
    {
        return $this->getEntityName() === $class || is_subclass_of($class,
$this->getEntityName());
    }
}

To finish the implementation, the configuration of the security layer must be changed to tell Symfony to
use the new custom entity provider instead of the generic Doctrine entity provider. It's trival to achieve
by removing the property field in the security.providers.administrators.entity section of the
security.yml file.

# app/config/security.yml                                                                                                          Listing
                                                                                                                                    67-6
security:
    # ...
    providers:
        administrators:
            entity: { class: AcmeUserBundle:User }
    # ...

By doing this, the security layer will use an instance of UserRepository and call its
loadUserByUsername() method to fetch a user from the database whether he filled in his username or
email address.



Managing Roles in the Database
The end of this tutorial focuses on how to store and retrieve a list of roles from the database. As
mentioned previously, when your user is loaded, its getRoles() method returns the array of security
roles that should be assigned to the user. You can load this data from anywhere - a hardcoded list
used for all users (e.g. array('ROLE_USER')), a Doctrine array property called roles, or via a Doctrine
relationship, as we'll learn about in this section.


              In a typical setup, you should always return at least 1 role from the getRoles() method. By
              convention, a role called ROLE_USER is usually returned. If you fail to return any roles, it may appear
              as if your user isn't authenticated at all.




PDF brought to you by                       Chapter 67: How to load Security Users from the Database (the Entity Provider) | 389
generated on June 20, 2012
In this example, the AcmeUserBundle:User entity class defines a many-to-many relationship with a
          AcmeUserBundle:Group entity class. A user can be related to several groups and a group can be composed
          of one or more users. As a group is also a role, the previous getRoles() method now returns the list of
          related groups:

Listing   // src/Acme/UserBundle/Entity/User.php
 67-7

          namespace AcmeBundleUserBundleEntity;

          use DoctrineCommonCollectionsArrayCollection;

          // ...
          class User implements AdvancedUserInterface
          {
              /**
               * @ORMManyToMany(targetEntity="Group", inversedBy="users")
               *
               */
              private $groups;

               public function __construct()
               {
                   $this->groups = new ArrayCollection();
               }

               // ...

               public function getRoles()
               {
                   return $this->groups->toArray();
               }
          }

          The AcmeUserBundle:Group entity class defines three table fields (id, name and role). The unique role
          field contains the role name used by the Symfony security layer to secure parts of the application.
          The most important thing to notice is that the AcmeUserBundle:Group entity class implements the
          RoleInterface13 that forces it to have a getRole() method:

Listing   namespace AcmeBundleUserBundleEntity;
 67-8

          use SymfonyComponentSecurityCoreRoleRoleInterface;
          use DoctrineCommonCollectionsArrayCollection;
          use DoctrineORMMapping as ORM;

          /**
            * @ORMTable(name="acme_groups")
            * @ORMEntity()
            */
          class Group implements RoleInterface
          {
               /**
                * @ORMColumn(name="id", type="integer")
                * @ORMId()
                * @ORMGeneratedValue(strategy="AUTO")
                */
               private $id;

               /**


          13. http://api.symfony.com/2.0/Symfony/Component/Security/Core/Role/RoleInterface.html


          PDF brought to you by                            Chapter 67: How to load Security Users from the Database (the Entity Provider) | 390
          generated on June 20, 2012
* @ORMColumn(name="name", type="string", length=30)
      */
     private $name;

     /**
      * @ORMColumn(name="role", type="string", length=20, unique=true)
      */
     private $role;

     /**
      * @ORMManyToMany(targetEntity="User", mappedBy="groups")
      */
     private $users;

     public function __construct()
     {
         $this->users = new ArrayCollection();
     }

     // ... getters and setters for each property

     /**
       * @see RoleInterface
       */
     public function getRole()
     {
          return $this->role;
     }
}

To improve performances and avoid lazy loading of groups when retrieving a user from the custom
entity provider, the best solution is to join the groups relationship in the
UserRepository::loadUserByUsername() method. This will fetch the user and his associated roles /
groups with a single query:

// src/Acme/UserBundle/Entity/UserRepository.php                                                                           Listing
                                                                                                                            67-9

namespace AcmeBundleUserBundleEntity;

// ...

class UserRepository extends EntityRepository implements UserProviderInterface
{
    public function loadUserByUsername($username)
    {
        $q = $this
            ->createQueryBuilder('u')
            ->select('u, g')
            ->leftJoin('u.groups', 'g')
            ->where('u.username = :username OR u.email = :email')
            ->setParameter('username', $username)
            ->setParameter('email', $username)
            ->getQuery()
        ;

           // ...
     }

     // ...
}



PDF brought to you by               Chapter 67: How to load Security Users from the Database (the Entity Provider) | 391
generated on June 20, 2012
The QueryBuilder::leftJoin() method joins and fetches related groups from                                          the
AcmeUserBundle:User model class when a user is retrieved with his email address or username.




PDF brought to you by               Chapter 67: How to load Security Users from the Database (the Entity Provider) | 392
generated on June 20, 2012
Chapter 68
                 How to add "Remember Me" Login
                           Functionality

Once a user is authenticated, their credentials are typically stored in the session. This means that when
the session ends they will be logged out and have to provide their login details again next time they wish
to access the application. You can allow users to choose to stay logged in for longer than the session lasts
using a cookie with the remember_me firewall option. The firewall needs to have a secret key configured,
which is used to encrypt the cookie's content. It also has several options with default values which are
shown here:

# app/config/security.yml                                                                                               Listing
                                                                                                                         68-1

firewalls:
    main:
        remember_me:
            key:             "%secret%"
            lifetime:        3600
            path:            /
            domain:          ~ # Defaults to the current domain from $_SERVER

It's a good idea to provide the user with the option to use or not use the remember me functionality, as
it will not always be appropriate. The usual way of doing this is to add a checkbox to the login form. By
giving the checkbox the name _remember_me, the cookie will automatically be set when the checkbox is
checked and the user successfully logs in. So, your specific login form might ultimately look like this:

{# src/Acme/SecurityBundle/Resources/views/Security/login.html.twig #}                                                  Listing
                                                                                                                         68-2
{% 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" />


PDF brought to you by                                  Chapter 68: How to add "Remember Me" Login Functionality | 393
generated on June 20, 2012
<input type="checkbox" id="remember_me" name="_remember_me" checked />
               <label for="remember_me">Keep me logged in</label>

              <input type="submit" name="login" />
          </form>

          The user will then automatically be logged in on subsequent visits while the cookie remains valid.



          Forcing the User to Re-authenticate before accessing certain Resources
          When the user returns to your site, he/she is authenticated automatically based on the information stored
          in the remember me cookie. This allows the user to access protected resources as if the user had actually
          authenticated upon visiting the site.
          In some cases, however, you may want to force the user to actually re-authenticate before accessing
          certain resources. For example, you might allow "remember me" users to see basic account information,
          but then require them to actually re-authenticate before modifying that information.
          The security component provides an easy way to do this. In addition to roles explicitly assigned to them,
          users are automatically given one of the following roles depending on how they are authenticated:

            • IS_AUTHENTICATED_ANONYMOUSLY - automatically assigned to a user who is in a firewall
              protected part of the site but who has not actually logged in. This is only possible if anonymous
              access has been allowed.
            • IS_AUTHENTICATED_REMEMBERED - automatically assigned to a user who was authenticated via
              a remember me cookie.
            • IS_AUTHENTICATED_FULLY - automatically assigned to a user that has provided their login
              details during the current session.

          You can use these to control access beyond the explicitly assigned roles.


                        If you have the IS_AUTHENTICATED_REMEMBERED role, then you also have the
                        IS_AUTHENTICATED_ANONYMOUSLY role. If you have the IS_AUTHENTICATED_FULLY role, then you
                        also have the other two roles. In other words, these roles represent three levels of increasing
                        "strength" of authentication.

          You can use these additional roles for finer grained control over access to parts of a site. For example,
          you may want your user to be able to view their account at /account when authenticated by cookie but
          to have to provide their login details to be able to edit the account details. You can do this by securing
          specific controller actions using these roles. The edit action in the controller could be secured using the
          service context.
          In the following example, the action is only allowed if the user has the IS_AUTHENTICATED_FULLY role.

Listing   use SymfonyComponentSecurityCoreExceptionAccessDeniedException
 68-3
          // ...

          public function editAction()
          {
              if (false === $this->get('security.context')->isGranted(
                  'IS_AUTHENTICATED_FULLY'
              )) {
                  throw new AccessDeniedException();
              }




          PDF brought to you by                                   Chapter 68: How to add "Remember Me" Login Functionality | 394
          generated on June 20, 2012
// ...
}

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

use JMSSecurityExtraBundleAnnotationSecure;                                                                             Listing
                                                                                                                            68-4

/**
  * @Secure(roles="IS_AUTHENTICATED_FULLY")
  */
public function editAction($name)
{
     // ...
}



              If you also had an access control in your security configuration that required the user to have a
              ROLE_USER role in order to access any of the account area, then you'd have the following situation:

                 • If a non-authenticated (or anonymously authenticated user) tries to access the account
                   area, the user will be asked to authenticate.
                 • Once the user has entered his username and password, assuming the user receives
                   the ROLE_USER role per your configuration, the user will have the
                   IS_AUTHENTICATED_FULLY role and be able to access any page in the account section,
                   including the editAction controller.
                 • If the user's session ends, when the user returns to the site, he will be able to access
                   every account page - except for the edit page - without being forced to re-authenticate.
                   However, when he tries to access the editAction controller, he will be forced to re-
                   authenticate, since he is not, yet, fully authenticated.


For more information on securing services or methods in this way, see How to secure any Service or
Method in your Application.




1. https://github.com/schmittjoh/JMSSecurityExtraBundle


PDF brought to you by                                     Chapter 68: How to add "Remember Me" Login Functionality | 395
generated on June 20, 2012
Chapter 69
           How to implement your own Voter to blacklist
                         IP Addresses

          The Symfony2 security component provides several layers to authenticate users. One of the layers is
          called a voter. A voter is a dedicated class that checks if the user has the rights to be connected to the
          application. For instance, Symfony2 provides a layer that checks if the user is fully authenticated or if it
          has some expected roles.
          It is sometimes useful to create a custom voter to handle a specific case not handled by the framework.
          In this section, you'll learn how to create a voter that will allow you to blacklist users by their IP.



          The Voter Interface
          A custom voter must implement VoterInterface1, which requires the following three methods:

Listing   interface VoterInterface
 69-1
          {
              function supportsAttribute($attribute);
              function supportsClass($class);
              function vote(TokenInterface $token, $object, array $attributes);
          }

          The supportsAttribute() method is used to check if the voter supports the given user attribute (i.e: a
          role, an acl, etc.).
          The supportsClass() method is used to check if the voter supports the current user token class.
          The vote() method must implement the business logic that verifies whether or not the user is granted
          access. This method must return one of the following values:

            • VoterInterface::ACCESS_GRANTED: The user is allowed to access the application
            • VoterInterface::ACCESS_ABSTAIN: The voter cannot decide if the user is granted or not
            • VoterInterface::ACCESS_DENIED: The user is not allowed to access the application


          1. http://api.symfony.com/2.0/Symfony/Component/Security/Core/Authorization/Voter/VoterInterface.html


          PDF brought to you by                                   Chapter 69: How to implement your own Voter to blacklist IP Addresses | 396
          generated on June 20, 2012
In this example, we will check if the user's IP address matches against a list of blacklisted addresses. If
the user's IP is blacklisted, we will return VoterInterface::ACCESS_DENIED, otherwise we will return
VoterInterface::ACCESS_ABSTAIN as this voter's purpose is only to deny access, not to grant access.



Creating a Custom Voter
To blacklist a user based on its IP, we can use the request service and compare the IP address against a
set of blacklisted IP addresses:

namespace AcmeDemoBundleSecurityAuthorizationVoter;                                                                      Listing
                                                                                                                              69-2

use SymfonyComponentDependencyInjectionContainerInterface;
use SymfonyComponentSecurityCoreAuthorizationVoterVoterInterface;
use SymfonyComponentSecurityCoreAuthenticationTokenTokenInterface;

class ClientIpVoter implements VoterInterface
{
    public function __construct(ContainerInterface $container, array $blacklistedIp = array())
    {
        $this->container     = $container;
        $this->blacklistedIp = $blacklistedIp;
    }

     public function supportsAttribute($attribute)
     {
         // we won't check against a user attribute, so we return true
         return true;
     }

     public function supportsClass($class)
     {
         // our voter supports all type of token classes, so we return true
         return true;
     }

     function vote(TokenInterface $token, $object, array $attributes)
     {
         $request = $this->container->get('request');
         if (in_array($request->getClientIp(), $this->blacklistedIp)) {
             return VoterInterface::ACCESS_DENIED;
         }

           return VoterInterface::ACCESS_ABSTAIN;
     }
}

That's it! The voter is done. The next step is to inject the voter into the security layer. This can be done
easily through the service container.



Declaring the Voter as a Service
To inject the voter into the security layer, we must declare it as a service, and tag it as a "security.voter":

# src/Acme/AcmeBundle/Resources/config/services.yml                                                                          Listing
                                                                                                                              69-3

services:
    security.access.blacklist_voter:

PDF brought to you by                          Chapter 69: How to implement your own Voter to blacklist IP Addresses | 397
generated on June 20, 2012
class:     AcmeDemoBundleSecurityAuthorizationVoterClientIpVoter
                     arguments: [@service_container, [123.123.123.123, 171.171.171.171]]
                     public:    false
                     tags:
                         -      { name: security.voter }



                        Be sure to import this configuration file from your main application configuration file (e.g. app/
                        config/config.yml). For more information see Importing Configuration with imports. To read
                        more about defining services in general, see the Service Container chapter.



          Changing the Access Decision Strategy
          In order for the new voter to take effect, we need to change the default access decision strategy, which,
          by default, grants access if any voter grants access.
          In our case, we will choose the unanimous strategy. Unlike the affirmative strategy (the default), with
          the unanimous strategy, if only one voter denies access (e.g. the ClientIpVoter), access is not granted to
          the end user.
          To do that, override the default access_decision_manager section of your application configuration file
          with the following code.

Listing   # app/config/security.yml
 69-4
          security:
              access_decision_manager:
                  # Strategy can be: affirmative, unanimous or consensus
                  strategy: unanimous

          That's it! Now, when deciding whether or not a user should have access, the new voter will deny access
          to any user in the list of blacklisted IPs.




          PDF brought to you by                            Chapter 69: How to implement your own Voter to blacklist IP Addresses | 398
          generated on June 20, 2012
Chapter 70
                             Access Control Lists (ACLs)

In complex applications, you will often face the problem that access decisions cannot only be based
on the person (Token) who is requesting access, but also involve a domain object that access is being
requested for. This is where the ACL system comes in.
Imagine you are designing a blog system where your users can comment on your posts. Now, you want a
user to be able to edit his own comments, but not those of other users; besides, you yourself want to be
able to edit all comments. In this scenario, Comment would be our domain object that you want to restrict
access to. You could take several approaches to accomplish this using Symfony2, two basic approaches
are (non-exhaustive):

  • Enforce security in your business methods: Basically, that means keeping a reference inside each
    Comment to all users who have access, and then compare these users to the provided Token.
  • Enforce security with roles: In this approach, you would add a role for each Comment object, i.e.
    ROLE_COMMENT_1, ROLE_COMMENT_2, etc.

Both approaches are perfectly valid. However, they couple your authorization logic to your business code
which makes it less reusable elsewhere, and also increases the difficulty of unit testing. Besides, you could
run into performance issues if many users would have access to a single domain object.
Fortunately, there is a better way, which we will talk about now.



Bootstrapping
Now, before we finally can get into action, we need to do some bootstrapping. First, we need to configure
the connection the ACL system is supposed to use:

# app/config/security.yml                                                                                               Listing
                                                                                                                         70-1
security:
    acl:
        connection: default




PDF brought to you by                                                   Chapter 70: Access Control Lists (ACLs) | 399
generated on June 20, 2012
The ACL system requires at least one Doctrine DBAL connection to be configured. However, that
                        does not mean that you have to use Doctrine for mapping your domain objects. You can use
                        whatever mapper you like for your objects, be it Doctrine ORM, Mongo ODM, Propel, or raw
                        SQL, the choice is yours.

          After the connection is configured, we have to import the database structure. Fortunately, we have a task
          for this. Simply run the following command:

Listing   php app/console init:acl
 70-2




          Getting Started
          Coming back to our small example from the beginning, let's implement ACL for it.

          Creating an ACL, and adding an ACE
Listing   use SymfonyComponentSecurityCoreExceptionAccessDeniedException;
 70-3
          use SymfonyComponentSecurityAclDomainObjectIdentity;
          use SymfonyComponentSecurityAclDomainUserSecurityIdentity;
          use SymfonyComponentSecurityAclPermissionMaskBuilder;
          // ...

          // BlogController.php
          public function addCommentAction(Post $post)
          {
              $comment = new Comment();

               // setup $form, and bind data
               // ...

               if ($form->isValid()) {
                   $entityManager = $this->get('doctrine.orm.default_entity_manager');
                   $entityManager->persist($comment);
                   $entityManager->flush();

                     // creating the ACL
                     $aclProvider = $this->get('security.acl.provider');
                     $objectIdentity = ObjectIdentity::fromDomainObject($comment);
                     $acl = $aclProvider->createAcl($objectIdentity);

                     // retrieving the security identity of the currently logged-in user
                     $securityContext = $this->get('security.context');
                     $user = $securityContext->getToken()->getUser();
                     $securityIdentity = UserSecurityIdentity::fromAccount($user);

                     // grant owner access
                     $acl->insertObjectAce($securityIdentity, MaskBuilder::MASK_OWNER);
                     $aclProvider->updateAcl($acl);
               }
          }

          There are a couple of important implementation decisions in this code snippet. For now, I only want to
          highlight two:
          First, you may have noticed that ->createAcl() does not accept domain objects directly, but only
          implementations of the ObjectIdentityInterface. This additional step of indirection allows you to


          PDF brought to you by                                                  Chapter 70: Access Control Lists (ACLs) | 400
          generated on June 20, 2012
work with ACLs even when you have no actual domain object instance at hand. This will be extremely
helpful if you want to check permissions for a large number of objects without actually hydrating these
objects.
The other interesting part is the ->insertObjectAce() call. In our example, we are granting the user who
is currently logged in owner access to the Comment. The MaskBuilder::MASK_OWNER is a pre-defined
integer bitmask; don't worry the mask builder will abstract away most of the technical details, but using
this technique we can store many different permissions in one database row which gives us a considerable
boost in performance.


              The order in which ACEs are checked is significant. As a general rule, you should place more
              specific entries at the beginning.



Checking Access
// BlogController.php                                                                                                  Listing
                                                                                                                        70-4
public function editCommentAction(Comment $comment)
{
    $securityContext = $this->get('security.context');

     // check for edit access
     if (false === $securityContext->isGranted('EDIT', $comment))
     {
         throw new AccessDeniedException();
     }

     // retrieve actual comment object, and do your editing here
     // ...
}

In this example, we check whether the user has the EDIT permission. Internally, Symfony2 maps the
permission to several integer bitmasks, and checks whether the user has any of them.


              You can define up to 32 base permissions (depending on your OS PHP might vary between 30 to
              32). In addition, you can also define cumulative permissions.




Cumulative Permissions
In our first example above, we only granted the user the OWNER base permission. While this effectively
also allows the user to perform any operation such as view, edit, etc. on the domain object, there are
cases where we want to grant these permissions explicitly.
The MaskBuilder can be used for creating bit masks easily by combining several base permissions:

$builder = new MaskBuilder();                                                                                          Listing
                                                                                                                        70-5
$builder
    ->add('view')
    ->add('edit')
    ->add('delete')
    ->add('undelete')
;
$mask = $builder->get(); // int(15)



PDF brought to you by                                                  Chapter 70: Access Control Lists (ACLs) | 401
generated on June 20, 2012
This integer bitmask can then be used to grant a user the base permissions you added above:

Listing   $acl->insertObjectAce(new UserSecurityIdentity('johannes'), $mask);
 70-6

          The user is now allowed to view, edit, delete, and un-delete objects.




          PDF brought to you by                                                   Chapter 70: Access Control Lists (ACLs) | 402
          generated on June 20, 2012
Chapter 71
                             Advanced ACL Concepts

The aim of this chapter is to give a more in-depth view of the ACL system, and also explain some of the
design decisions behind it.



Design Concepts
Symfony2's object instance security capabilities are based on the concept of an Access Control List. Every
domain object instance has its own ACL. The ACL instance holds a detailed list of Access Control
Entries (ACEs) which are used to make access decisions. Symfony2's ACL system focuses on two main
objectives:

  • providing a way to efficiently retrieve a large amount of ACLs/ACEs for your domain objects,
    and to modify them;
  • providing a way to easily make decisions of whether a person is allowed to perform an action
    on a domain object or not.

As indicated by the first point, one of the main capabilities of Symfony2's ACL system is a high-
performance way of retrieving ACLs/ACEs. This is extremely important since each ACL might have
several ACEs, and inherit from another ACL in a tree-like fashion. Therefore, we specifically do not
leverage any ORM, but the default implementation interacts with your connection directly using
Doctrine's DBAL.

Object Identities
The ACL system is completely decoupled from your domain objects. They don't even have to be stored
in the same database, or on the same server. In order to achieve this decoupling, in the ACL system your
objects are represented through object identity objects. Everytime, you want to retrieve the ACL for a
domain object, the ACL system will first create an object identity from your domain object, and then pass
this object identity to the ACL provider for further processing.

Security Identities
This is analog to the object identity, but represents a user, or a role in your application. Each role, or user
has its own security identity.

PDF brought to you by                                                      Chapter 71: Advanced ACL Concepts | 403
generated on June 20, 2012
Database Table Structure
The default implementation uses five database tables as listed below. The tables are ordered from least
rows to most rows in a typical application:

  • acl_security_identities: This table records all security identities (SID) which hold ACEs. The
    default implementation ships with two security identities: RoleSecurityIdentity, and
    UserSecurityIdentity
  • acl_classes: This table maps class names to a unique id which can be referenced from other
    tables.
  • acl_object_identities: Each row in this table represents a single domain object instance.
  • acl_object_identity_ancestors: This table allows us to determine all the ancestors of an ACL in
    a very efficient way.
  • acl_entries: This table contains all ACEs. This is typically the table with the most rows. It can
    contain tens of millions without significantly impacting performance.



Scope of Access Control Entries
Access control entries can have different scopes in which they apply. In Symfony2, we have basically two
different scopes:

  • Class-Scope: These entries apply to all objects with the same class.
  • Object-Scope: This was the scope we solely used in the previous chapter, and it only applies to
    one specific object.

Sometimes, you will find the need to apply an ACE only to a specific field of the object. Let's say you want
the ID only to be viewable by an administrator, but not by your customer service. To solve this common
problem, we have added two more sub-scopes:

  • Class-Field-Scope: These entries apply to all objects with the same class, but only to a specific
    field of the objects.
  • Object-Field-Scope: These entries apply to a specific object, and only to a specific field of that
    object.



Pre-Authorization Decisions
For pre-authorization decisions, that is decisions before any method, or secure action is invoked, we rely
on the proven AccessDecisionManager service that is also used for reaching authorization decisions based
on roles. Just like roles, the ACL system adds several new attributes which may be used to check for
different permissions.

Built-in Permission Map

 Attribute                   Intended Meaning                     Integer Bitmasks
 VIEW                        Whether someone is allowed to        VIEW, EDIT, OPERATOR,
                             view the domain object.              MASTER, or OWNER
 EDIT                        Whether someone is allowed to      EDIT, OPERATOR, MASTER, or
                             make changes to the domain object. OWNER
 CREATE                      Whether someone is allowed to        CREATE, OPERATOR, MASTER, or
                             create the domain object.            OWNER



PDF brought to you by                                                    Chapter 71: Advanced ACL Concepts | 404
generated on June 20, 2012
Attribute                        Intended Meaning                      Integer Bitmasks
 DELETE                           Whether someone is allowed to         DELETE, OPERATOR, MASTER, or
                                  delete the domain object.             OWNER
 UNDELETE                         Whether someone is allowed to         UNDELETE, OPERATOR,
                                  restore a previously deleted domain   MASTER, or OWNER
                                  object.
 OPERATOR                         Whether someone is allowed to         OPERATOR, MASTER, or OWNER
                                  perform all of the above actions.
 MASTER                           Whether someone is allowed to         MASTER, or OWNER
                                  perform all of the above actions,
                                  and in addition is allowed to grant
                                  any of the above permissions to
                                  others.
 OWNER                            Whether someone owns the              OWNER
                                  domain object. An owner can
                                  perform any of the above actions
                                  and grant master and owner
                                  permissions.


Permission Attributes vs. Permission Bitmasks
Attributes are used by the AccessDecisionManager, just like roles are attributes used by the
AccessDecisionManager. Often, these attributes represent in fact an aggregate of integer bitmasks.
Integer bitmasks on the other hand, are used by the ACL system internally to efficiently store your users'
permissions in the database, and perform access checks using extremely fast bitmask operations.

Extensibility
The above permission map is by no means static, and theoretically could be completely replaced at will.
However, it should cover most problems you encounter, and for interoperability with other bundles, we
encourage you to stick to the meaning we have envisaged for them.



Post Authorization Decisions
Post authorization decisions are made after a secure method has been invoked, and typically involve the
domain object which is returned by such a method. After invocation providers also allow to modify, or
filter the domain object before it is returned.
Due to current limitations of the PHP language, there are no post-authorization capabilities build into the
core Security component. However, there is an experimental JMSSecurityExtraBundle1 which adds these
capabilities. See its documentation for further information on how this is accomplished.



Process for Reaching Authorization Decisions
The ACL class provides two methods for determining whether a security identity has the required
bitmasks, isGranted and isFieldGranted. When the ACL receives an authorization request through
one of these methods, it delegates this request to an implementation of PermissionGrantingStrategy. This


1. https://github.com/schmittjoh/JMSSecurityExtraBundle


PDF brought to you by                                                        Chapter 71: Advanced ACL Concepts | 405
generated on June 20, 2012
allows you to replace the way access decisions are reached without actually modifying the ACL class
itself.
The PermissionGrantingStrategy first checks all your object-scope ACEs if none is applicable, the class-
scope ACEs will be checked, if none is applicable, then the process will be repeated with the ACEs of the
parent ACL. If no parent ACL exists, an exception will be thrown.




PDF brought to you by                                                  Chapter 71: Advanced ACL Concepts | 406
generated on June 20, 2012
Chapter 72
 How to force HTTPS or HTTP for Different URLs

You can force areas of your site to use the HTTPS protocol in the security config. This is done through the
access_control rules using the requires_channel option. For example, if you want to force all URLs
starting with /secure to use HTTPS then you could use the following config:

access_control:                                                                                                         Listing
                                                                                                                         72-1
    - path: ^/secure
      roles: ROLE_ADMIN
      requires_channel: https

The login form itself needs to allow anonymous access otherwise users will be unable to authenticate.
To force it to use HTTPS you can still use access_control rules by using the
IS_AUTHENTICATED_ANONYMOUSLY role:

access_control:                                                                                                         Listing
                                                                                                                         72-2
    - path: ^/login
      roles: IS_AUTHENTICATED_ANONYMOUSLY
      requires_channel: https

It is also possible to specify using HTTPS in the routing configuration see How to force routes to always use
HTTPS or HTTP for more details.




PDF brought to you by                                 Chapter 72: How to force HTTPS or HTTP for Different URLs | 407
generated on June 20, 2012
Chapter 73
                           How to customize your Form Login

          Using a form login for authentication is a common, and flexible, method for handling authentication in
          Symfony2. Pretty much every aspect of the form login can be customized. The full, default configuration
          is shown in the next section.



          Form Login Configuration Reference
Listing   # app/config/security.yml
 73-1
          security:
              firewalls:
                  main:
                      form_login:
                          # the user is redirected here when he/she needs to login
                          login_path:                     /login

                                # if true, forward the user to the login form instead of redirecting
                                use_forward:                    false

                                # submit the login form here
                                check_path:                    /login_check

                                # by default, the login form *must* be a POST, not a GET
                                post_only:                      true

                                # login success redirecting options (read further below)
                                always_use_default_target_path: false
                                default_target_path:            /
                                target_path_parameter:          _target_path
                                use_referer:                    false

                                # login failure redirecting options (read further below)
                                failure_path:                   null
                                failure_forward:                false

                                # field names for the username and password fields
                                username_parameter:             _username


          PDF brought to you by                                           Chapter 73: How to customize your Form Login | 408
          generated on June 20, 2012
password_parameter:             _password

                      # csrf token options
                      csrf_parameter:                 _csrf_token
                      intention:                      authenticate



Redirecting after Success
You can change where the login form redirects after a successful login using the various config options.
By default the form will redirect to the URL the user requested (i.e. the URL which triggered the login
form being shown). For example, if the user requested http://www.example.com/admin/post/18/edit
then after he/she will eventually be sent back to http://www.example.com/admin/post/18/edit after
successfully logging in. This is done by storing the requested URL in the session. If no URL is present in
the session (perhaps the user went directly to the login page), then the user is redirected to the default
page, which is / (i.e. the homepage) by default. You can change this behavior in several ways.


Changing the Default Page
First, the default page can be set (i.e. the page the user is redirected to if no previous page was stored in
the session). To set it to /admin use the following config:

# app/config/security.yml                                                                                              Listing
                                                                                                                        73-2
security:
    firewalls:
        main:
            form_login:
                # ...
                default_target_path: /admin

Now, when no URL is set in the session users will be sent to /admin.


Always Redirect to the Default Page
You can make it so that users are always redirected to the default page regardless of what URL they had
requested previously by setting the always_use_default_target_path option to true:

# app/config/security.yml                                                                                              Listing
                                                                                                                        73-3
security:
    firewalls:
        main:
            form_login:
                # ...
                always_use_default_target_path: true


Using the Referring URL
In case no previous URL was stored in the session, you may wish to try using the HTTP_REFERER instead,
as this will often be the same. You can do this by setting use_referer to true (it defaults to false):

# app/config/security.yml                                                                                              Listing
                                                                                                                        73-4
security:
    firewalls:
        main:
            form_login:
                # ...
                use_referer:                 true


PDF brought to you by                                             Chapter 73: How to customize your Form Login | 409
generated on June 20, 2012
Control the Redirect URL from inside the Form
          You can also override where the user is redirected to via the form itself by including a hidden field with
          the name _target_path. For example, to redirect to the URL defined by some acount route, use the
          following:

Listing   {# src/Acme/SecurityBundle/Resources/views/Security/login.html.twig #}
 73-5
          {% 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" />

               <input type="hidden" name="_target_path" value="account" />

              <input type="submit" name="login" />
          </form>

          Now, the user will be redirected to the value of the hidden form field. The value attribute can be a relative
          path, absolute URL, or a route name. You can even change the name of the hidden form field by changing
          the target_path_parameter option to another value.

Listing   # app/config/security.yml
 73-6
          security:
              firewalls:
                  main:
                      form_login:
                          target_path_parameter: redirect_url


          Redirecting on Login Failure
          In addition to redirect the user after a successful login, you can also set the URL that the user should be
          redirected to after a failed login (e.g. an invalid username or password was submitted). By default, the
          user is redirected back to the login form itself. You can set this to a different URL with the following
          config:

Listing   # app/config/security.yml
 73-7
          security:
              firewalls:
                  main:
                      form_login:
                          # ...
                          failure_path: /login_failure




          PDF brought to you by                                            Chapter 73: How to customize your Form Login | 410
          generated on June 20, 2012
Chapter 74
    How to secure any Service or Method in your
                   Application

In the security chapter, you can see how to secure a controller by requesting the security.context
service from the Service Container and checking the current user's role:

use SymfonyComponentSecurityCoreExceptionAccessDeniedException;                                                     Listing
                                                                                                                          74-1
// ...

public function helloAction($name)
{
    if (false === $this->get('security.context')->isGranted('ROLE_ADMIN')) {
        throw new AccessDeniedException();
    }

     // ...
}

You can also secure any service in a similar way by injecting the security.context service into it. For
a general introduction to injecting dependencies into services see the Service Container chapter of the
book. For example, suppose you have a NewsletterManager class that sends out emails and you want to
restrict its use to only users who have some ROLE_NEWSLETTER_ADMIN role. Before you add security, the
class looks something like this:

namespace AcmeHelloBundleNewsletter;                                                                                   Listing
                                                                                                                          74-2

class NewsletterManager
{

     public function sendNewsletter()
     {
         // where you actually do the work
     }

     // ...
}


PDF brought to you by                        Chapter 74: How to secure any Service or Method in your Application | 411
generated on June 20, 2012
Your goal is to check the user's role when the sendNewsletter() method is called. The first step towards
          this is to inject the security.context service into the object. Since it won't make sense not to perform
          the security check, this is an ideal candidate for constructor injection, which guarantees that the security
          context object will be available inside the NewsletterManager class:

Listing   namespace AcmeHelloBundleNewsletter;
 74-3

          use SymfonyComponentSecurityCoreSecurityContextInterface;

          class NewsletterManager
          {
              protected $securityContext;

               public function __construct(SecurityContextInterface $securityContext)
               {
                   $this->securityContext = $securityContext;
               }

               // ...
          }

          Then in your service configuration, you can inject the service:

Listing   # src/Acme/HelloBundle/Resources/config/services.yml
 74-4
          parameters:
              newsletter_manager.class: AcmeHelloBundleNewsletterNewsletterManager

          services:
              newsletter_manager:
                  class:     %newsletter_manager.class%
                  arguments: [@security.context]

          The injected service can then be used to perform the security check when the sendNewsletter() method
          is called:

Listing   namespace AcmeHelloBundleNewsletter;
 74-5

          use SymfonyComponentSecurityCoreExceptionAccessDeniedException;
          use SymfonyComponentSecurityCoreSecurityContextInterface;
          // ...

          class NewsletterManager
          {
              protected $securityContext;

               public function __construct(SecurityContextInterface $securityContext)
               {
                   $this->securityContext = $securityContext;
               }

               public function sendNewsletter()
               {
                   if (false === $this->securityContext->isGranted('ROLE_NEWSLETTER_ADMIN')) {
                       throw new AccessDeniedException();
                   }

                     //--
               }




          PDF brought to you by                           Chapter 74: How to secure any Service or Method in your Application | 412
          generated on June 20, 2012
// ...
}

If the current user does not have the ROLE_NEWSLETTER_ADMIN, they will be prompted to log in.



Securing Methods Using Annotations
You can also secure method calls in any service with annotations by using the optional
JMSSecurityExtraBundle1 bundle. This bundle is included in the Symfony2 Standard Distribution.
To enable the annotations functionality, tag the service you want to secure with the
security.secure_service tag (you can also automatically enable this functionality for all services, see
the sidebar below):

# src/Acme/HelloBundle/Resources/config/services.yml                                                                                  Listing
                                                                                                                                       74-6
# ...

services:
    newsletter_manager:
        # ...
        tags:
            - { name: security.secure_service }

You can then achieve the same results as above using an annotation:

namespace AcmeHelloBundleNewsletter;                                                                                                Listing
                                                                                                                                       74-7

use JMSSecurityExtraBundleAnnotationSecure;
// ...

class NewsletterManager
{

     /**
       * @Secure(roles="ROLE_NEWSLETTER_ADMIN")
       */
     public function sendNewsletter()
     {
          //--
     }

     // ...
}



              The annotations work because a proxy class is created for your class which performs the security
              checks. This means that, whilst you can use annotations on public and protected methods, you
              cannot use them with private methods or methods marked final.

The JMSSecurityExtraBundle also allows you to secure the parameters and return values of methods.
For more information, see the JMSSecurityExtraBundle2 documentation.




1. https://github.com/schmittjoh/JMSSecurityExtraBundle
2. https://github.com/schmittjoh/JMSSecurityExtraBundle


PDF brought to you by                                     Chapter 74: How to secure any Service or Method in your Application | 413
generated on June 20, 2012
Activating the Annotations Functionality for all Services
                When securing the method of a service (as shown above), you can either tag each service
                individually, or activate the functionality for all services at once. To do so, set the
                secure_all_services configuration option to true:

      Listing   # app/config/config.yml
       74-8
                jms_security_extra:
                    # ...
                    secure_all_services: true

                The disadvantage of this method is that, if activated, the initial page load may be very slow
                depending on how many services you have defined.




PDF brought to you by                                  Chapter 74: How to secure any Service or Method in your Application | 414
generated on June 20, 2012
Chapter 75
             How to create a custom User Provider

Part of Symfony's standard authentication process depends on "user providers". When a user submits a
username and password, the authentication layer asks the configured user provider to return a user object
for a given username. Symfony then checks whether the password of this user is correct and generates a
security token so the user stays authenticated during the current session. Out of the box, Symfony has
an "in_memory" and an "entity" user provider. In this entry we'll see how you can create your own user
provider, which could be useful if your users are accessed via a custom database, a file, or - as we show
in this example - a web service.



Create a User Class
First, regardless of where your user data is coming from, you'll need to create a User class that represents
that data. The User can look however you want and contain any data. The only requirement is that
the class implements UserInterface1. The methods in this interface should therefore be defined in
the custom user class: getRoles(), getPassword(), getSalt(), getUsername(), eraseCredentials(),
equals().
Let's see this in action:

// src/Acme/WebserviceUserBundle/Security/User.php                                                                                     Listing
                                                                                                                                        75-1
namespace AcmeWebserviceUserBundleSecurityUser;

use SymfonyComponentSecurityCoreUserUserInterface;

class WebserviceUser implements UserInterface
{
    private $username;
    private $password;
    private $salt;
    private $roles;

     public function __construct($username, $password, $salt, array $roles)
     {
         $this->username = $username;

1. http://api.symfony.com/2.0/Symfony/Component/Security/Core/User/UserInterface.html


PDF brought to you by                                                         Chapter 75: How to create a custom User Provider | 415
generated on June 20, 2012
$this->password = $password;
           $this->salt = $salt;
           $this->roles = $roles;
     }

     public function getRoles()
     {
         return $this->roles;
     }

     public function getPassword()
     {
         return $this->password;
     }

     public function getSalt()
     {
         return $this->salt;
     }

     public function getUsername()
     {
         return $this->username;
     }

     public function eraseCredentials()
     {
     }

     public function equals(UserInterface $user)
     {
         if (!$user instanceof WebserviceUser) {
             return false;
         }

           if ($this->password !== $user->getPassword()) {
               return false;
           }

           if ($this->getSalt() !== $user->getSalt()) {
               return false;
           }

           if ($this->username !== $user->getUsername()) {
               return false;
           }

           return true;
     }
}

If you have more information about your users - like a "first name" - then you can add a firstName field
to hold that data.
For more details on each of the methods, see UserInterface2.




2. http://api.symfony.com/2.0/Symfony/Component/Security/Core/User/UserInterface.html


PDF brought to you by                                                         Chapter 75: How to create a custom User Provider | 416
generated on June 20, 2012
Create a User Provider
Now that we have a User class, we'll create a user provider, which will grab user information from some
web service, create a WebserviceUser object, and populate it with data.
The user provider is just a plain PHP class that has to implement the UserProviderInterface3, which
requires three methods to be defined: loadUserByUsername($username), refreshUser(UserInterface
$user), and supportsClass($class). For more details, see UserProviderInterface4.
Here's an example of how this might look:

// src/Acme/WebserviceUserBundle/Security/User/WebserviceUserProvider.php                                                              Listing
                                                                                                                                        75-2
namespace AcmeWebserviceUserBundleSecurityUser;

use   SymfonyComponentSecurityCoreUserUserProviderInterface;
use   SymfonyComponentSecurityCoreUserUserInterface;
use   SymfonyComponentSecurityCoreExceptionUsernameNotFoundException;
use   SymfonyComponentSecurityCoreExceptionUnsupportedUserException;

class WebserviceUserProvider implements UserProviderInterface
{
    public function loadUserByUsername($username)
    {
        // make a call to your webservice here
        // $userData = ...
        // pretend it returns an array on success, false if there is no user

           if ($userData) {
               // $password = '...';
               // ...

             return new WebserviceUser($username, $password, $salt, $roles)
        } else {
             throw new UsernameNotFoundException(sprintf('Username "%s" does not exist.',
$username));
        }
    }

    public function refreshUser(UserInterface $user)
    {
        if (!$user instanceof WebserviceUser) {
            throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.',
get_class($user)));
        }

           return $this->loadUserByUsername($user->getUsername());
      }

      public function supportsClass($class)
      {
          return $class === 'AcmeWebserviceUserBundleSecurityUserWebserviceUser';
      }
}




3. http://api.symfony.com/2.0/Symfony/Component/Security/Core/User/UserProviderInterface.html
4. http://api.symfony.com/2.0/Symfony/Component/Security/Core/User/UserProviderInterface.html


PDF brought to you by                                                         Chapter 75: How to create a custom User Provider | 417
generated on June 20, 2012
Create a Service for the User Provider
          Now we make the user provider available as a service.

Listing   # src/Acme/WebserviceUserBundle/Resources/config/services.yml
 75-3
          parameters:
              webservice_user_provider.class:
          AcmeWebserviceUserBundleSecurityUserWebserviceUserProvider

          services:
              webservice_user_provider:
                  class: %webservice_user_provider.class%



                        The real implementation of the user provider will probably have some dependencies or
                        configuration options or other services. Add these as arguments in the service definition.




                        Make sure the services file is being imported. See Importing Configuration with imports for details.




          Modify security.yml
          In /app/config/security.yml everything comes together. Add the user provider to the list of providers
          in the "security" section. Choose a name for the user provider (e.g. "webservice") and mention the id of
          the service you just defined.

Listing   security:
 75-4
              providers:
                  webservice:
                      id: webservice_user_provider

          Symfony also needs to know how to encode passwords that are supplied by website users, e.g. by filling in
          a login form. You can do this by adding a line to the "encoders" section in /app/config/security.yml.

Listing   security:
 75-5
              encoders:
                  AcmeWebserviceUserBundleSecurityUserWebserviceUser: sha512

          The value here should correspond with however the passwords were originally encoded when creating
          your users (however those users were created). When a user submits her password, the password
          is appended to the salt value and then encoded using this algorithm before being compared to the
          hashed password returned by your getPassword() method. Additionally, depending on your options,
          the password may be encoded multiple times and encoded to base64.




          PDF brought to you by                                               Chapter 75: How to create a custom User Provider | 418
          generated on June 20, 2012
Specifics on how passwords are encoded
              Symfony uses a specific method to combine the salt and encode the password before comparing it
              to your encoded password. If getSalt() returns nothing, then the submitted password is simply
              encoded using the algorithm you specify in security.yml. If a salt is specified, then the following
              value is created and then hashed via the algorithm:

                     $password.'{'.$salt.'}';

              If your external users have their passwords salted via a different method, then you'll need to do
              a bit more work so that Symfony properly encodes the password. That is beyond the scope of
              this entry, but would include sub-classing MessageDigestPasswordEncoder and overriding the
              mergePasswordAndSalt method.
              Additionally, the hash, by default, is encoded multiple times and encoded to base64. For specific
              details, see MessageDigestPasswordEncoder5. To prevent this, configure it in security.yml:

              security:                                                                                                                 Listing
                                                                                                                                         75-6
                  encoders:
                      AcmeWebserviceUserBundleSecurityUserWebserviceUser:
                          algorithm: sha512
                          encode_as_base64: false
                          iterations: 1




5. https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Security/Core/Encoder/MessageDigestPasswordEncoder.php


PDF brought to you by                                                          Chapter 75: How to create a custom User Provider | 419
generated on June 20, 2012
Chapter 76
           How to create a custom Authentication
                          Provider

If you have read the chapter on Security, you understand the distinction Symfony2 makes between
authentication and authorization in the implementation of security. This chapter discusses the core
classes involved in the authentication process, and how to implement a custom authentication provider.
Because authentication and authorization are separate concepts, this extension will be user-provider
agnostic, and will function with your application's user providers, may they be based in memory, a
database, or wherever else you choose to store them.



Meet WSSE
The following chapter demonstrates how to create a custom authentication provider for WSSE
authentication. The security protocol for WSSE provides several security benefits:
     1. Username / Password encryption
     2. Safe guarding against replay attacks
     3. No web server configuration required
WSSE is very useful for the securing of web services, may they be SOAP or REST.
There is plenty of great documentation on WSSE1, but this article will focus not on the security protocol,
but rather the manner in which a custom protocol can be added to your Symfony2 application. The basis
of WSSE is that a request header is checked for encrypted credentials, verified using a timestamp and
nonce2, and authenticated for the requested user using a password digest.


              WSSE also supports application key validation, which is useful for web services, but is outside the
              scope of this chapter.




1. http://www.xml.com/pub/a/2003/12/17/dive.html
2. http://en.wikipedia.org/wiki/Cryptographic_nonce


PDF brought to you by                                       Chapter 76: How to create a custom Authentication Provider | 420
generated on June 20, 2012
The Token
The role of the token in the Symfony2 security context is an important one. A token represents the user
authentication data present in the request. Once a request is authenticated, the token retains the user's
data, and delivers this data across the security context. First, we will create our token class. This will
allow the passing of all relevant information to our authentication provider.

// src/Acme/DemoBundle/Security/Authentication/Token/WsseUserToken.php                                                                 Listing
                                                                                                                                        76-1
namespace AcmeDemoBundleSecurityAuthenticationToken;

use SymfonyComponentSecurityCoreAuthenticationTokenAbstractToken;

class WsseUserToken extends AbstractToken
{
    public $created;
    public $digest;
    public $nonce;

      public function __construct(array $roles = array())
      {
          parent::__construct($roles);

           // If the user has roles, consider it authenticated
           $this->setAuthenticated(count($roles) > 0);
      }

      public function getCredentials()
      {
          return '';
      }
}



              The WsseUserToken class extends the security component's AbstractToken3 class, which provides
              basic token functionality. Implement the TokenInterface4 on any class to use as a token.




The Listener
Next, you need a listener to listen on the security context. The listener is responsible for fielding
requests to the firewall and calling the authentication provider. A listener must be an instance of
ListenerInterface5. A security listener should handle the GetResponseEvent6 event, and set an
authenticated token in the security context if successful.

// src/Acme/DemoBundle/Security/Firewall/WsseListener.php                                                                              Listing
                                                                                                                                        76-2
namespace AcmeDemoBundleSecurityFirewall;

use   SymfonyComponentHttpFoundationResponse;
use   SymfonyComponentHttpKernelEventGetResponseEvent;
use   SymfonyComponentSecurityHttpFirewallListenerInterface;
use   SymfonyComponentSecurityCoreExceptionAuthenticationException;
use   SymfonyComponentSecurityCoreSecurityContextInterface;


3. http://api.symfony.com/2.0/Symfony/Component/Security/Core/Authentication/Token/AbstractToken.html
4. http://api.symfony.com/2.0/Symfony/Component/Security/Core/Authentication/Token/TokenInterface.html
5. http://api.symfony.com/2.0/Symfony/Component/Security/Http/Firewall/ListenerInterface.html
6. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/Event/GetResponseEvent.html


PDF brought to you by                                               Chapter 76: How to create a custom Authentication Provider | 421
generated on June 20, 2012
use SymfonyComponentSecurityCoreAuthenticationAuthenticationManagerInterface;
use SymfonyComponentSecurityCoreAuthenticationTokenTokenInterface;
use AcmeDemoBundleSecurityAuthenticationTokenWsseUserToken;

class WsseListener implements ListenerInterface
{
    protected $securityContext;
    protected $authenticationManager;

    public function __construct(SecurityContextInterface $securityContext,
AuthenticationManagerInterface $authenticationManager)
    {
        $this->securityContext = $securityContext;
        $this->authenticationManager = $authenticationManager;
    }

     public function handle(GetResponseEvent $event)
     {
         $request = $event->getRequest();

           if ($request->headers->has('x-wsse')) {

            $wsseRegex = '/UsernameToken Username="([^"]+)", PasswordDigest="([^"]+)",
Nonce="([^"]+)", Created="([^"]+)"/';

                if (preg_match($wsseRegex, $request->headers->get('x-wsse'), $matches)) {
                    $token = new WsseUserToken();
                    $token->setUser($matches[1]);

                      $token->digest         = $matches[2];
                      $token->nonce          = $matches[3];
                      $token->created        = $matches[4];

                      try {
                          $returnValue = $this->authenticationManager->authenticate($token);

                          if ($returnValue instanceof TokenInterface) {
                              return $this->securityContext->setToken($returnValue);
                          } else if ($returnValue instanceof Response) {
                              return $event->setResponse($returnValue);
                          }
                      } catch (AuthenticationException $e) {
                          // you might log something here
                      }
                }
           }

           $response = new Response();
           $response->setStatusCode(403);
           $event->setResponse($response);
     }
}

This listener checks the request for the expected X-WSSE header, matches the value returned for the
expected WSSE information, creates a token using that information, and passes the token on to the
authentication manager. If the proper information is not provided, or the authentication manager throws
an AuthenticationException7, a 403 Response is returned.



7. http://api.symfony.com/2.0/Symfony/Component/Security/Core/Exception/AuthenticationException.html


PDF brought to you by                                               Chapter 76: How to create a custom Authentication Provider | 422
generated on June 20, 2012
A class not used above, the AbstractAuthenticationListener8 class, is a very useful base class
               which provides commonly needed functionality for security extensions. This includes maintaining
               the token in the session, providing success / failure handlers, login form urls, and more. As WSSE
               does not require maintaining authentication sessions or login forms, it won't be used for this
               example.



The Authentication Provider
The authentication provider will do the verification of the WsseUserToken. Namely, the provider will
verify the Created header value is valid within five minutes, the Nonce header value is unique within five
minutes, and the PasswordDigest header value matches with the user's password.

// src/Acme/DemoBundle/Security/Authentication/Provider/WsseProvider.php                                                               Listing
                                                                                                                                        76-3
namespace AcmeDemoBundleSecurityAuthenticationProvider;

use   SymfonyComponentSecurityCoreAuthenticationProviderAuthenticationProviderInterface;
use   SymfonyComponentSecurityCoreUserUserProviderInterface;
use   SymfonyComponentSecurityCoreExceptionAuthenticationException;
use   SymfonyComponentSecurityCoreExceptionNonceExpiredException;
use   SymfonyComponentSecurityCoreAuthenticationTokenTokenInterface;
use   AcmeDemoBundleSecurityAuthenticationTokenWsseUserToken;

class WsseProvider implements AuthenticationProviderInterface
{
    private $userProvider;
    private $cacheDir;

      public function __construct(UserProviderInterface $userProvider, $cacheDir)
      {
          $this->userProvider = $userProvider;
          $this->cacheDir     = $cacheDir;
      }

      public function authenticate(TokenInterface $token)
      {
          $user = $this->userProvider->loadUserByUsername($token->getUsername());

        if ($user && $this->validateDigest($token->digest, $token->nonce, $token->created,
$user->getPassword())) {
            $authenticatedToken = new WsseUserToken($user->getRoles());
            $authenticatedToken->setUser($user);

                return $authenticatedToken;
           }

           throw new AuthenticationException('The WSSE authentication failed.');
      }

      protected function validateDigest($digest, $nonce, $created, $secret)
      {
          // Expire timestamp after 5 minutes
          if (time() - strtotime($created) > 300) {
              return false;
          }


8. http://api.symfony.com/2.0/Symfony/Component/Security/Http/Firewall/AbstractAuthenticationListener.html


PDF brought to you by                                               Chapter 76: How to create a custom Authentication Provider | 423
generated on June 20, 2012
// Validate nonce is unique within 5 minutes
                  if (file_exists($this->cacheDir.'/'.$nonce) &&
          file_get_contents($this->cacheDir.'/'.$nonce) + 300 < time()) {
                      throw new NonceExpiredException('Previously used nonce detected');
                  }
                  file_put_contents($this->cacheDir.'/'.$nonce, time());

                     // Validate Secret
                     $expected = base64_encode(sha1(base64_decode($nonce).$created.$secret, true));

                     return $digest === $expected;
               }

               public function supports(TokenInterface $token)
               {
                   return $token instanceof WsseUserToken;
               }
          }



                        The AuthenticationProviderInterface9 requires an authenticate method on the user token,
                        and a supports method, which tells the authentication manager whether or not to use this
                        provider for the given token. In the case of multiple providers, the authentication manager will
                        then move to the next provider in the list.



          The Factory
          You have created a custom token, custom listener, and custom provider. Now you need to tie them all
          together. How do you make your provider available to your security configuration? The answer is by
          using a factory. A factory is where you hook into the security component, telling it the name of your
          provider and any configuration options available for it. First, you must create a class which implements
          SecurityFactoryInterface10.

Listing   // src/Acme/DemoBundle/DependencyInjection/Security/Factory/WsseFactory.php
 76-4
          namespace AcmeDemoBundleDependencyInjectionSecurityFactory;

          use SymfonyComponentDependencyInjectionContainerBuilder;
          use SymfonyComponentDependencyInjectionReference;
          use SymfonyComponentDependencyInjectionDefinitionDecorator;
          use SymfonyComponentConfigDefinitionBuilderNodeDefinition;
          use
          SymfonyBundleSecurityBundleDependencyInjectionSecurityFactorySecurityFactoryInterface;

          class WsseFactory implements SecurityFactoryInterface
          {
              public function create(ContainerBuilder $container, $id, $config, $userProvider,
          $defaultEntryPoint)
              {
                  $providerId = 'security.authentication.provider.wsse.'.$id;
                  $container
                      ->setDefinition($providerId, new
          DefinitionDecorator('wsse.security.authentication.provider'))
                      ->replaceArgument(0, new Reference($userProvider))
                  ;


          9. http://api.symfony.com/2.0/Symfony/Component/Security/Core/Authentication/Provider/AuthenticationProviderInterface.html
          10. http://api.symfony.com/2.0/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SecurityFactoryInterface.html


          PDF brought to you by                                               Chapter 76: How to create a custom Authentication Provider | 424
          generated on June 20, 2012
$listenerId = 'security.authentication.listener.wsse.'.$id;
        $listener = $container->setDefinition($listenerId, new
DefinitionDecorator('wsse.security.authentication.listener'));

           return array($providerId, $listenerId, $defaultEntryPoint);
      }

      public function getPosition()
      {
          return 'pre_auth';
      }

      public function getKey()
      {
          return 'wsse';
      }

      public function addConfiguration(NodeDefinition $node)
      {}
}

The SecurityFactoryInterface11 requires the following methods:

    • create method, which adds the listener and authentication provider to the DI container for
      the appropriate security context;
    • getPosition method, which must be of type pre_auth, form, http, and remember_me and
      defines the position at which the provider is called;
    • getKey method which defines the configuration key used to reference the provider;
    • addConfiguration method, which is used to define the configuration options underneath the
      configuration key in your security configuration. Setting configuration options are explained
      later in this chapter.


              A class not used in this example, AbstractFactory12, is a very useful base class which provides
              commonly needed functionality for security factories. It may be useful when defining an
              authentication provider of a different type.

Now that you have created a factory class, the wsse key can be used as a firewall in your security
configuration.


              You may be wondering "why do we need a special factory class to add listeners and providers to
              the dependency injection container?". This is a very good question. The reason is you can use your
              firewall multiple times, to secure multiple parts of your application. Because of this, each time your
              firewall is used, a new service is created in the DI container. The factory is what creates these new
              services.



Configuration
It's time to see your authentication provider in action. You will need to do a few things in order to
make this work. The first thing is to add the services above to the DI container. Your factory class above
makes reference to service ids that do not exist yet: wsse.security.authentication.provider and
wsse.security.authentication.listener. It's time to define those services.


11. http://api.symfony.com/2.0/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SecurityFactoryInterface.html
12. http://api.symfony.com/2.0/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.html


PDF brought to you by                                               Chapter 76: How to create a custom Authentication Provider | 425
generated on June 20, 2012
Listing   # src/Acme/DemoBundle/Resources/config/services.yml
 76-5
          services:
            wsse.security.authentication.provider:
              class: AcmeDemoBundleSecurityAuthenticationProviderWsseProvider
              arguments: ['', %kernel.cache_dir%/security/nonces]

            wsse.security.authentication.listener:
              class: AcmeDemoBundleSecurityFirewallWsseListener
              arguments: [@security.context, @security.authentication.manager]

          Now that your services are defined, tell your security context about your factory. Factories must be
          included in an individual configuration file, at the time of this writing. So, start first by creating the file
          with the factory service, tagged as security.listener.factory:

Listing   # src/Acme/DemoBundle/Resources/config/security_factories.yml
 76-6
          services:
              security.authentication.factory.wsse:
                  class: AcmeDemoBundleDependencyInjectionSecurityFactoryWsseFactory
                  tags:
                      - { name: security.listener.factory }

          Now, import the factory configuration via the the factories key in your security configuration:

Listing   # app/config/security.yml
 76-7
          security:
            factories:
              - "%kernel.root_dir%/../src/Acme/DemoBundle/Resources/config/security_factories.yml"

          You are finished! You can now define parts of your app as under WSSE protection.

Listing   security:
 76-8
              firewalls:
                  wsse_secured:
                      pattern:  /api/.*
                      wsse:     true

          Congratulations! You have written your very own custom security authentication provider!



          A Little Extra
          How about making your WSSE authentication provider a bit more exciting? The possibilities are endless.
          Why don't you start by adding some sparkle to that shine?

          Configuration
          You can add custom options under the wsse key in your security configuration. For instance, the time
          allowed before expiring the Created header item, by default, is 5 minutes. Make this configurable, so
          different firewalls can have different timeout lengths.
          You will first need to edit WsseFactory and define the new option in the addConfiguration method.

Listing   class WsseFactory implements SecurityFactoryInterface
 76-9
          {
              # ...

               public function addConfiguration(NodeDefinition $node)
               {
                 $node


          PDF brought to you by                                   Chapter 76: How to create a custom Authentication Provider | 426
          generated on June 20, 2012
->children()
               ->scalarNode('lifetime')->defaultValue(300)
             ->end()
         ;
     }
}

Now, in the create method of the factory, the $config argument will contain a 'lifetime' key, set
to 5 minutes (300 seconds) unless otherwise set in the configuration. Pass this argument to your
authentication provider in order to put it to use.

class WsseFactory implements SecurityFactoryInterface                                                                           Listing
                                                                                                                                76-10
{
    public function create(ContainerBuilder $container, $id, $config, $userProvider,
$defaultEntryPoint)
    {
        $providerId = 'security.authentication.provider.wsse.'.$id;
        $container
            ->setDefinition($providerId,
               new DefinitionDecorator('wsse.security.authentication.provider'))
            ->replaceArgument(0, new Reference($userProvider))
            ->replaceArgument(2, $config['lifetime'])
        ;
        // ...
    }
    // ...
}



               You'll also need to add a third argument to the wsse.security.authentication.provider
               service configuration, which can be blank, but will be filled in with the lifetime in the factory. The
               WsseProvider class will also now need to accept a third constructor argument - the lifetime - which
               it should use instead of the hard-coded 300 seconds. These two steps are not shown here.

The lifetime of each wsse request is now configurable, and can be set to any desirable value per firewall.

security:                                                                                                                       Listing
                                                                                                                                76-11
    firewalls:
        wsse_secured:
            pattern:  /api/.*
            wsse:     { lifetime: 30 }

The rest is up to you! Any relevant configuration items can be defined in the factory and consumed or
passed to the other classes in the container.




PDF brought to you by                                        Chapter 76: How to create a custom Authentication Provider | 427
generated on June 20, 2012
Chapter 77
              How to use Varnish to speed up my Website

          Because Symfony2's cache uses the standard HTTP cache headers, the Symfony2 Reverse Proxy can easily
          be replaced with any other reverse proxy. Varnish is a powerful, open-source, HTTP accelerator capable
          of serving cached content quickly and including support for Edge Si
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0
Symfony metabook 2.0

More Related Content

PDF
Wireless notes
PDF
Mysql tutorial-excerpt-5.1-en
PDF
Ibm system storage solutions handbook
PDF
Tortoise svn 1.7-en
PDF
TsDisaster recovery using Veritas Storage Foundation Enterprise HA with IBM S...
PDF
Red hat open_stack_platform-10-manual_installation_procedures-en-us
PDF
Getting Started with KVM for IBM z Systems
PDF
Wireless notes
Mysql tutorial-excerpt-5.1-en
Ibm system storage solutions handbook
Tortoise svn 1.7-en
TsDisaster recovery using Veritas Storage Foundation Enterprise HA with IBM S...
Red hat open_stack_platform-10-manual_installation_procedures-en-us
Getting Started with KVM for IBM z Systems

What's hot (18)

PDF
RHEL-7 Administrator Guide for RedHat 7
PDF
Load runner generator
PDF
Implementing IBM SmartCloud Entry on IBM PureFlex System
DOCX
latest_ wasit rp data collection report_korrektur_ar
PDF
Spring Framework Upgrade
PDF
Maya2010installationlicensingguide
PDF
I do like cfd vol 1 2ed_v2p2
PDF
80573292 ultra grid-manual
PDF
Red hat storage-3-administration_guide-en-us
PDF
Seam reference
PDF
Cinelerra Video Editing Manual
PDF
Lumion o guia_definitivo_mastertuts_lumionvietnam
PDF
document 1
PDF
IBM Flex System Interoperability Guide
PDF
IBM Streams - Redbook
PDF
Firebird v2.1.4.installation guide
PDF
Wireshark user's guide
RHEL-7 Administrator Guide for RedHat 7
Load runner generator
Implementing IBM SmartCloud Entry on IBM PureFlex System
latest_ wasit rp data collection report_korrektur_ar
Spring Framework Upgrade
Maya2010installationlicensingguide
I do like cfd vol 1 2ed_v2p2
80573292 ultra grid-manual
Red hat storage-3-administration_guide-en-us
Seam reference
Cinelerra Video Editing Manual
Lumion o guia_definitivo_mastertuts_lumionvietnam
document 1
IBM Flex System Interoperability Guide
IBM Streams - Redbook
Firebird v2.1.4.installation guide
Wireshark user's guide
Ad

Similar to Symfony metabook 2.0 (20)

PDF
The Naked Bundle - Tryout
PDF
Symfony2 - from the trenches
PDF
Symfony2 from the Trenches
ODP
An introduction to Symfony 2 for symfony 1 developers
PDF
The Naked Bundle - Symfony Usergroup Belgium
ODP
Learning Symfony2 by practice
PDF
The Naked Bundle - Symfony Barcelona
PDF
The Naked Bundle - Symfony Live London 2014
PPT
Workshop: Symfony2 Intruduction: (Controller, Routing, Model)
PDF
Symfony quick tour_2.3
PDF
How Symfony Changed My Life
PDF
Symfony War Stories
PDF
How Symfony changed my life (#SfPot, Paris, 19th November 2015)
PDF
Myphp-busters: symfony framework (php|tek 09)
PDF
Myphp-busters: symfony framework
PDF
C:\fake path\askeet 1.0-en
PDF
Some tips to improve developer experience with Symfony
PPTX
Symfony2 Introduction Presentation
PDF
Myphp-busters: symfony framework (PHPCon.it)
PDF
Improve your web and app development with the Symfony3 framework.
The Naked Bundle - Tryout
Symfony2 - from the trenches
Symfony2 from the Trenches
An introduction to Symfony 2 for symfony 1 developers
The Naked Bundle - Symfony Usergroup Belgium
Learning Symfony2 by practice
The Naked Bundle - Symfony Barcelona
The Naked Bundle - Symfony Live London 2014
Workshop: Symfony2 Intruduction: (Controller, Routing, Model)
Symfony quick tour_2.3
How Symfony Changed My Life
Symfony War Stories
How Symfony changed my life (#SfPot, Paris, 19th November 2015)
Myphp-busters: symfony framework (php|tek 09)
Myphp-busters: symfony framework
C:\fake path\askeet 1.0-en
Some tips to improve developer experience with Symfony
Symfony2 Introduction Presentation
Myphp-busters: symfony framework (PHPCon.it)
Improve your web and app development with the Symfony3 framework.
Ad

Recently uploaded (20)

PDF
Review of recent advances in non-invasive hemoglobin estimation
PPTX
VMware vSphere Foundation How to Sell Presentation-Ver1.4-2-14-2024.pptx
PDF
Spectral efficient network and resource selection model in 5G networks
PPT
Teaching material agriculture food technology
PDF
Approach and Philosophy of On baking technology
PPTX
Big Data Technologies - Introduction.pptx
PDF
Optimiser vos workloads AI/ML sur Amazon EC2 et AWS Graviton
DOCX
The AUB Centre for AI in Media Proposal.docx
PPTX
MYSQL Presentation for SQL database connectivity
PDF
NewMind AI Weekly Chronicles - August'25 Week I
PDF
KodekX | Application Modernization Development
PPTX
Understanding_Digital_Forensics_Presentation.pptx
PDF
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
PPTX
ACSFv1EN-58255 AWS Academy Cloud Security Foundations.pptx
PPTX
Detection-First SIEM: Rule Types, Dashboards, and Threat-Informed Strategy
PDF
Unlocking AI with Model Context Protocol (MCP)
PDF
Encapsulation_ Review paper, used for researhc scholars
PDF
Diabetes mellitus diagnosis method based random forest with bat algorithm
PPTX
Cloud computing and distributed systems.
PPTX
sap open course for s4hana steps from ECC to s4
Review of recent advances in non-invasive hemoglobin estimation
VMware vSphere Foundation How to Sell Presentation-Ver1.4-2-14-2024.pptx
Spectral efficient network and resource selection model in 5G networks
Teaching material agriculture food technology
Approach and Philosophy of On baking technology
Big Data Technologies - Introduction.pptx
Optimiser vos workloads AI/ML sur Amazon EC2 et AWS Graviton
The AUB Centre for AI in Media Proposal.docx
MYSQL Presentation for SQL database connectivity
NewMind AI Weekly Chronicles - August'25 Week I
KodekX | Application Modernization Development
Understanding_Digital_Forensics_Presentation.pptx
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
ACSFv1EN-58255 AWS Academy Cloud Security Foundations.pptx
Detection-First SIEM: Rule Types, Dashboards, and Threat-Informed Strategy
Unlocking AI with Model Context Protocol (MCP)
Encapsulation_ Review paper, used for researhc scholars
Diabetes mellitus diagnosis method based random forest with bat algorithm
Cloud computing and distributed systems.
sap open course for s4hana steps from ECC to s4

Symfony metabook 2.0

  • 1. The all-in-one Book for Symfony 2.0 generated on June 20, 2012
  • 2. The all-in-one Book (2.0) This work is licensed under the “Attribution-Share Alike 3.0 Unported” license (http://creativecommons.org/ licenses/by-sa/3.0/). You are free to share (to copy, distribute and transmit the work), and to remix (to adapt the work) under the following conditions: • Attribution: You must attribute the work in the manner specified by the author or licensor (but not in any way that suggests that they endorse you or your use of the work). • Share Alike: If you alter, transform, or build upon this work, you may distribute the resulting work only under the same, similar or a compatible license. For any reuse or distribution, you must make clear to others the license terms of this work. The information in this book is distributed on an “as is” basis, without warranty. Although every precaution has been taken in the preparation of this work, neither the author(s) nor SensioLabs shall have any liability to any person or entity with respect to any loss or damage caused or alleged to be caused directly or indirectly by the information contained in this work. If you find typos or errors, feel free to report them by creating a ticket on the Symfony ticketing system (http://github.com/symfony/symfony-docs/issues). Based on tickets and users feedback, this book is continuously updated.
  • 3. Contents at a Glance The Quick Tour The Big Picture ...................................................................................................................................9 The View ..........................................................................................................................................17 The Controller ..................................................................................................................................22 The Architecture ...............................................................................................................................27 The Book Symfony2 and HTTP Fundamentals ..................................................................................................34 Symfony2 versus Flat PHP.................................................................................................................44 Installing and Configuring Symfony...................................................................................................56 Creating Pages in Symfony2 ..............................................................................................................60 Controller.........................................................................................................................................73 Routing ............................................................................................................................................84 Creating and using Templates ...........................................................................................................95 Databases and Doctrine .................................................................................................................. 111 Databases and Propel ...................................................................................................................... 132 Testing ........................................................................................................................................... 140 Validation....................................................................................................................................... 153 Forms ............................................................................................................................................. 161 Security .......................................................................................................................................... 181 HTTP Cache................................................................................................................................... 201 Translations.................................................................................................................................... 216 Service Container ............................................................................................................................ 228 Performance ................................................................................................................................... 239 Internals ......................................................................................................................................... 242 The Symfony2 Stable API ................................................................................................................ 251 The Cookbook How to Create and store a Symfony2 Project in git ........................................................................... 254 How to Create and store a Symfony2 Project in Subversion .............................................................. 258 How to customize Error Pages......................................................................................................... 262 How to define Controllers as Services .............................................................................................. 264 PDF brought to you by Contents at a Glance | iii generated on June 20, 2012
  • 4. How to force routes to always use HTTPS or HTTP......................................................................... 265 How to allow a "/" character in a route parameter ............................................................................ 266 How to Use Assetic for Asset Management ...................................................................................... 267 How to Minify JavaScripts and Stylesheets with YUI Compressor..................................................... 272 How to Use Assetic For Image Optimization with Twig Functions ................................................... 274 How to Apply an Assetic Filter to a Specific File Extension............................................................... 276 How to handle File Uploads with Doctrine ...................................................................................... 278 Doctrine Extensions: Timestampable, Sluggable, Translatable, etc. .................................................. 285 Registering Event Listeners and Subscribers ..................................................................................... 286 How to use Doctrine's DBAL Layer ................................................................................................. 288 How to generate Entities from an Existing Database......................................................................... 290 How to work with Multiple Entity Managers ................................................................................... 293 Registering Custom DQL Functions ................................................................................................ 295 How to customize Form Rendering ................................................................................................. 296 Using Data Transformers ................................................................................................................ 307 How to Dynamically Generate Forms Using Form Events ................................................................ 311 How to Embed a Collection of Forms .............................................................................................. 314 How to Create a Custom Form Field Type....................................................................................... 325 How to use the Virtual Form Field Option....................................................................................... 330 How to create a Custom Validation Constraint ................................................................................ 333 How to Master and Create new Environments ................................................................................. 336 How to Set External Parameters in the Service Container ................................................................. 340 How to use PdoSessionStorage to store Sessions in the Database ...................................................... 343 How to use the Apache Router ........................................................................................................ 345 How to create an Event Listener ...................................................................................................... 347 How to work with Scopes ............................................................................................................... 349 How to work with Compiler Passes in Bundles ................................................................................ 352 Bundle Structure and Best Practices ................................................................................................. 353 How to use Bundle Inheritance to Override parts of a Bundle ........................................................... 358 How to Override any Part of a Bundle ............................................................................................. 361 How to expose a Semantic Configuration for a Bundle ..................................................................... 363 How to send an Email ..................................................................................................................... 370 How to use Gmail to send Emails .................................................................................................... 372 How to Work with Emails During Development .............................................................................. 373 How to Spool Email ........................................................................................................................ 375 How to simulate HTTP Authentication in a Functional Test ............................................................ 377 How to test the Interaction of several Clients ................................................................................... 378 How to use the Profiler in a Functional Test..................................................................................... 379 How to test Doctrine Repositories ................................................................................................... 381 How to load Security Users from the Database (the Entity Provider) ................................................. 383 How to add "Remember Me" Login Functionality ............................................................................ 393 How to implement your own Voter to blacklist IP Addresses............................................................ 396 Access Control Lists (ACLs) ............................................................................................................ 399 Advanced ACL Concepts ................................................................................................................ 403 How to force HTTPS or HTTP for Different URLs ........................................................................... 407 How to customize your Form Login ................................................................................................ 408 How to secure any Service or Method in your Application................................................................ 411 iv | Contents at a Glance Contents at a Glance | 4
  • 5. How to create a custom User Provider ............................................................................................. 415 How to create a custom Authentication Provider ............................................................................. 420 How to use Varnish to speed up my Website ................................................................................... 428 Injecting variables into all templates (i.e. Global Variables) .............................................................. 430 How to use PHP instead of Twig for Templates ............................................................................... 432 How to write a custom Twig Extension ........................................................................................... 437 How to use Monolog to write Logs.................................................................................................. 440 How to Configure Monolog to Email Errors .................................................................................... 443 How to create a Console Command ................................................................................................ 445 How to optimize your development Environment for debugging ...................................................... 448 How to extend a Class without using Inheritance............................................................................. 450 How to customize a Method Behavior without using Inheritance...................................................... 453 How to register a new Request Format and Mime Type.................................................................... 455 How to create a custom Data Collector............................................................................................ 457 How to Create a SOAP Web Service in a Symfony2 Controller ......................................................... 460 How Symfony2 differs from symfony1 ............................................................................................. 463 The Components The ClassLoader Component .......................................................................................................... 469 The Console Component ................................................................................................................ 472 The CssSelector Component ........................................................................................................... 479 The DomCrawler Component ......................................................................................................... 481 The Dependency Injection Component............................................................................................ 487 Working with Container Parameters and Definitions ....................................................................... 491 Compiling the Container................................................................................................................. 494 Working with Tagged Services ........................................................................................................ 498 Using a Factory to Create Services ................................................................................................... 501 Managing Common Dependencies with Parent Services ................................................................... 503 The Event Dispatcher Component................................................................................................... 508 The Finder Component................................................................................................................... 515 The HttpFoundation Component.................................................................................................... 520 The Locale Component................................................................................................................... 526 The Process Component ................................................................................................................. 528 The Routing Component ................................................................................................................ 530 The Templating Component ........................................................................................................... 536 The YAML Component................................................................................................................... 539 Contributing Reporting a Bug .............................................................................................................................. 547 Submitting a Patch .......................................................................................................................... 548 Reporting a Security Issue ............................................................................................................... 554 Running Symfony2 Tests................................................................................................................. 555 Coding Standards ........................................................................................................................... 557 Conventions ................................................................................................................................... 560 Symfony2 License ........................................................................................................................... 562 PDF brought to you by Contents at a Glance | v generated on June 20, 2012
  • 6. Contributing to the Documentation ................................................................................................ 563 Documentation Format................................................................................................................... 565 Translations.................................................................................................................................... 568 Symfony2 Documentation License................................................................................................... 570 IRC Meetings.................................................................................................................................. 571 Other Resources ............................................................................................................................. 573 Reference Documents FrameworkBundle Configuration ("framework").............................................................................. 575 AsseticBundle Configuration Reference ........................................................................................... 580 Configuration Reference.................................................................................................................. 582 Security Configuration Reference..................................................................................................... 586 SwiftmailerBundle Configuration ("swiftmailer").............................................................................. 590 TwigBundle Configuration Reference .............................................................................................. 594 Configuration Reference.................................................................................................................. 596 WebProfilerBundle Configuration ................................................................................................... 598 Form Types Reference..................................................................................................................... 599 birthday Field Type......................................................................................................................... 601 checkbox Field Type ....................................................................................................................... 605 choice Field Type ............................................................................................................................ 607 collection Field Type....................................................................................................................... 611 country Field Type.......................................................................................................................... 617 csrf Field Type ................................................................................................................................ 620 date Field Type ............................................................................................................................... 622 datetime Field Type ........................................................................................................................ 626 email Field Type ............................................................................................................................. 630 entity Field Type ............................................................................................................................. 632 file Field Type ................................................................................................................................. 636 The Abstract "field" Type ................................................................................................................ 639 form Field Type .............................................................................................................................. 641 hidden Field Type ........................................................................................................................... 642 integer Field Type ........................................................................................................................... 644 language Field Type ........................................................................................................................ 647 locale Field Type............................................................................................................................. 650 money Field Type ........................................................................................................................... 653 number Field Type.......................................................................................................................... 656 password Field Type ....................................................................................................................... 659 percent Field Type .......................................................................................................................... 661 radio Field Type.............................................................................................................................. 664 repeated Field Type......................................................................................................................... 666 search Field Type ............................................................................................................................ 669 text Field Type................................................................................................................................ 671 textarea Field Type ......................................................................................................................... 673 time Field Type............................................................................................................................... 675 timezone Field Type........................................................................................................................ 679 url Field Type ................................................................................................................................. 682 vi | Contents at a Glance Contents at a Glance | 6
  • 7. Twig Template Form Function Reference ........................................................................................ 684 Validation Constraints Reference..................................................................................................... 686 NotBlank........................................................................................................................................ 688 Blank.............................................................................................................................................. 689 NotNull.......................................................................................................................................... 690 Null................................................................................................................................................ 691 True ............................................................................................................................................... 692 False............................................................................................................................................... 694 Type............................................................................................................................................... 696 Email.............................................................................................................................................. 698 MinLength...................................................................................................................................... 700 MaxLength ..................................................................................................................................... 702 Url.................................................................................................................................................. 704 Regex ............................................................................................................................................. 706 Ip ................................................................................................................................................... 708 Max................................................................................................................................................ 710 Min ................................................................................................................................................ 712 Date ............................................................................................................................................... 714 DateTime ....................................................................................................................................... 715 Time............................................................................................................................................... 716 Choice............................................................................................................................................ 717 Collection....................................................................................................................................... 720 UniqueEntity .................................................................................................................................. 723 Language ........................................................................................................................................ 725 Locale............................................................................................................................................. 726 Country.......................................................................................................................................... 727 File ................................................................................................................................................. 728 Image ............................................................................................................................................. 731 Callback ......................................................................................................................................... 732 Valid .............................................................................................................................................. 735 All .................................................................................................................................................. 737 The Dependency Injection Tags....................................................................................................... 739 Requirements for running Symfony2................................................................................................ 747 PDF brought to you by Contents at a Glance | vii generated on June 20, 2012
  • 9. Chapter 1 The Big Picture Start using Symfony2 in 10 minutes! This chapter will walk you through some of the most important concepts behind Symfony2 and explain how you can get started quickly by showing you a simple project in action. If you've used a web framework before, you should feel right at home with Symfony2. If not, welcome to a whole new way of developing web applications! Want to learn why and when you need to use a framework? Read the "Symfony in 5 minutes" document. Downloading Symfony2 First, check that you have installed and configured a Web server (such as Apache) with PHP 5.3.2 or higher. Ready? Start by downloading the "Symfony2 Standard Edition1", a Symfony distribution that is preconfigured for the most common use cases and also contains some code that demonstrates how to use Symfony2 (get the archive with the vendors included to get started even faster). After unpacking the archive under your web server root directory, you should have a Symfony/ directory that looks like this: www/ <- your web root directory Listing 1-1 Symfony/ <- the unpacked archive app/ cache/ config/ logs/ Resources/ bin/ src/ Acme/ 1. http://symfony.com/download PDF brought to you by Chapter 1: The Big Picture | 9 generated on June 20, 2012
  • 10. DemoBundle/ Controller/ Resources/ ... vendor/ symfony/ doctrine/ ... web/ app.php ... If you downloaded the Standard Edition without vendors, simply run the following command to download all of the vendor libraries: Listing php bin/vendors install 1-2 Checking the Configuration Symfony2 comes with a visual server configuration tester to help avoid some headaches that come from Web server or PHP misconfiguration. Use the following URL to see the diagnostics for your machine: Listing http://localhost/Symfony/web/config.php 1-3 If there are any outstanding issues listed, correct them. You might also tweak your configuration by following any given recommendations. When everything is fine, click on "Bypass configuration and go to the Welcome page" to request your first "real" Symfony2 webpage: Listing http://localhost/Symfony/web/app_dev.php/ 1-4 Symfony2 should welcome and congratulate you for your hard work so far! PDF brought to you by Chapter 1: The Big Picture | 10 generated on June 20, 2012
  • 11. Understanding the Fundamentals One of the main goals of a framework is to ensure Separation of Concerns2. This keeps your code organized and allows your application to evolve easily over time by avoiding the mixing of database calls, HTML tags, and business logic in the same script. To achieve this goal with Symfony, you'll first need to learn a few fundamental concepts and terms. Want proof that using a framework is better than mixing everything in the same script? Read the "Symfony2 versus Flat PHP" chapter of the book. The distribution comes with some sample code that you can use to learn more about the main Symfony2 concepts. Go to the following URL to be greeted by Symfony2 (replace Fabien with your first name): http://localhost/Symfony/web/app_dev.php/demo/hello/Fabien Listing 1-5 What's going on here? Let's dissect the URL: • app_dev.php: This is a front controller. It is the unique entry point of the application and it responds to all user requests; • /demo/hello/Fabien: This is the virtual path to the resource the user wants to access. Your responsibility as a developer is to write the code that maps the user's request (/demo/hello/Fabien) to the resource associated with it (the Hello Fabien! HTML page). Routing Symfony2 routes the request to the code that handles it by trying to match the requested URL against some configured patterns. By default, these patterns (called routes) are defined in the app/config/ routing.yml configuration file. When you're in the dev environment - indicated by the app_**dev**.php front controller - the app/config/routing_dev.yml configuration file is also loaded. In the Standard Edition, the routes to these "demo" pages are placed in that file: # app/config/routing_dev.yml Listing 1-6 _welcome: 2. http://en.wikipedia.org/wiki/Separation_of_concerns PDF brought to you by Chapter 1: The Big Picture | 11 generated on June 20, 2012
  • 12. pattern: / defaults: { _controller: AcmeDemoBundle:Welcome:index } _demo: resource: "@AcmeDemoBundle/Controller/DemoController.php" type: annotation prefix: /demo # ... The first three lines (after the comment) define the code that is executed when the user requests the "/" resource (i.e. the welcome page you saw earlier). When requested, the AcmeDemoBundle:Welcome:index controller will be executed. In the next section, you'll learn exactly what that means. The Symfony2 Standard Edition uses YAML3 for its configuration files, but Symfony2 also supports XML, PHP, and annotations natively. The different formats are compatible and may be used interchangeably within an application. Also, the performance of your application does not depend on the configuration format you choose as everything is cached on the very first request. Controllers A controller is a fancy name for a PHP function or method that handles incoming requests and returns responses (often HTML code). Instead of using the PHP global variables and functions (like $_GET or header()) to manage these HTTP messages, Symfony uses objects: Request4 and Response5. The simplest possible controller might create the response by hand, based on the request: Listing use SymfonyComponentHttpFoundationResponse; 1-7 $name = $request->query->get('name'); return new Response('Hello '.$name, 200, array('Content-Type' => 'text/plain')); Symfony2 embraces the HTTP Specification, which are the rules that govern all communication on the Web. Read the "Symfony2 and HTTP Fundamentals" chapter of the book to learn more about this and the added power that this brings. Symfony2 chooses the controller based on the _controller value from the routing configuration: AcmeDemoBundle:Welcome:index. This string is the controller logical name, and it references the indexAction method from the AcmeDemoBundleControllerWelcomeController class: Listing // src/Acme/DemoBundle/Controller/WelcomeController.php 1-8 namespace AcmeDemoBundleController; use SymfonyBundleFrameworkBundleControllerController; class WelcomeController extends Controller { public function indexAction() { return $this->render('AcmeDemoBundle:Welcome:index.html.twig'); } } 3. http://www.yaml.org/ 4. http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/Request.html 5. http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/Response.html PDF brought to you by Chapter 1: The Big Picture | 12 generated on June 20, 2012
  • 13. You could have used the full class and method name - AcmeDemoBundleControllerWelcomeController::indexAction - for the _controller value. But if you follow some simple conventions, the logical name is shorter and allows for more flexibility. The WelcomeController class extends the built-in Controller class, which provides useful shortcut methods, like the render()6 method that loads and renders a template (AcmeDemoBundle:Welcome:index.html.twig). The returned value is a Response object populated with the rendered content. So, if the needs arise, the Response can be tweaked before it is sent to the browser: public function indexAction() Listing 1-9 { $response = $this->render('AcmeDemoBundle:Welcome:index.txt.twig'); $response->headers->set('Content-Type', 'text/plain'); return $response; } No matter how you do it, the end goal of your controller is always to return the Response object that should be delivered back to the user. This Response object can be populated with HTML code, represent a client redirect, or even return the contents of a JPG image with a Content-Type header of image/jpg. Extending the Controller base class is optional. As a matter of fact, a controller can be a plain PHP function or even a PHP closure. "The Controller" chapter of the book tells you everything about Symfony2 controllers. The template name, AcmeDemoBundle:Welcome:index.html.twig, is the template logical name and it references the Resources/views/Welcome/index.html.twig file inside the AcmeDemoBundle (located at src/Acme/DemoBundle). The bundles section below will explain why this is useful. Now, take a look at the routing configuration again and find the _demo key: # app/config/routing_dev.yml Listing 1-10 _demo: resource: "@AcmeDemoBundle/Controller/DemoController.php" type: annotation prefix: /demo Symfony2 can read/import the routing information from different files written in YAML, XML, PHP, or even embedded in PHP annotations. Here, the file's logical name is @AcmeDemoBundle/Controller/ DemoController.php and refers to the src/Acme/DemoBundle/Controller/DemoController.php file. In this file, routes are defined as annotations on action methods: // src/Acme/DemoBundle/Controller/DemoController.php Listing 1-11 use SensioBundleFrameworkExtraBundleConfigurationRoute; use SensioBundleFrameworkExtraBundleConfigurationTemplate; class DemoController extends Controller { /** * @Route("/hello/{name}", name="_demo_hello") * @Template() */ public function helloAction($name) 6. http://api.symfony.com/2.0/Symfony/Bundle/FrameworkBundle/Controller/Controller.html#render() PDF brought to you by Chapter 1: The Big Picture | 13 generated on June 20, 2012
  • 14. { return array('name' => $name); } // ... } The @Route() annotation defines a new route with a pattern of /hello/{name} that executes the helloAction method when matched. A string enclosed in curly brackets like {name} is called a placeholder. As you can see, its value can be retrieved through the $name method argument. Even if annotations are not natively supported by PHP, you use them extensively in Symfony2 as a convenient way to configure the framework behavior and keep the configuration next to the code. If you take a closer look at the controller code, you can see that instead of rendering a template and returning a Response object like before, it just returns an array of parameters. The @Template() annotation tells Symfony to render the template for you, passing in each variable of the array to the template. The name of the template that's rendered follows the name of the controller. So, in this example, the AcmeDemoBundle:Demo:hello.html.twig template is rendered (located at src/Acme/ DemoBundle/Resources/views/Demo/hello.html.twig). The @Route() and @Template() annotations are more powerful than the simple examples shown in this tutorial. Learn more about "annotations in controllers" in the official documentation. Templates The controller renders the src/Acme/DemoBundle/Resources/views/Demo/hello.html.twig template (or AcmeDemoBundle:Demo:hello.html.twig if you use the logical name): Listing {# src/Acme/DemoBundle/Resources/views/Demo/hello.html.twig #} 1-12 {% extends "AcmeDemoBundle::layout.html.twig" %} {% block title "Hello " ~ name %} {% block content %} <h1>Hello {{ name }}!</h1> {% endblock %} By default, Symfony2 uses Twig7 as its template engine but you can also use traditional PHP templates if you choose. The next chapter will introduce how templates work in Symfony2. Bundles You might have wondered why the bundle word is used in many names we have seen so far. All the code you write for your application is organized in bundles. In Symfony2 speak, a bundle is a structured set of files (PHP files, stylesheets, JavaScripts, images, ...) that implements a single feature (a blog, a forum, ...) and which can be easily shared with other developers. As of now, we have manipulated one bundle, AcmeDemoBundle. You will learn more about bundles in the last chapter of this tutorial. 7. http://twig.sensiolabs.org/ PDF brought to you by Chapter 1: The Big Picture | 14 generated on June 20, 2012
  • 15. Working with Environments Now that you have a better understanding of how Symfony2 works, take a closer look at the bottom of any Symfony2 rendered page. You should notice a small bar with the Symfony2 logo. This is called the "Web Debug Toolbar" and it is the developer's best friend. But what you see initially is only the tip of the iceberg; click on the weird hexadecimal number to reveal yet another very useful Symfony2 debugging tool: the profiler. Of course, you won't want to show these tools when you deploy your application to production. That's why you will find another front controller in the web/ directory (app.php), which is optimized for the production environment: http://localhost/Symfony/web/app.php/demo/hello/Fabien Listing 1-13 And if you use Apache with mod_rewrite enabled, you can even omit the app.php part of the URL: http://localhost/Symfony/web/demo/hello/Fabien Listing 1-14 Last but not least, on the production servers, you should point your web root directory to the web/ directory to secure your installation and have an even better looking URL: PDF brought to you by Chapter 1: The Big Picture | 15 generated on June 20, 2012
  • 16. Listing http://localhost/demo/hello/Fabien 1-15 Note that the three URLs above are provided here only as examples of how a URL looks like when the production front controller is used (with or without mod_rewrite). If you actually try them in an out of the box installation of Symfony Standard Edition you will get a 404 error as AcmeDemoBundle is enabled only in dev environment and its routes imported in app/config/ routing_dev.yml. To make you application respond faster, Symfony2 maintains a cache under the app/cache/ directory. In the development environment (app_dev.php), this cache is flushed automatically whenever you make changes to any code or configuration. But that's not the case in the production environment (app.php) where performance is key. That's why you should always use the development environment when developing your application. Different environments of a given application differ only in their configuration. In fact, a configuration can inherit from another one: Listing # app/config/config_dev.yml 1-16 imports: - { resource: config.yml } web_profiler: toolbar: true intercept_redirects: false The dev environment (which loads the config_dev.yml configuration file) imports the global config.yml file and then modifies it by, in this example, enabling the web debug toolbar. Final Thoughts Congratulations! You've had your first taste of Symfony2 code. That wasn't so hard, was it? There's a lot more to explore, but you should already see how Symfony2 makes it really easy to implement web sites better and faster. If you are eager to learn more about Symfony2, dive into the next section: "The View". PDF brought to you by Chapter 1: The Big Picture | 16 generated on June 20, 2012
  • 17. Chapter 2 The View After reading the first part of this tutorial, you have decided that Symfony2 was worth another 10 minutes. Great choice! In this second part, you will learn more about the Symfony2 template engine, Twig1. Twig is a flexible, fast, and secure template engine for PHP. It makes your templates more readable and concise; it also makes them more friendly for web designers. Instead of Twig, you can also use PHP for your templates. Both template engines are supported by Symfony2. Getting familiar with Twig If you want to learn Twig, we highly recommend you to read its official documentation2. This section is just a quick overview of the main concepts. A Twig template is a text file that can generate any type of content (HTML, XML, CSV, LaTeX, ...). Twig defines two kinds of delimiters: • {{ ... }}: Prints a variable or the result of an expression; • {% ... %}: Controls the logic of the template; it is used to execute for loops and if statements, for example. Below is a minimal template that illustrates a few basics, using two variables page_title and navigation, which would be passed into the template: <!DOCTYPE html> Listing 2-1 <html> <head> <title>My Webpage</title> 1. http://twig.sensiolabs.org/ 2. http://twig.sensiolabs.org/documentation PDF brought to you by Chapter 2: The View | 17 generated on June 20, 2012
  • 18. </head> <body> <h1>{{ page_title }}</h1> <ul id="navigation"> {% for item in navigation %} <li><a href="{{ item.href }}">{{ item.caption }}</a></li> {% endfor %} </ul> </body> </html> Comments can be included inside templates using the {# ... #} delimiter. To render a template in Symfony, use the render method from within a controller and pass it any variables needed in the template: Listing $this->render('AcmeDemoBundle:Demo:hello.html.twig', array( 2-2 'name' => $name, )); Variables passed to a template can be strings, arrays, or even objects. Twig abstracts the difference between them and lets you access "attributes" of a variable with the dot (.) notation: Listing {# array('name' => 'Fabien') #} 2-3 {{ name }} {# array('user' => array('name' => 'Fabien')) #} {{ user.name }} {# force array lookup #} {{ user['name'] }} {# array('user' => new User('Fabien')) #} {{ user.name }} {{ user.getName }} {# force method name lookup #} {{ user.name() }} {{ user.getName() }} {# pass arguments to a method #} {{ user.date('Y-m-d') }} It's important to know that the curly braces are not part of the variable but the print statement. If you access variables inside tags don't put the braces around. Decorating Templates More often than not, templates in a project share common elements, like the well-known header and footer. In Symfony2, we like to think about this problem differently: a template can be decorated by another one. This works exactly the same as PHP classes: template inheritance allows you to build a PDF brought to you by Chapter 2: The View | 18 generated on June 20, 2012
  • 19. base "layout" template that contains all the common elements of your site and defines "blocks" that child templates can override. The hello.html.twig template inherits from layout.html.twig, thanks to the extends tag: {# src/Acme/DemoBundle/Resources/views/Demo/hello.html.twig #} Listing 2-4 {% extends "AcmeDemoBundle::layout.html.twig" %} {% block title "Hello " ~ name %} {% block content %} <h1>Hello {{ name }}!</h1> {% endblock %} The AcmeDemoBundle::layout.html.twig notation sounds familiar, doesn't it? It is the same notation used to reference a regular template. The :: part simply means that the controller element is empty, so the corresponding file is directly stored under the Resources/views/ directory. Now, let's have a look at a simplified layout.html.twig: {# src/Acme/DemoBundle/Resources/views/layout.html.twig #} Listing 2-5 <div class="symfony-content"> {% block content %} {% endblock %} </div> The {% block %} tags define blocks that child templates can fill in. All the block tag does is to tell the template engine that a child template may override those portions of the template. In this example, the hello.html.twig template overrides the content block, meaning that the "Hello Fabien" text is rendered inside the div.symfony-content element. Using Tags, Filters, and Functions One of the best feature of Twig is its extensibility via tags, filters, and functions. Symfony2 comes bundled with many of these built-in to ease the work of the template designer. Including other Templates The best way to share a snippet of code between several distinct templates is to create a new template that can then be included from other templates. Create an embedded.html.twig template: {# src/Acme/DemoBundle/Resources/views/Demo/embedded.html.twig #} Listing 2-6 Hello {{ name }} And change the index.html.twig template to include it: {# src/Acme/DemoBundle/Resources/views/Demo/hello.html.twig #} Listing 2-7 {% extends "AcmeDemoBundle::layout.html.twig" %} {# override the body block from embedded.html.twig #} {% block content %} {% include "AcmeDemoBundle:Demo:embedded.html.twig" %} {% endblock %} PDF brought to you by Chapter 2: The View | 19 generated on June 20, 2012
  • 20. Embedding other Controllers And what if you want to embed the result of another controller in a template? That's very useful when working with Ajax, or when the embedded template needs some variable not available in the main template. Suppose you've created a fancy action, and you want to include it inside the index template. To do this, use the render tag: Listing {# src/Acme/DemoBundle/Resources/views/Demo/index.html.twig #} 2-8 {% render "AcmeDemoBundle:Demo:fancy" with { 'name': name, 'color': 'green' } %} Here, the AcmeDemoBundle:Demo:fancy string refers to the fancy action of the Demo controller. The arguments (name and color) act like simulated request variables (as if the fancyAction were handling a whole new request) and are made available to the controller: Listing // src/Acme/DemoBundle/Controller/DemoController.php 2-9 class DemoController extends Controller { public function fancyAction($name, $color) { // create some object, based on the $color variable $object = ...; return $this->render('AcmeDemoBundle:Demo:fancy.html.twig', array('name' => $name, 'object' => $object)); } // ... } Creating Links between Pages Speaking of web applications, creating links between pages is a must. Instead of hardcoding URLs in templates, the path function knows how to generate URLs based on the routing configuration. That way, all your URLs can be easily updated by just changing the configuration: Listing <a href="{{ path('_demo_hello', { 'name': 'Thomas' }) }}">Greet Thomas!</a> 2-10 The path function takes the route name and an array of parameters as arguments. The route name is the main key under which routes are referenced and the parameters are the values of the placeholders defined in the route pattern: Listing // src/Acme/DemoBundle/Controller/DemoController.php 2-11 use SensioBundleFrameworkExtraBundleConfigurationRoute; use SensioBundleFrameworkExtraBundleConfigurationTemplate; /** * @Route("/hello/{name}", name="_demo_hello") * @Template() */ public function helloAction($name) { return array('name' => $name); } PDF brought to you by Chapter 2: The View | 20 generated on June 20, 2012
  • 21. The url function generates absolute URLs: {{ url('_demo_hello', { 'name': 'Thomas' }) }}. Including Assets: images, JavaScripts, and stylesheets What would the Internet be without images, JavaScripts, and stylesheets? Symfony2 provides the asset function to deal with them easily: <link href="{{ asset('css/blog.css') }}" rel="stylesheet" type="text/css" /> Listing 2-12 <img src="{{ asset('images/logo.png') }}" /> The asset function's main purpose is to make your application more portable. Thanks to this function, you can move the application root directory anywhere under your web root directory without changing anything in your template's code. Escaping Variables Twig is configured to automatically escapes all output by default. Read Twig documentation3 to learn more about output escaping and the Escaper extension. Final Thoughts Twig is simple yet powerful. Thanks to layouts, blocks, templates and action inclusions, it is very easy to organize your templates in a logical and extensible way. However, if you're not comfortable with Twig, you can always use PHP templates inside Symfony without any issues. You have only been working with Symfony2 for about 20 minutes, but you can already do pretty amazing stuff with it. That's the power of Symfony2. Learning the basics is easy, and you will soon learn that this simplicity is hidden under a very flexible architecture. But I'm getting ahead of myself. First, you need to learn more about the controller and that's exactly the topic of the next part of this tutorial. Ready for another 10 minutes with Symfony2? 3. http://twig.sensiolabs.org/documentation PDF brought to you by Chapter 2: The View | 21 generated on June 20, 2012
  • 22. Chapter 3 The Controller Still with us after the first two parts? You are already becoming a Symfony2 addict! Without further ado, let's discover what controllers can do for you. Using Formats Nowadays, a web application should be able to deliver more than just HTML pages. From XML for RSS feeds or Web Services, to JSON for Ajax requests, there are plenty of different formats to choose from. Supporting those formats in Symfony2 is straightforward. Tweak the route by adding a default value of xml for the _format variable: Listing // src/Acme/DemoBundle/Controller/DemoController.php 3-1 use SensioBundleFrameworkExtraBundleConfigurationRoute; use SensioBundleFrameworkExtraBundleConfigurationTemplate; /** * @Route("/hello/{name}", defaults={"_format"="xml"}, name="_demo_hello") * @Template() */ public function helloAction($name) { return array('name' => $name); } By using the request format (as defined by the _format value), Symfony2 automatically selects the right template, here hello.xml.twig: Listing <!-- src/Acme/DemoBundle/Resources/views/Demo/hello.xml.twig --> 3-2 <hello> <name>{{ name }}</name> </hello> That's all there is to it. For standard formats, Symfony2 will also automatically choose the best Content- Type header for the response. If you want to support different formats for a single action, use the {_format} placeholder in the route pattern instead: PDF brought to you by Chapter 3: The Controller | 22 generated on June 20, 2012
  • 23. // src/Acme/DemoBundle/Controller/DemoController.php Listing 3-3 use SensioBundleFrameworkExtraBundleConfigurationRoute; use SensioBundleFrameworkExtraBundleConfigurationTemplate; /** * @Route("/hello/{name}.{_format}", defaults={"_format"="html"}, requirements={"_format"="html|xml|json"}, name="_demo_hello") * @Template() */ public function helloAction($name) { return array('name' => $name); } The controller will now be called for URLs like /demo/hello/Fabien.xml or /demo/hello/ Fabien.json. The requirements entry defines regular expressions that placeholders must match. In this example, if you try to request the /demo/hello/Fabien.js resource, you will get a 404 HTTP error, as it does not match the _format requirement. Redirecting and Forwarding If you want to redirect the user to another page, use the redirect() method: return $this->redirect($this->generateUrl('_demo_hello', array('name' => 'Lucas'))); Listing 3-4 The generateUrl() is the same method as the path() function we used in templates. It takes the route name and an array of parameters as arguments and returns the associated friendly URL. You can also easily forward the action to another one with the forward() method. Internally, Symfony makes a "sub-request", and returns the Response object from that sub-request: $response = $this->forward('AcmeDemoBundle:Hello:fancy', array('name' => $name, 'color' => Listing 3-5 'green')); // do something with the response or return it directly Getting information from the Request Besides the values of the routing placeholders, the controller also has access to the Request object: $request = $this->getRequest(); Listing 3-6 $request->isXmlHttpRequest(); // is it an Ajax request? $request->getPreferredLanguage(array('en', 'fr')); $request->query->get('page'); // get a $_GET parameter $request->request->get('page'); // get a $_POST parameter In a template, you can also access the Request object via the app.request variable: {{ app.request.query.get('page') }} Listing 3-7 {{ app.request.parameter('page') }} PDF brought to you by Chapter 3: The Controller | 23 generated on June 20, 2012
  • 24. Persisting Data in the Session Even if the HTTP protocol is stateless, Symfony2 provides a nice session object that represents the client (be it a real person using a browser, a bot, or a web service). Between two requests, Symfony2 stores the attributes in a cookie by using native PHP sessions. Storing and retrieving information from the session can be easily achieved from any controller: Listing $session = $this->getRequest()->getSession(); 3-8 // store an attribute for reuse during a later user request $session->set('foo', 'bar'); // in another controller for another request $foo = $session->get('foo'); // set the user locale $session->setLocale('fr'); You can also store small messages that will only be available for the very next request: Listing // store a message for the very next request (in a controller) 3-9 $session->setFlash('notice', 'Congratulations, your action succeeded!'); // display the message back in the next request (in a template) {{ app.session.flash('notice') }} This is useful when you need to set a success message before redirecting the user to another page (which will then show the message). Securing Resources The Symfony Standard Edition comes with a simple security configuration that fits most common needs: Listing # app/config/security.yml 3-10 security: encoders: SymfonyComponentSecurityCoreUserUser: plaintext role_hierarchy: ROLE_ADMIN: ROLE_USER ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH] providers: in_memory: users: user: { password: userpass, roles: [ 'ROLE_USER' ] } admin: { password: adminpass, roles: [ 'ROLE_ADMIN' ] } firewalls: dev: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false login: pattern: ^/demo/secured/login$ security: false PDF brought to you by Chapter 3: The Controller | 24 generated on June 20, 2012
  • 25. secured_area: pattern: ^/demo/secured/ form_login: check_path: /demo/secured/login_check login_path: /demo/secured/login logout: path: /demo/secured/logout target: /demo/ This configuration requires users to log in for any URL starting with /demo/secured/ and defines two valid users: user and admin. Moreover, the admin user has a ROLE_ADMIN role, which includes the ROLE_USER role as well (see the role_hierarchy setting). For readability, passwords are stored in clear text in this simple configuration, but you can use any hashing algorithm by tweaking the encoders section. Going to the http://localhost/Symfony/web/app_dev.php/demo/secured/hello URL will automatically redirect you to the login form because this resource is protected by a firewall. You can also force the action to require a given role by using the @Secure annotation on the controller: use SensioBundleFrameworkExtraBundleConfigurationRoute; Listing 3-11 use SensioBundleFrameworkExtraBundleConfigurationTemplate; use JMSSecurityExtraBundleAnnotationSecure; /** * @Route("/hello/admin/{name}", name="_demo_secured_hello_admin") * @Secure(roles="ROLE_ADMIN") * @Template() */ public function helloAdminAction($name) { return array('name' => $name); } Now, log in as user (who does not have the ROLE_ADMIN role) and from the secured hello page, click on the "Hello resource secured" link. Symfony2 should return a 403 HTTP status code, indicating that the user is "forbidden" from accessing that resource. The Symfony2 security layer is very flexible and comes with many different user providers (like one for the Doctrine ORM) and authentication providers (like HTTP basic, HTTP digest, or X509 certificates). Read the "Security" chapter of the book for more information on how to use and configure them. Caching Resources As soon as your website starts to generate more traffic, you will want to avoid generating the same resource again and again. Symfony2 uses HTTP cache headers to manage resources cache. For simple caching strategies, use the convenient @Cache() annotation: use SensioBundleFrameworkExtraBundleConfigurationRoute; Listing 3-12 use SensioBundleFrameworkExtraBundleConfigurationTemplate; use SensioBundleFrameworkExtraBundleConfigurationCache; PDF brought to you by Chapter 3: The Controller | 25 generated on June 20, 2012
  • 26. /** * @Route("/hello/{name}", name="_demo_hello") * @Template() * @Cache(maxage="86400") */ public function helloAction($name) { return array('name' => $name); } In this example, the resource will be cached for a day. But you can also use validation instead of expiration or a combination of both if that fits your needs better. Resource caching is managed by the Symfony2 built-in reverse proxy. But because caching is managed using regular HTTP cache headers, you can replace the built-in reverse proxy with Varnish or Squid and easily scale your application. But what if you cannot cache whole pages? Symfony2 still has the solution via Edge Side Includes (ESI), which are supported natively. Learn more by reading the "HTTP Cache" chapter of the book. Final Thoughts That's all there is to it, and I'm not even sure we have spent the full 10 minutes. We briefly introduced bundles in the first part, and all the features we've learned about so far are part of the core framework bundle. But thanks to bundles, everything in Symfony2 can be extended or replaced. That's the topic of the next part of this tutorial. PDF brought to you by Chapter 3: The Controller | 26 generated on June 20, 2012
  • 27. Chapter 4 The Architecture You are my hero! Who would have thought that you would still be here after the first three parts? Your efforts will be well rewarded soon. The first three parts didn't look too deeply at the architecture of the framework. Because it makes Symfony2 stand apart from the framework crowd, let's dive into the architecture now. Understanding the Directory Structure The directory structure of a Symfony2 application is rather flexible, but the directory structure of the Standard Edition distribution reflects the typical and recommended structure of a Symfony2 application: • app/: The application configuration; • src/: The project's PHP code; • vendor/: The third-party dependencies; • web/: The web root directory. The web/ Directory The web root directory is the home of all public and static files like images, stylesheets, and JavaScript files. It is also where each front controller lives: // web/app.php Listing 4-1 require_once __DIR__.'/../app/bootstrap.php.cache'; require_once __DIR__.'/../app/AppKernel.php'; use SymfonyComponentHttpFoundationRequest; $kernel = new AppKernel('prod', false); $kernel->loadClassCache(); $kernel->handle(Request::createFromGlobals())->send(); The kernel first requires the bootstrap.php.cache file, which bootstraps the framework and registers the autoloader (see below). Like any front controller, app.php uses a Kernel Class, AppKernel, to bootstrap the application. PDF brought to you by Chapter 4: The Architecture | 27 generated on June 20, 2012
  • 28. The app/ Directory The AppKernel class is the main entry point of the application configuration and as such, it is stored in the app/ directory. This class must implement two methods: • registerBundles() must return an array of all bundles needed to run the application; • registerContainerConfiguration() loads the application configuration (more on this later). PHP autoloading can be configured via app/autoload.php: Listing // app/autoload.php 4-2 use SymfonyComponentClassLoaderUniversalClassLoader; $loader = new UniversalClassLoader(); $loader->registerNamespaces(array( 'Symfony' => array(__DIR__.'/../vendor/symfony/src', __DIR__.'/../vendor/ bundles'), 'Sensio' => __DIR__.'/../vendor/bundles', 'JMS' => __DIR__.'/../vendor/bundles', 'DoctrineCommon' => __DIR__.'/../vendor/doctrine-common/lib', 'DoctrineDBAL' => __DIR__.'/../vendor/doctrine-dbal/lib', 'Doctrine' => __DIR__.'/../vendor/doctrine/lib', 'Monolog' => __DIR__.'/../vendor/monolog/src', 'Assetic' => __DIR__.'/../vendor/assetic/src', 'Metadata' => __DIR__.'/../vendor/metadata/src', )); $loader->registerPrefixes(array( 'Twig_Extensions_' => __DIR__.'/../vendor/twig-extensions/lib', 'Twig_' => __DIR__.'/../vendor/twig/lib', )); // ... $loader->registerNamespaceFallbacks(array( __DIR__.'/../src', )); $loader->register(); The UniversalClassLoader1 is used to autoload files that respect either the technical interoperability standards2 for PHP 5.3 namespaces or the PEAR naming convention3 for classes. As you can see here, all dependencies are stored under the vendor/ directory, but this is just a convention. You can store them wherever you want, globally on your server or locally in your projects. If you want to learn more about the flexibility of the Symfony2 autoloader, read the "The ClassLoader Component" chapter. Understanding the Bundle System This section introduces one of the greatest and most powerful features of Symfony2, the bundle system. 1. http://api.symfony.com/2.0/Symfony/Component/ClassLoader/UniversalClassLoader.html 2. http://symfony.com/PSR0 3. http://pear.php.net/ PDF brought to you by Chapter 4: The Architecture | 28 generated on June 20, 2012
  • 29. A bundle is kind of like a plugin in other software. So why is it called a bundle and not a plugin? This is because everything is a bundle in Symfony2, from the core framework features to the code you write for your application. Bundles are first-class citizens in Symfony2. This gives you the flexibility to use pre- built features packaged in third-party bundles or to distribute your own bundles. It makes it easy to pick and choose which features to enable in your application and optimize them the way you want. And at the end of the day, your application code is just as important as the core framework itself. Registering a Bundle An application is made up of bundles as defined in the registerBundles() method of the AppKernel class. Each bundle is a directory that contains a single Bundle class that describes it: // app/AppKernel.php Listing 4-3 public function registerBundles() { $bundles = array( new SymfonyBundleFrameworkBundleFrameworkBundle(), new SymfonyBundleSecurityBundleSecurityBundle(), new SymfonyBundleTwigBundleTwigBundle(), new SymfonyBundleMonologBundleMonologBundle(), new SymfonyBundleSwiftmailerBundleSwiftmailerBundle(), new SymfonyBundleDoctrineBundleDoctrineBundle(), new SymfonyBundleAsseticBundleAsseticBundle(), new SensioBundleFrameworkExtraBundleSensioFrameworkExtraBundle(), new JMSSecurityExtraBundleJMSSecurityExtraBundle(), ); if (in_array($this->getEnvironment(), array('dev', 'test'))) { $bundles[] = new AcmeDemoBundleAcmeDemoBundle(); $bundles[] = new SymfonyBundleWebProfilerBundleWebProfilerBundle(); $bundles[] = new SensioBundleDistributionBundleSensioDistributionBundle(); $bundles[] = new SensioBundleGeneratorBundleSensioGeneratorBundle(); } return $bundles; } In addition to the AcmeDemoBundle that we have already talked about, notice that the kernel also enables other bundles such as the FrameworkBundle, DoctrineBundle, SwiftmailerBundle, and AsseticBundle bundle. They are all part of the core framework. Configuring a Bundle Each bundle can be customized via configuration files written in YAML, XML, or PHP. Have a look at the default configuration: # app/config/config.yml Listing 4-4 imports: - { resource: parameters.ini } - { resource: security.yml } framework: secret: "%secret%" charset: UTF-8 router: { resource: "%kernel.root_dir%/config/routing.yml" } form: true csrf_protection: true validation: { enable_annotations: true } templating: { engines: ['twig'] } #assets_version: SomeVersionScheme PDF brought to you by Chapter 4: The Architecture | 29 generated on June 20, 2012
  • 30. session: default_locale: "%locale%" auto_start: true # Twig Configuration twig: debug: "%kernel.debug%" strict_variables: "%kernel.debug%" # Assetic Configuration assetic: debug: "%kernel.debug%" use_controller: false filters: cssrewrite: ~ # closure: # jar: "%kernel.root_dir%/java/compiler.jar" # yui_css: # jar: "%kernel.root_dir%/java/yuicompressor-2.4.2.jar" # Doctrine Configuration doctrine: dbal: driver: "%database_driver%" host: "%database_host%" dbname: "%database_name%" user: "%database_user%" password: "%database_password%" charset: UTF8 orm: auto_generate_proxy_classes: "%kernel.debug%" auto_mapping: true # Swiftmailer Configuration swiftmailer: transport: "%mailer_transport%" host: "%mailer_host%" username: "%mailer_user%" password: "%mailer_password%" jms_security_extra: secure_controllers: true secure_all_services: false Each entry like framework defines the configuration for a specific bundle. For example, framework configures the FrameworkBundle while swiftmailer configures the SwiftmailerBundle. Each environment can override the default configuration by providing a specific configuration file. For example, the dev environment loads the config_dev.yml file, which loads the main configuration (i.e. config.yml) and then modifies it to add some debugging tools: Listing # app/config/config_dev.yml 4-5 imports: - { resource: config.yml } framework: router: { resource: "%kernel.root_dir%/config/routing_dev.yml" } profiler: { only_exceptions: false } web_profiler: PDF brought to you by Chapter 4: The Architecture | 30 generated on June 20, 2012
  • 31. toolbar: true intercept_redirects: false monolog: handlers: main: type: stream path: "%kernel.logs_dir%/%kernel.environment%.log" level: debug firephp: type: firephp level: info assetic: use_controller: true Extending a Bundle In addition to being a nice way to organize and configure your code, a bundle can extend another bundle. Bundle inheritance allows you to override any existing bundle in order to customize its controllers, templates, or any of its files. This is where the logical names (e.g. @AcmeDemoBundle/Controller/ SecuredController.php) come in handy: they abstract where the resource is actually stored. Logical File Names When you want to reference a file from a bundle, use this notation: @BUNDLE_NAME/path/to/file; Symfony2 will resolve @BUNDLE_NAME to the real path to the bundle. For instance, the logical path @AcmeDemoBundle/Controller/DemoController.php would be converted to src/Acme/DemoBundle/ Controller/DemoController.php, because Symfony knows the location of the AcmeDemoBundle. Logical Controller Names For controllers, you need to reference method names using the format BUNDLE_NAME:CONTROLLER_NAME:ACTION_NAME. For instance, AcmeDemoBundle:Welcome:index maps to the indexAction method from the AcmeDemoBundleControllerWelcomeController class. Logical Template Names For templates, the logical name AcmeDemoBundle:Welcome:index.html.twig is converted to the file path src/Acme/DemoBundle/Resources/views/Welcome/index.html.twig. Templates become even more interesting when you realize they don't need to be stored on the filesystem. You can easily store them in a database table for instance. Extending Bundles If you follow these conventions, then you can use bundle inheritance to "override" files, controllers or templates. For example, you can create a bundle - AcmeNewBundle - and specify that its parent is AcmeDemoBundle. When Symfony loads the AcmeDemoBundle:Welcome:index controller, it will first look for the WelcomeController class in AcmeNewBundle and then look inside AcmeDemoBundle. This means that one bundle can override almost any part of another bundle! Do you understand now why Symfony2 is so flexible? Share your bundles between applications, store them locally or globally, your choice. PDF brought to you by Chapter 4: The Architecture | 31 generated on June 20, 2012
  • 32. Using Vendors Odds are that your application will depend on third-party libraries. Those should be stored in the vendor/ directory. This directory already contains the Symfony2 libraries, the SwiftMailer library, the Doctrine ORM, the Twig templating system, and some other third party libraries and bundles. Understanding the Cache and Logs Symfony2 is probably one of the fastest full-stack frameworks around. But how can it be so fast if it parses and interprets tens of YAML and XML files for each request? The speed is partly due to its cache system. The application configuration is only parsed for the very first request and then compiled down to plain PHP code stored in the app/cache/ directory. In the development environment, Symfony2 is smart enough to flush the cache when you change a file. But in the production environment, it is your responsibility to clear the cache when you update your code or change its configuration. When developing a web application, things can go wrong in many ways. The log files in the app/logs/ directory tell you everything about the requests and help you fix the problem quickly. Using the Command Line Interface Each application comes with a command line interface tool (app/console) that helps you maintain your application. It provides commands that boost your productivity by automating tedious and repetitive tasks. Run it without any arguments to learn more about its capabilities: Listing php app/console 4-6 The --help option helps you discover the usage of a command: Listing php app/console router:debug --help 4-7 Final Thoughts Call me crazy, but after reading this part, you should be comfortable with moving things around and making Symfony2 work for you. Everything in Symfony2 is designed to get out of your way. So, feel free to rename and move directories around as you see fit. And that's all for the quick tour. From testing to sending emails, you still need to learn a lot to become a Symfony2 master. Ready to dig into these topics now? Look no further - go to the official The Book and pick any topic you want. PDF brought to you by Chapter 4: The Architecture | 32 generated on June 20, 2012
  • 34. Chapter 5 Symfony2 and HTTP Fundamentals Congratulations! By learning about Symfony2, you're well on your way towards being a more productive, well-rounded and popular web developer (actually, you're on your own for the last part). Symfony2 is built to get back to basics: to develop tools that let you develop faster and build more robust applications, while staying out of your way. Symfony is built on the best ideas from many technologies: the tools and concepts you're about to learn represent the efforts of thousands of people, over many years. In other words, you're not just learning "Symfony", you're learning the fundamentals of the web, development best practices, and how to use many amazing new PHP libraries, inside or independent of Symfony2. So, get ready. True to the Symfony2 philosophy, this chapter begins by explaining the fundamental concept common to web development: HTTP. Regardless of your background or preferred programming language, this chapter is a must-read for everyone. HTTP is Simple HTTP (Hypertext Transfer Protocol to the geeks) is a text language that allows two machines to communicate with each other. That's it! For example, when checking for the latest xkcd1 comic, the following (approximate) conversation takes place: 1. http://xkcd.com/ PDF brought to you by Chapter 5: Symfony2 and HTTP Fundamentals | 34 generated on June 20, 2012
  • 35. And while the actual language used is a bit more formal, it's still dead-simple. HTTP is the term used to describe this simple text-based language. And no matter how you develop on the web, the goal of your server is always to understand simple text requests, and return simple text responses. Symfony2 is built from the ground-up around that reality. Whether you realize it or not, HTTP is something you use everyday. With Symfony2, you'll learn how to master it. Step1: The Client sends a Request Every conversation on the web starts with a request. The request is a text message created by a client (e.g. a browser, an iPhone app, etc) in a special format known as HTTP. The client sends that request to a server, and then waits for the response. Take a look at the first part of the interaction (the request) between a browser and the xkcd web server: In HTTP-speak, this HTTP request would actually look something like this: GET / HTTP/1.1 Listing 5-1 Host: xkcd.com Accept: text/html User-Agent: Mozilla/5.0 (Macintosh) This simple message communicates everything necessary about exactly which resource the client is requesting. The first line of an HTTP request is the most important and contains two things: the URI and the HTTP method. The URI (e.g. /, /contact, etc) is the unique address or location that identifies the resource the client wants. The HTTP method (e.g. GET) defines what you want to do with the resource. The HTTP methods are the verbs of the request and define the few common ways that you can act upon the resource: PDF brought to you by Chapter 5: Symfony2 and HTTP Fundamentals | 35 generated on June 20, 2012
  • 36. GET Retrieve the resource from the server POST Create a resource on the server PUT Update the resource on the server DELETE Delete the resource from the server With this in mind, you can imagine what an HTTP request might look like to delete a specific blog entry, for example: Listing DELETE /blog/15 HTTP/1.1 5-2 There are actually nine HTTP methods defined by the HTTP specification, but many of them are not widely used or supported. In reality, many modern browsers don't support the PUT and DELETE methods. In addition to the first line, an HTTP request invariably contains other lines of information called request headers. The headers can supply a wide range of information such as the requested Host, the response formats the client accepts (Accept) and the application the client is using to make the request (User- Agent). Many other headers exist and can be found on Wikipedia's List of HTTP header fields2 article. Step 2: The Server returns a Response Once a server has received the request, it knows exactly which resource the client needs (via the URI) and what the client wants to do with that resource (via the method). For example, in the case of a GET request, the server prepares the resource and returns it in an HTTP response. Consider the response from the xkcd web server: Translated into HTTP, the response sent back to the browser will look something like this: Listing HTTP/1.1 200 OK 5-3 Date: Sat, 02 Apr 2011 21:05:05 GMT Server: lighttpd/1.4.19 Content-Type: text/html <html> <!-- HTML for the xkcd comic --> </html> 2. http://en.wikipedia.org/wiki/List_of_HTTP_header_fields PDF brought to you by Chapter 5: Symfony2 and HTTP Fundamentals | 36 generated on June 20, 2012
  • 37. The HTTP response contains the requested resource (the HTML content in this case), as well as other information about the response. The first line is especially important and contains the HTTP response status code (200 in this case). The status code communicates the overall outcome of the request back to the client. Was the request successful? Was there an error? Different status codes exist that indicate success, an error, or that the client needs to do something (e.g. redirect to another page). A full list can be found on Wikipedia's List of HTTP status codes3 article. Like the request, an HTTP response contains additional pieces of information known as HTTP headers. For example, one important HTTP response header is Content-Type. The body of the same resource could be returned in multiple different formats like HTML, XML, or JSON and the Content-Type header uses Internet Media Types like text/html to tell the client which format is being returned. A list of common media types can be found on Wikipedia's List of common media types4 article. Many other headers exist, some of which are very powerful. For example, certain headers can be used to create a powerful caching system. Requests, Responses and Web Development This request-response conversation is the fundamental process that drives all communication on the web. And as important and powerful as this process is, it's inescapably simple. The most important fact is this: regardless of the language you use, the type of application you build (web, mobile, JSON API), or the development philosophy you follow, the end goal of an application is always to understand each request and create and return the appropriate response. Symfony is architected to match this reality. To learn more about the HTTP specification, read the original HTTP 1.1 RFC5 or the HTTP Bis6, which is an active effort to clarify the original specification. A great tool to check both the request and response headers while browsing is the Live HTTP Headers7 extension for Firefox. Requests and Responses in PHP So how do you interact with the "request" and create a "response" when using PHP? In reality, PHP abstracts you a bit from the whole process: <?php Listing 5-4 $uri = $_SERVER['REQUEST_URI']; $foo = $_GET['foo']; header('Content-type: text/html'); echo 'The URI requested is: '.$uri; echo 'The value of the "foo" parameter is: '.$foo; As strange as it sounds, this small application is in fact taking information from the HTTP request and using it to create an HTTP response. Instead of parsing the raw HTTP request message, PHP prepares superglobal variables such as $_SERVER and $_GET that contain all the information from the request. Similarly, instead of returning the HTTP-formatted text response, you can use the header() function to create response headers and simply print out the actual content that will be the content portion of the response message. PHP will create a true HTTP response and return it to the client: 3. http://en.wikipedia.org/wiki/List_of_HTTP_status_codes 4. http://en.wikipedia.org/wiki/Internet_media_type#List_of_common_media_types 5. http://www.w3.org/Protocols/rfc2616/rfc2616.html 6. http://datatracker.ietf.org/wg/httpbis/ 7. https://addons.mozilla.org/en-US/firefox/addon/live-http-headers/ PDF brought to you by Chapter 5: Symfony2 and HTTP Fundamentals | 37 generated on June 20, 2012
  • 38. Listing HTTP/1.1 200 OK 5-5 Date: Sat, 03 Apr 2011 02:14:33 GMT Server: Apache/2.2.17 (Unix) Content-Type: text/html The URI requested is: /testing?foo=symfony The value of the "foo" parameter is: symfony Requests and Responses in Symfony Symfony provides an alternative to the raw PHP approach via two classes that allow you to interact with the HTTP request and response in an easier way. The Request8 class is a simple object-oriented representation of the HTTP request message. With it, you have all the request information at your fingertips: Listing use SymfonyComponentHttpFoundationRequest; 5-6 $request = Request::createFromGlobals(); // the URI being requested (e.g. /about) minus any query parameters $request->getPathInfo(); // retrieve GET and POST variables respectively $request->query->get('foo'); $request->request->get('bar', 'default value if bar does not exist'); // retrieve SERVER variables $request->server->get('HTTP_HOST'); // retrieves an instance of UploadedFile identified by foo $request->files->get('foo'); // retrieve a COOKIE value $request->cookies->get('PHPSESSID'); // retrieve an HTTP request header, with normalized, lowercase keys $request->headers->get('host'); $request->headers->get('content_type'); $request->getMethod(); // GET, POST, PUT, DELETE, HEAD $request->getLanguages(); // an array of languages the client accepts As a bonus, the Request class does a lot of work in the background that you'll never need to worry about. For example, the isSecure() method checks the three different values in PHP that can indicate whether or not the user is connecting via a secured connection (i.e. https). 8. http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/Request.html PDF brought to you by Chapter 5: Symfony2 and HTTP Fundamentals | 38 generated on June 20, 2012
  • 39. ParameterBags and Request attributes As seen above, the $_GET and $_POST variables are accessible via the public query and request properties respectively. Each of these objects is a ParameterBag9 object, which has methods like get()10, has()11, all()12 and more. In fact, every public property used in the previous example is some instance of the ParameterBag. The Request class also has a public attributes property, which holds special data related to how the application works internally. For the Symfony2 framework, the attributes holds the values returned by the matched route, like _controller, id (if you have an {id} wildcard), and even the name of the matched route (_route). The attributes property exists entirely to be a place where you can prepare and store context-specific information about the request. Symfony also provides a Response class: a simple PHP representation of an HTTP response message. This allows your application to use an object-oriented interface to construct the response that needs to be returned to the client: use SymfonyComponentHttpFoundationResponse; Listing 5-7 $response = new Response(); $response->setContent('<html><body><h1>Hello world!</h1></body></html>'); $response->setStatusCode(200); $response->headers->set('Content-Type', 'text/html'); // prints the HTTP headers followed by the content $response->send(); If Symfony offered nothing else, you would already have a toolkit for easily accessing request information and an object-oriented interface for creating the response. Even as you learn the many powerful features in Symfony, keep in mind that the goal of your application is always to interpret a request and create the appropriate response based on your application logic. The Request and Response classes are part of a standalone component included with Symfony called HttpFoundation. This component can be used entirely independent of Symfony and also provides classes for handling sessions and file uploads. The Journey from the Request to the Response Like HTTP itself, the Request and Response objects are pretty simple. The hard part of building an application is writing what comes in between. In other words, the real work comes in writing the code that interprets the request information and creates the response. Your application probably does many things, like sending emails, handling form submissions, saving things to a database, rendering HTML pages and protecting content with security. How can you manage all of this and still keep your code organized and maintainable? Symfony was created to solve these problems so that you don't have to. 9. http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/ParameterBag.html 10. http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/ParameterBag.html#get() 11. http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/ParameterBag.html#has() 12. http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/ParameterBag.html#all() PDF brought to you by Chapter 5: Symfony2 and HTTP Fundamentals | 39 generated on June 20, 2012
  • 40. The Front Controller Traditionally, applications were built so that each "page" of a site was its own physical file: Listing index.php 5-8 contact.php blog.php There are several problems with this approach, including the inflexibility of the URLs (what if you wanted to change blog.php to news.php without breaking all of your links?) and the fact that each file must manually include some set of core files so that security, database connections and the "look" of the site can remain consistent. A much better solution is to use a front controller: a single PHP file that handles every request coming into your application. For example: /index.php executes index.php /index.php/contact executes index.php /index.php/blog executes index.php Using Apache's mod_rewrite (or equivalent with other web servers), the URLs can easily be cleaned up to be just /, /contact and /blog. Now, every request is handled exactly the same. Instead of individual URLs executing different PHP files, the front controller is always executed, and the routing of different URLs to different parts of your application is done internally. This solves both problems with the original approach. Almost all modern web apps do this - including apps like WordPress. Stay Organized But inside your front controller, how do you know which page should be rendered and how can you render each in a sane way? One way or another, you'll need to check the incoming URI and execute different parts of your code depending on that value. This can get ugly quickly: Listing // index.php 5-9 $request = Request::createFromGlobals(); $path = $request->getPathInfo(); // the URI path being requested if (in_array($path, array('', '/')) { $response = new Response('Welcome to the homepage.'); } elseif ($path == '/contact') { $response = new Response('Contact us'); } else { $response = new Response('Page not found.', 404); } $response->send(); Solving this problem can be difficult. Fortunately it's exactly what Symfony is designed to do. The Symfony Application Flow When you let Symfony handle each request, life is much easier. Symfony follows the same simple pattern for every request: PDF brought to you by Chapter 5: Symfony2 and HTTP Fundamentals | 40 generated on June 20, 2012
  • 41. Incoming requests are interpreted by the routing and passed to controller functions that return Response objects. Each "page" of your site is defined in a routing configuration file that maps different URLs to different PHP functions. The job of each PHP function, called a controller, is to use information from the request - along with many other tools Symfony makes available - to create and return a Response object. In other words, the controller is where your code goes: it's where you interpret the request and create a response. It's that easy! Let's review: • Each request executes a front controller file; • The routing system determines which PHP function should be executed based on information from the request and routing configuration you've created; • The correct PHP function is executed, where your code creates and returns the appropriate Response object. A Symfony Request in Action Without diving into too much detail, let's see this process in action. Suppose you want to add a /contact page to your Symfony application. First, start by adding an entry for /contact to your routing configuration file: contact: Listing 5-10 pattern: /contact defaults: { _controller: AcmeDemoBundle:Main:contact } This example uses YAML to define the routing configuration. Routing configuration can also be written in other formats such as XML or PHP. When someone visits the /contact page, this route is matched, and the specified controller is executed. As you'll learn in the routing chapter, the AcmeDemoBundle:Main:contact string is a short syntax that points to a specific PHP method contactAction inside a class called MainController: class MainController Listing 5-11 { public function contactAction() { return new Response('<h1>Contact us!</h1>'); } } PDF brought to you by Chapter 5: Symfony2 and HTTP Fundamentals | 41 generated on June 20, 2012
  • 42. In this very simple example, the controller simply creates a Response object with the HTML "<h1>Contact us!</h1>". In the controller chapter, you'll learn how a controller can render templates, allowing your "presentation" code (i.e. anything that actually writes out HTML) to live in a separate template file. This frees up the controller to worry only about the hard stuff: interacting with the database, handling submitted data, or sending email messages. Symfony2: Build your App, not your Tools. You now know that the goal of any app is to interpret each incoming request and create an appropriate response. As an application grows, it becomes more difficult to keep your code organized and maintainable. Invariably, the same complex tasks keep coming up over and over again: persisting things to the database, rendering and reusing templates, handling form submissions, sending emails, validating user input and handling security. The good news is that none of these problems is unique. Symfony provides a framework full of tools that allow you to build your application, not your tools. With Symfony2, nothing is imposed on you: you're free to use the full Symfony framework, or just one piece of Symfony all by itself. Standalone Tools: The Symfony2 Components So what is Symfony2? First, Symfony2 is a collection of over twenty independent libraries that can be used inside any PHP project. These libraries, called the Symfony2 Components, contain something useful for almost any situation, regardless of how your project is developed. To name a few: • HttpFoundation13 - Contains the Request and Response classes, as well as other classes for handling sessions and file uploads; • Routing14 - Powerful and fast routing system that allows you to map a specific URI (e.g. /contact) to some information about how that request should be handled (e.g. execute the contactAction() method); • Form15 - A full-featured and flexible framework for creating forms and handling form submissions; • Validator16 A system for creating rules about data and then validating whether or not user- submitted data follows those rules; • ClassLoader17 An autoloading library that allows PHP classes to be used without needing to manually require the files containing those classes; • Templating18 A toolkit for rendering templates, handling template inheritance (i.e. a template is decorated with a layout) and performing other common template tasks; • Security19 - A powerful library for handling all types of security inside an application; • Translation20 A framework for translating strings in your application. Each and every one of these components is decoupled and can be used in any PHP project, regardless of whether or not you use the Symfony2 framework. Every part is made to be used if needed and replaced when necessary. 13. https://github.com/symfony/HttpFoundation 14. https://github.com/symfony/Routing 15. https://github.com/symfony/Form 16. https://github.com/symfony/Validator 17. https://github.com/symfony/ClassLoader 18. https://github.com/symfony/Templating 19. https://github.com/symfony/Security 20. https://github.com/symfony/Translation PDF brought to you by Chapter 5: Symfony2 and HTTP Fundamentals | 42 generated on June 20, 2012
  • 43. The Full Solution: The Symfony2 Framework So then, what is the Symfony2 Framework? The Symfony2 Framework is a PHP library that accomplishes two distinct tasks: 1. Provides a selection of components (i.e. the Symfony2 Components) and third-party libraries (e.g. Swiftmailer for sending emails); 2. Provides sensible configuration and a "glue" library that ties all of these pieces together. The goal of the framework is to integrate many independent tools in order to provide a consistent experience for the developer. Even the framework itself is a Symfony2 bundle (i.e. a plugin) that can be configured or replaced entirely. Symfony2 provides a powerful set of tools for rapidly developing web applications without imposing on your application. Normal users can quickly start development by using a Symfony2 distribution, which provides a project skeleton with sensible defaults. For more advanced users, the sky is the limit. PDF brought to you by Chapter 5: Symfony2 and HTTP Fundamentals | 43 generated on June 20, 2012
  • 44. Chapter 6 Symfony2 versus Flat PHP Why is Symfony2 better than just opening up a file and writing flat PHP? If you've never used a PHP framework, aren't familiar with the MVC philosophy, or just wonder what all the hype is around Symfony2, this chapter is for you. Instead of telling you that Symfony2 allows you to develop faster and better software than with flat PHP, you'll see for yourself. In this chapter, you'll write a simple application in flat PHP, and then refactor it to be more organized. You'll travel through time, seeing the decisions behind why web development has evolved over the past several years to where it is now. By the end, you'll see how Symfony2 can rescue you from mundane tasks and let you take back control of your code. A simple Blog in flat PHP In this chapter, you'll build the token blog application using only flat PHP. To begin, create a single page that displays blog entries that have been persisted to the database. Writing in flat PHP is quick and dirty: Listing <?php 6-1 // index.php $link = mysql_connect('localhost', 'myuser', 'mypassword'); mysql_select_db('blog_db', $link); $result = mysql_query('SELECT id, title FROM post', $link); ?> <html> <head> <title>List of Posts</title> </head> <body> <h1>List of Posts</h1> <ul> <?php while ($row = mysql_fetch_assoc($result)): ?> <li> PDF brought to you by Chapter 6: Symfony2 versus Flat PHP | 44 generated on June 20, 2012
  • 45. <a href="/show.php?id=<?php echo $row['id'] ?>"> <?php echo $row['title'] ?> </a> </li> <?php endwhile; ?> </ul> </body> </html> <?php mysql_close($link); That's quick to write, fast to execute, and, as your app grows, impossible to maintain. There are several problems that need to be addressed: • No error-checking: What if the connection to the database fails? • Poor organization: If the application grows, this single file will become increasingly unmaintainable. Where should you put code to handle a form submission? How can you validate data? Where should code go for sending emails? • Difficult to reuse code: Since everything is in one file, there's no way to reuse any part of the application for other "pages" of the blog. Another problem not mentioned here is the fact that the database is tied to MySQL. Though not covered here, Symfony2 fully integrates Doctrine1, a library dedicated to database abstraction and mapping. Let's get to work on solving these problems and more. Isolating the Presentation The code can immediately gain from separating the application "logic" from the code that prepares the HTML "presentation": <?php Listing 6-2 // index.php $link = mysql_connect('localhost', 'myuser', 'mypassword'); mysql_select_db('blog_db', $link); $result = mysql_query('SELECT id, title FROM post', $link); $posts = array(); while ($row = mysql_fetch_assoc($result)) { $posts[] = $row; } mysql_close($link); // include the HTML presentation code require 'templates/list.php'; The HTML code is now stored in a separate file (templates/list.php), which is primarily an HTML file that uses a template-like PHP syntax: <html> Listing 6-3 <head> 1. http://www.doctrine-project.org PDF brought to you by Chapter 6: Symfony2 versus Flat PHP | 45 generated on June 20, 2012
  • 46. <title>List of Posts</title> </head> <body> <h1>List of Posts</h1> <ul> <?php foreach ($posts as $post): ?> <li> <a href="/read?id=<?php echo $post['id'] ?>"> <?php echo $post['title'] ?> </a> </li> <?php endforeach; ?> </ul> </body> </html> By convention, the file that contains all of the application logic - index.php - is known as a "controller". The term controller is a word you'll hear a lot, regardless of the language or framework you use. It refers simply to the area of your code that processes user input and prepares the response. In this case, our controller prepares data from the database and then includes a template to present that data. With the controller isolated, you could easily change just the template file if you needed to render the blog entries in some other format (e.g. list.json.php for JSON format). Isolating the Application (Domain) Logic So far the application contains only one page. But what if a second page needed to use the same database connection, or even the same array of blog posts? Refactor the code so that the core behavior and data- access functions of the application are isolated in a new file called model.php: Listing <?php 6-4 // model.php function open_database_connection() { $link = mysql_connect('localhost', 'myuser', 'mypassword'); mysql_select_db('blog_db', $link); return $link; } function close_database_connection($link) { mysql_close($link); } function get_all_posts() { $link = open_database_connection(); $result = mysql_query('SELECT id, title FROM post', $link); $posts = array(); while ($row = mysql_fetch_assoc($result)) { $posts[] = $row; } close_database_connection($link); return $posts; } PDF brought to you by Chapter 6: Symfony2 versus Flat PHP | 46 generated on June 20, 2012
  • 47. The filename model.php is used because the logic and data access of an application is traditionally known as the "model" layer. In a well-organized application, the majority of the code representing your "business logic" should live in the model (as opposed to living in a controller). And unlike in this example, only a portion (or none) of the model is actually concerned with accessing a database. The controller (index.php) is now very simple: <?php Listing 6-5 require_once 'model.php'; $posts = get_all_posts(); require 'templates/list.php'; Now, the sole task of the controller is to get data from the model layer of the application (the model) and to call a template to render that data. This is a very simple example of the model-view-controller pattern. Isolating the Layout At this point, the application has been refactored into three distinct pieces offering various advantages and the opportunity to reuse almost everything on different pages. The only part of the code that can't be reused is the page layout. Fix that by creating a new layout.php file: <!-- templates/layout.php --> Listing 6-6 <html> <head> <title><?php echo $title ?></title> </head> <body> <?php echo $content ?> </body> </html> The template (templates/list.php) can now be simplified to "extend" the layout: <?php $title = 'List of Posts' ?> Listing 6-7 <?php ob_start() ?> <h1>List of Posts</h1> <ul> <?php foreach ($posts as $post): ?> <li> <a href="/read?id=<?php echo $post['id'] ?>"> <?php echo $post['title'] ?> </a> </li> <?php endforeach; ?> </ul> <?php $content = ob_get_clean() ?> <?php include 'layout.php' ?> You've now introduced a methodology that allows for the reuse of the layout. Unfortunately, to accomplish this, you're forced to use a few ugly PHP functions (ob_start(), ob_get_clean()) in the template. Symfony2 uses a Templating component that allows this to be accomplished cleanly and easily. You'll see it in action shortly. PDF brought to you by Chapter 6: Symfony2 versus Flat PHP | 47 generated on June 20, 2012
  • 48. Adding a Blog "show" Page The blog "list" page has now been refactored so that the code is better-organized and reusable. To prove it, add a blog "show" page, which displays an individual blog post identified by an id query parameter. To begin, create a new function in the model.php file that retrieves an individual blog result based on a given id: Listing // model.php 6-8 function get_post_by_id($id) { $link = open_database_connection(); $id = mysql_real_escape_string($id); $query = 'SELECT date, title, body FROM post WHERE id = '.$id; $result = mysql_query($query); $row = mysql_fetch_assoc($result); close_database_connection($link); return $row; } Next, create a new file called show.php - the controller for this new page: Listing <?php 6-9 require_once 'model.php'; $post = get_post_by_id($_GET['id']); require 'templates/show.php'; Finally, create the new template file - templates/show.php - to render the individual blog post: Listing <?php $title = $post['title'] ?> 6-10 <?php ob_start() ?> <h1><?php echo $post['title'] ?></h1> <div class="date"><?php echo $post['date'] ?></div> <div class="body"> <?php echo $post['body'] ?> </div> <?php $content = ob_get_clean() ?> <?php include 'layout.php' ?> Creating the second page is now very easy and no code is duplicated. Still, this page introduces even more lingering problems that a framework can solve for you. For example, a missing or invalid id query parameter will cause the page to crash. It would be better if this caused a 404 page to be rendered, but this can't really be done easily yet. Worse, had you forgotten to clean the id parameter via the mysql_real_escape_string() function, your entire database would be at risk for an SQL injection attack. Another major problem is that each individual controller file must include the model.php file. What if each controller file suddenly needed to include an additional file or perform some other global task (e.g. enforce security)? As it stands now, that code would need to be added to every controller file. If you forget to include something in one file, hopefully it doesn't relate to security... PDF brought to you by Chapter 6: Symfony2 versus Flat PHP | 48 generated on June 20, 2012
  • 49. A "Front Controller" to the Rescue The solution is to use a front controller: a single PHP file through which all requests are processed. With a front controller, the URIs for the application change slightly, but start to become more flexible: Without a front controller Listing 6-11 /index.php => Blog post list page (index.php executed) /show.php => Blog post show page (show.php executed) With index.php as the front controller /index.php => Blog post list page (index.php executed) /index.php/show => Blog post show page (index.php executed) The index.php portion of the URI can be removed if using Apache rewrite rules (or equivalent). In that case, the resulting URI of the blog show page would be simply /show. When using a front controller, a single PHP file (index.php in this case) renders every request. For the blog post show page, /index.php/show will actually execute the index.php file, which is now responsible for routing requests internally based on the full URI. As you'll see, a front controller is a very powerful tool. Creating the Front Controller You're about to take a big step with the application. With one file handling all requests, you can centralize things such as security handling, configuration loading, and routing. In this application, index.php must now be smart enough to render the blog post list page or the blog post show page based on the requested URI: <?php Listing 6-12 // index.php // load and initialize any global libraries require_once 'model.php'; require_once 'controllers.php'; // route the request internally $uri = $_SERVER['REQUEST_URI']; if ($uri == '/index.php') { list_action(); } elseif ($uri == '/index.php/show' && isset($_GET['id'])) { show_action($_GET['id']); } else { header('Status: 404 Not Found'); echo '<html><body><h1>Page Not Found</h1></body></html>'; } For organization, both controllers (formerly index.php and show.php) are now PHP functions and each has been moved into a separate file, controllers.php: function list_action() Listing 6-13 { $posts = get_all_posts(); require 'templates/list.php'; } function show_action($id) PDF brought to you by Chapter 6: Symfony2 versus Flat PHP | 49 generated on June 20, 2012
  • 50. { $post = get_post_by_id($id); require 'templates/show.php'; } As a front controller, index.php has taken on an entirely new role, one that includes loading the core libraries and routing the application so that one of the two controllers (the list_action() and show_action() functions) is called. In reality, the front controller is beginning to look and act a lot like Symfony2's mechanism for handling and routing requests. Another advantage of a front controller is flexible URLs. Notice that the URL to the blog post show page could be changed from /show to /read by changing code in only one location. Before, an entire file needed to be renamed. In Symfony2, URLs are even more flexible. By now, the application has evolved from a single PHP file into a structure that is organized and allows for code reuse. You should be happier, but far from satisfied. For example, the "routing" system is fickle, and wouldn't recognize that the list page (/index.php) should be accessible also via / (if Apache rewrite rules were added). Also, instead of developing the blog, a lot of time is being spent working on the "architecture" of the code (e.g. routing, calling controllers, templates, etc.). More time will need to be spent to handle form submissions, input validation, logging and security. Why should you have to reinvent solutions to all these routine problems? Add a Touch of Symfony2 Symfony2 to the rescue. Before actually using Symfony2, you need to make sure PHP knows how to find the Symfony2 classes. This is accomplished via an autoloader that Symfony provides. An autoloader is a tool that makes it possible to start using PHP classes without explicitly including the file containing the class. First, download symfony2 and place it into a vendor/symfony/ directory. Next, create an app/ bootstrap.php file. Use it to require the two files in the application and to configure the autoloader: Listing <?php 6-14 // bootstrap.php require_once 'model.php'; require_once 'controllers.php'; require_once 'vendor/symfony/src/Symfony/Component/ClassLoader/UniversalClassLoader.php'; $loader = new SymfonyComponentClassLoaderUniversalClassLoader(); $loader->registerNamespaces(array( 'Symfony' => __DIR__.'/../vendor/symfony/src', )); $loader->register(); This tells the autoloader where the Symfony classes are. With this, you can start using Symfony classes without using the require statement for the files that contain them. Core to Symfony's philosophy is the idea that an application's main job is to interpret each request and return a response. To this end, Symfony2 provides both a Request3 and a Response4 class. These classes are object-oriented representations of the raw HTTP request being processed and the HTTP response being returned. Use them to improve the blog: 2. http://symfony.com/download 3. http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/Request.html 4. http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/Response.html PDF brought to you by Chapter 6: Symfony2 versus Flat PHP | 50 generated on June 20, 2012
  • 51. <?php Listing 6-15 // index.php require_once 'app/bootstrap.php'; use SymfonyComponentHttpFoundationRequest; use SymfonyComponentHttpFoundationResponse; $request = Request::createFromGlobals(); $uri = $request->getPathInfo(); if ($uri == '/') { $response = list_action(); } elseif ($uri == '/show' && $request->query->has('id')) { $response = show_action($request->query->get('id')); } else { $html = '<html><body><h1>Page Not Found</h1></body></html>'; $response = new Response($html, 404); } // echo the headers and send the response $response->send(); The controllers are now responsible for returning a Response object. To make this easier, you can add a new render_template() function, which, incidentally, acts quite a bit like the Symfony2 templating engine: // controllers.php Listing 6-16 use SymfonyComponentHttpFoundationResponse; function list_action() { $posts = get_all_posts(); $html = render_template('templates/list.php', array('posts' => $posts)); return new Response($html); } function show_action($id) { $post = get_post_by_id($id); $html = render_template('templates/show.php', array('post' => $post)); return new Response($html); } // helper function to render templates function render_template($path, array $args) { extract($args); ob_start(); require $path; $html = ob_get_clean(); return $html; } By bringing in a small part of Symfony2, the application is more flexible and reliable. The Request provides a dependable way to access information about the HTTP request. Specifically, the getPathInfo() method returns a cleaned URI (always returning /show and never /index.php/show). PDF brought to you by Chapter 6: Symfony2 versus Flat PHP | 51 generated on June 20, 2012
  • 52. So, even if the user goes to /index.php/show, the application is intelligent enough to route the request through show_action(). The Response object gives flexibility when constructing the HTTP response, allowing HTTP headers and content to be added via an object-oriented interface. And while the responses in this application are simple, this flexibility will pay dividends as your application grows. The Sample Application in Symfony2 The blog has come a long way, but it still contains a lot of code for such a simple application. Along the way, we've also invented a simple routing system and a method using ob_start() and ob_get_clean() to render templates. If, for some reason, you needed to continue building this "framework" from scratch, you could at least use Symfony's standalone Routing5 and Templating6 components, which already solve these problems. Instead of re-solving common problems, you can let Symfony2 take care of them for you. Here's the same sample application, now built in Symfony2: Listing <?php 6-17 // src/Acme/BlogBundle/Controller/BlogController.php namespace AcmeBlogBundleController; use SymfonyBundleFrameworkBundleControllerController; class BlogController extends Controller { public function listAction() { $posts = $this->get('doctrine')->getEntityManager() ->createQuery('SELECT p FROM AcmeBlogBundle:Post p') ->execute(); return $this->render('AcmeBlogBundle:Blog:list.html.php', array('posts' => $posts)); } public function showAction($id) { $post = $this->get('doctrine') ->getEntityManager() ->getRepository('AcmeBlogBundle:Post') ->find($id); if (!$post) { // cause the 404 page not found to be displayed throw $this->createNotFoundException(); } return $this->render('AcmeBlogBundle:Blog:show.html.php', array('post' => $post)); } } The two controllers are still lightweight. Each uses the Doctrine ORM library to retrieve objects from the database and the Templating component to render a template and return a Response object. The list template is now quite a bit simpler: Listing <!-- src/Acme/BlogBundle/Resources/views/Blog/list.html.php --> 6-18 <?php $view->extend('::layout.html.php') ?> 5. https://github.com/symfony/Routing 6. https://github.com/symfony/Templating PDF brought to you by Chapter 6: Symfony2 versus Flat PHP | 52 generated on June 20, 2012
  • 53. <?php $view['slots']->set('title', 'List of Posts') ?> <h1>List of Posts</h1> <ul> <?php foreach ($posts as $post): ?> <li> <a href="<?php echo $view['router']->generate('blog_show', array('id' => $post->getId())) ?>"> <?php echo $post->getTitle() ?> </a> </li> <?php endforeach; ?> </ul> The layout is nearly identical: <!-- app/Resources/views/layout.html.php --> Listing 6-19 <html> <head> <title><?php echo $view['slots']->output('title', 'Default title') ?></title> </head> <body> <?php echo $view['slots']->output('_content') ?> </body> </html> We'll leave the show template as an exercise, as it should be trivial to create based on the list template. When Symfony2's engine (called the Kernel) boots up, it needs a map so that it knows which controllers to execute based on the request information. A routing configuration map provides this information in a readable format: # app/config/routing.yml Listing 6-20 blog_list: pattern: /blog defaults: { _controller: AcmeBlogBundle:Blog:list } blog_show: pattern: /blog/show/{id} defaults: { _controller: AcmeBlogBundle:Blog:show } Now that Symfony2 is handling all the mundane tasks, the front controller is dead simple. And since it does so little, you'll never have to touch it once it's created (and if you use a Symfony2 distribution, you won't even need to create it!): <?php Listing 6-21 // web/app.php require_once __DIR__.'/../app/bootstrap.php'; require_once __DIR__.'/../app/AppKernel.php'; use SymfonyComponentHttpFoundationRequest; $kernel = new AppKernel('prod', false); $kernel->handle(Request::createFromGlobals())->send(); PDF brought to you by Chapter 6: Symfony2 versus Flat PHP | 53 generated on June 20, 2012
  • 54. The front controller's only job is to initialize Symfony2's engine (Kernel) and pass it a Request object to handle. Symfony2's core then uses the routing map to determine which controller to call. Just like before, the controller method is responsible for returning the final Response object. There's really not much else to it. For a visual representation of how Symfony2 handles each request, see the request flow diagram. Where Symfony2 Delivers In the upcoming chapters, you'll learn more about how each piece of Symfony works and the recommended organization of a project. For now, let's see how migrating the blog from flat PHP to Symfony2 has improved life: • Your application now has clear and consistently organized code (though Symfony doesn't force you into this). This promotes reusability and allows for new developers to be productive in your project more quickly. • 100% of the code you write is for your application. You don't need to develop or maintain low-level utilities such as autoloading, routing, or rendering controllers. • Symfony2 gives you access to open source tools such as Doctrine and the Templating, Security, Form, Validation and Translation components (to name a few). • The application now enjoys fully-flexible URLs thanks to the Routing component. • Symfony2's HTTP-centric architecture gives you access to powerful tools such as HTTP caching powered by Symfony2's internal HTTP cache or more powerful tools such as Varnish7. This is covered in a later chapter all about caching. And perhaps best of all, by using Symfony2, you now have access to a whole set of high-quality open source tools developed by the Symfony2 community! A good selection of Symfony2 community tools can be found on KnpBundles.com8. Better templates If you choose to use it, Symfony2 comes standard with a templating engine called Twig9 that makes templates faster to write and easier to read. It means that the sample application could contain even less code! Take, for example, the list template written in Twig: Listing {# src/Acme/BlogBundle/Resources/views/Blog/list.html.twig #} 6-22 {% extends "::layout.html.twig" %} {% block title %}List of Posts{% endblock %} {% block body %} <h1>List of Posts</h1> <ul> {% for post in posts %} <li> <a href="{{ path('blog_show', { 'id': post.id }) }}"> {{ post.title }} </a> </li> {% endfor %} </ul> {% endblock %} 7. http://www.varnish-cache.org 8. http://knpbundles.com/ 9. http://twig.sensiolabs.org PDF brought to you by Chapter 6: Symfony2 versus Flat PHP | 54 generated on June 20, 2012
  • 55. The corresponding layout.html.twig template is also easier to write: {# app/Resources/views/layout.html.twig #} Listing 6-23 <html> <head> <title>{% block title %}Default title{% endblock %}</title> </head> <body> {% block body %}{% endblock %} </body> </html> Twig is well-supported in Symfony2. And while PHP templates will always be supported in Symfony2, we'll continue to discuss the many advantages of Twig. For more information, see the templating chapter. Learn more from the Cookbook • How to use PHP instead of Twig for Templates • How to define Controllers as Services PDF brought to you by Chapter 6: Symfony2 versus Flat PHP | 55 generated on June 20, 2012
  • 56. Chapter 7 Installing and Configuring Symfony The goal of this chapter is to get you up and running with a working application built on top of Symfony. Fortunately, Symfony offers "distributions", which are functional Symfony "starter" projects that you can download and begin developing in immediately. If you're looking for instructions on how best to create a new project and store it via source control, see Using Source Control. Downloading a Symfony2 Distribution First, check that you have installed and configured a Web server (such as Apache) with PHP 5.3.2 or higher. For more information on Symfony2 requirements, see the requirements reference. For information on configuring your specific web server document root, see the following documentation: Apache1 | Nginx2 . Symfony2 packages "distributions", which are fully-functional applications that include the Symfony2 core libraries, a selection of useful bundles, a sensible directory structure and some default configuration. When you download a Symfony2 distribution, you're downloading a functional application skeleton that can be used immediately to begin developing your application. Start by visiting the Symfony2 download page at http://symfony.com/download3. On this page, you'll see the Symfony Standard Edition, which is the main Symfony2 distribution. Here, you'll need to make two choices: • Download either a .tgz or .zip archive - both are equivalent, download whatever you're more comfortable using; 1. http://httpd.apache.org/docs/current/mod/core.html#documentroot 2. http://wiki.nginx.org/Symfony 3. http://symfony.com/download PDF brought to you by Chapter 7: Installing and Configuring Symfony | 56 generated on June 20, 2012
  • 57. • Download the distribution with or without vendors. If you have Git4 installed on your computer, you should download Symfony2 "without vendors", as it adds a bit more flexibility when including third-party/vendor libraries. Download one of the archives somewhere under your local web server's root directory and unpack it. From a UNIX command line, this can be done with one of the following commands (replacing ### with your actual filename): # for .tgz file Listing 7-1 tar zxvf Symfony_Standard_Vendors_2.0.###.tgz # for a .zip file unzip Symfony_Standard_Vendors_2.0.###.zip When you're finished, you should have a Symfony/ directory that looks something like this: www/ <- your web root directory Listing 7-2 Symfony/ <- the unpacked archive app/ cache/ config/ logs/ src/ ... vendor/ ... web/ app.php ... Updating Vendors Finally, if you downloaded the archive "without vendors", install the vendors by running the following command from the command line: php bin/vendors install Listing 7-3 This command downloads all of the necessary vendor libraries - including Symfony itself - into the vendor/ directory. For more information on how third-party vendor libraries are managed inside Symfony2, see "Managing Vendor Libraries with bin/vendors and deps". Configuration and Setup At this point, all of the needed third-party libraries now live in the vendor/ directory. You also have a default application setup in app/ and some sample code inside the src/ directory. Symfony2 comes with a visual server configuration tester to help make sure your Web server and PHP are configured to use Symfony. Use the following URL to check your configuration: http://localhost/Symfony/web/config.php Listing 7-4 If there are any issues, correct them now before moving on. 4. http://git-scm.com/ PDF brought to you by Chapter 7: Installing and Configuring Symfony | 57 generated on June 20, 2012
  • 58. Setting up Permissions One common issue is that the app/cache and app/logs directories must be writable both by the web server and the command line user. On a UNIX system, if your web server user is different from your command line user, you can run the following commands just once in your project to ensure that permissions will be setup properly. Change www-data to your web server user: 1. Using ACL on a system that supports chmod +a Many systems allow you to use the chmod +a command. Try this first, and if you get an error - try the next method: Listing rm -rf app/cache/* 7-5 rm -rf app/logs/* sudo chmod +a "www-data allow delete,write,append,file_inherit,directory_inherit" app/ cache app/logs sudo chmod +a "`whoami` allow delete,write,append,file_inherit,directory_inherit" app/ cache app/logs 2. Using Acl on a system that does not support chmod +a Some systems don't support chmod +a, but do support another utility called setfacl. You may need to enable ACL support5 on your partition and install setfacl before using it (as is the case with Ubuntu), like so: Listing sudo setfacl -R -m u:www-data:rwx -m u:`whoami`:rwx app/cache app/logs 7-6 sudo setfacl -dR -m u:www-data:rwx -m u:`whoami`:rwx app/cache app/logs Note that not all web servers run as the user www-data. You have to check which user the web server is being run as and put it in for www-data. This can be done by checking your process list to see which user is running your web server processes. 3. Without using ACL If you don't have access to changing the ACL of the directories, you will need to change the umask so that the cache and log directories will be group-writable or world-writable (depending if the web server user and the command line user are in the same group or not). To achieve this, put the following line at the beginning of the app/console, web/app.php and web/app_dev.php files: Listing umask(0002); // This will let the permissions be 0775 7-7 // or umask(0000); // This will let the permissions be 0777 Note that using the ACL is recommended when you have access to them on your server because changing the umask is not thread-safe. When everything is fine, click on "Go to the Welcome page" to request your first "real" Symfony2 webpage: Listing http://localhost/Symfony/web/app_dev.php/ 7-8 Symfony2 should welcome and congratulate you for your hard work so far! 5. https://help.ubuntu.com/community/FilePermissionsACLs PDF brought to you by Chapter 7: Installing and Configuring Symfony | 58 generated on June 20, 2012
  • 59. Beginning Development Now that you have a fully-functional Symfony2 application, you can begin development! Your distribution may contain some sample code - check the README.rst file included with the distribution (open it as a text file) to learn about what sample code was included with your distribution and how you can remove it later. If you're new to Symfony, join us in the "Creating Pages in Symfony2", where you'll learn how to create pages, change configuration, and do everything else you'll need in your new application. Using Source Control If you're using a version control system like Git or Subversion, you can setup your version control system and begin committing your project to it as normal. The Symfony Standard edition is the starting point for your new project. For specific instructions on how best to setup your project to be stored in git, see How to Create and store a Symfony2 Project in git. Ignoring the vendor/ Directory If you've downloaded the archive without vendors, you can safely ignore the entire vendor/ directory and not commit it to source control. With Git, this is done by creating and adding the following to a .gitignore file: vendor/ Listing 7-9 Now, the vendor directory won't be committed to source control. This is fine (actually, it's great!) because when someone else clones or checks out the project, he/she can simply run the php bin/vendors install script to download all the necessary vendor libraries. PDF brought to you by Chapter 7: Installing and Configuring Symfony | 59 generated on June 20, 2012
  • 60. Chapter 8 Creating Pages in Symfony2 Creating a new page in Symfony2 is a simple two-step process: • Create a route: A route defines the URL (e.g. /about) to your page and specifies a controller (which is a PHP function) that Symfony2 should execute when the URL of an incoming request matches the route pattern; • Create a controller: A controller is a PHP function that takes the incoming request and transforms it into the Symfony2 Response object that's returned to the user. This simple approach is beautiful because it matches the way that the Web works. Every interaction on the Web is initiated by an HTTP request. The job of your application is simply to interpret the request and return the appropriate HTTP response. Symfony2 follows this philosophy and provides you with tools and conventions to keep your application organized as it grows in users and complexity. Sounds simple enough? Let's dive in! The "Hello Symfony!" Page Let's start with a spin off of the classic "Hello World!" application. When you're finished, the user will be able to get a personal greeting (e.g. "Hello Symfony") by going to the following URL: Listing http://localhost/app_dev.php/hello/Symfony 8-1 Actually, you'll be able to replace Symfony with any other name to be greeted. To create the page, follow the simple two-step process. The tutorial assumes that you've already downloaded Symfony2 and configured your webserver. The above URL assumes that localhost points to the web directory of your new Symfony2 project. For detailed information on this process, see the documentation on the web server you are using. Here's the relevant documentation page for some web server you might be using: • For Apache HTTP Server, refer to Apache's DirectoryIndex documentation1. PDF brought to you by Chapter 8: Creating Pages in Symfony2 | 60 generated on June 20, 2012
  • 61. • For Nginx, refer to Nginx HttpCoreModule location documentation2. Before you begin: Create the Bundle Before you begin, you'll need to create a bundle. In Symfony2, a bundle is like a plugin, except that all of the code in your application will live inside a bundle. A bundle is nothing more than a directory that houses everything related to a specific feature, including PHP classes, configuration, and even stylesheets and Javascript files (see The Bundle System). To create a bundle called AcmeHelloBundle (a play bundle that you'll build in this chapter), run the following command and follow the on-screen instructions (use all of the default options): php app/console generate:bundle --namespace=Acme/HelloBundle --format=yml Listing 8-2 Behind the scenes, a directory is created for the bundle at src/Acme/HelloBundle. A line is also automatically added to the app/AppKernel.php file so that the bundle is registered with the kernel: // app/AppKernel.php Listing 8-3 public function registerBundles() { $bundles = array( // ... new AcmeHelloBundleAcmeHelloBundle(), ); // ... return $bundles; } Now that you have a bundle setup, you can begin building your application inside the bundle. Step 1: Create the Route By default, the routing configuration file in a Symfony2 application is located at app/config/ routing.yml. Like all configuration in Symfony2, you can also choose to use XML or PHP out of the box to configure routes. If you look at the main routing file, you'll see that Symfony already added an entry when you generated the AcmeHelloBundle: # app/config/routing.yml Listing 8-4 AcmeHelloBundle: resource: "@AcmeHelloBundle/Resources/config/routing.yml" prefix: / This entry is pretty basic: it tells Symfony to load routing configuration from the Resources/config/ routing.yml file that lives inside the AcmeHelloBundle. This means that you place routing configuration directly in app/config/routing.yml or organize your routes throughout your application, and import them from here. Now that the routing.yml file from the bundle is being imported, add the new route that defines the URL of the page that you're about to create: 1. http://httpd.apache.org/docs/2.0/mod/mod_dir.html 2. http://wiki.nginx.org/HttpCoreModule#location PDF brought to you by Chapter 8: Creating Pages in Symfony2 | 61 generated on June 20, 2012
  • 62. Listing # src/Acme/HelloBundle/Resources/config/routing.yml 8-5 hello: pattern: /hello/{name} defaults: { _controller: AcmeHelloBundle:Hello:index } The routing consists of two basic pieces: the pattern, which is the URL that this route will match, and a defaults array, which specifies the controller that should be executed. The placeholder syntax in the pattern ({name}) is a wildcard. It means that /hello/Ryan, /hello/Fabien or any other similar URL will match this route. The {name} placeholder parameter will also be passed to the controller so that you can use its value to personally greet the user. The routing system has many more great features for creating flexible and powerful URL structures in your application. For more details, see the chapter all about Routing. Step 2: Create the Controller When a URL such as /hello/Ryan is handled by the application, the hello route is matched and the AcmeHelloBundle:Hello:index controller is executed by the framework. The second step of the page- creation process is to create that controller. The controller - AcmeHelloBundle:Hello:index is the logical name of the controller, and it maps to the indexAction method of a PHP class called AcmeHelloBundleControllerHello. Start by creating this file inside your AcmeHelloBundle: Listing // src/Acme/HelloBundle/Controller/HelloController.php 8-6 namespace AcmeHelloBundleController; use SymfonyComponentHttpFoundationResponse; class HelloController { } In reality, the controller is nothing more than a PHP method that you create and Symfony executes. This is where your code uses information from the request to build and prepare the resource being requested. Except in some advanced cases, the end product of a controller is always the same: a Symfony2 Response object. Create the indexAction method that Symfony will execute when the hello route is matched: Listing // src/Acme/HelloBundle/Controller/HelloController.php 8-7 // ... class HelloController { public function indexAction($name) { return new Response('<html><body>Hello '.$name.'!</body></html>'); } } The controller is simple: it creates a new Response object, whose first argument is the content that should be used in the response (a small HTML page in this example). Congratulations! After creating only a route and a controller, you already have a fully-functional page! If you've setup everything correctly, your application should greet you: PDF brought to you by Chapter 8: Creating Pages in Symfony2 | 62 generated on June 20, 2012
  • 63. http://localhost/app_dev.php/hello/Ryan Listing 8-8 You can also view your app in the "prod" environment by visiting: http://localhost/app.php/hello/Ryan Listing 8-9 If you get an error, it's likely because you need to clear your cache by running: php app/console cache:clear --env=prod --no-debug Listing 8-10 An optional, but common, third step in the process is to create a template. Controllers are the main entry point for your code and a key ingredient when creating pages. Much more information can be found in the Controller Chapter. Optional Step 3: Create the Template Templates allows you to move all of the presentation (e.g. HTML code) into a separate file and reuse different portions of the page layout. Instead of writing the HTML inside the controller, render a template instead: 1 // src/Acme/HelloBundle/Controller/HelloController.php Listing Listing 8-11 8-12 2 namespace AcmeHelloBundleController; 3 4 use SymfonyBundleFrameworkBundleControllerController; 5 6 class HelloController extends Controller 7 { 8 public function indexAction($name) 9 { 10 return $this->render('AcmeHelloBundle:Hello:index.html.twig', array('name' => 11 $name)); 12 13 // render a PHP template instead 14 // return $this->render('AcmeHelloBundle:Hello:index.html.php', array('name' => 15 $name)); } } In order to use the render() method, your controller must extend the SymfonyBundleFrameworkBundleControllerController class (API docs: Controller3), which adds shortcuts for tasks that are common inside controllers. This is done in the above example by adding the use statement on line 4 and then extending Controller on line 6. The render() method creates a Response object filled with the content of the given, rendered template. Like any other controller, you will ultimately return that Response object. 3. http://api.symfony.com/2.0/Symfony/Bundle/FrameworkBundle/Controller/Controller.html PDF brought to you by Chapter 8: Creating Pages in Symfony2 | 63 generated on June 20, 2012
  • 64. Notice that there are two different examples for rendering the template. By default, Symfony2 supports two different templating languages: classic PHP templates and the succinct but powerful Twig4 templates. Don't be alarmed - you're free to choose either or even both in the same project. The controller renders the AcmeHelloBundle:Hello:index.html.twig template, which uses the following naming convention: BundleName:ControllerName:TemplateName This is the logical name of the template, which is mapped to a physical location using the following convention. /path/to/BundleName/Resources/views/ControllerName/TemplateName In this case, AcmeHelloBundle is the bundle name, Hello is the controller, and index.html.twig the template: Listing 1 Listing {# src/Acme/HelloBundle/Resources/views/Hello/index.html.twig #} 8-13 8-14 2 {% extends '::base.html.twig' %} 3 4 {% block body %} 5 Hello {{ name }}! 6 {% endblock %} Let's step through the Twig template line-by-line: • line 2: The extends token defines a parent template. The template explicitly defines a layout file inside of which it will be placed. • line 4: The block token says that everything inside should be placed inside a block called body. As you'll see, it's the responsibility of the parent template (base.html.twig) to ultimately render the block called body. The parent template, ::base.html.twig, is missing both the BundleName and ControllerName portions of its name (hence the double colon (::) at the beginning). This means that the template lives outside of the bundles and in the app directory: Listing {# app/Resources/views/base.html.twig #} 8-15 <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>{% block title %}Welcome!{% endblock %}</title> {% block stylesheets %}{% endblock %} <link rel="shortcut icon" href="{{ asset('favicon.ico') }}" /> </head> <body> {% block body %}{% endblock %} {% block javascripts %}{% endblock %} </body> </html> The base template file defines the HTML layout and renders the body block that you defined in the index.html.twig template. It also renders a title block, which you could choose to define in the index.html.twig template. Since you did not define the title block in the child template, it defaults to "Welcome!". 4. http://twig.sensiolabs.org PDF brought to you by Chapter 8: Creating Pages in Symfony2 | 64 generated on June 20, 2012
  • 65. Templates are a powerful way to render and organize the content for your page. A template can render anything, from HTML markup, to CSS code, or anything else that the controller may need to return. In the lifecycle of handling a request, the templating engine is simply an optional tool. Recall that the goal of each controller is to return a Response object. Templates are a powerful, but optional, tool for creating the content for that Response object. The Directory Structure After just a few short sections, you already understand the philosophy behind creating and rendering pages in Symfony2. You've also already begun to see how Symfony2 projects are structured and organized. By the end of this section, you'll know where to find and put different types of files and why. Though entirely flexible, by default, each Symfony application has the same basic and recommended directory structure: • app/: This directory contains the application configuration; • src/: All the project PHP code is stored under this directory; • vendor/: Any vendor libraries are placed here by convention; • web/: This is the web root directory and contains any publicly accessible files; The Web Directory The web root directory is the home of all public and static files including images, stylesheets, and JavaScript files. It is also where each front controller lives: // web/app.php Listing 8-16 require_once __DIR__.'/../app/bootstrap.php.cache'; require_once __DIR__.'/../app/AppKernel.php'; use SymfonyComponentHttpFoundationRequest; $kernel = new AppKernel('prod', false); $kernel->loadClassCache(); $kernel->handle(Request::createFromGlobals())->send(); The front controller file (app.php in this example) is the actual PHP file that's executed when using a Symfony2 application and its job is to use a Kernel class, AppKernel, to bootstrap the application. Having a front controller means different and more flexible URLs than are used in a typical flat PHP application. When using a front controller, URLs are formatted in the following way: http://localhost/app.php/hello/Ryan Listing 8-17 The front controller, app.php, is executed and the "internal:" URL /hello/Ryan is routed internally using the routing configuration. By using Apache mod_rewrite rules, you can force the app.php file to be executed without needing to specify it in the URL: http://localhost/hello/Ryan Listing 8-18 Though front controllers are essential in handling every request, you'll rarely need to modify or even think about them. We'll mention them again briefly in the Environments section. PDF brought to you by Chapter 8: Creating Pages in Symfony2 | 65 generated on June 20, 2012
  • 66. The Application (app) Directory As you saw in the front controller, the AppKernel class is the main entry point of the application and is responsible for all configuration. As such, it is stored in the app/ directory. This class must implement two methods that define everything that Symfony needs to know about your application. You don't even need to worry about these methods when starting - Symfony fills them in for you with sensible defaults. • registerBundles(): Returns an array of all bundles needed to run the application (see The Bundle System); • registerContainerConfiguration(): Loads the main application configuration resource file (see the Application Configuration section). In day-to-day development, you'll mostly use the app/ directory to modify configuration and routing files in the app/config/ directory (see Application Configuration). It also contains the application cache directory (app/cache), a log directory (app/logs) and a directory for application-level resource files, such as templates (app/Resources). You'll learn more about each of these directories in later chapters. Autoloading When Symfony is loading, a special file - app/autoload.php - is included. This file is responsible for configuring the autoloader, which will autoload your application files from the src/ directory and third-party libraries from the vendor/ directory. Because of the autoloader, you never need to worry about using include or require statements. Instead, Symfony2 uses the namespace of a class to determine its location and automatically includes the file on your behalf the instant you need a class. The autoloader is already configured to look in the src/ directory for any of your PHP classes. For autoloading to work, the class name and path to the file have to follow the same pattern: Listing Class Name: 8-19 AcmeHelloBundleControllerHelloController Path: src/Acme/HelloBundle/Controller/HelloController.php Typically, the only time you'll need to worry about the app/autoload.php file is when you're including a new third-party library in the vendor/ directory. For more information on autoloading, see How to autoload Classes. The Source (src) Directory Put simply, the src/ directory contains all of the actual code (PHP code, templates, configuration files, stylesheets, etc) that drives your application. When developing, the vast majority of your work will be done inside one or more bundles that you create in this directory. But what exactly is a bundle? The Bundle System A bundle is similar to a plugin in other software, but even better. The key difference is that everything is a bundle in Symfony2, including both the core framework functionality and the code written for your application. Bundles are first-class citizens in Symfony2. This gives you the flexibility to use pre-built PDF brought to you by Chapter 8: Creating Pages in Symfony2 | 66 generated on June 20, 2012
  • 67. features packaged in third-party bundles5 or to distribute your own bundles. It makes it easy to pick and choose which features to enable in your application and to optimize them the way you want. While you'll learn the basics here, an entire cookbook entry is devoted to the organization and best practices of bundles. A bundle is simply a structured set of files within a directory that implement a single feature. You might create a BlogBundle, a ForumBundle or a bundle for user management (many of these exist already as open source bundles). Each directory contains everything related to that feature, including PHP files, templates, stylesheets, JavaScripts, tests and anything else. Every aspect of a feature exists in a bundle and every feature lives in a bundle. An application is made up of bundles as defined in the registerBundles() method of the AppKernel class: // app/AppKernel.php Listing 8-20 public function registerBundles() { $bundles = array( new SymfonyBundleFrameworkBundleFrameworkBundle(), new SymfonyBundleSecurityBundleSecurityBundle(), new SymfonyBundleTwigBundleTwigBundle(), new SymfonyBundleMonologBundleMonologBundle(), new SymfonyBundleSwiftmailerBundleSwiftmailerBundle(), new SymfonyBundleDoctrineBundleDoctrineBundle(), new SymfonyBundleAsseticBundleAsseticBundle(), new SensioBundleFrameworkExtraBundleSensioFrameworkExtraBundle(), new JMSSecurityExtraBundleJMSSecurityExtraBundle(), ); if (in_array($this->getEnvironment(), array('dev', 'test'))) { $bundles[] = new AcmeDemoBundleAcmeDemoBundle(); $bundles[] = new SymfonyBundleWebProfilerBundleWebProfilerBundle(); $bundles[] = new SensioBundleDistributionBundleSensioDistributionBundle(); $bundles[] = new SensioBundleGeneratorBundleSensioGeneratorBundle(); } return $bundles; } With the registerBundles() method, you have total control over which bundles are used by your application (including the core Symfony bundles). A bundle can live anywhere as long as it can be autoloaded (via the autoloader configured at app/ autoload.php). Creating a Bundle The Symfony Standard Edition comes with a handy task that creates a fully-functional bundle for you. Of course, creating a bundle by hand is pretty easy as well. To show you how simple the bundle system is, create a new bundle called AcmeTestBundle and enable it. 5. http://symfony2bundles.org/ PDF brought to you by Chapter 8: Creating Pages in Symfony2 | 67 generated on June 20, 2012
  • 68. The Acme portion is just a dummy name that should be replaced by some "vendor" name that represents you or your organization (e.g. ABCTestBundle for some company named ABC). Start by creating a src/Acme/TestBundle/ directory and adding a new file called AcmeTestBundle.php: Listing // src/Acme/TestBundle/AcmeTestBundle.php 8-21 namespace AcmeTestBundle; use SymfonyComponentHttpKernelBundleBundle; class AcmeTestBundle extends Bundle { } The name AcmeTestBundle follows the standard Bundle naming conventions. You could also choose to shorten the name of the bundle to simply TestBundle by naming this class TestBundle (and naming the file TestBundle.php). This empty class is the only piece you need to create the new bundle. Though commonly empty, this class is powerful and can be used to customize the behavior of the bundle. Now that you've created the bundle, enable it via the AppKernel class: Listing // app/AppKernel.php 8-22 public function registerBundles() { $bundles = array( // ... // register your bundles new AcmeTestBundleAcmeTestBundle(), ); // ... return $bundles; } And while it doesn't do anything yet, AcmeTestBundle is now ready to be used. And as easy as this is, Symfony also provides a command-line interface for generating a basic bundle skeleton: Listing php app/console generate:bundle --namespace=Acme/TestBundle 8-23 The bundle skeleton generates with a basic controller, template and routing resource that can be customized. You'll learn more about Symfony2's command-line tools later. Whenever creating a new bundle or using a third-party bundle, always make sure the bundle has been enabled in registerBundles(). When using the generate:bundle command, this is done for you. PDF brought to you by Chapter 8: Creating Pages in Symfony2 | 68 generated on June 20, 2012
  • 69. Bundle Directory Structure The directory structure of a bundle is simple and flexible. By default, the bundle system follows a set of conventions that help to keep code consistent between all Symfony2 bundles. Take a look at AcmeHelloBundle, as it contains some of the most common elements of a bundle: • Controller/ contains the controllers of the bundle (e.g. HelloController.php); • DependencyInjection/ holds certain dependency injection extension classes, which may import service configuration, register compiler passes or more (this directory is not necessary); • Resources/config/ houses configuration, including routing configuration (e.g. routing.yml); • Resources/views/ holds templates organized by controller name (e.g. Hello/ index.html.twig); • Resources/public/ contains web assets (images, stylesheets, etc) and is copied or symbolically linked into the project web/ directory via the assets:install console command; • Tests/ holds all tests for the bundle. A bundle can be as small or large as the feature it implements. It contains only the files you need and nothing else. As you move through the book, you'll learn how to persist objects to a database, create and validate forms, create translations for your application, write tests and much more. Each of these has their own place and role within the bundle. Application Configuration An application consists of a collection of bundles representing all of the features and capabilities of your application. Each bundle can be customized via configuration files written in YAML, XML or PHP. By default, the main configuration file lives in the app/config/ directory and is called either config.yml, config.xml or config.php depending on which format you prefer: # app/config/config.yml Listing 8-24 imports: - { resource: parameters.ini } - { resource: security.yml } framework: secret: "%secret%" charset: UTF-8 router: { resource: "%kernel.root_dir%/config/routing.yml" } form: true csrf_protection: true validation: { enable_annotations: true } templating: { engines: ['twig'] } #assets_version: SomeVersionScheme session: default_locale: "%locale%" auto_start: true # Twig Configuration twig: debug: "%kernel.debug%" strict_variables: "%kernel.debug%" # ... PDF brought to you by Chapter 8: Creating Pages in Symfony2 | 69 generated on June 20, 2012
  • 70. You'll learn exactly how to load each file/format in the next section Environments. Each top-level entry like framework or twig defines the configuration for a particular bundle. For example, the framework key defines the configuration for the core Symfony FrameworkBundle and includes configuration for the routing, templating, and other core systems. For now, don't worry about the specific configuration options in each section. The configuration file ships with sensible defaults. As you read more and explore each part of Symfony2, you'll learn about the specific configuration options of each feature. Configuration Formats Throughout the chapters, all configuration examples will be shown in all three formats (YAML, XML and PHP). Each has its own advantages and disadvantages. The choice of which to use is up to you: • YAML: Simple, clean and readable; • XML: More powerful than YAML at times and supports IDE autocompletion; • PHP: Very powerful but less readable than standard configuration formats. Environments An application can run in various environments. The different environments share the same PHP code (apart from the front controller), but use different configuration. For instance, a dev environment will log warnings and errors, while a prod environment will only log errors. Some files are rebuilt on each request in the dev environment (for the developer's convenience), but cached in the prod environment. All environments live together on the same machine and execute the same application. A Symfony2 project generally begins with three environments (dev, test and prod), though creating new environments is easy. You can view your application in different environments simply by changing the front controller in your browser. To see the application in the dev environment, access the application via the development front controller: Listing http://localhost/app_dev.php/hello/Ryan 8-25 If you'd like to see how your application will behave in the production environment, call the prod front controller instead: Listing http://localhost/app.php/hello/Ryan 8-26 Since the prod environment is optimized for speed; the configuration, routing and Twig templates are compiled into flat PHP classes and cached. When viewing changes in the prod environment, you'll need to clear these cached files and allow them to rebuild: Listing php app/console cache:clear --env=prod --no-debug 8-27 If you open the web/app.php file, you'll find that it's configured explicitly to use the prod environment: PDF brought to you by Chapter 8: Creating Pages in Symfony2 | 70 generated on June 20, 2012
  • 71. $kernel = new AppKernel('prod', false); Listing 8-28 You can create a new front controller for a new environment by copying this file and changing prod to some other value. The test environment is used when running automated tests and cannot be accessed directly through the browser. See the testing chapter for more details. Environment Configuration The AppKernel class is responsible for actually loading the configuration file of your choice: // app/AppKernel.php Listing 8-29 public function registerContainerConfiguration(LoaderInterface $loader) { $loader->load(__DIR__.'/config/config_'.$this->getEnvironment().'.yml'); } You already know that the .yml extension can be changed to .xml or .php if you prefer to use either XML or PHP to write your configuration. Notice also that each environment loads its own configuration file. Consider the configuration file for the dev environment. # app/config/config_dev.yml Listing 8-30 imports: - { resource: config.yml } framework: router: { resource: "%kernel.root_dir%/config/routing_dev.yml" } profiler: { only_exceptions: false } # ... The imports key is similar to a PHP include statement and guarantees that the main configuration file (config.yml) is loaded first. The rest of the file tweaks the default configuration for increased logging and other settings conducive to a development environment. Both the prod and test environments follow the same model: each environment imports the base configuration file and then modifies its configuration values to fit the needs of the specific environment. This is just a convention, but one that allows you to reuse most of your configuration and customize just pieces of it between environments. Summary Congratulations! You've now seen every fundamental aspect of Symfony2 and have hopefully discovered how easy and flexible it can be. And while there are a lot of features still to come, be sure to keep the following basic points in mind: • creating a page is a three-step process involving a route, a controller and (optionally) a template. • each project contains just a few main directories: web/ (web assets and the front controllers), app/ (configuration), src/ (your bundles), and vendor/ (third-party code) (there's also a bin/ directory that's used to help updated vendor libraries); PDF brought to you by Chapter 8: Creating Pages in Symfony2 | 71 generated on June 20, 2012
  • 72. • each feature in Symfony2 (including the Symfony2 framework core) is organized into a bundle, which is a structured set of files for that feature; • the configuration for each bundle lives in the app/config directory and can be specified in YAML, XML or PHP; • each environment is accessible via a different front controller (e.g. app.php and app_dev.php) and loads a different configuration file. From here, each chapter will introduce you to more and more powerful tools and advanced concepts. The more you know about Symfony2, the more you'll appreciate the flexibility of its architecture and the power it gives you to rapidly develop applications. PDF brought to you by Chapter 8: Creating Pages in Symfony2 | 72 generated on June 20, 2012
  • 73. Chapter 9 Controller A controller is a PHP function you create that takes information from the HTTP request and constructs and returns an HTTP response (as a Symfony2 Response object). The response could be an HTML page, an XML document, a serialized JSON array, an image, a redirect, a 404 error or anything else you can dream up. The controller contains whatever arbitrary logic your application needs to render the content of a page. To see how simple this is, let's look at a Symfony2 controller in action. The following controller would render a page that simply prints Hello world!: use SymfonyComponentHttpFoundationResponse; Listing 9-1 public function helloAction() { return new Response('Hello world!'); } The goal of a controller is always the same: create and return a Response object. Along the way, it might read information from the request, load a database resource, send an email, or set information on the user's session. But in all cases, the controller will eventually return the Response object that will be delivered back to the client. There's no magic and no other requirements to worry about! Here are a few common examples: • Controller A prepares a Response object representing the content for the homepage of the site. • Controller B reads the slug parameter from the request to load a blog entry from the database and create a Response object displaying that blog. If the slug can't be found in the database, it creates and returns a Response object with a 404 status code. • Controller C handles the form submission of a contact form. It reads the form information from the request, saves the contact information to the database and emails the contact information to the webmaster. Finally, it creates a Response object that redirects the client's browser to the contact form "thank you" page. PDF brought to you by Chapter 9: Controller | 73 generated on June 20, 2012
  • 74. Requests, Controller, Response Lifecycle Every request handled by a Symfony2 project goes through the same simple lifecycle. The framework takes care of the repetitive tasks and ultimately executes a controller, which houses your custom application code: 1. Each request is handled by a single front controller file (e.g. app.php or app_dev.php) that bootstraps the application; 2. The Router reads information from the request (e.g. the URI), finds a route that matches that information, and reads the _controller parameter from the route; 3. The controller from the matched route is executed and the code inside the controller creates and returns a Response object; 4. The HTTP headers and content of the Response object are sent back to the client. Creating a page is as easy as creating a controller (#3) and making a route that maps a URL to that controller (#2). Though similarly named, a "front controller" is different from the "controllers" we'll talk about in this chapter. A front controller is a short PHP file that lives in your web directory and through which all requests are directed. A typical application will have a production front controller (e.g. app.php) and a development front controller (e.g. app_dev.php). You'll likely never need to edit, view or worry about the front controllers in your application. A Simple Controller While a controller can be any PHP callable (a function, method on an object, or a Closure), in Symfony2, a controller is usually a single method inside a controller object. Controllers are also called actions. 1 Listing Listing // src/Acme/HelloBundle/Controller/HelloController.php 9-2 9-3 2 3 namespace AcmeHelloBundleController; 4 use SymfonyComponentHttpFoundationResponse; 5 6 class HelloController 7 { 8 public function indexAction($name) 9 { 10 return new Response('<html><body>Hello '.$name.'!</body></html>'); 11 } 12 } Note that the controller is the indexAction method, which lives inside a controller class (HelloController). Don't be confused by the naming: a controller class is simply a convenient way to group several controllers/actions together. Typically, the controller class will house several controllers/actions (e.g. updateAction, deleteAction, etc). This controller is pretty straightforward, but let's walk through it: • line 3: Symfony2 takes advantage of PHP 5.3 namespace functionality to namespace the entire controller class. The use keyword imports the Response class, which our controller must return. PDF brought to you by Chapter 9: Controller | 74 generated on June 20, 2012
  • 75. • line 6: The class name is the concatenation of a name for the controller class (i.e. Hello) and the word Controller. This is a convention that provides consistency to controllers and allows them to be referenced only by the first part of the name (i.e. Hello) in the routing configuration. • line 8: Each action in a controller class is suffixed with Action and is referenced in the routing configuration by the action's name (index). In the next section, you'll create a route that maps a URI to this action. You'll learn how the route's placeholders ({name}) become arguments to the action method ($name). • line 10: The controller creates and returns a Response object. Mapping a URL to a Controller The new controller returns a simple HTML page. To actually view this page in your browser, you need to create a route, which maps a specific URL pattern to the controller: # app/config/routing.yml Listing 9-4 hello: pattern: /hello/{name} defaults: { _controller: AcmeHelloBundle:Hello:index } Going to /hello/ryan now executes the HelloController::indexAction() controller and passes in ryan for the $name variable. Creating a "page" means simply creating a controller method and associated route. Notice the syntax used to refer to the controller: AcmeHelloBundle:Hello:index. Symfony2 uses a flexible string notation to refer to different controllers. This is the most common syntax and tells Symfony2 to look for a controller class called HelloController inside a bundle named AcmeHelloBundle. The method indexAction() is then executed. For more details on the string format used to reference different controllers, see Controller Naming Pattern. This example places the routing configuration directly in the app/config/ directory. A better way to organize your routes is to place each route in the bundle it belongs to. For more information on this, see Including External Routing Resources. You can learn much more about the routing system in the Routing chapter. Route Parameters as Controller Arguments You already know that the _controller parameter AcmeHelloBundle:Hello:index refers to a HelloController::indexAction() method that lives inside the AcmeHelloBundle bundle. What's more interesting is the arguments that are passed to that method: <?php Listing 9-5 // src/Acme/HelloBundle/Controller/HelloController.php namespace AcmeHelloBundleController; use SymfonyBundleFrameworkBundleControllerController; class HelloController extends Controller { PDF brought to you by Chapter 9: Controller | 75 generated on June 20, 2012
  • 76. public function indexAction($name) { // ... } } The controller has a single argument, $name, which corresponds to the {name} parameter from the matched route (ryan in our example). In fact, when executing your controller, Symfony2 matches each argument of the controller with a parameter from the matched route. Take the following example: Listing # app/config/routing.yml 9-6 hello: pattern: /hello/{first_name}/{last_name} defaults: { _controller: AcmeHelloBundle:Hello:index, color: green } The controller for this can take several arguments: Listing public function indexAction($first_name, $last_name, $color) 9-7 { // ... } Notice that both placeholder variables ({first_name}, {last_name}) as well as the default color variable are available as arguments in the controller. When a route is matched, the placeholder variables are merged with the defaults to make one array that's available to your controller. Mapping route parameters to controller arguments is easy and flexible. Keep the following guidelines in mind while you develop. • The order of the controller arguments does not matter Symfony is able to match the parameter names from the route to the variable names in the controller method's signature. In other words, it realizes that the {last_name} parameter matches up with the $last_name argument. The arguments of the controller could be totally reordered and still work perfectly: Listing public function indexAction($last_name, $color, $first_name) 9-8 { // .. } • Each required controller argument must match up with a routing parameter The following would throw a RuntimeException because there is no foo parameter defined in the route: Listing public function indexAction($first_name, $last_name, $color, $foo) 9-9 { // .. } Making the argument optional, however, is perfectly ok. The following example would not throw an exception: Listing public function indexAction($first_name, $last_name, $color, $foo = 'bar') 9-10 { // .. } • Not all routing parameters need to be arguments on your controller PDF brought to you by Chapter 9: Controller | 76 generated on June 20, 2012
  • 77. If, for example, the last_name weren't important for your controller, you could omit it entirely: public function indexAction($first_name, $color) Listing 9-11 { // .. } Every route also has a special _route parameter, which is equal to the name of the route that was matched (e.g. hello). Though not usually useful, this is equally available as a controller argument. The Request as a Controller Argument For convenience, you can also have Symfony pass you the Request object as an argument to your controller. This is especially convenient when you're working with forms, for example: use SymfonyComponentHttpFoundationRequest; Listing 9-12 public function updateAction(Request $request) { $form = $this->createForm(...); $form->bindRequest($request); // ... } The Base Controller Class For convenience, Symfony2 comes with a base Controller class that assists with some of the most common controller tasks and gives your controller class access to any resource it might need. By extending this Controller class, you can take advantage of several helper methods. Add the use statement atop the Controller class and then modify the HelloController to extend it: // src/Acme/HelloBundle/Controller/HelloController.php Listing 9-13 namespace AcmeHelloBundleController; use SymfonyBundleFrameworkBundleControllerController; use SymfonyComponentHttpFoundationResponse; class HelloController extends Controller { public function indexAction($name) { return new Response('<html><body>Hello '.$name.'!</body></html>'); } } This doesn't actually change anything about how your controller works. In the next section, you'll learn about the helper methods that the base controller class makes available. These methods are just shortcuts to using core Symfony2 functionality that's available to you with or without the use of the base Controller class. A great way to see the core functionality in action is to look in the Controller1 class itself. 1. http://api.symfony.com/2.0/Symfony/Bundle/FrameworkBundle/Controller/Controller.html PDF brought to you by Chapter 9: Controller | 77 generated on June 20, 2012
  • 78. Extending the base class is optional in Symfony; it contains useful shortcuts but nothing mandatory. You can also extend SymfonyComponentDependencyInjectionContainerAware. The service container object will then be accessible via the container property. You can also define your Controllers as Services. Common Controller Tasks Though a controller can do virtually anything, most controllers will perform the same basic tasks over and over again. These tasks, such as redirecting, forwarding, rendering templates and accessing core services, are very easy to manage in Symfony2. Redirecting If you want to redirect the user to another page, use the redirect() method: Listing public function indexAction() 9-14 { return $this->redirect($this->generateUrl('homepage')); } The generateUrl() method is just a helper function that generates the URL for a given route. For more information, see the Routing chapter. By default, the redirect() method performs a 302 (temporary) redirect. To perform a 301 (permanent) redirect, modify the second argument: Listing public function indexAction() 9-15 { return $this->redirect($this->generateUrl('homepage'), 301); } The redirect() method is simply a shortcut that creates a Response object that specializes in redirecting the user. It's equivalent to: Listing use SymfonyComponentHttpFoundationRedirectResponse; 9-16 return new RedirectResponse($this->generateUrl('homepage')); Forwarding You can also easily forward to another controller internally with the forward() method. Instead of redirecting the user's browser, it makes an internal sub-request, and calls the specified controller. The forward() method returns the Response object that's returned from that controller: Listing public function indexAction($name) 9-17 { $response = $this->forward('AcmeHelloBundle:Hello:fancy', array( 'name' => $name, PDF brought to you by Chapter 9: Controller | 78 generated on June 20, 2012
  • 79. 'color' => 'green' )); // further modify the response or return it directly return $response; } Notice that the forward() method uses the same string representation of the controller used in the routing configuration. In this case, the target controller class will be HelloController inside some AcmeHelloBundle. The array passed to the method becomes the arguments on the resulting controller. This same interface is used when embedding controllers into templates (see Embedding Controllers). The target controller method should look something like the following: public function fancyAction($name, $color) Listing 9-18 { // ... create and return a Response object } And just like when creating a controller for a route, the order of the arguments to fancyAction doesn't matter. Symfony2 matches the index key names (e.g. name) with the method argument names (e.g. $name). If you change the order of the arguments, Symfony2 will still pass the correct value to each variable. Like other base Controller methods, the forward method is just a shortcut for core Symfony2 functionality. A forward can be accomplished directly via the http_kernel service. A forward returns a Response object: $httpKernel = $this->container->get('http_kernel'); Listing 9-19 $response = $httpKernel->forward('AcmeHelloBundle:Hello:fancy', array( 'name' => $name, 'color' => 'green', )); Rendering Templates Though not a requirement, most controllers will ultimately render a template that's responsible for generating the HTML (or other format) for the controller. The renderView() method renders a template and returns its content. The content from the template can be used to create a Response object: $content = $this->renderView('AcmeHelloBundle:Hello:index.html.twig', array('name' => $name)); Listing 9-20 return new Response($content); This can even be done in just one step with the render() method, which returns a Response object containing the content from the template: return $this->render('AcmeHelloBundle:Hello:index.html.twig', array('name' => $name)); Listing 9-21 In both cases, the Resources/views/Hello/index.html.twig template inside the AcmeHelloBundle will be rendered. The Symfony templating engine is explained in great detail in the Templating chapter. PDF brought to you by Chapter 9: Controller | 79 generated on June 20, 2012
  • 80. The renderView method is a shortcut to direct use of the templating service. The templating service can also be used directly: Listing $templating = $this->get('templating'); 9-22 $content = $templating->render('AcmeHelloBundle:Hello:index.html.twig', array('name' => $name)); It is possible to render templates in deeper subdirectories as well, however be careful to avoid the pitfall of making your directory structure unduly elaborate: Listing $templating->render('AcmeHelloBundle:Hello/Greetings:index.html.twig', array('name' => 9-23 $name)); // index.html.twig found in Resources/views/Hello/Greetings is rendered. Accessing other Services When extending the base controller class, you can access any Symfony2 service via the get() method. Here are several common services you might need: Listing $request = $this->getRequest(); 9-24 $templating = $this->get('templating'); $router = $this->get('router'); $mailer = $this->get('mailer'); There are countless other services available and you are encouraged to define your own. To list all available services, use the container:debug console command: Listing php app/console container:debug 9-25 For more information, see the Service Container chapter. Managing Errors and 404 Pages When things are not found, you should play well with the HTTP protocol and return a 404 response. To do this, you'll throw a special type of exception. If you're extending the base controller class, do the following: Listing public function indexAction() 9-26 { $product = // retrieve the object from database if (!$product) { throw $this->createNotFoundException('The product does not exist'); } return $this->render(...); } The createNotFoundException() method creates a special NotFoundHttpException object, which ultimately triggers a 404 HTTP response inside Symfony. PDF brought to you by Chapter 9: Controller | 80 generated on June 20, 2012
  • 81. Of course, you're free to throw any Exception class in your controller - Symfony2 will automatically return a 500 HTTP response code. throw new Exception('Something went wrong!'); Listing 9-27 In every case, a styled error page is shown to the end user and a full debug error page is shown to the developer (when viewing the page in debug mode). Both of these error pages can be customized. For details, read the "How to customize Error Pages" cookbook recipe. Managing the Session Symfony2 provides a nice session object that you can use to store information about the user (be it a real person using a browser, a bot, or a web service) between requests. By default, Symfony2 stores the attributes in a cookie by using the native PHP sessions. Storing and retrieving information from the session can be easily achieved from any controller: $session = $this->getRequest()->getSession(); Listing 9-28 // store an attribute for reuse during a later user request $session->set('foo', 'bar'); // in another controller for another request $foo = $session->get('foo'); // set the user locale $session->setLocale('fr'); These attributes will remain on the user for the remainder of that user's session. Flash Messages You can also store small messages that will be stored on the user's session for exactly one additional request. This is useful when processing a form: you want to redirect and have a special message shown on the next request. These types of messages are called "flash" messages. For example, imagine you're processing a form submit: public function updateAction() Listing 9-29 { $form = $this->createForm(...); $form->bindRequest($this->getRequest()); if ($form->isValid()) { // do some sort of processing $this->get('session')->setFlash('notice', 'Your changes were saved!'); return $this->redirect($this->generateUrl(...)); } return $this->render(...); } After processing the request, the controller sets a notice flash message and then redirects. The name (notice) isn't significant - it's just what you're using to identify the type of the message. In the template of the next action, the following code could be used to render the notice message: PDF brought to you by Chapter 9: Controller | 81 generated on June 20, 2012
  • 82. Listing {% if app.session.hasFlash('notice') %} 9-30 <div class="flash-notice"> {{ app.session.flash('notice') }} </div> {% endif %} By design, flash messages are meant to live for exactly one request (they're "gone in a flash"). They're designed to be used across redirects exactly as you've done in this example. The Response Object The only requirement for a controller is to return a Response object. The Response2 class is a PHP abstraction around the HTTP response - the text-based message filled with HTTP headers and content that's sent back to the client: Listing // create a simple Response with a 200 status code (the default) 9-31 $response = new Response('Hello '.$name, 200); // create a JSON-response with a 200 status code $response = new Response(json_encode(array('name' => $name))); $response->headers->set('Content-Type', 'application/json'); The headers property is a HeaderBag3 object with several useful methods for reading and mutating the Response headers. The header names are normalized so that using Content-Type is equivalent to content-type or even content_type. The Request Object Besides the values of the routing placeholders, the controller also has access to the Request object when extending the base Controller class: Listing $request = $this->getRequest(); 9-32 $request->isXmlHttpRequest(); // is it an Ajax request? $request->getPreferredLanguage(array('en', 'fr')); $request->query->get('page'); // get a $_GET parameter $request->request->get('page'); // get a $_POST parameter Like the Response object, the request headers are stored in a HeaderBag object and are easily accessible. Final Thoughts Whenever you create a page, you'll ultimately need to write some code that contains the logic for that page. In Symfony, this is called a controller, and it's a PHP function that can do anything it needs in order to return the final Response object that will be returned to the user. 2. http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/Response.html 3. http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/HeaderBag.html PDF brought to you by Chapter 9: Controller | 82 generated on June 20, 2012
  • 83. To make life easier, you can choose to extend a base Controller class, which contains shortcut methods for many common controller tasks. For example, since you don't want to put HTML code in your controller, you can use the render() method to render and return the content from a template. In other chapters, you'll see how the controller can be used to persist and fetch objects from a database, process form submissions, handle caching and more. Learn more from the Cookbook • How to customize Error Pages • How to define Controllers as Services PDF brought to you by Chapter 9: Controller | 83 generated on June 20, 2012
  • 84. Chapter 10 Routing Beautiful URLs are an absolute must for any serious web application. This means leaving behind ugly URLs like index.php?article_id=57 in favor of something like /read/intro-to-symfony. Having flexibility is even more important. What if you need to change the URL of a page from /blog to /news? How many links should you need to hunt down and update to make the change? If you're using Symfony's router, the change is simple. The Symfony2 router lets you define creative URLs that you map to different areas of your application. By the end of this chapter, you'll be able to: • Create complex routes that map to controllers • Generate URLs inside templates and controllers • Load routing resources from bundles (or anywhere else) • Debug your routes Routing in Action A route is a map from a URL pattern to a controller. For example, suppose you want to match any URL like /blog/my-post or /blog/all-about-symfony and send it to a controller that can look up and render that blog entry. The route is simple: Listing # app/config/routing.yml 10-1 blog_show: pattern: /blog/{slug} defaults: { _controller: AcmeBlogBundle:Blog:show } The pattern defined by the blog_show route acts like /blog/* where the wildcard is given the name slug. For the URL /blog/my-blog-post, the slug variable gets a value of my-blog-post, which is available for you to use in your controller (keep reading). The _controller parameter is a special key that tells Symfony which controller should be executed when a URL matches this route. The _controller string is called the logical name. It follows a pattern that points to a specific PHP class and method: Listing 10-2 PDF brought to you by Chapter 10: Routing | 84 generated on June 20, 2012
  • 85. // src/Acme/BlogBundle/Controller/BlogController.php namespace AcmeBlogBundleController; use SymfonyBundleFrameworkBundleControllerController; class BlogController extends Controller { public function showAction($slug) { $blog = // use the $slug variable to query the database return $this->render('AcmeBlogBundle:Blog:show.html.twig', array( 'blog' => $blog, )); } } Congratulations! You've just created your first route and connected it to a controller. Now, when you visit /blog/my-post, the showAction controller will be executed and the $slug variable will be equal to my-post. This is the goal of the Symfony2 router: to map the URL of a request to a controller. Along the way, you'll learn all sorts of tricks that make mapping even the most complex URLs easy. Routing: Under the Hood When a request is made to your application, it contains an address to the exact "resource" that the client is requesting. This address is called the URL, (or URI), and could be /contact, /blog/read-me, or anything else. Take the following HTTP request for example: GET /blog/my-blog-post Listing 10-3 The goal of the Symfony2 routing system is to parse this URL and determine which controller should be executed. The whole process looks like this: 1. The request is handled by the Symfony2 front controller (e.g. app.php); 2. The Symfony2 core (i.e. Kernel) asks the router to inspect the request; 3. The router matches the incoming URL to a specific route and returns information about the route, including the controller that should be executed; 4. The Symfony2 Kernel executes the controller, which ultimately returns a Response object. The routing layer is a tool that translates the incoming URL into a specific controller to execute. PDF brought to you by Chapter 10: Routing | 85 generated on June 20, 2012
  • 86. Creating Routes Symfony loads all the routes for your application from a single routing configuration file. The file is usually app/config/routing.yml, but can be configured to be anything (including an XML or PHP file) via the application configuration file: Listing # app/config/config.yml 10-4 framework: # ... router: { resource: "%kernel.root_dir%/config/routing.yml" } Even though all routes are loaded from a single file, it's common practice to include additional routing resources from inside the file. See the Including External Routing Resources section for more information. Basic Route Configuration Defining a route is easy, and a typical application will have lots of routes. A basic route consists of just two parts: the pattern to match and a defaults array: Listing _welcome: 10-5 pattern: / defaults: { _controller: AcmeDemoBundle:Main:homepage } This route matches the homepage (/) and maps it to the AcmeDemoBundle:Main:homepage controller. The _controller string is translated by Symfony2 into an actual PHP function and executed. That process will be explained shortly in the Controller Naming Pattern section. Routing with Placeholders Of course the routing system supports much more interesting routes. Many routes will contain one or more named "wildcard" placeholders: Listing blog_show: 10-6 pattern: /blog/{slug} defaults: { _controller: AcmeBlogBundle:Blog:show } The pattern will match anything that looks like /blog/*. Even better, the value matching the {slug} placeholder will be available inside your controller. In other words, if the URL is /blog/hello-world, a $slug variable, with a value of hello-world, will be available in the controller. This can be used, for example, to load the blog post matching that string. The pattern will not, however, match simply /blog. That's because, by default, all placeholders are required. This can be changed by adding a placeholder value to the defaults array. Required and Optional Placeholders To make things more exciting, add a new route that displays a list of all the available blog posts for this imaginary blog application: Listing blog: 10-7 pattern: /blog defaults: { _controller: AcmeBlogBundle:Blog:index } PDF brought to you by Chapter 10: Routing | 86 generated on June 20, 2012
  • 87. So far, this route is as simple as possible - it contains no placeholders and will only match the exact URL /blog. But what if you need this route to support pagination, where /blog/2 displays the second page of blog entries? Update the route to have a new {page} placeholder: blog: Listing 10-8 pattern: /blog/{page} defaults: { _controller: AcmeBlogBundle:Blog:index } Like the {slug} placeholder before, the value matching {page} will be available inside your controller. Its value can be used to determine which set of blog posts to display for the given page. But hold on! Since placeholders are required by default, this route will no longer match on simply /blog. Instead, to see page 1 of the blog, you'd need to use the URL /blog/1! Since that's no way for a rich web app to behave, modify the route to make the {page} parameter optional. This is done by including it in the defaults collection: blog: Listing 10-9 pattern: /blog/{page} defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 } By adding page to the defaults key, the {page} placeholder is no longer required. The URL /blog will match this route and the value of the page parameter will be set to 1. The URL /blog/2 will also match, giving the page parameter a value of 2. Perfect. /blog {page} = 1 /blog/1 {page} = 1 /blog/2 {page} = 2 Adding Requirements Take a quick look at the routes that have been created so far: blog: Listing 10-10 pattern: /blog/{page} defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 } blog_show: pattern: /blog/{slug} defaults: { _controller: AcmeBlogBundle:Blog:show } Can you spot the problem? Notice that both routes have patterns that match URL's that look like /blog/*. The Symfony router will always choose the first matching route it finds. In other words, the blog_show route will never be matched. Instead, a URL like /blog/my-blog-post will match the first route (blog) and return a nonsense value of my-blog-post to the {page} parameter. URL route parameters /blog/2 blog {page} = 2 /blog/my-blog-post blog {page} = my-blog-post The answer to the problem is to add route requirements. The routes in this example would work perfectly if the /blog/{page} pattern only matched URLs where the {page} portion is an integer. Fortunately, regular expression requirements can easily be added for each parameter. For example: blog: Listing 10-11 pattern: /blog/{page} PDF brought to you by Chapter 10: Routing | 87 generated on June 20, 2012
  • 88. defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 } requirements: page: d+ The d+ requirement is a regular expression that says that the value of the {page} parameter must be a digit (i.e. a number). The blog route will still match on a URL like /blog/2 (because 2 is a number), but it will no longer match a URL like /blog/my-blog-post (because my-blog-post is not a number). As a result, a URL like /blog/my-blog-post will now properly match the blog_show route. URL route parameters /blog/2 blog {page} = 2 /blog/my-blog-post blog_show {slug} = my-blog-post Earlier Routes always Win What this all means is that the order of the routes is very important. If the blog_show route were placed above the blog route, the URL /blog/2 would match blog_show instead of blog since the {slug} parameter of blog_show has no requirements. By using proper ordering and clever requirements, you can accomplish just about anything. Since the parameter requirements are regular expressions, the complexity and flexibility of each requirement is entirely up to you. Suppose the homepage of your application is available in two different languages, based on the URL: Listing homepage: 10-12 pattern: /{culture} defaults: { _controller: AcmeDemoBundle:Main:homepage, culture: en } requirements: culture: en|fr For incoming requests, the {culture} portion of the URL is matched against the regular expression (en|fr). / {culture} = en /en {culture} = en /fr {culture} = fr /es won't match this route Adding HTTP Method Requirements In addition to the URL, you can also match on the method of the incoming request (i.e. GET, HEAD, POST, PUT, DELETE). Suppose you have a contact form with two controllers - one for displaying the form (on a GET request) and one for processing the form when it's submitted (on a POST request). This can be accomplished with the following route configuration: Listing contact: 10-13 pattern: /contact defaults: { _controller: AcmeDemoBundle:Main:contact } requirements: _method: GET contact_process: PDF brought to you by Chapter 10: Routing | 88 generated on June 20, 2012
  • 89. pattern: /contact defaults: { _controller: AcmeDemoBundle:Main:contactProcess } requirements: _method: POST Despite the fact that these two routes have identical patterns (/contact), the first route will match only GET requests and the second route will match only POST requests. This means that you can display the form and submit the form via the same URL, while using distinct controllers for the two actions. If no _method requirement is specified, the route will match on all methods. Like the other requirements, the _method requirement is parsed as a regular expression. To match GET or POST requests, you can use GET|POST. Advanced Routing Example At this point, you have everything you need to create a powerful routing structure in Symfony. The following is an example of just how flexible the routing system can be: article_show: Listing 10-14 pattern: /articles/{culture}/{year}/{title}.{_format} defaults: { _controller: AcmeDemoBundle:Article:show, _format: html } requirements: culture: en|fr _format: html|rss year: d+ As you've seen, this route will only match if the {culture} portion of the URL is either en or fr and if the {year} is a number. This route also shows how you can use a period between placeholders instead of a slash. URLs matching this route might look like: • /articles/en/2010/my-post • /articles/fr/2010/my-post.rss The Special _format Routing Parameter This example also highlights the special _format routing parameter. When using this parameter, the matched value becomes the "request format" of the Request object. Ultimately, the request format is used for such things such as setting the Content-Type of the response (e.g. a json request format translates into a Content-Type of application/json). It can also be used in the controller to render a different template for each value of _format. The _format parameter is a very powerful way to render the same content in different formats. Special Routing Parameters As you've seen, each routing parameter or default value is eventually available as an argument in the controller method. Additionally, there are three parameters that are special: each adds a unique piece of functionality inside your application: • _controller: As you've seen, this parameter is used to determine which controller is executed when the route is matched; • _format: Used to set the request format (read more); • _locale: Used to set the locale on the session (read more); PDF brought to you by Chapter 10: Routing | 89 generated on June 20, 2012
  • 90. Controller Naming Pattern Every route must have a _controller parameter, which dictates which controller should be executed when that route is matched. This parameter uses a simple string pattern called the logical controller name, which Symfony maps to a specific PHP method and class. The pattern has three parts, each separated by a colon: bundle:controller:action For example, a _controller value of AcmeBlogBundle:Blog:show means: Bundle Controller Class Method Name AcmeBlogBundle BlogController showAction The controller might look like this: Listing // src/Acme/BlogBundle/Controller/BlogController.php 10-15 namespace AcmeBlogBundleController; use SymfonyBundleFrameworkBundleControllerController; class BlogController extends Controller { public function showAction($slug) { // ... } } Notice that Symfony adds the string Controller to the class name (Blog => BlogController) and Action to the method name (show => showAction). You could also refer to this controller using its fully-qualified class name and method: AcmeBlogBundleControllerBlogController::showAction. But if you follow some simple conventions, the logical name is more concise and allows more flexibility. In addition to using the logical name or the fully-qualified class name, Symfony supports a third way of referring to a controller. This method uses just one colon separator (e.g. service_name:indexAction) and refers to the controller as a service (see How to define Controllers as Services). Route Parameters and Controller Arguments The route parameters (e.g. {slug}) are especially important because each is made available as an argument to the controller method: Listing public function showAction($slug) 10-16 { // ... } In reality, the entire defaults collection is merged with the parameter values to form a single array. Each key of that array is available as an argument on the controller. PDF brought to you by Chapter 10: Routing | 90 generated on June 20, 2012
  • 91. In other words, for each argument of your controller method, Symfony looks for a route parameter of that name and assigns its value to that argument. In the advanced example above, any combination (in any order) of the following variables could be used as arguments to the showAction() method: • $culture • $year • $title • $_format • $_controller Since the placeholders and defaults collection are merged together, even the $_controller variable is available. For a more detailed discussion, see Route Parameters as Controller Arguments. You can also use a special $_route variable, which is set to the name of the route that was matched. Including External Routing Resources All routes are loaded via a single configuration file - usually app/config/routing.yml (see Creating Routes above). Commonly, however, you'll want to load routes from other places, like a routing file that lives inside a bundle. This can be done by "importing" that file: # app/config/routing.yml Listing 10-17 acme_hello: resource: "@AcmeHelloBundle/Resources/config/routing.yml" When importing resources from YAML, the key (e.g. acme_hello) is meaningless. Just be sure that it's unique so no other lines override it. The resource key loads the given routing resource. In this example the resource is the full path to a file, where the @AcmeHelloBundle shortcut syntax resolves to the path of that bundle. The imported file might look like this: # src/Acme/HelloBundle/Resources/config/routing.yml Listing 10-18 acme_hello: pattern: /hello/{name} defaults: { _controller: AcmeHelloBundle:Hello:index } The routes from this file are parsed and loaded in the same way as the main routing file. Prefixing Imported Routes You can also choose to provide a "prefix" for the imported routes. For example, suppose you want the acme_hello route to have a final pattern of /admin/hello/{name} instead of simply /hello/{name}: # app/config/routing.yml Listing 10-19 acme_hello: resource: "@AcmeHelloBundle/Resources/config/routing.yml" prefix: /admin PDF brought to you by Chapter 10: Routing | 91 generated on June 20, 2012
  • 92. The string /admin will now be prepended to the pattern of each route loaded from the new routing resource. Visualizing & Debugging Routes While adding and customizing routes, it's helpful to be able to visualize and get detailed information about your routes. A great way to see every route in your application is via the router:debug console command. Execute the command by running the following from the root of your project. Listing php app/console router:debug 10-20 The command will print a helpful list of all the configured routes in your application: Listing homepage ANY / 10-21 contact GET /contact contact_process POST /contact article_show ANY /articles/{culture}/{year}/{title}.{_format} blog ANY /blog/{page} blog_show ANY /blog/{slug} You can also get very specific information on a single route by including the route name after the command: Listing php app/console router:debug article_show 10-22 Generating URLs The routing system should also be used to generate URLs. In reality, routing is a bi-directional system: mapping the URL to a controller+parameters and a route+parameters back to a URL. The match()1 and generate()2 methods form this bi-directional system. Take the blog_show example route from earlier: Listing $params = $router->match('/blog/my-blog-post'); 10-23 // array('slug' => 'my-blog-post', '_controller' => 'AcmeBlogBundle:Blog:show') $uri = $router->generate('blog_show', array('slug' => 'my-blog-post')); // /blog/my-blog-post To generate a URL, you need to specify the name of the route (e.g. blog_show) and any wildcards (e.g. slug = my-blog-post) used in the pattern for that route. With this information, any URL can easily be generated: Listing class MainController extends Controller 10-24 { public function showAction($slug) { // ... $url = $this->get('router')->generate('blog_show', array('slug' => 'my-blog-post')); } } In an upcoming section, you'll learn how to generate URLs from inside templates. 1. http://api.symfony.com/2.0/Symfony/Component/Routing/Router.html#match() 2. http://api.symfony.com/2.0/Symfony/Component/Routing/Router.html#generate() PDF brought to you by Chapter 10: Routing | 92 generated on June 20, 2012
  • 93. If the frontend of your application uses AJAX requests, you might want to be able to generate URLs in JavaScript based on your routing configuration. By using the FOSJsRoutingBundle3, you can do exactly that: var url = Routing.generate('blog_show', { "slug": 'my-blog-post'}); Listing 10-25 For more information, see the documentation for that bundle. Generating Absolute URLs By default, the router will generate relative URLs (e.g. /blog). To generate an absolute URL, simply pass true to the third argument of the generate() method: $router->generate('blog_show', array('slug' => 'my-blog-post'), true); Listing 10-26 // http://www.example.com/blog/my-blog-post The host that's used when generating an absolute URL is the host of the current Request object. This is detected automatically based on server information supplied by PHP. When generating absolute URLs for scripts run from the command line, you'll need to manually set the desired host on the Request object: $request->headers->set('HOST', 'www.example.com'); Listing 10-27 Generating URLs with Query Strings The generate method takes an array of wildcard values to generate the URI. But if you pass extra ones, they will be added to the URI as a query string: $router->generate('blog', array('page' => 2, 'category' => 'Symfony')); Listing 10-28 // /blog/2?category=Symfony Generating URLs from a template The most common place to generate a URL is from within a template when linking between pages in your application. This is done just as before, but using a template helper function: <a href="{{ path('blog_show', { 'slug': 'my-blog-post' }) }}"> Listing 10-29 Read this blog post. </a> Absolute URLs can also be generated. <a href="{{ url('blog_show', { 'slug': 'my-blog-post' }) }}"> Listing 10-30 Read this blog post. </a> Summary Routing is a system for mapping the URL of incoming requests to the controller function that should be called to process the request. It both allows you to specify beautiful URLs and keeps the functionality of 3. https://github.com/FriendsOfSymfony/FOSJsRoutingBundle PDF brought to you by Chapter 10: Routing | 93 generated on June 20, 2012
  • 94. your application decoupled from those URLs. Routing is a two-way mechanism, meaning that it should also be used to generate URLs. Learn more from the Cookbook • How to force routes to always use HTTPS or HTTP PDF brought to you by Chapter 10: Routing | 94 generated on June 20, 2012
  • 95. Chapter 11 Creating and using Templates As you know, the controller is responsible for handling each request that comes into a Symfony2 application. In reality, the controller delegates the most of the heavy work to other places so that code can be tested and reused. When a controller needs to generate HTML, CSS or any other content, it hands the work off to the templating engine. In this chapter, you'll learn how to write powerful templates that can be used to return content to the user, populate email bodies, and more. You'll learn shortcuts, clever ways to extend templates and how to reuse template code. Templates A template is simply a text file that can generate any text-based format (HTML, XML, CSV, LaTeX ...). The most familiar type of template is a PHP template - a text file parsed by PHP that contains a mix of text and PHP code: <!DOCTYPE html> Listing 11-1 <html> <head> <title>Welcome to Symfony!</title> </head> <body> <h1><?php echo $page_title ?></h1> <ul id="navigation"> <?php foreach ($navigation as $item): ?> <li> <a href="<?php echo $item->getHref() ?>"> <?php echo $item->getCaption() ?> </a> </li> <?php endforeach; ?> </ul> </body> </html> PDF brought to you by Chapter 11: Creating and using Templates | 95 generated on June 20, 2012
  • 96. But Symfony2 packages an even more powerful templating language called Twig1. Twig allows you to write concise, readable templates that are more friendly to web designers and, in several ways, more powerful than PHP templates: Listing <!DOCTYPE html> 11-2 <html> <head> <title>Welcome to Symfony!</title> </head> <body> <h1>{{ page_title }}</h1> <ul id="navigation"> {% for item in navigation %} <li><a href="{{ item.href }}">{{ item.caption }}</a></li> {% endfor %} </ul> </body> </html> Twig defines two types of special syntax: • {{ ... }}: "Says something": prints a variable or the result of an expression to the template; • {% ... %}: "Does something": a tag that controls the logic of the template; it is used to execute statements such as for-loops for example. There is a third syntax used for creating comments: {# this is a comment #}. This syntax can be used across multiple lines like the PHP-equivalent /* comment */ syntax. Twig also contains filters, which modify content before being rendered. The following makes the title variable all uppercase before rendering it: Listing {{ title|upper }} 11-3 Twig comes with a long list of tags2 and filters3 that are available by default. You can even add your own extensions4 to Twig as needed. Registering a Twig extension is as easy as creating a new service and tagging it with twig.extension tag. As you'll see throughout the documentation, Twig also supports functions and new functions can be easily added. For example, the following uses a standard for tag and the cycle function to print ten div tags, with alternating odd, even classes: Listing {% for i in 0..10 %} 11-4 <div class="{{ cycle(['odd', 'even'], i) }}"> <!-- some HTML here --> </div> {% endfor %} 1. http://twig.sensiolabs.org 2. http://twig.sensiolabs.org/doc/tags/index.html 3. http://twig.sensiolabs.org/doc/filters/index.html 4. http://twig.sensiolabs.org/doc/advanced.html#creating-an-extension PDF brought to you by Chapter 11: Creating and using Templates | 96 generated on June 20, 2012
  • 97. Throughout this chapter, template examples will be shown in both Twig and PHP. Why Twig? Twig templates are meant to be simple and won't process PHP tags. This is by design: the Twig template system is meant to express presentation, not program logic. The more you use Twig, the more you'll appreciate and benefit from this distinction. And of course, you'll be loved by web designers everywhere. Twig can also do things that PHP can't, such as true template inheritance (Twig templates compile down to PHP classes that inherit from each other), whitespace control, sandboxing, and the inclusion of custom functions and filters that only affect templates. Twig contains little features that make writing templates easier and more concise. Take the following example, which combines a loop with a logical if statement: <ul> Listing 11-5 {% for user in users %} <li>{{ user.username }}</li> {% else %} <li>No users found</li> {% endfor %} </ul> Twig Template Caching Twig is fast. Each Twig template is compiled down to a native PHP class that is rendered at runtime. The compiled classes are located in the app/cache/{environment}/twig directory (where {environment} is the environment, such as dev or prod) and in some cases can be useful while debugging. See Environments for more information on environments. When debug mode is enabled (common in the dev environment), a Twig template will be automatically recompiled when changes are made to it. This means that during development you can happily make changes to a Twig template and instantly see the changes without needing to worry about clearing any cache. When debug mode is disabled (common in the prod environment), however, you must clear the Twig cache directory so that the Twig templates will regenerate. Remember to do this when deploying your application. Template Inheritance and Layouts More often than not, templates in a project share common elements, like the header, footer, sidebar or more. In Symfony2, we like to think about this problem differently: a template can be decorated by another one. This works exactly the same as PHP classes: template inheritance allows you to build a base "layout" template that contains all the common elements of your site defined as blocks (think "PHP class with base methods"). A child template can extend the base layout and override any of its blocks (think "PHP subclass that overrides certain methods of its parent class"). First, build a base layout file: {# app/Resources/views/base.html.twig #} Listing 11-6 <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>{% block title %}Test Application{% endblock %}</title> PDF brought to you by Chapter 11: Creating and using Templates | 97 generated on June 20, 2012
  • 98. </head> <body> <div id="sidebar"> {% block sidebar %} <ul> <li><a href="/">Home</a></li> <li><a href="/blog">Blog</a></li> </ul> {% endblock %} </div> <div id="content"> {% block body %}{% endblock %} </div> </body> </html> Though the discussion about template inheritance will be in terms of Twig, the philosophy is the same between Twig and PHP templates. This template defines the base HTML skeleton document of a simple two-column page. In this example, three {% block %} areas are defined (title, sidebar and body). Each block may be overridden by a child template or left with its default implementation. This template could also be rendered directly. In that case the title, sidebar and body blocks would simply retain the default values used in this template. A child template might look like this: Listing {# src/Acme/BlogBundle/Resources/views/Blog/index.html.twig #} 11-7 {% extends '::base.html.twig' %} {% block title %}My cool blog posts{% endblock %} {% block body %} {% for entry in blog_entries %} <h2>{{ entry.title }}</h2> <p>{{ entry.body }}</p> {% endfor %} {% endblock %} The parent template is identified by a special string syntax (::base.html.twig) that indicates that the template lives in the app/Resources/views directory of the project. This naming convention is explained fully in Template Naming and Locations. The key to template inheritance is the {% extends %} tag. This tells the templating engine to first evaluate the base template, which sets up the layout and defines several blocks. The child template is then rendered, at which point the title and body blocks of the parent are replaced by those from the child. Depending on the value of blog_entries, the output might look like this: Listing <!DOCTYPE html> 11-8 <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>My cool blog posts</title> </head> <body> <div id="sidebar"> PDF brought to you by Chapter 11: Creating and using Templates | 98 generated on June 20, 2012
  • 99. <ul> <li><a href="/">Home</a></li> <li><a href="/blog">Blog</a></li> </ul> </div> <div id="content"> <h2>My first post</h2> <p>The body of the first post.</p> <h2>Another post</h2> <p>The body of the second post.</p> </div> </body> </html> Notice that since the child template didn't define a sidebar block, the value from the parent template is used instead. Content within a {% block %} tag in a parent template is always used by default. You can use as many levels of inheritance as you want. In the next section, a common three-level inheritance model will be explained along with how templates are organized inside a Symfony2 project. When working with template inheritance, here are some tips to keep in mind: • If you use {% extends %} in a template, it must be the first tag in that template. • The more {% block %} tags you have in your base templates, the better. Remember, child templates don't have to define all parent blocks, so create as many blocks in your base templates as you want and give each a sensible default. The more blocks your base templates have, the more flexible your layout will be. • If you find yourself duplicating content in a number of templates, it probably means you should move that content to a {% block %} in a parent template. In some cases, a better solution may be to move the content to a new template and include it (see Including other Templates). • If you need to get the content of a block from the parent template, you can use the {{ parent() }} function. This is useful if you want to add to the contents of a parent block instead of completely overriding it: {% block sidebar %} Listing 11-9 <h3>Table of Contents</h3> ... {{ parent() }} {% endblock %} Template Naming and Locations By default, templates can live in two different locations: • app/Resources/views/: The applications views directory can contain application-wide base templates (i.e. your application's layouts) as well as templates that override bundle templates (see Overriding Bundle Templates); • path/to/bundle/Resources/views/: Each bundle houses its templates in its Resources/ views directory (and subdirectories). The majority of templates will live inside a bundle. Symfony2 uses a bundle:controller:template string syntax for templates. This allows for several different types of templates, each which lives in a specific location: PDF brought to you by Chapter 11: Creating and using Templates | 99 generated on June 20, 2012
  • 100. • AcmeBlogBundle:Blog:index.html.twig: This syntax is used to specify a template for a specific page. The three parts of the string, each separated by a colon (:), mean the following: • AcmeBlogBundle: (bundle) the template lives inside the AcmeBlogBundle (e.g. src/Acme/BlogBundle); • Blog: (controller) indicates that the template lives inside the Blog subdirectory of Resources/views; • index.html.twig: (template) the actual name of the file is index.html.twig. Assuming that the AcmeBlogBundle lives at src/Acme/BlogBundle, the final path to the layout would be src/Acme/BlogBundle/Resources/views/Blog/index.html.twig. • AcmeBlogBundle::layout.html.twig: This syntax refers to a base template that's specific to the AcmeBlogBundle. Since the middle, "controller", portion is missing (e.g. Blog), the template lives at Resources/views/layout.html.twig inside AcmeBlogBundle. • ::base.html.twig: This syntax refers to an application-wide base template or layout. Notice that the string begins with two colons (::), meaning that both the bundle and controller portions are missing. This means that the template is not located in any bundle, but instead in the root app/Resources/views/ directory. In the Overriding Bundle Templates section, you'll find out how each template living inside the AcmeBlogBundle, for example, can be overridden by placing a template of the same name in the app/ Resources/AcmeBlogBundle/views/ directory. This gives the power to override templates from any vendor bundle. Hopefully the template naming syntax looks familiar - it's the same naming convention used to refer to Controller Naming Pattern. Template Suffix The bundle:controller:template format of each template specifies where the template file is located. Every template name also has two extensions that specify the format and engine for that template. • AcmeBlogBundle:Blog:index.html.twig - HTML format, Twig engine • AcmeBlogBundle:Blog:index.html.php - HTML format, PHP engine • AcmeBlogBundle:Blog:index.css.twig - CSS format, Twig engine By default, any Symfony2 template can be written in either Twig or PHP, and the last part of the extension (e.g. .twig or .php) specifies which of these two engines should be used. The first part of the extension, (e.g. .html, .css, etc) is the final format that the template will generate. Unlike the engine, which determines how Symfony2 parses the template, this is simply an organizational tactic used in case the same resource needs to be rendered as HTML (index.html.twig), XML (index.xml.twig), or any other format. For more information, read the Debugging section. The available "engines" can be configured and even new engines added. See Templating Configuration for more details. PDF brought to you by Chapter 11: Creating and using Templates | 100 generated on June 20, 2012
  • 101. Tags and Helpers You already understand the basics of templates, how they're named and how to use template inheritance. The hardest parts are already behind you. In this section, you'll learn about a large group of tools available to help perform the most common template tasks such as including other templates, linking to pages and including images. Symfony2 comes bundled with several specialized Twig tags and functions that ease the work of the template designer. In PHP, the templating system provides an extensible helper system that provides useful features in a template context. We've already seen a few built-in Twig tags ({% block %} & {% extends %}) as well as an example of a PHP helper ($view['slots']). Let's learn a few more. Including other Templates You'll often want to include the same template or code fragment on several different pages. For example, in an application with "news articles", the template code displaying an article might be used on the article detail page, on a page displaying the most popular articles, or in a list of the latest articles. When you need to reuse a chunk of PHP code, you typically move the code to a new PHP class or function. The same is true for templates. By moving the reused template code into its own template, it can be included from any other template. First, create the template that you'll need to reuse. {# src/Acme/ArticleBundle/Resources/views/Article/articleDetails.html.twig #} Listing 11-10 <h2>{{ article.title }}</h2> <h3 class="byline">by {{ article.authorName }}</h3> <p> {{ article.body }} </p> Including this template from any other template is simple: {# src/Acme/ArticleBundle/Resources/Article/list.html.twig #} Listing 11-11 {% extends 'AcmeArticleBundle::layout.html.twig' %} {% block body %} <h1>Recent Articles<h1> {% for article in articles %} {% include 'AcmeArticleBundle:Article:articleDetails.html.twig' with {'article': article} %} {% endfor %} {% endblock %} The template is included using the {% include %} tag. Notice that the template name follows the same typical convention. The articleDetails.html.twig template uses an article variable. This is passed in by the list.html.twig template using the with command. The {'article': article} syntax is the standard Twig syntax for hash maps (i.e. an array with named keys). If we needed to pass in multiple elements, it would look like this: {'foo': foo, 'bar': bar}. PDF brought to you by Chapter 11: Creating and using Templates | 101 generated on June 20, 2012
  • 102. Embedding Controllers In some cases, you need to do more than include a simple template. Suppose you have a sidebar in your layout that contains the three most recent articles. Retrieving the three articles may include querying the database or performing other heavy logic that can't be done from within a template. The solution is to simply embed the result of an entire controller from your template. First, create a controller that renders a certain number of recent articles: Listing // src/Acme/ArticleBundle/Controller/ArticleController.php 11-12 class ArticleController extends Controller { public function recentArticlesAction($max = 3) { // make a database call or other logic to get the "$max" most recent articles $articles = ...; return $this->render('AcmeArticleBundle:Article:recentList.html.twig', array('articles' => $articles)); } } The recentList template is perfectly straightforward: Listing {# src/Acme/ArticleBundle/Resources/views/Article/recentList.html.twig #} 11-13 {% for article in articles %} <a href="/article/{{ article.slug }}"> {{ article.title }} </a> {% endfor %} Notice that we've cheated and hardcoded the article URL in this example (e.g. /article/*slug*). This is a bad practice. In the next section, you'll learn how to do this correctly. To include the controller, you'll need to refer to it using the standard string syntax for controllers (i.e. bundle:controller:action): Listing {# app/Resources/views/base.html.twig #} 11-14 ... <div id="sidebar"> {% render "AcmeArticleBundle:Article:recentArticles" with {'max': 3} %} </div> Whenever you find that you need a variable or a piece of information that you don't have access to in a template, consider rendering a controller. Controllers are fast to execute and promote good code organization and reuse. Linking to Pages Creating links to other pages in your application is one of the most common jobs for a template. Instead of hardcoding URLs in templates, use the path Twig function (or the router helper in PHP) to generate URLs based on the routing configuration. Later, if you want to modify the URL of a particular page, all you'll need to do is change the routing configuration; the templates will automatically generate the new URL. PDF brought to you by Chapter 11: Creating and using Templates | 102 generated on June 20, 2012
  • 103. First, link to the "_welcome" page, which is accessible via the following routing configuration: _welcome: Listing 11-15 pattern: / defaults: { _controller: AcmeDemoBundle:Welcome:index } To link to the page, just use the path Twig function and refer to the route: <a href="{{ path('_welcome') }}">Home</a> Listing 11-16 As expected, this will generate the URL /. Let's see how this works with a more complicated route: article_show: Listing 11-17 pattern: /article/{slug} defaults: { _controller: AcmeArticleBundle:Article:show } In this case, you need to specify both the route name (article_show) and a value for the {slug} parameter. Using this route, let's revisit the recentList template from the previous section and link to the articles correctly: {# src/Acme/ArticleBundle/Resources/views/Article/recentList.html.twig #} Listing 11-18 {% for article in articles %} <a href="{{ path('article_show', { 'slug': article.slug }) }}"> {{ article.title }} </a> {% endfor %} You can also generate an absolute URL by using the url Twig function: <a href="{{ url('_welcome') }}">Home</a> Listing 11-19 The same can be done in PHP templates by passing a third argument to the generate() method: <a href="<?php echo $view['router']->generate('_welcome', array(), true) ?>">Home</a> Listing 11-20 Linking to Assets Templates also commonly refer to images, Javascript, stylesheets and other assets. Of course you could hard-code the path to these assets (e.g. /images/logo.png), but Symfony2 provides a more dynamic option via the asset Twig function: <img src="{{ asset('images/logo.png') }}" alt="Symfony!" /> Listing 11-21 <link href="{{ asset('css/blog.css') }}" rel="stylesheet" type="text/css" /> The asset function's main purpose is to make your application more portable. If your application lives at the root of your host (e.g. http://example.com5), then the rendered paths should be /images/logo.png. But if your application lives in a subdirectory (e.g. http://example.com/my_app6), each asset path should render with the subdirectory (e.g. /my_app/images/logo.png). The asset function takes care of this by determining how your application is being used and generating the correct paths accordingly. Additionally, if you use the asset function, Symfony can automatically append a query string to your asset, in order to guarantee that updated static assets won't be cached when deployed. For example, 5. http://example.com 6. http://example.com/my_app PDF brought to you by Chapter 11: Creating and using Templates | 103 generated on June 20, 2012
  • 104. /images/logo.png might look like /images/logo.png?v2. For more information, see the assets_version configuration option. Including Stylesheets and Javascripts in Twig No site would be complete without including Javascript files and stylesheets. In Symfony, the inclusion of these assets is handled elegantly by taking advantage of Symfony's template inheritance. This section will teach you the philosophy behind including stylesheet and Javascript assets in Symfony. Symfony also packages another library, called Assetic, which follows this philosophy but allows you to do much more interesting things with those assets. For more information on using Assetic see How to Use Assetic for Asset Management. Start by adding two blocks to your base template that will hold your assets: one called stylesheets inside the head tag and another called javascripts just above the closing body tag. These blocks will contain all of the stylesheets and Javascripts that you'll need throughout your site: Listing {# 'app/Resources/views/base.html.twig' #} 11-22 <html> <head> {# ... #} {% block stylesheets %} <link href="{{ asset('/css/main.css') }}" type="text/css" rel="stylesheet" /> {% endblock %} </head> <body> {# ... #} {% block javascripts %} <script src="{{ asset('/js/main.js') }}" type="text/javascript"></script> {% endblock %} </body> </html> That's easy enough! But what if you need to include an extra stylesheet or Javascript from a child template? For example, suppose you have a contact page and you need to include a contact.css stylesheet just on that page. From inside that contact page's template, do the following: Listing {# src/Acme/DemoBundle/Resources/views/Contact/contact.html.twig #} 11-23 {% extends '::base.html.twig' %} {% block stylesheets %} {{ parent() }} <link href="{{ asset('/css/contact.css') }}" type="text/css" rel="stylesheet" /> {% endblock %} {# ... #} In the child template, you simply override the stylesheets block and put your new stylesheet tag inside of that block. Of course, since you want to add to the parent block's content (and not actually replace it), you should use the parent() Twig function to include everything from the stylesheets block of the base template. PDF brought to you by Chapter 11: Creating and using Templates | 104 generated on June 20, 2012
  • 105. You can also include assets located in your bundles' Resources/public folder. You will need to run the php app/console assets:install target [--symlink] command, which moves (or symlinks) files into the correct location. (target is by default "web"). <link href="{{ asset('bundles/acmedemo/css/contact.css') }}" type="text/css" rel="stylesheet" Listing 11-24 /> The end result is a page that includes both the main.css and contact.css stylesheets. Global Template Variables During each request, Symfony2 will set a global template variable app in both Twig and PHP template engines by default. The app variable is a GlobalVariables7 instance which will give you access to some application specific variables automatically: • app.security - The security context. • app.user - The current user object. • app.request - The request object. • app.session - The session object. • app.environment - The current environment (dev, prod, etc). • app.debug - True if in debug mode. False otherwise. <p>Username: {{ app.user.username }}</p> Listing 11-25 {% if app.debug %} <p>Request method: {{ app.request.method }}</p> <p>Application Environment: {{ app.environment }}</p> {% endif %} You can add your own global template variables. See the cookbook example on Global Variables. Configuring and using the templating Service The heart of the template system in Symfony2 is the templating Engine. This special object is responsible for rendering templates and returning their content. When you render a template in a controller, for example, you're actually using the templating engine service. For example: return $this->render('AcmeArticleBundle:Article:index.html.twig'); Listing 11-26 is equivalent to: $engine = $this->container->get('templating'); $content = $engine- >render('AcmeArticleBundle:Article:index.html.twig'); return $response = new Response($content); The templating engine (or "service") is preconfigured to work automatically inside Symfony2. It can, of course, be configured further in the application configuration file: # app/config/config.yml Listing 11-27 framework: 7. http://api.symfony.com/2.0/Symfony/Bundle/FrameworkBundle/Templating/GlobalVariables.html PDF brought to you by Chapter 11: Creating and using Templates | 105 generated on June 20, 2012
  • 106. # ... templating: { engines: ['twig'] } Several configuration options are available and are covered in the Configuration Appendix. The twig engine is mandatory to use the webprofiler (as well as many third-party bundles). Overriding Bundle Templates The Symfony2 community prides itself on creating and maintaining high quality bundles (see KnpBundles.com8) for a large number of different features. Once you use a third-party bundle, you'll likely need to override and customize one or more of its templates. Suppose you've included the imaginary open-source AcmeBlogBundle in your project (e.g. in the src/ Acme/BlogBundle directory). And while you're really happy with everything, you want to override the blog "list" page to customize the markup specifically for your application. By digging into the Blog controller of the AcmeBlogBundle, you find the following: Listing public function indexAction() 11-28 { $blogs = // some logic to retrieve the blogs $this->render('AcmeBlogBundle:Blog:index.html.twig', array('blogs' => $blogs)); } When the AcmeBlogBundle:Blog:index.html.twig is rendered, Symfony2 actually looks in two different locations for the template: 1. app/Resources/AcmeBlogBundle/views/Blog/index.html.twig 2. src/Acme/BlogBundle/Resources/views/Blog/index.html.twig To override the bundle template, just copy the index.html.twig template from the bundle to app/ Resources/AcmeBlogBundle/views/Blog/index.html.twig (the app/Resources/AcmeBlogBundle directory won't exist, so you'll need to create it). You're now free to customize the template. This logic also applies to base bundle templates. Suppose also that each template in AcmeBlogBundle inherits from a base template called AcmeBlogBundle::layout.html.twig. Just as before, Symfony2 will look in the following two places for the template: 1. app/Resources/AcmeBlogBundle/views/layout.html.twig 2. src/Acme/BlogBundle/Resources/views/layout.html.twig Once again, to override the template, just copy it from the bundle to app/Resources/AcmeBlogBundle/ views/layout.html.twig. You're now free to customize this copy as you see fit. If you take a step back, you'll see that Symfony2 always starts by looking in the app/Resources/ {BUNDLE_NAME}/views/ directory for a template. If the template doesn't exist there, it continues by checking inside the Resources/views directory of the bundle itself. This means that all bundle templates can be overridden by placing them in the correct app/Resources subdirectory. You can also override templates from within a bundle by using bundle inheritance. For more information, see How to use Bundle Inheritance to Override parts of a Bundle. 8. http://knpbundles.com PDF brought to you by Chapter 11: Creating and using Templates | 106 generated on June 20, 2012
  • 107. Overriding Core Templates Since the Symfony2 framework itself is just a bundle, core templates can be overridden in the same way. For example, the core TwigBundle contains a number of different "exception" and "error" templates that can be overridden by copying each from the Resources/views/Exception directory of the TwigBundle to, you guessed it, the app/Resources/TwigBundle/views/Exception directory. Three-level Inheritance One common way to use inheritance is to use a three-level approach. This method works perfectly with the three different types of templates we've just covered: • Create a app/Resources/views/base.html.twig file that contains the main layout for your application (like in the previous example). Internally, this template is called ::base.html.twig; • Create a template for each "section" of your site. For example, an AcmeBlogBundle, would have a template called AcmeBlogBundle::layout.html.twig that contains only blog section- specific elements; {# src/Acme/BlogBundle/Resources/views/layout.html.twig #} Listing 11-29 {% extends '::base.html.twig' %} {% block body %} <h1>Blog Application</h1> {% block content %}{% endblock %} {% endblock %} • Create individual templates for each page and make each extend the appropriate section template. For example, the "index" page would be called something close to AcmeBlogBundle:Blog:index.html.twig and list the actual blog posts. {# src/Acme/BlogBundle/Resources/views/Blog/index.html.twig #} Listing 11-30 {% extends 'AcmeBlogBundle::layout.html.twig' %} {% block content %} {% for entry in blog_entries %} <h2>{{ entry.title }}</h2> <p>{{ entry.body }}</p> {% endfor %} {% endblock %} Notice that this template extends the section template -(AcmeBlogBundle::layout.html.twig) which in-turn extends the base application layout (::base.html.twig). This is the common three-level inheritance model. When building your application, you may choose to follow this method or simply make each page template extend the base application template directly (e.g. {% extends '::base.html.twig' %}). The three-template model is a best-practice method used by vendor bundles so that the base template for a bundle can be easily overridden to properly extend your application's base layout. Output Escaping When generating HTML from a template, there is always a risk that a template variable may output unintended HTML or dangerous client-side code. The result is that dynamic content could break the PDF brought to you by Chapter 11: Creating and using Templates | 107 generated on June 20, 2012
  • 108. HTML of the resulting page or allow a malicious user to perform a Cross Site Scripting9 (XSS) attack. Consider this classic example: Listing Hello {{ name }} 11-31 Imagine that the user enters the following code as his/her name: Listing <script>alert('hello!')</script> 11-32 Without any output escaping, the resulting template will cause a JavaScript alert box to pop up: Listing Hello <script>alert('hello!')</script> 11-33 And while this seems harmless, if a user can get this far, that same user should also be able to write JavaScript that performs malicious actions inside the secure area of an unknowing, legitimate user. The answer to the problem is output escaping. With output escaping on, the same template will render harmlessly, and literally print the script tag to the screen: Listing Hello &lt;script&gt;alert(&#39;helloe&#39;)&lt;/script&gt; 11-34 The Twig and PHP templating systems approach the problem in different ways. If you're using Twig, output escaping is on by default and you're protected. In PHP, output escaping is not automatic, meaning you'll need to manually escape where necessary. Output Escaping in Twig If you're using Twig templates, then output escaping is on by default. This means that you're protected out-of-the-box from the unintentional consequences of user-submitted code. By default, the output escaping assumes that content is being escaped for HTML output. In some cases, you'll need to disable output escaping when you're rendering a variable that is trusted and contains markup that should not be escaped. Suppose that administrative users are able to write articles that contain HTML code. By default, Twig will escape the article body. To render it normally, add the raw filter: {{ article.body|raw }}. You can also disable output escaping inside a {% block %} area or for an entire template. For more information, see Output Escaping10 in the Twig documentation. Output Escaping in PHP Output escaping is not automatic when using PHP templates. This means that unless you explicitly choose to escape a variable, you're not protected. To use output escaping, use the special escape() view method: Listing Hello <?php echo $view->escape($name) ?> 11-35 By default, the escape() method assumes that the variable is being rendered within an HTML context (and thus the variable is escaped to be safe for HTML). The second argument lets you change the context. For example, to output something in a JavaScript string, use the js context: Listing var myMsg = 'Hello <?php echo $view->escape($name, 'js') ?>'; 11-36 9. http://en.wikipedia.org/wiki/Cross-site_scripting 10. http://twig.sensiolabs.org/doc/api.html#escaper-extension PDF brought to you by Chapter 11: Creating and using Templates | 108 generated on June 20, 2012
  • 109. Debugging New in version 2.0.9: This feature is available as of Twig 1.5.x, which was first shipped with Symfony 2.0.9. When using PHP, you can use var_dump() if you need to quickly find the value of a variable passed. This is useful, for example, inside your controller. The same can be achieved when using Twig by using the debug extension. This needs to be enabled in the config: # app/config/config.yml Listing 11-37 services: acme_hello.twig.extension.debug: class: Twig_Extension_Debug tags: - { name: 'twig.extension' } Template parameters can then be dumped using the dump function: {# src/Acme/ArticleBundle/Resources/views/Article/recentList.html.twig #} Listing 11-38 {{ dump(articles) }} {% for article in articles %} <a href="/article/{{ article.slug }}"> {{ article.title }} </a> {% endfor %} The variables will only be dumped if Twig's debug setting (in config.yml) is true. By default this means that the variables will be dumped in the dev environment but not the prod environment. Template Formats Templates are a generic way to render content in any format. And while in most cases you'll use templates to render HTML content, a template can just as easily generate JavaScript, CSS, XML or any other format you can dream of. For example, the same "resource" is often rendered in several different formats. To render an article index page in XML, simply include the format in the template name: • XML template name: AcmeArticleBundle:Article:index.xml.twig • XML template filename: index.xml.twig In reality, this is nothing more than a naming convention and the template isn't actually rendered differently based on its format. In many cases, you may want to allow a single controller to render multiple different formats based on the "request format". For that reason, a common pattern is to do the following: public function indexAction() Listing 11-39 { $format = $this->getRequest()->getRequestFormat(); return $this->render('AcmeBlogBundle:Blog:index.'.$format.'.twig'); } PDF brought to you by Chapter 11: Creating and using Templates | 109 generated on June 20, 2012
  • 110. The getRequestFormat on the Request object defaults to html, but can return any other format based on the format requested by the user. The request format is most often managed by the routing, where a route can be configured so that /contact sets the request format to html while /contact.xml sets the format to xml. For more information, see the Advanced Example in the Routing chapter. To create links that include the format parameter, include a _format key in the parameter hash: Listing <a href="{{ path('article_show', {'id': 123, '_format': 'pdf'}) }}"> 11-40 PDF Version </a> Final Thoughts The templating engine in Symfony is a powerful tool that can be used each time you need to generate presentational content in HTML, XML or any other format. And though templates are a common way to generate content in a controller, their use is not mandatory. The Response object returned by a controller can be created with or without the use of a template: Listing // creates a Response object whose content is the rendered template 11-41 $response = $this->render('AcmeArticleBundle:Article:index.html.twig'); // creates a Response object whose content is simple text $response = new Response('response content'); Symfony's templating engine is very flexible and two different template renderers are available by default: the traditional PHP templates and the sleek and powerful Twig templates. Both support a template hierarchy and come packaged with a rich set of helper functions capable of performing the most common tasks. Overall, the topic of templating should be thought of as a powerful tool that's at your disposal. In some cases, you may not need to render a template, and in Symfony2, that's absolutely fine. Learn more from the Cookbook • How to use PHP instead of Twig for Templates • How to customize Error Pages • How to write a custom Twig Extension PDF brought to you by Chapter 11: Creating and using Templates | 110 generated on June 20, 2012
  • 111. Chapter 12 Databases and Doctrine Let's face it, one of the most common and challenging tasks for any application involves persisting and reading information to and from a database. Fortunately, Symfony comes integrated with Doctrine1, a library whose sole goal is to give you powerful tools to make this easy. In this chapter, you'll learn the basic philosophy behind Doctrine and see how easy working with a database can be. Doctrine is totally decoupled from Symfony and using it is optional. This chapter is all about the Doctrine ORM, which aims to let you map objects to a relational database (such as MySQL, PostgreSQL or Microsoft SQL). If you prefer to use raw database queries, this is easy, and explained in the "How to use Doctrine's DBAL Layer" cookbook entry. You can also persist data to MongoDB2 using Doctrine ODM library. For more information, read the "DoctrineMongoDBBundle" documentation. A Simple Example: A Product The easiest way to understand how Doctrine works is to see it in action. In this section, you'll configure your database, create a Product object, persist it to the database and fetch it back out. Code along with the example If you want to follow along with the example in this chapter, create an AcmeStoreBundle via: php app/console generate:bundle --namespace=Acme/StoreBundle Listing 12-1 1. http://www.doctrine-project.org/ 2. http://www.mongodb.org/ PDF brought to you by Chapter 12: Databases and Doctrine | 111 generated on June 20, 2012
  • 112. Configuring the Database Before you really begin, you'll need to configure your database connection information. By convention, this information is usually configured in an app/config/parameters.ini file: Listing ;app/config/parameters.ini 12-2 [parameters] database_driver = pdo_mysql database_host = localhost database_name = test_project database_user = root database_password = password Defining the configuration via parameters.ini is just a convention. The parameters defined in that file are referenced by the main configuration file when setting up Doctrine: Listing doctrine: 12-3 dbal: driver: %database_driver% host: %database_host% dbname: %database_name% user: %database_user% password: %database_password% By separating the database information into a separate file, you can easily keep different versions of the file on each server. You can also easily store database configuration (or any sensitive information) outside of your project, like inside your Apache configuration, for example. For more information, see How to Set External Parameters in the Service Container. Now that Doctrine knows about your database, you can have it create the database for you: Listing php app/console doctrine:database:create 12-4 Creating an Entity Class Suppose you're building an application where products need to be displayed. Without even thinking about Doctrine or databases, you already know that you need a Product object to represent those products. Create this class inside the Entity directory of your AcmeStoreBundle: Listing // src/Acme/StoreBundle/Entity/Product.php 12-5 namespace AcmeStoreBundleEntity; class Product { protected $name; protected $price; protected $description; } The class - often called an "entity", meaning a basic class that holds data - is simple and helps fulfill the business requirement of needing products in your application. This class can't be persisted to a database yet - it's just a simple PHP class. PDF brought to you by Chapter 12: Databases and Doctrine | 112 generated on June 20, 2012
  • 113. Once you learn the concepts behind Doctrine, you can have Doctrine create this entity class for you: php app/console doctrine:generate:entity --entity="AcmeStoreBundle:Product" Listing 12-6 --fields="name:string(255) price:float description:text" Add Mapping Information Doctrine allows you to work with databases in a much more interesting way than just fetching rows of a column-based table into an array. Instead, Doctrine allows you to persist entire objects to the database and fetch entire objects out of the database. This works by mapping a PHP class to a database table, and the properties of that PHP class to columns on the table: For Doctrine to be able to do this, you just have to create "metadata", or configuration that tells Doctrine exactly how the Product class and its properties should be mapped to the database. This metadata can be specified in a number of different formats including YAML, XML or directly inside the Product class via annotations: A bundle can accept only one metadata definition format. For example, it's not possible to mix YAML metadata definitions with annotated PHP entity class definitions. // src/Acme/StoreBundle/Entity/Product.php Listing 12-7 namespace AcmeStoreBundleEntity; use DoctrineORMMapping as ORM; /** * @ORMEntity * @ORMTable(name="product") */ class Product { /** * @ORMId * @ORMColumn(type="integer") * @ORMGeneratedValue(strategy="AUTO") */ protected $id; /** * @ORMColumn(type="string", length=100) */ PDF brought to you by Chapter 12: Databases and Doctrine | 113 generated on June 20, 2012
  • 114. protected $name; /** * @ORMColumn(type="decimal", scale=2) */ protected $price; /** * @ORMColumn(type="text") */ protected $description; } The table name is optional and if omitted, will be determined automatically based on the name of the entity class. Doctrine allows you to choose from a wide variety of different field types, each with their own options. For information on the available field types, see the Doctrine Field Types Reference section. You can also check out Doctrine's Basic Mapping Documentation3 for all details about mapping information. If you use annotations, you'll need to prepend all annotations with ORM (e.g. ORMColumn(..)), which is not shown in Doctrine's documentation. You'll also need to include the use DoctrineORMMapping as ORM; statement, which imports the ORM annotations prefix. Be careful that your class name and properties aren't mapped to a protected SQL keyword (such as group or user). For example, if your entity class name is Group, then, by default, your table name will be group, which will cause an SQL error in some engines. See Doctrine's Reserved SQL keywords documentation4 on how to properly escape these names. Alternatively, if you're free to choose your database schema, simply map to a different table name or column name. See Doctrine's Persistent classes5 and Property Mapping6 documentation. When using another library or program (ie. Doxygen) that uses annotations, you should place the @IgnoreAnnotation annotation on the class to indicate which annotations Symfony should ignore. For example, to prevent the @fn annotation from throwing an exception, add the following: Listing /** 12-8 * @IgnoreAnnotation("fn") */ class Product Generating Getters and Setters Even though Doctrine now knows how to persist a Product object to the database, the class itself isn't really useful yet. Since Product is just a regular PHP class, you need to create getter and setter methods (e.g. getName(), setName()) in order to access its properties (since the properties are protected). Fortunately, Doctrine can do this for you by running: 3. http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/reference/basic-mapping.html 4. http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/reference/basic-mapping.html#quoting-reserved-words 5. http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/reference/basic-mapping.html#persistent-classes 6. http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/reference/basic-mapping.html#property-mapping PDF brought to you by Chapter 12: Databases and Doctrine | 114 generated on June 20, 2012
  • 115. php app/console doctrine:generate:entities Acme/StoreBundle/Entity/Product Listing 12-9 This command makes sure that all of the getters and setters are generated for the Product class. This is a safe command - you can run it over and over again: it only generates getters and setters that don't exist (i.e. it doesn't replace your existing methods). More about doctrine:generate:entities With the doctrine:generate:entities command you can: • generate getters and setters, • generate repository classes configured with the @ORMEntity(repositoryClass="...") annotation, • generate the appropriate constructor for 1:n and n:m relations. The doctrine:generate:entities command saves a backup of the original Product.php named Product.php~. In some cases, the presence of this file can cause a "Cannot redeclare class" error. It can be safely removed. Note that you don't need to use this command. Doctrine doesn't rely on code generation. Like with normal PHP classes, you just need to make sure that your protected/private properties have getter and setter methods. Since this is a common thing to do when using Doctrine, this command was created. You can also generate all known entities (i.e. any PHP class with Doctrine mapping information) of a bundle or an entire namespace: php app/console doctrine:generate:entities AcmeStoreBundle Listing 12-10 php app/console doctrine:generate:entities Acme Doctrine doesn't care whether your properties are protected or private, or whether or not you have a getter or setter function for a property. The getters and setters are generated here only because you'll need them to interact with your PHP object. Creating the Database Tables/Schema You now have a usable Product class with mapping information so that Doctrine knows exactly how to persist it. Of course, you don't yet have the corresponding product table in your database. Fortunately, Doctrine can automatically create all the database tables needed for every known entity in your application. To do this, run: php app/console doctrine:schema:update --force Listing 12-11 Actually, this command is incredibly powerful. It compares what your database should look like (based on the mapping information of your entities) with how it actually looks, and generates the SQL statements needed to update the database to where it should be. In other words, if you add a new property with mapping metadata to Product and run this task again, it will generate the "alter table" statement needed to add that new column to the existing product table. An even better way to take advantage of this functionality is via migrations, which allow you to generate these SQL statements and store them in migration classes that can be run systematically on your production server in order to track and migrate your database schema safely and reliably. PDF brought to you by Chapter 12: Databases and Doctrine | 115 generated on June 20, 2012
  • 116. Your database now has a fully-functional product table with columns that match the metadata you've specified. Persisting Objects to 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: 1 Listing Listing // src/Acme/StoreBundle/Controller/DefaultController.php 12-12 12-13 2 use AcmeStoreBundleEntityProduct; 3 use SymfonyComponentHttpFoundationResponse; 4 // ... 5 6 public function createAction() 7 { 8 $product = new Product(); 9 $product->setName('A Foo Bar'); 10 $product->setPrice('19.99'); 11 $product->setDescription('Lorem ipsum dolor'); 12 13 $em = $this->getDoctrine()->getEntityManager(); 14 $em->persist($product); 15 $em->flush(); 16 17 return new Response('Created product id '.$product->getId()); 18 } If you're following along with this example, you'll need to create a route that points to this action to see it work. Let's walk through this example: • lines 8-11 In this section, you instantiate and work with the $product object like any other, normal PHP object; • line 13 This line fetches Doctrine's entity manager object, which is responsible for handling the process of persisting and fetching objects to and from the database; • line 14 The persist() method tells Doctrine to "manage" the $product object. This does not actually cause a query to be made to the database (yet). • line 15 When the flush() method is called, Doctrine looks through all of the objects that it's managing to see if they need to be persisted to the database. In this example, the $product object has not been persisted yet, so the entity manager executes an INSERT query and a row is created in the product table. In fact, since Doctrine is aware of all your managed entities, when you call the flush() method, it calculates an overall changeset and executes the most efficient query/queries possible. For example, if you persist a total of 100 Product objects and then subsequently call flush(), Doctrine will create a single prepared statement and re-use it for each insert. This pattern is called Unit of Work, and it's used because it's fast and efficient. PDF brought to you by Chapter 12: Databases and Doctrine | 116 generated on June 20, 2012
  • 117. When creating or updating objects, the workflow is always the same. In the next section, you'll see how Doctrine is smart enough to automatically issue an UPDATE query if the record already exists in the database. Doctrine provides a library that allows you to programmatically load testing data into your project (i.e. "fixture data"). For information, see DoctrineFixturesBundle. Fetching Objects from the Database Fetching an object back out of the database is even easier. For example, suppose you've configured a route to display a specific Product based on its id value: public function showAction($id) Listing 12-14 { $product = $this->getDoctrine() ->getRepository('AcmeStoreBundle:Product') ->find($id); if (!$product) { throw $this->createNotFoundException('No product found for id '.$id); } // do something, like pass the $product object into a template } When you query for a particular type of object, you always use what's known as its "repository". You can think of a repository as a PHP class whose only job is to help you fetch entities of a certain class. You can access the repository object for an entity class via: $repository = $this->getDoctrine() Listing 12-15 ->getRepository('AcmeStoreBundle:Product'); The AcmeStoreBundle:Product string is a shortcut you can use anywhere in Doctrine instead of the full class name of the entity (i.e. AcmeStoreBundleEntityProduct). As long as your entity lives under the Entity namespace of your bundle, this will work. Once you have your repository, you have access to all sorts of helpful methods: // query by the primary key (usually "id") Listing 12-16 $product = $repository->find($id); // dynamic method names to find based on a column value $product = $repository->findOneById($id); $product = $repository->findOneByName('foo'); // find *all* products $products = $repository->findAll(); // find a group of products based on an arbitrary column value $products = $repository->findByPrice(19.99); PDF brought to you by Chapter 12: Databases and Doctrine | 117 generated on June 20, 2012
  • 118. Of course, you can also issue complex queries, which you'll learn more about in the Querying for Objects section. You can also take advantage of the useful findBy and findOneBy methods to easily fetch objects based on multiple conditions: Listing // query for one product matching be name and price 12-17 $product = $repository->findOneBy(array('name' => 'foo', 'price' => 19.99)); // query for all products matching the name, ordered by price $product = $repository->findBy( array('name' => 'foo'), array('price' => 'ASC') ); When you render any page, you can see how many queries were made in the bottom right corner of the web debug toolbar. If you click the icon, the profiler will open, showing you the exact queries that were made. Updating an Object Once you've fetched an object from Doctrine, updating it is easy. Suppose you have a route that maps a product id to an update action in a controller: Listing public function updateAction($id) 12-18 { $em = $this->getDoctrine()->getEntityManager(); $product = $em->getRepository('AcmeStoreBundle:Product')->find($id); if (!$product) { throw $this->createNotFoundException('No product found for id '.$id); } $product->setName('New product name!'); $em->flush(); PDF brought to you by Chapter 12: Databases and Doctrine | 118 generated on June 20, 2012
  • 119. return $this->redirect($this->generateUrl('homepage')); } Updating an object involves just three steps: 1. fetching the object from Doctrine; 2. modifying the object; 3. calling flush() on the entity manager Notice that calling $em->persist($product) isn't necessary. Recall that this method simply tells Doctrine to manage or "watch" the $product object. In this case, since you fetched the $product object from Doctrine, it's already managed. Deleting an Object Deleting an object is very similar, but requires a call to the remove() method of the entity manager: $em->remove($product); Listing 12-19 $em->flush(); As you might expect, the remove() method notifies Doctrine that you'd like to remove the given entity from the database. The actual DELETE query, however, isn't actually executed until the flush() method is called. Querying for Objects You've already seen how the repository object allows you to run basic queries without any work: $repository->find($id); Listing 12-20 $repository->findOneByName('Foo'); Of course, Doctrine also allows you to write more complex queries using the Doctrine Query Language (DQL). DQL is similar to SQL except that you should imagine that you're querying for one or more objects of an entity class (e.g. Product) instead of querying for rows on a table (e.g. product). When querying in Doctrine, you have two options: writing pure Doctrine queries or using Doctrine's Query Builder. Querying for Objects with DQL Imagine that you want to query for products, but only return products that cost more than 19.99, ordered from cheapest to most expensive. From inside a controller, do the following: $em = $this->getDoctrine()->getEntityManager(); Listing 12-21 $query = $em->createQuery( 'SELECT p FROM AcmeStoreBundle:Product p WHERE p.price > :price ORDER BY p.price ASC' )->setParameter('price', '19.99'); $products = $query->getResult(); If you're comfortable with SQL, then DQL should feel very natural. The biggest difference is that you need to think in terms of "objects" instead of rows in a database. For this reason, you select from AcmeStoreBundle:Product and then alias it as p. The getResult() method returns an array of results. If you're querying for just one object, you can use the getSingleResult() method instead: PDF brought to you by Chapter 12: Databases and Doctrine | 119 generated on June 20, 2012
  • 120. Listing $product = $query->getSingleResult(); 12-22 The getSingleResult() method throws a DoctrineORMNoResultException exception if no results are returned and a DoctrineORMNonUniqueResultException if more than one result is returned. If you use this method, you may need to wrap it in a try-catch block and ensure that only one result is returned (if you're querying on something that could feasibly return more than one result): Listing $query = $em->createQuery('SELECT ....') 12-23 ->setMaxResults(1); try { $product = $query->getSingleResult(); } catch (DoctrineOrmNoResultException $e) { $product = null; } // ... The DQL syntax is incredibly powerful, allowing you to easily join between entities (the topic of relations will be covered later), group, etc. For more information, see the official Doctrine Doctrine Query Language7 documentation. Setting Parameters Take note of the setParameter() method. When working with Doctrine, it's always a good idea to set any external values as "placeholders", which was done in the above query: Listing ... WHERE p.price > :price ... 12-24 You can then set the value of the price placeholder by calling the setParameter() method: Listing ->setParameter('price', '19.99') 12-25 Using parameters instead of placing values directly in the query string is done to prevent SQL injection attacks and should always be done. If you're using multiple parameters, you can set their values at once using the setParameters() method: Listing ->setParameters(array( 12-26 'price' => '19.99', 'name' => 'Foo', )) Using Doctrine's Query Builder Instead of writing the queries directly, you can alternatively use Doctrine's QueryBuilder to do the same job using a nice, object-oriented interface. If you use an IDE, you can also take advantage of auto- completion as you type the method names. From inside a controller: Listing $repository = $this->getDoctrine() 12-27 ->getRepository('AcmeStoreBundle:Product'); $query = $repository->createQueryBuilder('p') ->where('p.price > :price') 7. http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/reference/dql-doctrine-query-language.html PDF brought to you by Chapter 12: Databases and Doctrine | 120 generated on June 20, 2012
  • 121. ->setParameter('price', '19.99') ->orderBy('p.price', 'ASC') ->getQuery(); $products = $query->getResult(); The QueryBuilder object contains every method necessary to build your query. By calling the getQuery() method, the query builder returns a normal Query object, which is the same object you built directly in the previous section. For more information on Doctrine's Query Builder, consult Doctrine's Query Builder8 documentation. Custom Repository Classes In the previous sections, you began constructing and using more complex queries from inside a controller. In order to isolate, test and reuse these queries, it's a good idea to create a custom repository class for your entity and add methods with your query logic there. To do this, add the name of the repository class to your mapping definition. // src/Acme/StoreBundle/Entity/Product.php Listing 12-28 namespace AcmeStoreBundleEntity; use DoctrineORMMapping as ORM; /** * @ORMEntity(repositoryClass="AcmeStoreBundleRepositoryProductRepository") */ class Product { //... } Doctrine can generate the repository class for you by running the same command used earlier to generate the missing getter and setter methods: php app/console doctrine:generate:entities Acme Listing 12-29 Next, add a new method - findAllOrderedByName() - to the newly generated repository class. This method will query for all of the Product entities, ordered alphabetically. // src/Acme/StoreBundle/Repository/ProductRepository.php Listing 12-30 namespace AcmeStoreBundleRepository; use DoctrineORMEntityRepository; class ProductRepository extends EntityRepository { public function findAllOrderedByName() { return $this->getEntityManager() ->createQuery('SELECT p FROM AcmeStoreBundle:Product p ORDER BY p.name ASC') ->getResult(); } } 8. http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/reference/query-builder.html PDF brought to you by Chapter 12: Databases and Doctrine | 121 generated on June 20, 2012
  • 122. The entity manager can be accessed via $this->getEntityManager() from inside the repository. You can use this new method just like the default finder methods of the repository: Listing $em = $this->getDoctrine()->getEntityManager(); 12-31 $products = $em->getRepository('AcmeStoreBundle:Product') ->findAllOrderedByName(); When using a custom repository class, you still have access to the default finder methods such as find() and findAll(). Entity Relationships/Associations Suppose that the products in your application all belong to exactly one "category". In this case, you'll need a Category object and a way to relate a Product object to a Category object. Start by creating the Category entity. Since you know that you'll eventually need to persist the class through Doctrine, you can let Doctrine create the class for you. Listing php app/console doctrine:generate:entity --entity="AcmeStoreBundle:Category" 12-32 --fields="name:string(255)" This task generates the Category entity for you, with an id field, a name field and the associated getter and setter functions. Relationship Mapping Metadata To relate the Category and Product entities, start by creating a products property on the Category class: Listing // src/Acme/StoreBundle/Entity/Category.php 12-33 // ... use DoctrineCommonCollectionsArrayCollection; class Category { // ... /** * @ORMOneToMany(targetEntity="Product", mappedBy="category") */ protected $products; public function __construct() { $this->products = new ArrayCollection(); } } First, since a Category object will relate to many Product objects, a products array property is added to hold those Product objects. Again, this isn't done because Doctrine needs it, but instead because it makes sense in the application for each Category to hold an array of Product objects. PDF brought to you by Chapter 12: Databases and Doctrine | 122 generated on June 20, 2012
  • 123. The code in the __construct() method is important because Doctrine requires the $products property to be an ArrayCollection object. This object looks and acts almost exactly like an array, but has some added flexibility. If this makes you uncomfortable, don't worry. Just imagine that it's an array and you'll be in good shape. The targetEntity value in the decorator used above can reference any entity with a valid namespace, not just entities defined in the same class. To relate to an entity defined in a different class or bundle, enter a full namespace as the targetEntity. Next, since each Product class can relate to exactly one Category object, you'll want to add a $category property to the Product class: // src/Acme/StoreBundle/Entity/Product.php Listing 12-34 // ... class Product { // ... /** * @ORMManyToOne(targetEntity="Category", inversedBy="products") * @ORMJoinColumn(name="category_id", referencedColumnName="id") */ protected $category; } Finally, now that you've added a new property to both the Category and Product classes, tell Doctrine to generate the missing getter and setter methods for you: php app/console doctrine:generate:entities Acme Listing 12-35 Ignore the Doctrine metadata for a moment. You now have two classes - Category and Product with a natural one-to-many relationship. The Category class holds an array of Product objects and the Product object can hold one Category object. In other words - you've built your classes in a way that makes sense for your needs. The fact that the data needs to be persisted to a database is always secondary. Now, look at the metadata above the $category property on the Product class. The information here tells doctrine that the related class is Category and that it should store the id of the category record on a category_id field that lives on the product table. In other words, the related Category object will be stored on the $category property, but behind the scenes, Doctrine will persist this relationship by storing the category's id value on a category_id column of the product table. PDF brought to you by Chapter 12: Databases and Doctrine | 123 generated on June 20, 2012
  • 124. The metadata above the $products property of the Category object is less important, and simply tells Doctrine to look at the Product.category property to figure out how the relationship is mapped. Before you continue, be sure to tell Doctrine to add the new category table, and product.category_id column, and new foreign key: Listing php app/console doctrine:schema:update --force 12-36 This task should only be really used during development. For a more robust method of systematically updating your production database, read about Doctrine migrations. Saving Related Entities Now, let's see the code in action. Imagine you're inside a controller: Listing // ... 12-37 use AcmeStoreBundleEntityCategory; use AcmeStoreBundleEntityProduct; PDF brought to you by Chapter 12: Databases and Doctrine | 124 generated on June 20, 2012
  • 125. use SymfonyComponentHttpFoundationResponse; // ... class DefaultController extends Controller { public function createProductAction() { $category = new Category(); $category->setName('Main Products'); $product = new Product(); $product->setName('Foo'); $product->setPrice(19.99); // relate this product to the category $product->setCategory($category); $em = $this->getDoctrine()->getEntityManager(); $em->persist($category); $em->persist($product); $em->flush(); return new Response( 'Created product id: '.$product->getId().' and category id: '.$category->getId() ); } } Now, a single row is added to both the category and product tables. The product.category_id column for the new product is set to whatever the id is of the new category. Doctrine manages the persistence of this relationship for you. Fetching Related Objects When you need to fetch associated objects, your workflow looks just like it did before. First, fetch a $product object and then access its related Category: public function showAction($id) Listing 12-38 { $product = $this->getDoctrine() ->getRepository('AcmeStoreBundle:Product') ->find($id); $categoryName = $product->getCategory()->getName(); // ... } In this example, you first query for a Product object based on the product's id. This issues a query for just the product data and hydrates the $product object with that data. Later, when you call $product- >getCategory()->getName(), Doctrine silently makes a second query to find the Category that's related to this Product. It prepares the $category object and returns it to you. PDF brought to you by Chapter 12: Databases and Doctrine | 125 generated on June 20, 2012
  • 126. What's important is the fact that you have easy access to the product's related category, but the category data isn't actually retrieved until you ask for the category (i.e. it's "lazily loaded"). You can also query in the other direction: Listing public function showProductAction($id) 12-39 { $category = $this->getDoctrine() ->getRepository('AcmeStoreBundle:Category') ->find($id); $products = $category->getProducts(); // ... } In this case, the same things occurs: you first query out for a single Category object, and then Doctrine makes a second query to retrieve the related Product objects, but only once/if you ask for them (i.e. when you call ->getProducts()). The $products variable is an array of all Product objects that relate to the given Category object via their category_id value. PDF brought to you by Chapter 12: Databases and Doctrine | 126 generated on June 20, 2012
  • 127. Relationships and Proxy Classes This "lazy loading" is possible because, when necessary, Doctrine returns a "proxy" object in place of the true object. Look again at the above example: $product = $this->getDoctrine() Listing 12-40 ->getRepository('AcmeStoreBundle:Product') ->find($id); $category = $product->getCategory(); // prints "ProxiesAcmeStoreBundleEntityCategoryProxy" echo get_class($category); This proxy object extends the true Category object, and looks and acts exactly like it. The difference is that, by using a proxy object, Doctrine can delay querying for the real Category data until you actually need that data (e.g. until you call $category->getName()). The proxy classes are generated by Doctrine and stored in the cache directory. And though you'll probably never even notice that your $category object is actually a proxy object, it's important to keep in mind. In the next section, when you retrieve the product and category data all at once (via a join), Doctrine will return the true Category object, since nothing needs to be lazily loaded. Joining to Related Records In the above examples, two queries were made - one for the original object (e.g. a Category) and one for the related object(s) (e.g. the Product objects). Remember that you can see all of the queries made during a request via the web debug toolbar. Of course, if you know up front that you'll need to access both objects, you can avoid the second query by issuing a join in the original query. Add the following method to the ProductRepository class: // src/Acme/StoreBundle/Repository/ProductRepository.php Listing 12-41 public function findOneByIdJoinedToCategory($id) { $query = $this->getEntityManager() ->createQuery(' SELECT p, c FROM AcmeStoreBundle:Product p JOIN p.category c WHERE p.id = :id' )->setParameter('id', $id); try { return $query->getSingleResult(); } catch (DoctrineORMNoResultException $e) { return null; } } Now, you can use this method in your controller to query for a Product object and its related Category with just one query: PDF brought to you by Chapter 12: Databases and Doctrine | 127 generated on June 20, 2012
  • 128. Listing public function showAction($id) 12-42 { $product = $this->getDoctrine() ->getRepository('AcmeStoreBundle:Product') ->findOneByIdJoinedToCategory($id); $category = $product->getCategory(); // ... } More Information on Associations This section has been an introduction to one common type of entity relationship, the one-to-many relationship. For more advanced details and examples of how to use other types of relations (e.g. one- to-one, many-to-many), see Doctrine's Association Mapping Documentation9. If you're using annotations, you'll need to prepend all annotations with ORM (e.g. ORMOneToMany), which is not reflected in Doctrine's documentation. You'll also need to include the use DoctrineORMMapping as ORM; statement, which imports the ORM annotations prefix. Configuration Doctrine is highly configurable, though you probably won't ever need to worry about most of its options. To find out more about configuring Doctrine, see the Doctrine section of the reference manual. Lifecycle Callbacks Sometimes, you need to perform an action right before or after an entity is inserted, updated, or deleted. These types of actions are known as "lifecycle" callbacks, as they're callback methods that you need to execute during different stages of the lifecycle of an entity (e.g. the entity is inserted, updated, deleted, etc). If you're using annotations for your metadata, start by enabling the lifecycle callbacks. This is not necessary if you're using YAML or XML for your mapping: Listing /** 12-43 * @ORMEntity() * @ORMHasLifecycleCallbacks() */ class Product { // ... } Now, you can tell Doctrine to execute a method on any of the available lifecycle events. For example, suppose you want to set a created date column to the current date, only when the entity is first persisted (i.e. inserted): Listing /** 12-44 * @ORMPrePersist */ 9. http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/reference/association-mapping.html PDF brought to you by Chapter 12: Databases and Doctrine | 128 generated on June 20, 2012
  • 129. public function setCreatedValue() { $this->created = new DateTime(); } The above example assumes that you've created and mapped a created property (not shown here). Now, right before the entity is first persisted, Doctrine will automatically call this method and the created field will be set to the current date. This can be repeated for any of the other lifecycle events, which include: • preRemove • postRemove • prePersist • postPersist • preUpdate • postUpdate • postLoad • loadClassMetadata For more information on what these lifecycle events mean and lifecycle callbacks in general, see Doctrine's Lifecycle Events documentation10 Lifecycle Callbacks and Event Listeners Notice that the setCreatedValue() method receives no arguments. This is always the case for lifecycle callbacks and is intentional: lifecycle callbacks should be simple methods that are concerned with internally transforming data in the entity (e.g. setting a created/updated field, generating a slug value). If you need to do some heavier lifting - like perform logging or send an email - you should register an external class as an event listener or subscriber and give it access to whatever resources you need. For more information, see Registering Event Listeners and Subscribers. Doctrine Extensions: Timestampable, Sluggable, etc. Doctrine is quite flexible, and a number of third-party extensions are available that allow you to easily perform repeated and common tasks on your entities. These include thing such as Sluggable, Timestampable, Loggable, Translatable, and Tree. For more information on how to find and use these extensions, see the cookbook article about using common Doctrine extensions. Doctrine Field Types Reference Doctrine comes with a large number of field types available. Each of these maps a PHP data type to a specific column type in whatever database you're using. The following types are supported in Doctrine: • Strings 10. http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/reference/events.html#lifecycle-events PDF brought to you by Chapter 12: Databases and Doctrine | 129 generated on June 20, 2012
  • 130. • string (used for shorter strings) • text (used for larger strings) • Numbers • integer • smallint • bigint • decimal • float • Dates and Times (use a DateTime11 object for these fields in PHP) • date • time • datetime • Other Types • boolean • object (serialized and stored in a CLOB field) • array (serialized and stored in a CLOB field) For more information, see Doctrine's Mapping Types documentation12. Field Options Each field can have a set of options applied to it. The available options include type (defaults to string), name, length, unique and nullable. Take a few examples: Listing /** 12-45 * A string field with length 255 that cannot be null * (reflecting the default values for the "type", "length" and *nullable* options) * * @ORMColumn() */ protected $name; /** * A string field of length 150 that persists to an "email_address" column * and has a unique index. * * @ORMColumn(name="email_address", unique=true, length=150) */ protected $email; There are a few more options not listed here. For more details, see Doctrine's Property Mapping documentation13 11. http://php.net/manual/en/class.datetime.php 12. http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/reference/basic-mapping.html#doctrine-mapping-types 13. http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/reference/basic-mapping.html#property-mapping PDF brought to you by Chapter 12: Databases and Doctrine | 130 generated on June 20, 2012
  • 131. Console Commands The Doctrine2 ORM integration offers several console commands under the doctrine namespace. To view the command list you can run the console without any arguments: php app/console Listing 12-46 A list of available command will print out, many of which start with the doctrine: prefix. You can find out more information about any of these commands (or any Symfony command) by running the help command. For example, to get details about the doctrine:database:create task, run: php app/console help doctrine:database:create Listing 12-47 Some notable or interesting tasks include: • doctrine:ensure-production-settings - checks to see if the current environment is configured efficiently for production. This should always be run in the prod environment: php app/console doctrine:ensure-production-settings --env=prod Listing 12-48 • doctrine:mapping:import - allows Doctrine to introspect an existing database and create mapping information. For more information, see How to generate Entities from an Existing Database. • doctrine:mapping:info - tells you all of the entities that Doctrine is aware of and whether or not there are any basic errors with the mapping. • doctrine:query:dql and doctrine:query:sql - allow you to execute DQL or SQL queries directly from the command line. To be able to load data fixtures to your database, you will need to have the DoctrineFixturesBundle bundle installed. To learn how to do it, read the "DoctrineFixturesBundle" entry of the documentation. Summary With Doctrine, you can focus on your objects and how they're useful in your application and worry about database persistence second. This is because Doctrine allows you to use any PHP object to hold your data and relies on mapping metadata information to map an object's data to a particular database table. And even though Doctrine revolves around a simple concept, it's incredibly powerful, allowing you to create complex queries and subscribe to events that allow you to take different actions as objects go through their persistence lifecycle. For more information about Doctrine, see the Doctrine section of the cookbook, which includes the following articles: • DoctrineFixturesBundle • Doctrine Extensions: Timestampable, Sluggable, Translatable, etc. PDF brought to you by Chapter 12: Databases and Doctrine | 131 generated on June 20, 2012
  • 132. Chapter 13 Databases and Propel Let's face it, one of the most common and challenging tasks for any application involves persisting and reading information to and from a database. Symfony2 does not come integrated with any ORMs but the Propel integration is easy. To get started, read Working With Symfony21. A Simple Example: A Product In this section, you'll configure your database, create a Product object, persist it to the database and fetch it back out. Code along with the example If you want to follow along with the example in this chapter, create an AcmeStoreBundle via: php app/console generate:bundle --namespace=Acme/StoreBundle. Configuring the Database Before you can start, you'll need to configure your database connection information. By convention, this information is usually configured in an app/config/parameters.ini file: Listing ;app/config/parameters.ini 13-1 [parameters] database_driver = mysql database_host = localhost database_name = test_project database_user = root database_password = password database_charset = UTF8 1. http://www.propelorm.org/cookbook/symfony2/working-with-symfony2.html#installation PDF brought to you by Chapter 13: Databases and Propel | 132 generated on June 20, 2012
  • 133. Defining the configuration via parameters.ini is just a convention. The parameters defined in that file are referenced by the main configuration file when setting up Propel: propel: Listing 13-2 dbal: driver: %database_driver% user: %database_user% password: %database_password% dsn: %database_driver%:host=%database_host%;dbname=%database_name%;charset=%database_charset% Now that Propel knows about your database, Symfony2 can create the database for you: php app/console propel:database:create Listing 13-3 In this example, you have one configured connection, named default. If you want to configure more than one connection, read the PropelBundle configuration section. Creating a Model Class In the Propel world, ActiveRecord classes are known as models because classes generated by Propel contain some business logic. For people who use Symfony2 with Doctrine2, models are equivalent to entities. Suppose you're building an application where products need to be displayed. First, create a schema.xml file inside the Resources/config directory of your AcmeStoreBundle: <?xml version="1.0" encoding="UTF-8"?> Listing 13-4 <database name="default" namespace="AcmeStoreBundleModel" defaultIdMethod="native"> <table name="product"> <column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true" /> <column name="name" type="varchar" primaryString="true" size="100" /> <column name="price" type="decimal" /> <column name="description" type="longvarchar" /> </table> </database> Building the Model After creating your schema.xml, generate your model from it by running: php app/console propel:model:build Listing 13-5 This generates each model class to quickly develop your application in the Model/ directory the AcmeStoreBundle bundle. PDF brought to you by Chapter 13: Databases and Propel | 133 generated on June 20, 2012
  • 134. Creating the Database Tables/Schema Now you have a usable Product class and all you need to persist it. Of course, you don't yet have the corresponding product table in your database. Fortunately, Propel can automatically create all the database tables needed for every known model in your application. To do this, run: Listing php app/console propel:sql:build 13-6 php app/console propel:sql:insert --force Your database now has a fully-functional product table with columns that match the schema you've specified. You can run the last three commands combined by using the following command: php app/ console propel:build --insert-sql. Persisting Objects to the Database Now that you have a Product object 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: Listing // src/Acme/StoreBundle/Controller/DefaultController.php 13-7 use AcmeStoreBundleModelProduct; use SymfonyComponentHttpFoundationResponse; // ... public function createAction() { $product = new Product(); $product->setName('A Foo Bar'); $product->setPrice(19.99); $product->setDescription('Lorem ipsum dolor'); $product->save(); return new Response('Created product id '.$product->getId()); } In this piece of code, you instantiate and work with the $product object. When you call the save() method on it, you persist it to the database. No need to use other services, the object knows how to persist itself. If you're following along with this example, you'll need to create a route that points to this action to see it in action. Fetching Objects from the Database Fetching an object back from the database is even easier. For example, suppose you've configured a route to display a specific Product based on its id value: Listing use AcmeStoreBundleModelProductQuery; 13-8 PDF brought to you by Chapter 13: Databases and Propel | 134 generated on June 20, 2012
  • 135. public function showAction($id) { $product = ProductQuery::create() ->findPk($id); if (!$product) { throw $this->createNotFoundException('No product found for id '.$id); } // do something, like pass the $product object into a template } Updating an Object Once you've fetched an object from Propel, updating it is easy. Suppose you have a route that maps a product id to an update action in a controller: use AcmeStoreBundleModelProductQuery; Listing 13-9 public function updateAction($id) { $product = ProductQuery::create() ->findPk($id); if (!$product) { throw $this->createNotFoundException('No product found for id '.$id); } $product->setName('New product name!'); $product->save(); return $this->redirect($this->generateUrl('homepage')); } Updating an object involves just three steps: 1. fetching the object from Propel; 2. modifying the object; 3. saving it. Deleting an Object Deleting an object is very similar, but requires a call to the delete() method on the object: $product->delete(); Listing 13-10 Querying for Objects Propel provides generated Query classes to run both basic and complex queries without any work: AcmeStoreBundleModelProductQuery::create()->findPk($id); Listing 13-11 AcmeStoreBundleModelProductQuery::create() ->filterByName('Foo') ->findOne(); Imagine that you want to query for products which cost more than 19.99, ordered from cheapest to most expensive. From inside a controller, do the following: PDF brought to you by Chapter 13: Databases and Propel | 135 generated on June 20, 2012
  • 136. Listing $products = AcmeStoreBundleModelProductQuery::create() 13-12 ->filterByPrice(array('min' => 19.99)) ->orderByPrice() ->find(); In one line, you get your products in a powerful oriented object way. No need to waste your time with SQL or whatever, Symfony2 offers fully object oriented programming and Propel respects the same philosophy by providing an awesome abstraction layer. If you want to reuse some queries, you can add your own methods to the ProductQuery class: Listing // src/Acme/StoreBundle/Model/ProductQuery.php 13-13 class ProductQuery extends BaseProductQuery { public function filterByExpensivePrice() { return $this ->filterByPrice(array('min' => 1000)) } } But note that Propel generates a lot of methods for you and a simple findAllOrderedByName() can be written without any effort: Listing AcmeStoreBundleModelProductQuery::create() 13-14 ->orderByName() ->find(); Relationships/Associations Suppose that the products in your application all belong to exactly one "category". In this case, you'll need a Category object and a way to relate a Product object to a Category object. Start by adding the category definition in your schema.xml: Listing <database name="default" namespace="AcmeStoreBundleModel" defaultIdMethod="native"> 13-15 <table name="product"> <column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true" /> <column name="name" type="varchar" primaryString="true" size="100" /> <column name="price" type="decimal" /> <column name="description" type="longvarchar" /> <column name="category_id" type="integer" /> <foreign-key foreignTable="category"> <reference local="category_id" foreign="id" /> </foreign-key> </table> <table name="category"> <column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true" /> <column name="name" type="varchar" primaryString="true" size="100" /> </table> </database> Create the classes: PDF brought to you by Chapter 13: Databases and Propel | 136 generated on June 20, 2012
  • 137. php app/console propel:model:build Listing 13-16 Assuming you have products in your database, you don't want lose them. Thanks to migrations, Propel will be able to update your database without losing existing data. php app/console propel:migration:generate-diff Listing 13-17 php app/console propel:migration:migrate Your database has been updated, you can continue to write your application. Saving Related Objects Now, let's see the code in action. Imagine you're inside a controller: // ... Listing 13-18 use AcmeStoreBundleModelCategory; use AcmeStoreBundleModelProduct; use SymfonyComponentHttpFoundationResponse; // ... class DefaultController extends Controller { public function createProductAction() { $category = new Category(); $category->setName('Main Products'); $product = new Product(); $product->setName('Foo'); $product->setPrice(19.99); // relate this product to the category $product->setCategory($category); // save the whole $product->save(); return new Response( 'Created product id: '.$product->getId().' and category id: '.$category->getId() ); } } Now, a single row is added to both the category and product tables. The product.category_id column for the new product is set to whatever the id is of the new category. Propel manages the persistence of this relationship for you. Fetching Related Objects When you need to fetch associated objects, your workflow looks just like it did before. First, fetch a $product object and then access its related Category: // ... Listing 13-19 use AcmeStoreBundleModelProductQuery; public function showAction($id) { $product = ProductQuery::create() ->joinWithCategory() PDF brought to you by Chapter 13: Databases and Propel | 137 generated on June 20, 2012
  • 138. ->findPk($id); $categoryName = $product->getCategory()->getName(); // ... } Note, in the above example, only one query was made. More information on Associations You will find more information on relations by reading the dedicated chapter on Relationships2. Lifecycle Callbacks Sometimes, you need to perform an action right before or after an object is inserted, updated, or deleted. These types of actions are known as "lifecycle" callbacks or "hooks", as they're callback methods that you need to execute during different stages of the lifecycle of an object (e.g. the object is inserted, updated, deleted, etc). To add a hook, just add a new method to the object class: Listing // src/Acme/StoreBundle/Model/Product.php 13-20 // ... class Product extends BaseProduct { public function preInsert(PropelPDO $con = null) { // do something before the object is inserted } } Propel provides the following hooks: • preInsert() code executed before insertion of a new object • postInsert() code executed after insertion of a new object • preUpdate() code executed before update of an existing object • postUpdate() code executed after update of an existing object • preSave() code executed before saving an object (new or existing) • postSave() code executed after saving an object (new or existing) • preDelete() code executed before deleting an object • postDelete() code executed after deleting an object Behaviors All bundled behaviors in Propel are working with Symfony2. To get more information about how to use Propel behaviors, look at the Behaviors reference section. 2. http://www.propelorm.org/documentation/04-relationships.html PDF brought to you by Chapter 13: Databases and Propel | 138 generated on June 20, 2012
  • 139. Commands You should read the dedicated section for Propel commands in Symfony23. 3. http://www.propelorm.org/cookbook/symfony2/working-with-symfony2#commands PDF brought to you by Chapter 13: Databases and Propel | 139 generated on June 20, 2012
  • 140. Chapter 14 Testing Whenever you write a new line of code, you also potentially add new bugs. To build better and more reliable applications, you should test your code using both functional and unit tests. The PHPUnit Testing Framework Symfony2 integrates with an independent library - called PHPUnit - to give you a rich testing framework. This chapter won't cover PHPUnit itself, but it has its own excellent documentation1. Symfony2 works with PHPUnit 3.5.11 or later. Each test - whether it's a unit test or a functional test - is a PHP class that should live in the Tests/ subdirectory of your bundles. If you follow this rule, then you can run all of your application's tests with the following command: Listing # specify the configuration directory on the command line 14-1 $ phpunit -c app/ The -c option tells PHPUnit to look in the app/ directory for a configuration file. If you're curious about the PHPUnit options, check out the app/phpunit.xml.dist file. Code coverage can be generated with the --coverage-html option. 1. http://www.phpunit.de/manual/3.5/en/ PDF brought to you by Chapter 14: Testing | 140 generated on June 20, 2012
  • 141. Unit Tests A unit test is usually a test against a specific PHP class. If you want to test the overall behavior of your application, see the section about Functional 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/Utility/Calculator.php Listing 14-2 namespace AcmeDemoBundleUtility; class Calculator { public function add($a, $b) { return $a + $b; } } To test this, create a CalculatorTest file in the Tests/Utility directory of your bundle: // src/Acme/DemoBundle/Tests/Utility/CalculatorTest.php Listing 14-3 namespace AcmeDemoBundleTestsUtility; use AcmeDemoBundleUtilityCalculator; 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. Just like in your real application - autoloading is automatically enabled via the bootstrap.php.cache file (as configured by default in the phpunit.xml.dist file). Running tests for a given file or directory is also very easy: # run all tests in the Utility directory Listing 14-4 $ 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/ PDF brought to you by Chapter 14: Testing | 141 generated on June 20, 2012
  • 142. Functional Tests Functional tests check the integration of the different layers of an application (from the routing to the views). 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. Your First Functional Test Functional tests are simple PHP files that typically live in the Tests/Controller directory of your bundle. If you want to test the pages handled by your DemoController class, start by creating a new DemoControllerTest.php file that extends a special WebTestCase class. For example, the Symfony2 Standard Edition provides a simple functional test for its DemoController (DemoControllerTest2) that reads as follows: Listing // src/Acme/DemoBundle/Tests/Controller/DemoControllerTest.php 14-5 namespace AcmeDemoBundleTestsController; use SymfonyBundleFrameworkBundleTestWebTestCase; 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()); } } To run your functional tests, the WebTestCase class bootstraps the kernel of your application. In most cases, this happens automatically. However, if your kernel is in a non-standard directory, you'll need to modify your phpunit.xml.dist file to set the KERNEL_DIR environment variable to the directory of your kernel: Listing <phpunit> 14-6 <!-- ... --> <php> <server name="KERNEL_DIR" value="/path/to/your/app/" /> </php> <!-- ... --> </phpunit> The createClient() method returns a client, which is like a browser that you'll use to crawl your site: 2. https://github.com/symfony/symfony-standard/blob/master/src/Acme/DemoBundle/Tests/Controller/DemoControllerTest.php PDF brought to you by Chapter 14: Testing | 142 generated on June 20, 2012
  • 143. $crawler = $client->request('GET', '/demo/hello/Fabien'); Listing 14-7 The request() method (see more about the request method) returns a Crawler3 object which can be used to select elements in the Response, click on links, and submit forms. The Crawler only works when the response is an XML or an HTML document. To get the raw content response, call $client->getResponse()->getContent(). Click on a link by first selecting it with the Crawler using either an XPath expression or a CSS selector, then use the Client to click on it. For example, the following code finds all links with the text Greet, then selects the second one, and ultimately clicks on it: $link = $crawler->filter('a:contains("Greet")')->eq(1)->link(); Listing 14-8 $crawler = $client->click($link); Submitting a form is very similar; select a form button, optionally override some form values, and submit the corresponding form: $form = $crawler->selectButton('submit')->form(); Listing 14-9 // set some values $form['name'] = 'Lucas'; $form['form_name[subject]'] = 'Hey there!'; // submit the form $crawler = $client->submit($form); The form can also handle uploads and contains methods to fill in different types of form fields (e.g. select() and tick()). For details, see the Forms section below. Now that you can easily navigate through an application, use assertions to test that it actually does what you expect it to. Use the Crawler to make assertions on the DOM: // Assert that the response matches a given CSS selector. Listing 14-10 $this->assertGreaterThan(0, $crawler->filter('h1')->count()); Or, test against the Response content directly if you just want to assert that the content contains some text, or if the Response is not an XML/HTML document: $this->assertRegExp('/Hello Fabien/', $client->getResponse()->getContent()); Listing 14-11 3. http://api.symfony.com/2.0/Symfony/Component/DomCrawler/Crawler.html PDF brought to you by Chapter 14: Testing | 143 generated on June 20, 2012
  • 144. More about the request() method: The full signature of the request() method is: Listing request( 14-12 $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: Listing $client->request( 14-13 'GET', '/demo/hello/Fabien', array(), array(), array( 'CONTENT_TYPE' => 'application/json', 'HTTP_REFERER' => '/foo/bar', ) ); 4. http://php.net/manual/en/reserved.variables.server.php PDF brought to you by Chapter 14: Testing | 144 generated on June 20, 2012
  • 145. Useful Assertions To get you started faster, here is a list of the most common and useful test assertions: // Assert that there is more than one h2 tag with the class "subtitle" Listing 14-14 $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()); Working with the Test Client The Test Client simulates an HTTP client like a browser and makes requests into your Symfony2 application: $crawler = $client->request('GET', '/hello/Fabien'); Listing 14-15 The request() method takes the HTTP method and a URL as arguments and returns a Crawler instance. Use the Crawler to find DOM elements in the Response. These elements can then be used to click on links and submit forms: $link = $crawler->selectLink('Go elsewhere...')->link(); Listing 14-16 $crawler = $client->click($link); $form = $crawler->selectButton('validate')->form(); $crawler = $client->submit($form, array('name' => 'Fabien')); The click() and submit() methods both return a Crawler object. These methods are the best way to browse your application as it takes care of a lot of things for you, like detecting the HTTP method from a form and giving you a nice API for uploading files. You will learn more about the Link and Form objects in the Crawler section below. PDF brought to you by Chapter 14: Testing | 145 generated on June 20, 2012
  • 146. The request method can also be used to simulate form submissions directly or perform more complex requests: Listing // Directly submit a form (but using the Crawler is easier!) 14-17 $client->request('POST', '/submit', array('name' => 'Fabien')); // Form submission with a file upload use SymfonyComponentHttpFoundationFileUploadedFile; $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') ); Last but not least, you can force each request to be executed in its own PHP process to avoid any side- effects when working with several clients in the same script: Listing $client->insulate(); 14-18 Browsing The Client supports many operations that can be done in a real browser: Listing $client->back(); 14-19 $client->forward(); $client->reload(); // Clears all cookies and the history $client->restart(); Accessing Internal Objects If you use the client to test your application, you might want to access the client's internal objects: PDF brought to you by Chapter 14: Testing | 146 generated on June 20, 2012
  • 147. $history = $client->getHistory(); Listing 14-20 $cookieJar = $client->getCookieJar(); You can also get the objects related to the latest request: $request = $client->getRequest(); Listing 14-21 $response = $client->getResponse(); $crawler = $client->getCrawler(); If your requests are not insulated, you can also access the Container and the Kernel: $container = $client->getContainer(); Listing 14-22 $kernel = $client->getKernel(); Accessing the Container It's highly recommended that a functional test only tests the Response. But under certain very rare circumstances, you might want to access some internal objects to write assertions. In such cases, you can access the dependency injection container: $container = $client->getContainer(); Listing 14-23 Be warned that this does not work if you insulate the client or if you use an HTTP layer. For a list of services available in your application, use the container:debug console task. If the information you need to check is available from the profiler, use it instead. Accessing the Profiler Data On each request, the Symfony profiler collects and stores a lot of data about the internal handling of that request. For example, the profiler could be used to verify that a given page executes less than a certain number of database queries when loading. To get the Profiler for the last request, do the following: $profile = $client->getProfile(); Listing 14-24 For specific details on using the profiler inside a test, see the How to use the Profiler in a Functional Test cookbook entry. Redirecting When a request returns a redirect response, the client does not follow it automatically. You can examine the response and force a redirection afterwards with the followRedirect() method: $crawler = $client->followRedirect(); Listing 14-25 If you want the client to automatically follow all redirects, you can force him with the followRedirects() method: $client->followRedirects(); Listing 14-26 PDF brought to you by Chapter 14: Testing | 147 generated on June 20, 2012
  • 148. 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. Traversing Like jQuery, the Crawler has methods to traverse the DOM of an HTML/XML document. For example, the following finds all input[type=submit] elements, selects the last one on the page, and then selects its immediate parent element: Listing $newCrawler = $crawler->filter('input[type=submit]') 14-27 ->last() ->parents() ->first() ; Many other methods are also available: Method Description 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 Since each of these methods returns a new Crawler instance, you can narrow down your node selection by chaining the method calls: Listing $crawler 14-28 ->filter('h1') ->reduce(function ($node, $i) { if (!$node->getAttribute('class')) { return false; } }) ->first(); Use the count() function to get the number of nodes stored in a Crawler: count($crawler) PDF brought to you by Chapter 14: Testing | 148 generated on June 20, 2012
  • 149. Extracting Information The Crawler can extract information from the nodes: // Returns the attribute value for the first node Listing 14-29 $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'); }); Links To select links, you can use the traversing methods above or the convenient selectLink() shortcut: $crawler->selectLink('Click here'); Listing 14-30 This selects all links that contain the given text, or clickable images for which the alt attribute contains the given text. Like the other filtering methods, this returns another Crawler object. Once you've selected a link, you have access to a special Link object, which has helpful methods specific to links (such as getMethod() and getUri()). To click on the link, use the Client's click() method and pass it a Link object: $link = $crawler->selectLink('Click here')->link(); Listing 14-31 $client->click($link); Forms Just like links, you select forms with the selectButton() method: $buttonCrawlerNode = $crawler->selectButton('submit'); Listing 14-32 Notice that we select form buttons and not forms as a form can have several buttons; if you use the traversing API, keep in mind that you must look for a button. The selectButton() method can select button tags and submit input tags. It uses several different parts of the buttons to find them: • The value attribute value; • The id or alt attribute value for images; • The id or name attribute value for button tags. Once you have a Crawler representing a button, call the form() method to get a Form instance for the form wrapping the button node: PDF brought to you by Chapter 14: Testing | 149 generated on June 20, 2012
  • 150. Listing $form = $buttonCrawlerNode->form(); 14-33 When calling the form() method, you can also pass an array of field values that overrides the default ones: Listing $form = $buttonCrawlerNode->form(array( 14-34 'name' => 'Fabien', 'my_form[subject]' => 'Symfony rocks!', )); And if you want to simulate a specific HTTP method for the form, pass it as a second argument: Listing $form = $buttonCrawlerNode->form(array(), 'DELETE'); 14-35 The Client can submit Form instances: Listing $client->submit($form); 14-36 The field values can also be passed as a second argument of the submit() method: Listing $client->submit($form, array( 14-37 'name' => 'Fabien', 'my_form[subject]' => 'Symfony rocks!', )); For more complex situations, use the Form instance as an array to set the value of each field individually: Listing // Change the value of a field 14-38 $form['name'] = 'Fabien'; $form['my_form[subject]'] = 'Symfony rocks!'; There is also a nice API to manipulate the values of the fields according to their type: Listing // Select an option or a radio 14-39 $form['country']->select('France'); // Tick a checkbox $form['like_symfony']->tick(); // Upload a file $form['photo']->upload('/path/to/lucas.jpg'); You can get the values that will be submitted by calling the getValues() method on the Form object. The uploaded files are available in a separate array returned by getFiles(). The getPhpValues() and getPhpFiles() methods also return the submitted values, but in the PHP format (it converts the keys with square brackets notation - e.g. my_form[subject] - to PHP arrays). Testing Configuration The Client used by functional tests creates a Kernel that runs in a special test environment. Since Symfony loads the app/config/config_test.yml in the test environment, you can tweak any of your application's settings specifically for testing. For example, by default, the swiftmailer is configured to not actually deliver emails in the test environment. You can see this under the swiftmailer configuration option: PDF brought to you by Chapter 14: Testing | 150 generated on June 20, 2012
  • 151. # app/config/config_test.yml Listing 14-40 # ... 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: $client = static::createClient(array( Listing 14-41 'environment' => 'my_test_env', 'debug' => false, )); If your application behaves according to some HTTP headers, pass them as the second argument of createClient(): $client = static::createClient(array(), array( Listing 14-42 'HTTP_HOST' => 'en.example.com', 'HTTP_USER_AGENT' => 'MySuperBrowser/1.0', )); You can also override HTTP headers on a per request basis: $client->request('GET', '/', array(), array(), array( Listing 14-43 'HTTP_HOST' => 'en.example.com', 'HTTP_USER_AGENT' => 'MySuperBrowser/1.0', )); The test client is available as a service in the container in the test environment (or wherever the framework.test option is enabled). This means you can override the service entirely if you need to. PHPUnit Configuration Each application has its own PHPUnit configuration, stored in the phpunit.xml.dist file. You can edit this file to change the defaults or create a phpunit.xml file to tweak the configuration for your local machine. Store the phpunit.xml.dist file in your code repository, and ignore the phpunit.xml file. By default, only the tests stored in "standard" bundles are run by the phpunit command (standard being tests in the src/*/Bundle/Tests or src/*/Bundle/*Bundle/Tests directories) But you can easily add more directories. For instance, the following configuration adds the tests from the installed third-party bundles: <!-- hello/phpunit.xml.dist --> Listing 14-44 <testsuites> <testsuite name="Project Test Suite"> <directory>../src/*/*Bundle/Tests</directory> <directory>../src/Acme/Bundle/*Bundle/Tests</directory> </testsuite> </testsuites> PDF brought to you by Chapter 14: Testing | 151 generated on June 20, 2012
  • 152. To include other directories in the code coverage, also edit the <filter> section: Listing <filter> 14-45 <whitelist> <directory>../src</directory> <exclude> <directory>../src/*/*Bundle/Resources</directory> <directory>../src/*/*Bundle/Tests</directory> <directory>../src/Acme/Bundle/*Bundle/Resources</directory> <directory>../src/Acme/Bundle/*Bundle/Tests</directory> </exclude> </whitelist> </filter> Learn more from the Cookbook • How to simulate HTTP Authentication in a Functional Test • How to test the Interaction of several Clients • How to use the Profiler in a Functional Test PDF brought to you by Chapter 14: Testing | 152 generated on June 20, 2012
  • 153. Chapter 15 Validation Validation is a very common task in web applications. Data entered in forms needs to be validated. Data also needs to be validated before it is written into a database or passed to a web service. Symfony2 ships with a Validator1 component that makes this task easy and transparent. This component is based on the JSR303 Bean Validation specification2. What? A Java specification in PHP? You heard right, but it's not as bad as it sounds. Let's look at how it can be used in PHP. The Basics of Validation The best way to understand validation is to see it in action. To start, suppose you've created a plain-old- PHP object that you need to use somewhere in your application: // src/Acme/BlogBundle/Entity/Author.php Listing 15-1 namespace AcmeBlogBundleEntity; class Author { public $name; } So far, this is just an ordinary class that serves some purpose inside your application. The goal of validation is to tell you whether or not the data of an object is valid. For this to work, you'll configure a list of rules (called constraints) that the object must follow in order to be valid. These rules can be specified via a number of different formats (YAML, XML, annotations, or PHP). For example, to guarantee that the $name property is not empty, add the following: # src/Acme/BlogBundle/Resources/config/validation.yml Listing 15-2 AcmeBlogBundleEntityAuthor: properties: name: - NotBlank: ~ 1. https://github.com/symfony/Validator 2. http://jcp.org/en/jsr/detail?id=303 PDF brought to you by Chapter 15: Validation | 153 generated on June 20, 2012
  • 154. Protected and private properties can also be validated, as well as "getter" methods (see validator- constraint-targets). Using the validator Service Next, to actually validate an Author object, use the validate method on the validator service (class Validator3). The job of the validator is easy: to read the constraints (i.e. rules) of a class and verify whether or not the data on the object satisfies those constraints. If validation fails, an array of errors is returned. Take this simple example from inside a controller: Listing use SymfonyComponentHttpFoundationResponse; 15-3 use AcmeBlogBundleEntityAuthor; // ... 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!'); } } If the $name property is empty, you will see the following error message: Listing AcmeBlogBundleAuthor.name: 15-4 This value should not be blank If you insert a value into the name property, the happy success message will appear. Most of the time, you won't interact directly with the validator service or need to worry about printing out the errors. Most of the time, you'll use validation indirectly when handling submitted form data. For more information, see the Validation and Forms. You could also pass the collection of errors into a template. Listing if (count($errors) > 0) { 15-5 return $this->render('AcmeBlogBundle:Author:validate.html.twig', array( 'errors' => $errors, )); } else { // ... } Inside the template, you can output the list of errors exactly as needed: Listing 15-6 3. http://api.symfony.com/2.0/Symfony/Component/Validator/Validator.html PDF brought to you by Chapter 15: Validation | 154 generated on June 20, 2012
  • 155. {# 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> Each validation error (called a "constraint violation"), is represented by a ConstraintViolation4 object. Validation and Forms The validator service can be used at any time to validate any object. In reality, however, you'll usually work with the validator indirectly when working with forms. Symfony's form library uses the validator service internally to validate the underlying object after values have been submitted and bound. The constraint violations on the object are converted into FieldError objects that can easily be displayed with your form. The typical form submission workflow looks like the following from inside a controller: use AcmeBlogBundleEntityAuthor; Listing 15-7 use AcmeBlogBundleFormAuthorType; use SymfonyComponentHttpFoundationRequest; // ... public function updateAction(Request $request) { $author = new AcmeBlogBundleEntityAuthor(); $form = $this->createForm(new AuthorType(), $author); if ($request->getMethod() == 'POST') { $form->bindRequest($request); if ($form->isValid()) { // the validation passed, do something with the $author object return $this->redirect($this->generateUrl('...')); } } return $this->render('BlogBundle:Author:form.html.twig', array( 'form' => $form->createView(), )); } This example uses an AuthorType form class, which is not shown here. For more information, see the Forms chapter. 4. http://api.symfony.com/2.0/Symfony/Component/Validator/ConstraintViolation.html PDF brought to you by Chapter 15: Validation | 155 generated on June 20, 2012
  • 156. Configuration The Symfony2 validator is enabled by default, but you must explicitly enable annotations if you're using the annotation method to specify your constraints: Listing # app/config/config.yml 15-8 framework: validation: { enable_annotations: true } 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. Behind the scenes, a constraint is simply a PHP object that makes an assertive statement. In real life, a constraint could be: "The cake must not be burned". In Symfony2, constraints are similar: they are assertions that a condition is true. Given a value, a constraint will tell you whether or not that value adheres to the rules of the constraint. Supported Constraints Symfony2 packages a large number of the most commonly-needed constraints: Basic Constraints These are the basic constraints: use them to assert very basic things about the value of properties or the return value of methods on your object. • NotBlank • Blank • NotNull • Null • True • False • Type String Constraints • Email • MinLength • MaxLength • Url • Regex • Ip Number Constraints • Max • Min Date Constraints • Date PDF brought to you by Chapter 15: Validation | 156 generated on June 20, 2012
  • 157. • DateTime • Time Collection Constraints • Choice • Collection • UniqueEntity • Language • Locale • Country File Constraints • File • Image Other Constraints • Callback • All • Valid You can also create your own custom constraints. This topic is covered in the "How to create a Custom Validation Constraint" article of the cookbook. Constraint Configuration Some constraints, like NotBlank, are simple whereas others, like the Choice constraint, have several configuration options available. Suppose that the Author class has another property, gender that can be set to either "male" or "female": # src/Acme/BlogBundle/Resources/config/validation.yml Listing 15-9 AcmeBlogBundleEntityAuthor: properties: gender: - Choice: { choices: [male, female], message: Choose a valid gender. } The options of a constraint can always be passed in as an array. Some constraints, however, also allow you to pass the value of one, "default", option in place of the array. In the case of the Choice constraint, the choices options can be specified in this way. # src/Acme/BlogBundle/Resources/config/validation.yml Listing 15-10 AcmeBlogBundleEntityAuthor: properties: gender: - Choice: [male, female] This is purely meant to make the configuration of the most common option of a constraint shorter and quicker. If you're ever unsure of how to specify an option, either check the API documentation for the constraint or play it safe by always passing in an array of options (the first method shown above). PDF brought to you by Chapter 15: Validation | 157 generated on June 20, 2012
  • 158. Translation Constraint Messages For information on translating the constraint messages, see Translating Constraint Messages. Constraint Targets Constraints can be applied to a class property (e.g. name) or a public getter method (e.g. getFullName). The first is the most common and easy to use, but the second allows you to specify more complex validation rules. Properties Validating class properties is the most basic validation technique. Symfony2 allows you to validate private, protected or public properties. The next listing shows you how to configure the $firstName property of an Author class to have at least 3 characters. Listing # src/Acme/BlogBundle/Resources/config/validation.yml 15-11 AcmeBlogBundleEntityAuthor: properties: firstName: - NotBlank: ~ - MinLength: 3 Getters Constraints can also be applied to the return value of a method. Symfony2 allows you to add a constraint to any public method whose name starts with "get" or "is". In this guide, both of these types of methods are referred to as "getters". The benefit of this technique is that it allows you to validate your object dynamically. For example, suppose you want to make sure that a password field doesn't match the first name of the user (for security reasons). You can do this by creating an isPasswordLegal method, and then asserting that this method must return true: Listing # src/Acme/BlogBundle/Resources/config/validation.yml 15-12 AcmeBlogBundleEntityAuthor: getters: passwordLegal: - "True": { message: "The password cannot match your first name" } Now, create the isPasswordLegal() method, and include the logic you need: Listing public function isPasswordLegal() 15-13 { return ($this->firstName != $this->password); } The keen-eyed among you will have noticed that the prefix of the getter ("get" or "is") is omitted in the mapping. This allows you to move the constraint to a property with the same name later (or vice versa) without changing your validation logic. PDF brought to you by Chapter 15: Validation | 158 generated on June 20, 2012
  • 159. Classes Some constraints apply to the entire class being validated. For example, the Callback constraint is a generic constraint that's applied to the class itself. When that class is validated, methods specified by that constraint are simply executed so that each can provide more custom validation. Validation Groups So far, you've been able to add constraints to a class and ask whether or not that class passes all of the defined constraints. In some cases, however, you'll need to validate an object against only some of the constraints on that class. To do this, you can organize each constraint into one or more "validation groups", and then apply validation against just one group of constraints. For example, suppose you have a User class, which is used both when a user registers and when a user updates his/her contact information later: # src/Acme/BlogBundle/Resources/config/validation.yml Listing 15-14 AcmeBlogBundleEntityUser: 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. To tell the validator to use a specific group, pass one or more group names as the second argument to the validate() method: $errors = $validator->validate($author, array('registration')); Listing 15-15 Of course, you'll usually work with validation indirectly through the form library. For information on how to use validation groups inside forms, see Validation Groups. Validating Values and Arrays So far, you've seen how you can validate entire objects. But sometimes, you just want to validate a simple value - like to verify that a string is a valid email address. This is actually pretty easy to do. From inside a controller, it looks like this: // add this to the top of your class Listing 15-16 use SymfonyComponentValidatorConstraintsEmail; 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 PDF brought to you by Chapter 15: Validation | 159 generated on June 20, 2012
  • 160. $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 } // ... } By calling validateValue on the validator, you can pass in a raw value and the constraint object that you want to validate that value against. A full list of the available constraints - as well as the full class name for each constraint - is available in the constraints reference section . The validateValue method returns a ConstraintViolationList5 object, which acts just like an array of errors. Each error in the collection is a ConstraintViolation6 object, which holds the error message on its getMessage method. Final Thoughts The Symfony2 validator is a powerful tool that can be leveraged to guarantee that the data of any object is "valid". The power behind validation lies in "constraints", which are rules that you can apply to properties or getter methods of your object. And while you'll most commonly use the validation framework indirectly when using forms, remember that it can be used anywhere to validate any object. Learn more from the Cookbook • How to create a Custom Validation Constraint 5. http://api.symfony.com/2.0/Symfony/Component/Validator/ConstraintViolationList.html 6. http://api.symfony.com/2.0/Symfony/Component/Validator/ConstraintViolation.html PDF brought to you by Chapter 15: Validation | 160 generated on June 20, 2012
  • 161. Chapter 16 Forms Dealing with HTML forms is one of the most common - and challenging - tasks for a web developer. Symfony2 integrates a Form component that makes dealing with forms easy. In this chapter, you'll build a complex form from the ground-up, learning the most important features of the form library along the way. The Symfony form component is a standalone library that can be used outside of Symfony2 projects. For more information, see the Symfony2 Form Component1 on Github. Creating a Simple Form Suppose you're building a simple todo list application that will need to display "tasks". Because your users will need to edit and create tasks, you're going to need to build a form. But before you begin, first focus on the generic Task class that represents and stores the data for a single task: // src/Acme/TaskBundle/Entity/Task.php Listing 16-1 namespace AcmeTaskBundleEntity; class Task { protected $task; protected $dueDate; public function getTask() { return $this->task; } public function setTask($task) { $this->task = $task; 1. https://github.com/symfony/Form PDF brought to you by Chapter 16: Forms | 161 generated on June 20, 2012
  • 162. } public function getDueDate() { return $this->dueDate; } public function setDueDate(DateTime $dueDate = null) { $this->dueDate = $dueDate; } } If you're coding along with this example, create the AcmeTaskBundle first by running the following command (and accepting all of the default options): Listing php app/console generate:bundle --namespace=Acme/TaskBundle 16-2 This class is a "plain-old-PHP-object" because, so far, it has nothing to do with Symfony or any other library. It's quite simply a normal PHP object that directly solves a problem inside your application (i.e. the need to represent a task in your application). Of course, by the end of this chapter, you'll be able to submit data to a Task instance (via an HTML form), validate its data, and persist it to the database. Building the Form Now that you've created a Task class, the next step is to create and render the actual HTML form. In Symfony2, this is done by building a form object and then rendering it in a template. For now, this can all be done from inside a controller: Listing // src/Acme/TaskBundle/Controller/DefaultController.php 16-3 namespace AcmeTaskBundleController; use SymfonyBundleFrameworkBundleControllerController; use AcmeTaskBundleEntityTask; use SymfonyComponentHttpFoundationRequest; class DefaultController extends Controller { public function newAction(Request $request) { // create a task and give it some dummy data for this example $task = 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(), )); } } PDF brought to you by Chapter 16: Forms | 162 generated on June 20, 2012
  • 163. This example shows you how to build your form directly in the controller. Later, in the "Creating Form Classes" section, you'll learn how to build your form in a standalone class, which is recommended as your form becomes reusable. Creating a form requires relatively little code because Symfony2 form objects are built with a "form builder". The form builder's purpose is to allow you to write simple form "recipes", and have it do all the heavy-lifting of actually building the form. In this example, you've added two fields to your form - task and dueDate - corresponding to the task and dueDate properties of the Task class. You've also assigned each a "type" (e.g. text, date), which, among other things, determines which HTML form tag(s) is rendered for that field. Symfony2 comes with many built-in types that will be discussed shortly (see Built-in Field Types). Rendering the Form Now that the form has been created, the next step is to render it. This is done by passing a special form "view" object to your template (notice the $form->createView() in the controller above) and using a set of form helper functions: {# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #} Listing 16-4 <form action="{{ path('task_new') }}" method="post" {{ form_enctype(form) }}> {{ form_widget(form) }} <input type="submit" /> </form> This example assumes that you've created a route called task_new that points to the AcmeTaskBundle:Default:new controller that was created earlier. That's it! By printing form_widget(form), each field in the form is rendered, along with a label and error message (if there is one). As easy as this is, it's not very flexible (yet). Usually, you'll want to render each form field individually so you can control how the form looks. You'll learn how to do that in the "Rendering a Form in a Template" section. Before moving on, notice how the rendered task input field has the value of the task property from the $task object (i.e. "Write a blog post"). This is the first job of a form: to take data from an object and translate it into a format that's suitable for being rendered in an HTML form. PDF brought to you by Chapter 16: Forms | 163 generated on June 20, 2012
  • 164. The form system is smart enough to access the value of the protected task property via the getTask() and setTask() methods on the Task class. Unless a property is public, it must have a "getter" and "setter" method so that the form component can get and put data onto the property. For a Boolean property, you can use an "isser" method (e.g. isPublished()) instead of a getter (e.g. getPublished()). Handling Form Submissions The second job of a form is to translate user-submitted data back to the properties of an object. To make this happen, the submitted data from the user must be bound to the form. Add the following functionality to your controller: Listing // ... 16-5 public function newAction(Request $request) { // just setup a fresh $task object (remove the dummy data) $task = new Task(); $form = $this->createFormBuilder($task) ->add('task', 'text') ->add('dueDate', 'date') ->getForm(); if ($request->getMethod() == 'POST') { $form->bindRequest($request); if ($form->isValid()) { // perform some action, such as saving the task to the database return $this->redirect($this->generateUrl('task_success')); } } // ... } Now, when submitting the form, the controller binds the submitted data to the form, which translates that data back to the task and dueDate properties of the $task object. This all happens via the bindRequest() method. As soon as bindRequest() is called, the submitted data is transferred to the underlying object immediately. This happens regardless of whether or not the underlying data is actually valid. This controller follows a common pattern for handling forms, and has three possible paths: 1. When initially loading the page in a browser, the request method is GET and the form is simply created and rendered; 2. When the user submits the form (i.e. the method is POST) with invalid data (validation is covered in the next section), the form is bound and then rendered, this time displaying all validation errors; 3. When the user submits the form with valid data, the form is bound and you have the opportunity to perform some actions using the $task object (e.g. persisting it to the database) before redirecting the user to some other page (e.g. a "thank you" or "success" page). PDF brought to you by Chapter 16: Forms | 164 generated on June 20, 2012
  • 165. Redirecting a user after a successful form submission prevents the user from being able to hit "refresh" and re-post the data. Form Validation In the previous section, you learned how a form can be submitted with valid or invalid data. In Symfony2, validation is applied to the underlying object (e.g. Task). In other words, the question isn't whether the "form" is valid, but whether or not the $task object is valid after the form has applied the submitted data to it. Calling $form->isValid() is a shortcut that asks the $task object whether or not it has valid data. Validation is done by adding a set of rules (called constraints) to a class. To see this in action, add validation constraints so that the task field cannot be empty and the dueDate field cannot be empty and must be a valid DateTime object. # Acme/TaskBundle/Resources/config/validation.yml Listing 16-6 AcmeTaskBundleEntityTask: properties: task: - NotBlank: ~ dueDate: - NotBlank: ~ - Type: DateTime That's it! If you re-submit the form with invalid data, you'll see the corresponding errors printed out with the form. HTML5 Validation As of HTML5, many browsers can natively enforce certain validation constraints on the client side. The most common validation is activated by rendering a required attribute on fields that are required. For browsers that support HTML5, this will result in a native browser message being displayed if the user tries to submit the form with that field blank. Generated forms take full advantage of this new feature by adding sensible HTML attributes that trigger the validation. The client-side validation, however, can be disabled by adding the novalidate attribute to the form tag or formnovalidate to the submit tag. This is especially useful when you want to test your server-side validation constraints, but are being prevented by your browser from, for example, submitting blank fields. Validation is a very powerful feature of Symfony2 and has its own dedicated chapter. Validation Groups If you're not using validation groups, then you can skip this section. If your object takes advantage of validation groups, you'll need to specify which validation group(s) your form should use: $form = $this->createFormBuilder($users, array( Listing 16-7 'validation_groups' => array('registration'), PDF brought to you by Chapter 16: Forms | 165 generated on June 20, 2012
  • 166. ))->add(...) ; If you're creating form classes (a good practice), then you'll need to add the following to the getDefaultOptions() method: Listing public function getDefaultOptions(array $options) 16-8 { return array( 'validation_groups' => array('registration') ); } In both of these cases, only the registration validation group will be used to validate the underlying object. Built-in Field Types Symfony comes standard with a large group of field types that cover all of the common form fields and data types you'll encounter: Text Fields • text • textarea • email • integer • money • number • password • percent • search • url Choice Fields • choice • entity • country • language • locale • timezone Date and Time Fields • date • datetime • time • birthday Other Fields • checkbox • file PDF brought to you by Chapter 16: Forms | 166 generated on June 20, 2012
  • 167. • radio Field Groups • collection • repeated Hidden Fields • hidden • csrf Base Fields • field • form You can also create your own custom field types. This topic is covered in the "How to Create a Custom Form Field Type" article of the cookbook. Field Type Options Each field type has a number of options that can be used to configure it. For example, the dueDate field is currently being rendered as 3 select boxes. However, the date field can be configured to be rendered as a single text box (where the user would enter the date as a string in the box): ->add('dueDate', 'date', array('widget' => 'single_text')) Listing 16-9 Each field type has a number of different options that can be passed to it. Many of these are specific to the field type and details can be found in the documentation for each type. The required option The most common option is the required option, which can be applied to any field. By default, the required option is set to true, meaning that HTML5-ready browsers will apply client-side validation if the field is left blank. If you don't want this behavior, either set the required option on your field to false or disable HTML5 validation. Also note that setting the required option to true will not result in server-side validation to be applied. In other words, if a user submits a blank value for the field (either with an old browser or web service, for example), it will be accepted as a valid value unless you use Symfony's NotBlank or NotNull validation constraint. In other words, the required option is "nice", but true server-side validation should always be used. PDF brought to you by Chapter 16: Forms | 167 generated on June 20, 2012
  • 168. The label option The label for the form field can be set using the label option, which can be applied to any field: Listing ->add('dueDate', 'date', array( 16-10 'widget' => 'single_text', 'label' => 'Due Date', )) The label for a field can also be set in the template rendering the form, see below. Field Type Guessing Now that you've added validation metadata to the Task class, Symfony already knows a bit about your fields. If you allow it, Symfony can "guess" the type of your field and set it up for you. In this example, Symfony can guess from the validation rules that both the task field is a normal text field and the dueDate field is a date field: Listing public function newAction() 16-11 { $task = new Task(); $form = $this->createFormBuilder($task) ->add('task') ->add('dueDate', null, array('widget' => 'single_text')) ->getForm(); } The "guessing" is activated when you omit the second argument to the add() method (or if you pass null to it). If you pass an options array as the third argument (done for dueDate above), these options are applied to the guessed field. If your form uses a specific validation group, the field type guesser will still consider all validation constraints when guessing your field types (including constraints that are not part of the validation group(s) being used). Field Type Options Guessing In addition to guessing the "type" for a field, Symfony can also try to guess the correct values of a number of field options. When these options are set, the field will be rendered with special HTML attributes that provide for HTML5 client-side validation. However, it doesn't generate the equivalent server-side constraints (e.g. AssertMaxLength). And though you'll need to manually add your server-side validation, these field type options can then be guessed from that information. • required: The required option can be guessed based off of the validation rules (i.e. is the field NotBlank or NotNull) or the Doctrine metadata (i.e. is the field nullable). This is very useful, as your client-side validation will automatically match your validation rules. PDF brought to you by Chapter 16: Forms | 168 generated on June 20, 2012
  • 169. • max_length: If the field is some sort of text field, then the max_length option can be guessed from the validation constraints (if MaxLength or Max is used) or from the Doctrine metadata (via the field's length). These field options are only guessed if you're using Symfony to guess the field type (i.e. omit or pass null as the second argument to add()). If you'd like to change one of the guessed values, you can override it by passing the option in the options field array: ->add('task', null, array('max_length' => 4)) Listing 16-12 Rendering a Form in a Template So far, you've seen how an entire form can be rendered with just one line of code. Of course, you'll usually need much more flexibility when rendering: {# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #} Listing 16-13 <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> Let's take a look at each part: • form_enctype(form) - If at least one field is a file upload field, this renders the obligatory enctype="multipart/form-data"; • form_errors(form) - Renders any errors global to the whole form (field-specific errors are displayed next to each field); • form_row(form.dueDate) - Renders the label, any errors, and the HTML form widget for the given field (e.g. dueDate) inside, by default, a div element; • form_rest(form) - Renders any fields that have not yet been rendered. It's usually a good idea to place a call to this helper at the bottom of each form (in case you forgot to output a field or don't want to bother manually rendering hidden fields). This helper is also useful for taking advantage of the automatic CSRF Protection. The majority of the work is done by the form_row helper, which renders the label, errors and HTML form widget of each field inside a div tag by default. In the Form Theming section, you'll learn how the form_row output can be customized on many different levels. You can access the current data of your form via form.vars.value: {{ form.vars.value.task }} Listing 16-14 PDF brought to you by Chapter 16: Forms | 169 generated on June 20, 2012
  • 170. Rendering each Field by Hand The form_row helper is great because you can very quickly render each field of your form (and the markup used for the "row" can be customized as well). But since life isn't always so simple, you can also render each field entirely by hand. The end-product of the following is the same as when you used the form_row helper: Listing {{ form_errors(form) }} 16-15 <div> {{ form_label(form.task) }} {{ form_errors(form.task) }} {{ form_widget(form.task) }} </div> <div> {{ form_label(form.dueDate) }} {{ form_errors(form.dueDate) }} {{ form_widget(form.dueDate) }} </div> {{ form_rest(form) }} If the auto-generated label for a field isn't quite right, you can explicitly specify it: Listing {{ form_label(form.task, 'Task Description') }} 16-16 Some field types have additional rendering options that can be passed to the widget. These options are documented with each type, but one common options is attr, which allows you to modify attributes on the form element. The following would add the task_field class to the rendered input text field: Listing {{ form_widget(form.task, { 'attr': {'class': 'task_field'} }) }} 16-17 If you need to render form fields "by hand" then you can access individual values for fields such as the id, name and label. For example to get the id: Listing {{ form.task.vars.id }} 16-18 To get the value used for the form field's name attribute you need to use the full_name value: Listing {{ form.task.vars.full_name }} 16-19 Twig Template Function Reference If you're using Twig, a full reference of the form rendering functions is available in the reference manual. Read this to know everything about the helpers available and the options that can be used with each. Creating Form Classes As you've seen, a form can be created and used directly in a controller. However, a better practice is to build the form in a separate, standalone PHP class, which can then be reused anywhere in your application. Create a new class that will house the logic for building the task form: Listing // src/Acme/TaskBundle/Form/Type/TaskType.php 16-20 namespace AcmeTaskBundleFormType; PDF brought to you by Chapter 16: Forms | 170 generated on June 20, 2012
  • 171. use SymfonyComponentFormAbstractType; use SymfonyComponentFormFormBuilder; class TaskType extends AbstractType { public function buildForm(FormBuilder $builder, array $options) { $builder->add('task'); $builder->add('dueDate', null, array('widget' => 'single_text')); } public function getName() { return 'task'; } } This new class contains all the directions needed to create the task form (note that the getName() method should return a unique identifier for this form "type"). It can be used to quickly build a form object in the controller: // src/Acme/TaskBundle/Controller/DefaultController.php Listing 16-21 // add this new use statement at the top of the class use AcmeTaskBundleFormTypeTaskType; public function newAction() { $task = // ... $form = $this->createForm(new TaskType(), $task); // ... } Placing the form logic into its own class means that the form can be easily reused elsewhere in your project. This is the best way to create forms, but the choice is ultimately up to you. Setting the data_class Every form needs to know the name of the class that holds the underlying data (e.g. AcmeTaskBundleEntityTask). Usually, this is just guessed based off of the object passed to the second argument to createForm (i.e. $task). Later, when you begin embedding forms, this will no longer be sufficient. So, while not always necessary, it's generally a good idea to explicitly specify the data_class option by adding the following to your form type class: public function getDefaultOptions(array $options) Listing 16-22 { return array( 'data_class' => 'AcmeTaskBundleEntityTask', ); } When mapping forms to objects, all fields are mapped. Any fields on the form that do not exist on the mapped object will cause an exception to be thrown. PDF brought to you by Chapter 16: Forms | 171 generated on June 20, 2012
  • 172. In cases where you need extra fields in the form (for example: a "do you agree with these terms" checkbox) that will not be mapped to the underlying object, you need to set the property_path option to false: Listing public function buildForm(FormBuilder $builder, array $options) 16-23 { $builder->add('task'); $builder->add('dueDate', null, array('property_path' => false)); } Additionally, if there are any fields on the form that aren't included in the submitted data, those fields will be explicitly set to null. The field data can be accessed in a controller with: Listing $form->get('dueDate')->getData(); 16-24 Forms and Doctrine The goal of a form is to translate data from an object (e.g. Task) to an HTML form and then translate user-submitted data back to the original object. As such, the topic of persisting the Task object to the database is entirely unrelated to the topic of forms. But, if you've configured the Task class to be persisted via Doctrine (i.e. you've added mapping metadata for it), then persisting it after a form submission can be done when the form is valid: Listing if ($form->isValid()) { 16-25 $em = $this->getDoctrine()->getEntityManager(); $em->persist($task); $em->flush(); return $this->redirect($this->generateUrl('task_success')); } If, for some reason, you don't have access to your original $task object, you can fetch it from the form: Listing $task = $form->getData(); 16-26 For more information, see the Doctrine ORM chapter. The key thing to understand is that when the form is bound, the submitted data is transferred to the underlying object immediately. If you want to persist that data, you simply need to persist the object itself (which already contains the submitted data). Embedded Forms Often, you'll want to build a form that will include fields from many different objects. For example, a registration form may contain data belonging to a User object as well as many Address objects. Fortunately, this is easy and natural with the form component. Embedding a Single Object Suppose that each Task belongs to a simple Category object. Start, of course, by creating the Category object: PDF brought to you by Chapter 16: Forms | 172 generated on June 20, 2012
  • 173. // src/Acme/TaskBundle/Entity/Category.php Listing 16-27 namespace AcmeTaskBundleEntity; use SymfonyComponentValidatorConstraints as Assert; class Category { /** * @AssertNotBlank() */ public $name; } Next, add a new category property to the Task class: // ... Listing 16-28 class Task { // ... /** * @AssertType(type="AcmeTaskBundleEntityCategory") */ protected $category; // ... public function getCategory() { return $this->category; } public function setCategory(Category $category = null) { $this->category = $category; } } Now that your application has been updated to reflect the new requirements, create a form class so that a Category object can be modified by the user: // src/Acme/TaskBundle/Form/Type/CategoryType.php Listing 16-29 namespace AcmeTaskBundleFormType; use SymfonyComponentFormAbstractType; use SymfonyComponentFormFormBuilder; class CategoryType extends AbstractType { public function buildForm(FormBuilder $builder, array $options) { $builder->add('name'); } public function getDefaultOptions(array $options) { return array( 'data_class' => 'AcmeTaskBundleEntityCategory', ); } PDF brought to you by Chapter 16: Forms | 173 generated on June 20, 2012
  • 174. public function getName() { return 'category'; } } The end goal is to allow the Category of a Task to be modified right inside the task form itself. To accomplish this, add a category field to the TaskType object whose type is an instance of the new CategoryType class: Listing public function buildForm(FormBuilder $builder, array $options) 16-30 { // ... $builder->add('category', new CategoryType()); } The fields from CategoryType can now be rendered alongside those from the TaskType class. Render the Category fields in the same way as the original Task fields: Listing {# ... #} 16-31 <h3>Category</h3> <div class="category"> {{ form_row(form.category.name) }} </div> {{ form_rest(form) }} {# ... #} When the user submits the form, the submitted data for the Category fields are used to construct an instance of Category, which is then set on the category field of the Task instance. The Category instance is accessible naturally via $task->getCategory() and can be persisted to the database or used however you need. Embedding a Collection of Forms You can also embed a collection of forms into one form (imagine a Category form with many Product sub-forms). This is done by using the collection field type. For more information see the "How to Embed a Collection of Forms" cookbook entry and the collection field type reference. Form Theming Every part of how a form is rendered can be customized. You're free to change how each form "row" renders, change the markup used to render errors, or even customize how a textarea tag should be rendered. Nothing is off-limits, and different customizations can be used in different places. Symfony uses templates to render each and every part of a form, such as label tags, input tags, error messages and everything else. 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. In PHP, each form "fragment" is rendered via an individual template file. To customize any part of how a form renders, you just need to override the existing template by creating a new one. PDF brought to you by Chapter 16: Forms | 174 generated on June 20, 2012
  • 175. 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 #} Listing 16-32 {% block field_row %} {% spaceless %} <div class="form_row"> {{ form_label(form) }} {{ form_errors(form) }} {{ form_widget(form) }} </div> {% endspaceless %} {% endblock field_row %} The field_row form fragment is used when rendering most fields via the form_row function. To tell the form component to use your new field_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 #} Listing 16-33 {% form_theme form 'AcmeTaskBundle:Form:fields.html.twig' %} {% form_theme form 'AcmeTaskBundle:Form:fields.html.twig' 'AcmeTaskBundle:Form:fields2.html.twig' %} <form ...> The form_theme tag (in Twig) "imports" the fragments defined in the given template and uses them when rendering the form. In other words, when the form_row function is called later in this template, it will use the field_row block from your custom theme (instead of the default field_row block that ships with Symfony). Your custom theme does not have to override all the blocks. When rendering a block which is not overridden in your custom theme, the theming engine will fall back to the global theme (defined at the bundle level). If several custom themes are provided they will be searched in the listed order before falling back to the global theme. To customize any portion of a form, you just need to override the appropriate fragment. Knowing exactly which block or file to override is the subject of the next section. For a more extensive discussion, see How to customize Form Rendering. Form Fragment Naming In Symfony, every part of a form that is rendered - HTML form elements, errors, labels, etc - is defined in a base theme, which is a collection of blocks in Twig and a collection of template files in PHP. In Twig, every block needed is defined in a single template file (form_div_layout.html.twig2) that lives inside the Twig Bridge3. Inside this file, you can see every block needed to render a form and every default field type. In PHP, the fragments are individual template files. By default they are located in the Resources/views/ Form directory of the framework bundle (view on GitHub4). 2. https://github.com/symfony/symfony/blob/master/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig 3. https://github.com/symfony/symfony/tree/master/src/Symfony/Bridge/Twig 4. https://github.com/symfony/symfony/tree/master/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form PDF brought to you by Chapter 16: Forms | 175 generated on June 20, 2012
  • 176. Each fragment name follows the same basic pattern and is broken up into two pieces, separated by a single underscore character (_). A few examples are: • field_row - used by form_row to render most fields; • textarea_widget - used by form_widget to render a textarea field type; • field_errors - used by form_errors to render errors for a field; Each fragment follows the same basic pattern: type_part. The type portion corresponds to the field type being rendered (e.g. textarea, checkbox, date, etc) whereas the part portion corresponds to what is being rendered (e.g. label, widget, errors, etc). By default, there are 4 possible parts of a form that can be rendered: label (e.g. field_label) renders the field's label widget (e.g. field_widget) renders the field's HTML representation errors (e.g. field_errors) renders the field's errors row (e.g. field_row) renders the field's entire row (label, widget & errors) There are actually 3 other parts - rows, rest, and enctype - but you should rarely if ever need to worry about overriding them. By knowing the field type (e.g. textarea) and which part you want to customize (e.g. widget), you can construct the fragment name that needs to be overridden (e.g. textarea_widget). Template Fragment Inheritance In some cases, the fragment you want to customize will appear to be missing. For example, there is no textarea_errors fragment in the default themes provided with Symfony. So how are the errors for a textarea field rendered? The answer is: via the field_errors fragment. When Symfony renders the errors for a textarea type, it looks first for a textarea_errors fragment before falling back to the field_errors fragment. Each field type has a parent type (the parent type of textarea is field), and Symfony uses the fragment for the parent type if the base fragment doesn't exist. So, to override the errors for only textarea fields, copy the field_errors fragment, rename it to textarea_errors and customize it. To override the default error rendering for all fields, copy and customize the field_errors fragment directly. The "parent" type of each field type is available in the form type reference for each field type. Global Form Theming In the above example, you used the form_theme helper (in Twig) to "import" the custom form fragments into just that form. You can also tell Symfony to import form customizations across your entire project. Twig To automatically include the customized blocks from the fields.html.twig template created earlier in all templates, modify your application configuration file: PDF brought to you by Chapter 16: Forms | 176 generated on June 20, 2012
  • 177. # app/config/config.yml Listing 16-34 twig: form: resources: - 'AcmeTaskBundle:Form:fields.html.twig' # ... Any blocks inside the fields.html.twig template are now used globally to define form output. Customizing Form Output all in a Single File with Twig In Twig, you can also customize a form block right inside the template where that customization is needed: {% extends '::base.html.twig' %} Listing 16-35 {# import "_self" as the form theme #} {% form_theme form _self %} {# make the form fragment customization #} {% block field_row %} {# custom field row output #} {% endblock field_row %} {% block content %} {# ... #} {{ form_row(form.task) }} {% endblock %} The {% form_theme form _self %} tag allows form blocks to be customized directly inside the template that will use those customizations. Use this method to quickly make form output customizations that will only ever be needed in a single template. This {% form_theme form _self %} functionality will only work if your template extends another. If your template does not, you must point form_theme to a separate template. PHP To automatically include the customized templates from the Acme/TaskBundle/Resources/views/Form directory created earlier in all templates, modify your application configuration file: # app/config/config.yml Listing 16-36 framework: templating: form: resources: - 'AcmeTaskBundle:Form' # ... Any fragments inside the Acme/TaskBundle/Resources/views/Form directory are now used globally to define form output. PDF brought to you by Chapter 16: Forms | 177 generated on June 20, 2012
  • 178. CSRF Protection CSRF - or Cross-site request forgery5 - is a method by which a malicious user attempts to make your legitimate users unknowingly submit data that they don't intend to submit. Fortunately, CSRF attacks can be prevented by using a CSRF token inside your forms. The good news is that, by default, Symfony embeds and validates CSRF tokens automatically for you. This means that you can take advantage of the CSRF protection without doing anything. In fact, every form in this chapter has taken advantage of the CSRF protection! CSRF protection works by adding a hidden field to your form - called _token by default - that contains a value that only you and your user knows. This ensures that the user - not some other entity - is submitting the given data. Symfony automatically validates the presence and accuracy of this token. The _token field is a hidden field and will be automatically rendered if you include the form_rest() function in your template, which ensures that all un-rendered fields are output. The CSRF token can be customized on a form-by-form basis. For example: Listing class TaskType extends AbstractType 16-37 { // ... public function getDefaultOptions(array $options) { return array( 'data_class' => 'AcmeTaskBundleEntityTask', 'csrf_protection' => true, 'csrf_field_name' => '_token', // a unique key to help generate the secret token 'intention' => 'task_item', ); } // ... } To disable CSRF protection, set the csrf_protection option to false. Customizations can also be made globally in your project. For more information, see the form configuration reference section. The intention option is optional but greatly enhances the security of the generated token by making it different for each form. Using a Form without a Class In most cases, a form is tied to an object, and the fields of the form get and store their data on the properties of that object. This is exactly what you've seen so far in this chapter with the Task class. But sometimes, you may just want to use a form without a class, and get back an array of the submitted data. This is actually really easy: Listing // make sure you've imported the Request namespace above the class 16-38 use SymfonyComponentHttpFoundationRequest // ... 5. http://en.wikipedia.org/wiki/Cross-site_request_forgery PDF brought to you by Chapter 16: Forms | 178 generated on June 20, 2012
  • 179. public function contactAction(Request $request) { $defaultData = array('message' => 'Type your message here'); $form = $this->createFormBuilder($defaultData) ->add('name', 'text') ->add('email', 'email') ->add('message', 'textarea') ->getForm(); if ($request->getMethod() == 'POST') { $form->bindRequest($request); // data is an array with "name", "email", and "message" keys $data = $form->getData(); } // ... render the form } By default, a form actually assumes that you want to work with arrays of data, instead of an object. There are exactly two ways that you can change this behavior and tie the form to an object instead: 1. Pass an object when creating the form (as the first argument to createFormBuilder or the second argument to createForm); 2. Declare the data_class option on your form. If you don't do either of these, then the form will return the data as an array. In this example, since $defaultData is not an object (and no data_class option is set), $form->getData() ultimately returns an array. You can also access POST values (in this case "name") directly through the request object, like so: $this->get('request')->request->get('name'); Listing 16-39 Be advised, however, that in most cases using the getData() method is a better choice, since it returns the data (usually an object) after it's been transformed by the form framework. Adding Validation The only missing piece is validation. Usually, when you call $form->isValid(), the object is validated by reading the constraints that you applied to that class. But without a class, how can you add constraints to the data of your form? The answer is to setup the constraints yourself, and pass them into your form. The overall approach is covered a bit more in the validation chapter, but here's a short example: // import the namespaces above your controller class Listing 16-40 use SymfonyComponentValidatorConstraintsEmail; use SymfonyComponentValidatorConstraintsMinLength; use SymfonyComponentValidatorConstraintsCollection; $collectionConstraint = new Collection(array( 'name' => new MinLength(5), 'email' => new Email(array('message' => 'Invalid email address')), )); // create a form, no default values, pass in the constraint option $form = $this->createFormBuilder(null, array( 'validation_constraint' => $collectionConstraint, ))->add('email', 'email') PDF brought to you by Chapter 16: Forms | 179 generated on June 20, 2012
  • 180. // ... ; Now, when you call $form->bindRequest($request), the constraints setup here are run against your form's data. If you're using a form class, override the getDefaultOptions method to specify the option: Listing namespace AcmeTaskBundleFormType; 16-41 use SymfonyComponentFormAbstractType; use SymfonyComponentFormFormBuilder; use SymfonyComponentValidatorConstraintsEmail; use SymfonyComponentValidatorConstraintsMinLength; use SymfonyComponentValidatorConstraintsCollection; class ContactType extends AbstractType { // ... public function getDefaultOptions(array $options) { $collectionConstraint = new Collection(array( 'name' => new MinLength(5), 'email' => new Email(array('message' => 'Invalid email address')), )); return array('validation_constraint' => $collectionConstraint); } } Now, you have the flexibility to create forms - with validation - that return an array of data, instead of an object. In most cases, it's better - and certainly more robust - to bind your form to an object. But for simple forms, this is a great approach. Final Thoughts You now know all of the building blocks necessary to build complex and functional forms for your application. When building forms, keep in mind that the first goal of a form is to translate data from an object (Task) to an HTML form so that the user can modify that data. The second goal of a form is to take the data submitted by the user and to re-apply it to the object. There's still much more to learn about the powerful world of forms, such as how to handle file uploads with Doctrine or how to create a form where a dynamic number of sub-forms can be added (e.g. a todo list where you can keep adding more fields via Javascript before submitting). See the cookbook for these topics. Also, be sure to lean on the field type reference documentation, which includes examples of how to use each field type and its options. Learn more from the Cookbook • How to handle File Uploads with Doctrine • File Field Reference • Creating Custom Field Types • How to customize Form Rendering • How to Dynamically Generate Forms Using Form Events • Using Data Transformers PDF brought to you by Chapter 16: Forms | 180 generated on June 20, 2012
  • 181. Chapter 17 Security 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. In the first step of the process, the security system identifies who the user is by requiring the user to submit some sort of identification. This is called authentication, and it means that the system is trying to find out who you are. Once the system knows who you are, the next step is to determine if you should have access to a given resource. This part of the process is called authorization, and it means that the system is checking to see if you have privileges to perform a certain action. Since the best way to learn is to see an example, let's dive right in. Symfony's security component1 is available as a standalone PHP library for use inside any PHP project. PDF brought to you by Chapter 17: Security | 181 generated on June 20, 2012
  • 182. Basic Example: HTTP Authentication The security component can be configured via your application configuration. In fact, most standard security setups are just a matter of using the right configuration. The following configuration tells Symfony to secure any URL matching /admin/* and to ask the user for credentials using basic HTTP authentication (i.e. the old-school username/password box): Listing # app/config/security.yml 17-1 security: firewalls: secured_area: pattern: ^/ anonymous: ~ http_basic: realm: "Secured Demo Area" access_control: - { path: ^/admin, roles: ROLE_ADMIN } providers: in_memory: users: ryan: { password: ryanpass, roles: 'ROLE_USER' } admin: { password: kitten, roles: 'ROLE_ADMIN' } encoders: SymfonyComponentSecurityCoreUserUser: plaintext A standard Symfony distribution separates the security configuration into a separate file (e.g. app/ config/security.yml). If you don't have a separate security file, you can put the configuration directly into your main config file (e.g. app/config/config.yml). The end result of this configuration is a fully-functional security system that looks like the following: • There are two users in the system (ryan and admin); • Users authenticate themselves via the basic HTTP authentication prompt; • Any URL matching /admin/* is secured, and only the admin user can access it; • All URLs not matching /admin/* are accessible by all users (and the user is never prompted to login). Let's look briefly at how security works and how each part of the configuration comes into play. How Security Works: Authentication and Authorization Symfony's security system works by determining who a user is (i.e. authentication) and then checking to see if that user should have access to a specific resource or URL. Firewalls (Authentication) When a user makes a request to a URL that's protected by a firewall, the security system is activated. The job of the firewall is to determine whether or not the user needs to be authenticated, and if he does, to send a response back to the user initiating the authentication process. 1. https://github.com/symfony/Security PDF brought to you by Chapter 17: Security | 182 generated on June 20, 2012
  • 183. A firewall is activated when the URL of an incoming request matches the configured firewall's regular expression pattern config value. In this example, the pattern (^/) will match every incoming request. The fact that the firewall is activated does not mean, however, that the HTTP authentication username and password box is displayed for every URL. For example, any user can access /foo without being prompted to authenticate. This works first because the firewall allows anonymous users via the anonymous configuration parameter. In other words, the firewall doesn't require the user to fully authenticate immediately. And because no special role is needed to access /foo (under the access_control section), the request can be fulfilled without ever asking the user to authenticate. If you remove the anonymous key, the firewall will always make a user fully authenticate immediately. Access Controls (Authorization) If a user requests /admin/foo, however, the process behaves differently. This is because of the access_control configuration section that says that any URL matching the regular expression pattern ^/admin (i.e. /admin or anything matching /admin/*) requires the ROLE_ADMIN role. Roles are the basis for most authorization: a user can access /admin/foo only if it has the ROLE_ADMIN role. PDF brought to you by Chapter 17: Security | 183 generated on June 20, 2012
  • 184. Like before, when the user originally makes the request, the firewall doesn't ask for any identification. However, as soon as the access control layer denies the user access (because the anonymous user doesn't have the ROLE_ADMIN role), the firewall jumps into action and initiates the authentication process. The authentication process depends on the authentication mechanism you're using. For example, if you're using the form login authentication method, the user will be redirected to the login page. If you're using HTTP authentication, the user will be sent an HTTP 401 response so that the user sees the username and password box. The user now has the opportunity to submit its credentials back to the application. If the credentials are valid, the original request can be re-tried. PDF brought to you by Chapter 17: Security | 184 generated on June 20, 2012
  • 185. In this example, the user ryan successfully authenticates with the firewall. But since ryan doesn't have the ROLE_ADMIN role, he's still denied access to /admin/foo. Ultimately, this means that the user will see some sort of message indicating that access has been denied. When Symfony denies the user access, the user sees an error screen and receives a 403 HTTP status code (Forbidden). You can customize the access denied error screen by following the directions in the Error Pages cookbook entry to customize the 403 error page. Finally, if the admin user requests /admin/foo, a similar process takes place, except now, after being authenticated, the access control layer will let the request pass through: PDF brought to you by Chapter 17: Security | 185 generated on June 20, 2012
  • 186. The request flow when a user requests a protected resource is straightforward, but incredibly flexible. As you'll see later, authentication can be handled in any number of ways, including via a form login, X.509 certificate, or by authenticating the user via Twitter. Regardless of the authentication method, the request flow is always the same: 1. A user accesses a protected resource; 2. The application redirects the user to the login form; 3. The user submits its credentials (e.g. username/password); 4. The firewall authenticates the user; 5. The authenticated user re-tries the original request. The exact process actually depends a little bit on which authentication mechanism you're using. For example, when using form login, the user submits its credentials to one URL that processes the form (e.g. /login_check) and then is redirected back to the originally requested URL (e.g. /admin/ foo). But with HTTP authentication, the user submits its credentials directly to the original URL (e.g. /admin/foo) and then the page is returned to the user in that same request (i.e. no redirect). These types of idiosyncrasies shouldn't cause you any problems, but they're good to keep in mind. You'll also learn later how anything can be secured in Symfony2, including specific controllers, objects, or even PHP methods. Using a Traditional Login Form In this section, you'll learn how to create a basic login form that continues to use the hard-coded users that are defined in the security.yml file. PDF brought to you by Chapter 17: Security | 186 generated on June 20, 2012
  • 187. To load users from the database, please read How to load Security Users from the Database (the Entity Provider). By reading that article and this section, you can create a full login form system that loads users from the database. So far, you've seen how to blanket your application beneath a firewall and then protect access to certain areas with roles. By using HTTP Authentication, you can effortlessly tap into the native username/ password box offered by all browsers. However, Symfony supports many authentication mechanisms out of the box. For details on all of them, see the Security Configuration Reference. In this section, you'll enhance this process by allowing the user to authenticate via a traditional HTML login form. First, enable form login under your firewall: # app/config/security.yml Listing 17-2 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: ~ Listing 17-3 Now, when the security system initiates the authentication process, it will redirect the user to the login form (/login by default). Implementing this login form visually is your job. First, create two routes: one that will display the login form (i.e. /login) and one that will handle the login form submission (i.e. /login_check): # app/config/routing.yml Listing 17-4 login: pattern: /login defaults: { _controller: AcmeSecurityBundle:Security:login } login_check: pattern: /login_check You will not need to implement a controller for the /login_check URL as the firewall will automatically catch and process any form submitted to this URL. It's optional, but helpful, to create a route so that you can use it to generate the form submission URL in the login template below. Notice that the name of the login route isn't important. What's important is that the URL of the route (/login) matches the login_path config value, as that's where the security system will redirect users that need to login. Next, create the controller that will display the login form: // src/Acme/SecurityBundle/Controller/SecurityController.php; Listing 17-5 namespace AcmeSecurityBundleController; PDF brought to you by Chapter 17: Security | 187 generated on June 20, 2012
  • 188. use SymfonyBundleFrameworkBundleControllerController; use SymfonyComponentSecurityCoreSecurityContext; 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, )); } } Don't let this controller confuse you. As you'll see in a moment, when the user submits the form, the security system automatically handles the form submission for you. If the user had submitted an invalid username or password, this controller reads the form submission error from the security system so that it can be displayed back to the user. In other words, your job is to display the login form and any login errors that may have occurred, but the security system itself takes care of checking the submitted username and password and authenticating the user. Finally, create the corresponding template: Listing {# src/Acme/SecurityBundle/Resources/views/Security/login.html.twig #} 17-6 {% 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> PDF brought to you by Chapter 17: Security | 188 generated on June 20, 2012
  • 189. The error variable passed into the template is an instance of AuthenticationException2. It may contain more information - or even sensitive information - about the authentication failure, so use it wisely! The form has very few requirements. First, by submitting the form to /login_check (via the login_check route), the security system will intercept the form submission and process the form for you automatically. Second, the security system expects the submitted fields to be called _username and _password (these field names can be configured). And that's it! When you submit the form, the security system will automatically check the user's credentials and either authenticate the user or send the user back to the login form where the error can be displayed. Let's review the whole process: 1. The user tries to access a resource that is protected; 2. The firewall initiates the authentication process by redirecting the user to the login form (/login); 3. The /login page renders login form via the route and controller created in this example; 4. The user submits the login form to /login_check; 5. The security system intercepts the request, checks the user's submitted credentials, authenticates the user if they are correct, and sends the user back to the login form if they are not. By default, if the submitted credentials are correct, the user will be redirected to the original page that was requested (e.g. /admin/foo). If the user originally went straight to the login page, he'll be redirected to the homepage. This can be highly customized, allowing you to, for example, redirect the user to a specific URL. For more details on this and how to customize the form login process in general, see How to customize your Form Login. 2. http://api.symfony.com/2.0/Symfony/Component/Security/Core/Exception/AuthenticationException.html PDF brought to you by Chapter 17: Security | 189 generated on June 20, 2012
  • 190. Avoid Common Pitfalls When setting up your login form, watch out for a few common pitfalls. 1. Create the correct routes First, be sure that you've defined the /login and /login_check routes correctly and that they correspond to the login_path and check_path config values. A misconfiguration here can mean that you're redirected to a 404 page instead of the login page, or that submitting the login form does nothing (you just see the login form over and over again). 2. Be sure the login page isn't secure Also, be sure that the login page does not require any roles to be viewed. For example, the following configuration - which requires the ROLE_ADMIN role for all URLs (including the /login URL), will cause a redirect loop: Listing access_control: 17-7 - { path: ^/, roles: ROLE_ADMIN } Removing the access control on the /login URL fixes the problem: Listing access_control: 17-8 - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/, roles: ROLE_ADMIN } Also, if your firewall does not allow for anonymous users, you'll need to create a special firewall that allows anonymous users for the login page: Listing firewalls: 17-9 login_firewall: pattern: ^/login$ anonymous: ~ secured_area: pattern: ^/ form_login: ~ 3. Be sure ``/login_check`` is behind a firewall Next, make sure that your check_path URL (e.g. /login_check) is behind the firewall you're using for your form login (in this example, the single firewall matches all URLs, including /login_check). If /login_check doesn't match any firewall, you'll receive a Unable to find the controller for path "/login_check" exception. 4. Multiple firewalls don't share security context If you're using multiple firewalls and you authenticate against one firewall, you will not be authenticated against any other firewalls automatically. Different firewalls are like different security systems. That's why, for most applications, having one main firewall is enough. Authorization The first step in security is always authentication: the process of verifying who the user is. With Symfony, authentication can be done in any way - via a form login, basic HTTP Authentication, or even via Facebook. Once the user has been authenticated, authorization begins. Authorization provides a standard and powerful way to decide if a user can access any resource (a URL, a model object, a method call, ...). This works by assigning specific roles to each user, and then requiring different roles for different resources. The process of authorization has two different sides: 1. The user has a specific set of roles; PDF brought to you by Chapter 17: Security | 190 generated on June 20, 2012
  • 191. 2. A resource requires a specific role in order to be accessed. In this section, you'll focus on how to secure different resources (e.g. URLs, method calls, etc) with different roles. Later, you'll learn more about how roles are created and assigned to users. 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. You can define as many URL patterns as you need - each is a regular expression. # app/config/security.yml Listing 17-10 security: # ... access_control: - { path: ^/admin/users, roles: ROLE_SUPER_ADMIN } - { path: ^/admin, roles: ROLE_ADMIN } Prepending the path with ^ ensures that only URLs beginning with the pattern are matched. For example, a path of simply /admin (without the ^) would correctly match /admin/foo but would also match URLs like /foo/admin. For each incoming request, Symfony2 tries to find a matching access control rule (the first one wins). If the user isn't authenticated yet, the authentication process is initiated (i.e. the user is given a chance to login). However, if the user is authenticated but doesn't have the required role, an AccessDeniedException3 exception is thrown, which you can handle and turn into a nice "access denied" error page for the user. See How to customize Error Pages for more information. Since Symfony uses the first access control rule it matches, a URL like /admin/users/new will match the first rule and require only the ROLE_SUPER_ADMIN role. Any URL like /admin/blog will match the second rule and require ROLE_ADMIN. Securing by IP Certain situations may arise when you may need to restrict access to a given route based on IP. This is particularly relevant in the case of Edge Side Includes (ESI), for example, which utilize a route named "_internal". When ESI is used, the _internal route is required by the gateway cache to enable different caching options for subsections within a given page. This route comes with the ^/_internal prefix by default in the standard edition (assuming you've uncommented those lines from the routing file). Here is an example of how you might secure this route from outside access: # app/config/security.yml Listing 17-11 security: # ... access_control: - { path: ^/_internal, roles: IS_AUTHENTICATED_ANONYMOUSLY, ip: 127.0.0.1 } Securing by Channel Much like securing based on IP, requiring the use of SSL is as simple as adding a new access_control entry: 3. http://api.symfony.com/2.0/Symfony/Component/Security/Core/Exception/AccessDeniedException.html PDF brought to you by Chapter 17: Security | 191 generated on June 20, 2012
  • 192. Listing # app/config/security.yml 17-12 security: # ... access_control: - { path: ^/cart/checkout, roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: https } Securing a Controller Protecting your application based on URL patterns is easy, but may not be fine-grained enough in certain cases. When necessary, you can easily force authorization from inside a controller: Listing use SymfonyComponentSecurityCoreExceptionAccessDeniedException; 17-13 // ... 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: Listing use JMSSecurityExtraBundleAnnotationSecure; 17-14 /** * @Secure(roles="ROLE_ADMIN") */ public function helloAction($name) { // ... } For more information, see the JMSSecurityExtraBundle4 documentation. If you're using Symfony's Standard Distribution, this bundle is available by default. If not, you can easily download and install it. Securing other Services In fact, anything in Symfony can be protected using a strategy similar to the one seen in the previous section. For example, suppose you have a service (i.e. a PHP class) whose job is to send emails from one user to another. You can restrict use of this class - no matter where it's being used from - to users that have a specific role. For more information on how you can use the security component to secure different services and methods in your application, see How to secure any Service or Method in your Application. Access Control Lists (ACLs): Securing Individual Database Objects Imagine you are designing a blog system where your users can comment on your posts. Now, you want a user to be able to edit his own comments, but not those of other users. Also, as the admin user, you yourself want to be able to edit all comments. 4. https://github.com/schmittjoh/JMSSecurityExtraBundle PDF brought to you by Chapter 17: Security | 192 generated on June 20, 2012
  • 193. The security component comes with an optional access control list (ACL) system that you can use when you need to control access to individual instances of an object in your system. Without ACL, you can secure your system so that only certain users can edit blog comments in general. But with ACL, you can restrict or allow access on a comment-by-comment basis. For more information, see the cookbook article: Access Control Lists (ACLs). Users In the previous sections, you learned how you can protect different resources by requiring a set of roles for a resource. In this section we'll explore the other side of authorization: users. Where do Users come from? (User Providers) During authentication, the user submits a set of credentials (usually a username and password). The job of the authentication system is to match those credentials against some pool of users. So where does this list of users come from? In Symfony2, users can come from anywhere - a configuration file, a database table, a web service, or anything else you can dream up. Anything that provides one or more users to the authentication system is known as a "user provider". Symfony2 comes standard with the two most common user providers: one that loads users from a configuration file and one that loads users from a database table. Specifying Users in a Configuration File The easiest way to specify your users is directly in a configuration file. In fact, you've seen this already in the example in this chapter. # app/config/security.yml Listing 17-15 security: # ... providers: default_provider: users: ryan: { password: ryanpass, roles: 'ROLE_USER' } admin: { password: kitten, roles: 'ROLE_ADMIN' } This user provider is called the "in-memory" user provider, since the users aren't stored anywhere in a database. The actual user object is provided by Symfony (User5). Any user provider can load users directly from configuration by specifying the users configuration parameter and listing the users beneath it. If your username is completely numeric (e.g. 77) or contains a dash (e.g. user-name), you should use that alternative syntax when specifying users in YAML: users: Listing 17-16 - { name: 77, password: pass, roles: 'ROLE_USER' } - { name: user-name, password: pass, roles: 'ROLE_USER' } 5. http://api.symfony.com/2.0/Symfony/Component/Security/Core/User/User.html PDF brought to you by Chapter 17: Security | 193 generated on June 20, 2012
  • 194. For smaller sites, this method is quick and easy to setup. For more complex systems, you'll want to load your users from the database. Loading Users from the Database If you'd like to load your users via the Doctrine ORM, you can easily do this by creating a User class and configuring the entity provider. With this approach, you'll first create your own User class, which will be stored in the database. Listing // src/Acme/UserBundle/Entity/User.php 17-17 namespace AcmeUserBundleEntity; use SymfonyComponentSecurityCoreUserUserInterface; use DoctrineORMMapping as ORM; /** * @ORMEntity */ class User implements UserInterface { /** * @ORMColumn(type="string", length=255) */ protected $username; // ... } As far as the security system is concerned, the only requirement for your custom user class is that it implements the UserInterface6 interface. This means that your concept of a "user" can be anything, as long as it implements this interface. The user object will be serialized and saved in the session during requests, therefore it is recommended that you implement the Serializable interface7 in your user object. This is especially important if your User class has a parent class with private properties. Next, configure an entity user provider, and point it to your User class: Listing # app/config/security.yml 17-18 security: providers: main: entity: { class: AcmeUserBundleEntityUser, property: username } With the introduction of this new provider, the authentication system will attempt to load a User object from the database by using the username field of that class. This example is just meant to show you the basic idea behind the entity provider. For a full working example, see How to load Security Users from the Database (the Entity Provider). For more information on creating your own custom provider (e.g. if you needed to load users via a web service), see How to create a custom User Provider. 6. http://api.symfony.com/2.0/Symfony/Component/Security/Core/User/UserInterface.html 7. http://php.net/manual/en/class.serializable.php PDF brought to you by Chapter 17: Security | 194 generated on June 20, 2012
  • 195. Encoding the User's Password So far, for simplicity, all the examples have stored the users' passwords in plain text (whether those users are stored in a configuration file or in a database somewhere). Of course, in a real application, you'll want to encode your users' passwords for security reasons. This is easily accomplished by mapping your User class to one of several built-in "encoders". For example, to store your users in memory, but obscure their passwords via sha1, do the following: # app/config/security.yml Listing 17-19 security: # ... providers: in_memory: users: ryan: { password: bb87a29949f3a1ee0559f8a57357487151281386, roles: 'ROLE_USER' } admin: { password: 74913f5cd5f61ec0bcfdb775414c2fb3d161b620, roles: 'ROLE_ADMIN' } encoders: SymfonyComponentSecurityCoreUserUser: algorithm: sha1 iterations: 1 encode_as_base64: false By setting the iterations to 1 and the encode_as_base64 to false, the password is simply run through the sha1 algorithm one time and without any extra encoding. You can now calculate the hashed password either programmatically (e.g. hash('sha1', 'ryanpass')) or via some online tool like functions-online.com8 If you're creating your users dynamically (and storing them in a database), you can use even tougher hashing algorithms and then rely on an actual password encoder object to help you encode passwords. For example, suppose your User object is AcmeUserBundleEntityUser (like in the above example). First, configure the encoder for that user: # app/config/security.yml Listing 17-20 security: # ... encoders: AcmeUserBundleEntityUser: sha512 In this case, you're using the stronger sha512 algorithm. Also, since you've simply specified the algorithm (sha512) as a string, the system will default to hashing your password 5000 times in a row and then encoding it as base64. In other words, the password has been greatly obfuscated so that the hashed password can't be decoded (i.e. you can't determine the password from the hashed password). If you have some sort of registration form for users, you'll need to be able to determine the hashed password so that you can set it on your user. No matter what algorithm you configure for your user object, the hashed password can always be determined in the following way from a controller: $factory = $this->get('security.encoder_factory'); Listing 17-21 $user = new AcmeUserBundleEntityUser(); $encoder = $factory->getEncoder($user); $password = $encoder->encodePassword('ryanpass', $user->getSalt()); $user->setPassword($password); 8. http://www.functions-online.com/sha1.html PDF brought to you by Chapter 17: Security | 195 generated on June 20, 2012
  • 196. Retrieving the User Object After authentication, the User object of the current user can be accessed via the security.context service. From inside a controller, this will look like: Listing public function indexAction() 17-22 { $user = $this->get('security.context')->getToken()->getUser(); } Anonymous users are technically authenticated, meaning that the isAuthenticated() method of an anonymous user object will return true. To check if your user is actually authenticated, check for the IS_AUTHENTICATED_FULLY role. In a Twig Template this object can be accessed via the app.user key, which calls the GlobalVariables::getUser()9 method: Listing <p>Username: {{ app.user.username }}</p> 17-23 Using Multiple User Providers Each authentication mechanism (e.g. HTTP Authentication, form login, etc) uses exactly one user provider, and will use the first declared user provider by default. But what if you want to specify a few users via configuration and the rest of your users in the database? This is possible by creating a new provider that chains the two together: Listing # app/config/security.yml 17-24 security: providers: chain_provider: providers: [in_memory, user_db] in_memory: users: foo: { password: test } user_db: entity: { class: AcmeUserBundleEntityUser, property: username } Now, all authentication mechanisms will use the chain_provider, since it's the first specified. The chain_provider will, in turn, try to load the user from both the in_memory and user_db providers. If you have no reasons to separate your in_memory users from your user_db users, you can accomplish this even more easily by combining the two sources into a single provider: Listing # app/config/security.yml 17-25 security: providers: main_provider: users: foo: { password: test } entity: { class: AcmeUserBundleEntityUser, 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: 9. http://api.symfony.com/2.0/Symfony/Bundle/FrameworkBundle/Templating/GlobalVariables.html#getUser() PDF brought to you by Chapter 17: Security | 196 generated on June 20, 2012
  • 197. # app/config/security.yml Listing 17-26 security: firewalls: secured_area: # ... provider: user_db http_basic: realm: "Secured Demo Area" provider: in_memory form_login: ~ In this example, if a user tries to login via HTTP authentication, the authentication system will use the in_memory user provider. But if the user tries to login via the form login, the user_db provider will be used (since it's the default for the firewall as a whole). For more information about user provider and firewall configuration, see the Security Configuration Reference. Roles The idea of a "role" is key to the authorization process. Each user is assigned a set of roles and then each resource requires one or more roles. If the user has the required roles, access is granted. Otherwise access is denied. Roles are pretty simple, and are basically strings that you can invent and use as needed (though roles are objects internally). For example, if you need to start limiting access to the blog admin section of your website, you could protect that section using a ROLE_BLOG_ADMIN role. This role doesn't need to be defined anywhere - you can just start using it. All roles must begin with the ROLE_ prefix to be managed by Symfony2. If you define your own roles with a dedicated Role class (more advanced), don't use the ROLE_ prefix. Hierarchical Roles Instead of associating many roles to users, you can define role inheritance rules by creating a role hierarchy: # app/config/security.yml Listing 17-27 security: role_hierarchy: ROLE_ADMIN: ROLE_USER ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH] In the above configuration, users with ROLE_ADMIN role will also have the ROLE_USER role. The ROLE_SUPER_ADMIN role has ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH and ROLE_USER (inherited from ROLE_ADMIN). Logging Out Usually, you'll also want your users to be able to log out. Fortunately, the firewall can handle this automatically for you when you activate the logout config parameter: Listing 17-28 PDF brought to you by Chapter 17: Security | 197 generated on June 20, 2012
  • 198. # app/config/security.yml security: firewalls: secured_area: # ... logout: path: /logout target: / # ... Once this is configured under your firewall, sending a user to /logout (or whatever you configure the path to be), will un-authenticate the current user. The user will then be sent to the homepage (the value defined by the target parameter). Both the path and target config parameters default to what's specified here. In other words, unless you need to customize them, you can omit them entirely and shorten your configuration: Listing logout: ~ 17-29 Note that you will not need to implement a controller for the /logout URL as the firewall takes care of everything. You may, however, want to create a route so that you can use it to generate the URL: Listing # app/config/routing.yml 17-30 logout: pattern: /logout Once the user has been logged out, he will be redirected to whatever path is defined by the target parameter above (e.g. the homepage). For more information on configuring the logout, see the Security Configuration Reference. Access Control in Templates If you want to check if the current user has a role inside a template, use the built-in helper function: Listing {% if is_granted('ROLE_ADMIN') %} 17-31 <a href="...">Delete</a> {% endif %} If you use this function and are not at a URL where there is a firewall active, an exception will be thrown. Again, it's almost always a good idea to have a main firewall that covers all URLs (as has been shown in this chapter). Access Control in Controllers If you want to check if the current user has a role in your controller, use the isGranted method of the security context: Listing public function indexAction() 17-32 { // show different content to admin users if ($this->get('security.context')->isGranted('ROLE_ADMIN')) { // Load admin content here } // load other regular content here } PDF brought to you by Chapter 17: Security | 198 generated on June 20, 2012
  • 199. A firewall must be active or an exception will be thrown when the isGranted method is called. See the note above about templates for more details. Impersonating a User 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 Listing 17-33 security: firewalls: main: # ... switch_user: true To switch to another user, just add a query string with the _switch_user parameter and the username as the value to the current URL: http://example.com/somewhere?_switch_user=thomas10 To switch back to the original user, use the special _exit username: http://example.com/somewhere?_switch_user=_exit11 Of course, this feature needs to be made available to a small group of users. By default, access is restricted to users having the ROLE_ALLOWED_TO_SWITCH role. The name of this role can be modified via the role setting. For extra security, you can also change the query parameter name via the parameter setting: # app/config/security.yml Listing 17-34 security: firewalls: main: // ... switch_user: { role: ROLE_ADMIN, parameter: _want_to_be_this_user } Stateless Authentication By default, Symfony2 relies on a cookie (the Session) to persist the security context of the user. But if you use certificates or HTTP authentication for instance, persistence is not needed as credentials are available for each request. In that case, and if you don't need to store anything else between requests, you can activate the stateless authentication (which means that no cookie will be ever created by Symfony2): # app/config/security.yml Listing 17-35 security: firewalls: main: http_basic: ~ stateless: true 10. http://example.com/somewhere?_switch_user=thomas 11. http://example.com/somewhere?_switch_user=_exit PDF brought to you by Chapter 17: Security | 199 generated on June 20, 2012
  • 200. If you use a form login, Symfony2 will create a cookie even if you set stateless to true. Final Words Security can be a deep and complex issue to solve correctly in your application. Fortunately, Symfony's security component follows a well-proven security model based around authentication and authorization. Authentication, which always happens first, is handled by a firewall whose job is to determine the identity of the user through several different methods (e.g. HTTP authentication, login form, etc). In the cookbook, you'll find examples of other methods for handling authentication, including how to implement a "remember me" cookie functionality. Once a user is authenticated, the authorization layer can determine whether or not the user should have access to a specific resource. Most commonly, roles are applied to URLs, classes or methods and if the current user doesn't have that role, access is denied. The authorization layer, however, is much deeper, and follows a system of "voting" so that multiple parties can determine if the current user should have access to a given resource. Find out more about this and other topics in the cookbook. Learn more from the Cookbook • Forcing HTTP/HTTPS • Blacklist users by IP address with a custom voter • Access Control Lists (ACLs) • How to add "Remember Me" Login Functionality PDF brought to you by Chapter 17: Security | 200 generated on June 20, 2012
  • 201. Chapter 18 HTTP Cache The nature of rich web applications means that they're dynamic. No matter how efficient your application, each request will always contain more overhead than serving a static file. And for most Web applications, that's fine. Symfony2 is lightning fast, and unless you're doing some serious heavy-lifting, each request will come back quickly without putting too much stress on your server. But as your site grows, that overhead can become a problem. The processing that's normally performed on every request should be done only once. This is exactly what caching aims to accomplish. Caching on the Shoulders of Giants The most effective way to improve performance of an application is to cache the full output of a page and then bypass the application entirely on each subsequent request. Of course, this isn't always possible for highly dynamic websites, or is it? In this chapter, we'll show you how the Symfony2 cache system works and why we think this is the best possible approach. The Symfony2 cache system is different because it relies on the simplicity and power of the HTTP cache as defined in the HTTP specification. Instead of reinventing a caching methodology, Symfony2 embraces the standard that defines basic communication on the Web. Once you understand the fundamental HTTP validation and expiration caching models, you'll be ready to master the Symfony2 cache system. 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). PDF brought to you by Chapter 18: HTTP Cache | 201 generated on June 20, 2012
  • 202. • 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. Since caching with HTTP isn't unique to Symfony, many articles already exist on the topic. If you're new to HTTP caching, we highly recommend Ryan Tomayko's article Things Caches Do1. Another in-depth resource is Mark Nottingham's Cache Tutorial2. Caching with a Gateway Cache When caching with HTTP, the cache is separated from your application entirely and sits between your application and the client making the request. The job of the cache is to accept requests from the client and pass them back to your application. The cache will also receive responses back from your application and forward them on to the client. The cache is the "middle-man" of the request-response communication between the client and your application. Along the way, the cache will store each response that is deemed "cacheable" (See Introduction to HTTP Caching). If the same resource is requested again, the cache sends the cached response to the client, ignoring your application entirely. This type of cache is known as a HTTP gateway cache and many exist such as Varnish3, Squid in reverse proxy mode4, and the Symfony2 reverse proxy. Types of Caches But a gateway cache isn't the only type of cache. In fact, the HTTP cache headers sent by your application are consumed and interpreted by up to three different types of caches: • Browser caches: Every browser comes with its own local cache that is mainly useful for when you hit "back" or for images and other assets. The browser cache is a private cache as cached resources aren't shared with anyone else. • Proxy caches: A proxy is a shared cache as many people can be behind a single one. It's usually installed by large corporations and ISPs to reduce latency and network traffic. • Gateway caches: Like a proxy, it's also a shared cache but on the server side. Installed by network administrators, it makes websites more scalable, reliable and performant. Gateway caches are sometimes referred to as reverse proxy caches, surrogate caches, or even HTTP accelerators. The significance of private versus shared caches will become more obvious as we talk about caching responses containing content that is specific to exactly one user (e.g. account information). Each response from your application will likely go through one or both of the first two cache types. These caches are outside of your control but follow the HTTP cache directions set in the response. 1. http://tomayko.com/writings/things-caches-do 2. http://www.mnot.net/cache_docs/ 3. http://www.varnish-cache.org/ 4. http://wiki.squid-cache.org/SquidFaq/ReverseProxy PDF brought to you by Chapter 18: HTTP Cache | 202 generated on June 20, 2012
  • 203. Symfony2 Reverse Proxy Symfony2 comes with a reverse proxy (also called a gateway cache) written in PHP. Enable it and cacheable responses from your application will start to be cached right away. Installing it is just as easy. Each new Symfony2 application comes with a pre-configured caching kernel (AppCache) that wraps the default one (AppKernel). The caching Kernel is the reverse proxy. To enable caching, modify the code of a front controller to use the caching kernel: // web/app.php Listing 18-1 require_once __DIR__.'/../app/bootstrap.php.cache'; require_once __DIR__.'/../app/AppKernel.php'; require_once __DIR__.'/../app/AppCache.php'; use SymfonyComponentHttpFoundationRequest; $kernel = new AppKernel('prod', false); $kernel->loadClassCache(); // wrap the default AppKernel with the AppCache one $kernel = new AppCache($kernel); $kernel->handle(Request::createFromGlobals())->send(); The caching kernel will immediately act as a reverse proxy - caching responses from your application and returning them to the client. 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()); Listing 18-2 The AppCache object has a sensible default configuration, but it can be finely tuned via a set of options you can set by overriding the getOptions() method: // app/AppCache.php Listing 18-3 use SymfonyBundleFrameworkBundleHttpCacheHttpCache; class AppCache extends HttpCache { protected function getOptions() { return array( 'debug' => false, 'default_ttl' => 0, 'private_headers' => array('Authorization', 'Cookie'), 'allow_reload' => false, 'allow_revalidate' => false, 'stale_while_revalidate' => 2, 'stale_if_error' => 60, ); } } PDF brought to you by Chapter 18: HTTP Cache | 203 generated on June 20, 2012
  • 204. Unless overridden in getOptions(), the debug option will be set to automatically be the debug value of the wrapped AppKernel. Here is a list of the main options: • default_ttl: The number of seconds that a cache entry should be considered fresh when no explicit freshness information is provided in a response. Explicit Cache-Control or Expires headers override this value (default: 0); • private_headers: Set of request headers that trigger "private" Cache-Control behavior on responses that don't explicitly state whether the response is public or private via a Cache- Control directive. (default: Authorization and Cookie); • allow_reload: Specifies whether the client can force a cache reload by including a Cache- Control "no-cache" directive in the request. Set it to true for compliance with RFC 2616 (default: false); • allow_revalidate: Specifies whether the client can force a cache revalidate by including a Cache-Control "max-age=0" directive in the request. Set it to true for compliance with RFC 2616 (default: false); • stale_while_revalidate: Specifies the default number of seconds (the granularity is the second as the Response TTL precision is a second) during which the cache can immediately return a stale response while it revalidates it in the background (default: 2); this setting is overridden by the stale-while-revalidate HTTP Cache-Control extension (see RFC 5861); • stale_if_error: Specifies the default number of seconds (the granularity is the second) during which the cache can serve a stale response when an error is encountered (default: 60). This setting is overridden by the stale-if-error HTTP Cache-Control extension (see RFC 5861). If debug is true, Symfony2 automatically adds a X-Symfony-Cache header to the response containing useful information about cache hits and misses. Changing from one Reverse Proxy to Another The Symfony2 reverse proxy is a great tool to use when developing your website or when you deploy your website to a shared host where you cannot install anything beyond PHP code. But being written in PHP, it cannot be as fast as a proxy written in C. That's why we highly recommend you to use Varnish or Squid on your production servers if possible. The good news is that the switch from one proxy server to another is easy and transparent as no code modification is needed in your application. Start easy with the Symfony2 reverse proxy and upgrade later to Varnish when your traffic increases. For more information on using Varnish with Symfony2, see the How to use Varnish cookbook chapter. The performance of the Symfony2 reverse proxy is independent of the complexity of the application. That's because the application kernel is only booted when the request needs to be forwarded to it. PDF brought to you by Chapter 18: HTTP Cache | 204 generated on June 20, 2012
  • 205. Introduction to HTTP Caching To take advantage of the available cache layers, your application must be able to communicate which responses are cacheable and the rules that govern when/how that cache should become stale. This is done by setting HTTP cache headers on the response. Keep in mind that "HTTP" is nothing more than the language (a simple text language) that web clients (e.g. browsers) and web servers use to communicate with each other. When we talk about HTTP caching, we're talking about the part of that language that allows clients and servers to exchange information related to caching. HTTP specifies four response cache headers that we're concerned with: • Cache-Control • Expires • ETag • Last-Modified The most important and versatile header is the Cache-Control header, which is actually a collection of various cache information. Each of the headers will be explained in full detail in the HTTP Expiration and Validation section. 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 an abstraction around the Cache-Control header to make its creation more manageable: $response = new Response(); Listing 18-4 // 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 PDF brought to you by Chapter 18: HTTP Cache | 205 generated on June 20, 2012
  • 206. 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! To handle this situation, every response may be set to be public or private: • 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. Symfony conservatively defaults each response to be private. To take advantage of shared caches (like the Symfony2 reverse proxy), the response will need to be explicitly set as public. Safe Methods 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). This has two very reasonable consequences: • You should never change the state of your application when responding to a GET or HEAD request. Even if you don't use a gateway cache, the presence of proxy caches mean that any GET or HEAD request may or may not actually hit your server. • Don't expect PUT, POST or DELETE methods to cache. These methods are meant to be used when mutating the state of your application (e.g. deleting a blog post). Caching them would prevent certain requests from hitting and mutating your application. Caching Rules and Defaults HTTP 1.1 allows caching anything by default unless there is an explicit Cache-Control header. In practice, most caches do nothing when requests have a cookie, an authorization header, use a non-safe method (i.e. PUT, POST, DELETE), or when responses have a redirect status code. 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). HTTP Expiration and Validation The HTTP specification defines two caching models: • With the expiration model5, you simply specify how long a response should be considered "fresh" by including a Cache-Control and/or an Expires header. Caches that understand expiration will not make the same request until the cached version reaches its expiration time and becomes "stale". • When pages are really dynamic (i.e. their representation changes often), the validation model6 is often necessary. With this model, the cache stores the response, but asks the server on each request whether or not the cached response is still valid. The application uses a unique 5. http://tools.ietf.org/html/rfc2616#section-13.2 6. http://tools.ietf.org/html/rfc2616#section-13.3 PDF brought to you by Chapter 18: HTTP Cache | 206 generated on June 20, 2012
  • 207. response identifier (the Etag header) and/or a timestamp (the Last-Modified header) to check if the page has changed since being cached. The goal of both models is to never generate the same response twice by relying on a cache to store and return "fresh" responses. Reading the HTTP Specification The HTTP specification defines a simple but powerful language in which clients and servers can communicate. As a web developer, the request-response model of the specification dominates our work. Unfortunately, the actual specification document - RFC 26167 - can be difficult to read. There is an on-going effort (HTTP Bis8) to rewrite the RFC 2616. It does not describe a new version of HTTP, but mostly clarifies the original HTTP specification. The organization is also improved as the specification is split into seven parts; everything related to HTTP caching can be found in two dedicated parts (P4 - Conditional Requests9 and P6 - Caching: Browser and intermediary caches). As a web developer, we strongly urge you to read the specification. Its clarity and power - even more than ten years after its creation - is invaluable. Don't be put-off by the appearance of the spec - its contents are much more beautiful than its cover. Expiration The expiration model is the more efficient and straightforward of the two caching models and should be used whenever possible. When a response is cached with an expiration, the cache will store the response and return it directly without hitting the application until it expires. The expiration model can be accomplished using one of two, nearly identical, HTTP headers: Expires or Cache-Control. Expiration with the Expires Header According to the HTTP specification, "the Expires header field gives the date/time after which the response is considered stale." The Expires header can be set with the setExpires() Response method. It takes a DateTime instance as an argument: $date = new DateTime(); Listing 18-5 $date->modify('+600 seconds'); $response->setExpires($date); The resulting HTTP header will look like this: Expires: Thu, 01 Mar 2011 16:00:00 GMT Listing 18-6 The setExpires() method automatically converts the date to the GMT timezone as required by the specification. Note that in HTTP versions before 1.1 the origin server wasn't required to send the Date header. Consequently the cache (e.g. the browser) might need to rely onto his local clock to evaluate the Expires header making the lifetime calculation vulnerable to clock skew. Another limitation of the Expires 7. http://tools.ietf.org/html/rfc2616 8. http://tools.ietf.org/wg/httpbis/ 9. http://tools.ietf.org/html/draft-ietf-httpbis-p4-conditional-12 PDF brought to you by Chapter 18: HTTP Cache | 207 generated on June 20, 2012
  • 208. header is that the specification states that "HTTP/1.1 servers should not send Expires dates more than one year in the future." Expiration with the Cache-Control Header Because of the Expires header limitations, most of the time, you should use the Cache-Control header instead. Recall that the Cache-Control header is used to specify many different cache directives. For expiration, there are two directives, max-age and s-maxage. The first one is used by all caches, whereas the second one is only taken into account by shared caches: Listing // Sets the number of seconds after which the response 18-7 // should no longer be considered fresh $response->setMaxAge(600); // Same as above but only for shared caches $response->setSharedMaxAge(600); The Cache-Control header would take on the following format (it may have additional directives): Listing Cache-Control: max-age=600, s-maxage=600 18-8 Validation When a resource needs to be updated as soon as a change is made to the underlying data, the expiration model falls short. With the expiration model, the application won't be asked to return the updated response until the cache finally becomes stale. The validation model addresses this issue. Under this model, the cache continues to store responses. The difference is that, for each request, the cache asks the application whether or not the cached response is still valid. If the cache is still valid, your application should return a 304 status code and no content. This tells the cache that it's ok to return the cached response. Under this model, you mainly save bandwidth as the representation is not sent twice to the same client (a 304 response is sent instead). But if you design your application carefully, you might be able to get the bare minimum data needed to send a 304 response and save CPU also (see below for an implementation example). The 304 status code means "Not Modified". It's important because with this status code do not contain the actual content being requested. Instead, the response is simply a light-weight set of directions that tell cache that it should use its stored version. Like with expiration, there are two different HTTP headers that can be used to implement the validation model: ETag and Last-Modified. Validation with 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. Let's walk through a simple implementation that generates the ETag as the md5 of the content: Listing public function indexAction() 18-9 { $response = $this->render('MyBundle:Main:index.html.twig'); PDF brought to you by Chapter 18: HTTP Cache | 208 generated on June 20, 2012
  • 209. $response->setETag(md5($response->getContent())); $response->isNotModified($this->getRequest()); return $response; } The Response::isNotModified() method compares the ETag sent with the Request with the one set on the Response. If the two match, the method automatically sets the Response status code to 304. This algorithm is simple enough and very generic, but you need to create the whole Response before being able to compute the ETag, which is sub-optimal. In other words, it saves on bandwidth, but not CPU cycles. In the Optimizing your Code with Validation section, we'll show how validation can be used more intelligently to determine the validity of a cache without doing so much work. Symfony2 also supports weak ETags by passing true as the second argument to the setETag()10 method. 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." In other words, the application decides whether or not the cached content has been updated based on whether or not it's been updated since the response was cached. For instance, you can use the latest update date for all the objects needed to compute the resource representation as the value for the Last-Modified header value: public function showAction($articleSlug) Listing 18-10 { // ... $articleDate = new DateTime($article->getUpdatedAt()); $authorDate = new DateTime($author->getUpdatedAt()); $date = $authorDate > $articleDate ? $authorDate : $articleDate; $response->setLastModified($date); $response->isNotModified($this->getRequest()); return $response; } The Response::isNotModified() method compares the If-Modified-Since header sent by the request with the Last-Modified header set on the response. If they are equivalent, the Response will be set to a 304 status code. The If-Modified-Since request header equals the Last-Modified header of the last response sent to the client for the particular resource. This is how the client and server communicate with each other and decide whether or not the resource has been updated since it was cached. 10. http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/Response.html#setETag() PDF brought to you by Chapter 18: HTTP Cache | 209 generated on June 20, 2012
  • 210. Optimizing your Code with Validation 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: Listing public function showAction($articleSlug) 18-11 { // 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()); // 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 ); } } When the Response is not modified, the isNotModified() automatically sets the response status code to 304, removes the content, and removes some headers that must not be present for 304 responses (see setNotModified()11). 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. In this case, we need the cache to store both a compressed and uncompressed version of the response for the particular URI and return them based on the request's Accept-Encoding value. This is done by using the Vary response header, which is a comma-separated list of different headers whose values trigger a different representation of the requested resource: Listing Vary: Accept-Encoding, User-Agent 18-12 11. http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/Response.html#setNotModified() PDF brought to you by Chapter 18: HTTP Cache | 210 generated on June 20, 2012
  • 211. This particular Vary header would cache different versions of each resource based on the URI and the value of the Accept-Encoding and User-Agent request header. The Response object offers a clean interface for managing the Vary header: // set one vary header Listing 18-13 $response->setVary('Accept-Encoding'); // set multiple vary headers $response->setVary(array('Accept-Encoding', 'User-Agent')); The setVary() method takes a header name or an array of header names for which the response varies. Expiration and Validation You can of course use both validation and expiration within the same Response. As expiration wins over validation, you can easily benefit from the best of both worlds. In other words, by using both expiration and validation, you can instruct the cache to serve the cached content, while checking back at some interval (the expiration) to verify that the content is still valid. More Response Methods The Response class provides many more methods related to the cache. Here are the most useful ones: // Marks the Response stale Listing 18-14 $response->expire(); // Force the response to return a proper 304 response with no content $response->setNotModified(); Additionally, most cache-related HTTP headers can be set via the single setCache() method: // Set cache settings in one call Listing 18-15 $response->setCache(array( 'etag' => $etag, 'last_modified' => $date, 'max_age' => 10, 's_maxage' => 10, 'public' => true, // 'private' => true, )); 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 ESI12, 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. The ESI specification describes tags you can embed in your pages to communicate with the gateway cache. Only one tag is implemented in Symfony2, include, as this is the only useful one outside of Akamaï context: 12. http://www.w3.org/TR/esi-lang PDF brought to you by Chapter 18: HTTP Cache | 211 generated on June 20, 2012
  • 212. Listing <html> 18-16 <body> Some content <!-- Embed the content of another page here --> <esi:include src="http://..." /> More content </body> </html> Notice from the example that each ESI tag has a fully-qualified URL. An ESI tag represents a page fragment that can be fetched via the given URL. When a request is handled, the gateway cache fetches the entire page from its cache or requests it from the backend application. If the response contains one or more ESI tags, these are processed in the same way. In other words, the gateway cache either retrieves the included page fragment from its cache or requests the page fragment from the backend application again. When all the ESI tags have been resolved, the gateway cache merges each into the main page and sends the final content to the client. All of this happens transparently at the gateway cache level (i.e. outside of your application). As you'll see, if you choose to take advantage of ESI tags, Symfony2 makes the process of including them almost effortless. Using ESI in Symfony2 First, to use ESI, be sure to enable it in your application configuration: Listing # app/config/config.yml 18-17 framework: # ... esi: { enabled: true } Now, suppose we have a page that is relatively static, except for a news ticker at the bottom of the content. With ESI, we can cache the news ticker independent of the rest of the page. Listing public function indexAction() 18-18 { $response = $this->render('MyBundle:MyController:index.html.twig'); $response->setSharedMaxAge(600); return $response; } In this example, we've given the full-page cache a lifetime of ten minutes. Next, let's include the news ticker in the template by embedding an action. This is done via the render helper (See Embedding Controllers for more details). As the embedded content comes from another page (or controller for that matter), Symfony2 uses the standard render helper to configure ESI tags: Listing {% render '...:news' with {}, {'standalone': true} %} 18-19 By setting standalone to true, you tell Symfony2 that the action should be rendered as an ESI tag. You might be wondering why you would want to use a helper instead of just writing the ESI tag yourself. That's because using a helper makes your application work even if there is no gateway cache installed. Let's see how it works. PDF brought to you by Chapter 18: HTTP Cache | 212 generated on June 20, 2012
  • 213. When standalone is false (the default), Symfony2 merges the included page content within the main one before sending the response to the client. But when standalone is true, and if Symfony2 detects that it's talking to a gateway cache that supports ESI, it generates an ESI include tag. But if there is no gateway cache or if it does not support ESI, Symfony2 will just merge the included page content within the main one as it would have done were standalone set to false. Symfony2 detects if a gateway cache supports ESI via another Akamaï specification that is supported out of the box by the Symfony2 reverse proxy. The embedded action can now specify its own caching rules, entirely independent of the master page. public function newsAction() Listing 18-20 { // ... $response->setSharedMaxAge(60); } With ESI, the full page cache will be valid for 600 seconds, but the news component cache will only last for 60 seconds. A requirement of ESI, however, is that the embedded action be accessible via a URL so the gateway cache can fetch it independently of the rest of the page. Of course, an action can't be accessed via a URL unless it has a route that points to it. Symfony2 takes care of this via a generic route and controller. For the ESI include tag to work properly, you must define the _internal route: # app/config/routing.yml Listing 18-21 _internal: resource: "@FrameworkBundle/Resources/config/routing/internal.xml" prefix: /_internal Since this route allows all actions to be accessed via a URL, you might want to protect it by using the Symfony2 firewall feature (by allowing access to your reverse proxy's IP range). See the Securing by IP section of the Security Chapter for more information on how to do this. One great advantage of this caching strategy is that you can make your application as dynamic as needed and at the same time, hit the application as little as possible. Once you start using ESI, remember to always use the s-maxage directive instead of max-age. As the browser only ever receives the aggregated resource, it is not aware of the sub-components, and so it will obey the max-age directive and cache the entire page. And you don't want that. The render helper supports two other useful options: • alt: used as the alt attribute on the ESI tag, which allows you to specify an alternative URL to be used if the src cannot be found; • ignore_errors: if set to true, an onerror attribute will be added to the ESI with a value of continue indicating that, in the event of a failure, the gateway cache will simply remove the ESI tag silently. PDF brought to you by Chapter 18: HTTP Cache | 213 generated on June 20, 2012
  • 214. Cache Invalidation "There are only two hard things in Computer Science: cache invalidation and naming things." --Phil Karlton You should never need to invalidate cached data because invalidation is already taken into account natively in the HTTP cache models. If you use validation, you never need to invalidate anything by definition; and if you use expiration and need to invalidate a resource, it means that you set the expires date too far away in the future. Since invalidation is a topic specific to each type of reverse proxy, if you don't worry about invalidation, you can switch between reverse proxies without changing anything in your application code. Actually, all reverse proxies provide ways to purge cached data, but you should avoid them as much as possible. The most standard way is to purge the cache for a given URL by requesting it with the special PURGE HTTP method. Here is how you can configure the Symfony2 reverse proxy to support the PURGE HTTP method: Listing // app/AppCache.php 18-22 use SymfonyBundleFrameworkBundleHttpCacheHttpCache; class AppCache extends HttpCache { protected function invalidate(Request $request) { if ('PURGE' !== $request->getMethod()) { return parent::invalidate($request); } $response = new Response(); if (!$this->getStore()->purge($request->getUri())) { $response->setStatusCode(404, 'Not purged'); } else { $response->setStatusCode(200, 'Purged'); } return $response; } } You must protect the PURGE HTTP method somehow to avoid random people purging your cached data. Summary Symfony2 was designed to follow the proven rules of the road: HTTP. Caching is no exception. Mastering the Symfony2 cache system means becoming familiar with the HTTP cache models and using them effectively. This means that, instead of relying only on Symfony2 documentation and code examples, you have access to a world of knowledge related to HTTP caching and gateway caches such as Varnish. PDF brought to you by Chapter 18: HTTP Cache | 214 generated on June 20, 2012
  • 215. Learn more from the Cookbook • How to use Varnish to speed up my Website PDF brought to you by Chapter 18: HTTP Cache | 215 generated on June 20, 2012
  • 216. Chapter 19 Translations The term "internationalization" (often abbreviated i18n1) refers to the process of abstracting strings and other locale-specific pieces out of your application and into a layer where they can be translated and converted based on the user's locale (i.e. language and country). For text, this means wrapping each with a function capable of translating the text (or "message") into the language of the user: Listing // text will *always* print out in English 19-1 echo 'Hello World'; // text can be translated into the end-user's language or default to English echo $translator->trans('Hello World'); The term locale refers roughly to the user's language and country. It can be any string that your application uses to manage translations and other format differences (e.g. currency format). We recommended the ISO639-12 language code, an underscore (_), then the ISO3166 Alpha-23 country code (e.g. fr_FR for French/France). In this chapter, we'll learn how to prepare an application to support multiple locales and then how to create translations for multiple locales. Overall, the process has several common steps: 1. Enable and configure Symfony's Translation component; 2. Abstract strings (i.e. "messages") by wrapping them in calls to the Translator; 3. Create translation resources for each supported locale that translate each message in the application; 4. Determine, set and manage the user's locale in the session. Configuration 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: 1. http://en.wikipedia.org/wiki/Internationalization_and_localization 2. http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes 3. http://en.wikipedia.org/wiki/ISO_3166-1#Current_codes PDF brought to you by Chapter 19: Translations | 216 generated on June 20, 2012
  • 217. # app/config/config.yml Listing 19-2 framework: translator: { fallback: en } The fallback option defines the fallback locale when a translation does not exist in the user's locale. When a translation does not exist for a locale, the translator first tries to find the translation for the language (fr if the locale is fr_FR for instance). If this also fails, it looks for a translation using the fallback locale. The locale used in translations is the one stored in the user session. Basic Translation Translation of text is done through the translator service (Translator4). To translate a block of text (called a message), use the trans()5 method. Suppose, for example, that we're translating a simple message from inside a controller: public function indexAction() Listing 19-3 { $t = $this->get('translator')->trans('Symfony2 is great'); return new Response($t); } When this code is executed, Symfony2 will attempt to translate the message "Symfony2 is great" based on the locale of the user. For this to work, we need to tell Symfony2 how to translate the message via a "translation resource", which is a collection of message translations for a given locale. This "dictionary" of translations can be created in several different formats, XLIFF being the recommended format: <!-- messages.fr.xliff --> Listing 19-4 <?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> </body> </file> </xliff> Now, if the language of the user's locale is French (e.g. fr_FR or fr_BE), the message will be translated into J'aime Symfony2. The Translation Process To actually translate the message, Symfony2 uses a simple process: • The locale of the current user, which is stored in the session, is determined; • A catalog of translated messages is loaded from translation resources defined for the locale (e.g. fr_FR). Messages from the fallback locale are also loaded and added to the catalog if 4. http://api.symfony.com/2.0/Symfony/Component/Translation/Translator.html 5. http://api.symfony.com/2.0/Symfony/Component/Translation/Translator.html#trans() PDF brought to you by Chapter 19: Translations | 217 generated on June 20, 2012
  • 218. they don't already exist. The end result is a large "dictionary" of translations. See Message Catalogues for more details; • If the message is located in the catalog, the translation is returned. If not, the translator returns the original message. When using the trans() method, Symfony2 looks for the exact string inside the appropriate message catalog and returns it (if it exists). Message Placeholders Sometimes, a message containing a variable needs to be translated: Listing public function indexAction($name) 19-5 { $t = $this->get('translator')->trans('Hello '.$name); return new Response($t); } However, creating a translation for this string is impossible since the translator will try to look up the exact message, including the variable portions (e.g. "Hello Ryan" or "Hello Fabien"). Instead of writing a translation for every possible iteration of the $name variable, we can replace the variable with a "placeholder": Listing public function indexAction($name) 19-6 { $t = $this->get('translator')->trans('Hello %name%', array('%name%' => $name)); new Response($t); } Symfony2 will now look for a translation of the raw message (Hello %name%) and then replace the placeholders with their values. Creating a translation is done just as before: Listing <!-- messages.fr.xliff --> 19-7 <?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>Hello %name%</source> <target>Bonjour %name%</target> </trans-unit> </body> </file> </xliff> The placeholders can take on any form as the full message is reconstructed using the PHP strtr function6. However, the %var% notation is required when translating in Twig templates, and is overall a sensible convention to follow. As we've seen, creating a translation is a two-step process: 1. Abstract the message that needs to be translated by processing it through the Translator. 2. Create a translation for the message in each locale that you choose to support. 6. http://www.php.net/manual/en/function.strtr.php PDF brought to you by Chapter 19: Translations | 218 generated on June 20, 2012
  • 219. The second step is done by creating message catalogues that define the translations for any number of different locales. Message Catalogues When a message is translated, Symfony2 compiles a message catalogue for the user's locale and looks in it for a translation of the message. A message catalogue is like a dictionary of translations for a specific locale. For example, the catalogue for the fr_FR locale might contain the following translation: Symfony2 is Great => J'aime Symfony2 It's the responsibility of the developer (or translator) of an internationalized application to create these translations. Translations are stored on the filesystem and discovered by Symfony, thanks to some conventions. 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: php app/console cache:clear Listing 19-8 Translation Locations and Naming Conventions Symfony2 looks for message files (i.e. translations) in two locations: • For messages found in a bundle, the corresponding message files should live in the Resources/ translations/ directory of the bundle; • To override any bundle translations, place message files in the app/Resources/translations directory. The filename of the translations is also important as Symfony2 uses a convention to determine details about the translations. Each message file must be named according to the following pattern: domain.locale.loader: • domain: An optional way to organize messages into groups (e.g. admin, navigation or the default messages) - see Using Message Domains; • locale: The locale that the translations are for (e.g. en_GB, en, etc); • loader: How Symfony2 should load and parse the file (e.g. xliff, php or yml). The loader can be the name of any registered loader. By default, Symfony provides the following loaders: • xliff: XLIFF file; • php: PHP file; • yml: YAML file. The choice of which loader to use is entirely up to you and is a matter of taste. You can also store translations in a database, or any other storage by providing a custom class implementing the LoaderInterface7 interface. 7. http://api.symfony.com/2.0/Symfony/Component/Translation/Loader/LoaderInterface.html PDF brought to you by Chapter 19: Translations | 219 generated on June 20, 2012
  • 220. Creating Translations The act of creating translation files is an important part of "localization" (often abbreviated L10n8). Translation files consist of a series of id-translation pairs for the given domain and locale. The source is the identifier for the individual translation, and can be the message in the main locale (e.g. "Symfony is great") of your application or a unique identifier (e.g. "symfony2.great" - see the sidebar below): Listing <!-- src/Acme/DemoBundle/Resources/translations/messages.fr.xliff --> 19-9 <?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>symfony2.great</source> <target>J'aime Symfony2</target> </trans-unit> </body> </file> </xliff> Symfony2 will discover these files and use them when translating either "Symfony2 is great" or "symfony2.great" into a French language locale (e.g. fr_FR or fr_BE). 8. http://en.wikipedia.org/wiki/Internationalization_and_localization PDF brought to you by Chapter 19: Translations | 220 generated on June 20, 2012
  • 221. Using Real or Keyword Messages This example illustrates the two different philosophies when creating messages to be translated: $t = $translator->trans('Symfony2 is great'); Listing 19-10 $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). The second method is handy because the message key won't need to be changed in every translation file if we decide that the message should actually read "Symfony2 is really great" in the default locale. The choice of which method to use is entirely up to you, but the "keyword" format is often recommended. Additionally, the php and yaml file formats support nested ids to avoid repeating yourself if you use keywords instead of real text for your ids: symfony2: Listing 19-11 is: great: Symfony2 is great amazing: Symfony2 is amazing has: bundles: Symfony2 has bundles user: login: Login The multiple levels are flattened into single id/translation pairs by adding a dot (.) between every level, therefore the above examples are equivalent to the following: symfony2.is.great: Symfony2 is great Listing 19-12 symfony2.is.amazing: Symfony2 is amazing symfony2.has.bundles: Symfony2 has bundles user.login: Login Using Message Domains As we've seen, message files are organized into the different locales that they translate. The message files can also be organized further into "domains". When creating message files, the domain is the first portion of the filename. The default domain is messages. For example, suppose that, for organization, translations were split into three different domains: messages, admin and navigation. The French translation would have the following message files: • messages.fr.xliff • admin.fr.xliff • navigation.fr.xliff When translating strings that are not in the default domain (messages), you must specify the domain as the third argument of trans(): $this->get('translator')->trans('Symfony2 is great', array(), 'admin'); Listing 19-13 PDF brought to you by Chapter 19: Translations | 221 generated on June 20, 2012
  • 222. Symfony2 will now look for the message in the admin domain of the user's locale. Handling the User's Locale The locale of the current user is stored in the session and is accessible via the session service: Listing $locale = $this->get('session')->getLocale(); 19-14 $this->get('session')->setLocale('en_US'); Fallback and Default Locale If the locale hasn't been set explicitly in the session, the fallback_locale configuration parameter will be used by the Translator. The parameter defaults to en (see Configuration). Alternatively, you can guarantee that a locale is set on the user's session by defining a default_locale for the session service: Listing # app/config/config.yml 19-15 framework: session: { default_locale: en } The Locale and the URL Since the locale of the user is stored in the session, it may be tempting to use the same URL to display a resource in many different languages based on the user's locale. For example, http://www.example.com/ contact could show content in English for one user and French for another user. Unfortunately, this violates a fundamental rule of the Web: that a particular URL returns the same resource regardless of the user. To further muddy the problem, which version of the content would be indexed by search engines? A better policy is to include the locale in the URL. This is fully-supported by the routing system using the special _locale parameter: Listing contact: 19-16 pattern: /{_locale}/contact defaults: { _controller: AcmeDemoBundle:Contact:index, _locale: en } requirements: _locale: en|fr|de When using the special _locale parameter in a route, the matched locale will automatically be set on the user's session. In other words, if a user visits the URI /fr/contact, the locale fr will automatically be set as the locale for the user's session. You can now use the user's locale to create routes to other translated pages in your application. Pluralization Message pluralization is a tough topic as the rules can be quite complex. For instance, here is the mathematic representation of the Russian pluralization rules: Listing (($number % 10 == 1) && ($number % 100 != 11)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 19-17 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2); As you can see, in Russian, you can have three different plural forms, each given an index of 0, 1 or 2. For each form, the plural is different, and so the translation is also different. PDF brought to you by Chapter 19: Translations | 222 generated on June 20, 2012
  • 223. When a translation has different forms due to pluralization, you can provide all the forms as a string separated by a pipe (|): 'There is one apple|There are %count% apples' Listing 19-18 To translate pluralized messages, use the transChoice()9 method: $t = $this->get('translator')->transChoice( Listing 19-19 'There is one apple|There are %count% apples', 10, array('%count%' => 10) ); The second argument (10 in this example), is the number of objects being described and is used to determine which translation to use and also to populate the %count% placeholder. Based on the given number, the translator chooses the right plural form. In English, most words have a singular form when there is exactly one object and a plural form for all other numbers (0, 2, 3...). So, if count is 1, the translator will use the first string (There is one apple) as the translation. Otherwise it will use There are %count% apples. Here is the French translation: 'Il y a %count% pomme|Il y a %count% pommes' Listing 19-20 Even if the string looks similar (it is made of two sub-strings separated by a pipe), the French rules are different: the first form (no plural) is used when count is 0 or 1. So, the translator will automatically use the first string (Il y a %count% pomme) when count is 0 or 1. Each locale has its own set of rules, with some having as many as six different plural forms with complex rules behind which numbers map to which plural form. The rules are quite simple for English and French, but for Russian, you'd may want a hint to know which rule matches which string. To help translators, you can optionally "tag" each string: 'one: There is one apple|some: There are %count% apples' Listing 19-21 'none_or_one: Il y a %count% pomme|some: Il y a %count% pommes' The tags are really only hints for translators and don't affect the logic used to determine which plural form to use. The tags can be any descriptive string that ends with a colon (:). The tags also do not need to be the same in the original message as in the translated one. Explicit Interval Pluralization The easiest way to pluralize a message is to let Symfony2 use internal logic to choose which string to use based on a given number. Sometimes, you'll need more control or want a different translation for specific cases (for 0, or when the count is negative, for example). For such cases, you can use explicit math intervals: '{0} There are no apples|{1} There is one apple|]1,19] There are %count% apples|[20,Inf] There Listing 19-22 are many apples' The intervals follow the ISO 31-1110 notation. The above string specifies four different intervals: exactly 0, exactly 1, 2-19, and 20 and higher. You can also mix explicit math rules and standard rules. In this case, if the count is not matched by a specific interval, the standard rules take effect after removing the explicit rules: 9. http://api.symfony.com/2.0/Symfony/Component/Translation/Translator.html#transChoice() 10. http://en.wikipedia.org/wiki/Interval_%28mathematics%29#The_ISO_notation PDF brought to you by Chapter 19: Translations | 223 generated on June 20, 2012
  • 224. Listing '{0} There are no apples|[20,Inf] There are many apples|There is one apple|a_few: There are 19-23 %count% apples' For example, for 1 apple, the standard rule There is one apple will be used. For 2-19 apples, the second standard rule There are %count% apples will be selected. An Interval11 can represent a finite set of numbers: Listing {1,2,3,4} 19-24 Or numbers between two other numbers: Listing [1, +Inf[ 19-25 ]-1,2[ The left delimiter can be [ (inclusive) or ] (exclusive). The right delimiter can be [ (exclusive) or ] (inclusive). Beside numbers, you can use -Inf and +Inf for the infinite. Translations in Templates Most of the time, translation occurs in templates. Symfony2 provides native support for both Twig and PHP templates. Twig Templates Symfony2 provides specialized Twig tags (trans and transchoice) to help with message translation of static blocks of text: Listing {% trans %}Hello %name%{% endtrans %} 19-26 {% transchoice count %} {0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples {% endtranschoice %} The transchoice tag automatically gets the %count% variable from the current context and passes it to the translator. This mechanism only works when you use a placeholder following the %var% pattern. If you need to use the percent character (%) in a string, escape it by doubling it: {% trans %}Percent: %percent%%%{% endtrans %} You can also specify the message domain and pass some additional variables: Listing {% trans with {'%name%': 'Fabien'} from "app" %}Hello %name%{% endtrans %} 19-27 {% trans with {'%name%': 'Fabien'} from "app" into "fr" %}Hello %name%{% endtrans %} {% transchoice count with {'%name%': 'Fabien'} from "app" %} {0} There is no apples|{1} There is one apple|]1,Inf] There are %count% apples {% endtranschoice %} The trans and transchoice filters can be used to translate variable texts and complex expressions: Listing {{ message|trans }} 19-28 11. http://api.symfony.com/2.0/Symfony/Component/Translation/Interval.html PDF brought to you by Chapter 19: Translations | 224 generated on June 20, 2012
  • 225. {{ message|transchoice(5) }} {{ message|trans({'%name%': 'Fabien'}, "app") }} {{ message|transchoice(5, {'%name%': 'Fabien'}, 'app') }} Using the translation tags or filters have the same effect, but with one subtle difference: automatic output escaping is only applied to variables translated using a filter. In other words, if you need to be sure that your translated variable is not output escaped, you must apply the raw filter after the translation filter: {# text translated between tags is never escaped #} Listing 19-29 {% trans %} <h3>foo</h3> {% endtrans %} {% set message = '<h3>foo</h3>' %} {# a variable translated via a filter is escaped by default #} {{ message|trans|raw }} {# but static strings are never escaped #} {{ '<h3>foo</h3>'|trans }} PHP Templates The translator service is accessible in PHP templates through the translator helper: <?php echo $view['translator']->trans('Symfony2 is great') ?> Listing 19-30 <?php echo $view['translator']->transChoice( '{0} There is no apples|{1} There is one apple|]1,Inf[ There are %count% apples', 10, array('%count%' => 10) ) ?> Forcing the Translator Locale When translating a message, Symfony2 uses the locale from the user's session or the fallback locale if necessary. You can also manually specify the locale to use for translation: $this->get('translator')->trans( Listing 19-31 'Symfony2 is great', array(), 'messages', 'fr_FR', ); $this->get('translator')->transChoice( '{0} There are no apples|{1} There is one apple|]1,Inf[ There are %count% apples', 10, array('%count%' => 10), 'messages', 'fr_FR', ); PDF brought to you by Chapter 19: Translations | 225 generated on June 20, 2012
  • 226. Translating Database Content The translation of database content should be handled by Doctrine through the Translatable Extension12. For more information, see the documentation for that library. Translating Constraint Messages The best way to understand constraint translation is to see it in action. To start, suppose you've created a plain-old-PHP object that you need to use somewhere in your application: Listing // src/Acme/BlogBundle/Entity/Author.php 19-32 namespace AcmeBlogBundleEntity; class Author { public $name; } Add constraints though any of the supported methods. Set the message option to the translation source text. For example, to guarantee that the $name property is not empty, add the following: Listing # src/Acme/BlogBundle/Resources/config/validation.yml 19-33 AcmeBlogBundleEntityAuthor: properties: name: - NotBlank: { message: "author.name.not_blank" } Create a translation file under the validators catalog for the constraint messages, typically in the Resources/translations/ directory of the bundle. See Message Catalogues for more details. Listing <!-- validators.fr.xliff --> 19-34 <?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> Summary With the Symfony2 Translation component, creating an internationalized application no longer needs to be a painful process and boils down to just a few basic steps: • Abstract messages in your application by wrapping each in either the trans()13 or transChoice()14 methods; 12. https://github.com/l3pp4rd/DoctrineExtensions 13. http://api.symfony.com/2.0/Symfony/Component/Translation/Translator.html#trans() 14. http://api.symfony.com/2.0/Symfony/Component/Translation/Translator.html#transChoice() PDF brought to you by Chapter 19: Translations | 226 generated on June 20, 2012
  • 227. • Translate each message into multiple locales by creating translation message files. Symfony2 discovers and processes each file because its name follows a specific convention; • Manage the user's locale, which is stored in the session. PDF brought to you by Chapter 19: Translations | 227 generated on June 20, 2012
  • 228. Chapter 20 Service Container A modern PHP application is full of objects. One object may facilitate the delivery of email messages while another may allow you to persist information into a database. In your application, you may create an object that manages your product inventory, or another object that processes data from a third-party API. The point is that a modern application does many things and is organized into many objects that handle each task. In this chapter, we'll talk about a special PHP object in Symfony2 that helps you instantiate, organize and retrieve the many objects of your application. This object, called a service container, will allow you to standardize and centralize the way objects are constructed in your application. The container makes your life easier, is super fast, and emphasizes an architecture that promotes reusable and decoupled code. And since all core Symfony2 classes use the container, you'll learn how to extend, configure and use any object in Symfony2. In large part, the service container is the biggest contributor to the speed and extensibility of Symfony2. Finally, configuring and using the service container is easy. By the end of this chapter, you'll be comfortable creating your own objects via the container and customizing objects from any third-party bundle. You'll begin writing code that is more reusable, testable and decoupled, simply because the service container makes writing good code so easy. What is a Service? Put simply, a Service is any PHP object that performs some sort of "global" task. It's a purposefully-generic name used in computer science to describe an object that's created for a specific purpose (e.g. delivering emails). Each service is used throughout your application whenever you need the specific functionality it provides. You don't have to do anything special to make a service: simply write a PHP class with some code that accomplishes a specific task. Congratulations, you've just created a service! As a rule, a PHP object is a service if it is used globally in your application. A single Mailer service is used globally to send email messages whereas the many Message objects that it delivers are not services. Similarly, a Product object is not a service, but an object that persists Product objects to a database is a service. PDF brought to you by Chapter 20: Service Container | 228 generated on June 20, 2012
  • 229. So what's the big deal then? The advantage of thinking about "services" is that you begin to think about separating each piece of functionality in your application into a series of services. Since each service does just one job, you can easily access each service and use its functionality wherever you need it. Each service can also be more easily tested and configured since it's separated from the other functionality in your application. This idea is called service-oriented architecture1 and is not unique to Symfony2 or even PHP. Structuring your application around a set of independent service classes is a well-known and trusted object-oriented best-practice. These skills are key to being a good developer in almost any language. What is a Service Container? A Service Container (or dependency injection container) is simply a PHP object that manages the instantiation of services (i.e. objects). For example, suppose we have a simple PHP class that delivers email messages. Without a service container, we must manually create the object whenever we need it: use AcmeHelloBundleMailer; Listing 20-1 $mailer = new Mailer('sendmail'); $mailer->send('ryan@foobar.net', ... ); This is easy enough. The imaginary Mailer class allows us to configure the method used to deliver the email messages (e.g. sendmail, smtp, etc). But what if we wanted to use the mailer service somewhere else? We certainly don't want to repeat the mailer configuration every time we need to use the Mailer object. What if we needed to change the transport from sendmail to smtp everywhere in the application? We'd need to hunt down every place we create a Mailer service and change it. Creating/Configuring Services in the Container A better answer is to let the service container create the Mailer object for you. In order for this to work, we must teach the container how to create the Mailer service. This is done via configuration, which can be specified in YAML, XML or PHP: # app/config/config.yml Listing 20-2 services: my_mailer: class: AcmeHelloBundleMailer arguments: [sendmail] When Symfony2 initializes, it builds the service container using the application configuration (app/config/config.yml by default). The exact file that's loaded is dictated by the AppKernel::registerContainerConfiguration() method, which loads an environment-specific configuration file (e.g. config_dev.yml for the dev environment or config_prod.yml for prod). An instance of the AcmeHelloBundleMailer object is now available via the service container. The container is available in any traditional Symfony2 controller where you can access the services of the container via the get() shortcut method: class HelloController extends Controller Listing 20-3 { // ... public function sendEmailAction() 1. http://wikipedia.org/wiki/Service-oriented_architecture PDF brought to you by Chapter 20: Service Container | 229 generated on June 20, 2012
  • 230. { // ... $mailer = $this->get('my_mailer'); $mailer->send('ryan@foobar.net', ... ); } } When we ask for the my_mailer service from the container, the container constructs the object and returns it. This is another major advantage of using the service container. Namely, a service is never constructed until it's needed. If you define a service and never use it on a request, the service is never created. This saves memory and increases the speed of your application. This also means that there's very little or no performance hit for defining lots of services. Services that are never used are never constructed. As an added bonus, the Mailer service is only created once and the same instance is returned each time you ask for the service. This is almost always the behavior you'll need (it's more flexible and powerful), but we'll learn later how you can configure a service that has multiple instances. Service Parameters The creation of new services (i.e. objects) via the container is pretty straightforward. Parameters make defining services more organized and flexible: Listing # app/config/config.yml 20-4 parameters: my_mailer.class: AcmeHelloBundleMailer my_mailer.transport: sendmail services: my_mailer: class: %my_mailer.class% arguments: [%my_mailer.transport%] The end result is exactly the same as before - the difference is only in how we defined the service. By surrounding the my_mailer.class and my_mailer.transport strings in percent (%) signs, the container knows to look for parameters with those names. When the container is built, it looks up the value of each parameter and uses it in the service definition. The percent sign inside a parameter or argument, as part of the string, must be escaped with another percent sign: Listing <argument type="string">http://symfony.com/?foo=%%s&bar=%%d</argument> 20-5 The purpose of parameters is to feed information into services. Of course there was nothing wrong with defining the service without using any parameters. Parameters, however, have several advantages: • separation and organization of all service "options" under a single parameters key; • parameter values can be used in multiple service definitions; • when creating a service in a bundle (we'll show this shortly), using parameters allows the service to be easily customized in your application. The choice of using or not using parameters is up to you. High-quality third-party bundles will always use parameters as they make the service stored in the container more configurable. For the services in your application, however, you may not need the flexibility of parameters. PDF brought to you by Chapter 20: Service Container | 230 generated on June 20, 2012
  • 231. Array Parameters Parameters do not need to be flat strings, they can also be arrays. For the XML format, you need to use the type="collection" attribute for all parameters that are arrays. # app/config/config.yml Listing 20-6 parameters: my_mailer.gateways: - mail1 - mail2 - mail3 my_multilang.language_fallback: en: - en - fr fr: - fr - en Importing other Container Configuration Resources In this section, we'll refer to service configuration files as resources. This is to highlight that fact that, while most configuration resources will be files (e.g. YAML, XML, PHP), Symfony2 is so flexible that configuration could be loaded from anywhere (e.g. a database or even via an external web service). The service container is built using a single configuration resource (app/config/config.yml by default). All other service configuration (including the core Symfony2 and third-party bundle configuration) must be imported from inside this file in one way or another. This gives you absolute flexibility over the services in your application. External service configuration can be imported in two different ways. First, we'll talk about the method that you'll use most commonly in your application: the imports directive. In the following section, we'll introduce the second method, which is the flexible and preferred method for importing service configuration from third-party bundles. Importing Configuration with imports So far, we've placed our my_mailer service container definition directly in the application configuration file (e.g. app/config/config.yml). Of course, since the Mailer class itself lives inside the AcmeHelloBundle, it makes more sense to put the my_mailer container definition inside the bundle as well. First, move the my_mailer container definition into a new container resource file inside AcmeHelloBundle. If the Resources or Resources/config directories don't exist, create them. # src/Acme/HelloBundle/Resources/config/services.yml Listing 20-7 parameters: my_mailer.class: AcmeHelloBundleMailer my_mailer.transport: sendmail services: my_mailer: class: %my_mailer.class% arguments: [%my_mailer.transport%] PDF brought to you by Chapter 20: Service Container | 231 generated on June 20, 2012
  • 232. The definition itself hasn't changed, only its location. Of course the service container doesn't know about the new resource file. Fortunately, we can easily import the resource file using the imports key in the application configuration. Listing # app/config/config.yml 20-8 imports: - { resource: @AcmeHelloBundle/Resources/config/services.yml } The imports directive allows your application to include service container configuration resources from any other location (most commonly from bundles). The resource location, for files, is the absolute path to the resource file. The special @AcmeHello syntax resolves the directory path of the AcmeHelloBundle bundle. This helps you specify the path to the resource without worrying later if you move the AcmeHelloBundle to a different directory. Importing Configuration via Container Extensions When developing in Symfony2, you'll most commonly use the imports directive to import container configuration from the bundles you've created specifically for your application. Third-party bundle container configuration, including Symfony2 core services, are usually loaded using another method that's more flexible and easy to configure in your application. Here's how it works. Internally, each bundle defines its services very much like we've seen so far. Namely, a bundle uses one or more configuration resource files (usually XML) to specify the parameters and services for that bundle. However, instead of importing each of these resources directly from your application configuration using the imports directive, you can simply invoke a service container extension inside the bundle that does the work for you. A service container extension is a PHP class created by the bundle author to accomplish two things: • import all service container resources needed to configure the services for the bundle; • provide semantic, straightforward configuration so that the bundle can be configured without interacting with the flat parameters of the bundle's service container configuration. In other words, a service container extension configures the services for a bundle on your behalf. And as we'll see in a moment, the extension provides a sensible, high-level interface for configuring the bundle. Take the FrameworkBundle - the core Symfony2 framework bundle - as an example. The presence of the following code in your application configuration invokes the service container extension inside the FrameworkBundle: Listing # app/config/config.yml 20-9 framework: secret: xxxxxxxxxx charset: UTF-8 form: true csrf_protection: true router: { resource: "%kernel.root_dir%/config/routing.yml" } # ... When the configuration is parsed, the container looks for an extension that can handle the framework configuration directive. The extension in question, which lives in the FrameworkBundle, is invoked and the service configuration for the FrameworkBundle is loaded. If you remove the framework key from your application configuration file entirely, the core Symfony2 services won't be loaded. The point is that you're in control: the Symfony2 framework doesn't contain any magic or perform any actions that you don't have control over. Of course you can do much more than simply "activate" the service container extension of the FrameworkBundle. Each extension allows you to easily customize the bundle, without worrying about how the internal services are defined. PDF brought to you by Chapter 20: Service Container | 232 generated on June 20, 2012
  • 233. In this case, the extension allows you to customize the charset, error_handler, csrf_protection, router configuration and much more. Internally, the FrameworkBundle uses the options specified here to define and configure the services specific to it. The bundle takes care of creating all the necessary parameters and services for the service container, while still allowing much of the configuration to be easily customized. As an added bonus, most service container extensions are also smart enough to perform validation - notifying you of options that are missing or the wrong data type. When installing or configuring a bundle, see the bundle's documentation for how the services for the bundle should be installed and configured. The options available for the core bundles can be found inside the Reference Guide. Natively, the service container only recognizes the parameters, services, and imports directives. Any other directives are handled by a service container extension. If you want to expose user friendly configuration in your own bundles, read the "How to expose a Semantic Configuration for a Bundle" cookbook recipe. Referencing (Injecting) Services So far, our original my_mailer service is simple: it takes just one argument in its constructor, which is easily configurable. As you'll see, the real power of the container is realized when you need to create a service that depends on one or more other services in the container. Let's start with an example. Suppose we have a new service, NewsletterManager, that helps to manage the preparation and delivery of an email message to a collection of addresses. Of course the my_mailer service is already really good at delivering email messages, so we'll use it inside NewsletterManager to handle the actual delivery of the messages. This pretend class might look something like this: namespace AcmeHelloBundleNewsletter; Listing 20-10 use AcmeHelloBundleMailer; class NewsletterManager { protected $mailer; public function __construct(Mailer $mailer) { $this->mailer = $mailer; } // ... } Without using the service container, we can create a new NewsletterManager fairly easily from inside a controller: public function sendNewsletterAction() Listing 20-11 { $mailer = $this->get('my_mailer'); $newsletter = new AcmeHelloBundleNewsletterNewsletterManager($mailer); // ... } This approach is fine, but what if we decide later that the NewsletterManager class needs a second or third constructor argument? What if we decide to refactor our code and rename the class? In both cases, PDF brought to you by Chapter 20: Service Container | 233 generated on June 20, 2012
  • 234. you'd need to find every place where the NewsletterManager is instantiated and modify it. Of course, the service container gives us a much more appealing option: Listing # src/Acme/HelloBundle/Resources/config/services.yml 20-12 parameters: # ... newsletter_manager.class: AcmeHelloBundleNewsletterNewsletterManager services: my_mailer: # ... newsletter_manager: class: %newsletter_manager.class% arguments: [@my_mailer] 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. Using references is a very powerful tool that allows you to create independent service classes with well- defined dependencies. In this example, the newsletter_manager service needs the my_mailer service in order to function. When you define this dependency in the service container, the container takes care of all the work of instantiating the objects. Optional Dependencies: Setter Injection Injecting dependencies into the constructor in this manner is an excellent way of ensuring that the dependency is available to use. If you have optional dependencies for a class, then "setter injection" may be a better option. This means injecting the dependency using a method call rather than through the constructor. The class would look like this: Listing namespace AcmeHelloBundleNewsletter; 20-13 use AcmeHelloBundleMailer; class NewsletterManager { protected $mailer; public function setMailer(Mailer $mailer) { $this->mailer = $mailer; } // ... } Injecting the dependency by the setter method just needs a change of syntax: Listing # src/Acme/HelloBundle/Resources/config/services.yml 20-14 parameters: # ... newsletter_manager.class: AcmeHelloBundleNewsletterNewsletterManager services: my_mailer: # ... newsletter_manager: class: %newsletter_manager.class% PDF brought to you by Chapter 20: Service Container | 234 generated on June 20, 2012
  • 235. calls: - [ setMailer, [ @my_mailer ] ] The approaches presented in this section are called "constructor injection" and "setter injection". The Symfony2 service container also supports "property injection". Making References Optional Sometimes, one of your services may have an optional dependency, meaning that the dependency is not required for your service to work properly. In the example above, the my_mailer service must exist, otherwise an exception will be thrown. By modifying the newsletter_manager service definition, you can make this reference optional. The container will then inject it if it exists and do nothing if it doesn't: # src/Acme/HelloBundle/Resources/config/services.yml Listing 20-15 parameters: # ... services: newsletter_manager: class: %newsletter_manager.class% arguments: [@?my_mailer] In YAML, the special @? syntax tells the service container that the dependency is optional. Of course, the NewsletterManager must also be written to allow for an optional dependency: public function __construct(Mailer $mailer = null) Listing 20-16 { // ... } Core Symfony and Third-Party Bundle Services Since Symfony2 and all third-party bundles configure and retrieve their services via the container, you can easily access them or even use them in your own services. To keep things simple, Symfony2 by default does not require that controllers be defined as services. Furthermore Symfony2 injects the entire service container into your controller. For example, to handle the storage of information on a user's session, Symfony2 provides a session service, which you can access inside a standard controller as follows: public function indexAction($bar) Listing 20-17 { $session = $this->get('session'); $session->set('foo', $bar); // ... } In Symfony2, you'll constantly use services provided by the Symfony core or other third-party bundles to perform tasks such as rendering templates (templating), sending emails (mailer), or accessing information on the request (request). We can take this a step further by using these services inside services that you've created for your application. Let's modify the NewsletterManager to use the real Symfony2 mailer service (instead of the PDF brought to you by Chapter 20: Service Container | 235 generated on June 20, 2012
  • 236. pretend my_mailer). Let's also pass the templating engine service to the NewsletterManager so that it can generate the email content via a template: Listing namespace AcmeHelloBundleNewsletter; 20-18 use SymfonyComponentTemplatingEngineInterface; class NewsletterManager { protected $mailer; protected $templating; public function __construct(Swift_Mailer $mailer, EngineInterface $templating) { $this->mailer = $mailer; $this->templating = $templating; } // ... } Configuring the service container is easy: Listing services: 20-19 newsletter_manager: class: %newsletter_manager.class% arguments: [@mailer, @templating] The newsletter_manager service now has access to the core mailer and templating services. This is a common way to create services specific to your application that leverage the power of different services within the framework. Be sure that swiftmailer entry appears in your application configuration. As we mentioned in Importing Configuration via Container Extensions, the swiftmailer key invokes the service extension from the SwiftmailerBundle, which registers the mailer service. Advanced Container Configuration As we've seen, defining services inside the container is easy, generally involving a service configuration key and a few parameters. However, the container has several other tools available that help to tag services for special functionality, create more complex services, and perform operations after the container is built. Marking Services as public / private When defining services, you'll usually want to be able to access these definitions within your application code. These services are called public. For example, the doctrine service registered with the container when using the DoctrineBundle is a public service as you can access it via: Listing $doctrine = $container->get('doctrine'); 20-20 However, there are use-cases when you don't want a service to be public. This is common when a service is only defined because it could be used as an argument for another service. PDF brought to you by Chapter 20: Service Container | 236 generated on June 20, 2012
  • 237. If you use a private service as an argument to more than one other service, this will result in two different instances being used as the instantiation of the private service is done inline (e.g. new PrivateFooBar()). Simply said: A service will be private when you do not want to access it directly from your code. Here is an example: services: Listing 20-21 foo: class: AcmeHelloBundleFoo public: false Now that the service is private, you cannot call: $container->get('foo'); Listing 20-22 However, if a service has been marked as private, you can still alias it (see below) to access this service (via the alias). Services are by default public. Aliasing When using core or third party bundles within your application, you may want to use shortcuts to access some services. You can do so by aliasing them and, furthermore, you can even alias non-public services. services: Listing 20-23 foo: class: AcmeHelloBundleFoo bar: alias: foo This means that when using the container directly, you can access the foo service by asking for the bar service like this: $container->get('bar'); // Would return the foo service Listing 20-24 Requiring files There might be use cases when you need to include another file just before the service itself gets loaded. To do so, you can use the file directive. services: Listing 20-25 foo: class: AcmeHelloBundleFooBar file: %kernel.root_dir%/src/path/to/file/foo.php Notice that symfony will internally call the PHP function require_once which means that your file will be included only once per request. PDF brought to you by Chapter 20: Service Container | 237 generated on June 20, 2012
  • 238. Tags (tags) In the same way that a blog post on the Web might be tagged with things such as "Symfony" or "PHP", services configured in your container can also be tagged. In the service container, a tag implies that the service is meant to be used for a specific purpose. Take the following example: Listing services: 20-26 foo.twig.extension: class: AcmeHelloBundleExtensionFooExtension tags: - { name: twig.extension } The twig.extension tag is a special tag that the TwigBundle uses during configuration. By giving the service this twig.extension tag, the bundle knows that the foo.twig.extension service should be registered as a Twig extension with Twig. In other words, Twig finds all services tagged with twig.extension and automatically registers them as extensions. Tags, then, are a way to tell Symfony2 or other third-party bundles that your service should be registered or used in some special way by the bundle. The following is a list of tags available with the core Symfony2 bundles. Each of these has a different effect on your service and many tags require additional arguments (beyond just the name parameter). • assetic.filter • assetic.templating.php • data_collector • form.field_factory.guesser • kernel.cache_warmer • kernel.event_listener • monolog.logger • routing.loader • security.listener.factory • security.voter • templating.helper • twig.extension • translation.loader • validator.constraint_validator Learn more • Using a Factory to Create Services • Managing Common Dependencies with Parent Services • How to define Controllers as Services PDF brought to you by Chapter 20: Service Container | 238 generated on June 20, 2012
  • 239. Chapter 21 Performance Symfony2 is fast, right out of the box. Of course, if you really need speed, there are many ways that you can make Symfony even faster. In this chapter, you'll explore many of the most common and powerful ways to make your Symfony application even faster. Use a Byte Code Cache (e.g. APC) One the best (and easiest) things that you should do to improve your performance is to use a "byte code cache". The idea of a byte code cache is to remove the need to constantly recompile the PHP source code. There are a number of byte code caches1 available, some of which are open source. The most widely used byte code cache is probably APC2 Using a byte code cache really has no downside, and Symfony2 has been architected to perform really well in this type of environment. Further Optimizations Byte code caches usually monitor the source files for changes. This ensures that if the source of a file changes, the byte code is recompiled automatically. This is really convenient, but obviously adds overhead. For this reason, some byte code caches offer an option to disable these checks. Obviously, when disabling these checks, it will be up to the server admin to ensure that the cache is cleared whenever any source files change. Otherwise, the updates you've made won't be seen. For example, to disable these checks in APC, simply add apc.stat=0 to your php.ini configuration. 1. http://en.wikipedia.org/wiki/List_of_PHP_accelerators 2. http://php.net/manual/en/book.apc.php PDF brought to you by Chapter 21: Performance | 239 generated on June 20, 2012
  • 240. Use an Autoloader that caches (e.g. ApcUniversalClassLoader) By default, the Symfony2 standard edition uses the UniversalClassLoader in the autoloader.php3 file. This autoloader is easy to use, as it will automatically find any new classes that you've placed in the registered directories. Unfortunately, this comes at a cost, as the loader iterates over all configured namespaces to find a particular file, making file_exists calls until it finally finds the file it's looking for. The simplest solution is to cache the location of each class after it's located the first time. Symfony comes with a class - ApcUniversalClassLoader - loader that extends the UniversalClassLoader and stores the class locations in APC. To use this class loader, simply adapt your autoloader.php as follows: Listing // app/autoload.php 21-1 require __DIR__.'/../vendor/symfony/src/Symfony/Component/ClassLoader/ ApcUniversalClassLoader.php'; use SymfonyComponentClassLoaderApcUniversalClassLoader; $loader = new ApcUniversalClassLoader('some caching unique prefix'); // ... When using the APC autoloader, if you add new classes, they will be found automatically and everything will work the same as before (i.e. no reason to "clear" the cache). However, if you change the location of a particular namespace or prefix, you'll need to flush your APC cache. Otherwise, the autoloader will still be looking at the old location for all classes inside that namespace. Use Bootstrap Files To ensure optimal flexibility and code reuse, Symfony2 applications leverage a variety of classes and 3rd party components. But loading all of these classes from separate files on each request can result in some overhead. To reduce this overhead, the Symfony2 Standard Edition provides a script to generate a so- called bootstrap file4, consisting of multiple classes definitions in a single file. By including this file (which contains a copy of many of the core classes), Symfony no longer needs to include any of the source files containing those classes. This will reduce disc IO quite a bit. If you're using the Symfony2 Standard Edition, then you're probably already using the bootstrap file. To be sure, open your front controller (usually app.php) and check to make sure that the following line exists: Listing require_once __DIR__.'/../app/bootstrap.php.cache'; 21-2 Note that there are two disadvantages when using a bootstrap file: • the file needs to be regenerated whenever any of the original sources change (i.e. when you update the Symfony2 source or vendor libraries); • when debugging, one will need to place break points inside the bootstrap file. If you're using Symfony2 Standard Edition, the bootstrap file is automatically rebuilt after updating the vendor libraries via the php bin/vendors install command. 3. https://github.com/symfony/symfony-standard/blob/master/app/autoload.php 4. https://github.com/sensio/SensioDistributionBundle/blob/2.0/Resources/bin/build_bootstrap.php PDF brought to you by Chapter 21: Performance | 240 generated on June 20, 2012
  • 241. Bootstrap Files and Byte Code Caches Even when using a byte code cache, performance will improve when using a bootstrap file since there will be less files to monitor for changes. Of course if this feature is disabled in the byte code cache (e.g. apc.stat=0 in APC), there is no longer a reason to use a bootstrap file. PDF brought to you by Chapter 21: Performance | 241 generated on June 20, 2012
  • 242. Chapter 22 Internals Looks like you want to understand how Symfony2 works and how to extend it. That makes me very happy! This section is an in-depth explanation of the Symfony2 internals. You need to read this section only if you want to understand how Symfony2 works behind the scene, or if you want to extend Symfony2. Overview The Symfony2 code is made of several independent layers. Each layer is built on top of the previous one. Autoloading is not managed by the framework directly; it's done independently with the help of the UniversalClassLoader1 class and the src/autoload.php file. Read the dedicated chapter for more information. HttpFoundation Component The deepest level is the HttpFoundation2 component. HttpFoundation provides the main objects needed to deal with HTTP. It is an Object-Oriented abstraction of some native PHP functions and variables: • The Request3 class abstracts the main PHP global variables like $_GET, $_POST, $_COOKIE, $_FILES, and $_SERVER; • The Response4 class abstracts some PHP functions like header(), setcookie(), and echo; 1. http://api.symfony.com/2.0/Symfony/Component/ClassLoader/UniversalClassLoader.html 2. http://api.symfony.com/2.0/Symfony/Component/HttpFoundation.html 3. http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/Request.html 4. http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/Response.html PDF brought to you by Chapter 22: Internals | 242 generated on June 20, 2012
  • 243. • The Session5 class and SessionStorageInterface6 interface abstract session management session_*() functions. HttpKernel Component On top of HttpFoundation is the HttpKernel7 component. HttpKernel handles the dynamic part of HTTP; it is a thin wrapper on top of the Request and Response classes to standardize the way requests are handled. It also provides extension points and tools that makes it the ideal starting point to create a Web framework without too much overhead. It also optionally adds configurability and extensibility, thanks to the Dependency Injection component and a powerful plugin system (bundles). Read more about Dependency Injection and Bundles. FrameworkBundle Bundle The FrameworkBundle8 bundle is the bundle that ties the main components and libraries together to make a lightweight and fast MVC framework. It comes with a sensible default configuration and conventions to ease the learning curve. Kernel The HttpKernel9 class is the central class of Symfony2 and is responsible for handling client requests. Its main goal is to "convert" a Request10 object to a Response11 object. Every Symfony2 Kernel implements HttpKernelInterface12: function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true) Listing 22-1 Controllers To convert a Request to a Response, the Kernel relies on a "Controller". A Controller can be any valid PHP callable. The Kernel delegates the selection of what Controller should be executed to an implementation of ControllerResolverInterface13: public function getController(Request $request); Listing 22-2 public function getArguments(Request $request, $controller); The getController()14 method returns the Controller (a PHP callable) associated with the given Request. The default implementation (ControllerResolver15) looks for a _controller request attribute 5. http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/Session.html 6. http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/SessionStorage/SessionStorageInterface.html 7. http://api.symfony.com/2.0/Symfony/Component/HttpKernel.html 8. http://api.symfony.com/2.0/Symfony/Bundle/FrameworkBundle.html 9. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/HttpKernel.html 10. http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/Request.html 11. http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/Response.html 12. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/HttpKernelInterface.html 13. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/Controller/ControllerResolverInterface.html 14. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/Controller/ControllerResolverInterface.html#getController() 15. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/Controller/ControllerResolver.html PDF brought to you by Chapter 22: Internals | 243 generated on June 20, 2012
  • 244. that represents the controller name (a "class::method" string, like BundleBlogBundlePostController:indexAction). The default implementation uses the RouterListener16 to define the _controller Request attribute (see kernel.request Event). The getArguments()17 method returns an array of arguments to pass to the Controller callable. The default implementation automatically resolves the method arguments, based on the Request attributes. Matching Controller method arguments from Request attributes For each method argument, Symfony2 tries to get the value of a Request attribute with the same name. If it is not defined, the argument default value is used if defined: Listing // Symfony2 will look for an 'id' attribute (mandatory) 22-3 // and an 'admin' one (optional) public function showAction($id, $admin = true) { // ... } Handling Requests The handle() method takes a Request and always returns a Response. To convert the Request, handle() relies on the Resolver and an ordered chain of Event notifications (see the next section for more information about each Event): 1. Before doing anything else, the kernel.request event is notified -- if one of the listeners returns a Response, it jumps to step 8 directly; 2. The Resolver is called to determine the Controller to execute; 3. Listeners of the kernel.controller event can now manipulate the Controller callable the way they want (change it, wrap it, ...); 4. The Kernel checks that the Controller is actually a valid PHP callable; 5. The Resolver is called to determine the arguments to pass to the Controller; 6. The Kernel calls the Controller; 7. If the Controller does not return a Response, listeners of the kernel.view event can convert the Controller return value to a Response; 8. Listeners of the kernel.response event can manipulate the Response (content and headers); 9. The Response is returned. If an Exception is thrown during processing, the kernel.exception is notified and listeners are given a chance to convert the Exception to a Response. If that works, the kernel.response event is notified; if not, the Exception is re-thrown. If you don't want Exceptions to be caught (for embedded requests for instance), disable the kernel.exception event by passing false as the third argument to the handle() method. Internal Requests At any time during the handling of a request (the 'master' one), a sub-request can be handled. You can pass the request type to the handle() method (its second argument): 16. http://api.symfony.com/2.0/Symfony/Bundle/FrameworkBundle/EventListener/RouterListener.html 17. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/Controller/ControllerResolverInterface.html#getArguments() PDF brought to you by Chapter 22: Internals | 244 generated on June 20, 2012
  • 245. • HttpKernelInterface::MASTER_REQUEST; • HttpKernelInterface::SUB_REQUEST. The type is passed to all events and listeners can act accordingly (some processing must only occur on the master request). Events Each event thrown by the Kernel is a subclass of KernelEvent18. This means that each event has access to the same basic information: • getRequestType() - returns the type of the request (HttpKernelInterface::MASTER_REQUEST or HttpKernelInterface::SUB_REQUEST); • getKernel() - returns the Kernel handling the request; • getRequest() - returns the current Request being handled. getRequestType() The getRequestType() method allows listeners to know the type of the request. For instance, if a listener must only be active for master requests, add the following code at the beginning of your listener method: use SymfonyComponentHttpKernelHttpKernelInterface; Listing 22-4 if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) { // return immediately return; } If you are not yet familiar with the Symfony2 Event Dispatcher, read the Event Dispatcher Component Documentation section first. kernel.request Event Event Class: GetResponseEvent19 The goal of this event is to either return a Response object immediately or setup variables so that a Controller can be called after the event. Any listener can return a Response object via the setResponse() method on the event. In this case, all other listeners won't be called. This event is used by FrameworkBundle to populate the _controller Request attribute, via the RouterListener20. RequestListener uses a RouterInterface21 object to match the Request and determine the Controller name (stored in the _controller Request attribute). kernel.controller Event Event Class: FilterControllerEvent22 This event is not used by FrameworkBundle, but can be an entry point used to modify the controller that should be executed: Listing 22-5 18. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/Event/KernelEvent.html 19. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/Event/GetResponseEvent.html 20. http://api.symfony.com/2.0/Symfony/Bundle/FrameworkBundle/EventListener/RouterListener.html 21. http://api.symfony.com/2.0/Symfony/Component/Routing/RouterInterface.html 22. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/Event/FilterControllerEvent.html PDF brought to you by Chapter 22: Internals | 245 generated on June 20, 2012
  • 246. use SymfonyComponentHttpKernelEventFilterControllerEvent; public function onKernelController(FilterControllerEvent $event) { $controller = $event->getController(); // ... // the controller can be changed to any PHP callable $event->setController($controller); } kernel.view Event Event Class: GetResponseForControllerResultEvent23 This event is not used by FrameworkBundle, but it can be used to implement a view sub-system. This event is called only if the Controller does not return a Response object. The purpose of the event is to allow some other return value to be converted into a Response. The value returned by the Controller is accessible via the getControllerResult method: Listing use SymfonyComponentHttpKernelEventGetResponseForControllerResultEvent; 22-6 use SymfonyComponentHttpFoundationResponse; public function onKernelView(GetResponseForControllerResultEvent $event) { $val = $event->getControllerResult(); $response = new Response(); // some how customize the Response from the return value $event->setResponse($response); } kernel.response Event Event Class: FilterResponseEvent24 The purpose of this event is to allow other systems to modify or replace the Response object after its creation: Listing public function onKernelResponse(FilterResponseEvent $event) 22-7 { $response = $event->getResponse(); // .. modify the response object } The FrameworkBundle registers several listeners: • ProfilerListener25: collects data for the current request; • WebDebugToolbarListener26: injects the Web Debug Toolbar; • ResponseListener27: fixes the Response Content-Type based on the request format; • EsiListener28: adds a Surrogate-Control HTTP header when the Response needs to be parsed for ESI tags. 23. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/Event/GetResponseForControllerResultEvent.html 24. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/Event/FilterResponseEvent.html 25. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/EventListener/ProfilerListener.html 26. http://api.symfony.com/2.0/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.html 27. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/EventListener/ResponseListener.html 28. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/EventListener/EsiListener.html PDF brought to you by Chapter 22: Internals | 246 generated on June 20, 2012
  • 247. kernel.exception Event Event Class: GetResponseForExceptionEvent29 FrameworkBundle registers an ExceptionListener30 that forwards the Request to a given Controller (the value of the exception_listener.controller parameter -- must be in the class::method notation). A listener on this event can create and set a Response object, create and set a new Exception object, or do nothing: use SymfonyComponentHttpKernelEventGetResponseForExceptionEvent; Listing 22-8 use SymfonyComponentHttpFoundationResponse; public function onKernelException(GetResponseForExceptionEvent $event) { $exception = $event->getException(); $response = new Response(); // setup the Response object based on the caught exception $event->setResponse($response); // you can alternatively set a new Exception // $exception = new Exception('Some special exception'); // $event->setException($exception); } The Event Dispatcher The event dispatcher is a standalone component that is responsible for much of the underlying logic and flow behind a Symfony request. For more information, see the Event Dispatcher Component Documentation. Profiler When enabled, the Symfony2 profiler collects useful information about each request made to your application and store them for later analysis. Use the profiler in the development environment to help you to debug your code and enhance performance; use it in the production environment to explore problems after the fact. You rarely have to deal with the profiler directly as Symfony2 provides visualizer tools like the Web Debug Toolbar and the Web Profiler. If you use the Symfony2 Standard Edition, the profiler, the web debug toolbar, and the web profiler are all already configured with sensible settings. The profiler collects information for all requests (simple requests, redirects, exceptions, Ajax requests, ESI requests; and for all HTTP methods and all formats). It means that for a single URL, you can have several associated profiling data (one per external request/response pair). 29. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/Event/GetResponseForExceptionEvent.html 30. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/EventListener/ExceptionListener.html PDF brought to you by Chapter 22: Internals | 247 generated on June 20, 2012
  • 248. Visualizing Profiling Data Using the Web Debug Toolbar In the development environment, the web debug toolbar is available at the bottom of all pages. It displays a good summary of the profiling data that gives you instant access to a lot of useful information when something does not work as expected. If the summary provided by the Web Debug Toolbar is not enough, click on the token link (a string made of 13 random characters) to access the Web Profiler. If the token is not clickable, it means that the profiler routes are not registered (see below for configuration information). Analyzing Profiling data with the Web Profiler The Web Profiler is a visualization tool for profiling data that you can use in development to debug your code and enhance performance; but it can also be used to explore problems that occur in production. It exposes all information collected by the profiler in a web interface. Accessing the Profiling information You don't need to use the default visualizer to access the profiling information. But how can you retrieve profiling information for a specific request after the fact? When the profiler stores data about a Request, it also associates a token with it; this token is available in the X-Debug-Token HTTP header of the Response: Listing $profile = $container->get('profiler')->loadProfileFromResponse($response); 22-9 $profile = $container->get('profiler')->loadProfile($token); When the profiler is enabled but not the web debug toolbar, or when you want to get the token for an Ajax request, use a tool like Firebug to get the value of the X-Debug-Token HTTP header. Use the find() method to access tokens based on some criteria: Listing // get the latest 10 tokens 22-10 $tokens = $container->get('profiler')->find('', '', 10); // get the latest 10 tokens for all URL containing /admin/ $tokens = $container->get('profiler')->find('', '/admin/', 10); // get the latest 10 tokens for local requests $tokens = $container->get('profiler')->find('127.0.0.1', '', 10); If you want to manipulate profiling data on a different machine than the one where the information were generated, use the export() and import() methods: Listing // on the production machine 22-11 $profile = $container->get('profiler')->loadProfile($token); $data = $profiler->export($profile); // on the development machine $profiler->import($data); PDF brought to you by Chapter 22: Internals | 248 generated on June 20, 2012
  • 249. Configuration The default Symfony2 configuration comes with sensible settings for the profiler, the web debug toolbar, and the web profiler. Here is for instance the configuration for the development environment: # load the profiler Listing 22-12 framework: profiler: { only_exceptions: false } # enable the web profiler web_profiler: toolbar: true intercept_redirects: true verbose: true When only-exceptions is set to true, the profiler only collects data when an exception is thrown by the application. When intercept-redirects is set to true, the web profiler intercepts the redirects and gives you the opportunity to look at the collected data before following the redirect. When verbose is set to true, the Web Debug Toolbar displays a lot of information. Setting verbose to false hides some secondary information to make the toolbar shorter. If you enable the web profiler, you also need to mount the profiler routes: _profiler: Listing 22-13 resource: @WebProfilerBundle/Resources/config/routing/profiler.xml prefix: /_profiler As the profiler adds some overhead, you might want to enable it only under certain circumstances in the production environment. The only-exceptions settings limits profiling to 500 pages, but what if you want to get information when the client IP comes from a specific address, or for a limited portion of the website? You can use a request matcher: # enables the profiler only for request coming for the 192.168.0.0 network Listing 22-14 framework: profiler: matcher: { ip: 192.168.0.0/24 } # enables the profiler only for the /admin URLs framework: profiler: matcher: { path: "^/admin/" } # combine rules framework: profiler: matcher: { ip: 192.168.0.0/24, path: "^/admin/" } # use a custom matcher instance defined in the "custom_matcher" service framework: profiler: matcher: { service: custom_matcher } Learn more from the Cookbook • How to use the Profiler in a Functional Test • How to create a custom Data Collector • How to extend a Class without using Inheritance PDF brought to you by Chapter 22: Internals | 249 generated on June 20, 2012
  • 250. • How to customize a Method Behavior without using Inheritance PDF brought to you by Chapter 22: Internals | 250 generated on June 20, 2012
  • 251. Chapter 23 The Symfony2 Stable API The Symfony2 stable API is a subset of all Symfony2 published public methods (components and core bundles) that share the following properties: • The namespace and class name won't change; • The method name won't change; • The method signature (arguments and return value type) won't change; • The semantic of what the method does won't change. The implementation itself can change though. The only valid case for a change in the stable API is in order to fix a security issue. The stable API is based on a whitelist, tagged with @api. Therefore, everything not tagged explicitly is not part of the stable API. Any third party bundle should also publish its own stable API. As of Symfony 2.0, the following components have a public tagged API: • BrowserKit • ClassLoader • Console • CssSelector • DependencyInjection • DomCrawler • EventDispatcher • Finder • HttpFoundation • HttpKernel • Locale • Process • Routing • Templating • Translation PDF brought to you by Chapter 23: The Symfony2 Stable API | 251 generated on June 20, 2012
  • 252. • Validator • Yaml PDF brought to you by Chapter 23: The Symfony2 Stable API | 252 generated on June 20, 2012
  • 254. Chapter 24 How to Create and store a Symfony2 Project in git Though this entry is specifically about git, the same generic principles will apply if you're storing your project in Subversion. Once you've read through Creating Pages in Symfony2 and become familiar with using Symfony, you'll no-doubt be ready to start your own project. In this cookbook article, you'll learn the best way to start a new Symfony2 project that's stored using the git1 source control management system. Initial Project Setup To get started, you'll need to download Symfony and initialize your local git repository: 1. Download the Symfony2 Standard Edition2 without vendors. 2. Unzip/untar the distribution. It will create a folder called Symfony with your new project structure, config files, etc. Rename it to whatever you like. 3. Create a new file called .gitignore at the root of your new project (e.g. next to the deps file) and paste the following into it. Files matching these patterns will be ignored by git: Listing /web/bundles/ 24-1 /app/bootstrap* /app/cache/* /app/logs/* /vendor/ /app/config/parameters.ini 1. http://git-scm.com/ 2. http://symfony.com/download PDF brought to you by Chapter 24: How to Create and store a Symfony2 Project in git | 254 generated on June 20, 2012
  • 255. You may also want to create a .gitignore file that can be used system-wide, in which case, you can find more information here: Github .gitignore3 This way you can exclude files/folders often used by your IDE for all of your projects. 4. Copy app/config/parameters.ini to app/config/parameters.ini.dist. The parameters.ini file is ignored by git (see above) so that machine-specific settings like database passwords aren't committed. By creating the parameters.ini.dist file, new developers can quickly clone the project, copy this file to parameters.ini, customize it, and start developing. 5. Initialize your git repository: $ git init Listing 24-2 6. Add all of the initial files to git: $ git add . Listing 24-3 7. Create an initial commit with your started project: $ git commit -m "Initial commit" Listing 24-4 8. Finally, download all of the third-party vendor libraries: $ php bin/vendors install Listing 24-5 At this point, you have a fully-functional Symfony2 project that's correctly committed to git. You can immediately begin development, committing the new changes to your git repository. After execution of the command: $ php bin/vendors install Listing 24-6 your project will contain complete the git history of all the bundles and libraries defined in the deps file. It can be as much as 100 MB! If you save the current versions of all your dependencies with the command: $ php bin/vendors lock Listing 24-7 then you can remove the git history directories with the following command: $ find vendor -name .git -type d | xargs rm -rf Listing 24-8 The command removes all .git directories contained inside the vendor directory. If you want to update bundles defined in deps file after this, you will have to reinstall them: $ php bin/vendors install --reinstall Listing 24-9 You can continue to follow along with the Creating Pages in Symfony2 chapter to learn more about how to configure and develop inside your application. 3. http://help.github.com/ignore-files/ PDF brought to you by Chapter 24: How to Create and store a Symfony2 Project in git | 255 generated on June 20, 2012
  • 256. The Symfony2 Standard Edition comes with some example functionality. To remove the sample code, follow the instructions on the Standard Edition Readme4. Managing Vendor Libraries with bin/vendors and deps How does it work? Every Symfony project uses a group of third-party "vendor" libraries. One way or another the goal is to download these files into your vendor/ directory and, ideally, to give you some sane way to manage the exact version you need for each. By default, these libraries are downloaded by running a php bin/vendors install "downloader" script. This script reads from the deps file at the root of your project. This is an ini-formatted script, which holds a list of each of the external libraries you need, the directory each should be downloaded to, and (optionally) the version to be downloaded. The bin/vendors script uses git to downloaded these, solely because these external libraries themselves tend to be stored via git. The bin/vendors script also reads the deps.lock file, which allows you to pin each library to an exact git commit hash. It's important to realize that these vendor libraries are not actually part of your repository. Instead, they're simply un-tracked files that are downloaded into the vendor/ directory by the bin/vendors script. But since all the information needed to download these files is saved in deps and deps.lock (which are stored) in our repository), any other developer can use our project, run php bin/vendors install, and download the exact same set of vendor libraries. This means that you're controlling exactly what each vendor library looks like, without needing to actually commit them to your repository. So, whenever a developer uses your project, he/she should run the php bin/vendors install script to ensure that all of the needed vendor libraries are downloaded. Upgrading Symfony Since Symfony is just a group of third-party libraries and third-party libraries are entirely controlled through deps and deps.lock, upgrading Symfony means simply upgrading each of these files to match their state in the latest Symfony Standard Edition. Of course, if you've added new entries to deps or deps.lock, be sure to replace only the original parts (i.e. be sure not to also delete any of your custom entries). There is also a php bin/vendors update command, but this has nothing to do with upgrading your project and you will normally not need to use it. This command is used to freeze the versions of all of your vendor libraries by updating them to the version specified in deps and recording it into the deps.lock file. Hacking vendor libraries Sometimes, you want a specific branch, tag, or commit of a library to be downloaded or upgraded. You can set that directly to the deps file : Listing [AcmeAwesomeBundle] 24-10 git=http://github.com/johndoe/Acme/AwesomeBundle.git 4. https://github.com/symfony/symfony-standard/blob/master/README.md PDF brought to you by Chapter 24: How to Create and store a Symfony2 Project in git | 256 generated on June 20, 2012
  • 257. target=/bundles/Acme/AwesomeBundle version=the-awesome-version • The git option sets the URL of the library. It can use various protocols, like http:// as well as git://. • The target option specifies where the repository will live : plain Symfony bundles should go under the vendor/bundles/Acme directory, other third-party libraries usually go to vendor/ my-awesome-library-name. The target directory defaults to this last option when not specified. • The version option allows you to set a specific revision. You can use a tag (version=origin/0.42) or a branch name (refs/remotes/origin/awesome-branch). It defaults to origin/HEAD. Updating workflow When you execute the php bin/vendors install, for every library, the script first checks if the install directory exists. If it does not (and ONLY if it does not), it runs a git clone. Then, it does a git fetch origin and a git reset --hard the-awesome-version. This means that the repository will only be cloned once. If you want to perform any change of the git remote, you MUST delete the entire target directory, not only its content. Vendors and Submodules Instead of using the deps, bin/vendors system for managing your vendor libraries, you may instead choose to use native git submodules5. There is nothing wrong with this approach, though the deps system is the official way to solve this problem and git submodules can be difficult to work with at times. Storing your Project on a Remote Server You now have a fully-functional Symfony2 project stored in git. However, in most cases, you'll also want to store your project on a remote server both for backup purposes, and so that other developers can collaborate on the project. The easiest way to store your project on a remote server is via GitHub6. Public repositories are free, however you will need to pay a monthly fee to host private repositories. Alternatively, you can store your git repository on any server by creating a barebones repository7 and then pushing to it. One library that helps manage this is Gitolite8. 5. http://book.git-scm.com/5_submodules.html 6. https://github.com/ 7. http://progit.org/book/ch4-4.html 8. https://github.com/sitaramc/gitolite PDF brought to you by Chapter 24: How to Create and store a Symfony2 Project in git | 257 generated on June 20, 2012
  • 258. Chapter 25 How to Create and store a Symfony2 Project in Subversion This entry is specifically about Subversion, and based on principles found in How to Create and store a Symfony2 Project in git. Once you've read through Creating Pages in Symfony2 and become familiar with using Symfony, you'll no-doubt be ready to start your own project. The preferred method to manage Symfony2 projects is using git1 but some prefer to use Subversion2 which is totally fine!. In this cookbook article, you'll learn how to manage your project using svn3 in a similar manner you would do with git4. This is a method to tracking your Symfony2 project in a Subversion repository. There are several ways to do and this one is simply one that works. The Subversion Repository For this article we will suppose that your repository layout follows the widespread standard structure: Listing myproject/ 25-1 branches/ tags/ trunk/ 1. http://git-scm.com/ 2. http://subversion.apache.org/ 3. http://subversion.apache.org/ 4. http://git-scm.com/ PDF brought to you by Chapter 25: How to Create and store a Symfony2 Project in Subversion | 258 generated on June 20, 2012
  • 259. Most subversion hosting should follow this standard practice. This is the recommended layout in Version Control with Subversion5 and the layout used by most free hosting (see Subversion hosting solutions). Initial Project Setup To get started, you'll need to download Symfony2 and get the basic Subversion setup: 1. Download the Symfony2 Standard Edition6 with or without vendors. 2. Unzip/untar the distribution. It will create a folder called Symfony with your new project structure, config files, etc. Rename it to whatever you like. 3. Checkout the Subversion repository that will host this project. Let's say it is hosted on Google code7 and called myproject: $ svn checkout http://myproject.googlecode.com/svn/trunk myproject Listing 25-2 4. Copy the Symfony2 project files in the subversion folder: $ mv Symfony/* myproject/ Listing 25-3 5. Let's now set the ignore rules. Not everything should be stored in your subversion repository. Some files (like the cache) are generated and others (like the database configuration) are meant to be customized on each machine. This makes use of the svn:ignore property, so that we can ignore specific files. $ cd myproject/ Listing 25-4 $ svn add --depth=empty app app/cache app/logs app/config web $ svn propset svn:ignore "vendor" . $ svn propset svn:ignore "bootstrap*" app/ $ svn propset svn:ignore "parameters.ini" app/config/ $ svn propset svn:ignore "*" app/cache/ $ svn propset svn:ignore "*" app/logs/ $ svn propset svn:ignore "bundles" web $ svn ci -m "commit basic symfony ignore list (vendor, app/bootstrap*, app/config/ parameters.ini, app/cache/*, app/logs/*, web/bundles)" 6. The rest of the files can now be added and committed to the project: $ svn add --force . Listing 25-5 $ svn ci -m "add basic Symfony Standard 2.X.Y" 7. Copy app/config/parameters.ini to app/config/parameters.ini.dist. The parameters.ini file is ignored by svn (see above) so that machine-specific settings like database passwords aren't committed. By creating the parameters.ini.dist file, new developers can quickly clone the project, copy this file to parameters.ini, customize it, and start developing. 8. Finally, download all of the third-party vendor libraries: 5. http://svnbook.red-bean.com/ 6. http://symfony.com/download 7. http://code.google.com/hosting/ PDF brought to you by Chapter 25: How to Create and store a Symfony2 Project in Subversion | 259 generated on June 20, 2012
  • 260. Listing $ php bin/vendors install 25-6 git8 has to be installed to run bin/vendors, this is the protocol used to fetch vendor libraries. This only means that git is used as a tool to basically help download the libraries in the vendor/ directory. At this point, you have a fully-functional Symfony2 project stored in your Subversion repository. The development can start with commits in the Subversion repository. You can continue to follow along with the Creating Pages in Symfony2 chapter to learn more about how to configure and develop inside your application. The Symfony2 Standard Edition comes with some example functionality. To remove the sample code, follow the instructions on the Standard Edition Readme9. Managing Vendor Libraries with bin/vendors and deps How does it work? Every Symfony project uses a group of third-party "vendor" libraries. One way or another the goal is to download these files into your vendor/ directory and, ideally, to give you some sane way to manage the exact version you need for each. By default, these libraries are downloaded by running a php bin/vendors install "downloader" script. This script reads from the deps file at the root of your project. This is an ini-formatted script, which holds a list of each of the external libraries you need, the directory each should be downloaded to, and (optionally) the version to be downloaded. The bin/vendors script uses git to downloaded these, solely because these external libraries themselves tend to be stored via git. The bin/vendors script also reads the deps.lock file, which allows you to pin each library to an exact git commit hash. It's important to realize that these vendor libraries are not actually part of your repository. Instead, they're simply un-tracked files that are downloaded into the vendor/ directory by the bin/vendors script. But since all the information needed to download these files is saved in deps and deps.lock (which are stored) in our repository), any other developer can use our project, run php bin/vendors install, and download the exact same set of vendor libraries. This means that you're controlling exactly what each vendor library looks like, without needing to actually commit them to your repository. So, whenever a developer uses your project, he/she should run the php bin/vendors install script to ensure that all of the needed vendor libraries are downloaded. Upgrading Symfony Since Symfony is just a group of third-party libraries and third-party libraries are entirely controlled through deps and deps.lock, upgrading Symfony means simply upgrading each of these files to match their state in the latest Symfony Standard Edition. Of course, if you've added new entries to deps or deps.lock, be sure to replace only the original parts (i.e. be sure not to also delete any of your custom entries). 8. http://git-scm.com/ 9. https://github.com/symfony/symfony-standard/blob/master/README.md PDF brought to you by Chapter 25: How to Create and store a Symfony2 Project in Subversion | 260 generated on June 20, 2012
  • 261. There is also a php bin/vendors update command, but this has nothing to do with upgrading your project and you will normally not need to use it. This command is used to freeze the versions of all of your vendor libraries by updating them to the version specified in deps and recording it into the deps.lock file. Hacking vendor libraries Sometimes, you want a specific branch, tag, or commit of a library to be downloaded or upgraded. You can set that directly to the deps file : [AcmeAwesomeBundle] Listing 25-7 git=http://github.com/johndoe/Acme/AwesomeBundle.git target=/bundles/Acme/AwesomeBundle version=the-awesome-version • The git option sets the URL of the library. It can use various protocols, like http:// as well as git://. • The target option specifies where the repository will live : plain Symfony bundles should go under the vendor/bundles/Acme directory, other third-party libraries usually go to vendor/ my-awesome-library-name. The target directory defaults to this last option when not specified. • The version option allows you to set a specific revision. You can use a tag (version=origin/0.42) or a branch name (refs/remotes/origin/awesome-branch). It defaults to origin/HEAD. Updating workflow When you execute the php bin/vendors install, for every library, the script first checks if the install directory exists. If it does not (and ONLY if it does not), it runs a git clone. Then, it does a git fetch origin and a git reset --hard the-awesome-version. This means that the repository will only be cloned once. If you want to perform any change of the git remote, you MUST delete the entire target directory, not only its content. Subversion hosting solutions The biggest difference between git10 and svn11 is that Subversion needs a central repository to work. You then have several solutions: • Self hosting: create your own repository and access it either through the filesystem or the network. To help in this task you can read Version Control with Subversion. • Third party hosting: there are a lot of serious free hosting solutions available like GitHub12, Google code13, SourceForge14 or Gna15. Some of them offer git hosting as well. 10. http://git-scm.com/ 11. http://subversion.apache.org/ 12. http://github.com/ 13. http://code.google.com/hosting/ 14. http://sourceforge.net/ 15. http://gna.org/ PDF brought to you by Chapter 25: How to Create and store a Symfony2 Project in Subversion | 261 generated on June 20, 2012
  • 262. Chapter 26 How to customize Error Pages When any exception is thrown in Symfony2, the exception is caught inside the Kernel class and eventually forwarded to a special controller, TwigBundle:Exception:show for handling. This controller, which lives inside the core TwigBundle, determines which error template to display and the status code that should be set for the given exception. Error pages can be customized in two different ways, depending on how much control you need: 1. Customize the error templates of the different error pages (explained below); 2. Replace the default exception controller TwigBundle::Exception:show with your own controller and handle it however you want (see exception_controller in the Twig reference); The customization of exception handling is actually much more powerful than what's written here. An internal event, kernel.exception, is thrown which allows complete control over exception handling. For more information, see kernel.exception Event. All of the error templates live inside TwigBundle. To override the templates, we simply rely on the standard method for overriding templates that live inside a bundle. For more information, see Overriding Bundle Templates. For example, to override the default error template that's shown to the end-user, create a new template located at app/Resources/TwigBundle/views/Exception/error.html.twig: Listing <!DOCTYPE html> 26-1 <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>An Error Occurred: {{ status_text }}</title> </head> <body> <h1>Oops! An Error Occurred</h1> <h2>The server returned a "{{ status_code }} {{ status_text }}".</h2> </body> </html> PDF brought to you by Chapter 26: How to customize Error Pages | 262 generated on June 20, 2012
  • 263. If you're not familiar with Twig, don't worry. Twig is a simple, powerful and optional templating engine that integrates with Symfony2. For more information about Twig see Creating and using Templates. In addition to the standard HTML error page, Symfony provides a default error page for many of the most common response formats, including JSON (error.json.twig), XML, (error.xml.twig), and even Javascript (error.js.twig), to name a few. To override any of these templates, just create a new file with the same name in the app/Resources/TwigBundle/views/Exception directory. This is the standard way of overriding any template that lives inside a bundle. Customizing the 404 Page and other Error Pages You can also customize specific error templates according to the HTTP status code. For instance, create a app/Resources/TwigBundle/views/Exception/error404.html.twig template to display a special page for 404 (page not found) errors. Symfony uses the following algorithm to determine which template to use: • First, it looks for a template for the given format and status code (like error404.json.twig); • If it does not exist, it looks for a template for the given format (like error.json.twig); • If it does not exist, it falls back to the HTML template (like error.html.twig). To see the full list of default error templates, see the Resources/views/Exception directory of the TwigBundle. In a standard Symfony2 installation, the TwigBundle can be found at vendor/ symfony/src/Symfony/Bundle/TwigBundle. Often, the easiest way to customize an error page is to copy it from the TwigBundle into app/Resources/TwigBundle/views/Exception and then modify it. The debug-friendly exception pages shown to the developer can even be customized in the same way by creating templates such as exception.html.twig for the standard HTML exception page or exception.json.twig for the JSON exception page. PDF brought to you by Chapter 26: How to customize Error Pages | 263 generated on June 20, 2012
  • 264. Chapter 27 How to define Controllers as Services In the book, you've learned how easily a controller can be used when it extends the base Controller1 class. While this works fine, controllers can also be specified as services. To refer to a controller that's defined as a service, use the single colon (:) notation. For example, suppose we've defined a service called my_controller and we want to forward to a method called indexAction() inside the service: Listing $this->forward('my_controller:indexAction', array('foo' => $bar)); 27-1 You need to use the same notation when defining the route _controller value: Listing my_controller: 27-2 pattern: / defaults: { _controller: my_controller:indexAction } To use a controller in this way, it must be defined in the service container configuration. For more information, see the Service Container chapter. When using a controller defined as a service, it will most likely not extend the base Controller class. Instead of relying on its shortcut methods, you'll interact directly with the services that you need. Fortunately, this is usually pretty easy and the base Controller class itself is a great source on how to perform many common tasks. Specifying a controller as a service takes a little bit more work. The primary advantage is that the entire controller or any services passed to the controller can be modified via the service container configuration. This is especially useful when developing an open-source bundle or any bundle that will be used in many different projects. So, even if you don't specify your controllers as services, you'll likely see this done in some open-source Symfony2 bundles. 1. http://api.symfony.com/2.0/Symfony/Bundle/FrameworkBundle/Controller/Controller.html PDF brought to you by Chapter 27: How to define Controllers as Services | 264 generated on June 20, 2012
  • 265. Chapter 28 How to force routes to always use HTTPS or HTTP Sometimes, you want to secure some routes and be sure that they are always accessed via the HTTPS protocol. The Routing component allows you to enforce the URI scheme via the _scheme requirement: secure: Listing 28-1 pattern: /secure defaults: { _controller: AcmeDemoBundle:Main:secure } requirements: _scheme: https The above configuration forces the secure route to always use HTTPS. When generating the secure URL, and if the current scheme is HTTP, Symfony will automatically generate an absolute URL with HTTPS as the scheme: # If the current scheme is HTTPS Listing 28-2 {{ path('secure') }} # generates /secure # If the current scheme is HTTP {{ path('secure') }} # generates https://example.com/secure The requirement is also enforced for incoming requests. If you try to access the /secure path with HTTP, you will automatically be redirected to the same URL, but with the HTTPS scheme. The above example uses https for the _scheme, but you can also force a URL to always use http. The Security component provides another way to enforce HTTP or HTTPs via the requires_channel setting. This alternative method is better suited to secure an "area" of your website (all URLs under /admin) or when you want to secure URLs defined in a third party bundle. PDF brought to you by Chapter 28: How to force routes to always use HTTPS or HTTP | 265 generated on June 20, 2012
  • 266. Chapter 29 How to allow a "/" character in a route parameter Sometimes, you need to compose URLs with parameters that can contain a slash /. For example, take the classic /hello/{name} route. By default, /hello/Fabien will match this route but not /hello/Fabien/ Kris. This is because Symfony uses this character as separator between route parts. This guide covers how you can modify a route so that /hello/Fabien/Kris matches the /hello/{name} route, where {name} equals Fabien/Kris. Configure the Route By default, the symfony routing components requires that the parameters match the following regex pattern: [^/]+. This means that all characters are allowed except /. You must explicitly allow / to be part of your parameter by specifying a more permissive regex pattern. Listing _hello: 29-1 pattern: /hello/{name} defaults: { _controller: AcmeDemoBundle:Demo:hello } requirements: name: ".+" That's it! Now, the {name} parameter can contain the / character. PDF brought to you by Chapter 29: How to allow a "/" character in a route parameter | 266 generated on June 20, 2012
  • 267. Chapter 30 How to Use Assetic for Asset Management Assetic combines two major ideas: assets and filters. The assets are files such as CSS, JavaScript and image files. The filters are things that can be applied to these files before they are served to the browser. This allows a separation between the asset files stored in the application and the files actually presented to the user. Without Assetic, you just serve the files that are stored in the application directly: <script src="{{ asset('js/script.js') }}" type="text/javascript" /> Listing 30-1 But with Assetic, you can manipulate these assets however you want (or load them from anywhere) before serving them. These means you can: • Minify and combine all of your CSS and JS files • Run all (or just some) of your CSS or JS files through some sort of compiler, such as LESS, SASS or CoffeeScript • Run image optimizations on your images Assets Using Assetic provides many advantages over directly serving the files. The files do not need to be stored where they are served from and can be drawn from various sources such as from within a bundle: {% javascripts Listing 30-2 '@AcmeFooBundle/Resources/public/js/*' %} <script type="text/javascript" src="{{ asset_url }}"></script> {% endjavascripts %} To bring in CSS stylesheets, you can use the same methodologies seen in this entry, except with the stylesheets tag: Listing 30-3 PDF brought to you by Chapter 30: How to Use Assetic for Asset Management | 267 generated on June 20, 2012
  • 268. {% stylesheets '@AcmeFooBundle/Resources/public/css/*' %} <link rel="stylesheet" href="{{ asset_url }}" /> {% endstylesheets %} In this example, all of the files in the Resources/public/js/ directory of the AcmeFooBundle will be loaded and served from a different location. The actual rendered tag might simply look like: Listing <script src="/app_dev.php/js/abcd123.js"></script> 30-4 This is a key point: once you let Assetic handle your assets, the files are served from a different location. This can cause problems with CSS files that reference images by their relative path. However, this can be fixed by using the cssrewrite filter, which updates paths in CSS files to reflect their new location. Combining Assets You can also combine several files into one. This helps to reduce the number of HTTP requests, which is great for front end performance. It also allows you to maintain the files more easily by splitting them into manageable parts. This can help with re-usability as you can easily split project-specific files from those which can be used in other applications, but still serve them as a single file: Listing {% javascripts 30-5 '@AcmeFooBundle/Resources/public/js/*' '@AcmeBarBundle/Resources/public/js/form.js' '@AcmeBarBundle/Resources/public/js/calendar.js' %} <script src="{{ asset_url }}"></script> {% endjavascripts %} In the dev environment, each file is still served individually, so that you can debug problems more easily. However, in the prod environment, this will be rendered as a single script tag. If you're new to Assetic and try to use your application in the prod environment (by using the app.php controller), you'll likely see that all of your CSS and JS breaks. Don't worry! This is on purpose. For details on using Assetic in the prod environment, see Dumping Asset Files. And combining files doesn't only apply to your files. You can also use Assetic to combine third party assets, such as jQuery, with your own into a single file: Listing {% javascripts 30-6 '@AcmeFooBundle/Resources/public/js/thirdparty/jquery.js' '@AcmeFooBundle/Resources/public/js/*' %} <script src="{{ asset_url }}"></script> {% endjavascripts %} PDF brought to you by Chapter 30: How to Use Assetic for Asset Management | 268 generated on June 20, 2012
  • 269. Filters Once they're managed by Assetic, you can apply filters to your assets before they are served. This includes filters that compress the output of your assets for smaller file sizes (and better front-end optimization). Other filters can compile JavaScript file from CoffeeScript files and process SASS into CSS. In fact, Assetic has a long list of available filters. Many of the filters do not do the work directly, but use existing third-party libraries to do the heavy- lifting. This means that you'll often need to install a third-party library to use a filter. The great advantage of using Assetic to invoke these libraries (as opposed to using them directly) is that instead of having to run them manually after you work on the files, Assetic will take care of this for you and remove this step altogether from your development and deployment processes. To use a filter, you first need to specify it in the Assetic configuration. Adding a filter here doesn't mean it's being used - it just means that it's available to use (we'll use the filter below). For example to use the JavaScript YUI Compressor the following config should be added: # app/config/config.yml Listing 30-7 assetic: filters: yui_js: jar: "%kernel.root_dir%/Resources/java/yuicompressor.jar" Now, to actually use the filter on a group of JavaScript files, add it into your template: {% javascripts Listing 30-8 '@AcmeFooBundle/Resources/public/js/*' filter='yui_js' %} <script src="{{ asset_url }}"></script> {% endjavascripts %} A more detailed guide about configuring and using Assetic filters as well as details of Assetic's debug mode can be found in How to Minify JavaScripts and Stylesheets with YUI Compressor. Controlling the URL used If you wish to, you can control the URLs that Assetic produces. This is done from the template and is relative to the public document root: {% javascripts Listing 30-9 '@AcmeFooBundle/Resources/public/js/*' output='js/compiled/main.js' %} <script src="{{ asset_url }}"></script> {% endjavascripts %} Symfony also contains a method for cache busting, where the final URL generated by Assetic contains a query parameter that can be incremented via configuration on each deployment. For more information, see the assets_version configuration option. PDF brought to you by Chapter 30: How to Use Assetic for Asset Management | 269 generated on June 20, 2012
  • 270. Dumping Asset Files In the dev environment, Assetic generates paths to CSS and JavaScript files that don't physically exist on your computer. But they render nonetheless because an internal Symfony controller opens the files and serves back the content (after running any filters). This kind of dynamic serving of processed assets is great because it means that you can immediately see the new state of any asset files you change. It's also bad, because it can be quite slow. If you're using a lot of filters, it might be downright frustrating. Fortunately, Assetic provides a way to dump your assets to real files, instead of being generated dynamically. Dumping Asset Files in the prod environment In the prod environment, your JS and CSS files are represented by a single tag each. In other words, instead of seeing each JavaScript file you're including in your source, you'll likely just see something like this: Listing <script src="/app_dev.php/js/abcd123.js"></script> 30-10 Moreover, that file does not actually exist, nor is it dynamically rendered by Symfony (as the asset files are in the dev environment). This is on purpose - letting Symfony generate these files dynamically in a production environment is just too slow. Instead, each time you use your app in the prod environment (and therefore, each time you deploy), you should run the following task: Listing php app/console assetic:dump --env=prod --no-debug 30-11 This will physically generate and write each file that you need (e.g. /js/abcd123.js). If you update any of your assets, you'll need to run this again to regenerate the file. Dumping Asset Files in the dev environment By default, each asset path generated in the dev environment is handled dynamically by Symfony. This has no disadvantage (you can see your changes immediately), except that assets can load noticeably slow. If you feel like your assets are loading too slowly, follow this guide. First, tell Symfony to stop trying to process these files dynamically. Make the following change in your config_dev.yml file: Listing # app/config/config_dev.yml 30-12 assetic: use_controller: false Next, since Symfony is no longer generating these assets for you, you'll need to dump them manually. To do so, run the following: Listing php app/console assetic:dump 30-13 This physically writes all of the asset files you need for your dev environment. The big disadvantage is that you need to run this each time you update an asset. Fortunately, by passing the --watch option, the command will automatically regenerate assets as they change: Listing php app/console assetic:dump --watch 30-14 PDF brought to you by Chapter 30: How to Use Assetic for Asset Management | 270 generated on June 20, 2012
  • 271. Since running this command in the dev environment may generate a bunch of files, it's usually a good idea to point your generated assets files to some isolated directory (e.g. /js/compiled), to keep things organized: {% javascripts Listing 30-15 '@AcmeFooBundle/Resources/public/js/*' output='js/compiled/main.js' %} <script src="{{ asset_url }}"></script> {% endjavascripts %} PDF brought to you by Chapter 30: How to Use Assetic for Asset Management | 271 generated on June 20, 2012
  • 272. Chapter 31 How to Minify JavaScripts and Stylesheets with YUI Compressor Yahoo! provides an excellent utility for minifying JavaScripts and stylesheets so they travel over the wire faster, the YUI Compressor1. Thanks to Assetic, you can take advantage of this tool very easily. Download the YUI Compressor JAR The YUI Compressor is written in Java and distributed as a JAR. Download the JAR2 from the Yahoo! site and save it to app/Resources/java/yuicompressor.jar. Configure the YUI Filters Now you need to configure two Assetic filters in your application, one for minifying JavaScripts with the YUI Compressor and one for minifying stylesheets: Listing # app/config/config.yml 31-1 assetic: # java: "/usr/bin/java" filters: yui_css: jar: "%kernel.root_dir%/Resources/java/yuicompressor.jar" yui_js: jar: "%kernel.root_dir%/Resources/java/yuicompressor.jar" Windows users need to remember to update config to proper java location. In Windows7 x64 bit by default it's C:Program Files (x86)Javajre6binjava.exe. 1. http://developer.yahoo.com/yui/compressor/ 2. http://yuilibrary.com/downloads/#yuicompressor PDF brought to you by Chapter 31: How to Minify JavaScripts and Stylesheets with YUI Compressor | 272 generated on June 20, 2012
  • 273. You now have access to two new Assetic filters in your application: yui_css and yui_js. These will use the YUI Compressor to minify stylesheets and JavaScripts, respectively. Minify your Assets You have YUI Compressor configured now, but nothing is going to happen until you apply one of these filters to an asset. Since your assets are a part of the view layer, this work is done in your templates: {% javascripts '@AcmeFooBundle/Resources/public/js/*' filter='yui_js' %} Listing 31-2 <script src="{{ asset_url }}"></script> {% endjavascripts %} The above example assumes that you have a bundle called AcmeFooBundle and your JavaScript files are in the Resources/public/js directory under your bundle. This isn't important however - you can include your Javascript files no matter where they are. With the addition of the yui_js filter to the asset tags above, you should now see minified JavaScripts coming over the wire much faster. The same process can be repeated to minify your stylesheets. {% stylesheets '@AcmeFooBundle/Resources/public/css/*' filter='yui_css' %} Listing 31-3 <link rel="stylesheet" type="text/css" media="screen" href="{{ asset_url }}" /> {% endstylesheets %} Disable Minification in Debug Mode Minified JavaScripts and Stylesheets are very difficult to read, let alone debug. Because of this, Assetic lets you disable a certain filter when your application is in debug mode. You can do this by prefixing the filter name in your template with a question mark: ?. This tells Assetic to only apply this filter when debug mode is off. {% javascripts '@AcmeFooBundle/Resources/public/js/*' filter='?yui_js' %} Listing 31-4 <script src="{{ asset_url }}"></script> {% endjavascripts %} Instead of adding the filter to the asset tags, you can also globally enable it by adding the apply- to attribute to the filter configuration, for example in the yui_js filter apply_to: ".js$". To only have the filter applied in production, add this to the config_prod file rather than the common config file. For details on applying filters by file extension, see Filtering based on a File Extension. PDF brought to you by Chapter 31: How to Minify JavaScripts and Stylesheets with YUI Compressor | 273 generated on June 20, 2012
  • 274. Chapter 32 How to Use Assetic For Image Optimization with Twig Functions Amongst its many filters, Assetic has four filters which can be used for on-the-fly image optimization. This allows you to get the benefits of smaller file sizes without having to use an image editor to process each image. The results are cached and can be dumped for production so there is no performance hit for your end users. Using Jpegoptim Jpegoptim1 is a utility for optimizing JPEG files. To use it with Assetic, add the following to the Assetic config: Listing # app/config/config.yml 32-1 assetic: filters: jpegoptim: bin: path/to/jpegoptim Notice that to use jpegoptim, you must have it already installed on your system. The bin option points to the location of the compiled binary. It can now be used from a template: Listing {% image '@AcmeFooBundle/Resources/public/images/example.jpg' 32-2 filter='jpegoptim' output='/images/example.jpg' %} <img src="{{ asset_url }}" alt="Example"/> {% endimage %} 1. http://www.kokkonen.net/tjko/projects.html PDF brought to you by Chapter 32: How to Use Assetic For Image Optimization with Twig Functions | 274 generated on June 20, 2012
  • 275. Removing all EXIF Data By default, running this filter only removes some of the meta information stored in the file. Any EXIF data and comments are not removed, but you can remove these by using the strip_all option: # app/config/config.yml Listing 32-3 assetic: filters: jpegoptim: bin: path/to/jpegoptim strip_all: true Lowering Maximum Quality The quality level of the JPEG is not affected by default. You can gain further file size reductions by setting the max quality setting lower than the current level of the images. This will of course be at the expense of image quality: # app/config/config.yml Listing 32-4 assetic: filters: jpegoptim: bin: path/to/jpegoptim max: 70 Shorter syntax: Twig Function If you're using Twig, it's possible to achieve all of this with a shorter syntax by enabling and using a special Twig function. Start by adding the following config: # app/config/config.yml Listing 32-5 assetic: filters: jpegoptim: bin: path/to/jpegoptim twig: functions: jpegoptim: ~ The Twig template can now be changed to the following: <img src="{{ jpegoptim('@AcmeFooBundle/Resources/public/images/example.jpg') }}" Listing 32-6 alt="Example"/> You can specify the output directory in the config in the following way: # app/config/config.yml Listing 32-7 assetic: filters: jpegoptim: bin: path/to/jpegoptim twig: functions: jpegoptim: { output: images/*.jpg } PDF brought to you by Chapter 32: How to Use Assetic For Image Optimization with Twig Functions | 275 generated on June 20, 2012
  • 276. Chapter 33 How to Apply an Assetic Filter to a Specific File Extension Assetic filters can be applied to individual files, groups of files or even, as you'll see here, files that have a specific extension. To show you how to handle each option, let's suppose that you want to use Assetic's CoffeeScript filter, which compiles CoffeeScript files into Javascript. The main configuration is just the paths to coffee and node. These default respectively to /usr/bin/ coffee and /usr/bin/node: Listing # app/config/config.yml 33-1 assetic: filters: coffee: bin: /usr/bin/coffee node: /usr/bin/node Filter a Single File You can now serve up a single CoffeeScript file as JavaScript from within your templates: Listing {% javascripts '@AcmeFooBundle/Resources/public/js/example.coffee' 33-2 filter='coffee' %} <script src="{{ asset_url }}" type="text/javascript"></script> {% endjavascripts %} This is all that's needed to compile this CoffeeScript file and server it as the compiled JavaScript. Filter Multiple Files You can also combine multiple CoffeeScript files into a single output file: PDF brought to you by Chapter 33: How to Apply an Assetic Filter to a Specific File Extension | 276 generated on June 20, 2012
  • 277. {% javascripts '@AcmeFooBundle/Resources/public/js/example.coffee' Listing 33-3 '@AcmeFooBundle/Resources/public/js/another.coffee' filter='coffee' %} <script src="{{ asset_url }}" type="text/javascript"></script> {% endjavascripts %} Both the files will now be served up as a single file compiled into regular JavaScript. Filtering based on a File Extension One of the great advantages of using Assetic is reducing the number of asset files to lower HTTP requests. In order to make full use of this, it would be good to combine all your JavaScript and CoffeeScript files together since they will ultimately all be served as JavaScript. Unfortunately just adding the JavaScript files to the files to be combined as above will not work as the regular JavaScript files will not survive the CoffeeScript compilation. This problem can be avoided by using the apply_to option in the config, which allows you to specify that a filter should always be applied to particular file extensions. In this case you can specify that the Coffee filter is applied to all .coffee files: # app/config/config.yml Listing 33-4 assetic: filters: coffee: bin: /usr/bin/coffee node: /usr/bin/node apply_to: ".coffee$" With this, you no longer need to specify the coffee filter in the template. You can also list regular JavaScript files, all of which will be combined and rendered as a single JavaScript file (with only the .coffee files being run through the CoffeeScript filter): {% javascripts '@AcmeFooBundle/Resources/public/js/example.coffee' Listing 33-5 '@AcmeFooBundle/Resources/public/js/another.coffee' '@AcmeFooBundle/Resources/public/js/regular.js' %} <script src="{{ asset_url }}" type="text/javascript"></script> {% endjavascripts %} PDF brought to you by Chapter 33: How to Apply an Assetic Filter to a Specific File Extension | 277 generated on June 20, 2012
  • 278. Chapter 34 How to handle File Uploads with Doctrine Handling file uploads with Doctrine entities is no different than handling any other file upload. In other words, you're free to move the file in your controller after handling a form submission. For examples of how to do this, see the file type reference page. If you choose to, you can also integrate the file upload into your entity lifecycle (i.e. creation, update and removal). In this case, as your entity is created, updated, and removed from Doctrine, the file uploading and removal processing will take place automatically (without needing to do anything in your controller); To make this work, you'll need to take care of a number of details, which will be covered in this cookbook entry. Basic Setup First, create a simple Doctrine Entity class to work with: Listing // src/Acme/DemoBundle/Entity/Document.php 34-1 namespace AcmeDemoBundleEntity; use DoctrineORMMapping as ORM; use SymfonyComponentValidatorConstraints as Assert; /** * @ORMEntity */ class Document { /** * @ORMId * @ORMColumn(type="integer") * @ORMGeneratedValue(strategy="AUTO") */ public $id; /** * @ORMColumn(type="string", length=255) * @AssertNotBlank PDF brought to you by Chapter 34: How to handle File Uploads with Doctrine | 278 generated on June 20, 2012
  • 279. */ public $name; /** * @ORMColumn(type="string", length=255, nullable=true) */ public $path; public function getAbsolutePath() { return null === $this->path ? null : $this->getUploadRootDir().'/'.$this->path; } public function getWebPath() { return null === $this->path ? null : $this->getUploadDir().'/'.$this->path; } protected function getUploadRootDir() { // the absolute directory path where uploaded documents should be saved return __DIR__.'/../../../../web/'.$this->getUploadDir(); } protected function getUploadDir() { // get rid of the __DIR__ so it doesn't screw when displaying uploaded doc/image in the view. return 'uploads/documents'; } } The Document entity has a name and it is associated with a file. The path property stores the relative path to the file and is persisted to the database. The getAbsolutePath() is a convenience method that returns the absolute path to the file while the getWebPath() is a convenience method that returns the web path, which can be used in a template to link to the uploaded file. If you have not done so already, you should probably read the file type documentation first to understand how the basic upload process works. If you're using annotations to specify your validation rules (as shown in this example), be sure that you've enabled validation by annotation (see validation configuration). To handle the actual file upload in the form, use a "virtual" file field. For example, if you're building your form directly in a controller, it might look like this: public function uploadAction() Listing 34-2 { // ... $form = $this->createFormBuilder($document) ->add('name') ->add('file') ->getForm() ; PDF brought to you by Chapter 34: How to handle File Uploads with Doctrine | 279 generated on June 20, 2012
  • 280. // ... } Next, create this property on your Document class and add some validation rules: Listing // src/Acme/DemoBundle/Entity/Document.php 34-3 // ... class Document { /** * @AssertFile(maxSize="6000000") */ public $file; // ... } As you are using the File constraint, Symfony2 will automatically guess that the form field is a file upload input. That's why you did not have to set it explicitly when creating the form above (->add('file')). The following controller shows you how to handle the entire process: Listing use AcmeDemoBundleEntityDocument; 34-4 use SensioBundleFrameworkExtraBundleConfigurationTemplate; // ... /** * @Template() */ public function uploadAction() { $document = new Document(); $form = $this->createFormBuilder($document) ->add('name') ->add('file') ->getForm() ; if ($this->getRequest()->getMethod() === 'POST') { $form->bindRequest($this->getRequest()); if ($form->isValid()) { $em = $this->getDoctrine()->getEntityManager(); $em->persist($document); $em->flush(); $this->redirect($this->generateUrl('...')); } } return array('form' => $form->createView()); } PDF brought to you by Chapter 34: How to handle File Uploads with Doctrine | 280 generated on June 20, 2012
  • 281. When writing the template, don't forget to set the enctype attribute: <h1>Upload File</h1> Listing 34-5 <form action="#" method="post" {{ form_enctype(form) }}> {{ form_widget(form) }} <input type="submit" value="Upload Document" /> </form> The previous controller will automatically persist the Document entity with the submitted name, but it will do nothing about the file and the path property will be blank. An easy way to handle the file upload is to move it just before the entity is persisted and then set the path property accordingly. Start by calling a new upload() method on the Document class, which you'll create in a moment to handle the file upload: if ($form->isValid()) { Listing 34-6 $em = $this->getDoctrine()->getEntityManager(); $document->upload(); $em->persist($document); $em->flush(); $this->redirect('...'); } The upload() method will take advantage of the UploadedFile1 object, which is what's returned after a file field is submitted: public function upload() Listing 34-7 { // the file property can be empty if the field is not required if (null === $this->file) { return; } // we use the original file name here but you should // sanitize it at least to avoid any security issues // move takes the target directory and then the target filename to move to $this->file->move($this->getUploadRootDir(), $this->file->getClientOriginalName()); // set the path property to the filename where you'ved saved the file $this->path = $this->file->getClientOriginalName(); // clean up the file property as you won't need it anymore $this->file = null; } 1. http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/File/UploadedFile.html PDF brought to you by Chapter 34: How to handle File Uploads with Doctrine | 281 generated on June 20, 2012
  • 282. Using Lifecycle Callbacks Even if this implementation works, it suffers from a major flaw: What if there is a problem when the entity is persisted? The file would have already moved to its final location even though the entity's path property didn't persist correctly. To avoid these issues, you should change the implementation so that the database operation and the moving of the file become atomic: if there is a problem persisting the entity or if the file cannot be moved, then nothing should happen. To do this, you need to move the file right as Doctrine persists the entity to the database. This can be accomplished by hooking into an entity lifecycle callback: Listing /** 34-8 * @ORMEntity * @ORMHasLifecycleCallbacks */ class Document { } Next, refactor the Document class to take advantage of these callbacks: Listing use SymfonyComponentHttpFoundationFileUploadedFile; 34-9 /** * @ORMEntity * @ORMHasLifecycleCallbacks */ class Document { /** * @ORMPrePersist() * @ORMPreUpdate() */ public function preUpload() { if (null !== $this->file) { // do whatever you want to generate a unique name $this->path = uniqid().'.'.$this->file->guessExtension(); } } /** * @ORMPostPersist() * @ORMPostUpdate() */ public function upload() { if (null === $this->file) { return; } // if there is an error when moving the file, an exception will // be automatically thrown by move(). This will properly prevent // the entity from being persisted to the database on error $this->file->move($this->getUploadRootDir(), $this->path); unset($this->file); } PDF brought to you by Chapter 34: How to handle File Uploads with Doctrine | 282 generated on June 20, 2012
  • 283. /** * @ORMPostRemove() */ public function removeUpload() { if ($file = $this->getAbsolutePath()) { unlink($file); } } } The class now does everything you need: it generates a unique filename before persisting, moves the file after persisting, and removes the file if the entity is ever deleted. Now that the moving of the file is handled atomically by the entity, the call to $document->upload() should be removed from the controller: if ($form->isValid()) { Listing 34-10 $em = $this->getDoctrine()->getEntityManager(); $em->persist($document); $em->flush(); $this->redirect('...'); } The @ORMPrePersist() and @ORMPostPersist() event callbacks are triggered before and after the entity is persisted to the database. On the other hand, the @ORMPreUpdate() and @ORMPostUpdate() event callbacks are called when the entity is updated. The PreUpdate and PostUpdate callbacks are only triggered if there is a change in one of the entity's field that are persisted. This means that, by default, if you modify only the $file property, these events will not be triggered, as the property itself is not directly persisted via Doctrine. One solution would be to use an updated field that's persisted to Doctrine, and to modify it manually when changing the file. Using the id as the filename If you want to use the id as the name of the file, the implementation is slightly different as you need to save the extension under the path property, instead of the actual filename: use SymfonyComponentHttpFoundationFileUploadedFile; Listing 34-11 /** * @ORMEntity * @ORMHasLifecycleCallbacks */ class Document { // a property used temporarily while deleting private $filenameForRemove; /** * @ORMPrePersist() * @ORMPreUpdate() PDF brought to you by Chapter 34: How to handle File Uploads with Doctrine | 283 generated on June 20, 2012
  • 284. */ public function preUpload() { if (null !== $this->file) { $this->path = $this->file->guessExtension(); } } /** * @ORMPostPersist() * @ORMPostUpdate() */ public function upload() { if (null === $this->file) { return; } // you must throw an exception here if the file cannot be moved // so that the entity is not persisted to the database // which the UploadedFile move() method does $this->file->move($this->getUploadRootDir(), $this->id.'.'.$this->file->guessExtension()); unset($this->file); } /** * @ORMPreRemove() */ public function storeFilenameForRemove() { $this->filenameForRemove = $this->getAbsolutePath(); } /** * @ORMPostRemove() */ public function removeUpload() { if ($this->filenameForRemove) { unlink($this->filenameForRemove); } } public function getAbsolutePath() { return null === $this->path ? null : $this->getUploadRootDir().'/'.$this->id.'.'.$this->path; } } You'll notice in this case that you need to do a little bit more work in order to remove the file. Before it's removed, you must store the file path (since it depends on the id). Then, once the object has been fully removed from the database, you can safely delete the file (in PostRemove). PDF brought to you by Chapter 34: How to handle File Uploads with Doctrine | 284 generated on June 20, 2012
  • 285. Chapter 35 Doctrine Extensions: Timestampable, Sluggable, Translatable, etc. Doctrine2 is very flexible, and the community has already created a series of useful Doctrine extensions to help you with common entity-related tasks. One library in particular - the DoctrineExtensions1 library - provides integration functionality for Sluggable2, Translatable3, Timestampable4, Loggable5, Tree6 and Sortable7 behaviors. The usage for each of these extensions is explained in that repository. However, to install/activate each extension you must register and activate an Event Listener. To do this, you have two options: 1. Use the StofDoctrineExtensionsBundle8, which integrates the above library. 2. Implement this services directly by following the documentation for integration with Symfony2: Install Gedmo Doctrine2 extensions in Symfony29 1. https://github.com/l3pp4rd/DoctrineExtensions 2. https://github.com/l3pp4rd/DoctrineExtensions/blob/master/doc/sluggable.md 3. https://github.com/l3pp4rd/DoctrineExtensions/blob/master/doc/translatable.md 4. https://github.com/l3pp4rd/DoctrineExtensions/blob/master/doc/timestampable.md 5. https://github.com/l3pp4rd/DoctrineExtensions/blob/master/doc/loggable.md 6. https://github.com/l3pp4rd/DoctrineExtensions/blob/master/doc/tree.md 7. https://github.com/l3pp4rd/DoctrineExtensions/blob/master/doc/sortable.md 8. https://github.com/stof/StofDoctrineExtensionsBundle 9. https://github.com/l3pp4rd/DoctrineExtensions/blob/master/doc/symfony2.md PDF brought to you by Chapter 35: Doctrine Extensions: Timestampable, Sluggable, Translatable, etc. | 285 generated on June 20, 2012
  • 286. Chapter 36 Registering Event Listeners and Subscribers Doctrine packages a rich event system that fires events when almost anything happens inside the system. For you, this means that you can create arbitrary services and tell Doctrine to notify those objects whenever a certain action (e.g. prePersist) happens within Doctrine. This could be useful, for example, to create an independent search index whenever an object in your database is saved. Doctrine defines two types of objects that can listen to Doctrine events: listeners and subscribers. Both are very similar, but listeners are a bit more straightforward. For more, see The Event System1 on Doctrine's website. Configuring the Listener/Subscriber To register a service to act as an event listener or subscriber you just have to tag it with the appropriate name. Depending on your use-case, you can hook a listener into every DBAL connection and ORM entity manager or just into one specific DBAL connection and all the entity managers that use this connection. Listing doctrine: 36-1 dbal: default_connection: default connections: default: driver: pdo_sqlite memory: true services: my.listener: class: AcmeSearchBundleListenerSearchIndexer tags: - { name: doctrine.event_listener, event: postPersist } my.listener2: class: AcmeSearchBundleListenerSearchIndexer2 tags: - { name: doctrine.event_listener, event: postPersist, connection: default } my.subscriber: 1. http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/reference/events.html PDF brought to you by Chapter 36: Registering Event Listeners and Subscribers | 286 generated on June 20, 2012
  • 287. class: AcmeSearchBundleListenerSearchIndexerSubscriber tags: - { name: doctrine.event_subscriber, connection: default } Creating the Listener Class In the previous example, a service my.listener was configured as a Doctrine listener on the event postPersist. That class behind that service must have a postPersist method, which will be called when the event is thrown: // src/Acme/SearchBundle/Listener/SearchIndexer.php Listing 36-2 namespace AcmeSearchBundleListener; use DoctrineORMEventLifecycleEventArgs; use AcmeStoreBundleEntityProduct; class SearchIndexer { public function postPersist(LifecycleEventArgs $args) { $entity = $args->getEntity(); $entityManager = $args->getEntityManager(); // perhaps you only want to act on some "Product" entity if ($entity instanceof Product) { // do something with the Product } } } In each event, you have access to a LifecycleEventArgs object, which gives you access to both the entity object of the event and the entity manager itself. One important thing to notice is that a listener will be listening for all entities in your application. So, if you're interested in only handling a specific type of entity (e.g. a Product entity but not a BlogPost entity), you should check for the class name of the entity in your method (as shown above). PDF brought to you by Chapter 36: Registering Event Listeners and Subscribers | 287 generated on June 20, 2012
  • 288. Chapter 37 How to use Doctrine's DBAL Layer This article is about Doctrine DBAL's layer. Typically, you'll work with the higher level Doctrine ORM layer, which simply uses the DBAL behind the scenes to actually communicate with the database. To read more about the Doctrine ORM, see "Databases and Doctrine". The Doctrine1 Database Abstraction Layer (DBAL) is an abstraction layer that sits on top of PDO2 and offers an intuitive and flexible API for communicating with the most popular relational databases. In other words, the DBAL library makes it easy to execute queries and perform other database actions. Read the official Doctrine DBAL Documentation3 to learn all the details and capabilities of Doctrine's DBAL library. To get started, configure the database connection parameters: Listing # app/config/config.yml 37-1 doctrine: dbal: driver: pdo_mysql dbname: Symfony2 user: root password: null charset: UTF8 For full DBAL configuration options, see Doctrine DBAL Configuration. You can then access the Doctrine DBAL connection by accessing the database_connection service: Listing class UserController extends Controller 37-2 { public function indexAction() 1. http://www.doctrine-project.org 2. http://www.php.net/pdo 3. http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/index.html PDF brought to you by Chapter 37: How to use Doctrine's DBAL Layer | 288 generated on June 20, 2012
  • 289. { $conn = $this->get('database_connection'); $users = $conn->fetchAll('SELECT * FROM users'); // ... } } Registering Custom Mapping Types You can register custom mapping types through Symfony's configuration. They will be added to all configured connections. For more information on custom mapping types, read Doctrine's Custom Mapping Types4 section of their documentation. # app/config/config.yml Listing 37-3 doctrine: dbal: types: custom_first: AcmeHelloBundleTypeCustomFirst custom_second: AcmeHelloBundleTypeCustomSecond Registering Custom Mapping Types in the SchemaTool The SchemaTool is used to inspect the database to compare the schema. To achieve this task, it needs to know which mapping type needs to be used for each database types. Registering new ones can be done through the configuration. Let's map the ENUM type (not suppoorted by DBAL by default) to a the string mapping type: # app/config/config.yml Listing 37-4 doctrine: dbal: connections: default: // Other connections parameters mapping_types: enum: string 4. http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/types.html#custom-mapping-types PDF brought to you by Chapter 37: How to use Doctrine's DBAL Layer | 289 generated on June 20, 2012
  • 290. Chapter 38 How to generate Entities from an Existing Database When starting work on a brand new project that uses a database, two different situations comes naturally. In most cases, the database model is designed and built from scratch. Sometimes, however, you'll start with an existing and probably unchangeable database model. Fortunately, Doctrine comes with a bunch of tools to help generate model classes from your existing database. As the Doctrine tools documentation1 says, reverse engineering is a one-time process to get started on a project. Doctrine is able to convert approximately 70-80% of the necessary mapping information based on fields, indexes and foreign key constraints. Doctrine can't discover inverse associations, inheritance types, entities with foreign keys as primary keys or semantical operations on associations such as cascade or lifecycle events. Some additional work on the generated entities will be necessary afterwards to design each to fit your domain model specificities. This tutorial assumes you're using a simple blog application with the following two tables: blog_post and blog_comment. A comment record is linked to a post record thanks to a foreign key constraint. Listing CREATE TABLE `blog_post` ( 38-1 `id` bigint(20) NOT NULL AUTO_INCREMENT, `title` varchar(100) COLLATE utf8_unicode_ci NOT NULL, `content` longtext COLLATE utf8_unicode_ci NOT NULL, `created_at` datetime NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; CREATE TABLE `blog_comment` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `post_id` bigint(20) NOT NULL, `author` varchar(20) COLLATE utf8_unicode_ci NOT NULL, `content` longtext COLLATE utf8_unicode_ci NOT NULL, `created_at` datetime NOT NULL, PRIMARY KEY (`id`), 1. http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/reference/tools.html#reverse-engineering PDF brought to you by Chapter 38: How to generate Entities from an Existing Database | 290 generated on June 20, 2012
  • 291. KEY `blog_comment_post_id_idx` (`post_id`), CONSTRAINT `blog_post_id` FOREIGN KEY (`post_id`) REFERENCES `blog_post` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; Before diving into the recipe, be sure your database connection parameters are correctly setup in the app/config/parameters.ini file (or wherever your database configuration is kept) and that you have initialized a bundle that will host your future entity class. In this tutorial, we will assume that an AcmeBlogBundle exists and is located under the src/Acme/BlogBundle folder. The first step towards building entity classes from an existing database is to ask Doctrine to introspect the database and generate the corresponding metadata files. Metadata files describe the entity class to generate based on tables fields. php app/console doctrine:mapping:convert xml ./src/Acme/BlogBundle/Resources/config/doctrine/ Listing 38-2 metadata/orm --from-database --force This command line tool asks Doctrine to introspect the database and generate the XML metadata files under the src/Acme/BlogBundle/Resources/config/doctrine/metadata/orm folder of your bundle. It's also possible to generate metadata class in YAML format by changing the first argument to yml. The generated BlogPost.dcm.xml metadata file looks as follows: <?xml version="1.0" encoding="utf-8"?> Listing 38-3 <doctrine-mapping> <entity name="BlogPost" table="blog_post"> <change-tracking-policy>DEFERRED_IMPLICIT</change-tracking-policy> <id name="id" type="bigint" column="id"> <generator strategy="IDENTITY"/> </id> <field name="title" type="string" column="title" length="100"/> <field name="content" type="text" column="content"/> <field name="isPublished" type="boolean" column="is_published"/> <field name="createdAt" type="datetime" column="created_at"/> <field name="updatedAt" type="datetime" column="updated_at"/> <field name="slug" type="string" column="slug" length="255"/> <lifecycle-callbacks/> </entity> </doctrine-mapping> Once the metadata files are generated, you can ask Doctrine to import the schema and build related entity classes by executing the following two commands. php app/console doctrine:mapping:import AcmeBlogBundle annotation Listing 38-4 php app/console doctrine:generate:entities AcmeBlogBundle The first command generates entity classes with an annotations mapping, but you can of course change the annotation argument to xml or yml. The newly created BlogComment entity class looks as follow: <?php Listing 38-5 // src/Acme/BlogBundle/Entity/BlogComment.php namespace AcmeBlogBundleEntity; use DoctrineORMMapping as ORM; PDF brought to you by Chapter 38: How to generate Entities from an Existing Database | 291 generated on June 20, 2012
  • 292. /** * AcmeBlogBundleEntityBlogComment * * @ORMTable(name="blog_comment") * @ORMEntity */ class BlogComment { /** * @var bigint $id * * @ORMColumn(name="id", type="bigint", nullable=false) * @ORMId * @ORMGeneratedValue(strategy="IDENTITY") */ private $id; /** * @var string $author * * @ORMColumn(name="author", type="string", length=100, nullable=false) */ private $author; /** * @var text $content * * @ORMColumn(name="content", type="text", nullable=false) */ private $content; /** * @var datetime $createdAt * * @ORMColumn(name="created_at", type="datetime", nullable=false) */ private $createdAt; /** * @var BlogPost * * @ORMManyToOne(targetEntity="BlogPost") * @ORMJoinColumn(name="post_id", referencedColumnName="id") */ private $post; } As you can see, Doctrine converts all table fields to pure private and annotated class properties. The most impressive thing is that it also discovered the relationship with the BlogPost entity class based on the foreign key constraint. Consequently, you can find a private $post property mapped with a BlogPost entity in the BlogComment entity class. The last command generated all getters and setters for your two BlogPost and BlogComment entity class properties. The generated entities are now ready to be used. Have fun! PDF brought to you by Chapter 38: How to generate Entities from an Existing Database | 292 generated on June 20, 2012
  • 293. Chapter 39 How to work with Multiple Entity Managers You can use multiple entity managers in a Symfony2 application. This is necessary if you are using different databases or even vendors with entirely different sets of entities. In other words, one entity manager that connects to one database will handle some entities while another entity manager that connects to another database might handle the rest. Using multiple entity managers is pretty easy, but more advanced and not usually required. Be sure you actually need multiple entity managers before adding in this layer of complexity. The following configuration code shows how you can configure two entity managers: doctrine: Listing 39-1 orm: default_entity_manager: default entity_managers: default: connection: default mappings: AcmeDemoBundle: ~ AcmeStoreBundle: ~ customer: connection: customer mappings: AcmeCustomerBundle: ~ In this case, you've defined two entity managers and called them default and customer. The default entity manager manages entities in the AcmeDemoBundle and AcmeStoreBundle, while the customer entity manager manages entities in the AcmeCustomerBundle. When working with multiple entity managers, you should be explicit about which entity manager you want. If you do omit the entity manager's name when asking for it, the default entity manager (i.e. default) is returned: class UserController extends Controller Listing 39-2 { public function indexAction() PDF brought to you by Chapter 39: How to work with Multiple Entity Managers | 293 generated on June 20, 2012
  • 294. { // both return the "default" em $em = $this->get('doctrine')->getEntityManager(); $em = $this->get('doctrine')->getEntityManager('default'); $customerEm = $this->get('doctrine')->getEntityManager('customer'); } } You can now use Doctrine just as you did before - using the default entity manager to persist and fetch entities that it manages and the customer entity manager to persist and fetch its entities. PDF brought to you by Chapter 39: How to work with Multiple Entity Managers | 294 generated on June 20, 2012
  • 295. Chapter 40 Registering Custom DQL Functions Doctrine allows you to specify custom DQL functions. For more information on this topic, read Doctrine's cookbook article "DQL User Defined Functions1". In Symfony, you can register your custom DQL functions as follows: # app/config/config.yml Listing 40-1 doctrine: orm: # ... entity_managers: default: # ... dql: string_functions: test_string: AcmeHelloBundleDQLStringFunction second_string: AcmeHelloBundleDQLSecondStringFunction numeric_functions: test_numeric: AcmeHelloBundleDQLNumericFunction datetime_functions: test_datetime: AcmeHelloBundleDQLDatetimeFunction 1. http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/cookbook/dql-user-defined-functions.html PDF brought to you by Chapter 40: Registering Custom DQL Functions | 295 generated on June 20, 2012
  • 296. Chapter 41 How to customize Form Rendering Symfony gives you a wide variety of ways to customize how a form is rendered. In this guide, you'll learn how to customize every possible part of your form with as little effort as possible whether you use Twig or PHP as your templating engine. Form Rendering Basics Recall that the label, error and HTML widget of a form field can easily be rendered by using the form_row Twig function or the row PHP helper method: Listing {{ form_row(form.age) }} 41-1 You can also render each of the three parts of the field individually: Listing <div> 41-2 {{ form_label(form.age) }} {{ form_errors(form.age) }} {{ form_widget(form.age) }} </div> In both cases, the form label, errors and HTML widget are rendered by using a set of markup that ships standard with Symfony. For example, both of the above templates would render: Listing <div> 41-3 <label for="form_age">Age</label> <ul> <li>This field is required</li> </ul> <input type="number" id="form_age" name="form[age]" /> </div> To quickly prototype and test a form, you can render the entire form with just one line: Listing {{ form_widget(form) }} 41-4 PDF brought to you by Chapter 41: How to customize Form Rendering | 296 generated on June 20, 2012
  • 297. The remainder of this recipe will explain how every part of the form's markup can be modified at several different levels. For more information about form rendering in general, see Rendering a Form in a Template. What are Form Themes? Symfony uses form fragments - a small piece of a template that renders just one part of a form - to render every part of a form - - field labels, errors, input text fields, select tags, etc The fragments are defined as blocks in Twig and as template files in PHP. A theme is nothing more than a set of fragments that you want to use when rendering a form. In other words, if you want to customize one portion of how a form is rendered, you'll import a theme which contains a customization of the appropriate form fragments. Symfony comes with a default theme (form_div_layout.html.twig1 in Twig and FrameworkBundle:Form in PHP) that defines each and every fragment needed to render every part of a form. In the next section you will learn how to customize a theme by overriding some or all of its fragments. For example, when the widget of a integer type field is rendered, an input number field is generated {{ form_widget(form.age) }} Listing 41-5 renders: <input type="number" id="form_age" name="form[age]" required="required" value="33" /> Listing 41-6 Internally, Symfony uses the integer_widget fragment to render the field. This is because the field type is integer and you're rendering its widget (as opposed to its label or errors). In Twig that would default to the block integer_widget from the form_div_layout.html.twig2 template. In PHP it would rather be the integer_widget.html.php file located in FrameworkBundle/Resources/ views/Form folder. The default implementation of the integer_widget fragment looks like this: {% block integer_widget %} Listing 41-7 {% set type = type|default('number') %} {{ block('field_widget') }} {% endblock integer_widget %} As you can see, this fragment itself renders another fragment - field_widget: {% block field_widget %} Listing 41-8 {% set type = type|default('text') %} <input type="{{ type }}" {{ block('widget_attributes') }} value="{{ value }}" /> {% endblock field_widget %} The point is, the fragments dictate the HTML output of each part of a form. To customize the form output, you just need to identify and override the correct fragment. A set of these form fragment customizations is known as a form "theme". When rendering a form, you can choose which form theme(s) you want to apply. In Twig a theme is a single template file and the fragments are the blocks defined in this file. In PHP a theme is a folder and the the fragments are individual template files in this folder. 1. https://github.com/symfony/symfony/blob/master/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig 2. https://github.com/symfony/symfony/blob/master/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig PDF brought to you by Chapter 41: How to customize Form Rendering | 297 generated on June 20, 2012
  • 298. Knowing which block to customize In this example, the customized fragment name is integer_widget because you want to override the HTML widget for all integer field types. If you need to customize textarea fields, you would customize textarea_widget. As you can see, the fragment name is a combination of the field type and which part of the field is being rendered (e.g. widget, label, errors, row). As such, to customize how errors are rendered for just input text fields, you should customize the text_errors fragment. More commonly, however, you'll want to customize how errors are displayed across all fields. You can do this by customizing the field_errors fragment. This takes advantage of field type inheritance. Specifically, since the text type extends from the field type, the form component will first look for the type-specific fragment (e.g. text_errors) before falling back to its parent fragment name if it doesn't exist (e.g. field_errors). For more information on this topic, see Form Fragment Naming. Form Theming To see the power of form theming, suppose you want to wrap every input number field with a div tag. The key to doing this is to customize the integer_widget fragment. Form Theming in Twig When customizing the form field block in Twig, you have two options on where the customized form block can live: Method Pros Cons Inside the same template as the Quick and easy Can't be reused in other templates form Inside a separate template Can be reused by many Requires an extra template to be templates created Both methods have the same effect but are better in different situations. Method 1: Inside the same Template as the Form The easiest way to customize the integer_widget block is to customize it directly in the template that's actually rendering the form. Listing {% extends '::base.html.twig' %} 41-9 {% form_theme form _self %} {% block integer_widget %} <div class="integer_widget"> {% set type = type|default('number') %} {{ block('field_widget') }} </div> {% endblock %} {% block content %} PDF brought to you by Chapter 41: How to customize Form Rendering | 298 generated on June 20, 2012
  • 299. {# render the form #} {{ form_row(form.age) }} {% endblock %} By using the special {% form_theme form _self %} tag, Twig looks inside the same template for any overridden form blocks. Assuming the form.age field is an integer type field, when its widget is rendered, the customized integer_widget block will be used. The disadvantage of this method is that the customized form block can't be reused when rendering other forms in other templates. In other words, this method is most useful when making form customizations that are specific to a single form in your application. If you want to reuse a form customization across several (or all) forms in your application, read on to the next section. Method 2: Inside a Separate Template You can also choose to put the customized integer_widget form block in a separate template entirely. The code and end-result are the same, but you can now re-use the form customization across many templates: {# src/Acme/DemoBundle/Resources/views/Form/fields.html.twig #} Listing 41-10 {% block integer_widget %} <div class="integer_widget"> {% set type = type|default('number') %} {{ block('field_widget') }} </div> {% endblock %} Now that you've created the customized form block, you need to tell Symfony to use it. Inside the template where you're actually rendering your form, tell Symfony to use the template via the form_theme tag: {% form_theme form 'AcmeDemoBundle:Form:fields.html.twig' %} Listing 41-11 {{ form_widget(form.age) }} When the form.age widget is rendered, Symfony will use the integer_widget block from the new template and the input tag will be wrapped in the div element specified in the customized block. Form Theming in PHP When using PHP as a templating engine, the only method to customize a fragment is to create a new template file - this is similar to the second method used by Twig. The template file must be named after the fragment. You must create a integer_widget.html.php file in order to customize the integer_widget fragment. <!-- src/Acme/DemoBundle/Resources/views/Form/integer_widget.html.php --> Listing 41-12 <div class="integer_widget"> <?php echo $view['form']->renderBlock('field_widget', array('type' => isset($type) ? $type : "number")) ?> </div> PDF brought to you by Chapter 41: How to customize Form Rendering | 299 generated on June 20, 2012
  • 300. Now that you've created the customized form template, you need to tell Symfony to use it. Inside the template where you're actually rendering your form, tell Symfony to use the theme via the setTheme helper method: Listing <?php $view['form']->setTheme($form, array('AcmeDemoBundle:Form')) ;?> 41-13 <?php $view['form']->widget($form['age']) ?> When the form.age widget is rendered, Symfony will use the customized integer_widget.html.php template and the input tag will be wrapped in the div element. Referencing Base Form Blocks (Twig specific) So far, to override a particular form block, the best method is to copy the default block from form_div_layout.html.twig3, paste it into a different template, and the customize it. In many cases, you can avoid doing this by referencing the base block when customizing it. This is easy to do, but varies slightly depending on if your form block customizations are in the same template as the form or a separate template. Referencing Blocks from inside the same Template as the Form Import the blocks by adding a use tag in the template where you're rendering the form: Listing {% use 'form_div_layout.html.twig' with integer_widget as base_integer_widget %} 41-14 Now, when the blocks from form_div_layout.html.twig4 are imported, the integer_widget block is called base_integer_widget. This means that when you redefine the integer_widget block, you can reference the default markup via base_integer_widget: Listing {% block integer_widget %} 41-15 <div class="integer_widget"> {{ block('base_integer_widget') }} </div> {% endblock %} Referencing Base Blocks from an External Template If your form customizations live inside an external template, you can reference the base block by using the parent() Twig function: Listing {# src/Acme/DemoBundle/Resources/views/Form/fields.html.twig #} 41-16 {% extends 'form_div_layout.html.twig' %} {% block integer_widget %} <div class="integer_widget"> {{ parent() }} </div> {% endblock %} 3. https://github.com/symfony/symfony/blob/master/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig 4. https://github.com/symfony/symfony/blob/master/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig PDF brought to you by Chapter 41: How to customize Form Rendering | 300 generated on June 20, 2012
  • 301. It is not possible to reference the base block when using PHP as the templating engine. You have to manually copy the content from the base block to your new template file. Making Application-wide Customizations 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: Twig By using the following configuration, any customized form blocks inside the AcmeDemoBundle:Form:fields.html.twig template will be used globally when a form is rendered. # app/config/config.yml Listing 41-17 twig: form: resources: - 'AcmeDemoBundle:Form:fields.html.twig' # ... By default, Twig uses a div layout when rendering forms. Some people, however, may prefer to render forms in a table layout. Use the form_table_layout.html.twig resource to use such a layout: # app/config/config.yml Listing 41-18 twig: form: resources: ['form_table_layout.html.twig'] # ... If you only want to make the change in one template, add the following line to your template file rather than adding the template as a resource: {% form_theme form 'form_table_layout.html.twig' %} Listing 41-19 Note that the form variable in the above code is the form view variable that you passed to your template. PHP By using the following configuration, any customized form fragments inside the src/Acme/DemoBundle/ Resources/views/Form folder will be used globally when a form is rendered. # app/config/config.yml Listing 41-20 framework: templating: form: resources: - 'AcmeDemoBundle:Form' # ... PDF brought to you by Chapter 41: How to customize Form Rendering | 301 generated on June 20, 2012
  • 302. By default, the PHP engine uses a div layout when rendering forms. Some people, however, may prefer to render forms in a table layout. Use the FrameworkBundle:FormTable resource to use such a layout: Listing # app/config/config.yml 41-21 framework: templating: form: resources: - 'FrameworkBundle:FormTable' If you only want to make the change in one template, add the following line to your template file rather than adding the template as a resource: Listing <?php $view['form']->setTheme($form, array('FrameworkBundle:FormTable')); ?> 41-22 Note that the $form variable in the above code is the form view variable that you passed to your template. How to customize an Individual field So far, you've seen the different ways you can customize the widget output of all text field types. You can also customize individual fields. For example, suppose you have two text fields - first_name and last_name - but you only want to customize one of the fields. This can be accomplished by customizing a fragment whose name is a combination of the field id attribute and which part of the field is being customized. For example: Listing {% form_theme form _self %} 41-23 {% block _product_name_widget %} <div class="text_widget"> {{ block('field_widget') }} </div> {% endblock %} {{ form_widget(form.name) }} Here, the _product_name_widget fragment defines the template to use for the field whose id is product_name (and name is product[name]). The product portion of the field is the form name, which may be set manually or generated automatically based on your form type name (e.g. ProductType equates to product). If you're not sure what your form name is, just view the source of your generated form. You can also override the markup for an entire field row using the same method: Listing {% form_theme form _self %} 41-24 {% block _product_name_row %} <div class="name_row"> {{ form_label(form) }} {{ form_errors(form) }} {{ form_widget(form) }} </div> {% endblock %} PDF brought to you by Chapter 41: How to customize Form Rendering | 302 generated on June 20, 2012
  • 303. Other Common Customizations So far, this recipe has shown you several different ways to customize a single piece of how a form is rendered. The key is to customize a specific fragment that corresponds to the portion of the form you want to control (see naming form blocks). In the next sections, you'll see how you can make several common form customizations. To apply these customizations, use one of the methods described in the Form Theming section. Customizing Error Output The form component only handles how the validation errors are rendered, and not the actual validation error messages. The error messages themselves are determined by the validation constraints you apply to your objects. For more information, see the chapter on validation. There are many different ways to customize how errors are rendered when a form is submitted with errors. The error messages for a field are rendered when you use the form_errors helper: {{ form_errors(form.age) }} Listing 41-25 By default, the errors are rendered inside an unordered list: <ul> Listing 41-26 <li>This field is required</li> </ul> To override how errors are rendered for all fields, simply copy, paste and customize the field_errors fragment. {% block field_errors %} Listing 41-27 {% spaceless %} {% if errors|length > 0 %} <ul class="error_list"> {% for error in errors %} <li>{{ error.messageTemplate|trans(error.messageParameters, 'validators') }}</li> {% endfor %} </ul> {% endif %} {% endspaceless %} {% endblock field_errors %} See Form Theming for how to apply this customization. You can also customize the error output for just one specific field type. For example, certain errors that are more global to your form (i.e. not specific to just one field) are rendered separately, usually at the top of your form: {{ form_errors(form) }} Listing 41-28 To customize only the markup used for these errors, follow the same directions as above, but now call the block form_errors (Twig) / the file form_errors.html.php (PHP). Now, when errors for the form type are rendered, your customized fragment will be used instead of the default field_errors. PDF brought to you by Chapter 41: How to customize Form Rendering | 303 generated on June 20, 2012
  • 304. Customizing the "Form Row" When you can manage it, the easiest way to render a form field is via the form_row function, which renders the label, errors and HTML widget of a field. To customize the markup used for rendering all form field rows, override the field_row fragment. For example, suppose you want to add a class to the div element around each row: Listing {% block field_row %} 41-29 <div class="form_row"> {{ form_label(form) }} {{ form_errors(form) }} {{ form_widget(form) }} </div> {% endblock field_row %} See Form Theming for how to apply this customization. Adding a "Required" Asterisk to Field Labels If you want to denote all of your required fields with a required asterisk (*), you can do this by customizing the field_label fragment. In Twig, if you're making the form customization inside the same template as your form, modify the use tag and add the following: Listing {% use 'form_div_layout.html.twig' with field_label as base_field_label %} 41-30 {% block field_label %} {{ block('base_field_label') }} {% if required %} <span class="required" title="This field is required">*</span> {% endif %} {% endblock %} In Twig, if you're making the form customization inside a separate template, use the following: Listing {% extends 'form_div_layout.html.twig' %} 41-31 {% block field_label %} {{ parent() }} {% if required %} <span class="required" title="This field is required">*</span> {% endif %} {% endblock %} When using PHP as a templating engine you have to copy the content from the original template: Listing <!-- field_label.html.php --> 41-32 <!-- original content --> <label for="<?php echo $view->escape($id) ?>" <?php foreach($attr as $k => $v) { printf('%s="%s" ', $view->escape($k), $view->escape($v)); } ?>><?php echo $view->escape($view['translator']->trans($label)) ?></label> PDF brought to you by Chapter 41: How to customize Form Rendering | 304 generated on June 20, 2012
  • 305. <!-- customization --> <?php if ($required) : ?> <span class="required" title="This field is required">*</span> <?php endif ?> See Form Theming for how to apply this customization. Adding "help" messages You can also customize your form widgets to have an optional "help" message. In Twig, If you're making the form customization inside the same template as your form, modify the use tag and add the following: {% use 'form_div_layout.html.twig' with field_widget as base_field_widget %} Listing 41-33 {% block field_widget %} {{ block('base_field_widget') }} {% if help is defined %} <span class="help">{{ help }}</span> {% endif %} {% endblock %} In twig, If you're making the form customization inside a separate template, use the following: {% extends 'form_div_layout.html.twig' %} Listing 41-34 {% block field_widget %} {{ parent() }} {% if help is defined %} <span class="help">{{ help }}</span> {% endif %} {% endblock %} When using PHP as a templating engine you have to copy the content from the original template: <!-- field_widget.html.php --> Listing 41-35 <!-- Original content --> <input type="<?php echo isset($type) ? $view->escape($type) : "text" ?>" value="<?php echo $view->escape($value) ?>" <?php echo $view['form']->renderBlock('attributes') ?> /> <!-- Customization --> <?php if (isset($help)) : ?> <span class="help"><?php echo $view->escape($help) ?></span> <?php endif ?> To render a help message below a field, pass in a help variable: {{ form_widget(form.title, { 'help': 'foobar' }) }} Listing 41-36 PDF brought to you by Chapter 41: How to customize Form Rendering | 305 generated on June 20, 2012
  • 306. See Form Theming for how to apply this customization. PDF brought to you by Chapter 41: How to customize Form Rendering | 306 generated on June 20, 2012
  • 307. Chapter 42 Using Data Transformers You'll often find the need to transform the data the user entered in a form into something else for use in your program. You could easily do this manually in your controller, but what if you want to use this specific form in different places? Say you have a one-to-one relation of Task to Issue, e.g. a Task optionally has an issue linked to it. Adding a listbox with all possible issues can eventually lead to a really long listbox in which it is impossible to find something. You'll rather want to add a textbox, in which the user can simply enter the number of the issue. In the controller you can convert this issue number to an actual task, and eventually add errors to the form if it was not found, but of course this is not really clean. It would be better if this issue was automatically looked up and converted to an Issue object, for use in your action. This is where Data Transformers come into play. First, create a custom form type which has a Data Transformer attached to it, which returns the Issue by number: the issue selector type. Eventually this will simply be a text field, as we configure the fields' parent to be a "text" field, in which you will enter the issue number. The field will display an error if a non existing number was entered: // src/Acme/TaskBundle/Form/Type/IssueSelectorType.php Listing 42-1 namespace AcmeTaskBundleFormType; use SymfonyComponentFormAbstractType; use SymfonyComponentFormFormBuilder; use AcmeTaskBundleFormDataTransformerIssueToNumberTransformer; use DoctrineCommonPersistenceObjectManager; class IssueSelectorType extends AbstractType { /** * @var ObjectManager */ private $om; /** * @param ObjectManager $om */ public function __construct(ObjectManager $om) { PDF brought to you by Chapter 42: Using Data Transformers | 307 generated on June 20, 2012
  • 308. $this->om = $om; } public function buildForm(FormBuilder $builder, array $options) { $transformer = new IssueToNumberTransformer($this->om); $builder->appendClientTransformer($transformer); } public function getDefaultOptions(array $options) { return array( 'invalid_message' => 'The selected issue does not exist', ); } public function getParent(array $options) { return 'text'; } public function getName() { return 'issue_selector'; } } You can also use transformers without creating a new custom form type by calling appendClientTransformer on any field builder: Listing use AcmeTaskBundleFormDataTransformerIssueToNumberTransformer; 42-2 class TaskType extends AbstractType { public function buildForm(FormBuilder $builder, array $options) { // ... // this assumes that the entity manager was passed in as an option $entityManager = $options['em']; $transformer = new IssueToNumberTransformer($entityManager); // use a normal text field, but transform the text into an issue object $builder ->add('issue', 'text') ->appendClientTransformer($transformer) ; } // ... } Next, we create the data transformer, which does the actual conversion: Listing // src/Acme/TaskBundle/Form/DataTransformer/IssueToNumberTransformer.php 42-3 namespace AcmeTaskBundleFormDataTransformer; use SymfonyComponentFormDataTransformerInterface; PDF brought to you by Chapter 42: Using Data Transformers | 308 generated on June 20, 2012
  • 309. use SymfonyComponentFormExceptionTransformationFailedException; use DoctrineCommonPersistenceObjectManager; use AcmeTaskBundleEntityIssue; class IssueToNumberTransformer implements DataTransformerInterface { /** * @var ObjectManager */ private $om; /** * @param ObjectManager $om */ public function __construct(ObjectManager $om) { $this->om = $om; } /** * Transforms an object (issue) to a string (number). * * @param Issue|null $issue * @return string */ public function transform($issue) { if (null === $issue) { return ""; } return $issue->getNumber(); } /** * Transforms a string (number) to an object (issue). * * @param string $number * @return Issue|null * @throws TransformationFailedException if object (issue) is not found. */ public function reverseTransform($number) { if (!$number) { return null; } $issue = $this->om ->getRepository('AcmeTaskBundle:Issue') ->findOneBy(array('number' => $number)) ; if (null === $issue) { throw new TransformationFailedException(sprintf( 'An issue with number "%s" does not exist!', $number )); } return $issue; PDF brought to you by Chapter 42: Using Data Transformers | 309 generated on June 20, 2012
  • 310. } } Finally, since we've decided to create a custom form type that uses the data transformer, register the Type in the service container, so that the entity manager can be automatically injected: Listing services: 42-4 acme_demo.type.issue_selector: class: AcmeTaskBundleFormTypeIssueSelectorType arguments: ["@doctrine.orm.entity_manager"] tags: - { name: form.type, alias: issue_selector } You can now add the type to your form by its alias as follows: Listing // src/Acme/TaskBundle/Form/Type/TaskType.php 42-5 namespace AcmeTaskBundleFormType; use SymfonyComponentFormAbstractType; use SymfonyComponentFormFormBuilder; class TaskType extends AbstractType { public function buildForm(FormBuilder $builder, array $options) { $builder ->add('task') ->add('dueDate', null, array('widget' => 'single_text')); ->add('issue', 'issue_selector') ; } public function getName() { return 'task'; } } Now it will be very easy at any random place in your application to use this selector type to select an issue by number. No logic has to be added to your Controller at all. If you want a new issue to be created when an unknown number is entered, you can instantiate it rather than throwing the TransformationFailedException, and even persist it to your entity manager if the task has no cascading options for the issue. PDF brought to you by Chapter 42: Using Data Transformers | 310 generated on June 20, 2012
  • 311. Chapter 43 How to Dynamically Generate Forms Using Form Events Before jumping right into dynamic form generation, let's have a quick review of what a bare form class looks like: //src/Acme/DemoBundle/Form/ProductType.php Listing 43-1 namespace AcmeDemoBundleForm; use SymfonyComponentFormAbstractType; use SymfonyComponentFormFormBuilder; class ProductType extends AbstractType { public function buildForm(FormBuilder $builder, array $options) { $builder->add('name'); $builder->add('price'); } public function getName() { return 'product'; } } If this particular section of code isn't already familiar to you, you probably need to take a step back and first review the Forms chapter before proceeding. Let's assume for a moment that this form utilizes an imaginary "Product" class that has only two relevant properties ("name" and "price"). The form generated from this class will look the exact same regardless of a new Product is being created or if an existing product is being edited (e.g. a product fetched from the database). PDF brought to you by Chapter 43: How to Dynamically Generate Forms Using Form Events | 311 generated on June 20, 2012
  • 312. Suppose now, that you don't want the user to be able to change the name value once the object has been created. To do this, you can rely on Symfony's Event Dispatcher system to analyze the data on the object and modify the form based on the Product object's data. In this entry, you'll learn how to add this level of flexibility to your forms. Adding An Event Subscriber To A Form Class So, instead of directly adding that "name" widget via our ProductType form class, let's delegate the responsibility of creating that particular field to an Event Subscriber: Listing //src/Acme/DemoBundle/Form/ProductType.php 43-2 namespace AcmeDemoBundleForm use SymfonyComponentFormAbstractType use SymfonyComponentFormFormBuilder; use AcmeDemoBundleFormEventListenerAddNameFieldSubscriber; class ProductType extends AbstractType { public function buildForm(FormBuilder $builder, array $options) { $subscriber = new AddNameFieldSubscriber($builder->getFormFactory()); $builder->addEventSubscriber($subscriber); $builder->add('price'); } public function getName() { return 'product'; } } The event subscriber is passed the FormFactory object in its constructor so that our new subscriber is capable of creating the form widget once it is notified of the dispatched event during form creation. Inside the Event Subscriber Class The goal is to create a "name" field only if the underlying Product object is new (e.g. hasn't been persisted to the database). Based on that, the subscriber might look like the following: Listing // src/Acme/DemoBundle/Form/EventListener/AddNameFieldSubscriber.php 43-3 namespace AcmeDemoBundleFormEventListener; use SymfonyComponentFormEventDataEvent; use SymfonyComponentFormFormFactoryInterface; use SymfonyComponentEventDispatcherEventSubscriberInterface; use SymfonyComponentFormFormEvents; class AddNameFieldSubscriber implements EventSubscriberInterface { private $factory; public function __construct(FormFactoryInterface $factory) { $this->factory = $factory; } PDF brought to you by Chapter 43: How to Dynamically Generate Forms Using Form Events | 312 generated on June 20, 2012
  • 313. public static function getSubscribedEvents() { // Tells the dispatcher that we want to listen on the form.pre_set_data // event and that the preSetData method should be called. return array(FormEvents::PRE_SET_DATA => 'preSetData'); } public function preSetData(DataEvent $event) { $data = $event->getData(); $form = $event->getForm(); // During form creation setData() is called with null as an argument // by the FormBuilder constructor. We're only concerned with when // setData is called with an actual Entity object in it (whether new, // or fetched with Doctrine). This if statement let's us skip right // over the null condition. if (null === $data) { return; } // check if the product object is "new" if (!$data->getId()) { $form->add($this->factory->createNamed('text', 'name')); } } } It is easy to misunderstand the purpose of the if (null === $data) segment of this event subscriber. To fully understand its role, you might consider also taking a look at the Form class1 and paying special attention to where setData() is called at the end of the constructor, as well as the setData() method itself. The FormEvents::PRE_SET_DATA line actually resolves to the string form.pre_set_data. The FormEvents class2 serves an organizational purpose. It is a centralized location in which you can find all of the various form events available. While this example could have used the form.set_data event or even the form.post_set_data events just as effectively, by using form.pre_set_data we guarantee that the data being retrieved from the Event object has in no way been modified by any other subscribers or listeners. This is because form.pre_set_data passes a DataEvent3 object instead of the FilterDataEvent4 object passed by the form.set_data event. DataEvent5, unlike its child FilterDataEvent6, lacks a setData() method. You may view the full list of form events via the FormEvents class7, found in the form bundle. 1. https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Form.php 2. https://github.com/symfony/Form/blob/master/FormEvents.php 3. https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Event/DataEvent.php 4. https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Event/FilterDataEvent.php 5. https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Event/DataEvent.php 6. https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Event/FilterDataEvent.php 7. https://github.com/symfony/Form/blob/master/FormEvents.php PDF brought to you by Chapter 43: How to Dynamically Generate Forms Using Form Events | 313 generated on June 20, 2012
  • 314. Chapter 44 How to Embed a Collection of Forms In this entry, you'll learn how to create a form that embeds a collection of many other forms. This could be useful, for example, if you had a Task class and you wanted to edit/create/remove many Tag objects related to that Task, right inside the same form. In this entry, we'll loosely assume that you're using Doctrine as your database store. But if you're not using Doctrine (e.g. Propel or just a database connection), it's all very similar. There are only a few parts of this tutorial that really care about "persistence". If you are using Doctrine, you'll need to add the Doctrine metadata, including the ManyToMany on the Task's tags property. Let's start there: suppose that each Task belongs to multiple Tags objects. Start by creating a simple Task class: Listing // src/Acme/TaskBundle/Entity/Task.php 44-1 namespace AcmeTaskBundleEntity; use DoctrineCommonCollectionsArrayCollection; class Task { protected $description; protected $tags; public function __construct() { $this->tags = new ArrayCollection(); } public function getDescription() { return $this->description; } public function setDescription($description) PDF brought to you by Chapter 44: How to Embed a Collection of Forms | 314 generated on June 20, 2012
  • 315. { $this->description = $description; } public function getTags() { return $this->tags; } public function setTags(ArrayCollection $tags) { $this->tags = $tags; } } The ArrayCollection is specific to Doctrine and is basically the same as using an array (but it must be an ArrayCollection) if you're using Doctrine. Now, create a Tag class. As you saw above, a Task can have many Tag objects: // src/Acme/TaskBundle/Entity/Tag.php Listing 44-2 namespace AcmeTaskBundleEntity; class Tag { public $name; } The name property is public here, but it can just as easily be protected or private (but then it would need getName and setName methods). Now let's get to the forms. Create a form class so that a Tag object can be modified by the user: // src/Acme/TaskBundle/Form/Type/TagType.php Listing 44-3 namespace AcmeTaskBundleFormType; use SymfonyComponentFormAbstractType; use SymfonyComponentFormFormBuilder; class TagType extends AbstractType { public function buildForm(FormBuilder $builder, array $options) { $builder->add('name'); } public function getDefaultOptions(array $options) { return array( 'data_class' => 'AcmeTaskBundleEntityTag', ); } public function getName() { return 'tag'; PDF brought to you by Chapter 44: How to Embed a Collection of Forms | 315 generated on June 20, 2012
  • 316. } } With this, we have enough to render a tag form by itself. But since the end goal is to allow the tags of a Task to be modified right inside the task form itself, create a form for the Task class. Notice that we embed a collection of TagType forms using the collection field type: Listing // src/Acme/TaskBundle/Form/Type/TaskType.php 44-4 namespace AcmeTaskBundleFormType; use SymfonyComponentFormAbstractType; use SymfonyComponentFormFormBuilder; class TaskType extends AbstractType { public function buildForm(FormBuilder $builder, array $options) { $builder->add('description'); $builder->add('tags', 'collection', array('type' => new TagType())); } public function getDefaultOptions(array $options) { return array( 'data_class' => 'AcmeTaskBundleEntityTask', ); } public function getName() { return 'task'; } } In your controller, you'll now initialize a new instance of TaskType: Listing // src/Acme/TaskBundle/Controller/TaskController.php 44-5 namespace AcmeTaskBundleController; use AcmeTaskBundleEntityTask; use AcmeTaskBundleEntityTag; use AcmeTaskBundleFormTypeTaskType; use SymfonyComponentHttpFoundationRequest; use SymfonyBundleFrameworkBundleControllerController; class TaskController extends Controller { public function newAction(Request $request) { $task = new Task(); // dummy code - this is here just so that the Task has some tags // otherwise, this isn't an interesting example $tag1 = new Tag(); $tag1->name = 'tag1'; $task->getTags()->add($tag1); $tag2 = new Tag(); $tag2->name = 'tag2'; $task->getTags()->add($tag2); PDF brought to you by Chapter 44: How to Embed a Collection of Forms | 316 generated on June 20, 2012
  • 317. // end dummy code $form = $this->createForm(new TaskType(), $task); // process the form on POST if ('POST' === $request->getMethod()) { $form->bindRequest($request); if ($form->isValid()) { // maybe do some form processing, like saving the Task and Tag objects } } return $this->render('AcmeTaskBundle:Task:new.html.twig', array( 'form' => $form->createView(), )); } } The corresponding template is now able to render both the description field for the task form as well as all the TagType forms for any tags that are already related to this Task. In the above controller, I added some dummy code so that you can see this in action (since a Task has zero tags when first created). {# src/Acme/TaskBundle/Resources/views/Task/new.html.twig #} Listing 44-6 {# ... #} <form action="..." method="POST" {{ form_enctype(form) }}> {# render the task's only field: description #} {{ form_row(form.description) }} <h3>Tags</h3> <ul class="tags"> {# iterate over each existing tag and render its only field: name #} {% for tag in form.tags %} <li>{{ form_row(tag.name) }}</li> {% endfor %} </ul> {{ form_rest(form) }} {# ... #} </form> When the user submits the form, the submitted data for the Tags fields are used to construct an ArrayCollection of Tag objects, which is then set on the tag field of the Task instance. The Tags collection is accessible naturally via $task->getTags() and can be persisted to the database or used however you need. So far, this works great, but this doesn't allow you to dynamically add new tags or delete existing tags. So, while editing existing tags will work great, your user can't actually add any new tags yet. In this entry, we embed only one collection, but you are not limited to this. You can also embed nested collection as many level down as you like. But if you use Xdebug in your development setup, you may receive a Maximum function nesting level of '100' reached, aborting! error. This is due to the xdebug.max_nesting_level PHP setting, which defaults to 100. This directive limits recursion to 100 calls which may not be enough for rendering the form in the template if you render the whole form at once (e.g form_widget(form)). To fix this you can set this directive to a higher value (either via a PHP ini file or via ini_set1, for example in app/ autoload.php) or render each form field by hand using form_row. PDF brought to you by Chapter 44: How to Embed a Collection of Forms | 317 generated on June 20, 2012
  • 318. Allowing "new" tags with the "prototype" Allowing the user to dynamically add new tags means that we'll need to use some JavaScript. Previously we added two tags to our form in the controller. Now we need to let the user add as many tag forms as he needs directly in the browser. This will be done through a bit of JavaScript. The first thing we need to do is to let the form collection know that it will receive an unknown number of tags. So far we've added two tags and the form type expects to receive exactly two, otherwise an error will be thrown: This form should not contain extra fields. To make this flexible, we add the allow_add option to our collection field: Listing // src/Acme/TaskBundle/Form/Type/TaskType.php 44-7 // ... public function buildForm(FormBuilder $builder, array $options) { $builder->add('description'); $builder->add('tags', 'collection', array( 'type' => new TagType(), 'allow_add' => true, 'by_reference' => false, )); } Note that we also added 'by_reference' => false. Normally, the form framework would modify the tags on a Task object without actually ever calling setTags. By setting by_reference to false, setTags will be called. This will be important later as you'll see. In addition to telling the field to accept any number of submitted objects, the allow_add also makes a "prototype" variable available to you. This "prototype" is a little "template" that contains all the HTML to be able to render any new "tag" forms. To render it, make the following change to your template: Listing <ul class="tags" data-prototype="{{ form_widget(form.tags.get('prototype')) | e }}"> 44-8 ... </ul> If you render your whole "tags" sub-form at once (e.g. form_row(form.tags)), then the prototype is automatically available on the outer div as the data-prototype attribute, similar to what you see above. The form.tags.get('prototype') is form element that looks and feels just like the individual form_widget(tag) elements inside our for loop. This means that you can call form_widget, form_row, or form_label on it. You could even choose to render only one of its fields (e.g. the name field): Listing {{ form_widget(form.tags.get('prototype').name) | e }} 44-9 On the rendered page, the result will look something like this: Listing <ul class="tags" data-prototype="&lt;div&gt;&lt;label class=&quot; 44-10 required&quot;&gt;$$name$$&lt;/label&gt;&lt;div id=&quot;task_tags_$$name$$&quot;&gt;&lt;div&gt;&lt;label 1. http://php.net/manual/en/function.ini-set.php PDF brought to you by Chapter 44: How to Embed a Collection of Forms | 318 generated on June 20, 2012
  • 319. for=&quot;task_tags_$$name$$_name&quot; class=&quot; required&quot;&gt;Name&lt;/ label&gt;&lt;input type=&quot;text&quot; id=&quot;task_tags_$$name$$_name&quot; name=&quot;task[tags][$$name$$][name]&quot; required=&quot;required&quot; maxlength=&quot;255&quot; /&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;"> The goal of this section will be to use JavaScript to read this attribute and dynamically add new tag forms when the user clicks a "Add a tag" link. To make things simple, we'll use jQuery and assume you have it included somewhere on your page. Add a script tag somewhere on your page so we can start writing some JavaScript. First, add a link to the bottom of the "tags" list via JavaScript. Second, bind to the "click" event of that link so we can add a new tag form (addTagForm will be show next): // Get the div that holds the collection of tags Listing 44-11 var collectionHolder = $('ul.tags'); // setup an "add a tag" link var $addTagLink = $('<a href="#" class="add_tag_link">Add a tag</a>'); var $newLinkLi = $('<li></li>').append($addTagLink); jQuery(document).ready(function() { // add the "add a tag" anchor and li to the tags ul collectionHolder.append($newLinkLi); $addTagLink.on('click', function(e) { // prevent the link from creating a "#" on the URL e.preventDefault(); // add a new tag form (see next code block) addTagForm(collectionHolder, $newLinkLi); }); }); The addTagForm function's job will be to use the data-prototype attribute to dynamically add a new form when this link is clicked. The data-prototype HTML contains the tag text input element with a name of task[tags][$$name$$][name] and id of task_tags_$$name$$_name. The $$name is a little "placeholder", which we'll replace with a unique, incrementing number (e.g. task[tags][3][name]). The actual code needed to make this all work can vary quite a bit, but here's one example: function addTagForm(collectionHolder, $newLinkLi) { Listing 44-12 // Get the data-prototype we explained earlier var prototype = collectionHolder.attr('data-prototype'); // Replace '$$name$$' in the prototype's HTML to // instead be a number based on the current collection's length. var newForm = prototype.replace(/$$name$$/g, collectionHolder.children().length); // Display the form in the page in an li, before the "Add a tag" link li var $newFormLi = $('<li></li>').append(newForm); $newLinkLi.before($newFormLi); } Now, each time a user clicks the Add a tag link, a new sub form will appear on the page. When we submit, any new tag forms will be converted into new Tag objects and added to the tags property of the Task object. PDF brought to you by Chapter 44: How to Embed a Collection of Forms | 319 generated on June 20, 2012
  • 320. Doctrine: Cascading Relations and saving the "Inverse" side To get the new tags to save in Doctrine, you need to consider a couple more things. First, unless you iterate over all of the new Tag objects and call $em->persist($tag) on each, you'll receive an error from Doctrine: A new entity was found through the relationship 'AcmeTaskBundleEntityTask#tags' that was not configured to cascade persist operations for entity... To fix this, you may choose to "cascade" the persist operation automatically from the Task object to any related tags. To do this, add the cascade option to your ManyToMany metadata: Listing /** 44-13 * @ORMManyToMany(targetEntity="Tag", cascade={"persist"}) */ protected $tags; A second potential issue deals with the Owning Side and Inverse Side2 of Doctrine relationships. In this example, if the "owning" side of the relationship is "Task", then persistence will work fine as the tags are properly added to the Task. However, if the owning side is on "Tag", then you'll need to do a little bit more work to ensure that the correct side of the relationship is modified. The trick is to make sure that the single "Task" is set on each "Tag". One easy way to do this is to add some extra logic to setTags(), which is called by the form framework since by_reference is set to false: Listing // src/Acme/TaskBundle/Entity/Task.php 44-14 // ... public function setTags(ArrayCollection $tags) { foreach ($tags as $tag) { $tag->addTask($this); } $this->tags = $tags; } Inside Tag, just make sure you have an addTask method: Listing // src/Acme/TaskBundle/Entity/Tag.php 44-15 // ... public function addTask(Task $task) { if (!$this->tasks->contains($task)) { $this->tasks->add($task); } } If you have a OneToMany relationship, then the workaround is similar, except that you can simply call setTask from inside setTags. 2. http://docs.doctrine-project.org/en/latest/reference/unitofwork-associations.html PDF brought to you by Chapter 44: How to Embed a Collection of Forms | 320 generated on June 20, 2012
  • 321. Allowing tags to be removed The next step is to allow the deletion of a particular item in the collection. The solution is similar to allowing tags to be added. Start by adding the allow_delete option in the form Type: // src/Acme/TaskBundle/Form/Type/TaskType.php Listing 44-16 // ... public function buildForm(FormBuilder $builder, array $options) { $builder->add('description'); $builder->add('tags', 'collection', array( 'type' => new TagType(), 'allow_add' => true, 'allow_delete' => true, 'by_reference' => false, )); } Templates Modifications The allow_delete option has one consequence: if an item of a collection isn't sent on submission, the related data is removed from the collection on the server. The solution is thus to remove the form element from the DOM. First, add a "delete this tag" link to each tag form: jQuery(document).ready(function() { Listing 44-17 // add a delete link to all of the existing tag form li elements collectionHolder.find('li').each(function() { addTagFormDeleteLink($(this)); }); // ... the rest of the block from above }); function addTagForm() { // ... // add a delete link to the new form addTagFormDeleteLink($newFormLi); } The addTagFormDeleteLink function will look something like this: function addTagFormDeleteLink($tagFormLi) { Listing 44-18 var $removeFormA = $('<a href="#">delete this tag</a>'); $tagFormLi.append($removeFormA); $removeFormA.on('click', function(e) { // prevent the link from creating a "#" on the URL e.preventDefault(); // remove the li for the tag form $tagFormLi.remove(); }); } PDF brought to you by Chapter 44: How to Embed a Collection of Forms | 321 generated on June 20, 2012
  • 322. When a tag form is removed from the DOM and submitted, the removed Tag object will not be included in the collection passed to setTags. Depending on your persistence layer, this may or may not be enough to actually remove the relationship between the removed Tag and Task object. PDF brought to you by Chapter 44: How to Embed a Collection of Forms | 322 generated on June 20, 2012
  • 323. Doctrine: Ensuring the database persistence When removing objects in this way, you may need to do a little bit more work to ensure that the relationship between the Task and the removed Tag is properly removed. In Doctrine, you have two side of the relationship: the owning side and the inverse side. Normally in this case you'll have a ManyToMany relation and the deleted tags will disappear and persist correctly (adding new tags also works effortlessly). But if you have an OneToMany relation or a ManyToMany with a mappedBy on the Task entity (meaning Task is the "inverse" side), you'll need to do more work for the removed tags to persist correctly. In this case, you can modify the controller to remove the relationship on the removed tag. This assumes that you have some editAction which is handling the "update" of your Task: // src/Acme/TaskBundle/Controller/TaskController.php Listing 44-19 // ... public function editAction($id, Request $request) { $em = $this->getDoctrine()->getEntityManager(); $task = $em->getRepository('AcmeTaskBundle:Task')->find($id); if (!$task) { throw $this->createNotFoundException('No task found for is '.$id); } // Create an array of the current Tag objects in the database foreach ($task->getTags() as $tag) $originalTags[] = $tag; $editForm = $this->createForm(new TaskType(), $task); if ('POST' === $request->getMethod()) { $editForm->bindRequest($this->getRequest()); if ($editForm->isValid()) { // filter $originalTags to contain tags no longer present foreach ($task->getTags() as $tag) { foreach ($originalTags as $key => $toDel) { if ($toDel->getId() === $tag->getId()) { unset($originalTags[$key]); } } } // remove the relationship between the tag and the Task foreach ($originalTags as $tag) { // remove the Task from the Tag $tag->getTasks()->removeElement($task); // if it were a ManyToOne relationship, remove the relationship like this // $tag->setTask(null); $em->persist($tag); // if you wanted to delete the Tag entirely, you can also do that // $em->remove($tag); } $em->persist($task); PDF brought to you by Chapter 44: How to Embed a Collection of Forms | 323 generated on June 20, 2012
  • 324. $em->flush(); // redirect back to some edit page return $this->redirect($this->generateUrl('task_edit', array('id' => $id))); } } // render some form template } As you can see, adding and removing the elements correctly can be tricky. Unless you have a ManyToMany relationship where Task is the "owning" side, you'll need to do extra work to make sure that the relationship is properly updated (whether you're adding new tags or removing existing tags) on each Tag object itself. PDF brought to you by Chapter 44: How to Embed a Collection of Forms | 324 generated on June 20, 2012
  • 325. Chapter 45 How to Create a Custom Form Field Type Symfony comes with a bunch of core field types available for building forms. However there are situations where we want to create a custom form field type for a specific purpose. This recipe assumes we need a field definition that holds a person's gender, based on the existing choice field. This section explains how the field is defined, how we can customize its layout and finally, how we can register it for use in our application. Defining the Field Type In order to create the custom field type, first we have to create the class representing the field. In our situation the class holding the field type will be called GenderType and the file will be stored in the default location for form fields, which is <BundleName>FormType. Make sure the field extends AbstractType1: # src/Acme/DemoBundle/Form/Type/GenderType.php Listing 45-1 namespace AcmeDemoBundleFormType; use SymfonyComponentFormAbstractType; use SymfonyComponentFormFormBuilder; class GenderType extends AbstractType { public function getDefaultOptions(array $options) { return array( 'choices' => array( 'm' => 'Male', 'f' => 'Female', ) ); } public function getParent(array $options) { return 'choice'; 1. http://api.symfony.com/2.0/Symfony/Component/Form/AbstractType.html PDF brought to you by Chapter 45: How to Create a Custom Form Field Type | 325 generated on June 20, 2012
  • 326. } public function getName() { return 'gender'; } } The location of this file is not important - the FormType directory is just a convention. Here, the return value of the getParent function indicates that we're extending the choice field type. This means that, by default, we inherit all of the logic and rendering of that field type. To see some of the logic, check out the ChoiceType2 class. There are three methods that are particularly important: • buildForm() - Each field type has a buildForm method, which is where you configure and build any field(s). Notice that this is the same method you use to setup your forms, and it works the same here. • buildView() - This method is used to set any extra variables you'll need when rendering your field in a template. For example, in ChoiceType3, a multiple variable is set and used in the template to set (or not set) the multiple attribute on the select field. See Creating a Template for the Field for more details. • getDefaultOptions() - This defines options for your form type that can be used in buildForm() and buildView(). There are a lot of options common to all fields (see FieldType4), but you can create any others that you need here. If you're creating a field that consists of many fields, then be sure to set your "parent" type as form or something that extends form. Also, if you need to modify the "view" of any of your child types from your parent type, use the buildViewBottomUp() method. The getName() method returns an identifier which should be unique in your application. This is used in various places, such as when customizing how your form type will be rendered. The goal of our field was to extend the choice type to enable selection of a gender. This is achieved by fixing the choices to a list of possible genders. Creating a Template for the Field Each field type is rendered by a template fragment, which is determined in part by the value of your getName() method. For more information, see What are Form Themes?. In this case, since our parent field is choice, we don't need to do any work as our custom field type will automatically be rendered like a choice type. But for the sake of this example, let's suppose that when our field is "expanded" (i.e. radio buttons or checkboxes, instead of a select field), we want to always render it in a ul element. In your form theme template (see above link for details), create a gender_widget block to handle this: Listing {# src/Acme/DemoBundle/Resources/views/Form/fields.html.twig #} 45-2 {% block gender_widget %} 2. https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php 3. https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php 4. https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Extension/Core/Type/FieldType.php PDF brought to you by Chapter 45: How to Create a Custom Form Field Type | 326 generated on June 20, 2012
  • 327. {% spaceless %} {% if expanded %} <ul {{ block('widget_container_attributes') }}> {% for child in form %} <li> {{ form_widget(child) }} {{ form_label(child) }} </li> {% endfor %} </ul> {% else %} {# just let the choice widget render the select tag #} {{ block('choice_widget') }} {% endif %} {% endspaceless %} {% endblock %} Make sure the correct widget prefix is used. In this example the name should be gender_widget, according to the value returned by getName. Further, the main config file should point to the custom form template so that it's used when rendering all forms. # app/config/config.yml Listing 45-3 twig: form: resources: - 'AcmeDemoBundle:Form:fields.html.twig' Using the Field Type You can now use your custom field type immediately, simply by creating a new instance of the type in one of your forms: // src/Acme/DemoBundle/Form/Type/AuthorType.php Listing 45-4 namespace AcmeDemoBundleFormType; use SymfonyComponentFormAbstractType; use SymfonyComponentFormFormBuilder; class AuthorType extends AbstractType { public function buildForm(FormBuilder $builder, array $options) { $builder->add('gender_code', new GenderType(), array( 'empty_value' => 'Choose a gender', )); } } But this only works because the GenderType() is very simple. What if the gender codes were stored in configuration or in a database? The next section explains how more complex field types solve this problem. PDF brought to you by Chapter 45: How to Create a Custom Form Field Type | 327 generated on June 20, 2012
  • 328. Creating your Field Type as a Service So far, this entry has assumed that you have a very simple custom field type. But if you need access to configuration, a database connection, or some other service, then you'll want to register your custom type as a service. For example, suppose that we're storing the gender parameters in configuration: Listing # app/config/config.yml 45-5 parameters: genders: m: Male f: Female To use the parameter, we'll define our custom field type as a service, injecting the genders parameter value as the first argument to its to-be-created __construct function: Listing # src/Acme/DemoBundle/Resources/config/services.yml 45-6 services: form.type.gender: class: AcmeDemoBundleFormTypeGenderType arguments: - "%genders%" tags: - { name: form.type, alias: gender } Make sure the services file is being imported. See Importing Configuration with imports for details. Be sure that the alias attribute of the tag corresponds with the value returned by the getName method defined earlier. We'll see the importance of this in a moment when we use the custom field type. But first, add a __construct argument to GenderType, which receives the gender configuration: Listing # src/Acme/DemoBundle/Form/Type/GenderType.php 45-7 namespace AcmeDemoBundleFormType; // ... class GenderType extends AbstractType { private $genderChoices; public function __construct(array $genderChoices) { $this->genderChoices = $genderChoices; } public function getDefaultOptions(array $options) { return array( 'choices' => $this->genderChoices, ); } // ... } Great! The GenderType is now fueled by the configuration parameters and registered as a service. And because we used the form.type alias in its configuration, using the field is now much easier: PDF brought to you by Chapter 45: How to Create a Custom Form Field Type | 328 generated on June 20, 2012
  • 329. // src/Acme/DemoBundle/Form/Type/AuthorType.php Listing 45-8 namespace AcmeDemoBundleFormType; // ... class AuthorType extends AbstractType { public function buildForm(FormBuilder $builder, array $options) { $builder->add('gender_code', 'gender', array( 'empty_value' => 'Choose a gender', )); } } Notice that instead of instantiating a new instance, we can just refer to it by the alias used in our service configuration, gender. Have fun! PDF brought to you by Chapter 45: How to Create a Custom Form Field Type | 329 generated on June 20, 2012
  • 330. Chapter 46 How to use the Virtual Form Field Option The virtual form field option can be very useful when you have some duplicated fields in different entities. For example, imagine you have two entities, a Company and a Customer: Listing // src/Acme/HelloBundle/Entity/Company.php 46-1 namespace AcmeHelloBundleEntity; class Company { private $name; private $website; private $address; private $zipcode; private $city; private $country; } Listing // src/Acme/HelloBundle/Entity/Company.php 46-2 namespace AcmeHelloBundleEntity; class Customer { private $firstName; private $lastName; private $address; private $zipcode; private $city; private $country; } Like you can see, each entity shares a few of the same fields: address, zipcode, city, country. Now, you want to build two forms: one for a Company and the second for a Customer. Start by creating a very simple CompanyType and CustomerType: PDF brought to you by Chapter 46: How to use the Virtual Form Field Option | 330 generated on June 20, 2012
  • 331. // src/Acme/HelloBundle/Form/Type/CompanyType.php Listing 46-3 namespace AcmeHelloBundleFormType; class CompanyType extends AbstractType { public function buildForm(FormBuilder $builder, array $options) { $builder ->add('name', 'text') ->add('website', 'text') ; } } // src/Acme/HelloBundle/Form/Type/CustomerType.php Listing 46-4 namespace AcmeHelloBundleFormType; class CustomerType extends AbstractType { public function buildForm(FormBuilder $builder, array $options) { $builder ->add('firstName', 'text') ->add('lastName', 'text') ; } } Now, we have to deal with the four duplicated fields. Here is a (simple) location form type: // src/Acme/HelloBundle/Form/Type/LocationType.php Listing 46-5 namespace AcmeHelloBundleFormType; class LocationType extends AbstractType { public function buildForm(FormBuilder $builder, array $options) { $builder ->add('address', 'textarea') ->add('zipcode', 'text') ->add('city', 'text') ->add('country', 'text') ; } public function getName() { return 'location'; } } We don't actually have a location field in each of our entities, so we can't directly link our LocationType to our CompanyType or CustomerType. But we absolutely want to have a dedicated form type to deal with location (remember, DRY!). The virtual form field option is the solution. We can set the option 'virtual' => true in the getDefaultOptions method of LocationType and directly start using it in the two original form types. Look at the result: PDF brought to you by Chapter 46: How to use the Virtual Form Field Option | 331 generated on June 20, 2012
  • 332. Listing // CompanyType 46-6 public function buildForm(FormBuilder $builder, array $options) { $builder->add('foo', new LocationType()); } Listing // CustomerType 46-7 public function buildForm(FormBuilder $builder, array $options) { $builder->add('bar', new LocationType()); } With the virtual option set to false (default behavior), the Form Component expect each underlying object to have a foo (or bar) property that is either some object or array which contains the four location fields. Of course, we don't have this object/array in our entities and we don't want it! With the virtual option set to true, the Form component skips the foo (or bar) property, and instead "gets" and "sets" the 4 location fields directly on the underlying object! Instead of setting the virtual option inside LocationType, you can (just like with any options) also pass it in as an array option to the third argument of $builder->add(). PDF brought to you by Chapter 46: How to use the Virtual Form Field Option | 332 generated on June 20, 2012
  • 333. Chapter 47 How to create a Custom Validation Constraint You can create a custom constraint by extending the base constraint class, Constraint1. As an example we're going to create a simple validator that checks if a string contains only alphanumeric characters. Creating Constraint class First you need to create a Constraint class and extend Constraint2: namespace AcmeDemoBundleValidatorConstraints; Listing 47-1 use SymfonyComponentValidatorConstraint; /** * @Annotation */ class ContainsAlphanumeric extends Constraint { public $message = 'The string "%string%" contains an illegal character: it can only contain letters or numbers.'; } The @Annotation annotation is necessary for this new constraint in order to make it available for use in classes via annotations. Options for your constraint are represented as public properties on the constraint class. 1. http://api.symfony.com/2.0/Symfony/Component/Validator/Constraint.html 2. http://api.symfony.com/2.0/Symfony/Component/Validator/Constraint.html PDF brought to you by Chapter 47: How to create a Custom Validation Constraint | 333 generated on June 20, 2012
  • 334. Creating the Validator itself As you can see, a constraint class is fairly minimal. The actual validation is performed by a another "constraint validator" class. The constraint validator class is specified by the constraint's validatedBy() method, which includes some simple default logic: Listing // in the base SymfonyComponentValidatorConstraint class 47-2 public function validatedBy() { return get_class($this).'Validator'; } In other words, if you create a custom Constraint (e.g. MyConstraint), Symfony2 will automatically look for another class, MyConstraintValidator when actually performing the validation. The validator class is also simple, and only has one required method: isValid: Listing namespace AcmeDemoBundleValidatorConstraints; 47-3 use SymfonyComponentValidatorConstraint; use SymfonyComponentValidatorConstraintValidator; class ContainsAlphanumericValidator extends ConstraintValidator { public function isValid($value, Constraint $constraint) { if (!preg_match('/^[a-zA-Za0-9]+$/', $value, $matches)) { $this->setMessage($constraint->message, array('%string%' => $value)); return false; } return true; } } Don't forget to call setMessage to construct an error message when the value is invalid. Using the new Validator Using custom validators is very easy, just as the ones provided by Symfony2 itself: Listing # src/Acme/BlogBundle/Resources/config/validation.yml 47-4 AcmeDemoBundleEntityAcmeEntity: properties: name: - NotBlank: ~ - AcmeDemoBundleValidatorConstraintsContainsAlphanumeric: ~ If your constraint contains options, then they should be public properties on the custom Constraint class you created earlier. These options can be configured like options on core Symfony constraints. PDF brought to you by Chapter 47: How to create a Custom Validation Constraint | 334 generated on June 20, 2012
  • 335. Constraint Validators with Dependencies If your constraint validator has dependencies, such as a database connection, it will need to be configured as a service in the dependency injection container. This service must include the validator.constraint_validator tag and an alias attribute: services: Listing 47-5 validator.unique.your_validator_name: class: FullyQualifiedValidatorClassName tags: - { name: validator.constraint_validator, alias: alias_name } Your constraint class should now use this alias to reference the appropriate validator: public function validatedBy() Listing 47-6 { return 'alias_name'; } As mentioned above, Symfony2 will automatically look for a class named after the constraint, with Validator appended. If your constraint validator is defined as a service, it's important that you override the validatedBy() method to return the alias used when defining your service, otherwise Symfony2 won't use the constraint validator service, and will instantiate the class instead, without any dependencies injected. Class Constraint Validator Beside validating a class property, a constraint can have a class scope by providing a target: public function getTargets() Listing 47-7 { return self::CLASS_CONSTRAINT; } With this, the validator isValid() method gets an object as its first argument: class ProtocolClassValidator extends ConstraintValidator Listing 47-8 { public function isValid($protocol, Constraint $constraint) { if ($protocol->getFoo() != $protocol->getBar()) { $propertyPath = $this->context->getPropertyPath() . 'foo'; $this->context->setPropertyPath($propertyPath); $this->context->addViolation($constraint->getMessage(), array(), null); return false; } return true; } } PDF brought to you by Chapter 47: How to create a Custom Validation Constraint | 335 generated on June 20, 2012
  • 336. Chapter 48 How to Master and Create new Environments Every application is the combination of code and a set of configuration that dictates how that code should function. The configuration may define the database being used, whether or not something should be cached, or how verbose logging should be. In Symfony2, the idea of "environments" is the idea that the same codebase can be run using multiple different configurations. For example, the dev environment should use configuration that makes development easy and friendly, while the prod environment should use a set of configuration optimized for speed. Different Environments, Different Configuration Files A typical Symfony2 application begins with three environments: dev, prod, and test. As discussed, each "environment" simply represents a way to execute the same codebase with different configuration. It should be no surprise then that each environment loads its own individual configuration file. If you're using the YAML configuration format, the following files are used: • for the dev environment: app/config/config_dev.yml • for the prod environment: app/config/config_prod.yml • for the test environment: app/config/config_test.yml This works via a simple standard that's used by default inside the AppKernel class: Listing // app/AppKernel.php 48-1 // ... class AppKernel extends Kernel { // ... public function registerContainerConfiguration(LoaderInterface $loader) { $loader->load(__DIR__.'/config/config_'.$this->getEnvironment().'.yml'); } } PDF brought to you by Chapter 48: How to Master and Create new Environments | 336 generated on June 20, 2012
  • 337. As you can see, when Symfony2 is loaded, it uses the given environment to determine which configuration file to load. This accomplishes the goal of multiple environments in an elegant, powerful and transparent way. Of course, in reality, each environment differs only somewhat from others. Generally, all environments will share a large base of common configuration. Opening the "dev" configuration file, you can see how this is accomplished easily and transparently: imports: Listing 48-2 - { resource: config.yml } # ... To share common configuration, each environment's configuration file simply first imports from a central configuration file (config.yml). The remainder of the file can then deviate from the default configuration by overriding individual parameters. For example, by default, the web_profiler toolbar is disabled. However, in the dev environment, the toolbar is activated by modifying the default value in the dev configuration file: # app/config/config_dev.yml Listing 48-3 imports: - { resource: config.yml } web_profiler: toolbar: true # ... Executing an Application in Different Environments To execute the application in each environment, load up the application using either the app.php (for the prod environment) or the app_dev.php (for the dev environment) front controller: http://localhost/app.php -> *prod* environment Listing 48-4 http://localhost/app_dev.php -> *dev* environment The given URLs assume that your web server is configured to use the web/ directory of the application as its root. Read more in Installing Symfony2. If you open up one of these files, you'll quickly see that the environment used by each is explicitly set: 1 <?php Listing Listing 48-5 48-6 2 3 require_once __DIR__.'/../app/bootstrap_cache.php'; 4 require_once __DIR__.'/../app/AppCache.php'; 5 6 use SymfonyComponentHttpFoundationRequest; 7 8 $kernel = new AppCache(new AppKernel('prod', false)); 9 $kernel->handle(Request::createFromGlobals())->send(); As you can see, the prod key specifies that this environment will run in the prod environment. A Symfony2 application can be executed in any environment by using this code and changing the environment string. PDF brought to you by Chapter 48: How to Master and Create new Environments | 337 generated on June 20, 2012
  • 338. The test environment is used when writing functional tests and is not accessible in the browser directly via a front controller. In other words, unlike the other environments, there is no app_test.php front controller file. Debug Mode Important, but unrelated to the topic of environments is the false key on line 8 of the front controller above. This specifies whether or not the application should run in "debug mode". Regardless of the environment, a Symfony2 application can be run with debug mode set to true or false. This affects many things in the application, such as whether or not errors should be displayed or if cache files are dynamically rebuilt on each request. Though not a requirement, debug mode is generally set to true for the dev and test environments and false for the prod environment. Internally, the value of the debug mode becomes the kernel.debug parameter used inside the service container. If you look inside the application configuration file, you'll see the parameter used, for example, to turn logging on or off when using the Doctrine DBAL: Listing doctrine: 48-7 dbal: logging: "%kernel.debug%" # ... Creating a New Environment By default, a Symfony2 application has three environments that handle most cases. Of course, since an environment is nothing more than a string that corresponds to a set of configuration, creating a new environment is quite easy. Suppose, for example, that before deployment, you need to benchmark your application. One way to benchmark the application is to use near-production settings, but with Symfony2's web_profiler enabled. This allows Symfony2 to record information about your application while benchmarking. The best way to accomplish this is via a new environment called, for example, benchmark. Start by creating a new configuration file: Listing # app/config/config_benchmark.yml 48-8 imports: - { resource: config_prod.yml } framework: profiler: { only_exceptions: false } And with this simple addition, the application now supports a new environment called benchmark. This new configuration file imports the configuration from the prod environment and modifies it. This guarantees that the new environment is identical to the prod environment, except for any changes explicitly made here. Because you'll want this environment to be accessible via a browser, you should also create a front controller for it. Copy the web/app.php file to web/app_benchmark.php and edit the environment to be benchmark: Listing 48-9 PDF brought to you by Chapter 48: How to Master and Create new Environments | 338 generated on June 20, 2012
  • 339. <?php require_once __DIR__.'/../app/bootstrap.php'; require_once __DIR__.'/../app/AppKernel.php'; use SymfonyComponentHttpFoundationRequest; $kernel = new AppKernel('benchmark', false); $kernel->handle(Request::createFromGlobals())->send(); The new environment is now accessible via: http://localhost/app_benchmark.php Listing 48-10 Some environments, like the dev environment, are never meant to be accessed on any deployed server by the general public. This is because certain environments, for debugging purposes, may give too much information about the application or underlying infrastructure. To be sure these environments aren't accessible, the front controller is usually protected from external IP addresses via the following code at the top of the controller: if (!in_array(@$_SERVER['REMOTE_ADDR'], array('127.0.0.1', '::1'))) { Listing 48-11 die('You are not allowed to access this file. Check '.basename(__FILE__).' for more information.'); } Environments and the Cache Directory Symfony2 takes advantage of caching in many ways: the application configuration, routing configuration, Twig templates and more are cached to PHP objects stored in files on the filesystem. By default, these cached files are largely stored in the app/cache directory. However, each environment caches its own set of files: app/cache/dev - cache directory for the *dev* environment Listing 48-12 app/cache/prod - cache directory for the *prod* environment Sometimes, when debugging, it may be helpful to inspect a cached file to understand how something is working. When doing so, remember to look in the directory of the environment you're using (most commonly dev while developing and debugging). While it can vary, the app/cache/dev directory includes the following: • appDevDebugProjectContainer.php - the cached "service container" that represents the cached application configuration; • appdevUrlGenerator.php - the PHP class generated from the routing configuration and used when generating URLs; • appdevUrlMatcher.php - the PHP class used for route matching - look here to see the compiled regular expression logic used to match incoming URLs to different routes; • twig/ - this directory contains all the cached Twig templates. Going Further Read the article on How to Set External Parameters in the Service Container. PDF brought to you by Chapter 48: How to Master and Create new Environments | 339 generated on June 20, 2012
  • 340. Chapter 49 How to Set External Parameters in the Service Container In the chapter How to Master and Create new Environments, you learned how to manage your application configuration. At times, it may benefit your application to store certain credentials outside of your project code. Database configuration is one such example. The flexibility of the symfony service container allows you to easily do this. Environment Variables Symfony will grab any environment variable prefixed with SYMFONY__ and set it as a parameter in the service container. Double underscores are replaced with a period, as a period is not a valid character in an environment variable name. For example, if you're using Apache, environment variables can be set using the following VirtualHost configuration: Listing <VirtualHost *:80> 49-1 ServerName Symfony2 DocumentRoot "/path/to/symfony_2_app/web" DirectoryIndex index.php index.html SetEnv SYMFONY__DATABASE__USER user SetEnv SYMFONY__DATABASE__PASSWORD secret <Directory "/path/to/symfony_2_app/web"> AllowOverride All Allow from All </Directory> </VirtualHost> The example above is for an Apache configuration, using the SetEnv1 directive. However, this will work for any web server which supports the setting of environment variables. PDF brought to you by Chapter 49: How to Set External Parameters in the Service Container | 340 generated on June 20, 2012
  • 341. Also, in order for your console to work (which does not use Apache), you must export these as shell variables. On a Unix system, you can run the following: export SYMFONY__DATABASE__USER=user Listing 49-2 export SYMFONY__DATABASE__PASSWORD=secret Now that you have declared an environment variable, it will be present in the PHP $_SERVER global variable. Symfony then automatically sets all $_SERVER variables prefixed with SYMFONY__ as parameters in the service container. You can now reference these parameters wherever you need them. doctrine: Listing 49-3 dbal: driver pdo_mysql dbname: symfony2_project user: %database.user% password: %database.password% Constants The container also has support for setting PHP constants as parameters. To take advantage of this feature, map the name of your constant to a parameter key, and define the type as constant. <?xml version="1.0" encoding="UTF-8"?> Listing 49-4 <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" > <parameters> <parameter key="global.constant.value" type="constant">GLOBAL_CONSTANT</parameter> <parameter key="my_class.constant.value" type="constant">My_Class::CONSTANT_NAME</parameter> </parameters> </container> This only works for XML configuration. If you're not using XML, simply import an XML file to take advantage of this functionality: // app/config/config.yml Listing 49-5 imports: - { resource: parameters.xml } Miscellaneous Configuration The imports directive can be used to pull in parameters stored elsewhere. Importing a PHP file gives you the flexibility to add whatever is needed in the container. The following imports a file named parameters.php. 1. http://httpd.apache.org/docs/current/env.html PDF brought to you by Chapter 49: How to Set External Parameters in the Service Container | 341 generated on June 20, 2012
  • 342. Listing # app/config/config.yml 49-6 imports: - { resource: parameters.php } A resource file can be one of many types. PHP, XML, YAML, INI, and closure resources are all supported by the imports directive. In parameters.php, tell the service container the parameters that you wish to set. This is useful when important configuration is in a nonstandard format. The example below includes a Drupal database's configuration in the symfony service container. Listing // app/config/parameters.php 49-7 include_once('/path/to/drupal/sites/default/settings.php'); $container->setParameter('drupal.database.url', $db_url); PDF brought to you by Chapter 49: How to Set External Parameters in the Service Container | 342 generated on June 20, 2012
  • 343. Chapter 50 How to use PdoSessionStorage to store Sessions in the Database The default session storage of Symfony2 writes the session information to file(s). Most medium to large websites use a database to store the session values instead of files, because databases are easier to use and scale in a multi-webserver environment. Symfony2 has a built-in solution for database session storage called PdoSessionStorage1. To use it, you just need to change some parameters in config.yml (or the configuration format of your choice): # app/config/config.yml Listing 50-1 framework: session: # ... storage_id: session.storage.pdo parameters: pdo.db_options: db_table: session db_id_col: session_id db_data_col: session_value db_time_col: session_time services: pdo: class: PDO arguments: dsn: "mysql:dbname=mydatabase" user: myuser password: mypassword session.storage.pdo: class: SymfonyComponentHttpFoundationSessionStoragePdoSessionStorage arguments: [@pdo, %session.storage.options%, %pdo.db_options%] 1. http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/SessionStorage/PdoSessionStorage.html PDF brought to you by Chapter 50: How to use PdoSessionStorage to store Sessions in the Database | 343 generated on June 20, 2012
  • 344. db_table: The name of the session table in your database • db_id_col: The name of the id column in your session table (VARCHAR(255) or larger) • db_data_col: The name of the value column in your session table (TEXT or CLOB) • db_time_col: The name of the time column in your session table (INTEGER) Sharing your Database Connection Information With the given configuration, the database connection settings are defined for the session storage connection only. This is OK when you use a separate database for the session data. But if you'd like to store the session data in the same database as the rest of your project's data, you can use the connection settings from the parameter.ini by referencing the database-related parameters defined there: Listing pdo: 50-2 class: PDO arguments: - "mysql:dbname=%database_name%" - %database_user% - %database_password% Example SQL Statements MySQL The SQL statement for creating the needed database table might look like the following (MySQL): Listing CREATE TABLE `session` ( 50-3 `session_id` varchar(255) NOT NULL, `session_value` text NOT NULL, `session_time` int(11) NOT NULL, PRIMARY KEY (`session_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; PostgreSQL For PostgreSQL, the statement should look like this: Listing CREATE TABLE session ( 50-4 session_id character varying(255) NOT NULL, session_value text NOT NULL, session_time integer NOT NULL, CONSTRAINT session_pkey PRIMARY KEY (session_id) ); PDF brought to you by Chapter 50: How to use PdoSessionStorage to store Sessions in the Database | 344 generated on June 20, 2012
  • 345. Chapter 51 How to use the Apache Router Symfony2, while fast out of the box, also provides various ways to increase that speed with a little bit of tweaking. One of these ways is by letting apache handle routes directly, rather than using Symfony2 for this task. Change Router Configuration Parameters To dump Apache routes we must first tweak some configuration parameters to tell Symfony2 to use the ApacheUrlMatcher instead of the default one: # app/config/config_prod.yml Listing 51-1 parameters: router.options.matcher.cache_class: ~ # disable router cache router.options.matcher_class: SymfonyComponentRoutingMatcherApacheUrlMatcher Note that ApacheUrlMatcher1 extends UrlMatcher2 so even if you don't regenerate the url_rewrite rules, everything will work (because at the end of ApacheUrlMatcher::match() a call to parent::match() is done). Generating mod_rewrite rules To test that it's working, let's create a very basic route for demo bundle: # app/config/routing.yml Listing 51-2 hello: pattern: /hello/{name} defaults: { _controller: AcmeDemoBundle:Demo:hello } Now we generate url_rewrite rules: 1. http://api.symfony.com/2.0/Symfony/Component/Routing/Matcher/ApacheUrlMatcher.html 2. http://api.symfony.com/2.0/Symfony/Component/Routing/Matcher/UrlMatcher.html PDF brought to you by Chapter 51: How to use the Apache Router | 345 generated on June 20, 2012
  • 346. Listing php app/console router:dump-apache -e=prod --no-debug 51-3 Which should roughly output the following: Listing # skip "real" requests 51-4 RewriteCond %{REQUEST_FILENAME} -f RewriteRule .* - [QSA,L] # hello RewriteCond %{REQUEST_URI} ^/hello/([^/]+?)$ RewriteRule .* app.php [QSA,L,E=_ROUTING__route:hello,E=_ROUTING_name:%1,E=_ROUTING__controller:AcmeDemoBundle:Demo:hello] You can now rewrite web/.htaccess to use the new rules, so with our example it should look like this: Listing <IfModule mod_rewrite.c> 51-5 RewriteEngine On # skip "real" requests RewriteCond %{REQUEST_FILENAME} -f RewriteRule .* - [QSA,L] # hello RewriteCond %{REQUEST_URI} ^/hello/([^/]+?)$ RewriteRule .* app.php [QSA,L,E=_ROUTING__route:hello,E=_ROUTING_name:%1,E=_ROUTING__controller:AcmeDemoBundle:Demo:hello] </IfModule> Procedure above should be done each time you add/change a route if you want to take full advantage of this setup That's it! You're now all set to use Apache Route rules. Additional tweaks To save a little bit of processing time, change occurrences of Request to ApacheRequest in web/app.php: Listing // web/app.php 51-6 require_once __DIR__.'/../app/bootstrap.php.cache'; require_once __DIR__.'/../app/AppKernel.php'; //require_once __DIR__.'/../app/AppCache.php'; use SymfonyComponentHttpFoundationApacheRequest; $kernel = new AppKernel('prod', false); $kernel->loadClassCache(); //$kernel = new AppCache($kernel); $kernel->handle(ApacheRequest::createFromGlobals())->send(); PDF brought to you by Chapter 51: How to use the Apache Router | 346 generated on June 20, 2012
  • 347. Chapter 52 How to create an Event Listener Symfony has various events and hooks that can be used to trigger custom behavior in your application. Those events are thrown by the HttpKernel component and can be viewed in the KernelEvents1 class. To hook into an event and add your own custom logic, you have to create a service that will act as an event listener on that event. In this entry, we will create a service that will act as an Exception Listener, allowing us to modify how exceptions are shown by our application. The KernelEvents::EXCEPTION event is just one of the core kernel events: // src/Acme/DemoBundle/Listener/AcmeExceptionListener.php Listing 52-1 namespace AcmeDemoBundleListener; use SymfonyComponentHttpKernelEventGetResponseForExceptionEvent; class AcmeExceptionListener { public function onKernelException(GetResponseForExceptionEvent $event) { // We get the exception object from the received event $exception = $event->getException(); $message = 'My Error says: ' . $exception->getMessage(); // Customize our response object to display our exception details $response->setContent($message); $response->setStatusCode($exception->getStatusCode()); // Send our modified response object to the event $event->setResponse($response); } } Each event receives a slightly different type of $event object. For the kernel.exception event, it is GetResponseForExceptionEvent2. To see what type of object each event listener receives, see KernelEvents3. 1. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/KernelEvents.html PDF brought to you by Chapter 52: How to create an Event Listener | 347 generated on June 20, 2012
  • 348. Now that the class is created, we just need to register it as a service and notify Symfony that it is a "listener" on the kernel.exception event by using a special "tag": Listing services: 52-2 kernel.listener.your_listener_name: class: AcmeDemoBundleListenerAcmeExceptionListener tags: - { name: kernel.event_listener, event: kernel.exception, method: onKernelException } There is an additional tag option priority that is optional and defaults to 0. This value can be from -255 to 255, and the listeners will be executed in the order of their priority. This is useful when you need to guarantee that one listener is executed before another. Request events, checking types A single page can make several requests (one mast request, and then multiple sub-requests), which is why when working with the KernelEvents::REQUEST event, you might need to check the type of the request. This can be easily done as follow: Listing // src/Acme/DemoBundle/Listener/AcmeRequestListener.php 52-3 namespace AcmeDemoBundleListener; use SymfonyComponentHttpKernelEventGetResponseEvent; use SymfonyComponentHttpKernelHttpKernel; class AcmeRequestListener { public function onKernelRequest(GetResponseEvent $event) { if (HttpKernel::MASTER_REQUEST != $event->getRequestType()) { // don't do anything if it's not the master request return; } // your code } } Two types of request are available in the HttpKernelInterface4 interface: HttpKernelInterface::MASTER_REQUEST and HttpKernelInterface::SUB_REQUEST. 2. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/Event/GetResponseForExceptionEvent.html 3. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/KernelEvents.html 4. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/HttpKernelInterface.html PDF brought to you by Chapter 52: How to create an Event Listener | 348 generated on June 20, 2012
  • 349. Chapter 53 How to work with Scopes This entry is all about scopes, a somewhat advanced topic related to the Service Container. If you've ever gotten an error mentioning "scopes" when creating services, or need to create a service that depends on the request service, then this entry is for you. Understanding Scopes The scope of a service controls how long an instance of a service is used by the container. The Dependency Injection component provides two generic scopes: • container (the default one): The same instance is used each time you request it from this container. • prototype: A new instance is created each time you request the service. The FrameworkBundle also defines a third scope: request. This scope is tied to the request, meaning a new instance is created for each subrequest and is unavailable outside the request (for instance in the CLI). Scopes add a constraint on the dependencies of a service: a service cannot depend on services from a narrower scope. For example, if you create a generic my_foo service, but try to inject the request component, you'll receive a ScopeWideningInjectionException1 when compiling the container. Read the sidebar below for more details. 1. http://api.symfony.com/2.0/Symfony/Component/DependencyInjection/Exception/ScopeWideningInjectionException.html PDF brought to you by Chapter 53: How to work with Scopes | 349 generated on June 20, 2012
  • 350. Scopes and Dependencies Imagine you've configured a my_mailer service. You haven't configured the scope of the service, so it defaults to container. In other words, everytime you ask the container for the my_mailer service, you get the same object back. This is usually how you want your services to work. Imagine, however, that you need the request service in your my_mailer service, maybe because you're reading the URL of the current request. So, you add it as a constructor argument. Let's look at why this presents a problem: • When requesting my_mailer, an instance of my_mailer (let's call it MailerA) is created and the request service (let's call it RequestA) is passed to it. Life is good! • You've now made a subrequest in Symfony, which is a fancy way of saying that you've called, for example, the {% render ... %} Twig function, which executes another controller. Internally, the old request service (RequestA) is actually replaced by a new request instance (RequestB). This happens in the background, and it's totally normal. • In your embedded controller, you once again ask for the my_mailer service. Since your service is in the container scope, the same instance (MailerA) is just re-used. But here's the problem: the MailerA instance still contains the old RequestA object, which is now not the correct request object to have (RequestB is now the current request service). This is subtle, but the mis-match could cause major problems, which is why it's not allowed. So, that's the reason why scopes exist, and how they can cause problems. Keep reading to find out the common solutions. A service can of course depend on a service from a wider scope without any issue. Setting the Scope in the Definition The scope of a service is set in the definition of the service: Listing # src/Acme/HelloBundle/Resources/config/services.yml 53-1 services: greeting_card_manager: class: AcmeHelloBundleMailGreetingCardManager scope: request If you don't specify the scope, it defaults to container, which is what you want most of the time. Unless your service depends on another service that's scoped to a narrower scope (most commonly, the request service), you probably don't need to set the scope. Using a Service from a narrower Scope If your service depends on a scoped service, the best solution is to put it in the same scope (or a narrower one). Usually, this means putting your new service in the request scope. But this is not always possible (for instance, a twig extension must be in the container scope as the Twig environment needs it as a dependency). In these cases, you should pass the entire container into your service and retrieve your dependency from the container each time we need it to be sure you have the right instance: PDF brought to you by Chapter 53: How to work with Scopes | 350 generated on June 20, 2012
  • 351. namespace AcmeHelloBundleMail; Listing 53-2 use SymfonyComponentDependencyInjectionContainerInterface; class Mailer { protected $container; public function __construct(ContainerInterface $container) { $this->container = $container; } public function sendEmail() { $request = $this->container->get('request'); // Do something using the request here } } Take care not to store the request in a property of the object for a future call of the service as it would cause the same issue described in the first section (except that Symfony cannot detect that you are wrong). The service config for this class would look something like this: # src/Acme/HelloBundle/Resources/config/services.yml Listing 53-3 parameters: # ... my_mailer.class: AcmeHelloBundleMailMailer services: my_mailer: class: %my_mailer.class% arguments: - "@service_container" # scope: container can be omitted as it is the default Injecting the whole container into a service is generally not a good idea (only inject what you need). In some rare cases, it's necessary when you have a service in the container scope that needs a service in the request scope. If you define a controller as a service then you can get the Request object without injecting the container by having it passed in as an argument of your action method. See The Request as a Controller Argument for details. PDF brought to you by Chapter 53: How to work with Scopes | 351 generated on June 20, 2012
  • 352. Chapter 54 How to work with Compiler Passes in Bundles Compiler passes give you an opportunity to manipulate other service definitions that have been registered with the service container. You can read about how to create them in the components section "Compiling the Container". To register a compiler pass from a bundle you need to add it to the build method of the bundle definition class: Listing namespace AcmeMailerBundle; 54-1 use SymfonyComponentHttpKernelBundleBundle; use SymfonyComponentDependencyInjectionContainerBuilder; use AcmeMailerBundleDependencyInjectionCompilerCustomCompilerPass; class AcmeMailerBundle extends Bundle { public function build(ContainerBuilder $container) { parent::build($container); $container->addCompilerPass(new CustomCompilerPass()); } } One of the most common use-cases of compiler passes is to work with tagged services (read more about tags in the components section "Working with Tagged Services"). If you are using custom tags in a bundle then by convention, tag names consist of the name of the bundle (lowercase, underscores as separators), followed by a dot, and finally the "real" name. For example, if you want to introduce some sort of "transport" tag in your AcmeMailerBundle, you should call it acme_mailer.transport. PDF brought to you by Chapter 54: How to work with Compiler Passes in Bundles | 352 generated on June 20, 2012
  • 353. Chapter 55 Bundle Structure and Best Practices A bundle is a directory that has a well-defined structure and can host anything from classes to controllers and web resources. Even if bundles are very flexible, you should follow some best practices if you want to distribute them. Bundle Name A bundle is also a PHP namespace. The namespace must follow the technical interoperability standards1 for PHP 5.3 namespaces and class names: it starts with a vendor segment, followed by zero or more category segments, and it ends with the namespace short name, which must end with a Bundle suffix. A namespace becomes a bundle as soon as you add a bundle class to it. The bundle class name must follow these simple rules: • Use only alphanumeric characters and underscores; • Use a CamelCased name; • Use a descriptive and short name (no more than 2 words); • Prefix the name with the concatenation of the vendor (and optionally the category namespaces); • Suffix the name with Bundle. Here are some valid bundle namespaces and class names: Namespace Bundle Class Name AcmeBundleBlogBundle AcmeBlogBundle AcmeBundleSocialBlogBundle AcmeSocialBlogBundle AcmeBlogBundle AcmeBlogBundle By convention, the getName() method of the bundle class should return the class name. 1. http://symfony.com/PSR0 PDF brought to you by Chapter 55: Bundle Structure and Best Practices | 353 generated on June 20, 2012
  • 354. If you share your bundle publicly, you must use the bundle class name as the name of the repository (AcmeBlogBundle and not BlogBundle for instance). Symfony2 core Bundles do not prefix the Bundle class with Symfony and always add a Bundle subnamespace; for example: FrameworkBundle2. Each bundle has an alias, which is the lower-cased short version of the bundle name using underscores (acme_hello for AcmeHelloBundle, or acme_social_blog for AcmeSocialBlogBundle for instance). This alias is used to enforce uniqueness within a bundle (see below for some usage examples). Directory Structure The basic directory structure of a HelloBundle bundle must read as follows: Listing XXX/... 55-1 HelloBundle/ HelloBundle.php Controller/ Resources/ meta/ LICENSE config/ doc/ index.rst translations/ views/ public/ Tests/ The XXX directory(ies) reflects the namespace structure of the bundle. The following files are mandatory: • HelloBundle.php; • Resources/meta/LICENSE: The full license for the code; • Resources/doc/index.rst: The root file for the Bundle documentation. These conventions ensure that automated tools can rely on this default structure to work. The depth of sub-directories should be kept to the minimal for most used classes and files (2 levels at a maximum). More levels can be defined for non-strategic, less-used files. The bundle directory is read-only. If you need to write temporary files, store them under the cache/ or log/ directory of the host application. Tools can generate files in the bundle directory structure, but only if the generated files are going to be part of the repository. The following classes and files have specific emplacements: 2. http://api.symfony.com/2.0/Symfony/Bundle/FrameworkBundle/FrameworkBundle.html PDF brought to you by Chapter 55: Bundle Structure and Best Practices | 354 generated on June 20, 2012
  • 355. Type Directory Commands Command/ Controllers Controller/ Service Container Extensions DependencyInjection/ Event Listeners EventListener/ Configuration Resources/config/ Web Resources Resources/public/ Translation files Resources/translations/ Templates Resources/views/ Unit and Functional Tests Tests/ Classes The bundle directory structure is used as the namespace hierarchy. For instance, a HelloController controller is stored in Bundle/HelloBundle/Controller/HelloController.php and the fully qualified class name is BundleHelloBundleControllerHelloController. All classes and files must follow the Symfony2 coding standards. Some classes should be seen as facades and should be as short as possible, like Commands, Helpers, Listeners, and Controllers. Classes that connect to the Event Dispatcher should be suffixed with Listener. Exceptions classes should be stored in an Exception sub-namespace. Vendors A bundle must not embed third-party PHP libraries. It should rely on the standard Symfony2 autoloading instead. A bundle should not embed third-party libraries written in JavaScript, CSS, or any other language. Tests A bundle should come with a test suite written with PHPUnit and stored under the Tests/ directory. Tests should follow the following principles: • The test suite must be executable with a simple phpunit command run from a sample application; • The functional tests should only be used to test the response output and some profiling information if you have some; • The code coverage should at least covers 95% of the code base. A test suite must not contain AllTests.php scripts, but must rely on the existence of a phpunit.xml.dist file. PDF brought to you by Chapter 55: Bundle Structure and Best Practices | 355 generated on June 20, 2012
  • 356. Documentation All classes and functions must come with full PHPDoc. Extensive documentation should also be provided in the reStructuredText format, under the Resources/ doc/ directory; the Resources/doc/index.rst file is the only mandatory file and must be the entry point for the documentation. Controllers As a best practice, controllers in a bundle that's meant to be distributed to others must not extend the Controller3 base class. They can implement ContainerAwareInterface4 or extend ContainerAware5 instead. If you have a look at Controller6 methods, you will see that they are only nice shortcuts to ease the learning curve. Routing If the bundle provides routes, they must be prefixed with the bundle alias. For an AcmeBlogBundle for instance, all routes must be prefixed with acme_blog_. Templates If a bundle provides templates, they must use Twig. A bundle must not provide a main layout, except if it provides a full working application. Translation Files If a bundle provides message translations, they must be defined in the XLIFF format; the domain should be named after the bundle name (bundle.hello). A bundle must not override existing messages from another bundle. Configuration To provide more flexibility, a bundle can provide configurable settings by using the Symfony2 built-in mechanisms. For simple configuration settings, rely on the default parameters entry of the Symfony2 configuration. Symfony2 parameters are simple key/value pairs; a value being any valid PHP value. Each parameter name should start with the bundle alias, though this is just a best-practice suggestion. The rest of the parameter name will use a period (.) to separate different parts (e.g. acme_hello.email.from). 3. http://api.symfony.com/2.0/Symfony/Bundle/FrameworkBundle/Controller/Controller.html 4. http://api.symfony.com/2.0/Symfony/Component/DependencyInjection/ContainerAwareInterface.html 5. http://api.symfony.com/2.0/Symfony/Component/DependencyInjection/ContainerAware.html 6. http://api.symfony.com/2.0/Symfony/Bundle/FrameworkBundle/Controller/Controller.html PDF brought to you by Chapter 55: Bundle Structure and Best Practices | 356 generated on June 20, 2012
  • 357. The end user can provide values in any configuration file: # app/config/config.yml Listing 55-2 parameters: acme_hello.email.from: fabien@example.com Retrieve the configuration parameters in your code from the container: $container->getParameter('acme_hello.email.from'); Listing 55-3 Even if this mechanism is simple enough, you are highly encouraged to use the semantic configuration described in the cookbook. If you are defining services, they should also be prefixed with the bundle alias. Learn more from the Cookbook • How to expose a Semantic Configuration for a Bundle PDF brought to you by Chapter 55: Bundle Structure and Best Practices | 357 generated on June 20, 2012
  • 358. Chapter 56 How to use Bundle Inheritance to Override parts of a Bundle When working with third-party bundles, you'll probably come across a situation where you want to override a file in that third-party bundle with a file in one of your own bundles. Symfony gives you a very convenient way to override things like controllers, templates, and other files in a bundle's Resources/ directory. For example, suppose that you're installing the FOSUserBundle1, but you want to override its base layout.html.twig template, as well as one of its controllers. Suppose also that you have your own AcmeUserBundle where you want the overridden files to live. Start by registering the FOSUserBundle as the "parent" of your bundle: Listing // src/Acme/UserBundle/AcmeUserBundle.php 56-1 namespace AcmeUserBundle; use SymfonyComponentHttpKernelBundleBundle; class AcmeUserBundle extends Bundle { public function getParent() { return 'FOSUserBundle'; } } By making this simple change, you can now override several parts of the FOSUserBundle simply by creating a file with the same name. 1. https://github.com/friendsofsymfony/fosuserbundle PDF brought to you by Chapter 56: How to use Bundle Inheritance to Override parts of a Bundle | 358 generated on June 20, 2012
  • 359. Overriding Controllers Suppose you want to add some functionality to the registerAction of a RegistrationController that lives inside FOSUserBundle. To do so, just create your own RegistrationController.php file, override the bundle's original method, and change its functionality: // src/Acme/UserBundle/Controller/RegistrationController.php Listing 56-2 namespace AcmeUserBundleController; use FOSUserBundleControllerRegistrationController as BaseController; class RegistrationController extends BaseController { public function registerAction() { $response = parent::registerAction(); // do custom stuff return $response; } } Depending on how severely you need to change the behavior, you might call parent::registerAction() or completely replace its logic with your own. Overriding controllers in this way only works if the bundle refers to the controller using the standard FOSUserBundle:Registration:register syntax in routes and templates. This is the best practice. Overriding Resources: Templates, Routing, Validation, etc Most resources can also be overridden, simply by creating a file in the same location as your parent bundle. For example, it's very common to need to override the FOSUserBundle's layout.html.twig template so that it uses your application's base layout. Since the file lives at Resources/views/layout.html.twig in the FOSUserBundle, you can create your own file in the same location of AcmeUserBundle. Symfony will ignore the file that lives inside the FOSUserBundle entirely, and use your file instead. The same goes for routing files, validation configuration and other resources. The overriding of resources only works when you refer to resources with the @FosUserBundle/ Resources/config/routing/security.xml method. If you refer to resources without using the @BundleName shortcut, they can't be overridden in this way. Translation files do not work in the same way as described above. All translation files are accumulated into a set of "pools" (one for each) domain. Symfony loads translation files from bundles first (in the order that the bundles are initialized) and then from your app/Resources PDF brought to you by Chapter 56: How to use Bundle Inheritance to Override parts of a Bundle | 359 generated on June 20, 2012
  • 360. directory. If the same translation is specified in two resources, the translation from the resource that's loaded last will win. PDF brought to you by Chapter 56: How to use Bundle Inheritance to Override parts of a Bundle | 360 generated on June 20, 2012
  • 361. Chapter 57 How to Override any Part of a Bundle This document is a quick reference for how to override different parts of third-party bundles. Templates For information on overriding templates, see * Overriding Bundle Templates. * How to use Bundle Inheritance to Override parts of a Bundle Routing Routing is never automatically imported in Symfony2. If you want to include the routes from any bundle, then they must be manually imported from somewhere in your application (e.g. app/config/ routing.yml). The easiest way to "override" a bundle's routing is to never import it at all. Instead of importing a third- party bundle's routing, simply copying that routing file into your application, modify it, and import it instead. Controllers Assuming the third-party bundle involved uses non-service controllers (which is almost always the case), you can easily override controllers via bundle inheritance. For more information, see How to use Bundle Inheritance to Override parts of a Bundle. Services & Configuration In progress... PDF brought to you by Chapter 57: How to Override any Part of a Bundle | 361 generated on June 20, 2012
  • 362. Entities & Entity mapping In progress... Forms In progress... Validation metadata In progress... Translations In progress... PDF brought to you by Chapter 57: How to Override any Part of a Bundle | 362 generated on June 20, 2012
  • 363. Chapter 58 How to expose a Semantic Configuration for a Bundle If you open your application configuration file (usually app/config/config.yml), you'll see a number of different configuration "namespaces", such as framework, twig, and doctrine. Each of these configures a specific bundle, allowing you to configure things at a high level and then let the bundle make all the low-level, complex changes that result. For example, the following tells the FrameworkBundle to enable the form integration, which involves the defining of quite a few services as well as integration of other related components: framework: Listing 58-1 # ... form: true When you create a bundle, you have two choices on how to handle configuration: 1. Normal Service Configuration (easy): You can specify your services in a configuration file (e.g. services.yml) that lives in your bundle and then import it from your main application configuration. This is really easy, quick and totally effective. If you make use of parameters, then you still have the flexibility to customize your bundle from your application configuration. See "Importing Configuration with imports" for more details. 2. Exposing Semantic Configuration (advanced): This is the way configuration is done with the core bundles (as described above). The basic idea is that, instead of having the user override individual parameters, you let the user configure just a few, specifically created options. As the bundle developer, you then parse through that configuration and load services inside an "Extension" class. With this method, you won't need to import any configuration resources from your main application configuration: the Extension class can handle all of this. PDF brought to you by Chapter 58: How to expose a Semantic Configuration for a Bundle | 363 generated on June 20, 2012
  • 364. The second option - which you'll learn about in this article - is much more flexible, but also requires more time to setup. If you're wondering which method you should use, it's probably a good idea to start with method #1, and then change to #2 later if you need to. The second method has several specific advantages: • Much more powerful than simply defining parameters: a specific option value might trigger the creation of many service definitions; • Ability to have configuration hierarchy • Smart merging when several configuration files (e.g. config_dev.yml and config.yml) override each other's configuration; • Configuration validation (if you use a Configuration Class); • IDE auto-completion when you create an XSD and developers use XML. Overriding bundle parameters If a Bundle provides an Extension class, then you should generally not override any service container parameters from that bundle. The idea is that if an Extension class is present, every setting that should be configurable should be present in the configuration made available by that class. In other words the extension class defines all the publicly supported configuration settings for which backward compatibility will be maintained. Creating an Extension Class If you do choose to expose a semantic configuration for your bundle, you'll first need to create a new "Extension" class, which will handle the process. This class should live in the DependencyInjection directory of your bundle and its name should be constructed by replacing the Bundle suffix of the Bundle class name with Extension. For example, the Extension class of AcmeHelloBundle would be called AcmeHelloExtension: Listing // Acme/HelloBundle/DependencyInjection/AcmeHelloExtension.php 58-2 use SymfonyComponentHttpKernelDependencyInjectionExtension; use SymfonyComponentDependencyInjectionContainerBuilder; class AcmeHelloExtension extends Extension { public function load(array $configs, ContainerBuilder $container) { // where all of the heavy logic is done } public function getXsdValidationBasePath() { return __DIR__.'/../Resources/config/'; } public function getNamespace() { return 'http://www.example.com/symfony/schema/'; } } PDF brought to you by Chapter 58: How to expose a Semantic Configuration for a Bundle | 364 generated on June 20, 2012
  • 365. The getXsdValidationBasePath and getNamespace methods are only required if the bundle provides optional XSD's for the configuration. The presence of the previous class means that you can now define an acme_hello configuration namespace in any configuration file. The namespace acme_hello is constructed from the extension's class name by removing the word Extension and then lowercasing and underscoring the rest of the name. In other words, AcmeHelloExtension becomes acme_hello. You can begin specifying configuration under this namespace immediately: # app/config/config.yml Listing 58-3 acme_hello: ~ If you follow the naming conventions laid out above, then the load() method of your extension code is always called as long as your bundle is registered in the Kernel. In other words, even if the user does not provide any configuration (i.e. the acme_hello entry doesn't even appear), the load() method will be called and passed an empty $configs array. You can still provide some sensible defaults for your bundle if you want. Parsing the $configs Array Whenever a user includes the acme_hello namespace in a configuration file, the configuration under it is added to an array of configurations and passed to the load() method of your extension (Symfony2 automatically converts XML and YAML to an array). Take the following configuration: # app/config/config.yml Listing 58-4 acme_hello: foo: fooValue bar: barValue The array passed to your load() method will look like this: array( Listing 58-5 array( 'foo' => 'fooValue', 'bar' => 'barValue', ) ) Notice that this is an array of arrays, not just a single flat array of the configuration values. This is intentional. For example, if acme_hello appears in another configuration file - say config_dev.yml - with different values beneath it, then the incoming array might look like this: array( Listing 58-6 array( 'foo' => 'fooValue', 'bar' => 'barValue', ), array( 'foo' => 'fooDevValue', 'baz' => 'newConfigEntry', PDF brought to you by Chapter 58: How to expose a Semantic Configuration for a Bundle | 365 generated on June 20, 2012
  • 366. ), ) The order of the two arrays depends on which one is set first. It's your job, then, to decide how these configurations should be merged together. You might, for example, have later values override previous values or somehow merge them together. Later, in the Configuration Class section, you'll learn of a truly robust way to handle this. But for now, you might just merge them manually: Listing public function load(array $configs, ContainerBuilder $container) 58-7 { $config = array(); foreach ($configs as $subConfig) { $config = array_merge($config, $subConfig); } // now use the flat $config array } Make sure the above merging technique makes sense for your bundle. This is just an example, and you should be careful to not use it blindly. Using the load() Method Within load(), the $container variable refers to a container that only knows about this namespace configuration (i.e. it doesn't contain service information loaded from other bundles). The goal of the load() method is to manipulate the container, adding and configuring any methods or services needed by your bundle. Loading External Configuration Resources One common thing to do is to load an external configuration file that may contain the bulk of the services needed by your bundle. For example, suppose you have a services.xml file that holds much of your bundle's service configuration: Listing use SymfonyComponentDependencyInjectionLoaderXmlFileLoader; 58-8 use SymfonyComponentConfigFileLocator; public function load(array $configs, ContainerBuilder $container) { // prepare your $config variable $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('services.xml'); } You might even do this conditionally, based on one of the configuration values. For example, suppose you only want to load a set of services if an enabled option is passed and set to true: Listing public function load(array $configs, ContainerBuilder $container) 58-9 { // prepare your $config variable $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); PDF brought to you by Chapter 58: How to expose a Semantic Configuration for a Bundle | 366 generated on June 20, 2012
  • 367. if (isset($config['enabled']) && $config['enabled']) { $loader->load('services.xml'); } } Configuring Services and Setting Parameters Once you've loaded some service configuration, you may need to modify the configuration based on some of the input values. For example, suppose you have a service whose first argument is some string "type" that it will use internally. You'd like this to be easily configured by the bundle user, so in your service configuration file (e.g. services.xml), you define this service and use a blank parameter - acme_hello.my_service_type - as its first argument: <!-- src/Acme/HelloBundle/Resources/config/services.xml --> Listing 58-10 <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/ services/services-1.0.xsd"> <parameters> <parameter key="acme_hello.my_service_type" /> </parameters> <services> <service id="acme_hello.my_service" class="AcmeHelloBundleMyService"> <argument>%acme_hello.my_service_type%</argument> </service> </services> </container> But why would you define an empty parameter and then pass it to your service? The answer is that you'll set this parameter in your extension class, based on the incoming configuration values. Suppose, for example, that you want to allow the user to define this type option under a key called my_type. Add the following to the load() method to do this: public function load(array $configs, ContainerBuilder $container) Listing 58-11 { // prepare your $config variable $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('services.xml'); if (!isset($config['my_type'])) { throw new InvalidArgumentException('The "my_type" option must be set'); } $container->setParameter('acme_hello.my_service_type', $config['my_type']); } Now, the user can effectively configure the service by specifying the my_type configuration value: # app/config/config.yml Listing 58-12 acme_hello: my_type: foo # ... PDF brought to you by Chapter 58: How to expose a Semantic Configuration for a Bundle | 367 generated on June 20, 2012
  • 368. Global Parameters When you're configuring the container, be aware that you have the following global parameters available to use: • kernel.name • kernel.environment • kernel.debug • kernel.root_dir • kernel.cache_dir • kernel.logs_dir • kernel.bundle_dirs • kernel.bundles • kernel.charset All parameter and service names starting with a _ are reserved for the framework, and new ones must not be defined by bundles. Validation and Merging with a Configuration Class So far, you've done the merging of your configuration arrays by hand and are checking for the presence of config values manually using the isset() PHP function. An optional Configuration system is also available which can help with merging, validation, default values, and format normalization. Format normalization refers to the fact that certain formats - largely XML - result in slightly different configuration arrays and that these arrays need to be "normalized" to match everything else. To take advantage of this system, you'll create a Configuration class and build a tree that defines your configuration in that class: Listing // src/Acme/HelloBundle/DependencyInjection/Configuration.php 58-13 namespace AcmeHelloBundleDependencyInjection; use SymfonyComponentConfigDefinitionBuilderTreeBuilder; use SymfonyComponentConfigDefinitionConfigurationInterface; class Configuration implements ConfigurationInterface { public function getConfigTreeBuilder() { $treeBuilder = new TreeBuilder(); $rootNode = $treeBuilder->root('acme_hello'); $rootNode ->children() ->scalarNode('my_type')->defaultValue('bar')->end() ->end() ; return $treeBuilder; } PDF brought to you by Chapter 58: How to expose a Semantic Configuration for a Bundle | 368 generated on June 20, 2012
  • 369. This is a very simple example, but you can now use this class in your load() method to merge your configuration and force validation. If any options other than my_type are passed, the user will be notified with an exception that an unsupported option was passed: public function load(array $configs, ContainerBuilder $container) Listing 58-14 { $configuration = new Configuration(); $config = $this->processConfiguration($configuration, $configs); // ... } The processConfiguration() method uses the configuration tree you've defined in the Configuration class to validate, normalize and merge all of the configuration arrays together. The Configuration class can be much more complicated than shown here, supporting array nodes, "prototype" nodes, advanced validation, XML-specific normalization and advanced merging. The best way to see this in action is to checkout out some of the core Configuration classes, such as the one from the FrameworkBundle Configuration1 or the TwigBundle Configuration2. Extension Conventions When creating an extension, follow these simple conventions: • The extension must be stored in the DependencyInjection sub-namespace; • The extension must be named after the bundle name and suffixed with Extension (AcmeHelloExtension for AcmeHelloBundle); • The extension should provide an XSD schema. If you follow these simple conventions, your extensions will be registered automatically by Symfony2. If not, override the Bundle build()3 method in your bundle: use AcmeHelloBundleDependencyInjectionUnconventionalExtensionClass; Listing 58-15 class AcmeHelloBundle extends Bundle { public function build(ContainerBuilder $container) { parent::build($container); // register extensions that do not follow the conventions manually $container->registerExtension(new UnconventionalExtensionClass()); } } In this case, the extension class must also implement a getAlias() method and return a unique alias named after the bundle (e.g. acme_hello). This is required because the class name doesn't follow the standards by ending in Extension. Additionally, the load() method of your extension will only be called if the user specifies the acme_hello alias in at least one configuration file. Once again, this is because the Extension class doesn't follow the standards set out above, so nothing happens automatically. 1. https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php 2. https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php 3. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/Bundle/Bundle.html#build() PDF brought to you by Chapter 58: How to expose a Semantic Configuration for a Bundle | 369 generated on June 20, 2012
  • 370. Chapter 59 How to send an Email Sending emails is a classic task for any web application and one that has special complications and potential pitfalls. Instead of recreating the wheel, one solution to send emails is to use the SwiftmailerBundle, which leverages the power of the Swiftmailer1 library. Don't forget to enable the bundle in your kernel before using it: Listing public function registerBundles() 59-1 { $bundles = array( // ... new SymfonyBundleSwiftmailerBundleSwiftmailerBundle(), ); // ... } Configuration Before using Swiftmailer, be sure to include its configuration. The only mandatory configuration parameter is transport: Listing # app/config/config.yml 59-2 swiftmailer: transport: smtp encryption: ssl auth_mode: login host: smtp.gmail.com username: your_username password: your_password 1. http://www.swiftmailer.org/ PDF brought to you by Chapter 59: How to send an Email | 370 generated on June 20, 2012
  • 371. The majority of the Swiftmailer configuration deals with how the messages themselves should be delivered. The following configuration attributes are available: • transport (smtp, mail, sendmail, or gmail) • username • password • host • port • encryption (tls, or ssl) • auth_mode (plain, login, or cram-md5) • spool • type (how to queue the messages, only file is supported currently) • path (where to store the messages) • delivery_address (an email address where to send ALL emails) • disable_delivery (set to true to disable delivery completely) Sending Emails The Swiftmailer library works by creating, configuring and then sending Swift_Message objects. The "mailer" is responsible for the actual delivery of the message and is accessible via the mailer service. Overall, sending an email is pretty straightforward: public function indexAction($name) Listing 59-3 { $message = Swift_Message::newInstance() ->setSubject('Hello Email') ->setFrom('send@example.com') ->setTo('recipient@example.com') ->setBody($this->renderView('HelloBundle:Hello:email.txt.twig', array('name' => $name))) ; $this->get('mailer')->send($message); return $this->render(...); } To keep things decoupled, the email body has been stored in a template and rendered with the renderView() method. The $message object supports many more options, such as including attachments, adding HTML content, and much more. Fortunately, Swiftmailer covers the topic of Creating Messages2 in great detail in its documentation. Several other cookbook articles are available related to sending emails in Symfony2: • How to use Gmail to send Emails • How to Work with Emails During Development • How to Spool Email 2. http://swiftmailer.org/docs/messages.html PDF brought to you by Chapter 59: How to send an Email | 371 generated on June 20, 2012
  • 372. Chapter 60 How to use Gmail to send Emails During development, instead of using a regular SMTP server to send emails, you might find using Gmail easier and more practical. The Swiftmailer bundle makes it really easy. Instead of using your regular Gmail account, it's of course recommended that you create a special account. In the development configuration file, change the transport setting to gmail and set the username and password to the Google credentials: Listing # app/config/config_dev.yml 60-1 swiftmailer: transport: gmail username: your_gmail_username password: your_gmail_password You're done! The gmail transport is simply a shortcut that uses the smtp transport and sets encryption, auth_mode and host to work with Gmail. PDF brought to you by Chapter 60: How to use Gmail to send Emails | 372 generated on June 20, 2012
  • 373. Chapter 61 How to Work with Emails During Development When developing an application which sends email, you will often not want to actually send the email to the specified recipient during development. If you are using the SwiftmailerBundle with Symfony2, you can easily achieve this through configuration settings without having to make any changes to your application's code at all. There are two main choices when it comes to handling email during development: (a) disabling the sending of email altogether or (b) sending all email to a specific address. Disabling Sending You can disable sending email by setting the disable_delivery option to true. This is the default in the test environment in the Standard distribution. If you do this in the test specific config then email will not be sent when you run tests, but will continue to be sent in the prod and dev environments: # app/config/config_test.yml Listing 61-1 swiftmailer: disable_delivery: true If you'd also like to disable deliver in the dev environment, simply add this same configuration to the config_dev.yml file. Sending to a Specified Address You can also choose to have all email sent to a specific address, instead of the address actually specified when sending the message. This can be done via the delivery_address option: # app/config/config_dev.yml Listing 61-2 swiftmailer: delivery_address: dev@example.com Now, suppose you're sending an email to recipient@example.com. public function indexAction($name) Listing 61-3 { PDF brought to you by Chapter 61: How to Work with Emails During Development | 373 generated on June 20, 2012
  • 374. $message = Swift_Message::newInstance() ->setSubject('Hello Email') ->setFrom('send@example.com') ->setTo('recipient@example.com') ->setBody($this->renderView('HelloBundle:Hello:email.txt.twig', array('name' => $name))) ; $this->get('mailer')->send($message); return $this->render(...); } In the dev environment, the email will instead be sent to dev@example.com. Swiftmailer will add an extra header to the email, X-Swift-To, containing the replaced address, so you can still see who it would have been sent to. In addition to the to addresses, this will also stop the email being sent to any CC and BCC addresses set for it. Swiftmailer will add additional headers to the email with the overridden addresses in them. These are X-Swift-Cc and X-Swift-Bcc for the CC and BCC addresses respectively. Viewing from the Web Debug Toolbar You can view any email sent during a single response when you are in the dev environment using the Web Debug Toolbar. The email icon in the toolbar will show how many emails were sent. If you click it, a report will open showing the details of the sent emails. If you're sending an email and then immediately redirecting to another page, the web debug toolbar will not display an email icon or a report on the next page. Instead, you can set the intercept_redirects option to true in the config_dev.yml file, which will cause the redirect to stop and allow you to open the report with details of the sent emails. Alternatively, you can open the profiler after the redirect and search by the submit URL used on previous request (e.g. /contact/handle). The profiler's search feature allows you to load the profiler information for any past requests. Listing # app/config/config_dev.yml 61-4 web_profiler: intercept_redirects: true PDF brought to you by Chapter 61: How to Work with Emails During Development | 374 generated on June 20, 2012
  • 375. Chapter 62 How to Spool Email When you are using the SwiftmailerBundle to send an email from a Symfony2 application, it will default to sending the email immediately. You may, however, want to avoid the performance hit of the communication between Swiftmailer and the email transport, which could cause the user to wait for the next page to load while the email is sending. This can be avoided by choosing to "spool" the emails instead of sending them directly. This means that Swiftmailer does not attempt to send the email but instead saves the message to somewhere such as a file. Another process can then read from the spool and take care of sending the emails in the spool. Currently only spooling to file is supported by Swiftmailer. In order to use the spool, use the following configuration: # app/config/config.yml Listing 62-1 swiftmailer: # ... spool: type: file path: /path/to/spool If you want to store the spool somewhere with your project directory, remember that you can use the %kernel.root_dir% parameter to reference the project's root: path: "%kernel.root_dir%/spool" Listing 62-2 Now, when your app sends an email, it will not actually be sent but instead added to the spool. Sending the messages from the spool is done separately. There is a console command to send the messages in the spool: php app/console swiftmailer:spool:send --env=prod Listing 62-3 It has an option to limit the number of messages to be sent: php app/console swiftmailer:spool:send --message-limit=10 --env=prod Listing 62-4 You can also set the time limit in seconds: PDF brought to you by Chapter 62: How to Spool Email | 375 generated on June 20, 2012
  • 376. Listing php app/console swiftmailer:spool:send --time-limit=10 --env=prod 62-5 Of course you will not want to run this manually in reality. Instead, the console command should be triggered by a cron job or scheduled task and run at a regular interval. PDF brought to you by Chapter 62: How to Spool Email | 376 generated on June 20, 2012
  • 377. Chapter 63 How to simulate HTTP Authentication in a Functional Test If your application needs HTTP authentication, pass the username and password as server variables to createClient(): $client = static::createClient(array(), array( Listing 63-1 'PHP_AUTH_USER' => 'username', 'PHP_AUTH_PW' => 'pa$$word', )); You can also override it on a per request basis: $client->request('DELETE', '/post/12', array(), array( Listing 63-2 'PHP_AUTH_USER' => 'username', 'PHP_AUTH_PW' => 'pa$$word', )); When your application is using a form_login, you can simplify your tests by allowing your test configuration to make use of HTTP authentication. This way you can use the above to authenticate in tests, but still have your users login via the normal form_login. The trick is to include the http_basic key in your firewall, along with the form_login key: # app/config/config_test.yml Listing 63-3 security: firewalls: your_firewall_name: http_basic: PDF brought to you by Chapter 63: How to simulate HTTP Authentication in a Functional Test | 377 generated on June 20, 2012
  • 378. Chapter 64 How to test the Interaction of several Clients If you need to simulate an interaction between different Clients (think of a chat for instance), create several Clients: Listing $harry = static::createClient(); 64-1 $sally = static::createClient(); $harry->request('POST', '/say/sally/Hello'); $sally->request('GET', '/messages'); $this->assertEquals(201, $harry->getResponse()->getStatusCode()); $this->assertRegExp('/Hello/', $sally->getResponse()->getContent()); This works except when your code maintains a global state or if it depends on a third-party library that has some kind of global state. In such a case, you can insulate your clients: Listing $harry = static::createClient(); 64-2 $sally = static::createClient(); $harry->insulate(); $sally->insulate(); $harry->request('POST', '/say/sally/Hello'); $sally->request('GET', '/messages'); $this->assertEquals(201, $harry->getResponse()->getStatusCode()); $this->assertRegExp('/Hello/', $sally->getResponse()->getContent()); Insulated clients transparently execute their requests in a dedicated and clean PHP process, thus avoiding any side-effects. As an insulated client is slower, you can keep one client in the main process, and insulate the other ones. PDF brought to you by Chapter 64: How to test the Interaction of several Clients | 378 generated on June 20, 2012
  • 379. Chapter 65 How to use the Profiler in a Functional Test It's highly recommended that a functional test only tests the Response. But if you write functional tests that monitor your production servers, you might want to write tests on the profiling data as it gives you a great way to check various things and enforce some metrics. The Symfony2 Profiler gathers a lot of data for each request. Use this data to check the number of database calls, the time spent in the framework, ... But before writing assertions, always check that the profiler is indeed available (it is enabled by default in the test environment): class HelloControllerTest extends WebTestCase Listing 65-1 { public function testIndex() { $client = static::createClient(); $crawler = $client->request('GET', '/hello/Fabien'); // Write some assertions about the Response // ... // Check that the profiler is enabled if ($profile = $client->getProfile()) { // check the number of requests $this->assertLessThan(10, $profile->getCollector('db')->getQueryCount()); // check the time spent in the framework $this->assertLessThan(0.5, $profile->getCollector('timer')->getTime()); } } } If a test fails because of profiling data (too many DB queries for instance), you might want to use the Web Profiler to analyze the request after the tests finish. It's easy to achieve if you embed the token in the error message: $this->assertLessThan( Listing 65-2 30, $profile->get('db')->getQueryCount(), PDF brought to you by Chapter 65: How to use the Profiler in a Functional Test | 379 generated on June 20, 2012
  • 380. sprintf('Checks that query count is less than 30 (token %s)', $profile->getToken()) ); The profiler store can be different depending on the environment (especially if you use the SQLite store, which is the default configured one). The profiler information is available even if you insulate the client or if you use an HTTP layer for your tests. Read the API for built-in data collectors to learn more about their interfaces. PDF brought to you by Chapter 65: How to use the Profiler in a Functional Test | 380 generated on June 20, 2012
  • 381. Chapter 66 How to test Doctrine Repositories Unit testing Doctrine repositories in a Symfony project is not recommended. When you're dealing with a repository, you're really dealing with something that's meant to be tested against a real database connection. Fortunately, you can easily test your queries against a real database, as described below. Functional Testing If you need to actually execute a query, you will need to boot the kernel to get a valid connection. In this case, you'll extend the WebTestCase, which makes all of this quite easy: // src/Acme/StoreBundle/Tests/Entity/ProductRepositoryFunctionalTest.php Listing 66-1 namespace AcmeStoreBundleTestsEntity; use SymfonyBundleFrameworkBundleTestWebTestCase; class ProductRepositoryFunctionalTest extends WebTestCase { /** * @var DoctrineORMEntityManager */ private $em; public function setUp() { $kernel = static::createKernel(); $kernel->boot(); $this->em = $kernel->getContainer()->get('doctrine.orm.entity_manager'); } public function testProductByCategoryName() { $results = $this->em ->getRepository('AcmeStoreBundle:Product') ->searchProductsByNameQuery('foo') PDF brought to you by Chapter 66: How to test Doctrine Repositories | 381 generated on June 20, 2012
  • 382. ->getResult() ; $this->assertCount(1, $results); } } PDF brought to you by Chapter 66: How to test Doctrine Repositories | 382 generated on June 20, 2012
  • 383. Chapter 67 How to load Security Users from the Database (the Entity Provider) The security layer is one of the smartest tools of Symfony. It handles two things: the authentication and the authorization processes. Although it may seem difficult to understand how it works internally, the security system is very flexible and allows you to integrate your application with any authentication backend, like Active Directory, an OAuth server or a database. Introduction This article focuses on how to authenticate users against a database table managed by a Doctrine entity class. The content of this cookbook entry is split in three parts. The first part is about designing a Doctrine User entity class and making it usable in the security layer of Symfony. The second part describes how to easily authenticate a user with the Doctrine EntityUserProvider1 object bundled with the framework and some configuration. Finally, the tutorial will demonstrate how to create a custom EntityUserProvider2 object to retrieve users from a database with custom conditions. This tutorial assumes there is a bootstrapped and loaded AcmeUserBundle bundle in the application kernel. The Data Model For the purpose of this cookbook, the AcmeUserBundle bundle contains a User entity class with the following fields: id, username, salt, password, email and isActive. The isActive field tells whether or not the user account is active. To make it shorter, the getter and setter methods for each have been removed to focus on the most important methods that come from the UserInterface3. 1. http://api.symfony.com/2.0/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.html 2. http://api.symfony.com/2.0/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.html 3. http://api.symfony.com/2.0/Symfony/Component/Security/Core/User/UserInterface.html PDF brought to you by Chapter 67: How to load Security Users from the Database (the Entity Provider) | 383 generated on June 20, 2012
  • 384. Listing // src/Acme/UserBundle/Entity/User.php 67-1 namespace AcmeUserBundleEntity; use DoctrineORMMapping as ORM; use SymfonyComponentSecurityCoreUserUserInterface; /** * AcmeUserBundleEntityUser * * @ORMTable(name="acme_users") * @ORMEntity(repositoryClass="AcmeUserBundleEntityUserRepository") */ class User implements UserInterface { /** * @ORMColumn(type="integer") * @ORMId * @ORMGeneratedValue(strategy="AUTO") */ private $id; /** * @ORMColumn(type="string", length=25, unique=true) */ private $username; /** * @ORMColumn(type="string", length=32) */ private $salt; /** * @ORMColumn(type="string", length=40) */ private $password; /** * @ORMColumn(type="string", length=60, unique=true) */ private $email; /** * @ORMColumn(name="is_active", type="boolean") */ private $isActive; public function __construct() { $this->isActive = true; $this->salt = md5(uniqid(null, true)); } /** * @inheritDoc */ public function getUsername() { return $this->username; } PDF brought to you by Chapter 67: How to load Security Users from the Database (the Entity Provider) | 384 generated on June 20, 2012
  • 385. /** * @inheritDoc */ public function getSalt() { return $this->salt; } /** * @inheritDoc */ public function getPassword() { return $this->password; } /** * @inheritDoc */ public function getRoles() { return array('ROLE_USER'); } /** * @inheritDoc */ public function eraseCredentials() { } /** * @inheritDoc */ public function equals(UserInterface $user) { return $this->username === $user->getUsername(); } } In order to use an instance of the AcmeUserBundle:User class in the Symfony security layer, the entity class must implement the UserInterface4. This interface forces the class to implement the six following methods: • getUsername() • getSalt() • getPassword() • getRoles() • eraseCredentials() • equals() For more details on each of these, see UserInterface5. To keep it simple, the equals() method just compares the username field but it's also possible to do more checks depending on the complexity of your data model. On the other hand, the eraseCredentials() method remains empty as we don't care about it in this tutorial. 4. http://api.symfony.com/2.0/Symfony/Component/Security/Core/User/UserInterface.html 5. http://api.symfony.com/2.0/Symfony/Component/Security/Core/User/UserInterface.html PDF brought to you by Chapter 67: How to load Security Users from the Database (the Entity Provider) | 385 generated on June 20, 2012
  • 386. Below is an export of my User table from MySQL. For details on how to create user records and encode their password, see Encoding the User's Password. Listing mysql> select * from user; 67-2 +----+----------+----------------------------------+------------------------------------------+---------------- | id | username | salt | password | email | is_active | +----+----------+----------------------------------+------------------------------------------+---------------- | 1 | hhamon | 7308e59b97f6957fb42d66f894793079 | 09610f61637408828a35d7debee5b38a8350eebe | hhamon@example.com | 1 | | 2 | jsmith | ce617a6cca9126bf4036ca0c02e82dee | 8390105917f3a3d533815250ed7c64b4594d7ebf | jsmith@example.com | 1 | | 3 | maxime | cd01749bb995dc658fa56ed45458d807 | 9764731e5f7fb944de5fd8efad4949b995b72a3c | maxime@example.com | 0 | | 4 | donald | 6683c2bfd90c0426088402930cadd0f8 | 5c3bcec385f59edcc04490d1db95fdb8673bf612 | donald@example.com | 1 | +----+----------+----------------------------------+------------------------------------------+---------------- 4 rows in set (0.00 sec) The database now contains four users with different usernames, emails and statuses. The next part will focus on how to authenticate one of these users thanks to the Doctrine entity user provider and a couple of lines of configuration. Authenticating Someone against a Database Authenticating a Doctrine user against the database with the Symfony security layer is a piece of cake. Everything resides in the configuration of the SecurityBundle stored in the app/config/security.yml file. Below is an example of configuration where the user will enter his/her username and password via HTTP basic authentication. That information will then be checked against our User entity records in the database: Listing # app/config/security.yml 67-3 security: encoders: AcmeUserBundleEntityUser: algorithm: sha1 encode_as_base64: false iterations: 1 role_hierarchy: ROLE_ADMIN: ROLE_USER ROLE_SUPER_ADMIN: [ ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH ] providers: administrators: entity: { class: AcmeUserBundle:User, property: username } firewalls: admin_area: pattern: ^/admin http_basic: ~ access_control: - { path: ^/admin, roles: ROLE_ADMIN } PDF brought to you by Chapter 67: How to load Security Users from the Database (the Entity Provider) | 386 generated on June 20, 2012
  • 387. The encoders section associates the sha1 password encoder to the entity class. This means that Symfony will expect the password that's stored in the database to be encoded using this algorithm. For details on how to create a new User object with a properly encoded password, see the Encoding the User's Password section of the security chapter. The providers section defines an administrators user provider. A user provider is a "source" of where users are loaded during authentication. In this case, the entity keyword means that Symfony will use the Doctrine entity user provider to load User entity objects from the database by using the username unique field. In other words, this tells Symfony how to fetch the user from the database before checking the password validity. This code and configuration works but it's not enough to secure the application for active users. As of now, we still can authenticate with maxime. The next section explains how to forbid non active users. Forbid non Active Users The easiest way to exclude non active users is to implement the AdvancedUserInterface6 interface that takes care of checking the user's account status. The AdvancedUserInterface7 extends the UserInterface8 interface, so you just need to switch to the new interface in the AcmeUserBundle:User entity class to benefit from simple and advanced authentication behaviors. The AdvancedUserInterface9 interface adds four extra methods to validate the account status: • isAccountNonExpired() checks whether the user's account has expired, • isAccountNonLocked() checks whether the user is locked, • isCredentialsNonExpired() checks whether the user's credentials (password) has expired, • isEnabled() checks whether the user is enabled. For this example, the first three methods will return true whereas the isEnabled() method will return the boolean value in the isActive field. // src/Acme/UserBundle/Entity/User.php Listing 67-4 namespace AcmeBundleUserBundleEntity; // ... use SymfonyComponentSecurityCoreUserAdvancedUserInterface; // ... class User implements AdvancedUserInterface { // ... public function isAccountNonExpired() { return true; } public function isAccountNonLocked() { return true; } public function isCredentialsNonExpired() { 6. http://api.symfony.com/2.0/Symfony/Component/Security/Core/User/AdvancedUserInterface.html 7. http://api.symfony.com/2.0/Symfony/Component/Security/Core/User/AdvancedUserInterface.html 8. http://api.symfony.com/2.0/Symfony/Component/Security/Core/User/UserInterface.html 9. http://api.symfony.com/2.0/Symfony/Component/Security/Core/User/AdvancedUserInterface.html PDF brought to you by Chapter 67: How to load Security Users from the Database (the Entity Provider) | 387 generated on June 20, 2012
  • 388. return true; } public function isEnabled() { return $this->isActive; } } If we try to authenticate a maxime, the access is now forbidden as this user does not have an enabled account. The next session will focus on how to write a custom entity provider to authenticate a user with his username or his email address. Authenticating Someone with a Custom Entity Provider The next step is to allow a user to authenticate with his username or his email address as they are both unique in the database. Unfortunately, the native entity provider is only able to handle a single property to fetch the user from the database. To accomplish this, create a custom entity provider that looks for a user whose username or email field matches the submitted login username. The good news is that a Doctrine repository object can act as an entity user provider if it implements the UserProviderInterface10. This interface comes with three methods to implement: loadUserByUsername($username), refreshUser(UserInterface $user), and supportsClass($class). For more details, see UserProviderInterface11. The code below shows the implementation of the UserProviderInterface12 in the UserRepository class: Listing // src/Acme/UserBundle/Entity/UserRepository.php 67-5 namespace AcmeUserBundleEntity; use SymfonyComponentSecurityCoreUserUserInterface; use SymfonyComponentSecurityCoreUserUserProviderInterface; use SymfonyComponentSecurityCoreExceptionUsernameNotFoundException; use SymfonyComponentSecurityCoreExceptionUnsupportedUserException; use DoctrineORMEntityRepository; use DoctrineORMNoResultException; class UserRepository extends EntityRepository implements UserProviderInterface { public function loadUserByUsername($username) { $q = $this ->createQueryBuilder('u') ->where('u.username = :username OR u.email = :email') ->setParameter('username', $username) ->setParameter('email', $username) ->getQuery() ; try { // The Query::getSingleResult() method throws an exception // if there is no record matching the criteria. $user = $q->getSingleResult(); 10. http://api.symfony.com/2.0/Symfony/Component/Security/Core/User/UserProviderInterface.html 11. http://api.symfony.com/2.0/Symfony/Component/Security/Core/User/UserProviderInterface.html 12. http://api.symfony.com/2.0/Symfony/Component/Security/Core/User/UserProviderInterface.html PDF brought to you by Chapter 67: How to load Security Users from the Database (the Entity Provider) | 388 generated on June 20, 2012
  • 389. } catch (NoResultException $e) { throw new UsernameNotFoundException(sprintf('Unable to find an active admin AcmeUserBundle:User object identified by "%s".', $username), null, 0, $e); } return $user; } public function refreshUser(UserInterface $user) { $class = get_class($user); if (!$this->supportsClass($class)) { throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', $class)); } return $this->loadUserByUsername($user->getUsername()); } public function supportsClass($class) { return $this->getEntityName() === $class || is_subclass_of($class, $this->getEntityName()); } } To finish the implementation, the configuration of the security layer must be changed to tell Symfony to use the new custom entity provider instead of the generic Doctrine entity provider. It's trival to achieve by removing the property field in the security.providers.administrators.entity section of the security.yml file. # app/config/security.yml Listing 67-6 security: # ... providers: administrators: entity: { class: AcmeUserBundle:User } # ... By doing this, the security layer will use an instance of UserRepository and call its loadUserByUsername() method to fetch a user from the database whether he filled in his username or email address. Managing Roles in the Database The end of this tutorial focuses on how to store and retrieve a list of roles from the database. As mentioned previously, when your user is loaded, its getRoles() method returns the array of security roles that should be assigned to the user. You can load this data from anywhere - a hardcoded list used for all users (e.g. array('ROLE_USER')), a Doctrine array property called roles, or via a Doctrine relationship, as we'll learn about in this section. In a typical setup, you should always return at least 1 role from the getRoles() method. By convention, a role called ROLE_USER is usually returned. If you fail to return any roles, it may appear as if your user isn't authenticated at all. PDF brought to you by Chapter 67: How to load Security Users from the Database (the Entity Provider) | 389 generated on June 20, 2012
  • 390. In this example, the AcmeUserBundle:User entity class defines a many-to-many relationship with a AcmeUserBundle:Group entity class. A user can be related to several groups and a group can be composed of one or more users. As a group is also a role, the previous getRoles() method now returns the list of related groups: Listing // src/Acme/UserBundle/Entity/User.php 67-7 namespace AcmeBundleUserBundleEntity; use DoctrineCommonCollectionsArrayCollection; // ... class User implements AdvancedUserInterface { /** * @ORMManyToMany(targetEntity="Group", inversedBy="users") * */ private $groups; public function __construct() { $this->groups = new ArrayCollection(); } // ... public function getRoles() { return $this->groups->toArray(); } } The AcmeUserBundle:Group entity class defines three table fields (id, name and role). The unique role field contains the role name used by the Symfony security layer to secure parts of the application. The most important thing to notice is that the AcmeUserBundle:Group entity class implements the RoleInterface13 that forces it to have a getRole() method: Listing namespace AcmeBundleUserBundleEntity; 67-8 use SymfonyComponentSecurityCoreRoleRoleInterface; use DoctrineCommonCollectionsArrayCollection; use DoctrineORMMapping as ORM; /** * @ORMTable(name="acme_groups") * @ORMEntity() */ class Group implements RoleInterface { /** * @ORMColumn(name="id", type="integer") * @ORMId() * @ORMGeneratedValue(strategy="AUTO") */ private $id; /** 13. http://api.symfony.com/2.0/Symfony/Component/Security/Core/Role/RoleInterface.html PDF brought to you by Chapter 67: How to load Security Users from the Database (the Entity Provider) | 390 generated on June 20, 2012
  • 391. * @ORMColumn(name="name", type="string", length=30) */ private $name; /** * @ORMColumn(name="role", type="string", length=20, unique=true) */ private $role; /** * @ORMManyToMany(targetEntity="User", mappedBy="groups") */ private $users; public function __construct() { $this->users = new ArrayCollection(); } // ... getters and setters for each property /** * @see RoleInterface */ public function getRole() { return $this->role; } } To improve performances and avoid lazy loading of groups when retrieving a user from the custom entity provider, the best solution is to join the groups relationship in the UserRepository::loadUserByUsername() method. This will fetch the user and his associated roles / groups with a single query: // src/Acme/UserBundle/Entity/UserRepository.php Listing 67-9 namespace AcmeBundleUserBundleEntity; // ... class UserRepository extends EntityRepository implements UserProviderInterface { public function loadUserByUsername($username) { $q = $this ->createQueryBuilder('u') ->select('u, g') ->leftJoin('u.groups', 'g') ->where('u.username = :username OR u.email = :email') ->setParameter('username', $username) ->setParameter('email', $username) ->getQuery() ; // ... } // ... } PDF brought to you by Chapter 67: How to load Security Users from the Database (the Entity Provider) | 391 generated on June 20, 2012
  • 392. The QueryBuilder::leftJoin() method joins and fetches related groups from the AcmeUserBundle:User model class when a user is retrieved with his email address or username. PDF brought to you by Chapter 67: How to load Security Users from the Database (the Entity Provider) | 392 generated on June 20, 2012
  • 393. Chapter 68 How to add "Remember Me" Login Functionality Once a user is authenticated, their credentials are typically stored in the session. This means that when the session ends they will be logged out and have to provide their login details again next time they wish to access the application. You can allow users to choose to stay logged in for longer than the session lasts using a cookie with the remember_me firewall option. The firewall needs to have a secret key configured, which is used to encrypt the cookie's content. It also has several options with default values which are shown here: # app/config/security.yml Listing 68-1 firewalls: main: remember_me: key: "%secret%" lifetime: 3600 path: / domain: ~ # Defaults to the current domain from $_SERVER It's a good idea to provide the user with the option to use or not use the remember me functionality, as it will not always be appropriate. The usual way of doing this is to add a checkbox to the login form. By giving the checkbox the name _remember_me, the cookie will automatically be set when the checkbox is checked and the user successfully logs in. So, your specific login form might ultimately look like this: {# src/Acme/SecurityBundle/Resources/views/Security/login.html.twig #} Listing 68-2 {% 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" /> PDF brought to you by Chapter 68: How to add "Remember Me" Login Functionality | 393 generated on June 20, 2012
  • 394. <input type="checkbox" id="remember_me" name="_remember_me" checked /> <label for="remember_me">Keep me logged in</label> <input type="submit" name="login" /> </form> The user will then automatically be logged in on subsequent visits while the cookie remains valid. Forcing the User to Re-authenticate before accessing certain Resources When the user returns to your site, he/she is authenticated automatically based on the information stored in the remember me cookie. This allows the user to access protected resources as if the user had actually authenticated upon visiting the site. In some cases, however, you may want to force the user to actually re-authenticate before accessing certain resources. For example, you might allow "remember me" users to see basic account information, but then require them to actually re-authenticate before modifying that information. The security component provides an easy way to do this. In addition to roles explicitly assigned to them, users are automatically given one of the following roles depending on how they are authenticated: • IS_AUTHENTICATED_ANONYMOUSLY - automatically assigned to a user who is in a firewall protected part of the site but who has not actually logged in. This is only possible if anonymous access has been allowed. • IS_AUTHENTICATED_REMEMBERED - automatically assigned to a user who was authenticated via a remember me cookie. • IS_AUTHENTICATED_FULLY - automatically assigned to a user that has provided their login details during the current session. You can use these to control access beyond the explicitly assigned roles. If you have the IS_AUTHENTICATED_REMEMBERED role, then you also have the IS_AUTHENTICATED_ANONYMOUSLY role. If you have the IS_AUTHENTICATED_FULLY role, then you also have the other two roles. In other words, these roles represent three levels of increasing "strength" of authentication. You can use these additional roles for finer grained control over access to parts of a site. For example, you may want your user to be able to view their account at /account when authenticated by cookie but to have to provide their login details to be able to edit the account details. You can do this by securing specific controller actions using these roles. The edit action in the controller could be secured using the service context. In the following example, the action is only allowed if the user has the IS_AUTHENTICATED_FULLY role. Listing use SymfonyComponentSecurityCoreExceptionAccessDeniedException 68-3 // ... public function editAction() { if (false === $this->get('security.context')->isGranted( 'IS_AUTHENTICATED_FULLY' )) { throw new AccessDeniedException(); } PDF brought to you by Chapter 68: How to add "Remember Me" Login Functionality | 394 generated on June 20, 2012
  • 395. // ... } You can also choose to install and use the optional JMSSecurityExtraBundle1, which can secure your controller using annotations: use JMSSecurityExtraBundleAnnotationSecure; Listing 68-4 /** * @Secure(roles="IS_AUTHENTICATED_FULLY") */ public function editAction($name) { // ... } If you also had an access control in your security configuration that required the user to have a ROLE_USER role in order to access any of the account area, then you'd have the following situation: • If a non-authenticated (or anonymously authenticated user) tries to access the account area, the user will be asked to authenticate. • Once the user has entered his username and password, assuming the user receives the ROLE_USER role per your configuration, the user will have the IS_AUTHENTICATED_FULLY role and be able to access any page in the account section, including the editAction controller. • If the user's session ends, when the user returns to the site, he will be able to access every account page - except for the edit page - without being forced to re-authenticate. However, when he tries to access the editAction controller, he will be forced to re- authenticate, since he is not, yet, fully authenticated. For more information on securing services or methods in this way, see How to secure any Service or Method in your Application. 1. https://github.com/schmittjoh/JMSSecurityExtraBundle PDF brought to you by Chapter 68: How to add "Remember Me" Login Functionality | 395 generated on June 20, 2012
  • 396. Chapter 69 How to implement your own Voter to blacklist IP Addresses The Symfony2 security component provides several layers to authenticate users. One of the layers is called a voter. A voter is a dedicated class that checks if the user has the rights to be connected to the application. For instance, Symfony2 provides a layer that checks if the user is fully authenticated or if it has some expected roles. It is sometimes useful to create a custom voter to handle a specific case not handled by the framework. In this section, you'll learn how to create a voter that will allow you to blacklist users by their IP. The Voter Interface A custom voter must implement VoterInterface1, which requires the following three methods: Listing interface VoterInterface 69-1 { function supportsAttribute($attribute); function supportsClass($class); function vote(TokenInterface $token, $object, array $attributes); } The supportsAttribute() method is used to check if the voter supports the given user attribute (i.e: a role, an acl, etc.). The supportsClass() method is used to check if the voter supports the current user token class. The vote() method must implement the business logic that verifies whether or not the user is granted access. This method must return one of the following values: • VoterInterface::ACCESS_GRANTED: The user is allowed to access the application • VoterInterface::ACCESS_ABSTAIN: The voter cannot decide if the user is granted or not • VoterInterface::ACCESS_DENIED: The user is not allowed to access the application 1. http://api.symfony.com/2.0/Symfony/Component/Security/Core/Authorization/Voter/VoterInterface.html PDF brought to you by Chapter 69: How to implement your own Voter to blacklist IP Addresses | 396 generated on June 20, 2012
  • 397. In this example, we will check if the user's IP address matches against a list of blacklisted addresses. If the user's IP is blacklisted, we will return VoterInterface::ACCESS_DENIED, otherwise we will return VoterInterface::ACCESS_ABSTAIN as this voter's purpose is only to deny access, not to grant access. Creating a Custom Voter To blacklist a user based on its IP, we can use the request service and compare the IP address against a set of blacklisted IP addresses: namespace AcmeDemoBundleSecurityAuthorizationVoter; Listing 69-2 use SymfonyComponentDependencyInjectionContainerInterface; use SymfonyComponentSecurityCoreAuthorizationVoterVoterInterface; use SymfonyComponentSecurityCoreAuthenticationTokenTokenInterface; class ClientIpVoter implements VoterInterface { public function __construct(ContainerInterface $container, array $blacklistedIp = array()) { $this->container = $container; $this->blacklistedIp = $blacklistedIp; } public function supportsAttribute($attribute) { // we won't check against a user attribute, so we return true return true; } public function supportsClass($class) { // our voter supports all type of token classes, so we return true return true; } function vote(TokenInterface $token, $object, array $attributes) { $request = $this->container->get('request'); if (in_array($request->getClientIp(), $this->blacklistedIp)) { return VoterInterface::ACCESS_DENIED; } return VoterInterface::ACCESS_ABSTAIN; } } That's it! The voter is done. The next step is to inject the voter into the security layer. This can be done easily through the service container. Declaring the Voter as a Service To inject the voter into the security layer, we must declare it as a service, and tag it as a "security.voter": # src/Acme/AcmeBundle/Resources/config/services.yml Listing 69-3 services: security.access.blacklist_voter: PDF brought to you by Chapter 69: How to implement your own Voter to blacklist IP Addresses | 397 generated on June 20, 2012
  • 398. class: AcmeDemoBundleSecurityAuthorizationVoterClientIpVoter arguments: [@service_container, [123.123.123.123, 171.171.171.171]] public: false tags: - { name: security.voter } Be sure to import this configuration file from your main application configuration file (e.g. app/ config/config.yml). For more information see Importing Configuration with imports. To read more about defining services in general, see the Service Container chapter. Changing the Access Decision Strategy In order for the new voter to take effect, we need to change the default access decision strategy, which, by default, grants access if any voter grants access. In our case, we will choose the unanimous strategy. Unlike the affirmative strategy (the default), with the unanimous strategy, if only one voter denies access (e.g. the ClientIpVoter), access is not granted to the end user. To do that, override the default access_decision_manager section of your application configuration file with the following code. Listing # app/config/security.yml 69-4 security: access_decision_manager: # Strategy can be: affirmative, unanimous or consensus strategy: unanimous That's it! Now, when deciding whether or not a user should have access, the new voter will deny access to any user in the list of blacklisted IPs. PDF brought to you by Chapter 69: How to implement your own Voter to blacklist IP Addresses | 398 generated on June 20, 2012
  • 399. Chapter 70 Access Control Lists (ACLs) In complex applications, you will often face the problem that access decisions cannot only be based on the person (Token) who is requesting access, but also involve a domain object that access is being requested for. This is where the ACL system comes in. Imagine you are designing a blog system where your users can comment on your posts. Now, you want a user to be able to edit his own comments, but not those of other users; besides, you yourself want to be able to edit all comments. In this scenario, Comment would be our domain object that you want to restrict access to. You could take several approaches to accomplish this using Symfony2, two basic approaches are (non-exhaustive): • Enforce security in your business methods: Basically, that means keeping a reference inside each Comment to all users who have access, and then compare these users to the provided Token. • Enforce security with roles: In this approach, you would add a role for each Comment object, i.e. ROLE_COMMENT_1, ROLE_COMMENT_2, etc. Both approaches are perfectly valid. However, they couple your authorization logic to your business code which makes it less reusable elsewhere, and also increases the difficulty of unit testing. Besides, you could run into performance issues if many users would have access to a single domain object. Fortunately, there is a better way, which we will talk about now. Bootstrapping Now, before we finally can get into action, we need to do some bootstrapping. First, we need to configure the connection the ACL system is supposed to use: # app/config/security.yml Listing 70-1 security: acl: connection: default PDF brought to you by Chapter 70: Access Control Lists (ACLs) | 399 generated on June 20, 2012
  • 400. The ACL system requires at least one Doctrine DBAL connection to be configured. However, that does not mean that you have to use Doctrine for mapping your domain objects. You can use whatever mapper you like for your objects, be it Doctrine ORM, Mongo ODM, Propel, or raw SQL, the choice is yours. After the connection is configured, we have to import the database structure. Fortunately, we have a task for this. Simply run the following command: Listing php app/console init:acl 70-2 Getting Started Coming back to our small example from the beginning, let's implement ACL for it. Creating an ACL, and adding an ACE Listing use SymfonyComponentSecurityCoreExceptionAccessDeniedException; 70-3 use SymfonyComponentSecurityAclDomainObjectIdentity; use SymfonyComponentSecurityAclDomainUserSecurityIdentity; use SymfonyComponentSecurityAclPermissionMaskBuilder; // ... // BlogController.php public function addCommentAction(Post $post) { $comment = new Comment(); // setup $form, and bind data // ... if ($form->isValid()) { $entityManager = $this->get('doctrine.orm.default_entity_manager'); $entityManager->persist($comment); $entityManager->flush(); // creating the ACL $aclProvider = $this->get('security.acl.provider'); $objectIdentity = ObjectIdentity::fromDomainObject($comment); $acl = $aclProvider->createAcl($objectIdentity); // retrieving the security identity of the currently logged-in user $securityContext = $this->get('security.context'); $user = $securityContext->getToken()->getUser(); $securityIdentity = UserSecurityIdentity::fromAccount($user); // grant owner access $acl->insertObjectAce($securityIdentity, MaskBuilder::MASK_OWNER); $aclProvider->updateAcl($acl); } } There are a couple of important implementation decisions in this code snippet. For now, I only want to highlight two: First, you may have noticed that ->createAcl() does not accept domain objects directly, but only implementations of the ObjectIdentityInterface. This additional step of indirection allows you to PDF brought to you by Chapter 70: Access Control Lists (ACLs) | 400 generated on June 20, 2012
  • 401. work with ACLs even when you have no actual domain object instance at hand. This will be extremely helpful if you want to check permissions for a large number of objects without actually hydrating these objects. The other interesting part is the ->insertObjectAce() call. In our example, we are granting the user who is currently logged in owner access to the Comment. The MaskBuilder::MASK_OWNER is a pre-defined integer bitmask; don't worry the mask builder will abstract away most of the technical details, but using this technique we can store many different permissions in one database row which gives us a considerable boost in performance. The order in which ACEs are checked is significant. As a general rule, you should place more specific entries at the beginning. Checking Access // BlogController.php Listing 70-4 public function editCommentAction(Comment $comment) { $securityContext = $this->get('security.context'); // check for edit access if (false === $securityContext->isGranted('EDIT', $comment)) { throw new AccessDeniedException(); } // retrieve actual comment object, and do your editing here // ... } In this example, we check whether the user has the EDIT permission. Internally, Symfony2 maps the permission to several integer bitmasks, and checks whether the user has any of them. You can define up to 32 base permissions (depending on your OS PHP might vary between 30 to 32). In addition, you can also define cumulative permissions. Cumulative Permissions In our first example above, we only granted the user the OWNER base permission. While this effectively also allows the user to perform any operation such as view, edit, etc. on the domain object, there are cases where we want to grant these permissions explicitly. The MaskBuilder can be used for creating bit masks easily by combining several base permissions: $builder = new MaskBuilder(); Listing 70-5 $builder ->add('view') ->add('edit') ->add('delete') ->add('undelete') ; $mask = $builder->get(); // int(15) PDF brought to you by Chapter 70: Access Control Lists (ACLs) | 401 generated on June 20, 2012
  • 402. This integer bitmask can then be used to grant a user the base permissions you added above: Listing $acl->insertObjectAce(new UserSecurityIdentity('johannes'), $mask); 70-6 The user is now allowed to view, edit, delete, and un-delete objects. PDF brought to you by Chapter 70: Access Control Lists (ACLs) | 402 generated on June 20, 2012
  • 403. Chapter 71 Advanced ACL Concepts The aim of this chapter is to give a more in-depth view of the ACL system, and also explain some of the design decisions behind it. Design Concepts Symfony2's object instance security capabilities are based on the concept of an Access Control List. Every domain object instance has its own ACL. The ACL instance holds a detailed list of Access Control Entries (ACEs) which are used to make access decisions. Symfony2's ACL system focuses on two main objectives: • providing a way to efficiently retrieve a large amount of ACLs/ACEs for your domain objects, and to modify them; • providing a way to easily make decisions of whether a person is allowed to perform an action on a domain object or not. As indicated by the first point, one of the main capabilities of Symfony2's ACL system is a high- performance way of retrieving ACLs/ACEs. This is extremely important since each ACL might have several ACEs, and inherit from another ACL in a tree-like fashion. Therefore, we specifically do not leverage any ORM, but the default implementation interacts with your connection directly using Doctrine's DBAL. Object Identities The ACL system is completely decoupled from your domain objects. They don't even have to be stored in the same database, or on the same server. In order to achieve this decoupling, in the ACL system your objects are represented through object identity objects. Everytime, you want to retrieve the ACL for a domain object, the ACL system will first create an object identity from your domain object, and then pass this object identity to the ACL provider for further processing. Security Identities This is analog to the object identity, but represents a user, or a role in your application. Each role, or user has its own security identity. PDF brought to you by Chapter 71: Advanced ACL Concepts | 403 generated on June 20, 2012
  • 404. Database Table Structure The default implementation uses five database tables as listed below. The tables are ordered from least rows to most rows in a typical application: • acl_security_identities: This table records all security identities (SID) which hold ACEs. The default implementation ships with two security identities: RoleSecurityIdentity, and UserSecurityIdentity • acl_classes: This table maps class names to a unique id which can be referenced from other tables. • acl_object_identities: Each row in this table represents a single domain object instance. • acl_object_identity_ancestors: This table allows us to determine all the ancestors of an ACL in a very efficient way. • acl_entries: This table contains all ACEs. This is typically the table with the most rows. It can contain tens of millions without significantly impacting performance. Scope of Access Control Entries Access control entries can have different scopes in which they apply. In Symfony2, we have basically two different scopes: • Class-Scope: These entries apply to all objects with the same class. • Object-Scope: This was the scope we solely used in the previous chapter, and it only applies to one specific object. Sometimes, you will find the need to apply an ACE only to a specific field of the object. Let's say you want the ID only to be viewable by an administrator, but not by your customer service. To solve this common problem, we have added two more sub-scopes: • Class-Field-Scope: These entries apply to all objects with the same class, but only to a specific field of the objects. • Object-Field-Scope: These entries apply to a specific object, and only to a specific field of that object. Pre-Authorization Decisions For pre-authorization decisions, that is decisions before any method, or secure action is invoked, we rely on the proven AccessDecisionManager service that is also used for reaching authorization decisions based on roles. Just like roles, the ACL system adds several new attributes which may be used to check for different permissions. Built-in Permission Map Attribute Intended Meaning Integer Bitmasks VIEW Whether someone is allowed to VIEW, EDIT, OPERATOR, view the domain object. MASTER, or OWNER EDIT Whether someone is allowed to EDIT, OPERATOR, MASTER, or make changes to the domain object. OWNER CREATE Whether someone is allowed to CREATE, OPERATOR, MASTER, or create the domain object. OWNER PDF brought to you by Chapter 71: Advanced ACL Concepts | 404 generated on June 20, 2012
  • 405. Attribute Intended Meaning Integer Bitmasks DELETE Whether someone is allowed to DELETE, OPERATOR, MASTER, or delete the domain object. OWNER UNDELETE Whether someone is allowed to UNDELETE, OPERATOR, restore a previously deleted domain MASTER, or OWNER object. OPERATOR Whether someone is allowed to OPERATOR, MASTER, or OWNER perform all of the above actions. MASTER Whether someone is allowed to MASTER, or OWNER perform all of the above actions, and in addition is allowed to grant any of the above permissions to others. OWNER Whether someone owns the OWNER domain object. An owner can perform any of the above actions and grant master and owner permissions. Permission Attributes vs. Permission Bitmasks Attributes are used by the AccessDecisionManager, just like roles are attributes used by the AccessDecisionManager. Often, these attributes represent in fact an aggregate of integer bitmasks. Integer bitmasks on the other hand, are used by the ACL system internally to efficiently store your users' permissions in the database, and perform access checks using extremely fast bitmask operations. Extensibility The above permission map is by no means static, and theoretically could be completely replaced at will. However, it should cover most problems you encounter, and for interoperability with other bundles, we encourage you to stick to the meaning we have envisaged for them. Post Authorization Decisions Post authorization decisions are made after a secure method has been invoked, and typically involve the domain object which is returned by such a method. After invocation providers also allow to modify, or filter the domain object before it is returned. Due to current limitations of the PHP language, there are no post-authorization capabilities build into the core Security component. However, there is an experimental JMSSecurityExtraBundle1 which adds these capabilities. See its documentation for further information on how this is accomplished. Process for Reaching Authorization Decisions The ACL class provides two methods for determining whether a security identity has the required bitmasks, isGranted and isFieldGranted. When the ACL receives an authorization request through one of these methods, it delegates this request to an implementation of PermissionGrantingStrategy. This 1. https://github.com/schmittjoh/JMSSecurityExtraBundle PDF brought to you by Chapter 71: Advanced ACL Concepts | 405 generated on June 20, 2012
  • 406. allows you to replace the way access decisions are reached without actually modifying the ACL class itself. The PermissionGrantingStrategy first checks all your object-scope ACEs if none is applicable, the class- scope ACEs will be checked, if none is applicable, then the process will be repeated with the ACEs of the parent ACL. If no parent ACL exists, an exception will be thrown. PDF brought to you by Chapter 71: Advanced ACL Concepts | 406 generated on June 20, 2012
  • 407. Chapter 72 How to force HTTPS or HTTP for Different URLs You can force areas of your site to use the HTTPS protocol in the security config. This is done through the access_control rules using the requires_channel option. For example, if you want to force all URLs starting with /secure to use HTTPS then you could use the following config: access_control: Listing 72-1 - path: ^/secure roles: ROLE_ADMIN requires_channel: https The login form itself needs to allow anonymous access otherwise users will be unable to authenticate. To force it to use HTTPS you can still use access_control rules by using the IS_AUTHENTICATED_ANONYMOUSLY role: access_control: Listing 72-2 - path: ^/login roles: IS_AUTHENTICATED_ANONYMOUSLY requires_channel: https It is also possible to specify using HTTPS in the routing configuration see How to force routes to always use HTTPS or HTTP for more details. PDF brought to you by Chapter 72: How to force HTTPS or HTTP for Different URLs | 407 generated on June 20, 2012
  • 408. Chapter 73 How to customize your Form Login Using a form login for authentication is a common, and flexible, method for handling authentication in Symfony2. Pretty much every aspect of the form login can be customized. The full, default configuration is shown in the next section. Form Login Configuration Reference Listing # app/config/security.yml 73-1 security: firewalls: main: form_login: # the user is redirected here when he/she needs to login login_path: /login # if true, forward the user to the login form instead of redirecting use_forward: false # submit the login form here check_path: /login_check # by default, the login form *must* be a POST, not a GET post_only: true # login success redirecting options (read further below) always_use_default_target_path: false default_target_path: / target_path_parameter: _target_path use_referer: false # login failure redirecting options (read further below) failure_path: null failure_forward: false # field names for the username and password fields username_parameter: _username PDF brought to you by Chapter 73: How to customize your Form Login | 408 generated on June 20, 2012
  • 409. password_parameter: _password # csrf token options csrf_parameter: _csrf_token intention: authenticate Redirecting after Success You can change where the login form redirects after a successful login using the various config options. By default the form will redirect to the URL the user requested (i.e. the URL which triggered the login form being shown). For example, if the user requested http://www.example.com/admin/post/18/edit then after he/she will eventually be sent back to http://www.example.com/admin/post/18/edit after successfully logging in. This is done by storing the requested URL in the session. If no URL is present in the session (perhaps the user went directly to the login page), then the user is redirected to the default page, which is / (i.e. the homepage) by default. You can change this behavior in several ways. Changing the Default Page First, the default page can be set (i.e. the page the user is redirected to if no previous page was stored in the session). To set it to /admin use the following config: # app/config/security.yml Listing 73-2 security: firewalls: main: form_login: # ... default_target_path: /admin Now, when no URL is set in the session users will be sent to /admin. Always Redirect to the Default Page You can make it so that users are always redirected to the default page regardless of what URL they had requested previously by setting the always_use_default_target_path option to true: # app/config/security.yml Listing 73-3 security: firewalls: main: form_login: # ... always_use_default_target_path: true Using the Referring URL In case no previous URL was stored in the session, you may wish to try using the HTTP_REFERER instead, as this will often be the same. You can do this by setting use_referer to true (it defaults to false): # app/config/security.yml Listing 73-4 security: firewalls: main: form_login: # ... use_referer: true PDF brought to you by Chapter 73: How to customize your Form Login | 409 generated on June 20, 2012
  • 410. Control the Redirect URL from inside the Form You can also override where the user is redirected to via the form itself by including a hidden field with the name _target_path. For example, to redirect to the URL defined by some acount route, use the following: Listing {# src/Acme/SecurityBundle/Resources/views/Security/login.html.twig #} 73-5 {% 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" /> <input type="hidden" name="_target_path" value="account" /> <input type="submit" name="login" /> </form> Now, the user will be redirected to the value of the hidden form field. The value attribute can be a relative path, absolute URL, or a route name. You can even change the name of the hidden form field by changing the target_path_parameter option to another value. Listing # app/config/security.yml 73-6 security: firewalls: main: form_login: target_path_parameter: redirect_url Redirecting on Login Failure In addition to redirect the user after a successful login, you can also set the URL that the user should be redirected to after a failed login (e.g. an invalid username or password was submitted). By default, the user is redirected back to the login form itself. You can set this to a different URL with the following config: Listing # app/config/security.yml 73-7 security: firewalls: main: form_login: # ... failure_path: /login_failure PDF brought to you by Chapter 73: How to customize your Form Login | 410 generated on June 20, 2012
  • 411. Chapter 74 How to secure any Service or Method in your Application In the security chapter, you can see how to secure a controller by requesting the security.context service from the Service Container and checking the current user's role: use SymfonyComponentSecurityCoreExceptionAccessDeniedException; Listing 74-1 // ... public function helloAction($name) { if (false === $this->get('security.context')->isGranted('ROLE_ADMIN')) { throw new AccessDeniedException(); } // ... } You can also secure any service in a similar way by injecting the security.context service into it. For a general introduction to injecting dependencies into services see the Service Container chapter of the book. For example, suppose you have a NewsletterManager class that sends out emails and you want to restrict its use to only users who have some ROLE_NEWSLETTER_ADMIN role. Before you add security, the class looks something like this: namespace AcmeHelloBundleNewsletter; Listing 74-2 class NewsletterManager { public function sendNewsletter() { // where you actually do the work } // ... } PDF brought to you by Chapter 74: How to secure any Service or Method in your Application | 411 generated on June 20, 2012
  • 412. Your goal is to check the user's role when the sendNewsletter() method is called. The first step towards this is to inject the security.context service into the object. Since it won't make sense not to perform the security check, this is an ideal candidate for constructor injection, which guarantees that the security context object will be available inside the NewsletterManager class: Listing namespace AcmeHelloBundleNewsletter; 74-3 use SymfonyComponentSecurityCoreSecurityContextInterface; class NewsletterManager { protected $securityContext; public function __construct(SecurityContextInterface $securityContext) { $this->securityContext = $securityContext; } // ... } Then in your service configuration, you can inject the service: Listing # src/Acme/HelloBundle/Resources/config/services.yml 74-4 parameters: newsletter_manager.class: AcmeHelloBundleNewsletterNewsletterManager services: newsletter_manager: class: %newsletter_manager.class% arguments: [@security.context] The injected service can then be used to perform the security check when the sendNewsletter() method is called: Listing namespace AcmeHelloBundleNewsletter; 74-5 use SymfonyComponentSecurityCoreExceptionAccessDeniedException; use SymfonyComponentSecurityCoreSecurityContextInterface; // ... class NewsletterManager { protected $securityContext; public function __construct(SecurityContextInterface $securityContext) { $this->securityContext = $securityContext; } public function sendNewsletter() { if (false === $this->securityContext->isGranted('ROLE_NEWSLETTER_ADMIN')) { throw new AccessDeniedException(); } //-- } PDF brought to you by Chapter 74: How to secure any Service or Method in your Application | 412 generated on June 20, 2012
  • 413. // ... } If the current user does not have the ROLE_NEWSLETTER_ADMIN, they will be prompted to log in. Securing Methods Using Annotations You can also secure method calls in any service with annotations by using the optional JMSSecurityExtraBundle1 bundle. This bundle is included in the Symfony2 Standard Distribution. To enable the annotations functionality, tag the service you want to secure with the security.secure_service tag (you can also automatically enable this functionality for all services, see the sidebar below): # src/Acme/HelloBundle/Resources/config/services.yml Listing 74-6 # ... services: newsletter_manager: # ... tags: - { name: security.secure_service } You can then achieve the same results as above using an annotation: namespace AcmeHelloBundleNewsletter; Listing 74-7 use JMSSecurityExtraBundleAnnotationSecure; // ... class NewsletterManager { /** * @Secure(roles="ROLE_NEWSLETTER_ADMIN") */ public function sendNewsletter() { //-- } // ... } The annotations work because a proxy class is created for your class which performs the security checks. This means that, whilst you can use annotations on public and protected methods, you cannot use them with private methods or methods marked final. The JMSSecurityExtraBundle also allows you to secure the parameters and return values of methods. For more information, see the JMSSecurityExtraBundle2 documentation. 1. https://github.com/schmittjoh/JMSSecurityExtraBundle 2. https://github.com/schmittjoh/JMSSecurityExtraBundle PDF brought to you by Chapter 74: How to secure any Service or Method in your Application | 413 generated on June 20, 2012
  • 414. Activating the Annotations Functionality for all Services When securing the method of a service (as shown above), you can either tag each service individually, or activate the functionality for all services at once. To do so, set the secure_all_services configuration option to true: Listing # app/config/config.yml 74-8 jms_security_extra: # ... secure_all_services: true The disadvantage of this method is that, if activated, the initial page load may be very slow depending on how many services you have defined. PDF brought to you by Chapter 74: How to secure any Service or Method in your Application | 414 generated on June 20, 2012
  • 415. Chapter 75 How to create a custom User Provider Part of Symfony's standard authentication process depends on "user providers". When a user submits a username and password, the authentication layer asks the configured user provider to return a user object for a given username. Symfony then checks whether the password of this user is correct and generates a security token so the user stays authenticated during the current session. Out of the box, Symfony has an "in_memory" and an "entity" user provider. In this entry we'll see how you can create your own user provider, which could be useful if your users are accessed via a custom database, a file, or - as we show in this example - a web service. Create a User Class First, regardless of where your user data is coming from, you'll need to create a User class that represents that data. The User can look however you want and contain any data. The only requirement is that the class implements UserInterface1. The methods in this interface should therefore be defined in the custom user class: getRoles(), getPassword(), getSalt(), getUsername(), eraseCredentials(), equals(). Let's see this in action: // src/Acme/WebserviceUserBundle/Security/User.php Listing 75-1 namespace AcmeWebserviceUserBundleSecurityUser; use SymfonyComponentSecurityCoreUserUserInterface; class WebserviceUser implements UserInterface { private $username; private $password; private $salt; private $roles; public function __construct($username, $password, $salt, array $roles) { $this->username = $username; 1. http://api.symfony.com/2.0/Symfony/Component/Security/Core/User/UserInterface.html PDF brought to you by Chapter 75: How to create a custom User Provider | 415 generated on June 20, 2012
  • 416. $this->password = $password; $this->salt = $salt; $this->roles = $roles; } public function getRoles() { return $this->roles; } public function getPassword() { return $this->password; } public function getSalt() { return $this->salt; } public function getUsername() { return $this->username; } public function eraseCredentials() { } public function equals(UserInterface $user) { if (!$user instanceof WebserviceUser) { return false; } if ($this->password !== $user->getPassword()) { return false; } if ($this->getSalt() !== $user->getSalt()) { return false; } if ($this->username !== $user->getUsername()) { return false; } return true; } } If you have more information about your users - like a "first name" - then you can add a firstName field to hold that data. For more details on each of the methods, see UserInterface2. 2. http://api.symfony.com/2.0/Symfony/Component/Security/Core/User/UserInterface.html PDF brought to you by Chapter 75: How to create a custom User Provider | 416 generated on June 20, 2012
  • 417. Create a User Provider Now that we have a User class, we'll create a user provider, which will grab user information from some web service, create a WebserviceUser object, and populate it with data. The user provider is just a plain PHP class that has to implement the UserProviderInterface3, which requires three methods to be defined: loadUserByUsername($username), refreshUser(UserInterface $user), and supportsClass($class). For more details, see UserProviderInterface4. Here's an example of how this might look: // src/Acme/WebserviceUserBundle/Security/User/WebserviceUserProvider.php Listing 75-2 namespace AcmeWebserviceUserBundleSecurityUser; use SymfonyComponentSecurityCoreUserUserProviderInterface; use SymfonyComponentSecurityCoreUserUserInterface; use SymfonyComponentSecurityCoreExceptionUsernameNotFoundException; use SymfonyComponentSecurityCoreExceptionUnsupportedUserException; class WebserviceUserProvider implements UserProviderInterface { public function loadUserByUsername($username) { // make a call to your webservice here // $userData = ... // pretend it returns an array on success, false if there is no user if ($userData) { // $password = '...'; // ... return new WebserviceUser($username, $password, $salt, $roles) } else { throw new UsernameNotFoundException(sprintf('Username "%s" does not exist.', $username)); } } public function refreshUser(UserInterface $user) { if (!$user instanceof WebserviceUser) { throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user))); } return $this->loadUserByUsername($user->getUsername()); } public function supportsClass($class) { return $class === 'AcmeWebserviceUserBundleSecurityUserWebserviceUser'; } } 3. http://api.symfony.com/2.0/Symfony/Component/Security/Core/User/UserProviderInterface.html 4. http://api.symfony.com/2.0/Symfony/Component/Security/Core/User/UserProviderInterface.html PDF brought to you by Chapter 75: How to create a custom User Provider | 417 generated on June 20, 2012
  • 418. Create a Service for the User Provider Now we make the user provider available as a service. Listing # src/Acme/WebserviceUserBundle/Resources/config/services.yml 75-3 parameters: webservice_user_provider.class: AcmeWebserviceUserBundleSecurityUserWebserviceUserProvider services: webservice_user_provider: class: %webservice_user_provider.class% The real implementation of the user provider will probably have some dependencies or configuration options or other services. Add these as arguments in the service definition. Make sure the services file is being imported. See Importing Configuration with imports for details. Modify security.yml In /app/config/security.yml everything comes together. Add the user provider to the list of providers in the "security" section. Choose a name for the user provider (e.g. "webservice") and mention the id of the service you just defined. Listing security: 75-4 providers: webservice: id: webservice_user_provider Symfony also needs to know how to encode passwords that are supplied by website users, e.g. by filling in a login form. You can do this by adding a line to the "encoders" section in /app/config/security.yml. Listing security: 75-5 encoders: AcmeWebserviceUserBundleSecurityUserWebserviceUser: sha512 The value here should correspond with however the passwords were originally encoded when creating your users (however those users were created). When a user submits her password, the password is appended to the salt value and then encoded using this algorithm before being compared to the hashed password returned by your getPassword() method. Additionally, depending on your options, the password may be encoded multiple times and encoded to base64. PDF brought to you by Chapter 75: How to create a custom User Provider | 418 generated on June 20, 2012
  • 419. Specifics on how passwords are encoded Symfony uses a specific method to combine the salt and encode the password before comparing it to your encoded password. If getSalt() returns nothing, then the submitted password is simply encoded using the algorithm you specify in security.yml. If a salt is specified, then the following value is created and then hashed via the algorithm: $password.'{'.$salt.'}'; If your external users have their passwords salted via a different method, then you'll need to do a bit more work so that Symfony properly encodes the password. That is beyond the scope of this entry, but would include sub-classing MessageDigestPasswordEncoder and overriding the mergePasswordAndSalt method. Additionally, the hash, by default, is encoded multiple times and encoded to base64. For specific details, see MessageDigestPasswordEncoder5. To prevent this, configure it in security.yml: security: Listing 75-6 encoders: AcmeWebserviceUserBundleSecurityUserWebserviceUser: algorithm: sha512 encode_as_base64: false iterations: 1 5. https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Security/Core/Encoder/MessageDigestPasswordEncoder.php PDF brought to you by Chapter 75: How to create a custom User Provider | 419 generated on June 20, 2012
  • 420. Chapter 76 How to create a custom Authentication Provider If you have read the chapter on Security, you understand the distinction Symfony2 makes between authentication and authorization in the implementation of security. This chapter discusses the core classes involved in the authentication process, and how to implement a custom authentication provider. Because authentication and authorization are separate concepts, this extension will be user-provider agnostic, and will function with your application's user providers, may they be based in memory, a database, or wherever else you choose to store them. Meet WSSE The following chapter demonstrates how to create a custom authentication provider for WSSE authentication. The security protocol for WSSE provides several security benefits: 1. Username / Password encryption 2. Safe guarding against replay attacks 3. No web server configuration required WSSE is very useful for the securing of web services, may they be SOAP or REST. There is plenty of great documentation on WSSE1, but this article will focus not on the security protocol, but rather the manner in which a custom protocol can be added to your Symfony2 application. The basis of WSSE is that a request header is checked for encrypted credentials, verified using a timestamp and nonce2, and authenticated for the requested user using a password digest. WSSE also supports application key validation, which is useful for web services, but is outside the scope of this chapter. 1. http://www.xml.com/pub/a/2003/12/17/dive.html 2. http://en.wikipedia.org/wiki/Cryptographic_nonce PDF brought to you by Chapter 76: How to create a custom Authentication Provider | 420 generated on June 20, 2012
  • 421. The Token The role of the token in the Symfony2 security context is an important one. A token represents the user authentication data present in the request. Once a request is authenticated, the token retains the user's data, and delivers this data across the security context. First, we will create our token class. This will allow the passing of all relevant information to our authentication provider. // src/Acme/DemoBundle/Security/Authentication/Token/WsseUserToken.php Listing 76-1 namespace AcmeDemoBundleSecurityAuthenticationToken; use SymfonyComponentSecurityCoreAuthenticationTokenAbstractToken; class WsseUserToken extends AbstractToken { public $created; public $digest; public $nonce; public function __construct(array $roles = array()) { parent::__construct($roles); // If the user has roles, consider it authenticated $this->setAuthenticated(count($roles) > 0); } public function getCredentials() { return ''; } } The WsseUserToken class extends the security component's AbstractToken3 class, which provides basic token functionality. Implement the TokenInterface4 on any class to use as a token. The Listener Next, you need a listener to listen on the security context. The listener is responsible for fielding requests to the firewall and calling the authentication provider. A listener must be an instance of ListenerInterface5. A security listener should handle the GetResponseEvent6 event, and set an authenticated token in the security context if successful. // src/Acme/DemoBundle/Security/Firewall/WsseListener.php Listing 76-2 namespace AcmeDemoBundleSecurityFirewall; use SymfonyComponentHttpFoundationResponse; use SymfonyComponentHttpKernelEventGetResponseEvent; use SymfonyComponentSecurityHttpFirewallListenerInterface; use SymfonyComponentSecurityCoreExceptionAuthenticationException; use SymfonyComponentSecurityCoreSecurityContextInterface; 3. http://api.symfony.com/2.0/Symfony/Component/Security/Core/Authentication/Token/AbstractToken.html 4. http://api.symfony.com/2.0/Symfony/Component/Security/Core/Authentication/Token/TokenInterface.html 5. http://api.symfony.com/2.0/Symfony/Component/Security/Http/Firewall/ListenerInterface.html 6. http://api.symfony.com/2.0/Symfony/Component/HttpKernel/Event/GetResponseEvent.html PDF brought to you by Chapter 76: How to create a custom Authentication Provider | 421 generated on June 20, 2012
  • 422. use SymfonyComponentSecurityCoreAuthenticationAuthenticationManagerInterface; use SymfonyComponentSecurityCoreAuthenticationTokenTokenInterface; use AcmeDemoBundleSecurityAuthenticationTokenWsseUserToken; class WsseListener implements ListenerInterface { protected $securityContext; protected $authenticationManager; public function __construct(SecurityContextInterface $securityContext, AuthenticationManagerInterface $authenticationManager) { $this->securityContext = $securityContext; $this->authenticationManager = $authenticationManager; } public function handle(GetResponseEvent $event) { $request = $event->getRequest(); if ($request->headers->has('x-wsse')) { $wsseRegex = '/UsernameToken Username="([^"]+)", PasswordDigest="([^"]+)", Nonce="([^"]+)", Created="([^"]+)"/'; if (preg_match($wsseRegex, $request->headers->get('x-wsse'), $matches)) { $token = new WsseUserToken(); $token->setUser($matches[1]); $token->digest = $matches[2]; $token->nonce = $matches[3]; $token->created = $matches[4]; try { $returnValue = $this->authenticationManager->authenticate($token); if ($returnValue instanceof TokenInterface) { return $this->securityContext->setToken($returnValue); } else if ($returnValue instanceof Response) { return $event->setResponse($returnValue); } } catch (AuthenticationException $e) { // you might log something here } } } $response = new Response(); $response->setStatusCode(403); $event->setResponse($response); } } This listener checks the request for the expected X-WSSE header, matches the value returned for the expected WSSE information, creates a token using that information, and passes the token on to the authentication manager. If the proper information is not provided, or the authentication manager throws an AuthenticationException7, a 403 Response is returned. 7. http://api.symfony.com/2.0/Symfony/Component/Security/Core/Exception/AuthenticationException.html PDF brought to you by Chapter 76: How to create a custom Authentication Provider | 422 generated on June 20, 2012
  • 423. A class not used above, the AbstractAuthenticationListener8 class, is a very useful base class which provides commonly needed functionality for security extensions. This includes maintaining the token in the session, providing success / failure handlers, login form urls, and more. As WSSE does not require maintaining authentication sessions or login forms, it won't be used for this example. The Authentication Provider The authentication provider will do the verification of the WsseUserToken. Namely, the provider will verify the Created header value is valid within five minutes, the Nonce header value is unique within five minutes, and the PasswordDigest header value matches with the user's password. // src/Acme/DemoBundle/Security/Authentication/Provider/WsseProvider.php Listing 76-3 namespace AcmeDemoBundleSecurityAuthenticationProvider; use SymfonyComponentSecurityCoreAuthenticationProviderAuthenticationProviderInterface; use SymfonyComponentSecurityCoreUserUserProviderInterface; use SymfonyComponentSecurityCoreExceptionAuthenticationException; use SymfonyComponentSecurityCoreExceptionNonceExpiredException; use SymfonyComponentSecurityCoreAuthenticationTokenTokenInterface; use AcmeDemoBundleSecurityAuthenticationTokenWsseUserToken; class WsseProvider implements AuthenticationProviderInterface { private $userProvider; private $cacheDir; public function __construct(UserProviderInterface $userProvider, $cacheDir) { $this->userProvider = $userProvider; $this->cacheDir = $cacheDir; } public function authenticate(TokenInterface $token) { $user = $this->userProvider->loadUserByUsername($token->getUsername()); if ($user && $this->validateDigest($token->digest, $token->nonce, $token->created, $user->getPassword())) { $authenticatedToken = new WsseUserToken($user->getRoles()); $authenticatedToken->setUser($user); return $authenticatedToken; } throw new AuthenticationException('The WSSE authentication failed.'); } protected function validateDigest($digest, $nonce, $created, $secret) { // Expire timestamp after 5 minutes if (time() - strtotime($created) > 300) { return false; } 8. http://api.symfony.com/2.0/Symfony/Component/Security/Http/Firewall/AbstractAuthenticationListener.html PDF brought to you by Chapter 76: How to create a custom Authentication Provider | 423 generated on June 20, 2012
  • 424. // Validate nonce is unique within 5 minutes if (file_exists($this->cacheDir.'/'.$nonce) && file_get_contents($this->cacheDir.'/'.$nonce) + 300 < time()) { throw new NonceExpiredException('Previously used nonce detected'); } file_put_contents($this->cacheDir.'/'.$nonce, time()); // Validate Secret $expected = base64_encode(sha1(base64_decode($nonce).$created.$secret, true)); return $digest === $expected; } public function supports(TokenInterface $token) { return $token instanceof WsseUserToken; } } The AuthenticationProviderInterface9 requires an authenticate method on the user token, and a supports method, which tells the authentication manager whether or not to use this provider for the given token. In the case of multiple providers, the authentication manager will then move to the next provider in the list. The Factory You have created a custom token, custom listener, and custom provider. Now you need to tie them all together. How do you make your provider available to your security configuration? The answer is by using a factory. A factory is where you hook into the security component, telling it the name of your provider and any configuration options available for it. First, you must create a class which implements SecurityFactoryInterface10. Listing // src/Acme/DemoBundle/DependencyInjection/Security/Factory/WsseFactory.php 76-4 namespace AcmeDemoBundleDependencyInjectionSecurityFactory; use SymfonyComponentDependencyInjectionContainerBuilder; use SymfonyComponentDependencyInjectionReference; use SymfonyComponentDependencyInjectionDefinitionDecorator; use SymfonyComponentConfigDefinitionBuilderNodeDefinition; use SymfonyBundleSecurityBundleDependencyInjectionSecurityFactorySecurityFactoryInterface; class WsseFactory implements SecurityFactoryInterface { public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint) { $providerId = 'security.authentication.provider.wsse.'.$id; $container ->setDefinition($providerId, new DefinitionDecorator('wsse.security.authentication.provider')) ->replaceArgument(0, new Reference($userProvider)) ; 9. http://api.symfony.com/2.0/Symfony/Component/Security/Core/Authentication/Provider/AuthenticationProviderInterface.html 10. http://api.symfony.com/2.0/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SecurityFactoryInterface.html PDF brought to you by Chapter 76: How to create a custom Authentication Provider | 424 generated on June 20, 2012
  • 425. $listenerId = 'security.authentication.listener.wsse.'.$id; $listener = $container->setDefinition($listenerId, new DefinitionDecorator('wsse.security.authentication.listener')); return array($providerId, $listenerId, $defaultEntryPoint); } public function getPosition() { return 'pre_auth'; } public function getKey() { return 'wsse'; } public function addConfiguration(NodeDefinition $node) {} } The SecurityFactoryInterface11 requires the following methods: • create method, which adds the listener and authentication provider to the DI container for the appropriate security context; • getPosition method, which must be of type pre_auth, form, http, and remember_me and defines the position at which the provider is called; • getKey method which defines the configuration key used to reference the provider; • addConfiguration method, which is used to define the configuration options underneath the configuration key in your security configuration. Setting configuration options are explained later in this chapter. A class not used in this example, AbstractFactory12, is a very useful base class which provides commonly needed functionality for security factories. It may be useful when defining an authentication provider of a different type. Now that you have created a factory class, the wsse key can be used as a firewall in your security configuration. You may be wondering "why do we need a special factory class to add listeners and providers to the dependency injection container?". This is a very good question. The reason is you can use your firewall multiple times, to secure multiple parts of your application. Because of this, each time your firewall is used, a new service is created in the DI container. The factory is what creates these new services. Configuration It's time to see your authentication provider in action. You will need to do a few things in order to make this work. The first thing is to add the services above to the DI container. Your factory class above makes reference to service ids that do not exist yet: wsse.security.authentication.provider and wsse.security.authentication.listener. It's time to define those services. 11. http://api.symfony.com/2.0/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SecurityFactoryInterface.html 12. http://api.symfony.com/2.0/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.html PDF brought to you by Chapter 76: How to create a custom Authentication Provider | 425 generated on June 20, 2012
  • 426. Listing # src/Acme/DemoBundle/Resources/config/services.yml 76-5 services: wsse.security.authentication.provider: class: AcmeDemoBundleSecurityAuthenticationProviderWsseProvider arguments: ['', %kernel.cache_dir%/security/nonces] wsse.security.authentication.listener: class: AcmeDemoBundleSecurityFirewallWsseListener arguments: [@security.context, @security.authentication.manager] Now that your services are defined, tell your security context about your factory. Factories must be included in an individual configuration file, at the time of this writing. So, start first by creating the file with the factory service, tagged as security.listener.factory: Listing # src/Acme/DemoBundle/Resources/config/security_factories.yml 76-6 services: security.authentication.factory.wsse: class: AcmeDemoBundleDependencyInjectionSecurityFactoryWsseFactory tags: - { name: security.listener.factory } Now, import the factory configuration via the the factories key in your security configuration: Listing # app/config/security.yml 76-7 security: factories: - "%kernel.root_dir%/../src/Acme/DemoBundle/Resources/config/security_factories.yml" You are finished! You can now define parts of your app as under WSSE protection. Listing security: 76-8 firewalls: wsse_secured: pattern: /api/.* wsse: true Congratulations! You have written your very own custom security authentication provider! A Little Extra How about making your WSSE authentication provider a bit more exciting? The possibilities are endless. Why don't you start by adding some sparkle to that shine? Configuration You can add custom options under the wsse key in your security configuration. For instance, the time allowed before expiring the Created header item, by default, is 5 minutes. Make this configurable, so different firewalls can have different timeout lengths. You will first need to edit WsseFactory and define the new option in the addConfiguration method. Listing class WsseFactory implements SecurityFactoryInterface 76-9 { # ... public function addConfiguration(NodeDefinition $node) { $node PDF brought to you by Chapter 76: How to create a custom Authentication Provider | 426 generated on June 20, 2012
  • 427. ->children() ->scalarNode('lifetime')->defaultValue(300) ->end() ; } } Now, in the create method of the factory, the $config argument will contain a 'lifetime' key, set to 5 minutes (300 seconds) unless otherwise set in the configuration. Pass this argument to your authentication provider in order to put it to use. class WsseFactory implements SecurityFactoryInterface Listing 76-10 { public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint) { $providerId = 'security.authentication.provider.wsse.'.$id; $container ->setDefinition($providerId, new DefinitionDecorator('wsse.security.authentication.provider')) ->replaceArgument(0, new Reference($userProvider)) ->replaceArgument(2, $config['lifetime']) ; // ... } // ... } You'll also need to add a third argument to the wsse.security.authentication.provider service configuration, which can be blank, but will be filled in with the lifetime in the factory. The WsseProvider class will also now need to accept a third constructor argument - the lifetime - which it should use instead of the hard-coded 300 seconds. These two steps are not shown here. The lifetime of each wsse request is now configurable, and can be set to any desirable value per firewall. security: Listing 76-11 firewalls: wsse_secured: pattern: /api/.* wsse: { lifetime: 30 } The rest is up to you! Any relevant configuration items can be defined in the factory and consumed or passed to the other classes in the container. PDF brought to you by Chapter 76: How to create a custom Authentication Provider | 427 generated on June 20, 2012
  • 428. Chapter 77 How to use Varnish to speed up my Website Because Symfony2's cache uses the standard HTTP cache headers, the Symfony2 Reverse Proxy can easily be replaced with any other reverse proxy. Varnish is a powerful, open-source, HTTP accelerator capable of serving cached content quickly and including support for Edge Si