Sie sind auf Seite 1von 914

The all-in-one Book

for Symfony 2.1


generated on October 26, 2012

The all-in-one Book (2.1) 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 .................................................................................................................................10 The View ..........................................................................................................................................19 The Controller ..................................................................................................................................24 The Architecture ...............................................................................................................................29

The Book
Symfony2 and HTTP Fundamentals ..................................................................................................36 Symfony2 versus Flat PHP.................................................................................................................46 Installing and Configuring Symfony...................................................................................................59 Creating Pages in Symfony2 ..............................................................................................................65 Controller.........................................................................................................................................78 Routing ............................................................................................................................................89 Creating and using Templates ......................................................................................................... 101 Databases and Doctrine .................................................................................................................. 119 Databases and Propel ...................................................................................................................... 142 Testing ........................................................................................................................................... 150 Validation....................................................................................................................................... 164 Forms ............................................................................................................................................. 172 Security .......................................................................................................................................... 195 HTTP Cache................................................................................................................................... 216 Translations.................................................................................................................................... 231 Service Container ............................................................................................................................ 244 Performance ................................................................................................................................... 254 Internals ......................................................................................................................................... 257 The Symfony2 Stable API ................................................................................................................ 266

The Cookbook
How to Create and store a Symfony2 Project in git ........................................................................... 269 How to Create and store a Symfony2 Project in Subversion .............................................................. 272 How to customize Error Pages......................................................................................................... 276 How to define Controllers as Services .............................................................................................. 278
PDF brought to you by generated on October 26, 2012

Contents at a Glance | iii

How to force routes to always use HTTPS or HTTP......................................................................... 280 How to allow a "/" character in a route parameter ............................................................................ 281 How to configure a redirect to another route without a custom controller......................................... 282 How to use HTTP Methods beyond GET and POST in Routes......................................................... 283 How to use Service Container Parameters in your Routes ................................................................. 285 How to Use Assetic for Asset Management ...................................................................................... 287 How to Minify JavaScripts and Stylesheets with YUI Compressor..................................................... 291 How to Use Assetic For Image Optimization with Twig Functions ................................................... 293 How to Apply an Assetic Filter to a Specific File Extension............................................................... 296 How to handle File Uploads with Doctrine ...................................................................................... 298 How to use Doctrine Extensions: Timestampable, Sluggable, Translatable, etc. ................................ 306 How to Register Event Listeners and Subscribers ............................................................................. 307 How to use Doctrine's DBAL Layer ................................................................................................. 309 How to generate Entities from an Existing Database......................................................................... 311 How to work with Multiple Entity Managers and Connections......................................................... 315 How to Register Custom DQL Functions......................................................................................... 318 How to Define Relationships with Abstract Classes and Interfaces.................................................... 319 How to implement a simple Registration Form ................................................................................ 322 How to customize Form Rendering ................................................................................................. 327 How to use Data Transformers........................................................................................................ 338 How to Dynamically Generate Forms Using Form Events ................................................................ 344 How to Embed a Collection of Forms .............................................................................................. 347 How to Create a Custom Form Field Type....................................................................................... 359 How to Create a Form Type Extension ............................................................................................ 364 How to use the Virtual Form Field Option....................................................................................... 369 How to create a Custom Validation Constraint ................................................................................ 372 How to Master and Create new Environments ................................................................................. 376 How to override Symfony's Default Directory Structure.................................................................... 381 How to Set External Parameters in the Service Container ................................................................. 384 How to use PdoSessionStorage to store Sessions in the Database ...................................................... 387 How to use the Apache Router ........................................................................................................ 389 How to create an Event Listener ...................................................................................................... 391 How to work with Scopes ............................................................................................................... 393 How to work with Compiler Passes in Bundles ................................................................................ 396 How to use Best Practices for Structuring Bundles............................................................................ 397 How to use Bundle Inheritance to Override parts of a Bundle ........................................................... 402 How to Override any Part of a Bundle ............................................................................................. 405 How to expose a Semantic Configuration for a Bundle ..................................................................... 408 How to send an Email ..................................................................................................................... 417 How to use Gmail to send Emails .................................................................................................... 419 How to Work with Emails During Development .............................................................................. 420 How to Spool Email ........................................................................................................................ 422 How to simulate HTTP Authentication in a Functional Test ............................................................ 424 How to test the Interaction of several Clients ................................................................................... 425 How to use the Profiler in a Functional Test..................................................................................... 426 How to test Doctrine Repositories ................................................................................................... 428 How to customize the Bootstrap Process before running Tests.......................................................... 430

iv | Contents at a Glance

Contents at a Glance | 4

How to load Security Users from the Database (the Entity Provider) ................................................. 432 How to add "Remember Me" Login Functionality ............................................................................ 442 How to implement your own Voter to blacklist IP Addresses............................................................ 445 How to use Access Control Lists (ACLs).......................................................................................... 448 How to use Advanced ACL Concepts .............................................................................................. 452 How to force HTTPS or HTTP for Different URLs ........................................................................... 456 How to customize your Form Login ................................................................................................ 457 How to secure any Service or Method in your Application................................................................ 461 How to create a custom User Provider ............................................................................................. 465 How to create a custom Authentication Provider ............................................................................. 470 How to change the Default Target Path Behavior ............................................................................. 479 How to use Varnish to speed up my Website ................................................................................... 481 How to Inject Variables into all Templates (i.e. Global Variables) ..................................................... 483 How to use PHP instead of Twig for Templates ............................................................................... 485 How to write a custom Twig Extension ........................................................................................... 490 How to use Monolog to write Logs.................................................................................................. 493 How to Configure Monolog to Email Errors .................................................................................... 497 How to log Messages to different Files ............................................................................................. 499 How to create a Console Command ................................................................................................ 501 How to use the Console .................................................................................................................. 504 How to generate URLs with a custom Host in Console Commands .................................................. 506 How to optimize your development Environment for debugging ...................................................... 508 How to setup before and after Filters ............................................................................................... 510 How to extend a Class without using Inheritance............................................................................. 514 How to customize a Method Behavior without using Inheritance...................................................... 517 How to register a new Request Format and Mime Type.................................................................... 519 How to create a custom Data Collector............................................................................................ 521 How to Create a SOAP Web Service in a Symfony2 Controller ......................................................... 524 How Symfony2 differs from symfony1 ............................................................................................. 528

The Components
The ClassLoader Component .......................................................................................................... 534 The Config Component .................................................................................................................. 537 Loading resources ........................................................................................................................... 538 Caching based on resources............................................................................................................. 540 Define and process configuration values .......................................................................................... 542 The Console Component ................................................................................................................ 550 Using Console Commands, Shortcuts and Built-in Commands......................................................... 558 How to build an Application that is a single Command .................................................................... 561 The CssSelector Component ........................................................................................................... 563 The DomCrawler Component ......................................................................................................... 565 The Dependency Injection Component............................................................................................ 572 Types of Injection ........................................................................................................................... 577 Working with Container Parameters and Definitions ....................................................................... 580 Compiling the Container................................................................................................................. 583 Working with Tagged Services ........................................................................................................ 591

PDF brought to you by generated on October 26, 2012

Contents at a Glance | v

Using a Factory to Create Services ................................................................................................... 595 Managing Common Dependencies with Parent Services ................................................................... 597 Advanced Container Configuration ................................................................................................. 602 Container Building Workflow ......................................................................................................... 604 The Event Dispatcher Component................................................................................................... 606 The Generic Event Object ............................................................................................................... 616 The Container Aware Event Dispatcher ........................................................................................... 619 The Filesystem Component............................................................................................................. 622 The Finder Component................................................................................................................... 627 The HttpFoundation Component.................................................................................................... 633 Session Management....................................................................................................................... 640 Configuring Sessions and Save Handlers .......................................................................................... 646 Testing with Sessions ...................................................................................................................... 651 The Locale Component................................................................................................................... 653 The Process Component ................................................................................................................. 655 The Routing Component ................................................................................................................ 657 The Serializer Component ............................................................................................................... 664 The Templating Component ........................................................................................................... 667 The YAML Component................................................................................................................... 670

Contributing
Reporting a Bug .............................................................................................................................. 679 Submitting a Patch .......................................................................................................................... 680 Reporting a Security Issue ............................................................................................................... 686 Running Symfony2 Tests................................................................................................................. 687 Coding Standards ........................................................................................................................... 690 Conventions ................................................................................................................................... 693 Symfony2 License ........................................................................................................................... 695 Contributing to the Documentation ................................................................................................ 696 Documentation Format................................................................................................................... 699 Translations.................................................................................................................................... 703 Symfony2 Documentation License................................................................................................... 705 The Release Process ........................................................................................................................ 706 IRC Meetings.................................................................................................................................. 709 Other Resources ............................................................................................................................. 711

Reference Documents
FrameworkBundle Configuration ("framework").............................................................................. 713 AsseticBundle Configuration Reference ........................................................................................... 719 Configuration Reference.................................................................................................................. 721 Security Configuration Reference..................................................................................................... 727 SwiftmailerBundle Configuration ("swiftmailer").............................................................................. 732 TwigBundle Configuration Reference .............................................................................................. 736 Configuration Reference.................................................................................................................. 738 WebProfilerBundle Configuration ................................................................................................... 740

vi | Contents at a Glance

Contents at a Glance | 6

Form Types Reference..................................................................................................................... 741 birthday Field Type......................................................................................................................... 743 checkbox Field Type ....................................................................................................................... 747 choice Field Type ............................................................................................................................ 749 collection Field Type....................................................................................................................... 754 country Field Type.......................................................................................................................... 760 csrf Field Type ................................................................................................................................ 763 date Field Type ............................................................................................................................... 765 datetime Field Type ........................................................................................................................ 769 email Field Type ............................................................................................................................. 773 entity Field Type ............................................................................................................................. 775 file Field Type ................................................................................................................................. 780 The Abstract "field" Type ................................................................................................................ 783 form Field Type .............................................................................................................................. 784 hidden Field Type ........................................................................................................................... 787 integer Field Type ........................................................................................................................... 789 language Field Type ........................................................................................................................ 792 locale Field Type............................................................................................................................. 795 money Field Type ........................................................................................................................... 798 number Field Type.......................................................................................................................... 801 password Field Type ....................................................................................................................... 804 percent Field Type .......................................................................................................................... 806 radio Field Type.............................................................................................................................. 809 repeated Field Type......................................................................................................................... 811 search Field Type ............................................................................................................................ 815 text Field Type................................................................................................................................ 817 textarea Field Type ......................................................................................................................... 819 time Field Type............................................................................................................................... 821 timezone Field Type........................................................................................................................ 825 url Field Type ................................................................................................................................. 828 Twig Template Form Function Reference ........................................................................................ 830 Validation Constraints Reference..................................................................................................... 833 NotBlank........................................................................................................................................ 835 Blank.............................................................................................................................................. 836 NotNull.......................................................................................................................................... 837 Null................................................................................................................................................ 838 True ............................................................................................................................................... 840 False............................................................................................................................................... 842 Type............................................................................................................................................... 844 Email.............................................................................................................................................. 846 MinLength...................................................................................................................................... 848 MaxLength ..................................................................................................................................... 850 Length ............................................................................................................................................ 852 Url.................................................................................................................................................. 854 Regex ............................................................................................................................................. 856 Ip ................................................................................................................................................... 858 Max................................................................................................................................................ 860

PDF brought to you by generated on October 26, 2012

Contents at a Glance | vii

Min ................................................................................................................................................ 862 Range ............................................................................................................................................. 864 Date ............................................................................................................................................... 866 DateTime ....................................................................................................................................... 867 Time............................................................................................................................................... 868 Choice............................................................................................................................................ 869 Collection....................................................................................................................................... 873 Count ............................................................................................................................................. 876 UniqueEntity .................................................................................................................................. 878 Language ........................................................................................................................................ 880 Locale............................................................................................................................................. 881 Country.......................................................................................................................................... 883 File ................................................................................................................................................. 884 Image ............................................................................................................................................. 887 Callback ......................................................................................................................................... 891 All .................................................................................................................................................. 894 UserPassword ................................................................................................................................. 896 Valid .............................................................................................................................................. 898 The Dependency Injection Tags....................................................................................................... 900 Requirements for running Symfony2................................................................................................ 911

viii | Contents at a Glance

Contents at a Glance | 8

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 the most recent PHP version possible (PHP 5.3.8 or newer is recommended). 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:
Listing 1-1

1 www/ <- your web root directory 2 Symfony/ <- the unpacked archive 3 app/ 4 cache/ 5 config/ 6 logs/ 7 Resources/ 8 bin/

1. http://symfony.com/download

PDF brought to you by generated on October 26, 2012

Chapter 1: The Big Picture | 10

9 10 11 12 13 14 15 16 17 18 19 20 21

src/ Acme/ DemoBundle/ Controller/ Resources/ ... vendor/ symfony/ doctrine/ ... web/ app.php ...

If you are familiar with Composer, you can run the following command instead of downloading the archive:
Listing 1-2

1 $ composer.phar create-project symfony/framework-standard-edition path/to/install 2 2.1.x-dev 3 4 # remove the Git history $ rm -rf .git

For an exact version, replace 2.1.x-dev with the latest Symfony version (e.g. 2.1.1). For details, see the Symfony Installation Page2

If you have PHP 5.4, you can use the built-in web server:
Listing 1-3

1 2 3 4 5

# check your PHP CLI configuration $ php ./app/check.php # run the built-in web server $ php ./app/console server:run

Then the URL to your application will be "http://localhost:8000/app_dev.php3" The built-in server should be used only for development purpose, but it can help you to start your project quickly and easily.

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 1-4

1 http://localhost/config.php

2. http://symfony.com/download 3. http://localhost:8000/app_dev.php

PDF brought to you by generated on October 26, 2012

Chapter 1: The Big Picture | 11

All of the example URLs assume that you've downloaded and unzipped Symfony directly into the web server web root. If you've followed the directions above and unzipped the Symfony directory into your web root, then add /Symfony/web after localhost for all the URLs you see:
Listing 1-5

1 http://localhost/Symfony/web/config.php

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 1-6

1 http://localhost/app_dev.php/

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

Understanding the Fundamentals


One of the main goals of a framework is to ensure Separation of Concerns4. 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):
Listing 1-7

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

PDF brought to you by generated on October 26, 2012

Chapter 1: The Big Picture | 12

1 http://localhost/app_dev.php/demo/hello/Fabien

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:
Listing 1-8

1 2 3 4 5 6 7 8 9 10 11

# app/config/routing_dev.yml _welcome: 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.

PDF brought to you by generated on October 26, 2012

Chapter 1: The Big Picture | 13

The Symfony2 Standard Edition uses YAML5 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: Request6 and Response7. The simplest possible controller might create the response by hand, based on the request:
Listing 1-9

1 use Symfony\Component\HttpFoundation\Response; 2 3 $name = $request->query->get('name'); 4 5 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 Acme\DemoBundle\Controller\WelcomeController class:
Listing 1-10

1 2 3 4 5 6 7 8 9 10 11 12

// src/Acme/DemoBundle/Controller/WelcomeController.php namespace Acme\DemoBundle\Controller;


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

You could have used the full class and method name Acme\DemoBundle\Controller\WelcomeController::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()8 method that loads and renders a template

5. http://www.yaml.org/ 6. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Request.html 7. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Response.html 8. http://api.symfony.com/2.1/Symfony/Bundle/FrameworkBundle/Controller/Controller.html#render()

PDF brought to you by generated on October 26, 2012

Chapter 1: The Big Picture | 14

(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:
Listing 1-11

1 public function indexAction() 2 { 3 $response = $this->render('AcmeDemoBundle:Welcome:index.txt.twig'); 4 $response->headers->set('Content-Type', 'text/plain'); 5 6 return $response; 7 }

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:
Listing 1-12

1 # app/config/routing_dev.yml 2 _demo: 3 resource: "@AcmeDemoBundle/Controller/DemoController.php" 4 type: annotation 5 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:
Listing 1-13

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

// src/Acme/DemoBundle/Controller/DemoController.php use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;


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

// ...
}

PDF brought to you by generated on October 26, 2012

Chapter 1: The Big Picture | 15

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 1-14

1 2 3 4 5 6 7 8

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


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

By default, Symfony2 uses Twig9 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.

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.

9. http://twig.sensiolabs.org/

PDF brought to you by generated on October 26, 2012

Chapter 1: The Big Picture | 16

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:
Listing 1-15

1 http://localhost/app.php/demo/hello/Fabien

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

1 http://localhost/demo/hello/Fabien

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:
Listing 1-17

1 http://localhost/demo/hello/Fabien

PDF brought to you by generated on October 26, 2012

Chapter 1: The Big Picture | 17

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 your 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 1-18

1 # app/config/config_dev.yml 2 imports: 3 - { resource: config.yml } 4 5 web_profiler: 6 toolbar: true 7 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 generated on October 26, 2012

Chapter 1: The Big Picture | 18

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:
Listing 2-1

1 <!DOCTYPE html> 2 <html> 3 <head>

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

PDF brought to you by generated on October 26, 2012

Chapter 2: The View | 19

4 <title>My Webpage</title> 5 </head> 6 <body> 7 <h1>{{ page_title }}</h1> 8 9 <ul id="navigation"> 10 {% for item in navigation %} 11 <li><a href="{{ item.href }}">{{ item.caption }}</a></li> 12 {% endfor %} 13 </ul> 14 </body> 15 </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 2-2

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

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 2-3

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

{# array('name' => 'Fabien') #} {{ 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.

PDF brought to you by generated on October 26, 2012

Chapter 2: The View | 20

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 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:
Listing 2-4

1 2 3 4 5 6 7 8

{# src/Acme/DemoBundle/Resources/views/Demo/hello.html.twig #} {% 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:
Listing 2-5

1 {# src/Acme/DemoBundle/Resources/views/layout.html.twig #} 2 <div class="symfony-content"> 3 {% block content %} 4 {% endblock %} 5 </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:
Listing 2-6

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

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


Listing 2-7

PDF brought to you by generated on October 26, 2012

Chapter 2: The View | 21

1 2 3 4 5 6 7

{# src/Acme/DemoBundle/Resources/views/Demo/hello.html.twig #} {% extends "AcmeDemoBundle::layout.html.twig" %} {# override the body block from embedded.html.twig #} {% block content %} {% include "AcmeDemoBundle:Demo:embedded.html.twig" %} {% endblock %}

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 2-8

1 {# src/Acme/DemoBundle/Resources/views/Demo/index.html.twig #} 2 {% 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 2-9

1 2 3 4 5 6 7 8 9 10 11 12 13 14

// src/Acme/DemoBundle/Controller/DemoController.php
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 2-10

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

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 2-11

PDF brought to you by generated on October 26, 2012

Chapter 2: The View | 22

1 2 3 4 5 6 7 8 9 10 11 12 13 14

// src/Acme/DemoBundle/Controller/DemoController.php use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; // ... /** * @Route("/hello/{name}", name="_demo_hello") * @Template() */ public function helloAction($name) { return array('name' => $name); }

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:
Listing 2-12

1 <link href="{{ asset('css/blog.css') }}" rel="stylesheet" type="text/css" /> 2 3 <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 escape 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 generated on October 26, 2012

Chapter 2: The View | 23

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 3-1

1 2 3 4 5 6 7 8 9 10 11 12 13 14

// src/Acme/DemoBundle/Controller/DemoController.php use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; // ... /** * @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 3-2

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

PDF brought to you by generated on October 26, 2012

Chapter 3: The Controller | 24

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

1 2 3 4 5 6 7 8 9 10 11 12 13 14

// src/Acme/DemoBundle/Controller/DemoController.php use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; // ... /** * @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:
Listing 3-4

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

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:
Listing 3-5

1 $response = $this->forward('AcmeDemoBundle:Hello:fancy', array('name' => $name, 'color' => 2 'green')); 3 // ... 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:
Listing 3-6

1 $request = $this->getRequest(); 2 3 $request->isXmlHttpRequest(); // is it an Ajax request? 4 5 $request->getPreferredLanguage(array('en', 'fr'));

PDF brought to you by generated on October 26, 2012

Chapter 3: The Controller | 25

6 7 $request->query->get('page'); // get a $_GET parameter 8 9 $request->request->get('page'); // get a $_POST parameter

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

1 {{ app.request.query.get('page') }} 2 3 {{ app.request.parameter('page') }}

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 3-8

1 2 3 4 5 6 7 8 9 10

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

// store an attribute for reuse during a later user request $session->set('foo', 'bar'); // in another controller for another request $foo = $session->get('foo'); // use a default value if the key doesn't exist $filters = $session->set('filters', array());

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

1 2 3 4 5 6 7 8

// store a message for the very next request (in a controller) $session->getFlashBag()->add('notice', 'Congratulations, your action succeeded!'); // display any messages back in the next request (in a template)
{% for flashMessage in app.session.flashbag.get('notice') %} <div>{{ flashMessage }}</div> {% endfor %}

This is useful when you need to set a success message before redirecting the user to another page (which will then show the message). Please note that when you use has() instead of get(), the flash message will not be cleared and thus remains available during the following requests.

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

1 # app/config/security.yml 2 security: 3 encoders:

PDF brought to you by generated on October 26, 2012

Chapter 3: The Controller | 26

4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33

Symfony\Component\Security\Core\User\User: plaintext role_hierarchy: ROLE_ADMIN: ROLE_USER ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH] providers: in_memory: 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 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/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:
Listing 3-11

1 2 3 4 5 6 7 8 9 10 11 12 13

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; use JMS\SecurityExtraBundle\Annotation\Secure;

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

PDF brought to you by generated on October 26, 2012

Chapter 3: The Controller | 27

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:
Listing 3-12

1 2 3 4 5 6 7 8 9 10 11 12 13

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;

/** * @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 generated on October 26, 2012

Chapter 3: The Controller | 28

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:
Listing 4-1

1 2 3 4 5 6 7 8 9

// web/app.php require_once __DIR__.'/../app/bootstrap.php.cache'; require_once __DIR__.'/../app/AppKernel.php';


use Symfony\Component\HttpFoundation\Request; $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 generated on October 26, 2012 Chapter 4: The Architecture | 29

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 4-2

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

// app/autoload.php use Symfony\Component\ClassLoader\UniversalClassLoader;


$loader = new UniversalClassLoader(); $loader->registerNamespaces(array( 'Symfony' => array(__DIR__.'/../vendor/symfony/symfony/src', __DIR__.'/../ vendor/bundles'), 'Sensio' => __DIR__.'/../vendor/bundles', 'JMS' => __DIR__.'/../vendor/jms/', 'Doctrine\\Common' => __DIR__.'/../vendor/doctrine/common/lib', 'Doctrine\\DBAL' => __DIR__.'/../vendor/doctrine/dbal/lib', 'Doctrine' => __DIR__.'/../vendor/doctrine/orm/lib', 'Monolog' => __DIR__.'/../vendor/monolog/monolog/src', 'Assetic' => __DIR__.'/../vendor/kriswallsmith/assetic/src', 'Metadata' => __DIR__.'/../vendor/jms/metadata/src', )); $loader->registerPrefixes(array( 'Twig_Extensions_' => __DIR__.'/../vendor/twig/extensions/lib', 'Twig_' => __DIR__.'/../vendor/twig/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.1/Symfony/Component/ClassLoader/UniversalClassLoader.html 2. http://symfony.com/PSR0 3. http://pear.php.net/

PDF brought to you by generated on October 26, 2012

Chapter 4: The Architecture | 30

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 prebuilt 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:
Listing 4-3

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

// app/AppKernel.php public function registerBundles() { $bundles = array( new Symfony\Bundle\FrameworkBundle\FrameworkBundle(), new Symfony\Bundle\SecurityBundle\SecurityBundle(), new Symfony\Bundle\TwigBundle\TwigBundle(), new Symfony\Bundle\MonologBundle\MonologBundle(), new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(), new Symfony\Bundle\DoctrineBundle\DoctrineBundle(), new Symfony\Bundle\AsseticBundle\AsseticBundle(), new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(), new JMS\SecurityExtraBundle\JMSSecurityExtraBundle(), );
if (in_array($this->getEnvironment(), array('dev', 'test'))) { $bundles[] = new Acme\DemoBundle\AcmeDemoBundle(); $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle(); $bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle(); $bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle(); } 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:
Listing 4-4

1 # app/config/config.yml 2 imports: 3 - { resource: parameters.yml } 4 - { resource: security.yml } 5 6 framework: 7 #esi: ~ 8 #translator: { fallback: "%locale%" } 9 secret: "%secret%" 10 router: { resource: "%kernel.root_dir%/config/routing.yml" } 11 form: true

PDF brought to you by generated on October 26, 2012

Chapter 4: The Architecture | 31

12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61

csrf_protection: validation: templating: default_locale: session: auto_start:

true { enable_annotations: true } { engines: ['twig'] } #assets_version: SomeVersionScheme "%locale%" true

# Twig Configuration twig: debug: "%kernel.debug%" strict_variables: "%kernel.debug%" # Assetic Configuration assetic: debug: "%kernel.debug%" use_controller: false bundles: [ ] # java: /usr/bin/java 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%" port: "%database_port%" 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 4-5

PDF brought to you by generated on October 26, 2012

Chapter 4: The Architecture | 32

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

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


framework: router: { resource: "%kernel.root_dir%/config/routing_dev.yml" } profiler: { only_exceptions: false } web_profiler: toolbar: true intercept_redirects: false monolog: handlers: main: type: path: level: firephp: type: level:

stream "%kernel.logs_dir%/%kernel.environment%.log" debug firephp 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 Acme\DemoBundle\Controller\WelcomeController 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 it overrides
PDF brought to you by generated on October 26, 2012 Chapter 4: The Architecture | 33

AcmeDemoBundle. When Symfony loads the AcmeDemoBundle:Welcome:index controller, it will first look for the WelcomeController class in AcmeNewBundle and, if it doesn't exist, 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.

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 4-6

1 $ php app/console

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


Listing 4-7

1 $ php app/console router:debug --help

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 generated on October 26, 2012

Chapter 4: The Architecture | 34

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 independently 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 generated on October 26, 2012

Chapter 5: Symfony2 and HTTP Fundamentals | 36

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:
Listing 5-1

1 2 3 4

GET / HTTP/1.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 generated on October 26, 2012 Chapter 5: Symfony2 and HTTP Fundamentals | 37

GET POST PUT

Retrieve the resource from the server Create a resource on the server 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 5-2

1 DELETE /blog/15 HTTP/1.1

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 (UserAgent). 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 5-3

1 2 3 4 5 6

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

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

PDF brought to you by generated on October 26, 2012

Chapter 5: Symfony2 and HTTP Fundamentals | 38

7 <!-- ... HTML for the xkcd comic --> 8 </html>

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:
Listing 5-4

1 2 3 4 5 6 7

<?php $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
3. http://en.wikipedia.org/wiki/List_of_HTTP_status_codes 4. 5. 6. 7. http://en.wikipedia.org/wiki/Internet_media_type#List_of_common_media_types http://www.w3.org/Protocols/rfc2616/rfc2616.html http://datatracker.ietf.org/wg/httpbis/ https://addons.mozilla.org/en-US/firefox/addon/live-http-headers/

PDF brought to you by generated on October 26, 2012

Chapter 5: Symfony2 and HTTP Fundamentals | 39

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:
Listing 5-5

1 2 3 4 5 6 7

HTTP/1.1 200 OK 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 5-6

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

use Symfony\Component\HttpFoundation\Request; $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(); $request->getLanguages();

// GET, POST, PUT, DELETE, HEAD // 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.1/Symfony/Component/HttpFoundation/Request.html

PDF brought to you by generated on October 26, 2012

Chapter 5: Symfony2 and HTTP Fundamentals | 40

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:
Listing 5-7

1 2 3 4 5 6 7 8 9

use Symfony\Component\HttpFoundation\Response; $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 independently 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. 10. 11. 12.

http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/ParameterBag.html http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/ParameterBag.html#get() http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/ParameterBag.html#has() http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/ParameterBag.html#all()

PDF brought to you by generated on October 26, 2012

Chapter 5: Symfony2 and HTTP Fundamentals | 41

The Front Controller


Traditionally, applications were built so that each "page" of a site was its own physical file:
Listing 5-8

1 index.php 2 contact.php 3 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 executes index.php

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

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 way. 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 5-9

1 2 3 4 5 6 7 8 9 10 11 12

// index.php $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 generated on October 26, 2012 Chapter 5: Symfony2 and HTTP Fundamentals | 42

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:
Listing 5-10

1 contact: 2 pattern: /contact 3 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:
Listing 5-11

1 class MainController 2 { 3 public function contactAction() 4 { 5 return new Response('<h1>Contact us!</h1>'); 6 } 7 }

PDF brought to you by generated on October 26, 2012

Chapter 5: Symfony2 and HTTP Fundamentals | 43

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: HttpFoundation - Contains the Request and Response classes, as well as other classes for handling sessions and file uploads; Routing - 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); Form13 - A full-featured and flexible framework for creating forms and handling form submissions; Validator14 A system for creating rules about data and then validating whether or not usersubmitted data follows those rules; ClassLoader An autoloading library that allows PHP classes to be used without needing to manually require the files containing those classes; Templating A toolkit for rendering templates, handling template inheritance (i.e. a template is decorated with a layout) and performing other common template tasks; Security15 - A powerful library for handling all types of security inside an application; Translation16 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.

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);
13. https://github.com/symfony/Form 14. https://github.com/symfony/Validator 15. https://github.com/symfony/Security 16. https://github.com/symfony/Translation

PDF brought to you by generated on October 26, 2012

Chapter 5: Symfony2 and HTTP Fundamentals | 44

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 generated on October 26, 2012

Chapter 5: Symfony2 and HTTP Fundamentals | 45

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 6-1

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

<?php // index.php $link = mysql_connect('localhost', 'myuser', 'mypassword'); mysql_select_db('blog_db', $link); $result = mysql_query('SELECT id, title FROM post', $link); ?> <!doctype html> <html> <head> <title>List of Posts</title> </head> <body> <h1>List of Posts</h1> <ul> <?php while ($row = mysql_fetch_assoc($result)): ?>

PDF brought to you by generated on October 26, 2012

Chapter 6: Symfony2 versus Flat PHP | 46

18 19 20 21 22 23 24 25 26 27 28 29 30

<li> <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":
Listing 6-2

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

<?php // 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:
1. http://www.doctrine-project.org

PDF brought to you by generated on October 26, 2012

Chapter 6: Symfony2 versus Flat PHP | 47

Listing 6-3

1 <!doctype html> 2 <html> 3 <head> 4 <title>List of Posts</title> 5 </head> 6 <body> 7 <h1>List of Posts</h1> 8 <ul> 9 <?php foreach ($posts as $post): ?> 10 <li> 11 <a href="/read?id=<?php echo $post['id'] ?>"> 12 <?php echo $post['title'] ?> 13 </a> 14 </li> 15 <?php endforeach; ?> 16 </ul> 17 </body> 18 </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 dataaccess functions of the application are isolated in a new file called model.php:
Listing 6-4

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

<?php // 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; }

PDF brought to you by generated on October 26, 2012

Chapter 6: Symfony2 versus Flat PHP | 48

25 26 27 28 }

close_database_connection($link); return $posts;

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:


Listing 6-5

1 2 3 4 5 6

<?php 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:
Listing 6-6

1 <!-- templates/layout.php --> 2 <html> 3 <head> 4 <title><?php echo $title ?></title> 5 </head> 6 <body> 7 <?php echo $content ?> 8 </body> 9 </html>

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


Listing 6-7

1 <?php $title = 'List of Posts' ?> 2 3 <?php ob_start() ?> 4 <h1>List of Posts</h1> 5 <ul> 6 <?php foreach ($posts as $post): ?> 7 <li> 8 <a href="/read?id=<?php echo $post['id'] ?>"> 9 <?php echo $post['title'] ?> 10 </a> 11 </li> 12 <?php endforeach; ?>

PDF brought to you by generated on October 26, 2012

Chapter 6: Symfony2 versus Flat PHP | 49

13 </ul> 14 <?php $content = ob_get_clean() ?> 15 16 <?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.

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 6-8

1 2 3 4 5 6 7 8 9 10 11 12 13 14

// model.php function get_post_by_id($id) { $link = open_database_connection();


$id = intval($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 6-9

1 2 3 4 5 6

<?php 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 6-10

1 <?php $title = $post['title'] ?> 2 3 <?php ob_start() ?> 4 <h1><?php echo $post['title'] ?></h1> 5 6 <div class="date"><?php echo $post['date'] ?></div> 7 <div class="body"> 8 <?php echo $post['body'] ?> 9 </div> 10 <?php $content = ob_get_clean() ?>

PDF brought to you by generated on October 26, 2012

Chapter 6: Symfony2 versus Flat PHP | 50

11 12 <?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 intval() 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...

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:
Listing 6-11

1 2 3 4 5 6 7

Without a front controller /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:
Listing 6-12

1 2 3 4 5 6 7 8 9

<?php // index.php

// load and initialize any global libraries require_once 'model.php'; require_once 'controllers.php'; // route the request internally $uri = $_SERVER['REQUEST_URI'];

PDF brought to you by generated on October 26, 2012

Chapter 6: Symfony2 versus Flat PHP | 51

10 11 12 13 14 15 16 17

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:
Listing 6-13

1 2 3 4 5 6 7 8 9 10 11

function list_action() { $posts = get_all_posts(); require 'templates/list.php'; } function show_action($id) { $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/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 6-14

2. http://symfony.com/download

PDF brought to you by generated on October 26, 2012

Chapter 6: Symfony2 versus Flat PHP | 52

1 2 3 4 5 6 7 8 9 10 11 12

<?php // bootstrap.php require_once 'model.php'; require_once 'controllers.php'; require_once 'vendor/symfony/symfony/src/Symfony/Component/ClassLoader/ UniversalClassLoader.php'; $loader = new Symfony\Component\ClassLoader\UniversalClassLoader(); $loader->registerNamespaces(array( 'Symfony' => __DIR__.'/../vendor/symfony/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:
Listing 6-15

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

<?php // index.php require_once 'app/bootstrap.php'; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; $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:
Listing 6-16

1 2 3 4 5 6

// controllers.php use Symfony\Component\HttpFoundation\Response;


function list_action() { $posts = get_all_posts();

3. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Request.html 4. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Response.html

PDF brought to you by generated on October 26, 2012

Chapter 6: Symfony2 versus Flat PHP | 53

7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29

$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). 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 6-17

1 2 3 4 5 6 7 8 9

<?php // src/Acme/BlogBundle/Controller/BlogController.php namespace Acme\BlogBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; class BlogController extends Controller { public function listAction()

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

PDF brought to you by generated on October 26, 2012

Chapter 6: Symfony2 versus Flat PHP | 54

10 { 11 $posts = $this->get('doctrine')->getManager() 12 ->createQuery('SELECT p FROM AcmeBlogBundle:Post p') 13 ->execute(); 14 15 return $this->render('AcmeBlogBundle:Blog:list.html.php', array('posts' => 16 $posts)); 17 } 18 19 public function showAction($id) 20 { 21 $post = $this->get('doctrine') 22 ->getManager() 23 ->getRepository('AcmeBlogBundle:Post') 24 ->find($id); 25 26 if (!$post) { 27 // cause the 404 page not found to be displayed 28 throw $this->createNotFoundException(); 29 } 30 31 return $this->render('AcmeBlogBundle:Blog:show.html.php', array('post' => $post)); 32 } }

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 6-18

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

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


<?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:


Listing 6-19

1 <!-- app/Resources/views/layout.html.php --> 2 <!doctype html> 3 <html> 4 <head> 5 <title><?php echo $view['slots']->output('title', 'Default title') ?></title> 6 </head> 7 <body> 8 <?php echo $view['slots']->output('_content') ?>

PDF brought to you by generated on October 26, 2012

Chapter 6: Symfony2 versus Flat PHP | 55

9 </body> 10 </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:
Listing 6-20

1 # app/config/routing.yml 2 blog_list: 3 pattern: /blog 4 defaults: { _controller: AcmeBlogBundle:Blog:list } 5 6 blog_show: 7 pattern: /blog/show/{id} 8 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!):
Listing 6-21

1 2 3 4 5 6 7 8 9

<?php // web/app.php require_once __DIR__.'/../app/bootstrap.php'; require_once __DIR__.'/../app/AppKernel.php'; use Symfony\Component\HttpFoundation\Request; $kernel = new AppKernel('prod', false); $kernel->handle(Request::createFromGlobals())->send();

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).

PDF brought to you by generated on October 26, 2012

Chapter 6: Symfony2 versus Flat PHP | 56

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 6-22

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

{# src/Acme/BlogBundle/Resources/views/Blog/list.html.twig #} {% 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 %}

The corresponding layout.html.twig template is also easier to write:


Listing 6-23

1 2 3 4 5 6 7 8 9 10

{# app/Resources/views/layout.html.twig #} <!doctype html> <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
7. http://www.varnish-cache.org 8. http://knpbundles.com/ 9. http://twig.sensiolabs.org

PDF brought to you by generated on October 26, 2012

Chapter 6: Symfony2 versus Flat PHP | 57

How to define Controllers as Services

PDF brought to you by generated on October 26, 2012

Chapter 6: Symfony2 versus Flat PHP | 58

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.

Installing a Symfony2 Distribution


First, check that you have installed and configured a Web server (such as Apache) with the most recent PHP version possible (PHP 5.3.8 or newer is recommended). 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. There are 2 ways to get your project started:

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 generated on October 26, 2012

Chapter 7: Installing and Configuring Symfony | 59

Option 1) Composer
Composer4 is a dependency management library for PHP, which you can use to download the Symfony2 Standard Edition. Start by downloading Composer5 anywhere onto your local computer. If you have curl installed, it's as easy as:
Listing 7-1

1 curl -s https://getcomposer.org/installer | php

If your computer is not ready to use Composer, you'll see some recommendations when running this command. Follow those recommendations to get Composer working properly.

Composer is an executable PHAR file, which you can use to download the Standard Distribution:
Listing 7-2

1 php composer.phar create-project symfony/framework-standard-edition /path/to/webroot/ Symfony 2.1.x-dev

For an exact version, replace 2.1.x-dev with the latest Symfony version (e.g. 2.1.1). For details, see the Symfony Installation Page6

This command may take several minutes to run as Composer download the Standard Distribution along with all of the vendor libraries that it needs. When it finishes, you should have a directory that looks something like this:
Listing 7-3

1 path/to/webroot/ <- your web root directory 2 Symfony/ <- the new directory 3 app/ 4 cache/ 5 config/ 6 logs/ 7 src/ 8 ... 9 vendor/ 10 ... 11 web/ 12 app.php 13 ...

Option 2) Download an Archive


You can also download an archive of the Standard Edition. 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; Download the distribution with or without vendors. If you're planning on using more thirdparty libraries or bundles and managing them via Composer, you should probably download "without vendors".
4. http://getcomposer.org/ 5. http://getcomposer.org/download/ 6. http://symfony.com/download

PDF brought to you by generated on October 26, 2012

Chapter 7: Installing and Configuring Symfony | 60

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):
Listing 7-4

1 2 3 4 5

# for .tgz file $ tar zxvf Symfony_Standard_Vendors_2.1.###.tgz # for a .zip file $ unzip Symfony_Standard_Vendors_2.1.###.zip

If you've downloaded "without vendors", you'll definitely need to read the next section.
You can easily override the default directory structure. See How to override Symfony's Default Directory Structure for more information.

Updating Vendors
At this point, you've downloaded a fully-functional Symfony project in which you'll start to develop your own application. A Symfony project depends on a number of external libraries. These are downloaded into the vendor/ directory of your project via a library called Composer7. Depending on how you downloaded Symfony, you may or may not need to do update your vendors right now. But, updating your vendors is always safe, and guarantees that you have all the vendor libraries you need. Step 1: Get Composer8 (The great new PHP packaging system)
Listing 7-5

1 curl -s http://getcomposer.org/installer | php

Make sure you download composer.phar in the same folder where the composer.json file is located (this is your Symfony project root by default). Step 2: Install vendors
Listing 7-6

1 $ php composer.phar install

This command downloads all of the necessary vendor libraries - including Symfony itself - into the vendor/ directory.
If you don't have curl installed, you can also just download the installer file manually at http://getcomposer.org/installer9. Place this file into your project and then run:
Listing 7-7

1 php installer 2 php composer.phar install

When running php composer.phar install or php composer.phar update, composer will execute post install/update commands to clear the cache and install assets. By default, the assets will be copied into your web directory. To create symlinks instead of copying the assets, you can
7. http://getcomposer.org/ 8. http://getcomposer.org/ 9. http://getcomposer.org/installer

PDF brought to you by generated on October 26, 2012

Chapter 7: Installing and Configuring Symfony | 61

add an entry in the extra node of your composer.json file with the key symfony-assets-install and the value symlink:
Listing 7-8

"extra": { "symfony-app-dir": "app", "symfony-web-dir": "web", "symfony-assets-install": "symlink" }

When passing relative instead of symlink to symfony-assets-install, the command will generate relative symlinks.

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:
Listing 7-9

1 http://localhost/config.php

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

PDF brought to you by generated on October 26, 2012

Chapter 7: Installing and Configuring Symfony | 62

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 7-10

1 2 3 4 5

$ rm -rf app/cache/* $ 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 support10 on your partition and install setfacl before using it (as is the case with Ubuntu), like so:
Listing 7-11

1 $ sudo setfacl -R -m u:www-data:rwx -m u:`whoami`:rwx app/cache app/logs 2 $ 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 7-12

1 umask(0002); // This will let the permissions be 0775 2 3 // or 4 5 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 7-13

1 http://localhost/app_dev.php/

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

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

PDF brought to you by generated on October 26, 2012

Chapter 7: Installing and Configuring Symfony | 63

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.md 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. Be sure to also check out the Cookbook, which contains a wide variety of articles about solving specific problems with Symfony.

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:
Listing 7-14

1 /vendor/

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 composer.phar install script to install all the necessary project dependencies.
PDF brought to you by generated on October 26, 2012 Chapter 7: Installing and Configuring Symfony | 64

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 8-1

1 http://localhost/app_dev.php/hello/Symfony

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:

PDF brought to you by generated on October 26, 2012

Chapter 8: Creating Pages in Symfony2 | 65

For Apache HTTP Server, refer to Apache's DirectoryIndex documentation1. 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):
Listing 8-2

1 $ php app/console generate:bundle --namespace=Acme/HelloBundle --format=yml

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:
Listing 8-3

1 2 3 4 5 6 7 8 9 10 11

// app/AppKernel.php public function registerBundles() { $bundles = array( // ... new Acme\HelloBundle\AcmeHelloBundle(), ); // ...
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:
Listing 8-4

1 # app/config/routing.yml 2 acme_hello: 3 resource: "@AcmeHelloBundle/Resources/config/routing.yml" 4 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.

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

PDF brought to you by generated on October 26, 2012

Chapter 8: Creating Pages in Symfony2 | 66

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:
Listing 8-5

1 # src/Acme/HelloBundle/Resources/config/routing.yml 2 hello: 3 pattern: /hello/{name} 4 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 pagecreation 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 Acme\HelloBundle\Controller\HelloController. Start by creating this file inside your AcmeHelloBundle:
Listing 8-6

1 2 3 4 5 6

// src/Acme/HelloBundle/Controller/HelloController.php namespace Acme\HelloBundle\Controller;


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 8-7

1 2 3 4 5 6 7 8 9 10 11 12

// src/Acme/HelloBundle/Controller/HelloController.php namespace Acme\HelloBundle\Controller;


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

PDF brought to you by generated on October 26, 2012

Chapter 8: Creating Pages in Symfony2 | 67

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:
Listing 8-8

1 http://localhost/app_dev.php/hello/Ryan

You can also view your app in the "prod" environment by visiting:
Listing 8-9

1 http://localhost/app.php/hello/Ryan

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

1 $ php app/console cache:clear --env=prod --no-debug

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 allow 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:
Listing 8-11

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

// src/Acme/HelloBundle/Controller/HelloController.php namespace Acme\HelloBundle\Controller;


use Symfony\Bundle\FrameworkBundle\Controller\Controller; class HelloController extends Controller { public function indexAction($name) { return $this->render('AcmeHelloBundle:Hello:index.html.twig', array('name' => $name));

// render a PHP template instead // return $this->render('AcmeHelloBundle:Hello:index.html.php', array('name' => $name)); } }

In order to use the render() method, your controller must extend the Symfony\Bundle\FrameworkBundle\Controller\Controller 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.

PDF brought to you by generated on October 26, 2012

Chapter 8: Creating Pages in Symfony2 | 68

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. 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 8-12

1 2 3 4 5 6

{# src/Acme/HelloBundle/Resources/views/Hello/index.html.twig #} {% extends '::base.html.twig' %}


{% block body %} Hello {{ name }}! {% 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 8-13

1 2 3 4 5 6 7 8 9 10 11 12 13 14

{# app/Resources/views/base.html.twig #} <!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
3. http://api.symfony.com/2.1/Symfony/Bundle/FrameworkBundle/Controller/Controller.html 4. http://twig.sensiolabs.org

PDF brought to you by generated on October 26, 2012

Chapter 8: Creating Pages in Symfony2 | 69

index.html.twig template. Since you did not define the title block in the child template, it defaults to "Welcome!". 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:
Listing 8-14

1 2 3 4 5 6 7 8 9

// web/app.php require_once __DIR__.'/../app/bootstrap.php.cache'; require_once __DIR__.'/../app/AppKernel.php';


use Symfony\Component\HttpFoundation\Request; $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:
Listing 8-15

1 http://localhost/app.php/hello/Ryan

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:
Listing 8-16

1 http://localhost/hello/Ryan

PDF brought to you by generated on October 26, 2012

Chapter 8: Creating Pages in Symfony2 | 70

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.

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 8-17

1 Class Name: 2 Acme\HelloBundle\Controller\HelloController 3 Path: 4 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
PDF brought to you by generated on October 26, 2012 Chapter 8: Creating Pages in Symfony2 | 71

application. Bundles are first-class citizens in Symfony2. This gives you the flexibility to use pre-built 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:
Listing 8-18

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

// app/AppKernel.php public function registerBundles() { $bundles = array( new Symfony\Bundle\FrameworkBundle\FrameworkBundle(), new Symfony\Bundle\SecurityBundle\SecurityBundle(), new Symfony\Bundle\TwigBundle\TwigBundle(), new Symfony\Bundle\MonologBundle\MonologBundle(), new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(), new Symfony\Bundle\DoctrineBundle\DoctrineBundle(), new Symfony\Bundle\AsseticBundle\AsseticBundle(), new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(), new JMS\SecurityExtraBundle\JMSSecurityExtraBundle(), );
if (in_array($this->getEnvironment(), array('dev', 'test'))) { $bundles[] = new Acme\DemoBundle\AcmeDemoBundle(); $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle(); $bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle(); $bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle(); } 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.

5. http://knpbundles.com

PDF brought to you by generated on October 26, 2012

Chapter 8: Creating Pages in Symfony2 | 72

To show you how simple the bundle system is, create a new bundle called AcmeTestBundle and enable it.
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 8-19

1 2 3 4 5 6 7 8

// src/Acme/TestBundle/AcmeTestBundle.php namespace Acme\TestBundle;


use Symfony\Component\HttpKernel\Bundle\Bundle; 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 8-20

1 2 3 4 5 6 7 8 9 10 11 12 13

// app/AppKernel.php public function registerBundles() { $bundles = array( // ... // register your bundles new Acme\TestBundle\AcmeTestBundle(),
); // ... 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 8-21

1 $ php app/console generate:bundle --namespace=Acme/TestBundle

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 generated on October 26, 2012

Chapter 8: Creating Pages in Symfony2 | 73

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:
Listing 8-22

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

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


framework: secret: router: # ... "%secret%" { resource: "%kernel.root_dir%/config/routing.yml" }

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

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.
PDF brought to you by generated on October 26, 2012 Chapter 8: Creating Pages in Symfony2 | 74

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.

Default Configuration Dump


New in version 2.1: The config:dump-reference command was added in Symfony 2.1

You can dump the default configuration for a bundle in yaml to the console using the config:dumpreference command. Here is an example of dumping the default FrameworkBundle configuration:
Listing 8-23

1 app/console config:dump-reference FrameworkBundle

The extension alias (configuration key) can also be used:


Listing 8-24

1 app/console config:dump-reference framework

See the cookbook article: How to expose a Semantic Configuration for a Bundle for information on adding configuration for your own bundle.

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 8-25

1 http://localhost/app_dev.php/hello/Ryan

PDF brought to you by generated on October 26, 2012

Chapter 8: Creating Pages in Symfony2 | 75

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

1 http://localhost/app.php/hello/Ryan

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 8-27

1 php app/console cache:clear --env=prod --no-debug

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

1 $kernel = new AppKernel('prod', false);

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:
Listing 8-29

1 2 3 4 5

// app/AppKernel.php 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.
Listing 8-30

1 2 3 4 5 6 7 8 9

# app/config/config_dev.yml 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.

PDF brought to you by generated on October 26, 2012

Chapter 8: Creating Pages in Symfony2 | 76

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); 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 Resources/config directory of the bundle and can be specified in YAML, XML or PHP; the global application configuration lives in the app/config directory; 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 generated on October 26, 2012

Chapter 8: Creating Pages in Symfony2 | 77

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!:
Listing 9-1

1 2 3 4 5 6

use Symfony\Component\HttpFoundation\Response; 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 generated on October 26, 2012

Chapter 9: Controller | 78

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.
Listing 9-2

1 2 3 4 5 6 7 8 9 10 11 12

// src/Acme/HelloBundle/Controller/HelloController.php namespace Acme\HelloBundle\Controller;


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

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 4: 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. 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
PDF brought to you by generated on October 26, 2012 Chapter 9: Controller | 79

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:
Listing 9-3

1 # app/config/routing.yml 2 hello: 3 pattern: /hello/{name} 4 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:
Listing 9-4

1 2 3 4 5 6 7 8

<?php // src/Acme/HelloBundle/Controller/HelloController.php namespace Acme\HelloBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; class HelloController extends Controller {

PDF brought to you by generated on October 26, 2012

Chapter 9: Controller | 80

9 10 11 12 13 }

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 9-5

1 # app/config/routing.yml 2 hello: 3 pattern: /hello/{first_name}/{last_name} 4 defaults: { _controller: AcmeHelloBundle:Hello:index, color: green }

The controller for this can take several arguments:


Listing 9-6

1 public function indexAction($first_name, $last_name, $color) 2 { 3 // ... 4 }

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 9-7

1 public function indexAction($last_name, $color, $first_name) 2 { 3 // ... 4 }

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 9-8

1 public function indexAction($first_name, $last_name, $color, $foo) 2 { 3 // ... 4 }

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

PDF brought to you by generated on October 26, 2012

Chapter 9: Controller | 81

1 public function indexAction($first_name, $last_name, $color, $foo = 2 'bar') 3 { 4 // ... }

Not all routing parameters need to be arguments on your controller If, for example, the last_name weren't important for your controller, you could omit it entirely:
Listing 9-10

1 public function indexAction($first_name, $color) 2 { 3 // ... 4 }

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:
Listing 9-11

1 2 3 4 5 6 7 8 9

use Symfony\Component\HttpFoundation\Request; public function updateAction(Request $request) { $form = $this->createForm(...); $form->bind($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:
Listing 9-12

1 2 3 4 5 6 7

// src/Acme/HelloBundle/Controller/HelloController.php namespace Acme\HelloBundle\Controller;


use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Response; class HelloController extends Controller

PDF brought to you by generated on October 26, 2012

Chapter 9: Controller | 82

8 { 9 10 11 12 13 }

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.
Extending the base class is optional in Symfony; it contains useful shortcuts but nothing mandatory. You can also extend Symfony\Component\DependencyInjection\ContainerAware. 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 9-13

1 public function indexAction() 2 { 3 return $this->redirect($this->generateUrl('homepage')); 4 }

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 9-14

1 public function indexAction() 2 { 3 return $this->redirect($this->generateUrl('homepage'), 301); 4 }

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

PDF brought to you by generated on October 26, 2012

Chapter 9: Controller | 83

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

1 use Symfony\Component\HttpFoundation\RedirectResponse; 2 3 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 9-16

1 public function indexAction($name) 2 { 3 $response = $this->forward('AcmeHelloBundle:Hello:fancy', array( 4 'name' => $name, 5 'color' => 'green' 6 )); 7 8 // ... further modify the response or return it directly 9 10 return $response; 11 }

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:
Listing 9-17

1 public function fancyAction($name, $color) 2 { 3 // ... create and return a Response object 4 }

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:
Listing 9-18

1 $httpKernel = $this->container->get('http_kernel'); 2 $response = $httpKernel->forward('AcmeHelloBundle:Hello:fancy', array( 3 'name' => $name, 4 'color' => 'green', 5 ));

PDF brought to you by generated on October 26, 2012

Chapter 9: Controller | 84

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:
Listing 9-19

1 $content = $this->renderView('AcmeHelloBundle:Hello:index.html.twig', array('name' => 2 $name)); 3 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:
Listing 9-20

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

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.
The renderView method is a shortcut to direct use of the templating service. The templating service can also be used directly:
Listing 9-21

1 $templating = $this->get('templating'); 2 $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 9-22

1 $templating->render('AcmeHelloBundle:Hello/Greetings:index.html.twig', array('name' 2 => $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 9-23

1 2 3 4 5 6 7

$request = $this->getRequest(); $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 9-24

PDF brought to you by generated on October 26, 2012

Chapter 9: Controller | 85

1 $ php app/console container:debug

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 9-25

1 public function indexAction() 2 { 3 // retrieve the object from database 4 $product = ...; 5 if (!$product) { 6 throw $this->createNotFoundException('The product does not exist'); 7 } 8 9 return $this->render(...); 10 }

The createNotFoundException() method creates a special NotFoundHttpException object, which ultimately triggers a 404 HTTP response inside Symfony. Of course, you're free to throw any Exception class in your controller - Symfony2 will automatically return a 500 HTTP response code.
Listing 9-26

1 throw new \Exception('Something went wrong!');

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:
Listing 9-27

1 2 3 4 5 6 7 8 9 10

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

// store an attribute for reuse during a later user request $session->set('foo', 'bar'); // in another controller for another request $foo = $session->get('foo'); // use a default value if the key doesn't exist $filters = $session->get('filters', array());

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

PDF brought to you by generated on October 26, 2012

Chapter 9: Controller | 86

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:
Listing 9-28

1 public function updateAction() 2 { 3 $form = $this->createForm(...); 4 5 $form->bind($this->getRequest()); 6 if ($form->isValid()) { 7 // do some sort of processing 8 9 $this->get('session')->getFlashBag()->add('notice', 'Your changes were saved!'); 10 11 return $this->redirect($this->generateUrl(...)); 12 } 13 14 return $this->render(...); 15 }

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:
Listing 9-29

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

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 9-30

1 2 3 4 5 6

// create a simple Response with a 200 status code (the default) $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');

2. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Response.html

PDF brought to you by generated on October 26, 2012

Chapter 9: Controller | 87

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 9-31

1 2 3 4 5 6 7 8 9

$request = $this->getRequest(); $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. 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

3. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/HeaderBag.html

PDF brought to you by generated on October 26, 2012

Chapter 9: Controller | 88

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 10-1

1 # app/config/routing.yml 2 blog_show: 3 pattern: /blog/{slug} 4 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 generated on October 26, 2012

Chapter 10: Routing | 89

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

// src/Acme/BlogBundle/Controller/BlogController.php namespace Acme\BlogBundle\Controller;


use Symfony\Bundle\FrameworkBundle\Controller\Controller; class BlogController extends Controller { public function showAction($slug) { // use the $slug variable to query the database $blog = ...; 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:
Listing 10-3

1 GET /blog/my-blog-post

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.

PDF brought to you by generated on October 26, 2012

Chapter 10: Routing | 90

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

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 10-4

1 # app/config/config.yml 2 framework: 3 # ... 4 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. To do so, just point out in the main routing configuration file which external files should be included. 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 10-5

1 _welcome: 2 pattern: 3 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 10-6

1 blog_show: 2 pattern: 3 defaults:

/blog/{slug} { _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:
PDF brought to you by generated on October 26, 2012 Chapter 10: Routing | 91

Listing 10-7

1 blog: 2 pattern: 3 defaults:

/blog { _controller: AcmeBlogBundle:Blog:index }

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:
Listing 10-8

1 blog: 2 pattern: 3 defaults:

/blog/{page} { _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:
Listing 10-9

1 blog: 2 pattern: 3 defaults:

/blog/{page} { _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 /blog/1 /blog/2 {page} = 1 {page} = 1 {page} = 2

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

1 blog: 2 pattern: 3 defaults: 4 5 blog_show: 6 pattern: 7 defaults:

/blog/{page} { _controller: AcmeBlogBundle:Blog:index, page: 1 } /blog/{slug} { _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 /blog/2 route parameters blog {page} = 2

PDF brought to you by generated on October 26, 2012

Chapter 10: Routing | 92

URL /blog/my-blog-post

route parameters 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:
Listing 10-11

1 blog: 2 pattern: /blog/{page} 3 defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 } 4 requirements: 5 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 /blog/2 /blog/my-blog-post route blog parameters {page} = 2

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 10-12

1 homepage: 2 pattern: /{culture} 3 defaults: { _controller: AcmeDemoBundle:Main:homepage, culture: en } 4 requirements: 5 culture: en|fr

For incoming requests, the {culture} portion of the URL is matched against the regular expression (en|fr). / /fr /es {culture} = en {culture} = fr won't match this route

/en {culture} = en

PDF brought to you by generated on October 26, 2012

Chapter 10: Routing | 93

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 10-13

1 contact: 2 pattern: /contact 3 defaults: { _controller: AcmeDemoBundle:Main:contact } 4 requirements: 5 _method: GET 6 7 contact_process: 8 pattern: /contact 9 defaults: { _controller: AcmeDemoBundle:Main:contactProcess } 10 requirements: 11 _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:
Listing 10-14

1 article_show: 2 pattern: /articles/{culture}/{year}/{title}.{_format} 3 defaults: { _controller: AcmeDemoBundle:Article:show, _format: html } 4 requirements: 5 culture: en|fr 6 _format: html|rss 7 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 dot between placeholders instead of a slash. URLs matching this route might look like: /articles/en/2010/my-post /articles/fr/2010/my-post.rss

PDF brought to you by generated on October 26, 2012

Chapter 10: Routing | 94

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 request (read more);
If you use the _locale parameter in a route, that value will also be stored on the session so that subsequent requests keep this same locale.

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 AcmeBlogBundle Controller Class BlogController Method Name showAction

The controller might look like this:


Listing 10-15

1 2 3 4 5 6 7 8 9 10

// src/Acme/BlogBundle/Controller/BlogController.php namespace Acme\BlogBundle\Controller;


use Symfony\Bundle\FrameworkBundle\Controller\Controller; class BlogController extends Controller { public function showAction($slug) { // ...

PDF brought to you by generated on October 26, 2012

Chapter 10: Routing | 95

11 12 }

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: Acme\BlogBundle\Controller\BlogController::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 10-16

1 public function showAction($slug) 2 { 3 // ... 4 }

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. 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:

PDF brought to you by generated on October 26, 2012

Chapter 10: Routing | 96

Listing 10-17

1 # app/config/routing.yml 2 acme_hello: 3 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:
Listing 10-18

1 # src/Acme/HelloBundle/Resources/config/routing.yml 2 acme_hello: 3 pattern: /hello/{name} 4 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}:
Listing 10-19

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

The string /admin will now be prepended to the pattern of each route loaded from the new routing resource.
You can also define routes using annotations. See the FrameworkExtraBundle documentation to see how.

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 10-20

1 $ php app/console router:debug

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

1 homepage 2 contact 3 contact_process

ANY GET POST

/ /contact /contact

PDF brought to you by generated on October 26, 2012

Chapter 10: Routing | 97

4 article_show 5 blog 6 blog_show

ANY ANY ANY

/articles/{culture}/{year}/{title}.{_format} /blog/{page} /blog/{slug}

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

1 $ php app/console router:debug article_show

New in version 2.1: The router:match command was added in Symfony 2.1

You can check which, if any, route matches a path with the router:match console command:
Listing 10-23

1 $ php app/console router:match /articles/en/2012/article.rss 2 Route "article_show" matches

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 10-24

1 2 3 4 5

$params = $router->match('/blog/my-blog-post'); // 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 10-25

1 class MainController extends Controller 2 { 3 public function showAction($slug) 4 { 5 // ... 6 7 $url = $this->get('router')->generate('blog_show', array('slug' => 'my-blog-post')); 8 } 9 }

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

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

PDF brought to you by generated on October 26, 2012

Chapter 10: Routing | 98

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:
Listing 10-26

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

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:
Listing 10-27

1 $router->generate('blog_show', array('slug' => 'my-blog-post'), true); 2 // 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 RequestContext object:
Listing 10-28

1 $router->getContext()->setHost('www.example.com');

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:
Listing 10-29

1 $router->generate('blog', array('page' => 2, 'category' => 'Symfony')); 2 // /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:
Listing 10-30

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

Absolute URLs can also be generated.


Listing 10-31

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

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

PDF brought to you by generated on October 26, 2012

Chapter 10: Routing | 99

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 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 generated on October 26, 2012

Chapter 10: Routing | 100

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:
Listing 11-1

1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title>Welcome to Symfony!</title> 5 </head> 6 <body> 7 <h1><?php echo $page_title ?></h1> 8 9 <ul id="navigation"> 10 <?php foreach ($navigation as $item): ?> 11 <li> 12 <a href="<?php echo $item->getHref() ?>"> 13 <?php echo $item->getCaption() ?> 14 </a> 15 </li> 16 <?php endforeach; ?> 17 </ul> 18 </body> 19 </html>

PDF brought to you by generated on October 26, 2012

Chapter 11: Creating and using Templates | 101

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 11-2

1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title>Welcome to Symfony!</title> 5 </head> 6 <body> 7 <h1>{{ page_title }}</h1> 8 9 <ul id="navigation"> 10 {% for item in navigation %} 11 <li><a href="{{ item.href }}">{{ item.caption }}</a></li> 12 {% endfor %} 13 </ul> 14 </body> 15 </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 11-3

1 {{ title|upper }}

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 11-4

1 {% for i in 0..10 %} 2 <div class="{{ cycle(['odd', 'even'], i) }}"> 3 <!-- some HTML here -->

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 generated on October 26, 2012

Chapter 11: Creating and using Templates | 102

4 </div> 5 {% endfor %}

Throughout this chapter, template examples will be shown in both Twig and PHP.
If you do choose to not use Twig and you disable it, you'll need to implement your own exception handler via the kernel.exception event.

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 whitespace control, sandboxing, automatic and contextual output escaping, 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:
Listing 11-5

1 <ul> 2 {% for user in users if user.active %} 3 <li>{{ user.username }}</li> 4 {% else %} 5 <li>No users found</li> 6 {% endfor %} 7 </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

PDF brought to you by generated on October 26, 2012

Chapter 11: Creating and using Templates | 103

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:
Listing 11-6

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

{# app/Resources/views/base.html.twig #} <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>{% block title %}Test Application{% endblock %}</title> </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 11-7

1 2 3 4 5 6 7 8 9 10 11

{# src/Acme/BlogBundle/Resources/views/Blog/index.html.twig #} {% 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.

PDF brought to you by generated on October 26, 2012

Chapter 11: Creating and using Templates | 104

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 11-8

1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 5 <title>My cool blog posts</title> 6 </head> 7 <body> 8 <div id="sidebar"> 9 <ul> 10 <li><a href="/">Home</a></li> 11 <li><a href="/blog">Blog</a></li> 12 </ul> 13 </div> 14 15 <div id="content"> 16 <h2>My first post</h2> 17 <p>The body of the first post.</p> 18 19 <h2>Another post</h2> 20 <p>The body of the second post.</p> 21 </div> 22 </body> 23 </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:
Listing 11-9

1 {% block sidebar %} 2 <h3>Table of Contents</h3> 3 ... 4 {{ parent() }} 5 {% endblock %}

PDF brought to you by generated on October 26, 2012

Chapter 11: Creating and using Templates | 105

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: 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,
PDF brought to you by generated on October 26, 2012 Chapter 11: Creating and using Templates | 106

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.

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.
Listing 11-10

1 2 3 4 5 6 7

{# src/Acme/ArticleBundle/Resources/views/Article/articleDetails.html.twig #} <h2>{{ article.title }}</h2> <h3 class="byline">by {{ article.authorName }}</h3>


<p> {{ article.body }} </p>

Including this template from any other template is simple:


Listing 11-11

1 2 3 4 5 6 7 8 9 10

{# src/Acme/ArticleBundle/Resources/views/Article/list.html.twig #} {% 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 %}

PDF brought to you by generated on October 26, 2012

Chapter 11: Creating and using Templates | 107

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}.

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 11-12

1 2 3 4 5 6 7 8 9 10 11 12

// src/Acme/ArticleBundle/Controller/ArticleController.php
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 11-13

1 {# src/Acme/ArticleBundle/Resources/views/Article/recentList.html.twig #} 2 {% for article in articles %} 3 <a href="/article/{{ article.slug }}"> 4 {{ article.title }} 5 </a> 6 {% 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 11-14

1 2 3 4 5 6 7

{# app/Resources/views/base.html.twig #} {# ... #}
<div id="sidebar"> {% render "AcmeArticleBundle:Article:recentArticles" with {'max': 3} %} </div>

PDF brought to you by generated on October 26, 2012

Chapter 11: Creating and using Templates | 108

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.

Asynchronous Content with hinclude.js


New in version 2.1: hinclude.js support was added in Symfony 2.1

Controllers can be embedded asyncronously using the hinclude.js5 javascript library. As the embedded content comes from another page (or controller for that matter), Symfony2 uses the standard render helper to configure hinclude tags:
Listing 11-15

1 {% render '...:news' with {}, {'standalone': 'js'} %}

hinclude.js6 needs to be included in your page to work.

Default content (while loading or if javascript is disabled) can be set globally in your application configuration:
Listing 11-16

1 # app/config/config.yml 2 framework: 3 # ... 4 templating: 5 hinclude_default_template: AcmeDemoBundle::hinclude.html.twig

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. First, link to the "_welcome" page, which is accessible via the following routing configuration:
Listing 11-17

1 _welcome: 2 pattern: / 3 defaults: { _controller: AcmeDemoBundle:Welcome:index }

To link to the page, just use the path Twig function and refer to the route:
Listing 11-18

1 <a href="{{ path('_welcome') }}">Home</a>

As expected, this will generate the URL /. Let's see how this works with a more complicated route:

5. http://mnot.github.com/hinclude/ 6. http://mnot.github.com/hinclude/

PDF brought to you by generated on October 26, 2012

Chapter 11: Creating and using Templates | 109

Listing 11-19

1 article_show: 2 pattern: /article/{slug} 3 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:
Listing 11-20

1 {# src/Acme/ArticleBundle/Resources/views/Article/recentList.html.twig #} 2 {% for article in articles %} 3 <a href="{{ path('article_show', {'slug': article.slug}) }}"> 4 {{ article.title }} 5 </a> 6 {% endfor %}

You can also generate an absolute URL by using the url Twig function:
Listing 11-21

1 <a href="{{ url('_welcome') }}">Home</a>

The same can be done in PHP templates by passing a third argument to the generate() method:
Listing 11-22

1 <a href="<?php echo $view['router']->generate('_welcome', array(), true) ?>">Home</a>

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:
Listing 11-23

1 <img src="{{ asset('images/logo.png') }}" alt="Symfony!" /> 2 3 <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.com7), then the rendered paths should be /images/logo.png. But if your application lives in a subdirectory (e.g. http://example.com/my_app8), 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, /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.
7. http://example.com 8. http://example.com/my_app

PDF brought to you by generated on October 26, 2012

Chapter 11: Creating and using Templates | 110

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 11-24

1 {# 'app/Resources/views/base.html.twig' #} 2 <html> 3 <head> 4 {# ... #} 5 6 {% block stylesheets %} 7 <link href="{{ asset('/css/main.css') }}" type="text/css" rel="stylesheet" /> 8 {% endblock %} 9 </head> 10 <body> 11 {# ... #} 12 13 {% block javascripts %} 14 <script src="{{ asset('/js/main.js') }}" type="text/javascript"></script> 15 {% endblock %} 16 </body> 17 </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 11-25

1 2 3 4 5 6 7 8 9 10

{# src/Acme/DemoBundle/Resources/views/Contact/contact.html.twig #} {% 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. 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").
Listing 11-26

1 <link href="{{ asset('bundles/acmedemo/css/contact.css') }}" type="text/css" rel="stylesheet" />

The end result is a page that includes both the main.css and contact.css stylesheets.

PDF brought to you by generated on October 26, 2012

Chapter 11: Creating and using Templates | 111

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 GlobalVariables9 instance which will give you access to some application specific variables automatically:
Listing 11-27

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.

1 <p>Username: {{ app.user.username }}</p> 2 {% if app.debug %} 3 <p>Request method: {{ app.request.method }}</p> 4 <p>Application Environment: {{ app.environment }}</p> 5 {% 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:
Listing 11-28

1 return $this->render('AcmeArticleBundle:Article:index.html.twig');

is equivalent to: $engine = $this->container->get('templating'); >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:
Listing 11-29

$content

$engine-

1 # app/config/config.yml 2 framework: 3 # ... 4 templating: { engines: ['twig'] }

Several configuration options are available and are covered in the Configuration Appendix.

9. http://api.symfony.com/2.1/Symfony/Bundle/FrameworkBundle/Templating/GlobalVariables.html

PDF brought to you by generated on October 26, 2012

Chapter 11: Creating and using Templates | 112

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.com10) 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 11-30

1 public function indexAction() 2 { 3 // some logic to retrieve the blogs 4 $blogs = ...; 5 6 $this->render('AcmeBlogBundle:Blog:index.html.twig', array('blogs' => $blogs)); 7 }

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.
If you add a template in a new location, you may need to clear your cache (php app/console cache:clear), even if you are in debug mode.

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.

10. http://knpbundles.com

PDF brought to you by generated on October 26, 2012

Chapter 11: Creating and using Templates | 113

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.

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 sectionspecific elements;
Listing 11-31

1 2 3 4 5 6 7 8

{# src/Acme/BlogBundle/Resources/views/layout.html.twig #} {% 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.
1 2 3 4 5 6 7 8 9

Listing 11-32

{# src/Acme/BlogBundle/Resources/views/Blog/index.html.twig #} {% 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.

PDF brought to you by generated on October 26, 2012

Chapter 11: Creating and using Templates | 114

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 HTML of the resulting page or allow a malicious user to perform a Cross Site Scripting11 (XSS) attack. Consider this classic example:
Listing 11-33

1 Hello {{ name }}

Imagine that the user enters the following code as his/her name:
Listing 11-34

1 <script>alert('hello!')</script>

Without any output escaping, the resulting template will cause a JavaScript alert box to pop up:
Listing 11-35

1 Hello <script>alert('hello!')</script>

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 11-36

1 Hello &lt;script&gt;alert(&#39;helloe&#39;)&lt;/script&gt;

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 Escaping12 in the Twig documentation.

11. http://en.wikipedia.org/wiki/Cross-site_scripting 12. http://twig.sensiolabs.org/doc/api.html#escaper-extension

PDF brought to you by generated on October 26, 2012

Chapter 11: Creating and using Templates | 115

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 11-37

1 Hello <?php echo $view->escape($name) ?>

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 11-38

1 var myMsg = 'Hello <?php echo $view->escape($name, 'js') ?>';

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:
Listing 11-39

1 # app/config/config.yml 2 services: 3 acme_hello.twig.extension.debug: 4 class: Twig_Extension_Debug 5 tags: 6 - { name: 'twig.extension' }

Template parameters can then be dumped using the dump function:


Listing 11-40

1 2 3 4 5 6 7 8

{# src/Acme/ArticleBundle/Resources/views/Article/recentList.html.twig #} {{ 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.

PDF brought to you by generated on October 26, 2012

Chapter 11: Creating and using Templates | 116

Syntax Checking
New in version 2.1: The twig:lint command was added in Symfony 2.1

You can check for syntax errors in Twig templates using the twig:lint console command:
Listing 11-41

1 2 3 4 5 6 7 8

# You can check by filename: $ php app/console twig:lint src/Acme/ArticleBundle/Resources/views/Article/ recentList.html.twig # or by directory: $ php app/console twig:lint src/Acme/ArticleBundle/Resources/views # or using the bundle name: $ php app/console twig:lint @AcmeArticleBundle

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:
Listing 11-42

1 public function indexAction() 2 { 3 $format = $this->getRequest()->getRequestFormat(); 4 5 return $this->render('AcmeBlogBundle:Blog:index.'.$format.'.twig'); 6 }

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 11-43

1 <a href="{{ path('article_show', {'id': 123, '_format': 'pdf'}) }}"> 2 PDF Version 3 </a>

PDF brought to you by generated on October 26, 2012

Chapter 11: Creating and using Templates | 117

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 11-44

1 2 3 4 5

// creates a Response object whose content is the rendered template $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 generated on October 26, 2012

Chapter 11: Creating and using Templates | 118

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:
Listing 12-1

1 $ php app/console generate:bundle --namespace=Acme/StoreBundle

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

PDF brought to you by generated on October 26, 2012

Chapter 12: Databases and Doctrine | 119

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.yml file:
Listing 12-2

1 # app/config/parameters.yml 2 parameters: 3 database_driver: pdo_mysql 4 database_host: localhost 5 database_name: test_project 6 database_user: root 7 database_password: password 8 9 # ...

Defining the configuration via parameters.yml is just a convention. The parameters defined in that file are referenced by the main configuration file when setting up Doctrine:
Listing 12-3

1 # app/config/config.yml 2 doctrine: 3 dbal: 4 driver: "%database_driver%" 5 host: "%database_host%" 6 dbname: "%database_name%" 7 user: "%database_user%" 8 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 12-4

1 $ php app/console doctrine:database:create

PDF brought to you by generated on October 26, 2012

Chapter 12: Databases and Doctrine | 120

Setting Up The Database


One mistake even seasoned developers make when starting a Symfony2 project is forgetting to setup default charset and collation on their database, ending up with latin type collations, which are default for most databases. They might even remember to do it the very first time, but forget that it's all gone after running a relatively common command during development:
Listing 12-5

1 $ php app/console doctrine:database:drop --force 2 $ php app/console doctrine:database:create

There's no way to configure these defaults inside Doctrine, as it tries to be as agnostic as possible in terms of environment configuration. One way to solve this problem is to configure server-level defaults. Setting UTF8 defaults for MySQL is as simple as adding a few lines to your configuration file (typically my.cnf):
Listing 12-6

1 [mysqld] 2 collation-server = utf8_general_ci 3 character-set-server = utf8

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 12-7

1 2 3 4 5 6 7 8 9 10 11

// src/Acme/StoreBundle/Entity/Product.php namespace Acme\StoreBundle\Entity;


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.
Once you learn the concepts behind Doctrine, you can have Doctrine create this entity class for you:
Listing 12-8

1 $ php app/console doctrine:generate:entity --entity="AcmeStoreBundle:Product" --fields="name:string(255) price:float description:text"

PDF brought to you by generated on October 26, 2012

Chapter 12: Databases and Doctrine | 121

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.

Listing 12-9

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

// src/Acme/StoreBundle/Entity/Product.php namespace Acme\StoreBundle\Entity;


use Doctrine\ORM\Mapping as ORM;

/** * @ORM\Entity * @ORM\Table(name="product") */ class Product { /** * @ORM\Id * @ORM\Column(type="integer") * @ORM\GeneratedValue(strategy="AUTO") */ protected $id; /** * @ORM\Column(type="string", length=100) */ protected $name; /** * @ORM\Column(type="decimal", scale=2) */ protected $price;

PDF brought to you by generated on October 26, 2012

Chapter 12: Databases and Doctrine | 122

29 30 31 32 33 }

/** * @ORM\Column(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. ORM\Column(..)), which is not shown in Doctrine's documentation. You'll also need to include the use Doctrine\ORM\Mapping 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-10

1 /** 2 * @IgnoreAnnotation("fn") 3 */ 4 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:
Listing 12-11

1 $ php app/console doctrine:generate:entities Acme/StoreBundle/Entity/Product

3. 4. 5. 6.

http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#quoting-reserved-words http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#persistent-classes http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#property-mapping

PDF brought to you by generated on October 26, 2012

Chapter 12: Databases and Doctrine | 123

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 @ORM\Entity(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:
Listing 12-12

1 $ php app/console doctrine:generate:entities AcmeStoreBundle 2 $ 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:
Listing 12-13

1 $ php app/console doctrine:schema:update --force

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 generated on October 26, 2012

Chapter 12: Databases and Doctrine | 124

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:
Listing 12-14

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

// src/Acme/StoreBundle/Controller/DefaultController.php // ... use Acme\StoreBundle\Entity\Product; use Symfony\Component\HttpFoundation\Response;


public function createAction() { $product = new Product(); $product->setName('A Foo Bar'); $product->setPrice('19.99'); $product->setDescription('Lorem ipsum dolor'); $em = $this->getDoctrine()->getManager(); $em->persist($product); $em->flush(); return new Response('Created product id '.$product->getId()); }

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 9-12 In this section, you instantiate and work with the $product object like any other, normal PHP object; line 14 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 15 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 16 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 generated on October 26, 2012

Chapter 12: Databases and Doctrine | 125

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:
Listing 12-15

1 public function showAction($id) 2 { 3 $product = $this->getDoctrine() 4 ->getRepository('AcmeStoreBundle:Product') 5 ->find($id); 6 7 if (!$product) { 8 throw $this->createNotFoundException('No product found for id '.$id); 9 } 10 11 // ... do something, like pass the $product object into a template 12 }

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:
Listing 12-16

1 $repository = $this->getDoctrine() 2 ->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. Acme\StoreBundle\Entity\Product). 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:
Listing 12-17

1 2 3 4 5 6 7 8 9 10 11 12

// query by the primary key (usually "id") $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 generated on October 26, 2012

Chapter 12: Databases and Doctrine | 126

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 12-18

1 2 3 4 5 6 7 8

// query for one product matching be name and price $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 12-19

1 public function updateAction($id) 2 { 3 $em = $this->getDoctrine()->getManager(); 4 $product = $em->getRepository('AcmeStoreBundle:Product')->find($id); 5 6 if (!$product) { 7 throw $this->createNotFoundException('No product found for id '.$id); 8 } 9

PDF brought to you by generated on October 26, 2012

Chapter 12: Databases and Doctrine | 127

10 11 12 13 14 }

$product->setName('New product name!'); $em->flush(); 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:
Listing 12-20

1 $em->remove($product); 2 $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:
Listing 12-21

1 $repository->find($id); 2 3 $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:
Listing 12-22

1 2 3 4 5 6

$em = $this->getDoctrine()->getManager(); $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();

PDF brought to you by generated on October 26, 2012

Chapter 12: Databases and Doctrine | 128

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:
Listing 12-23

1 $product = $query->getSingleResult();

The getSingleResult() method throws a Doctrine\ORM\NoResultException exception if no results are returned and a Doctrine\ORM\NonUniqueResultException 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 12-24

1 2 3 4 5 6 7 8 9

$query = $em->createQuery('SELECT ...') ->setMaxResults(1); try { $product = $query->getSingleResult(); } catch (\Doctrine\Orm\NoResultException $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 12-25

1 ... WHERE p.price > :price ...

You can then set the value of the price placeholder by calling the setParameter() method:
Listing 12-26

1 ->setParameter('price', '19.99')

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 12-27

1 ->setParameters(array( 2 'price' => '19.99', 3 'name' => 'Foo', 4 ))

7. http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html

PDF brought to you by generated on October 26, 2012

Chapter 12: Databases and Doctrine | 129

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 autocompletion as you type the method names. From inside a controller:
Listing 12-28

1 $repository = $this->getDoctrine() 2 ->getRepository('AcmeStoreBundle:Product'); 3 4 $query = $repository->createQueryBuilder('p') 5 ->where('p.price > :price') 6 ->setParameter('price', '19.99') 7 ->orderBy('p.price', 'ASC') 8 ->getQuery(); 9 10 $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.
Listing 12-29

1 2 3 4 5 6 7 8 9 10 11 12

// src/Acme/StoreBundle/Entity/Product.php namespace Acme\StoreBundle\Entity;


use Doctrine\ORM\Mapping as ORM;

/** * @ORM\Entity(repositoryClass="Acme\StoreBundle\Entity\ProductRepository") */ 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:
Listing 12-30

1 $ php app/console doctrine:generate:entities Acme

Next, add a new method - findAllOrderedByName() - to the newly generated repository class. This method will query for all of the Product entities, ordered alphabetically.
Listing 12-31

1 // src/Acme/StoreBundle/Entity/ProductRepository.php 2 namespace Acme\StoreBundle\Entity; 3

8. http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/query-builder.html

PDF brought to you by generated on October 26, 2012

Chapter 12: Databases and Doctrine | 130

4 5 6 7 8 9 10 11 12 13 14

use Doctrine\ORM\EntityRepository; class ProductRepository extends EntityRepository { public function findAllOrderedByName() { return $this->getEntityManager() ->createQuery('SELECT p FROM AcmeStoreBundle:Product p ORDER BY p.name ASC') ->getResult(); } }

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 12-32

1 $em = $this->getDoctrine()->getManager(); 2 $products = $em->getRepository('AcmeStoreBundle:Product') 3 ->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 12-33

1 $ php app/console doctrine:generate:entity --entity="AcmeStoreBundle:Category" --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 12-34

1 2 3 4 5 6 7

// src/Acme/StoreBundle/Entity/Category.php // ... use Doctrine\Common\Collections\ArrayCollection;


class Category {

PDF brought to you by generated on October 26, 2012

Chapter 12: Databases and Doctrine | 131

8 9 10 11 12 13 14 15 16 17 18 19 }

// ... /** * @ORM\OneToMany(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.
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:
Listing 12-35

1 2 3 4 5 6 7 8 9 10 11 12 13 14

// src/Acme/StoreBundle/Entity/Product.php // ...
class Product { // ...

/** * @ORM\ManyToOne(targetEntity="Category", inversedBy="products") * @ORM\JoinColumn(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:
Listing 12-36

1 $ php app/console doctrine:generate:entities Acme

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.

PDF brought to you by generated on October 26, 2012

Chapter 12: Databases and Doctrine | 132

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.

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 12-37

1 $ php app/console doctrine:schema:update --force

This task should only be really used during development. For a more robust method of systematically updating your production database, read about Doctrine migrations.

PDF brought to you by generated on October 26, 2012

Chapter 12: Databases and Doctrine | 133

Saving Related Entities


Now, let's see the code in action. Imagine you're inside a controller:
Listing 12-38

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29

// ...
use Acme\StoreBundle\Entity\Category; use Acme\StoreBundle\Entity\Product; use Symfony\Component\HttpFoundation\Response; 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()->getManager(); $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:
Listing 12-39

1 public function showAction($id) 2 { 3 $product = $this->getDoctrine() 4 ->getRepository('AcmeStoreBundle:Product') 5 ->find($id); 6 7 $categoryName = $product->getCategory()->getName(); 8 9 // ... 10 }

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-

PDF brought to you by generated on October 26, 2012

Chapter 12: Databases and Doctrine | 134

>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.

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 12-40

1 public function showProductAction($id) 2 { 3 $category = $this->getDoctrine() 4 ->getRepository('AcmeStoreBundle:Category') 5 ->find($id); 6 7 $products = $category->getProducts(); 8 9 // ... 10 }

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 generated on October 26, 2012

Chapter 12: Databases and Doctrine | 135

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:
Listing 12-41

1 2 3 4 5 6 7 8

$product = $this->getDoctrine() ->getRepository('AcmeStoreBundle:Product') ->find($id); $category = $product->getCategory();

// prints "Proxies\AcmeStoreBundleEntityCategoryProxy" 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:
Listing 12-42

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

// src/Acme/StoreBundle/Entity/ProductRepository.php 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 (\Doctrine\ORM\NoResultException $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 generated on October 26, 2012 Chapter 12: Databases and Doctrine | 136

Listing 12-43

1 public function showAction($id) 2 { 3 $product = $this->getDoctrine() 4 ->getRepository('AcmeStoreBundle:Product') 5 ->findOneByIdJoinedToCategory($id); 6 7 $category = $product->getCategory(); 8 9 // ... 10 }

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. oneto-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. ORM\OneToMany), which is not reflected in Doctrine's documentation. You'll also need to include the use Doctrine\ORM\Mapping 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-44

1 2 3 4 5 6 7 8

/** * @ORM\Entity() * @ORM\HasLifecycleCallbacks() */ 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):

9. http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html

PDF brought to you by generated on October 26, 2012

Chapter 12: Databases and Doctrine | 137

Listing 12-45

1 2 3 4 5 6 7

/** * @ORM\PrePersist */ 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 How to Register 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.

10. http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html#lifecycle-events

PDF brought to you by generated on October 26, 2012

Chapter 12: Databases and Doctrine | 138

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 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-46

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

/** * A string field with length 255 that cannot be null * (reflecting the default values for the "type", "length" and *nullable* options) * * @ORM\Column() */ protected $name; /** * A string field of length 150 that persists to an "email_address" column * and has a unique index. * * @ORM\Column(name="email_address", unique=true, length=150) */ protected $email;

11. http://php.net/manual/en/class.datetime.php 12. http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#doctrine-mapping-types

PDF brought to you by generated on October 26, 2012

Chapter 12: Databases and Doctrine | 139

There are a few more options not listed here. For more details, see Doctrine's Property Mapping documentation13

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:
Listing 12-47

1 $ php app/console

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:
Listing 12-48

1 $ php app/console help doctrine:database:create

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:
Listing 12-49

1 $ php app/console doctrine:ensure-production-settings --env=prod

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:
13. http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#property-mapping

PDF brought to you by generated on October 26, 2012

Chapter 12: Databases and Doctrine | 140

DoctrineFixturesBundle How to use Doctrine Extensions: Timestampable, Sluggable, Translatable, etc.

PDF brought to you by generated on October 26, 2012

Chapter 12: Databases and Doctrine | 141

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:
Listing 13-1

1 $ 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.yml file:
Listing 13-2

1 # app/config/parameters.yml 2 parameters: 3 database_driver: mysql 4 database_host: localhost 5 database_name: test_project 6 database_user: root 7 database_password: password 8 database_charset: UTF8

1. http://www.propelorm.org/cookbook/symfony2/working-with-symfony2.html#installation

PDF brought to you by generated on October 26, 2012

Chapter 13: Databases and Propel | 142

Defining the configuration via parameters.yml is just a convention. The parameters defined in that file are referenced by the main configuration file when setting up Propel:
Listing 13-3

1 propel: 2 dbal: 3 driver: "%database_driver%" 4 user: "%database_user%" 5 password: "%database_password%" 6 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:
Listing 13-4

1 $ php app/console propel:database:create

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:
Listing 13-5

1 <?xml version="1.0" encoding="UTF-8"?> 2 <database name="default" namespace="Acme\StoreBundle\Model" defaultIdMethod="native"> 3 <table name="product"> 4 <column name="id" type="integer" required="true" primaryKey="true" 5 autoIncrement="true" /> 6 <column name="name" type="varchar" primaryString="true" size="100" /> 7 <column name="price" type="decimal" /> 8 <column name="description" type="longvarchar" /> 9 </table> </database>

Building the Model


After creating your schema.xml, generate your model from it by running:
Listing 13-6

1 $ php app/console propel:model:build

This generates each model class to quickly develop your application in the Model/ directory the AcmeStoreBundle bundle.

PDF brought to you by generated on October 26, 2012

Chapter 13: Databases and Propel | 143

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 13-7

1 $ php app/console propel:sql:build 2 $ 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 13-8

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

// src/Acme/StoreBundle/Controller/DefaultController.php // ... use Acme\StoreBundle\Model\Product; use Symfony\Component\HttpFoundation\Response;


public function createAction() { $product = new Product(); $product->setName('A Foo Bar'); $product->setPrice(19.99); $product->setDescription('Lorem ipsum dolor'); $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 13-9

PDF brought to you by generated on October 26, 2012

Chapter 13: Databases and Propel | 144

1 2 3 4 5 6 7 8 9 10 11 12 13 14

// ... use Acme\StoreBundle\Model\ProductQuery;


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:
Listing 13-10

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

// ... use Acme\StoreBundle\Model\ProductQuery;


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:
Listing 13-11

1 $product->delete();

Querying for Objects


Propel provides generated Query classes to run both basic and complex queries without any work:

PDF brought to you by generated on October 26, 2012

Chapter 13: Databases and Propel | 145

Listing 13-12

1 \Acme\StoreBundle\Model\ProductQuery::create()->findPk($id); 2 3 \Acme\StoreBundle\Model\ProductQuery::create() 4 ->filterByName('Foo') 5 ->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:
Listing 13-13

1 $products = \Acme\StoreBundle\Model\ProductQuery::create() 2 ->filterByPrice(array('min' => 19.99)) 3 ->orderByPrice() 4 ->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 13-14

1 2 3 4 5 6 7 8 9

// src/Acme/StoreBundle/Model/ProductQuery.php 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 13-15

1 \Acme\StoreBundle\Model\ProductQuery::create() 2 ->orderByName() 3 ->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 13-16

1 <database name="default" namespace="Acme\StoreBundle\Model" defaultIdMethod="native"> 2 <table name="product"> 3 <column name="id" type="integer" required="true" primaryKey="true" 4 autoIncrement="true" /> 5 <column name="name" type="varchar" primaryString="true" size="100" /> 6 <column name="price" type="decimal" /> 7 <column name="description" type="longvarchar" /> 8 9 <column name="category_id" type="integer" /> 10 <foreign-key foreignTable="category">

PDF brought to you by generated on October 26, 2012

Chapter 13: Databases and Propel | 146

11 <reference local="category_id" foreign="id" /> 12 </foreign-key> 13 </table> 14 15 <table name="category"> 16 <column name="id" type="integer" required="true" primaryKey="true" 17 autoIncrement="true" /> 18 <column name="name" type="varchar" primaryString="true" size="100" /> </table> </database>

Create the classes:


Listing 13-17

1 $ php app/console propel:model:build

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.
Listing 13-18

1 $ php app/console propel:migration:generate-diff 2 $ 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-19

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

// ... use Acme\StoreBundle\Model\Category; use Acme\StoreBundle\Model\Product; use Symfony\Component\HttpFoundation\Response;


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() ); } }

PDF brought to you by generated on October 26, 2012

Chapter 13: Databases and Propel | 147

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-20

1 2 3 4 5 6 7 8 9 10 11 12 13

// ... use Acme\StoreBundle\Model\ProductQuery;


public function showAction($id) { $product = ProductQuery::create() ->joinWithCategory() ->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 13-21

1 2 3 4 5 6 7 8 9 10 11

// src/Acme/StoreBundle/Model/Product.php // ...
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
2. http://www.propelorm.org/documentation/04-relationships.html

PDF brought to you by generated on October 26, 2012

Chapter 13: Databases and Propel | 148

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.

Commands
You should read the dedicated section for Propel commands in Symfony23.

3. http://www.propelorm.org/cookbook/symfony2/working-with-symfony2#the_commands

PDF brought to you by generated on October 26, 2012

Chapter 13: Databases and Propel | 149

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, though version 3.6.4 is needed to test the Symfony core code itself.

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 14-1

1 # specify the configuration directory on the command line 2 $ 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 generated on October 26, 2012

Chapter 14: Testing | 150

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:
Listing 14-2

1 2 3 4 5 6 7 8 9 10

// src/Acme/DemoBundle/Utility/Calculator.php namespace Acme\DemoBundle\Utility;


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:
Listing 14-3

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

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


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

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

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

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:
Listing 14-4

1 2 3 4 5 6 7 8

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

PDF brought to you by generated on October 26, 2012

Chapter 14: Testing | 151

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 14-5

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

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


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

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 14-6

1 <phpunit> 2 <!-- ... --> 3 <php> 4 <server name="KERNEL_DIR" value="/path/to/your/app/" /> 5 </php> 6 <!-- ... --> 7 </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 generated on October 26, 2012

Chapter 14: Testing | 152

Listing 14-7

1 $crawler = $client->request('GET', '/demo/hello/Fabien');

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:
Listing 14-8

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

Submitting a form is very similar; select a form button, optionally override some form values, and submit the corresponding form:
Listing 14-9

1 2 3 4 5 6 7 8

$form = $crawler->selectButton('submit')->form();

// 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:
Listing 14-10

1 // Assert that the response matches a given CSS selector. 2 $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:
Listing 14-11

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

3. http://api.symfony.com/2.1/Symfony/Component/DomCrawler/Crawler.html

PDF brought to you by generated on October 26, 2012

Chapter 14: Testing | 153

More about the request() method:


The full signature of the request() method is:
Listing 14-12

1 request( 2 $method, 3 $uri, 4 array $parameters = array(), 5 array $files = array(), 6 array $server = array(), 7 $content = null, 8 $changeHistory = true 9 )

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 14-13

1 $client->request( 2 'GET', 3 '/demo/hello/Fabien', 4 array(), 5 array(), 6 array( 7 'CONTENT_TYPE' => 'application/json', 8 'HTTP_REFERER' => '/foo/bar', 9 ) 10 );

4. http://php.net/manual/en/reserved.variables.server.php

PDF brought to you by generated on October 26, 2012

Chapter 14: Testing | 154

Useful Assertions
To get you started faster, here is a list of the most common and useful test assertions:
Listing 14-14

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

// Assert that there is more than one h2 tag with the class "subtitle" $this->assertGreaterThan(0, $crawler->filter('h2.subtitle')->count()); // Assert that there are exactly 4 h2 tags on the page $this->assertCount(4, $crawler->filter('h2')); // Assert that the "Content-Type" header is "application/json" $this->assertTrue($client->getResponse()->headers->contains('Content-Type', 'application/json')); // Assert that the response content matches a regexp. $this->assertRegExp('/foo/', $client->getResponse()->getContent()); // Assert that the response status code is 2xx $this->assertTrue($client->getResponse()->isSuccessful()); // Assert that the response status code is 404 $this->assertTrue($client->getResponse()->isNotFound()); // Assert a specific 200 status code $this->assertEquals(200, $client->getResponse()->getStatusCode()); // Assert that the response is a redirect to /demo/contact $this->assertTrue($client->getResponse()->isRedirect('/demo/contact')); // or simply check that the response is a redirect to any URL $this->assertTrue($client->getResponse()->isRedirect());

Working with the Test Client


The Test Client simulates an HTTP client like a browser and makes requests into your Symfony2 application:
Listing 14-15

1 $crawler = $client->request('GET', '/hello/Fabien');

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:
Listing 14-16

1 2 3 4 5

$link = $crawler->selectLink('Go elsewhere...')->link(); $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.

PDF brought to you by generated on October 26, 2012

Chapter 14: Testing | 155

You will learn more about the Link and Form objects in the Crawler section below.

The request method can also be used to simulate form submissions directly or perform more complex requests:
Listing 14-17

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35

// Directly submit a form (but using the Crawler is easier!) $client->request('POST', '/submit', array('name' => 'Fabien')); // Form submission with a file upload use Symfony\Component\HttpFoundation\File\UploadedFile;
$photo = new UploadedFile( '/path/to/photo.jpg', 'photo.jpg', 'image/jpeg', 123 ); // or $photo = array( 'tmp_name' => '/path/to/photo.jpg', 'name' => 'photo.jpg', 'type' => 'image/jpeg', 'size' => 123, 'error' => UPLOAD_ERR_OK ); $client->request( 'POST', '/submit', array('name' => 'Fabien'), array('photo' => $photo) );

// Perform a DELETE requests, and pass HTTP headers $client->request( 'DELETE', '/post/12', array(), array(), array('PHP_AUTH_USER' => 'username', 'PHP_AUTH_PW' => 'pa$$word') );

Last but not least, you can force each request to be executed in its own PHP process to avoid any sideeffects when working with several clients in the same script:
Listing 14-18

1 $client->insulate();

Browsing
The Client supports many operations that can be done in a real browser:
Listing 14-19

1 $client->back(); 2 $client->forward(); 3 $client->reload();

PDF brought to you by generated on October 26, 2012

Chapter 14: Testing | 156

4 5 // Clears all cookies and the history 6 $client->restart();

Accessing Internal Objects


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

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

You can also get the objects related to the latest request:
Listing 14-21

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

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

1 $container = $client->getContainer(); 2 $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:
Listing 14-23

1 $container = $client->getContainer();

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:
Listing 14-24

1 $profile = $client->getProfile();

For specific details on using the profiler inside a test, see the How to use the Profiler in a Functional Test cookbook entry.

PDF brought to you by generated on October 26, 2012

Chapter 14: Testing | 157

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:
Listing 14-25

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

If you want the client to automatically follow all redirects, you can force him with the followRedirects() method:
Listing 14-26

1 $client->followRedirects();

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 14-27

1 $newCrawler = $crawler->filter('input[type=submit]') 2 ->last() 3 ->parents() 4 ->first() 5 ;

Many other methods are also available: Method filter('h1.title') filterXpath('h1') eq(1) first() last() siblings() nextAll() previousAll() parents() children() reduce($lambda) Description Nodes that match the CSS selector Nodes that match the XPath expression Node for the specified index First node Last node Siblings All following siblings All preceding siblings Returns the parent nodes Returns children nodes 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:

PDF brought to you by generated on October 26, 2012

Chapter 14: Testing | 158

Listing 14-28

1 $crawler 2 ->filter('h1') 3 ->reduce(function ($node, $i) 4 { 5 if (!$node->getAttribute('class')) { 6 return false; 7 } 8 }) 9 ->first();

Use the count() function to get the number of nodes stored in a Crawler: count($crawler)

Extracting Information
The Crawler can extract information from the nodes:
Listing 14-29

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

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

Links
To select links, you can use the traversing methods above or the convenient selectLink() shortcut:
Listing 14-30

1 $crawler->selectLink('Click here');

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:
Listing 14-31

1 $link = $crawler->selectLink('Click here')->link(); 2 3 $client->click($link);

PDF brought to you by generated on October 26, 2012

Chapter 14: Testing | 159

Forms
Just like links, you select forms with the selectButton() method:
Listing 14-32

1 $buttonCrawlerNode = $crawler->selectButton('submit');

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:
Listing 14-33

1 $form = $buttonCrawlerNode->form();

When calling the form() method, you can also pass an array of field values that overrides the default ones:
Listing 14-34

1 $form = $buttonCrawlerNode->form(array( 2 'name' => 'Fabien', 3 'my_form[subject]' => 'Symfony rocks!', 4 ));

And if you want to simulate a specific HTTP method for the form, pass it as a second argument:
Listing 14-35

1 $form = $buttonCrawlerNode->form(array(), 'DELETE');

The Client can submit Form instances:


Listing 14-36

1 $client->submit($form);

The field values can also be passed as a second argument of the submit() method:
Listing 14-37

1 $client->submit($form, array( 2 'name' => 'Fabien', 3 'my_form[subject]' => 'Symfony rocks!', 4 ));

For more complex situations, use the Form instance as an array to set the value of each field individually:
Listing 14-38

1 // Change the value of a field 2 $form['name'] = 'Fabien'; 3 $form['my_form[subject]'] = 'Symfony rocks!';

There is also a nice API to manipulate the values of the fields according to their type:
Listing 14-39

PDF brought to you by generated on October 26, 2012

Chapter 14: Testing | 160

1 2 3 4 5 6 7 8

// Select an option or a radio $form['country']->select('France'); // Tick a checkbox $form['like_symfony']->tick(); // Upload a file $form['photo']->upload('/path/to/lucas.jpg');

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:
Listing 14-40

1 # app/config/config_test.yml 2 3 # ... 4 5 swiftmailer: 6 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:
Listing 14-41

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

If your application behaves according to some HTTP headers, pass them as the second argument of createClient():
Listing 14-42

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

You can also override HTTP headers on a per request basis:


Listing 14-43

1 $client->request('GET', '/', array(), array(), array( 2 'HTTP_HOST' => 'en.example.com',

PDF brought to you by generated on October 26, 2012

Chapter 14: Testing | 161

3 'HTTP_USER_AGENT' => 'MySuperBrowser/1.0', 4 ));

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:
Listing 14-44

1 <!-- hello/phpunit.xml.dist --> 2 <testsuites> 3 <testsuite name="Project Test Suite"> 4 <directory>../src/*/*Bundle/Tests</directory> 5 <directory>../src/Acme/Bundle/*Bundle/Tests</directory> 6 </testsuite> 7 </testsuites>

To include other directories in the code coverage, also edit the <filter> section:
Listing 14-45

1 <filter> 2 <whitelist> 3 <directory>../src</directory> 4 <exclude> 5 <directory>../src/*/*Bundle/Resources</directory> 6 <directory>../src/*/*Bundle/Tests</directory> 7 <directory>../src/Acme/Bundle/*Bundle/Resources</directory> 8 <directory>../src/Acme/Bundle/*Bundle/Tests</directory> 9 </exclude> 10 </whitelist> 11 </filter>

Learn more
The DomCrawler Component The CssSelector Component How to simulate HTTP Authentication in a Functional Test How to test the Interaction of several Clients

PDF brought to you by generated on October 26, 2012

Chapter 14: Testing | 162

How to use the Profiler in a Functional Test How to customize the Bootstrap Process before running Tests

PDF brought to you by generated on October 26, 2012

Chapter 14: Testing | 163

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-oldPHP object that you need to use somewhere in your application:
Listing 15-1

1 2 3 4 5 6 7

// src/Acme/BlogBundle/Entity/Author.php namespace Acme\BlogBundle\Entity;


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:
Listing 15-2

1 # src/Acme/BlogBundle/Resources/config/validation.yml 2 Acme\BlogBundle\Entity\Author: 3 properties:

1. https://github.com/symfony/Validator 2. http://jcp.org/en/jsr/detail?id=303

PDF brought to you by generated on October 26, 2012

Chapter 15: Validation | 164

4 5

name: - NotBlank: ~

Protected and private properties can also be validated, as well as "getter" methods (see validatorconstraint-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 15-3

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

// ... use Symfony\Component\HttpFoundation\Response; use Acme\BlogBundle\Entity\Author;


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

If the $name property is empty, you will see the following error message:
Listing 15-4

1 Acme\BlogBundle\Author.name: 2 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 15-5

1 if (count($errors) > 0) { 2 return $this->render('AcmeBlogBundle:Author:validate.html.twig', array( 3 'errors' => $errors, 4 ));

3. http://api.symfony.com/2.1/Symfony/Component/Validator/Validator.html

PDF brought to you by generated on October 26, 2012

Chapter 15: Validation | 165

5 } else { 6 // ... 7 }

Inside the template, you can output the list of errors exactly as needed:
Listing 15-6

1 2 3 4 5 6 7

{# 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:
Listing 15-7

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

// ... use Acme\BlogBundle\Entity\Author; use Acme\BlogBundle\Form\AuthorType; use Symfony\Component\HttpFoundation\Request;


public function updateAction(Request $request) { $author = new Author(); $form = $this->createForm(new AuthorType(), $author); if ($request->isMethod('POST')) { $form->bind($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(), )); }

4. http://api.symfony.com/2.1/Symfony/Component/Validator/ConstraintViolation.html

PDF brought to you by generated on October 26, 2012

Chapter 15: Validation | 166

This example uses an AuthorType form class, which is not shown here.

For more information, see the Forms chapter.

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 15-8

1 # app/config/config.yml 2 framework: 3 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 Length Url Regex Ip
Chapter 15: Validation | 167

PDF brought to you by generated on October 26, 2012

Number Constraints
Max Min Range

Date Constraints
Date DateTime Time

Collection Constraints
Choice Collection Count UniqueEntity Language Locale Country

File Constraints
File Image

Other Constraints
Callback All UserPassword 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":
Listing 15-9

1 # src/Acme/BlogBundle/Resources/config/validation.yml 2 Acme\BlogBundle\Entity\Author: 3 properties: 4 gender: 5 - 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.
Listing 15-10

PDF brought to you by generated on October 26, 2012

Chapter 15: Validation | 168

1 # src/Acme/BlogBundle/Resources/config/validation.yml 2 Acme\BlogBundle\Entity\Author: 3 properties: 4 gender: 5 - 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).

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 15-11

1 # src/Acme/BlogBundle/Resources/config/validation.yml 2 Acme\BlogBundle\Entity\Author: 3 properties: 4 firstName: 5 - NotBlank: ~ 6 - 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 15-12

1 # src/Acme/BlogBundle/Resources/config/validation.yml 2 Acme\BlogBundle\Entity\Author: 3 getters: 4 passwordLegal: 5 - "True": { message: "The password cannot match your first name" }

PDF brought to you by generated on October 26, 2012

Chapter 15: Validation | 169

Now, create the isPasswordLegal() method, and include the logic you need:
Listing 15-13

1 public function isPasswordLegal() 2 { 3 return ($this->firstName != $this->password); 4 }

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.

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:
Listing 15-14

1 # src/Acme/BlogBundle/Resources/config/validation.yml 2 Acme\BlogBundle\Entity\User: 3 properties: 4 email: 5 - Email: { groups: [registration] } 6 password: 7 - NotBlank: { groups: [registration] } 8 - MinLength: { limit: 7, groups: [registration] } 9 city: 10 - 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:
Listing 15-15

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

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.

PDF brought to you by generated on October 26, 2012

Chapter 15: Validation | 170

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:
Listing 15-16

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

// add this to the top of your class use Symfony\Component\Validator\Constraints\Email;


public function addEmailAction($email) { $emailConstraint = new Email(); // all constraint "options" can be set this way $emailConstraint->message = 'Invalid email address';

// use the validator to validate the value $errorList = $this->get('validator')->validateValue($email, $emailConstraint);


if (count($errorList) == 0) { // this IS a valid email address, do something } else { // this is *not* a valid email address $errorMessage = $errorList[0]->getMessage()

// ... do something with the error


}

// ...
}

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.1/Symfony/Component/Validator/ConstraintViolationList.html 6. http://api.symfony.com/2.1/Symfony/Component/Validator/ConstraintViolation.html

PDF brought to you by generated on October 26, 2012

Chapter 15: Validation | 171

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:
Listing 16-1

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

// src/Acme/TaskBundle/Entity/Task.php namespace Acme\TaskBundle\Entity;


class Task { protected $task; protected $dueDate; public function getTask() { return $this->task; } public function setTask($task) {

1. https://github.com/symfony/Form

PDF brought to you by generated on October 26, 2012

Chapter 16: Forms | 172

16 17 18 19 20 21 22 23 24 25 26 27 }

$this->task = $task; } 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 16-2

1 $ php app/console generate:bundle --namespace=Acme/TaskBundle

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 16-3

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

// src/Acme/TaskBundle/Controller/DefaultController.php namespace Acme\TaskBundle\Controller;


use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Acme\TaskBundle\Entity\Task; use Symfony\Component\HttpFoundation\Request; class DefaultController extends Controller { public function newAction(Request $request) { // create a task and give it some dummy data for this example $task = 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 generated on October 26, 2012

Chapter 16: Forms | 173

25 26 }

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:
Listing 16-4

1 {# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #} 2 <form action="{{ path('task_new') }}" method="post" {{ form_enctype(form) }}> 3 {{ form_widget(form) }} 4 5 <input type="submit" /> 6 </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 generated on October 26, 2012

Chapter 16: Forms | 174

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

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

// ...
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->isMethod('POST')) { $form->bind($request); if ($form->isValid()) { // perform some action, such as saving the task to the database return $this->redirect($this->generateUrl('task_success')); } }

// ...
}

New in version 2.1: The bind method was made more flexible in Symfony 2.1. It now accepts the raw client data (same as before) or a Symfony Request object. This is preferred over the deprecated bindRequest method.

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 bind() method.
As soon as bind() 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;

PDF brought to you by generated on October 26, 2012

Chapter 16: Forms | 175

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).
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.
Listing 16-6

1 # Acme/TaskBundle/Resources/config/validation.yml 2 Acme\TaskBundle\Entity\Task: 3 properties: 4 task: 5 - NotBlank: ~ 6 dueDate: 7 - NotBlank: ~ 8 - 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

PDF brought to you by generated on October 26, 2012

Chapter 16: Forms | 176

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:
Listing 16-7

1 $form = $this->createFormBuilder($users, array( 2 'validation_groups' => array('registration'), 3 ))->add(...);

If you're creating form classes (a good practice), then you'll need to add the following to the setDefaultOptions() method:
Listing 16-8

1 2 3 4 5 6 7 8

use Symfony\Component\OptionsResolver\OptionsResolverInterface; public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'validation_groups' => array('registration') )); }

In both of these cases, only the registration validation group will be used to validate the underlying object.

Groups based on Submitted Data


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

If you need some advanced logic to determine the validation groups (e.g. based on submitted data), you can set the validation_groups option to an array callback, or a Closure:
Listing 16-9

1 2 3 4 5 6 7 8

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

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

1 use Symfony\Component\Form\FormInterface; 2 use Symfony\Component\OptionsResolver\OptionsResolverInterface; 3 4 public function setDefaultOptions(OptionsResolverInterface $resolver)

PDF brought to you by generated on October 26, 2012

Chapter 16: Forms | 177

5 { 6 7 8 9 10 11 12 13 14 15 16 }

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

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 radio
PDF brought to you by generated on October 26, 2012 Chapter 16: Forms | 178

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):
Listing 16-11

1 ->add('dueDate', 'date', array('widget' => 'single_text'))

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 generated on October 26, 2012

Chapter 16: Forms | 179

The label option


The label for the form field can be set using the label option, which can be applied to any field:
Listing 16-12

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

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 16-13

1 public function newAction() 2 { 3 $task = new Task(); 4 5 $form = $this->createFormBuilder($task) 6 ->add('task') 7 ->add('dueDate', null, array('widget' => 'single_text')) 8 ->getForm(); 9 }

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. Assert\MaxLength). 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 on 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 generated on October 26, 2012

Chapter 16: Forms | 180

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:
Listing 16-14

1 ->add('task', null, array('max_length' => 4))

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:
Listing 16-15

1 {# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #} 2 <form action="{{ path('task_new') }}" method="post" {{ form_enctype(form) }}> 3 {{ form_errors(form) }} 4 5 {{ form_row(form.task) }} 6 {{ form_row(form.dueDate) }} 7 8 {{ form_rest(form) }} 9 10 <input type="submit" /> 11 </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:
Listing 16-16

1 {{ form.vars.value.task }}

PDF brought to you by generated on October 26, 2012

Chapter 16: Forms | 181

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 16-17

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

{{ form_errors(form) }} <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 16-18

1 {{ form_label(form.task, 'Task Description') }}

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 16-19

1 {{ form_widget(form.task, { 'attr': {'class': 'task_field'} }) }}

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 16-20

1 {{ form.task.vars.id }}

To get the value used for the form field's name attribute you need to use the full_name value:
Listing 16-21

1 {{ form.task.vars.full_name }}

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:

PDF brought to you by generated on October 26, 2012

Chapter 16: Forms | 182

Listing 16-22

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

// src/Acme/TaskBundle/Form/Type/TaskType.php namespace Acme\TaskBundle\Form\Type;


use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; class TaskType extends AbstractType { public function buildForm(FormBuilderInterface $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:
Listing 16-23

1 2 3 4 5 6 7 8 9 10 11 12

// src/Acme/TaskBundle/Controller/DefaultController.php // add this new use statement at the top of the class use Acme\TaskBundle\Form\Type\TaskType;
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.

PDF brought to you by generated on October 26, 2012

Chapter 16: Forms | 183

Setting the data_class


Every form needs to know the name of the class that holds the underlying data (e.g. Acme\TaskBundle\Entity\Task). 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:
Listing 16-24

1 2 3 4 5 6 7 8

use Symfony\Component\OptionsResolver\OptionsResolverInterface; public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'data_class' => 'Acme\TaskBundle\Entity\Task', )); }

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. 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 mapped option to false:
Listing 16-25

1 2 3 4 5 6 7

use Symfony\Component\Form\FormBuilderInterface; public function buildForm(FormBuilderInterface $builder, array $options) { $builder->add('task'); $builder->add('dueDate', null, array('mapped' => 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 16-26

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

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 16-27

1 if ($form->isValid()) { 2 $em = $this->getDoctrine()->getManager(); 3 $em->persist($task); 4 $em->flush(); 5

PDF brought to you by generated on October 26, 2012

Chapter 16: Forms | 184

6 7 }

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 16-28

1 $task = $form->getData();

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:
Listing 16-29

1 2 3 4 5 6 7 8 9 10 11 12

// src/Acme/TaskBundle/Entity/Category.php namespace Acme\TaskBundle\Entity;


use Symfony\Component\Validator\Constraints as Assert; class Category { /** * @Assert\NotBlank() */ public $name; }

Next, add a new category property to the Task class:


Listing 16-30

1 // ... 2 3 class Task 4 { 5 // ... 6 7 /** 8 * @Assert\Type(type="Acme\TaskBundle\Entity\Category") 9 */ 10 protected $category; 11 12 // ... 13 14 public function getCategory() 15 {

PDF brought to you by generated on October 26, 2012

Chapter 16: Forms | 185

16 17 18 19 20 21 22 23 }

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:
Listing 16-31

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

// src/Acme/TaskBundle/Form/Type/CategoryType.php namespace Acme\TaskBundle\Form\Type;


use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class CategoryType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder->add('name'); } public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'data_class' => 'Acme\TaskBundle\Entity\Category', )); } 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 16-32

1 2 3 4 5 6 7 8

use Symfony\Component\Form\FormBuilderInterface; public function buildForm(FormBuilderInterface $builder, array $options) { // ... $builder->add('category', new CategoryType()); }

The fields from CategoryType can now be rendered alongside those from the TaskType class. To activate validation on CategoryType, add the cascade_validation option to TaskType:
Listing 16-33

PDF brought to you by generated on October 26, 2012

Chapter 16: Forms | 186

1 public function setDefaultOptions(OptionsResolverInterface $resolver) 2 { 3 $resolver->setDefaults(array( 4 'data_class' => 'Acme\TaskBundle\Entity\Task', 5 'cascade_validation' => true, 6 )); 7 }

Render the Category fields in the same way as the original Task fields:
Listing 16-34

1 2 3 4 5 6 7 8 9

{# ... #}
<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. 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:
Listing 16-35

1 {# src/Acme/TaskBundle/Resources/views/Form/fields.html.twig #} 2 {% block form_row %} 3 {% spaceless %} 4 <div class="form_row"> 5 {{ form_label(form) }}

PDF brought to you by generated on October 26, 2012

Chapter 16: Forms | 187

6 {{ form_errors(form) }} 7 {{ form_widget(form) }} 8 </div> 9 {% endspaceless %} 10 {% endblock form_row %}

The form_row form fragment is used when rendering most fields via the form_row function. To tell the form component to use your new form_row fragment defined above, add the following to the top of the template that renders the form:
Listing 16-36

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

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 form_row block from your custom theme (instead of the default form_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.
New in version 2.1: An alternate Twig syntax for form_theme has been introduced in 2.1. It accepts any valid Twig expression (the most noticeable difference is using an array when using multiple themes).

Listing 16-37

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

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.

PDF brought to you by generated on October 26, 2012

Chapter 16: Forms | 188

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). 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: form_row - used by form_row to render most fields; textarea_widget - used by form_widget to render a textarea field type; form_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 widget errors row (e.g. form_label) | renders the field's label (e.g. form_widget) | renders the field's HTML representation (e.g. form_errors) | renders the field's errors (e.g. form_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 form_errors fragment. When Symfony renders the errors for a textarea type, it looks first for a textarea_errors fragment before falling back to the form_errors fragment. Each field type has a parent type (the parent type of textarea is text, its parent is form), 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 form_errors fragment, rename it to textarea_errors and customize it. To override the default error rendering for all fields, copy and customize the form_errors fragment directly.
The "parent" type of each field type is available in the form type reference for each field type.

2. https://github.com/symfony/symfony/blob/2.1/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 generated on October 26, 2012

Chapter 16: Forms | 189

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:
Listing 16-38

1 # app/config/config.yml 2 twig: 3 form: 4 resources: 5 - 'AcmeTaskBundle:Form:fields.html.twig' 6 # ...

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:
Listing 16-39

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

{% extends '::base.html.twig' %}

{# import "_self" as the form theme #} {% form_theme form _self %} {# make the form fragment customization #} {% block form_row %} {# custom field row output #} {% endblock form_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:
Listing 16-40

PDF brought to you by generated on October 26, 2012

Chapter 16: Forms | 190

1 # app/config/config.yml 2 framework: 3 templating: 4 form: 5 resources: 6 - 'AcmeTaskBundle:Form' 7 # ...

Any fragments inside the Acme/TaskBundle/Resources/views/Form directory are now used globally to define form output.

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 16-41

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

use Symfony\Component\OptionsResolver\OptionsResolverInterface; class TaskType extends AbstractType { // ... public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'data_class' => 'Acme\TaskBundle\Entity\Task', '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.

5. http://en.wikipedia.org/wiki/Cross-site_request_forgery

PDF brought to you by generated on October 26, 2012

Chapter 16: Forms | 191

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 16-42

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

// make sure you've imported the Request namespace above the class use Symfony\Component\HttpFoundation\Request // ...
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->isMethod('POST')) { $form->bind($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:
Listing 16-43

1 $this->get('request')->request->get('name');

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.

PDF brought to you by generated on October 26, 2012

Chapter 16: Forms | 192

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:
Listing 16-44

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

// import the namespaces above your controller class use Symfony\Component\Validator\Constraints\Email; use Symfony\Component\Validator\Constraints\MinLength; use Symfony\Component\Validator\Constraints\Collection;
$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( 'constraints' => $collectionConstraint, ))->add('email', 'email') // ... ;

Now, when you call $form->bind($request), the constraints setup here are run against your form's data. If you're using a form class, override the setDefaultOptions() method to specify the option:
Listing 16-45

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

namespace Acme\TaskBundle\Form\Type; use use use use use use Symfony\Component\Form\AbstractType; Symfony\Component\Form\FormBuilder; Symfony\Component\OptionsResolver\OptionsResolverInterface; Symfony\Component\Validator\Constraints\Email; Symfony\Component\Validator\Constraints\MinLength; Symfony\Component\Validator\Constraints\Collection;

class ContactType extends AbstractType { // ... public function setDefaultOptions(OptionsResolverInterface $resolver) { $collectionConstraint = new Collection(array( 'name' => new MinLength(5), 'email' => new Email(array('message' => 'Invalid email address')), )); $resolver->setDefaults(array( 'constraints' => $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.

PDF brought to you by generated on October 26, 2012

Chapter 16: Forms | 193

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 How to use Data Transformers

PDF brought to you by generated on October 26, 2012

Chapter 16: Forms | 194

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 generated on October 26, 2012

Chapter 17: Security | 195

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 17-1

1 # app/config/security.yml 2 security: 3 firewalls: 4 secured_area: 5 pattern: ^/ 6 anonymous: ~ 7 http_basic: 8 realm: "Secured Demo Area" 9 10 access_control: 11 - { path: ^/admin, roles: ROLE_ADMIN } 12 13 providers: 14 in_memory: 15 memory: 16 users: 17 ryan: { password: ryanpass, roles: 'ROLE_USER' } 18 admin: { password: kitten, roles: 'ROLE_ADMIN' } 19 20 encoders: 21 Symfony\Component\Security\Core\User\User: 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.

1. https://github.com/symfony/Security

PDF brought to you by generated on October 26, 2012

Chapter 17: Security | 196

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. 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 generated on October 26, 2012

Chapter 17: Security | 197

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 generated on October 26, 2012

Chapter 17: Security | 198

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 generated on October 26, 2012

Chapter 17: Security | 199

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 generated on October 26, 2012

Chapter 17: Security | 200

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:
Listing 17-2

1 # app/config/security.yml 2 security: 3 firewalls: 4 secured_area: 5 pattern: ^/ 6 anonymous: ~ 7 form_login: 8 login_path: 9 check_path:

/login /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:
Listing 17-3

1 form_login: ~

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):
Listing 17-4

1 # app/config/routing.yml 2 login: 3 pattern: /login 4 defaults: { _controller: AcmeSecurityBundle:Security:login } 5 login_check: 6 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.

New in version 2.1: As of Symfony 2.1, you must have routes configured for your login_path (e.g. /login), check_path (e.g. /login_check) and logout (e.g. /logout - see Logging Out) URLs.

PDF brought to you by generated on October 26, 2012

Chapter 17: Security | 201

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:
Listing 17-5

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

// src/Acme/SecurityBundle/Controller/SecurityController.php; namespace Acme\SecurityBundle\Controller;


use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\Security\Core\SecurityContext; class SecurityController extends Controller { public function loginAction() { $request = $this->getRequest(); $session = $request->getSession();

// get the login error if there is one if ($request->attributes->has(SecurityContext::AUTHENTICATION_ERROR)) { $error = $request->attributes->get(SecurityContext::AUTHENTICATION_ERROR); } else { $error = $session->get(SecurityContext::AUTHENTICATION_ERROR); $session->remove(SecurityContext::AUTHENTICATION_ERROR); }
return $this->render('AcmeSecurityBundle:Security:login.html.twig', array( // last username entered by the user 'last_username' => $session->get(SecurityContext::LAST_USERNAME), 'error' => $error, )); } }

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 17-6

1 2 3 4 5 6 7 8 9 10 11 12 13 14

{# src/Acme/SecurityBundle/Resources/views/Security/login.html.twig #} {% if error %} <div>{{ error.message }}</div> {% endif %}


<form action="{{ path('login_check') }}" method="post"> <label for="username">Username:</label> <input type="text" id="username" name="_username" value="{{ last_username }}" /> <label for="password">Password:</label> <input type="password" id="password" name="_password" />

{# If you want to control the URL the user is redirected to on success (more details

PDF brought to you by generated on October 26, 2012

Chapter 17: Security | 202

15 below) 16 <input type="hidden" name="_target_path" value="/account" /> 17 #} 18 19 <button type="submit">login</button> </form>

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.1/Symfony/Component/Security/Core/Exception/AuthenticationException.html

PDF brought to you by generated on October 26, 2012

Chapter 17: Security | 203

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 17-7

1 access_control: 2 - { path: ^/, roles: ROLE_ADMIN }

Removing the access control on the /login URL fixes the problem:
Listing 17-8

1 access_control: 2 - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY } 3 - { 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 17-9

1 firewalls: 2 login_firewall: 3 pattern: 4 anonymous: 5 secured_area: 6 pattern: 7 form_login:

^/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.
PDF brought to you by generated on October 26, 2012 Chapter 17: Security | 204

The process of authorization has two different sides: 1. The user has a specific set of roles; 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.
Listing 17-10

1 # app/config/security.yml 2 security: 3 # ... 4 access_control: 5 - { path: ^/admin/users, roles: ROLE_SUPER_ADMIN } 6 - { 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:
Listing 17-11

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

3. http://api.symfony.com/2.1/Symfony/Component/Security/Core/Exception/AccessDeniedException.html

PDF brought to you by generated on October 26, 2012

Chapter 17: Security | 205

Securing by Channel
Much like securing based on IP, requiring the use of SSL is as simple as adding a new access_control entry:
Listing 17-12

1 # app/config/security.yml 2 security: 3 # ... 4 access_control: 5 - { 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 17-13

1 2 3 4 5 6 7 8 9 10 11

// ... use Symfony\Component\Security\Core\Exception\AccessDeniedException;


public function helloAction($name) { if (false === $this->get('security.context')->isGranted('ROLE_ADMIN')) { throw new AccessDeniedException(); }

// ...
}

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

1 2 3 4 5 6 7 8 9 10

// ... use JMS\SecurityExtraBundle\Annotation\Secure; /** * @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.
4. https://github.com/schmittjoh/JMSSecurityExtraBundle

PDF brought to you by generated on October 26, 2012

Chapter 17: Security | 206

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. 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: How to use 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.
Listing 17-15

1 # app/config/security.yml 2 security: 3 # ... 4 providers: 5 default_provider: 6 memory: 7 users: 8 ryan: { password: ryanpass, roles: 'ROLE_USER' } 9 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.

5. http://api.symfony.com/2.1/Symfony/Component/Security/Core/User/User.html

PDF brought to you by generated on October 26, 2012

Chapter 17: Security | 207

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:
Listing 17-16

1 users: 2 - { name: 77, password: pass, roles: 'ROLE_USER' } 3 - { name: user-name, password: pass, roles: 'ROLE_USER' }

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 17-17

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

// src/Acme/UserBundle/Entity/User.php namespace Acme\UserBundle\Entity;


use Symfony\Component\Security\Core\User\UserInterface; use Doctrine\ORM\Mapping as ORM;

/** * @ORM\Entity */ class User implements UserInterface { /** * @ORM\Column(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.
New in version 2.1: In Symfony 2.1, the equals method was removed from UserInterface. If you need to override the default implementation of comparison logic, implement the new EquatableInterface7 interface.

The user object will be serialized and saved in the session during requests, therefore it is recommended that you implement the Serializable interface8 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:

6. http://api.symfony.com/2.1/Symfony/Component/Security/Core/User/UserInterface.html 7. http://api.symfony.com/2.1/Symfony/Component/Security/Core/User/EquatableInterface.html 8. http://php.net/manual/en/class.serializable.php

PDF brought to you by generated on October 26, 2012

Chapter 17: Security | 208

Listing 17-18

1 # app/config/security.yml 2 security: 3 providers: 4 main: 5 entity: { class: Acme\UserBundle\Entity\User, 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.

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:
Listing 17-19

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

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

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.com9 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 Acme\UserBundle\Entity\User (like in the above example). First, configure the encoder for that user:
Listing 17-20

9. http://www.functions-online.com/sha1.html

PDF brought to you by generated on October 26, 2012

Chapter 17: Security | 209

1 # app/config/security.yml 2 security: 3 # ... 4 5 encoders: 6 Acme\UserBundle\Entity\User: 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:
Listing 17-21

1 2 3 4 5 6

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

Retrieving the User Object


After authentication, the User object of the current user can be accessed via the security.context service. From inside a controller, this will look like:
Listing 17-22

1 public function indexAction() 2 { 3 $user = $this->get('security.context')->getToken()->getUser(); 4 }

In a controller this can be shortcut to:


Listing 17-23

1 public function indexAction() 2 { 3 $user = $this->getUser(); 4 }

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()10 method:
Listing 17-24

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

10. http://api.symfony.com/2.1/Symfony/Bundle/FrameworkBundle/Templating/GlobalVariables.html#getUser()

PDF brought to you by generated on October 26, 2012

Chapter 17: Security | 210

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 17-25

1 # app/config/security.yml 2 security: 3 providers: 4 chain_provider: 5 chain: 6 providers: [in_memory, user_db] 7 in_memory: 8 memory: 9 users: 10 foo: { password: test } 11 user_db: 12 entity: { class: Acme\UserBundle\Entity\User, 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 17-26

1 # app/config/security.yml 2 security: 3 providers: 4 main_provider: 5 memory: 6 users: 7 foo: { password: test } 8 entity: 9 class: Acme\UserBundle\Entity\User, 10 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:
Listing 17-27

1 # app/config/security.yml 2 security: 3 firewalls: 4 secured_area: 5 # ... 6 provider: user_db 7 http_basic: 8 realm: "Secured Demo Area" 9 provider: in_memory 10 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.
PDF brought to you by generated on October 26, 2012 Chapter 17: Security | 211

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:
Listing 17-28

1 # app/config/security.yml 2 security: 3 role_hierarchy: 4 ROLE_ADMIN: ROLE_USER 5 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-29

1 # app/config/security.yml 2 security: 3 firewalls: 4 secured_area: 5 # ... 6 logout: 7 path: /logout 8 target: / 9 # ...

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 17-30

1 logout: ~

PDF brought to you by generated on October 26, 2012

Chapter 17: Security | 212

Note that you will not need to implement a controller for the /logout URL as the firewall takes care of everything. You do, however, need to create a route so that you can use it to generate the URL:
As of Symfony 2.1, you must have a route that corresponds to your logout path. Without this route, logging out will not work.

Listing 17-31

1 # app/config/routing.yml 2 logout: 3 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 17-32

1 {% if is_granted('ROLE_ADMIN') %} 2 <a href="...">Delete</a> 3 {% 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 17-33

1 public function indexAction() 2 { 3 // show different content to admin users 4 if ($this->get('security.context')->isGranted('ROLE_ADMIN')) { 5 // Load admin content here 6 } 7 // load other regular content here 8 }

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.

PDF brought to you by generated on October 26, 2012

Chapter 17: Security | 213

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:
Listing 17-34

1 # app/config/security.yml 2 security: 3 firewalls: 4 main: 5 # ... 6 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=thomas11 To switch back to the original user, use the special _exit username: http://example.com/somewhere?_switch_user=_exit12 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:
Listing 17-35

1 # app/config/security.yml 2 security: 3 firewalls: 4 main: 5 // ... 6 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):
Listing 17-36

1 # app/config/security.yml 2 security: 3 firewalls: 4 main: 5 http_basic: ~ 6 stateless: true

11. http://example.com/somewhere?_switch_user=thomas 12. http://example.com/somewhere?_switch_user=_exit

PDF brought to you by generated on October 26, 2012

Chapter 17: Security | 214

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 generated on October 26, 2012

Chapter 17: Security | 215

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 generated on October 26, 2012

Chapter 18: HTTP Cache | 216

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 generated on October 26, 2012

Chapter 18: HTTP Cache | 217

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:
Listing 18-1

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

// web/app.php require_once __DIR__.'/../app/bootstrap.php.cache'; require_once __DIR__.'/../app/AppKernel.php'; require_once __DIR__.'/../app/AppCache.php';


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

The 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:
Listing 18-2

1 error_log($kernel->getLog());

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:
Listing 18-3

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

// app/AppCache.php use Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache;


class AppCache extends HttpCache { protected function getOptions() { return array( 'debug' 'default_ttl' 'private_headers' 'allow_reload' 'allow_revalidate' 'stale_while_revalidate' 'stale_if_error' ); } }

=> => => => => => =>

false, 0, array('Authorization', 'Cookie'), false, false, 2, 60,

PDF brought to you by generated on October 26, 2012

Chapter 18: HTTP Cache | 218

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 CacheControl directive. (default: Authorization and Cookie); allow_reload: Specifies whether the client can force a cache reload by including a CacheControl "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 generated on October 26, 2012

Chapter 18: HTTP Cache | 219

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:
Listing 18-4

1 2 3 4 5 6 7 8 9 10 11 12

$response = new Response();

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

Public vs Private Responses


Both gateway and proxy caches are considered "shared" caches as the cached content is shared by more than one user. If a user-specific response were ever mistakenly stored by a shared cache, it might be
PDF brought to you by generated on October 26, 2012 Chapter 18: HTTP Cache | 220

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), CacheControl 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 smaxage 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 generated on October 26, 2012

Chapter 18: HTTP Cache | 221

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:
Listing 18-5

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

The resulting HTTP header will look like this:


Listing 18-6

1 Expires: Thu, 01 Mar 2011 16:00:00 GMT

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
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 generated on October 26, 2012

Chapter 18: HTTP Cache | 222

header making the lifetime calculation vulnerable to clock skew. Another limitation of the Expires 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 18-7

1 2 3 4 5 6

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

The Cache-Control header would take on the following format (it may have additional directives):
Listing 18-8

1 Cache-Control: max-age=600, s-maxage=600

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:

PDF brought to you by generated on October 26, 2012

Chapter 18: HTTP Cache | 223

Listing 18-9

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

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:
Listing 18-10

1 public function showAction($articleSlug) 2 { 3 // ... 4 5 $articleDate = new \DateTime($article->getUpdatedAt()); 6 $authorDate = new \DateTime($author->getUpdatedAt()); 7 8 $date = $authorDate > $articleDate ? $authorDate : $articleDate; 9 10 $response->setLastModified($date); 11 // Set response as public. Otherwise it will be private by default. 12 $response->setPublic(); 13 14 if ($response->isNotModified($this->getRequest())) { 15 return $response; 16 } 17 18 // do more work to populate the response will the full content 19 20 return $response; 21 }

10. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Response.html#setETag()

PDF brought to you by generated on October 26, 2012

Chapter 18: HTTP Cache | 224

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.

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 18-11

1 public function showAction($articleSlug) 2 { 3 // Get the minimum information to compute 4 // the ETag or the Last-Modified value 5 // (based on the Request, data is retrieved from 6 // a database or a key-value store for instance) 7 $article = ...; 8 9 // create a Response with a ETag and/or a Last-Modified header 10 $response = new Response(); 11 $response->setETag($article->computeETag()); 12 $response->setLastModified($article->getPublishedAt()); 13 14 // Set response as public. Otherwise it will be private by default. 15 $response->setPublic(); 16 17 // Check that the Response is not modified for the given Request 18 if ($response->isNotModified($this->getRequest())) { 19 // return the 304 Response immediately 20 return $response; 21 } else { 22 // do more work here - like retrieving more data 23 $comments = ...; 24 25 // or render a template with the $response you've already started 26 return $this->render( 27 'MyBundle:MyController:article.html.twig', 28 array('article' => $article, 'comments' => $comments), 29 $response 30 ); 31 } 32 }

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).

11. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Response.html#setNotModified()

PDF brought to you by generated on October 26, 2012

Chapter 18: HTTP Cache | 225

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 18-12

1 Vary: Accept-Encoding, User-Agent

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:
Listing 18-13

1 2 3 4 5

// set one vary header $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:
Listing 18-14

1 2 3 4 5

// Marks the Response stale $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:
Listing 18-15

1 // Set cache settings in one call 2 $response->setCache(array( 3 'etag' => $etag,

PDF brought to you by generated on October 26, 2012

Chapter 18: HTTP Cache | 226

4 5 6 7 8 9 ));

'last_modified' 'max_age' 's_maxage' 'public' // 'private'

=> => => => =>

$date, 10, 10, true, 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:
Listing 18-16

1 <!doctype html> 2 <html> 3 <body> 4 ... some content 5 6 <!-- Embed the content of another page here --> 7 <esi:include src="http://..." /> 8 9 ... more content 10 </body> 11 </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 18-17

12. http://www.w3.org/TR/esi-lang

PDF brought to you by generated on October 26, 2012

Chapter 18: HTTP Cache | 227

1 # app/config/config.yml 2 framework: 3 # ... 4 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 18-18

1 public function indexAction() 2 { 3 $response = $this->render('MyBundle:MyController:index.html.twig'); 4 // set the shared max age - the also marks the response as public 5 $response->setSharedMaxAge(600); 6 7 return $response; 8 }

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 18-19

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

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. 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.
Listing 18-20

1 public function newsAction() 2 { 3 // ... 4 5 $response->setSharedMaxAge(60); 6 }

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

PDF brought to you by generated on October 26, 2012

Chapter 18: HTTP Cache | 228

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:
Listing 18-21

1 # app/config/routing.yml 2 _internal: 3 resource: "@FrameworkBundle/Resources/config/routing/internal.xml" 4 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.

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 18-22

1 // app/AppCache.php 2 3 use Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache; 4

PDF brought to you by generated on October 26, 2012

Chapter 18: HTTP Cache | 229

5 class AppCache extends HttpCache 6 { 7 protected function invalidate(Request $request) 8 { 9 if ('PURGE' !== $request->getMethod()) { 10 return parent::invalidate($request); 11 } 12 13 $response = new Response(); 14 if (!$this->getStore()->purge($request->getUri())) { 15 $response->setStatusCode(404, 'Not purged'); 16 } else { 17 $response->setStatusCode(200, 'Purged'); 18 } 19 20 return $response; 21 } 22 }

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.

Learn more from the Cookbook


How to use Varnish to speed up my Website

PDF brought to you by generated on October 26, 2012

Chapter 18: HTTP Cache | 230

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 19-1

1 2 3 4 5

// text will *always* print out in English 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 for the request and optionally on the user's entire session.

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 generated on October 26, 2012

Chapter 19: Translations | 231

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:
Listing 19-2

1 # app/config/config.yml 2 framework: 3 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 on the request. This is typically set via a _locale attribute on your routes (see The Locale and the URL).

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:
Listing 19-3

1 public function indexAction() 2 { 3 $t = $this->get('translator')->trans('Symfony2 is great'); 4 5 return new Response($t); 6 }

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:
Listing 19-4

1 2 3 4 5 6 7 8 9 10 11 12

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

4. http://api.symfony.com/2.1/Symfony/Component/Translation/Translator.html 5. http://api.symfony.com/2.1/Symfony/Component/Translation/Translator.html#trans()

PDF brought to you by generated on October 26, 2012

Chapter 19: Translations | 232

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 on the request (or stored as _locale on 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 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 19-5

1 public function indexAction($name) 2 { 3 $t = $this->get('translator')->trans('Hello '.$name); 4 5 return new Response($t); 6 }

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 19-6

1 public function indexAction($name) 2 { 3 $t = $this->get('translator')->trans('Hello %name%', array('%name%' => $name)); 4 5 new Response($t); 6 }

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 19-7

1 <!-- messages.fr.xliff --> 2 <?xml version="1.0"?> 3 <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> 4 <file source-language="en" datatype="plaintext" original="file.ext"> 5 <body> 6 <trans-unit id="1"> 7 <source>Hello %name%</source> 8 <target>Bonjour %name%</target> 9 </trans-unit> 10 </body>

PDF brought to you by generated on October 26, 2012

Chapter 19: Translations | 233

11 </file> 12 </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. 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:
Listing 19-8

1 $ php app/console cache:clear

Translation Locations and Naming Conventions


Symfony2 looks for message files (i.e. translations) in the following locations: the <kernel root directory>/Resources/translations directory; the <kernel root directory>/Resources/<bundle name>/translations directory; the Resources/translations/ directory of the bundle. The locations are listed with the highest priority first. That is you can override the translation messages of a bundle in any of the top 2 directories. The override mechanism works at a key level: only the overriden keys need to be listed in a higher priority message file. When a key is not found in a message file, the translator will automatically fallback to the lower priority message files. 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:

6. http://www.php.net/manual/en/function.strtr.php

PDF brought to you by generated on October 26, 2012

Chapter 19: Translations | 234

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.

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 19-9

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

<!-- src/Acme/DemoBundle/Resources/translations/messages.fr.xliff --> <?xml version="1.0"?> <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <file source-language="en" datatype="plaintext" original="file.ext"> <body> <trans-unit id="1"> <source>Symfony2 is great</source> <target>J'aime Symfony2</target> </trans-unit> <trans-unit id="2"> <source>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).

7. http://api.symfony.com/2.1/Symfony/Component/Translation/Loader/LoaderInterface.html 8. http://en.wikipedia.org/wiki/Internationalization_and_localization

PDF brought to you by generated on October 26, 2012

Chapter 19: Translations | 235

Using Real or Keyword Messages


This example illustrates the two different philosophies when creating messages to be translated:
Listing 19-10

1 $t = $translator->trans('Symfony2 is great'); 2 3 $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:
Listing 19-11

1 symfony2: 2 is: 3 great: Symfony2 is great 4 amazing: Symfony2 is amazing 5 has: 6 bundles: Symfony2 has bundles 7 user: 8 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:
Listing 19-12

1 2 3 4

symfony2.is.great: Symfony2 is great 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():

PDF brought to you by generated on October 26, 2012

Chapter 19: Translations | 236

Listing 19-13

1 $this->get('translator')->trans('Symfony2 is great', array(), 'admin');

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 request and is accessible via the request object:
Listing 19-14

1 2 3 4 5 6

// access the reqest object in a standard controller $request = $this->getRequest();


$locale = $request->getLocale(); $request->setLocale('en_US');

It is also possible to store the locale in the session instead of on a per request basis. If you do this, each subsequent request will have this locale.
Listing 19-15

1 $this->get('session')->set('_locale', 'en_US');

See the The Locale and the URL section below about setting the locale via routing.

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 each user's request by defining a default_locale for the framework:
Listing 19-16

1 # app/config/config.yml 2 framework: 3 default_locale: en

New in version 2.1: The default_locale parameter was defined under the session key originally, however, as of 2.1 this has been moved. This is because the locale is now set on the request instead of the session.

The Locale and the URL


Since you can store the locale of the user 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 19-17

PDF brought to you by generated on October 26, 2012

Chapter 19: Translations | 237

1 contact: 2 pattern: /{_locale}/contact 3 defaults: { _controller: AcmeDemoBundle:Contact:index, _locale: en } 4 requirements: 5 _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 19-18

1 (($number % 10 == 1) && ($number % 100 != 11)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 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. When a translation has different forms due to pluralization, you can provide all the forms as a string separated by a pipe (|):
Listing 19-19

1 'There is one apple|There are %count% apples'

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


Listing 19-20

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

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:
Listing 19-21

1 'Il y a %count% pomme|Il y a %count% pommes'

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
9. http://api.symfony.com/2.1/Symfony/Component/Translation/Translator.html#transChoice()

PDF brought to you by generated on October 26, 2012

Chapter 19: Translations | 238

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:
Listing 19-22

1 'one: There is one apple|some: There are %count% apples' 2 3 '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:
Listing 19-23

1 '{0} There are no apples|{1} There is one apple|]1,19] There are %count% apples|[20,Inf] There 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:
Listing 19-24

1 '{0} There are no apples|[20,Inf] There are many apples|There is one apple|a_few: There are %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 19-25

1 {1,2,3,4}

Or numbers between two other numbers:


Listing 19-26

1 [1, +Inf[ 2 ]-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.

10. http://en.wikipedia.org/wiki/Interval_%28mathematics%29#The_ISO_notation 11. http://api.symfony.com/2.1/Symfony/Component/Translation/Interval.html

PDF brought to you by generated on October 26, 2012

Chapter 19: Translations | 239

Twig Templates
Symfony2 provides specialized Twig tags (trans and transchoice) to help with message translation of static blocks of text:
Listing 19-27

1 {% trans %}Hello %name%{% endtrans %} 2 3 {% transchoice count %} 4 {0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples 5 {% 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 19-28

1 2 3 4 5 6 7

{% trans with {'%name%': 'Fabien'} from "app" %}Hello %name%{% endtrans %} {% 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 19-29

1 2 3 4 5 6 7

{{ message|trans }} {{ 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:
Listing 19-30

1 2 3 4 5 6 7 8 9 10

{# text translated between tags is never escaped #} {% trans %} <h3>foo</h3> {% endtrans %}


{% set message = '<h3>foo</h3>' %}

{# a variable translated via a filter is escaped by default #} {{ message|trans|raw }}

PDF brought to you by generated on October 26, 2012

Chapter 19: Translations | 240

11 {# but static strings are never escaped #} 12 {{ '<h3>foo</h3>'|trans }}

New in version 2.1: You can now set the translation domain for an entire Twig template with a single tag:

Listing 19-31

1 {% trans_default_domain "app" %}

Note that this only influences the current template, not any "included" templates (in order to avoid side effects).

PHP Templates
The translator service is accessible in PHP templates through the translator helper:
Listing 19-32

1 <?php echo $view['translator']->trans('Symfony2 is great') ?> 2 3 <?php echo $view['translator']->transChoice( 4 '{0} There is no apples|{1} There is one apple|]1,Inf[ There are %count% apples', 5 10, 6 array('%count%' => 10) 7 ) ?>

Forcing the Translator Locale


When translating a message, Symfony2 uses the locale from the current request or the fallback locale if necessary. You can also manually specify the locale to use for translation:
Listing 19-33

1 2 3 4 5 6 7 8 9 10 11 12 13 14

$this->get('translator')->trans( '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 generated on October 26, 2012

Chapter 19: Translations | 241

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 19-34

1 2 3 4 5 6 7

// src/Acme/BlogBundle/Entity/Author.php namespace Acme\BlogBundle\Entity;


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 19-35

1 # src/Acme/BlogBundle/Resources/config/validation.yml 2 Acme\BlogBundle\Entity\Author: 3 properties: 4 name: 5 - 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 19-36

1 2 3 4 5 6 7 8 9 10 11 12

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

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.1/Symfony/Component/Translation/Translator.html#trans()

PDF brought to you by generated on October 26, 2012

Chapter 19: Translations | 242

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 on the request, but can also be set once the user's session.

14. http://api.symfony.com/2.1/Symfony/Component/Translation/Translator.html#transChoice()

PDF brought to you by generated on October 26, 2012

Chapter 19: Translations | 243

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 generated on October 26, 2012

Chapter 20: Service Container | 244

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:
Listing 20-1

1 use Acme\HelloBundle\Mailer; 2 3 $mailer = new Mailer('sendmail'); 4 $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:
Listing 20-2

1 # app/config/config.yml 2 services: 3 my_mailer: 4 class: Acme\HelloBundle\Mailer 5 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 Acme\HelloBundle\Mailer 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:
Listing 20-3

1 class HelloController extends Controller 2 {

1. http://wikipedia.org/wiki/Service-oriented_architecture

PDF brought to you by generated on October 26, 2012

Chapter 20: Service Container | 245

3 4 5 6 7 8 9 10 11 }

// ...
public function sendEmailAction() { // ... $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 20-4

# app/config/config.yml parameters: my_mailer.class: my_mailer.transport: services: my_mailer: class: arguments:

Acme\HelloBundle\Mailer sendmail

"%my_mailer.class%" [%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 20-5

<argument type="string">http://symfony.com/?foo=%%s&bar=%%d</argument>

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.

PDF brought to you by generated on October 26, 2012

Chapter 20: Service Container | 246

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.

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.
Listing 20-6

1 # app/config/config.yml 2 parameters: 3 my_mailer.gateways: 4 - mail1 5 - mail2 6 - mail3 7 my_multilang.language_fallback: 8 en: 9 - en 10 - fr 11 fr: 12 - fr 13 - 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.
Listing 20-7

# src/Acme/HelloBundle/Resources/config/services.yml parameters: my_mailer.class: Acme\HelloBundle\Mailer my_mailer.transport: sendmail

PDF brought to you by generated on October 26, 2012

Chapter 20: Service Container | 247

services: my_mailer: class: arguments:

"%my_mailer.class%" [%my_mailer.transport%]

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 20-8

# app/config/config.yml 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 20-9

1 # app/config/config.yml 2 framework: 3 secret: xxxxxxxxxx 4 form: true 5 csrf_protection: true 6 router: { resource: "%kernel.root_dir%/config/routing.yml" } 7 # ...

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

PDF brought to you by generated on October 26, 2012

Chapter 20: Service Container | 248

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. In this case, the extension allows you to customize the 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:
Listing 20-10

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

// src/Acme/HelloBundle/Newsletter/NewsletterManager.php namespace Acme\HelloBundle\Newsletter;


use Acme\HelloBundle\Mailer; 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:
Listing 20-11

PDF brought to you by generated on October 26, 2012

Chapter 20: Service Container | 249

1 public function sendNewsletterAction() 2 { 3 $mailer = $this->get('my_mailer'); 4 $newsletter = new Acme\HelloBundle\Newsletter\NewsletterManager($mailer); 5 // ... 6 }

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, 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 20-12

# src/Acme/HelloBundle/Resources/config/services.yml parameters: # ... newsletter_manager.class: Acme\HelloBundle\Newsletter\NewsletterManager 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 welldefined 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 20-13

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

namespace Acme\HelloBundle\Newsletter; use Acme\HelloBundle\Mailer; class NewsletterManager { protected $mailer; public function setMailer(Mailer $mailer) { $this->mailer = $mailer; }

// ...
}

PDF brought to you by generated on October 26, 2012

Chapter 20: Service Container | 250

Injecting the dependency by the setter method just needs a change of syntax:
Listing 20-14

# src/Acme/HelloBundle/Resources/config/services.yml parameters: # ... newsletter_manager.class: Acme\HelloBundle\Newsletter\NewsletterManager services: my_mailer: # ... newsletter_manager: class: "%newsletter_manager.class%" 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:
Listing 20-15

# src/Acme/HelloBundle/Resources/config/services.yml 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:
Listing 20-16

1 public function __construct(Mailer $mailer = null) 2 { 3 // ... 4 }

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:
Listing 20-17

PDF brought to you by generated on October 26, 2012

Chapter 20: Service Container | 251

1 public function indexAction($bar) 2 { 3 $session = $this->get('session'); 4 $session->set('foo', $bar); 5 6 // ... 7 }

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 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 20-18

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

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

// ...
}

Configuring the service container is easy:


Listing 20-19

services: 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.

PDF brought to you by generated on October 26, 2012

Chapter 20: Service Container | 252

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 20-20

1 services: 2 foo.twig.extension: 3 class: Acme\HelloBundle\Extension\FooExtension 4 tags: 5 - { 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). For a list of all the tags available in the core Symfony Framework, check out The Dependency Injection Tags.

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

1 $ php app/console container:debug

By default only public services are shown, but you can also view private services:
Listing 20-22

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

You can get more detailed information about a particular service by specifying its id:
Listing 20-23

1 $ php app/console container:debug my_mailer

Learn more
Compiling the Container Working with Container Parameters and Definitions Using a Factory to Create Services Managing Common Dependencies with Parent Services Working with Tagged Services How to define Controllers as Services How to work with Scopes How to work with Compiler Passes in Bundles Advanced Container Configuration

PDF brought to you by generated on October 26, 2012

Chapter 20: Service Container | 253

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 of 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 generated on October 26, 2012

Chapter 21: Performance | 254

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 - ApcClassLoader4 - that does exactly this. To use it, just adapt your front controller file. If you're using the Standard Distribution, this code should already be available as comments in this file:
Listing 21-1

1 2 3 4 5 6 7 8 9 10 11 12 13

// app.php // ...
$loader = require_once __DIR__.'/../app/bootstrap.php.cache';

// Use APC for autoloading to improve performance // Change 'sf2' by the prefix you want in order to prevent key conflict with another application /* $loader = new ApcClassLoader('sf2', $loader); $loader->register(true); */ // ...

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 socalled bootstrap file5, 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 21-2

1 require_once __DIR__.'/../app/bootstrap.php.cache';

Note that there are two disadvantages when using a bootstrap file:

3. https://github.com/symfony/symfony-standard/blob/master/app/autoload.php 4. http://api.symfony.com/2.1/Symfony/Component/ClassLoader/ApcClassLoader.html 5. https://github.com/sensio/SensioDistributionBundle/blob/master/Composer/ScriptHandler.php

PDF brought to you by generated on October 26, 2012

Chapter 21: Performance | 255

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 composer.phar install command.

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 fewer 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 generated on October 26, 2012

Chapter 21: Performance | 256

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.1/Symfony/Component/ClassLoader/UniversalClassLoader.html 2. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation.html 3. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Request.html 4. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Response.html

PDF brought to you by generated on October 26, 2012

Chapter 22: Internals | 257

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:
Listing 22-1

1 function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true)

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:
Listing 22-2

1 public function getController(Request $request); 2 3 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.1/Symfony/Component/HttpFoundation/Session.html 6. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/SessionStorage/SessionStorageInterface.html 7. http://api.symfony.com/2.1/Symfony/Component/HttpKernel.html 8. http://api.symfony.com/2.1/Symfony/Bundle/FrameworkBundle.html 9. http://api.symfony.com/2.1/Symfony/Component/HttpKernel/HttpKernel.html 10. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Request.html 11. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Response.html 12. http://api.symfony.com/2.1/Symfony/Component/HttpKernel/HttpKernelInterface.html 13. http://api.symfony.com/2.1/Symfony/Component/HttpKernel/Controller/ControllerResolverInterface.html 14. http://api.symfony.com/2.1/Symfony/Component/HttpKernel/Controller/ControllerResolverInterface.html#getController()

PDF brought to you by generated on October 26, 2012

Chapter 22: Internals | 258

that represents the controller name Bundle\BlogBundle\PostController:indexAction).

(a

"class::method"

string,

like

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 22-3

1 2 3 4 5 6

// Symfony2 will look for an 'id' attribute (mandatory) // 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.

15. http://api.symfony.com/2.1/Symfony/Component/HttpKernel/Controller/ControllerResolver.html 16. http://api.symfony.com/2.1/Symfony/Bundle/FrameworkBundle/EventListener/RouterListener.html 17. http://api.symfony.com/2.1/Symfony/Component/HttpKernel/Controller/ControllerResolverInterface.html#getArguments()

PDF brought to you by generated on October 26, 2012

Chapter 22: Internals | 259

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): 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:
Listing 22-4

1 use Symfony\Component\HttpKernel\HttpKernelInterface; 2 3 if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) { 4 // return immediately 5 return; 6 }

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
18. http://api.symfony.com/2.1/Symfony/Component/HttpKernel/Event/KernelEvent.html 19. http://api.symfony.com/2.1/Symfony/Component/HttpKernel/Event/GetResponseEvent.html 20. http://api.symfony.com/2.1/Symfony/Bundle/FrameworkBundle/EventListener/RouterListener.html 21. http://api.symfony.com/2.1/Symfony/Component/Routing/RouterInterface.html

PDF brought to you by generated on October 26, 2012

Chapter 22: Internals | 260

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

1 2 3 4 5 6 7 8 9 10

use Symfony\Component\HttpKernel\Event\FilterControllerEvent; 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 22-6

1 2 3 4 5 6 7 8 9 10 11

use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; use Symfony\Component\HttpFoundation\Response; 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 22-7

1 public function onKernelResponse(FilterResponseEvent $event) 2 { 3 $response = $event->getResponse(); 4 // ... modify the response object 5 }

The FrameworkBundle registers several listeners: ProfilerListener25: collects data for the current request; WebDebugToolbarListener26: injects the Web Debug Toolbar;
22. http://api.symfony.com/2.1/Symfony/Component/HttpKernel/Event/FilterControllerEvent.html 23. http://api.symfony.com/2.1/Symfony/Component/HttpKernel/Event/GetResponseForControllerResultEvent.html 24. http://api.symfony.com/2.1/Symfony/Component/HttpKernel/Event/FilterResponseEvent.html 25. http://api.symfony.com/2.1/Symfony/Component/HttpKernel/EventListener/ProfilerListener.html 26. http://api.symfony.com/2.1/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.html

PDF brought to you by generated on October 26, 2012

Chapter 22: Internals | 261

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.

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:
Listing 22-8

1 2 3 4 5 6 7 8 9 10 11 12 13 14

use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; use Symfony\Component\HttpFoundation\Response; 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);
}

As Symfony ensures that the Response status code is set to the most appropriate one depending on the exception, setting the status on the response won't work. If you want to overwrite the status code (which you should not without a good reason), set the X-Status-Code header:
Listing 22-9

1 return new Response('Error', 404 /* ignored */, array('X-Status-Code' => 200));

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.

27. http://api.symfony.com/2.1/Symfony/Component/HttpKernel/EventListener/ResponseListener.html 28. http://api.symfony.com/2.1/Symfony/Component/HttpKernel/EventListener/EsiListener.html 29. http://api.symfony.com/2.1/Symfony/Component/HttpKernel/Event/GetResponseForExceptionEvent.html 30. http://api.symfony.com/2.1/Symfony/Component/HttpKernel/EventListener/ExceptionListener.html

PDF brought to you by generated on October 26, 2012

Chapter 22: Internals | 262

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).

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 22-10

1 $profile = $container->get('profiler')->loadProfileFromResponse($response); 2 3 $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 22-11

1 2 3 4 5 6

// get the latest 10 tokens $tokens = $container->get('profiler')->find('', '', 10); // get the latest 10 tokens for all URL containing /admin/ $tokens = $container->get('profiler')->find('', '/admin/', 10);

PDF brought to you by generated on October 26, 2012

Chapter 22: Internals | 263

7 // get the latest 10 tokens for local requests 8 $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 22-12

1 2 3 4 5 6

// on the production machine $profile = $container->get('profiler')->loadProfile($token); $data = $profiler->export($profile); // on the development machine $profiler->import($data);

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:
Listing 22-13

1 2 3 4 5 6 7 8

# load the profiler framework: profiler: { only_exceptions: false } # enable the web profiler web_profiler: toolbar: true intercept_redirects: 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. If you enable the web profiler, you also need to mount the profiler routes:
Listing 22-14

_profiler: 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:
Listing 22-15

1 2 3 4 5 6 7 8 9 10 11 12

# enables the profiler only for request coming for the 192.168.0.0 network 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:

PDF brought to you by generated on October 26, 2012

Chapter 22: Internals | 264

13 profiler: 14 matcher: { ip: 192.168.0.0/24, path: "^/admin/" } 15 16 # use a custom matcher instance defined in the "custom_matcher" service 17 framework: 18 profiler: 19 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 How to customize a Method Behavior without using Inheritance

PDF brought to you by generated on October 26, 2012

Chapter 22: Internals | 265

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
Chapter 23: The Symfony2 Stable API | 266

PDF brought to you by generated on October 26, 2012

Validator Yaml

PDF brought to you by generated on October 26, 2012

Chapter 23: The Symfony2 Stable API | 267

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 composer.json file) and paste the following into it. Files matching these patterns will be ignored by git:
Listing 24-1

1 2 3 4 5 6

/web/bundles/ /app/bootstrap* /app/cache/* /app/logs/* /vendor/ /app/config/parameters.yml

1. http://git-scm.com/ 2. http://symfony.com/download

PDF brought to you by generated on October 26, 2012

Chapter 24: How to Create and store a Symfony2 Project in git | 269

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.yml to app/config/parameters.yml.dist. The parameters.yml file is ignored by git (see above) so that machine-specific settings like database passwords aren't committed. By creating the parameters.yml.dist file, new developers can quickly clone the project, copy this file to parameters.yml, customize it, and start developing. 5. Initialize your git repository:
Listing 24-2

1 $ git init

6. Add all of the initial files to git:


Listing 24-3

1 $ git add .

7. Create an initial commit with your started project:


Listing 24-4

1 $ git commit -m "Initial commit"

8. Finally, download all of the third-party vendor libraries by executing composer. For details, see Updating Vendors. 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. 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 Readme4.

Managing Vendor Libraries with composer.json


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 composer.phar install "downloader" binary. This composer.phar file is from a library called Composer5 and you can read more about installing it in the Installation chapter. The composer.phar file reads from the composer.json file at the root of your project. This is an JSONformatted file, which holds a list of each of the external packages you need, the version to be downloaded and more. The composer.phar file also reads from a composer.lock file, which allows you to pin each

3. http://help.github.com/ignore-files/ 4. https://github.com/symfony/symfony-standard/blob/master/README.md 5. http://getcomposer.org/

PDF brought to you by generated on October 26, 2012

Chapter 24: How to Create and store a Symfony2 Project in git | 270

library to an exact version. In fact, if a composer.lock file exists, the versions inside will override those in composer.json. To upgrade your libraries to new versions, run php composer.phar update. To learn more about Composer, see GetComposer.org6: 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/. But since all the information needed to download these files is saved in composer.json and composer.lock (which are stored) in our repository, any other developer can use our project, run php composer.phar 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 composer.phar 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 composer.json and composer.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 composer.json, be sure to replace only the original parts (i.e. be sure not to also delete any of your custom entries).

Vendors and Submodules


Instead of using the composer.json system for managing your vendor libraries, you may instead choose to use native git submodules7. There is nothing wrong with this approach, though the composer.json system is the official way to solve this problem and probably much easier to deal with. Unlike git submodules, Composer is smart enough to calculate which libraries depend on which other libraries.

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 GitHub8. 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 repository9 and then pushing to it. One library that helps manage this is Gitolite10.

6. http://getcomposer.org/ 7. http://git-scm.com/book/en/Git-Tools-Submodules 8. https://github.com/ 9. http://git-scm.com/book/en/Git-Basics-Getting-a-Git-Repository 10. https://github.com/sitaramc/gitolite

PDF brought to you by generated on October 26, 2012

Chapter 24: How to Create and store a Symfony2 Project in git | 271

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 25-1

1 myproject/ 2 branches/ 3 tags/ 4 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 generated on October 26, 2012

Chapter 25: How to Create and store a Symfony2 Project in Subversion | 272

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:
Listing 25-2

1 $ svn checkout http://myproject.googlecode.com/svn/trunk myproject

4. Copy the Symfony2 project files in the subversion folder:


Listing 25-3

1 $ mv Symfony/* myproject/

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.
Listing 25-4

1 2 3 4 5 6 7 8 9 10 11 12

$ cd myproject/ $ svn add --depth=empty app app/cache app/logs app/config web $ $ $ $ $ svn svn svn svn svn propset propset propset propset propset svn:ignore svn:ignore svn:ignore svn:ignore svn:ignore "vendor" . "bootstrap*" app/ "parameters.yml" app/config/ "*" app/cache/ "*" app/logs/

$ svn propset svn:ignore "bundles" web $ svn ci -m "commit basic symfony ignore list (vendor, app/bootstrap*, app/config/ parameters.yml, app/cache/*, app/logs/*, web/bundles)"

6. The rest of the files can now be added and committed to the project:
Listing 25-5

1 $ svn add --force . 2 $ svn ci -m "add basic Symfony Standard 2.X.Y"

7. Copy app/config/parameters.yml to app/config/parameters.yml.dist. The parameters.yml file is ignored by svn (see above) so that machine-specific settings like database passwords aren't committed. By creating the parameters.yml.dist file, new developers can quickly clone the project, copy this file to parameters.yml, customize it, and start developing.

5. http://svnbook.red-bean.com/ 6. http://symfony.com/download 7. http://code.google.com/hosting/

PDF brought to you by generated on October 26, 2012

Chapter 25: How to Create and store a Symfony2 Project in Subversion | 273

8. Finally, download all of the third-party vendor libraries by executing composer. For details, see Updating Vendors.
If you rely on any "dev" versions, then git may be used to install those libraries, since there is no archive available for download.

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 Readme8.

Managing Vendor Libraries with composer.json


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 composer.phar install "downloader" binary. This composer.phar file is from a library called Composer9 and you can read more about installing it in the Installation chapter. The composer.phar file reads from the composer.json file at the root of your project. This is an JSONformatted file, which holds a list of each of the external packages you need, the version to be downloaded and more. The composer.phar file also reads from a composer.lock file, which allows you to pin each library to an exact version. In fact, if a composer.lock file exists, the versions inside will override those in composer.json. To upgrade your libraries to new versions, run php composer.phar update. To learn more about Composer, see GetComposer.org10: 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/. But since all the information needed to download these files is saved in composer.json and composer.lock (which are stored) in our repository, any other developer can use our project, run php composer.phar 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 composer.phar install script to ensure that all of the needed vendor libraries are downloaded.

8. https://github.com/symfony/symfony-standard/blob/master/README.md 9. http://getcomposer.org/ 10. http://getcomposer.org/

PDF brought to you by generated on October 26, 2012

Chapter 25: How to Create and store a Symfony2 Project in Subversion | 274

Upgrading Symfony
Since Symfony is just a group of third-party libraries and third-party libraries are entirely controlled through composer.json and composer.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 composer.json, be sure to replace only the original parts (i.e. be sure not to also delete any of your custom entries).

Subversion hosting solutions


The biggest difference between git11 and svn12 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 GitHub13, Google code14, SourceForge15 or Gna16. Some of them offer git hosting as well.

11. http://git-scm.com/ 12. http://subversion.apache.org/ 13. http://github.com/ 14. http://code.google.com/hosting/ 15. http://sourceforge.net/ 16. http://gna.org/

PDF brought to you by generated on October 26, 2012

Chapter 25: How to Create and store a Symfony2 Project in Subversion | 275

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 26-1

1 2 3 4 5 6 7 8 9 10 11

<!DOCTYPE html> <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 generated on October 26, 2012

Chapter 26: How to customize Error Pages | 276

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/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 generated on October 26, 2012

Chapter 26: How to customize Error Pages | 277

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 27-1

1 $this->forward('my_controller:indexAction', array('foo' => $bar));

You need to use the same notation when defining the route _controller value:
Listing 27-2

1 my_controller: 2 pattern: / 3 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.1/Symfony/Bundle/FrameworkBundle/Controller/Controller.html

PDF brought to you by generated on October 26, 2012

Chapter 27: How to define Controllers as Services | 278

Using Annotation Routing


When using annotations to setup routing when using a controller defined as a service, you need to specify your service as follows:
Listing 27-3

1 2 3 4 5 6 7

/** * @Route("/blog", service="my_bundle.annot_controller") * @Cache(expires="tomorrow") */ class AnnotController extends Controller { }

In this example, my_bundle.annot_controller should be the id of the AnnotController instance defined in the service container. This is documented in the @Route and @Method chapter.

PDF brought to you by generated on October 26, 2012

Chapter 27: How to define Controllers as Services | 279

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:
Listing 28-1

1 secure: 2 pattern: /secure 3 defaults: { _controller: AcmeDemoBundle:Main:secure } 4 requirements: 5 _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:
Listing 28-2

1 2 3 4 5 6 7

{# If the current scheme is HTTPS #} {{ 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 generated on October 26, 2012

Chapter 28: How to force routes to always use HTTPS or HTTP | 280

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 29-1

1 _hello: 2 pattern: /hello/{name} 3 defaults: { _controller: AcmeDemoBundle:Demo:hello } 4 requirements: 5 name: ".+"

That's it! Now, the {name} parameter can contain the / character.

PDF brought to you by generated on October 26, 2012

Chapter 29: How to allow a "/" character in a route parameter | 281

Chapter 30

How to configure a redirect to another route without a custom controller


This guide explains how to configure a redirect from one route to another without using a custom controller. Let's assume that there is no useful default controller for the / path of your application and you want to redirect theses requests to /app. Your configuration will look like this:
Listing 30-1

1 AppBundle: 2 resource: "@App/Controller/" 3 type: annotation 4 prefix: /app 5 6 root: 7 pattern: / 8 defaults: 9 _controller: FrameworkBundle:Redirect:urlRedirect 10 path: /app 11 permanent: true

Your AppBundle is registered to handle all requests under /app. We configure a route for the / path and let RedirectController1 handle it. This controller is built-in and offers two methods for redirecting request: redirect redirects to another route. You must provide the route parameter with the name of the route you want to redirect to. urlRedirect redirects to another path. You must provide the path parameter containing the path of the resource you want to redirect to. The permanent switch tells both methods to issue a 301 HTTP status code.

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

PDF brought to you by generated on October 26, 2012

Chapter 30: How to configure a redirect to another route without a custom controller | 282

Chapter 31

How to use HTTP Methods beyond GET and POST in Routes


The HTTP method of a request is one of the requirements that can be checked when seeing if it matches a route. This is introduced in the routing chapter of the book "Routing" with examples using GET and POST. You can also use other HTTP verbs in this way. For example, if you have a blog post entry then you could use the same URL pattern to show it, make changes to it and delete it by matching on GET, PUT and DELETE.
Listing 31-1

1 blog_show: 2 pattern: /blog/{slug} 3 defaults: { _controller: AcmeDemoBundle:Blog:show } 4 requirements: 5 _method: GET 6 7 blog_update: 8 pattern: /blog/{slug} 9 defaults: { _controller: AcmeDemoBundle:Blog:update } 10 requirements: 11 _method: PUT 12 13 blog_delete: 14 pattern: /blog/{slug} 15 defaults: { _controller: AcmeDemoBundle:Blog:delete } 16 requirements: 17 _method: DELETE

Unfortunately, life isn't quite this simple, since most browsers do not support sending PUT and DELETE requests. Fortunately Symfony2 provides you with a simple way of working around this limitation. By including a _method parameter in the query string or parameters of an HTTP request, Symfony2 will use this as the method when matching routes. This can be done easily in forms with a hidden field. Suppose you have a form for editing a blog post:
Listing 31-2

PDF brought to you by generated on October 26, 2012

Chapter 31: How to use HTTP Methods beyond GET and POST in Routes | 283

1 <form action="{{ path('blog_update', {'slug': blog.slug}) }}" method="post"> 2 <input type="hidden" name="_method" value="PUT" /> 3 {{ form_widget(form) }} 4 <input type="submit" value="Update" /> 5 </form>

The submitted request will now match the blog_update route and the updateAction will be used to process the form. Likewise the delete form could be changed to look like this:
Listing 31-3

1 <form action="{{ path('blog_delete', {'slug': blog.slug}) }}" method="post"> 2 <input type="hidden" name="_method" value="DELETE" /> 3 {{ form_widget(delete_form) }} 4 <input type="submit" value="Delete" /> 5 </form>

It will then match the blog_delete route.

PDF brought to you by generated on October 26, 2012

Chapter 31: How to use HTTP Methods beyond GET and POST in Routes | 284

Chapter 32

How to use Service Container Parameters in your Routes

New in version 2.1: The ability to use parameters in your routes was added in Symfony 2.1.

Sometimes you may find it useful to make some parts of your routes globally configurable. For instance, if you build an internationalized site, you'll probably start with one or two locales. Surely you'll add a requirement to your routes to prevent a user from matching a locale other than the locales your support. You could hardcode your _locale requirement in all your routes. But a better solution is to use a configurable service container parameter right inside your routing configuration:
Listing 32-1

contact: pattern: /{_locale}/contact defaults: { _controller: AcmeDemoBundle:Main:contact } requirements: _locale: %acme_demo.locales%

You can now control and set the acme_demo.locales parameter somewhere in your container:
Listing 32-2

1 # app/config/config.yml 2 parameters: 3 acme_demo.locales: en|es

You can also use a parameter to define your route pattern (or part of your pattern):
Listing 32-3

1 some_route: 2 pattern: /%acme_demo.route_prefix%/contact 3 defaults: { _controller: AcmeDemoBundle:Main:contact }

PDF brought to you by generated on October 26, 2012

Chapter 32: How to use Service Container Parameters in your Routes | 285

Just like in normal service container configuration files, if you actually need a % in your route, you can escape the percent sign by doubling it, e.g. /score-50%%, which would resolve to /score-50%.

PDF brought to you by generated on October 26, 2012

Chapter 32: How to use Service Container Parameters in your Routes | 286

Chapter 33

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:
Listing 33-1

1 <script src="{{ asset('js/script.js') }}" type="text/javascript" />

But with Assetic, you can manipulate these assets however you want (or load them from anywhere) before serving them. This 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:
Listing 33-2

1 {% javascripts '@AcmeFooBundle/Resources/public/js/*' %} 2 <script type="text/javascript" src="{{ asset_url }}"></script> 3 {% endjavascripts %}

To bring in CSS stylesheets, you can use the same methodologies seen in this entry, except with the stylesheets tag:
Listing 33-3

PDF brought to you by generated on October 26, 2012

Chapter 33: How to Use Assetic for Asset Management | 287

1 {% stylesheets '@AcmeFooBundle/Resources/public/css/*' %} 2 <link rel="stylesheet" href="{{ asset_url }}" /> 3 {% 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 33-4

1 <script src="/app_dev.php/js/abcd123.js"></script>

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 33-5

1 {% javascripts 2 '@AcmeFooBundle/Resources/public/js/*' 3 '@AcmeBarBundle/Resources/public/js/form.js' 4 '@AcmeBarBundle/Resources/public/js/calendar.js' %} 5 <script src="{{ asset_url }}"></script> 6 {% 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 33-6

1 {% javascripts 2 '@AcmeFooBundle/Resources/public/js/thirdparty/jquery.js' 3 '@AcmeFooBundle/Resources/public/js/*' %} 4 <script src="{{ asset_url }}"></script> 5 {% endjavascripts %}

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).
PDF brought to you by generated on October 26, 2012 Chapter 33: How to Use Assetic for Asset Management | 288

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 heavylifting. 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:
Listing 33-7

1 # app/config/config.yml 2 assetic: 3 filters: 4 yui_js: 5 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:
Listing 33-8

1 {% javascripts '@AcmeFooBundle/Resources/public/js/*' filter='yui_js' %} 2 <script src="{{ asset_url }}"></script> 3 {% 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:
Listing 33-9

1 {% javascripts '@AcmeFooBundle/Resources/public/js/*' output='js/compiled/main.js' %} 2 <script src="{{ asset_url }}"></script> 3 {% 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.

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.
PDF brought to you by generated on October 26, 2012 Chapter 33: How to Use Assetic for Asset Management | 289

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 33-10

1 <script src="/app_dev.php/js/abcd123.js"></script>

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 33-11

1 $ php app/console assetic:dump --env=prod --no-debug

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 33-12

1 # app/config/config_dev.yml 2 assetic: 3 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 33-13

1 $ php app/console assetic:dump

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 33-14

1 $ php app/console assetic:dump --watch

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:
Listing 33-15

1 {% javascripts '@AcmeFooBundle/Resources/public/js/*' output='js/compiled/main.js' %} 2 <script src="{{ asset_url }}"></script> 3 {% endjavascripts %}

PDF brought to you by generated on October 26, 2012

Chapter 33: How to Use Assetic for Asset Management | 290

Chapter 34

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 34-1

1 # app/config/config.yml 2 assetic: 3 # java: "/usr/bin/java" 4 filters: 5 yui_css: 6 jar: "%kernel.root_dir%/Resources/java/yuicompressor.jar" 7 yui_js: 8 jar: "%kernel.root_dir%/Resources/java/yuicompressor.jar"

1. http://developer.yahoo.com/yui/compressor/ 2. http://yuilibrary.com/projects/yuicompressor/

PDF brought to you by generated on October 26, 2012

Chapter 34: How to Minify JavaScripts and Stylesheets with YUI Compressor | 291

Windows users need to remember to update config to proper java location. In Windows7 x64 bit by default it's C:\Program Files (x86)\Java\jre6\bin\java.exe.

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:
Listing 34-2

1 {% javascripts '@AcmeFooBundle/Resources/public/js/*' filter='yui_js' %} 2 <script src="{{ asset_url }}"></script> 3 {% 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.
Listing 34-3

1 {% stylesheets '@AcmeFooBundle/Resources/public/css/*' filter='yui_css' %} 2 <link rel="stylesheet" type="text/css" media="screen" href="{{ asset_url }}" /> 3 {% 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.
Listing 34-4

1 {% javascripts '@AcmeFooBundle/Resources/public/js/*' filter='?yui_js' %} 2 <script src="{{ asset_url }}"></script> 3 {% endjavascripts %}

Instead of adding the filter to the asset tags, you can also globally enable it by adding the applyto 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 generated on October 26, 2012

Chapter 34: How to Minify JavaScripts and Stylesheets with YUI Compressor | 292

Chapter 35

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 35-1

1 # app/config/config.yml 2 assetic: 3 filters: 4 jpegoptim: 5 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 35-2

1 {% image '@AcmeFooBundle/Resources/public/images/example.jpg' 2 filter='jpegoptim' output='/images/example.jpg' %} 3 <img src="{{ asset_url }}" alt="Example"/> 4 {% endimage %}

1. http://www.kokkonen.net/tjko/projects.html

PDF brought to you by generated on October 26, 2012

Chapter 35: How to Use Assetic For Image Optimization with Twig Functions | 293

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:
Listing 35-3

1 # app/config/config.yml 2 assetic: 3 filters: 4 jpegoptim: 5 bin: path/to/jpegoptim 6 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:
Listing 35-4

1 # app/config/config.yml 2 assetic: 3 filters: 4 jpegoptim: 5 bin: path/to/jpegoptim 6 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:
Listing 35-5

1 # app/config/config.yml 2 assetic: 3 filters: 4 jpegoptim: 5 bin: path/to/jpegoptim 6 twig: 7 functions: 8 jpegoptim: ~

The Twig template can now be changed to the following:


Listing 35-6

1 <img src="{{ jpegoptim('@AcmeFooBundle/Resources/public/images/example.jpg') }}" alt="Example"/>

You can specify the output directory in the config in the following way:
Listing 35-7

1 # app/config/config.yml 2 assetic: 3 filters: 4 jpegoptim: 5 bin: path/to/jpegoptim 6 twig:

PDF brought to you by generated on October 26, 2012

Chapter 35: How to Use Assetic For Image Optimization with Twig Functions | 294

7 8

functions: jpegoptim: { output: images/*.jpg }

PDF brought to you by generated on October 26, 2012

Chapter 35: How to Use Assetic For Image Optimization with Twig Functions | 295

Chapter 36

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 36-1

1 # app/config/config.yml 2 assetic: 3 filters: 4 coffee: 5 bin: /usr/bin/coffee 6 node: /usr/bin/node

Filter a Single File


You can now serve up a single CoffeeScript file as JavaScript from within your templates:
Listing 36-2

1 {% javascripts '@AcmeFooBundle/Resources/public/js/example.coffee' filter='coffee' %} 2 <script src="{{ asset_url }}" type="text/javascript"></script> 3 {% 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 generated on October 26, 2012

Chapter 36: How to Apply an Assetic Filter to a Specific File Extension | 296

Listing 36-3

1 {% javascripts '@AcmeFooBundle/Resources/public/js/example.coffee' 2 '@AcmeFooBundle/Resources/public/js/another.coffee' 3 filter='coffee' %} 4 <script src="{{ asset_url }}" type="text/javascript"></script> 5 {% 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:
Listing 36-4

# app/config/config.yml 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):
Listing 36-5

1 {% javascripts '@AcmeFooBundle/Resources/public/js/example.coffee' 2 '@AcmeFooBundle/Resources/public/js/another.coffee' 3 '@AcmeFooBundle/Resources/public/js/regular.js' %} 4 <script src="{{ asset_url }}" type="text/javascript"></script> 5 {% endjavascripts %}

PDF brought to you by generated on October 26, 2012

Chapter 36: How to Apply an Assetic Filter to a Specific File Extension | 297

Chapter 37

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 37-1

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

// src/Acme/DemoBundle/Entity/Document.php namespace Acme\DemoBundle\Entity;


use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert;

/** * @ORM\Entity */ class Document { /** * @ORM\Id * @ORM\Column(type="integer") * @ORM\GeneratedValue(strategy="AUTO") */ public $id; /** * @ORM\Column(type="string", length=255)

PDF brought to you by generated on October 26, 2012

Chapter 37: How to handle File Uploads with Doctrine | 298

21 * @Assert\NotBlank 22 */ 23 public $name; 24 25 /** 26 * @ORM\Column(type="string", length=255, nullable=true) 27 */ 28 public $path; 29 30 public function getAbsolutePath() 31 { 32 return null === $this->path ? null : $this->getUploadRootDir().'/'.$this->path; 33 } 34 35 public function getWebPath() 36 { 37 return null === $this->path ? null : $this->getUploadDir().'/'.$this->path; 38 } 39 40 protected function getUploadRootDir() 41 { 42 // the absolute directory path where uploaded documents should be saved 43 return __DIR__.'/../../../../web/'.$this->getUploadDir(); 44 } 45 46 protected function getUploadDir() 47 { 48 // get rid of the __DIR__ so it doesn't screw when displaying uploaded doc/image 49 in the view. 50 return 'uploads/documents'; 51 } }

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:
Listing 37-2

1 public function uploadAction() 2 { 3 // ... 4 5 $form = $this->createFormBuilder($document) 6 ->add('name')

PDF brought to you by generated on October 26, 2012

Chapter 37: How to handle File Uploads with Doctrine | 299

7 8 9 10 11 }

->add('file') ->getForm();

// ...

Next, create this property on your Document class and add some validation rules:
Listing 37-3

1 2 3 4 5 6 7 8 9 10 11 12

// src/Acme/DemoBundle/Entity/Document.php // ... class Document { /** * @Assert\File(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 37-4

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

use Acme\DemoBundle\Entity\Document; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; // ...

/** * @Template() */ public function uploadAction() { $document = new Document(); $form = $this->createFormBuilder($document) ->add('name') ->add('file') ->getForm() ;
if ($this->getRequest()->isMethod('POST')) { $form->bind($this->getRequest()); if ($form->isValid()) { $em = $this->getDoctrine()->getManager(); $em->persist($document); $em->flush(); $this->redirect($this->generateUrl(...)); } }

PDF brought to you by generated on October 26, 2012

Chapter 37: How to handle File Uploads with Doctrine | 300

29 30 }

return array('form' => $form->createView());

When writing the template, don't forget to set the enctype attribute:
Listing 37-5

<h1>Upload File</h1> <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:
Listing 37-6

1 if ($form->isValid()) { 2 $em = $this->getDoctrine()->getManager(); 3 4 $document->upload(); 5 6 $em->persist($document); 7 $em->flush(); 8 9 $this->redirect(...); 10 }

The upload() method will take advantage of the UploadedFile1 object, which is what's returned after a file field is submitted:
Listing 37-7

1 public function upload() 2 { 3 // the file property can be empty if the field is not required 4 if (null === $this->file) { 5 return; 6 } 7 8 // we use the original file name here but you should 9 // sanitize it at least to avoid any security issues 10 11 // move takes the target directory and then the target filename to move to 12 $this->file->move($this->getUploadRootDir(), $this->file->getClientOriginalName()); 13 14 // set the path property to the filename where you'ved saved the file 15 $this->path = $this->file->getClientOriginalName(); 16 17 // clean up the file property as you won't need it anymore 18 $this->file = null; 19 }

1. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/File/UploadedFile.html

PDF brought to you by generated on October 26, 2012

Chapter 37: How to handle File Uploads with Doctrine | 301

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 37-8

1 2 3 4 5 6 7

/** * @ORM\Entity * @ORM\HasLifecycleCallbacks */ class Document { }

Next, refactor the Document class to take advantage of these callbacks:


Listing 37-9

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36

use Symfony\Component\HttpFoundation\File\UploadedFile;

/** * @ORM\Entity * @ORM\HasLifecycleCallbacks */ class Document { /** * @ORM\PrePersist() * @ORM\PreUpdate() */ public function preUpload() { if (null !== $this->file) { // do whatever you want to generate a unique name $this->path = sha1(uniqid(mt_rand(), true)).'.'.$this->file->guessExtension(); } } /** * @ORM\PostPersist() * @ORM\PostUpdate() */ 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 generated on October 26, 2012

Chapter 37: How to handle File Uploads with Doctrine | 302

37 38 39 40 41 42 43 44 45 46 47 48 }

/** * @ORM\PostRemove() */ 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:
Listing 37-10

1 if ($form->isValid()) { 2 $em = $this->getDoctrine()->getManager(); 3 4 $em->persist($document); 5 $em->flush(); 6 7 $this->redirect(...); 8 }

The @ORM\PrePersist() and @ORM\PostPersist() event callbacks are triggered before and after the entity is persisted to the database. On the other hand, the @ORM\PreUpdate() and @ORM\PostUpdate() 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:
Listing 37-11

1 2 3 4 5 6 7 8

use Symfony\Component\HttpFoundation\File\UploadedFile;

/** * @ORM\Entity * @ORM\HasLifecycleCallbacks */ class Document {

PDF brought to you by generated on October 26, 2012

Chapter 37: How to handle File Uploads with Doctrine | 303

9 // a property used temporarily while deleting 10 private $filenameForRemove; 11 12 /** 13 * @ORM\PrePersist() 14 * @ORM\PreUpdate() 15 */ 16 public function preUpload() 17 { 18 if (null !== $this->file) { 19 $this->path = $this->file->guessExtension(); 20 } 21 } 22 23 /** 24 * @ORM\PostPersist() 25 * @ORM\PostUpdate() 26 */ 27 public function upload() 28 { 29 if (null === $this->file) { 30 return; 31 } 32 33 // you must throw an exception here if the file cannot be moved 34 // so that the entity is not persisted to the database 35 // which the UploadedFile move() method does 36 $this->file->move($this->getUploadRootDir(), 37 $this->id.'.'.$this->file->guessExtension()); 38 39 unset($this->file); 40 } 41 42 /** 43 * @ORM\PreRemove() 44 */ 45 public function storeFilenameForRemove() 46 { 47 $this->filenameForRemove = $this->getAbsolutePath(); 48 } 49 50 /** 51 * @ORM\PostRemove() 52 */ 53 public function removeUpload() 54 { 55 if ($this->filenameForRemove) { 56 unlink($this->filenameForRemove); 57 } 58 } 59 60 public function getAbsolutePath() 61 { 62 return null === $this->path ? null : 63 $this->getUploadRootDir().'/'.$this->id.'.'.$this->path; } }

PDF brought to you by generated on October 26, 2012

Chapter 37: How to handle File Uploads with Doctrine | 304

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 generated on October 26, 2012

Chapter 37: How to handle File Uploads with Doctrine | 305

Chapter 38

How to use 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 generated on October 26, 2012

Chapter 38: How to use Doctrine Extensions: Timestampable, Sluggable, Translatable, etc. | 306

Chapter 39

How to Register 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 39-1

1 doctrine: 2 dbal: 3 default_connection: default 4 connections: 5 default: 6 driver: pdo_sqlite 7 memory: true 8 9 services: 10 my.listener: 11 class: Acme\SearchBundle\Listener\SearchIndexer 12 tags: 13 - { name: doctrine.event_listener, event: postPersist } 14 my.listener2:

1. http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html

PDF brought to you by generated on October 26, 2012

Chapter 39: How to Register Event Listeners and Subscribers | 307

15 16 17 18 19 20 21

class: Acme\SearchBundle\Listener\SearchIndexer2 tags: - { name: doctrine.event_listener, event: postPersist, connection: default } my.subscriber: class: Acme\SearchBundle\Listener\SearchIndexerSubscriber 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:
Listing 39-2

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

// src/Acme/SearchBundle/Listener/SearchIndexer.php namespace Acme\SearchBundle\Listener;


use Doctrine\ORM\Event\LifecycleEventArgs; use Acme\StoreBundle\Entity\Product; 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 generated on October 26, 2012

Chapter 39: How to Register Event Listeners and Subscribers | 308

Chapter 40

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 40-1

1 # app/config/config.yml 2 doctrine: 3 dbal: 4 driver: pdo_mysql 5 dbname: Symfony2 6 user: root 7 password: null 8 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 40-2

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 generated on October 26, 2012

Chapter 40: How to use Doctrine's DBAL Layer | 309

1 class UserController extends Controller 2 { 3 public function indexAction() 4 { 5 $conn = $this->get('database_connection'); 6 $users = $conn->fetchAll('SELECT * FROM users'); 7 8 // ... 9 } 10 }

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.
Listing 40-3

1 # app/config/config.yml 2 doctrine: 3 dbal: 4 types: 5 custom_first: Acme\HelloBundle\Type\CustomFirst 6 custom_second: Acme\HelloBundle\Type\CustomSecond

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:
Listing 40-4

1 # app/config/config.yml 2 doctrine: 3 dbal: 4 connections: 5 default: 6 // Other connections parameters 7 mapping_types: 8 enum: string

4. http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/types.html#custom-mapping-types

PDF brought to you by generated on October 26, 2012

Chapter 40: How to use Doctrine's DBAL Layer | 310

Chapter 41

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 41-1

1 CREATE TABLE `blog_post` ( 2 `id` bigint(20) NOT NULL AUTO_INCREMENT, 3 `title` varchar(100) COLLATE utf8_unicode_ci NOT NULL, 4 `content` longtext COLLATE utf8_unicode_ci NOT NULL, 5 `created_at` datetime NOT NULL, 6 PRIMARY KEY (`id`) 7 ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; 8 9 CREATE TABLE `blog_comment` ( 10 `id` bigint(20) NOT NULL AUTO_INCREMENT, 11 `post_id` bigint(20) NOT NULL, 12 `author` varchar(20) COLLATE utf8_unicode_ci NOT NULL, 13 `content` longtext COLLATE utf8_unicode_ci NOT NULL, 14 `created_at` datetime NOT NULL,

1. http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/tools.html#reverse-engineering

PDF brought to you by generated on October 26, 2012

Chapter 41: How to generate Entities from an Existing Database | 311

15 PRIMARY KEY (`id`), 16 KEY `blog_comment_post_id_idx` (`post_id`), 17 CONSTRAINT `blog_post_id` FOREIGN KEY (`post_id`) REFERENCES `blog_post` (`id`) ON 18 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.yml 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.
Listing 41-2

1 $ php app/console doctrine:mapping:convert xml ./src/Acme/BlogBundle/Resources/config/ doctrine/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:


Listing 41-3

1 <?xml version="1.0" encoding="utf-8"?> 2 <doctrine-mapping> 3 <entity name="BlogPost" table="blog_post"> 4 <change-tracking-policy>DEFERRED_IMPLICIT</change-tracking-policy> 5 <id name="id" type="bigint" column="id"> 6 <generator strategy="IDENTITY"/> 7 </id> 8 <field name="title" type="string" column="title" length="100"/> 9 <field name="content" type="text" column="content"/> 10 <field name="isPublished" type="boolean" column="is_published"/> 11 <field name="createdAt" type="datetime" column="created_at"/> 12 <field name="updatedAt" type="datetime" column="updated_at"/> 13 <field name="slug" type="string" column="slug" length="255"/> 14 <lifecycle-callbacks/> 15 </entity> 16 </doctrine-mapping>

If you have oneToMany relationships between your entities, you will need to edit the generated xml or yml files to add a section on the specific entities for oneToMany defining the inversedBy and the mappedBy pieces.

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.
Listing 41-4

1 $ php app/console doctrine:mapping:import AcmeBlogBundle annotation 2 $ php app/console doctrine:generate:entities AcmeBlogBundle

PDF brought to you by generated on October 26, 2012

Chapter 41: How to generate Entities from an Existing Database | 312

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:
Listing 41-5

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53

<?php

// src/Acme/BlogBundle/Entity/BlogComment.php namespace Acme\BlogBundle\Entity;


use Doctrine\ORM\Mapping as ORM;

/** * Acme\BlogBundle\Entity\BlogComment * * @ORM\Table(name="blog_comment") * @ORM\Entity */ class BlogComment { /** * @var bigint $id * * @ORM\Column(name="id", type="bigint", nullable=false) * @ORM\Id * @ORM\GeneratedValue(strategy="IDENTITY") */ private $id; /** * @var string $author * * @ORM\Column(name="author", type="string", length=100, nullable=false) */ private $author; /** * @var text $content * * @ORM\Column(name="content", type="text", nullable=false) */ private $content; /** * @var datetime $createdAt * * @ORM\Column(name="created_at", type="datetime", nullable=false) */ private $createdAt; /** * @var BlogPost * * @ORM\ManyToOne(targetEntity="BlogPost") * @ORM\JoinColumn(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

PDF brought to you by generated on October 26, 2012

Chapter 41: How to generate Entities from an Existing Database | 313

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 generated on October 26, 2012

Chapter 41: How to generate Entities from an Existing Database | 314

Chapter 42

How to work with Multiple Entity Managers and Connections


You can use multiple Doctrine entity managers or connections 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:
Listing 42-1

doctrine: dbal: default_connection: default connections: default: driver: %database_driver% host: %database_host% port: %database_port% dbname: %database_name% user: %database_user% password: %database_password% charset: UTF8 customer: driver: %database_driver2% host: %database_host2% port: %database_port2% dbname: %database_name2% user: %database_user2% password: %database_password2% charset: UTF8

PDF brought to you by generated on October 26, 2012

Chapter 42: How to work with Multiple Entity Managers and Connections | 315

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. You've also defined two connections, one for each entity manager.
When working with multiple connections and entity managers, you should be explicit about which configuration you want. If you do omit the name of the connection or entity manager, the default (i.e. default) is used.

When working with multiple connections to create your databases:


Listing 42-2

1 2 3 4 5

# Play only with "default" connection $ php app/console doctrine:database:create # Play only with "customer" connection $ php app/console doctrine:database:create --connection=customer

When working with multiple entity managers to update your schema:


Listing 42-3

1 2 3 4 5

# Play only with "default" mappings $ php app/console doctrine:schema:update --force # Play only with "customer" mappings $ php app/console doctrine:schema:update --force --em=customer

If you do omit the entity manager's name when asking for it, the default entity manager (i.e. default) is returned:
Listing 42-4

1 class UserController extends Controller 2 { 3 public function indexAction() 4 { 5 // both return the "default" em 6 $em = $this->get('doctrine')->getManager(); 7 $em = $this->get('doctrine')->getManager('default'); 8 9 $customerEm = $this->get('doctrine')->getManager('customer'); 10 } 11 }

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. The same applies to repository call:
PDF brought to you by generated on October 26, 2012 Chapter 42: How to work with Multiple Entity Managers and Connections | 316

Listing 42-5

1 class UserController extends Controller 2 { 3 public function indexAction() 4 { 5 // Retrieves a repository managed by the "default" em 6 $products = $this->get('doctrine') 7 ->getRepository('AcmeStoreBundle:Product') 8 ->findAll(); 9 10 // Explicit way to deal with the "default" em 11 $products = $this->get('doctrine') 12 ->getRepository('AcmeStoreBundle:Product', 'default') 13 ->findAll(); 14 15 // Retrieves a repository managed by the "customer" em 16 $customers = $this->get('doctrine') 17 ->getRepository('AcmeCustomerBundle:Customer', 'customer') 18 ->findAll(); 19 } 20 }

PDF brought to you by generated on October 26, 2012

Chapter 42: How to work with Multiple Entity Managers and Connections | 317

Chapter 43

How to Register 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:
Listing 43-1

1 # app/config/config.yml 2 doctrine: 3 orm: 4 # ... 5 entity_managers: 6 default: 7 # ... 8 dql: 9 string_functions: 10 test_string: Acme\HelloBundle\DQL\StringFunction 11 second_string: Acme\HelloBundle\DQL\SecondStringFunction 12 numeric_functions: 13 test_numeric: Acme\HelloBundle\DQL\NumericFunction 14 datetime_functions: 15 test_datetime: Acme\HelloBundle\DQL\DatetimeFunction

1. http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/cookbook/dql-user-defined-functions.html

PDF brought to you by generated on October 26, 2012

Chapter 43: How to Register Custom DQL Functions | 318

Chapter 44

How to Define Relationships with Abstract Classes and Interfaces

New in version 2.1: The ResolveTargetEntityListener is new to Doctrine 2.2, which was first packaged with Symfony 2.1.

One of the goals of bundles is to create discreet bundles of functionality that do not have many (if any) dependencies, allowing you to use that functionality in other applications without including unnecessary items. Doctrine 2.2 includes a new utility called the ResolveTargetEntityListener, that functions by intercepting certain calls inside Doctrine and rewriting targetEntity parameters in your metadata mapping at runtime. It means that in your bundle you are able to use an interface or abstract class in your mappings and expect correct mapping to a concrete entity at runtime. This functionality allows you to define relationships between different entities without making them hard dependencies.

Background
Suppose you have an InvoiceBundle which provides invoicing functionality and a CustomerBundle that contains customer management tools. You want to keep these separated, because they can be used in other systems without each other, but for your application you want to use them together. In this case, you have an Invoice entity with a relationship to a non-existent object, an InvoiceSubjectInterface. The goal is to get the ResolveTargetEntityListener to replace any mention of the interface with a real object that implements that interface.

PDF brought to you by generated on October 26, 2012

Chapter 44: How to Define Relationships with Abstract Classes and Interfaces | 319

Set up
Let's use the following basic entities (which are incomplete for brevity) to explain how to set up and use the RTEL. A Customer entity:
Listing 44-1

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

// src/Acme/AppBundle/Entity/Customer.php
namespace Acme\AppBundle\Entity; use Doctrine\ORM\Mapping as ORM; use Acme\CustomerBundle\Entity\Customer as BaseCustomer; use Acme\InvoiceBundle\Model\InvoiceSubjectInterface;

/** * @ORM\Entity * @ORM\Table(name="customer") */ class Customer extends BaseCustomer implements InvoiceSubjectInterface { // In our example, any methods defined in the InvoiceSubjectInterface // are already implemented in the BaseCustomer }

An Invoice entity:
Listing 44-2

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

// src/Acme/InvoiceBundle/Entity/Invoice.php
namespace Acme\InvoiceBundle\Entity; use Doctrine\ORM\Mapping AS ORM; use Acme\InvoiceBundle\Model\InvoiceSubjectInterface;

/** * Represents an Invoice. * * @ORM\Entity * @ORM\Table(name="invoice") */ class Invoice { /** * @ORM\ManyToOne(targetEntity="Acme\InvoiceBundle\Model\InvoiceSubjectInterface") * @var InvoiceSubjectInterface */ protected $subject; }

An InvoiceSubjectInterface:
Listing 44-3

1 // src/Acme/InvoiceBundle/Model/InvoiceSubjectInterface.php 2 3 namespace Acme\InvoiceBundle\Model; 4 5 /** 6 * An interface that the invoice Subject object should implement. 7 * In most circumstances, only a single object should implement

PDF brought to you by generated on October 26, 2012

Chapter 44: How to Define Relationships with Abstract Classes and Interfaces | 320

8 * this interface as the ResolveTargetEntityListener can only 9 * change the target to a single object. 10 */ 11 interface InvoiceSubjectInterface 12 { 13 // List any additional methods that your InvoiceBundle 14 // will need to access on the subject so that you can 15 // be sure that you have access to those methods. 16 17 /** 18 * @return string 19 */ 20 public function getName(); 21 }

Next, you need to configure the listener, which tells the DoctrineBundle about the replacement:
Listing 44-4

1 # app/config/config.yml 2 doctrine: 3 # .... 4 orm: 5 # .... 6 resolve_target_entities: 7 Acme\InvoiceBundle\Model\InvoiceSubjectInterface: Acme\AppBundle\Entity\Customer

Final Thoughts
With the ResolveTargetEntityListener, you are able to decouple your bundles, keeping them usable by themselves, but still being able to define relationships between different objects. By using this method, your bundles will end up being easier to maintain independently.

PDF brought to you by generated on October 26, 2012

Chapter 44: How to Define Relationships with Abstract Classes and Interfaces | 321

Chapter 45

How to implement a simple Registration Form


Some forms have extra fields whose values don't need to be stored in the database. For example, you may want to create a registration form with some extra fields (like a "terms accepted" checkbox field) and embed the form that actually stores the account information.

The simple User model


You have a simple User entity mapped to the database:
Listing 45-1

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

// src/Acme/AccountBundle/Entity/User.php namespace Acme\AccountBundle\Entity;


use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;

/** * @ORM\Entity * @UniqueEntity(fields="email", message="Email already taken") */ class User { /** * @ORM\Id * @ORM\Column(type="integer") * @ORM\GeneratedValue(strategy="AUTO") */ protected $id; /** * @ORM\Column(type="string", length=255) * @Assert\NotBlank() * @Assert\Email() */ protected $email;

PDF brought to you by generated on October 26, 2012

Chapter 45: How to implement a simple Registration Form | 322

27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 }

/** * @ORM\Column(type="string", length=255) * @Assert\NotBlank() */ protected $plainPassword;


public function getId() { return $this->id; } public function getEmail() { return $this->email; } public function setEmail($email) { $this->email = $email; } public function getPlainPassword() { return $this->plainPassword; } public function setPlainPassword($password) { $this->plainPassword = $password; }

This User entity contains three fields and two of them (email and plainPassword) should display on the form. The email property must be unique in the database, this is enforced by adding this validation at the top of the class.
If you want to integrate this User within the security system, you need to implement the UserInterface of the security component.

Create a Form for the Model


Next, create the form for the User model:
Listing 45-2

1 2 3 4 5 6 7 8 9

// src/Acme/AccountBundle/Form/Type/UserType.php namespace Acme\AccountBundle\Form\Type;


use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class UserType extends AbstractType {

PDF brought to you by generated on October 26, 2012

Chapter 45: How to implement a simple Registration Form | 323

10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 }

public function buildForm(FormBuilderInterface $builder, array $options) { $builder->add('email', 'email'); $builder->add('plainPassword', 'repeated', array( 'first_name' => 'password', 'second_name' => 'confirm', 'type' => 'password', )); } public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'data_class' => 'Acme\AccountBundle\Entity\User' )); } public function getName() { return 'user'; }

There are just two fields: email and plainPassword (repeated to confirm the entered password). The data_class option tells the form the name of data class (i.e. your User entity).
To explore more things about the form component, read Forms.

Embedding the User form into a Registration Form


The form that you'll use for the registration page is not the same as the form used to simply modify the User (i.e. UserType). The registration form will contain further fields like "accept the terms", whose value won't be stored in the database. Start by creating a simple class which represents the "registration":
Listing 45-3

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

// src/Acme/AccountBundle/Form/Model/Registration.php namespace Acme\AccountBundle\Form\Model;


use Symfony\Component\Validator\Constraints as Assert; use Acme\AccountBundle\Entity\User; class Registration { /** * @Assert\Type(type="Acme\AccountBundle\Entity\User") */ protected $user;

/** * @Assert\NotBlank() * @Assert\True()

PDF brought to you by generated on October 26, 2012

Chapter 45: How to implement a simple Registration Form | 324

18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 }

*/ protected $termsAccepted;
public function setUser(User $user) { $this->user = $user; } public function getUser() { return $this->user; } public function getTermsAccepted() { return $this->termsAccepted; } public function setTermsAccepted($termsAccepted) { $this->termsAccepted = (Boolean) $termsAccepted; }

Next, create the form for this Registration model:


Listing 45-4

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

// src/Acme/AccountBundle/Form/Type/RegistrationType.php namespace Acme\AccountBundle\Form\Type;


use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; class RegistrationType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder->add('user', new UserType()); $builder->add('terms', 'checkbox', array('property_path' => 'termsAccepted')); } public function getName() { return 'registration'; } }

You don't need to use special method for embedding the UserType form. A form is a field, too - so you can add this like any other field, with the expectation that the Registration.user property will hold an instance of the User class.

Handling the Form Submission


Next, you need a controller to handle the form. Start by creating a simple controller for displaying the registration form:
Listing 45-5

PDF brought to you by generated on October 26, 2012

Chapter 45: How to implement a simple Registration Form | 325

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

// src/Acme/AccountBundle/Controller/AccountController.php namespace Acme\AccountBundle\Controller;


use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Response; use Acme\AccountBundle\Form\Type\RegistrationType; use Acme\AccountBundle\Form\Model\Registration; class AccountController extends Controller { public function registerAction() { $form = $this->createForm(new RegistrationType(), new Registration()); return $this->render('AcmeAccountBundle:Account:register.html.twig', array('form' => $form->createView())); } }

and its template:


Listing 45-6

1 {# src/Acme/AccountBundle/Resources/views/Account/register.html.twig #} 2 <form action="{{ path('create')}}" method="post" {{ form_enctype(form) }}> 3 {{ form_widget(form) }} 4 5 <input type="submit" /> 6 </form>

Finally, create the controller which handles the form submission. This performs the validation and saves the data into the database:
Listing 45-7

1 public function createAction() 2 { 3 $em = $this->getDoctrine()->getEntityManager(); 4 5 $form = $this->createForm(new RegistrationType(), new Registration()); 6 7 $form->bind($this->getRequest()); 8 9 if ($form->isValid()) { 10 $registration = $form->getData(); 11 12 $em->persist($registration->getUser()); 13 $em->flush(); 14 15 return $this->redirect(...); 16 } 17 18 return $this->render('AcmeAccountBundle:Account:register.html.twig', array('form' => 19 $form->createView())); }

That's it! Your form now validates, and allows you to save the User object to the database. The extra terms checkbox on the Registration model class is used during validation, but not actually used afterwards when saving the User to the database.

PDF brought to you by generated on October 26, 2012

Chapter 45: How to implement a simple Registration Form | 326

Chapter 46

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 46-1

1 {{ form_row(form.age) }}

You can also render each of the three parts of the field individually:
Listing 46-2

1 <div> 2 {{ form_label(form.age) }} 3 {{ form_errors(form.age) }} 4 {{ form_widget(form.age) }} 5 </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 46-3

1 <div> 2 <label for="form_age">Age</label> 3 <ul> 4 <li>This field is required</li> 5 </ul> 6 <input type="number" id="form_age" name="form[age]" /> 7 </div>

To quickly prototype and test a form, you can render the entire form with just one line:
Listing 46-4

PDF brought to you by generated on October 26, 2012

Chapter 46: How to customize Form Rendering | 327

1 {{ form_widget(form) }}

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 each 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 an integer type field is rendered, an input number field is generated
Listing 46-5

1 {{ form_widget(form.age) }}

renders:
Listing 46-6

1 <input type="number" id="form_age" name="form[age]" required="required" value="33" />

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 the FrameworkBundle/ Resources/views/Form folder. The default implementation of the integer_widget fragment looks like this:
Listing 46-7

1 {# form_div_layout.html.twig #} 2 {% block integer_widget %} 3 {% set type = type|default('number') %} 4 {{ block('form_widget_simple') }} 5 {% endblock integer_widget %}

As you can see, this fragment itself renders another fragment - form_widget_simple:
Listing 46-8

1 {# form_div_layout.html.twig #} 2 {% block form_widget_simple %} 3 {% set type = type|default('text') %} 4 <input type="{{ type }}" {{ block('widget_attributes') }} {% if value is not empty 5 %}value="{{ value }}" {% endif %}/> {% endblock form_widget_simple %}

1. https://github.com/symfony/symfony/blob/2.1/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig 2. https://github.com/symfony/symfony/blob/2.1/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig

PDF brought to you by generated on October 26, 2012

Chapter 46: How to customize Form Rendering | 328

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 fragments are individual template files in this folder.

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 form_errors fragment. This takes advantage of field type inheritance. Specifically, since the text type extends from the form 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. form_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 Inside the same template as the form Inside a separate template Pros Quick and easy Can be reused by many templates Cons Can't be reused in other templates Requires an extra template to be 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 46-9

1 {% extends '::base.html.twig' %} 2

PDF brought to you by generated on October 26, 2012

Chapter 46: How to customize Form Rendering | 329

3 4 5 6 7 8 9 10 11 12 13 14 15 16

{% form_theme form _self %} {% block integer_widget %} <div class="integer_widget"> {% set type = type|default('number') %} {{ block('form_widget_simple') }} </div> {% endblock %} {% block content %} {# ... 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:
Listing 46-10

1 {# src/Acme/DemoBundle/Resources/views/Form/fields.html.twig #} 2 {% block integer_widget %} 3 <div class="integer_widget"> 4 {% set type = type|default('number') %} 5 {{ block('form_widget_simple') }} 6 </div> 7 {% 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:
Listing 46-11

1 {% form_theme form 'AcmeDemoBundle:Form:fields.html.twig' %} 2 3 {{ 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.

PDF brought to you by generated on October 26, 2012

Chapter 46: How to customize Form Rendering | 330

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.
Listing 46-12

1 <!-- src/Acme/DemoBundle/Resources/views/Form/integer_widget.html.php --> 2 <div class="integer_widget"> 3 <?php echo $view['form']->block($form, 'form_widget_simple', array('type' => 4 isset($type) ? $type : "number")) ?> </div>

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 46-13

1 <?php $view['form']->setTheme($form, array('AcmeDemoBundle:Form')) ;?> 2 3 <?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 then 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 46-14

1 {% use 'form_div_layout.html.twig' with integer_widget as base_integer_widget %}

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 46-15

1 {% block integer_widget %} 2 <div class="integer_widget"> 3 {{ block('base_integer_widget') }} 4 </div> 5 {% 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 46-16

3. https://github.com/symfony/symfony/blob/2.1/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig 4. https://github.com/symfony/symfony/blob/2.1/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig

PDF brought to you by generated on October 26, 2012

Chapter 46: How to customize Form Rendering | 331

1 2 3 4 5 6 7 8

{# src/Acme/DemoBundle/Resources/views/Form/fields.html.twig #} {% extends 'form_div_layout.html.twig' %}


{% block integer_widget %} <div class="integer_widget"> {{ parent() }} </div> {% endblock %}

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.
Listing 46-17

1 # app/config/config.yml 2 twig: 3 form: 4 resources: 5 - 'AcmeDemoBundle:Form:fields.html.twig' 6 # ...

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:
Listing 46-18

1 # app/config/config.yml 2 twig: 3 form: 4 resources: ['form_table_layout.html.twig'] 5 # ...

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 46-19

1 {% form_theme form 'form_table_layout.html.twig' %}

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.
PDF brought to you by generated on October 26, 2012 Chapter 46: How to customize Form Rendering | 332

Listing 46-20

1 # app/config/config.yml 2 framework: 3 templating: 4 form: 5 resources: 6 - 'AcmeDemoBundle:Form' 7 # ...

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 46-21

1 # app/config/config.yml 2 framework: 3 templating: 4 form: 5 resources: 6 - '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 46-22

1 <?php $view['form']->setTheme($form, array('FrameworkBundle:FormTable')); ?>

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 46-23

1 2 3 4 5 6 7 8 9

{% form_theme form _self %} {% block _product_name_widget %} <div class="text_widget"> {{ block('form_widget_simple') }} </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 46-24

PDF brought to you by generated on October 26, 2012

Chapter 46: How to customize Form Rendering | 333

1 2 3 4 5 6 7 8 9 10

{# _product_name_row.html.twig #} {% form_theme form _self %}


{% block _product_name_row %} <div class="name_row"> {{ form_label(form) }} {{ form_errors(form) }} {{ form_widget(form) }} </div> {% endblock %}

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:
Listing 46-25

1 {{ form_errors(form.age) }}

By default, the errors are rendered inside an unordered list:


Listing 46-26

1 <ul> 2 <li>This field is required</li> 3 </ul>

To override how errors are rendered for all fields, simply copy, paste and customize the form_errors fragment.
Listing 46-27

{# form_errors.html.twig #} {% block form_errors %} {% spaceless %} {% if errors|length > 0 %} <ul class="error_list"> {% for error in errors %} <li>{{ error.messagePluralization is null ? error.messageTemplate|trans(error.messageParameters, 'validators') : error.messageTemplate|transchoice(error.messagePluralization, error.messageParameters, 'validators') }}</li> {% endfor %}
PDF brought to you by generated on October 26, 2012 Chapter 46: How to customize Form Rendering | 334

</ul> {% endif %} {% endspaceless %} {% endblock form_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:
Listing 46-28

1 {{ form_errors(form) }}

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 form_errors.

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 form_row fragment. For example, suppose you want to add a class to the div element around each row:
Listing 46-29

1 {# form_row.html.twig #} 2 {% block form_row %} 3 <div class="form_row"> 4 {{ form_label(form) }} 5 {{ form_errors(form) }} 6 {{ form_widget(form) }} 7 </div> 8 {% endblock form_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 form_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 46-30

1 {% use 'form_div_layout.html.twig' with form_label as base_form_label %} 2 3 {% block form_label %} 4 {{ block('base_form_label') }} 5 6 {% if required %}

PDF brought to you by generated on October 26, 2012

Chapter 46: How to customize Form Rendering | 335

7 <span class="required" title="This field is required">*</span> 8 {% endif %} 9 {% endblock %}

In Twig, if you're making the form customization inside a separate template, use the following:
Listing 46-31

1 {% extends 'form_div_layout.html.twig' %} 2 3 {% block form_label %} 4 {{ parent() }} 5 6 {% if required %} 7 <span class="required" title="This field is required">*</span> 8 {% endif %} 9 {% endblock %}

When using PHP as a templating engine you have to copy the content from the original template:
Listing 46-32

1 2 3 4 5 6 7 8 9 10 11 12

<!-- form_label.html.php --> <!-- original content --> <?php if ($required) { $label_attr['class'] = trim((isset($label_attr['class']) ? $label_attr['class'] : '').' required'); } ?> <?php if (!$compound) { $label_attr['for'] = $id; } ?> <?php if (!$label) { $label = $view['form']->humanize($name); } ?> <label <?php foreach ($label_attr as $k => $v) { printf('%s="%s" ', $view->escape($k), $view->escape($v)); } ?>><?php echo $view->escape($view['translator']->trans($label, array(), $translation_domain)) ?></label> <!-- 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:
Listing 46-33

1 {% use 'form_div_layout.html.twig' with form_widget_simple as base_form_widget_simple %} 2 3 {% block form_widget_simple %} 4 {{ block('base_form_widget_simple') }} 5 6 {% if help is defined %} 7 <span class="help">{{ help }}</span> 8 {% endif %} 9 {% endblock %}

PDF brought to you by generated on October 26, 2012

Chapter 46: How to customize Form Rendering | 336

In twig, If you're making the form customization inside a separate template, use the following:
Listing 46-34

1 {% extends 'form_div_layout.html.twig' %} 2 3 {% block form_widget_simple %} 4 {{ parent() }} 5 6 {% if help is defined %} 7 <span class="help">{{ help }}</span> 8 {% endif %} 9 {% endblock %}

When using PHP as a templating engine you have to copy the content from the original template:
Listing 46-35

1 2 3 4 5 6 7 8 9 10 11 12 13

<!-- form_widget_simple.html.php --> <!-- Original content --> <input type="<?php echo isset($type) ? $view->escape($type) : 'text' ?>" <?php if (!empty($value)): ?>value="<?php echo $view->escape($value) ?>"<?php endif ?> <?php echo $view['form']->block($form, 'widget_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:


Listing 46-36

1 {{ form_widget(form.title, {'help': 'foobar'}) }}

See Form Theming for how to apply this customization.

Using Form Variables


Most of the functions available for rendering different parts of a form (e.g. the form widget, form label, form widget, etc) also allow you to make certain customizations directly. Look at the following example:
Listing 46-37

1 {# render a widget, but add a "foo" class to it #} 2 {{ form_widget(form.name, { 'attr': {'class': 'foo'} }) }}

The array passed as the second argument contains form "variables". For more details about this concept in Twig, see More about Form "Variables".

PDF brought to you by generated on October 26, 2012

Chapter 46: How to customize Form Rendering | 337

Chapter 47

How to use 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 might want to add a textbox instead, where the user can simply enter the issue number. You could try to do this in your controller, but it's not the best solution. It would be better if this issue were automatically converted to an Issue object. This is where Data Transformers come into play.

Creating the Transformer


First, create an IssueToNumberTransformer class - this class will be responsible for converting to and from the issue number and the Issue object:
Listing 47-1

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

// src/Acme/TaskBundle/Form/DataTransformer/IssueToNumberTransformer.php namespace Acme\TaskBundle\Form\DataTransformer;


use use use use Symfony\Component\Form\DataTransformerInterface; Symfony\Component\Form\Exception\TransformationFailedException; Doctrine\Common\Persistence\ObjectManager; Acme\TaskBundle\Entity\Issue;

class IssueToNumberTransformer implements DataTransformerInterface { /** * @var ObjectManager */ private $om;

/** * @param ObjectManager $om */ public function __construct(ObjectManager $om)

PDF brought to you by generated on October 26, 2012

Chapter 47: How to use Data Transformers | 338

20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 }

{ $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; }

If you want a new issue to be created when an unknown number is entered, you can instantiate it rather than throwing the TransformationFailedException.

Using the Transformer


Now that you have the transformer built, you just need to add it to your issue field in some form.

PDF brought to you by generated on October 26, 2012

Chapter 47: How to use Data Transformers | 339

You can also use transformers without creating a new custom form type by calling addModelTransformer (or addViewTransformer - see Model and View Transformers) on any field builder:
Listing 47-2

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

use Symfony\Component\Form\FormBuilderInterface; use Acme\TaskBundle\Form\DataTransformer\IssueToNumberTransformer; class TaskType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { // ...

// this assumes that the entity manager was passed in as an option $entityManager = $options['em']; $transformer = new IssueToNumberTransformer($entityManager); // add a normal text field, but add our transformer to it $builder->add( $builder->create('issue', 'text') ->addModelTransformer($transformer) );
}

// ...
}

This example requires that you pass in the entity manager as an option when creating your form. Later, you'll learn how you could create a custom issue field type to avoid needing to do this in your controller:
Listing 47-3

1 $taskForm = $this->createForm(new TaskType(), $task, array( 2 'em' => $this->getDoctrine()->getEntityManager(), 3 ));

Cool, you're done! Your user will be able to enter an issue number into the text field and it will be transformed back into an Issue object. This means that, after a successful bind, the Form framework will pass a real Issue object to Task::setIssue() instead of the issue number. If the issue isn't found, a form error will be created for that field and its error message can be controlled with the invalid_message field option.
Notice that adding a transformer requires using a slightly more complicated syntax when adding the field. The following is wrong, as the transformer would be applied to the entire form, instead of just this field:
Listing 47-4

1 // THIS IS WRONG - TRANSFORMER WILL BE APPLIED TO THE ENTIRE FORM 2 // see above example for correct code 3 $builder->add('issue', 'text') 4 ->addModelTransformer($transformer);

PDF brought to you by generated on October 26, 2012

Chapter 47: How to use Data Transformers | 340

Model and View Transformers


New in version 2.1: The names and method of the transformers were changed in Symfony 2.1. prependNormTransformer became addModelTransformer and appendClientTransformer became addViewTransformer.

In the above example, the transformer was used as a "model" transformer. In fact, there are two different type of transformers and three different types of underlying data. In any form, the 3 different types of data are: 1) Model data - This is the data in the format used in your application (e.g. an Issue object). If you call Form::getData or Form::setData, you're dealing with the "model" data. 2) Norm Data - This is a normalized version of your data, and is commonly the same as your "model" data (though not in our example). It's not commonly used directly. 3) View Data - This is the format that's used to fill in the form fields themselves. It's also the format in which the user will submit the data. When you call Form::bind($data), the $data is in the "view" data format. The 2 different types of transformers help convert to and from each of these types of data: Model transformers: transform: "model data" => "norm data" reverseTransform: "norm data" => "model data" View transformers: transform: "norm data" => "view data" reverseTransform: "view data" => "norm data" Which transformer you need depends on your situation. To use the view transformer, call addViewTransformer.

So why did we use the model transformer?


In our example, the field is a text field, and we always expect a text field to be a simple, scalar format in the "norm" and "view" formats. For this reason, the most appropriate transformer was the "model" transformer (which converts to/from the norm format - string issue number - to the model format - Issue object). The difference between the transformers is subtle and you should always think about what the "norm" data for a field should really be. For example, the "norm" data for a text field is a string, but is a DateTime object for a date field.

Using Transformers in a custom field type


In the above example, you applied the transformer to a normal text field. This was easy, but has two downsides: 1) You need to always remember to apply the transformer whenever you're adding a field for issue numbers 2) You need to worry about passing in the em option whenever you're creating a form that uses the transformer.

PDF brought to you by generated on October 26, 2012

Chapter 47: How to use Data Transformers | 341

Because of these, you may choose to create a create a custom field type. First, create the custom field type class:
Listing 47-5

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47

// src/Acme/TaskBundle/Form/Type/IssueSelectorType.php namespace Acme\TaskBundle\Form\Type;


use use use use use Symfony\Component\Form\AbstractType; Symfony\Component\Form\FormBuilderInterface; Acme\TaskBundle\Form\DataTransformer\IssueToNumberTransformer; Doctrine\Common\Persistence\ObjectManager; Symfony\Component\OptionsResolver\OptionsResolverInterface;

class IssueSelectorType extends AbstractType { /** * @var ObjectManager */ private $om;

/** * @param ObjectManager $om */ public function __construct(ObjectManager $om) { $this->om = $om; }
public function buildForm(FormBuilderInterface $builder, array $options) { $transformer = new IssueToNumberTransformer($this->om); $builder->addModelTransformer($transformer); } public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'invalid_message' => 'The selected issue does not exist', )); } public function getParent() { return 'text'; } public function getName() { return 'issue_selector'; } }

Next, register your type as a service and tag it with form.type so that it's recognized as a custom field type:
Listing 47-6

1 services: 2 acme_demo.type.issue_selector: 3 class: Acme\TaskBundle\Form\Type\IssueSelectorType 4 arguments: ["@doctrine.orm.entity_manager"]

PDF brought to you by generated on October 26, 2012

Chapter 47: How to use Data Transformers | 342

5 6

tags: - { name: form.type, alias: issue_selector }

Now, whenever you need to use your special issue_selector field type, it's quite easy:
Listing 47-7

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

// src/Acme/TaskBundle/Form/Type/TaskType.php namespace Acme\TaskBundle\Form\Type;


use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; class TaskType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('task') ->add('dueDate', null, array('widget' => 'single_text')); ->add('issue', 'issue_selector'); } public function getName() { return 'task'; } }

PDF brought to you by generated on October 26, 2012

Chapter 47: How to use Data Transformers | 343

Chapter 48

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:
Listing 48-1

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

// src/Acme/DemoBundle/Form/Type/ProductType.php namespace Acme\DemoBundle\Form\Type;


use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; class ProductType extends AbstractType { public function buildForm(FormBuilderInterface $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 generated on October 26, 2012 Chapter 48: How to Dynamically Generate Forms Using Form Events | 344

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 48-2

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

// src/Acme/DemoBundle/Form/Type/ProductType.php namespace Acme\DemoBundle\Form\Type;


use Symfony\Component\Form\AbstractType use Symfony\Component\Form\FormBuilderInterface; use Acme\DemoBundle\Form\EventListener\AddNameFieldSubscriber; class ProductType extends AbstractType { public function buildForm(FormBuilderInterface $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 48-3

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

// src/Acme/DemoBundle/Form/EventListener/AddNameFieldSubscriber.php namespace Acme\DemoBundle\Form\EventListener;


use use use use Symfony\Component\Form\Event\DataEvent; Symfony\Component\Form\FormFactoryInterface; Symfony\Component\EventDispatcher\EventSubscriberInterface; Symfony\Component\Form\FormEvents;

class AddNameFieldSubscriber implements EventSubscriberInterface { private $factory; public function __construct(FormFactoryInterface $factory) { $this->factory = $factory;

PDF brought to you by generated on October 26, 2012

Chapter 48: How to Dynamically Generate Forms Using Form Events | 345

16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 }

} 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();

// // // // // if
}

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. (null === $data) { return;

// check if the product object is "new" if (!$data->getId()) { $form->add($this->factory->createNamed('name', 'text')); }


}

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 generated on October 26, 2012

Chapter 48: How to Dynamically Generate Forms Using Form Events | 346

Chapter 49

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 association mapping definition 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 49-1

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

// src/Acme/TaskBundle/Entity/Task.php namespace Acme\TaskBundle\Entity;


use Doctrine\Common\Collections\ArrayCollection; class Task { protected $description; protected $tags; public function __construct() { $this->tags = new ArrayCollection(); } public function getDescription() { return $this->description; }

PDF brought to you by generated on October 26, 2012

Chapter 49: How to Embed a Collection of Forms | 347

22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 }

public function setDescription($description) { $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:
Listing 49-2

1 2 3 4 5 6 7

// src/Acme/TaskBundle/Entity/Tag.php namespace Acme\TaskBundle\Entity;


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:
Listing 49-3

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

// src/Acme/TaskBundle/Form/Type/TagType.php namespace Acme\TaskBundle\Form\Type;


use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class TagType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder->add('name'); } public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'data_class' => 'Acme\TaskBundle\Entity\Tag',

PDF brought to you by generated on October 26, 2012

Chapter 49: How to Embed a Collection of Forms | 348

19 20 21 22 23 24 25 26 }

)); } public function getName() { return 'tag'; }

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 49-4

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

// src/Acme/TaskBundle/Form/Type/TaskType.php namespace Acme\TaskBundle\Form\Type;


use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class TaskType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder->add('description'); $builder->add('tags', 'collection', array('type' => new TagType())); } public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'data_class' => 'Acme\TaskBundle\Entity\Task', )); } public function getName() { return 'task'; } }

In your controller, you'll now initialize a new instance of TaskType:


Listing 49-5

1 2 3 4 5 6 7 8 9 10 11 12 13

// src/Acme/TaskBundle/Controller/TaskController.php namespace Acme\TaskBundle\Controller;


use use use use use Acme\TaskBundle\Entity\Task; Acme\TaskBundle\Entity\Tag; Acme\TaskBundle\Form\Type\TaskType; Symfony\Component\HttpFoundation\Request; Symfony\Bundle\FrameworkBundle\Controller\Controller;

class TaskController extends Controller { public function newAction(Request $request) {

PDF brought to you by generated on October 26, 2012

Chapter 49: How to Embed a Collection of Forms | 349

14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 }

$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); // end dummy code
$form = $this->createForm(new TaskType(), $task);

// process the form on POST if ($request->isMethod('POST')) { $form->bind($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).
Listing 49-6

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

{# src/Acme/TaskBundle/Resources/views/Task/new.html.twig #} {# ... #}
<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.

PDF brought to you by generated on October 26, 2012

Chapter 49: How to Embed a Collection of Forms | 350

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.

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 49-7

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

// src/Acme/TaskBundle/Form/Type/TaskType.php // ...
use Symfony\Component\Form\FormBuilderInterface; public function buildForm(FormBuilderInterface $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 49-8

1 <ul class="tags" data-prototype="{{ form_widget(form.tags.vars.prototype)|e }}"> 2 ... 3 </ul>

1. http://php.net/manual/en/function.ini-set.php

PDF brought to you by generated on October 26, 2012

Chapter 49: How to Embed a Collection of Forms | 351

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.vars.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 49-9

1 {{ form_widget(form.tags.vars.prototype.name)|e }}

On the rendered page, the result will look something like this:
Listing 49-10

1 <ul class="tags" data-prototype="&lt;div&gt;&lt;label class=&quot; required&quot;&gt;__name__&lt;/label&gt;&lt;div id=&quot;task_tags___name__&quot;&gt;&lt;div&gt;&lt;label 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):
Listing 49-11

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

// Get the div that holds the collection of tags 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]).

PDF brought to you by generated on October 26, 2012

Chapter 49: How to Embed a Collection of Forms | 352

New in version 2.1: The placeholder was changed from $$name$$ to __name__ in Symfony 2.1

The actual code needed to make this all work can vary quite a bit, but here's one example:
Listing 49-12

1 function addTagForm(collectionHolder, $newLinkLi) { 2 // Get the data-prototype we explained earlier 3 var prototype = collectionHolder.attr('data-prototype'); 4 5 // Replace '__name__' in the prototype's HTML to 6 // instead be a number based on the current collection's length. 7 var newForm = prototype.replace(/__name__/g, collectionHolder.children().length); 8 9 // Display the form in the page in an li, before the "Add a tag" link li 10 var $newFormLi = $('<li></li>').append(newForm); 11 $newLinkLi.before($newFormLi); 12 }

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 generated on October 26, 2012

Chapter 49: How to Embed a Collection of Forms | 353

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 49-13

1 2 3 4 5 6 7 8

// src/Acme/TaskBundle/Entity/Task.php // ... /** * @ORM\ManyToMany(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 49-14

1 2 3 4 5 6 7 8 9 10 11 12

// src/Acme/TaskBundle/Entity/Task.php // ...
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 49-15

1 2 3 4 5 6 7 8 9 10

// src/Acme/TaskBundle/Entity/Tag.php // ...
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.

PDF brought to you by generated on October 26, 2012

Chapter 49: How to Embed a Collection of Forms | 354

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:
Listing 49-16

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

// src/Acme/TaskBundle/Form/Type/TaskType.php // ... use Symfony\Component\Form\FormBuilderInterface;


public function buildForm(FormBuilderInterface $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:
Listing 49-17

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

jQuery(document).ready(function() { // 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:


Listing 49-18

1 function addTagFormDeleteLink($tagFormLi) { 2 var $removeFormA = $('<a href="#">delete this tag</a>'); 3 $tagFormLi.append($removeFormA); 4 5 $removeFormA.on('click', function(e) {

2. http://docs.doctrine-project.org/en/latest/reference/unitofwork-associations.html

PDF brought to you by generated on October 26, 2012

Chapter 49: How to Embed a Collection of Forms | 355

6 7 8 9 10 11 12 }

// prevent the link from creating a "#" on the URL e.preventDefault(); // remove the li for the tag form $tagFormLi.remove();
});

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 generated on October 26, 2012

Chapter 49: How to Embed a Collection of Forms | 356

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:
Listing 49-19

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45

// src/Acme/TaskBundle/Controller/TaskController.php // ...
public function editAction($id, Request $request) { $em = $this->getDoctrine()->getManager(); $task = $em->getRepository('AcmeTaskBundle:Task')->find($id); if (!$task) { throw $this->createNotFoundException('No task found for is '.$id); } $originalTags = array();

// 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 ($request->isMethod('POST')) { $editForm->bind($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);

PDF brought to you by generated on October 26, 2012

Chapter 49: How to Embed a Collection of Forms | 357

46 // if you wanted to delete the Tag entirely, you can also do that 47 // $em->remove($tag); 48 } 49 50 $em->persist($task); 51 $em->flush(); 52 53 // redirect back to some edit page 54 return $this->redirect($this->generateUrl('task_edit', array('id' => 55 $id))); 56 } 57 } 58 // 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 generated on October 26, 2012

Chapter 49: How to Embed a Collection of Forms | 358

Chapter 50

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>\Form\Type. Make sure the field extends AbstractType1:
Listing 50-1

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

// src/Acme/DemoBundle/Form/Type/GenderType.php namespace Acme\DemoBundle\Form\Type;


use Symfony\Component\Form\AbstractType; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class GenderType extends AbstractType { public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'choices' => array( 'm' => 'Male', 'f' => 'Female', ) )); } public function getParent() {

1. http://api.symfony.com/2.1/Symfony/Component/Form/AbstractType.html

PDF brought to you by generated on October 26, 2012

Chapter 50: How to Create a Custom Form Field Type | 359

21 22 23 24 25 26 27 28 }

return 'choice'; } public function getName() { return 'gender'; }

The location of this file is not important - the Form\Type 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. setDefaultOptions() - 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 form Field Type), 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 finishView() 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 50-2

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

PDF brought to you by generated on October 26, 2012

Chapter 50: How to Create a Custom Form Field Type | 360

1 {# src/Acme/DemoBundle/Resources/views/Form/fields.html.twig #} 2 {% block gender_widget %} 3 {% spaceless %} 4 {% if expanded %} 5 <ul {{ block('widget_container_attributes') }}> 6 {% for child in form %} 7 <li> 8 {{ form_widget(child) }} 9 {{ form_label(child) }} 10 </li> 11 {% endfor %} 12 </ul> 13 {% else %} 14 {# just let the choice widget render the select tag #} 15 {{ block('choice_widget') }} 16 {% endif %} 17 {% endspaceless %} 18 {% 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.
Listing 50-3

1 # app/config/config.yml 2 twig: 3 form: 4 resources: 5 - '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:
Listing 50-4

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

// src/Acme/DemoBundle/Form/Type/AuthorType.php namespace Acme\DemoBundle\Form\Type;


use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; class AuthorType extends AbstractType { public function buildForm(FormBuilderInterface $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 generated on October 26, 2012 Chapter 50: How to Create a Custom Form Field Type | 361

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 50-5

1 # app/config/config.yml 2 parameters: 3 genders: 4 m: Male 5 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 50-6

1 # src/Acme/DemoBundle/Resources/config/services.yml 2 services: 3 form.type.gender: 4 class: Acme\DemoBundle\Form\Type\GenderType 5 arguments: 6 - "%genders%" 7 tags: 8 - { 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 50-7

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

// src/Acme/DemoBundle/Form/Type/GenderType.php namespace Acme\DemoBundle\Form\Type;


use Symfony\Component\OptionsResolver\OptionsResolverInterface;

// ...
class GenderType extends AbstractType { private $genderChoices; public function __construct(array $genderChoices) { $this->genderChoices = $genderChoices; } public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'choices' => $this->genderChoices, )); }

PDF brought to you by generated on October 26, 2012

Chapter 50: How to Create a Custom Form Field Type | 362

24 25 }

// ...

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:
Listing 50-8

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

// src/Acme/DemoBundle/Form/Type/AuthorType.php namespace Acme\DemoBundle\Form\Type;


use Symfony\Component\Form\FormBuilderInterface;

// ...
class AuthorType extends AbstractType { public function buildForm(FormBuilderInterface $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 generated on October 26, 2012

Chapter 50: How to Create a Custom Form Field Type | 363

Chapter 51

How to Create a Form Type Extension


Custom form field types are great when you need field types with a specific purpose, such as a gender selector, or a VAT number input. But sometimes, you don't really need to add new field types - you want to add features on top of existing types. This is where form type extensions come in. Form type extensions have 2 main use-cases: 1. You want to add a generic feature to several types (such as adding a "help" text to every field type); 2. You want to add a specific feature to a single type (such as adding a "download" feature to the "file" field type). In both those cases, it might be possible to achieve your goal with custom form rendering, or custom form field types. But using form type extensions can be cleaner (by limiting the amount of business logic in templates) and more flexible (you can add several type extensions to a single form type). Form type extensions can achieve most of what custom field types can do, but instead of being field types of their own, they plug into existing types. Imagine that you manage a Media entity, and that each media is associated to a file. Your Media form uses a file type, but when editing the entity, you would like to see its image automatically rendered next to the file input. You could of course do this by customizing how this field is rendered in a template. But field type extensions allow you to do this in a nice DRY fashion.

Defining the Form Type Extension


Your first task will be to create the form type extension class. Let's call it ImageTypeExtension. By standard, form extensions usually live in the Form\Extension directory of one of your bundles. When creating a form type extension, you can either implement the FormTypeExtensionInterface1 interface or extend the AbstractTypeExtension2 class. In most cases, it's easier to extend the abstract class:
1. http://api.symfony.com/2.1/Symfony/Component/Form/FormTypeExtensionInterface.html 2. http://api.symfony.com/2.1/Symfony/Component/Form/AbstractTypeExtension.html

PDF brought to you by generated on October 26, 2012

Chapter 51: How to Create a Form Type Extension | 364

Listing 51-1

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

// src/Acme/DemoBundle/Form/Extension/ImageTypeExtension.php namespace Acme\DemoBundle\Form\Extension;


use Symfony\Component\Form\AbstractTypeExtension; class ImageTypeExtension extends AbstractTypeExtension { /** * Returns the name of the type being extended. * * @return string The name of the type being extended */ public function getExtendedType() { return 'file'; } }

The only method you must implement is the getExtendedType function. It is used to indicate the name of the form type that will be extended by your extension.
The value you return in the getExtendedType method corresponds to the value returned by the getName method in the form type class you wish to extend.

In addition to the getExtendedType function, you will probably want to override one of the following methods: buildForm() buildView() setDefaultOptions() finishView()

For more information on what those methods do, you can refer to the Creating Custom Field Types cookbook article.

Registering your Form Type Extension as a Service


The next step is to make Symfony aware of your extension. All you need to do is to declare it as a service by using the form.type_extension tag:
Listing 51-2

1 services: 2 acme_demo_bundle.image_type_extension: 3 class: Acme\DemoBundle\Form\Type\ImageTypeExtension 4 tags: 5 - { name: form.type_extension, alias: file }

The alias key of the tag is the type of field that this extension should be applied to. In your case, as you want to extend the file field type, you will use file as an alias.

PDF brought to you by generated on October 26, 2012

Chapter 51: How to Create a Form Type Extension | 365

Adding the extension Business Logic


The goal of your extension is to display nice images next to file inputs (when the underlying model contains images). For that purpose, let's assume that you use an approach similar to the one described in How to handle File Uploads with Doctrine: you have a Media model with a file property (corresponding to the file field in the form) and a path property (corresponding to the image path in the database):
Listing 51-3

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33

// src/Acme/DemoBundle/Entity/Media.php namespace Acme\DemoBundle\Entity;


use Symfony\Component\Validator\Constraints as Assert; class Media { // ...

/** * @var string The path - typically stored in the database */ private $path; /** * @var \Symfony\Component\HttpFoundation\File\UploadedFile * @Assert\File(maxSize="2M") */ public $file; // ... /** * Get the image url * * @return null|string */ public function getWebPath() { // ... $webPath being the full image url, to be used in templates
return $webPath; }

Your form type extension class will need to do two things in order to extend the file form type: 1. Override the setDefaultOptions method in order to add an image_path option; 2. Override the buildForm and buildView methods in order to pass the image url to the view. The logic is the following: when adding a form field of type file, you will be able to specify a new option: image_path. This option will tell the file field how to get the actual image url in order to display it in the view:
Listing 51-4

1 2 3 4 5 6 7 8 9

// src/Acme/DemoBundle/Form/Extension/ImageTypeExtension.php namespace Acme\DemoBundle\Form\Extension;


use use use use use use Symfony\Component\Form\AbstractTypeExtension; Symfony\Component\Form\FormBuilder; Symfony\Component\Form\FormView; Symfony\Component\Form\FormInterface; Symfony\Component\Form\Util\PropertyPath; Symfony\Component\OptionsResolver\OptionsResolverInterface;

PDF brought to you by generated on October 26, 2012

Chapter 51: How to Create a Form Type Extension | 366

10 11 class ImageTypeExtension extends AbstractTypeExtension 12 { 13 /** 14 * Returns the name of the type being extended. 15 * 16 * @return string The name of the type being extended 17 */ 18 public function getExtendedType() 19 { 20 return 'file'; 21 } 22 23 /** 24 * Add the image_path option 25 * 26 * @param \Symfony\Component\OptionsResolver\OptionsResolverInterface $resolver 27 */ 28 public function setDefaultOptions(OptionsResolverInterface $resolver) 29 { 30 $resolver->setOptional(array('image_path')); 31 } 32 33 /** 34 * Pass the image url to the view 35 * 36 * @param \Symfony\Component\Form\FormView $view 37 * @param \Symfony\Component\Form\FormInterface $form 38 */ 39 public function buildView(FormView $view, FormInterface $form) 40 { 41 if (array_key_exists('image_path', $options)) { 42 $parentData = $form->getParent()->getData(); 43 44 $propertyPath = new PropertyPath($options['image_path']); 45 $imageUrl = $propertyPath->getValue($parentData); 46 // set an "image_url" variable that will be available when rendering this field 47 $view->set('image_url', $imageUrl); 48 } 49 } 50 51 }

Override the File Widget Template Fragment


Each field type is rendered by a template fragment. Those template fragments can be overridden in order to customize form rendering. For more information, you can refer to the What are Form Themes? article. In your extension class, you have added a new variable (image_url), but you still need to take advantage of this new variable in your templates. Specifically, you need to override the file_widget block:
Listing 51-5

1 {# src/Acme/DemoBundle/Resources/views/Form/fields.html.twig #} 2 {% extends 'form_div_layout.html.twig' %} 3 4 {% block file_widget %} 5 {% spaceless %}

PDF brought to you by generated on October 26, 2012

Chapter 51: How to Create a Form Type Extension | 367

6 7 {{ block('form_widget') }} 8 {% if image_url is not null %} 9 <img src="{{ asset(image_url) }}"/> 10 {% endif %} 11 12 {% endspaceless %} 13 {% endblock %}

You will need to change your config file or explicitly specify how you want your form to be themed in order for Symfony to use your overridden block. See What are Form Themes? for more information.

Using the Form Type Extension


From now on, when adding a field of type file in your form, you can specify an image_path option that will be used to display an image next to the file field. For example:
Listing 51-6

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

// src/Acme/DemoBundle/Form/Type/MediaType.php namespace Acme\DemoBundle\Form\Type;


use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; class MediaType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('name', 'text') ->add('file', 'file', array('image_path' => 'webPath')); } public function getName() { return 'media'; } }

When displaying the form, if the underlying model has already been associated with an image, you will see it displayed next to the file input.

PDF brought to you by generated on October 26, 2012

Chapter 51: How to Create a Form Type Extension | 368

Chapter 52

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 52-1

1 2 3 4 5 6 7 8 9 10 11 12 13

// src/Acme/HelloBundle/Entity/Company.php namespace Acme\HelloBundle\Entity;


class Company { private $name; private $website; private private private private } $address; $zipcode; $city; $country;

Listing 52-2

1 2 3 4 5 6 7 8 9 10 11 12 13

// src/Acme/HelloBundle/Entity/Customer.php namespace Acme\HelloBundle\Entity;


class Customer { private $firstName; private $lastName; private private private private } $address; $zipcode; $city; $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.
PDF brought to you by generated on October 26, 2012 Chapter 52: How to use the Virtual Form Field Option | 369

Start by creating a very simple CompanyType and CustomerType:


Listing 52-3

1 2 3 4 5 6 7 8 9 10 11 12 13 14

// src/Acme/HelloBundle/Form/Type/CompanyType.php namespace Acme\HelloBundle\Form\Type;


use Symfony\Component\Form\FormBuilderInterface; class CompanyType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('name', 'text') ->add('website', 'text'); } }

Listing 52-4

1 2 3 4 5 6 7 8 9 10 11 12 13 14

// src/Acme/HelloBundle/Form/Type/CustomerType.php namespace Acme\HelloBundle\Form\Type;


use Symfony\Component\Form\FormBuilderInterface; class CustomerType extends AbstractType { public function buildForm(FormBuilderInterface $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:
Listing 52-5

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

// src/Acme/HelloBundle/Form/Type/LocationType.php namespace Acme\HelloBundle\Form\Type;


use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class LocationType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('address', 'textarea') ->add('zipcode', 'text') ->add('city', 'text') ->add('country', 'text'); } public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'virtual' => true )); }

PDF brought to you by generated on October 26, 2012

Chapter 52: How to use the Virtual Form Field Option | 370

25 26 27 28 29 }

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 setDefaultOptions() method of LocationType and directly start using it in the two original form types. Look at the result:
Listing 52-6

1 2 3 4 5 6 7

// CompanyType public function buildForm(FormBuilderInterface $builder, array $options) { $builder->add('foo', new LocationType(), array( 'data_class' => 'Acme\HelloBundle\Entity\Company' )); }

Listing 52-7

1 2 3 4 5 6 7

// CustomerType public function buildForm(FormBuilderInterface $builder, array $options) { $builder->add('bar', new LocationType(), array( 'data_class' => 'Acme\HelloBundle\Entity\Customer' )); }

With the virtual option set to false (default behavior), the Form Component expects 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 generated on October 26, 2012

Chapter 52: How to use the Virtual Form Field Option | 371

Chapter 53

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:
Listing 53-1

1 2 3 4 5 6 7 8 9 10 11 12

// src/Acme/DemoBundle/Validator/constraints/ContainsAlphanumeric.php namespace Acme\DemoBundle\Validator\Constraints;


use Symfony\Component\Validator\Constraint;

/** * @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.1/Symfony/Component/Validator/Constraint.html 2. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraint.html

PDF brought to you by generated on October 26, 2012

Chapter 53: How to create a Custom Validation Constraint | 372

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 53-2

1 2 3 4 5

// in the base Symfony\Component\Validator\Constraint class 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: validate:
Listing 53-3

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

// src/Acme/DemoBundle/Validator/Constraints/ContainsAlphanumericValidator.php namespace Acme\DemoBundle\Validator\Constraints;


use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; class ContainsAlphanumericValidator extends ConstraintValidator { public function validate($value, Constraint $constraint) { if (!preg_match('/^[a-zA-Za0-9]+$/', $value, $matches)) { $this->context->addViolation($constraint->message, array('%string%' => $value)); } } }

The validate method does not return a value; instead, it adds violations to the validator's context property with an addViolation method call if there are validation failures. Therefore, a value could be considered as being valid if it causes no violations to be added to the context. The first parameter of the addViolation call is the error message to use for that violation.

New in version 2.1: The isValid method was renamed to validate in Symfony 2.1. The setMessage method was also deprecated, in favor of calling addViolation on the context.

Using the new Validator


Using custom validators is very easy, just as the ones provided by Symfony2 itself:
Listing 53-4

1 # src/Acme/BlogBundle/Resources/config/validation.yml 2 Acme\DemoBundle\Entity\AcmeEntity: 3 properties: 4 name:

PDF brought to you by generated on October 26, 2012

Chapter 53: How to create a Custom Validation Constraint | 373

5 6

- NotBlank: ~ - Acme\DemoBundle\Validator\Constraints\ContainsAlphanumeric: ~

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.

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:
Listing 53-5

1 services: 2 validator.unique.your_validator_name: 3 class: Fully\Qualified\Validator\Class\Name 4 tags: 5 - { name: validator.constraint_validator, alias: alias_name }

Your constraint class should now use this alias to reference the appropriate validator:
Listing 53-6

1 public function validatedBy() 2 { 3 return 'alias_name'; 4 }

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:
Listing 53-7

1 public function getTargets() 2 { 3 return self::CLASS_CONSTRAINT; 4 }

With this, the validator validate() method gets an object as its first argument:
Listing 53-8

1 class ProtocolClassValidator extends ConstraintValidator 2 { 3 public function validate($protocol, Constraint $constraint) 4 { 5 if ($protocol->getFoo() != $protocol->getBar()) { 6 $this->context->addViolationAtSubPath('foo', $constraint->message, array(), 7 null); 8 } 9 } }

Note that a class constraint validator is applied to the class itself, and not to the property:

PDF brought to you by generated on October 26, 2012

Chapter 53: How to create a Custom Validation Constraint | 374

Listing 53-9

1 # src/Acme/BlogBundle/Resources/config/validation.yml 2 Acme\DemoBundle\Entity\AcmeEntity: 3 constraints: 4 - ContainsAlphanumeric

PDF brought to you by generated on October 26, 2012

Chapter 53: How to create a Custom Validation Constraint | 375

Chapter 54

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 54-1

1 2 3 4 5 6 7 8 9 10 11 12 13

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

PDF brought to you by generated on October 26, 2012

Chapter 54: How to Master and Create new Environments | 376

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:
Listing 54-2

1 imports: 2 - { resource: config.yml } 3 # ...

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:
Listing 54-3

1 # app/config/config_dev.yml 2 imports: 3 - { resource: config.yml } 4 5 web_profiler: 6 toolbar: true 7 # ...

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:
Listing 54-4

1 http://localhost/app.php 2 http://localhost/app_dev.php

-> *prod* environment -> *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:
Listing 54-5

1 2 3 4 5 6 7 8 9

<?php require_once __DIR__.'/../app/bootstrap_cache.php'; require_once __DIR__.'/../app/AppCache.php'; use Symfony\Component\HttpFoundation\Request; $kernel = new AppCache(new AppKernel('prod', false)); $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 generated on October 26, 2012 Chapter 54: How to Master and Create new Environments | 377

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 54-6

1 doctrine: 2 dbal: 3 logging: "%kernel.debug%" 4 # ...

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 54-7

1 # app/config/config_benchmark.yml 2 imports: 3 - { resource: config_prod.yml } 4 5 framework: 6 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 54-8

PDF brought to you by generated on October 26, 2012

Chapter 54: How to Master and Create new Environments | 378

1 2 3 4 5 6 7 8 9

<?php require_once __DIR__.'/../app/bootstrap.php'; require_once __DIR__.'/../app/AppKernel.php'; use Symfony\Component\HttpFoundation\Request; $kernel = new AppKernel('benchmark', false); $kernel->handle(Request::createFromGlobals())->send();

The new environment is now accessible via:


Listing 54-9

1 http://localhost/app_benchmark.php

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:
1 if (!in_array(@$_SERVER['REMOTE_ADDR'], array('127.0.0.1', '::1'))) { 2 die('You are not allowed to access this file. Check 3 '.basename(__FILE__).' for more information.'); }

Listing 54-10

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:
Listing 54-11

1 app/cache/dev 2 app/cache/prod

- cache directory for the *dev* environment - 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.

PDF brought to you by generated on October 26, 2012

Chapter 54: How to Master and Create new Environments | 379

You can easily change the directory location and name. For more information read the article How to override Symfony's Default Directory Structure.

Going Further
Read the article on How to Set External Parameters in the Service Container.

PDF brought to you by generated on October 26, 2012

Chapter 54: How to Master and Create new Environments | 380

Chapter 55

How to override Symfony's Default Directory Structure


Symfony automatically ships with a default directory structure. You can easily override this directory structure to create your own. The default directory structure is:
Listing 55-1

1 2 3 4 5 6 7 8 9 10 11 12

app/ cache/ config/ logs/ ... src/ ... vendor/ ... web/ app.php ...

Override the cache directory


You can override the cache directory by overriding the getCacheDir method in the AppKernel class of you application:
Listing 55-2

1 2 3 4 5 6 7 8

// app/AppKernel.php // ... class AppKernel extends Kernel { // ...


public function getCacheDir()

PDF brought to you by generated on October 26, 2012

Chapter 55: How to override Symfony's Default Directory Structure | 381

9 10 11 12 }

{ return $this->rootDir.'/'.$this->environment.'/cache/'; }

$this->rootDir is the absolute path to the app directory and $this->environment is the current environment (i.e. dev). In this case we have changed the location of the cache directory to app/ {environment}/cache.
You should keep the cache directory different for each environment, otherwise some unexpected behaviour may happen. Each environment generates its own cached config files, and so each needs its own directory to store those cache files.

Override the logs directory


Overriding the logs directory is the same as overriding the cache directory, the only difference is that you need to override the getLogDir method:
Listing 55-3

1 2 3 4 5 6 7 8 9 10 11 12

// app/AppKernel.php // ... class AppKernel extends Kernel { // ...


public function getLogDir() { return $this->rootDir.'/'.$this->environment.'/logs/'; } }

Here we have changed the location of the directory to app/{environment}/logs.

Override the web directory


If you need to rename or move your web directory, the only thing you need to guarantee is that the path to the app directory is still correct in your app.php and app_dev.php front controllers. If you simply renamed the directory, you're fine. But if you moved it in some way, you may need to modify the paths inside these files:
Listing 55-4

1 require_once __DIR__.'/../Symfony/app/bootstrap.php.cache'; 2 require_once __DIR__.'/../Symfony/app/AppKernel.php';

Some shared hosts have a public_html web directory root. Renaming your web directory from web to public_html is one way to make your Symfony project work on your shared host. Another way is to deploy your application to a directory outside of your web root, delete your public_html directory, and then replace it with a symbolic link to the web in your project.

PDF brought to you by generated on October 26, 2012

Chapter 55: How to override Symfony's Default Directory Structure | 382

If you use the AsseticBundle you need to configure this, so it can use the correct web directory:
Listing 55-5

1 # app/config/config.yml 2 3 # ... 4 assetic: 5 # ... 6 read_from: "%kernel.root_dir%/../../public_html"

Now you just need to dump the assets again and your application should work:
Listing 55-6

1 $ php app/console assetic:dump --env=prod --no-debug

PDF brought to you by generated on October 26, 2012

Chapter 55: How to override Symfony's Default Directory Structure | 383

Chapter 56

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 56-1

1 <VirtualHost *:80> 2 ServerName Symfony2 3 DocumentRoot "/path/to/symfony_2_app/web" 4 DirectoryIndex index.php index.html 5 SetEnv SYMFONY__DATABASE__USER user 6 SetEnv SYMFONY__DATABASE__PASSWORD secret 7 8 <Directory "/path/to/symfony_2_app/web"> 9 AllowOverride All 10 Allow from All 11 </Directory> 12 </VirtualHost>

PDF brought to you by generated on October 26, 2012

Chapter 56: How to Set External Parameters in the Service Container | 384

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. 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:
Listing 56-2

1 $ export SYMFONY__DATABASE__USER=user 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.
Listing 56-3

1 doctrine: 2 dbal: 3 driver 4 dbname: 5 user: 6 password:

pdo_mysql symfony2_project "%database.user%" "%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.
1 2 3 4 5 6 7 8 9 10 <?xml version="1.0" encoding="UTF-8"?> <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>

Listing 56-4

This only works for XML configuration. If you're not using XML, simply import an XML file to take advantage of this functionality:
Listing 56-5

1 # app/config/config.yml 2 imports: 3 - { resource: parameters.xml }

1. http://httpd.apache.org/docs/current/env.html

PDF brought to you by generated on October 26, 2012

Chapter 56: How to Set External Parameters in the Service Container | 385

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.
Listing 56-6

1 # app/config/config.yml 2 imports: 3 - { 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 56-7

1 // app/config/parameters.php 2 include_once('/path/to/drupal/sites/default/settings.php'); 3 $container->setParameter('drupal.database.url', $db_url);

PDF brought to you by generated on October 26, 2012

Chapter 56: How to Set External Parameters in the Service Container | 386

Chapter 57

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 PdoSessionHandler1. To use it, you just need to change some parameters in config.yml (or the configuration format of your choice):
New in version 2.1: In Symfony2.1 the class and namespace are slightly modified. You can now find the session storage classes in the Session\Storage namespace: Symfony\Component\HttpFoundation\Session\Storage. Also note that in Symfony2.1 you should configure handler_id not storage_id like in Symfony2.0. Below, you'll notice that %session.storage.options% is not used anymore.
# app/config/config.yml framework: session: # ... handler_id: session.handler.pdo parameters: pdo.db_options: db_table: db_id_col: db_data_col: db_time_col: services: pdo: class: PDO arguments: dsn:

Listing 57-1

session session_id session_value session_time

"mysql:dbname=mydatabase"

1. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.html

PDF brought to you by generated on October 26, 2012

Chapter 57: How to use PdoSessionStorage to store Sessions in the Database | 387

user: myuser password: mypassword session.handler.pdo: class: Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler arguments: [@pdo, %pdo.db_options%]

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 57-2

pdo: 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 57-3

1 CREATE TABLE `session` ( 2 `session_id` varchar(255) NOT NULL, 3 `session_value` text NOT NULL, 4 `session_time` int(11) NOT NULL, 5 PRIMARY KEY (`session_id`) 6 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

PostgreSQL
For PostgreSQL, the statement should look like this:
Listing 57-4

1 CREATE TABLE session ( 2 session_id character varying(255) NOT NULL, 3 session_value text NOT NULL, 4 session_time integer NOT NULL, 5 CONSTRAINT session_pkey PRIMARY KEY (session_id) 6 );

PDF brought to you by generated on October 26, 2012

Chapter 57: How to use PdoSessionStorage to store Sessions in the Database | 388

Chapter 58

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:
Listing 58-1

1 # app/config/config_prod.yml 2 parameters: 3 router.options.matcher.cache_class: ~ # disable router cache 4 router.options.matcher_class: Symfony\Component\Routing\Matcher\ApacheUrlMatcher

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:
Listing 58-2

1 # app/config/routing.yml 2 hello: 3 pattern: /hello/{name} 4 defaults: { _controller: AcmeDemoBundle:Demo:hello }

1. http://api.symfony.com/2.1/Symfony/Component/Routing/Matcher/ApacheUrlMatcher.html 2. http://api.symfony.com/2.1/Symfony/Component/Routing/Matcher/UrlMatcher.html

PDF brought to you by generated on October 26, 2012

Chapter 58: How to use the Apache Router | 389

Now we generate url_rewrite rules:


Listing 58-3

1 $ php app/console router:dump-apache -e=prod --no-debug

Which should roughly output the following:


Listing 58-4

1 2 3 4 5 6 7

# 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]

You can now rewrite web/.htaccess to use the new rules, so with our example it should look like this:
Listing 58-5

1 <IfModule mod_rewrite.c> 2 RewriteEngine On 3 4 # skip "real" requests 5 RewriteCond %{REQUEST_FILENAME} -f 6 RewriteRule .* - [QSA,L] 7 8 # hello 9 RewriteCond %{REQUEST_URI} ^/hello/([^/]+?)$ 10 RewriteRule .* app.php 11 [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 58-6

1 2 3 4 5 6 7 8 9 10 11 12

// web/app.php
require_once __DIR__.'/../app/bootstrap.php.cache'; require_once __DIR__.'/../app/AppKernel.php'; //require_once __DIR__.'/../app/AppCache.php'; use Symfony\Component\HttpFoundation\ApacheRequest; $kernel = new AppKernel('prod', false); $kernel->loadClassCache(); //$kernel = new AppCache($kernel); $kernel->handle(ApacheRequest::createFromGlobals())->send();

PDF brought to you by generated on October 26, 2012

Chapter 58: How to use the Apache Router | 390

Chapter 59

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:
Listing 59-1

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

// src/Acme/DemoBundle/Listener/AcmeExceptionListener.php namespace Acme\DemoBundle\Listener;


use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; use Symfony\Component\HttpFoundation\Response; 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 = new Response(); $response->setContent($message); $response->setStatusCode($exception->getStatusCode()); // Send our modified response object to the event $event->setResponse($response);
} }

1. http://api.symfony.com/2.1/Symfony/Component/HttpKernel/KernelEvents.html

PDF brought to you by generated on October 26, 2012

Chapter 59: How to create an Event Listener | 391

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.

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 59-2

1 # app/config/config.yml 2 services: 3 kernel.listener.your_listener_name: 4 class: Acme\DemoBundle\Listener\AcmeExceptionListener 5 tags: 6 - { 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 master 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 59-3

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

// src/Acme/DemoBundle/Listener/AcmeRequestListener.php namespace Acme\DemoBundle\Listener;


use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\HttpKernel\HttpKernel; 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; }

// ...
} }

Two types of request are available in the HttpKernelInterface4 interface: HttpKernelInterface::MASTER_REQUEST and HttpKernelInterface::SUB_REQUEST.

2. http://api.symfony.com/2.1/Symfony/Component/HttpKernel/Event/GetResponseForExceptionEvent.html 3. http://api.symfony.com/2.1/Symfony/Component/HttpKernel/KernelEvents.html 4. http://api.symfony.com/2.1/Symfony/Component/HttpKernel/HttpKernelInterface.html

PDF brought to you by generated on October 26, 2012

Chapter 59: How to create an Event Listener | 392

Chapter 60

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.1/Symfony/Component/DependencyInjection/Exception/ScopeWideningInjectionException.html

PDF brought to you by generated on October 26, 2012

Chapter 60: How to work with Scopes | 393

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 60-1

1 # src/Acme/HelloBundle/Resources/config/services.yml 2 services: 3 greeting_card_manager: 4 class: Acme\HelloBundle\Mail\GreetingCardManager 5 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 generated on October 26, 2012 Chapter 60: How to work with Scopes | 394

Listing 60-2

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

// src/Acme/HelloBundle/Mail/Mailer.php namespace Acme\HelloBundle\Mail;


use Symfony\Component\DependencyInjection\ContainerInterface; 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:
Listing 60-3

1 # src/Acme/HelloBundle/Resources/config/services.yml 2 parameters: 3 # ... 4 my_mailer.class: Acme\HelloBundle\Mail\Mailer 5 services: 6 my_mailer: 7 class: "%my_mailer.class%" 8 arguments: 9 - "@service_container" 10 # 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 generated on October 26, 2012

Chapter 60: How to work with Scopes | 395

Chapter 61

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 61-1

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

// src/Acme/MailerBundle/AcmeMailerBundle.php namespace Acme\MailerBundle;


use Symfony\Component\HttpKernel\Bundle\Bundle; use Symfony\Component\DependencyInjection\ContainerBuilder; use Acme\MailerBundle\DependencyInjection\Compiler\CustomCompilerPass; 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 generated on October 26, 2012

Chapter 61: How to work with Compiler Passes in Bundles | 396

Chapter 62

How to use Best Practices for Structuring Bundles


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 Acme\Bundle\BlogBundle Bundle Class Name AcmeBlogBundle

Acme\Bundle\Social\BlogBundle AcmeSocialBlogBundle Acme\BlogBundle AcmeBlogBundle

1. http://symfony.com/PSR0

PDF brought to you by generated on October 26, 2012

Chapter 62: How to use Best Practices for Structuring Bundles | 397

By convention, the getName() method of the bundle class should return the class name.
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 Acme\Social\BlogBundle 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 62-1

1 XXX/... 2 HelloBundle/ 3 HelloBundle.php 4 Controller/ 5 Resources/ 6 meta/ 7 LICENSE 8 config/ 9 doc/ 10 index.rst 11 translations/ 12 views/ 13 public/ 14 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.1/Symfony/Bundle/FrameworkBundle/FrameworkBundle.html

PDF brought to you by generated on October 26, 2012

Chapter 62: How to use Best Practices for Structuring Bundles | 398

Type Commands Controllers Event Listeners Configuration Web Resources Translation files Templates Unit and Functional Tests

Directory Command/ Controller/

Service Container Extensions DependencyInjection/ EventListener/ Resources/config/ Resources/public/ Resources/translations/ Resources/views/ 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 Bundle\HelloBundle\Controller\HelloController. 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 tests should cover at least 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 generated on October 26, 2012

Chapter 62: How to use Best Practices for Structuring Bundles | 399

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.1/Symfony/Bundle/FrameworkBundle/Controller/Controller.html 4. http://api.symfony.com/2.1/Symfony/Component/DependencyInjection/ContainerAwareInterface.html 5. http://api.symfony.com/2.1/Symfony/Component/DependencyInjection/ContainerAware.html 6. http://api.symfony.com/2.1/Symfony/Bundle/FrameworkBundle/Controller/Controller.html

PDF brought to you by generated on October 26, 2012

Chapter 62: How to use Best Practices for Structuring Bundles | 400

The end user can provide values in any configuration file:


Listing 62-2

1 # app/config/config.yml 2 parameters: 3 acme_hello.email.from: fabien@example.com

Retrieve the configuration parameters in your code from the container:


Listing 62-3

1 $container->getParameter('acme_hello.email.from');

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 generated on October 26, 2012

Chapter 62: How to use Best Practices for Structuring Bundles | 401

Chapter 63

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 63-1

1 2 3 4 5 6 7 8 9 10 11 12

// src/Acme/UserBundle/AcmeUserBundle.php namespace Acme\UserBundle;


use Symfony\Component\HttpKernel\Bundle\Bundle; 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.
Despite the method name, there is no parent/child relationship between the bundles, it is just a way to extend and override an existing bundle.

1. https://github.com/friendsofsymfony/fosuserbundle

PDF brought to you by generated on October 26, 2012

Chapter 63: How to use Bundle Inheritance to Override parts of a Bundle | 402

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:
Listing 63-2

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

// src/Acme/UserBundle/Controller/RegistrationController.php namespace Acme\UserBundle\Controller;


use FOS\UserBundle\Controller\RegistrationController 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 generated on October 26, 2012

Chapter 63: How to use Bundle Inheritance to Override parts of a Bundle | 403

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 generated on October 26, 2012

Chapter 63: How to use Bundle Inheritance to Override parts of a Bundle | 404

Chapter 64

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 thirdparty 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 order to override/extend a service, there are two options. First, you can set the parameter holding the service's class name to your own class by setting it in app/config/config.yml. This of course is only possible if the class name is defined as a parameter in the service config of the bundle containing the service. For example, to override the class used for Symfony's translator service, you would override the
PDF brought to you by generated on October 26, 2012 Chapter 64: How to Override any Part of a Bundle | 405

translator.class parameter. Knowing exactly which parameter to override may take some research. For the translator, the parameter is defined and used in the Resources/config/translation.xml file in the core FrameworkBundle:
Listing 64-1

1 # app/config/config.yml 2 parameters: 3 translator.class:

Acme\HelloBundle\Translation\Translator

Secondly, if the class is not available as a parameter, you want to make sure the class is always overridden when your bundle is used, or you need to modify something beyond just the class name, you should use a compiler pass:
Listing 64-2

1 2 3 4 5 6 7 8 9 10 11 12 13 14

// src/Acme/FooBundle/DependencyInjection/Compiler/OverrideServiceCompilerPass.php namespace Acme\DemoBundle\DependencyInjection\Compiler;


use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; class OverrideServiceCompilerPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { $definition = $container->getDefinition('original-service-id'); $definition->setClass('Acme\DemoBundle\YourService'); } }

In this example we fetch the service definition of the original service, and set its class name to our own class. See How to work with Compiler Passes in Bundles for information on how to use compiler passes. If you want to do something beyond just overriding the class - like adding a method call - you can only use the compiler pass method.

Entities & Entity mapping


In progress...

Forms
In order to override a form type, it has to be registered as a service (meaning it is tagged as "form.type"). You can then override it as you would override any service as explained in Services & Configuration. This, of course, will only work if the type is referred to by its alias rather than being instantiated, e.g.:
Listing 64-3

1 $builder->add('name', 'custom_type');

rather than:
Listing 64-4

1 $builder->add('name', new CustomType());

PDF brought to you by generated on October 26, 2012

Chapter 64: How to Override any Part of a Bundle | 406

Validation metadata
In progress...

Translations
In progress...

PDF brought to you by generated on October 26, 2012

Chapter 64: How to Override any Part of a Bundle | 407

Chapter 65

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:
Listing 65-1

1 framework: 2 # ... 3 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 generated on October 26, 2012

Chapter 65: How to expose a Semantic Configuration for a Bundle | 408

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 65-2

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

// Acme/HelloBundle/DependencyInjection/AcmeHelloExtension.php namespace Acme\HelloBundle\DependencyInjection;


use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\DependencyInjection\ContainerBuilder; 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 generated on October 26, 2012

Chapter 65: How to expose a Semantic Configuration for a Bundle | 409

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:
Listing 65-3

1 # app/config/config.yml 2 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:
Listing 65-4

1 # app/config/config.yml 2 acme_hello: 3 foo: fooValue 4 bar: barValue

The array passed to your load() method will look like this:
Listing 65-5

1 array( 2 array( 3 'foo' => 'fooValue', 4 'bar' => 'barValue', 5 ) 6 )

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:
Listing 65-6

1 array( 2 array( 3 'foo' => 'fooValue', 4 'bar' => 'barValue', 5 ), 6 array(

PDF brought to you by generated on October 26, 2012

Chapter 65: How to expose a Semantic Configuration for a Bundle | 410

7 8 9 10 )

'foo' => 'fooDevValue', 'baz' => 'newConfigEntry', ),

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 65-7

1 public function load(array $configs, ContainerBuilder $container) 2 { 3 $config = array(); 4 foreach ($configs as $subConfig) { 5 $config = array_merge($config, $subConfig); 6 } 7 8 // ... now use the flat $config array 9 }

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 65-8

1 2 3 4 5 6 7 8 9 10

use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\Config\FileLocator; public function load(array $configs, ContainerBuilder $container) { // ... prepare your $config variable $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/ config')); $loader->load('services.xml'); }

PDF brought to you by generated on October 26, 2012

Chapter 65: How to expose a Semantic Configuration for a Bundle | 411

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 65-9

1 public function load(array $configs, ContainerBuilder $container) 2 { 3 // ... prepare your $config variable 4 5 $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/ 6 config')); 7 8 if (isset($config['enabled']) && $config['enabled']) { 9 $loader->load('services.xml'); 10 } }

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:
Listing 65-10

1 <!-- src/Acme/HelloBundle/Resources/config/services.xml --> 2 <container xmlns="http://symfony.com/schema/dic/services" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/ 5 dic/services/services-1.0.xsd"> 6 7 <parameters> 8 <parameter key="acme_hello.my_service_type" /> 9 </parameters> 10 11 <services> 12 <service id="acme_hello.my_service" class="Acme\HelloBundle\MyService"> 13 <argument>%acme_hello.my_service_type%</argument> 14 </service> 15 </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:
Listing 65-11

1 public function load(array $configs, ContainerBuilder $container) 2 { 3 // ... prepare your $config variable 4 5 $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/ 6 config')); 7 $loader->load('services.xml'); 8 9 if (!isset($config['my_type'])) { 10 throw new \InvalidArgumentException('The "my_type" option must be set'); 11 }

PDF brought to you by generated on October 26, 2012

Chapter 65: How to expose a Semantic Configuration for a Bundle | 412

12 13 }

$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:
Listing 65-12

1 # app/config/config.yml 2 acme_hello: 3 my_type: foo 4 # ...

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 65-13

1 2 3 4 5 6 7

// src/Acme/HelloBundle/DependencyInjection/Configuration.php namespace Acme\HelloBundle\DependencyInjection;


use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; class Configuration implements ConfigurationInterface

PDF brought to you by generated on October 26, 2012

Chapter 65: How to expose a Semantic Configuration for a Bundle | 413

8 { 9 10 11 12 13 14 15 16 17 18 19 20

public function getConfigTreeBuilder() { $treeBuilder = new TreeBuilder(); $rootNode = $treeBuilder->root('acme_hello'); $rootNode ->children() ->scalarNode('my_type')->defaultValue('bar')->end() ->end(); return $treeBuilder; }

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:
Listing 65-14

1 public function load(array $configs, ContainerBuilder $container) 2 { 3 $configuration = new Configuration(); 4 5 $config = $this->processConfiguration($configuration, $configs); 6 7 // ... 8 }

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. You can read more about this in the Config Component documentation. You can also see it action by checking out some of the core Configuration classes, such as the one from the FrameworkBundle Configuration1 or the TwigBundle Configuration2.

Default Configuration Dump


New in version 2.1: The config:dump-reference command was added in Symfony 2.1

The config:dump-reference command allows a bundle's default configuration to be output to the console in yaml. As long as your bundle's configuration is located in the standard location (YourBundle\DependencyInjection\Configuration) and does not have a __constructor() it will work automatically. If you have a something different your Extension class will have to override the Extension::getConfiguration() method. Have it return an instance of your Configuration. Comments and examples can be added to your configuration nodes using the ->info() and >example() methods:
Listing 65-15

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

PDF brought to you by generated on October 26, 2012

Chapter 65: How to expose a Semantic Configuration for a Bundle | 414

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

// src/Acme/HelloBundle/DependencyExtension/Configuration.php namespace Acme\HelloBundle\DependencyInjection;


use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; class Configuration implements ConfigurationInterface { public function getConfigTreeBuilder() { $treeBuilder = new TreeBuilder(); $rootNode = $treeBuilder->root('acme_hello'); $rootNode ->children() ->scalarNode('my_type') ->defaultValue('bar') ->info('what my_type configures') ->example('example setting') ->end() ->end() ; return $treeBuilder; }

This text appears as yaml comments in the output of the config:dump-reference command.

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:
Listing 65-16

1 2 3 4 5 6 7 8 9 10 11 12 13

// ... use Acme\HelloBundle\DependencyInjection\UnconventionalExtensionClass;


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());
} }

3. http://api.symfony.com/2.1/Symfony/Component/HttpKernel/Bundle/Bundle.html#build()

PDF brought to you by generated on October 26, 2012

Chapter 65: How to expose a Semantic Configuration for a Bundle | 415

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.

PDF brought to you by generated on October 26, 2012

Chapter 65: How to expose a Semantic Configuration for a Bundle | 416

Chapter 66

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 66-1

1 public function registerBundles() 2 { 3 $bundles = array( 4 ..., 5 new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(), 6 ); 7 8 // ... 9 }

Configuration
Before using Swiftmailer, be sure to include its configuration. The only mandatory configuration parameter is transport:
Listing 66-2

1 # app/config/config.yml 2 swiftmailer: 3 transport: smtp 4 encryption: ssl 5 auth_mode: login 6 host: smtp.gmail.com 7 username: your_username 8 password: your_password

1. http://www.swiftmailer.org/

PDF brought to you by generated on October 26, 2012

Chapter 66: How to send an Email | 417

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, file or memory is supported, see How to Spool Email) 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:
Listing 66-3

1 public function indexAction($name) 2 { 3 $message = \Swift_Message::newInstance() 4 ->setSubject('Hello Email') 5 ->setFrom('send@example.com') 6 ->setTo('recipient@example.com') 7 ->setBody($this->renderView('HelloBundle:Hello:email.txt.twig', array('name' => 8 $name))) 9 ; 10 $this->get('mailer')->send($message); 11 12 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 generated on October 26, 2012

Chapter 66: How to send an Email | 418

Chapter 67

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 67-1

1 # app/config/config_dev.yml 2 swiftmailer: 3 transport: gmail 4 username: your_gmail_username 5 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 generated on October 26, 2012

Chapter 67: How to use Gmail to send Emails | 419

Chapter 68

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:
Listing 68-1

1 # app/config/config_test.yml 2 swiftmailer: 3 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:
Listing 68-2

1 # app/config/config_dev.yml 2 swiftmailer: 3 delivery_address: dev@example.com

Now, suppose you're sending an email to recipient@example.com.


Listing 68-3

PDF brought to you by generated on October 26, 2012

Chapter 68: How to Work with Emails During Development | 420

1 public function indexAction($name) 2 { 3 $message = \Swift_Message::newInstance() 4 ->setSubject('Hello Email') 5 ->setFrom('send@example.com') 6 ->setTo('recipient@example.com') 7 ->setBody($this->renderView('HelloBundle:Hello:email.txt.twig', array('name' => 8 $name))) 9 ; 10 $this->get('mailer')->send($message); 11 12 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 68-4

1 # app/config/config_dev.yml 2 web_profiler: 3 intercept_redirects: true

PDF brought to you by generated on October 26, 2012

Chapter 68: How to Work with Emails During Development | 421

Chapter 69

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 or memory is supported by Swiftmailer.

Spool using memory


When you use spooling to store the emails to memory, they will get sent right before the kernel terminates. This means the email only gets sent if the whole request got executed without any unhandled Exception or any errors. To configure swiftmailer with the memory option, use the following configuration:
Listing 69-1

1 # app/config/config.yml 2 swiftmailer: 3 # ... 4 spool: { type: memory }

Spool using a file


In order to use the spool with a file, use the following configuration:
Listing 69-2

1 # app/config/config.yml 2 swiftmailer: 3 # ... 4 spool:

PDF brought to you by generated on October 26, 2012

Chapter 69: How to Spool Email | 422

5 6

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:
Listing 69-3

1 path: "%kernel.root_dir%/spool"

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:
Listing 69-4

1 $ php app/console swiftmailer:spool:send --env=prod

It has an option to limit the number of messages to be sent:


Listing 69-5

1 $ php app/console swiftmailer:spool:send --message-limit=10 --env=prod

You can also set the time limit in seconds:


Listing 69-6

1 $ php app/console swiftmailer:spool:send --time-limit=10 --env=prod

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 generated on October 26, 2012

Chapter 69: How to Spool Email | 423

Chapter 70

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():
Listing 70-1

1 $client = static::createClient(array(), array( 2 'PHP_AUTH_USER' => 'username', 3 'PHP_AUTH_PW' => 'pa$$word', 4 ));

You can also override it on a per request basis:


Listing 70-2

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

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:
Listing 70-3

1 # app/config/config_test.yml 2 security: 3 firewalls: 4 your_firewall_name: 5 http_basic:

PDF brought to you by generated on October 26, 2012

Chapter 70: How to simulate HTTP Authentication in a Functional Test | 424

Chapter 71

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 71-1

1 2 3 4 5 6 7 8

$harry = static::createClient(); $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 71-2

1 2 3 4 5 6 7 8 9 10 11

$harry = static::createClient(); $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 generated on October 26, 2012

Chapter 71: How to test the Interaction of several Clients | 425

Chapter 72

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):
Listing 72-1

1 class HelloControllerTest extends WebTestCase 2 { 3 public function testIndex() 4 { 5 $client = static::createClient(); 6 $crawler = $client->request('GET', '/hello/Fabien'); 7 8 // ... write some assertions about the Response 9 10 // Check that the profiler is enabled 11 if ($profile = $client->getProfile()) { 12 // check the number of requests 13 $this->assertLessThan(10, $profile->getCollector('db')->getQueryCount()); 14 15 // check the time spent in the framework 16 $this->assertLessThan(500, $profile->getCollector('time')->getTotalTime()); 17 } 18 } 19 }

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:
Listing 72-2

1 $this->assertLessThan( 2 30, 3 $profile->get('db')->getQueryCount(),

PDF brought to you by generated on October 26, 2012

Chapter 72: How to use the Profiler in a Functional Test | 426

4 5 );

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 generated on October 26, 2012

Chapter 72: How to use the Profiler in a Functional Test | 427

Chapter 73

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:
Listing 73-1

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

// src/Acme/StoreBundle/Tests/Entity/ProductRepositoryFunctionalTest.php namespace Acme\StoreBundle\Tests\Entity;


use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; class ProductRepositoryFunctionalTest extends WebTestCase { /** * @var \Doctrine\ORM\EntityManager */ private $em; public function setUp() { static::$kernel = static::createKernel(); static::$kernel->boot(); $this->em = static::$kernel->getContainer()->get('doctrine.orm.entity_manager'); } public function testSearchByCategoryName() { $products = $this->em ->getRepository('AcmeStoreBundle:Product')

PDF brought to you by generated on October 26, 2012

Chapter 73: How to test Doctrine Repositories | 428

24 25 26 27 28 29 }

->searchByCategoryName('foo') ; $this->assertCount(1, $products); }

PDF brought to you by generated on October 26, 2012

Chapter 73: How to test Doctrine Repositories | 429

Chapter 74

How to customize the Bootstrap Process before running Tests


Sometimes when running tests, you need to do additional bootstrap work before running those tests. For example, if you're running a functional test and have introduced a new translation resource, then you will need to clear your cache before running those tests. This cookbook covers how to do that. First, add the following file:
Listing 74-1

1 2 3 4 5 6 7 8 9 10

// app/tests.bootstrap.php if (isset($_ENV['BOOTSTRAP_CLEAR_CACHE_ENV'])) { passthru(sprintf( 'php "%s/console" cache:clear --env=%s --no-warmup', __DIR__, $_ENV['BOOTSTRAP_CLEAR_CACHE_ENV'] )); }
require __DIR__.'/bootstrap.php.cache';

Replace the test bootstrap tests.bootstrap.php:


Listing 74-2

file

bootstrap.php.cache

in

app/phpunit.xml.dist

with

1 <!-- app/phpunit.xml.dist --> 2 bootstrap = "tests.bootstrap.php"

Now, you can define in your phpunit.xml.dist file which environment you want the cache to be cleared:
Listing 74-3

1 <!-- app/phpunit.xml.dist --> 2 <php> 3 <env name="BOOTSTRAP_CLEAR_CACHE_ENV" value="test"/> 4 </php>

PDF brought to you by generated on October 26, 2012

Chapter 74: How to customize the Bootstrap Process before running Tests | 430

This now becomes an environment variable (i.e. $_ENV) that's available in the custom bootstrap file (tests.bootstrap.php).

PDF brought to you by generated on October 26, 2012

Chapter 74: How to customize the Bootstrap Process before running Tests | 431

Chapter 75

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 Acme\UserBundle 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.1/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.html 2. http://api.symfony.com/2.1/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.html 3. http://api.symfony.com/2.1/Symfony/Component/Security/Core/User/UserInterface.html

PDF brought to you by generated on October 26, 2012

Chapter 75: How to load Security Users from the Database (the Entity Provider) | 432

Listing 75-1

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59

// src/Acme/UserBundle/Entity/User.php namespace Acme\UserBundle\Entity;


use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Security\Core\User\UserInterface;

/** * Acme\UserBundle\Entity\User * * @ORM\Table(name="acme_users") * @ORM\Entity(repositoryClass="Acme\UserBundle\Entity\UserRepository") */ class User implements UserInterface { /** * @ORM\Column(type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ private $id; /** * @ORM\Column(type="string", length=25, unique=true) */ private $username; /** * @ORM\Column(type="string", length=32) */ private $salt; /** * @ORM\Column(type="string", length=40) */ private $password; /** * @ORM\Column(type="string", length=60, unique=true) */ private $email; /** * @ORM\Column(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 generated on October 26, 2012

Chapter 75: How to load Security Users from the Database (the Entity Provider) | 433

60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 }

/** * @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() { }

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 five following methods: getRoles(), getPassword(), getSalt(), getUsername(), eraseCredentials()

For more details on each of these, see UserInterface5.


New in version 2.1: In Symfony 2.1, the equals method was removed from UserInterface. If you need to override the default implementation of comparison logic, implement the new EquatableInterface6 interface and implement the isEqualTo method.

Listing 75-2

1 // src/Acme/UserBundle/Entity/User.php 2 3 namespace Acme\UserBundle\Entity;

4. http://api.symfony.com/2.1/Symfony/Component/Security/Core/User/UserInterface.html 5. http://api.symfony.com/2.1/Symfony/Component/Security/Core/User/UserInterface.html 6. http://api.symfony.com/2.1/Symfony/Component/Security/Core/User/EquatableInterface.html

PDF brought to you by generated on October 26, 2012

Chapter 75: How to load Security Users from the Database (the Entity Provider) | 434

4 5 6 7 8 9 10 11 12

use Symfony\Component\Security\Core\User\EquatableInterface;

// ...
public function isEqualTo(UserInterface $user) { return $this->username === $user->getUsername(); }

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 75-3

1 2 3 4 5 6 7 8 9 10

mysql> select * from user; +----+----------+----------------------------------+------------------------------------------+-----------| id | username | salt | password | email +----+----------+----------------------------------+------------------------------------------+-----------| 1 | hhamon | 7308e59b97f6957fb42d66f894793079 | 09610f61637408828a35d7debee5b38a8350eebe | hhamon@exam | 2 | jsmith | ce617a6cca9126bf4036ca0c02e82dee | 8390105917f3a3d533815250ed7c64b4594d7ebf | jsmith@exam | 3 | maxime | cd01749bb995dc658fa56ed45458d807 | 9764731e5f7fb944de5fd8efad4949b995b72a3c | maxime@exam | 4 | donald | 6683c2bfd90c0426088402930cadd0f8 | 5c3bcec385f59edcc04490d1db95fdb8673bf612 | donald@exam +----+----------+----------------------------------+------------------------------------------+-----------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 75-4

1 # app/config/security.yml 2 security: 3 encoders: 4 Acme\UserBundle\Entity\User: 5 algorithm: sha1 6 encode_as_base64: false 7 iterations: 1 8 9 role_hierarchy: 10 ROLE_ADMIN: ROLE_USER 11 ROLE_SUPER_ADMIN: [ ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH ] 12 13 providers: 14 administrators: 15 entity: { class: AcmeUserBundle:User, property: username } 16 17 firewalls:

PDF brought to you by generated on October 26, 2012

Chapter 75: How to load Security Users from the Database (the Entity Provider) | 435

18 19 20 21 22 23

admin_area: pattern: ^/admin http_basic: ~ access_control: - { path: ^/admin, roles: ROLE_ADMIN }

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 AdvancedUserInterface7 interface that takes care of checking the user's account status. The AdvancedUserInterface8 extends the UserInterface9 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 AdvancedUserInterface10 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.
Listing 75-5

1 2 3 4 5 6 7 8 9 10 11 12 13

// src/Acme/UserBundle/Entity/User.php namespace Acme\Bundle\UserBundle\Entity; // ... use Symfony\Component\Security\Core\User\AdvancedUserInterface;


class User implements AdvancedUserInterface { // ... public function isAccountNonExpired() { return true;

7. http://api.symfony.com/2.1/Symfony/Component/Security/Core/User/AdvancedUserInterface.html 8. http://api.symfony.com/2.1/Symfony/Component/Security/Core/User/AdvancedUserInterface.html 9. http://api.symfony.com/2.1/Symfony/Component/Security/Core/User/UserInterface.html 10. http://api.symfony.com/2.1/Symfony/Component/Security/Core/User/AdvancedUserInterface.html

PDF brought to you by generated on October 26, 2012

Chapter 75: How to load Security Users from the Database (the Entity Provider) | 436

14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 }

} public function isAccountNonLocked() { return true; } public function isCredentialsNonExpired() { 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 UserProviderInterface11. This interface comes with three methods to implement: loadUserByUsername($username), refreshUser(UserInterface $user), and supportsClass($class). For more details, see UserProviderInterface12. The code below shows the implementation of the UserProviderInterface13 in the UserRepository class:
Listing 75-6

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

// src/Acme/UserBundle/Entity/UserRepository.php namespace Acme\UserBundle\Entity;


use use use use use use Symfony\Component\Security\Core\User\UserInterface; Symfony\Component\Security\Core\User\UserProviderInterface; Symfony\Component\Security\Core\Exception\UsernameNotFoundException; Symfony\Component\Security\Core\Exception\UnsupportedUserException; Doctrine\ORM\EntityRepository; Doctrine\ORM\NoResultException;

class UserRepository extends EntityRepository implements UserProviderInterface { public function loadUserByUsername($username) { $q = $this

11. http://api.symfony.com/2.1/Symfony/Component/Security/Core/User/UserProviderInterface.html 12. http://api.symfony.com/2.1/Symfony/Component/Security/Core/User/UserProviderInterface.html 13. http://api.symfony.com/2.1/Symfony/Component/Security/Core/User/UserProviderInterface.html

PDF brought to you by generated on October 26, 2012

Chapter 75: How to load Security Users from the Database (the Entity Provider) | 437

16 ->createQueryBuilder('u') 17 ->where('u.username = :username OR u.email = :email') 18 ->setParameter('username', $username) 19 ->setParameter('email', $username) 20 ->getQuery() 21 ; 22 23 try { 24 // The Query::getSingleResult() method throws an exception 25 // if there is no record matching the criteria. 26 $user = $q->getSingleResult(); 27 } catch (NoResultException $e) { 28 throw new UsernameNotFoundException(sprintf('Unable to find an active admin 29 AcmeUserBundle:User object identified by "%s".', $username), null, 0, $e); 30 } 31 32 return $user; 33 } 34 35 public function refreshUser(UserInterface $user) 36 { 37 $class = get_class($user); 38 if (!$this->supportsClass($class)) { 39 throw new UnsupportedUserException(sprintf('Instances of "%s" are not 40 supported.', $class)); 41 } 42 43 return $this->loadUserByUsername($user->getUsername()); 44 } 45 46 public function supportsClass($class) 47 { 48 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.
Listing 75-7

1 # app/config/security.yml 2 security: 3 # ... 4 providers: 5 administrators: 6 entity: { class: AcmeUserBundle:User } 7 # ...

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.

PDF brought to you by generated on October 26, 2012

Chapter 75: How to load Security Users from the Database (the Entity Provider) | 438

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.

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 75-8

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

// src/Acme/UserBundle/Entity/User.php namespace Acme\Bundle\UserBundle\Entity;


use Doctrine\Common\Collections\ArrayCollection; // ... class User implements AdvancedUserInterface { /** * @ORM\ManyToMany(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 RoleInterface14 that forces it to have a getRole() method:
Listing 75-9

1 // src/Acme/Bundle/UserBundle/Entity/Group.php 2 namespace Acme\Bundle\UserBundle\Entity; 3 4 use Symfony\Component\Security\Core\Role\RoleInterface;

14. http://api.symfony.com/2.1/Symfony/Component/Security/Core/Role/RoleInterface.html

PDF brought to you by generated on October 26, 2012

Chapter 75: How to load Security Users from the Database (the Entity Provider) | 439

5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50

use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\Mapping as ORM;

/** * @ORM\Table(name="acme_groups") * @ORM\Entity() */ class Group implements RoleInterface { /** * @ORM\Column(name="id", type="integer") * @ORM\Id() * @ORM\GeneratedValue(strategy="AUTO") */ private $id; /** * @ORM\Column(name="name", type="string", length=30) */ private $name; /** * @ORM\Column(name="role", type="string", length=20, unique=true) */ private $role; /** * @ORM\ManyToMany(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:
Listing 75-10

1 2 3 4 5 6

// src/Acme/UserBundle/Entity/UserRepository.php namespace Acme\Bundle\UserBundle\Entity; // ...


class UserRepository extends EntityRepository implements UserProviderInterface

PDF brought to you by generated on October 26, 2012

Chapter 75: How to load Security Users from the Database (the Entity Provider) | 440

7 { 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 }

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();

// ...
}

// ...

The QueryBuilder::leftJoin() method joins and fetches related groups from AcmeUserBundle:User model class when a user is retrieved with his email address or username.

the

PDF brought to you by generated on October 26, 2012

Chapter 75: How to load Security Users from the Database (the Entity Provider) | 441

Chapter 76

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:
Listing 76-1

1 # app/config/security.yml 2 firewalls: 3 main: 4 remember_me: 5 key: "%secret%" 6 lifetime: 31536000 # 365 days in seconds 7 path: / 8 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:
Listing 76-2

1 2 3 4 5 6 7 8 9 10

{# src/Acme/SecurityBundle/Resources/views/Security/login.html.twig #} {% if error %} <div>{{ error.message }}</div> {% endif %}


<form action="{{ path('login_check') }}" method="post"> <label for="username">Username:</label> <input type="text" id="username" name="_username" value="{{ last_username }}" /> <label for="password">Password:</label>

PDF brought to you by generated on October 26, 2012

Chapter 76: How to add "Remember Me" Login Functionality | 442

11 <input 12 13 <input 14 <label 15 16 <input 17 </form>

type="password" id="password" name="_password" /> type="checkbox" id="remember_me" name="_remember_me" checked /> for="remember_me">Keep me logged in</label> type="submit" name="login" />

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 76-3

1 2 3 4 5 6 7 8 9

// ... use Symfony\Component\Security\Core\Exception\AccessDeniedException


public function editAction() { if (false === $this->get('security.context')->isGranted( 'IS_AUTHENTICATED_FULLY' )) { throw new AccessDeniedException();

PDF brought to you by generated on October 26, 2012

Chapter 76: How to add "Remember Me" Login Functionality | 443

10 11 12 13 }

// ...

You can also choose to install and use the optional JMSSecurityExtraBundle1, which can secure your controller using annotations:
Listing 76-4

1 2 3 4 5 6 7 8 9

use JMS\SecurityExtraBundle\Annotation\Secure;

/** * @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 reauthenticate, 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 generated on October 26, 2012

Chapter 76: How to add "Remember Me" Login Functionality | 444

Chapter 77

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 77-1

1 interface VoterInterface 2 { 3 function supportsAttribute($attribute); 4 function supportsClass($class); 5 function vote(TokenInterface $token, $object, array $attributes); 6 }

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.1/Symfony/Component/Security/Core/Authorization/Voter/VoterInterface.html

PDF brought to you by generated on October 26, 2012

Chapter 77: How to implement your own Voter to blacklist IP Addresses | 445

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:
Listing 77-2

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37

// src/Acme/DemoBundle/Security/Authorization/Voter/ClientIpVoter.php namespace Acme\DemoBundle\Security\Authorization\Voter;


use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; 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":
Listing 77-3

PDF brought to you by generated on October 26, 2012

Chapter 77: How to implement your own Voter to blacklist IP Addresses | 446

# src/Acme/AcmeBundle/Resources/config/services.yml services: security.access.blacklist_voter: class: Acme\DemoBundle\Security\Authorization\Voter\ClientIpVoter 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 77-4

1 # app/config/security.yml 2 security: 3 access_decision_manager: 4 # Strategy can be: affirmative, unanimous or consensus 5 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 generated on October 26, 2012

Chapter 77: How to implement your own Voter to blacklist IP Addresses | 447

Chapter 78

How to use 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:
Listing 78-1

1 # app/config/security.yml 2 security: 3 acl: 4 connection: default

PDF brought to you by generated on October 26, 2012

Chapter 78: How to use Access Control Lists (ACLs) | 448

The ACL system requires a connection from either Doctrine DBAL (usable by default) or Doctrine MongoDB (usable with MongoDBAclBundle1). However, that does not mean that you have to use Doctrine ORM or ODM for mapping your domain objects. You can use whatever mapper you like for your objects, be it Doctrine ORM, MongoDB ODM, Propel, raw SQL, etc. 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 78-2

1 $ php app/console init:acl

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 78-3

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35

// src/Acme/DemoBundle/Controller/BlogController.php namespace Acme\DemoBundle\Controller;


use use use use use Symfony\Bundle\FrameworkBundle\Controller\Controller; Symfony\Component\Security\Core\Exception\AccessDeniedException; Symfony\Component\Security\Acl\Domain\ObjectIdentity; Symfony\Component\Security\Acl\Domain\UserSecurityIdentity; Symfony\Component\Security\Acl\Permission\MaskBuilder;

class BlogController { // ... 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

1. https://github.com/IamPersistent/MongoDBAclBundle

PDF brought to you by generated on October 26, 2012

Chapter 78: How to use Access Control Lists (ACLs) | 449

36 37 38 39 40 }

$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 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
Listing 78-4

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

// src/Acme/DemoBundle/Controller/BlogController.php // ...
class BlogController { // ... 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.

PDF brought to you by generated on October 26, 2012

Chapter 78: How to use Access Control Lists (ACLs) | 450

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:
Listing 78-5

1 2 3 4 5 6 7 8

$builder = new MaskBuilder(); $builder ->add('view') ->add('edit') ->add('delete') ->add('undelete') ; $mask = $builder->get(); // int(29)

This integer bitmask can then be used to grant a user the base permissions you added above:
Listing 78-6

1 $identity = new UserSecurityIdentity('johannes', 'Acme\UserBundle\Entity\User'); 2 $acl->insertObjectAce($identity, $mask);

The user is now allowed to view, edit, delete, and un-delete objects.

PDF brought to you by generated on October 26, 2012

Chapter 78: How to use Access Control Lists (ACLs) | 451

Chapter 79

How to use 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 highperformance 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 generated on October 26, 2012 Chapter 79: How to use Advanced ACL Concepts | 452

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 made before any secure 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 VIEW EDIT CREATE Intended Meaning Whether someone is allowed to view the domain object. Integer Bitmasks VIEW, EDIT, OPERATOR, MASTER, or OWNER

Whether someone is allowed to EDIT, OPERATOR, MASTER, or make changes to the domain object. OWNER Whether someone is allowed to create the domain object. CREATE, OPERATOR, MASTER, or OWNER

PDF brought to you by generated on October 26, 2012

Chapter 79: How to use Advanced ACL Concepts | 453

Attribute DELETE UNDELETE

Intended Meaning Whether someone is allowed to delete the domain object. Whether someone is allowed to restore a previously deleted domain object. Whether someone is allowed to perform all of the above actions. Whether someone is allowed to perform all of the above actions, and in addition is allowed to grant any of the above permissions to others. Whether someone owns the domain object. An owner can perform any of the above actions and grant master and owner permissions.

Integer Bitmasks DELETE, OPERATOR, MASTER, or OWNER UNDELETE, OPERATOR, MASTER, or OWNER OPERATOR, MASTER, or OWNER MASTER, or OWNER

OPERATOR MASTER

OWNER

OWNER

Permission Attributes vs. Permission Bitmasks


Attributes are used by the AccessDecisionManager, just like roles. 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 generated on October 26, 2012

Chapter 79: How to use Advanced ACL Concepts | 454

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 classscope 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 generated on October 26, 2012

Chapter 79: How to use Advanced ACL Concepts | 455

Chapter 80

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 configuration:
Listing 80-1

1 access_control: 2 - path: ^/secure 3 roles: ROLE_ADMIN 4 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:
Listing 80-2

1 access_control: 2 - path: ^/login 3 roles: IS_AUTHENTICATED_ANONYMOUSLY 4 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 generated on October 26, 2012

Chapter 80: How to force HTTPS or HTTP for Different URLs | 456

Chapter 81

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 81-1

1 # app/config/security.yml 2 security: 3 firewalls: 4 main: 5 form_login: 6 # the user is redirected here when he/she needs to login 7 login_path: /login 8 9 # if true, forward the user to the login form instead of redirecting 10 use_forward: false 11 12 # submit the login form here 13 check_path: /login_check 14 15 # by default, the login form *must* be a POST, not a GET 16 post_only: true 17 18 # login success redirecting options (read further below) 19 always_use_default_target_path: false 20 default_target_path: / 21 target_path_parameter: _target_path 22 use_referer: false 23 24 # login failure redirecting options (read further below) 25 failure_path: null 26 failure_forward: false 27

PDF brought to you by generated on October 26, 2012

Chapter 81: How to customize your Form Login | 457

28 29 30 31 32 33 34

# field names for the username and password fields username_parameter: _username password_parameter: _password # csrf token options csrf_parameter: intention:
_csrf_token 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 she successfully logs in, she will eventually be sent back to http://www.example.com/admin/ post/18/edit. 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.
As mentioned, by default the user is redirected back to the page he originally requested. Sometimes, this can cause problems, like if a background AJAX request "appears" to be the last visited URL, causing the user to be redirected there. For information on controlling this behavior, see How to change the Default Target Path Behavior.

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:
Listing 81-2

1 # app/config/security.yml 2 security: 3 firewalls: 4 main: 5 form_login: 6 # ... 7 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:
Listing 81-3

1 # app/config/security.yml 2 security: 3 firewalls: 4 main: 5 form_login: 6 # ... 7 always_use_default_target_path: true

PDF brought to you by generated on October 26, 2012

Chapter 81: How to customize your Form Login | 458

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):
Listing 81-4

1 # app/config/security.yml 2 security: 3 firewalls: 4 main: 5 form_login: 6 # ... 7 use_referer:

true

New in version 2.1: As of 2.1, if the referer is equal to the login_path option, the user will be redirected to the default_target_path.

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 account route, use the following:
Listing 81-5

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

{# src/Acme/SecurityBundle/Resources/views/Security/login.html.twig #} {% if error %} <div>{{ error.message }}</div> {% endif %}


<form action="{{ path('login_check') }}" method="post"> <label for="username">Username:</label> <input type="text" id="username" name="_username" value="{{ last_username }}" /> <label for="password">Password:</label> <input type="password" id="password" name="_password" /> <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 81-6

1 # app/config/security.yml 2 security: 3 firewalls: 4 main: 5 form_login: 6 target_path_parameter: redirect_url

PDF brought to you by generated on October 26, 2012

Chapter 81: How to customize your Form Login | 459

Redirecting on Login Failure


In addition to redirecting 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 81-7

1 # app/config/security.yml 2 security: 3 firewalls: 4 main: 5 form_login: 6 # ... 7 failure_path: /login_failure

PDF brought to you by generated on October 26, 2012

Chapter 81: How to customize your Form Login | 460

Chapter 82

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:
Listing 82-1

1 2 3 4 5 6 7 8 9 10 11

// ... use Symfony\Component\Security\Core\Exception\AccessDeniedException;


public function helloAction($name) { if (false === $this->get('security.context')->isGranted('ROLE_ADMIN')) { throw new AccessDeniedException(); }

// ...
}

You can also 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:
Listing 82-2

1 2 3 4 5 6 7 8 9 10

// src/Acme/HelloBundle/Newsletter/NewsletterManager.php namespace Acme\HelloBundle\Newsletter;


class NewsletterManager { public function sendNewsletter() { // ... where you actually do the work }

PDF brought to you by generated on October 26, 2012

Chapter 82: How to secure any Service or Method in your Application | 461

11 12 13 }

// ...

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 82-3

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

namespace Acme\HelloBundle\Newsletter; use Symfony\Component\Security\Core\SecurityContextInterface; class NewsletterManager { protected $securityContext; public function __construct(SecurityContextInterface $securityContext) { $this->securityContext = $securityContext; }

// ...
}

Then in your service configuration, you can inject the service:


Listing 82-4

# src/Acme/HelloBundle/Resources/config/services.yml parameters: newsletter_manager.class: Acme\HelloBundle\Newsletter\NewsletterManager 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 82-5

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

namespace Acme\HelloBundle\Newsletter; use Symfony\Component\Security\Core\Exception\AccessDeniedException; use Symfony\Component\Security\Core\SecurityContextInterface; // ... class NewsletterManager { protected $securityContext; public function __construct(SecurityContextInterface $securityContext) { $this->securityContext = $securityContext; } public function sendNewsletter() { if (false === $this->securityContext->isGranted('ROLE_NEWSLETTER_ADMIN')) {

PDF brought to you by generated on October 26, 2012

Chapter 82: How to secure any Service or Method in your Application | 462

19 20 21 22 23 24 25 26 }

throw new AccessDeniedException(); }

// ...
}

// ...

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):
Listing 82-6

1 # src/Acme/HelloBundle/Resources/config/services.yml 2 # ... 3 4 services: 5 newsletter_manager: 6 # ... 7 tags: 8 - { name: security.secure_service }

You can then achieve the same results as above using an annotation:
Listing 82-7

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

namespace Acme\HelloBundle\Newsletter; use JMS\SecurityExtraBundle\Annotation\Secure; // ... class NewsletterManager {

/** * @Secure(roles="ROLE_NEWSLETTER_ADMIN") */ public function sendNewsletter() { // ... } // ...


}

1. https://github.com/schmittjoh/JMSSecurityExtraBundle

PDF brought to you by generated on October 26, 2012

Chapter 82: How to secure any Service or Method in your Application | 463

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.

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 82-8

1 # app/config/config.yml 2 jms_security_extra: 3 # ... 4 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.

2. https://github.com/schmittjoh/JMSSecurityExtraBundle

PDF brought to you by generated on October 26, 2012

Chapter 82: How to secure any Service or Method in your Application | 464

Chapter 83

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:
Listing 83-1

1 2 3 4 5 6 7 8 9 10 11 12 13 14

// src/Acme/WebserviceUserBundle/Security/User/WebserviceUser.php namespace Acme\WebserviceUserBundle\Security\User;


use Symfony\Component\Security\Core\User\UserInterface; class WebserviceUser implements UserInterface { private $username; private $password; private $salt; private $roles; public function __construct($username, $password, $salt, array $roles) {

1. http://api.symfony.com/2.1/Symfony/Component/Security/Core/User/UserInterface.html

PDF brought to you by generated on October 26, 2012

Chapter 83: How to create a custom User Provider | 465

15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 }

$this->username = $username; $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.1/Symfony/Component/Security/Core/User/UserInterface.html

PDF brought to you by generated on October 26, 2012

Chapter 83: How to create a custom User Provider | 466

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:
Listing 83-2

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41

// src/Acme/WebserviceUserBundle/Security/User/WebserviceUserProvider.php namespace Acme\WebserviceUserBundle\Security\User;


use use use use Symfony\Component\Security\Core\User\UserProviderInterface; Symfony\Component\Security\Core\User\UserInterface; Symfony\Component\Security\Core\Exception\UsernameNotFoundException; Symfony\Component\Security\Core\Exception\UnsupportedUserException;

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) } 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 === 'Acme\WebserviceUserBundle\Security\User\WebserviceUser'; } }

3. http://api.symfony.com/2.1/Symfony/Component/Security/Core/User/UserProviderInterface.html 4. http://api.symfony.com/2.1/Symfony/Component/Security/Core/User/UserProviderInterface.html

PDF brought to you by generated on October 26, 2012

Chapter 83: How to create a custom User Provider | 467

Create a Service for the User Provider


Now we make the user provider available as a service.
Listing 83-3

1 2 3 4 5 6 7

# src/Acme/WebserviceUserBundle/Resources/config/services.yml parameters: webservice_user_provider.class: Acme\WebserviceUserBundle\Security\User\WebserviceUserProvider


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 83-4

1 security: 2 providers: 3 webservice: 4 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 83-5

1 security: 2 encoders: 3 Acme\WebserviceUserBundle\Security\User\WebserviceUser: 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 generated on October 26, 2012

Chapter 83: How to create a custom User Provider | 468

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:
Listing 83-6

1 security: 2 encoders: 3 Acme\WebserviceUserBundle\Security\User\WebserviceUser: 4 algorithm: sha512 5 encode_as_base64: false 6 iterations: 1

5. https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Security/Core/Encoder/MessageDigestPasswordEncoder.php

PDF brought to you by generated on October 26, 2012

Chapter 83: How to create a custom User Provider | 469

Chapter 84

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 generated on October 26, 2012

Chapter 84: How to create a custom Authentication Provider | 470

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.
Listing 84-1

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

// src/Acme/DemoBundle/Security/Authentication/Token/WsseUserToken.php namespace Acme\DemoBundle\Security\Authentication\Token;


use Symfony\Component\Security\Core\Authentication\Token\AbstractToken; 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.
Listing 84-2

1 2 3 4 5 6

// src/Acme/DemoBundle/Security/Firewall/WsseListener.php namespace Acme\DemoBundle\Security\Firewall;


use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\Security\Http\Firewall\ListenerInterface;

3. http://api.symfony.com/2.1/Symfony/Component/Security/Core/Authentication/Token/AbstractToken.html 4. http://api.symfony.com/2.1/Symfony/Component/Security/Core/Authentication/Token/TokenInterface.html 5. http://api.symfony.com/2.1/Symfony/Component/Security/Http/Firewall/ListenerInterface.html 6. http://api.symfony.com/2.1/Symfony/Component/HttpKernel/Event/GetResponseEvent.html

PDF brought to you by generated on October 26, 2012

Chapter 84: How to create a custom Authentication Provider | 471

7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57

use use use use

Symfony\Component\Security\Core\Exception\AuthenticationException; Symfony\Component\Security\Core\SecurityContextInterface; Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; Acme\DemoBundle\Security\Authentication\Token\WsseUserToken;

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(); $wsseRegex = '/UsernameToken Username="([^"]+)", PasswordDigest="([^"]+)", Nonce="([^"]+)", Created="([^"]+)"/'; if (!$request->headers->has('x-wsse') || 1 !== preg_match($wsseRegex, $request->headers->get('x-wsse'), $matches)) { return; } $token = new WsseUserToken(); $token->setUser($matches[1]); $token->digest $token->nonce $token->created = $matches[2]; = $matches[3]; = $matches[4];

try { $authToken = $this->authenticationManager->authenticate($token); $this->securityContext->setToken($authToken); } catch (AuthenticationException $failed) { // ... you might log something here

// To deny the authentication clear the token. This will redirect to the login page. // $this->securityContext->setToken(null); // return; // Deny authentication with a '403 Forbidden' HTTP response $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

PDF brought to you by generated on October 26, 2012

Chapter 84: How to create a custom Authentication Provider | 472

authentication manager. If the proper information is not provided, or the authentication manager throws an AuthenticationException7, a 403 Response is returned.
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.
Listing 84-3

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38

// src/Acme/DemoBundle/Security/Authentication/Provider/WsseProvider.php namespace Acme\DemoBundle\Security\Authentication\Provider;


use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\Exception\NonceExpiredException; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Acme\DemoBundle\Security\Authentication\Token\WsseUserToken; 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)

7. http://api.symfony.com/2.1/Symfony/Component/Security/Core/Exception/AuthenticationException.html 8. http://api.symfony.com/2.1/Symfony/Component/Security/Http/Firewall/AbstractAuthenticationListener.html

PDF brought to you by generated on October 26, 2012

Chapter 84: How to create a custom Authentication Provider | 473

39 { 40 // Expire timestamp after 5 minutes 41 if (time() - strtotime($created) > 300) { 42 return false; 43 } 44 45 // Validate nonce is unique within 5 minutes 46 if (file_exists($this->cacheDir.'/'.$nonce) && 47 file_get_contents($this->cacheDir.'/'.$nonce) + 300 > time()) { 48 throw new NonceExpiredException('Previously used nonce detected'); 49 } 50 file_put_contents($this->cacheDir.'/'.$nonce, time()); 51 52 // Validate Secret 53 $expected = base64_encode(sha1(base64_decode($nonce).$created.$secret, true)); 54 55 return $digest === $expected; 56 } 57 58 public function supports(TokenInterface $token) 59 { 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 84-4

1 2 3 4 5 6 7 8 9 10 11 12

// src/Acme/DemoBundle/DependencyInjection/Security/Factory/WsseFactory.php namespace Acme\DemoBundle\DependencyInjection\Security\Factory;


use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\DefinitionDecorator; use Symfony\Component\Config\Definition\Builder\NodeDefinition; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface; class WsseFactory implements SecurityFactoryInterface {

9. http://api.symfony.com/2.1/Symfony/Component/Security/Core/Authentication/Provider/AuthenticationProviderInterface.html 10. http://api.symfony.com/2.1/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SecurityFactoryInterface.html

PDF brought to you by generated on October 26, 2012

Chapter 84: How to create a custom Authentication Provider | 474

13 public function create(ContainerBuilder $container, $id, $config, $userProvider, 14 $defaultEntryPoint) 15 { 16 $providerId = 'security.authentication.provider.wsse.'.$id; 17 $container 18 ->setDefinition($providerId, new 19 DefinitionDecorator('wsse.security.authentication.provider')) 20 ->replaceArgument(0, new Reference($userProvider)) 21 ; 22 23 $listenerId = 'security.authentication.listener.wsse.'.$id; 24 $listener = $container->setDefinition($listenerId, new 25 DefinitionDecorator('wsse.security.authentication.listener')); 26 27 return array($providerId, $listenerId, $defaultEntryPoint); 28 } 29 30 public function getPosition() 31 { 32 return 'pre_auth'; 33 } 34 35 public function getKey() 36 { 37 return 'wsse'; 38 } 39 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.

11. http://api.symfony.com/2.1/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SecurityFactoryInterface.html 12. http://api.symfony.com/2.1/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.html

PDF brought to you by generated on October 26, 2012

Chapter 84: How to create a custom Authentication Provider | 475

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.
Listing 84-5

# src/Acme/DemoBundle/Resources/config/services.yml services: wsse.security.authentication.provider: class: Acme\DemoBundle\Security\Authentication\Provider\WsseProvider arguments: ['', %kernel.cache_dir%/security/nonces] wsse.security.authentication.listener: class: Acme\DemoBundle\Security\Firewall\WsseListener arguments: [@security.context, @security.authentication.manager]

Now that your services are defined, tell your security context about your factory in your bundle class:
New in version 2.1: Before 2.1, the factory below was added via security.yml instead.

Listing 84-6

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

// src/Acme/DemoBundle/AcmeDemoBundle.php namespace Acme\DemoBundle;


use Acme\DemoBundle\DependencyInjection\Security\Factory\WsseFactory; use Symfony\Component\HttpKernel\Bundle\Bundle; use Symfony\Component\DependencyInjection\ContainerBuilder; class AcmeDemoBundle extends Bundle { public function build(ContainerBuilder $container) { parent::build($container); $extension = $container->getExtension('security'); $extension->addSecurityListenerFactory(new WsseFactory()); } }

You are finished! You can now define parts of your app as under WSSE protection.
Listing 84-7

1 security: 2 firewalls: 3 wsse_secured:

PDF brought to you by generated on October 26, 2012

Chapter 84: How to create a custom Authentication Provider | 476

4 5

pattern: wsse:

/api/.* 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 84-8

1 class WsseFactory implements SecurityFactoryInterface 2 { 3 // ... 4 5 public function addConfiguration(NodeDefinition $node) 6 { 7 $node 8 ->children() 9 ->scalarNode('lifetime')->defaultValue(300) 10 ->end(); 11 } 12 }

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.
Listing 84-9

1 class WsseFactory implements SecurityFactoryInterface 2 { 3 public function create(ContainerBuilder $container, $id, $config, $userProvider, 4 $defaultEntryPoint) 5 { 6 $providerId = 'security.authentication.provider.wsse.'.$id; 7 $container 8 ->setDefinition($providerId, 9 new DefinitionDecorator('wsse.security.authentication.provider')) 10 ->replaceArgument(0, new Reference($userProvider)) 11 ->replaceArgument(2, $config['lifetime']); 12 // ... 13 } 14 15 // ... }

PDF brought to you by generated on October 26, 2012

Chapter 84: How to create a custom Authentication Provider | 477

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.
Listing 84-10

1 security: 2 firewalls: 3 wsse_secured: 4 pattern: 5 wsse:

/api/.* { 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 generated on October 26, 2012

Chapter 84: How to create a custom Authentication Provider | 478

Chapter 85

How to change the Default Target Path Behavior


By default, the security component retains the information of the last request URI in a session variable named _security.target_path. Upon a successful login, the user is redirected to this path, as to help her continue from the last known page she visited. On some occasions, this is unexpected. For example when the last request URI was an HTTP POST against a route which is configured to allow only a POST method, the user is redirected to this route only to get a 404 error. To get around this behavior, you would simply need to extend the ExceptionListener class and override the default method named setTargetPath(). First, override the security.exception_listener.class parameter in your configuration file. This can be done from your main configuration file (in app/config) or from a configuration file being imported from a bundle:
Listing 85-1

1 # src/Acme/HelloBundle/Resources/config/services.yml 2 parameters: 3 # ... 4 security.exception_listener.class: Acme\HelloBundle\Security\Firewall\ExceptionListener

Next, create your own ExceptionListener:


Listing 85-2

1 2 3 4 5 6 7 8 9 10 11

// src/Acme/HelloBundle/Security/Firewall/ExceptionListener.php namespace Acme\HelloBundle\Security\Firewall;


use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Security\Http\Firewall\ExceptionListener as BaseExceptionListener; class ExceptionListener extends BaseExceptionListener { protected function setTargetPath(Request $request) { // Do not save target path for XHR and non-GET requests

PDF brought to you by generated on October 26, 2012

Chapter 85: How to change the Default Target Path Behavior | 479

12 13 14 15 16 17 18 19 }

// You can add any more logic here you want if ($request->isXmlHttpRequest() || 'GET' !== $request->getMethod()) { return; }
$request->getSession()->set('_security.target_path', $request->getUri()); }

Add as much or few logic here as required for your scenario!

PDF brought to you by generated on October 26, 2012

Chapter 85: How to change the Default Target Path Behavior | 480

Chapter 86

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 Side Includes.

Configuration
As seen previously, Symfony2 is smart enough to detect whether it talks to a reverse proxy that understands ESI or not. It works out of the box when you use the Symfony2 reverse proxy, but you need a special configuration to make it work with Varnish. Thankfully, Symfony2 relies on yet another standard written by Akama (Edge Architecture1), so the configuration tips in this chapter can be useful even if you don't use Symfony2.
Varnish only supports the src attribute for ESI tags (onerror and alt attributes are ignored).

First, configure Varnish so that it advertises its ESI support by adding a Surrogate-Capability header to requests forwarded to the backend application:
Listing 86-1

1 sub vcl_recv { 2 set req.http.Surrogate-Capability = "abc=ESI/1.0"; 3 }

Then, optimize Varnish so that it only parses the Response contents when there is at least one ESI tag by checking the Surrogate-Control header that Symfony2 adds automatically:
Listing 86-2

1 sub vcl_fetch { 2 if (beresp.http.Surrogate-Control ~ "ESI/1.0") { 3 unset beresp.http.Surrogate-Control;

1. http://www.w3.org/TR/edge-arch

PDF brought to you by generated on October 26, 2012

Chapter 86: How to use Varnish to speed up my Website | 481

4 5 6 7 8 9 10 }

// for Varnish >= 3.0 set beresp.do_esi = true; // for Varnish < 3.0 // esi; }

Compression with ESI was not supported in Varnish until version 3.0 (read GZIP and Varnish2). If you're not using Varnish 3.0, put a web server in front of Varnish to perform the compression.

Cache Invalidation
You should never need to invalidate cached data because invalidation is already taken into account natively in the HTTP cache models (see Cache Invalidation). Still, Varnish can be configured to accept a special HTTP PURGE method that will invalidate the cache for a given resource:
Listing 86-3

1 2 3 4 5 6 7 8 9 10 11 12

sub vcl_hit { if (req.request == "PURGE") { set obj.ttl = 0s; error 200 "Purged"; } } sub vcl_miss { if (req.request == "PURGE") { error 404 "Not purged"; } }

You must protect the PURGE HTTP method somehow to avoid random people purging your cached data.

2. https://www.varnish-cache.org/docs/3.0/phk/gzip.html

PDF brought to you by generated on October 26, 2012

Chapter 86: How to use Varnish to speed up my Website | 482

Chapter 87

How to Inject Variables into all Templates (i.e. Global Variables)


Sometimes you want a variable to be accessible to all the templates you use. This is possible inside your app/config/config.yml file:
Listing 87-1

1 # app/config/config.yml 2 twig: 3 # ... 4 globals: 5 ga_tracking: UA-xxxxx-x

Now, the variable ga_tracking is available in all Twig templates:


Listing 87-2

1 <p>Our google tracking code is: {{ ga_tracking }} </p>

It's that easy! You can also take advantage of the built-in Service Parameters system, which lets you isolate or reuse the value:
Listing 87-3

; app/config/parameters.yml [parameters] ga_tracking: UA-xxxxx-x

Listing 87-4

1 # app/config/config.yml 2 twig: 3 globals: 4 ga_tracking: "%ga_tracking%"

The same variable is available exactly as before.

PDF brought to you by generated on October 26, 2012

Chapter 87: How to Inject Variables into all Templates (i.e. Global Variables) | 483

More Complex Global Variables


If the global variable you want to set is more complicated - say an object - then you won't be able to use the above method. Instead, you'll need to create a Twig Extension and return the global variable as one of the entries in the getGlobals method.

PDF brought to you by generated on October 26, 2012

Chapter 87: How to Inject Variables into all Templates (i.e. Global Variables) | 484

Chapter 88

How to use PHP instead of Twig for Templates


Even if Symfony2 defaults to Twig for its template engine, you can still use plain PHP code if you want. Both templating engines are supported equally in Symfony2. Symfony2 adds some nice features on top of PHP to make writing templates with PHP more powerful.

Rendering PHP Templates


If you want to use the PHP templating engine, first, make sure to enable it in your application configuration file:
Listing 88-1

1 # app/config/config.yml 2 framework: 3 # ... 4 templating: { engines: ['twig', 'php'] }

You can now render a PHP template instead of a Twig one simply by using the .php extension in the template name instead of .twig. The controller below renders the index.html.php template:
Listing 88-2

1 2 3 4 5 6 7 8

// src/Acme/HelloBundle/Controller/HelloController.php // ...
public function indexAction($name) { return $this->render('AcmeHelloBundle:Hello:index.html.php', array('name' => $name)); }

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.
PDF brought to you by generated on October 26, 2012 Chapter 88: How to use PHP instead of Twig for Templates | 485

The index.html.php template is decorated by layout.html.php, thanks to the extend() call:


Listing 88-3

1 <!-- src/Acme/HelloBundle/Resources/views/Hello/index.html.php --> 2 <?php $view->extend('AcmeHelloBundle::layout.html.php') ?> 3 4 Hello <?php echo $name ?>!

The AcmeHelloBundle::layout.html.php notation sounds familiar, doesn't it? It is the same notation used to reference a template. The :: part simply means that the controller element is empty, so the corresponding file is directly stored under views/. Now, let's have a look at the layout.html.php file:
Listing 88-4

1 2 3 4 5 6

<!-- src/Acme/HelloBundle/Resources/views/layout.html.php --> <?php $view->extend('::base.html.php') ?>


<h1>Hello Application</h1> <?php $view['slots']->output('_content') ?>

The layout is itself decorated by another one (::base.html.php). Symfony2 supports multiple decoration levels: a layout can itself be decorated by another one. When the bundle part of the template name is empty, views are looked for in the app/Resources/views/ directory. This directory store global views for your entire project:
Listing 88-5

1 2 3 4 5 6 7 8 9 10 11

<!-- app/Resources/views/base.html.php --> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title><?php $view['slots']->output('title', 'Hello Application') ?></title> </head> <body> <?php $view['slots']->output('_content') ?> </body> </html>

For both layouts, the $view['slots']->output('_content') expression is replaced by the content of the child template, index.html.php and layout.html.php respectively (more on slots in the next section). As you can see, Symfony2 provides methods on a mysterious $view object. In a template, the $view variable is always available and refers to a special object that provides a bunch of methods that makes the template engine tick.

Working with Slots


A slot is a snippet of code, defined in a template, and reusable in any layout decorating the template. In the index.html.php template, define a title slot:
Listing 88-6

1 <!-- src/Acme/HelloBundle/Resources/views/Hello/index.html.php --> 2 <?php $view->extend('AcmeHelloBundle::layout.html.php') ?> 3 4 <?php $view['slots']->set('title', 'Hello World Application') ?>

PDF brought to you by generated on October 26, 2012

Chapter 88: How to use PHP instead of Twig for Templates | 486

5 6 Hello <?php echo $name ?>!

The base layout already has the code to output the title in the header:
Listing 88-7

1 <!-- app/Resources/views/base.html.php --> 2 <head> 3 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 4 <title><?php $view['slots']->output('title', 'Hello Application') ?></title> 5 </head>

The output() method inserts the content of a slot and optionally takes a default value if the slot is not defined. And _content is just a special slot that contains the rendered child template. For large slots, there is also an extended syntax:
Listing 88-8

1 <?php $view['slots']->start('title') ?> 2 Some large amount of HTML 3 <?php $view['slots']->stop() ?>

Including other Templates


The best way to share a snippet of template code is to define a template that can then be included into other templates. Create a hello.html.php template:
Listing 88-9

1 <!-- src/Acme/HelloBundle/Resources/views/Hello/hello.html.php --> 2 Hello <?php echo $name ?>!

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


Listing 88-10

1 <!-- src/Acme/HelloBundle/Resources/views/Hello/index.html.php --> 2 <?php $view->extend('AcmeHelloBundle::layout.html.php') ?> 3 4 <?php echo $view->render('AcmeHelloBundle:Hello:hello.html.php', array('name' => $name)) ?>

The render() method evaluates and returns the content of another template (this is the exact same method as the one used in the controller).

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. If you create a fancy action, and want to include it into the index.html.php template, simply use the following code:
Listing 88-11

1 <!-- src/Acme/HelloBundle/Resources/views/Hello/index.html.php --> 2 <?php echo $view['actions']->render('AcmeHelloBundle:Hello:fancy', array('name' => $name, 'color' => 'green')) ?>

PDF brought to you by generated on October 26, 2012

Chapter 88: How to use PHP instead of Twig for Templates | 487

Here, the AcmeHelloBundle:Hello:fancy string refers to the fancy action of the Hello controller:
Listing 88-12

1 2 3 4 5 6 7 8 9 10 11 12 13 14

// src/Acme/HelloBundle/Controller/HelloController.php
class HelloController extends Controller { public function fancyAction($name, $color) { // create some object, based on the $color variable $object = ...; return $this->render('AcmeHelloBundle:Hello:fancy.html.php', array('name' => $name, 'object' => $object)); }

// ...
}

But where is the $view['actions'] array element defined? Like $view['slots'], it's called a template helper, and the next section tells you more about those.

Using Template Helpers


The Symfony2 templating system can be easily extended via helpers. Helpers are PHP objects that provide features useful in a template context. actions and slots are two of the built-in Symfony2 helpers.

Creating Links between Pages


Speaking of web applications, creating links between pages is a must. Instead of hardcoding URLs in templates, the router helper knows how to generate URLs based on the routing configuration. That way, all your URLs can be easily updated by changing the configuration:
Listing 88-13

1 <a href="<?php echo $view['router']->generate('hello', array('name' => 'Thomas')) ?>"> 2 Greet Thomas! 3 </a>

The generate() method 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 88-14

1 # src/Acme/HelloBundle/Resources/config/routing.yml 2 hello: # The route name 3 pattern: /hello/{name} 4 defaults: { _controller: AcmeHelloBundle:Hello:index }

Using Assets: images, JavaScripts, and stylesheets


What would the Internet be without images, JavaScripts, and stylesheets? Symfony2 provides the assets tag to deal with them easily:
Listing 88-15

1 <link href="<?php echo $view['assets']->getUrl('css/blog.css') ?>" rel="stylesheet" 2 type="text/css" /> 3

PDF brought to you by generated on October 26, 2012

Chapter 88: How to use PHP instead of Twig for Templates | 488

<img src="<?php echo $view['assets']->getUrl('images/logo.png') ?>" />

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

Output Escaping
When using PHP templates, escape variables whenever they are displayed to the user:
Listing 88-16

1 <?php echo $view->escape($var) ?>

By default, the escape() method assumes that the variable is outputted within an HTML context. The second argument lets you change the context. For instance, to output something in a JavaScript script, use the js context:
Listing 88-17

1 <?php echo $view->escape($var, 'js') ?>

PDF brought to you by generated on October 26, 2012

Chapter 88: How to use PHP instead of Twig for Templates | 489

Chapter 89

How to write a custom Twig Extension


The main motivation for writing an extension is to move often used code into a reusable class like adding support for internationalization. An extension can define tags, filters, tests, operators, global variables, functions, and node visitors. Creating an extension also makes for a better separation of code that is executed at compilation time and code needed at runtime. As such, it makes your code faster.
Before writing your own extensions, have a look at the Twig official extension repository1.

Create the Extension Class


To get your custom functionality you must first create a Twig Extension class. As an example we will create a price filter to format a given number into price:
Listing 89-1

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

// src/Acme/DemoBundle/Twig/AcmeExtension.php namespace Acme\DemoBundle\Twig;


class AcmeExtension extends \Twig_Extension { public function getFilters() { return array( 'price' => new \Twig_Filter_Method($this, 'priceFilter'), ); } public function priceFilter($number, $decimals = 0, $decPoint = '.', $thousandsSep = ',') {

1. http://github.com/fabpot/Twig-extensions

PDF brought to you by generated on October 26, 2012

Chapter 89: How to write a custom Twig Extension | 490

16 17 18 19 20 21 22 23 24 25 }

$price = number_format($number, $decimals, $decPoint, $thousandsSep); $price = '$' . $price; return $price; } public function getName() { return 'acme_extension'; }

Along with custom filters, you can also add custom functions and register global variables.

Register an Extension as a Service


Now you must let Service Container know about your newly created Twig Extension:
Listing 89-2

1 <!-- src/Acme/DemoBundle/Resources/config/services.xml --> 2 <services> 3 <service id="acme.twig.acme_extension" class="Acme\DemoBundle\Twig\AcmeExtension"> 4 <tag name="twig.extension" /> 5 </service> 6 </services>

Keep in mind that Twig Extensions are not lazily loaded. This means that there's a higher chance that you'll get a CircularReferenceException or a ScopeWideningInjectionException if any services (or your Twig Extension in this case) are dependent on the request service. For more information take a look at How to work with Scopes.

Using the custom Extension


Using your newly created Twig Extension is no different than any other:
Listing 89-3

1 {# outputs $5,500.00 #} 2 {{ '5500'|price }}

Passing other arguments to your filter:


Listing 89-4

1 {# outputs $5500,2516 #} 2 {{ '5500.25155'|price(4, ',', '') }}

PDF brought to you by generated on October 26, 2012

Chapter 89: How to write a custom Twig Extension | 491

Learning further
For a more in-depth look into Twig Extensions, please take a look at the Twig extensions documentation2.

2. http://twig.sensiolabs.org/doc/advanced.html#creating-an-extension

PDF brought to you by generated on October 26, 2012

Chapter 89: How to write a custom Twig Extension | 492

Chapter 90

How to use Monolog to write Logs


Monolog1 is a logging library for PHP 5.3 used by Symfony2. It is inspired by the Python LogBook library.

Usage
In Monolog each logger defines a logging channel. Each channel has a stack of handlers to write the logs (the handlers can be shared).
When injecting the logger in a service you can use a custom channel to see easily which part of the application logged the message.

The basic handler is the StreamHandler which writes logs in a stream (by default in the app/logs/ prod.log in the prod environment and app/logs/dev.log in the dev environment). Monolog comes also with a powerful built-in handler for the logging in prod environment: FingersCrossedHandler. It allows you to store the messages in a buffer and to log them only if a message reaches the action level (ERROR in the configuration provided in the standard edition) by forwarding the messages to another handler. To log a message simply get the logger service from the container in your controller:
Listing 90-1

1 $logger = $this->get('logger'); 2 $logger->info('We just got the logger'); 3 $logger->err('An error occurred');

Using only the methods of the LoggerInterface2 interface allows to change the logger implementation without changing your code.

1. https://github.com/Seldaek/monolog 2. http://api.symfony.com/2.1/Symfony/Component/HttpKernel/Log/LoggerInterface.html

PDF brought to you by generated on October 26, 2012

Chapter 90: How to use Monolog to write Logs | 493

Using several handlers


The logger uses a stack of handlers which are called successively. This allows you to log the messages in several ways easily.
Listing 90-2

1 # app/config/config*.yml 2 monolog: 3 handlers: 4 syslog: 5 type: stream 6 path: /var/log/symfony.log 7 level: error 8 main: 9 type: fingers_crossed 10 action_level: warning 11 handler: file 12 file: 13 type: stream 14 level: debug

The above configuration defines a stack of handlers which will be called in the order where they are defined.
The handler named "file" will not be included in the stack itself as it is used as a nested handler of the fingers_crossed handler.

If you want to change the config of MonologBundle in another config file you need to redefine the whole stack. It cannot be merged because the order matters and a merge does not allow to control the order.

Changing the formatter


The handler uses a Formatter to format the record before logging it. All Monolog handlers use an instance of Monolog\Formatter\LineFormatter by default but you can replace it easily. Your formatter must implement Monolog\Formatter\FormatterInterface.
Listing 90-3

1 # app/config/config.yml 2 services: 3 my_formatter: 4 class: Monolog\Formatter\JsonFormatter 5 monolog: 6 handlers: 7 file: 8 type: stream 9 level: debug 10 formatter: my_formatter

Adding some extra data in the log messages


Monolog allows to process the record before logging it to add some extra data. A processor can be applied for the whole handler stack or only for a specific handler.

PDF brought to you by generated on October 26, 2012

Chapter 90: How to use Monolog to write Logs | 494

A processor is simply a callable receiving the record as its first argument. Processors are configured using the monolog.processor DIC tag. See the reference about it.

Adding a Session/Request Token


Sometimes it is hard to tell which entries in the log belong to which session and/or request. The following example will add a unique token for each request using a processor.
Listing 90-4

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29

namespace Acme\MyBundle; use Symfony\Component\HttpFoundation\Session\Session; class SessionRequestProcessor { private $session; private $token; public function __construct(Session $session) { $this->session = $session; } public function processRecord(array $record) { if (null === $this->token) { try { $this->token = substr($this->session->getId(), 0, 8); } catch (\RuntimeException $e) { $this->token = '????????'; } $this->token .= '-' . substr(uniqid(), -8); } $record['extra']['token'] = $this->token; return $record; } }

Listing 90-5

# app/config/config.yml services: monolog.formatter.session_request: class: Monolog\Formatter\LineFormatter arguments: - "[%%datetime%%] [%%extra.token%%] %%channel%%.%%level_name%%: %%message%%\n" monolog.processor.session_request: class: Acme\MyBundle\SessionRequestProcessor arguments: [ @session ] tags: - { name: monolog.processor, method: processRecord } monolog: handlers: main: type: stream path: "%kernel.logs_dir%/%kernel.environment%.log" level: debug formatter: monolog.formatter.session_request

PDF brought to you by generated on October 26, 2012

Chapter 90: How to use Monolog to write Logs | 495

If you use several handlers, you can also register the processor at the handler level instead of globally.

PDF brought to you by generated on October 26, 2012

Chapter 90: How to use Monolog to write Logs | 496

Chapter 91

How to Configure Monolog to Email Errors


Monolog1 can be configured to send an email when an error occurs with an application. The configuration for this requires a few nested handlers in order to avoid receiving too many emails. This configuration looks complicated at first but each handler is fairly straight forward when it is broken down.
Listing 91-1

1 # app/config/config.yml 2 monolog: 3 handlers: 4 mail: 5 type: fingers_crossed 6 action_level: critical 7 handler: buffered 8 buffered: 9 type: buffer 10 handler: swift 11 swift: 12 type: swift_mailer 13 from_email: error@example.com 14 to_email: error@example.com 15 subject: An Error Occurred! 16 level: debug

The mail handler is a fingers_crossed handler which means that it is only triggered when the action level, in this case critical is reached. It then logs everything including messages below the action level. The critical level is only triggered for 5xx HTTP code errors. The handler setting means that the output is then passed onto the buffered handler.
If you want both 400 level and 500 level errors to trigger an email, set the action_level to error instead of critical.

The buffered handler simply keeps all the messages for a request and then passes them onto the nested handler in one go. If you do not use this handler then each message will be emailed separately. This is
1. https://github.com/Seldaek/monolog

PDF brought to you by generated on October 26, 2012

Chapter 91: How to Configure Monolog to Email Errors | 497

then passed to the swift handler. This is the handler that actually deals with emailing you the error. The settings for this are straightforward, the to and from addresses and the subject. You can combine these handlers with other handlers so that the errors still get logged on the server as well as the emails being sent:
Listing 91-2

1 # app/config/config.yml 2 monolog: 3 handlers: 4 main: 5 type: fingers_crossed 6 action_level: critical 7 handler: grouped 8 grouped: 9 type: group 10 members: [streamed, buffered] 11 streamed: 12 type: stream 13 path: "%kernel.logs_dir%/%kernel.environment%.log" 14 level: debug 15 buffered: 16 type: buffer 17 handler: swift 18 swift: 19 type: swift_mailer 20 from_email: error@example.com 21 to_email: error@example.com 22 subject: An Error Occurred! 23 level: debug

This uses the group handler to send the messages to the two group members, the buffered and the stream handlers. The messages will now be both written to the log file and emailed.

PDF brought to you by generated on October 26, 2012

Chapter 91: How to Configure Monolog to Email Errors | 498

Chapter 92

How to log Messages to different Files

New in version 2.1: The ability to specify channels for a specific handler was added to the MonologBundle for Symfony 2.1.

The Symfony Standard Edition contains a bunch of channels for logging: doctrine, event, security and request. Each channel corresponds to a logger service (monolog.logger.XXX) in the container and is injected to the concerned service. The purpose of channels is to be able to organize different types of log messages. By default, Symfony2 logs every messages into a single file (regardless of the channel).

Switching a Channel to a different Handler


Now, suppose you want to log the doctrine channel to a different file. To do so, just create a new handler and configure it like this:
Listing 92-1

1 monolog: 2 handlers: 3 main: 4 type: stream 5 path: /var/log/symfony.log 6 channels: !doctrine 7 doctrine: 8 type: stream 9 path: /var/log/doctrine.log 10 channels: doctrine

Yaml specification
You can specify the configuration by many forms:
PDF brought to you by generated on October 26, 2012 Chapter 92: How to log Messages to different Files | 499

Listing 92-2

1 2 3 4 5 6 7 8 9 10 11 12 13 14

channels: ~

# Include all the channels

channels: foo # Include only channel "foo" channels: !foo # Include all channels, except "foo" channels: [foo, bar] # Include only channels "foo" and "bar" channels: [!foo, !bar] # Include all channels, except "foo" and "bar" channels: type: elements: channels: type: elements: inclusive # Include only those listed below [ foo, bar ] exclusive # Include all, except those listed below [ foo, bar ]

Creating your own Channel


You can change the channel monolog logs to one service at a time. This is done by tagging your service with monolog.logger and specifying which channel the service should log to. By doing this, the logger that is injected into that service is preconfigured to use the channel you've specified. For more information - including a full example - read "monolog.logger" in the Dependency Injection Tags reference section.

Learn more from the Cookbook


How to use Monolog to write Logs

PDF brought to you by generated on October 26, 2012

Chapter 92: How to log Messages to different Files | 500

Chapter 93

How to create a Console Command


The Console page of the Components section (The Console Component) covers how to create a Console command. This cookbook article covers the differences when creating Console commands within the Symfony2 framework.

Automatically Registering Commands


To make the console commands available automatically with Symfony2, create a Command directory inside your bundle and create a php file suffixed with Command.php for each command that you want to provide. For example, if you want to extend the AcmeDemoBundle (available in the Symfony Standard Edition) to greet us from the command line, create GreetCommand.php and add the following to it:
Listing 93-1

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

// src/Acme/DemoBundle/Command/GreetCommand.php namespace Acme\DemoBundle\Command;


use use use use use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; Symfony\Component\Console\Input\InputArgument; Symfony\Component\Console\Input\InputInterface; Symfony\Component\Console\Input\InputOption; Symfony\Component\Console\Output\OutputInterface;

class GreetCommand extends ContainerAwareCommand { protected function configure() { $this ->setName('demo:greet') ->setDescription('Greet someone') ->addArgument('name', InputArgument::OPTIONAL, 'Who do you want to greet?') ->addOption('yell', null, InputOption::VALUE_NONE, 'If set, the task will yell in uppercase letters') ; } protected function execute(InputInterface $input, OutputInterface $output)

PDF brought to you by generated on October 26, 2012

Chapter 93: How to create a Console Command | 501

24 25 26 27 28 29 30 31 32 33 34 35 36 37 }

{ $name = $input->getArgument('name'); if ($name) { $text = 'Hello '.$name; } else { $text = 'Hello'; } if ($input->getOption('yell')) { $text = strtoupper($text); } $output->writeln($text); }

This command will now automatically be available to run:


Listing 93-2

1 $ app/console demo:greet Fabien

Testing Commands
When testing commands used as part of the full framework Application1 should be used instead of Application2:
Listing 93-3

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

use Symfony\Component\Console\Tester\CommandTester; use Symfony\Bundle\FrameworkBundle\Console\Application; use Acme\DemoBundle\Command\GreetCommand; class ListCommandTest extends \PHPUnit_Framework_TestCase { public function testExecute() { // mock the Kernel or create one depending on your needs $application = new Application($kernel); $application->add(new GreetCommand()); $command = $application->find('demo:greet'); $commandTester = new CommandTester($command); $commandTester->execute(array('command' => $command->getName())); $this->assertRegExp('/.../', $commandTester->getDisplay());

// ...
} }

1. http://api.symfony.com/2.1/Symfony/Bundle/FrameworkBundle/Console/Application.html 2. http://api.symfony.com/2.1/Symfony/Component/Console/Application.html

PDF brought to you by generated on October 26, 2012

Chapter 93: How to create a Console Command | 502

Getting Services from the Service Container


By using ContainerAwareCommand3 as the base class for the command (instead of the more basic Command4), you have access to the service container. In other words, you have access to any configured service. For example, you could easily extend the task to be translatable:
Listing 93-4

1 protected function execute(InputInterface $input, OutputInterface $output) 2 { 3 $name = $input->getArgument('name'); 4 $translator = $this->getContainer()->get('translator'); 5 if ($name) { 6 $output->writeln($translator->trans('Hello %name%!', array('%name%' => $name))); 7 } else { 8 $output->writeln($translator->trans('Hello!')); 9 } 10 }

3. http://api.symfony.com/2.1/Symfony/Bundle/FrameworkBundle/Command/ContainerAwareCommand.html 4. http://api.symfony.com/2.1/Symfony/Component/Console/Command/Command.html

PDF brought to you by generated on October 26, 2012

Chapter 93: How to create a Console Command | 503

Chapter 94

How to use the Console


The Using Console Commands, Shortcuts and Built-in Commands page of the components documentation looks at the global console options. When you use the console as part of the full stack framework, some additional global options are available as well. By default, console commands run in the dev environment and you may want to change this for some commands. For example, you may want to run some commands in the prod environment for performance reasons. Also, the result of some commands will be different depending on the environment. for example, the cache:clear command will clear and warm the cache for the specified environment only. To clear and warm the prod cache you need to run:
Listing 94-1

1 $ php app/console cache:clear --env=prod

or the equivalent:
Listing 94-2

1 $ php app/console cache:clear -e=prod

In addition to changing the environment, you can also choose to disable debug mode. This can be useful where you want to run commands in the dev environment but avoid the performance hit of collecting debug data:
Listing 94-3

1 $ php app/console list --no-debug

There is an interactive shell which allows you to enter commands without having to specify php app/ console each time, which is useful if you need to run several commands. To enter the shell run:
Listing 94-4

1 $ php app/console --shell 2 $ php app/console -s

You can now just run commands with the command name:
Listing 94-5

1 Symfony > list

When using the shell you can choose to run each command in a separate process:
PDF brought to you by generated on October 26, 2012 Chapter 94: How to use the Console | 504

Listing 94-6

1 $ php app/console --shell --process-isolation 2 $ php app/console -s --process-isolation

When you do this, the output will not be colorized and interactivity is not supported so you will need to pass all command params explicitly.
Unless you are using isolated processes, clearing the cache in the shell will not have an effect on subsequent commands you run. This is because the original cached files are still being used.

PDF brought to you by generated on October 26, 2012

Chapter 94: How to use the Console | 505

Chapter 95

How to generate URLs with a custom Host in Console Commands


Unfortunately, the command line context does not know about your VirtualHost or domain name. This means that if if you generate absolute URLs within a Console Command you'll probably end up with something like http://localhost/foo/bar which is not very useful. To fix this, you need to configure the "request context", which is a fancy way of saying that you need to configure your environment so that it knows what URL it should use when generating URLs. There are two ways of configuring the request context: at the application level and per Command.

Configuring the Request Context globally


To configure the Request Context - which is used by the URL Generator - you can redefine the parameters it uses as default values to change the default host (localhost) and scheme (http). Note that this does not impact URLs generated via normal web requests, since those will override the defaults.
Listing 95-1

1 # app/config/parameters.yml 2 parameters: 3 router.request_context.host: example.org 4 router.request_context.scheme: https

Configuring the Request Context per Command


To change it only in one command you can simply fetch the Request Context service and override its settings:
Listing 95-2

1 // src/Acme/DemoBundle/Command/DemoCommand.php 2 3 // ...

PDF brought to you by generated on October 26, 2012

Chapter 95: How to generate URLs with a custom Host in Console Commands | 506

4 class DemoCommand extends ContainerAwareCommand 5 { 6 protected function execute(InputInterface $input, OutputInterface $output) 7 { 8 $context = $this->getContainer()->get('router')->getContext(); 9 $context->setHost('example.com'); 10 $context->setScheme('https'); 11 12 // ... your code here 13 } 14 }

PDF brought to you by generated on October 26, 2012

Chapter 95: How to generate URLs with a custom Host in Console Commands | 507

Chapter 96

How to optimize your development Environment for debugging


When you work on a Symfony project on your local machine, you should use the dev environment (app_dev.php front controller). This environment configuration is optimized for two main purposes: Give the developer accurate feedback whenever something goes wrong (web debug toolbar, nice exception pages, profiler, ...); Be as similar as possible as the production environment to avoid problems when deploying the project.

Disabling the Bootstrap File and Class Caching


And to make the production environment as fast as possible, Symfony creates big PHP files in your cache containing the aggregation of PHP classes your project needs for every request. However, this behavior can confuse your IDE or your debugger. This recipe shows you how you can tweak this caching mechanism to make it friendlier when you need to debug code that involves Symfony classes. The app_dev.php front controller reads as follows by default:
Listing 96-1

1 2 3 4 5 6 7 8 9 10

// ...
require_once __DIR__.'/../app/bootstrap.php.cache'; require_once __DIR__.'/../app/AppKernel.php'; use Symfony\Component\HttpFoundation\Request; $kernel = new AppKernel('dev', true); $kernel->loadClassCache(); $kernel->handle(Request::createFromGlobals())->send();

To make your debugger happier, disable all PHP class caches by removing the call to loadClassCache() and by replacing the require statements like below:

PDF brought to you by generated on October 26, 2012

Chapter 96: How to optimize your development Environment for debugging | 508

Listing 96-2

1 2 3 4 5 6 7 8 9 10 11 12

// ... // require_once __DIR__.'/../app/bootstrap.php.cache'; require_once __DIR__.'/../vendor/symfony/symfony/src/Symfony/Component/ClassLoader/ UniversalClassLoader.php'; require_once __DIR__.'/../app/autoload.php'; require_once __DIR__.'/../app/AppKernel.php';
use Symfony\Component\HttpFoundation\Request; $kernel = new AppKernel('dev', true); // $kernel->loadClassCache(); $kernel->handle(Request::createFromGlobals())->send();

If you disable the PHP caches, don't forget to revert after your debugging session.

Some IDEs do not like the fact that some classes are stored in different locations. To avoid problems, you can either tell your IDE to ignore the PHP cache files, or you can change the extension used by Symfony for these files:
Listing 96-3

1 $kernel->loadClassCache('classes', '.php.cache');

PDF brought to you by generated on October 26, 2012

Chapter 96: How to optimize your development Environment for debugging | 509

Chapter 97

How to setup before and after Filters


It is quite common in web application development to need some logic to be executed just before or just after your controller actions acting as filters or hooks. In symfony1, this was achieved with the preExecute and postExecute methods. Most major frameworks have similar methods but there is no such thing in Symfony2. The good news is that there is a much better way to interfere with the Request -> Response process using the EventDispatcher component.

Token validation Example


Imagine that you need to develop an API where some controllers are public but some others are restricted to one or some clients. For these private features, you might provide a token to your clients to identify themselves. So, before executing your controller action, you need to check if the action is restricted or not. If it is restricted, you need to validate the provided token.
Please note that for simplicity in this recipe, tokens will be defined in config and neither database setup nor authentication via the Security component will be used.

Before filters with the kernel.controller Event


First, store some basic token configuration using config.yml and the parameters key:
Listing 97-1

1 # app/config/config.yml 2 parameters: 3 tokens: 4 client1: pass1 5 client2: pass2

PDF brought to you by generated on October 26, 2012

Chapter 97: How to setup before and after Filters | 510

Tag Controllers to be checked


A kernel.controller listener gets notified on every request, right before the controller is executed. So, first, you need some way to identify if the controller that matches the request needs token validation. A clean and easy way is to create an empty interface and make the controllers implement it:
Listing 97-2

1 2 3 4 5 6

namespace Acme\DemoBundle\Controller; interface TokenAuthenticatedController { // ... }

A controller that implements this interface simply looks like this:


Listing 97-3

1 2 3 4 5 6 7 8 9 10 11 12 13

namespace Acme\DemoBundle\Controller; use Acme\DemoBundle\Controller\TokenAuthenticatedController; use Symfony\Bundle\FrameworkBundle\Controller\Controller; class FooController extends Controller implements TokenAuthenticatedController { // An action that needs authentication public function barAction() { // ... } }

Creating an Event Listener


Next, you'll need to create an event listener, which will hold the logic that you want executed before your controllers. If you're not familiar with event listeners, you can learn more about them at How to create an Event Listener:
Listing 97-4

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

// src/Acme/DemoBundle/EventListener/TokenListener.php namespace Acme\DemoBundle\EventListener;


use Acme\DemoBundle\Controller\TokenAuthenticatedController; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\Event\FilterControllerEvent; class TokenListener { private $tokens; public function __construct($tokens) { $this->tokens = $tokens; } public function onKernelController(FilterControllerEvent $event) { $controller = $event->getController();

/*

PDF brought to you by generated on October 26, 2012

Chapter 97: How to setup before and after Filters | 511

22 * $controller passed can be either a class or a Closure. This is not usual in 23 Symfony2 but it may happen. 24 * If it is a class, it comes in array format 25 */ 26 if (!is_array($controller)) { 27 return; 28 } 29 30 if ($controller[0] instanceof TokenAuthenticatedController) { 31 $token = $event->getRequest()->query->get('token'); 32 if (!in_array($token, $this->tokens)) { 33 throw new AccessDeniedHttpException('This action needs a valid token!'); 34 } 35 } 36 } }

Registering the Listener


Finally, register your listener as a service and tag it as an event listener. By listening on kernel.controller, you're telling Symfony that you want your listener to be called just before any controller is executed.
Listing 97-5

# app/config/config.yml (or inside your services.yml) services: demo.tokens.action_listener: class: Acme\DemoBundle\EventListener\TokenListener arguments: [ %tokens% ] tags: - { name: kernel.event_listener, event: kernel.controller, method: onKernelController }

With this configuration, your TokenListener onKernelController method will be executed on each request. If the controller that is about to be executed implements TokenAuthenticatedController, token authentication is applied. This let's us have a "before" filter on any controller that you want.

After filters with the kernel.response Event


In addition to having a "hook" that's executed before your controller, you can also add a hook that's executed after your controller. For this example, imagine that you want to add a sha1 hash (with a salt using that token) to all responses that have passed this token authentication. Another core Symfony event - called kernel.response - is notified on every request, but after the controller returns a Response object. Creating an "after" listener is as easy as creating a listener class and registering it as a service on this event. For example, take the TokenListener from the previous example and first record the authentication token inside the request attributes. This will serve as a basic flag that this request underwent token authentication:
Listing 97-6

1 public function onKernelController(FilterControllerEvent $event) 2 { 3 // ... 4 5 if ($controller[0] instanceof TokenAuthenticatedController) {

PDF brought to you by generated on October 26, 2012

Chapter 97: How to setup before and after Filters | 512

6 7 8 9 10 11 12 13 14 }

$token = $event->getRequest()->query->get('token'); if (!in_array($token, $this->tokens)) { throw new AccessDeniedHttpException('This action needs a valid token!'); }

// mark the request as having passed token authentication $event->getRequest()->attributes->set('auth_token', $token);


}

Now, add another method to this class - onKernelResponse - that looks for this flag on the request object and sets a custom header on the response if it's found:
Listing 97-7

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

// add the new use statement at the top of your file use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
public function onKernelResponse(FilterResponseEvent $event) { // check to see if onKernelController marked this as a token "auth'ed" request if (!$token = $event->getRequest()->attributes->get('auth_token')) { return; } $response = $event->getResponse();

// create a hash and set it as a response header $hash = sha1($response->getContent().$token); $response->headers->set('X-CONTENT-HASH', $hash);
}

Finally, a second "tag" is needed on the service definition to notify Symfony that the onKernelResponse event should be notified for the kernel.response event:
Listing 97-8

# app/config/config.yml (or inside your services.yml) services: demo.tokens.action_listener: class: Acme\DemoBundle\EventListener\TokenListener arguments: [ %tokens% ] tags: - { name: kernel.event_listener, event: kernel.controller, method: onKernelController } - { name: kernel.event_listener, event: kernel.response, method: onKernelResponse }

That's it! The TokenListener is now notified before every controller is executed (onKernelController) and after every controller returns a response (onKernelResponse). By making specific controllers implement the TokenAuthenticatedController interface, our listener knows which controllers it should take action on. And by storing a value in the request's "attributes" bag, the onKernelResponse method knows to add the extra header. Have fun!

PDF brought to you by generated on October 26, 2012

Chapter 97: How to setup before and after Filters | 513

Chapter 98

How to extend a Class without using Inheritance


To allow multiple classes to add methods to another one, you can define the magic __call() method in the class you want to be extended like this:
Listing 98-1

1 class Foo 2 { 3 // ... 4 5 public function __call($method, $arguments) 6 { 7 // create an event named 'foo.method_is_not_found' 8 $event = new HandleUndefinedMethodEvent($this, $method, $arguments); 9 $this->dispatcher->dispatch('foo.method_is_not_found', $event); 10 11 // no listener was able to process the event? The method does not exist 12 if (!$event->isProcessed()) { 13 throw new \Exception(sprintf('Call to undefined method %s::%s.', 14 get_class($this), $method)); 15 } 16 17 // return the listener returned value 18 return $event->getReturnValue(); 19 } }

This uses a special HandleUndefinedMethodEvent that should also be created. This is a generic class that could be reused each time you need to use this pattern of class extension:
Listing 98-2

1 use Symfony\Component\EventDispatcher\Event; 2 3 class HandleUndefinedMethodEvent extends Event 4 { 5 protected $subject;

PDF brought to you by generated on October 26, 2012

Chapter 98: How to extend a Class without using Inheritance | 514

6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 }

protected protected protected protected

$method; $arguments; $returnValue; $isProcessed = false;

public function __construct($subject, $method, $arguments) { $this->subject = $subject; $this->method = $method; $this->arguments = $arguments; } public function getSubject() { return $this->subject; } public function getMethod() { return $this->method; } public function getArguments() { return $this->arguments; }

/** * Sets the value to return and stops other listeners from being notified */ public function setReturnValue($val) { $this->returnValue = $val; $this->isProcessed = true; $this->stopPropagation(); }
public function getReturnValue($val) { return $this->returnValue; } public function isProcessed() { return $this->isProcessed; }

Next, create a class that will listen to the foo.method_is_not_found event and add the method bar():
Listing 98-3

1 class Bar 2 { 3 public function onFooMethodIsNotFound(HandleUndefinedMethodEvent $event) 4 { 5 // we only want to respond to the calls to the 'bar' method 6 if ('bar' != $event->getMethod()) { 7 // allow another listener to take care of this unknown method 8 return;

PDF brought to you by generated on October 26, 2012

Chapter 98: How to extend a Class without using Inheritance | 515

9 10 11 12 13 14 15 16 17 18 19 20 21 22 }

// the subject object (the foo instance) $foo = $event->getSubject(); // the bar method arguments $arguments = $event->getArguments(); // ... do something // set the return value $event->setReturnValue($someValue);
}

Finally, add the new bar method to the Foo class by register an instance of Bar with the foo.method_is_not_found event:
Listing 98-4

1 $bar = new Bar(); 2 $dispatcher->addListener('foo.method_is_not_found', array($bar, 'onFooMethodIsNotFound'));

PDF brought to you by generated on October 26, 2012

Chapter 98: How to extend a Class without using Inheritance | 516

Chapter 99

How to customize a Method Behavior without using Inheritance

Doing something before or after a Method Call


If you want to do something just before, or just after a method is called, you can dispatch an event respectively at the beginning or at the end of the method:
Listing 99-1

1 class Foo 2 { 3 // ... 4 5 public function send($foo, $bar) 6 { 7 // do something before the method 8 $event = new FilterBeforeSendEvent($foo, $bar); 9 $this->dispatcher->dispatch('foo.pre_send', $event); 10 11 // get $foo and $bar from the event, they may have been modified 12 $foo = $event->getFoo(); 13 $bar = $event->getBar(); 14 15 // the real method implementation is here 16 $ret = ...; 17 18 // do something after the method 19 $event = new FilterSendReturnValue($ret); 20 $this->dispatcher->dispatch('foo.post_send', $event); 21 22 return $event->getReturnValue(); 23 } 24 }

PDF brought to you by generated on October 26, 2012

Chapter 99: How to customize a Method Behavior without using Inheritance | 517

In this example, two events are thrown: foo.pre_send, before the method is executed, and foo.post_send after the method is executed. Each uses a custom Event class to communicate information to the listeners of the two events. These event classes would need to be created by you and should allow, in this example, the variables $foo, $bar and $ret to be retrieved and set by the listeners. For example, assuming the FilterSendReturnValue has a setReturnValue method, one listener might look like this:
Listing 99-2

1 public function onFooPostSend(FilterSendReturnValue $event) 2 { 3 $ret = $event->getReturnValue(); 4 // modify the original ``$ret`` value 5 6 $event->setReturnValue($ret); 7 }

PDF brought to you by generated on October 26, 2012

Chapter 99: How to customize a Method Behavior without using Inheritance | 518

Chapter 100

How to register a new Request Format and Mime Type


Every Request has a "format" (e.g. html, json), which is used to determine what type of content to return in the Response. In fact, the request format, accessible via getRequestFormat()1, is used to set the MIME type of the Content-Type header on the Response object. Internally, Symfony contains a map of the most common formats (e.g. html, json) and their associated MIME types (e.g. text/ html, application/json). Of course, additional format-MIME type entries can easily be added. This document will show how you can add the jsonp format and corresponding MIME type.

Create a kernel.request Listener


The key to defining a new MIME type is to create a class that will "listen" to the kernel.request event dispatched by the Symfony kernel. The kernel.request event is dispatched early in Symfony's request handling process and allows you to modify the request object. Create the following class, replacing the path with a path to a bundle in your project:
Listing 100-1

1 2 3 4 5 6 7 8 9 10 11 12 13

// src/Acme/DemoBundle/RequestListener.php namespace Acme\DemoBundle;


use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpKernel\Event\GetResponseEvent; class RequestListener { public function onKernelRequest(GetResponseEvent $event) { $event->getRequest()->setFormat('jsonp', 'application/javascript'); } }

1. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Request.html#getRequestFormat()

PDF brought to you by generated on October 26, 2012

Chapter 100: How to register a new Request Format and Mime Type | 519

Registering your Listener


As for any other listener, you need to add it in one of your configuration file and register it as a listener by adding the kernel.event_listener tag:
Listing 100-2

1 2 3 4 5 6 7 8 9 10 11 12

<!-- app/config/config.xml --> <?xml version="1.0" ?>


<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"> <services> <service id="acme.demobundle.listener.request" class="Acme\DemoBundle\RequestListener"> <tag name="kernel.event_listener" event="kernel.request" method="onKernelRequest" /> </service> </services> </container>

At this point, the acme.demobundle.listener.request service has been configured and will be notified when the Symfony kernel dispatches the kernel.request event.
You can also register the listener in a configuration extension class (see Importing Configuration via Container Extensions for more information).

PDF brought to you by generated on October 26, 2012

Chapter 100: How to register a new Request Format and Mime Type | 520

Chapter 101

How to create a custom Data Collector


The Symfony2 Profiler delegates data collecting to data collectors. Symfony2 comes bundled with a few of them, but you can easily create your own.

Creating a Custom Data Collector


Creating a custom data collector is as simple as implementing the DataCollectorInterface1:
Listing 101-1

1 interface DataCollectorInterface 2 { 3 /** 4 * Collects data for the given Request and Response. 5 * 6 * @param Request $request A Request instance 7 * @param Response $response A Response instance 8 * @param \Exception $exception An Exception instance 9 */ 10 function collect(Request $request, Response $response, \Exception $exception = null); 11 12 /** 13 * Returns the name of the collector. 14 * 15 * @return string The collector name 16 */ 17 function getName(); 18 }

The getName() method must return a unique name. This is used to access the information later on (see How to use the Profiler in a Functional Test for instance). The collect() method is responsible for storing the data it wants to give access to in local properties.

1. http://api.symfony.com/2.1/Symfony/Component/HttpKernel/DataCollector/DataCollectorInterface.html

PDF brought to you by generated on October 26, 2012

Chapter 101: How to create a custom Data Collector | 521

As the profiler serializes data collector instances, you should not store objects that cannot be serialized (like PDO objects), or you need to provide your own serialize() method.

Most of the time, it is convenient to extend DataCollector2 and populate the $this->data property (it takes care of serializing the $this->data property):
Listing 101-2

1 class MemoryDataCollector extends DataCollector 2 { 3 public function collect(Request $request, Response $response, \Exception $exception = 4 null) 5 { 6 $this->data = array( 7 'memory' => memory_get_peak_usage(true), 8 ); 9 } 10 11 public function getMemory() 12 { 13 return $this->data['memory']; 14 } 15 16 public function getName() 17 { 18 return 'memory'; 19 } }

Enabling Custom Data Collectors


To enable a data collector, add it as a regular service in one of your configuration, and tag it with data_collector:
Listing 101-3

1 services: 2 data_collector.your_collector_name: 3 class: Fully\Qualified\Collector\Class\Name 4 tags: 5 - { name: data_collector }

Adding Web Profiler Templates


When you want to display the data collected by your Data Collector in the web debug toolbar or the web profiler, create a Twig template following this skeleton:
Listing 101-4

1 {% extends 'WebProfilerBundle:Profiler:layout.html.twig' %} 2 3 {% block toolbar %} 4 {# the web debug toolbar content #} 5 {% endblock %}

2. http://api.symfony.com/2.1/Symfony/Component/HttpKernel/DataCollector/DataCollector.html

PDF brought to you by generated on October 26, 2012

Chapter 101: How to create a custom Data Collector | 522

6 7 8 9 10 11 12 13 14 15 16 17

{% block head %} {# if the web profiler panel needs some specific JS or CSS files #} {% endblock %} {% block menu %} {# the menu content #} {% endblock %} {% block panel %} {# the panel content #} {% endblock %}

Each block is optional. The toolbar block is used for the web debug toolbar and menu and panel are used to add a panel to the web profiler. All blocks have access to the collector object.
Built-in templates use a base64 encoded image for the toolbar (<img src="src="data:image/ png;base64,..."). You can easily calculate the base64 value for an image with this little script: echo base64_encode(file_get_contents($_SERVER['argv'][1]));.

To enable the template, add a template attribute to the data_collector tag in your configuration. For example, assuming your template is in some AcmeDebugBundle:
Listing 101-5

1 services: 2 data_collector.your_collector_name: 3 class: Acme\DebugBundle\Collector\Class\Name 4 tags: 5 - { name: data_collector, template: "AcmeDebug:Collector:templatename", id: "your_collector_name" }

PDF brought to you by generated on October 26, 2012

Chapter 101: How to create a custom Data Collector | 523

Chapter 102

How to Create a SOAP Web Service in a Symfony2 Controller


Setting up a controller to act as a SOAP server is simple with a couple tools. You must, of course, have the PHP SOAP1 extension installed. As the PHP SOAP extension can not currently generate a WSDL, you must either create one from scratch or use a 3rd party generator.
There are several SOAP server implementations available for use with PHP. Zend SOAP2 and NuSOAP3 are two examples. Although we use the PHP SOAP extension in our examples, the general idea should still be applicable to other implementations.

SOAP works by exposing the methods of a PHP object to an external entity (i.e. the person using the SOAP service). To start, create a class - HelloService - which represents the functionality that you'll expose in your SOAP service. In this case, the SOAP service will allow the client to call a method called hello, which happens to send an email:
Listing 102-1

1 2 3 4 5 6 7 8 9 10 11 12 13 14

// src/Acme/SoapBundle/HelloService.php namespace Acme\SoapBundle;


class HelloService { private $mailer; public function __construct(\Swift_Mailer $mailer) { $this->mailer = $mailer; } public function hello($name) {

1. http://php.net/manual/en/book.soap.php 2. http://framework.zend.com/manual/en/zend.soap.server.html 3. http://sourceforge.net/projects/nusoap

PDF brought to you by generated on October 26, 2012

Chapter 102: How to Create a SOAP Web Service in a Symfony2 Controller | 524

15 16 17 18 19 20 21 22 23 24 25 26 }

$message = \Swift_Message::newInstance() ->setTo('me@example.com') ->setSubject('Hello Service') ->setBody($name . ' says hi!'); $this->mailer->send($message); return 'Hello, '.$name; }

Next, you can train Symfony to be able to create an instance of this class. Since the class sends an e-mail, it's been designed to accept a Swift_Mailer instance. Using the Service Container, we can configure Symfony to construct a HelloService object properly:
Listing 102-2

# app/config/config.yml services: hello_service: class: Acme\DemoBundle\Services\HelloService arguments: [@mailer]

Below is an example of a controller that is capable of handling a SOAP request. If indexAction() is accessible via the route /soap, then the WSDL document can be retrieved via /soap?wsdl.
Listing 102-3

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

namespace Acme\SoapBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; class HelloServiceController extends Controller { public function indexAction() { $server = new \SoapServer('/path/to/hello.wsdl'); $server->setObject($this->get('hello_service')); $response = new Response(); $response->headers->set('Content-Type', 'text/xml; charset=ISO-8859-1'); ob_start(); $server->handle(); $response->setContent(ob_get_clean()); return $response; } }

Take note of the calls to ob_start() and ob_get_clean(). These methods control output buffering4 which allows you to "trap" the echoed output of $server->handle(). This is necessary because Symfony expects your controller to return a Response object with the output as its "content". You must also remember to set the "Content-Type" header to "text/xml", as this is what the client will expect. So, you use ob_start() to start buffering the STDOUT and use ob_get_clean() to dump the echoed output into the content of the Response and clear the output buffer. Finally, you're ready to return the Response.

4. http://php.net/manual/en/book.outcontrol.php

PDF brought to you by generated on October 26, 2012

Chapter 102: How to Create a SOAP Web Service in a Symfony2 Controller | 525

Below is an example calling the service using NuSOAP5 client. This example assumes that the indexAction in the controller above is accessible via the route /soap:
Listing 102-4

1 $client = new \Soapclient('http://example.com/app.php/soap?wsdl', true); 2 3 $result = $client->call('hello', array('name' => 'Scott'));

An example WSDL is below.


Listing 102-5

1 <?xml version="1.0" encoding="ISO-8859-1"?> 2 <definitions xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" 3 xmlns:xsd="http://www.w3.org/2001/XMLSchema" 4 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 5 xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" 6 xmlns:tns="urn:arnleadservicewsdl" 7 xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" 8 xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" 9 xmlns="http://schemas.xmlsoap.org/wsdl/" 10 targetNamespace="urn:helloservicewsdl"> 11 <types> 12 <xsd:schema targetNamespace="urn:hellowsdl"> 13 <xsd:import namespace="http://schemas.xmlsoap.org/soap/encoding/" /> 14 <xsd:import namespace="http://schemas.xmlsoap.org/wsdl/" /> 15 </xsd:schema> 16 </types> 17 <message name="helloRequest"> 18 <part name="name" type="xsd:string" /> 19 </message> 20 <message name="helloResponse"> 21 <part name="return" type="xsd:string" /> 22 </message> 23 <portType name="hellowsdlPortType"> 24 <operation name="hello"> 25 <documentation>Hello World</documentation> 26 <input message="tns:helloRequest"/> 27 <output message="tns:helloResponse"/> 28 </operation> 29 </portType> 30 <binding name="hellowsdlBinding" type="tns:hellowsdlPortType"> 31 <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/> 32 <operation name="hello"> 33 <soap:operation soapAction="urn:arnleadservicewsdl#hello" style="rpc"/> 34 <input> 35 <soap:body use="encoded" namespace="urn:hellowsdl" 36 encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/> 37 </input> 38 <output> 39 <soap:body use="encoded" namespace="urn:hellowsdl" 40 encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/> 41 </output> 42 </operation> 43 </binding> 44 <service name="hellowsdl"> 45 <port name="hellowsdlPort" binding="tns:hellowsdlBinding"> 46 <soap:address location="http://example.com/app.php/soap" /> 47 </port>

5. http://sourceforge.net/projects/nusoap

PDF brought to you by generated on October 26, 2012

Chapter 102: How to Create a SOAP Web Service in a Symfony2 Controller | 526

48 </service> 49 </definitions>

PDF brought to you by generated on October 26, 2012

Chapter 102: How to Create a SOAP Web Service in a Symfony2 Controller | 527

Chapter 103

How Symfony2 differs from symfony1


The Symfony2 framework embodies a significant evolution when compared with the first version of the framework. Fortunately, with the MVC architecture at its core, the skills used to master a symfony1 project continue to be very relevant when developing in Symfony2. Sure, app.yml is gone, but routing, controllers and templates all remain. In this chapter, we'll walk through the differences between symfony1 and Symfony2. As you'll see, many tasks are tackled in a slightly different way. You'll come to appreciate these minor differences as they promote stable, predictable, testable and decoupled code in your Symfony2 applications. So, sit back and relax as we take you from "then" to "now".

Directory Structure
When looking at a Symfony2 project - for example, the Symfony2 Standard1 - you'll notice a very different directory structure than in symfony1. The differences, however, are somewhat superficial.

The app/ Directory


In symfony1, your project has one or more applications, and each lives inside the apps/ directory (e.g. apps/frontend). By default in Symfony2, you have just one application represented by the app/ directory. Like in symfony1, the app/ directory contains configuration specific to that application. It also contains application-specific cache, log and template directories as well as a Kernel class (AppKernel), which is the base object that represents the application. Unlike symfony1, almost no PHP code lives in the app/ directory. This directory is not meant to house modules or library files as it did in symfony1. Instead, it's simply the home of configuration and other resources (templates, translation files).

The src/ Directory


Put simply, your actual code goes here. In Symfony2, all actual application-code lives inside a bundle (roughly equivalent to a symfony1 plugin) and, by default, each bundle lives inside the src directory.
1. https://github.com/symfony/symfony-standard

PDF brought to you by generated on October 26, 2012

Chapter 103: How Symfony2 differs from symfony1 | 528

In that way, the src directory is a bit like the plugins directory in symfony1, but much more flexible. Additionally, while your bundles will live in the src/ directory, third-party bundles will live somewhere in the vendor/ directory. To get a better picture of the src/ directory, let's first think of a symfony1 application. First, part of your code likely lives inside one or more applications. Most commonly these include modules, but could also include any other PHP classes you put in your application. You may have also created a schema.yml file in the config directory of your project and built several model files. Finally, to help with some common functionality, you're using several third-party plugins that live in the plugins/ directory. In other words, the code that drives your application lives in many different places. In Symfony2, life is much simpler because all Symfony2 code must live in a bundle. In our pretend symfony1 project, all the code could be moved into one or more plugins (which is a very good practice, in fact). Assuming that all modules, PHP classes, schema, routing configuration, etc were moved into a plugin, the symfony1 plugins/ directory would be very similar to the Symfony2 src/ directory. Put simply again, the src/ directory is where your code, assets, templates and most anything else specific to your project will live.

The vendor/ Directory


The vendor/ directory is basically equivalent to the lib/vendor/ directory in symfony1, which was the conventional directory for all vendor libraries and bundles. By default, you'll find the Symfony2 library files in this directory, along with several other dependent libraries such as Doctrine2, Twig and Swiftmailer. 3rd party Symfony2 bundles live somewhere in the vendor/.

The web/ Directory


Not much has changed in the web/ directory. The most noticeable difference is the absence of the css/, js/ and images/ directories. This is intentional. Like with your PHP code, all assets should also live inside a bundle. With the help of a console command, the Resources/public/ directory of each bundle is copied or symbolically-linked to the web/bundles/ directory. This allows you to keep assets organized inside your bundle, but still make them available to the public. To make sure that all bundles are available, run the following command:
Listing 103-1

1 php app/console assets:install web

This command is the Symfony2 equivalent to the symfony1 plugin:publish-assets command.

Autoloading
One of the advantages of modern frameworks is never needing to worry about requiring files. By making use of an autoloader, you can refer to any class in your project and trust that it's available. Autoloading has changed in Symfony2 to be more universal, faster, and independent of needing to clear your cache. In symfony1, autoloading was done by searching the entire project for the presence of PHP class files and caching this information in a giant array. That array told symfony1 exactly which file contained each class. In the production environment, this caused you to need to clear the cache when classes were added or moved. In Symfony2, a new class - UniversalClassLoader - handles this process. The idea behind the autoloader is simple: the name of your class (including the namespace) must match up with the path to

PDF brought to you by generated on October 26, 2012

Chapter 103: How Symfony2 differs from symfony1 | 529

the file containing that class. Take the FrameworkExtraBundle from the Symfony2 Standard Edition as an example:
Listing 103-2

1 2 3 4 5 6 7 8 9

namespace Sensio\Bundle\FrameworkExtraBundle; use Symfony\Component\HttpKernel\Bundle\Bundle; // ... class SensioFrameworkExtraBundle extends Bundle { // ... }

The file itself lives at vendor/sensio/framework-extra-bundle/Sensio/Bundle/ FrameworkExtraBundle/SensioFrameworkExtraBundle.php. As you can see, the location of the file follows the namespace of the class. Specifically, the namespace, Sensio\Bundle\FrameworkExtraBundle, spells out the directory that the file should live in (vendor/ sensio/framework-extra-bundle/Sensio/Bundle/FrameworkExtraBundle/). This is because, in the app/autoload.php file, you'll configure Symfony to look for the Sensio namespace in the vendor/ sensio directory:
Listing 103-3

1 2 3 4 5 6 7

// app/autoload.php // ... $loader->registerNamespaces(array( ..., 'Sensio' => __DIR__.'/../vendor/sensio/framework-extra-bundle', ));

If the file did not live at this exact location, you'd receive a Class "Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle" does not exist. error. In Symfony2, a "class does not exist" means that the suspect class namespace and physical location do not match. Basically, Symfony2 is looking in one exact location for that class, but that location doesn't exist (or contains a different class). In order for a class to be autoloaded, you never need to clear your cache in Symfony2. As mentioned before, for the autoloader to work, it needs to know that the Sensio namespace lives in the vendor/bundles directory and that, for example, the Doctrine namespace lives in the vendor/ doctrine/orm/lib/ directory. This mapping is entirely controlled by you via the app/autoload.php file. If you look at the HelloController from the Symfony2 Standard Edition you can see that it lives in the Acme\DemoBundle\Controller namespace. Yet, the Acme namespace is not defined in the app/ autoload.php. By default you do not need to explicitly configure the location of bundles that live in the src/ directory. The UniversalClassLoader is configured to fallback to the src/ directory using its registerNamespaceFallbacks method:
Listing 103-4

1 2 3 4 5 6

// app/autoload.php // ... $loader->registerNamespaceFallbacks(array( __DIR__.'/../src', ));

PDF brought to you by generated on October 26, 2012

Chapter 103: How Symfony2 differs from symfony1 | 530

Using the Console


In symfony1, the console is in the root directory of your project and is called symfony:
Listing 103-5

1 php symfony

In Symfony2, the console is now in the app sub-directory and is called console:
Listing 103-6

1 php app/console

Applications
In a symfony1 project, it is common to have several applications: one for the frontend and one for the backend for instance. In a Symfony2 project, you only need to create one application (a blog application, an intranet application, ...). Most of the time, if you want to create a second application, you might instead create another project and share some bundles between them. And if you need to separate the frontend and the backend features of some bundles, you can create sub-namespaces for controllers, sub-directories for templates, different semantic configurations, separate routing configurations, and so on. Of course, there's nothing wrong with having multiple applications in your project, that's entirely up to you. A second application would mean a new directory, e.g. my_app/, with the same basic setup as the app/ directory.
Read the definition of a Project, an Application, and a Bundle in the glossary.

Bundles and Plugins


In a symfony1 project, a plugin could contain configuration, modules, PHP libraries, assets and anything else related to your project. In Symfony2, the idea of a plugin is replaced by the "bundle". A bundle is even more powerful than a plugin because the core Symfony2 framework is brought in via a series of bundles. In Symfony2, bundles are first-class citizens that are so flexible that even core code itself is a bundle. In symfony1, a plugin must be enabled inside the ProjectConfiguration class:
Listing 103-7

1 2 3 4 5

// config/ProjectConfiguration.class.php public function setup() { $this->enableAllPluginsExcept(array(... some plugins here)); }

In Symfony2, the bundles are activated inside the application kernel:


Listing 103-8

1 // app/AppKernel.php 2 public function registerBundles()

PDF brought to you by generated on October 26, 2012

Chapter 103: How Symfony2 differs from symfony1 | 531

3 { 4 5 6 7 8 9 10 11 12 }

$bundles = array( new Symfony\Bundle\FrameworkBundle\FrameworkBundle(), new Symfony\Bundle\TwigBundle\TwigBundle(), ..., new Acme\DemoBundle\AcmeDemoBundle(), ); return $bundles;

Routing (routing.yml) and Configuration (config.yml)


In symfony1, the routing.yml and app.yml configuration files were automatically loaded inside any plugin. In Symfony2, routing and application configuration inside a bundle must be included manually. For example, to include a routing resource from a bundle called AcmeDemoBundle, you can do the following:
Listing 103-9

1 # app/config/routing.yml 2 _hello: 3 resource: "@AcmeDemoBundle/Resources/config/routing.yml"

This will load the routes found in the Resources/config/routing.yml file of the AcmeDemoBundle. The special @AcmeDemoBundle is a shortcut syntax that, internally, resolves to the full path to that bundle. You can use this same strategy to bring in configuration from a bundle:
Listing 103-10 1

# app/config/config.yml 2 imports: 3 - { resource: "@AcmeDemoBundle/Resources/config/config.yml" }

In Symfony2, configuration is a bit like app.yml in symfony1, except much more systematic. With app.yml, you could simply create any keys you wanted. By default, these entries were meaningless and depended entirely on how you used them in your application:
Listing 103-11 1

# some app.yml file from symfony1 2 all: 3 email: 4 from_address: foo.bar@example.com

In Symfony2, you can also create arbitrary entries under the parameters key of your configuration:
Listing 103-12 1

parameters: email.from_address: foo.bar@example.com

You can now access this from a controller, for example:


Listing 103-13 1

public function helloAction($name) 2 { 3 $fromAddress = $this->container->getParameter('email.from_address'); 4 }

In reality, the Symfony2 configuration is much more powerful and is used primarily to configure objects that you can use. For more information, see the chapter titled "Service Container".
PDF brought to you by generated on October 26, 2012 Chapter 103: How Symfony2 differs from symfony1 | 532

Part IV

The Components

Chapter 104

The ClassLoader Component


The ClassLoader Component loads your project classes automatically if they follow some standard PHP conventions. Whenever you use an undefined class, PHP uses the autoloading mechanism to delegate the loading of a file defining the class. Symfony2 provides a "universal" autoloader, which is able to load classes from files that implement one of the following conventions: The technical interoperability standards1 for PHP 5.3 namespaces and class names; The PEAR2 naming convention for classes. If your classes and the third-party libraries you use for your project follow these standards, the Symfony2 autoloader is the only autoloader you will ever need.

Installation
You can install the component in many different ways: Use the official Git repository (https://github.com/symfony/ClassLoader3); Install it via PEAR ( pear.symfony.com/ClassLoader); Install it via Composer (symfony/class-loader on Packagist).

Usage
New in version 2.1: The useIncludePath method was added in Symfony 2.1.

1. http://symfony.com/PSR0 2. http://pear.php.net/manual/en/standards.php 3. https://github.com/symfony/ClassLoader

PDF brought to you by generated on October 26, 2012

Chapter 104: The ClassLoader Component | 534

Registering the UniversalClassLoader4 autoloader is straightforward:


Listing 104-1

1 2 3 4 5 6 7 8 9 10 11 12

require_once '/path/to/src/Symfony/Component/ClassLoader/UniversalClassLoader.php'; use Symfony\Component\ClassLoader\UniversalClassLoader; $loader = new UniversalClassLoader();

// You can search the include_path as a last resort. $loader->useIncludePath(true); // ... register namespaces and prefixes here - see below
$loader->register();

For minor performance gains class paths can be cached in memory using APC by registering the ApcUniversalClassLoader5:
Listing 104-2

1 2 3 4 5 6 7

require_once '/path/to/src/Symfony/Component/ClassLoader/UniversalClassLoader.php'; require_once '/path/to/src/Symfony/Component/ClassLoader/ApcUniversalClassLoader.php'; use Symfony\Component\ClassLoader\ApcUniversalClassLoader; $loader = new ApcUniversalClassLoader('apc.prefix.'); $loader->register();

The autoloader is useful only if you add some libraries to autoload.


The autoloader is automatically registered in a Symfony2 application (see app/autoload.php).

If the classes to autoload use namespaces, use the registerNamespace()6 or registerNamespaces()7 methods:
Listing 104-3

1 2 3 4 5 6 7 8

$loader->registerNamespace('Symfony', __DIR__.'/vendor/symfony/symfony/src'); $loader->registerNamespaces(array( 'Symfony' => __DIR__.'/../vendor/symfony/symfony/src', 'Monolog' => __DIR__.'/../vendor/monolog/monolog/src', )); $loader->register();

For classes that follow the registerPrefixes()9 methods:


Listing 104-4

PEAR

naming

convention,

use

the

registerPrefix()8 or

1 $loader->registerPrefix('Twig_', __DIR__.'/vendor/twig/twig/lib'); 2

4. http://api.symfony.com/2.1/Symfony/Component/ClassLoader/UniversalClassLoader.html 5. http://api.symfony.com/2.1/Symfony/Component/ClassLoader/ApcUniversalClassLoader.html 6. http://api.symfony.com/2.1/Symfony/Component/ClassLoader/UniversalClassLoader.html#registerNamespace() 7. http://api.symfony.com/2.1/Symfony/Component/ClassLoader/UniversalClassLoader.html#registerNamespaces() 8. http://api.symfony.com/2.1/Symfony/Component/ClassLoader/UniversalClassLoader.html#registerPrefix() 9. http://api.symfony.com/2.1/Symfony/Component/ClassLoader/UniversalClassLoader.html#registerPrefixes()

PDF brought to you by generated on October 26, 2012

Chapter 104: The ClassLoader Component | 535

3 $loader->registerPrefixes(array( 4 'Swift_' => __DIR__.'/vendor/swiftmailer/swiftmailer/lib/classes', 5 'Twig_' => __DIR__.'/vendor/twig/twig/lib', 6 )); 7 8 $loader->register();

Some libraries also require their root path be registered in the PHP include path (set_include_path()).

Classes from a sub-namespace or a sub-hierarchy of PEAR classes can be looked for in a location list to ease the vendoring of a sub-set of classes for large projects:
Listing 104-5

1 $loader->registerNamespaces(array( 2 'Doctrine\\Common' => __DIR__.'/vendor/doctrine/common/lib', 3 'Doctrine\\DBAL\\Migrations' => __DIR__.'/vendor/doctrine/migrations/lib', 4 'Doctrine\\DBAL' => __DIR__.'/vendor/doctrine/dbal/lib', 5 'Doctrine' => __DIR__.'/vendor/doctrine/orm/lib', 6 )); 7 8 $loader->register();

In this example, if you try to use a class in the Doctrine\Common namespace or one of its children, the autoloader will first look for the class under the doctrine-common directory, and it will then fallback to the default Doctrine directory (the last one configured) if not found, before giving up. The order of the registrations is significant in this case.

PDF brought to you by generated on October 26, 2012

Chapter 104: The ClassLoader Component | 536

Chapter 105

The Config Component

Introduction
The Config Component provides several classes to help you find, load, combine, autofill and validate configuration values of any kind, whatever their source may be (Yaml, XML, INI files, or for instance a database).

Installation
You can install the component in many different ways: Use the official Git repository (https://github.com/symfony/Config1); Install it via PEAR ( pear.symfony.com/Config); Install it via Composer (symfony/config on Packagist).

Sections
Loading resources Caching based on resources Define and process configuration values

1. https://github.com/symfony/Config

PDF brought to you by generated on October 26, 2012

Chapter 105: The Config Component | 537

Chapter 106

Loading resources

Locating resources
Loading the configuration normally starts with a search for resources in most cases: files. This can be done with the FileLocator1:
Listing 106-1

1 2 3 4 5 6

use Symfony\Component\Config\FileLocator; $configDirectories = array(__DIR__.'/app/config'); $locator = new FileLocator($configDirectories); $yamlUserFiles = $locator->locate('users.yml', null, false);

The locator receives a collection of locations where it should look for files. The first argument of locate() is the name of the file to look for. The second argument may be the current path and when supplied, the locator will look in this directory first. The third argument indicates whether or not the locator should return the first file it has found, or an array containing all matches.

Resource loaders
For each type of resource (Yaml, XML, annotation, etc.) a loader must be defined. Each loader should implement LoaderInterface2 or extend the abstract FileLoader3 class, which allows for recursively importing other resources:
Listing 106-2

1 use Symfony\Component\Config\Loader\FileLoader; 2 use Symfony\Component\Yaml\Yaml; 3

1. http://api.symfony.com/2.1/Symfony/Component/Config/FileLocator.html 2. http://api.symfony.com/2.1/Symfony/Component/Config/Loader/LoaderInterface.html 3. http://api.symfony.com/2.1/Symfony/Component/Config/Loader/FileLoader.html

PDF brought to you by generated on October 26, 2012

Chapter 106: Loading resources | 538

4 class YamlUserLoader extends FileLoader 5 { 6 public function load($resource, $type = null) 7 { 8 $configValues = Yaml::parse($resource); 9 10 // ... handle the config values 11 12 // maybe import some other resource: 13 14 // $this->import('extra_users.yml'); 15 } 16 17 public function supports($resource, $type = null) 18 { 19 return is_string($resource) && 'yml' === pathinfo( 20 $resource, 21 PATHINFO_EXTENSION 22 ); 23 } 24 }

Finding the right loader


The LoaderResolver4 receives as its first constructor argument a collection of loaders. When a resource (for instance an XML file) should be loaded, it loops through this collection of loaders and returns the loader which supports this particular resource type. The DelegatingLoader5 makes use of the LoaderResolver6. When it is asked to load a resource, it delegates this question to the LoaderResolver7. In case the resolver has found a suitable loader, this loader will be asked to load the resource:
Listing 106-3

1 2 3 4 5 6 7 8 9 10 11

use Symfony\Component\Config\Loader\LoaderResolver; use Symfony\Component\Config\Loader\DelegatingLoader; $loaderResolver = new LoaderResolver(array(new YamlUserLoader($locator))); $delegatingLoader = new DelegatingLoader($loaderResolver); $delegatingLoader->load(__DIR__.'/users.yml'); /* The YamlUserLoader will be used to load this resource, since it supports files with a "yml" extension */

4. http://api.symfony.com/2.1/Symfony/Component/Config/Loader/LoaderResolver.html 5. http://api.symfony.com/2.1/Symfony/Component/Config/Loader/DelegatingLoader.html 6. http://api.symfony.com/2.1/Symfony/Component/Config/Loader/LoaderResolver.html 7. http://api.symfony.com/2.1/Symfony/Component/Config/Loader/LoaderResolver.html

PDF brought to you by generated on October 26, 2012

Chapter 106: Loading resources | 539

Chapter 107

Caching based on resources


When all configuration resources are loaded, you may want to process the configuration values and combine them all in one file. This file acts like a cache. Its contents dont have to be regenerated every time the application runs only when the configuration resources are modified. For example, the Symfony Routing component allows you to load all routes, and then dump a URL matcher or a URL generator based on these routes. In this case, when one of the resources is modified (and you are working in a development environment), the generated file should be invalidated and regenerated. This can be accomplished by making use of the ConfigCache1 class. The example below shows you how to collect resources, then generate some code based on the resources that were loaded, and write this code to the cache. The cache also receives the collection of resources that were used for generating the code. By looking at the "last modified" timestamp of these resources, the cache can tell if it is still fresh or that its contents should be regenerated:
Listing 107-1

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

use Symfony\Component\Config\ConfigCache; use Symfony\Component\Config\Resource\FileResource; $cachePath = __DIR__.'/cache/appUserMatcher.php';

// the second argument indicates whether or not you want to use debug mode $userMatcherCache = new ConfigCache($cachePath, true);
if (!$userMatcherCache->isFresh()) { // fill this with an array of 'users.yml' file paths $yamlUserFiles = ...; $resources = array(); foreach ($yamlUserFiles as $yamlUserFile) { // see the previous article "Loading resources" to // see where $delegatingLoader comes from $delegatingLoader->load($yamlUserFile); $resources[] = new FileResource($yamlUserFile); }

1. http://api.symfony.com/2.1/Symfony/Component/Config/ConfigCache.html

PDF brought to you by generated on October 26, 2012

Chapter 107: Caching based on resources | 540

21 22 // the code for the UserMatcher is generated elsewhere 23 $code = ...; 24 25 $userMatcherCache->write($code, $resources); 26 } 27 28 // you may want to require the cached code: 29 require $cachePath;

In debug mode, a .meta file will be created in the same directory as the cache file itself. This .meta file contains the serialized resources, whose timestamps are used to determine if the cache is still fresh. When not in debug mode, the cache is considered to be "fresh" as soon as it exists, and therefore no .meta file will be generated.

PDF brought to you by generated on October 26, 2012

Chapter 107: Caching based on resources | 541

Chapter 108

Define and process configuration values

Validate configuration values


After loading configuration values from all kinds of resources, the values and their structure can be validated using the "Definition" part of the Config Component. Configuration values are usually expected to show some kind of hierarchy. Also, values should be of a certain type, be restricted in number or be one of a given set of values. For example, the following configuration (in Yaml) shows a clear hierarchy and some validation rules that should be applied to it (like: "the value for auto_connect must be a boolean value"):
Listing 108-1

1 auto_connect: true 2 default_connection: mysql 3 connections: 4 mysql: 5 host: localhost 6 driver: mysql 7 username: user 8 password: pass 9 sqlite: 10 host: localhost 11 driver: sqlite 12 memory: true 13 username: user 14 password: pass

When loading multiple configuration files, it should be possible to merge and overwrite some values. Other values should not be merged and stay as they are when first encountered. Also, some keys are only available when another key has a specific value (in the sample configuration above: the memory key only makes sense when the driver is sqlite).

PDF brought to you by generated on October 26, 2012

Chapter 108: Define and process configuration values | 542

Define a hierarchy of configuration values using the TreeBuilder


All the rules concerning configuration values can be defined using the TreeBuilder1. A TreeBuilder2 instance should be returned from a custom Configuration class which implements the ConfigurationInterface3:
Listing 108-2

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

namespace Acme\DatabaseConfiguration; use Symfony\Component\Config\Definition\ConfigurationInterface; use Symfony\Component\Config\Definition\Builder\TreeBuilder; class DatabaseConfiguration implements ConfigurationInterface { public function getConfigTreeBuilder() { $treeBuilder = new TreeBuilder(); $rootNode = $treeBuilder->root('database');

// ... add node definitions to the root of the tree


return $treeBuilder; } }

Add node definitions to the tree


Variable nodes
A tree contains node definitions which can be laid out in a semantic way. This means, using indentation and the fluent notation, it is possible to reflect the real structure of the configuration values:
Listing 108-3

1 $rootNode 2 ->children() 3 ->booleanNode('auto_connect') 4 ->defaultTrue() 5 ->end() 6 ->scalarNode('default_connection') 7 ->defaultValue('default') 8 ->end() 9 ->end() 10 ;

The root node itself is an array node, and has children, like the boolean node auto_connect and the scalar node default_connection. In general: after defining a node, a call to end() takes you one step up in the hierarchy.

Node type
It is possible to validate the type of a provided value by using the appropriate node definition. Node type are available for:
1. http://api.symfony.com/2.1/Symfony/Component/Config/Definition/Builder/TreeBuilder.html 2. http://api.symfony.com/2.1/Symfony/Component/Config/Definition/Builder/TreeBuilder.html 3. http://api.symfony.com/2.1/Symfony/Component/Config/Definition/ConfigurationInterface.html

PDF brought to you by generated on October 26, 2012

Chapter 108: Define and process configuration values | 543

scalar boolean array variable (no validation)

and are created with node($name, $type) or their associated shortcut xxxxNode($name) method.

Array nodes
It is possible to add a deeper level to the hierarchy, by adding an array node. The array node itself, may have a pre-defined set of variable nodes:
Listing 108-4

1 $rootNode 2 ->arrayNode('connection') 3 ->scalarNode('driver')->end() 4 ->scalarNode('host')->end() 5 ->scalarNode('username')->end() 6 ->scalarNode('password')->end() 7 ->end() 8 ;

Or you may define a prototype for each node inside an array node:
Listing 108-5

1 $rootNode 2 ->arrayNode('connections') 3 ->prototype('array') 4 ->children() 5 ->scalarNode('driver')->end() 6 ->scalarNode('host')->end() 7 ->scalarNode('username')->end() 8 ->scalarNode('password')->end() 9 ->end() 10 ->end() 11 ->end() 12 ;

A prototype can be used to add a definition which may be repeated many times inside the current node. According to the prototype definition in the example above, it is possible to have multiple connection arrays (containing a driver, host, etc.).

Array node options


Before defining the children of an array node, you can provide options like: useAttributeAsKey() Provide the name of a child node, whose value should be used as the key in the resulting array requiresAtLeastOneElement() There should be at least one element in the array (works only when isRequired() is also called). An example of this:
Listing 108-6

1 $rootNode 2 ->arrayNode('parameters') 3 ->isRequired() 4 ->requiresAtLeastOneElement() 5 ->useAttributeAsKey('name')

PDF brought to you by generated on October 26, 2012

Chapter 108: Define and process configuration values | 544

6 7 8 9 10 11 12 13 ;

->prototype('array') ->children() ->scalarNode('name')->isRequired()->end() ->scalarNode('value')->isRequired()->end() ->end() ->end() ->end()

Default and required values


For all node types, it is possible to define default values and replacement values in case a node has a certain value: defaultValue() Set a default value isRequired() Must be defined (but may be empty) cannotBeEmpty() May not contain an empty value default*() (null, true, false), shortcut for defaultValue() treat*Like() (null, true, false), provide a replacement value in case the value is *.
Listing 108-7

1 $rootNode 2 ->arrayNode('connection') 3 ->children() 4 ->scalarNode('driver') 5 ->isRequired() 6 ->cannotBeEmpty() 7 ->end() 8 ->scalarNode('host') 9 ->defaultValue('localhost') 10 ->end() 11 ->scalarNode('username')->end() 12 ->scalarNode('password')->end() 13 ->booleanNode('memory') 14 ->defaultFalse() 15 ->end() 16 ->end() 17 ->end() 18 ;

Merging options
Extra options concerning the merge process may be provided. For arrays:

PDF brought to you by generated on October 26, 2012

Chapter 108: Define and process configuration values | 545

performNoDeepMerging() When the value is also defined in a second configuration array, dont try to merge an array, but overwrite it entirely For all nodes: cannotBeOverwritten() dont let other configuration arrays overwrite an existing value for this node

Appending sections
If you have a complex configuration to validate then the tree can grow to be large and you may want to split it up into sections. You can do this by making a section a separate node and then appending it into the main tree with append():
Listing 108-8

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43

public function getConfigTreeBuilder() { $treeBuilder = new TreeBuilder(); $rootNode = $treeBuilder->root('database'); $rootNode ->arrayNode('connection') ->children() ->scalarNode('driver') ->isRequired() ->cannotBeEmpty() ->end() ->scalarNode('host') ->defaultValue('localhost') ->end() ->scalarNode('username')->end() ->scalarNode('password')->end() ->booleanNode('memory') ->defaultFalse() ->end() ->end() ->append($this->addParametersNode()) ->end() ; return $treeBuilder; } public function addParametersNode() { $builder = new TreeBuilder(); $node = $builder->root('parameters'); $node ->isRequired() ->requiresAtLeastOneElement() ->useAttributeAsKey('name') ->prototype('array') ->children() ->scalarNode('name')->isRequired()->end() ->scalarNode('value')->isRequired()->end() ->end() ->end()

PDF brought to you by generated on October 26, 2012

Chapter 108: Define and process configuration values | 546

44 45 46 47 }

; return $node;

This is also useful to help you avoid repeating yourself if you have sections of the config that are repeated in different places.

Normalization
When the config files are processed they are first normalized, then merged and finally the tree is used to validate the resulting array. The normalization process is used to remove some of the differences that result from different configuration formats, mainly the differences between Yaml and XML. The separator used in keys is typically _ in Yaml and - in XML. For example, auto_connect in Yaml and auto-connect. The normalization would make both of these auto_connect. Another difference between Yaml and XML is in the way arrays of values may be represented. In Yaml you may have:
Listing 108-9

1 twig: 2 extensions: ['twig.extension.foo', 'twig.extension.bar']

and in XML:
Listing 108-10 1

<twig:config> 2 <twig:extension>twig.extension.foo</twig:extension> 3 <twig:extension>twig.extension.bar</twig:extension> 4 </twig:config>

This difference can be removed in normalization by pluralizing the key used in XML. You can specify that you want a key to be pluralized in this way with fixXmlConfig():
Listing 108-11 1

$rootNode 2 ->fixXmlConfig('extension') 3 ->children() 4 ->arrayNode('extensions') 5 ->prototype('scalar')->end() 6 ->end() 7 ->end() 8 ;

If it is an irregular pluralization you can specify the plural to use as a second argument:
Listing 108-12 1

$rootNode 2 ->fixXmlConfig('child', 'children') 3 ->children() 4 ->arrayNode('children') 5 ->end() 6 ;

As well as fixing this, fixXmlConfig ensures that single xml elements are still turned into an array. So you may have:
Listing 108-13

PDF brought to you by generated on October 26, 2012

Chapter 108: Define and process configuration values | 547

1 <connection>default</connection> 2 <connection>extra</connection>

and sometimes only:


Listing 108-14 1

<connection>default</connection>

By default connection would be an array in the first case and a string in the second making it difficult to validate. You can ensure it is always an array with with fixXmlConfig. You can further control the normalization process if you need to. For example, you may want to allow a string to be set and used as a particular key or several keys to be set explicitly. So that, if everything apart from id is optional in this config:
Listing 108-15 1

2 3 4 5 6

connection: name: my_mysql_connection host: localhost driver: mysql username: user password: pass

you can allow the following as well:


Listing 108-16 1

connection: my_mysql_connection

By changing a string value into an associative array with name as the key:
Listing 108-17

1 $rootNode 2 ->arrayNode('connection') 3 ->beforeNormalization() 4 ->ifString() 5 ->then(function($v) { return array('name'=> $v); }) 6 ->end() 7 ->scalarValue('name')->isRequired() 8 // ... 9 ->end() 10 ;

Validation rules
More advanced validation rules can be provided using the ExprBuilder4. This builder implements a fluent interface for a well-known control structure. The builder is used for adding advanced validation rules to node definitions, like:
Listing 108-18

1 $rootNode 2 ->arrayNode('connection') 3 ->children() 4 ->scalarNode('driver') 5 ->isRequired() 6 ->validate() 7 ->ifNotInArray(array('mysql', 'sqlite', 'mssql'))

4. http://api.symfony.com/2.1/Symfony/Component/Config/Definition/Builder/ExprBuilder.html

PDF brought to you by generated on October 26, 2012

Chapter 108: Define and process configuration values | 548

8 9 10 11 12 13 ;

->thenInvalid('Invalid database driver "%s"') ->end() ->end() ->end() ->end()

A validation rule always has an "if" part. You can specify this part in the following ways: ifTrue() ifString() ifNull() ifArray() ifInArray() ifNotInArray() always()

A validation rule also requires a "then" part: then() thenEmptyArray() thenInvalid() thenUnset()

Usually, "then" is a closure. Its return value will be used as a new value for the node, instead of the node's original value.

Processing configuration values


The Processor5 uses the tree as it was built using the TreeBuilder6 to process multiple arrays of configuration values that should be merged. If any value is not of the expected type, is mandatory and yet undefined, or could not be validated in some other way, an exception will be thrown. Otherwise the result is a clean array of configuration values:
Listing 108-19

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

use Symfony\Component\Yaml\Yaml; use Symfony\Component\Config\Definition\Processor; use Acme\DatabaseConfiguration; $config1 = Yaml::parse(__DIR__.'/src/Matthias/config/config.yml'); $config2 = Yaml::parse(__DIR__.'/src/Matthias/config/config_extra.yml'); $configs = array($config1, $config2); $processor = new Processor(); $configuration = new DatabaseConfiguration; $processedConfiguration = $processor->processConfiguration( $configuration, $configs) ;

5. http://api.symfony.com/2.1/Symfony/Component/Config/Definition/Processor.html 6. http://api.symfony.com/2.1/Symfony/Component/Config/Definition/Builder/TreeBuilder.html

PDF brought to you by generated on October 26, 2012

Chapter 108: Define and process configuration values | 549

Chapter 109

The Console Component


The Console component eases the creation of beautiful and testable command line interfaces. The Console component allows you to create command-line commands. Your console commands can be used for any recurring task, such as cronjobs, imports, or other batch jobs.

Installation
You can install the component in many different ways: Use the official Git repository (https://github.com/symfony/Console1); Install it via PEAR ( pear.symfony.com/Console); Install it via Composer (symfony/console on Packagist).

Creating a basic Command


To make a console command to greet us from the command line, create GreetCommand.php and add the following to it:
Listing 109-1

1 2 3 4 5 6 7 8 9 10 11

namespace Acme\DemoBundle\Command; use use use use use Symfony\Component\Console\Command\Command; Symfony\Component\Console\Input\InputArgument; Symfony\Component\Console\Input\InputInterface; Symfony\Component\Console\Input\InputOption; Symfony\Component\Console\Output\OutputInterface;

class GreetCommand extends Command { protected function configure()

1. https://github.com/symfony/Console

PDF brought to you by generated on October 26, 2012

Chapter 109: The Console Component | 550

12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 }

{ $this ->setName('demo:greet') ->setDescription('Greet someone') ->addArgument( 'name', InputArgument::OPTIONAL, 'Who do you want to greet?' ) ->addOption( 'yell', null, InputOption::VALUE_NONE, 'If set, the task will yell in uppercase letters' ) ; } protected function execute(InputInterface $input, OutputInterface $output) { $name = $input->getArgument('name'); if ($name) { $text = 'Hello '.$name; } else { $text = 'Hello'; } if ($input->getOption('yell')) { $text = strtoupper($text); } $output->writeln($text); }

You also need to create the file to run at the command line which creates an Application and adds commands to it:
Listing 109-2

1 2 3 4 5 6 7 8 9 10

#!/usr/bin/env php # app/console <?php


use Acme\DemoBundle\Command\GreetCommand; use Symfony\Component\Console\Application; $application = new Application(); $application->add(new GreetCommand); $application->run();

Test the new console command by running the following


Listing 109-3

1 $ app/console demo:greet Fabien

This will print the following to the command line:


Listing 109-4

1 Hello Fabien

PDF brought to you by generated on October 26, 2012

Chapter 109: The Console Component | 551

You can also use the --yell option to make everything uppercase:
Listing 109-5

1 $ app/console demo:greet Fabien --yell

This prints:
Listing 109-6

1 HELLO FABIEN

Coloring the Output


Whenever you output text, you can surround the text with tags to color its output. For example:
Listing 109-7

1 2 3 4 5 6 7 8 9 10 11

// green text $output->writeln('<info>foo</info>'); // yellow text $output->writeln('<comment>foo</comment>'); // black text on a cyan background $output->writeln('<question>foo</question>'); // white text on a red background $output->writeln('<error>foo</error>');

It is possible to define your own styles using the class OutputFormatterStyle2:


Listing 109-8

1 $style = new OutputFormatterStyle('red', 'yellow', array('bold', 'blink')); 2 $output->getFormatter()->setStyle('fire', $style); 3 $output->writeln('<fire>foo</fire>');

Available foreground and background colors are: black, red, green, yellow, blue, magenta, cyan and white. And available options are: bold, underscore, blink, reverse and conceal.

Using Command Arguments


The most interesting part of the commands are the arguments and options that you can make available. Arguments are the strings - separated by spaces - that come after the command name itself. They are ordered, and can be optional or required. For example, add an optional last_name argument to the command and make the name argument required:
Listing 109-9

1 $this 2 // ... 3 ->addArgument( 4 'name', 5 InputArgument::REQUIRED, 6 'Who do you want to greet?' 7 ) 8 ->addArgument( 9 'last_name', 10 InputArgument::OPTIONAL,

2. http://api.symfony.com/2.1/Symfony/Component/Console/Formatter/OutputFormatterStyle.html

PDF brought to you by generated on October 26, 2012

Chapter 109: The Console Component | 552

11 12

'Your last name?' );

You now have access to a last_name argument in your command:


Listing 109-10 1

if ($lastName = $input->getArgument('last_name')) { 2 $text .= ' '.$lastName; 3 }

The command can now be used in either of the following ways:


Listing 109-11 1

$ app/console demo:greet Fabien 2 $ app/console demo:greet Fabien Potencier

Using Command Options


Unlike arguments, options are not ordered (meaning you can specify them in any order) and are specified with two dashes (e.g. --yell - you can also declare a one-letter shortcut that you can call with a single dash like -y). Options are always optional, and can be setup to accept a value (e.g. dir=src) or simply as a boolean flag without a value (e.g. yell).
It is also possible to make an option optionally accept a value (so that --yell or yell=loud work). Options can also be configured to accept an array of values.

For example, add a new option to the command that can be used to specify how many times in a row the message should be printed:
Listing 109-12 1

2 3 4 5 6 7 8 9

$this // ... ->addOption( 'iterations', null, InputOption::VALUE_REQUIRED, 'How many times should the message be printed?', 1 );

Next, use this in the command to print the message multiple times:
Listing 109-13 1

for ($i = 0; $i < $input->getOption('iterations'); $i++) { 2 $output->writeln($text); 3 }

Now, when you run the task, you can optionally specify a --iterations flag:
Listing 109-14 1

$ app/console demo:greet Fabien 2 $ app/console demo:greet Fabien --iterations=5

The first example will only print once, since iterations is empty and defaults to 1 (the last argument of addOption). The second example will print five times.
PDF brought to you by generated on October 26, 2012 Chapter 109: The Console Component | 553

Recall that options don't care about their order. So, either of the following will work:
Listing 109-15 1

$ app/console demo:greet Fabien --iterations=5 --yell 2 $ app/console demo:greet Fabien --yell --iterations=5

There are 4 option variants you can use: Option InputOption::VALUE_IS_ARRAY InputOption::VALUE_NONE Value This option accepts multiple values (e.g. --dir=/foo --dir=/ bar) Do not accept input for this option (e.g. --yell)

InputOption::VALUE_REQUIRED This value is required (e.g. --iterations=5), the option itself is still optional InputOption::VALUE_OPTIONAL This option may or may not have a value (e.g. yell or yell=loud) You can combine VALUE_IS_ARRAY with VALUE_REQUIRED or VALUE_OPTIONAL like this:
Listing 109-16 1

2 3 4 5 6 7 8 9

$this // ... ->addOption( 'iterations', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'How many times should the message be printed?', 1 );

Asking the User for Information


When creating commands, you have the ability to collect more information from the user by asking him/ her questions. For example, suppose you want to confirm an action before actually executing it. Add the following to your command:
Listing 109-17 1

$dialog = $this->getHelperSet()->get('dialog'); 2 if (!$dialog->askConfirmation( 3 $output, 4 '<question>Continue with this action?</question>', 5 false 6 )) { 7 return; 8 }

In this case, the user will be asked "Continue with this action", and unless they answer with y, the task will stop running. The third argument to askConfirmation is the default value to return if the user doesn't enter any input. You can also ask questions with more than a simple yes/no answer. For example, if you needed to know the name of something, you might do the following:
Listing 109-18

PDF brought to you by generated on October 26, 2012

Chapter 109: The Console Component | 554

1 $dialog = $this->getHelperSet()->get('dialog'); 2 $name = $dialog->ask( 3 $output, 4 'Please enter the name of the widget', 5 'foo' 6 );

Testing Commands
Symfony2 provides several tools to help you test your commands. The most useful one is the CommandTester3 class. It uses special input and output classes to ease testing without a real console:
Listing 109-19

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

use Symfony\Component\Console\Application; use Symfony\Component\Console\Tester\CommandTester; use Acme\DemoBundle\Command\GreetCommand; class ListCommandTest extends \PHPUnit_Framework_TestCase { public function testExecute() { $application = new Application(); $application->add(new GreetCommand()); $command = $application->find('demo:greet'); $commandTester = new CommandTester($command); $commandTester->execute(array('command' => $command->getName())); $this->assertRegExp('/.../', $commandTester->getDisplay());

// ...
} }

The getDisplay()4 method returns what would have been displayed during a normal call from the console. You can test sending arguments and options to the command by passing them as an array to the execute()5 method:
Listing 109-20

1 2 3 4 5 6 7 8 9 10 11 12

use Symfony\Component\Console\Application; use Symfony\Component\Console\Tester\CommandTester; use Acme\DemoBundle\Command\GreetCommand; class ListCommandTest extends \PHPUnit_Framework_TestCase { // ... public function testNameIsOutput() { $application = new Application(); $application->add(new GreetCommand());

3. http://api.symfony.com/2.1/Symfony/Component/Console/Tester/CommandTester.html 4. http://api.symfony.com/2.1/Symfony/Component/Console/Tester/CommandTester.html#getDisplay() 5. http://api.symfony.com/2.1/Symfony/Component/Console/Tester/CommandTester.html#execute()

PDF brought to you by generated on October 26, 2012

Chapter 109: The Console Component | 555

13 14 15 16 17 18 19 20 21 22 }

$command = $application->find('demo:greet'); $commandTester = new CommandTester($command); $commandTester->execute( array('command' => $command->getName(), 'name' => 'Fabien') ); $this->assertRegExp('/Fabien/', $commandTester->getDisplay()); }

You can also test a whole console application by using ApplicationTester6.

Calling an existing Command


If a command depends on another one being run before it, instead of asking the user to remember the order of execution, you can call it directly yourself. This is also useful if you want to create a "meta" command that just runs a bunch of other commands (for instance, all commands that need to be run when the project's code has changed on the production servers: clearing the cache, generating Doctrine2 proxies, dumping Assetic assets, ...). Calling a command from another one is straightforward:
Listing 109-21

1 protected function execute(InputInterface $input, OutputInterface $output) 2 { 3 $command = $this->getApplication()->find('demo:greet'); 4 5 $arguments = array( 6 'command' => 'demo:greet', 7 'name' => 'Fabien', 8 '--yell' => true, 9 ); 10 11 $input = new ArrayInput($arguments); 12 $returnCode = $command->run($input, $output); 13 14 // ... 15 }

First, you find()7 the command you want to execute by passing the command name. Then, you need to create a new ArrayInput8 with the arguments and options you want to pass to the command. Eventually, calling the run() method actually executes the command and returns the returned code from the command (return value from command's execute() method).

6. http://api.symfony.com/2.1/Symfony/Component/Console/Tester/ApplicationTester.html 7. http://api.symfony.com/2.1/Symfony/Component/Console/Application.html#find() 8. http://api.symfony.com/2.1/Symfony/Component/Console/Input/ArrayInput.html

PDF brought to you by generated on October 26, 2012

Chapter 109: The Console Component | 556

Most of the time, calling a command from code that is not executed on the command line is not a good idea for several reasons. First, the command's output is optimized for the console. But more important, you can think of a command as being like a controller; it should use the model to do something and display feedback to the user. So, instead of calling a command from the Web, refactor your code and move the logic to a new class.

Learn More!
Using Console Commands, Shortcuts and Built-in Commands How to build an Application that is a single Command

PDF brought to you by generated on October 26, 2012

Chapter 109: The Console Component | 557

Chapter 110

Using Console Commands, Shortcuts and Builtin Commands


In addition to the options you specify for your commands, there are some built-in options as well as a couple of built-in commands for the console component.
These examples assume you have added a file app/console to run at the cli:
Listing 110-1

1 2 3 4 5 6 7 8 9

#!/usr/bin/env php # app/console <?php


use Symfony\Component\Console\Application; $application = new Application(); // ... $application->run();

Built-in Commands
There is a built-in command list which outputs all the standard options and the registered commands:
Listing 110-2

1 $ php app/console list

You can get the same output by not running any command as well
Listing 110-3

1 $ php app/console

The help command lists the help information for the specified command. For example, to get the help for the list command:

PDF brought to you by generated on October 26, 2012

Chapter 110: Using Console Commands, Shortcuts and Built-in Commands | 558

Listing 110-4

1 $ php app/console help list

Running help without specifying a command will list the global options:
Listing 110-5

1 $ php app/console help

Global Options
You can get help information for any command with the --help option. To get help for the list command:
Listing 110-6

1 $ php app/console list --help 2 $ php app/console list -h

You can suppress output with:


Listing 110-7

1 $ php app/console list --quiet 2 $ php app/console list -q

You can get more verbose messages (if this is supported for a command) with:
Listing 110-8

1 $ php app/console list --verbose 2 $ php app/console list -v

If you set the optional arguments to give your application a name and version:
Listing 110-9

1 $application = new Application('Acme Console Application', '1.2');

then you can use:


Listing 110-10 1

$ php app/console list --version 2 $ php app/console list -V

to get this information output:


Listing 110-11 1

Acme Console Application version 1.2

If you do not provide both arguments then it will just output:


Listing 110-12 1

console tool

You can force turning on ANSI output coloring with:


Listing 110-13 1

$ php app/console list --ansi

or turn it off with:


Listing 110-14 1

$ php app/console list --no-ansi

PDF brought to you by generated on October 26, 2012

Chapter 110: Using Console Commands, Shortcuts and Built-in Commands | 559

You can suppress any interactive questions from the command you are running with:
Listing 110-15 1

$ php app/console list --no-interaction 2 $ php app/console list -n

Shortcut Syntax
You do not have to type out the full command names. You can just type the shortest unambiguous name to run a command. So if there are non-clashing commands, then you can run help like this:
Listing 110-16 1

$ php app/console h

If you have commands using : to namespace commands then you just have to type the shortest unambiguous text for each part. If you have created the demo:greet as shown in The Console Component then you can run it with:
Listing 110-17 1

$ php app/console d:g Fabien

If you enter a short command that's ambiguous (i.e. there are more than one command that match), then no command will be run and some suggestions of the possible commands to choose from will be output.

PDF brought to you by generated on October 26, 2012

Chapter 110: Using Console Commands, Shortcuts and Built-in Commands | 560

Chapter 111

How to build an Application that is a single Command


When building a command line tool, you may not need to provide several commands. In such case, having to pass the command name each time is tedious. Fortunately, it is possible to remove this need by extending the application:
Listing 111-1

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29

namespace Acme\Tool; use Symfony\Component\Console\Application; use Symfony\Component\Console\Input\InputInterface; class MyApplication extends Application { /** * Gets the name of the command based on input. * * @param InputInterface $input The input interface * * @return string The command name */ protected function getCommandName(InputInterface $input) { // This should return the name of your command. return 'my_command'; }

/** * Gets the default commands that should always be available. * * @return array An array of default Command instances */ protected function getDefaultCommands() { // Keep the core default commands to have the HelpCommand // which is used when using the --help option

PDF brought to you by generated on October 26, 2012

Chapter 111: How to build an Application that is a single Command | 561

30 31 32 33 34 35 36 }

$defaultCommands = parent::getDefaultCommands() $defaultCommands[] = new MyCommand(); return $defaultCommands; }

When calling your console script, the command MyCommand will then always be used, without having to pass its name.

PDF brought to you by generated on October 26, 2012

Chapter 111: How to build an Application that is a single Command | 562

Chapter 112

The CssSelector Component


The CssSelector Component converts CSS selectors to XPath expressions.

Installation
You can install the component in several different ways: Use the official Git repository (https://github.com/symfony/CssSelector1); Install it via PEAR ( pear.symfony.com/CssSelector); Install it via Composer (symfony/css-selector on Packagist).

Usage
Why use CSS selectors?
When you're parsing an HTML or an XML document, by far the most powerful method is XPath. XPath expressions are incredibly flexible, so there is almost always an XPath expression that will find the element you need. Unfortunately, they can also become very complicated, and the learning curve is steep. Even common operations (such as finding an element with a particular class) can require long and unwieldy expressions. Many developers -- particularly web developers -- are more comfortable using CSS selectors to find elements. As well as working in stylesheets, CSS selectors are used in Javascript with the querySelectorAll function and in popular Javascript libraries such as jQuery, Prototype and MooTools. CSS selectors are less powerful than XPath, but far easier to write, read and understand. Since they are less powerful, almost all CSS selectors can be converted to an XPath equivalent. This XPath expression can then be used with other functions and classes that use XPath to find elements in a document.

1. https://github.com/symfony/CssSelector

PDF brought to you by generated on October 26, 2012

Chapter 112: The CssSelector Component | 563

The CssSelector component


The component's only goal is to convert CSS selectors to their XPath equivalents:
Listing 112-1

1 use Symfony\Component\CssSelector\CssSelector; 2 3 print CssSelector::toXPath('div.item > h4 > a');

This gives the following output:


Listing 112-2

1 descendant-or-self::div[contains(concat(' ',normalize-space(@class), ' '), ' item ')]/h4/a

You can use this expression with, for instance, DOMXPath2 or SimpleXMLElement3 to find elements in a document.
The Crawler::filter()4 method uses the CssSelector component to find elements based on a CSS selector string. See the The DomCrawler Component for more details.

Limitations of the CssSelector component


Not all CSS selectors can be converted to XPath equivalents. There are several CSS selectors that only make sense in the context of a web-browser. link-state selectors: :link, :visited, :target selectors based on user action: :hover, :focus, :active UI-state selectors: :enabled, :disabled, :indeterminate (however, :checked and :unchecked are available) Pseudo-elements (:before, :after, :first-line, :first-letter) are not supported because they select portions of text rather than elements. Several pseudo-classes are not yet supported: :lang(language) root *:first-of-type, *:last-of-type, *:nth-of-type, *:nth-last-of-type, *:only-oftype. (These work with an element name (e.g. li:first-of-type) but not with *.

2. http://php.net/manual/en/class.domxpath.php 3. http://php.net/manual/en/class.simplexmlelement.php 4. http://api.symfony.com/2.1/Symfony/Component/DomCrawler/Crawler.html#filter()

PDF brought to you by generated on October 26, 2012

Chapter 112: The CssSelector Component | 564

Chapter 113

The DomCrawler Component


The DomCrawler Component eases DOM navigation for HTML and XML documents.

Installation
You can install the component in many different ways: Use the official Git repository (https://github.com/symfony/DomCrawler1); Install it via PEAR ( pear.symfony.com/DomCrawler); Install it via Composer (symfony/dom-crawler on Packagist).

Usage
The Crawler2 class provides methods to query and manipulate HTML and XML documents. An instance of the Crawler represents a set (SplObjectStorage3) of DOMElement4 objects, which are basically nodes that you can traverse easily:
Listing 113-1

1 2 3 4 5 6 7 8 9 10

use Symfony\Component\DomCrawler\Crawler; $html = <<<'HTML' <!DOCTYPE html> <html> <body> <p class="message">Hello World!</p> <p>Hello Crawler!</p> </body> </html>

1. https://github.com/symfony/DomCrawler 2. http://api.symfony.com/2.1/Symfony/Component/DomCrawler/Crawler.html 3. http://php.net/manual/en/class.splobjectstorage.php 4. http://php.net/manual/en/class.domelement.php

PDF brought to you by generated on October 26, 2012

Chapter 113: The DomCrawler Component | 565

11 12 13 14 15 16 17

HTML; $crawler = new Crawler($html); foreach ($crawler as $domElement) { print $domElement->nodeName; }

Specialized Link5 and Form6 classes are useful for interacting with html links and forms as you traverse through the HTML tree.

Node Filtering
Using XPath expressions is really easy:
Listing 113-2

1 $crawler = $crawler->filterXPath('descendant-or-self::body/p');

DOMXPath::query is used internally to actually perform an XPath query.

Filtering is even easier if you have the CssSelector Component installed. This allows you to use jQuerylike selectors to traverse:
Listing 113-3

1 $crawler = $crawler->filter('body > p');

Anonymous function can be used to filter with more complex criteria:


Listing 113-4

1 $crawler = $crawler->filter('body > p')->reduce(function ($node, $i) { 2 // filter even nodes 3 return ($i % 2) == 0; 4 });

To remove a node the anonymous function must return false.


All filter methods return a new Crawler7 instance with filtered content.

Node Traversing
Access node by its position on the list:
Listing 113-5

1 $crawler->filter('body > p')->eq(0);

Get the first or last node of the current selection:


Listing 113-6

5. http://api.symfony.com/2.1/Symfony/Component/DomCrawler/Link.html 6. http://api.symfony.com/2.1/Symfony/Component/DomCrawler/Form.html 7. http://api.symfony.com/2.1/Symfony/Component/DomCrawler/Crawler.html

PDF brought to you by generated on October 26, 2012

Chapter 113: The DomCrawler Component | 566

1 $crawler->filter('body > p')->first(); 2 $crawler->filter('body > p')->last();

Get the nodes of the same level as the current selection:


Listing 113-7

1 $crawler->filter('body > p')->siblings();

Get the same level nodes after or before the current selection:
Listing 113-8

1 $crawler->filter('body > p')->nextAll(); 2 $crawler->filter('body > p')->previousAll();

Get all the child or parent nodes:


Listing 113-9

1 $crawler->filter('body')->children(); 2 $crawler->filter('body > p')->parents();

All the traversal methods return a new Crawler8 instance.

Accessing Node Values


Access the value of the first node of the current selection:
Listing 113-10 1

$message = $crawler->filterXPath('//body/p')->text();

Access the attribute value of the first node of the current selection:
Listing 113-11 1

$class = $crawler->filterXPath('//body/p')->attr('class');

Extract attribute and/or node values from the list of nodes:


Listing 113-12 1

$attributes = $crawler->filterXpath('//body/p')->extract(array('_text', 'class'));

Special attribute _text represents a node value.

Call an anonymous function on each node of the list:


Listing 113-13 1

$nodeValues = $crawler->filter('p')->each(function ($node, $i) { 2 return $node->nodeValue; 3 });

The anonymous function receives the position and the node as arguments. The result is an array of values returned by the anonymous function calls.

8. http://api.symfony.com/2.1/Symfony/Component/DomCrawler/Crawler.html

PDF brought to you by generated on October 26, 2012

Chapter 113: The DomCrawler Component | 567

Adding the Content


The crawler supports multiple ways of adding the content:
Listing 113-14

1 2 3 4 5 6 7 8 9 10

$crawler = new Crawler('<html><body /></html>'); $crawler->addHtmlContent('<html><body /></html>'); $crawler->addXmlContent('<root><node /></root>'); $crawler->addContent('<html><body /></html>'); $crawler->addContent('<root><node /></root>', 'text/xml'); $crawler->add('<html><body /></html>'); $crawler->add('<root><node /></root>');

As the Crawler's implementation is based on the DOM extension, it is also able to interact with native DOMDocument9, DOMNodeList10 and DOMNode11 objects:
Listing 113-15

1 2 3 4 5 6 7 8 9 10

$document = new \DOMDocument(); $document->loadXml('<root><node /><node /></root>'); $nodeList = $document->getElementsByTagName('node'); $node = $document->getElementsByTagName('node')->item(0); $crawler->addDocument($document); $crawler->addNodeList($nodeList); $crawler->addNodes(array($node)); $crawler->addNode($node); $crawler->add($document);

Form and Link support


Special treatment is given to links and forms inside the DOM tree.

Links
To find a link by name (or a clickable image by its alt attribute), use the selectLink method on an existing crawler. This returns a Crawler instance with just the selected link(s). Calling link() gives us a special Link12 object:
Listing 113-16 1

$linksCrawler = $crawler->selectLink('Go elsewhere...'); 2 $link = $linksCrawler->link(); 3 4 // or do this all at once 5 $link = $crawler->selectLink('Go elsewhere...')->link();

The Link13 object has several useful methods to get more information about the selected link itself:
Listing 113-17 1

// return the proper URI that can be used to make another request 2 $uri = $link->getUri();

9. http://php.net/manual/en/class.domdocument.php 10. http://php.net/manual/en/class.domnodelist.php 11. http://php.net/manual/en/class.domnode.php 12. http://api.symfony.com/2.1/Symfony/Component/DomCrawler/Link.html 13. http://api.symfony.com/2.1/Symfony/Component/DomCrawler/Link.html

PDF brought to you by generated on October 26, 2012

Chapter 113: The DomCrawler Component | 568

The getUri() is especially useful as it cleans the href value and transforms it into how it should really be processed. For example, for a link with href="#foo", this would return the full URI of the current page suffixed with #foo. The return from getUri() is always a full URI that you can act on.

Forms
Special treatment is also given to forms. A selectButton() method is available on the Crawler which returns another Crawler that matches a button (input[type=submit], input[type=image], or a button) with the given text. This method is especially useful because you can use it to return a Form14 object that represents the form that the button lives in:
Listing 113-18 1

$form = $crawler->selectButton('validate')->form(); 2 3 // or "fill" the form fields with data 4 $form = $crawler->selectButton('validate')->form(array( 5 'name' => 'Ryan', 6 ));

The Form15 object has lots of very useful methods for working with forms:
Listing 113-19 1

$uri = $form->getUri(); 2 3 $method = $form->getMethod();

The getUri()16 method does more than just return the action attribute of the form. If the form method is GET, then it mimics the browser's behavior and returns the action attribute followed by a query string of all of the form's values. You can virtually set and get values on the form:
Listing 113-20

1 2 3 4 5 6 7 8 9 10 11

// set values on the form internally $form->setValues(array( 'registration[username]' => 'symfonyfan', 'registration[terms]' => 1, )); // get back an array of values - in the "flat" array like above $values = $form->getValues(); // returns the values like PHP would see them, where "registration" is its own array $values = $form->getPhpValues();

To work with multi-dimensional fields:


Listing 113-21 1

<form> 2 <input name="multi[]" /> 3 <input name="multi[]" /> 4 <input name="multi[dimensional]" /> 5 </form>

You must specify the fully qualified name of the field:


14. http://api.symfony.com/2.1/Symfony/Component/DomCrawler/Form.html 15. http://api.symfony.com/2.1/Symfony/Component/DomCrawler/Form.html 16. http://api.symfony.com/2.1/Symfony/Component/DomCrawler/Form.html#getUri()

PDF brought to you by generated on October 26, 2012

Chapter 113: The DomCrawler Component | 569

Listing 113-22 1

2 3 4 5 6 7 8

// Set a single field $form->setValue('multi[0]', 'value'); // Set multiple fields at once $form->setValue('multi', array( 1 => 'value', 'dimensional' => 'an other value' ));

This is great, but it gets better! The Form object allows you to interact with your form like a browser, selecting radio values, ticking checkboxes, and uploading files:
Listing 113-23

1 2 3 4 5 6 7 8 9 10 11 12 13 14

$form['registration[username]']->setValue('symfonyfan');

// check or uncheck a checkbox $form['registration[terms]']->tick(); $form['registration[terms]']->untick(); // select an option $form['registration[birthday][year]']->select(1984); // select many options from a "multiple" select or checkboxes $form['registration[interests]']->select(array('symfony', 'cookies')); // even fake a file upload $form['registration[photo]']->upload('/path/to/lucas.jpg');

What's the point of doing all of this? If you're testing internally, you can grab the information off of your form as if it had just been submitted by using the PHP values:
Listing 113-24 1

$values = $form->getPhpValues(); 2 $files = $form->getPhpFiles();

If you're using an external HTTP client, you can use the form to grab all of the information you need to create a POST request for the form:
Listing 113-25 1

2 3 4 5 6

$uri = $form->getUri(); $method = $form->getMethod(); $values = $form->getValues(); $files = $form->getFiles();

// now use some HTTP client and post using this information

One great example of an integrated system that uses all of this is Goutte17. Goutte understands the Symfony Crawler object and can use it to submit forms directly:
Listing 113-26

1 2 3 4 5 6 7 8

use Goutte\Client;

// make a real request to an external site $client = new Client(); $crawler = $client->request('GET', 'https://github.com/login'); // select the form and fill in some values $form = $crawler->selectButton('Log in')->form();

17. https://github.com/fabpot/goutte

PDF brought to you by generated on October 26, 2012

Chapter 113: The DomCrawler Component | 570

9 10 11 12 13

$form['login'] = 'symfonyfan'; $form['password'] = 'anypass';

// submit that form $crawler = $client->submit($form);

PDF brought to you by generated on October 26, 2012

Chapter 113: The DomCrawler Component | 571

Chapter 114

The Dependency Injection Component


The Dependency Injection component allows you to standardize and centralize the way objects are constructed in your application. For an introduction to Dependency Injection and service containers see Service Container

Installation
You can install the component in many different ways: Use the official Git repository (https://github.com/symfony/DependencyInjection1); Install it via PEAR ( pear.symfony.com/DependencyInjection); Install it via Composer (symfony/dependency-injection on Packagist).

Basic Usage
You might have a simple class like the following Mailer that you want to make available as a service:
Listing 114-1

1 class Mailer 2 { 3 private $transport; 4 5 public function __construct() 6 { 7 $this->transport = 'sendmail'; 8 } 9 10 // ... 11 }

You can register this in the container as a service:


1. https://github.com/symfony/DependencyInjection

PDF brought to you by generated on October 26, 2012

Chapter 114: The Dependency Injection Component | 572

Listing 114-2

1 use Symfony\Component\DependencyInjection\ContainerBuilder; 2 3 $container = new ContainerBuilder(); 4 $container->register('mailer', 'Mailer');

An improvement to the class to make it more flexible would be to allow the container to set the transport used. If you change the class so this is passed into the constructor:
Listing 114-3

1 class Mailer 2 { 3 private $transport; 4 5 public function __construct($transport) 6 { 7 $this->transport = $transport; 8 } 9 10 // ... 11 }

Then you can set the choice of transport in the container:


Listing 114-4

1 use Symfony\Component\DependencyInjection\ContainerBuilder; 2 3 $container = new ContainerBuilder(); 4 $container->register('mailer', 'Mailer') 5 ->addArgument('sendmail');

This class is now much more flexible as you have separated the choice of transport out of the implementation and into the container. Which mail transport you have chosen may be something other services need to know about. You can avoid having to change it in multiple places by making it a parameter in the container and then referring to this parameter for the Mailer service's constructor argument:
Listing 114-5

1 2 3 4 5 6

use Symfony\Component\DependencyInjection\ContainerBuilder; $container = new ContainerBuilder(); $container->setParameter('mailer.transport', 'sendmail'); $container->register('mailer', 'Mailer') ->addArgument('%mailer.transport%');

Now that the mailer service is in the container you can inject it as a dependency of other classes. If you have a NewsletterManager class like this:
Listing 114-6

1 use Mailer; 2 3 class NewsletterManager 4 { 5 private $mailer; 6 7 public function __construct(Mailer $mailer) 8 { 9 $this->mailer = $mailer; 10 } 11

PDF brought to you by generated on October 26, 2012

Chapter 114: The Dependency Injection Component | 573

12 13 }

// ...

Then you can register this as a service as well and pass the mailer service into it:
Listing 114-7

1 2 3 4 5 6 7 8 9 10 11

use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; $container = new ContainerBuilder(); $container->setParameter('mailer.transport', 'sendmail'); $container->register('mailer', 'Mailer') ->addArgument('%mailer.transport%'); $container->register('newsletter_manager', 'NewsletterManager') ->addArgument(new Reference('mailer');

If the NewsletterManager did not require the Mailer and injecting it was only optional then you could use setter injection instead:
Listing 114-8

1 2 3 4 5 6 7 8 9 10 11 12 13

use Mailer; class NewsletterManager { private $mailer; public function setMailer(Mailer $mailer) { $this->mailer = $mailer; }

// ...
}

You can now choose not to inject a Mailer into the NewsletterManager. If you do want to though then the container can call the setter method:
Listing 114-9

1 2 3 4 5 6 7 8 9 10 11

use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; $container = new ContainerBuilder(); $container->setParameter('mailer.transport', 'sendmail'); $container->register('mailer', 'Mailer') ->addArgument('%mailer.transport%'); $container->register('newsletter_manager', 'NewsletterManager') ->addMethodCall('setMailer', new Reference('mailer');

You could then get your newsletter_manager service from the container like this:
Listing 114-10 1

use Symfony\Component\DependencyInjection\ContainerBuilder; 2 use Symfony\Component\DependencyInjection\Reference; 3 4 $container = new ContainerBuilder(); 5

PDF brought to you by generated on October 26, 2012

Chapter 114: The Dependency Injection Component | 574

6 // ... 7 8 $newsletterManager = $container->get('newsletter_manager');

Avoiding Your Code Becoming Dependent on the Container


Whilst you can retrieve services from the container directly it is best to minimize this. For example, in the NewsletterManager you injected the mailer service in rather than asking for it from the container. You could have injected the container in and retrieved the mailer service from it but it would then be tied to this particular container making it difficult to reuse the class elsewhere. You will need to get a service from the container at some point but this should be as few times as possible at the entry point to your application.

Setting Up the Container with Configuration Files


As well as setting up the services using PHP as above you can also use configuration files. To do this you also need to install the Config Component. Loading an XML config file:
Listing 114-11 1

2 3 4 5 6 7

use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; $container = new ContainerBuilder(); $loader = new XmlFileLoader($container, new FileLocator(__DIR__)); $loader->load('services.xml');

Loading a YAML config file:


Listing 114-12 1

2 3 4 5 6 7

use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; $container = new ContainerBuilder(); $loader = new YamlFileLoader($container, new FileLocator(__DIR__)); $loader->load('services.yml');

If you want to load YAML config files then you will also need to install The YAML component.

The newsletter_manager and mailer services can be set up using config files:
Listing 114-13#

src/Acme/HelloBundle/Resources/config/services.yml parameters: # ... mailer.transport: sendmail services: mailer:


PDF brought to you by generated on October 26, 2012 Chapter 114: The Dependency Injection Component | 575

class: Mailer arguments: [%mailer.transport%] newsletter_manager: class: NewsletterManager calls: - [ setMailer, [ @mailer ] ]

PDF brought to you by generated on October 26, 2012

Chapter 114: The Dependency Injection Component | 576

Chapter 115

Types of Injection
Making a class's dependencies explicit and requiring that they be injected into it is a good way of making a class more reusable, testable and decoupled from others. There are several ways that the dependencies can be injected. Each injection point has advantages and disadvantages to consider, as well as different ways of working with them when using the service container.

Constructor Injection
The most common way to inject dependencies is via a class's constructor. To do this you need to add an argument to the constructor signature to accept the dependency:
Listing 115-1

1 class NewsletterManager 2 { 3 protected $mailer; 4 5 public function __construct(Mailer $mailer) 6 { 7 $this->mailer = $mailer; 8 } 9 10 // ... 11 }

You can specify what service you would like to inject into this in the service container configuration:
Listing 115-2

services: my_mailer: # ... newsletter_manager: class: NewsletterManager arguments: [@my_mailer]

PDF brought to you by generated on October 26, 2012

Chapter 115: Types of Injection | 577

Type hinting the injected object means that you can be sure that a suitable dependency has been injected. By type-hinting, you'll get a clear error immediately if an unsuitable dependency is injected. By type hinting using an interface rather than a class you can make the choice of dependency more flexible. And assuming you only use methods defined in the interface, you can gain that flexibility and still safely use the object.

There are several advantages to using constructor injection: If the dependency is a requirement and the class cannot work without it then injecting it via the constructor ensures it is present when the class is used as the class cannot be constructed without it. The constructor is only ever called once when the object is created, so you can be sure that the dependency will not change during the object's lifetime. These advantages do mean that constructor injection is not suitable for working with optional dependencies. It is also more difficult to use in combination with class hierarchies: if a class uses constructor injection then extending it and overriding the constructor becomes problematic.

Setter Injection
Another possible injection point into a class is by adding a setter method that accepts the dependency:
Listing 115-3

1 class NewsletterManager 2 { 3 protected $mailer; 4 5 public function setMailer(Mailer $mailer) 6 { 7 $this->mailer = $mailer; 8 } 9 10 // ... 11 }

Listing 115-4

services: my_mailer: # ... newsletter_manager: class: NewsletterManager calls: - [ setMailer, [ @my_mailer ] ]

This time the advantages are: Setter injection works well with optional dependencies. If you do not need the dependency, then just do not call the setter. You can call the setter multiple times. This is particularly useful if the method adds the dependency to a collection. You can then have a variable number of dependencies. The disadvantages of setter injection are: The setter can be called more than just at the time of construction so you cannot be sure the dependency is not replaced during the lifetime of the object (except by explicitly writing the setter method to check if has already been called).

PDF brought to you by generated on October 26, 2012

Chapter 115: Types of Injection | 578

You cannot be sure the setter will be called and so you need to add checks that any required dependencies are injected.

Property Injection
Another possibility is just setting public fields of the class directly:
Listing 115-5

1 class NewsletterManager 2 { 3 public $mailer; 4 5 // ... 6 }

Listing 115-6

services: my_mailer: # ... newsletter_manager: class: NewsletterManager properties: mailer: @my_mailer

There are mainly only disadvantages to using property injection, it is similar to setter injection but with these additional important problems: You cannot control when the dependency is set at all, it can be changed at any point in the object's lifetime. You cannot use type hinting so you cannot be sure what dependency is injected except by writing into the class code to explicitly test the class instance before using it. But, it is useful to know that this can be done with the service container, especially if you are working with code that is out of your control, such as in a third party library, which uses public properties for its dependencies.

PDF brought to you by generated on October 26, 2012

Chapter 115: Types of Injection | 579

Chapter 116

Working with Container Parameters and Definitions

Getting and Setting Container Parameters


Working with container parameters is straight forward using the container's accessor methods for parameters. You can check if a parameter has been defined in the container with:
Listing 116-1

1 $container->hasParameter($name);

You can retrieve parameters set in the container with:


Listing 116-2

1 $container->getParameter($name);

and set a parameter in the container with:


Listing 116-3

1 $container->setParameter($name, $value);

Getting and Setting Service Definitions


There are also some helpful methods for working with the service definitions. To find out if there is a definition for a service id:
Listing 116-4

1 $container->hasDefinition($serviceId);

This is useful if you only want to do something if a particular definition exists. You can retrieve a definition with:

PDF brought to you by generated on October 26, 2012

Chapter 116: Working with Container Parameters and Definitions | 580

Listing 116-5

1 $container->getDefinition($serviceId);

or:
Listing 116-6

1 $container->findDefinition($serviceId);

which unlike getDefinition() also resolves aliases so if the $serviceId argument is an alias you will get the underlying definition. The service definitions themselves are objects so if you retrieve a definition with these methods and make changes to it these will be reflected in the container. If, however, you are creating a new definition then you can add it to the container using:
Listing 116-7

1 $container->setDefinition($id, $definition);

Working with a definition


Creating a new definition
If you need to create a new definition rather than manipulate one retrieved from then container then the definition class is Definition1.

Class
First up is the class of a definition, this is the class of the object returned when the service is requested from the container. To find out what class is set for a definition:
Listing 116-8

1 $definition->getClass();

and to set a different class:


Listing 116-9

1 $definition->setClass($class); // Fully qualified class name as string

Constructor Arguments
To get an array of the constructor arguments for a definition you can use:
Listing 116-10 1

$definition->getArguments();

or to get a single argument by its position:


Listing 116-11 1

$definition->getArgument($index); 2 //e.g. $definition->getArguments(0) for the first argument

You can add a new argument to the end of the arguments array using:
Listing 116-12

1. http://api.symfony.com/2.1/Symfony/Component/DependencyInjection/Definition.html

PDF brought to you by generated on October 26, 2012

Chapter 116: Working with Container Parameters and Definitions | 581

1 $definition->addArgument($argument);

The argument can be a string, an array, a service parameter by using %paramater_name% or a service id by using
Listing 116-13 1

use Symfony\Component\DependencyInjection\Reference; 2 3 // ... 4 5 $definition->addArgument(new Reference('service_id'));

In a similar way you can replace an already set argument by index using:
Listing 116-14 1

$definition->replaceArgument($index, $argument);

You can also replace all the arguments (or set some if there are none) with an array of arguments:
Listing 116-15 1

$definition->replaceArguments($arguments);

Method Calls
If the service you are working with uses setter injection then you can manipulate any method calls in the definitions as well. You can get an array of all the method calls with:
Listing 116-16 1

$definition->getMethodCalls();

Add a method call with:


Listing 116-17 1

$definition->addMethodCall($method, $arguments);

Where $method is the method name and $arguments is an array of the arguments to call the method with. The arguments can be strings, arrays, parameters or service ids as with the constructor arguments. You can also replace any existing method calls with an array of new ones with:
Listing 116-18 1

$definition->setMethodCalls($methodCalls);

PDF brought to you by generated on October 26, 2012

Chapter 116: Working with Container Parameters and Definitions | 582

Chapter 117

Compiling the Container


The service container can be compiled for various reasons. These reasons include checking for any potential issues such as circular references and making the container more efficient by resolving parameters and removing unused services. It is compiled by running:
Listing 117-1

1 $container->compile();

The compile method uses Compiler Passes for the compilation. The Dependency Injection component comes with several passes which are automatically registered for compilation. For example the CheckDefinitionValidityPass1 checks for various potential issues with the definitions that have been set in the container. After this and several other passes that check the container's validity, further compiler passes are used to optimize the configuration before it is cached. For example, private services and abstract services are removed, and aliases are resolved.

Managing Configuration with Extensions


As well as loading configuration directly into the container as shown in The Dependency Injection Component, you can manage it by registering extensions with the container. The first step in the compilation process is to load configuration from any extension classes registered with the container. Unlike the configuration loaded directly, they are only processed when the container is compiled. If your application is modular then extensions allow each module to register and manage their own service configuration. The extensions must implement ExtensionInterface2 and can be registered with the container with:
Listing 117-2

1 $container->registerExtension($extension);

The main work of the extension is done in the load method. In the load method you can load configuration from one or more configuration files as well as manipulate the container definitions using the methods shown in Working with Container Parameters and Definitions.
1. http://api.symfony.com/2.1/Symfony/Component/DependencyInjection/Compiler/CheckDefinitionValidityPass.html 2. http://api.symfony.com/2.1/Symfony/Component/DependencyInjection/Extension/ExtensionInterface.html

PDF brought to you by generated on October 26, 2012

Chapter 117: Compiling the Container | 583

The load method is passed a fresh container to set up, which is then merged afterwards into the container it is registered with. This allows you to have several extensions managing container definitions independently. The extensions do not add to the containers configuration when they are added but are processed when the container's compile method is called. A very simple extension may just load configuration files into the container:
Listing 117-3

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

use use use use

Symfony\Component\DependencyInjection\ContainerBuilder; Symfony\Component\DependencyInjection\Loader\XmlFileLoader; Symfony\Component\DependencyInjection\Extension\ExtensionInterface; Symfony\Component\Config\FileLocator;

class AcmeDemoExtension implements ExtensionInterface { public function load(array $configs, ContainerBuilder $container) { $loader = new XmlFileLoader( $container, new FileLocator(__DIR__.'/../Resources/config') ); $loader->load('services.xml'); }

// ...
}

This does not gain very much compared to loading the file directly into the overall container being built. It just allows the files to be split up amongst the modules/bundles. Being able to affect the configuration of a module from configuration files outside of the module/bundle is needed to make a complex application configurable. This can be done by specifying sections of config files loaded directly into the container as being for a particular extension. These sections on the config will not be processed directly by the container but by the relevant Extension. The Extension must specify a getAlias method to implement the interface:
Listing 117-4

1 2 3 4 5 6 7 8 9 10 11

// ...
class AcmeDemoExtension implements ExtensionInterface { // ... public function getAlias() { return 'acme_demo'; } }

For YAML configuration files specifying the alias for the Extension as a key will mean that those values are passed to the Extension's load method:
Listing 117-5

1 # ... 2 acme_demo: 3 foo: fooValue 4 bar: barValue

If this file is loaded into the configuration then the values in it are only processed when the container is compiled at which point the Extensions are loaded:
Listing 117-6

PDF brought to you by generated on October 26, 2012

Chapter 117: Compiling the Container | 584

1 2 3 4 5 6 7 8 9 10 11

use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; $container = new ContainerBuilder(); $loader = new YamlFileLoader($container, new FileLocator(__DIR__)); $loader->load('config.yml'); $container->registerExtension(new AcmeDemoExtension); // ... $container->compile();

The values from those sections of the config files are passed into the first argument of the load method of the extension:
Listing 117-7

1 public function load(array $configs, ContainerBuilder $container) 2 { 3 $foo = $configs[0]['foo']; //fooValue 4 $bar = $configs[0]['bar']; //barValue 5 }

The $configs argument is an array containing each different config file that was loaded into the container. You are only loading a single config file in the above example but it will still be within an array. The array will look like this:
Listing 117-8

1 array( 2 array( 3 'foo' => 'fooValue', 4 'bar' => 'barValue', 5 ) 6 )

Whilst you can manually manage merging the different files, it is much better to use the Config Component to merge and validate the config values. Using the configuration processing you could access the config value this way:
Listing 117-9

1 2 3 4 5 6 7 8 9 10 11 12 13 14

use Symfony\Component\Config\Definition\Processor; // ... public function load(array $configs, ContainerBuilder $container) { $configuration = new Configuration(); $processor = new Processor(); $config = $processor->processConfiguration($configuration, $configs); $foo = $config['foo']; //fooValue $bar = $config['bar']; //barValue

// ...
}

There are a further two methods you must implement. One to return the XML namespace so that the relevant parts of an XML config file are passed to the extension. The other to specify the base path to XSD files to validate the XML configuration:
Listing 117-10

PDF brought to you by generated on October 26, 2012

Chapter 117: Compiling the Container | 585

1 2 3 4 5 6 7 8 9

public function getXsdValidationBasePath() { return __DIR__.'/../Resources/config/'; } public function getNamespace() { return 'http://www.example.com/symfony/schema/'; }

XSD validation is optional, returning false from the getXsdValidationBasePath method will disable it.

The XML version of the config would then look like this:
Listing 117-11

1 <?xml version="1.0" ?> 2 <container xmlns="http://symfony.com/schema/dic/services" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:acme_demo="http://www.example.com/symfony/schema/" 5 xsi:schemaLocation="http://www.example.com/symfony/schema/ http://www.example.com/ 6 symfony/schema/hello-1.0.xsd"> 7 8 <acme_demo:config> 9 <acme_demo:foo>fooValue</acme_hello:foo> 10 <acme_demo:bar>barValue</acme_demo:bar> 11 </acme_demo:config> 12 </container>

In the Symfony2 full stack framework there is a base Extension class which implements these methods as well as a shortcut method for processing the configuration. See How to expose a Semantic Configuration for a Bundle for more details.

The processed config value can now be added as container parameters as if it were listed in a parameters section of the config file but with the additional benefit of merging multiple files and validation of the configuration:
Listing 117-12

1 public function load(array $configs, ContainerBuilder $container) 2 { 3 $configuration = new Configuration(); 4 $processor = new Processor(); 5 $config = $processor->processConfiguration($configuration, $configs); 6 7 $container->setParameter('acme_demo.FOO', $config['foo']) 8 9 // ... 10 }

More complex configuration requirements can be catered for in the Extension classes. For example, you may choose to load a main service configuration file but also load a secondary one only if a certain parameter is set:
PDF brought to you by generated on October 26, 2012 Chapter 117: Compiling the Container | 586

Listing 117-13

1 public function load(array $configs, ContainerBuilder $container) 2 { 3 $configuration = new Configuration(); 4 $processor = new Processor(); 5 $config = $processor->processConfiguration($configuration, $configs); 6 7 $loader = new XmlFileLoader( 8 $container, 9 new FileLocator(__DIR__.'/../Resources/config') 10 ); 11 $loader->load('services.xml'); 12 13 if ($config['advanced']) { 14 $loader->load('advanced.xml'); 15 } 16 }

If you need to manipulate the configuration loaded by an extension then you cannot do it from another extension as it uses a fresh container. You should instead use a compiler pass which works with the full container after the extensions have been processed.

Creating a Compiler Pass


You can also create and register your own compiler passes with the container. To create a compiler pass it needs to implements the CompilerPassInterface3 interface. The compiler pass gives you an opportunity to manipulate the service definitions that have been compiled. This can be very powerful, but is not something needed in everyday use. The compiler pass must have the process method which is passed the container being compiled:
Listing 117-14 1

class CustomCompilerPass 2 { 3 public function process(ContainerBuilder $container) 4 { 5 // ... 6 } 7 }

The container's parameters and definitions can be manipulated using the methods described in the Working with Container Parameters and Definitions. One common thing to do in a compiler pass is to search for all services that have a certain tag in order to process them in some way or dynamically plug each into some other service.

Registering a Compiler Pass


You need to register your custom pass with the container. Its process method will then be called when the container is compiled:
Listing 117-15

3. http://api.symfony.com/2.1/Symfony/Component/DependencyInjection/Compiler/CompilerPassInterface.html

PDF brought to you by generated on October 26, 2012

Chapter 117: Compiling the Container | 587

1 use Symfony\Component\DependencyInjection\ContainerBuilder; 2 3 $container = new ContainerBuilder(); 4 $container->addCompilerPass(new CustomCompilerPass);

Compiler passes are registered differently if you are using the full stack framework, see How to work with Compiler Passes in Bundles for more details.

Controlling the Pass Ordering


The default compiler passes are grouped into optimization passes and removal passes. The optimization passes run first and include tasks such as resolving references within the definitions. The removal passes perform tasks such as removing private aliases and unused services. You can choose where in the order any custom passes you add are run. By default they will be run before the optimization passes. You can use the following constants as the second argument when registering a pass with the container to control where it goes in the order: PassConfig::TYPE_BEFORE_OPTIMIZATION PassConfig::TYPE_OPTIMIZE PassConfig::TYPE_BEFORE_REMOVING PassConfig::TYPE_REMOVE PassConfig::TYPE_AFTER_REMOVING

For example, to run your custom pass after the default removal passes have been run:
Listing 117-16 1

2 3 4 5 6 7 8

use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Compiler\PassConfig; $container = new ContainerBuilder(); $container->addCompilerPass( new CustomCompilerPass, PassConfig::TYPE_AFTER_REMOVING );

Dumping the Configuration for Performance


Using configuration files to manage the service container can be much easier to understand than using PHP once there are a lot of services. This ease comes at a price though when it comes to performance as the config files need to be parsed and the PHP configuration built from them. The compilation process makes the container more efficient but it takes time to run. You can have the best of both worlds though by using configuration files and then dumping and caching the resulting configuration. The PhpDumper makes dumping the compiled container easy:
Listing 117-17

1 2 3 4 5 6

use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Dumper\PhpDumper $file = __DIR__ .'/cache/container.php'; if (file_exists($file)) {

PDF brought to you by generated on October 26, 2012

Chapter 117: Compiling the Container | 588

7 require_once $file; 8 $container = new ProjectServiceContainer(); 9 } else { 10 $container = new ContainerBuilder(); 11 // ... 12 $container->compile(); 13 14 $dumper = new PhpDumper($container); 15 file_put_contents($file, $dumper->dump()); 16 }

ProjectServiceContainer is the default name given to the dumped container class, you can change this though this with the class option when you dump it:
Listing 117-18

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

// ... $file = __DIR__ .'/cache/container.php';


if (file_exists($file)) { require_once $file; $container = new MyCachedContainer(); } else { $container = new ContainerBuilder(); // ... $container->compile(); $dumper = new PhpDumper($container); file_put_contents( $file, $dumper->dump(array('class' => 'MyCachedContainer')) ); }

You will now get the speed of the PHP configured container with the ease of using configuration files. Additionally dumping the container in this way further optimizes how the services are created by the container. In the above example you will need to delete the cached container file whenever you make any changes. Adding a check for a variable that determines if you are in debug mode allows you to keep the speed of the cached container in production but getting an up to date configuration whilst developing your application:
Listing 117-19

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

// ... // based on something in your project $isDebug = ...;


$file = __DIR__ .'/cache/container.php'; if (!$isDebug && file_exists($file)) { require_once $file; $container = new MyCachedContainer(); } else { $container = new ContainerBuilder(); // ... $container->compile(); if (!$isDebug) {

PDF brought to you by generated on October 26, 2012

Chapter 117: Compiling the Container | 589

17 18 19 20 21 22 23 }

$dumper = new PhpDumper($container); file_put_contents( $file, $dumper->dump(array('class' => 'MyCachedContainer')) ); }

This could be further improved by only recompiling the container in debug mode when changes have been made to its configuration rather than on every request. This can be done by caching the resource files used to configure the container in the way described in "Caching based on resources" in the config component documentation. You do not need to work out which files to cache as the container builder keeps track of all the resources used to configure it, not just the configuration files but the extension classes and compiler passes as well. This means that any changes to any of these files will invalidate the cache and trigger the container being rebuilt. You just need to ask the container for these resources and use them as metadata for the cache:
Listing 117-20

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

// ... // based on something in your project $isDebug = ...;


$file = __DIR__ .'/cache/container.php'; $containerConfigCache = new ConfigCache($file, $isDebug); if (!$containerConfigCache->isFresh()) { $containerBuilder = new ContainerBuilder(); // ... $containerBuilder->compile(); $dumper = new PhpDumper($containerBuilder); $containerConfigCache->write( $dumper->dump(array('class' => 'MyCachedContainer')), $containerBuilder->getResources() ); } require_once $file; $container = new MyCachedContainer();

Now the cached dumped container is used regardless of whether debug mode is on or not. The difference is that the ConfigCache is set to debug mode with its second constructor argument. When the cache is not in debug mode the cached container will always be used if it exists. In debug mode, an additional metadata file is written with the timestamps of all the resource files. These are then checked to see if the files have changed, if they have the cache will be considered stale.
In the full stack framework the compilation and caching of the container is taken care of for you.

PDF brought to you by generated on October 26, 2012

Chapter 117: Compiling the Container | 590

Chapter 118

Working with Tagged Services


Tags are a generic string (along with some options) that can be applied to any service. By themselves, tags don't actually alter the functionality of your services in any way. But if you choose to, you can ask a container builder for a list of all services that were tagged with some specific tag. This is useful in compiler passes where you can find these services and use or modify them in some specific way. For example, if you are using Swift Mailer you might imagine that you want to implement a "transport chain", which is a collection of classes implementing \Swift_Transport. Using the chain, you'll want Swift Mailer to try several ways of transporting the message until one succeeds. To begin with, define the TransportChain class:
Listing 118-1

1 class TransportChain 2 { 3 private $transports; 4 5 public function __construct() 6 { 7 $this->transports = array(); 8 } 9 10 public function addTransport(\Swift_Transport $transport) 11 { 12 $this->transports[] = $transport; 13 } 14 }

Then, define the chain as a service:


Listing 118-2

1 parameters: 2 acme_mailer.transport_chain.class: TransportChain 3 4 services: 5 acme_mailer.transport_chain: 6 class: "%acme_mailer.transport_chain.class%"

PDF brought to you by generated on October 26, 2012

Chapter 118: Working with Tagged Services | 591

Define Services with a Custom Tag


Now you might want several of the \Swift_Transport classes to be instantiated and added to the chain automatically using the addTransport() method. For example you may add the following transports as services:
Listing 118-3

services: acme_mailer.transport.smtp: class: \Swift_SmtpTransport arguments: - %mailer_host% tags: - { name: acme_mailer.transport } acme_mailer.transport.sendmail: class: \Swift_SendmailTransport tags: - { name: acme_mailer.transport }

Notice that each was given a tag named acme_mailer.transport. This is the custom tag that you'll use in your compiler pass. The compiler pass is what makes this tag "mean" something.

Create a CompilerPass
Your compiler pass can now ask the container for any services with the custom tag:
Listing 118-4

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Reference; class TransportCompilerPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { if (!$container->hasDefinition('acme_mailer.transport_chain')) { return; } $definition = $container->getDefinition( 'acme_mailer.transport_chain' ); $taggedServices = $container->findTaggedServiceIds( 'acme_mailer.transport' ); foreach ($taggedServices as $id => $attributes) { $definition->addMethodCall( 'addTransport', array(new Reference($id)) ); } } }

The process() method checks for the existence of the acme_mailer.transport_chain service, then looks for all services tagged acme_mailer.transport. It adds to the definition of the acme_mailer.transport_chain service a call to addTransport() for each "acme_mailer.transport" service it has found. The first argument of each of these calls will be the mailer transport service itself.
PDF brought to you by generated on October 26, 2012 Chapter 118: Working with Tagged Services | 592

Register the Pass with the Container


You also need to register the pass with the container, it will then be run when the container is compiled:
Listing 118-5

1 use Symfony\Component\DependencyInjection\ContainerBuilder; 2 3 $container = new ContainerBuilder(); 4 $container->addCompilerPass(new TransportCompilerPass);

Compiler passes are registered differently is you are using the full stack framework, see How to work with Compiler Passes in Bundles for more details.

Adding additional attributes on Tags


Sometimes you need additional information about each service that's tagged with your tag. For example, you might want to add an alias to each TransportChain. To begin with, change the TransportChain class:
Listing 118-6

1 class TransportChain 2 { 3 private $transports; 4 5 public function __construct() 6 { 7 $this->transports = array(); 8 } 9 10 public function addTransport(\Swift_Transport $transport, $alias) 11 { 12 $this->transports[$alias] = $transport; 13 } 14 15 public function getTransport($alias) 16 { 17 if (array_key_exists($alias, $this->transports)) { 18 return $this->transports[$alias]; 19 } 20 else { 21 return; 22 } 23 } 24 }

As you can see, when addTransport is called, it takes not only a Swift_Transport object, but also a string alias for that transport. So, how can you allow each tagged transport service to also supply an alias? To answer this, change the service declaration:
Listing 118-7

services: acme_mailer.transport.smtp: class: \Swift_SmtpTransport arguments: - %mailer_host%

PDF brought to you by generated on October 26, 2012

Chapter 118: Working with Tagged Services | 593

tags: - { name: acme_mailer.transport, alias: foo } acme_mailer.transport.sendmail: class: \Swift_SendmailTransport tags: - { name: acme_mailer.transport, alias: bar }

Notice that you've added a generic alias key to the tag. To actually use this, update the compiler:
Listing 118-8

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29

use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Reference; class TransportCompilerPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { if (!$container->hasDefinition('acme_mailer.transport_chain')) { return; } $definition = $container->getDefinition( 'acme_mailer.transport_chain' ); $taggedServices = $container->findTaggedServiceIds( 'acme_mailer.transport' ); foreach ($taggedServices as $id => $tagAttributes) { foreach ($tagAttributes as $attributes) { $definition->addMethodCall( 'addTransport', array(new Reference($id), $attributes["alias"]) ); } } } }

The trickiest part is the $attributes variable. Because you can use the same tag many times on the same service (e.g. you could theoretically tag the same service 5 times with the acme_mailer.transport tag), $attributes is an array of the tag information for each tag on that service.

PDF brought to you by generated on October 26, 2012

Chapter 118: Working with Tagged Services | 594

Chapter 119

Using a Factory to Create Services


Symfony2's Service Container provides a powerful way of controlling the creation of objects, allowing you to specify arguments passed to the constructor as well as calling methods and setting parameters. Sometimes, however, this will not provide you with everything you need to construct your objects. For this situation, you can use a factory to create the object and tell the service container to call a method on the factory rather than directly instantiating the object. Suppose you have a factory that configures and returns a new NewsletterManager object:
Listing 119-1

1 class NewsletterFactory 2 { 3 public function get() 4 { 5 $newsletterManager = new NewsletterManager(); 6 7 // ... 8 9 return $newsletterManager; 10 } 11 }

To make the NewsletterManager object available as a service, you can configure the service container to use the NewsletterFactory factory class:
Listing 119-2

1 parameters: 2 # ... 3 newsletter_manager.class: NewsletterManager 4 newsletter_factory.class: NewsletterFactory 5 services: 6 newsletter_manager: 7 class: "%newsletter_manager.class%" 8 factory_class: "%newsletter_factory.class%" 9 factory_method: get

When you specify the class to use for the factory (via factory_class) the method will be called statically. If the factory itself should be instantiated and the resulting object's method called (as in this example), configure the factory itself as a service:
PDF brought to you by generated on October 26, 2012 Chapter 119: Using a Factory to Create Services | 595

Listing 119-3

1 parameters: 2 # ... 3 newsletter_manager.class: NewsletterManager 4 newsletter_factory.class: NewsletterFactory 5 services: 6 newsletter_factory: 7 class: "%newsletter_factory.class%" 8 newsletter_manager: 9 class: "%newsletter_manager.class%" 10 factory_service: newsletter_factory 11 factory_method: get

The factory service is specified by its id name and not a reference to the service itself. So, you do not need to use the @ syntax.

Passing Arguments to the Factory Method


If you need to pass arguments to the factory method, you can use the arguments options inside the service container. For example, suppose the get method in the previous example takes the templating service as an argument:
Listing 119-4

parameters: # ... newsletter_manager.class: NewsletterManager newsletter_factory.class: NewsletterFactory services: newsletter_factory: class: "%newsletter_factory.class%" newsletter_manager: class: "%newsletter_manager.class%" factory_service: newsletter_factory factory_method: get arguments: @templating

PDF brought to you by generated on October 26, 2012

Chapter 119: Using a Factory to Create Services | 596

Chapter 120

Managing Common Dependencies with Parent Services


As you add more functionality to your application, you may well start to have related classes that share some of the same dependencies. For example you may have a Newsletter Manager which uses setter injection to set its dependencies:
Listing 120-1

1 class NewsletterManager 2 { 3 protected $mailer; 4 protected $emailFormatter; 5 6 public function setMailer(Mailer $mailer) 7 { 8 $this->mailer = $mailer; 9 } 10 11 public function setEmailFormatter(EmailFormatter $emailFormatter) 12 { 13 $this->emailFormatter = $emailFormatter; 14 } 15 16 // ... 17 }

and also a Greeting Card class which shares the same dependencies:
Listing 120-2

1 class GreetingCardManager 2 { 3 protected $mailer; 4 protected $emailFormatter; 5 6 public function setMailer(Mailer $mailer) 7 { 8 $this->mailer = $mailer;

PDF brought to you by generated on October 26, 2012

Chapter 120: Managing Common Dependencies with Parent Services | 597

9 10 11 12 13 14 15 16 17 }

} public function setEmailFormatter(EmailFormatter $emailFormatter) { $this->emailFormatter = $emailFormatter; }

// ...

The service config for these classes would look something like this:
Listing 120-3

parameters: # ... newsletter_manager.class: NewsletterManager greeting_card_manager.class: GreetingCardManager services: my_mailer: # ... my_email_formatter: # ... newsletter_manager: class: "%newsletter_manager.class%" calls: - [ setMailer, [ @my_mailer ] ] - [ setEmailFormatter, [ @my_email_formatter] ] greeting_card_manager: class: "%greeting_card_manager.class%" calls: - [ setMailer, [ @my_mailer ] ] - [ setEmailFormatter, [ @my_email_formatter] ]

There is a lot of repetition in both the classes and the configuration. This means that if you changed, for example, the Mailer of EmailFormatter classes to be injected via the constructor, you would need to update the config in two places. Likewise if you needed to make changes to the setter methods you would need to do this in both classes. The typical way to deal with the common methods of these related classes would be to extract them to a super class:
Listing 120-4

1 abstract class MailManager 2 { 3 protected $mailer; 4 protected $emailFormatter; 5 6 public function setMailer(Mailer $mailer) 7 { 8 $this->mailer = $mailer; 9 } 10 11 public function setEmailFormatter(EmailFormatter $emailFormatter) 12 { 13 $this->emailFormatter = $emailFormatter; 14 } 15 16 // ... 17 }

The NewsletterManager and GreetingCardManager can then extend this super class:
PDF brought to you by generated on October 26, 2012 Chapter 120: Managing Common Dependencies with Parent Services | 598

Listing 120-5

1 class NewsletterManager extends MailManager 2 { 3 // ... 4 }

and:
Listing 120-6

1 class GreetingCardManager extends MailManager 2 { 3 // ... 4 }

In a similar fashion, the Symfony2 service container also supports extending services in the configuration so you can also reduce the repetition by specifying a parent for a service.
Listing 120-7

parameters: # ... newsletter_manager.class: NewsletterManager greeting_card_manager.class: GreetingCardManager mail_manager.class: MailManager services: my_mailer: # ... my_email_formatter: # ... mail_manager: class: "%mail_manager.class%" abstract: true calls: - [ setMailer, [ @my_mailer ] ] - [ setEmailFormatter, [ @my_email_formatter] ] newsletter_manager: class: "%newsletter_manager.class%" parent: mail_manager greeting_card_manager: class: "%greeting_card_manager.class%" parent: mail_manager

In this context, having a parent service implies that the arguments and method calls of the parent service should be used for the child services. Specifically, the setter methods defined for the parent service will be called when the child services are instantiated.
If you remove the parent config key, the services will still be instantiated and they will still of course extend the MailManager class. The difference is that omitting the parent config key will mean that the calls defined on the mail_manager service will not be executed when the child services are instantiated.

The parent class is abstract as it should not be directly instantiated. Setting it to abstract in the config file as has been done above will mean that it can only be used as a parent service and cannot be used directly as a service to inject and will be removed at compile time. In other words, it exists merely as a "template" that other services can use.

PDF brought to you by generated on October 26, 2012

Chapter 120: Managing Common Dependencies with Parent Services | 599

In order for parent dependencies to resolve, the ContainerBuilder must first be compiled. See Compiling the Container for more details.

Overriding Parent Dependencies


There may be times where you want to override what class is passed in for a dependency of one child service only. Fortunately, by adding the method call config for the child service, the dependencies set by the parent class will be overridden. So if you needed to pass a different dependency just to the NewsletterManager class, the config would look like this:
Listing 120-8

parameters: # ... newsletter_manager.class: NewsletterManager greeting_card_manager.class: GreetingCardManager mail_manager.class: MailManager services: my_mailer: # ... my_alternative_mailer: # ... my_email_formatter: # ... mail_manager: class: "%mail_manager.class%" abstract: true calls: - [ setMailer, [ @my_mailer ] ] - [ setEmailFormatter, [ @my_email_formatter] ] newsletter_manager: class: "%newsletter_manager.class%" parent: mail_manager calls: - [ setMailer, [ @my_alternative_mailer ] ] greeting_card_manager: class: "%greeting_card_manager.class%" parent: mail_manager

The GreetingCardManager will receive the same dependencies as before, but the NewsletterManager will be passed the my_alternative_mailer instead of the my_mailer service.

Collections of Dependencies
It should be noted that the overridden setter method in the previous example is actually called twice once per the parent definition and once per the child definition. In the previous example, that was fine, since the second setMailer call replaces mailer object set by the first call. In some cases, however, this can be a problem. For example, if the overridden method call involves adding something to a collection, then two objects will be added to that collection. The following shows such a case, if the parent class looks like this:
Listing 120-9

PDF brought to you by generated on October 26, 2012

Chapter 120: Managing Common Dependencies with Parent Services | 600

1 abstract class MailManager 2 { 3 protected $filters; 4 5 public function setFilter($filter) 6 { 7 $this->filters[] = $filter; 8 } 9 10 // ... 11 }

If you had the following config:


Listing 120-10parameters:

# ... newsletter_manager.class: NewsletterManager mail_manager.class: MailManager services: my_filter: # ... another_filter: # ... mail_manager: class: "%mail_manager.class%" abstract: true calls: - [ setFilter, [ @my_filter ] ] newsletter_manager: class: "%newsletter_manager.class%" parent: mail_manager calls: - [ setFilter, [ @another_filter ] ]

In this example, the setFilter of the newsletter_manager service will be called twice, resulting in the $filters array containing both my_filter and another_filter objects. This is great if you just want to add additional filters to the subclasses. If you want to replace the filters passed to the subclass, removing the parent setting from the config will prevent the base class from calling setFilter.

PDF brought to you by generated on October 26, 2012

Chapter 120: Managing Common Dependencies with Parent Services | 601

Chapter 121

Advanced Container Configuration

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 121-1

1 $doctrine = $container->get('doctrine');

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.
If you use a private service as an argument to only one other service, this will result in an inlined instantiation (e.g. new PrivateFooBar()) inside this other service, making it publicly unavailable at runtime.

Simply said: A service will be private when you do not want to access it directly from your code. Here is an example:
Listing 121-2

1 services: 2 foo: 3 class: Example\Foo 4 public: false

Now that the service is private, you cannot call:


Listing 121-3

1 $container->get('foo');

However, if a service has been marked as private, you can still alias it (see below) to access this service (via the alias).

PDF brought to you by generated on October 26, 2012

Chapter 121: Advanced Container Configuration | 602

Services are by default public.

Aliasing
You may sometimes want to use shortcuts to access some services. You can do so by aliasing them and, furthermore, you can even alias non-public services.
Listing 121-4

1 services: 2 foo: 3 class: Example\Foo 4 bar: 5 alias: foo

This means that when using the container directly, you can access the foo service by asking for the bar service like this:
Listing 121-5

1 $container->get('bar'); // Would return the foo service

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.
Listing 121-6

1 services: 2 foo: 3 class: Example\Foo\Bar 4 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 generated on October 26, 2012

Chapter 121: Advanced Container Configuration | 603

Chapter 122

Container Building Workflow


In the preceding pages of this section, there has been little to say about where the various files and classes should be located. This is because this depends on the application, library or framework in which you want to use the container. Looking at how the container is configured and built in the Symfony2 full stack framework will help you see how this all fits together, whether you are using the full stack framework or looking to use the service container in another application. The full stack framework uses the HttpKernel component to manage the loading of the service container configuration from the application and bundles and also handles the compilation and caching. Even if you are not using HttpKernel, it should give you an idea of one way of organizing configuration in a modular application.

Working with cached Container


Before building it, the kernel checks to see if a cached version of the container exists. The HttpKernel has a debug setting and if this is false, the cached version is used if it exists. If debug is true then the kernel checks to see if configuration is fresh and if it is, the cached version of the container is. If not then the container is built from the application-level configuration and the bundles's extension configuration. Read Dumping the Configuration for Performance for more details.

Application-level Configuration
Application level config is loaded from the app/config directory. Multiple files are loaded which are then merged when the extensions are processed. This allows for different configuration for different environments e.g. dev, prod. These files contain parameters and services that are loaded directly into the container as per Setting Up the Container with Configuration Files. They also contain configuration that is processed by extensions as per Managing Configuration with Extensions. These are considered to be bundle configuration since each bundle contains an Extension class.

PDF brought to you by generated on October 26, 2012

Chapter 122: Container Building Workflow | 604

Bundle-level Configuration with Extensions


By convention, each bundle contains an Extension class which is in the bundle's DependencyInjection directory. These are registered with the ContainerBuilder when the kernel is booted. When the ContainerBuilder is compiled, the application-level configuration relevant to the bundle's extension is passed to the Extension which also usually loads its own config file(s), typically from the bundle's Resources/config directory. The application-level config is usually processed with a Configuration object also stored in the bundle's DependencyInjection directory.

Compiler passes to allow Interaction between Bundles


Compiler passes are used to allow interaction between different bundles as they cannot affect each other's configuration in the extension classes. One of the main uses is to process tagged services, allowing bundles to register services to picked up by other bundles, such as Monolog loggers, Twig extensions and Data Collectors for the Web Profiler. Compiler passes are usually placed in the bundle's DependencyInjection/Compiler directory.

Compilation and Caching


After the compilation process has loaded the services from the configuration, extensions and the compiler passes, it is dumped so that the cache can be used next time. The dumped version is then used during subsequent requests as it is more efficient.

PDF brought to you by generated on October 26, 2012

Chapter 122: Container Building Workflow | 605

Chapter 123

The Event Dispatcher Component

Introduction
Objected Oriented code has gone a long way to ensuring code extensibility. By creating classes that have well defined responsibilities, your code becomes more flexible and a developer can extend them with subclasses to modify their behaviors. But if he wants to share his changes with other developers who have also made their own subclasses, code inheritance is no longer the answer. Consider the real-world example where you want to provide a plugin system for your project. A plugin should be able to add methods, or do something before or after a method is executed, without interfering with other plugins. This is not an easy problem to solve with single inheritance, and multiple inheritance (were it possible with PHP) has its own drawbacks. The Symfony2 Event Dispatcher component implements the Observer1 pattern in a simple and effective way to make all these things possible and to make your projects truly extensible. Take a simple example from the Symfony2 HttpKernel component2. Once a Response object has been created, it may be useful to allow other elements in the system to modify it (e.g. add some cache headers) before it's actually used. To make this possible, the Symfony2 kernel throws an event - kernel.response. Here's how it works: A listener (PHP object) tells a central dispatcher object that it wants to listen to the kernel.response event; At some point, the Symfony2 kernel tells the dispatcher object to dispatch the kernel.response event, passing with it an Event object that has access to the Response object; The dispatcher notifies (i.e. calls a method on) all listeners of the kernel.response event, allowing each of them to make modifications to the Response object.

1. http://en.wikipedia.org/wiki/Observer_pattern 2. https://github.com/symfony/HttpKernel

PDF brought to you by generated on October 26, 2012

Chapter 123: The Event Dispatcher Component | 606

Installation
You can install the component in many different ways: Use the official Git repository (https://github.com/symfony/EventDispatcher3); Install it via PEAR ( pear.symfony.com/EventDispatcher); Install it via Composer (symfony/event-dispatcher on Packagist).

Usage
Events
When an event is dispatched, it's identified by a unique name (e.g. kernel.response), which any number of listeners might be listening to. An Event4 instance is also created and passed to all of the listeners. As you'll see later, the Event object itself often contains data about the event being dispatched.

Naming Conventions
The unique event name can be any string, but optionally follows a few simple naming conventions: use only lowercase letters, numbers, dots (.), and underscores (_); prefix names with a namespace followed by a dot (e.g. kernel.); end names with a verb that indicates what action is being taken (e.g. request). Here are some examples of good event names: kernel.response form.pre_set_data

Event Names and Event Objects


When the dispatcher notifies listeners, it passes an actual Event object to those listeners. The base Event class is very simple: it contains a method for stopping event propagation, but not much else. Often times, data about a specific event needs to be passed along with the Event object so that the listeners have needed information. In the case of the kernel.response event, the Event object that's created and passed to each listener is actually of type FilterResponseEvent5, a subclass of the base Event object. This class contains methods such as getResponse and setResponse, allowing listeners to get or even replace the Response object. The moral of the story is this: When creating a listener to an event, the Event object that's passed to the listener may be a special subclass that has additional methods for retrieving information from and responding to the event.

The Dispatcher
The dispatcher is the central object of the event dispatcher system. In general, a single dispatcher is created, which maintains a registry of listeners. When an event is dispatched via the dispatcher, it notifies all listeners registered with that event:
Listing 123-1

3. https://github.com/symfony/EventDispatcher 4. http://api.symfony.com/2.1/Symfony/Component/EventDispatcher/Event.html 5. http://api.symfony.com/2.1/Symfony/Component/HttpKernel/Event/FilterResponseEvent.html

PDF brought to you by generated on October 26, 2012

Chapter 123: The Event Dispatcher Component | 607

1 use Symfony\Component\EventDispatcher\EventDispatcher; 2 3 $dispatcher = new EventDispatcher();

Connecting Listeners
To take advantage of an existing event, you need to connect a listener to the dispatcher so that it can be notified when the event is dispatched. A call to the dispatcher addListener() method associates any valid PHP callable to an event:
Listing 123-2

1 $listener = new AcmeListener(); 2 $dispatcher->addListener('foo.action', array($listener, 'onFooAction'));

The addListener() method takes up to three arguments: The event name (string) that this listener wants to listen to; A PHP callable that will be notified when an event is thrown that it listens to; An optional priority integer (higher equals more important) that determines when a listener is triggered versus other listeners (defaults to 0). If two listeners have the same priority, they are executed in the order that they were added to the dispatcher.
A PHP callable6 is a PHP variable that can be used by the call_user_func() function and returns true when passed to the is_callable() function. It can be a \Closure instance, an object implementing an __invoke method (which is what closures are in fact), a string representing a function, or an array representing an object method or a class method. So far, you've seen how PHP objects can be registered as listeners. You can also register PHP Closures7 as event listeners:
Listing 123-3

1 use Symfony\Component\EventDispatcher\Event; 2 3 $dispatcher->addListener('foo.action', function (Event $event) { 4 // will be executed when the foo.action event is dispatched 5 });

Once a listener is registered with the dispatcher, it waits until the event is notified. In the above example, when the foo.action event is dispatched, the dispatcher calls the AcmeListener::onFooAction method and passes the Event object as the single argument:
Listing 123-4

1 2 3 4 5 6 7 8 9 10 11

use Symfony\Component\EventDispatcher\Event; class AcmeListener { // ... public function onFooAction(Event $event) { // ... do something } }

6. http://www.php.net/manual/en/language.pseudo-types.php#language.types.callback 7. http://php.net/manual/en/functions.anonymous.php

PDF brought to you by generated on October 26, 2012

Chapter 123: The Event Dispatcher Component | 608

In many cases, a special Event subclass that's specific to the given event is passed to the listener. This gives the listener access to special information about the event. Check the documentation or implementation of each event to determine the exact Symfony\Component\EventDispatcher\Event instance that's being passed. For example, the kernel.event event passes an instance of Symfony\Component\HttpKernel\Event\FilterResponseEvent:
Listing 123-5

1 2 3 4 5 6 7 8 9

use Symfony\Component\HttpKernel\Event\FilterResponseEvent public function onKernelResponse(FilterResponseEvent $event) { $response = $event->getResponse(); $request = $event->getRequest();

// ...
}

Creating and Dispatching an Event


In addition to registering listeners with existing events, you can create and dispatch your own events. This is useful when creating third-party libraries and also when you want to keep different components of your own system flexible and decoupled.

The Static Events Class


Suppose you want to create a new Event - store.order - that is dispatched each time an order is created inside your application. To keep things organized, start by creating a StoreEvents class inside your application that serves to define and document your event:
Listing 123-6

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

namespace Acme\StoreBundle; final class StoreEvents { /** * The store.order event is thrown each time an order is created * in the system. * * The event listener receives an Acme\StoreBundle\Event\FilterOrderEvent * instance. * * @var string */ const STORE_ORDER = 'store.order'; }

Notice that this class doesn't actually do anything. The purpose of the StoreEvents class is just to be a location where information about common events can be centralized. Notice also that a special FilterOrderEvent class will be passed to each listener of this event.

Creating an Event object


Later, when you dispatch this new event, you'll create an Event instance and pass it to the dispatcher. The dispatcher then passes this same instance to each of the listeners of the event. If you don't need to pass any information to your listeners, you can use the default Symfony\Component\EventDispatcher\Event class. Most of the time, however, you will need to pass information about the event to each listener. To accomplish this, you'll create a new class that extends Symfony\Component\EventDispatcher\Event.

PDF brought to you by generated on October 26, 2012

Chapter 123: The Event Dispatcher Component | 609

In this example, each listener will need access to some pretend Order object. Create an Event class that makes this possible:
Listing 123-7

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

namespace Acme\StoreBundle\Event; use Symfony\Component\EventDispatcher\Event; use Acme\StoreBundle\Order; class FilterOrderEvent extends Event { protected $order; public function __construct(Order $order) { $this->order = $order; } public function getOrder() { return $this->order; } }

Each listener now has access to the Order object via the getOrder method.

Dispatch the Event


The dispatch()8 method notifies all listeners of the given event. It takes two arguments: the name of the event to dispatch and the Event instance to pass to each listener of that event:
Listing 123-8

1 2 3 4 5 6 7 8 9 10 11

use Acme\StoreBundle\StoreEvents; use Acme\StoreBundle\Order; use Acme\StoreBundle\Event\FilterOrderEvent;

// the order is somehow created or retrieved $order = new Order(); // ... // create the FilterOrderEvent and dispatch it $event = new FilterOrderEvent($order); $dispatcher->dispatch(StoreEvents::STORE_ORDER, $event);

Notice that the special FilterOrderEvent object is created and passed to the dispatch method. Now, any listener to the store.order event will receive the FilterOrderEvent and have access to the Order object via the getOrder method:
Listing 123-9

1 2 3 4 5 6 7 8

// some listener class that's been registered for "STORE_ORDER" event use Acme\StoreBundle\Event\FilterOrderEvent;
public function onStoreOrder(FilterOrderEvent $event) { $order = $event->getOrder(); // do something to or with the order }

8. http://api.symfony.com/2.1/Symfony/Component/EventDispatcher/EventDispatcher.html#dispatch()

PDF brought to you by generated on October 26, 2012

Chapter 123: The Event Dispatcher Component | 610

Using Event Subscribers


The most common way to listen to an event is to register an event listener with the dispatcher. This listener can listen to one or more events and is notified each time those events are dispatched. Another way to listen to events is via an event subscriber. An event subscriber is a PHP class that's able to tell the dispatcher exactly which events it should subscribe to. It implements the EventSubscriberInterface9 interface, which requires a single static method called getSubscribedEvents. Take the following example of a subscriber that subscribes to the kernel.response and store.order events:
Listing 123-10

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39

namespace Acme\StoreBundle\Event; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\FilterResponseEvent; class StoreSubscriber implements EventSubscriberInterface { static public function getSubscribedEvents() { return array( 'kernel.response' => array( array('onKernelResponsePre', 10), array('onKernelResponseMid', 5), array('onKernelResponsePost', 0), ), 'store.order' => array('onStoreOrder', 0), ); } public function onKernelResponsePre(FilterResponseEvent $event) { // ... } public function onKernelResponseMid(FilterResponseEvent $event) { // ... } public function onKernelResponsePost(FilterResponseEvent $event) { // ... } public function onStoreOrder(FilterOrderEvent $event) { // ... } }

This is very similar to a listener class, except that the class itself can tell the dispatcher which events it should listen to. To register a subscriber with the dispatcher, use the addSubscriber()10 method:
Listing 123-11 1

use Acme\StoreBundle\Event\StoreSubscriber;

9. http://api.symfony.com/2.1/Symfony/Component/EventDispatcher/EventSubscriberInterface.html 10. http://api.symfony.com/2.1/Symfony/Component/EventDispatcher/EventDispatcher.html#addSubscriber()

PDF brought to you by generated on October 26, 2012

Chapter 123: The Event Dispatcher Component | 611

3 $subscriber = new StoreSubscriber(); 4 $dispatcher->addSubscriber($subscriber);

The dispatcher will automatically register the subscriber for each event returned by the getSubscribedEvents method. This method returns an array indexed by event names and whose values are either the method name to call or an array composed of the method name to call and a priority. The example above shows how to register several listener methods for the same event in subscriber and also shows how to pass the priority of each listener method.

Stopping Event Flow/Propagation


In some cases, it may make sense for a listener to prevent any other listeners from being called. In other words, the listener needs to be able to tell the dispatcher to stop all propagation of the event to future listeners (i.e. to not notify any more listeners). This can be accomplished from inside a listener via the stopPropagation()11 method:
Listing 123-12 1

use Acme\StoreBundle\Event\FilterOrderEvent; 2 3 public function onStoreOrder(FilterOrderEvent $event) 4 { 5 // ... 6 7 $event->stopPropagation(); 8 }

Now, any listeners to store.order that have not yet been called will not be called. It is possible to detect if an event was stopped by using the isPropagationStopped()12 method which returns a boolean value:
Listing 123-13 1

$dispatcher->dispatch('foo.event', $event); 2 if ($event->isPropagationStopped()) { 3 // ... 4 }

EventDispatcher aware Events and Listeners


New in version 2.1: The Event object contains a reference to the invoking dispatcher since Symfony 2.1

The EventDispatcher always injects a reference to itself in the passed event object. This means that all listeners have direct access to the EventDispatcher object that notified the listener via the passed Event object's getDispatcher()13 method. This can lead to some advanced applications of the EventDispatcher including letting listeners dispatch other events, event chaining or even lazy loading of more listeners into the dispatcher object. Examples follow: Lazy loading listeners:

11. http://api.symfony.com/2.1/Symfony/Component/EventDispatcher/Event.html#stopPropagation() 12. http://api.symfony.com/2.1/Symfony/Component/EventDispatcher/Event.html#isPropagationStopped() 13. http://api.symfony.com/2.1/Symfony/Component/EventDispatcher/Event.html#getDispatcher()

PDF brought to you by generated on October 26, 2012

Chapter 123: The Event Dispatcher Component | 612

Listing 123-14

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

use Symfony\Component\EventDispatcher\Event; use Acme\StoreBundle\Event\StoreSubscriber; class Foo { private $started = false; public function myLazyListener(Event $event) { if (false === $this->started) { $subscriber = new StoreSubscriber(); $event->getDispatcher()->addSubscriber($subscriber); } $this->started = true;

// ... more code


} }

Dispatching another event from within a listener:


Listing 123-15

1 2 3 4 5 6 7 8 9 10 11

use Symfony\Component\EventDispatcher\Event; class Foo { public function myFooListener(Event $event) { $event->getDispatcher()->dispatch('log', $event);

// ... more code


} }

While this above is sufficient for most uses, if your application uses multiple EventDispatcher instances, you might need to specifically inject a known instance of the EventDispatcher into your listeners. This could be done using constructor or setter injection as follows: Constructor injection:
Listing 123-16

1 2 3 4 5 6 7 8 9 10 11

use Symfony\Component\EventDispatcher\EventDispatcherInterface; class Foo { protected $dispatcher = null; public function __construct(EventDispatcherInterface $dispatcher) { $this->dispatcher = $dispatcher; } }

Or setter injection:
Listing 123-17

1 use Symfony\Component\EventDispatcher\EventDispatcherInterface; 2 3 class Foo

PDF brought to you by generated on October 26, 2012

Chapter 123: The Event Dispatcher Component | 613

4 { 5 6 7 8 9 10 11 }

protected $dispatcher = null; public function setEventDispatcher(EventDispatcherInterface $dispatcher) { $this->dispatcher = $dispatcher; }

Choosing between the two is really a matter of taste. Many tend to prefer the constructor injection as the objects are fully initialized at construction time. But when you have a long list of dependencies, using setter injection can be the way to go, especially for optional dependencies.

Dispatcher Shortcuts
New in version 2.1: EventDispatcher::dispatch() method returns the event since Symfony 2.1.

The EventDispatcher::dispatch14 method always returns an Event15 object. This allows for various shortcuts. For example if one does not need a custom event object, one can simply rely on a plain Event16 object. You do not even need to pass this to the dispatcher as it will create one by default unless you specifically pass one:
Listing 123-18 1

$dispatcher->dispatch('foo.event');

Moreover, the EventDispatcher always returns whichever event object that was dispatched, i.e. either the event that was passed or the event that was created internally by the dispatcher. This allows for nice shortcuts:
Listing 123-19 1

if (!$dispatcher->dispatch('foo.event')->isPropagationStopped()) { 2 // ... 3 }

Or:
Listing 123-20 1

$barEvent = new BarEvent(); 2 $bar = $dispatcher->dispatch('bar.event', $barEvent)->getBar();

Or:
Listing 123-21 1

$response = $dispatcher->dispatch('bar.event', new BarEvent())->getBar();

and so on...

14. http://api.symfony.com/2.1/Symfony/Component/EventDispatcher/EventDispatcher.html#dispatch() 15. http://api.symfony.com/2.1/Symfony/Component/EventDispatcher/Event.html 16. http://api.symfony.com/2.1/Symfony/Component/EventDispatcher/Event.html

PDF brought to you by generated on October 26, 2012

Chapter 123: The Event Dispatcher Component | 614

Event Name Introspection


New in version 2.1: Added event name to the Event object since Symfony 2.1

Since the EventDispatcher already knows the name of the event when dispatching it, the event name is also injected into the Event17 objects, making it available to event listeners via the getName()18 method. The event name, (as with any other data in a custom event object) can be used as part of the listener's processing logic:
Listing 123-22 1

use Symfony\Component\EventDispatcher\Event; 2 3 class Foo 4 { 5 public function myEventListener(Event $event) 6 { 7 echo $event->getName(); 8 } 9 }

17. http://api.symfony.com/2.1/Symfony/Component/EventDispatcher/Event.html 18. http://api.symfony.com/2.1/Symfony/Component/EventDispatcher/Event.html#getName()

PDF brought to you by generated on October 26, 2012

Chapter 123: The Event Dispatcher Component | 615

Chapter 124

The Generic Event Object

New in version 2.1: The GenericEvent event class was added in Symfony 2.1

The base Event1 class provided by the Event Dispatcher component is deliberately sparse to allow the creation of API specific event objects by inheritance using OOP. This allow for elegant and readable code in complex applications. The GenericEvent2 is available for convenience for those who wish to use just one event object throughout their application. It is suitable for most purposes straight out of the box, because it follows the standard observer pattern where the event object encapsulates an event 'subject', but has the addition of optional extra arguments.

GenericEvent3 has a simple API in addition to the base class Event4


__construct()5: Constructor takes the event subject and any arguments; getSubject()6: Get the subject; setArgument()7: Sets an argument by key; setArguments()8: Sets arguments array; getArgument()9: Gets an argument by key; getArguments()10: Getter for all arguments; hasArgument()11: Returns true if the argument key exists;

1. http://api.symfony.com/2.1/Symfony/Component/EventDispatcher/Event.html 2. http://api.symfony.com/2.1/Symfony/Component/EventDispatcher/GenericEvent.html 3. http://api.symfony.com/2.1/Symfony/Component/EventDispatcher/GenericEvent.html 4. http://api.symfony.com/2.1/Symfony/Component/EventDispatcher/Event.html 5. http://api.symfony.com/2.1/Symfony/Component/EventDispatcher/GenericEvent.html#__construct() 6. http://api.symfony.com/2.1/Symfony/Component/EventDispatcher/GenericEvent.html#getSubject() 7. http://api.symfony.com/2.1/Symfony/Component/EventDispatcher/GenericEvent.html#setArgument() 8. http://api.symfony.com/2.1/Symfony/Component/EventDispatcher/GenericEvent.html#setArguments() 9. http://api.symfony.com/2.1/Symfony/Component/EventDispatcher/GenericEvent.html#getArgument() 10. http://api.symfony.com/2.1/Symfony/Component/EventDispatcher/GenericEvent.html#getArguments() 11. http://api.symfony.com/2.1/Symfony/Component/EventDispatcher/GenericEvent.html#hasArgument()

PDF brought to you by generated on October 26, 2012

Chapter 124: The Generic Event Object | 616

The GenericEvent also implements ArrayAccess12 on the event arguments which makes it very convenient to pass extra arguments regarding the event subject. The following examples show use-cases to give a general idea of the flexibility. The examples assume event listeners have been added to the dispatcher. Simply passing a subject:
Listing 124-1

1 2 3 4 5 6 7 8 9 10 11 12 13 14

use Symfony\Component\EventDispatcher\GenericEvent; $event = GenericEvent($subject); $dispatcher->dispatch('foo', $event); class FooListener { public function handler(GenericEvent $event) { if ($event->getSubject() instanceof Foo) { // ... } } }

Passing and processing arguments using the ArrayAccess13 API to access the event arguments:
Listing 124-2

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

use Symfony\Component\EventDispatcher\GenericEvent; $event = new GenericEvent($subject, array('type' => 'foo', 'counter' => 0))); $dispatcher->dispatch('foo', $event); echo $event['counter']; class FooListener { public function handler(GenericEvent $event) { if (isset($event['type']) && $event['type'] === 'foo') { // ... do something } $event['counter']++; } }

Filtering data:
Listing 124-3

1 2 3 4 5 6 7 8 9 10

use Symfony\Component\EventDispatcher\GenericEvent; $event = new GenericEvent($subject, array('data' => 'foo')); $dispatcher->dispatch('foo', $event); echo $event['data']; class FooListener { public function filter(GenericEvent $event)

12. http://php.net/manual/en/class.arrayaccess.php 13. http://php.net/manual/en/class.arrayaccess.php

PDF brought to you by generated on October 26, 2012

Chapter 124: The Generic Event Object | 617

11 12 13 14 }

{ strtolower($event['data']); }

PDF brought to you by generated on October 26, 2012

Chapter 124: The Generic Event Object | 618

Chapter 125

The Container Aware Event Dispatcher

New in version 2.1: This feature was moved into the EventDispatcher component in Symfony 2.1.

Introduction
The ContainerAwareEventDispatcher1 is a special event dispatcher implementation which is coupled to the service container that is part of the Dependency Injection component. It allows services to be specified as event listeners making the event dispatcher extremely powerful. Services are lazy loaded meaning the services attached as listeners will only be created if an event is dispatched that requires those listeners.

Setup
Setup is straightforward by ContainerAwareEventDispatcher3:
Listing 125-1

injecting

ContainerInterface2

into

the

1 2 3 4 5

use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher; $container = new ContainerBuilder(); $dispatcher = new ContainerAwareEventDispatcher($container);

1. http://api.symfony.com/2.1/Symfony/Component/EventDispatcher/ContainerAwareEventDispatcher.html 2. http://api.symfony.com/2.1/Symfony/Component/DependencyInjection/ContainerInterface.html 3. http://api.symfony.com/2.1/Symfony/Component/EventDispatcher/ContainerAwareEventDispatcher.html

PDF brought to you by generated on October 26, 2012

Chapter 125: The Container Aware Event Dispatcher | 619

Adding Listeners
The Container Aware Event Dispatcher can either load specified services directly, or services that implement EventSubscriberInterface4. The following examples assume the service container has been loaded with any services that are mentioned.
Services must be marked as public in the container.

Adding Services
To connect existing service definitions, use the addListenerService()5 method where the $callback is an array of array($serviceId, $methodName):
Listing 125-2

1 $dispatcher->addListenerService($eventName, array('foo', 'logListener'));

Adding Subscriber Services


EventSubscribers can be added using the addSubscriberService()6 method where the first argument is the service ID of the subscriber service, and the second argument is the the service's class name (which must implement EventSubscriberInterface7) as follows:
Listing 125-3

1 $dispatcher->addSubscriberService('kernel.store_subscriber', 'StoreSubscriber');

The EventSubscriberInterface will be exactly as you would expect:


Listing 125-4

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

use Symfony\Component\EventDispatcher\EventSubscriberInterface; // ... class StoreSubscriber implements EventSubscriberInterface { static public function getSubscribedEvents() { return array( 'kernel.response' => array( array('onKernelResponsePre', 10), array('onKernelResponsePost', 0), ), 'store.order' => array('onStoreOrder', 0), ); } public function onKernelResponsePre(FilterResponseEvent $event) { // ... }

4. http://api.symfony.com/2.1/Symfony/Component/EventDispatcher/EventSubscriberInterface.html 5. http://api.symfony.com/2.1/Symfony/Component/EventDispatcher/ContainerAwareEventDispatcher.html#addListenerService() 6. http://api.symfony.com/2.1/Symfony/Component/EventDispatcher/ContainerAwareEventDispatcher.html#addSubscriberService() 7. http://api.symfony.com/2.1/Symfony/Component/EventDispatcher/EventSubscriberInterface.html

PDF brought to you by generated on October 26, 2012

Chapter 125: The Container Aware Event Dispatcher | 620

21 22 23 24 25 26 27 28 29 30 31 }

public function onKernelResponsePost(FilterResponseEvent $event) { // ... } public function onStoreOrder(FilterOrderEvent $event) { // ... }

PDF brought to you by generated on October 26, 2012

Chapter 125: The Container Aware Event Dispatcher | 621

Chapter 126

The Filesystem Component


The Filesystem components provides basic utilities for the filesystem.
New in version 2.1: The Filesystem Component is new to Symfony 2.1. Previously, the Filesystem class was located in the HttpKernel component.

Installation
You can install the component in many different ways: Use the official Git repository (https://github.com/symfony/Filesystem1); Install it via PEAR ( pear.symfony.com/Filesystem); Install it via Composer (symfony/filesystem on Packagist).

Usage
The Filesystem2 class is the unique endpoint for filesystem operations:
Listing 126-1

1 2 3 4 5 6 7 8

use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Filesystem\Exception\IOException; $fs = new Filesystem(); try { $fs->mkdir('/tmp/random/dir/' . mt_rand()); } catch (IOException $e) {

1. https://github.com/symfony/Filesystem 2. http://api.symfony.com/2.1/Symfony/Component/Filesystem/Filesystem.html

PDF brought to you by generated on October 26, 2012

Chapter 126: The Filesystem Component | 622

9 10 }

echo "An error occured while creating your directory";

Methods mkdir()3, chown()4, chgrp()5, chown()6, remove()7 and touch()8 can receive a string, an array or any object implementing Traversable9 as the target argument.

Mkdir
Mkdir creates directory. On posix filesystems, directories are created with a default mode value 0777. You can use the second argument to set your own mode:
Listing 126-2

1 $fs->mkdir('/tmp/photos', 0700);

You can pass an array or any Traversable10 object as the first argument.

Exists
Exists checks for the presence of all files or directories and returns false if a file is missing:
Listing 126-3

1 2 3 4 5

// this directory exists, return true $fs->exists('/tmp/photos'); // rabbit.jpg exists, bottle.png does not exists, return false $fs->exists(array('rabbit.jpg', 'bottle.png'));

You can pass an array or any Traversable11 object as the first argument.

Copy
This method is used to copy files. If the target already exists, the file is copied only if the source modification date is earlier than the target. This behavior can be overridden by the third boolean argument:
Listing 126-4

3. 4. 5. 6. 7. 8. 9. 10. 11.

http://api.symfony.com/2.1/Symfony/Component/Filesystem/Filesystem.html#mkdir() http://api.symfony.com/2.1/Symfony/Component/Filesystem/Filesystem.html#chown() http://api.symfony.com/2.1/Symfony/Component/Filesystem/Filesystem.html#chgrp() http://api.symfony.com/2.1/Symfony/Component/Filesystem/Filesystem.html#chown() http://api.symfony.com/2.1/Symfony/Component/Filesystem/Filesystem.html#remove() http://api.symfony.com/2.1/Symfony/Component/Filesystem/Filesystem.html#touch() http://php.net/manual/en/class.traversable.php http://php.net/manual/en/class.traversable.php http://php.net/manual/en/class.traversable.php

PDF brought to you by generated on October 26, 2012

Chapter 126: The Filesystem Component | 623

1 2 3 4 5

// works only if image-ICC has been modified after image.jpg $fs->copy('image-ICC.jpg', 'image.jpg'); // image.jpg will be overridden $fs->copy('image-ICC.jpg', 'image.jpg', true);

Touch
Touch sets access and modification time for a file. The current time is used by default. You can set your own with the second argument. The third argument is the access time:
Listing 126-5

1 2 3 4 5 6

// set modification time to the current timestamp $fs->touch('file.txt'); // set modification time 10 seconds in the future $fs->touch('file.txt', time() + 10); // set access time 10 seconds in the past $fs->touch('file.txt', time(), time() - 10);

You can pass an array or any Traversable12 object as the first argument.

Chown
Chown is used to change the owner of a file. The third argument is a boolean recursive option:
Listing 126-6

1 2 3 4

// set the owner of the lolcat video to www-data $fs->chown('lolcat.mp4', 'www-data'); // change the owner of the video directory recursively $fs->chown('/video', 'www-data', true);

You can pass an array or any Traversable13 object as the first argument.

Chgrp
Chgrp is used to change the group of a file. The third argument is a boolean recursive option:
Listing 126-7

1 2 3 4

// set the group of the lolcat video to nginx $fs->chgrp('lolcat.mp4', 'nginx'); // change the group of the video directory recursively $fs->chgrp('/video', 'nginx', true);

12. http://php.net/manual/en/class.traversable.php 13. http://php.net/manual/en/class.traversable.php

PDF brought to you by generated on October 26, 2012

Chapter 126: The Filesystem Component | 624

You can pass an array or any Traversable14 object as the first argument.

Chmod
Chmod is used to change the mode of a file. The third argument is a boolean recursive option:
Listing 126-8

1 2 3 4

// set the mode of the video to 0600 $fs->chmod('video.ogg', 0600); // change the mod of the src directory recursively $fs->chmod('src', 0700, true);

You can pass an array or any Traversable15 object as the first argument.

Remove
Remove let's you remove files, symlink, directories easily:
Listing 126-9

1 $fs->remove(array('symlink', '/path/to/directory', 'activity.log'));

You can pass an array or any Traversable16 object as the first argument.

Rename
Rename is used to rename files and directories:
Listing 126-10 1

//rename a file 2 $fs->rename('/tmp/processed_video.ogg', '/path/to/store/video_647.ogg'); 3 //rename a directory 4 $fs->rename('/tmp/files', '/path/to/store/files');

symlink
Creates a symbolic link from the target to the destination. If the filesystem does not support symbolic links, a third boolean argument is available:
Listing 126-11 1

// create a symbolic link 2 $fs->symlink('/path/to/source', '/path/to/destination'); 3 // duplicate the source directory if the filesystem does not support symbolic links 4 $fs->symlink('/path/to/source', '/path/to/destination', true);

14. http://php.net/manual/en/class.traversable.php 15. http://php.net/manual/en/class.traversable.php 16. http://php.net/manual/en/class.traversable.php

PDF brought to you by generated on October 26, 2012

Chapter 126: The Filesystem Component | 625

makePathRelative
Return the relative path of a directory given another one:
Listing 126-12

1 2 3 4

// returns '../' $fs->makePathRelative('/var/lib/symfony/src/Symfony/', '/var/lib/symfony/src/Symfony/ Component'); // returns 'videos' $fs->makePathRelative('/tmp', '/tmp/videos');

mirror
Mirrors a directory:
Listing 126-13 1

$fs->mirror('/path/to/source', '/path/to/target');

isAbsolutePath
isAbsolutePath returns true if the given path is absolute, false otherwise:
Listing 126-14 1

2 3 4 5 6 7 8

// return true $fs->isAbsolutePath('/tmp'); // return true $fs->isAbsolutePath('c:\\Windows'); // return false $fs->isAbsolutePath('tmp'); // return false $fs->isAbsolutePath('../dir');

Error Handling
Whenever something wrong happens, an exception implementing ExceptionInterface17 is thrown.
Prior to version 2.1, mkdir()18 returned a boolean and did not throw exceptions. As of 2.1, a IOException19 is thrown if a directory creation fails.

17. http://api.symfony.com/2.1/Symfony/Component/Filesystem/Exception/ExceptionInterface.html 18. http://api.symfony.com/2.1/Symfony/Component/Filesystem/Filesystem.html#mkdir() 19. http://api.symfony.com/2.1/Symfony/Component/Filesystem/Exception/IOException.html

PDF brought to you by generated on October 26, 2012

Chapter 126: The Filesystem Component | 626

Chapter 127

The Finder Component


The Finder Component finds files and directories via an intuitive fluent interface.

Installation
You can install the component in many different ways: Use the official Git repository (https://github.com/symfony/Finder1); Install it via PEAR ( pear.symfony.com/Finder); Install it via Composer (symfony/finder on Packagist).

Usage
The Finder2 class finds files and/or directories:
Listing 127-1

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

use Symfony\Component\Finder\Finder; $finder = new Finder(); $finder->files()->in(__DIR__); foreach ($finder as $file) { // Print the absolute path print $file->getRealpath()."\n";

// Print the relative path to the file, omitting the filename print $file->getRelativePath()."\n"; // Print the relative path to the file print $file->getRelativePathname()."\n";
}

1. https://github.com/symfony/Finder 2. http://api.symfony.com/2.1/Symfony/Component/Finder/Finder.html

PDF brought to you by generated on October 26, 2012

Chapter 127: The Finder Component | 627

The $file is an instance of SplFileInfo3 which extends SplFileInfo4 to provide methods to work with relative paths. The above code prints the names of all the files in the current directory recursively. The Finder class uses a fluent interface, so all methods return the Finder instance.
A Finder instance is a PHP Iterator5. So, instead of iterating over the Finder with foreach, you can also convert it to an array with the iterator_to_array6 method, or get the number of items with iterator_count7.

Criteria
Location
The location is the only mandatory criteria. It tells the finder which directory to use for the search:
Listing 127-2

1 $finder->in(__DIR__);

Search in several locations by chaining calls to in()8:


Listing 127-3

1 $finder->files()->in(__DIR__)->in('/elsewhere');

Exclude directories from matching with the exclude()9 method:


Listing 127-4

1 $finder->in(__DIR__)->exclude('ruby');

As the Finder uses PHP iterators, you can pass any URL with a supported protocol10:
Listing 127-5

1 $finder->in('ftp://example.com/pub/');

And it also works with user-defined streams:


Listing 127-6

1 2 3 4 5 6 7 8 9 10 11 12

use Symfony\Component\Finder\Finder; $s3 = new \Zend_Service_Amazon_S3($key, $secret); $s3->registerStreamWrapper("s3"); $finder = new Finder(); $finder->name('photos*')->size('< 100K')->date('since 1 hour ago'); foreach ($finder->in('s3://bucket-name') as $file) { // ... do something print $file->getFilename()."\n"; }

3. http://api.symfony.com/2.1/Symfony/Component/Finder/SplFileInfo.html 4. http://php.net/manual/en/class.splfileinfo.php 5. http://www.php.net/manual/en/spl.iterators.php 6. http://php.net/manual/en/function.iterator-to-array.php 7. http://php.net/manual/en/function.iterator-count.php 8. http://api.symfony.com/2.1/Symfony/Component/Finder/Finder.html#in() 9. http://api.symfony.com/2.1/Symfony/Component/Finder/Finder.html#exclude() 10. http://www.php.net/manual/en/wrappers.php

PDF brought to you by generated on October 26, 2012

Chapter 127: The Finder Component | 628

Read the Streams11 documentation to learn how to create your own streams.

Files or Directories
By default, the Finder returns files and directories; but the files()12 and directories()13 methods control that:
Listing 127-7

1 $finder->files(); 2 3 $finder->directories();

If you want to follow links, use the followLinks() method:


Listing 127-8

1 $finder->files()->followLinks();

By default, the iterator ignores popular VCS files. This can be changed with the ignoreVCS() method:
Listing 127-9

1 $finder->ignoreVCS(false);

Sorting
Sort the result by name or by type (directories first, then files):
Listing 127-10 1

$finder->sortByName(); 2 3 $finder->sortByType();

Notice that the sort* methods need to get all matching elements to do their jobs. For large iterators, it is slow.

You can also define your own sorting algorithm with sort() method:
Listing 127-11 1

$sort = function (\SplFileInfo $a, \SplFileInfo $b) 2 { 3 return strcmp($a->getRealpath(), $b->getRealpath()); 4 }; 5 6 $finder->sort($sort);

File Name
Restrict files by name with the name()14 method:

11. http://www.php.net/streams 12. http://api.symfony.com/2.1/Symfony/Component/Finder/Finder.html#files() 13. http://api.symfony.com/2.1/Symfony/Component/Finder/Finder.html#directories() 14. http://api.symfony.com/2.1/Symfony/Component/Finder/Finder.html#name()

PDF brought to you by generated on October 26, 2012

Chapter 127: The Finder Component | 629

Listing 127-12 1

$finder->files()->name('*.php');

The name() method accepts globs, strings, or regexes:


Listing 127-13 1

$finder->files()->name('/\.php$/');

The notName() method excludes files matching a pattern:


Listing 127-14 1

$finder->files()->notName('*.rb');

File Contents
New in version 2.1: Methods contains() and notContains() have been introduced in version 2.1.

Restrict files by contents with the contains()15 method:


Listing 127-15 1

$finder->files()->contains('lorem ipsum');

The contains() method accepts strings or regexes:


Listing 127-16 1

$finder->files()->contains('/lorem\s+ipsum$/i');

The notContains() method excludes files containing given pattern:


Listing 127-17 1

$finder->files()->notContains('dolor sit amet');

File Size
Restrict files by size with the size()16 method:
Listing 127-18 1

$finder->files()->size('< 1.5K');

Restrict by a size range by chaining calls:


Listing 127-19 1

$finder->files()->size('>= 1K')->size('<= 2K');

The comparison operator can be any of the following: >, >=, <, <=, ==, !=.
New in version 2.1: The operator != was added in version 2.1.

The target value may use magnitudes of kilobytes (k, ki), megabytes (m, mi), or gigabytes (g, gi). Those suffixed with an i use the appropriate 2**n version in accordance with the IEC standard17.
15. http://api.symfony.com/2.1/Symfony/Component/Finder/Finder.html#contains() 16. http://api.symfony.com/2.1/Symfony/Component/Finder/Finder.html#size()

PDF brought to you by generated on October 26, 2012

Chapter 127: The Finder Component | 630

File Date
Restrict files by last modified dates with the date()18 method:
Listing 127-20 1

$finder->date('since yesterday');

The comparison operator can be any of the following: >, >=, <, '<=', '=='. You can also use since or after as an alias for >, and until or before as an alias for <. The target value can be any date supported by the strtotime19 function.

Directory Depth
By default, the Finder recursively traverse directories. Restrict the depth of traversing with depth()20:
Listing 127-21 1

$finder->depth('== 0'); 2 $finder->depth('< 3');

Custom Filtering
To restrict the matching file with your own strategy, use filter()21:
Listing 127-22 1

$filter = function (\SplFileInfo $file) 2 { 3 if (strlen($file) > 10) { 4 return false; 5 } 6 }; 7 8 $finder->files()->filter($filter);

The filter() method takes a Closure as an argument. For each matching file, it is called with the file as a SplFileInfo22 instance. The file is excluded from the result set if the Closure returns false.

Reading contents of returned files


New in version 2.1: Method getContents() have been introduced in version 2.1.

The contents of returned files can be read with getContents()23:


Listing 127-23 1

use Symfony\Component\Finder\Finder; 2 3 $finder = new Finder(); 4 $finder->files()->in(__DIR__);

17. http://physics.nist.gov/cuu/Units/binary.html 18. http://api.symfony.com/2.1/Symfony/Component/Finder/Finder.html#date() 19. http://www.php.net/manual/en/datetime.formats.php 20. http://api.symfony.com/2.1/Symfony/Component/Finder/Finder.html#depth() 21. http://api.symfony.com/2.1/Symfony/Component/Finder/Finder.html#filter() 22. http://api.symfony.com/2.1/Symfony/Component/Finder/SplFileInfo.html 23. http://api.symfony.com/2.1/Symfony/Component/Finder/SplFileInfo.html#getContents()

PDF brought to you by generated on October 26, 2012

Chapter 127: The Finder Component | 631

5 6 foreach ($finder as $file) { 7 $contents = $file->getContents(); 8 ... 9 }

PDF brought to you by generated on October 26, 2012

Chapter 127: The Finder Component | 632

Chapter 128

The HttpFoundation Component


The HttpFoundation Component defines an object-oriented layer for the HTTP specification. In PHP, the request is represented by some global variables ($_GET, $_POST, $_FILE, $_COOKIE, $_SESSION, ...) and the response is generated by some functions (echo, header, setcookie, ...). The Symfony2 HttpFoundation component replaces these default PHP global variables and functions by an Object-Oriented layer.

Installation
You can install the component in many different ways: Use the official Git repository (https://github.com/symfony/HttpFoundation1); Install it via PEAR ( pear.symfony.com/HttpFoundation); Install it via Composer (symfony/http-foundation on Packagist).

Request
The most common way to create request is to base it on the current PHP global variables with createFromGlobals()2:
Listing 128-1

1 use Symfony\Component\HttpFoundation\Request; 2 3 $request = Request::createFromGlobals();

which is almost equivalent to the more verbose, but also more flexible, __construct()3 call:
Listing 128-2

1. https://github.com/symfony/HttpFoundation 2. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Request.html#createFromGlobals() 3. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Request.html#__construct()

PDF brought to you by generated on October 26, 2012

Chapter 128: The HttpFoundation Component | 633

1 $request = new Request($_GET, $_POST, array(), $_COOKIE, $_FILES, $_SERVER);

Accessing Request Data


A Request object holds information about the client request. This information can be accessed via several public properties: request: equivalent of $_POST; query: equivalent of $_GET ($request->query->get('name')); cookies: equivalent of $_COOKIE; attributes: no equivalent - used by your app to store other data (see below) files: equivalent of $_FILE; server: equivalent of $_SERVER; headers: mostly equivalent to a sub-set of $_SERVER ($request->headers->get('ContentType')).

Each property is a ParameterBag4 instance (or a sub-class of), which is a data holder class: request: ParameterBag5; query: ParameterBag6; cookies: ParameterBag7; attributes: ParameterBag8; files: FileBag9; server: ServerBag10; headers: HeaderBag11.

All ParameterBag12 instances have methods to retrieve and update its data:

all()13: Returns the parameters; keys()14: Returns the parameter keys; replace()15: Replaces the current parameters by a new set; add()16: Adds parameters; get()17: Returns a parameter by name; set()18: Sets a parameter by name; has()19: Returns true if the parameter is defined; remove()20: Removes a parameter.

The ParameterBag21 instance also has some methods to filter the input values:
4. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/ParameterBag.html 5. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/ParameterBag.html 6. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/ParameterBag.html 7. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/ParameterBag.html 8. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/ParameterBag.html 9. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/FileBag.html 10. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/ServerBag.html 11. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/HeaderBag.html 12. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/ParameterBag.html 13. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/ParameterBag.html#all() 14. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/ParameterBag.html#keys() 15. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/ParameterBag.html#replace() 16. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/ParameterBag.html#add() 17. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/ParameterBag.html#get() 18. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/ParameterBag.html#set() 19. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/ParameterBag.html#has() 20. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/ParameterBag.html#remove() 21. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/ParameterBag.html

PDF brought to you by generated on October 26, 2012

Chapter 128: The HttpFoundation Component | 634

getAlpha()22: Returns the alphabetic characters of the parameter value; getAlnum()23: Returns the alphabetic characters and digits of the parameter value; getDigits()24: Returns the digits of the parameter value; getInt()25: Returns the parameter value converted to integer; filter()26: Filters the parameter by using the PHP filter_var() function.

All getters takes up to three arguments: the first one is the parameter name and the second one is the default value to return if the parameter does not exist:
Listing 128-3

1 2 3 4 5 6 7 8 9 10

// the query string is '?foo=bar'


$request->query->get('foo'); // returns bar $request->query->get('bar'); // returns null $request->query->get('bar', 'bar'); // returns 'bar'

When PHP imports the request query, it handles request parameters like foo[bar]=bar in a special way as it creates an array. So you can get the foo parameter and you will get back an array with a bar element. But sometimes, you might want to get the value for the "original" parameter name: foo[bar]. This is possible with all the ParameterBag getters like get()27 via the third argument:
Listing 128-4

1 2 3 4 5 6 7 8 9 10

// the query string is '?foo[bar]=bar'


$request->query->get('foo'); // returns array('bar' => 'bar') $request->query->get('foo[bar]'); // returns null $request->query->get('foo[bar]', null, true); // returns 'bar'

Last, but not the least, you can also store additional data in the request, thanks to the attributes public property, which is also an instance of ParameterBag28. This is mostly used to attach information that belongs to the Request and that needs to be accessed from many different points in your application. For information on how this is used in the Symfony2 framework, see read more.

Identifying a Request
In your application, you need a way to identify a request; most of the time, this is done via the "path info" of the request, which can be accessed via the getPathInfo()29 method:
Listing 128-5

22. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Request.html#getAlpha() 23. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Request.html#getAlnum() 24. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Request.html#getDigits() 25. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Request.html#getInt() 26. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Request.html#filter() 27. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Request.html#get() 28. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/ParameterBag.html 29. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Request.html#getPathInfo()

PDF brought to you by generated on October 26, 2012

Chapter 128: The HttpFoundation Component | 635

1 // for a request to http://example.com/blog/index.php/post/hello-world 2 // the path info is "/post/hello-world" 3 $request->getPathInfo();

Simulating a Request
Instead of creating a Request based on the PHP globals, you can also simulate a Request:
Listing 128-6

1 $request = Request::create('/hello-world', 'GET', array('name' => 'Fabien'));

The create()30 method creates a request based on a path info, a method and some parameters (the query parameters or the request ones depending on the HTTP method); and of course, you an also override all other variables as well (by default, Symfony creates sensible defaults for all the PHP global variables). Based on such a request, you can override the PHP global variables via overrideGlobals()31:
Listing 128-7

1 $request->overrideGlobals();

You can also duplicate an existing query via duplicate()32 or change a bunch of parameters with a single call to initialize()33.

Accessing the Session


If you have a session attached to the Request, you can access it via the getSession()34 method; the hasPreviousSession()35 method tells you if the request contains a Session which was started in one of the previous requests.

Accessing other Data


The Request class has many other methods that you can use to access the request information. Have a look at the API for more information about them.

Response
A Response36 object holds all the information that needs to be sent back to the client from a given request. The constructor takes up to three arguments: the response content, the status code, and an array of HTTP headers:
Listing 128-8

1 use Symfony\Component\HttpFoundation\Response; 2 3 $response = new Response('Content', 200, array('content-type' => 'text/html'));

30. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Request.html#create() 31. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Request.html#overrideGlobals() 32. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Request.html#duplicate() 33. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Request.html#initialize() 34. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Request.html#getSession() 35. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Request.html#hasPreviousSession() 36. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Response.html

PDF brought to you by generated on October 26, 2012

Chapter 128: The HttpFoundation Component | 636

These information can also be manipulated after the Response object creation:
Listing 128-9

1 2 3 4 5 6

$response->setContent('Hello World');

// the headers public attribute is a ResponseHeaderBag $response->headers->set('Content-Type', 'text/plain');


$response->setStatusCode(404);

When setting the Content-Type of the Response, you can set the charset, but it is better to set it via the setCharset()37 method:
Listing 128-10 1

$response->setCharset('ISO-8859-1');

Note that by default, Symfony assumes that your Responses are encoded in UTF-8.

Sending the Response


Before sending the Response, you can ensure that it is compliant with the HTTP specification by calling the prepare()38 method:
Listing 128-11 1

$response->prepare($request);

Sending the response to the client is then as simple as calling send()39:


Listing 128-12 1

$response->send();

Setting Cookies
The response cookies can be manipulated though the headers public attribute:
Listing 128-13 1

use Symfony\Component\HttpFoundation\Cookie; 2 3 $response->headers->setCookie(new Cookie('foo', 'bar'));

The setCookie()40 method takes an instance of Cookie41 as an argument. You can clear a cookie via the clearCookie()42 method.

Managing the HTTP Cache


The Response43 class has a rich set of methods to manipulate the HTTP headers related to the cache: setPublic()44; setPrivate()45; expire()46;
37. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Response.html#setCharset() 38. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Response.html#prepare() 39. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Response.html#send() 40. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/ResponseHeaderBag.html#setCookie() 41. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Cookie.html 42. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Response.html#clearCookie() 43. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Response.html 44. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Response.html#setPublic() 45. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Response.html#setPrivate()

PDF brought to you by generated on October 26, 2012

Chapter 128: The HttpFoundation Component | 637

setExpires()47; setMaxAge()48; setSharedMaxAge()49; setTtl()50; setClientTtl()51; setLastModified()52; setEtag()53; setVary()54;

The setCache()55 method can be used to set the most commonly used cache information in one method call:
Listing 128-14 1

$response->setCache(array( 2 'etag' => 'abcdef', 3 'last_modified' => new \DateTime(), 4 'max_age' => 600, 5 's_maxage' => 600, 6 'private' => false, 7 'public' => true, 8 ));

To check if the Response validators (ETag, Last-Modified) match a conditional value specified in the client Request, use the isNotModified()56 method:
Listing 128-15 1

if ($response->isNotModified($request)) { 2 $response->send(); 3 }

If the Response is not modified, it sets the status code to 304 and remove the actual response content.

Redirecting the User


To redirect the client to another URL, you can use the RedirectResponse57 class:
Listing 128-16 1

use Symfony\Component\HttpFoundation\RedirectResponse; 2 3 $response = new RedirectResponse('http://example.com/');

46. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Response.html#expire() 47. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Response.html#setExpires() 48. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Response.html#setMaxAge() 49. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Response.html#setSharedMaxAge() 50. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Response.html#setTtl() 51. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Response.html#setClientTtl() 52. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Response.html#setLastModified() 53. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Response.html#setEtag() 54. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Response.html#setVary() 55. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Response.html#setCache() 56. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Response.html#isNotModified() 57. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/RedirectResponse.html

PDF brought to you by generated on October 26, 2012

Chapter 128: The HttpFoundation Component | 638

Streaming a Response
New in version 2.1: Support for streamed responses was added in Symfony 2.1.

The StreamedResponse58 class allows you to stream the Response back to the client. The response content is represented by a PHP callable instead of a string:
Listing 128-17

1 2 3 4 5 6 7 8 9 10 11

use Symfony\Component\HttpFoundation\StreamedResponse; $response = new StreamedResponse(); $response->setCallback(function () { echo 'Hello World'; flush(); sleep(2); echo 'Hello World'; flush(); }); $response->send();

Downloading Files
New in version 2.1: The makeDisposition method was added in Symfony 2.1.

When uploading a file, you must add a Content-Disposition header to your response. While creating this header for basic file downloads is easy, using non-ASCII filenames is more involving. The makeDisposition()59 abstracts the hard work behind a simple API:
Listing 128-18

1 use Symfony\Component\HttpFoundation\ResponseHeaderBag; 2 3 $d = $response->headers->makeDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, 4 'foo.pdf'); 5 $response->headers->set('Content-Disposition', $d);

Session
The session information is in its own document: Session Management.

58. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/StreamedResponse.html 59. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Response.html#makeDisposition()

PDF brought to you by generated on October 26, 2012

Chapter 128: The HttpFoundation Component | 639

Chapter 129

Session Management
The Symfony2 HttpFoundation Component has a very powerful and flexible session subsystem which is designed to provide session management through a simple object-oriented interface using a variety of session storage drivers.
New in version 2.1: The SessionInterface1 interface, as well as a number of other changes, are new as of Symfony 2.1.

Sessions are used via the simple Session2 implementation of SessionInterface3 interface. Quick example:
Listing 129-1

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

use Symfony\Component\HttpFoundation\Session\Session; $session = new Session(); $session->start();

// set and get session attributes $session->set('name', 'Drak'); $session->get('name'); // set flash messages $session->getFlashBag()->add('notice', 'Profile updated'); // retrieve messages foreach ($session->getFlashBag()->get('notice', array()) as $message) { echo "<div class='flash-notice'>$message</div>"; }

1. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/SessionInterface.html 2. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Session.html 3. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/SessionInterface.html

PDF brought to you by generated on October 26, 2012

Chapter 129: Session Management | 640

Symfony sessions are designed to replace several native PHP funtions. Applications should avoid using session_start(), session_regenerate_id(), session_id(), session_name(), and session_destroy() and instead use the APIs in the following section.

While it is recommended to explicitly start a session, a sessions will actually start on demand, that is, if any session request is made to read/write session data.

Symfony sessions are incompatible with PHP ini directive session.auto_start = 1 This directive should be turned off in php.ini, in the webserver directives or in .htaccess.

Session API
The Session4 class implements SessionInterface5. The Session6 has a simple API as follows divided into a couple of groups. Session workflow start()7: Starts the session - do not use session_start(). migrate()8: Regenerates the session ID - do not use session_regenerate_id(). This method can optionally change the lifetime of the new cookie that will be emitted by calling this method. invalidate()9: Clears all session data and regenerates session ID. Do not use session_destroy(). getId()10: Gets the session ID. Do not use session_id(). setId()11: Sets the session ID. Do not use session_id(). getName()12: Gets the session name. Do not use session_name(). setName()13: Sets the session name. Do not use session_name(). Session attributes

set()14: Sets an attribute by key; get()15: Gets an attribute by key; all()16: Gets all attributes as an array of key => value; has()17: Returns true if the attribute exists; keys()18: Returns an array of stored attribute keys;

4. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Session.html 5. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/SessionInterface.html 6. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Session.html 7. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Session.html#start() 8. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Session.html#migrate() 9. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Session.html#invalidate() 10. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Session.html#getId() 11. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Session.html#setId() 12. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Session.html#getName() 13. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Session.html#setName() 14. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Session.html#set() 15. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Session.html#get() 16. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Session.html#all() 17. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Session.html#has() 18. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Session.html#keys()

PDF brought to you by generated on October 26, 2012

Chapter 129: Session Management | 641

replace()19: Sets multiple attributes at once: takes a keyed array and sets each key => value pair. remove()20: Deletes an attribute by key; clear()21: Clear all attributes; The attributes are stored internally in an "Bag", a PHP object that acts like an array. A few methods exist for "Bag" management: registerBag()22: Registers a SessionBagInterface23 getBag()24: Gets a SessionBagInterface25 by bag name. getFlashBag()26: Gets the FlashBagInterface27. This is just a shortcut for convenience. Session meta-data getMetadataBag()28: Gets the StorageMetadataBag29 which contains information about the session.

Session Data Management


PHP's session management requires the use of the $_SESSION super-global, however, this interferes somewhat with code testability and encapsulation in a OOP paradigm. To help overcome this, Symfony2 uses 'session bags' linked to the session to encapsulate a specific dataset of 'attributes' or 'flash messages'. This approach also mitigates namespace pollution within the $_SESSION super-global because each bag stores all its data under a unique namespace. This allows Symfony2 to peacefully co-exist with other applications or libraries that might use the $_SESSION super-global and all data remains completely compatible with Symfony2's session management. Symfony2 provides 2 kinds of storage bags, with two separate implementations. Everything is written against interfaces so you may extend or create your own bag types if necessary.

SessionBagInterface30 has the following API which is intended mainly for internal purposes:
getStorageKey()31: Returns the key which the bag will ultimately store its array under in $_SESSION. Generally this value can be left at its default and is for internal use. initialize()32: This is called internally by Symfony2 session storage classes to link bag data to the session. getName()33: Returns the name of the session bag.

19. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Session.html#replace() 20. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Session.html#remove() 21. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Session.html#clear() 22. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Session.html#registerBag() 23. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/SessionBagInterface.html 24. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Session.html#getBag() 25. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/SessionBagInterface.html 26. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Session.html#getFlashBag() 27. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Flash/FlashBagInterface.html 28. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Session.html#getMetadataBag() 29. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/StorageMetadataBag.html 30. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/SessionBagInterface.html 31. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/SessionBagInterface.html#getStorageKey() 32. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/SessionBagInterface.html#initialize() 33. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/SessionBagInterface.html#getName()

PDF brought to you by generated on October 26, 2012

Chapter 129: Session Management | 642

Attributes
The purpose of the bags implementing the AttributeBagInterface34 is to handle session attribute storage. This might include things like user ID, and remember me login settings or other user based state information. AttributeBag35 This is the standard default implementation. NamespacedAttributeBag36 This implementation allows for attributes to be stored in a structured namespace. Any plain key => value storage system is limited in the extent to which complex data can be stored since each key must be unique. You can achieve namespacing by introducing a naming convention to the keys so different parts of your application could operate without clashing. For example, module1.foo and module2.foo. However, sometimes this is not very practical when the attributes data is an array, for example a set of tokens. In this case, managing the array becomes a burden because you have to retrieve the array then process it and store it again:
Listing 129-2

1 $tokens = array('tokens' => array('a' => 'a6c1e0b6', 2 'b' => 'f4a7b1f3'));

So any processing of this might quickly get ugly, even simply adding a token to the array:
Listing 129-3

1 $tokens = $session->get('tokens'); 2 $tokens['c'] = $value; 3 $session->set('tokens', $tokens);

With structured namespacing, the key can be translated to the array structure like this using a namespace character (defaults to /):
Listing 129-4

1 $session->set('tokens/c', $value);

This way you can easily access a key within the stored array directly and easily.

AttributeBagInterface37 has a simple API set()38: Sets an attribute by key; get()39: Gets an attribute by key; all()40: Gets all attributes as an array of key => value; has()41: Returns true if the attribute exists; keys()42: Returns an array of stored attribute keys; replace()43: Sets multiple attributes at once: takes a keyed array and sets each key => value pair. remove()44: Deletes an attribute by key; clear()45: Clear the bag;

34. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBagInterface.html 35. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBag.html 36. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Attribute/NamespacedAttributeBag.html 37. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBagInterface.html 38. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBagInterface.html#set() 39. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBagInterface.html#get() 40. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBagInterface.html#all() 41. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBagInterface.html#has() 42. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBagInterface.html#keys() 43. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBagInterface.html#replace() 44. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBagInterface.html#remove() 45. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBagInterface.html#clear()

PDF brought to you by generated on October 26, 2012

Chapter 129: Session Management | 643

Flash messages
The purpose of the FlashBagInterface46 is to provide a way of settings and retrieving messages on a per session basis. The usual workflow for flash messages would be set in an request, and displayed after a page redirect. For example, a user submits a form which hits an update controller, and after processing the controller redirects the page to either the updated page or an error page. Flash messages set in the previous page request would be displayed immediately on the subsequent page load for that session. This is however just one application for flash messages. AutoExpireFlashBag47 This implementation messages set in one page-load will be available for display only on the next page load. These messages will auto expire regardless of if they are retrieved or not. FlashBag48 In this implementation, messages will remain in the session until they are explicitly retrieved or cleared. This makes it possible to use ESI caching.

FlashBagInterface49 has a simple API


add()50: Adds a flash message to the stack of specified type; set()51: Sets flashes by type; This method conveniently takes both singles messages as a string or multiple messages in an array. get()52: Gets flashes by type and clears those flashes from the bag; setAll()53: Sets all flashes, accepts a keyed array of arrays type => array(messages); all()54: Gets all flashes (as a keyed array of arrays) and clears the flashes from the bag; peek()55: Gets flashes by type (read only); peekAll()56: Gets all flashes (read only) as keyed array of arrays; has()57: Returns true if the type exists, false if not; keys()58: Returns an array of the stored flash types; clear()59: Clears the bag; For simple applications it is usually sufficient to have one flash message per type, for example a confirmation notice after a form is submitted. However, flash messages are stored in a keyed array by flash $type which means your application can issue multiple messages for a given type. This allows the API to be used for more complex messaging in your application. Examples of setting multiple flashes:
Listing 129-5

1 use Symfony\Component\HttpFoundation\Session\Session; 2 3 $session = new Session();

46. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Flash/FlashBagInterface.html 47. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Flash/AutoExpireFlashBag.html 48. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Flash/FlashBag.html 49. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Flash/FlashBagInterface.html 50. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Flash/FlashBagInterface.html#add() 51. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Flash/FlashBagInterface.html#set() 52. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Flash/FlashBagInterface.html#get() 53. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Flash/FlashBagInterface.html#setAll() 54. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Flash/FlashBagInterface.html#all() 55. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Flash/FlashBagInterface.html#peek() 56. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Flash/FlashBagInterface.html#peekAll() 57. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Flash/FlashBagInterface.html#has() 58. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Flash/FlashBagInterface.html#keys() 59. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Flash/FlashBagInterface.html#clear()

PDF brought to you by generated on October 26, 2012

Chapter 129: Session Management | 644

4 5 6 7 8 9

$session->start();

// add flash messages $session->getFlashBag()->add('warning', 'Your config file is writable, it should be set read-only'); $session->getFlashBag()->add('error', 'Failed to update name'); $session->getFlashBag()->add('error', 'Another error');

Displaying the flash messages might look like this: Simple, display one type of message:
Listing 129-6

1 2 3 4 5 6 7 8 9

// display warnings foreach ($session->getFlashBag()->get('warning', array()) as $message) { echo "<div class='flash-warning'>$message</div>"; } // display errors foreach ($session->getFlashBag()->get('error', array()) as $message) { echo "<div class='flash-error'>$message</div>"; }

Compact method to process display all flashes at once:


Listing 129-7

1 foreach ($session->getFlashBag()->all() as $type => $messages) { 2 foreach ($messages as $message) { 3 echo "<div class='flash-$type'>$message</div>\n"; 4 } 5 }

PDF brought to you by generated on October 26, 2012

Chapter 129: Session Management | 645

Chapter 130

Configuring Sessions and Save Handlers


This section deals with how to configure session management and fine tune it to your specific needs. This documentation covers save handlers, which store and retrieve session data, and configuring session behaviour.

Save Handlers
The PHP session workflow has 6 possible operations that may occur. The normal session follows open, read, write and close, with the possibility of destroy and gc (garbage collection which will expire any old sessions: gc is called randomly according to PHP's configuration and if called, it is invoked after the open operation). You can read more about this at php.net/session.customhandler1

Native PHP Save Handlers


So-called 'native' handlers, are save handlers which are either compiled into PHP or provided by PHP extensions, such as PHP-Sqlite, PHP-Memcached and so on. All native save handlers are internal to PHP and as such, have no public facing API. They must be configured by PHP ini directives, usually session.save_path and potentially other driver specific directives. Specific details can be found in docblock of the setOptions() method of each class. While native save handlers can be activated by directly using ini_set('session.save_handler', $name);, Symfony2 provides a convenient way to activate these in the same way as custom handlers. Symfony2 provides drivers for the following native save handler as an example: NativeFileSessionHandler2 Example usage:
Listing 130-1

1 use Symfony\Component\HttpFoundation\Session\Session; 2 use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;

1. http://php.net/session.customhandler 2. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Storage/Handler/NativeFileSessionHandler.html

PDF brought to you by generated on October 26, 2012

Chapter 130: Configuring Sessions and Save Handlers | 646

3 use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler; 4 5 $storage = new NativeSessionStorage(array(), new NativeFileSessionHandler()); 6 $session = new Session($storage);

With the exception of the files handler which is built into PHP and always available, the availability of the other handlers depends on those PHP extensions being active at runtime.

Native save handlers provide a quick solution to session storage, however, in complex systems where you need more control, custom save handlers may provide more freedom and flexibility. Symfony2 provides several implementations which you may further customise as required.

Custom Save Handlers


Custom handlers are those which completely replace PHP's built in session save handlers by providing six callback functions which PHP calls internally at various points in the session workflow. Symfony2 HttpFoundation provides some by default and these can easily serve as examples if you wish to write your own.

PdoSessionHandler3 MemcacheSessionHandler4 MemcachedSessionHandler5 NullSessionHandler6

Example usage:
Listing 130-2

1 2 3 4 5 6

use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\HttpFoundation\Session\Storage\SessionStorage; use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler; $storage = new NativeSessionStorage(array(), new PdoSessionHandler()); $session = new Session($storage);

Configuring PHP Sessions


The NativeSessionStorage7 can configure most of the PHP ini configuration directives which are documented at php.net/session.configuration8. To configure these settings, pass the keys (omitting the initial session. part of the key) as a key-value array to the $options constructor argument. Or set them via the setOptions()9 method. For the sake of clarity, some key options are explained in this documentation.
3. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.html 4. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcacheSessionHandler.html 5. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.html 6. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Storage/Handler/NullSessionHandler.html 7. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.html 8. http://php.net/session.configuration 9. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.html#setOptions()

PDF brought to you by generated on October 26, 2012

Chapter 130: Configuring Sessions and Save Handlers | 647

Session Cookie Lifetime


For security, session tokens are generally recommended to be sent as session cookies. You can configure the lifetime of session cookies by specifying the lifetime (in seconds) using the cookie_lifetime key in the constructor's $options argument in NativeSessionStorage10. Setting a cookie_lifetime to 0 will cause the cookie to live only as long as the browser remains open. Generally, cookie_lifetime would be set to a relatively large number of days, weeks or months. It is not uncommon to set cookies for a year or more depending on the application. Since session cookies are just a client-side token, they are less important in controlling the fine details of your security settings which ultimately can only be securely controlled from the server side.
The cookie_lifetime setting is the number of seconds the cookie should live for, it is not a Unix timestamp. The resulting session cookie will be stamped with an expiry time of time()``+``cookie_lifetime where the time is taken from the server.

Configuring Garbage Collection


When a session opens, PHP will call the gc handler randomly according to the probability set by session.gc_probability / session.gc_divisor. For example if these were set to 5/100 respectively, it would mean a probability of 5%. Similarly, 3/4 would mean a 3 in 4 chance of being called, i.e. 75%. If the garbage collection handler is invoked, PHP will pass the value stored in the PHP ini directive session.gc_maxlifetime. The meaning in this context is that any stored session that was saved more than maxlifetime ago should be deleted. This allows one to expire records based on idle time. You can configure these settings by passing gc_probability, gc_divisor and gc_maxlifetime in an array to the constructor of NativeSessionStorage11 or to the setOptions()12 method.

Session Lifetime
When a new session is created, meaning Symfony2 issues a new session cookie to the client, the cookie will be stamped with an expiry time. This is calculated by adding the PHP runtime configuration value in session.cookie_lifetime with the current server time.
PHP will only issue a cookie once. The client is expected to store that cookie for the entire lifetime. A new cookie will only be issued when the session is destroyed, the browser cookie is deleted, or the session ID is regenerated using the migrate() or invalidate() methods of the Session class. The initial cookie lifetime can be set by configuring NativeSessionStorage using the setOptions(array('cookie_lifetime' => 1234)) method.

A cookie lifetime of 0 means the cookie expires when the browser is closed.

10. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.html 11. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.html 12. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.html#setOptions()

PDF brought to you by generated on October 26, 2012

Chapter 130: Configuring Sessions and Save Handlers | 648

Session Idle Time/Keep Alive


There are often circumstances where you may want to protect, or minimize unauthorized use of a session when a user steps away from their terminal while logged in by destroying the session after a certain period of idle time. For example, it is common for banking applications to log the user out after just 5 to 10 minutes of inactivity. Setting the cookie lifetime here is not appropriate because that can be manipulated by the client, so we must do the expiry on the server side. The easiest way is to implement this via garbage collection which runs reasonably frequently. The cookie lifetime would be set to a relatively high value, and the garbage collection maxlifetime would be set to destroy sessions at whatever the desired idle period is. The other option is to specifically checking if a session has expired after the session is started. The session can be destroyed as required. This method of processing can allow the expiry of sessions to be integrated into the user experience, for example, by displaying a message. Symfony2 records some basic meta-data about each session to give you complete freedom in this area.

Session meta-data
Sessions are decorated with some basic meta-data to enable fine control over the security settings. The session object has a getter for the meta-data, getMetadataBag()13 which exposes an instance of MetadataBag14:
Listing 130-3

1 $session->getMetadataBag()->getCreated(); 2 $session->getMetadataBag()->getLastUsed();

Both methods return a Unix timestamp (relative to the server). This meta-data can be used to explicitly expire a session on access, e.g.:
Listing 130-4

1 $session->start(); 2 if (time() - $session->getMetadataBag()->getLastUpdate() > $maxIdleTime) { 3 $session->invalidate(); 4 throw new SessionExpired(); // redirect to expired session page 5 }

It is also possible to tell what the cookie_lifetime was set to for a particular cookie by reading the getLifetime() method:
Listing 130-5

1 $session->getMetadataBag()->getLifetime();

The expiry time of the cookie can be determined by adding the created timestamp and the lifetime.

PHP 5.4 compatibility


Since PHP 5.4.0, SessionHandler15 and SessionHandlerInterface16 are available. Symfony 2.1 provides forward compatibility for the SessionHandlerInterface17 so it can be used under PHP 5.3. This greatly improves inter-operability with other libraries.
13. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Session.html#getMetadataBag() 14. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Storage/MetadataBag.html 15. http://php.net/manual/en/class.sessionhandler.php 16. http://php.net/manual/en/class.sessionhandlerinterface.php 17. http://php.net/manual/en/class.sessionhandlerinterface.php

PDF brought to you by generated on October 26, 2012

Chapter 130: Configuring Sessions and Save Handlers | 649

SessionHandler18 is a special PHP internal class which exposes native save handlers to PHP user-space.
In order to provide a solution for those using PHP 5.4, Symfony2 has a special class called NativeSessionHandler19 which under PHP 5.4, extends from SessionHandler and under PHP 5.3 is just a empty base class. This provides some interesting opportunities to leverage PHP 5.4 functionality if it is available.

Save Handler Proxy


There are two kinds of save handler class proxies which inherit from AbstractProxy20: they are NativeProxy21 and SessionHandlerProxy22.

NativeSessionStorage23 automatically injects storage handlers into a save handler proxy unless already wrapped by one. NativeProxy24 is used automatically under PHP 5.3 when internal PHP save handlers are specified using the Native*SessionHandler classes, while SessionHandlerProxy25 will be used to wrap any custom save handlers, that implement SessionHandlerInterface26.
Under PHP 5.4 and above, all session handlers implement SessionHandlerInterface27 including Native*SessionHandler classes which inherit from SessionHandler28. The proxy mechanism allows you to get more deeply involved in session save handler classes. A proxy for example could be used to encrypt any session transaction without knowledge of the specific save handler.

18. http://php.net/manual/en/class.sessionhandler.php 19. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Storage/Handler/NativeSessionHandler.html 20. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Storage/Handler/AbstractProxy.html 21. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Storage/Handler/NativeProxy.html 22. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Storage/Handler/SessionHandlerProxy.html 23. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.html 24. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Storage/Handler/NativeProxy.html 25. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Storage/Handler/SessionHandlerProxy.html 26. http://php.net/manual/en/class.sessionhandlerinterface.php 27. http://php.net/manual/en/class.sessionhandlerinterface.php 28. http://php.net/manual/en/class.sessionhandler.php

PDF brought to you by generated on October 26, 2012

Chapter 130: Configuring Sessions and Save Handlers | 650

Chapter 131

Testing with Sessions


Symfony2 is designed from the ground up with code-testability in mind. In order to make your code which utilizes session easily testable we provide two separate mock storage mechanisms for both unit testing and functional testing. Testing code using real sessions is tricky because PHP's workflow state is global and it is not possible to have multiple concurrent sessions in the same PHP process. The mock storage engines simulate the PHP session workflow without actually starting one allowing you to test your code without complications. You may also run multiple instances in the same PHP process. The mock storage drivers do not read or write the system globals session_id() or session_name(). Methods are provided to simulate this if required:

getId()1: Gets the session ID. setId()2: Sets the session ID. getName()3: Gets the session name. setName()4: Sets the session name.

Unit Testing
For unit testing where it is not necessary to persist the session, you should simply swap out the default storage engine with MockArraySessionStorage5:
Listing 131-1

1 use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; 2 use Symfony\Component\HttpFoundation\Session\Session; 3 4 $session = new Session(new MockArraySessionStorage());

1. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/SessionStorageInterface.html#getId() 2. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/SessionStorageInterface.html#setId() 3. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/SessionStorageInterface.html#getName() 4. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/SessionStorageInterface.html#setName() 5. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Storage/MockArraySessionStorage.html

PDF brought to you by generated on October 26, 2012

Chapter 131: Testing with Sessions | 651

Functional Testing
For functional testing where you may need to persist session data across separate PHP processes, simply change the storage engine to MockFileSessionStorage6:
Listing 131-2

1 use Symfony\Component\HttpFoundation\Session\Session; 2 use Symfony\Component\HttpFoundation\Session\Storage\MockFileSessionStorage; 3 4 $session = new Session(new MockFileSessionStorage());

6. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Session/Storage/MockFileSessionStorage.html

PDF brought to you by generated on October 26, 2012

Chapter 131: Testing with Sessions | 652

Chapter 132

The Locale Component


Locale component provides fallback code to handle cases when the intl extension is missing. Additionally it extends the implementation of a native Locale1 class with several handy methods. Replacement for the following functions and classes is provided:

intl_is_failure2 intl_get_error_code3 intl_get_error_message4 Collator5 IntlDateFormatter6 Locale7 NumberFormatter8


Stub implementation only supports the en locale.

Installation
You can install the component in many different ways: Use the official Git repository (https://github.com/symfony/Locale9);
1. http://php.net/manual/en/class.locale.php 2. http://php.net/manual/en/function.intl-is-failure.php 3. http://php.net/manual/en/function.intl-get-error-code.php 4. http://php.net/manual/en/function.intl-get-error-message.php 5. http://php.net/manual/en/class.collator.php 6. http://php.net/manual/en/class.intldateformatter.php 7. http://php.net/manual/en/class.locale.php 8. http://php.net/manual/en/class.numberformatter.php 9. https://github.com/symfony/Locale

PDF brought to you by generated on October 26, 2012

Chapter 132: The Locale Component | 653

Install it via PEAR ( pear.symfony.com/Locale); Install it via Composer (symfony/locale on Packagist).

Usage
Taking advantage of the fallback code includes requiring function stubs and adding class stubs to the autoloader. When using the ClassLoader component following code is sufficient to supplement missing intl extension:
Listing 132-1

1 if (!function_exists('intl_get_error_code')) { 2 require __DIR__.'/path/to/src/Symfony/Component/Locale/Resources/stubs/functions.php'; 3 4 $loader->registerPrefixFallbacks(array(__DIR__.'/path/to/src/Symfony/Component/Locale/ 5 Resources/stubs')); }

Locale10 class enriches native Locale11 class with additional features:


Listing 132-2

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

use Symfony\Component\Locale\Locale;

// Get the country names for a locale or get all country codes $countries = Locale::getDisplayCountries('pl'); $countryCodes = Locale::getCountries(); // Get the language names for a locale or get all language codes $languages = Locale::getDisplayLanguages('fr'); $languageCodes = Locale::getLanguages(); // Get the locale names for a given code or get all locale codes $locales = Locale::getDisplayLocales('en'); $localeCodes = Locale::getLocales(); // Get ICU versions $icuVersion = Locale::getIcuVersion(); $icuDataVersion = Locale::getIcuDataVersion();

10. http://api.symfony.com/2.1/Symfony/Component/Locale/Locale.html 11. http://php.net/manual/en/class.locale.php

PDF brought to you by generated on October 26, 2012

Chapter 132: The Locale Component | 654

Chapter 133

The Process Component


The Process Component executes commands in sub-processes.

Installation
You can install the component in many different ways: Use the official Git repository (https://github.com/symfony/Process1); Install it via PEAR ( pear.symfony.com/Process); Install it via Composer (symfony/process on Packagist).

Usage
The Process2 class allows you to execute a command in a sub-process:
Listing 133-1

1 2 3 4 5 6 7 8 9 10

use Symfony\Component\Process\Process; $process = new Process('ls -lsa'); $process->setTimeout(3600); $process->run(); if (!$process->isSuccessful()) { throw new \RuntimeException($process->getErrorOutput()); } print $process->getOutput();

The run()3 method takes care of the subtle differences between the different platforms when executing the command.

1. https://github.com/symfony/Process 2. http://api.symfony.com/2.1/Symfony/Component/Process/Process.html 3. http://api.symfony.com/2.1/Symfony/Component/Process/Process.html#run()

PDF brought to you by generated on October 26, 2012

Chapter 133: The Process Component | 655

When executing a long running command (like rsync-ing files to a remote server), you can give feedback to the end user in real-time by passing an anonymous function to the run()4 method:
Listing 133-2

1 2 3 4 5 6 7 8 9 10

use Symfony\Component\Process\Process; $process = new Process('ls -lsa'); $process->run(function ($type, $buffer) { if ('err' === $type) { echo 'ERR > '.$buffer; } else { echo 'OUT > '.$buffer; } });

If you want to execute some PHP code in isolation, use the PhpProcess instead:
Listing 133-3

1 2 3 4 5 6 7

use Symfony\Component\Process\PhpProcess; $process = new PhpProcess(<<<EOF <?php echo 'Hello World'; ?> EOF ); $process->run();

New in version 2.1: The ProcessBuilder class has been as of 2.1.

To make your code work better on all platforms, you might want to use the ProcessBuilder5 class instead:
Listing 133-4

1 use Symfony\Component\Process\ProcessBuilder; 2 3 $builder = new ProcessBuilder(array('ls', '-lsa')); 4 $builder->getProcess()->run();

4. http://api.symfony.com/2.1/Symfony/Component/Process/Process.html#run() 5. http://api.symfony.com/2.1/Symfony/Component/Process/ProcessBuilder.html

PDF brought to you by generated on October 26, 2012

Chapter 133: The Process Component | 656

Chapter 134

The Routing Component


The Routing Component maps an HTTP request to a set of configuration variables.

Installation
You can install the component in many different ways: Use the official Git repository (https://github.com/symfony/Routing1); Install it via PEAR (pear.symfony.com/Routing); Install it via Composer (symfony/routing on Packagist)

Usage
In order to set up a basic routing system you need three parts: A RouteCollection2, which contains the route definitions (instances of the class Route3) A RequestContext4, which has information about the request A UrlMatcher5, which performs the mapping of the request to a single route Let's see a quick example. Notice that this assumes that you've already configured your autoloader to load the Routing component:
Listing 134-1

1 2 3 4 5

use use use use

Symfony\Component\Routing\Matcher\UrlMatcher; Symfony\Component\Routing\RequestContext; Symfony\Component\Routing\RouteCollection; Symfony\Component\Routing\Route;

1. https://github.com/symfony/Routing 2. http://api.symfony.com/2.1/Symfony/Component/Routing/RouteCollection.html 3. http://api.symfony.com/2.1/Symfony/Component/Routing/Route.html 4. http://api.symfony.com/2.1/Symfony/Component/Routing/RequestContext.html 5. http://api.symfony.com/2.1/Symfony/Component/Routing/Matcher/UrlMatcher.html

PDF brought to you by generated on October 26, 2012

Chapter 134: The Routing Component | 657

6 7 8 9 10 11 12 13 14 15

$route = new Route('/foo', array('controller' => 'MyController')) $routes = new RouteCollection(); $routes->add('route_name', $route); $context = new RequestContext($_SERVER['REQUEST_URI']); $matcher = new UrlMatcher($routes, $context); $parameters = $matcher->match('/foo'); // array('controller' => 'MyController', '_route' => 'route_name')

Be careful when using $_SERVER['REQUEST_URI'], as it may include any query parameters on the URL, which will cause problems with route matching. An easy way to solve this is to use the HttpFoundation component as explained below.

You can add as many routes as you like to a RouteCollection6. The RouteCollection::add()7 method takes two arguments. The first is the name of the route. The second is a Route8 object, which expects a URL path and some array of custom variables in its constructor. This array of custom variables can be anything that's significant to your application, and is returned when that route is matched. If no matching route can be found a ResourceNotFoundException9 will be thrown. In addition to your array of custom variables, a _route key is added, which holds the name of the matched route.

Defining routes
A full route definition can contain up to four parts: 1. The URL pattern route. This is matched against the URL passed to the RequestContext, and can contain named wildcard placeholders (e.g. {placeholders}) to match dynamic parts in the URL. 2. An array of default values. This contains an array of arbitrary values that will be returned when the request matches the route. 3. An array of requirements. These define constraints for the values of the placeholders as regular expressions. 4. An array of options. These contain internal settings for the route and are the least commonly needed. Take the following route, which combines several of these ideas:
Listing 134-2

1 2 3 4 5 6 7 8 9 10

$route = new Route( '/archive/{month}', // path array('controller' => 'showArchive'), // default values array('month' => '[0-9]{4}-[0-9]{2}'), // requirements array() // options );

// ...
$parameters = $matcher->match('/archive/2012-01');

6. http://api.symfony.com/2.1/Symfony/Component/Routing/RouteCollection.html 7. http://api.symfony.com/2.1/Symfony/Component/Routing/RouteCollection.html#add() 8. http://api.symfony.com/2.1/Symfony/Component/Routing/Route.html 9. http://api.symfony.com/2.1/Symfony/Component/Routing/Exception/ResourceNotFoundException.html

PDF brought to you by generated on October 26, 2012

Chapter 134: The Routing Component | 658

11 12 13 14 15 16 17 18

// array( // 'controller' => 'showArchive', // 'month' => '2012-01', // '_route' => ... // )
$parameters = $matcher->match('/archive/foo'); // throws ResourceNotFoundException

In this case, the route is matched by /archive/2012-01, because the {month} wildcard matches the regular expression wildcard given. However, /archive/foo does not match, because "foo" fails the month wildcard. Besides the regular expression constraints there are two special requirements you can define: _method enforces a certain HTTP request method (HEAD, GET, POST, ...) _scheme enforces a certain HTTP scheme (http, https) For example, the following route would only accept requests to /foo with the POST method and a secure connection:
Listing 134-3

1 $route = new Route( 2 '/foo', 3 array(), 4 array('_method' => 'post', '_scheme' => 'https' ) 5 );

If you want to match all urls which start with a certain path and end in an arbitrary suffix you can use the following route definition:
Listing 134-4

1 $route = new Route( 2 '/start/{suffix}', 3 array('suffix' => ''), 4 array('suffix' => '.*') 5 );

Using Prefixes
You can add routes or other instances of RouteCollection10 to another collection. This way you can build a tree of routes. Additionally you can define a prefix, default requirements and default options to all routes of a subtree:
Listing 134-5

1 2 3 4 5 6 7 8 9

$rootCollection = new RouteCollection(); $subCollection = new RouteCollection(); $subCollection->add(...); $subCollection->add(...); $rootCollection->addCollection( $subCollection, '/prefix',

10. http://api.symfony.com/2.1/Symfony/Component/Routing/RouteCollection.html

PDF brought to you by generated on October 26, 2012

Chapter 134: The Routing Component | 659

10 11 );

array('_scheme' => 'https')

Set the Request Parameters


The RequestContext11 provides information about the current request. You can define all parameters of an HTTP request with this class via its constructor:
Listing 134-6

1 public function __construct( 2 $baseUrl = '', 3 $method = 'GET', 4 $host = 'localhost', 5 $scheme = 'http', 6 $httpPort = 80, 7 $httpsPort = 443 8 )

Normally you can pass the values from the $_SERVER variable to populate the RequestContext12. But If you use the HttpFoundation component, you can use its Request13 class to feed the RequestContext14 in a shortcut:
Listing 134-7

1 use Symfony\Component\HttpFoundation\Request; 2 3 $context = new RequestContext(); 4 $context->fromRequest(Request::createFromGlobals());

Generate a URL
While the UrlMatcher15 tries to find a route that fits the given request you can also build a URL from a certain route:
Listing 134-8

1 2 3 4 5 6 7 8 9 10 11 12 13

use Symfony\Component\Routing\Generator\UrlGenerator; $routes = new RouteCollection(); $routes->add('show_post', new Route('/show/{slug}')); $context = new RequestContext($_SERVER['REQUEST_URI']); $generator = new UrlGenerator($routes, $context); $url = $generator->generate('show_post', array( 'slug' => 'my-blog-post' )); // /show/my-blog-post

11. http://api.symfony.com/2.1/Symfony/Component/Routing/RequestContext.html 12. http://api.symfony.com/2.1/Symfony/Component/Routing/RequestContext.html 13. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/Request.html 14. http://api.symfony.com/2.1/Symfony/Component/Routing/RequestContext.html 15. http://api.symfony.com/2.1/Symfony/Component/Routing/Matcher/UrlMatcher.html

PDF brought to you by generated on October 26, 2012

Chapter 134: The Routing Component | 660

If you have defined the _scheme requirement, an absolute URL is generated if the scheme of the current RequestContext16 does not match the requirement.

Load Routes from a File


You've already seen how you can easily add routes to a collection right inside PHP. But you can also load routes from a number of different files. The Routing component comes with a number of loader classes, each giving you the ability to load a collection of route definitions from an external file of some format. Each loader expects a FileLocator17 instance as the constructor argument. You can use the FileLocator18 to define an array of paths in which the loader will look for the requested files. If the file is found, the loader returns a RouteCollection19. If you're using the YamlFileLoader, then route definitions look like this:
Listing 134-9

1 # routes.yml 2 route1: 3 pattern: /foo 4 defaults: { controller: 'MyController::fooAction' } 5 6 route2: 7 pattern: /foo/bar 8 defaults: { controller: 'MyController::foobarAction' }

To load this file, you can use the following code. This assumes that your routes.yml file is in the same directory as the below code:
Listing 134-10 1

2 3 4 5 6 7

use Symfony\Component\Config\FileLocator; use Symfony\Component\Routing\Loader\YamlFileLoader;

// look inside *this* directory $locator = new FileLocator(array(__DIR__)); $loader = new YamlFileLoader($locator); $collection = $loader->load('routes.yml');

Besides YamlFileLoader20 there are two other loaders that work the same way: XmlFileLoader21 PhpFileLoader22 If you use the PhpFileLoader23 you have to provide the name of a php file which returns a RouteCollection24:
Listing 134-11

1 // RouteProvider.php 2 use Symfony\Component\Routing\RouteCollection; 3 use Symfony\Component\Routing\Route;

16. http://api.symfony.com/2.1/Symfony/Component/Routing/RequestContext.html 17. http://api.symfony.com/2.1/Symfony/Component/Config/FileLocator.html 18. http://api.symfony.com/2.1/Symfony/Component/Config/FileLocator.html 19. http://api.symfony.com/2.1/Symfony/Component/Routing/RouteCollection.html 20. http://api.symfony.com/2.1/Symfony/Component/Routing/Loader/YamlFileLoader.html 21. http://api.symfony.com/2.1/Symfony/Component/Routing/Loader/XmlFileLoader.html 22. http://api.symfony.com/2.1/Symfony/Component/Routing/Loader/PhpFileLoader.html 23. http://api.symfony.com/2.1/Symfony/Component/Routing/Loader/PhpFileLoader.html 24. http://api.symfony.com/2.1/Symfony/Component/Routing/RouteCollection.html

PDF brought to you by generated on October 26, 2012

Chapter 134: The Routing Component | 661

4 5 6 7 8 9 10 11 12

$collection = new RouteCollection(); $collection->add( 'route_name', new Route('/foo', array('controller' => 'ExampleController')) ); // ... return $collection;

Routes as Closures
There is also the ClosureLoader25, which calls a closure and uses the result as a RouteCollection26:
Listing 134-12 1

use Symfony\Component\Routing\Loader\ClosureLoader; $closure = function() { return new RouteCollection(); }; $loader = new ClosureLoader(); $collection = $loader->load($closure);

2 3 4 5 6 7 8

Routes as Annotations
Last but not least there are AnnotationDirectoryLoader27 and AnnotationFileLoader28 to load route definitions from class annotations. The specific details are left out here.

The all-in-one Router


The Router29 class is a all-in-one package to quickly use the Routing component. The constructor expects a loader instance, a path to the main route definition and some other settings:
Listing 134-13 1

public function __construct( 2 LoaderInterface $loader, 3 $resource, 4 array $options = array(), 5 RequestContext $context = null, 6 array $defaults = array() 7 );

With the cache_dir option you can enable route caching (if you provide a path) or disable caching (if it's set to null). The caching is done automatically in the background if you want to use it. A basic example of the Router30 class would look like:
Listing 134-14

1 $locator = new FileLocator(array(__DIR__)); 2 $requestContext = new RequestContext($_SERVER['REQUEST_URI']); 3

25. http://api.symfony.com/2.1/Symfony/Component/Routing/Loader/ClosureLoader.html 26. http://api.symfony.com/2.1/Symfony/Component/Routing/RouteCollection.html 27. http://api.symfony.com/2.1/Symfony/Component/Routing/Loader/AnnotationDirectoryLoader.html 28. http://api.symfony.com/2.1/Symfony/Component/Routing/Loader/AnnotationFileLoader.html 29. http://api.symfony.com/2.1/Symfony/Component/Routing/Router.html 30. http://api.symfony.com/2.1/Symfony/Component/Routing/Router.html

PDF brought to you by generated on October 26, 2012

Chapter 134: The Routing Component | 662

4 $router = new Router( 5 new YamlFileLoader($locator), 6 "routes.yml", 7 array('cache_dir' => __DIR__.'/cache'), 8 $requestContext, 9 ); 10 $router->match('/foo/bar');

If you use caching, the Routing component will compile new classes which are saved in the cache_dir. This means your script must have write permissions for that location.

PDF brought to you by generated on October 26, 2012

Chapter 134: The Routing Component | 663

Chapter 135

The Serializer Component


The Serializer Component is meant to be used to turn objects into a specific format (XML, JSON, Yaml, ...) and the other way around. In order to do so, the Serializer Component follows the following simple schema.

As you can see in the picture above, an array is used as a man in the middle. This way, Encoders will only deal with turning specific formats into arrays and vice versa. The same way, Normalizers will deal with turning specific objects into arrays and vice versa. Serialization is a complicated topic, and while this component may not work in all cases, it can be a useful tool while developing tools to serialize and deserialize your objects.

Installation
You can install the component in many different ways: Use the official Git repository (https://github.com/symfony/Serializer1); Install it via PEAR ( pear.symfony.com/Serializer);
PDF brought to you by generated on October 26, 2012 Chapter 135: The Serializer Component | 664

Install it via Composer (symfony/serializer on Packagist).

Usage
Using the Serializer component is really simple. You just need to set up the Serializer2 specifying which Encoders and Normalizer are going to be available:
Listing 135-1

1 2 3 4 5 6 7 8 9

use use use use

Symfony\Component\Serializer\Serializer; Symfony\Component\Serializer\Encoder\XmlEncoder; Symfony\Component\Serializer\Encoder\JsonEncoder; Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;

$encoders = array(new XmlEncoder(), new JsonEncoder()); $normalizers = array(new GetSetMethodNormalizer()); $serializer = new Serializer($normalizers, $encoders);

Serializing an object
For the sake of this example, let's assume the following class already exists in our project:
Listing 135-2

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29

namespace Acme; class Person { private $age; private $name;

// Getters public function getName() { return $this->name; }


public function getAge() { return $this->age; }

// Setters public function setName($name) { $this->name = $name; }


public function setAge($age) { $this->age = $age; } }

Now, if you want to serialize this object into JSON, you only need to use the Serializer service created before:
1. https://github.com/symfony/Serializer 2. http://api.symfony.com/2.1/Symfony/Component/Serializer/Serializer.html

PDF brought to you by generated on October 26, 2012

Chapter 135: The Serializer Component | 665

Listing 135-3

1 2 3 4 5

$person = new Acme\Person(); $person->setName('foo'); $person->setAge(99); $serializer->serialize($person, 'json'); // Output: {"name":"foo","age":99}

The first parameter of the serialize()3 is the object to be serialized and the second is used to choose the proper encoder, in this case JsonEncoder4.

Deserializing an Object
Let's see now how to do the exactly the opposite. This time, the information of the People class would be encoded in XML format:
Listing 135-4

1 2 3 4 5 6 7 8

$data = <<<EOF <person> <name>foo</name> <age>99</age> </person> EOF; $person = $serializer->deserialize($data,'Acme\Person','xml');

In this case, deserialize()5 needs three parameters: 1. The information to be decoded 2. The name of the class this information will be decoded to 3. The encoder used to convert that information into an array

JMSSerializationBundle
A popular third-party bundle, JMSSerializationBundle6 exists and extends (and sometimes replaces) the serialization functionality. This includes the ability to configure how your objects should be serialize/ deserialized via annotations (as well as YML, XML and PHP), integration with the Doctrine ORM, and handling of other complex cases (e.g. circular references).

3. http://api.symfony.com/2.1/Symfony/Component/Serializer/Serializer.html#serialize() 4. http://api.symfony.com/2.1/Symfony/Component/Serializer/Encoder/JsonEncoder.html 5. http://api.symfony.com/2.1/Symfony/Component/Serializer/Serializer.html#deserialize() 6. https://github.com/schmittjoh/JMSSerializerBundle

PDF brought to you by generated on October 26, 2012

Chapter 135: The Serializer Component | 666

Chapter 136

The Templating Component


Templating provides all the tools needed to build any kind of template system. It provides an infrastructure to load template files and optionally monitor them for changes. It also provides a concrete template engine implementation using PHP with additional tools for escaping and separating templates into blocks and layouts.

Installation
You can install the component in many different ways: Use the official Git repository (https://github.com/symfony/Templating1); Install it via PEAR (pear.symfony.com/Templating); Install it via Composer (symfony/templating on Packagist).

Usage
The PhpEngine2 class is the entry point of the component. It needs a template name parser (TemplateNameParserInterface3) to convert a template name to a template reference and template loader (LoaderInterface4) to find the template associated to a reference:
Listing 136-1

1 2 3 4 5 6 7

use Symfony\Component\Templating\PhpEngine; use Symfony\Component\Templating\TemplateNameParser; use Symfony\Component\Templating\Loader\FilesystemLoader; $loader = new FilesystemLoader(__DIR__ . '/views/%name%'); $view = new PhpEngine(new TemplateNameParser(), $loader);

1. https://github.com/symfony/Templating 2. http://api.symfony.com/2.1/Symfony/Component/Templating/PhpEngine.html 3. http://api.symfony.com/2.1/Symfony/Component/Templating/TemplateNameParserInterface.html 4. http://api.symfony.com/2.1/Symfony/Component/Templating/Loader/LoaderInterface.html

PDF brought to you by generated on October 26, 2012

Chapter 136: The Templating Component | 667

8 9 echo $view->render('hello.php', array('firstname' => 'Fabien'));

The render()5 method executes the file views/hello.php and returns the output text.
Listing 136-2

1 <!-- views/hello.php --> 2 Hello, <?php echo $firstname ?>!

Template Inheritance with Slots


The template inheritance is designed to share layouts with many templates.
Listing 136-3

1 <!-- views/layout.php --> 2 <html> 3 <head> 4 <title><?php $view['slots']->output('title', 'Default title') ?></title> 5 </head> 6 <body> 7 <?php $view['slots']->output('_content') ?> 8 </body> 9 </html>

The extend()6 method is called in the sub-template to set its parent template.
Listing 136-4

1 2 3 4 5 6 7 8 9 10 11

<!-- views/page.php --> <?php $view->extend('layout.php') ?>


<?php $view['slots']->set('title', $page->title) ?> <h1> <?php echo $page->title ?> </h1> <p> <?php echo $page->body ?> </p>

To use template inheritance, the SlotsHelper7 helper must be registered:


Listing 136-5

1 2 3 4 5 6 7 8

use Symfony\Component\Templating\Helper\SlotsHelper; $view->set(new SlotsHelper());

// Retrieve page object $page = ...;


echo $view->render('page.php', array('page' => $page));

5. http://api.symfony.com/2.1/Symfony/Component/Templating/PhpEngine.html#render() 6. http://api.symfony.com/2.1/Symfony/Component/Templating/PhpEngine.html#extend() 7. http://api.symfony.com/2.1/Symfony/Component/Templating/Helper/SlotsHelper.html

PDF brought to you by generated on October 26, 2012

Chapter 136: The Templating Component | 668

Multiple levels of inheritance is possible: a layout can extend an other layout.

Output Escaping
This documentation is still being written.

The Asset Helper


This documentation is still being written.

PDF brought to you by generated on October 26, 2012

Chapter 136: The Templating Component | 669

Chapter 137

The YAML Component


The YAML Component loads and dumps YAML files.

What is it?
The Symfony2 YAML Component parses YAML strings to convert them to PHP arrays. It is also able to convert PHP arrays to YAML strings. YAML1, YAML Ain't Markup Language, is a human friendly data serialization standard for all programming languages. YAML is a great format for your configuration files. YAML files are as expressive as XML files and as readable as INI files. The Symfony2 YAML Component implements the YAML 1.2 version of the specification.

Installation
You can install the component in many different ways: Use the official Git repository (https://github.com/symfony/Yaml2); Install it via PEAR ( pear.symfony.com/Yaml); Install it via Composer (symfony/yaml on Packagist).

Why?
Fast
One of the goal of Symfony YAML is to find the right balance between speed and features. It supports just the needed feature to handle configuration files.

1. http://yaml.org/ 2. https://github.com/symfony/Yaml

PDF brought to you by generated on October 26, 2012

Chapter 137: The YAML Component | 670

Real Parser
It sports a real parser and is able to parse a large subset of the YAML specification, for all your configuration needs. It also means that the parser is pretty robust, easy to understand, and simple enough to extend.

Clear error messages


Whenever you have a syntax problem with your YAML files, the library outputs a helpful message with the filename and the line number where the problem occurred. It eases the debugging a lot.

Dump support
It is also able to dump PHP arrays to YAML with object support, and inline level configuration for pretty outputs.

Types Support
It supports most of the YAML built-in types like dates, integers, octals, booleans, and much more...

Full merge key support


Full support for references, aliases, and full merge key. Don't repeat yourself by referencing common configuration bits.

Using the Symfony2 YAML Component


The Symfony2 YAML Component is very simple and consists of two main classes: one parses YAML strings (Parser3), and the other dumps a PHP array to a YAML string (Dumper4). On top of these two classes, the Yaml5 class acts as a thin wrapper that simplifies common uses.

Reading YAML Files


The parse()6 method parses a YAML string and converts it to a PHP array:
Listing 137-1

1 use Symfony\Component\Yaml\Parser; 2 3 $yaml = new Parser(); 4 5 $value = $yaml->parse(file_get_contents('/path/to/file.yml'));

If an error occurs during parsing, the parser throws a ParseException7 exception indicating the error type and the line in the original YAML string where the error occurred:
Listing 137-2

1 use Symfony\Component\Yaml\Exception\ParseException; 2 3 try {

3. http://api.symfony.com/2.1/Symfony/Component/Yaml/Parser.html 4. http://api.symfony.com/2.1/Symfony/Component/Yaml/Dumper.html 5. http://api.symfony.com/2.1/Symfony/Component/Yaml/Yaml.html 6. http://api.symfony.com/2.1/Symfony/Component/Yaml/Parser.html#parse() 7. http://api.symfony.com/2.1/Symfony/Component/Yaml/Exception/ParseException.html

PDF brought to you by generated on October 26, 2012

Chapter 137: The YAML Component | 671

4 $value = $yaml->parse(file_get_contents('/path/to/file.yml')); 5 } catch (ParseException $e) { 6 printf("Unable to parse the YAML string: %s", $e->getMessage()); 7 }

As the parser is re-entrant, you can use the same parser object to load different YAML strings.

When loading a YAML file, it is sometimes better to use the parse()8 wrapper method:
Listing 137-3

1 use Symfony\Component\Yaml\Yaml; 2 3 $loader = Yaml::parse('/path/to/file.yml');

The parse()9 static method takes a YAML string or a file containing YAML. Internally, it calls the parse()10 method, but enhances the error if something goes wrong by adding the filename to the message.

Executing PHP Inside YAML Files


New in version 2.1: The Yaml::enablePhpParsing() method is new to Symfony 2.1. Prior to 2.1, PHP was always executed when calling the parse() function.

By default, if you include PHP inside a YAML file, it will not be parsed. If you do want PHP to be parsed, you must call Yaml::enablePhpParsing() before parsing the file to activate this mode. If you only want to allow PHP code for a single YAML file, be sure to disable PHP parsing after parsing the single file by calling Yaml::$enablePhpParsing = false; ($enablePhpParsing is a public property).

Writing YAML Files


The dump()11 method dumps any PHP array to its YAML representation:
Listing 137-4

1 2 3 4 5 6 7 8 9

use Symfony\Component\Yaml\Dumper; $array = array('foo' => 'bar', 'bar' => array('foo' => 'bar', 'bar' => 'baz')); $dumper = new Dumper(); $yaml = $dumper->dump($array); file_put_contents('/path/to/file.yml', $yaml);

8. http://api.symfony.com/2.1/Symfony/Component/Yaml/Yaml.html#parse() 9. http://api.symfony.com/2.1/Symfony/Component/Yaml/Yaml.html#parse() 10. http://api.symfony.com/2.1/Symfony/Component/Yaml/Parser.html#parse() 11. http://api.symfony.com/2.1/Symfony/Component/Yaml/Dumper.html#dump()

PDF brought to you by generated on October 26, 2012

Chapter 137: The YAML Component | 672

Of course, the Symfony2 YAML dumper is not able to dump resources. Also, even if the dumper is able to dump PHP objects, it is considered to be a not supported feature.

If an error occurs during the dump, the parser throws a DumpException12 exception. If you only need to dump one array, you can use the dump()13 static method shortcut:
Listing 137-5

1 use Symfony\Component\Yaml\Yaml; 2 3 $yaml = Yaml::dump($array, $inline);

The YAML format supports two kind of representation for arrays, the expanded one, and the inline one. By default, the dumper uses the inline representation:
Listing 137-6

1 { foo: bar, bar: { foo: bar, bar: baz } }

The second argument of the dump()14 method customizes the level at which the output switches from the expanded representation to the inline one:
Listing 137-7

1 echo $dumper->dump($array, 1);

Listing 137-8

1 foo: bar 2 bar: { foo: bar, bar: baz }

Listing 137-9

1 echo $dumper->dump($array, 2);

Listing 137-10 1

foo: bar 2 bar: 3 foo: bar 4 bar: baz

The YAML Format


According to the official YAML15 website, YAML is "a human friendly data serialization standard for all programming languages". Even if the YAML format can describe complex nested data structure, this chapter only describes the minimum set of features needed to use YAML as a configuration file format. YAML is a simple language that describes data. As PHP, it has a syntax for simple types like strings, booleans, floats, or integers. But unlike PHP, it makes a difference between arrays (sequences) and hashes (mappings).

12. http://api.symfony.com/2.1/Symfony/Component/Yaml/Exception/DumpException.html 13. http://api.symfony.com/2.1/Symfony/Component/Yaml/Yaml.html#dump() 14. http://api.symfony.com/2.1/Symfony/Component/Yaml/Dumper.html#dump() 15. http://yaml.org/

PDF brought to you by generated on October 26, 2012

Chapter 137: The YAML Component | 673

Scalars
The syntax for scalars is similar to the PHP syntax.

Strings
Listing 137-11 1

A string in YAML

Listing 137-12 1

'A singled-quoted string in YAML'

In a single quoted string, a single quote ' must be doubled:


Listing 137-13 1

'A single quote '' in a single-quoted string'

Listing 137-14 1

"A double-quoted string in YAML\n"

Quoted styles are useful when a string starts or ends with one or more relevant spaces.
The double-quoted style provides a way to express arbitrary strings, by using \ escape sequences. It is very useful when you need to embed a \n or a unicode character in a string.

When a string contains line breaks, you can use the literal style, indicated by the pipe (|), to indicate that the string will span several lines. In literals, newlines are preserved:
Listing 137-15 1

| \/ /| |\/| | / / | | | |__

2 3

Alternatively, strings can be written with the folded style, denoted by >, where each line break is replaced by a space:
Listing 137-16 1

> This is a very long sentence that spans several lines in the YAML but which will be rendered as a string without carriage returns.

2 3 4 5

Notice the two spaces before each line in the previous examples. They won't appear in the resulting PHP strings.

Numbers
Listing 137-17 1

# an integer 2 12

PDF brought to you by generated on October 26, 2012

Chapter 137: The YAML Component | 674

Listing 137-18 1

# an octal 2 014

Listing 137-19 1

# an hexadecimal 2 0xC

Listing 137-20 1

# a float 2 13.4

Listing 137-21 1

# an exponential number 2 1.2e+34

Listing 137-22 1

# infinity 2 .inf

Nulls
Nulls in YAML can be expressed with null or ~.

Booleans
Booleans in YAML are expressed with true and false.

Dates
YAML uses the ISO-8601 standard to express dates:
Listing 137-23 1

2001-12-14t21:59:43.10-05:00

Listing 137-24 1

# simple date 2 2002-12-14

Collections
A YAML file is rarely used to describe a simple scalar. Most of the time, it describes a collection. A collection can be a sequence or a mapping of elements. Both sequences and mappings are converted to PHP arrays. Sequences use a dash followed by a space:
Listing 137-25 1

- PHP 2 - Perl 3 - Python

The previous YAML file is equivalent to the following PHP code:


Listing 137-26 1

array('PHP', 'Perl', 'Python');

Mappings use a colon followed by a space (: ) to mark each key/value pair:


PDF brought to you by generated on October 26, 2012 Chapter 137: The YAML Component | 675

Listing 137-27 1

PHP: 5.2 2 MySQL: 5.1 3 Apache: 2.2.20

which is equivalent to this PHP code:


Listing 137-28 1

array('PHP' => 5.2, 'MySQL' => 5.1, 'Apache' => '2.2.20');

In a mapping, a key can be any valid scalar.

The number of spaces between the colon and the value does not matter:
Listing 137-29 1

PHP: 5.2 2 MySQL: 5.1 3 Apache: 2.2.20

YAML uses indentation with one or more spaces to describe nested collections:
Listing 137-30 1

"symfony 1.0": 2 PHP: 5.0 3 Propel: 1.2 4 "symfony 1.2": 5 PHP: 5.2 6 Propel: 1.3

The following YAML is equivalent to the following PHP code:


Listing 137-31

1 array( 2 'symfony 1.0' 3 'PHP' => 4 'Propel' => 5 ), 6 'symfony 1.2' 7 'PHP' => 8 'Propel' => 9 ), 10 );

=> array( 5.0, 1.2, => array( 5.2, 1.3,

There is one important thing you need to remember when using indentation in a YAML file: Indentation must be done with one or more spaces, but never with tabulations. You can nest sequences and mappings as you like:
Listing 137-32 1

'Chapter 1': 2 - Introduction 3 - Event Types 4 'Chapter 2': 5 - Introduction 6 - Helpers

YAML can also use flow styles for collections, using explicit indicators rather than indentation to denote scope.
PDF brought to you by generated on October 26, 2012 Chapter 137: The YAML Component | 676

A sequence can be written as a comma separated list within square brackets ([]):
Listing 137-33 1

[PHP, Perl, Python]

A mapping can be written as a comma separated list of key/values within curly braces ({}):
Listing 137-34 1

{ PHP: 5.2, MySQL: 5.1, Apache: 2.2.20 }

You can mix and match styles to achieve a better readability:


Listing 137-35 1

'Chapter 1': [Introduction, Event Types] 2 'Chapter 2': [Introduction, Helpers]

Listing 137-36 1

"symfony 1.0": { PHP: 5.0, Propel: 1.2 } 2 "symfony 1.2": { PHP: 5.2, Propel: 1.3 }

Comments
Comments can be added in YAML by prefixing them with a hash mark (#):
Listing 137-37 1

# Comment on a line 2 "symfony 1.0": { PHP: 5.0, Propel: 1.2 } # Comment at the end of a line 3 "symfony 1.2": { PHP: 5.2, Propel: 1.3 }

Comments are simply ignored by the YAML parser and do not need to be indented according to the current level of nesting in a collection.

PDF brought to you by generated on October 26, 2012

Chapter 137: The YAML Component | 677

Part V

Contributing

Chapter 138

Reporting a Bug
Whenever you find a bug in Symfony2, we kindly ask you to report it. It helps us make a better Symfony2.
If you think you've found a security issue, please use the special procedure instead.

Before submitting a bug: Double-check the official documentation1 to see if you're not misusing the framework; Ask for assistance on the users mailing-list2, the forum3, or on the #symfony IRC channel4 if you're not sure if your issue is really a bug. If your problem definitely looks like a bug, report it using the official bug tracker5 and follow some basic rules: Use the title field to clearly describe the issue; Describe the steps needed to reproduce the bug with short code examples (providing a unit test that illustrates the bug is best); Give as much details as possible about your environment (OS, PHP version, Symfony version, enabled extensions, ...); (optional) Attach a patch.

1. http://symfony.com/doc/2.0/ 2. http://groups.google.com/group/symfony-users 3. http://forum.symfony-project.org/ 4. irc://irc.freenode.net/symfony 5. https://github.com/symfony/symfony/issues

PDF brought to you by generated on October 26, 2012

Chapter 138: Reporting a Bug | 679

Chapter 139

Submitting a Patch
Patches are the best way to provide a bug fix or to propose enhancements to Symfony2.

Step 1: Setup your Environment


Install the Software Stack
Before working on Symfony2, setup a friendly environment with the following software: Git; PHP version 5.3.3 or above; PHPUnit 3.6.4 or above.

Configure Git
Set up your user information with your real name and a working email address:
Listing 139-1

1 $ git config --global user.name "Your Name" 2 $ git config --global user.email you@example.com

If you are new to Git, we highly recommend you to read the excellent and free ProGit1 book.

If your IDE creates configuration files inside project's directory, you can use global .gitignore file (for all projects) or .git/info/exclude file (per project) to ignore them. See Github's documentation2.

1. http://progit.org/ 2. https://help.github.com/articles/ignoring-files

PDF brought to you by generated on October 26, 2012

Chapter 139: Submitting a Patch | 680

Windows users: when installing Git, the installer will ask what to do with line endings and suggests to replace all Lf by CRLF. This is the wrong setting if you wish to contribute to Symfony! Selecting the as-is method is your best choice, as git will convert your line feeds to the ones in the repository. If you have already installed Git, you can check the value of this setting by typing:
Listing 139-2

1 $ git config core.autocrlf

This will return either "false", "input" or "true", "true" and "false" being the wrong values. Set it to another value by typing:
Listing 139-3

1 $ git config --global core.autocrlf input

Replace --global by --local if you want to set it only for the active repository

Get the Symfony Source Code


Get the Symfony2 source code: Create a GitHub3 account and sign in; Fork the Symfony2 repository4 (click on the "Fork" button); After the "hardcore forking action" has completed, clone your fork locally (this will create a symfony directory):
Listing 139-4

1 $ git clone git@github.com:USERNAME/symfony.git

Add the upstream repository as remote:


Listing 139-5

1 $ cd symfony 2 $ git remote add upstream git://github.com/symfony/symfony.git

Check that the current Tests pass


Now that Symfony2 is installed, check that all unit tests pass for your environment as explained in the dedicated document.

Step 2: Work on your Patch


The License
Before you start, you must know that all the patches you are going to submit must be released under the MIT license, unless explicitly specified in your commits.

3. https://github.com/signup/free 4. https://github.com/symfony/symfony

PDF brought to you by generated on October 26, 2012

Chapter 139: Submitting a Patch | 681

Choose the right Branch


Before working on a patch, you must determine on which branch you need to work. The branch should be based on the master branch if you want to add a new feature. But if you want to fix a bug, use the oldest but still maintained version of Symfony where the bug happens (like 2.0).
All bug fixes merged into maintenance branches are also merged into more recent branches on a regular basis. For instance, if you submit a patch for the 2.0 branch, the patch will also be applied by the core team on the master branch.

Create a Topic Branch


Each time you want to work on a patch for a bug or on an enhancement, create a topic branch:
Listing 139-6

1 $ git checkout -b BRANCH_NAME master

Or, if you want to provide a bugfix for the 2.0 branch, first track the remote 2.0 branch locally:
Listing 139-7

1 $ git checkout -t origin/2.0

Then create a new branch off the 2.0 branch to work on the bugfix:
Listing 139-8

1 $ git checkout -b BRANCH_NAME 2.0

Use a descriptive name for your branch (ticket_XXX where XXX is the ticket number is a good convention for bug fixes).

The above checkout commands automatically switch the code to the newly created branch (check the branch you are working on with git branch).

Work on your Patch


Work on the code as much as you want and commit as much as you want; but keep in mind the following: Follow the coding standards (use git diff --check to check for trailing spaces -- also read the tip below); Add unit tests to prove that the bug is fixed or that the new feature actually works; Try hard to not break backward compatibility (if you must do so, try to provide a compatibility layer to support the old way) -- patches that break backward compatibility have less chance to be merged; Do atomic and logically separate commits (use the power of git rebase to have a clean and logical history); Squash irrelevant commits that are just about fixing coding standards or fixing typos in your own code; Never fix coding standards in some existing code as it makes the code review more difficult; Write good commit messages (see the tip below).

PDF brought to you by generated on October 26, 2012

Chapter 139: Submitting a Patch | 682

You can check the coding standards of your patch by running the following script5 (source6):
Listing 139-9

1 $ cd /path/to/symfony/src 2 $ php symfony-cs-fixer.phar fix . Symfony20Finder

A good commit message is composed of a summary (the first line), optionally followed by a blank line and a more detailed description. The summary should start with the Component you are working on in square brackets ([DependencyInjection], [FrameworkBundle], ...). Use a verb (fixed ..., added ..., ...) to start the summary and don't add a period at the end.

Prepare your Patch for Submission


When your patch is not about a bug fix (when you add a new feature or change an existing one for instance), it must also include the following: An explanation of the changes in the relevant CHANGELOG file(s); An explanation on how to upgrade an existing application in the relevant UPGRADE file(s) if the changes break backward compatibility.

Step 3: Submit your Patch


Whenever you feel that your patch is ready for submission, follow the following steps.

Rebase your Patch


Before submitting your patch, update your branch (needed if it takes you a while to finish your changes):
Listing 139-10 1

2 3 4 5

$ $ $ $ $

git git git git git

checkout master fetch upstream merge upstream/master checkout BRANCH_NAME rebase master

Replace master with 2.0 if you are working on a bugfix

When doing the rebase command, you might have to fix merge conflicts. git status will show you the unmerged files. Resolve all the conflicts, then continue the rebase:
Listing 139-11 1

$ git add ... # add resolved files 2 $ git rebase --continue

Check that all tests still pass and push your branch remotely:
Listing 139-12

5. http://cs.sensiolabs.org/get/php-cs-fixer.phar 6. https://github.com/fabpot/PHP-CS-Fixer

PDF brought to you by generated on October 26, 2012

Chapter 139: Submitting a Patch | 683

1 $ git push origin BRANCH_NAME

Make a Pull Request


You can now make a pull request on the symfony/symfony Github repository.
Take care to point your pull request towards symfony:2.0 if you want the core team to pull a bugfix based on the 2.0 branch.

To ease the core team work, always include the modified components in your pull request message, like in:
Listing 139-13 1

[Yaml] fixed something 2 [Form] [Validator] [FrameworkBundle] added something

Please use the title with "[WIP]" if the submission is not yet completed or the tests are incomplete or not yet passing.

The pull request description must include the following check list to ensure that contributions may be reviewed without needless feedback loops and that your contributions can be included into Symfony2 as quickly as possible:
Listing 139-14 1

2 3 4 5 6 7 8

Bug fix: [yes|no] Feature addition: [yes|no] Backwards compatibility break: [yes|no] Symfony2 tests pass: [yes|no] Fixes the following tickets: [comma separated list of tickets fixed by the PR] Todo: [list of todos pending] License of the code: MIT Documentation PR: [The reference to the documentation PR if any]

An example submission could now look as follows:


Listing 139-15 1

2 3 4 5 6 7 8

Bug fix: no Feature addition: yes Backwards compatibility break: no Symfony2 tests pass: yes Fixes the following tickets: #12, #43 Todo: License of the code: MIT Documentation PR: symfony/symfony-docs#123

In the pull request description, give as much details as possible about your changes (don't hesitate to give code examples to illustrate your points). If your pull request is about adding a new feature or modifying an existing one, explain the rationale for the changes. The pull request description helps the code review and it serves as a reference when the code is merged (the pull request description and all its associated comments are part of the merge commit message).

PDF brought to you by generated on October 26, 2012

Chapter 139: Submitting a Patch | 684

In addition to this "code" pull request, you must also send a pull request to the documentation repository7 to update the documentation when appropriate.

Rework your Patch


Based on the feedback on the pull request, you might need to rework your patch. Before re-submitting the patch, rebase with upstream/master or upstream/2.0, don't merge; and force the push to the origin:
Listing 139-16 1

$ git rebase -f upstream/master 2 $ git push -f origin BRANCH_NAME

when doing a push --force, always specify the branch name explicitly to avoid messing other branches in the repo (--force tells git that you really want to mess with things so do it carefully).

Often, moderators will ask you to "squash" your commits. This means you will convert many commits to one commit. To do this, use the rebase command:
Listing 139-17 1

$ git rebase -i HEAD~3 2 $ git push -f origin BRANCH_NAME

The number 3 here must equal the amount of commits in your branch. After you type this command, an editor will popup showing a list of commits:
Listing 139-18 1

pick 1a31be6 first commit 2 pick 7fc64b4 second commit 3 pick 7d33018 third commit

To squash all commits into the first one, remove the word "pick" before the second and the last commits, and replace it by the word "squash" or just "s". When you save, git will start rebasing, and if successful, will ask you to edit the commit message, which by default is a listing of the commit messages of all the commits. When you finish, execute the push command.
To automatically get your feature branch tested, you can add your fork to travis-ci.org8. Just login using your github.com account and then simply flip a single switch to enable automated testing. In your pull request, instead of specifying "Symfony2 tests pass: [yes|no]", you can link to the travisci.org status icon9. For more details, see the travis-ci.org Getting Started Guide10. This could easily be done by clicking on the wrench icon on the build page of Travis. First select your feature branch and then copy the markdown to your PR description.

7. https://github.com/symfony/symfony-docs 8. http://travis-ci.org 9. http://about.travis-ci.org/docs/user/status-images/ 10. http://about.travis-ci.org/docs/user/getting-started/

PDF brought to you by generated on October 26, 2012

Chapter 139: Submitting a Patch | 685

Chapter 140

Reporting a Security Issue


Found a security issue in Symfony2? Don't use the mailing-list or the bug tracker. All security issues must be sent to security [at] symfony-project.com instead. Emails sent to this address are forwarded to the Symfony core-team private mailing-list. For each report, we first try to confirm the vulnerability. When it is confirmed, the core-team works on a solution following these steps: 1. Send an acknowledgement to the reporter; 2. Work on a patch; 3. Write a post describing the vulnerability, the possible exploits, and how to patch/upgrade affected applications; 4. Apply the patch to all maintained versions of Symfony; 5. Publish the post on the official Symfony blog.
While we are working on a patch, please do not reveal the issue publicly.

PDF brought to you by generated on October 26, 2012

Chapter 140: Reporting a Security Issue | 686

Chapter 141

Running Symfony2 Tests


Before submitting a patch for inclusion, you need to run the Symfony2 test suite to check that you have not broken anything.

PHPUnit
To run the Symfony2 test suite, install1 PHPUnit 3.6.4 or later first:
Listing 141-1

1 $ pear config-set auto_discover 1 2 $ pear install pear.phpunit.de/PHPUnit

Dependencies (optional)
To run the entire test suite, including tests that depend on external dependencies, Symfony2 needs to be able to autoload them. By default, they are autoloaded from vendor/ under the main root directory (see autoload.php.dist). The test suite needs the following third-party libraries: Doctrine Swiftmailer Twig Monolog

To install them all, use Composer2: Step 1: Get Composer3


Listing 141-2

1 curl -s http://getcomposer.org/installer | php

1. http://www.phpunit.de/manual/current/en/installation.html 2. http://getcomposer.org/ 3. http://getcomposer.org/

PDF brought to you by generated on October 26, 2012

Chapter 141: Running Symfony2 Tests | 687

Make sure you download composer.phar in the same folder where the composer.json file is located. Step 2: Install vendors
Listing 141-3

1 $ php composer.phar --dev install

Note that the script takes some time to finish.

If you don't have curl installed, you can also just download the installer file manually at http://getcomposer.org/installer4. Place this file into your project and then run:
Listing 141-4

1 $ php installer 2 $ php composer.phar --dev install

After installation, you can update the vendors to their latest version with the follow command:
Listing 141-5

1 $ php composer.phar --dev update

Running
First, update the vendors (see above). Then, run the test suite from the Symfony2 root directory with the following command:
Listing 141-6

1 $ phpunit

The output should display OK. If not, you need to figure out what's going on and if the tests are broken because of your modifications.
If you want to test a single component type its path after the phpunit command, e.g.:
Listing 141-7

1 $ phpunit src/Symfony/Component/Finder/

Run the test suite before applying your modifications to check that they run fine on your configuration.

Code Coverage
If you add a new feature, you also need to check the code coverage by using the coverage-html option:
Listing 141-8

4. http://getcomposer.org/installer

PDF brought to you by generated on October 26, 2012

Chapter 141: Running Symfony2 Tests | 688

1 $ phpunit --coverage-html=cov/

Check the code coverage by opening the generated cov/index.html page in a browser.
The code coverage only works if you have XDebug enabled and all dependencies installed.

PDF brought to you by generated on October 26, 2012

Chapter 141: Running Symfony2 Tests | 689

Chapter 142

Coding Standards
When contributing code to Symfony2, you must follow its coding standards. To make a long story short, here is the golden rule: Imitate the existing Symfony2 code. Most open-source Bundles and libraries used by Symfony2 also follow the same guidelines, and you should too. Remember that the main advantage of standards is that every piece of code looks and feels familiar, it's not about this or that being more readable. Symfony follows the standards defined in the PSR-01, PSR-12 and PSR-23 documents. Since a picture - or some code - is worth a thousand words, here's a short example containing most features described below:
Listing 142-1

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

<?php

/* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */
namespace Acme; class FooBar { const SOME_CONST = 42; private $fooBar;

/** * @param string $dummy Some argument description

1. https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md 2. https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md 3. https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md

PDF brought to you by generated on October 26, 2012

Chapter 142: Coding Standards | 690

22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 }

*/ public function __construct($dummy) { $this->fooBar = $this->transformText($dummy); } /** * @param string $dummy Some argument description * @return string|null Transformed input */ private function transformText($dummy, $options = array()) { $mergedOptions = array_merge($options, array( 'some_default' => 'values', ));
if (true === $dummy) { return; } if ('string' === $dummy) { if ('values' === $mergedOptions['some_default']) { $dummy = substr($dummy, 0, 5); } else { $dummy = ucwords($dummy); } } return $dummy; }

Structure
Add a single space after each comma delimiter; Add a single space around operators (==, &&, ...); Add a blank line before return statements, unless the return is alone inside a statement-group (like an if statement); Use braces to indicate control structure body regardless of the number of statements it contains; Define one class per file - this does not apply to private helper classes that are not intended to be instantiated from the outside and thus are not concerned by the PSR-0 standard; Declare class properties before methods; Declare public methods first, then protected ones and finally private ones.

Naming Conventions
Use camelCase, not underscores, for variable, function and method names, arguments; Use underscores for option names and parameter names; Use namespaces for all classes; Suffix interfaces with Interface; Use alphanumeric characters and underscores for file names; Don't forget to look at the more verbose Conventions document for more subjective naming considerations.

PDF brought to you by generated on October 26, 2012

Chapter 142: Coding Standards | 691

Documentation
Add PHPDoc blocks for all classes, methods, and functions; Omit the @return tag if the method does not return anything; The @package and @subpackage annotations are not used.

License
Symfony is released under the MIT license, and the license block has to be present at the top of every PHP file, before the namespace.

PDF brought to you by generated on October 26, 2012

Chapter 142: Coding Standards | 692

Chapter 143

Conventions
The Coding Standards document describes the coding standards for the Symfony2 projects and the internal and third-party bundles. This document describes coding standards and conventions used in the core framework to make it more consistent and predictable. You are encouraged to follow them in your own code, but you don't need to.

Method Names
When an object has a "main" many relation with related "things" (objects, parameters, ...), the method names are normalized: get() set() has() all() replace() remove() clear() isEmpty() add() register() count() keys()

The usage of these methods are only allowed when it is clear that there is a main relation: a CookieJar has many Cookie objects; a Service Container has many services and many parameters (as services is the main relation, we use the naming convention for this relation); a Console Input has many arguments and many options. There is no "main" relation, and so the naming convention does not apply. For many relations where the convention does not apply, the following methods must be used instead (where XXX is the name of the related thing):
PDF brought to you by generated on October 26, 2012 Chapter 143: Conventions | 693

Main Relation Other Relations get() set() n/a has() all() replace() remove() clear() isEmpty() add() register() count() keys() getXXX() setXXX() replaceXXX() hasXXX() getXXXs() setXXXs() removeXXX() clearXXX() isEmptyXXX() addXXX() registerXXX() countXXX() n/a

While "setXXX" and "replaceXXX" are very similar, there is one notable difference: "setXXX" may replace, or add new elements to the relation. "replaceXXX", on the other hand, cannot add new elements. If an unrecognized key as passed to "replaceXXX" it must throw an exception.

PDF brought to you by generated on October 26, 2012

Chapter 143: Conventions | 694

Chapter 144

Symfony2 License
Symfony2 is released under the MIT license. According to Wikipedia1: "It is a permissive license, meaning that it permits reuse within proprietary software on the condition that the license is distributed with that software. The license is also GPL-compatible, meaning that the GPL permits combination and redistribution with software that uses the MIT License."

The License
Copyright (c) 2004-2012 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

1. http://en.wikipedia.org/wiki/MIT_License

PDF brought to you by generated on October 26, 2012

Chapter 144: Symfony2 License | 695

Chapter 145

Contributing to the Documentation


Documentation is as important as code. It follows the exact same principles: DRY, tests, ease of maintenance, extensibility, optimization, and refactoring just to name a few. And of course, documentation has bugs, typos, hard to read tutorials, and more.

Contributing
Before contributing, you need to become familiar with the markup language used by the documentation. The Symfony2 documentation is hosted on GitHub:
Listing 145-1

1 https://github.com/symfony/symfony-docs

If you want to submit a patch, fork1 the official repository on GitHub and then clone your fork:
Listing 145-2

1 $ git clone git://github.com/YOURUSERNAME/symfony-docs.git

Unless you're documenting a feature that's new to Symfony 2.1, your changes should be based on the 2.0 branch instead of the master branch. To do this checkout the 2.0 branch before the next step:
Listing 145-3

1 $ git checkout 2.0

Next, create a dedicated branch for your changes (for organization):


Listing 145-4

1 $ git checkout -b improving_foo_and_bar

You can now make your changes directly to this branch and commit them. When you're done, push this branch to your GitHub fork and initiate a pull request. The pull request will be between your improving_foo_and_bar branch and the symfony-docs master branch. ../../_images/docs-pull-request.png

1. http://help.github.com/fork-a-repo/

PDF brought to you by generated on October 26, 2012

Chapter 145: Contributing to the Documentation | 696

If you have made your changes based on the 2.0 branch then you need to follow the change commit link and change the base branch to be @2.0: ../../_images/docs-pull-request-change-base.png GitHub covers the topic of pull requests2 in detail.
The Symfony2 documentation is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License.

Your changes appear on the symfony.com website no more than 15 minutes after the documentation team merges your pull request. You can check if your changes have introduced some markup issues by going to the Documentation Build Errors3 page (it is updated each French night at 3AM when the server rebuilds the documentation).

Standards
In order to help the reader as much as possible and to create code examples that look and feel familiar, you should follow these rules: The code follows the Symfony Coding Standards as well as the Twig Coding Standards4; Each line should break approximately after the first word that crosses the 72nd character (so most lines end up being 72-78 lines); When we fold one or more lines of code, we place ... in a comment at the point of the fold. These comments are: // ... (php), # ... (yaml/bash), {# ... #} (twig), <!-- ... --> (xml/html), ; ... (ini), ... (text); When we fold a part of a line, e.g. a variable value, we put ... (without comment) at the place of the fold; Description of the folded code: (optional) If we fold several lines: the description of the fold can be placed after the ... If we fold only part of a line: the description can be placed before the line; If useful, a codeblock should begin with a comment containing the filename of the file in the code block. Don't place a blank line after this comment, unless the next line is also a comment; You should put a $ in front of every bash line; We prefer the :: shorthand over .. code-block:: php to begin a PHP code block. An example:
Listing 145-5

1 2 3 4 5 6 7 8 9 10

// src/Foo/Bar.php // ... class Bar { // ...


public function foo($bar) { // set foo with a value of bar

2. http://help.github.com/pull-requests/ 3. http://symfony.com/doc/build_errors 4. http://twig.sensiolabs.org/doc/coding_standards.html

PDF brought to you by generated on October 26, 2012

Chapter 145: Contributing to the Documentation | 697

11 12 13 14 15 16 17 }

$foo = ...;

// ... check if $bar has the correct value


return $foo->baz($bar, ...); }

In Yaml you should put a space after { and before } (e.g. { _controller: ... }), but this should not be done in Twig (e.g. {'hello' : 'value'}). An array item is a part of a line, not a complete line. So you should not use // ... but ..., (the comma because of the Coding Standards):
Listing 145-6

1 array( 2 'some value', 3 ..., 4 )

Reporting an Issue
The most easy contribution you can make is reporting issues: a typo, a grammar mistake, a bug in code example, a missing explanation, and so on. Steps: Submit a bug in the bug tracker; (optional) Submit a patch.

Translating
Read the dedicated document.

PDF brought to you by generated on October 26, 2012

Chapter 145: Contributing to the Documentation | 698

Chapter 146

Documentation Format
The Symfony2 documentation uses reStructuredText1 as its markup language and Sphinx2 for building the output (HTML, PDF, ...).

reStructuredText
reStructuredText "is an easy-to-read, what-you-see-is-what-you-get plaintext markup syntax and parser system". You can learn more about its syntax by reading existing Symfony2 documents3 or by reading the reStructuredText Primer4 on the Sphinx website. If you are familiar with Markdown, be careful as things are sometimes very similar but different: Lists starts at the beginning of a line (no indentation is allowed); Inline code blocks use double-ticks (``like this``).

Sphinx
Sphinx is a build system that adds some nice tools to create documentation from reStructuredText documents. As such, it adds new directives and interpreted text roles to standard reST markup5.

Syntax Highlighting
All code examples uses PHP as the default highlighted language. You can change it with the code-block directive:
Listing 146-1

1. http://docutils.sf.net/rst.html 2. http://sphinx.pocoo.org/ 3. http://github.com/symfony/symfony-docs 4. http://sphinx.pocoo.org/rest.html 5. http://sphinx.pocoo.org/markup/

PDF brought to you by generated on October 26, 2012

Chapter 146: Documentation Format | 699

1 .. code-block:: yaml 2 3 { foo: bar, bar: { foo: bar, bar: baz } }

If your PHP code begins with <?php, then you need to use html+php as the highlighted pseudo-language:
Listing 146-2

1 .. code-block:: html+php 2 3 <?php echo $this->foobar(); ?>

A list of supported languages is available on the Pygments website6.

Configuration Blocks
Whenever you show a configuration, you must use the configuration-block directive to show the configuration in all supported configuration formats (PHP, YAML, and XML)
Listing 146-3

1 .. configuration-block:: 2 3 .. code-block:: yaml 4 5 # Configuration in YAML 6 7 .. code-block:: xml 8 9 <!-- Configuration in XML //--> 10 11 .. code-block:: php 12 13 // Configuration in PHP

The previous reST snippet renders as follow:


Listing 146-4

1 # Configuration in YAML

The current list of supported formats are the following: Markup format Displayed html xml php yaml jinja html+jinja jinja+html HTML XML PHP YAML Twig Twig Twig

6. http://pygments.org/languages/

PDF brought to you by generated on October 26, 2012

Chapter 146: Documentation Format | 700

Markup format Displayed php+html html+php ini php-annotations PHP PHP INI Annotations

Adding Links
To add links to other pages in the documents use the following syntax:
Listing 146-5

1 :doc:`/path/to/page`

Using the path and filename of the page without the extension, for example:
Listing 146-6

1 :doc:`/book/controller` 2 3 :doc:`/components/event_dispatcher/introduction` 4 5 :doc:`/cookbook/configuration/environments`

The link text will be the main heading of the document linked to. You can also specify alternative text for the link:
Listing 146-7

1 :doc:`Spooling Email</cookbook/email/spool>`

You can also add links to the API documentation:


Listing 146-8

1 :namespace:`Symfony\\Component\\BrowserKit` 2 3 :class:`Symfony\\Component\\Routing\\Matcher\\ApacheUrlMatcher` 4 5 :method:`Symfony\\Component\\HttpKernel\\Bundle\\Bundle::build`

and to the PHP documentation:


Listing 146-9

1 :phpclass:`SimpleXMLElement` 2 3 :phpmethod:`DateTime::createFromFormat` 4 5 :phpfunction:`iterator_to_array`

Testing Documentation
To test documentation before a commit: Install Sphinx7; Run the Sphinx quick setup8; Install the Sphinx extensions (see below); Run make html and view the generated HTML in the build directory.

7. http://sphinx.pocoo.org/ 8. http://sphinx.pocoo.org/tutorial.html#setting-up-the-documentation-sources

PDF brought to you by generated on October 26, 2012

Chapter 146: Documentation Format | 701

Installing the Sphinx extensions


Download the extension from the source9 repository Copy the sensio directory to the _exts folder under your source folder (where conf.py is located) Add the following to the conf.py file:
Listing 146-10

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

# ... sys.path.append(os.path.abspath('_exts')) # adding PhpLexer from sphinx.highlighting import lexers from pygments.lexers.web import PhpLexer # ... # add the extensions to the list of extensions extensions = [..., 'sensio.sphinx.refinclude', 'sensio.sphinx.configurationblock', 'sensio.sphinx.phpcode'] # enable highlighting for PHP code not between ``<?php ... ?>`` by default lexers['php'] = PhpLexer(startinline=True) lexers['php-annotations'] = PhpLexer(startinline=True) # use PHP as the primary domain primary_domain = 'php' # set url for API links api_url = 'http://api.symfony.com/master/%s'

9. https://github.com/fabpot/sphinx-php

PDF brought to you by generated on October 26, 2012

Chapter 146: Documentation Format | 702

Chapter 147

Translations
The Symfony2 documentation is written in English and many people are involved in the translation process.

Contributing
First, become familiar with the markup language used by the documentation. Then, subscribe to the Symfony docs mailing-list1, as collaboration happens there. Finally, find the master repository for the language you want to contribute for. Here is the list of the official master repositories: English: https://github.com/symfony/symfony-docs2 French: https://github.com/symfony-fr/symfony-docs-fr3 Italian: https://github.com/garak/symfony-docs-it4 Japanese: https://github.com/symfony-japan/symfony-docs-ja5 Polish: https://github.com/ampluso/symfony-docs-pl6 Portuguese (Brazilian): https://github.com/andreia/symfony-docs-pt-BR7 Romanian: https://github.com/sebio/symfony-docs-ro8 Russian: https://github.com/avalanche123/symfony-docs-ru9 Spanish: https://github.com/gitnacho/symfony-docs-es10 Turkish: https://github.com/symfony-tr/symfony-docs-tr11

1. http://groups.google.com/group/symfony-docs 2. https://github.com/symfony/symfony-docs 3. https://github.com/symfony-fr/symfony-docs-fr 4. https://github.com/garak/symfony-docs-it 5. https://github.com/symfony-japan/symfony-docs-ja 6. https://github.com/ampluso/symfony-docs-pl 7. https://github.com/andreia/symfony-docs-pt-BR 8. https://github.com/sebio/symfony-docs-ro 9. https://github.com/avalanche123/symfony-docs-ru 10. https://github.com/gitnacho/symfony-docs-es 11. https://github.com/symfony-tr/symfony-docs-tr

PDF brought to you by generated on October 26, 2012

Chapter 147: Translations | 703

If you want to contribute translations for a new language, read the dedicated section.

Joining the Translation Team


If you want to help translating some documents for your language or fix some bugs, consider joining us; it's a very easy process: Introduce yourself on the Symfony docs mailing-list12; (optional) Ask which documents you can work on; Fork the master repository for your language (click the "Fork" button on the GitHub page); Translate some documents; Ask for a pull request (click on the "Pull Request" from your page on GitHub); The team manager accepts your modifications and merges them into the master repository; The documentation website is updated every other night from the master repository.

Adding a new Language


This section gives some guidelines for starting the translation of the Symfony2 documentation for a new language. As starting a translation is a lot of work, talk about your plan on the Symfony docs mailing-list13 and try to find motivated people willing to help. When the team is ready, nominate a team manager; he will be responsible for the master repository. Create the repository and copy the English documents. The team can now start the translation process. When the team is confident that the repository is in a consistent and stable state (everything is translated, or non-translated documents have been removed from the toctrees -- files named index.rst and map.rst.inc), the team manager can ask that the repository is added to the list of official master repositories by sending an email to Fabien (fabien at symfony.com).

Maintenance
Translation does not end when everything is translated. The documentation is a moving target (new documents are added, bugs are fixed, paragraphs are reorganized, ...). The translation team need to closely follow the English repository and apply changes to the translated documents as soon as possible.
Non maintained languages are removed from the official list of repositories as obsolete documentation is dangerous.

12. http://groups.google.com/group/symfony-docs 13. http://groups.google.com/group/symfony-docs

PDF brought to you by generated on October 26, 2012

Chapter 147: Translations | 704

Chapter 148

Symfony2 Documentation License


The Symfony2 documentation is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License1. You are free: to Share to copy, distribute and transmit the work; 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 or similar license to this one. With the understanding that: Waiver Any of the above conditions can be waived if you get permission from the copyright holder; Public Domain Where the work or any of its elements is in the public domain under applicable law, that status is in no way affected by the license; Other Rights In no way are any of the following rights affected by the license: Your fair dealing or fair use rights, or other applicable copyright exceptions and limitations; The author's moral rights; Rights other persons may have either in the work itself or in how the work is used, such as publicity or privacy rights. Notice For any reuse or distribution, you must make clear to others the license terms of this work. The best way to do this is with a link to this web page. This is a human-readable summary of the Legal Code (the full license)2.
1. http://creativecommons.org/licenses/by-sa/3.0/ 2. http://creativecommons.org/licenses/by-sa/3.0/legalcode

PDF brought to you by generated on October 26, 2012

Chapter 148: Symfony2 Documentation License | 705

Chapter 149

The Release Process


This document explains the Symfony release process (Symfony being the code hosted on the main symfony/symfony Git repository1). Symfony manages its releases through a time-based model; a new Symfony release comes out every six months: one in May and one in November.
This release process has been adopted as of Symfony 2.2, and all the "rules" explained in this document must be strictly followed as of Symfony 2.4.

Development
The six-months period is divided into two phases: Development: Four months to add new features and to enhance existing ones; Stabilisation: Two months to fix bugs, prepare the release, and wait for the whole Symfony ecosystem (third-party libraries, bundles, and projects using Symfony) to catch up. During the development phase, any new feature can be reverted if it won't be finished in time or if it won't be stable enough to be included in the current final release.

Maintenance
Each Symfony version is maintained for a fixed period of time, depending on the type of the release.

Standard Releases
A standard release is maintained for an eight month period.

1. https://github.com/symfony/symfony

PDF brought to you by generated on October 26, 2012

Chapter 149: The Release Process | 706

Long Term Support Releases


Every two years, a new Long Term Support Release (aka LTS release) is published. Each LTS release is supported for a three year period.
Paid support after the three year support provided by the community can also be bought from SensioLabs2.

Schedule
Below is the schedule for the first few versions that use this release model: ../../_images/release-process.jpg Yellow represents the Development phase Blue represents the Stabilisation phase Green represents the Maintenance period This results in very predictable dates and maintenance periods. (special) Symfony 2.2 will be released at the end of February 2013; (special) Symfony 2.3 (the first LTS) will be released at the end of May 2013; Symfony 2.4 will be released at the end of November 2013; Symfony 2.5 will be released at the end of May 2014; ...

Backward Compatibility
After the release of Symfony 2.3, backward compatibility will be kept at all cost. If it is not possible, the feature, the enhancement, or the bug fix will be scheduled for the next major version: Symfony 3.0.
The work on Symfony 3.0 will start whenever enough major features breaking backward compatibility are waiting on the todo-list.

Rationale
This release process was adopted to give more predictability and transparency. It was discussed based on the following goals: Shorten the release cycle (allow developers to benefit from the new features faster); Give more visibility to the developers using the framework and Open-Source projects using Symfony;

2. http://sensiolabs.com/

PDF brought to you by generated on October 26, 2012

Chapter 149: The Release Process | 707

Improve the experience of Symfony core contributors: everyone knows when a feature might be available in Symfony; Coordinate the Symfony timeline with popular PHP projects that work well with Symfony and with projects using Symfony; Give time to the Symfony ecosystem to catch up with the new versions (bundle authors, documentation writers, translators, ...). The six month period was chosen as two releases fit in a year. It also allows for plenty of time to work on new features and it allows for non-ready features to be postponed to the next version without having to wait too long for the next cycle. The dual maintenance mode was adopted to make every Symfony user happy. Fast movers, who want to work with the latest and the greatest, use the standard releases: a new version is published every six months, and there is a two months period to upgrade. Companies wanting more stability use the LTS releases: a new version is published every two years and there is a year to upgrade.

PDF brought to you by generated on October 26, 2012

Chapter 149: The Release Process | 708

Chapter 150

IRC Meetings
The purpose of this meeting is to discuss topics in real time with many of the Symfony2 devs. Anyone may propose topics on the symfony-dev1 mailing-list until 24 hours before the meeting, ideally including well prepared relevant information via some URL. 24 hours before the meeting a link to a doodle2 will be posted including a list of all proposed topics. Anyone can vote on the topics until the beginning of the meeting to define the order in the agenda. Each topic will be timeboxed to 15mins and the meeting lasts one hour, leaving enough time for at least 4 topics.
Note that its not the expected goal of them meeting to find final solutions, but more to ensure that there is a common understanding of the issue at hand and move the discussion forward in ways which are hard to achieve with less real time communication tools.

Meetings will happen each Thursday at 17:00 CET (+01:00) on the #symfony-dev channel on the Freenode IRC server. The IRC logs3 will later be published on the trac wiki, which will include a short summary for each of the topics. Tickets will be created for any tasks or issues identified during the meeting and referenced in the summary. Some simple guidelines and pointers for participation: It's possible to change votes until the beginning of the meeting by clicking on "Edit an entry"; The doodle will be closed for voting at the beginning of the meeting; Agenda is defined by which topics got the most votes in the doodle, or whichever was proposed first in case of a tie; At the beginning of the meeting one person will identify him/herself as the moderator; The moderator is essentially responsible for ensuring the 15min timebox and ensuring that tasks are clearly identified; Usually the moderator will also handle writing the summary and creating trac tickets unless someone else steps up; Anyone can join and is explicitly invited to participate; Ideally one should familiarize oneself with the proposed topic before the meeting;
1. http://groups.google.com/group/symfony-devs 2. http://doodle.com 3. http://trac.symfony-project.org/wiki/Symfony2IRCMeetingLogs

PDF brought to you by generated on October 26, 2012

Chapter 150: IRC Meetings | 709

When starting on a new topic the proposer is invited to start things off with a few words; Anyone can then comment as they see fit; Depending on how many people participate one should potentially retrain oneself from pushing a specific argument too hard; Remember the IRC logs4 will be published later on, so people have the chance to review comments later on once more; People are encouraged to raise their hand to take on tasks defined during the meeting. Here is an example5 doodle.

4. http://trac.symfony-project.org/wiki/Symfony2IRCMeetingLogs 5. http://doodle.com/4cnzme7xys3ay53w

PDF brought to you by generated on October 26, 2012

Chapter 150: IRC Meetings | 710

Chapter 151

Other Resources
In order to follow what is happening in the community you might find helpful these additional resources: List of open pull requests1 List of recent commits2 List of open bugs and enhancements3 List of open source bundles4

1. https://github.com/symfony/symfony/pulls 2. https://github.com/symfony/symfony/commits/master 3. https://github.com/symfony/symfony/issues 4. http://knpbundles.com/

PDF brought to you by generated on October 26, 2012

Chapter 151: Other Resources | 711

Part VI

Reference Documents

Chapter 152

FrameworkBundle Configuration ("framework")


This reference document is a work in progress. It should be accurate, but all options are not yet fully covered. The FrameworkBundle contains most of the "base" framework functionality and can be configured under the framework key in your application configuration. This includes settings related to sessions, translation, forms, validation, routing and more.

Configuration
secret ide test trust_proxy_headers form enabled csrf_protection enabled field_name session lifetime templating assets_base_urls assets_version assets_version_format
PDF brought to you by generated on October 26, 2012 Chapter 152: FrameworkBundle Configuration ("framework") | 713

secret
type: string required This is a string that should be unique to your application. In practice, it's used for generating the CSRF tokens, but it could be used in any other context where having a unique string is useful. It becomes the service container parameter named kernel.secret.

ide
type: string default: null If you're using an IDE like TextMate or Mac Vim, then Symfony can turn all of the file paths in an exception message into a link, which will open that file in your IDE. If you use TextMate or Mac Vim, you can simply use one of the following built-in values: textmate macvim You can also specify a custom file link string. If you do this, all percentage signs (%) must be doubled to escape that character. For example, the full TextMate string would look like this:
Listing 152-1

1 framework: 2 ide: "txmt://open?url=file://%%f&line=%%l"

Of course, since every developer uses a different IDE, it's better to set this on a system level. This can be done by setting the xdebug.file_link_format PHP.ini value to the file link string. If this configuration value is set, then the ide option does not need to be specified.

test
type: Boolean If this configuration parameter is present (and not false), then the services related to testing your application (e.g. test.client) are loaded. This setting should be present in your test environment (usually via app/config/config_test.yml). For more information, see Testing.

trust_proxy_headers
type: Boolean Configures if HTTP headers (like HTTP_X_FORWARDED_FOR, X_FORWARDED_PROTO, and X_FORWARDED_HOST) are trusted as indication for an SSL connection. By default, it is set to false and only SSL_HTTPS connections are indicated as secure. You should enable this setting if your application is behind a reverse proxy.

form csrf_protection session


lifetime
type: integer default: 0

PDF brought to you by generated on October 26, 2012

Chapter 152: FrameworkBundle Configuration ("framework") | 714

This determines the lifetime of the session - in seconds. By default it will use 0, which means the cookie is valid for the length of the browser session.

templating
assets_base_urls
default: { http: [], ssl: [] } This option allows you to define base URL's to be used for assets referenced from http and ssl (https) pages. A string value may be provided in lieu of a single-element array. If multiple base URL's are provided, Symfony2 will select one from the collection each time it generates an asset's path. For your convenience, assets_base_urls can be set directly with a string or array of strings, which will be automatically organized into collections of base URL's for http and https requests. If a URL starts with https:// or is protocol-relative1 (i.e. starts with //) it will be added to both collections. URL's starting with http:// will only be added to the http collection.
New in version 2.1: Unlike most configuration blocks, successive values for assets_base_urls will overwrite each other instead of being merged. This behavior was chosen because developers will typically define base URL's for each environment. Given that most projects tend to inherit configurations (e.g. config_test.yml imports config_dev.yml) and/or share a common base configuration (i.e. config.yml), merging could yield a set of base URL's for multiple environments.

assets_version
type: string This option is used to bust the cache on assets by globally adding a query parameter to all rendered asset paths (e.g. /images/logo.png?v2). This applies only to assets rendered via the Twig asset function (or PHP equivalent) as well as assets rendered with Assetic. For example, suppose you have the following:
Listing 152-2

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

By default, this will render a path to your image such as /images/logo.png. Now, activate the assets_version option:
Listing 152-3

1 # app/config/config.yml 2 framework: 3 # ... 4 templating: { engines: ['twig'], assets_version: v2 }

Now, the same asset will be rendered as /images/logo.png?v2 If you use this feature, you must manually increment the assets_version value before each deployment so that the query parameters change. You can also control how the query string works via the assets_version_format option.

assets_version_format
type: string default: %%s?%%s

1. http://tools.ietf.org/html/rfc3986#section-4.2

PDF brought to you by generated on October 26, 2012

Chapter 152: FrameworkBundle Configuration ("framework") | 715

This specifies a sprintf()2 pattern that will be used with the assets_version option to construct an asset's path. By default, the pattern adds the asset's version as a query string. For example, if assets_version_format is set to %%s?version=%%s and assets_version is set to 5, the asset's path would be /images/logo.png?version=5.
All percentage signs (%) in the format string must be doubled to escape the character. Without escaping, values might inadvertently be interpretted as Service Parameters.

Some CDN's do not support cache-busting via query strings, so injecting the version into the actual file path is necessary. Thankfully, assets_version_format is not limited to producing versioned query strings. The pattern receives the asset's original path and version as its first and second parameters, respectively. Since the asset's path is one parameter, we cannot modify it in-place (e.g. /images/ logo-v5.png); however, we can prefix the asset's path using a pattern of version-%%2$s/%%1$s, which would result in the path version-5/images/logo.png. URL rewrite rules could then be used to disregard the version prefix before serving the asset. Alternatively, you could copy assets to the appropriate version path as part of your deployment process and forgo any URL rewriting. The latter option is useful if you would like older asset versions to remain accessible at their original URL.

Full Default Configuration


framework:
Listing 152-4

# general configuration trust_proxy_headers: false secret: ~ # Required ide: ~ test: ~ default_locale: en # form configuration form: enabled: csrf_protection: enabled: field_name: # esi configuration esi: enabled:

true true _token

true

# profiler configuration profiler: only_exceptions: false only_master_requests: false dsn: file:%kernel.cache_dir%/profiler username: password: lifetime: 86400 matcher:
2. http://php.net/manual/en/function.sprintf.php

PDF brought to you by generated on October 26, 2012

Chapter 152: FrameworkBundle Configuration ("framework") | 716

ip:

# use the urldecoded format path: ~ # Example: ^/path to resource/ service: ~ # router configuration router: resource: ~ # Required type: ~ http_port: 80 https_port: 443 # if false, an empty URL will be generated if a route is missing required parameters strict_requirements: %kernel.debug% # session configuration session: auto_start: storage_id: handler_id: name: cookie_lifetime: cookie_path: cookie_domain: cookie_secure: cookie_httponly: gc_divisor: gc_probability: gc_maxlifetime: save_path:

false session.storage.native session.handler.native_file ~ ~ ~ ~ ~ ~ ~ ~ ~ %kernel.cache_dir%/sessions

# DEPRECATED! Please use: cookie_lifetime lifetime: ~ # DEPRECATED! Please use: cookie_path path: ~ # DEPRECATED! Please use: cookie_domain domain: ~ # DEPRECATED! Please use: cookie_secure secure: ~ # DEPRECATED! Please use: cookie_httponly httponly: ~ # templating configuration templating: assets_version: ~ assets_version_format: %%s?%%s hinclude_default_template: ~ form: resources: # Default: - FrameworkBundle:Form assets_base_urls: http: [] ssl: [] cache: ~ engines: # Required

PDF brought to you by generated on October 26, 2012

Chapter 152: FrameworkBundle Configuration ("framework") | 717

# Example: - twig loaders: packages:

[]

# A collection of named packages some_package_name: version: ~ version_format: %%s?%%s base_urls: http: [] ssl: [] # translator configuration translator: enabled: true fallback: en # validation configuration validation: enabled: true cache: ~ enable_annotations: false # annotation configuration annotations: cache: file file_cache_dir: "%kernel.cache_dir%/annotations" debug: true

PDF brought to you by generated on October 26, 2012

Chapter 152: FrameworkBundle Configuration ("framework") | 718

Chapter 153

AsseticBundle Configuration Reference

Full Default Configuration


Listing 153-1

1 assetic: 2 debug: "%kernel.debug%" 3 use_controller: 4 enabled: "%kernel.debug%" 5 profiler: false 6 read_from: "%kernel.root_dir%/../web" 7 write_to: "%assetic.read_from%" 8 java: /usr/bin/java 9 node: /usr/bin/node 10 ruby: /usr/bin/ruby 11 sass: /usr/bin/sass 12 # An key-value pair of any number of named elements 13 variables: 14 some_name: [] 15 bundles: 16 17 # Defaults (all currently registered bundles): 18 - FrameworkBundle 19 - SecurityBundle 20 - TwigBundle 21 - MonologBundle 22 - SwiftmailerBundle 23 - DoctrineBundle 24 - AsseticBundle 25 - ... 26 assets: 27 # An array of named assets (e.g. some_asset, some_other_asset) 28 some_asset: 29 inputs: [] 30 filters: [] 31 options:

PDF brought to you by generated on October 26, 2012

Chapter 153: AsseticBundle Configuration Reference | 719

32 33 34 35 36 37 38 39 40 41

# A key-value array of options and values some_option_name: []


filters:

# An array of named filters (e.g. some_filter, some_other_filter) some_filter: [] twig: functions: # An array of named functions (e.g. some_function, some_other_function) some_function: []

PDF brought to you by generated on October 26, 2012

Chapter 153: AsseticBundle Configuration Reference | 720

Chapter 154

Configuration Reference
doctrine: dbal: default_connection: default types: # A collection of custom types # Example some_custom_type: class: Acme\HelloBundle\MyCustomType commented: true connections: default: dbname:

Listing 154-1

database

# A collection of different named connections (e.g. default, conn2, etc) default: dbname: ~ host: localhost port: ~ user: root password: ~ charset: ~ path: ~ memory: ~ # The unix socket to use for MySQL unix_socket: ~ # True to use as persistent connection for the ibm_db2 driver persistent: ~ # The protocol to use for the ibm_db2 driver (default to TCPIP if ommited) protocol: ~ # True to use dbname as service name instead of SID for Oracle service: ~

PDF brought to you by generated on October 26, 2012

Chapter 154: Configuration Reference | 721

# The session mode to use for the oci8 driver sessionMode: ~ # True to use a pooled server with the oci8 driver pooled: ~ # Configuring MultipleActiveResultSets for the pdo_sqlsrv driver MultipleActiveResultSets: ~ driver: pdo_mysql platform_service: ~ logging: %kernel.debug% profiling: %kernel.debug% driver_class: ~ wrapper_class: ~ options: # an array of options key: [] mapping_types: # an array of mapping types name: [] slaves: # a collection of named slave connections (e.g. slave1, slave2) slave1: dbname: ~ host: localhost port: ~ user: root password: ~ charset: ~ path: ~ memory: ~ # The unix socket to use for MySQL unix_socket: ~ # True to use as persistent connection for the ibm_db2 driver persistent: ~ # The protocol to use for the ibm_db2 driver (default to TCPIP if ommited) protocol: ~ # True to use dbname as service name instead of SID for Oracle service: ~ # The session mode to use for the oci8 driver sessionMode: ~ # True to use a pooled server with the oci8 driver pooled: ~ # Configuring MultipleActiveResultSets for the pdo_sqlsrv driver MultipleActiveResultSets: ~ orm: default_entity_manager: ~ auto_generate_proxy_classes: false proxy_dir: %kernel.cache_dir%/doctrine/orm/Proxies proxy_namespace: Proxies # search for the "ResolveTargetEntityListener" class for a cookbook about this

PDF brought to you by generated on October 26, 2012

Chapter 154: Configuration Reference | 722

resolve_target_entities: [] entity_managers: # A collection of different named entity managers (e.g. some_em, another_em) some_em: query_cache_driver: type: array # Required host: ~ port: ~ instance_class: ~ class: ~ metadata_cache_driver: type: array # Required host: ~ port: ~ instance_class: ~ class: ~ result_cache_driver: type: array # Required host: ~ port: ~ instance_class: ~ class: ~ connection: ~ class_metadata_factory_name: Doctrine\ORM\Mapping\ClassMetadataFactory default_repository_class: Doctrine\ORM\EntityRepository auto_mapping: false hydrators: # An array of hydrator names hydrator_name: [] mappings: # An array of mappings, which may be a bundle name or something else mapping_name: mapping: true type: ~ dir: ~ alias: ~ prefix: ~ is_bundle: ~ dql: # a collection of string functions string_functions: # example # test_string: Acme\HelloBundle\DQL\StringFunction # a collection of numeric functions numeric_functions: # example # test_numeric: Acme\HelloBundle\DQL\NumericFunction # a collection of datetime functions datetime_functions: # example # test_datetime: Acme\HelloBundle\DQL\DatetimeFunction # Register SQL Filters in the entity manager filters: # An array of filters some_filter: class: ~ # Required enabled: false

PDF brought to you by generated on October 26, 2012

Chapter 154: Configuration Reference | 723

Configuration Overview
This following configuration example shows all the configuration defaults that the ORM resolves to:
Listing 154-2

1 doctrine: 2 orm: 3 auto_mapping: true 4 # the standard distribution overrides this to be true in debug, false otherwise 5 auto_generate_proxy_classes: false 6 proxy_namespace: Proxies 7 proxy_dir: "%kernel.cache_dir%/doctrine/orm/Proxies" 8 default_entity_manager: default 9 metadata_cache_driver: array 10 query_cache_driver: array 11 result_cache_driver: array

There are lots of other configuration options that you can use to overwrite certain classes, but those are for very advanced use-cases only.

Caching Drivers
For the caching drivers you can specify the values "array", "apc", "memcache", "memcached", "xcache" or "service". The following example shows an overview of the caching configurations:
Listing 154-3

1 doctrine: 2 orm: 3 auto_mapping: true 4 metadata_cache_driver: apc 5 query_cache_driver: 6 type: service 7 id: my_doctrine_common_cache_service 8 result_cache_driver: 9 type: memcache 10 host: localhost 11 port: 11211 12 instance_class: Memcache

Mapping Configuration
Explicit definition of all the mapped entities is the only necessary configuration for the ORM and there are several configuration options that you can control. The following configuration options exist for a mapping: type One of annotation, xml, yml, php or staticphp. This specifies which type of metadata type your mapping uses. dir Path to the mapping or entity files (depending on the driver). If this path is relative it is assumed to be relative to the bundle root. This only works if the name of your mapping is a bundle name. If you want to use this option to specify absolute paths you should prefix the path with the kernel parameters that exist in the DIC (for example %kernel.root_dir%). prefix A common namespace prefix that all entities of this mapping share. This prefix should never conflict with prefixes of other defined mappings otherwise some of your entities cannot be found by Doctrine. This option defaults to the bundle namespace + Entity, for example for an application bundle called AcmeHelloBundle prefix would be Acme\HelloBundle\Entity.

PDF brought to you by generated on October 26, 2012

Chapter 154: Configuration Reference | 724

alias Doctrine offers a way to alias entity namespaces to simpler, shorter names to be used in DQL queries or for Repository access. When using a bundle the alias defaults to the bundle name. is_bundle This option is a derived value from dir and by default is set to true if dir is relative proved by a file_exists() check that returns false. It is false if the existence check returns true. In this case an absolute path was specified and the metadata files are most likely in a directory outside of a bundle.

Doctrine DBAL Configuration


DoctrineBundle supports all parameters that default Doctrine drivers accept, converted to the XML or YAML naming standards that Symfony enforces. See the Doctrine DBAL documentation1 for more information.

Besides default Doctrine options, there are some Symfony-related ones that you can configure. The following block shows all possible configuration keys:
Listing 154-4

1 doctrine: 2 dbal: 3 dbname: database 4 host: localhost 5 port: 1234 6 user: user 7 password: secret 8 driver: pdo_mysql 9 driver_class: MyNamespace\MyDriverImpl 10 options: 11 foo: bar 12 path: "%kernel.data_dir%/data.sqlite" 13 memory: true 14 unix_socket: /tmp/mysql.sock 15 wrapper_class: MyDoctrineDbalConnectionWrapper 16 charset: UTF8 17 logging: "%kernel.debug%" 18 platform_service: MyOwnDatabasePlatformService 19 mapping_types: 20 enum: string 21 types: 22 custom: Acme\HelloBundle\MyCustomType

If you want to configure multiple connections in YAML, put them under the connections key and give them a unique name:
Listing 154-5

1 doctrine: 2 dbal: 3 default_connection: 4 connections: 5 default: 6 dbname: 7 user: 8 password: 9 host:

default Symfony2 root null localhost

1. http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/index.html

PDF brought to you by generated on October 26, 2012

Chapter 154: Configuration Reference | 725

10 11 12 13 14

customer: dbname: user: password: host:

customer root null localhost

The database_connection service always refers to the default connection, which is the first one defined or the one configured via the default_connection parameter. Each connection is also accessible via the doctrine.dbal.[name]_connection service where [name] if the name of the connection.

PDF brought to you by generated on October 26, 2012

Chapter 154: Configuration Reference | 726

Chapter 155

Security Configuration Reference


The security system is one of the most powerful parts of Symfony2, and can largely be controlled via its configuration.

Full Default Configuration


The following is the full default configuration for the security system. Each part will be explained in the next section.
Listing 155-1

1 # app/config/security.yml 2 security: 3 access_denied_url: ~ # Example: /foo/error403 4 5 # strategy can be: none, migrate, invalidate 6 session_fixation_strategy: migrate 7 hide_user_not_found: true 8 always_authenticate_before_granting: false 9 erase_credentials: true 10 access_decision_manager: 11 strategy: affirmative 12 allow_if_all_abstain: false 13 allow_if_equal_granted_denied: true 14 acl: 15 16 # any name configured in doctrine.dbal section 17 connection: ~ 18 cache: 19 id: ~ 20 prefix: sf2_acl_ 21 provider: ~ 22 tables: 23 class: acl_classes 24 entry: acl_entries 25 object_identity: acl_object_identities 26 object_identity_ancestors: acl_object_identity_ancestors

PDF brought to you by generated on October 26, 2012

Chapter 155: Security Configuration Reference | 727

27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85

security_identity: acl_security_identities voter: allow_if_object_identity_unavailable: true encoders: # Examples: Acme\DemoBundle\Entity\User1: sha512 Acme\DemoBundle\Entity\User2: algorithm: sha512 encode_as_base64: true iterations: 5000

# Example options/values Acme\Your\Class\Name: algorithm: ignore_case: encode_as_base64: iterations: id:


providers: # Examples: memory: name: users: foo: bar: password: roles: entity: entity: class: property:

for what a custom encoder might look like


~ false true 5000 ~

# Required
memory password: roles: foo ROLE_USER bar [ROLE_USER, ROLE_ADMIN] SecurityBundle:User username

# Example custom provider some_custom_provider: id: ~ chain: providers:

[]

firewalls: # Required # Examples: somename: pattern: .* request_matcher: some.service.id access_denied_url: /foo/error403 access_denied_handler: some.service.id entry_point: some.service.id provider: some_key_from_above context: name stateless: false x509: provider: some_key_from_above http_basic: provider: some_key_from_above http_digest: provider: some_key_from_above

PDF brought to you by generated on October 26, 2012

Chapter 155: Security Configuration Reference | 728

86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144

form_login: check_path: /login_check login_path: /login use_forward: false always_use_default_target_path: false default_target_path: / target_path_parameter: _target_path use_referer: false failure_path: /foo failure_forward: false failure_handler: some.service.id success_handler: some.service.id username_parameter: _username password_parameter: _password csrf_parameter: _csrf_token intention: authenticate csrf_provider: my.csrf_provider.id post_only: true remember_me: false remember_me: token_provider: name key: someS3cretKey name: NameOfTheCookie lifetime: 3600 # in seconds path: /foo domain: somedomain.foo secure: false httponly: true always_remember_me: false remember_me_parameter: _remember_me logout: path: /logout target: / invalidate_session: false delete_cookies: a: { path: null, domain: null } b: { path: null, domain: null } handlers: [some.service.id, another.service.id] success_handler: some.service.id anonymous: ~

# Default values and options for any firewall some_firewall_listener: pattern: ~ security: true request_matcher: ~ access_denied_url: ~ access_denied_handler: ~ entry_point: ~ provider: ~ stateless: false context: ~ logout: csrf_parameter: _csrf_token csrf_provider: ~ intention: logout path: /logout target: / success_handler: ~

PDF brought to you by generated on October 26, 2012

Chapter 155: Security Configuration Reference | 729

145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171

invalidate_session: delete_cookies:

true

# Prototype name: path: domain: handlers: anonymous: key: switch_user: provider: parameter: role:
access_control: requires_channel: ~

~ ~ [] 4f954a0667e01 ~ _switch_user ROLE_ALLOWED_TO_SWITCH

# use the urldecoded format path: ~ # Example: ^/path to resource/ host: ~ ip: ~ methods: [] roles: [] role_hierarchy: ROLE_ADMIN: [ROLE_ORGANIZER, ROLE_USER] ROLE_SUPERADMIN: [ROLE_ADMIN]

Form Login Configuration


When using the form_login authentication listener beneath a firewall, there are several common options for configuring the "form login" experience:

The Login Form and Process


login_path (type: string, default: /login) This is the URL that the user will be redirected to (unless use_forward is set to true) when he/she tries to access a protected resource but isn't fully authenticated. This URL must be accessible by a normal, un-authenticated user, else you may create a redirect loop. For details, see "Avoid Common Pitfalls". check_path (type: string, default: /login_check) This is the URL that your login form must submit to. The firewall will intercept any requests (POST requests only, by default) to this URL and process the submitted login credentials. Be sure that this URL is covered by your main firewall (i.e. don't create a separate firewall just for check_path URL). use_forward (type: Boolean, default: false) If you'd like the user to be forwarded to the login form instead of being redirected, set this option to true. username_parameter (type: string, default: _username) This is the field name that you should give to the username field of your login form. When you submit the form to check_path, the security system will look for a POST parameter with this name.

PDF brought to you by generated on October 26, 2012

Chapter 155: Security Configuration Reference | 730

password_parameter (type: string, default: _password) This is the field name that you should give to the password field of your login form. When you submit the form to check_path, the security system will look for a POST parameter with this name. post_only (type: Boolean, default: true) By default, you must submit your login form to the check_path URL as a POST request. By setting this option to false, you can send a GET request to the check_path URL.

Redirecting after Login


always_use_default_target_path (type: Boolean, default: false) default_target_path (type: string, default: /) target_path_parameter (type: string, default: _target_path) use_referer (type: Boolean, default: false)

PDF brought to you by generated on October 26, 2012

Chapter 155: Security Configuration Reference | 731

Chapter 156

SwiftmailerBundle Configuration ("swiftmailer")


This reference document is a work in progress. It should be accurate, but all options are not yet fully covered. For a full list of the default configuration options, see Full Default Configuration The swiftmailer key configures Symfony's integration with Swiftmailer, which is responsible for creating and delivering email messages.

Configuration
transport username password host port encryption auth_mode spool type path sender_address antiflood threshold sleep delivery_address disable_delivery

PDF brought to you by generated on October 26, 2012

Chapter 156: SwiftmailerBundle Configuration ("swiftmailer") | 732

logging

transport
type: string default: smtp The exact transport method to use to deliver emails. Valid values are: smtp gmail (see How to use Gmail to send Emails) mail sendmail null (same as setting disable_delivery to true)

username
type: string The username when using smtp as the transport.

password
type: string The password when using smtp as the transport.

host
type: string default: localhost The host to connect to when using smtp as the transport.

port
type: string default: 25 or 465 (depending on encryption) The port when using smtp as the transport. This defaults to 465 if encryption is ssl and 25 otherwise.

encryption
type: string The encryption mode to use when using smtp as the transport. Valid values are tls, ssl, or null (indicating no encryption).

auth_mode
type: string The authentication mode to use when using smtp as the transport. Valid values are plain, login, crammd5, or null.

spool
For details on email spooling, see How to Spool Email.

type
type: string default: file
PDF brought to you by generated on October 26, 2012 Chapter 156: SwiftmailerBundle Configuration ("swiftmailer") | 733

The method used to store spooled messages. Currently only file is supported. However, a custom spool should be possible by creating a service called swiftmailer.spool.myspool and setting this value to myspool.

path
type: string default: %kernel.cache_dir%/swiftmailer/spool When using the file spool, this is the path where the spooled messages will be stored.

sender_address
type: string If set, all messages will be delivered with this address as the "return path" address, which is where bounced messages should go. This is handled internally by Swiftmailer's Swift_Plugins_ImpersonatePlugin class.

antiflood
threshold
type: string default: 99 Used with Swift_Plugins_AntiFloodPlugin. This is the number of emails to send before restarting the transport.

sleep
type: string default: 0 Used with Swift_Plugins_AntiFloodPlugin. This is the number of seconds to sleep for during a transport restart.

delivery_address
type: string If set, all email messages will be sent to this address instead of being sent to their actual recipients. This is often useful when developing. For example, by setting this in the config_dev.yml file, you can guarantee that all emails sent during development go to a single account. This uses Swift_Plugins_RedirectingPlugin. Original recipients are available on the X-Swift-To, XSwift-Cc and X-Swift-Bcc headers.

disable_delivery
type: Boolean default: false If true, the transport will automatically be set to null, and no emails will actually be delivered.

logging
type: Boolean default: %kernel.debug% If true, Symfony's data collector will be activated for Swiftmailer and the information will be available in the profiler.

PDF brought to you by generated on October 26, 2012

Chapter 156: SwiftmailerBundle Configuration ("swiftmailer") | 734

Full Default Configuration


Listing 156-1

1 swiftmailer: 2 transport: 3 username: 4 password: 5 host: 6 port: 7 encryption: 8 auth_mode: 9 spool: 10 type: 11 path: 12 sender_address: 13 antiflood: 14 threshold: 15 sleep: 16 delivery_address: 17 disable_delivery: 18 logging:

smtp ~ ~ localhost false ~ ~ file "%kernel.cache_dir%/swiftmailer/spool" ~ 99 0 ~ ~ "%kernel.debug%"

PDF brought to you by generated on October 26, 2012

Chapter 156: SwiftmailerBundle Configuration ("swiftmailer") | 735

Chapter 157

TwigBundle Configuration Reference


1 twig: 2 exception_controller: 3 Symfony\Bundle\TwigBundle\Controller\ExceptionController::showAction 4 form: 5 resources: 6 7 # Default: 8 - form_div_layout.html.twig 9 10 # Example: 11 - MyBundle::form.html.twig 12 globals: 13 14 # Examples: 15 foo: "@bar" 16 pi: 3.14 17 18 # Example options, but the easiest use is as seen above 19 some_variable_name: 20 # a service id that should be the value 21 id: ~ 22 # set to service or leave blank 23 type: ~ 24 value: ~ 25 autoescape: ~ 26 base_template_class: ~ # Example: Twig_Template 27 cache: "%kernel.cache_dir%/twig" 28 charset: "%kernel.charset%" 29 debug: "%kernel.debug%" 30 strict_variables: ~ 31 auto_reload: ~ optimizations: ~

Listing 157-1

PDF brought to you by generated on October 26, 2012

Chapter 157: TwigBundle Configuration Reference | 736

Configuration
exception_controller
type: string Symfony\\Bundle\\TwigBundle\\Controller\\ExceptionController::showAction default:

This is the controller that is activated after an exception is thrown anywhere in your application. The default controller (ExceptionController1) is what's responsible for rendering specific templates under different error conditions (see How to customize Error Pages). Modifying this option is advanced. If you need to customize an error page you should use the previous link. If you need to perform some behavior on an exception, you should add a listener to the kernel.exception event (see kernel.event_listener).

1. http://api.symfony.com/2.1/Symfony/Bundle/TwigBundle/Controller/ExceptionController.html

PDF brought to you by generated on October 26, 2012

Chapter 157: TwigBundle Configuration Reference | 737

Chapter 158

Configuration Reference
1 monolog: 2 handlers: 3 4 # Examples: 5 syslog: 6 type: stream 7 path: /var/log/symfony.log 8 level: ERROR 9 bubble: false 10 formatter: my_formatter 11 processors: 12 - some_callable 13 main: 14 type: fingers_crossed 15 action_level: WARNING 16 buffer_size: 30 17 handler: custom 18 custom: 19 type: service 20 id: my_handler 21 22 # Default options and values for some "my_custom_handler" 23 my_custom_handler: 24 type: ~ # Required 25 id: ~ 26 priority: 0 27 level: DEBUG 28 bubble: true 29 path: "%kernel.logs_dir%/%kernel.environment%.log" 30 ident: false 31 facility: user 32 max_files: 0 33 action_level: WARNING 34 activation_strategy: ~ 35 stop_buffering: true 36 buffer_size: 0

Listing 158-1

PDF brought to you by generated on October 26, 2012

Chapter 158: Configuration Reference | 738

37 38 39 40 41 42 43 44 45 46 47 48 49 50 51

handler: members: channels: type: ~ elements: ~ from_email: to_email: subject: email_prototype: id: factory-method: channels: type: elements: formatter:

~ []

~ ~ ~ ~ # Required (when the email_prototype is used) ~ ~ [] ~

When the profiler is enabled, a handler is added to store the logs' messages in the profiler. The profiler uses the name "debug" so it is reserved and cannot be used in the configuration.

PDF brought to you by generated on October 26, 2012

Chapter 158: Configuration Reference | 739

Chapter 159

WebProfilerBundle Configuration

Full Default Configuration


Listing 159-1

1 web_profiler: 2 3 # DEPRECATED, it is not useful anymore and can be removed safely from your configuration 4 verbose: true 5 6 # display the web debug toolbar at the bottom of pages with a summary of profiler info 7 toolbar: false 8 position: bottom 9 intercept_redirects: false

PDF brought to you by generated on October 26, 2012

Chapter 159: WebProfilerBundle Configuration | 740

Chapter 160

Form Types Reference


A form is composed of fields, each of which are built with the help of a field type (e.g. a text type, choice type, etc). Symfony2 comes standard with a large list of field types that can be used in your application.

Supported Field Types


The following field types are natively available in Symfony2:

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
PDF brought to you by generated on October 26, 2012 Chapter 160: Form Types Reference | 741

datetime time birthday

Other Fields
checkbox file radio

Field Groups
collection repeated

Hidden Fields
hidden csrf

Base Fields
field form

PDF brought to you by generated on October 26, 2012

Chapter 160: Form Types Reference | 742

Chapter 161

birthday Field Type


A date field that specializes in handling birthdate data. Can be rendered as a single text box, three text boxes (month, day, and year), or three select boxes. This type is essentially the same as the date type, but with a more appropriate default for the years option. The years option defaults to 120 years ago to the current year. Underlying Data Type Rendered as Options Inherited options can be DateTime, string, timestamp, or array (see the input option) can be three select boxes or 1 or 3 text boxes, based on the widget option years date widget input months days format pattern data_timezone user_timezone invalid_message invalid_message_parameters

Parent type Class

BirthdayType1

1. http://api.symfony.com/2.1/Symfony/Component/Form/Extension/Core/Type/BirthdayType.html

PDF brought to you by generated on October 26, 2012

Chapter 161: birthday Field Type | 743

Field Options
years
type: array default: 120 years ago to the current year List of years available to the year field type. This option is only relevant when the widget option is set to choice.

Inherited options
These options inherit from the date type:

widget
type: string default: choice The basic way in which this field should be rendered. Can be one of the following: choice: renders three select inputs. The order of the selects is defined in the pattern option. text: renders a three field input of type text (month, day, year). single_text: renders a single input of type text. User's input is validated based on the format option.

input
type: string default: datetime The format of the input data - i.e. the format that the date is stored on your underlying object. Valid values are: string (e.g. 2011-06-05) datetime (a DateTime object) array (e.g. array('year' => 2011, 'month' => 06, 'day' => 05)) timestamp (e.g. 1307232000)

The value that comes back from the form will also be normalized back into this format.

months
type: array default: 1 to 12 List of months available to the month field type. This option is only relevant when the widget option is set to choice.

days
type: array default: 1 to 31 List of days available to the day field type. This option is only relevant when the widget option is set to choice:
Listing 161-1

1 'days' => range(1,31)

PDF brought to you by generated on October 26, 2012

Chapter 161: birthday Field Type | 744

format
type: integer or string default: IntlDateFormatter::MEDIUM Option passed to the IntlDateFormatter class, used to transform user input into the proper format. This is critical when the widget option is set to single_text, and will define how the user will input the data. By default, the format is determined based on the current user locale: meaning that the expected format will be different for different users. You can override it by passing the format as a string. For more information on valid formats, see Date/Time Format Syntax2. For example, to render a single text box that expects the user to end yyyy-MM-dd, use the following options:
Listing 161-2

1 $builder->add('date_created', 'date', array( 2 'widget' => 'single_text', 3 'format' => 'yyyy-MM-dd', 4 ));

pattern
type: string This option is only relevant when the widget is set to choice. The default pattern is based off the format option, and tries to match the characters M, d, and y in the format pattern. If no match is found, the default is the string {{ year }}-{{ month }}-{{ day }}. Tokens for this option include: {{ year }}: Replaced with the year widget {{ month }}: Replaced with the month widget {{ day }}: Replaced with the day widget

data_timezone
type: string default: system default timezone Timezone that the input data is stored in. This must be one of the PHP supported timezones3

user_timezone
type: string default: system default timezone Timezone for how the data should be shown to the user (and therefore also the data that the user submits). This must be one of the PHP supported timezones4 These options inherit from the date type:

invalid_message
type: string default: This value is not valid This is the validation error message that's used if the data entered into this field doesn't make sense (i.e. fails validation). This might happen, for example, if the user enters a nonsense string into a time field that cannot be converted into a real time or if the user enters a string (e.g. apple) into a number field. Normal (business logic) validation (such as when setting a minimum length for a field) should be set using validation messages with your validation rules (reference).
2. http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax 3. http://php.net/manual/en/timezones.php 4. http://php.net/manual/en/timezones.php

PDF brought to you by generated on October 26, 2012

Chapter 161: birthday Field Type | 745

invalid_message_parameters
type: array default: array() When setting the invalid_message option, you may need to include some variables in the string. This can be done by adding placeholders to that option and including the variables in this option:
Listing 161-3

1 $builder->add('some_field', 'some_type', array( 2 // ... 3 'invalid_message' => 'You entered an invalid value - it should include %num% 4 letters', 5 'invalid_message_parameters' => array('%num%' => 6), ));

PDF brought to you by generated on October 26, 2012

Chapter 161: birthday Field Type | 746

Chapter 162

checkbox Field Type


Creates a single input checkbox. This should always be used for a field that has a Boolean value: if the box is checked, the field will be set to true, if the box is unchecked, the value will be set to false. Rendered as Options Inherited options input text field value field required label read_only error_bubbling

Parent type Class

CheckboxType1

Example Usage
Listing 162-1

1 $builder->add('public', 'checkbox', array( 2 'label' => 'Show this entry publicly?', 3 'required' => false, 4 ));

1. http://api.symfony.com/2.1/Symfony/Component/Form/Extension/Core/Type/CheckboxType.html

PDF brought to you by generated on October 26, 2012

Chapter 162: checkbox Field Type | 747

Field Options
value
type: mixed default: 1 The value that's actually used as the value for the checkbox. This does not affect the value that's set on your object.

Inherited options
These options inherit from the field type:

required
type: Boolean default: true If true, an HTML5 required attribute2 will be rendered. The corresponding label will also render with a required class. This is superficial and independent from validation. At best, if you let Symfony guess your field type, then the value of this option will be guessed from your validation information.

label
type: string default: The label is "guessed" from the field name Sets the label that will be used when rendering the field. The label can also be directly set inside the template:
Listing 162-2

1 {{ form_label(form.name, 'Your name') }}

read_only
type: Boolean default: false If this option is true, the field will be rendered with the disabled attribute so that the field is not editable.

error_bubbling
type: Boolean default: false If true, any errors for this field will be passed to the parent field or form. For example, if set to true on a normal field, any errors for that field will be attached to the main form, not to the specific field.

2. http://diveintohtml5.info/forms.html

PDF brought to you by generated on October 26, 2012

Chapter 162: checkbox Field Type | 748

Chapter 163

choice Field Type


A multi-purpose field used to allow the user to "choose" one or more options. It can be rendered as a select tag, radio buttons, or checkboxes. To use this field, you must specify either the choice_list or choices option. Rendered as Options can be various tags (see below) choices choice_list multiple expanded preferred_choices empty_value empty_data required label read_only error_bubbling

Inherited options

Parent type Class

form (if expanded), field otherwise

ChoiceType1

Example Usage
The easiest way to use this field is to specify the choices directly via the choices option. The key of the array becomes the value that's actually set on your underlying object (e.g. m), while the value is what the user sees on the form (e.g. Male).

1. http://api.symfony.com/2.1/Symfony/Component/Form/Extension/Core/Type/ChoiceType.html

PDF brought to you by generated on October 26, 2012

Chapter 163: choice Field Type | 749

Listing 163-1

1 $builder->add('gender', 'choice', array( 2 'choices' => array('m' => 'Male', 'f' => 'Female'), 3 'required' => false, 4 ));

By setting multiple to true, you can allow the user to choose multiple values. The widget will be rendered as a multiple select tag or a series of checkboxes depending on the expanded option:
Listing 163-2

1 $builder->add('availability', 'choice', array( 2 'choices' => array( 3 'morning' => 'Morning', 4 'afternoon' => 'Afternoon', 5 'evening' => 'Evening', 6 ), 7 'multiple' => true, 8 ));

You can also use the choice_list option, which takes an object that can specify the choices for your widget.

Select tag, Checkboxes or Radio Buttons


This field may be rendered as one of several different HTML fields, depending on the expanded and multiple options: element type select tag select tag (with multiple attribute) radio buttons checkboxes expanded multiple false false true true false true false true

Field Options
choices
type: array default: array() This is the most basic way to specify the choices that should be used by this field. The choices option is an array, where the array key is the item value and the array value is the item's label:
Listing 163-3

1 $builder->add('gender', 'choice', array( 2 'choices' => array('m' => 'Male', 'f' => 'Female') 3 ));

choice_list
type: Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface

PDF brought to you by generated on October 26, 2012

Chapter 163: choice Field Type | 750

This is one way of specifying the options to be used for this field. The choice_list option must be an instance of the ChoiceListInterface. For more advanced cases, a custom class that implements the interface can be created to supply the choices.

multiple
type: Boolean default: false If true, the user will be able to select multiple options (as opposed to choosing just one option). Depending on the value of the expanded option, this will render either a select tag or checkboxes if true and a select tag or radio buttons if false. The returned value will be an array.

expanded
type: Boolean default: false If set to true, radio buttons or checkboxes will be rendered (depending on the multiple value). If false, a select element will be rendered.

preferred_choices
type: array default: array() If this option is specified, then a sub-set of all of the options will be moved to the top of the select menu. The following would move the "Baz" option to the top, with a visual separator between it and the rest of the options:
Listing 163-4

1 $builder->add('foo_choices', 'choice', array( 2 'choices' => array('foo' => 'Foo', 'bar' => 'Bar', 'baz' => 'Baz'), 3 'preferred_choices' => array('baz'), 4 ));

Note that preferred choices are only meaningful when rendering as a select element (i.e. expanded is false). The preferred choices and normal choices are separated visually by a set of dotted lines (i.e. ------------------). This can be customized when rendering the field:
Listing 163-5

1 {{ form_widget(form.foo_choices, { 'separator': '=====' }) }}

empty_value
type: string or Boolean This option determines whether or not a special "empty" option (e.g. "Choose an option") will appear at the top of a select widget. This option only applies if both the expanded and multiple options are set to false. Add an empty value with "Choose an option" as the text:
Listing 163-6

1 $builder->add('states', 'choice', array( 2 'empty_value' => 'Choose an option', 3 ));

Guarantee that no "empty" value option is displayed:


Listing 163-7

PDF brought to you by generated on October 26, 2012

Chapter 163: choice Field Type | 751

1 $builder->add('states', 'choice', array( 2 'empty_value' => false, 3 ));

If you leave the empty_value option unset, then a blank (with no text) option will automatically be added if and only if the required option is false:
Listing 163-8

1 // a blank (with no text) option will be added 2 $builder->add('states', 'choice', array( 3 'required' => false, 4 ));

empty_data
type: mixed default: array() if multiple or expanded, '' otherwise This option determines what value the field will return when the empty_value choice is selected. For example, if you want the gender field to be set to null when no value is selected, you can do it like this:
Listing 163-9

1 $builder->add('gender', 'choice', array( 2 'choices' => array( 3 'm' => 'Male', 4 'f' => 'Female' 5 ), 6 'required' => false, 7 'empty_value' => 'Choose your gender', 8 'empty_data' => null 9 ));

Inherited options
These options inherit from the field type:

required
type: Boolean default: true If true, an HTML5 required attribute2 will be rendered. The corresponding label will also render with a required class. This is superficial and independent from validation. At best, if you let Symfony guess your field type, then the value of this option will be guessed from your validation information.

label
type: string default: The label is "guessed" from the field name Sets the label that will be used when rendering the field. The label can also be directly set inside the template:
Listing 163-10

2. http://diveintohtml5.info/forms.html

PDF brought to you by generated on October 26, 2012

Chapter 163: choice Field Type | 752

1 {{ form_label(form.name, 'Your name') }}

read_only
type: Boolean default: false If this option is true, the field will be rendered with the disabled attribute so that the field is not editable.

error_bubbling
type: Boolean default: false If true, any errors for this field will be passed to the parent field or form. For example, if set to true on a normal field, any errors for that field will be attached to the main form, not to the specific field.

PDF brought to you by generated on October 26, 2012

Chapter 163: choice Field Type | 753

Chapter 164

collection Field Type


This field type is used to render a "collection" of some field or form. In the easiest sense, it could be an array of text fields that populate an array emails field. In more complex examples, you can embed entire forms, which is useful when creating forms that expose one-to-many relationships (e.g. a product from where you can manage many related product photos). Rendered as Options depends on the type option type options allow_add allow_delete prototype prototype_name

Inherited options

label error_bubbling by_reference form

Parent type Class

CollectionType1

Basic Usage
This type is used when you want to manage a collection of similar items in a form. For example, suppose you have an emails field that corresponds to an array of email addresses. In the form, you want to expose each email address as its own input text box:
Listing 164-1

1. http://api.symfony.com/2.1/Symfony/Component/Form/Extension/Core/Type/CollectionType.html

PDF brought to you by generated on October 26, 2012

Chapter 164: collection Field Type | 754

1 $builder->add('emails', 'collection', array( 2 // each item in the array will be an "email" field 3 'type' => 'email', 4 // these options are passed to each "email" type 5 'options' => array( 6 'required' => false, 7 'attr' => array('class' => 'email-box') 8 ), 9 ));

The simplest way to render this is all at once:


Listing 164-2

1 {{ form_row(form.emails) }}

A much more flexible method would look like this:


Listing 164-3

1 2 3 4 5 6 7 8 9 10 11

{{ form_label(form.emails) }} {{ form_errors(form.emails) }} <ul> {% for emailField in form.emails %} <li> {{ form_errors(emailField) }} {{ form_widget(emailField) }} </li> {% endfor %} </ul>

In both cases, no input fields would render unless your emails data array already contained some emails. In this simple example, it's still impossible to add new addresses or remove existing addresses. Adding new addresses is possible by using the allow_add option (and optionally the prototype option) (see example below). Removing emails from the emails array is possible with the allow_delete option.

Adding and Removing items


If allow_add is set to true, then if any unrecognized items are submitted, they'll be added seamlessly to the array of items. This is great in theory, but takes a little bit more effort in practice to get the client-side JavaScript correct. Following along with the previous example, suppose you start with two emails in the emails data array. In that case, two input fields will be rendered that will look something like this (depending on the name of your form):
Listing 164-4

1 <input type="email" id="form_emails_1" name="form[emails][0]" value="foo@foo.com" /> 2 <input type="email" id="form_emails_1" name="form[emails][1]" value="bar@bar.com" />

To allow your user to add another email, just set allow_add to true and - via JavaScript - render another field with the name form[emails][2] (and so on for more and more fields). To help make this easier, setting the prototype option to true allows you to render a "template" field, which you can then use in your JavaScript to help you dynamically create these new fields. A rendered prototype field will look like this:
Listing 164-5

1 <input type="email" id="form_emails___name__" name="form[emails][__name__]" value="" />

PDF brought to you by generated on October 26, 2012

Chapter 164: collection Field Type | 755

By replacing __name__ with some unique value (e.g. 2), you can build and insert new HTML fields into your form. Using jQuery, a simple example might look like this. If you're rendering your collection fields all at once (e.g. form_row(form.emails)), then things are even easier because the data-prototype attribute is rendered automatically for you (with a slight difference - see note below) and all you need is the JavaScript:
Listing 164-6

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42

<form action="..." method="POST" {{ form_enctype(form) }}> {# ... #}

{# store the prototype on the data-prototype attribute #} <ul id="email-fields-list" data-prototype="{{ form_widget(form.emails.vars.prototype) | e }}"> {% for emailField in form.emails %} <li> {{ form_errors(emailField) }} {{ form_widget(emailField) }} </li> {% endfor %} </ul>
<a href="#" id="add-another-email">Add another email</a>

{# ... #} </form>
<script type="text/javascript"> // keep track of how many email fields have been rendered var emailCount = '{{ form.emails | length }}'; jQuery(document).ready(function() { jQuery('#add-another-email').click(function() { var emailList = jQuery('#email-fields-list');

// grab the prototype template var newWidget = emailList.attr('data-prototype'); // replace the "__name__" used in the id and name of the prototype // with a number that's unique to our emails // end name attribute looks like name="contact[emails][2]" newWidget = newWidget.replace(/__name__/g, emailCount); emailCount++; // create a new list element and add it to our list var newLi = jQuery('<li></li>').html(newWidget); newLi.appendTo(jQuery('#email-fields-list'));
return false; }); }) </script>

If you're rendering the entire collection at once, then the prototype is automatically available on the data-prototype attribute of the element (e.g. div or table) that surrounds your collection. The only difference is that the entire "form row" is rendered for you, meaning you wouldn't have to wrap it in any container element like we've done above.

PDF brought to you by generated on October 26, 2012

Chapter 164: collection Field Type | 756

Field Options
type
type: string or FormTypeInterface2 required This is the field type for each item in this collection (e.g. text, choice, etc). For example, if you have an array of email addresses, you'd use the email type. If you want to embed a collection of some other form, create a new instance of your form type and pass it as this option.

options
type: array default: array() This is the array that's passed to the form type specified in the type option. For example, if you used the choice type as your type option (e.g. for a collection of drop-down menus), then you'd need to at least pass the choices option to the underlying type:
Listing 164-7

1 $builder->add('favorite_cities', 'collection', array( 2 'type' => 'choice', 3 'options' => array( 4 'choices' => array( 5 'nashville' => 'Nashville', 6 'paris' => 'Paris', 7 'berlin' => 'Berlin', 8 'london' => 'London', 9 ), 10 ), 11 ));

allow_add
type: Boolean default: false If set to true, then if unrecognized items are submitted to the collection, they will be added as new items. The ending array will contain the existing items as well as the new item that was in the submitted data. See the above example for more details. The prototype option can be used to help render a prototype item that can be used - with JavaScript - to create new form items dynamically on the client side. For more information, see the above example and Allowing "new" tags with the "prototype".
If you're embedding entire other forms to reflect a one-to-many database relationship, you may need to manually ensure that the foreign key of these new objects is set correctly. If you're using Doctrine, this won't happen automatically. See the above link for more details.

allow_delete
type: Boolean default: false If set to true, then if an existing item is not contained in the submitted data, it will be correctly absent from the final array of items. This means that you can implement a "delete" button via JavaScript which simply removes a form element from the DOM. When the user submits the form, its absence from the submitted data will mean that it's removed from the final array.
2. http://api.symfony.com/2.1/Symfony/Component/Form/FormTypeInterface.html

PDF brought to you by generated on October 26, 2012

Chapter 164: collection Field Type | 757

For more information, see Allowing tags to be removed.


Be careful when using this option when you're embedding a collection of objects. In this case, if any embedded forms are removed, they will correctly be missing from the final array of objects. However, depending on your application logic, when one of those objects is removed, you may want to delete it or at least remove its foreign key reference to the main object. None of this is handled automatically. For more information, see Allowing tags to be removed.

prototype
type: Boolean default: true This option is useful when using the allow_add option. If true (and if allow_add is also true), a special "prototype" attribute will be available so that you can render a "template" example on your page of what a new element should look like. The name attribute given to this element is __name__. This allows you to add a "add another" button via JavaScript which reads the prototype, replaces __name__ with some unique name or number, and render it inside your form. When submitted, it will be added to your underlying array due to the allow_add option. The prototype field can be rendered via the prototype variable in the collection field:
Listing 164-8

1 {{ form_row(form.emails.vars.prototype) }}

Note that all you really need is the "widget", but depending on how you're rendering your form, having the entire "form row" may be easier for you.
If you're rendering the entire collection field at once, then the prototype form row is automatically available on the data-prototype attribute of the element (e.g. div or table) that surrounds your collection.

For details on how to actually use this option, see the above example as well as Allowing "new" tags with the "prototype".

prototype_name
New in version 2.1: The prototype_name option was added in Symfony 2.1

type: String default: __name__ If you have several collections in your form, or worse, nested collections you may want to change the placeholder so that unrelated placeholders are not replaced with the same value.

Inherited options
These options inherit from the field type. Not all options are listed here - only the most applicable to this type:

label
type: string default: The label is "guessed" from the field name
PDF brought to you by generated on October 26, 2012 Chapter 164: collection Field Type | 758

Sets the label that will be used when rendering the field. The label can also be directly set inside the template:
Listing 164-9

1 {{ form_label(form.name, 'Your name') }}

error_bubbling
type: Boolean default: true If true, any errors for this field will be passed to the parent field or form. For example, if set to true on a normal field, any errors for that field will be attached to the main form, not to the specific field.

by_reference
type: Boolean default: true In most cases, if you have a name field, then you expect setName to be called on the underlying object. In some cases, however, setName may not be called. Setting by_reference ensures that the setter is called in all cases. To understand this further, let's look at a simple example:
Listing 164-10 1

$builder = $this->createFormBuilder($article); 2 $builder 3 ->add('title', 'text') 4 ->add( 5 $builder->create('author', 'form', array('by_reference' => ?)) 6 ->add('name', 'text') 7 ->add('email', 'email') 8 )

If by_reference is true, the following takes place behind the scenes when you call bind on the form:
Listing 164-11 1

$article->setTitle('...'); 2 $article->getAuthor()->setName('...'); 3 $article->getAuthor()->setEmail('...');

Notice that setAuthor is not called. The author is modified by reference. If we set by_reference to false, binding looks like this:
Listing 164-12 1

2 3 4 5

$article->setTitle('...'); $author = $article->getAuthor(); $author->setName('...'); $author->setEmail('...'); $article->setAuthor($author);

So, all that by_reference=false really does is force the framework to call the setter on the parent object. Similarly, if you're using the collection form type where your underlying collection data is an object (like with Doctrine's ArrayCollection), then by_reference must be set to false if you need the setter (e.g. setAuthors) to be called.

PDF brought to you by generated on October 26, 2012

Chapter 164: collection Field Type | 759

Chapter 165

country Field Type


The country type is a subset of the ChoiceType that displays countries of the world. As an added bonus, the country names are displayed in the language of the user. The "value" for each country is the two-letter country code.
The locale of your user is guessed using Locale::getDefault()1

Unlike the choice type, you don't need to specify a choices or choice_list option as the field type automatically uses all of the countries of the world. You can specify either of these options manually, but then you should just use the choice type directly. Rendered as Inherited options can be various tags (see Select tag, Checkboxes or Radio Buttons) multiple expanded preferred_choices empty_value error_bubbling required label read_only

Parent type Class

choice

CountryType2

1. http://php.net/manual/en/locale.getdefault.php 2. http://api.symfony.com/2.1/Symfony/Component/Form/Extension/Core/Type/CountryType.html

PDF brought to you by generated on October 26, 2012

Chapter 165: country Field Type | 760

Inherited options
These options inherit from the choice type:

multiple
type: Boolean default: false If true, the user will be able to select multiple options (as opposed to choosing just one option). Depending on the value of the expanded option, this will render either a select tag or checkboxes if true and a select tag or radio buttons if false. The returned value will be an array.

expanded
type: Boolean default: false If set to true, radio buttons or checkboxes will be rendered (depending on the multiple value). If false, a select element will be rendered.

preferred_choices
type: array default: array() If this option is specified, then a sub-set of all of the options will be moved to the top of the select menu. The following would move the "Baz" option to the top, with a visual separator between it and the rest of the options:
Listing 165-1

1 $builder->add('foo_choices', 'choice', array( 2 'choices' => array('foo' => 'Foo', 'bar' => 'Bar', 'baz' => 'Baz'), 3 'preferred_choices' => array('baz'), 4 ));

Note that preferred choices are only meaningful when rendering as a select element (i.e. expanded is false). The preferred choices and normal choices are separated visually by a set of dotted lines (i.e. ------------------). This can be customized when rendering the field:
Listing 165-2

1 {{ form_widget(form.foo_choices, { 'separator': '=====' }) }}

empty_value
type: string or Boolean This option determines whether or not a special "empty" option (e.g. "Choose an option") will appear at the top of a select widget. This option only applies if both the expanded and multiple options are set to false. Add an empty value with "Choose an option" as the text:
Listing 165-3

1 $builder->add('states', 'choice', array( 2 'empty_value' => 'Choose an option', 3 ));

Guarantee that no "empty" value option is displayed:


Listing 165-4

PDF brought to you by generated on October 26, 2012

Chapter 165: country Field Type | 761

1 $builder->add('states', 'choice', array( 2 'empty_value' => false, 3 ));

If you leave the empty_value option unset, then a blank (with no text) option will automatically be added if and only if the required option is false:
Listing 165-5

1 // a blank (with no text) option will be added 2 $builder->add('states', 'choice', array( 3 'required' => false, 4 ));

error_bubbling
type: Boolean default: false If true, any errors for this field will be passed to the parent field or form. For example, if set to true on a normal field, any errors for that field will be attached to the main form, not to the specific field. These options inherit from the field type:

required
type: Boolean default: true If true, an HTML5 required attribute3 will be rendered. The corresponding label will also render with a required class. This is superficial and independent from validation. At best, if you let Symfony guess your field type, then the value of this option will be guessed from your validation information.

label
type: string default: The label is "guessed" from the field name Sets the label that will be used when rendering the field. The label can also be directly set inside the template:
Listing 165-6

1 {{ form_label(form.name, 'Your name') }}

read_only
type: Boolean default: false If this option is true, the field will be rendered with the disabled attribute so that the field is not editable.

3. http://diveintohtml5.info/forms.html

PDF brought to you by generated on October 26, 2012

Chapter 165: country Field Type | 762

Chapter 166

csrf Field Type


The csrf type is a hidden input field containing a CSRF token. Rendered as input hidden field Options csrf_provider intention property_path hidden

Parent type Class

CsrfType1

Field Options
csrf_provider
type: Symfony\Component\Form\CsrfProvider\CsrfProviderInterface The CsrfProviderInterface object that should generate the CSRF token. If not set, this defaults to the default provider.

intention
type: string An optional unique identifier used to generate the CSRF token.

property_path
type: any default: the field's value

1. http://api.symfony.com/2.1/Symfony/Component/Form/Extension/Csrf/Type/CsrfType.html

PDF brought to you by generated on October 26, 2012

Chapter 166: csrf Field Type | 763

Fields display a property value of the form's domain object by default. When the form is submitted, the submitted value is written back into the object. If you want to override the property that a field reads from and writes to, you can set the property_path option. Its default value is the field's name. If you wish the field to be ignored when reading or writing to the object you can set the property_path option to false

PDF brought to you by generated on October 26, 2012

Chapter 166: csrf Field Type | 764

Chapter 167

date Field Type


A field that allows the user to modify date information via a variety of different HTML elements. The underlying data used for this field type can be a DateTime object, a string, a timestamp or an array. As long as the input option is set correctly, the field will take care of all of the details. The field can be rendered as a single text box, three text boxes (month, day, and year) or three select boxes (see the widget_ option). Underlying Data Type Rendered as Options can be DateTime, string, timestamp, or array (see the input option) single text box or three select fields widget input empty_value years months days format pattern data_timezone user_timezone

Inherited options

invalid_message invalid_message_parameters field (if text), form otherwise

Parent type Class

DateType1

1. http://api.symfony.com/2.1/Symfony/Component/Form/Extension/Core/Type/DateType.html

PDF brought to you by generated on October 26, 2012

Chapter 167: date Field Type | 765

Basic Usage
This field type is highly configurable, but easy to use. The most important options are input and widget. Suppose that you have a publishedAt field whose underlying date is a DateTime object. The following configures the date type for that field as three different choice fields:
Listing 167-1

1 $builder->add('publishedAt', 'date', array( 2 'input' => 'datetime', 3 'widget' => 'choice', 4 ));

The input option must be changed to match the type of the underlying date data. For example, if the publishedAt field's data were a unix timestamp, you'd need to set input to timestamp:
Listing 167-2

1 $builder->add('publishedAt', 'date', array( 2 'input' => 'timestamp', 3 'widget' => 'choice', 4 ));

The field also supports an array and string as valid input option values.

Field Options
widget
type: string default: choice The basic way in which this field should be rendered. Can be one of the following: choice: renders three select inputs. The order of the selects is defined in the pattern option. text: renders a three field input of type text (month, day, year). single_text: renders a single input of type text. User's input is validated based on the format option.

input
type: string default: datetime The format of the input data - i.e. the format that the date is stored on your underlying object. Valid values are: string (e.g. 2011-06-05) datetime (a DateTime object) array (e.g. array('year' => 2011, 'month' => 06, 'day' => 05)) timestamp (e.g. 1307232000)

The value that comes back from the form will also be normalized back into this format.

empty_value
type: string or array If your widget option is set to choice, then this field will be represented as a series of select boxes. The empty_value option can be used to add a "blank" entry to the top of each select box:

PDF brought to you by generated on October 26, 2012

Chapter 167: date Field Type | 766

Listing 167-3

1 $builder->add('dueDate', 'date', array( 2 'empty_value' => '', 3 ));

Alternatively, you can specify a string to be displayed for the "blank" value:
Listing 167-4

1 $builder->add('dueDate', 'date', array( 2 'empty_value' => array('year' => 'Year', 'month' => 'Month', 'day' => 'Day') 3 ));

years
type: array default: five years before to five years after the current year List of years available to the year field type. This option is only relevant when the widget option is set to choice.

months
type: array default: 1 to 12 List of months available to the month field type. This option is only relevant when the widget option is set to choice.

days
type: array default: 1 to 31 List of days available to the day field type. This option is only relevant when the widget option is set to choice:
Listing 167-5

1 'days' => range(1,31)

format
type: integer or string default: IntlDateFormatter::MEDIUM Option passed to the IntlDateFormatter class, used to transform user input into the proper format. This is critical when the widget option is set to single_text, and will define how the user will input the data. By default, the format is determined based on the current user locale: meaning that the expected format will be different for different users. You can override it by passing the format as a string. For more information on valid formats, see Date/Time Format Syntax2. For example, to render a single text box that expects the user to end yyyy-MM-dd, use the following options:
Listing 167-6

1 $builder->add('date_created', 'date', array( 2 'widget' => 'single_text', 3 'format' => 'yyyy-MM-dd', 4 ));

2. http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax

PDF brought to you by generated on October 26, 2012

Chapter 167: date Field Type | 767

pattern
type: string This option is only relevant when the widget is set to choice. The default pattern is based off the format option, and tries to match the characters M, d, and y in the format pattern. If no match is found, the default is the string {{ year }}-{{ month }}-{{ day }}. Tokens for this option include: {{ year }}: Replaced with the year widget {{ month }}: Replaced with the month widget {{ day }}: Replaced with the day widget

data_timezone
type: string default: system default timezone Timezone that the input data is stored in. This must be one of the PHP supported timezones3

user_timezone
type: string default: system default timezone Timezone for how the data should be shown to the user (and therefore also the data that the user submits). This must be one of the PHP supported timezones4

Inherited options
These options inherit from the field type:

invalid_message
type: string default: This value is not valid This is the validation error message that's used if the data entered into this field doesn't make sense (i.e. fails validation). This might happen, for example, if the user enters a nonsense string into a time field that cannot be converted into a real time or if the user enters a string (e.g. apple) into a number field. Normal (business logic) validation (such as when setting a minimum length for a field) should be set using validation messages with your validation rules (reference).

invalid_message_parameters
type: array default: array() When setting the invalid_message option, you may need to include some variables in the string. This can be done by adding placeholders to that option and including the variables in this option:
Listing 167-7

1 $builder->add('some_field', 'some_type', array( 2 // ... 3 'invalid_message' => 'You entered an invalid value - it should include %num% 4 letters', 5 'invalid_message_parameters' => array('%num%' => 6), ));

3. http://php.net/manual/en/timezones.php 4. http://php.net/manual/en/timezones.php

PDF brought to you by generated on October 26, 2012

Chapter 167: date Field Type | 768

Chapter 168

datetime Field Type


This field type allows the user to modify data that represents a specific date and time (e.g. 1984-06-05 12:15:30). Can be rendered as a text input or select tags. The underlying format of the data can be a DateTime object, a string, a timestamp or an array. Underlying Data Type Rendered as Options can be DateTime, string, timestamp, or array (see the input option) single text box or three select fields date_widget time_widget input date_format hours minutes seconds years months days with_seconds data_timezone user_timezone

Inherited options

invalid_message invalid_message_parameters form

Parent type Class

DateTimeType1

1. http://api.symfony.com/2.1/Symfony/Component/Form/Extension/Core/Type/DateTimeType.html

PDF brought to you by generated on October 26, 2012

Chapter 168: datetime Field Type | 769

Field Options
date_widget
type: string default: choice Defines the widget option for the date type

time_widget
type: string default: choice Defines the widget option for the time type

input
type: string default: datetime The format of the input data - i.e. the format that the date is stored on your underlying object. Valid values are: string (e.g. 2011-06-05 12:15:00) datetime (a DateTime object) array (e.g. array(2011, 06, 05, 12, 15, 0)) timestamp (e.g. 1307276100)

The value that comes back from the form will also be normalized back into this format.

date_format
type: integer or string default: IntlDateFormatter::MEDIUM Defines the format option that will be passed down to the date field. See the date type's format option for more details.

hours
type: integer default: 0 to 23 List of hours available to the hours field type. This option is only relevant when the widget option is set to choice.

minutes
type: integer default: 0 to 59 List of minutes available to the minutes field type. This option is only relevant when the widget option is set to choice.

seconds
type: integer default: 0 to 59 List of seconds available to the seconds field type. This option is only relevant when the widget option is set to choice.

PDF brought to you by generated on October 26, 2012

Chapter 168: datetime Field Type | 770

years
type: array default: five years before to five years after the current year List of years available to the year field type. This option is only relevant when the widget option is set to choice.

months
type: array default: 1 to 12 List of months available to the month field type. This option is only relevant when the widget option is set to choice.

days
type: array default: 1 to 31 List of days available to the day field type. This option is only relevant when the widget option is set to choice:
Listing 168-1

1 'days' => range(1,31)

with_seconds
type: Boolean default: false Whether or not to include seconds in the input. This will result in an additional input to capture seconds.

data_timezone
type: string default: system default timezone Timezone that the input data is stored in. This must be one of the PHP supported timezones2

user_timezone
type: string default: system default timezone Timezone for how the data should be shown to the user (and therefore also the data that the user submits). This must be one of the PHP supported timezones3

Inherited options
These options inherit from the field type:

invalid_message
type: string default: This value is not valid This is the validation error message that's used if the data entered into this field doesn't make sense (i.e. fails validation).

2. http://php.net/manual/en/timezones.php 3. http://php.net/manual/en/timezones.php

PDF brought to you by generated on October 26, 2012

Chapter 168: datetime Field Type | 771

This might happen, for example, if the user enters a nonsense string into a time field that cannot be converted into a real time or if the user enters a string (e.g. apple) into a number field. Normal (business logic) validation (such as when setting a minimum length for a field) should be set using validation messages with your validation rules (reference).

invalid_message_parameters
type: array default: array() When setting the invalid_message option, you may need to include some variables in the string. This can be done by adding placeholders to that option and including the variables in this option:
Listing 168-2

1 $builder->add('some_field', 'some_type', array( 2 // ... 3 'invalid_message' => 'You entered an invalid value - it should include %num% 4 letters', 5 'invalid_message_parameters' => array('%num%' => 6), ));

PDF brought to you by generated on October 26, 2012

Chapter 168: datetime Field Type | 772

Chapter 169

email Field Type


The email field is a text field that is rendered using the HTML5 <input type="email" /> tag. Rendered as Inherited options input email field (a text box) field max_length required label trim read_only error_bubbling

Parent type Class

EmailType1

Inherited Options
These options inherit from the field type:

max_length
type: integer This option is used to add a max_length attribute, which is used by some browsers to limit the amount of text in a field.

required
type: Boolean default: true

1. http://api.symfony.com/2.1/Symfony/Component/Form/Extension/Core/Type/EmailType.html

PDF brought to you by generated on October 26, 2012

Chapter 169: email Field Type | 773

If true, an HTML5 required attribute2 will be rendered. The corresponding label will also render with a required class. This is superficial and independent from validation. At best, if you let Symfony guess your field type, then the value of this option will be guessed from your validation information.

label
type: string default: The label is "guessed" from the field name Sets the label that will be used when rendering the field. The label can also be directly set inside the template:
Listing 169-1

1 {{ form_label(form.name, 'Your name') }}

trim
type: Boolean default: true If true, the whitespace of the submitted string value will be stripped via the trim() function when the data is bound. This guarantees that if a value is submitted with extra whitespace, it will be removed before the value is merged back onto the underlying object.

read_only
type: Boolean default: false If this option is true, the field will be rendered with the disabled attribute so that the field is not editable.

error_bubbling
type: Boolean default: false If true, any errors for this field will be passed to the parent field or form. For example, if set to true on a normal field, any errors for that field will be attached to the main form, not to the specific field.

2. http://diveintohtml5.info/forms.html

PDF brought to you by generated on October 26, 2012

Chapter 169: email Field Type | 774

Chapter 170

entity Field Type


A special choice field that's designed to load options from a Doctrine entity. For example, if you have a Category entity, you could use this field to display a select field of all, or some, of the Category objects from the database. Rendered as Options can be various tags (see Select tag, Checkboxes or Radio Buttons) class property group_by query_builder em required label multiple expanded preferred_choices empty_value read_only error_bubbling

Inherited options

Parent type Class

choice

EntityType1

Basic Usage
The entity type has just one required option: the entity which should be listed inside the choice field:
Listing 170-1

1. http://api.symfony.com/2.1/Symfony/Bridge/Doctrine/Form/Type/EntityType.html

PDF brought to you by generated on October 26, 2012

Chapter 170: entity Field Type | 775

1 $builder->add('users', 'entity', array( 2 'class' => 'AcmeHelloBundle:User', 3 'property' => 'username', 4 ));

In this case, all User objects will be loaded from the database and rendered as either a select tag, a set or radio buttons or a series of checkboxes (this depends on the multiple and expanded values). If the entity object does not have a __toString() method the property option is needed.

Using a Custom Query for the Entities


If you need to specify a custom query to use when fetching the entities (e.g. you only want to return some entities, or need to order them), use the query_builder option. The easiest way to use the option is as follows:
Listing 170-2

1 2 3 4 5 6 7 8 9 10

use Doctrine\ORM\EntityRepository; // ... $builder->add('users', 'entity', array( 'class' => 'AcmeHelloBundle:User', 'query_builder' => function(EntityRepository $er) { return $er->createQueryBuilder('u') ->orderBy('u.username', 'ASC'); }, ));

Select tag, Checkboxes or Radio Buttons


This field may be rendered as one of several different HTML fields, depending on the expanded and multiple options: element type select tag select tag (with multiple attribute) radio buttons checkboxes expanded multiple false false true true false true false true

Field Options
class
type: string required The class of your entity (e.g. AcmeStoreBundle:Category). This can be a fully-qualified class name (e.g. Acme\StoreBundle\Entity\Category) or the short alias name (as shown prior).

property
type: string
PDF brought to you by generated on October 26, 2012 Chapter 170: entity Field Type | 776

This is the property that should be used for displaying the entities as text in the HTML element. If left blank, the entity object will be cast into a string and so must have a __toString() method.

group_by
type: string This is a property path (e.g. author.name) used to organize the available choices in groups. It only works when rendered as a select tag and does so by adding optgroup tags around options. Choices that do not return a value for this property path are rendered directly under the select tag, without a surrounding optgroup.

query_builder
type: Doctrine\ORM\QueryBuilder or a Closure If specified, this is used to query the subset of options (and their order) that should be used for the field. The value of this option can either be a QueryBuilder object or a Closure. If using a Closure, it should take a single argument, which is the EntityRepository of the entity.

em
type: string default: the default entity manager If specified, the specified entity manager will be used to load the choices instead of the default entity manager.

Inherited options
These options inherit from the choice type:

multiple
type: Boolean default: false If true, the user will be able to select multiple options (as opposed to choosing just one option). Depending on the value of the expanded option, this will render either a select tag or checkboxes if true and a select tag or radio buttons if false. The returned value will be an array.

expanded
type: Boolean default: false If set to true, radio buttons or checkboxes will be rendered (depending on the multiple value). If false, a select element will be rendered.

preferred_choices
type: array default: array() If this option is specified, then a sub-set of all of the options will be moved to the top of the select menu. The following would move the "Baz" option to the top, with a visual separator between it and the rest of the options:
Listing 170-3

1 $builder->add('foo_choices', 'choice', array( 2 'choices' => array('foo' => 'Foo', 'bar' => 'Bar', 'baz' => 'Baz'),

PDF brought to you by generated on October 26, 2012

Chapter 170: entity Field Type | 777

3 'preferred_choices' => array('baz'), 4 ));

Note that preferred choices are only meaningful when rendering as a select element (i.e. expanded is false). The preferred choices and normal choices are separated visually by a set of dotted lines (i.e. ------------------). This can be customized when rendering the field:
Listing 170-4

1 {{ form_widget(form.foo_choices, { 'separator': '=====' }) }}

empty_value
type: string or Boolean This option determines whether or not a special "empty" option (e.g. "Choose an option") will appear at the top of a select widget. This option only applies if both the expanded and multiple options are set to false. Add an empty value with "Choose an option" as the text:
Listing 170-5

1 $builder->add('states', 'choice', array( 2 'empty_value' => 'Choose an option', 3 ));

Guarantee that no "empty" value option is displayed:


Listing 170-6

1 $builder->add('states', 'choice', array( 2 'empty_value' => false, 3 ));

If you leave the empty_value option unset, then a blank (with no text) option will automatically be added if and only if the required option is false:
Listing 170-7

1 // a blank (with no text) option will be added 2 $builder->add('states', 'choice', array( 3 'required' => false, 4 ));

These options inherit from the field type:

required
type: Boolean default: true If true, an HTML5 required attribute2 will be rendered. The corresponding label will also render with a required class. This is superficial and independent from validation. At best, if you let Symfony guess your field type, then the value of this option will be guessed from your validation information.

label
type: string default: The label is "guessed" from the field name
2. http://diveintohtml5.info/forms.html

PDF brought to you by generated on October 26, 2012

Chapter 170: entity Field Type | 778

Sets the label that will be used when rendering the field. The label can also be directly set inside the template:
Listing 170-8

1 {{ form_label(form.name, 'Your name') }}

read_only
type: Boolean default: false If this option is true, the field will be rendered with the disabled attribute so that the field is not editable.

error_bubbling
type: Boolean default: false If true, any errors for this field will be passed to the parent field or form. For example, if set to true on a normal field, any errors for that field will be attached to the main form, not to the specific field.

PDF brought to you by generated on October 26, 2012

Chapter 170: entity Field Type | 779

Chapter 171

file Field Type


The file type represents a file input in your form. Rendered as Inherited options input file field form required label read_only error_bubbling

Parent type Class

FileType1

Basic Usage
Let's say you have this form definition:
Listing 171-1

1 $builder->add('attachment', 'file');

Don't forget to add the enctype attribute in the form tag: <form action="#" method="post" {{ form_enctype(form) }}>.

When the form is submitted, the attachment field will be an instance of UploadedFile2. It can be used to move the attachment file to a permanent location:
Listing 171-2

1. http://api.symfony.com/2.1/Symfony/Component/Form/Extension/Core/Type/FileType.html 2. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/File/UploadedFile.html

PDF brought to you by generated on October 26, 2012

Chapter 171: file Field Type | 780

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

use Symfony\Component\HttpFoundation\File\UploadedFile; public function uploadAction() { // ... if ($form->isValid()) { $someNewFilename = ... $form['attachment']->getData()->move($dir, $someNewFilename);

// ...
}

// ...
}

The move() method takes a directory and a file name as its arguments. You might calculate the filename in one of the following ways:
Listing 171-3

1 2 3 4 5 6 7 8 9 10

// use the original file name $file->move($dir, $file->getClientOriginalName()); // compute a random name and try to guess the extension (more secure) $extension = $file->guessExtension(); if (!$extension) { // extension cannot be guessed $extension = 'bin'; } $file->move($dir, rand(1, 99999).'.'.$extension);

Using the original name via getClientOriginalName() is not safe as it could have been manipulated by the end-user. Moreover, it can contain characters that are not allowed in file names. You should sanitize the name before using it directly. Read the cookbook for an example of how to manage a file upload associated with a Doctrine entity.

Inherited options
These options inherit from the field type:

required
type: Boolean default: true If true, an HTML5 required attribute3 will be rendered. The corresponding label will also render with a required class. This is superficial and independent from validation. At best, if you let Symfony guess your field type, then the value of this option will be guessed from your validation information.

label
type: string default: The label is "guessed" from the field name
3. http://diveintohtml5.info/forms.html

PDF brought to you by generated on October 26, 2012

Chapter 171: file Field Type | 781

Sets the label that will be used when rendering the field. The label can also be directly set inside the template:
Listing 171-4

1 {{ form_label(form.name, 'Your name') }}

read_only
type: Boolean default: false If this option is true, the field will be rendered with the disabled attribute so that the field is not editable.

error_bubbling
type: Boolean default: false If true, any errors for this field will be passed to the parent field or form. For example, if set to true on a normal field, any errors for that field will be attached to the main form, not to the specific field.

PDF brought to you by generated on October 26, 2012

Chapter 171: file Field Type | 782

Chapter 172

The Abstract "field" Type


The field form type is deprecated as of Symfony 2.1. Please use the Form field type instead.

PDF brought to you by generated on October 26, 2012

Chapter 172: The Abstract "field" Type | 783

Chapter 173

form Field Type


See FormType1. The form type predefines a couple of options that are then available on all fields.

data
type: mixed default: Defaults to field of the underlying object (if there is one) When you create a form, each field initially displays the value of the corresponding property of the form's domain object (if an object is bound to the form). If you want to override the initial value for the form or just an individual field, you can set it in the data option:
Listing 173-1

1 $builder->add('token', 'hidden', array( 2 'data' => 'abcdef', 3 ));

required
type: Boolean default: true If true, an HTML5 required attribute2 will be rendered. The corresponding label will also render with a required class. This is superficial and independent from validation. At best, if you let Symfony guess your field type, then the value of this option will be guessed from your validation information.

cascade_validation
type: Boolean default: false
1. http://api.symfony.com/2.1/Symfony/Component/Form/Extension/Core/Type/FormType.html 2. http://diveintohtml5.info/forms.html

PDF brought to you by generated on October 26, 2012

Chapter 173: form Field Type | 784

Set this option to true to force validation on embedded form types. For example, if you have a ProductType with an embedded CategoryType, setting cascade_validation to true on ProductType will cause the data from CategoryType to also be validated. Instead of using this option, you can also use the Valid constraint in your model to force validation on a child object stored on a property.

disabled
type: boolean default: false If you don't want a user to modify the value of a field, you can set the disabled option to true. Any submitted value will be ignored.
Listing 173-2

1 2 3 4 5 6 7 8 9 10

use Symfony\Component\Form\TextField $field = new TextField('status', array( 'data' => 'Old data', 'disabled' => true, )); $field->submit('New data');

// prints "Old data" echo $field->getData();

trim
type: Boolean default: true If true, the whitespace of the submitted string value will be stripped via the trim() function when the data is bound. This guarantees that if a value is submitted with extra whitespace, it will be removed before the value is merged back onto the underlying object.

property_path
type: any default: the field's value Fields display a property value of the form's domain object by default. When the form is submitted, the submitted value is written back into the object. If you want to override the property that a field reads from and writes to, you can set the property_path option. Its default value is the field's name. If you wish the field to be ignored when reading or writing to the object you can set the property_path option to false

attr
type: array default: Empty array If you want to add extra attributes to HTML field representation you can use attr option. It's an associative array with HTML attribute as a key. This can be useful when you need to set a custom class for some widget:

PDF brought to you by generated on October 26, 2012

Chapter 173: form Field Type | 785

Listing 173-3

1 $builder->add('body', 'textarea', array( 2 'attr' => array('class' => 'tinymce'), 3 ));

translation_domain
type: string default: messages This is the translation domain that will be used for any labels or options that are rendered for this field.

PDF brought to you by generated on October 26, 2012

Chapter 173: form Field Type | 786

Chapter 174

hidden Field Type


The hidden type represents a hidden input field. Rendered as Inherited options Parent type Class input hidden field data property_path field

HiddenType1

Inherited Options
These options inherit from the field type:

data
type: mixed default: Defaults to field of the underlying object (if there is one) When you create a form, each field initially displays the value of the corresponding property of the form's domain object (if an object is bound to the form). If you want to override the initial value for the form or just an individual field, you can set it in the data option:
Listing 174-1

1 $builder->add('token', 'hidden', array( 2 'data' => 'abcdef', 3 ));

property_path
type: any default: the field's value
1. http://api.symfony.com/2.1/Symfony/Component/Form/Extension/Core/Type/HiddenType.html

PDF brought to you by generated on October 26, 2012

Chapter 174: hidden Field Type | 787

Fields display a property value of the form's domain object by default. When the form is submitted, the submitted value is written back into the object. If you want to override the property that a field reads from and writes to, you can set the property_path option. Its default value is the field's name. If you wish the field to be ignored when reading or writing to the object you can set the property_path option to false

PDF brought to you by generated on October 26, 2012

Chapter 174: hidden Field Type | 788

Chapter 175

integer Field Type


Renders an input "number" field. Basically, this is a text field that's good at handling data that's in an integer form. The input number field looks like a text box, except that - if the user's browser supports HTML5 - it will have some extra frontend functionality. This field has different options on how to handle input values that aren't integers. By default, all noninteger values (e.g. 6.78) will round down (e.g. 6). Rendered as Options input text field rounding_mode grouping field required label read_only error_bubbling invalid_message invalid_message_parameters

Inherited options

Parent type Class

IntegerType1

Field Options
rounding_mode
type: integer default: IntegerToLocalizedStringTransformer::ROUND_DOWN

1. http://api.symfony.com/2.1/Symfony/Component/Form/Extension/Core/Type/IntegerType.html

PDF brought to you by generated on October 26, 2012

Chapter 175: integer Field Type | 789

By default, if the user enters a non-integer number, it will be rounded down. There are several other rounding methods, and each is a constant on the IntegerToLocalizedStringTransformer2: IntegerToLocalizedStringTransformer::ROUND_DOWN Rounding mode to round towards zero. IntegerToLocalizedStringTransformer::ROUND_FLOOR Rounding mode to round towards negative infinity. IntegerToLocalizedStringTransformer::ROUND_UP Rounding mode to round away from zero. IntegerToLocalizedStringTransformer::ROUND_CEILING Rounding mode to round towards positive infinity.

grouping
type: integer default: false This value is used internally as the NumberFormatter::GROUPING_USED value when using PHP's NumberFormatter class. Its documentation is non-existent, but it appears that if you set this to true, numbers will be grouped with a comma or period (depending on your locale): 12345.123 would display as 12,345.123.

Inherited options
These options inherit from the field type:

required
type: Boolean default: true If true, an HTML5 required attribute3 will be rendered. The corresponding label will also render with a required class. This is superficial and independent from validation. At best, if you let Symfony guess your field type, then the value of this option will be guessed from your validation information.

label
type: string default: The label is "guessed" from the field name Sets the label that will be used when rendering the field. The label can also be directly set inside the template:
Listing 175-1

1 {{ form_label(form.name, 'Your name') }}

read_only
type: Boolean default: false If this option is true, the field will be rendered with the disabled attribute so that the field is not editable.

error_bubbling
type: Boolean default: false
2. http://api.symfony.com/2.1/Symfony/Component/Form/Extension/Core/DataTransformer/IntegerToLocalizedStringTransformer.html 3. http://diveintohtml5.info/forms.html

PDF brought to you by generated on October 26, 2012

Chapter 175: integer Field Type | 790

If true, any errors for this field will be passed to the parent field or form. For example, if set to true on a normal field, any errors for that field will be attached to the main form, not to the specific field.

invalid_message
type: string default: This value is not valid This is the validation error message that's used if the data entered into this field doesn't make sense (i.e. fails validation). This might happen, for example, if the user enters a nonsense string into a time field that cannot be converted into a real time or if the user enters a string (e.g. apple) into a number field. Normal (business logic) validation (such as when setting a minimum length for a field) should be set using validation messages with your validation rules (reference).

invalid_message_parameters
type: array default: array() When setting the invalid_message option, you may need to include some variables in the string. This can be done by adding placeholders to that option and including the variables in this option:
Listing 175-2

1 $builder->add('some_field', 'some_type', array( 2 // ... 3 'invalid_message' => 'You entered an invalid value - it should include %num% 4 letters', 5 'invalid_message_parameters' => array('%num%' => 6), ));

PDF brought to you by generated on October 26, 2012

Chapter 175: integer Field Type | 791

Chapter 176

language Field Type


The language type is a subset of the ChoiceType that allows the user to select from a large list of languages. As an added bonus, the language names are displayed in the language of the user. The "value" for each language is the Unicode language identifier (e.g. fr or zh-Hant).
The locale of your user is guessed using Locale::getDefault()1

Unlike the choice type, you don't need to specify a choices or choice_list option as the field type automatically uses a large list of languages. You can specify either of these options manually, but then you should just use the choice type directly. Rendered as Inherited options can be various tags (see Select tag, Checkboxes or Radio Buttons) multiple expanded preferred_choices empty_value error_bubbling required label read_only

Parent type Class

choice

LanguageType2

1. http://php.net/manual/en/locale.getdefault.php 2. http://api.symfony.com/2.1/Symfony/Component/Form/Extension/Core/Type/LanguageType.html

PDF brought to you by generated on October 26, 2012

Chapter 176: language Field Type | 792

Inherited Options
These options inherit from the choice type:

multiple
type: Boolean default: false If true, the user will be able to select multiple options (as opposed to choosing just one option). Depending on the value of the expanded option, this will render either a select tag or checkboxes if true and a select tag or radio buttons if false. The returned value will be an array.

expanded
type: Boolean default: false If set to true, radio buttons or checkboxes will be rendered (depending on the multiple value). If false, a select element will be rendered.

preferred_choices
type: array default: array() If this option is specified, then a sub-set of all of the options will be moved to the top of the select menu. The following would move the "Baz" option to the top, with a visual separator between it and the rest of the options:
Listing 176-1

1 $builder->add('foo_choices', 'choice', array( 2 'choices' => array('foo' => 'Foo', 'bar' => 'Bar', 'baz' => 'Baz'), 3 'preferred_choices' => array('baz'), 4 ));

Note that preferred choices are only meaningful when rendering as a select element (i.e. expanded is false). The preferred choices and normal choices are separated visually by a set of dotted lines (i.e. ------------------). This can be customized when rendering the field:
Listing 176-2

1 {{ form_widget(form.foo_choices, { 'separator': '=====' }) }}

empty_value
type: string or Boolean This option determines whether or not a special "empty" option (e.g. "Choose an option") will appear at the top of a select widget. This option only applies if both the expanded and multiple options are set to false. Add an empty value with "Choose an option" as the text:
Listing 176-3

1 $builder->add('states', 'choice', array( 2 'empty_value' => 'Choose an option', 3 ));

Guarantee that no "empty" value option is displayed:


Listing 176-4

PDF brought to you by generated on October 26, 2012

Chapter 176: language Field Type | 793

1 $builder->add('states', 'choice', array( 2 'empty_value' => false, 3 ));

If you leave the empty_value option unset, then a blank (with no text) option will automatically be added if and only if the required option is false:
Listing 176-5

1 // a blank (with no text) option will be added 2 $builder->add('states', 'choice', array( 3 'required' => false, 4 ));

error_bubbling
type: Boolean default: false If true, any errors for this field will be passed to the parent field or form. For example, if set to true on a normal field, any errors for that field will be attached to the main form, not to the specific field. These options inherit from the field type:

required
type: Boolean default: true If true, an HTML5 required attribute3 will be rendered. The corresponding label will also render with a required class. This is superficial and independent from validation. At best, if you let Symfony guess your field type, then the value of this option will be guessed from your validation information.

label
type: string default: The label is "guessed" from the field name Sets the label that will be used when rendering the field. The label can also be directly set inside the template:
Listing 176-6

1 {{ form_label(form.name, 'Your name') }}

read_only
type: Boolean default: false If this option is true, the field will be rendered with the disabled attribute so that the field is not editable.

3. http://diveintohtml5.info/forms.html

PDF brought to you by generated on October 26, 2012

Chapter 176: language Field Type | 794

Chapter 177

locale Field Type


The locale type is a subset of the ChoiceType that allows the user to select from a large list of locales (language+country). As an added bonus, the locale names are displayed in the language of the user. The "value" for each locale is either the two letter ISO639-1 language code (e.g. fr), or the language code followed by an underscore (_), then the ISO3166 country code (e.g. fr_FR for French/France).
The locale of your user is guessed using Locale::getDefault()1

Unlike the choice type, you don't need to specify a choices or choice_list option as the field type automatically uses a large list of locales. You can specify either of these options manually, but then you should just use the choice type directly. Rendered as Inherited options can be various tags (see Select tag, Checkboxes or Radio Buttons) multiple expanded preferred_choices empty_value error_bubbling required label read_only

Parent type Class

choice

LanguageType2

1. http://php.net/manual/en/locale.getdefault.php 2. http://api.symfony.com/2.1/Symfony/Component/Form/Extension/Core/Type/LanguageType.html

PDF brought to you by generated on October 26, 2012

Chapter 177: locale Field Type | 795

Inherited options
These options inherit from the choice type:

multiple
type: Boolean default: false If true, the user will be able to select multiple options (as opposed to choosing just one option). Depending on the value of the expanded option, this will render either a select tag or checkboxes if true and a select tag or radio buttons if false. The returned value will be an array.

expanded
type: Boolean default: false If set to true, radio buttons or checkboxes will be rendered (depending on the multiple value). If false, a select element will be rendered.

preferred_choices
type: array default: array() If this option is specified, then a sub-set of all of the options will be moved to the top of the select menu. The following would move the "Baz" option to the top, with a visual separator between it and the rest of the options:
Listing 177-1

1 $builder->add('foo_choices', 'choice', array( 2 'choices' => array('foo' => 'Foo', 'bar' => 'Bar', 'baz' => 'Baz'), 3 'preferred_choices' => array('baz'), 4 ));

Note that preferred choices are only meaningful when rendering as a select element (i.e. expanded is false). The preferred choices and normal choices are separated visually by a set of dotted lines (i.e. ------------------). This can be customized when rendering the field:
Listing 177-2

1 {{ form_widget(form.foo_choices, { 'separator': '=====' }) }}

empty_value
type: string or Boolean This option determines whether or not a special "empty" option (e.g. "Choose an option") will appear at the top of a select widget. This option only applies if both the expanded and multiple options are set to false. Add an empty value with "Choose an option" as the text:
Listing 177-3

1 $builder->add('states', 'choice', array( 2 'empty_value' => 'Choose an option', 3 ));

Guarantee that no "empty" value option is displayed:


Listing 177-4

PDF brought to you by generated on October 26, 2012

Chapter 177: locale Field Type | 796

1 $builder->add('states', 'choice', array( 2 'empty_value' => false, 3 ));

If you leave the empty_value option unset, then a blank (with no text) option will automatically be added if and only if the required option is false:
Listing 177-5

1 // a blank (with no text) option will be added 2 $builder->add('states', 'choice', array( 3 'required' => false, 4 ));

error_bubbling
type: Boolean default: false If true, any errors for this field will be passed to the parent field or form. For example, if set to true on a normal field, any errors for that field will be attached to the main form, not to the specific field. These options inherit from the field type:

required
type: Boolean default: true If true, an HTML5 required attribute3 will be rendered. The corresponding label will also render with a required class. This is superficial and independent from validation. At best, if you let Symfony guess your field type, then the value of this option will be guessed from your validation information.

label
type: string default: The label is "guessed" from the field name Sets the label that will be used when rendering the field. The label can also be directly set inside the template:
Listing 177-6

1 {{ form_label(form.name, 'Your name') }}

read_only
type: Boolean default: false If this option is true, the field will be rendered with the disabled attribute so that the field is not editable.

3. http://diveintohtml5.info/forms.html

PDF brought to you by generated on October 26, 2012

Chapter 177: locale Field Type | 797

Chapter 178

money Field Type


Renders an input text field and specializes in handling submitted "money" data. This field type allows you to specify a currency, whose symbol is rendered next to the text field. There are also several other options for customizing how the input and output of the data is handled. Rendered as Options input text field field currency divisor precision grouping required label read_only error_bubbling invalid_message invalid_message_parameters

Inherited options

Parent type Class

MoneyType1

Field Options
currency
type: string default: EUR

1. http://api.symfony.com/2.1/Symfony/Component/Form/Extension/Core/Type/MoneyType.html

PDF brought to you by generated on October 26, 2012

Chapter 178: money Field Type | 798

Specifies the currency that the money is being specified in. This determines the currency symbol that should be shown by the text box. Depending on the currency - the currency symbol may be shown before or after the input text field. This can also be set to false to hide the currency symbol.

divisor
type: integer default: 1 If, for some reason, you need to divide your starting value by a number before rendering it to the user, you can use the divisor option. For example:
Listing 178-1

1 $builder->add('price', 'money', array( 2 'divisor' => 100, 3 ));

In this case, if the price field is set to 9900, then the value 99 will actually be rendered to the user. When the user submits the value 99, it will be multiplied by 100 and 9900 will ultimately be set back on your object.

precision
type: integer default: 2 For some reason, if you need some precision other than 2 decimal places, you can modify this value. You probably won't need to do this unless, for example, you want to round to the nearest dollar (set the precision to 0).

grouping
type: integer default: false This value is used internally as the NumberFormatter::GROUPING_USED value when using PHP's NumberFormatter class. Its documentation is non-existent, but it appears that if you set this to true, numbers will be grouped with a comma or period (depending on your locale): 12345.123 would display as 12,345.123.

Inherited Options
These options inherit from the field type:

required
type: Boolean default: true If true, an HTML5 required attribute2 will be rendered. The corresponding label will also render with a required class. This is superficial and independent from validation. At best, if you let Symfony guess your field type, then the value of this option will be guessed from your validation information.

label
type: string default: The label is "guessed" from the field name
2. http://diveintohtml5.info/forms.html

PDF brought to you by generated on October 26, 2012

Chapter 178: money Field Type | 799

Sets the label that will be used when rendering the field. The label can also be directly set inside the template:
Listing 178-2

1 {{ form_label(form.name, 'Your name') }}

read_only
type: Boolean default: false If this option is true, the field will be rendered with the disabled attribute so that the field is not editable.

error_bubbling
type: Boolean default: false If true, any errors for this field will be passed to the parent field or form. For example, if set to true on a normal field, any errors for that field will be attached to the main form, not to the specific field.

invalid_message
type: string default: This value is not valid This is the validation error message that's used if the data entered into this field doesn't make sense (i.e. fails validation). This might happen, for example, if the user enters a nonsense string into a time field that cannot be converted into a real time or if the user enters a string (e.g. apple) into a number field. Normal (business logic) validation (such as when setting a minimum length for a field) should be set using validation messages with your validation rules (reference).

invalid_message_parameters
type: array default: array() When setting the invalid_message option, you may need to include some variables in the string. This can be done by adding placeholders to that option and including the variables in this option:
Listing 178-3

1 $builder->add('some_field', 'some_type', array( 2 // ... 3 'invalid_message' => 'You entered an invalid value - it should include %num% 4 letters', 5 'invalid_message_parameters' => array('%num%' => 6), ));

PDF brought to you by generated on October 26, 2012

Chapter 178: money Field Type | 800

Chapter 179

number Field Type


Renders an input text field and specializes in handling number input. This type offers different options for the precision, rounding, and grouping that you want to use for your number. Rendered as Options input text field rounding_mode precision grouping field required label read_only error_bubbling invalid_message invalid_message_parameters

Inherited options

Parent type Class

NumberType1

Field Options
precision
type: integer default: Locale-specific (usually around 3) This specifies how many decimals will be allowed until the field rounds the submitted value (via rounding_mode). For example, if precision is set to 2, a submitted value of 20.123 will be rounded to, for example, 20.12 (depending on your rounding_mode).

1. http://api.symfony.com/2.1/Symfony/Component/Form/Extension/Core/Type/NumberType.html

PDF brought to you by generated on October 26, 2012

Chapter 179: number Field Type | 801

rounding_mode
type: integer default: IntegerToLocalizedStringTransformer::ROUND_HALFUP If a submitted number needs to be rounded (based on the precision option), you have several configurable options for that rounding. Each option is a constant on the IntegerToLocalizedStringTransformer2: IntegerToLocalizedStringTransformer::ROUND_DOWN Rounding mode to round towards zero. IntegerToLocalizedStringTransformer::ROUND_FLOOR Rounding mode to round towards negative infinity. IntegerToLocalizedStringTransformer::ROUND_UP Rounding mode to round away from zero. IntegerToLocalizedStringTransformer::ROUND_CEILING Rounding mode to round towards positive infinity. IntegerToLocalizedStringTransformer::ROUND_HALFDOWN Rounding mode to round towards "nearest neighbor" unless both neighbors are equidistant, in which case round down. IntegerToLocalizedStringTransformer::ROUND_HALFEVEN Rounding mode to round towards the "nearest neighbor" unless both neighbors are equidistant, in which case, round towards the even neighbor. IntegerToLocalizedStringTransformer::ROUND_HALFUP Rounding mode to round towards "nearest neighbor" unless both neighbors are equidistant, in which case round up.

grouping
type: integer default: false This value is used internally as the NumberFormatter::GROUPING_USED value when using PHP's NumberFormatter class. Its documentation is non-existent, but it appears that if you set this to true, numbers will be grouped with a comma or period (depending on your locale): 12345.123 would display as 12,345.123.

Inherited Options
These options inherit from the field type:

required
type: Boolean default: true If true, an HTML5 required attribute3 will be rendered. The corresponding label will also render with a required class. This is superficial and independent from validation. At best, if you let Symfony guess your field type, then the value of this option will be guessed from your validation information.

label
type: string default: The label is "guessed" from the field name Sets the label that will be used when rendering the field. The label can also be directly set inside the template:

2. http://api.symfony.com/2.1/Symfony/Component/Form/Extension/Core/DataTransformer/IntegerToLocalizedStringTransformer.html 3. http://diveintohtml5.info/forms.html

PDF brought to you by generated on October 26, 2012

Chapter 179: number Field Type | 802

Listing 179-1

1 {{ form_label(form.name, 'Your name') }}

read_only
type: Boolean default: false If this option is true, the field will be rendered with the disabled attribute so that the field is not editable.

error_bubbling
type: Boolean default: false If true, any errors for this field will be passed to the parent field or form. For example, if set to true on a normal field, any errors for that field will be attached to the main form, not to the specific field.

invalid_message
type: string default: This value is not valid This is the validation error message that's used if the data entered into this field doesn't make sense (i.e. fails validation). This might happen, for example, if the user enters a nonsense string into a time field that cannot be converted into a real time or if the user enters a string (e.g. apple) into a number field. Normal (business logic) validation (such as when setting a minimum length for a field) should be set using validation messages with your validation rules (reference).

invalid_message_parameters
type: array default: array() When setting the invalid_message option, you may need to include some variables in the string. This can be done by adding placeholders to that option and including the variables in this option:
Listing 179-2

1 $builder->add('some_field', 'some_type', array( 2 // ... 3 'invalid_message' => 'You entered an invalid value - it should include %num% 4 letters', 5 'invalid_message_parameters' => array('%num%' => 6), ));

PDF brought to you by generated on October 26, 2012

Chapter 179: number Field Type | 803

Chapter 180

password Field Type


The password field renders an input password text box. Rendered as Options Inherited options input password field always_empty text max_length required label trim read_only error_bubbling

Parent type Class

PasswordType1

Field Options
always_empty
type: Boolean default: true If set to true, the field will always render blank, even if the corresponding field has a value. When set to false, the password field will be rendered with the value attribute set to its true value. Put simply, if for some reason you want to render your password field with the password value already entered into the box, set this to false.

1. http://api.symfony.com/2.1/Symfony/Component/Form/Extension/Core/Type/PasswordType.html

PDF brought to you by generated on October 26, 2012

Chapter 180: password Field Type | 804

Inherited Options
These options inherit from the field type:

max_length
type: integer This option is used to add a max_length attribute, which is used by some browsers to limit the amount of text in a field.

required
type: Boolean default: true If true, an HTML5 required attribute2 will be rendered. The corresponding label will also render with a required class. This is superficial and independent from validation. At best, if you let Symfony guess your field type, then the value of this option will be guessed from your validation information.

label
type: string default: The label is "guessed" from the field name Sets the label that will be used when rendering the field. The label can also be directly set inside the template:
Listing 180-1

1 {{ form_label(form.name, 'Your name') }}

trim
type: Boolean default: true If true, the whitespace of the submitted string value will be stripped via the trim() function when the data is bound. This guarantees that if a value is submitted with extra whitespace, it will be removed before the value is merged back onto the underlying object.

read_only
type: Boolean default: false If this option is true, the field will be rendered with the disabled attribute so that the field is not editable.

error_bubbling
type: Boolean default: false If true, any errors for this field will be passed to the parent field or form. For example, if set to true on a normal field, any errors for that field will be attached to the main form, not to the specific field.

2. http://diveintohtml5.info/forms.html

PDF brought to you by generated on October 26, 2012

Chapter 180: password Field Type | 805

Chapter 181

percent Field Type


The percent type renders an input text field and specializes in handling percentage data. If your percentage data is stored as a decimal (e.g. .95), you can use this field out-of-the-box. If you store your data as a number (e.g. 95), you should set the type option to integer. This field adds a percentage sign "%" after the input box. Rendered as Options input text field type precision field required label read_only error_bubbling invalid_message invalid_message_parameters

Inherited options

Parent type Class

PercentType1

Options
type
type: string default: fractional This controls how your data is stored on your object. For example, a percentage corresponding to "55%", might be stored as .55 or 55 on your object. The two "types" handle these two cases:
1. http://api.symfony.com/2.1/Symfony/Component/Form/Extension/Core/Type/PercentType.html

PDF brought to you by generated on October 26, 2012

Chapter 181: percent Field Type | 806

fractional If your data is stored as a decimal (e.g. .55), use this type. The data will be multiplied by 100 before being shown to the user (e.g. 55). The submitted data will be divided by 100 on form submit so that the decimal value is stored (.55); integer If your data is stored as an integer (e.g. 55), then use this option. The raw value (55) is shown to the user and stored on your object. Note that this only works for integer values.

precision
type: integer default: 0 By default, the input numbers are rounded. To allow for more decimal places, use this option.

Inherited Options
These options inherit from the field type:

required
type: Boolean default: true If true, an HTML5 required attribute2 will be rendered. The corresponding label will also render with a required class. This is superficial and independent from validation. At best, if you let Symfony guess your field type, then the value of this option will be guessed from your validation information.

label
type: string default: The label is "guessed" from the field name Sets the label that will be used when rendering the field. The label can also be directly set inside the template:
Listing 181-1

1 {{ form_label(form.name, 'Your name') }}

read_only
type: Boolean default: false If this option is true, the field will be rendered with the disabled attribute so that the field is not editable.

error_bubbling
type: Boolean default: false If true, any errors for this field will be passed to the parent field or form. For example, if set to true on a normal field, any errors for that field will be attached to the main form, not to the specific field.

invalid_message
type: string default: This value is not valid This is the validation error message that's used if the data entered into this field doesn't make sense (i.e. fails validation).

2. http://diveintohtml5.info/forms.html

PDF brought to you by generated on October 26, 2012

Chapter 181: percent Field Type | 807

This might happen, for example, if the user enters a nonsense string into a time field that cannot be converted into a real time or if the user enters a string (e.g. apple) into a number field. Normal (business logic) validation (such as when setting a minimum length for a field) should be set using validation messages with your validation rules (reference).

invalid_message_parameters
type: array default: array() When setting the invalid_message option, you may need to include some variables in the string. This can be done by adding placeholders to that option and including the variables in this option:
Listing 181-2

1 $builder->add('some_field', 'some_type', array( 2 // ... 3 'invalid_message' => 'You entered an invalid value - it should include %num% 4 letters', 5 'invalid_message_parameters' => array('%num%' => 6), ));

PDF brought to you by generated on October 26, 2012

Chapter 181: percent Field Type | 808

Chapter 182

radio Field Type


Creates a single radio button. This should always be used for a field that has a Boolean value: if the radio button is selected, the field will be set to true, if the button is not selected, the value will be set to false. The radio type isn't usually used directly. More commonly it's used internally by other types such as choice. If you want to have a Boolean field, use checkbox. Rendered as Options Inherited options input radio field value field required label read_only error_bubbling

Parent type Class

RadioType1

Field Options
value
type: mixed default: 1 The value that's actually used as the value for the radio button. This does not affect the value that's set on your object.

1. http://api.symfony.com/2.1/Symfony/Component/Form/Extension/Core/Type/RadioType.html

PDF brought to you by generated on October 26, 2012

Chapter 182: radio Field Type | 809

Inherited Options
These options inherit from the field type:

required
type: Boolean default: true If true, an HTML5 required attribute2 will be rendered. The corresponding label will also render with a required class. This is superficial and independent from validation. At best, if you let Symfony guess your field type, then the value of this option will be guessed from your validation information.

label
type: string default: The label is "guessed" from the field name Sets the label that will be used when rendering the field. The label can also be directly set inside the template:
Listing 182-1

1 {{ form_label(form.name, 'Your name') }}

read_only
type: Boolean default: false If this option is true, the field will be rendered with the disabled attribute so that the field is not editable.

error_bubbling
type: Boolean default: false If true, any errors for this field will be passed to the parent field or form. For example, if set to true on a normal field, any errors for that field will be attached to the main form, not to the specific field.

2. http://diveintohtml5.info/forms.html

PDF brought to you by generated on October 26, 2012

Chapter 182: radio Field Type | 810

Chapter 183

repeated Field Type


This is a special field "group", that creates two identical fields whose values must match (or a validation error is thrown). The most common use is when you need the user to repeat his or her password or email to verify accuracy. Rendered as Options input text field by default, but see type option type options first_options second_options first_name second_name

Inherited options

invalid_message invalid_message_parameters error_bubbling field

Parent type Class

RepeatedType1

Example Usage
Listing 183-1

1 $builder->add('password', 'repeated', array( 2 'type' => 'password', 3 'invalid_message' => 'The password fields must match.', 4 'options' => array('attr' => array('class' => 'password-field')), 5 'required' => true,

1. http://api.symfony.com/2.1/Symfony/Component/Form/Extension/Core/Type/RepeatedType.html

PDF brought to you by generated on October 26, 2012

Chapter 183: repeated Field Type | 811

6 'first_options' => array('label' => 'Password'), 7 'second_options' => array('label' => 'Repeat Password'), 8 ));

Upon a successful form submit, the value entered into both of the "password" fields becomes the data of the password key. In other words, even though two fields are actually rendered, the end data from the form is just the single value (usually a string) that you need. The most important option is type, which can be any field type and determines the actual type of the two underlying fields. The options option is passed to each of those individual fields, meaning - in this example - any option supported by the password type can be passed in this array.

Validation
One of the key features of the repeated field is internal validation (you don't need to do anything to set this up) that forces the two fields to have a matching value. If the two fields don't match, an error will be shown to the user. The invalid_message is used to customize the error that will be displayed when the two fields do not match each other.

Field Options
type
type: string default: text The two underlying fields will be of this field type. For example, passing a type of password will render two password fields.

options
type: array default: array() This options array will be passed to each of the two underlying fields. In other words, these are the options that customize the individual field types. For example, if the type option is set to password, this array might contain the options always_empty or required - both options that are supported by the password field type.

first_options
type: array default: array()
New in version 2.1: The first_options option is new in Symfony 2.1.

Additional options (will be merged into options above) that should be passed only to the first field. This is especially useful for customizing the label:
Listing 183-2

1 $builder->add('password', 'repeated', array( 2 'first_options' => array('label' => 'Password'),

PDF brought to you by generated on October 26, 2012

Chapter 183: repeated Field Type | 812

3 'second_options' => array('label' => 'Repeat Password'), 4 ));

second_options
type: array default: array()
New in version 2.1: The second_options option is new in Symfony 2.1.

Additional options (will be merged into options above) that should be passed only to the second field. This is especially useful for customizing the label (see first_options).

first_name
type: string default: first This is the actual field name to be used for the first field. This is mostly meaningless, however, as the actual data entered into both of the fields will be available under the key assigned to the repeated field itself (e.g. password). However, if you don't specify a label, this field name is used to "guess" the label for you.

second_name
type: string default: second The same as first_name, but for the second field.

Inherited options
These options inherit from the field type:

invalid_message
type: string default: This value is not valid This is the validation error message that's used if the data entered into this field doesn't make sense (i.e. fails validation). This might happen, for example, if the user enters a nonsense string into a time field that cannot be converted into a real time or if the user enters a string (e.g. apple) into a number field. Normal (business logic) validation (such as when setting a minimum length for a field) should be set using validation messages with your validation rules (reference).

invalid_message_parameters
type: array default: array() When setting the invalid_message option, you may need to include some variables in the string. This can be done by adding placeholders to that option and including the variables in this option:
Listing 183-3

PDF brought to you by generated on October 26, 2012

Chapter 183: repeated Field Type | 813

1 $builder->add('some_field', 'some_type', array( 2 // ... 3 'invalid_message' => 'You entered an invalid value - it should include %num% 4 letters', 5 'invalid_message_parameters' => array('%num%' => 6), ));

error_bubbling
type: Boolean default: false If true, any errors for this field will be passed to the parent field or form. For example, if set to true on a normal field, any errors for that field will be attached to the main form, not to the specific field.

PDF brought to you by generated on October 26, 2012

Chapter 183: repeated Field Type | 814

Chapter 184

search Field Type


This renders an <input type="search" /> field, which is a text box with special functionality supported by some browsers. Read about the input search field at DiveIntoHTML5.info1 Rendered as Inherited options input search field text max_length required label trim read_only error_bubbling

Parent type Class

SearchType2

Inherited Options
These options inherit from the field type:

max_length
type: integer This option is used to add a max_length attribute, which is used by some browsers to limit the amount of text in a field.

1. http://diveintohtml5.info/forms.html#type-search 2. http://api.symfony.com/2.1/Symfony/Component/Form/Extension/Core/Type/SearchType.html

PDF brought to you by generated on October 26, 2012

Chapter 184: search Field Type | 815

required
type: Boolean default: true If true, an HTML5 required attribute3 will be rendered. The corresponding label will also render with a required class. This is superficial and independent from validation. At best, if you let Symfony guess your field type, then the value of this option will be guessed from your validation information.

label
type: string default: The label is "guessed" from the field name Sets the label that will be used when rendering the field. The label can also be directly set inside the template:
Listing 184-1

1 {{ form_label(form.name, 'Your name') }}

trim
type: Boolean default: true If true, the whitespace of the submitted string value will be stripped via the trim() function when the data is bound. This guarantees that if a value is submitted with extra whitespace, it will be removed before the value is merged back onto the underlying object.

read_only
type: Boolean default: false If this option is true, the field will be rendered with the disabled attribute so that the field is not editable.

error_bubbling
type: Boolean default: false If true, any errors for this field will be passed to the parent field or form. For example, if set to true on a normal field, any errors for that field will be attached to the main form, not to the specific field.

3. http://diveintohtml5.info/forms.html

PDF brought to you by generated on October 26, 2012

Chapter 184: search Field Type | 816

Chapter 185

text Field Type


The text field represents the most basic input text field. Rendered as Inherited options input text field field max_length required label trim read_only error_bubbling

Parent type Class

TextType1

Inherited Options
These options inherit from the field type:

max_length
type: integer This option is used to add a max_length attribute, which is used by some browsers to limit the amount of text in a field.

required
type: Boolean default: true

1. http://api.symfony.com/2.1/Symfony/Component/Form/Extension/Core/Type/TextType.html

PDF brought to you by generated on October 26, 2012

Chapter 185: text Field Type | 817

If true, an HTML5 required attribute2 will be rendered. The corresponding label will also render with a required class. This is superficial and independent from validation. At best, if you let Symfony guess your field type, then the value of this option will be guessed from your validation information.

label
type: string default: The label is "guessed" from the field name Sets the label that will be used when rendering the field. The label can also be directly set inside the template:
Listing 185-1

1 {{ form_label(form.name, 'Your name') }}

trim
type: Boolean default: true If true, the whitespace of the submitted string value will be stripped via the trim() function when the data is bound. This guarantees that if a value is submitted with extra whitespace, it will be removed before the value is merged back onto the underlying object.

read_only
type: Boolean default: false If this option is true, the field will be rendered with the disabled attribute so that the field is not editable.

error_bubbling
type: Boolean default: false If true, any errors for this field will be passed to the parent field or form. For example, if set to true on a normal field, any errors for that field will be attached to the main form, not to the specific field.

2. http://diveintohtml5.info/forms.html

PDF brought to you by generated on October 26, 2012

Chapter 185: text Field Type | 818

Chapter 186

textarea Field Type


Renders a textarea HTML element. Rendered as Inherited options textarea tag field max_length required label trim read_only error_bubbling

Parent type Class

TextareaType1

Inherited Options
These options inherit from the field type:

max_length
type: integer This option is used to add a max_length attribute, which is used by some browsers to limit the amount of text in a field.

required
type: Boolean default: true

1. http://api.symfony.com/2.1/Symfony/Component/Form/Extension/Core/Type/TextareaType.html

PDF brought to you by generated on October 26, 2012

Chapter 186: textarea Field Type | 819

If true, an HTML5 required attribute2 will be rendered. The corresponding label will also render with a required class. This is superficial and independent from validation. At best, if you let Symfony guess your field type, then the value of this option will be guessed from your validation information.

label
type: string default: The label is "guessed" from the field name Sets the label that will be used when rendering the field. The label can also be directly set inside the template:
Listing 186-1

1 {{ form_label(form.name, 'Your name') }}

trim
type: Boolean default: true If true, the whitespace of the submitted string value will be stripped via the trim() function when the data is bound. This guarantees that if a value is submitted with extra whitespace, it will be removed before the value is merged back onto the underlying object.

read_only
type: Boolean default: false If this option is true, the field will be rendered with the disabled attribute so that the field is not editable.

error_bubbling
type: Boolean default: false If true, any errors for this field will be passed to the parent field or form. For example, if set to true on a normal field, any errors for that field will be attached to the main form, not to the specific field.

2. http://diveintohtml5.info/forms.html

PDF brought to you by generated on October 26, 2012

Chapter 186: textarea Field Type | 820

Chapter 187

time Field Type


A field to capture time input. This can be rendered as a text field, a series of text fields (e.g. hour, minute, second) or a series of select fields. The underlying data can be stored as a DateTime object, a string, a timestamp or an array. Underlying Data Type Rendered as Options can be DateTime, string, timestamp, or array (see the input option) can be various tags (see below) widget input with_seconds hours minutes seconds data_timezone user_timezone

Inherited options

invalid_message invalid_message_parameters form

Parent type Class

TimeType1

Basic Usage
This field type is highly configurable, but easy to use. The most important options are input and widget.

1. http://api.symfony.com/2.1/Symfony/Component/Form/Extension/Core/Type/TimeType.html

PDF brought to you by generated on October 26, 2012

Chapter 187: time Field Type | 821

Suppose that you have a startTime field whose underlying time data is a DateTime object. The following configures the time type for that field as three different choice fields:
Listing 187-1

1 $builder->add('startTime', 'time', array( 2 'input' => 'datetime', 3 'widget' => 'choice', 4 ));

The input option must be changed to match the type of the underlying date data. For example, if the startTime field's data were a unix timestamp, you'd need to set input to timestamp:
Listing 187-2

1 $builder->add('startTime', 'time', array( 2 'input' => 'timestamp', 3 'widget' => 'choice', 4 ));

The field also supports an array and string as valid input option values.

Field Options
widget
type: string default: choice The basic way in which this field should be rendered. Can be one of the following: choice: renders two (or three if with_seconds is true) select inputs. text: renders a two or three text inputs (hour, minute, second). single_text: renders a single input of type text. User's input will be validated against the form hh:mm (or hh:mm:ss if using seconds).

input
type: string default: datetime The format of the input data - i.e. the format that the date is stored on your underlying object. Valid values are: string (e.g. 12:17:26) datetime (a DateTime object) array (e.g. array('hour' => 12, 'minute' => 17, 'second' => 26)) timestamp (e.g. 1307232000)

The value that comes back from the form will also be normalized back into this format.

with_seconds
type: Boolean default: false Whether or not to include seconds in the input. This will result in an additional input to capture seconds.

hours
type: integer default: 0 to 23

PDF brought to you by generated on October 26, 2012

Chapter 187: time Field Type | 822

List of hours available to the hours field type. This option is only relevant when the widget option is set to choice.

minutes
type: integer default: 0 to 59 List of minutes available to the minutes field type. This option is only relevant when the widget option is set to choice.

seconds
type: integer default: 0 to 59 List of seconds available to the seconds field type. This option is only relevant when the widget option is set to choice.

data_timezone
type: string default: system default timezone Timezone that the input data is stored in. This must be one of the PHP supported timezones2

user_timezone
type: string default: system default timezone Timezone for how the data should be shown to the user (and therefore also the data that the user submits). This must be one of the PHP supported timezones3

Inherited options
These options inherit from the field type:

invalid_message
type: string default: This value is not valid This is the validation error message that's used if the data entered into this field doesn't make sense (i.e. fails validation). This might happen, for example, if the user enters a nonsense string into a time field that cannot be converted into a real time or if the user enters a string (e.g. apple) into a number field. Normal (business logic) validation (such as when setting a minimum length for a field) should be set using validation messages with your validation rules (reference).

invalid_message_parameters
type: array default: array() When setting the invalid_message option, you may need to include some variables in the string. This can be done by adding placeholders to that option and including the variables in this option:
Listing 187-3

2. http://php.net/manual/en/timezones.php 3. http://php.net/manual/en/timezones.php

PDF brought to you by generated on October 26, 2012

Chapter 187: time Field Type | 823

1 $builder->add('some_field', 'some_type', array( 2 // ... 3 'invalid_message' => 'You entered an invalid value - it should include %num% 4 letters', 5 'invalid_message_parameters' => array('%num%' => 6), ));

PDF brought to you by generated on October 26, 2012

Chapter 187: time Field Type | 824

Chapter 188

timezone Field Type


The timezone type is a subset of the ChoiceType that allows the user to select from all possible timezones. The "value" for each timezone is the full timezone name, such as America/Chicago or Europe/Istanbul. Unlike the choice type, you don't need to specify a choices or choice_list option as the field type automatically uses a large list of locales. You can specify either of these options manually, but then you should just use the choice type directly. Rendered as Inherited options can be various tags (see Select tag, Checkboxes or Radio Buttons) multiple expanded preferred_choices empty_value error_bubbling required label read_only

Parent type Class

choice

TimezoneType1

Inherited options
These options inherit from the choice type:

multiple
type: Boolean default: false
1. http://api.symfony.com/2.1/Symfony/Component/Form/Extension/Core/Type/TimezoneType.html

PDF brought to you by generated on October 26, 2012

Chapter 188: timezone Field Type | 825

If true, the user will be able to select multiple options (as opposed to choosing just one option). Depending on the value of the expanded option, this will render either a select tag or checkboxes if true and a select tag or radio buttons if false. The returned value will be an array.

expanded
type: Boolean default: false If set to true, radio buttons or checkboxes will be rendered (depending on the multiple value). If false, a select element will be rendered.

preferred_choices
type: array default: array() If this option is specified, then a sub-set of all of the options will be moved to the top of the select menu. The following would move the "Baz" option to the top, with a visual separator between it and the rest of the options:
Listing 188-1

1 $builder->add('foo_choices', 'choice', array( 2 'choices' => array('foo' => 'Foo', 'bar' => 'Bar', 'baz' => 'Baz'), 3 'preferred_choices' => array('baz'), 4 ));

Note that preferred choices are only meaningful when rendering as a select element (i.e. expanded is false). The preferred choices and normal choices are separated visually by a set of dotted lines (i.e. ------------------). This can be customized when rendering the field:
Listing 188-2

1 {{ form_widget(form.foo_choices, { 'separator': '=====' }) }}

empty_value
type: string or Boolean This option determines whether or not a special "empty" option (e.g. "Choose an option") will appear at the top of a select widget. This option only applies if both the expanded and multiple options are set to false. Add an empty value with "Choose an option" as the text:
Listing 188-3

1 $builder->add('states', 'choice', array( 2 'empty_value' => 'Choose an option', 3 ));

Guarantee that no "empty" value option is displayed:


Listing 188-4

1 $builder->add('states', 'choice', array( 2 'empty_value' => false, 3 ));

If you leave the empty_value option unset, then a blank (with no text) option will automatically be added if and only if the required option is false:
Listing 188-5

PDF brought to you by generated on October 26, 2012

Chapter 188: timezone Field Type | 826

1 // a blank (with no text) option will be added 2 $builder->add('states', 'choice', array( 3 'required' => false, 4 ));

These options inherit from the field type:

required
type: Boolean default: true If true, an HTML5 required attribute2 will be rendered. The corresponding label will also render with a required class. This is superficial and independent from validation. At best, if you let Symfony guess your field type, then the value of this option will be guessed from your validation information.

label
type: string default: The label is "guessed" from the field name Sets the label that will be used when rendering the field. The label can also be directly set inside the template:
Listing 188-6

1 {{ form_label(form.name, 'Your name') }}

read_only
type: Boolean default: false If this option is true, the field will be rendered with the disabled attribute so that the field is not editable.

error_bubbling
type: Boolean default: false If true, any errors for this field will be passed to the parent field or form. For example, if set to true on a normal field, any errors for that field will be attached to the main form, not to the specific field.

2. http://diveintohtml5.info/forms.html

PDF brought to you by generated on October 26, 2012

Chapter 188: timezone Field Type | 827

Chapter 189

url Field Type


The url field is a text field that prepends the submitted value with a given protocol (e.g. http://) if the submitted value doesn't already have a protocol. Rendered as Options Inherited options input url field default_protocol text max_length required label trim read_only error_bubbling

Parent type Class

UrlType1

Field Options
default_protocol
type: string default: http If a value is submitted that doesn't begin with some protocol (e.g. http://, ftp://, etc), this protocol will be prepended to the string when the data is bound to the form.

1. http://api.symfony.com/2.1/Symfony/Component/Form/Extension/Core/Type/UrlType.html

PDF brought to you by generated on October 26, 2012

Chapter 189: url Field Type | 828

Inherited Options
These options inherit from the field type:

max_length
type: integer This option is used to add a max_length attribute, which is used by some browsers to limit the amount of text in a field.

required
type: Boolean default: true If true, an HTML5 required attribute2 will be rendered. The corresponding label will also render with a required class. This is superficial and independent from validation. At best, if you let Symfony guess your field type, then the value of this option will be guessed from your validation information.

label
type: string default: The label is "guessed" from the field name Sets the label that will be used when rendering the field. The label can also be directly set inside the template:
Listing 189-1

1 {{ form_label(form.name, 'Your name') }}

trim
type: Boolean default: true If true, the whitespace of the submitted string value will be stripped via the trim() function when the data is bound. This guarantees that if a value is submitted with extra whitespace, it will be removed before the value is merged back onto the underlying object.

read_only
type: Boolean default: false If this option is true, the field will be rendered with the disabled attribute so that the field is not editable.

error_bubbling
type: Boolean default: false If true, any errors for this field will be passed to the parent field or form. For example, if set to true on a normal field, any errors for that field will be attached to the main form, not to the specific field.

2. http://diveintohtml5.info/forms.html

PDF brought to you by generated on October 26, 2012

Chapter 189: url Field Type | 829

Chapter 190

Twig Template Form Function Reference


This reference manual covers all the possible Twig functions available for rendering forms. There are several different functions available, and each is responsible for rendering a different part of a form (e.g. labels, errors, widgets, etc).

form_label(form.name, label, variables)


Renders the label for the given field. You can optionally pass the specific label you want to display as the second argument.
Listing 190-1

1 2 3 4 5

{{ form_label(form.name) }}

{# The two following syntaxes are equivalent #} {{ form_label(form.name, 'Your Name', {'label_attr': {'class': 'foo'}}) }} {{ form_label(form.name, null, {'label': 'Your name', 'label_attr': {'class': 'foo'}}) }}

See "More about Form "Variables"" to learn about the variables argument.

form_errors(form.name)
Renders any errors for the given field.
Listing 190-2

1 {{ form_errors(form.name) }} 2 3 {# render any "global" errors #} 4 {{ form_errors(form) }}

PDF brought to you by generated on October 26, 2012

Chapter 190: Twig Template Form Function Reference | 830

form_widget(form.name, variables)
Renders the HTML widget of a given field. If you apply this to an entire form or collection of fields, each underlying form row will be rendered.
Listing 190-3

1 {# render a widget, but add a "foo" class to it #} 2 {{ form_widget(form.name, {'attr': {'class': 'foo'}}) }}

The second argument to form_widget is an array of variables. The most common variable is attr, which is an array of HTML attributes to apply to the HTML widget. In some cases, certain types also have other template-related options that can be passed. These are discussed on a type-by-type basis. The attributes are not applied recursively to child fields if you're rendering many fields at once (e.g. form_widget(form)). See "More about Form "Variables"" to learn more about the variables argument.

form_row(form.name, variables)
Renders the "row" of a given field, which is the combination of the field's label, errors and widget.
Listing 190-4

1 {# render a field row, but display a label with text "foo" #} 2 {{ form_row(form.name, {'label': 'foo'}) }}

The second argument to form_row is an array of variables. The templates provided in Symfony only allow to override the label as shown in the example above. See "More about Form "Variables"" to learn about the variables argument.

form_rest(form, variables)
This renders all fields that have not yet been rendered for the given form. It's a good idea to always have this somewhere inside your form as it'll render hidden fields for you and make any fields you forgot to render more obvious (since it'll render the field for you).
Listing 190-5

1 {{ form_rest(form) }}

form_enctype(form)
If the form contains at least one file upload field, this will render the required enctype="multipart/ form-data" form attribute. It's always a good idea to include this in your form tag:
Listing 190-6

1 <form action="{{ path('form_submit') }}" method="post" {{ form_enctype(form) }}>

More about Form "Variables"


In almost every Twig function above, the final argument is an array of "variables" that are used when rendering that one part of the form. For example, the following would render the "widget" for a field, and modify its attributes to include a special class:
PDF brought to you by generated on October 26, 2012 Chapter 190: Twig Template Form Function Reference | 831

Listing 190-7

1 {# render a widget, but add a "foo" class to it #} 2 {{ form_widget(form.name, { 'attr': {'class': 'foo'} }) }}

The purpose of these variables - what they do & where they come from - may not be immediately clear, but they're incredibly powerful. Whenever you render any part of a form, the block that renders it makes use of a number of variables. By default, these blocks live inside form_div_layout.html.twig1. Look at the form_label as an example:
Listing 190-8

1 {% block form_label %} 2 {% if not compound %} 3 {% set label_attr = label_attr|merge({'for': id}) %} 4 {% endif %} 5 {% if required %} 6 {% set label_attr = label_attr|merge({'class': (label_attr.class|default('') ~ ' 7 required')|trim}) %} 8 {% endif %} 9 {% if label is empty %} 10 {% set label = name|humanize %} 11 {% endif %} 12 <label{% for attrname, attrvalue in label_attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %}>{{ label|trans({}, translation_domain) }}</label> {% endblock form_label %}

This block makes use of several variables: compound, label_attr, required, label, name and translation_domain. These variables are made available by the form rendering system. But more importantly, these are the variables that you can override when calling form_label (since in this example, you're rendering the label). The exact variables available to override depends on which part of the form you're rendering (e.g. label versus widget) and which field you're rendering (e.g. a choice widget has an extra expanded option). If you get comfortable with looking through form_div_layout.html.twig2, you'll always be able to see what options you have available.
Behind the scenes, these variables are made available to the FormView object of your form when the form component calls buildView and buildViewBottomUp on each "node" of your form tree. To see what "view" variables a particularly field has, find the source code for the form field (and its parent fields) and look at the above two functions.

If you're rendering an entire form at once (or an entire embedded form), the variables argument will only be applied to the form itself and not its children. In other words, the following will not pass a "foo" class attribute to all of the child fields in the form:
Listing 190-9

1 {# does **not** work - the variables are not recursive #} 2 {{ form_widget(form, { 'attr': {'class': 'foo'} }) }}

1. https://github.com/symfony/symfony/blob/2.1/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig 2. https://github.com/symfony/symfony/blob/2.1/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig

PDF brought to you by generated on October 26, 2012

Chapter 190: Twig Template Form Function Reference | 832

Chapter 191

Validation Constraints Reference


The Validator is designed to validate objects against constraints. 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.

Supported Constraints
The following constraints are natively available in Symfony2:

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 Length Url Regex Ip

PDF brought to you by generated on October 26, 2012

Chapter 191: Validation Constraints Reference | 833

Number Constraints
Max Min Range

Date Constraints
Date DateTime Time

Collection Constraints
Choice Collection Count UniqueEntity Language Locale Country

File Constraints
File Image

Other Constraints
Callback All UserPassword Valid

PDF brought to you by generated on October 26, 2012

Chapter 191: Validation Constraints Reference | 834

Chapter 192

NotBlank
Validates that a value is not blank, defined as not equal to a blank string and also not equal to null. To force that a value is simply not equal to null, see the NotNull constraint. Applies to property or method Options Class Validator message

NotBlank1 NotBlankValidator2

Basic Usage
If you wanted to ensure that the firstName property of an Author class were not blank, you could do the following:
Listing 192-1

1 properties: 2 firstName: 3 - NotBlank: ~

Options
message
type: string default: This value should not be blank This is the message that will be shown if the value is blank.

1. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/NotBlank.html 2. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/NotBlankValidator.html

PDF brought to you by generated on October 26, 2012

Chapter 192: NotBlank | 835

Chapter 193

Blank
Validates that a value is blank, defined as equal to a blank string or equal to null. To force that a value strictly be equal to null, see the Null constraint. To force that a value is not blank, see NotBlank. Applies to property or method Options Class Validator message

Blank1 BlankValidator2

Basic Usage
If, for some reason, you wanted to ensure that the firstName property of an Author class were blank, you could do the following:
Listing 193-1

1 properties: 2 firstName: 3 - Blank: ~

Options
message
type: string default: This value should be blank This is the message that will be shown if the value is not blank.

1. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/Blank.html 2. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/BlankValidator.html

PDF brought to you by generated on October 26, 2012

Chapter 193: Blank | 836

Chapter 194

NotNull
Validates that a value is not strictly equal to null. To ensure that a value is simply not blank (not a blank string), see the NotBlank constraint. Applies to property or method Options Class Validator message

NotNull1 NotNullValidator2

Basic Usage
If you wanted to ensure that the firstName property of an Author class were not strictly equal to null, you would:
Listing 194-1

1 properties: 2 firstName: 3 - NotNull: ~

Options
message
type: string default: This value should not be null This is the message that will be shown if the value is null.

1. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/NotNull.html 2. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/NotNullValidator.html

PDF brought to you by generated on October 26, 2012

Chapter 194: NotNull | 837

Chapter 195

Null
Validates that a value is exactly equal to null. To force that a property is simply blank (blank string or null), see the Blank constraint. To ensure that a property is not null, see NotNull. Applies to property or method Options Class Validator message

Null1 NullValidator2

Basic Usage
If, for some reason, you wanted to ensure that the firstName property of an Author class exactly equal to null, you could do the following:
Listing 195-1

1 # src/Acme/BlogBundle/Resources/config/validation.yml 2 Acme\BlogBundle\Entity\Author: 3 properties: 4 firstName: 5 - Null: ~

Options
message
type: string default: This value should be null
1. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/Null.html 2. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/NullValidator.html

PDF brought to you by generated on October 26, 2012

Chapter 195: Null | 838

This is the message that will be shown if the value is not null.

PDF brought to you by generated on October 26, 2012

Chapter 195: Null | 839

Chapter 196

True
Validates that a value is true. Specifically, this checks to see if the value is exactly true, exactly the integer 1, or exactly the string "1". Also see False. Applies to property or method Options Class Validator message

True1 TrueValidator2

Basic Usage
This constraint can be applied to properties (e.g. a termsAccepted property on a registration model) or to a "getter" method. It's most powerful in the latter case, where you can assert that a method returns a true value. For example, suppose you have the following method:
Listing 196-1

1 2 3 4 5 6 7 8 9 10

// src/Acme/BlogBundle/Entity/Author.php namespace Acme\BlogBundle\Entity;


class Author { protected $token; public function isTokenValid() { return $this->token == $this->generateToken();

1. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/True.html 2. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/TrueValidator.html

PDF brought to you by generated on October 26, 2012

Chapter 196: True | 840

11 12 }

Then you can constrain this method with True.


Listing 196-2

1 # src/Acme/BlogBundle/Resources/config/validation.yml 2 Acme\BlogBundle\Entity\Author: 3 getters: 4 tokenValid: 5 - "True": { message: "The token is invalid" }

If the isTokenValid() returns false, the validation will fail.

Options
message
type: string default: This value should be true This message is shown if the underlying data is not true.

PDF brought to you by generated on October 26, 2012

Chapter 196: True | 841

Chapter 197

False
Validates that a value is false. Specifically, this checks to see if the value is exactly false, exactly the integer 0, or exactly the string "0". Also see True. Applies to property or method Options Class Validator message

False1 FalseValidator2

Basic Usage
The False constraint can be applied to a property or a "getter" method, but is most commonly useful in the latter case. For example, suppose that you want to guarantee that some state property is not in a dynamic invalidStates array. First, you'd create a "getter" method:
Listing 197-1

1 2 3 4 5 6 7 8

protected $state; protected $invalidStates = array(); public function isStateInvalid() { return in_array($this->state, $this->invalidStates); }

In this case, the underlying object is only valid if the isStateInvalid method returns false:
Listing 197-2

1. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/False.html 2. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/FalseValidator.html

PDF brought to you by generated on October 26, 2012

Chapter 197: False | 842

1 # src/BlogBundle/Resources/config/validation.yml 2 Acme\BlogBundle\Entity\Author 3 getters: 4 stateInvalid: 5 - "False": 6 message: You've entered an invalid state.

When using YAML, be sure to surround False with quotes ("False") or else YAML will convert this into a Boolean value.

Options
message
type: string default: This value should be false This message is shown if the underlying data is not false.

PDF brought to you by generated on October 26, 2012

Chapter 197: False | 843

Chapter 198

Type
Validates that a value is of a specific data type. For example, if a variable should be an array, you can use this constraint with the array type option to validate this. Applies to property or method Options type message

Class Validator

Type1 TypeValidator2

Basic Usage
Listing 198-1

1 # src/BlogBundle/Resources/config/validation.yml 2 Acme\BlogBundle\Entity\Author: 3 properties: 4 age: 5 - Type: 6 type: integer 7 message: The value {{ value }} is not a valid {{ type }}.

1. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/Type.html 2. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/TypeValidator.html

PDF brought to you by generated on October 26, 2012

Chapter 198: Type | 844

Options
type
type: string [default option] This required option is the fully qualified class name or one of the PHP datatypes as determined by PHP's is_ functions. array3 bool4 callable5 float6 double7 int8 integer9 long10 null11 numeric12 object13 real14 resource15 scalar16 string17

message
type: string default: This value should be of type {{ type }} The message if the underlying data is not of the given type.

3. http://php.net/is_array 4. http://php.net/is_bool 5. http://php.net/is_callable 6. http://php.net/is_float 7. http://php.net/is_double 8. http://php.net/is_int 9. http://php.net/is_integer 10. http://php.net/is_long 11. http://php.net/is_null 12. http://php.net/is_numeric 13. http://php.net/is_object 14. http://php.net/is_real 15. http://php.net/is_resource 16. http://php.net/is_scalar 17. http://php.net/is_string

PDF brought to you by generated on October 26, 2012

Chapter 198: Type | 845

Chapter 199

Email
Validates that a value is a valid email address. The underlying value is cast to a string before being validated. Applies to property or method Options message checkMX checkHost

Class Validator

Email1 EmailValidator2

Basic Usage
Listing 199-1

1 # src/BlogBundle/Resources/config/validation.yml 2 Acme\BlogBundle\Entity\Author: 3 properties: 4 email: 5 - Email: 6 message: The email "{{ value }}" is not a valid email. 7 checkMX: true

1. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/Email.html 2. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/EmailValidator.html

PDF brought to you by generated on October 26, 2012

Chapter 199: Email | 846

Options
message
type: string default: This value is not a valid email address This message is shown if the underlying data is not a valid email address.

checkMX
type: Boolean default: false If true, then the checkdnsrr3 PHP function will be used to check the validity of the MX record of the host of the given email.

checkHost
New in version 2.1: The checkHost option was added in Symfony 2.1

type: Boolean default: false If true, then the checkdnsrr4 PHP function will be used to check the validity of the MX or the A or the AAAA record of the host of the given email.

3. http://php.net/manual/en/function.checkdnsrr.php 4. http://php.net/manual/en/function.checkdnsrr.php

PDF brought to you by generated on October 26, 2012

Chapter 199: Email | 847

Chapter 200

MinLength
Validates that the length of a string is at least as long as the given limit. Applies to property or method Options limit message charset

Class Validator

MinLength1 MinLengthValidator2

Basic Usage
Listing 200-1

1 # src/Acme/BlogBundle/Resources/config/validation.yml 2 Acme\BlogBundle\Entity\Blog: 3 properties: 4 firstName: 5 - MinLength: { limit: 3, message: "Your name must have at least {{ limit }} characters." }

Options
limit
type: integer [default option]
1. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/MinLength.html 2. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/MinLengthValidator.html

PDF brought to you by generated on October 26, 2012

Chapter 200: MinLength | 848

This required option is the "min" value. Validation will fail if the length of the give string is less than this number.

message
type: string default: This value is too short. It should have {{ limit }} characters or more The message that will be shown if the underlying string has a length that is shorter than the limit option.

charset
type: charset default: UTF-8 If the PHP extension "mbstring" is installed, then the PHP function mb_strlen3 will be used to calculate the length of the string. The value of the charset option is passed as the second argument to that function.

3. http://php.net/manual/en/function.mb-strlen.php

PDF brought to you by generated on October 26, 2012

Chapter 200: MinLength | 849

Chapter 201

MaxLength
Validates that the length of a string is not larger than the given limit. Applies to property or method Options limit message charset

Class Validator

MaxLength1 MaxLengthValidator2

Basic Usage
Listing 201-1

1 # src/Acme/BlogBundle/Resources/config/validation.yml 2 Acme\BlogBundle\Entity\Blog: 3 properties: 4 summary: 5 - MaxLength: 100

Options
limit
type: integer [default option]

1. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/MaxLength.html 2. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/MaxLengthValidator.html

PDF brought to you by generated on October 26, 2012

Chapter 201: MaxLength | 850

This required option is the "max" value. Validation will fail if the length of the give string is greater than this number.

message
type: string default: This value is too long. It should have {{ limit }} characters or less The message that will be shown if the underlying string has a length that is longer than the limit option.

charset
type: charset default: UTF-8 If the PHP extension "mbstring" is installed, then the PHP function mb_strlen3 will be used to calculate the length of the string. The value of the charset option is passed as the second argument to that function.

3. http://php.net/manual/en/function.mb-strlen.php

PDF brought to you by generated on October 26, 2012

Chapter 201: MaxLength | 851

Chapter 202

Length
Validates that a given string length is between some minimum and maximum value.
New in version 2.1: The Length constraint was added in Symfony 2.1.

Applies to property or method Options min max charset minMessage maxMessage exactMessage

Class Validator

Length1 LengthValidator2

Basic Usage
To verify that the firstName field length of a class is between "2" and "50", you might add the following:
Listing 202-1

1 # src/Acme/EventBundle/Resources/config/validation.yml 2 Acme\EventBundle\Entity\Participant: 3 properties: 4 firstName: 5 - Length:

1. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/Length.html 2. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/LengthValidator.html

PDF brought to you by generated on October 26, 2012

Chapter 202: Length | 852

6 7 8 9

min: 2 max: 50 minMessage: Your first name must be at least 2 characters length maxMessage: Your first name cannot be longer than than 50 characters length

Options
min
type: integer [default option] This required option is the "min" length value. Validation will fail if the given value's length is less than this min value.

max
type: integer [default option] This required option is the "max" length value. Validation will fail if the given value's length is greater than this max value.

charset
type: string default: UTF-8 The charset to be used when computing value's length. The grapheme_strlen3 PHP function is used if available. If not, the the mb_strlen4 PHP function is used if available. If neither are available, the strlen5 PHP function is used.

minMessage
type: string default: This value is too short. It should have {{ limit }} characters or more.. The message that will be shown if the underlying value's length is less than the min option.

maxMessage
type: string default: This value is too long. It should have {{ limit }} characters or less.. The message that will be shown if the underlying value's length is more than the max option.

exactMessage
type: string default: This value should have exactly {{ limit }} characters.. The message that will be shown if min and max values are equal and the underlying value's length is not exactly this value.

3. http://php.net/manual/en/function.grapheme-strlen.php 4. http://php.net/manual/en/function.mb-strlen.php 5. http://php.net/manual/en/function.strlen.php

PDF brought to you by generated on October 26, 2012

Chapter 202: Length | 853

Chapter 203

Url
Validates that a value is a valid URL string. Applies to property or method Options message protocols

Class Validator

Url1 UrlValidator2

Basic Usage
Listing 203-1

1 # src/BlogBundle/Resources/config/validation.yml 2 Acme\BlogBundle\Entity\Author: 3 properties: 4 bioUrl: 5 - Url:

Options
message
type: string default: This value is not a valid URL This message is shown if the URL is invalid.

1. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/Url.html 2. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/UrlValidator.html

PDF brought to you by generated on October 26, 2012

Chapter 203: Url | 854

protocols
type: array default: array('http', 'https') The protocols that will be considered to be valid. For example, if you also needed ftp:// type URLs to be valid, you'd redefine the protocols array, listing http, https, and also ftp.

PDF brought to you by generated on October 26, 2012

Chapter 203: Url | 855

Chapter 204

Regex
Validates that a value matches a regular expression. Applies to property or method Options pattern match message

Class Validator

Regex1 RegexValidator2

Basic Usage
Suppose you have a description field and you want to verify that it begins with a valid word character. The regular expression to test for this would be /^\w+/, indicating that you're looking for at least one or more word characters at the beginning of your string:
Listing 204-1

# src/Acme/BlogBundle/Resources/config/validation.yml Acme\BlogBundle\Entity\Author: properties: description: - Regex: "/^\w+/"

Alternatively, you can set the match option to false in order to assert that a given string does not match. In the following example, you'll assert that the firstName field does not contain any numbers and give it a custom message:
Listing 204-2

# src/Acme/BlogBundle/Resources/config/validation.yml Acme\BlogBundle\Entity\Author:

1. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/Regex.html 2. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/RegexValidator.html

PDF brought to you by generated on October 26, 2012

Chapter 204: Regex | 856

properties: firstName: - Regex: pattern: "/\d/" match: false message: Your name cannot contain a number

Options
pattern
type: string [default option] This required option is the regular expression pattern that the input will be matched against. By default, this validator will fail if the input string does not match this regular expression (via the preg_match3 PHP function). However, if match is set to false, then validation will fail if the input string does match this pattern.

match
type: Boolean default: true If true (or not set), this validator will pass if the given string matches the given pattern regular expression. However, when this option is set to false, the opposite will occur: validation will pass only if the given string does not match the pattern regular expression.

message
type: string default: This value is not valid This is the message that will be shown if this validator fails.

3. http://php.net/manual/en/function.preg-match.php

PDF brought to you by generated on October 26, 2012

Chapter 204: Regex | 857

Chapter 205

Ip
Validates that a value is a valid IP address. By default, this will validate the value as IPv4, but a number of different options exist to validate as IPv6 and many other combinations. Applies to property or method Options version message

Class Validator

Ip1 IpValidator2

Basic Usage
Listing 205-1

1 # src/BlogBundle/Resources/config/validation.yml 2 Acme\BlogBundle\Entity\Author: 3 properties: 4 ipAddress: 5 - Ip:

Options
version
type: string default: 4 This determines exactly how the ip address is validated and can take one of a variety of different values:
1. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/Ip.html 2. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/IpValidator.html

PDF brought to you by generated on October 26, 2012

Chapter 205: Ip | 858

All ranges 4 - Validates for IPv4 addresses 6 - Validates for IPv6 addresses all - Validates all IP formats No private ranges 4_no_priv - Validates for IPv4 but without private IP ranges 6_no_priv - Validates for IPv6 but without private IP ranges all_no_priv - Validates for all IP formats but without private IP ranges No reserved ranges 4_no_res - Validates for IPv4 but without reserved IP ranges 6_no_res - Validates for IPv6 but without reserved IP ranges all_no_res - Validates for all IP formats but without reserved IP ranges Only public ranges 4_public - Validates for IPv4 but without private and reserved ranges 6_public - Validates for IPv6 but without private and reserved ranges all_public - Validates for all IP formats but without private and reserved ranges

message
type: string default: This is not a valid IP address This message is shown if the string is not a valid IP address.

PDF brought to you by generated on October 26, 2012

Chapter 205: Ip | 859

Chapter 206

Max
Validates that a given number is less than some maximum number. Applies to property or method Options limit message invalidMessage

Class Validator

Max1 MaxValidator2

Basic Usage
To verify that the "age" field of a class is not greater than "50", you might add the following:
Listing 206-1

1 # src/Acme/EventBundle/Resources/config/validation.yml 2 Acme\EventBundle\Entity\Participant: 3 properties: 4 age: 5 - Max: { limit: 50, message: You must be 50 or under to enter. }

Options
limit
type: integer [default option]
1. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/Max.html 2. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/MaxValidator.html

PDF brought to you by generated on October 26, 2012

Chapter 206: Max | 860

This required option is the "max" value. Validation will fail if the given value is greater than this max value.

message
type: string default: This value should be {{ limit }} or less The message that will be shown if the underlying value is greater than the limit option.

invalidMessage
type: string default: This value should be a valid number The message that will be shown if the underlying value is not a number (per the is_numeric3 PHP function).

3. http://www.php.net/manual/en/function.is-numeric.php

PDF brought to you by generated on October 26, 2012

Chapter 206: Max | 861

Chapter 207

Min
Validates that a given number is greater than some minimum number. Applies to property or method Options limit message invalidMessage

Class Validator

Min1 MinValidator2

Basic Usage
To verify that the "age" field of a class is "18" or greater, you might add the following:
Listing 207-1

1 # src/Acme/EventBundle/Resources/config/validation.yml 2 Acme\EventBundle\Entity\Participant: 3 properties: 4 age: 5 - Min: { limit: 18, message: You must be 18 or older to enter. }

Options
limit
type: integer [default option]
1. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/Min.html 2. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/MinValidator.html

PDF brought to you by generated on October 26, 2012

Chapter 207: Min | 862

This required option is the "min" value. Validation will fail if the given value is less than this min value.

message
type: string default: This value should be {{ limit }} or more The message that will be shown if the underlying value is less than the limit option.

invalidMessage
type: string default: This value should be a valid number The message that will be shown if the underlying value is not a number (per the is_numeric3 PHP function).

3. http://www.php.net/manual/en/function.is-numeric.php

PDF brought to you by generated on October 26, 2012

Chapter 207: Min | 863

Chapter 208

Range
Validates that a given number is between some minimum and maximum number.
New in version 2.1: The Range constraint was added in Symfony 2.1.

Applies to property or method Options min max minMessage maxMessage invalidMessage

Class Validator

Range1 RangeValidator2

Basic Usage
To verify that the "height" field of a class is between "120" and "180", you might add the following:
Listing 208-1

1 # src/Acme/EventBundle/Resources/config/validation.yml 2 Acme\EventBundle\Entity\Participant: 3 properties: 4 height: 5 - Range: 6 min: 120

1. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/Range.html 2. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/RangeValidator.html

PDF brought to you by generated on October 26, 2012

Chapter 208: Range | 864

7 8 9

max: 180 minMessage: You must be at least 120cm tall to enter maxMessage: You cannot be taller than 180cm to enter

Options
min
type: integer [default option] This required option is the "min" value. Validation will fail if the given value is less than this min value.

max
type: integer [default option] This required option is the "max" value. Validation will fail if the given value is greater than this max value.

minMessage
type: string default: This value should be {{ limit }} or more. The message that will be shown if the underlying value is less than the min option.

maxMessage
type: string default: This value should be {{ limit }} or less. The message that will be shown if the underlying value is more than the max option.

invalidMessage
type: string default: This value should be a valid number. The message that will be shown if the underlying value is not a number (per the is_numeric3 PHP function).

3. http://www.php.net/manual/en/function.is-numeric.php

PDF brought to you by generated on October 26, 2012

Chapter 208: Range | 865

Chapter 209

Date
Validates that a value is a valid date, meaning either a DateTime object or a string (or an object that can be cast into a string) that follows a valid YYYY-MM-DD format. Applies to property or method Options Class Validator message

Date1 DateValidator2

Basic Usage
Listing 209-1

1 # src/Acme/BlogBundle/Resources/config/validation.yml 2 Acme\BlogBundle\Entity\Author: 3 properties: 4 birthday: 5 - Date: ~

Options
message
type: string default: This value is not a valid date This message is shown if the underlying data is not a valid date.

1. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/Date.html 2. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/DateValidator.html

PDF brought to you by generated on October 26, 2012

Chapter 209: Date | 866

Chapter 210

DateTime
Validates that a value is a valid "datetime", meaning either a DateTime object or a string (or an object that can be cast into a string) that follows a valid YYYY-MM-DD HH:MM:SS format. Applies to property or method Options Class Validator message

DateTime1 DateTimeValidator2

Basic Usage
Listing 210-1

1 # src/Acme/BlogBundle/Resources/config/validation.yml 2 Acme\BlogBundle\Entity\Author: 3 properties: 4 createdAt: 5 - DateTime: ~

Options
message
type: string default: This value is not a valid datetime This message is shown if the underlying data is not a valid datetime.

1. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/DateTime.html 2. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/DateTimeValidator.html

PDF brought to you by generated on October 26, 2012

Chapter 210: DateTime | 867

Chapter 211

Time
Validates that a value is a valid time, meaning either a DateTime object or a string (or an object that can be cast into a string) that follows a valid "HH:MM:SS" format. Applies to property or method Options Class Validator message

Time1 TimeValidator2

Basic Usage
Suppose you have an Event class, with a startAt field that is the time of the day when the event starts:
Listing 211-1

1 # src/Acme/EventBundle/Resources/config/validation.yml 2 Acme\EventBundle\Entity\Event: 3 properties: 4 startsAt: 5 - Time: ~

Options
message
type: string default: This value is not a valid time This message is shown if the underlying data is not a valid time.
1. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/Time.html 2. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/TimeValidator.html

PDF brought to you by generated on October 26, 2012

Chapter 211: Time | 868

Chapter 212

Choice
This constraint is used to ensure that the given value is one of a given set of valid choices. It can also be used to validate that each item in an array of items is one of those valid choices. Applies to property or method Options choices callback multiple min max message multipleMessage minMessage maxMessage strict

Class Validator

Choice1 ChoiceValidator2

Basic Usage
The basic idea of this constraint is that you supply it with an array of valid values (this can be done in several ways) and it validates that the value of the given property exists in that array. If your valid choice list is simple, you can pass them in directly via the choices option:
Listing 212-1

1 # src/Acme/BlogBundle/Resources/config/validation.yml 2 Acme\BlogBundle\Entity\Author:

1. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/Choice.html 2. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/ChoiceValidator.html

PDF brought to you by generated on October 26, 2012

Chapter 212: Choice | 869

3 4 5 6 7

properties: gender: - Choice: choices: message:

[male, female] Choose a valid gender.

Supplying the Choices with a Callback Function


You can also use a callback function to specify your options. This is useful if you want to keep your choices in some central location so that, for example, you can easily access those choices for validation or for building a select form element.
Listing 212-2

1 2 3 4 5 6 7 8

// src/Acme/BlogBundle/Entity/Author.php class Author { public static function getGenders() { return array('male', 'female'); } }

You can pass the name of this method to the callback_ option of the Choice constraint.
Listing 212-3

1 # src/Acme/BlogBundle/Resources/config/validation.yml 2 Acme\BlogBundle\Entity\Author: 3 properties: 4 gender: 5 - Choice: { callback: getGenders }

If the static callback is stored in a different class, for example Util, you can pass the class name and the method as an array.
Listing 212-4

1 # src/Acme/BlogBundle/Resources/config/validation.yml 2 Acme\BlogBundle\Entity\Author: 3 properties: 4 gender: 5 - Choice: { callback: [Util, getGenders] }

Available Options
choices
type: array [default option] A required option (unless callback is specified) - this is the array of options that should be considered in the valid set. The input value will be matched against this array.

callback
type: string|array|Closure

PDF brought to you by generated on October 26, 2012

Chapter 212: Choice | 870

This is a callback method that can be used instead of the choices option to return the choices array. See Supplying the Choices with a Callback Function for details on its usage.

multiple
type: Boolean default: false If this option is true, the input value is expected to be an array instead of a single, scalar value. The constraint will check that each value of the input array can be found in the array of valid choices. If even one of the input values cannot be found, the validation will fail.

min
type: integer If the multiple option is true, then you can use the min option to force at least XX number of values to be selected. For example, if min is 3, but the input array only contains 2 valid items, the validation will fail.

max
type: integer If the multiple option is true, then you can use the max option to force no more than XX number of values to be selected. For example, if max is 3, but the input array contains 4 valid items, the validation will fail.

message
type: string default: The value you selected is not a valid choice This is the message that you will receive if the multiple option is set to false, and the underlying value is not in the valid array of choices.

multipleMessage
type: string default: One or more of the given values is invalid This is the message that you will receive if the multiple option is set to true, and one of the values on the underlying array being checked is not in the array of valid choices.

minMessage
type: string default: You must select at least {{ limit }} choices This is the validation error message that's displayed when the user chooses too few choices per the min option.

maxMessage
type: string default: You must select at most {{ limit }} choices This is the validation error message that's displayed when the user chooses too many options per the max option.

strict
type: Boolean default: false
PDF brought to you by generated on October 26, 2012 Chapter 212: Choice | 871

If true, the validator will also check the type of the input value. Specifically, this value is passed to as the third argument to the PHP in_array3 method when checking to see if a value is in the valid choices array.

3. http://php.net/manual/en/function.in-array.php

PDF brought to you by generated on October 26, 2012

Chapter 212: Choice | 872

Chapter 213

Collection
This constraint is used when the underlying data is a collection (i.e. an array or an object that implements Traversable and ArrayAccess), but you'd like to validate different keys of that collection in different ways. For example, you might validate the email key using the Email constraint and the inventory key of the collection with the Min constraint. This constraint can also make sure that certain collection keys are present and that extra keys are not present. Applies to property or method Options fields allowExtraFields extraFieldsMessage allowMissingFields missingFieldsMessage

Class Validator

Collection1 CollectionValidator2

Basic Usage
The Collection constraint allows you to validate the different keys of a collection individually. Take the following example:
Listing 213-1

1 namespace Acme\BlogBundle\Entity; 2 3 class Author 4 {

1. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/Collection.html 2. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/CollectionValidator.html

PDF brought to you by generated on October 26, 2012

Chapter 213: Collection | 873

5 6 7 8 9 10 11 12 13 14 }

protected $profileData = array( 'personal_email', 'short_bio', ); public function setProfileData($key, $value) { $this->profileData[$key] = $value; }

To validate that the personal_email element of the profileData array property is a valid email address and that the short_bio element is not blank but is no longer than 100 characters in length, you would do the following:
Listing 213-2

1 properties: 2 profileData: 3 - Collection: 4 fields: 5 personal_email: Email 6 short_bio: 7 - NotBlank 8 - MaxLength: 9 limit: 100 10 message: Your short bio is too long! 11 allowMissingfields: true

Presence and Absence of Fields


By default, this constraint validates more than simply whether or not the individual fields in the collection pass their assigned constraints. In fact, if any keys of a collection are missing or if there are any unrecognized keys in the collection, validation errors will be thrown. If you would like to allow for keys to be absent from the collection or if you would like "extra" keys to be allowed in the collection, you can modify the allowMissingFields and allowExtraFields options respectively. In the above example, the allowMissingFields option was set to true, meaning that if either of the personal_email or short_bio elements were missing from the $personalData property, no validation error would occur.

Options
fields
type: array [default option] This option is required, and is an associative array defining all of the keys in the collection and, for each key, exactly which validator(s) should be executed against that element of the collection.

allowExtraFields
type: Boolean default: false If this option is set to false and the underlying collection contains one or more elements that are not included in the fields option, a validation error will be returned. If set to true, extra fields are ok.

PDF brought to you by generated on October 26, 2012

Chapter 213: Collection | 874

extraFieldsMessage
type: Boolean default: The fields {{ fields }} were not expected The message shown if allowExtraFields is false and an extra field is detected.

allowMissingFields
type: Boolean default: false If this option is set to false and one or more fields from the fields option are not present in the underlying collection, a validation error will be returned. If set to true, it's ok if some fields in the fields_ option are not present in the underlying collection.

missingFieldsMessage
type: Boolean default: The fields {{ fields }} are missing The message shown if allowMissingFields is false and one or more fields are missing from the underlying collection.

PDF brought to you by generated on October 26, 2012

Chapter 213: Collection | 875

Chapter 214

Count
Validates that a given collection's (i.e. an array or an object that implements Countable) element count is between some minimum and maximum value.
New in version 2.1: The Count constraint was added in Symfony 2.1.

Applies to property or method Options min max minMessage maxMessage exactMessage

Class Validator

Count1 CountValidator2

Basic Usage
To verify that the emails array field contains between 1 and 5 elements you might add the following:
Listing 214-1

1 # src/Acme/EventBundle/Resources/config/validation.yml 2 Acme\EventBundle\Entity\Participant: 3 properties: 4 emails: 5 - Count:

1. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/Count.html 2. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/CountValidator.html

PDF brought to you by generated on October 26, 2012

Chapter 214: Count | 876

6 7 8 9

min: 1 max: 5 minMessage: You must specify at least one email maxMessage: You cannot specify more than 5 emails

Options
min
type: integer [default option] This required option is the "min" count value. Validation will fail if the given collection elements count is less than this min value.

max
type: integer [default option] This required option is the "max" count value. Validation will fail if the given collection elements count is greater than this max value.

minMessage
type: string default: This collection should contain {{ limit }} elements or more.. The message that will be shown if the underlying collection elements count is less than the min option.

maxMessage
type: string default: This collection should contain {{ limit }} elements or less.. The message that will be shown if the underlying collection elements count is more than the max option.

exactMessage
type: string default: This collection should contain exactly {{ limit }} elements.. The message that will be shown if min and max values are equal and the underlying collection elements count is not exactly this value.

PDF brought to you by generated on October 26, 2012

Chapter 214: Count | 877

Chapter 215

UniqueEntity
Validates that a particular field (or fields) in a Doctrine entity is (are) unique. This is commonly used, for example, to prevent a new user to register using an email address that already exists in the system. Applies to class Options fields message em

Class Validator

UniqueEntity1 UniqueEntityValidator2

Basic Usage
Suppose you have an AcmeUserBundle bundle with a User entity that has an email field. You can use the UniqueEntity constraint to guarantee that the email field remains unique between all of the constraints in your user table:
Listing 215-1

1 2 3 4 5 6 7 8 9 10

// Acme/UserBundle/Entity/User.php use Symfony\Component\Validator\Constraints as Assert; use Doctrine\ORM\Mapping as ORM; // DON'T forget this use statement!!! use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; /** * @ORM\Entity * @UniqueEntity("email")

1. http://api.symfony.com/2.1/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.html 2. http://api.symfony.com/2.1/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.html

PDF brought to you by generated on October 26, 2012

Chapter 215: UniqueEntity | 878

11 */ 12 class Author 13 { 14 /** 15 * @var string $email 16 * 17 * @ORM\Column(name="email", type="string", length=255, unique=true) 18 * @Assert\Email() 19 */ 20 protected $email; 21 22 // ... 23 }

Options
fields
type: array``|``string [default option] This required option is the field (or list of fields) on which this entity should be unique. For example, if you specified both the email and name field in a single UniqueEntity constraint, then it would enforce that the combination value where unique (e.g. two users could have the same email, as long as they don't have the same name also). If you need to require two fields to be individually unique (e.g. a unique email and a unique username), you use two UniqueEntity entries, each with a single field.

message
type: string default: This value is already used. The message that's displayed when this constraint fails.

em
type: string The name of the entity manager to use for making the query to determine the uniqueness. If it's left blank, the correct entity manager will determined for this class. For that reason, this option should probably not need to be used.

PDF brought to you by generated on October 26, 2012

Chapter 215: UniqueEntity | 879

Chapter 216

Language
Validates that a value is a valid language code. Applies to property or method Options Class Validator message

Language1 LanguageValidator2

Basic Usage
Listing 216-1

1 # src/UserBundle/Resources/config/validation.yml 2 Acme\UserBundle\Entity\User: 3 properties: 4 preferredLanguage: 5 - Language:

Options
message
type: string default: This value is not a valid language This message is shown if the string is not a valid language code.

1. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/Language.html 2. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/LanguageValidator.html

PDF brought to you by generated on October 26, 2012

Chapter 216: Language | 880

Chapter 217

Locale
Validates that a value is a valid locale. The "value" for each locale is either the two letter ISO639-1 language code (e.g. fr), or the language code followed by an underscore (_), then the ISO3166 country code (e.g. fr_FR for French/France). Applies to property or method Options Class Validator message

Locale1 LocaleValidator2

Basic Usage
Listing 217-1

1 # src/UserBundle/Resources/config/validation.yml 2 Acme\UserBundle\Entity\User: 3 properties: 4 locale: 5 - Locale:

Options
message
type: string default: This value is not a valid locale

1. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/Locale.html 2. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/LocaleValidator.html

PDF brought to you by generated on October 26, 2012

Chapter 217: Locale | 881

This message is shown if the string is not a valid locale.

PDF brought to you by generated on October 26, 2012

Chapter 217: Locale | 882

Chapter 218

Country
Validates that a value is a valid two-letter country code. Applies to property or method Options Class Validator message

Country1 CountryValidator2

Basic Usage
Listing 218-1

1 # src/UserBundle/Resources/config/validation.yml 2 Acme\UserBundle\Entity\User: 3 properties: 4 country: 5 - Country:

Options
message
type: string default: This value is not a valid country This message is shown if the string is not a valid country code.

1. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/Country.html 2. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/CountryValidator.html

PDF brought to you by generated on October 26, 2012

Chapter 218: Country | 883

Chapter 219

File
Validates that a value is a valid "file", which can be one of the following: A string (or object with a __toString() method) path to an existing file; A valid File1 object (including objects of class UploadedFile2). This constraint is commonly used in forms with the file form type.
If the file you're validating is an image, try the Image constraint.

Applies to property or method Options maxSize mimeTypes maxSizeMessage mimeTypesMessage notFoundMessage notReadableMessage uploadIniSizeErrorMessage uploadFormSizeErrorMessage uploadErrorMessage

Class Validator

File3 FileValidator4

1. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/File/File.html 2. http://api.symfony.com/2.1/Symfony/Component/HttpFoundation/File/UploadedFile.html 3. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/File.html 4. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/FileValidator.html

PDF brought to you by generated on October 26, 2012

Chapter 219: File | 884

Basic Usage
This constraint is most commonly used on a property that will be rendered in a form as a file form type. For example, suppose you're creating an author form where you can upload a "bio" PDF for the author. In your form, the bioFile property would be a file type. The Author class might look as follows:
Listing 219-1

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

// src/Acme/BlogBundle/Entity/Author.php namespace Acme\BlogBundle\Entity;


use Symfony\Component\HttpFoundation\File\File; class Author { protected $bioFile; public function setBioFile(File $file = null) { $this->bioFile = $file; } public function getBioFile() { return $this->bioFile; } }

To guarantee that the bioFile File object is valid, and that it is below a certain file size and a valid PDF, add the following:
Listing 219-2

1 # src/Acme/BlogBundle/Resources/config/validation.yml 2 Acme\BlogBundle\Entity\Author 3 properties: 4 bioFile: 5 - File: 6 maxSize: 1024k 7 mimeTypes: [application/pdf, application/x-pdf] 8 mimeTypesMessage: Please upload a valid PDF

The bioFile property is validated to guarantee that it is a real file. Its size and mime type are also validated because the appropriate options have been specified.

Options
maxSize
type: mixed If set, the size of the underlying file must be below this file size in order to be valid. The size of the file can be given in one of the following formats: bytes: To specify the maxSize in bytes, pass a value that is entirely numeric (e.g. 4096); kilobytes: To specify the maxSize in kilobytes, pass a number and suffix it with a lowercase "k" (e.g. 200k); megabytes: To specify the maxSize in megabytes, pass a number and suffix it with a capital "M" (e.g. 4M).

PDF brought to you by generated on October 26, 2012

Chapter 219: File | 885

mimeTypes
type: array or string If set, the validator will check that the mime type of the underlying file is equal to the given mime type (if a string) or exists in the collection of given mime types (if an array). You can find a list of existing mime types on the IANA website5

maxSizeMessage
type: string default: The file is too large ({{ size }}). Allowed maximum size is {{ limit }} The message displayed if the file is larger than the maxSize option.

mimeTypesMessage
type: string default: The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }} The message displayed if the mime type of the file is not a valid mime type per the mimeTypes option.

notFoundMessage
type: string default: The file could not be found The message displayed if no file can be found at the given path. This error is only likely if the underlying value is a string path, as a File object cannot be constructed with an invalid file path.

notReadableMessage
type: string default: The file is not readable The message displayed if the file exists, but the PHP is_readable function fails when passed the path to the file.

uploadIniSizeErrorMessage
type: string default: The file is too large. Allowed maximum size is {{ limit }} The message that is displayed if the uploaded file is larger than the upload_max_filesize PHP.ini setting.

uploadFormSizeErrorMessage
type: string default: The file is too large The message that is displayed if the uploaded file is larger than allowed by the HTML file input field.

uploadErrorMessage
type: string default: The file could not be uploaded The message that is displayed if the uploaded file could not be uploaded for some unknown reason, such as the file upload failed or it couldn't be written to disk.

5. http://www.iana.org/assignments/media-types/index.html

PDF brought to you by generated on October 26, 2012

Chapter 219: File | 886

Chapter 220

Image
The Image constraint works exactly like the File constraint, except that its mimeTypes and mimeTypesMessage options are automatically setup to work for image files specifically. Additionally, as of Symfony 2.1, it has options so you can validate against the width and height of the image. See the File constraint for the bulk of the documentation on this constraint. Applies to property or method Options mimeTypes minWidth maxWidth maxHeight minHeight mimeTypesMessage sizeNotDetectedMessage maxWidthMessage minWidthMessage maxHeightMessage minHeightMessage See File for inherited options

Class Validator

File1 FileValidator2

Basic Usage
This constraint is most commonly used on a property that will be rendered in a form as a file form type. For example, suppose you're creating an author form where you can upload a "headshot" image for the
1. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/File.html 2. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/FileValidator.html

PDF brought to you by generated on October 26, 2012

Chapter 220: Image | 887

author. In your form, the headshot property would be a file type. The Author class might look as follows:
Listing 220-1

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

// src/Acme/BlogBundle/Entity/Author.php namespace Acme\BlogBundle\Entity;


use Symfony\Component\HttpFoundation\File\File; class Author { protected $headshot; public function setHeadshot(File $file = null) { $this->headshot = $file; } public function getHeadshot() { return $this->headshot; } }

To guarantee that the headshot File object is a valid image and that it is between a certain size, add the following:
Listing 220-2

1 # src/Acme/BlogBundle/Resources/config/validation.yml 2 Acme\BlogBundle\Entity\Author 3 properties: 4 headshot: 5 - Image: 6 minWidth: 200 7 maxWidth: 400 8 minHeight: 200 9 maxHeight: 400

The headshot property is validated to guarantee that it is a real image and that it is between a certain width and height.

Options
This constraint shares all of its options with the File constraint. It does, however, modify two of the default option values and add several other options.

mimeTypes
type: array or string default: image/* You can find a list of existing image mime types on the IANA website3

mimeTypesMessage
type: string default: This file is not a valid image

3. http://www.iana.org/assignments/media-types/image/index.html

PDF brought to you by generated on October 26, 2012

Chapter 220: Image | 888

New in version 2.1: All of the min/max width/height options are new to Symfony 2.1.

minWidth
type: integer If set, the width of the image file must be greater than or equal to this value in pixels.

maxWidth
type: integer If set, the width of the image file must be less than or equal to this value in pixels.

minHeight
type: integer If set, the height of the image file must be greater than or equal to this value in pixels.

maxHeight
type: integer If set, the height of the image file must be less than or equal to this value in pixels.

sizeNotDetectedMessage
type: string default: The size of the image could not be detected If the system is unable to determine the size of the image, this error will be displayed. This will only occur when at least one of the four size constraint options has been set.

maxWidthMessage
type: string default: The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px The error message if the width of the image exceeds maxWidth.

minWidthMessage
type: string default: The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px The error message if the width of the image is less than minWidth.

maxHeightMessage
type: string default: The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px The error message if the height of the image exceeds maxHeight.

PDF brought to you by generated on October 26, 2012

Chapter 220: Image | 889

minHeightMessage
type: string default: The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px The error message if the height of the image is less than minHeight.

PDF brought to you by generated on October 26, 2012

Chapter 220: Image | 890

Chapter 221

Callback
The purpose of the Callback assertion is to let you create completely custom validation rules and to assign any validation errors to specific fields on your object. If you're using validation with forms, this means that you can make these custom errors display next to a specific field, instead of simply at the top of your form. This process works by specifying one or more callback methods, each of which will be called during the validation process. Each of those methods can do anything, including creating and assigning validation errors.
A callback method itself doesn't fail or return any value. Instead, as you'll see in the example, a callback method has the ability to directly add validator "violations".

Applies to class Options Class Validator methods

Callback1 CallbackValidator2

Setup
Listing 221-1

1 # src/Acme/BlogBundle/Resources/config/validation.yml 2 Acme\BlogBundle\Entity\Author: 3 constraints:

1. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/Callback.html 2. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/CallbackValidator.html

PDF brought to you by generated on October 26, 2012

Chapter 221: Callback | 891

4 5

- Callback: methods:

[isAuthorValid]

The Callback Method


The callback method is passed a special ExecutionContext object. You can set "violations" directly on this object and determine to which field those errors should be attributed:
Listing 221-2

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

// ... use Symfony\Component\Validator\ExecutionContext;


class Author { // ... private $firstName; public function isAuthorValid(ExecutionContext $context) { // somehow you have an array of "fake names" $fakeNames = array();

// check if the name is actually a fake name if (in_array($this->getFirstName(), $fakeNames)) { $context->addViolationAtSubPath('firstname', 'This name sounds totally fake!', array(), null); } }

Options
methods
type: array default: array() [default option] This is an array of the methods that should be executed during the validation process. Each method can be one of the following formats: 1. String method name If the name of a method is a simple string (e.g. isAuthorValid), that method will be called on the same object that's being validated and the ExecutionContext will be the only argument (see the above example). 2. Static array callback Each method can also be specified as a standard array callback:
Listing 221-3

1 # src/Acme/BlogBundle/Resources/config/validation.yml 2 Acme\BlogBundle\Entity\Author: 3 constraints: 4 - Callback: methods:

PDF brought to you by generated on October 26, 2012

Chapter 221: Callback | 892

5 6 isAuthorValid]

[Acme\BlogBundle\MyStaticValidatorClass,

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

1 2 3 4 5 6 7 8 9 10 11 12

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

If you specify your Callback constraint via PHP, then you also have the option to make your callback either a PHP closure or a non-static callback. It is not currently possible, however, to specify a service as a constraint. To validate using a service, you should create a custom validation constraint and add that new constraint to your class.

PDF brought to you by generated on October 26, 2012

Chapter 221: Callback | 893

Chapter 222

All
When applied to an array (or Traversable object), this constraint allows you to apply a collection of constraints to each element of the array. Applies to property or method Options Class Validator constraints

All1 AllValidator2

Basic Usage
Suppose that you have an array of strings, and you want to validate each entry in that array:
Listing 222-1

1 # src/UserBundle/Resources/config/validation.yml 2 Acme\UserBundle\Entity\User: 3 properties: 4 favoriteColors: 5 - All: 6 - NotBlank: ~ 7 - MinLength: 5

Now, each entry in the favoriteColors array will be validated to not be blank and to be at least 5 characters long.

1. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/All.html 2. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/AllValidator.html

PDF brought to you by generated on October 26, 2012

Chapter 222: All | 894

Options
constraints
type: array [default option] This required option is the array of validation constraints that you want to apply to each element of the underlying array.

PDF brought to you by generated on October 26, 2012

Chapter 222: All | 895

Chapter 223

UserPassword

New in version 2.1: This constraint is new in version 2.1.

This validates that an input value is equal to the current authenticated user's password. This is useful in a form where a user can change his password, but needs to enter his old password for security.
This should not be used to validate a login form, since this is done automatically by the security system.

When applied to an array (or Traversable object), this constraint allows you to apply a collection of constraints to each element of the array. Applies to property or method Options Class Validator message

UserPassword1 UserPasswordValidator2

Basic Usage
Suppose you have a PasswordChange class, that's used in a form where the user can change his password by entering his old password and a new password. This constraint will validate that the old password matches the user's current password:
1. http://api.symfony.com/2.1/Symfony/Component/Security/Core/Validator/Constraint/UserPassword.html 2. http://api.symfony.com/2.1/Symfony/Component/Security/Core/Validator/Constraint/UserPasswordValidator.html

PDF brought to you by generated on October 26, 2012

Chapter 223: UserPassword | 896

Listing 223-1

1 # src/UserBundle/Resources/config/validation.yml 2 Acme\UserBundle\Form\Model\ChangePassword: 3 properties: 4 oldPassword: 5 - Symfony\Component\Security\Core\Validator\Constraint\UserPassword: 6 message: "Wrong value for your current password"

Options
message
type: message default: This value should be the user current password This is the message that's displayed when the underlying string does not match the current user's password.

PDF brought to you by generated on October 26, 2012

Chapter 223: UserPassword | 897

Chapter 224

Valid
This constraint is used to enable validation on objects that are embedded as properties on an object being validated. This allows you to validate an object and all sub-objects associated with it. Applies to property or method Options Class traverse

Type1

Basic Usage
In the following example, we create two classes Author and Address that both have constraints on their properties. Furthermore, Author stores an Address instance in the $address property.
Listing 224-1

1 2 3 4 5 6

// src/Acme/HelloBundle/Address.php class Address { protected $street; protected $zipCode; }

Listing 224-2

1 2 3 4 5 6 7

// src/Acme/HelloBundle/Author.php class Author { protected $firstName; protected $lastName; protected $address; }

1. http://api.symfony.com/2.1/Symfony/Component/Validator/Constraints/Type.html

PDF brought to you by generated on October 26, 2012

Chapter 224: Valid | 898

Listing 224-3

1 # src/Acme/HelloBundle/Resources/config/validation.yml 2 Acme\HelloBundle\Address: 3 properties: 4 street: 5 - NotBlank: ~ 6 zipCode: 7 - NotBlank: ~ 8 - MaxLength: 5 9 10 Acme\HelloBundle\Author: 11 properties: 12 firstName: 13 - NotBlank: ~ 14 - MinLength: 4 15 lastName: 16 - NotBlank: ~

With this mapping, it is possible to successfully validate an author with an invalid address. To prevent that, add the Valid constraint to the $address property.
Listing 224-4

1 # src/Acme/HelloBundle/Resources/config/validation.yml 2 Acme\HelloBundle\Author: 3 properties: 4 address: 5 - Valid: ~

If you validate an author with an invalid address now, you can see that the validation of the Address fields failed. AcmeHelloBundleAuthor.address.zipCode: This value is too long. It should have 5 characters or less

Options
traverse
type: string default: true If this constraint is applied to a property that holds an array of objects, then each object in that array will be validated only if this option is set to true.

PDF brought to you by generated on October 26, 2012

Chapter 224: Valid | 899

Chapter 225

The Dependency Injection Tags


Dependency Injection Tags are little strings that can be applied to a service to "flag" it to be used in some special way. For example, if you have a service that you would like to register as a listener to one of Symfony's core events, you can flag it with the kernel.event_listener tag. You can learn a little bit more about "tags" by reading the "Tags" section of the Service Container chapter. Below is information about all of the tags available inside Symfony2. There may also be tags in other bundles you use that aren't listed here. For example, the AsseticBundle has several tags that aren't listed here. Tag Name data_collector form.type form.type_extension form.type_guesser kernel.cache_warmer kernel.event_listener kernel.event_subscriber monolog.logger monolog.processor routing.loader security.voter security.remember_me_aware security.listener.factory swiftmailer.plugin templating.helper translation.loader
PDF brought to you by generated on October 26, 2012

Usage Create a class that collects custom data for the profiler Create a custom form field type Create a custom "form extension" Add your own logic for "form type guessing" Register your service to be called during the cache warming process Listen to different events/hooks in Symfony To subscribe to a set of different events/hooks in Symfony Logging with a custom logging channel Add a custom processor for logging Register a custom service that loads routes Add a custom voter to Symfony's authorization logic To allow remember me authentication Necessary when creating a custom authentication system Register a custom SwiftMailer Plugin Make your service available in PHP templates Register a custom service that loads translations
Chapter 225: The Dependency Injection Tags | 900

twig.extension validator.constraint_validator validator.initializer

Register a custom Twig Extension Create your own custom validation constraint Register a service that initializes objects before validation

data_collector
Purpose: Create a class that collects custom data for the profiler For details on creating your own custom data collection, read the cookbook article: How to create a custom Data Collector.

form.type
Purpose: Create a custom form field type For details on creating your own custom form type, read the cookbook article: How to Create a Custom Form Field Type.

form.type_extension
Purpose: Create a custom "form extension" Form type extensions are a way for you took "hook into" the creation of any field in your form. For example, the addition of the CSRF token is done via a form type extension (FormTypeCsrfExtension1). A form type extension can modify any part of any field in your form. To create a form type extension, first create a class that implements the FormTypeExtensionInterface2 interface. For simplicity, you'll often extend an AbstractTypeExtension3 class instead of the interface directly:
Listing 225-1

1 2 3 4 5 6 7 8 9 10

// src/Acme/MainBundle/Form/Type/MyFormTypeExtension.php namespace Acme\MainBundle\Form\Type\MyFormTypeExtension;


use Symfony\Component\Form\AbstractTypeExtension; class MyFormTypeExtension extends AbstractTypeExtension { // ... fill in whatever methods you want to override // like buildForm(), buildView(), finishView(), setDefaultOptions() }

In order for Symfony to know about your form extension and use it, give it the form.type_extension tag:
Listing 225-2

1 services: 2 main.form.type.my_form_type_extension: 3 class: Acme\MainBundle\Form\Type\MyFormTypeExtension 4 tags: 5 - { name: form.type_extension, alias: field }

1. http://api.symfony.com/2.1/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.html 2. http://api.symfony.com/2.1/Symfony/Component/Form/FormTypeExtensionInterface.html 3. http://api.symfony.com/2.1/Symfony/Component/Form/AbstractTypeExtension.html

PDF brought to you by generated on October 26, 2012

Chapter 225: The Dependency Injection Tags | 901

The alias key of the tag is the type of field that this extension should be applied to. For example, to apply the extension to any "field", use the "field" value.

form.type_guesser
Purpose: Add your own logic for "form type guessing" This tag allows you to add your own logic to the Form Guessing process. By default, form guessing is done by "guessers" based on the validation metadata and Doctrine metadata (if you're using Doctrine). To add your own form type guesser, create a class that implements the FormTypeGuesserInterface4 interface. Next, tag its service definition with form.type_guesser (it has no options). To see an example of how this class might look, see the ValidatorTypeGuesser class in the Form component.

kernel.cache_warmer
Purpose: Register your service to be called during the cache warming process Cache warming occurs whenever you run the cache:warmup or cache:clear task (unless you pass --nowarmup to cache:clear). The purpose is to initialize any cache that will be needed by the application and prevent the first user from any significant "cache hit" where the cache is generated dynamically. To register your own cache warmer, first create a service that implements the CacheWarmerInterface5 interface:
Listing 225-3

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

// src/Acme/MainBundle/Cache/MyCustomWarmer.php namespace Acme\MainBundle\Cache;


use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; class MyCustomWarmer implements CacheWarmerInterface { public function warmUp($cacheDir) { // do some sort of operations to "warm" your cache } public function isOptional() { return true; } }

The isOptional method should return true if it's possible to use the application without calling this cache warmer. In Symfony 2.0, optional warmers are always executed anyways, so this function has no real effect. To register your warmer with Symfony, give it the kernel.cache_warmer tag:
Listing 225-4

1 services: 2 main.warmer.my_custom_warmer: 3 class: Acme\MainBundle\Cache\MyCustomWarmer

4. http://api.symfony.com/2.1/Symfony/Component/Form/FormTypeGuesserInterface.html 5. http://api.symfony.com/2.1/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmerInterface.html

PDF brought to you by generated on October 26, 2012

Chapter 225: The Dependency Injection Tags | 902

4 5

tags: - { name: kernel.cache_warmer, priority: 0 }

The priority value is optional, and defaults to 0. This value can be from -255 to 255, and the warmers will be executed in the order of their priority.

kernel.event_listener
Purpose: To listen to different events/hooks in Symfony This tag allows you to hook your own classes into Symfony's process at different points. For a full example of this listener, read the How to create an Event Listener cookbook entry. For another practical example of a kernel listener, see the cookbook article: How to register a new Request Format and Mime Type.

Core Event Listener Reference


When adding your own listeners, it might be useful to know about the other core Symfony listeners and their priorities.
All listeners listed here may not be listening depending on your environment, settings and bundles. Additionally, third-party bundles will bring in additional listener not listed here.

kernel.request
Listener Class Name Priority 1024
7

ProfilerListener SessionListener8 RouterListener9 LocaleListener10 Firewall


11

TestSessionListener

192 128 32 16 8

kernel.controller
Listener Class Name Priority
12

RequestDataCollector

6. http://api.symfony.com/2.1/Symfony/Component/HttpKernel/EventListener/ProfilerListener.html 7. http://api.symfony.com/2.1/Symfony/Bundle/FrameworkBundle/EventListener/TestSessionListener.html 8. http://api.symfony.com/2.1/Symfony/Bundle/FrameworkBundle/EventListener/SessionListener.html 9. http://api.symfony.com/2.1/Symfony/Component/HttpKernel/EventListener/RouterListener.html 10. http://api.symfony.com/2.1/Symfony/Component/HttpKernel/EventListener/LocaleListener.html 11. http://api.symfony.com/2.1/Symfony/Component/Security/Http/Firewall.html 12. http://api.symfony.com/2.1/Symfony/Bundle/FrameworkBundle/DataCollector/RequestDataCollector.html

PDF brought to you by generated on October 26, 2012

Chapter 225: The Dependency Injection Tags | 903

kernel.response
Listener Class Name Priority 0
14

EsiListener13 ResponseListener ResponseListener15 ProfilerListener16 TestSessionListener17 WebDebugToolbarListener


18 19

0 0 -100 -128 -128 -1024

StreamedResponseListener

kernel.exception
Listener Class Name Priority 0 -128

ProfilerListener

20

ExceptionListener21

kernel.terminate
Listener Class Name Priority 0

EmailSenderListener22

kernel.event_subscriber
Purpose: To subscribe to a set of different events/hooks in Symfony
New in version 2.1: The ability to add kernel event subscribers is new to 2.1.

To enable a custom subscriber, add it as a regular service in one of your configuration, and tag it with kernel.event_subscriber:
Listing 225-5

1 services: 2 kernel.subscriber.your_subscriber_name: 3 class: Fully\Qualified\Subscriber\Class\Name

13. http://api.symfony.com/2.1/Symfony/Component/HttpKernel/EventListener/EsiListener.html 14. http://api.symfony.com/2.1/Symfony/Component/HttpKernel/EventListener/ResponseListener.html 15. http://api.symfony.com/2.1/Symfony/Bundle/SecurityBundle/EventListener/ResponseListener.html 16. http://api.symfony.com/2.1/Symfony/Component/HttpKernel/EventListener/ProfilerListener.html 17. http://api.symfony.com/2.1/Symfony/Bundle/FrameworkBundle/EventListener/TestSessionListener.html 18. http://api.symfony.com/2.1/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.html 19. http://api.symfony.com/2.1/Symfony/Component/HttpKernel/EventListener/StreamedResponseListener.html 20. http://api.symfony.com/2.1/Symfony/Component/HttpKernel/EventListener/ProfilerListener.html 21. http://api.symfony.com/2.1/Symfony/Component/HttpKernel/EventListener/ExceptionListener.html 22. http://api.symfony.com/2.1/Symfony/Bundle/SwiftmailerBundle/EventListener/EmailSenderListener.html

PDF brought to you by generated on October 26, 2012

Chapter 225: The Dependency Injection Tags | 904

4 5

tags: - { name: kernel.event_subscriber }

Your service must implement SymfonyComponentEventDispatcherEventSubscriberInterface23 interface.

the

If your service is created by a factory, you MUST correctly set the class parameter for this tag to work correctly.

monolog.logger
Purpose: To use a custom logging channel with Monolog Monolog allows you to share its handlers between several logging channels. The logger service uses the channel app but you can change the channel when injecting the logger in a service.
Listing 225-6

services: my_service: class: Fully\Qualified\Loader\Class\Name arguments: [@logger] tags: - { name: monolog.logger, channel: acme }

This works only when the logger service is a constructor argument, not when it is injected through a setter.

monolog.processor
Purpose: Add a custom processor for logging Monolog allows you to add processors in the logger or in the handlers to add extra data in the records. A processor receives the record as an argument and must return it after adding some extra data in the extra attribute of the record. Let's see how you can use the built-in IntrospectionProcessor to add the file, the line, the class and the method where the logger was triggered. You can add a processor globally:
Listing 225-7

1 services: 2 my_service: 3 class: Monolog\Processor\IntrospectionProcessor 4 tags: 5 - { name: monolog.processor }

23. http://api.symfony.com/2.1/SymfonyComponentEventDispatcherEventSubscriberInterface.html

PDF brought to you by generated on October 26, 2012

Chapter 225: The Dependency Injection Tags | 905

If your service is not a callable (using __invoke) you can add the method attribute in the tag to use a specific method.

You can add also a processor for a specific handler by using the handler attribute:
Listing 225-8

1 services: 2 my_service: 3 class: Monolog\Processor\IntrospectionProcessor 4 tags: 5 - { name: monolog.processor, handler: firephp }

You can also add a processor for a specific logging channel by using the channel attribute. This will register the processor only for the security logging channel used in the Security component:
Listing 225-9

1 services: 2 my_service: 3 class: Monolog\Processor\IntrospectionProcessor 4 tags: 5 - { name: monolog.processor, channel: security }

You cannot use both the handler and channel attributes for the same tag as handlers are shared between all channels.

routing.loader
Purpose: Register a custom service that loads routes To enable a custom routing loader, add it as a regular service in one of your configuration, and tag it with routing.loader:
Listing 225-10 1

2 3 4 5

services: routing.loader.your_loader_name: class: Fully\Qualified\Loader\Class\Name tags: - { name: routing.loader }

security.listener.factory
Purpose: Necessary when creating a custom authentication system This tag is used when creating your own custom authentication system. For details, see How to create a custom Authentication Provider.

security.remember_me_aware
Purpose: To allow remember me authentication

PDF brought to you by generated on October 26, 2012

Chapter 225: The Dependency Injection Tags | 906

This tag is used internally to allow remember-me authentication to work. If you have a custom authentication method where a user can be remember-me authenticated, then you may need to use this tag. If your custom authentication factory extends AbstractFactory24 and your custom authentication listener extends AbstractAuthenticationListener25, then your custom authentication listener will automatically have this tagged applied and it will function automatically.

security.voter
Purpose: To add a custom voter to Symfony's authorization logic When you call isGranted on Symfony's security context, a system of "voters" is used behind the scenes to determine if the user should have access. The security.voter tag allows you to add your own custom voter to that system. For more information, read the cookbook article: How to implement your own Voter to blacklist IP Addresses.

swiftmailer.plugin
Purpose: Register a custom SwiftMailer Plugin If you're using a custom SwiftMailer plugin (or want to create one), you can register it with SwiftMailer by creating a service for your plugin and tagging it with swiftmailer.plugin (it has no options). A SwiftMailer plugin must implement the Swift_Events_EventListener interface. For more information on plugins, see SwiftMailer's Plugin Documentation26. Several SwiftMailer plugins are core to Symfony and can be activated via different configuration. For details, see SwiftmailerBundle Configuration ("swiftmailer").

templating.helper
Purpose: Make your service available in PHP templates To enable a custom template helper, add it as a regular service in one of your configuration, tag it with templating.helper and define an alias attribute (the helper will be accessible via this alias in the templates):
Listing 225-11 1

2 3 4 5

services: templating.helper.your_helper_name: class: Fully\Qualified\Helper\Class\Name tags: - { name: templating.helper, alias: alias_name }

translation.loader
Purpose: To register a custom service that loads translations

24. http://api.symfony.com/2.1/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.html 25. http://api.symfony.com/2.1/Symfony/Component/Security/Http/Firewall/AbstractAuthenticationListener.html 26. http://swiftmailer.org/docs/plugins.html

PDF brought to you by generated on October 26, 2012

Chapter 225: The Dependency Injection Tags | 907

By default, translations are loaded form the filesystem in a variety of different formats (YAML, XLIFF, PHP, etc). If you need to load translations from some other source, first create a class that implements the LoaderInterface27 interface:
Listing 225-12

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

// src/Acme/MainBundle/Translation/MyCustomLoader.php namespace Acme\MainBundle\Translation;


use Symfony\Component\Translation\Loader\LoaderInterface use Symfony\Component\Translation\MessageCatalogue; class MyCustomLoader implements LoaderInterface { public function load($resource, $locale, $domain = 'messages') { $catalogue = new MessageCatalogue($locale);

// some how load up some translations from the "resource" // then set them into the catalogue $catalogue->set('hello.world', 'Hello World!', $domain);
return $catalogue; } }

Your custom loader's load method is responsible for returning a MessageCatalogue28. Now, register your loader as a service and tag it with translation.loader:
Listing 225-13 1

2 3 4 5

services: main.translation.my_custom_loader: class: Acme\MainBundle\Translation\MyCustomLoader tags: - { name: translation.loader, alias: bin }

Listing 225-14

1 <service id="main.translation.my_custom_loader" 2 class="Acme\MainBundle\Translation\MyCustomLoader"> 3 <tag name="translation.loader" alias="bin" /> </service>

Listing 225-15

1 $container 2 ->register('main.translation.my_custom_loader', 3 'Acme\MainBundle\Translation\MyCustomLoader') 4 ->addTag('translation.loader', array('alias' => 'bin')) ;

The alias option is required and very important: it defines the file "suffix" that will be used for the resource files that use this loader. For example, suppose you have some custom bin format that you need to load. If you have a bin file that contains French translations for the messages domain, then you might have a file app/Resources/translations/messages.fr.bin.

27. http://api.symfony.com/2.1/Symfony/Component/Translation/Loader/LoaderInterface.html 28. http://api.symfony.com/2.1/Symfony/Component/Translation/MessageCatalogue.html

PDF brought to you by generated on October 26, 2012

Chapter 225: The Dependency Injection Tags | 908

When Symfony tries to load the bin file, it passes the path to your custom loader as the $resource argument. You can then perform any logic you need on that file in order to load your translations. If you're loading translations from a database, you'll still need a resource file, but it might either be blank or contain a little bit of information about loading those resources from the database. The file is key to trigger the load method on your custom loader.

twig.extension
Purpose: To register a custom Twig Extension To enable a Twig extension, add it as a regular service in one of your configuration, and tag it with twig.extension:
Listing 225-16 1

2 3 4 5

services: twig.extension.your_extension_name: class: Fully\Qualified\Extension\Class\Name tags: - { name: twig.extension }

For information on how to create the actual Twig Extension class, see Twig's documentation29 on the topic or read the cookbook article: How to write a custom Twig Extension Before writing your own extensions, have a look at the Twig official extension repository30 which already includes several useful extensions. For example Intl and its localizeddate filter that formats a date according to user's locale. These official Twig extensions also have to be added as regular services:
Listing 225-17 1

2 3 4 5

services: twig.extension.intl: class: Twig_Extensions_Extension_Intl tags: - { name: twig.extension }

validator.constraint_validator
Purpose: Create your own custom validation constraint This tag allows you to create and register your own custom validation constraint. For more information, read the cookbook article: How to create a Custom Validation Constraint.

validator.initializer
Purpose: Register a service that initializes objects before validation This tag provides a very uncommon piece of functionality that allows you to perform some sort of action on an object right before it's validated. For example, it's used by Doctrine to query for all of the lazilyloaded data on an object before it's validated. Without this, some data on a Doctrine entity would appear to be "missing" when validated, even though this is not really the case. If you do need to use this tag, just make a new class that implements the ObjectInitializerInterface31 interface. Then, tag it with the validator.initializer tag (it has no options).
29. http://twig.sensiolabs.org/doc/advanced.html#creating-an-extension 30. http://github.com/fabpot/Twig-extensions 31. http://api.symfony.com/2.1/Symfony/Component/Validator/ObjectInitializerInterface.html

PDF brought to you by generated on October 26, 2012

Chapter 225: The Dependency Injection Tags | 909

For an example, see the EntityInitializer class inside the Doctrine Bridge.

PDF brought to you by generated on October 26, 2012

Chapter 225: The Dependency Injection Tags | 910

Chapter 226

Requirements for running Symfony2


To run Symfony2, your system needs to adhere to a list of requirements. You can easily see if your system passes all requirements by running the web/config.php in your Symfony distribution. Since the CLI often uses a different php.ini configuration file, it's also a good idea to check your requirements from the command line via:
Listing 226-1

1 php app/check.php

Below is the list of required and optional requirements.

Required
PHP needs to be a minimum version of PHP 5.3.3 JSON needs to be enabled ctype needs to be enabled Your PHP.ini needs to have the date.timezone setting

Optional
You need to have the PHP-XML module installed You need to have at least version 2.6.21 of libxml PHP tokenizer needs to be enabled mbstring functions need to be enabled iconv needs to be enabled POSIX needs to be enabled (only on *nix) Intl needs to be installed with ICU 4+ APC 3.0.17+ (or another opcode cache needs to be installed) PHP.ini recommended settings short_open_tag = Off magic_quotes_gpc = Off register_globals = Off
PDF brought to you by generated on October 26, 2012 Chapter 226: Requirements for running Symfony2 | 911

session.autostart = Off

Doctrine
If you want to use Doctrine, you will need to have PDO installed. Additionally, you need to have the PDO driver installed for the database server you want to use.

PDF brought to you by generated on October 26, 2012

Chapter 226: Requirements for running Symfony2 | 912

Das könnte Ihnen auch gefallen