Symfony Book 3.1
Symfony Book 3.1
Version: 3.1
generated on July 28, 2016
The Book (3.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
Congratulations! By learning about Symfony, 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). Symfony 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 Symfony. So,
get ready.
True to the Symfony 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/
In HTTP-speak, this HTTP request would actually look something like this:
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, because it contains two important
things: the HTTP method (GET) and the URL (/).
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 the client wants to do with the resource. The HTTP
With this in mind, you can imagine what an HTTP request might look like to delete a specific blog entry,
for example:
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 only support POST and GET in HTML
forms. Various others are however supported in XMLHttpRequest2, as well as by Symfony's Routing
component.
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 host of the resource being
requested (Host), the response formats the client accepts (Accept) and the application the client is
using to make the request (User-Agent). Many other headers exist and can be found on Wikipedia's
List of HTTP header fields3 article.
Translated into HTTP, the response sent back to the browser will look something like this:
Listing 1-3
2. https://en.wikipedia.org/wiki/XMLHttpRequest
3. https://en.wikipedia.org/wiki/List_of_HTTP_header_fields
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 codes4 article.
Like the request, an HTTP response contains additional pieces of information known as HTTP headers.
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. You can see a List of common media types5 from IANA.
Many other headers exist, some of which are very powerful. For example, certain headers can be used to
create a powerful caching system.
To learn more about the HTTP specification, read the original HTTP 1.1 RFC6 or the HTTP Bis7,
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 Headers8 extension for Firefox.
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
4. https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
5. https://www.iana.org/assignments/media-types/media-types.xhtml
6. http://www.w3.org/Protocols/rfc2616/rfc2616.html
7. http://datatracker.ietf.org/wg/httpbis/
8. https://addons.mozilla.org/en-US/firefox/addon/live-http-headers/
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).
9. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/Request.html
There are also special classes to make certain types of responses easier to create:
• JsonResponse;
• BinaryFileResponse (for streaming files and sending file downloads);
• StreamedResponse (for streaming any other large responses);
The Request and Response classes are part of a standalone component called symfony/http-
foundation that yo can use in any PHP project. This also contains classes for handling sessions, file
uploads and more.
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.
10. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/ParameterBag.html
11. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/ParameterBag.html#method_get
12. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/ParameterBag.html#method_has
13. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/ParameterBag.html#method_all
14. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/Response.html
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:
By using rewrite rules in your web server configuration, the index.php won't be needed and you
will have beautiful, clean URLs (e.g. /show).
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
Inside your front controller, you have to figure out which code should be executed and what the content
to return should be. To figure this out, you'll need to check the incoming URI and execute different parts
of your code depending on that value. This can get ugly quickly:
Solving this problem can be difficult. Fortunately it's exactly what Symfony is designed to do.
Incoming requests are interpreted by the Routing component and passed to PHP 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! To review:
• Each request executes the same, single file (called a "front controller");
• The front controller boots Symfony, and passes it request information;
• The router matches the incoming URL to a specific route and returns information about the route,
including the controller (i.e. function) that should be executed;
• The controller (function) is executed: this is where your code creates and returns the appropriate
Response object;
• The HTTP headers and content of the Response object are sent back to the client.
In this example, the controller creates a Response15 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.
Routing
Powerful and fast routing system that allows you to map a specific URI (e.g. /contact) to information
about how that request should be handled (e.g. that the contactAction() controller method should be
executed).
Form
A full-featured and flexible framework for creating forms and handling form submissions.
15. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/Response.html
Templating
A toolkit for rendering templates, handling template inheritance (i.e. a template is decorated with a
layout) and performing other common template tasks.
Security
A powerful library for handling all types of security inside an application.
Translation
A framework for translating strings in your application.
Each one of these components is decoupled and can be used in any PHP project, regardless of whether
or not you use the Symfony Framework. Every part is made to be used if needed and replaced when
necessary.
16. https://github.com/symfony/validator
17. http://swiftmailer.org/
Why is Symfony better than just opening up a file and writing flat PHP?
If you've never used a PHP framework, aren't familiar with the Model-View-Controller1 (MVC)
philosophy, or just wonder what all the hype is around Symfony, this chapter is for you. Instead of telling
you that Symfony 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 Symfony can rescue you from mundane tasks and let you take back control of
your code.
1. https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller
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:
Another problem not mentioned here is the fact that the database is tied to MySQL. Though not
covered here, Symfony fully integrates Doctrine2, a library dedicated to database abstraction and
mapping.
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:
2. http://www.doctrine-project.org
By convention, the file that contains all 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, the 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).
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.
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.
You now have a setup that will allow you to reuse the layout. Unfortunately, to accomplish this, you're
forced to use a few ugly PHP functions (ob_start(), ob_get_clean()) in the template. Symfony
uses a Templating component that allows this to be accomplished cleanly and easily. You'll see it in action
shortly.
Next, create a new file called show.php - the controller for this new page:
Finally, create the new template file - templates/show.php - to render the individual blog post:
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.
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...
By using rewrite rules in your web server configuration, the index.php won't be needed and you
will have beautiful, clean URLs (e.g. /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.
For organization, both controllers (formerly index.php and show.php) are now PHP functions and
each has been moved into a separate file named controllers.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 how Symfony handles and routes requests.
But but careful not to confuse the terms front controller and controller. Your app will usually have just
one front controller, which boots your code. You will have many controller functions: one for each page.
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?
Listing 2-14 1 {
2 "require": {
3 "symfony/symfony": "3.1.*"
4 },
5 "autoload": {
6 "files": ["model.php","controllers.php"]
7 }
8 }
Next, download Composer4 and then run the following command, which will download Symfony into a
vendor/ directory:
Listing 2-15 1 $ composer install
3. https://getcomposer.org
4. https://getcomposer.org/download/
5. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/Request.html
6. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/Response.html
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 Symfony templating
engine:
By bringing in a small part of Symfony, the application is more flexible and reliable. The Request
provides a dependable way to access information about the HTTP request. Specifically, the
getPathInfo()7 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.
7. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/Request.html#method_getPathInfo
Notice, both controller functions now live inside a "controller class". This is a nice way to group related
pages. The controller functions are also sometimes called actions.
The two controllers (or actions) 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 list.php template is now quite a bit simpler:
Listing 2-20
The show show.php template is left as an exercise: updating it should be really similar to updating
the list.php template.
When Symfony'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 - app/config/routing.yml
- provides this information in a readable format:
Now that Symfony is handling all the mundane tasks, the front controller web/app.php is dead simple.
And since it does so little, you'll never have to touch it:
The front controller's only job is to initialize Symfony's engine (called the Kernel) and pass it a Request
object to handle. The Symfony core asks the router to inspect the request. The router matches the
incoming URL to a specific route and returns information about the route, including the controller that
should be executed. The correct controller from the matched route is executed and your code inside the
controller creates and returns the appropriate Response object. The HTTP headers and content of the
Response object are sent back to the client.
It's a beautiful thing.
Twig is well-supported in Symfony. And while PHP templates will always be supported in Symfony, the
many advantages of Twig will continue to be discussed. For more information, see the templating chapter.
8. http://twig.sensiolabs.org
And perhaps best of all, by using Symfony, you now have access to a whole set of high-quality open
source tools developed by the Symfony community! A good selection of Symfony community tools
can be found on KnpBundles.com12.
9. http://www.doctrine-project.org
10. https://github.com/symfony/validator
11. https://www.varnish-cache.org/
12. http://knpbundles.com/
Welcome to Symfony! Starting a new Symfony project is easy. In fact, you'll have your first working
Symfony application up and running in just a few short minutes.
Do you prefer video tutorials? Check out the Joyful Development with Symfony1 screencast series from
KnpUniversity.
To make creating new applications even simpler, Symfony provides an installer. Downloading it is your
first step.
The installer requires PHP 5.4 or higher. If you still use the legacy PHP 5.3 version, you cannot use
the Symfony Installer. Read the Creating Symfony Applications without the Installer section to learn
how to proceed.
Depending on your operating system, the installer must be installed in different ways.
1. http://knpuniversity.com/screencast/symfony
Then, move the downloaded symfony file to your project's directory and execute it as follows:
This command creates a new directory called my_project_name/ that contains a fresh new project
based on the most recent stable Symfony version available. In addition, the installer checks if your system
meets the technical requirements to execute Symfony applications. If not, you'll see the list of changes
needed to meet those requirements.
For security reasons, all Symfony versions are digitally signed before distributing them. If you want
to verify the integrity of any Symfony version, follow the steps explained in this post2.
If the installer doesn't work for you or doesn't output anything, make sure that the PHP Phar
extension3 is installed and enabled on your computer.
Listing 3-5 1 # use the most recent version in any Symfony branch
2 $ symfony new my_project_name 2.8
3 $ symfony new my_project_name 3.1
4
5 # use a specific Symfony version
6 $ symfony new my_project_name 2.8.1
7 $ symfony new my_project_name 3.0.2
8
9 # use a beta or RC version (useful for testing new Symfony versions)
10 $ symfony new my_project 3.0.0-BETA1
11 $ symfony new my_project 3.1.0-RC1
The installer also supports a special version called lts which installs the most recent Symfony LTS
version available:
2. http://fabien.potencier.org/signing-project-releases.html
3. http://php.net/manual/en/intro.phar.php
Read the Symfony Release process to better understand why there are several Symfony versions and which
one to use for your projects.
If you need to base your application on a specific Symfony version, provide that version as the second
argument of the create-project Composer command:
If your Internet connection is slow, you may think that Composer is not doing anything. If that's
your case, add the -vvv flag to the previous command to display a detailed output of everything that
Composer is doing.
Then, open your browser and access the http://localhost:8000/ URL to see the Welcome Page of
Symfony:
4. https://getcomposer.org/
When you are finished working on your Symfony application, you can stop the server by pressing Ctrl+C
from terminal.
If there are any issues, correct them now before moving on.
If used in a production environment, be sure this user only has limited privileges (no access to
private data or servers, launch of unsafe binaries, etc.) as a compromised server would give to
the hacker those privileges.
setfacl isn't available on NFS mount points. However, setting cache and logs over NFS is
strongly not recommended for performance.
5. https://help.ubuntu.com/community/FilePermissionsACLs
Depending on the complexity of your project, this update process can take up to several minutes to
complete.
Symfony provides a command to check whether your project's dependencies contain any known
security vulnerability:
A good security practice is to execute this command regularly to be able to update or replace
compromised dependencies as soon as possible.
Once downloaded, enter into the symfony_demo/ directory and run the PHP's built-in web server
executing the php bin/console server:run command. Access to the http://localhost:8000
URL in your browser to start using the Symfony Demo application.
• The Symfony CMF Standard Edition7 is the best distribution to get started with the Symfony
CMF8 project, which is a project that makes it easier for developers to add CMS functionality to
applications built with the Symfony Framework.
• The Symfony REST Edition9 shows how to build an application that provides a RESTful API using
the FOSRestBundle10 and several other related bundles.
How does Composer know which specific dependencies to install? Because when a Symfony application
is committed to a repository, the composer.json and composer.lock files are also committed.
These files tell Composer which dependencies (and which specific versions) to install for the application.
Beginning Development
Now that you have a fully-functional Symfony 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.
If you're new to Symfony, check out "Create your First Page in Symfony", 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.
6. https://github.com/symfony/symfony-standard
7. https://github.com/symfony-cmf/symfony-cmf-standard
8. http://cmf.symfony.com/
9. https://github.com/gimler/symfony-rest-edition
10. https://github.com/FriendsOfSymfony/FOSRestBundle
11. http://git-scm.com/
Creating a new page - whether it's an HTML page or a JSON endpoint - is a simple two-step process:
1. Create a route: A route is the URL (e.g. /about) to your page and points to a controller;
2. Create a controller: A controller is the PHP function you write that builds the page. You take
the incoming request information and use it to create a Symfony Response object, which can hold
HTML content, a JSON string or even a binary file like an image or PDF. The only rule is that
a controller must return a Symfony Response object (and you'll even learn to bend this rule
eventually).
Just like on the web, every interaction is initiated by an HTTP request. Your job is pure and simple:
understand that request and return a response.
Do you prefer video tutorials? Check out the Joyful Development with Symfony1 screencast series from
KnpUniversity.
Suppose you want to create a page - /lucky/number - that generates a lucky (well, random) number
and prints it. To do that, create a "Controller class" and a "controller" method inside of it that will be
executed when someone goes to /lucky/number:
1. http://knpuniversity.com/screencast/symfony/first-page
Before diving into this, test it out! If you are using PHP's internal web server go to:
http://localhost:8000/lucky/number
If you set up a virtual host in Apache or Nginx replace http://localhost:8000 with your host name
and add app_dev.php to make sure Symfony loads in the "dev" environment:
http://symfony.dev/app_dev.php/lucky/number
If you see a lucky number being printed back to you, congratulations! But before you run off to play the
lottery, check out how this works.
The @Route above numberAction() is called an annotation and it defines the URL pattern. You can
also write routes in YAML (or other formats): read about this in the routing chapter. Actually, most
routing examples in the docs have tabs that show you how each format looks.
The method below the annotation - numberAction - is called the controller and is where you build the
page. The only rule is that a controller must return a Symfony Response object (and you'll even learn to
bend this rule eventually).
http://localhost:8000/api/lucky/number
2. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/JsonResponse.html
http://localhost:8000/lucky/number/7
You can get the value of any ``{placeholder}`` in your route by adding a ``$placeholder`` argument
to your controller. Just make sure that the placeholder (e.g. ``{id}``) matches the argument name
(e.g. ``$id``).
The routing system can do a lot more, like supporting multiple placeholders (e.g. /blog/{category}/
{page})), making placeholders optional and forcing placeholder to match a regular expression (e.g. so
that {count} must be a number). Find out about all of this and become a routing expert in the Routing
chapter.
3. http://twig.sensiolabs.org
4. http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/Controller/Controller.html
You'll learn a lot more about the important "service container" as you keep reading. For now, you just
need to know that it holds a lot of objects, and you can get()5 any object by using its nickname,
like templating or logger. The templating service is an instance of TwigEngine6 and this has a
render()7 method.
But this can get even easier! By extending the Controller class, you also get a lot of shortcut methods,
like render()8:
5. http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/Controller/Controller.html#method_get
6. http://api.symfony.com/3.1/Symfony/Bundle/TwigBundle/TwigEngine.html
7. http://api.symfony.com/3.1/Symfony/Bundle/TwigBundle/TwigEngine.html#method_render
8. http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/Controller/Controller.html#method_render
You will learn more about these shortcut methods and how they work in the Controller chapter.
Welcome to Twig! This simple file already shows off the basics:
• The {{ variableName }} syntax is used to print something. In this template, luckyNumberList is a variable
that you're passing into the template from the render call in the controller.
• The {% extends 'base.html.twig' %} points to a layout file that lives at app/Resources/views/
base.html.twig9 and came with your new project. It's really basic (an unstyled HTML structure) and
it's yours to customize.
• The {% block body %} part uses Twig's inheritance system to put the content into the middle of the
base.html.twig layout.
http://localhost:8000/lucky/number/7
If you view the source code of the displayed page, you now have a basic HTML structure thanks to
base.html.twig.
This is just the surface of Twig's power. When you're ready to master its syntax, loop over arrays, render
other templates and other cool things, read the Templating chapter.
9. https://github.com/symfony/symfony-standard/blob/2.7/app/Resources/views/base.html.twig
src/
Your PHP code lives here.
99% of the time, you'll be working in src/ (PHP files) or app/ (everything else). As you get more
advanced, you'll learn what can be done inside each of these.
The app/ directory also holds some other things, like app/AppKernel.php, which you'll use to enable
new bundles (this is one of a very short list of PHP files in app/).
The src/ directory has just one directory - src/AppBundle - and everything lives inside of it. A bundle
is like a "plugin" and you can find open source bundles10 and install them into your project. But even
your code lives in a bundle - typically AppBundle (though there's nothing special about AppBundle). To
find out more about bundles and why you might create multiple bundles (hint: sharing code between
projects), see the Bundles chapter.
So what about the other directories in the project?
web/
This is the document root for the project and contains any publicly accessible files, like CSS, images
and the Symfony development and production front controllers that execute the app (app_dev.php and
app.php).
tests/
The automatic tests (e.g. Unit tests) of your application live here.
bin/
The "binary" files live here. The most important one is the console file which is used to execute
Symfony commands via the console.
var/
This is where automatically created files are stored, like cache files (var/cache/) and logs (var/logs/).
vendor/
Third-party (i.e. "vendor") libraries live here! These are typically downloaded via the Composer11
package manager.
Symfony is flexible. If you need to, you can easily override the default directory structure. See How to Override
Symfony's default Directory Structure.
Application Configuration
Symfony comes with several built-in bundles (open your app/AppKernel.php file) and you'll probably
install more. The main configuration file for bundles is app/config/config.yml:
10. http://knpbundles.com
11. https://getcomposer.org
The framework key configures FrameworkBundle, the twig key configures TwigBundle and so on. A
lot of behavior in Symfony can be controlled just by changing one option in this configuration file. To
find out how, see the Configuration Reference section.
Or, to get a big example dump of all of the valid configuration under a key, use the handy bin/console
command:
There's a lot more power behind Symfony's configuration system, including environments, imports and
parameters. To learn all of it, see the Configuring Symfony (and Environments) chapter.
What's Next?
Congrats! You're already starting to master Symfony and learn a whole new way of building beautiful,
functional, fast and maintainable apps.
Ok, time to finish mastering the fundamentals by reading these chapters:
• Controller
• Routing
• Creating and Using Templates
Then, in the Symfony Book, learn about the service container, the form system, using Doctrine (if you need
to query a database) and more!
There's also a Cookbook packed with more advanced "how to" articles to solve a lot of problems.
Have fun!
A controller is a PHP callable you create that takes information from the HTTP request and creates and
returns an HTTP response (as a Symfony 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.
See how simple this is by looking at a Symfony controller in action. This renders a page that prints the
famous 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} placeholder from the request to load a blog entry from the database and
creates 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 you.
Finally, it creates a Response object that redirects the client's browser to the contact form "thank you"
page.
Though similarly named, a "front controller" is different from the PHP functions called "controllers"
talked about in this chapter. A front controller is a short PHP file that lives in your web/ directory
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. The "controller class" is
a convenient way to group several "controllers", also called actions, together in one class (e.g.
updateAction(), deleteAction(), etc). So, a controller is a method inside a controller class.
They hold your code which creates and returns the appropriate Response object.
A Simple Controller
While a controller can be any PHP callable (a function, method on an object, or a Closure), a controller
is usually a method inside a controller class:
The controller is the indexAction() method, which lives inside a controller class
HelloController.
This controller is pretty straightforward:
• line 2: Symfony takes advantage of PHP's namespace functionality to namespace the entire
controller class.
• line 4: Symfony again takes advantage of PHP's namespace functionality: the use keyword imports
the Response class, which the 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 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 (e.g. 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
controller method ($name).
• line 10: The controller creates and returns a Response object.
The controller has a single argument, $name, which corresponds to the {name} placeholder from the
matched route (e.g. ryan if you go to /hello/ryan). When executing the controller, Symfony matches
each argument with a placeholder from the route. So the value for {name} is passed to $name. Just make
sure that the name of the placeholder is the same as the name of the argument variable.
Take the following more-interesting example, where the controller has two arguments:
Mapping route parameters to controller arguments is easy and flexible. Keep the following guidelines in
mind while you develop.
1. The order of the controller arguments does not matter
Symfony matches the parameter names from the route to the variable names of the controller.
The arguments of the controller could be totally reordered and still work perfectly:
Listing 5-6
public function indexAction($lastName, $firstName)
{
// ...
}
Listing 5-7
public function indexAction($firstName, $lastName, $foo)
{
// ...
}
Making the argument optional, however, is perfectly ok. The following example would not
throw an exception:
Listing 5-8
public function indexAction($firstName, $lastName, $foo = 'bar')
{
// ...
}
Listing 5-9
public function indexAction($firstName)
{
// ...
}
You can also pass other variables from your route to your controller arguments. See How to Pass
Extra Information from a Route to a Controller.
Helper methods are just shortcuts to using core Symfony 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 Controller2 class.
Generating URLs
The generateUrl()3 method is just a helper method that generates the URL for a given route.
1. http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/Controller/Controller.html
2. http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/Controller/Controller.html
3. http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/Controller/Controller.html#method_generateUrl
By default, the redirectToRoute() method performs a 302 (temporary) redirect. To perform a 301
(permanent) redirect, modify the third argument:
Listing 5-12
public function indexAction()
{
return $this->redirectToRoute('homepage', array(), 301);
}
To redirect to an external site, use redirect() and pass it the external URL:
Listing 5-13
public function indexAction()
{
return $this->redirect('http://symfony.com/doc');
}
The redirectToRoute() method is simply a shortcut that creates a Response object that
specializes in redirecting the user. It's equivalent to:
Rendering Templates
If you're serving HTML, you'll want to render a template. The render() method renders a template
and puts that content into a Response object for you:
Listing 5-15
// renders app/Resources/views/hello/index.html.twig
return $this->render('hello/index.html.twig', array('name' => $name));
Templates can also live in deeper sub-directories. Just try to avoid creating unnecessarily deep structures:
Listing 5-16
// renders app/Resources/views/hello/greetings/index.html.twig
return $this->render('hello/greetings/index.html.twig', array(
'name' => $name
));
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. To learn how to render different templating formats read the Template Formats
section of the Creating and Using Templates chapter.
The Symfony templating engine is explained in great detail in the Creating and Using Templates chapter.
What other services exist? To list all services, use the debug:container console command:
To get a container configuration parameter in controller you can use the getParameter()5
method:
$from = $this->getParameter('app.mailer.from');
Listing 5-19
4. http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/Controller/Controller.html#method_get
5. http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/Controller/Controller.html#method_getParameter
In every case, an error page is shown to the end user and a full debug error page is shown to the developer
(i.e. when you're using the app_dev.php front controller - see Environments).
You'll want to customize the error page your user sees. To do that, see the "How to Customize Error
Pages" cookbook recipe.
6. http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/Controller/Controller.html#method_createNotFoundException
7. http://api.symfony.com/3.1/Symfony/Component/HttpKernel/Exception/NotFoundHttpException.html
8. http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/Controller/Controller.html#method_getSession
9. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/Session/SessionInterface.html
Flash Messages
You can also store special messages, called "flash" messages, on the user's session. By design, flash
messages are meant to be used exactly once: they vanish from the session automatically as soon as you
retrieve them. This feature makes "flash" messages particularly great for storing user notifications.
For example, imagine you're processing a form submission:
After processing the request, the controller sets a flash message in the session and then redirects. The
message key (notice in this example) can be anything: you'll use this key to retrieve the message.
In the template of the next page (or even better, in your base layout template), read any flash messages
from the session:
It's common to use notice, warning and error as the keys of the different types of flash
messages, but you can use any key that fits your needs.
You can use the peek()10 method instead to retrieve the message while keeping it in the bag.
10. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/Session/Flash/FlashBagInterface.html#method_peek
The Request class has several public properties and methods that return any information you need
about the request.
Like the Request, the Response object has also a public headers property. This is a
ResponseHeaderBag11 that has some nice methods for getting and setting response headers. The
header names are normalized so that using Content-Type is equivalent to content-type or even
content_type.
The only requirement for a controller is to return a Response object. The Response12 class is an
abstraction around the HTTP response - the text-based message filled with headers and content that's
sent back to the client:
There are also special classes to make certain kinds of responses easier:
JSON Helper
New in version 3.1: The json() helper was introduced in Symfony 3.1.
11. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/ResponseHeaderBag.html
12. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/Response.html
13. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/JsonResponse.html
14. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/BinaryFileResponse.html
15. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/StreamedResponse.html
If the serializer service is enabled in your application, contents passed to json() are encoded with it.
Otherwise, the json_encode16 function is used.
Now that you know the basics you can continue your research on Symfony Request and Response object in the
HttpFoundation component documentation.
The array passed to the method becomes the arguments for the resulting controller. The target controller
method might look something like this:
Listing 5-30
public function fancyAction($name, $color)
{
// ... create and return a Response object
}
Just like when creating a controller for a route, the order of the arguments of fancyAction() doesn't
matter: the matching is done by name.
16. http://php.net/manual/en/function.json-encode.php
17. http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/Controller/Controller.html#method_forward
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 where you can do anything 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.
18. http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/Controller/Controller.html#method_isCsrfTokenValid
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 Symfony 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:
Routing in Action
A route is a map from a URL path 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:
The goal of the Symfony 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 Symfony front controller (e.g. app.php);
2. The Symfony 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 Symfony Kernel executes the controller, which ultimately returns a Response object.
The routing layer is a tool that translates the incoming URL into a specific controller to execute.
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.
This route matches the homepage (/) and maps it to the AppBundle:Main:homepage controller. The
_controller string is translated by Symfony into an actual PHP function and executed. That process
will be explained shortly in the Controller Naming Pattern section.
The path 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,
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:
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:
Of course, you can have more than one optional placeholder (e.g. /blog/{slug}/{page}), but
everything after an optional placeholder must be optional. For example, /{page}/blog is a valid
path, but page will always be required (i.e. simply /blog will not match this route).
Routes with optional parameters at the end will not match on requests with a trailing slash (i.e.
/blog/ will not match, /blog will match).
Adding Requirements
Take a quick look at the routes that have been created so far:
Can you spot the problem? Notice that both routes have patterns that match URLs 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.
The answer to the problem is to add route requirements or route conditions (see Completely Customized
Route Matching with Conditions). The routes in this example would work perfectly if the /blog/
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.
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:
For incoming requests, the {_locale} portion of the URL is matched against the regular expression
(en|fr).
The route requirements can also include container parameters, as explained in this article. This
comes in handy when the regular expression is very complex and used repeatedly in your
application.
Despite the fact that these two routes have identical paths (/api/posts/{id}), the first route will
match only GET or HEAD requests and the second route will match only PUT requests. This means that
you can display and edit the post with the same URL, while using distinct controllers for the two actions.
The condition is an expression, and you can learn more about its syntax here: The Expression Syntax.
With this, the route won't match unless the HTTP method is either GET or HEAD and if the User-
Agent header matches firefox.
You can do any complex logic you need in the expression by leveraging two variables that are passed into
the expression:
context
An instance of RequestContext1, which holds the most fundamental information about the route being
matched.
request
The Symfony Request2 object (see Request).
Because of this, using the condition key causes no extra overhead beyond the time it takes for the
underlying PHP to execute.
1. http://api.symfony.com/3.1/Symfony/Component/Routing/RequestContext.html
2. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/Request.html
As you've seen, this route will only match if the {_locale} 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
• /articles/en/2013/my-latest-post.html
Sometimes you want to make certain parts of your routes globally configurable. Symfony provides
you with a way to do this by leveraging service container parameters. Read more about this in "How
to Use Service Container Parameters in your Routes".
_format
Used to set the request format (read more).
_locale
Used to set the locale on the request (read more).
bundle:controller:action
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:
AppBundle\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).
Listing 6-17
public function showAction($slug)
{
// ...
}
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.
• $_locale
• $year
• $title
• $_format
• $_controller
• $_route
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.
The special $_route variable is set to the name of the route that was matched.
You can even add extra information to your route definition and access it within your controller. For
more information on this topic, see How to Pass Extra Information from a Route to a Controller.
When importing resources from YAML, the key (e.g. app) 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 a directory, where
the @AppBundle shortcut syntax resolves to the full path of the AppBundle. When pointing to a
directory, all files in that directory are parsed and put into the routing.
You can also include other routing configuration files, this is often used to import the routing of
third party bundles:
Listing 6-20
The path of each route being loaded from the new routing resource will now be prefixed with the string
/site.
This command will print a helpful list of all the configured routes in your application:
You can also get very specific information on a single route by including the route name after the
command:
Likewise, if you want to test whether a URL matches a given route, you can use the router:match
console command:
Generating URLs
The routing system should also be used to generate URLs. In reality, routing is a bidirectional system:
mapping the URL to a controller+parameters and a route+parameters back to a URL. The match()3
3. http://api.symfony.com/3.1/Symfony/Component/Routing/Router.html#method_match
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 path for that route. With this information, any URL can easily be
generated:
The generateUrl() method defined in the base Controller5 class is just a shortcut for this
code:
$url = $this->container->get('router')->generate(
Listing 6-28
'blog_show',
array('slug' => 'my-blog-post')
);
In an upcoming section, you'll learn how to generate URLs from inside templates.
If the front-end 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 FOSJsRoutingBundle6, you can do
exactly that:
4. http://api.symfony.com/3.1/Symfony/Component/Routing/Router.html#method_generate
5. http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/Controller/Controller.html
6. https://github.com/FriendsOfSymfony/FOSJsRoutingBundle
If you are generating the route inside a <script> element, it's a good practice to escape it for
JavaScript:
From a template, simply use the url() function (which generates an absolute URL) rather than the
path() function (which generates a relative URL):
Listing 6-34 1 <a href="{{ url('blog_show', {'slug': 'my-blog-post'}) }}">
2 Read this blog post.
3 </a>
The host that's used when generating an absolute URL is automatically detected using the current
Request object. When generating absolute URLs from outside the web context (for instance in a
console command) this doesn't work. See How to Generate URLs from the Console to learn how to
solve this problem.
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 bidirectional mechanism, meaning that it
should also be used to generate URLs.
As you know, the controller is responsible for handling each request that comes into a Symfony
application. In reality, the controller delegates 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:
{% ... %}
"Does something": a tag that controls the logic of the template; it is used to execute statements such
as for-loops for example.
{# ... #}
"Comment something": it's the equivalent of the PHP /* comment */ syntax. It's used to add single or
multi-line comments. The content of the comments isn't included in the rendered pages.
Twig also contains filters, which modify content before being rendered. The following makes the title
variable all uppercase before rendering it:
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:
Throughout this chapter, template examples will be shown in both Twig and PHP.
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
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 HTML
escaping, manual 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:
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:
The parent template is identified by a special string syntax (base.html.twig). This path is relative
to the app/Resources/views directory of the project. You could also use the logical name
equivalent: ::base.html.twig. This naming convention is explained fully in Template Naming
and Locations.
The key to template inheritance is the {% extends %} tag. This tells the templating engine to first
evaluate the base template, which sets up the layout and defines several blocks. The child template is
then rendered, at which point the title and body blocks of the parent are replaced by those from the
child. Depending on the value of blog_entries, the output might look like this:
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 Symfony 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:
path/to/bundle/Resources/views/
Each third party bundle houses its templates in its Resources/views/ directory (and subdirectories).
When you plan to share your bundle, you should put the templates in the bundle instead of the app/
directory.
• AcmeBlogBundle: (bundle) the template lives inside the AcmeBlogBundle (e.g. src/Acme/BlogBundle);
• Blog:(directory) indicates that the template lives inside the Blog subdirectory of Resources/views;
• index.html.twig: (filename) 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, "directory", portion is missing (e.g. Blog), the template
lives at Resources/views/layout.html.twig inside AcmeBlogBundle. Yes, there are 2
colons in the middle of the string when the "controller" subdirectory part is missing.
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 similar to the naming convention used to
refer to Controller Naming Pattern.
Template Suffix
Every template name also has two extensions that specify the format and engine for that template.
By default, any Symfony template can be written in either Twig or PHP, and the last part of the extension
(e.g. .twig or .php) specifies which of these two engines should be used. The first part of the extension,
(e.g. .html, .css, etc) is the final format that the template will generate. Unlike the engine, which
determines how Symfony 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 Template Formats section.
The template is included using the {{ include() }} function. Notice that the template name follows
the same typical convention. The article_details.html.twig template uses an article variable,
which we pass to it. In this case, you could avoid doing this entirely, as all of the variables available in
list.html.twig are also available in article_details.html.twig (unless you set with_context5
to false).
5. http://twig.sensiolabs.org/doc/functions/include.html
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:
Notice that the article URL is hardcoded 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):
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
When using a controller instead of a URL, you must enable the Symfony fragments configuration:
Default content (while loading or if JavaScript is disabled) can be set globally in your application
configuration:
You can define default templates per render function (which will override any global default template
that is defined):
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:
6. http://mnot.github.io/hinclude/
7. http://mnot.github.io/hinclude/
To link to the page, just use the path Twig function and refer to the route:
As expected, this will generate the URL /. Now, for a more complicated route:
In this case, you need to specify both the route name (article_show) and a value for the {slug}
parameter. Using this route, revisit the recent_list template from the previous section and link to the
articles correctly:
You can also generate an absolute URL by using the url function:
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 Symfony provides a more dynamic
option via the asset Twig function:
Listing 7-25
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.com), then the rendered paths should be /images/
logo.png. But if your application lives in a subdirectory (e.g. http://example.com/my_app),
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 loaded from cache after being deployed.
For example, /images/logo.png might look like /images/logo.png?v2. For more information,
see the version configuration option.
If you need absolute URLs for assets, use the absolute_url() Twig function as follows:
This section will teach you the philosophy behind including stylesheet and JavaScript assets in
Symfony. Symfony is also compatible with 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:
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 7-28
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 bin/console assets:install target [--symlink] command, which moves (or
symlinks) files into the correct location. (target is by default "web").
The end result is a page that includes both the main.css and contact.css stylesheets.
app.request
The Request10 object that represents the current request (depending on your application, this can be
a sub-request or a regular request, as explained later).
app.session
The Session11 object that represents the current user's session or null if there is none.
app.environment
The name of the current environment (dev, prod, etc).
app.debug
True if in debug mode. False otherwise.
8. http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/Templating/GlobalVariables.html
9. http://api.symfony.com/3.1/Symfony/Component/Security/Core/User/UserInterface.html
10. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/Request.html
11. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/Session/Session.html
Listing 7-31
return $this->render('article/index.html.twig');
is equivalent to:
The templating engine (or "service") is preconfigured to work automatically inside Symfony. It can, of
course, be configured further in the application configuration file:
Several configuration options are available and are covered in the Configuration Appendix.
The twig engine is mandatory to use the webprofiler (as well as many third-party bundles).
12. http://knpbundles.com
If you add a template in a new location, you may need to clear your cache (php bin/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, Symfony
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 Symfony always starts by looking in the app/Resources/
{BUNDLE_NAME}/views/ directory for a template. If the template doesn't exist there, it continues
by checking inside the Resources/views directory of the bundle itself. This means that all bundle
templates can be overridden by placing them in the correct app/Resources subdirectory.
You can also override templates from within a bundle by using bundle inheritance. For more
information, see How to Use Bundle Inheritance to Override Parts of a Bundle.
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 that were just covered:
• Create an 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, the blog functionality would have a
template called blog/layout.html.twig that contains only blog section-specific elements;
• 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 blog/index.html.twig and
list the actual blog posts.
Notice that this template extends the section template (blog/layout.html.twig) which in turn
extends the base application layout (base.html.twig). This is the common three-level inheritance
model.
When building your application, you may choose to follow this method or simply make each page
template extend the base application template directly (e.g. {% extends 'base.html.twig' %}).
The three-template model is a best-practice method used by vendor bundles so that the base template for
a bundle can be easily overridden to properly extend your application's base layout.
Output Escaping
When generating HTML from a template, there is always a risk that a template variable may output
unintended HTML or dangerous client-side code. The result is that dynamic content could break the
HTML of the resulting page or allow a malicious user to perform a Cross Site Scripting13 (XSS) attack.
Consider this classic example:
Imagine the user enters the following code for their name:
Without any output escaping, the resulting template will cause a JavaScript alert box to pop up:
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 7-40
13. https://en.wikipedia.org/wiki/Cross-site_scripting
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.
You can also disable output escaping inside a {% block %} area or for an entire template. For more
information, see Output Escaping14 in the Twig documentation.
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 7-43 1 var myMsg = 'Hello <?php echo $view->escape($name, 'js') ?>';
Debugging
When using PHP, you can use the dump() function from the VarDumper component if you need to
quickly find the value of a variable passed. This is useful, for example, inside your controller:
14. http://twig.sensiolabs.org/doc/api.html#escaper-extension
The output of the dump() function is then rendered in the web developer toolbar.
The same mechanism can be used in Twig templates thanks to dump function:
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.
Syntax Checking
You can check for syntax errors in Twig templates using the lint:twig console command:
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 formats. To render an article index page in
XML, simply include the format in the template name:
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 7-48 1 <a href="{{ path('article_show', {'id': 123, '_format': 'pdf'}) }}">
2 PDF Version
3 </a>
Final Thoughts
The templating engine in Symfony is a powerful tool that can be used each time you need to generate
presentational content in HTML, XML or any other format. And though templates are a common way
to generate content in a controller, their use is not mandatory. The Response object returned by a
controller can be created with or without the use of a template:
Listing 7-49 1 // creates a Response object whose content is the rendered template
2 $response = $this->render('article/index.html.twig');
3
4 // creates a Response object whose content is simple text
5 $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 Symfony, that's absolutely fine.
An application consists of a collection of bundles representing all 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-1 1 # app/config/config.yml
2 imports:
3 - { resource: parameters.yml }
4 - { resource: security.yml }
5
6 framework:
7 secret: '%secret%'
8 router: { resource: '%kernel.root_dir%/config/routing.yml' }
9 # ...
10
11 # Twig Configuration
12 twig:
13 debug: '%kernel.debug%'
14 strict_variables: '%kernel.debug%'
15
16 # ...
You'll learn exactly how to load each file/format in the next section Environments.
Each top-level entry like framework or twig defines the configuration for a particular bundle. For
example, the framework key defines the configuration for the core Symfony FrameworkBundle and
includes configuration for the routing, templating, and other core systems.
For now, don't worry about the specific configuration options in each section. The configuration file
ships with sensible defaults. As you read more and explore each part of Symfony, you'll learn about the
specific configuration options of each feature.
• YAML: Simple, clean and readable (learn more about YAML in "The YAML Format");
• XML: More powerful than YAML at times and supports IDE autocompletion;
• PHP: Very powerful but less readable than standard configuration formats.
See the cookbook article: How to Load Service Configuration inside 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 Symfony 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:
If you'd like to see how your application will behave in the production environment, call the prod front
controller instead:
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:
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.
When using the server:run command to start a server, http://localhost:8000/ will use the
dev front controller of your application.
Environment Configuration
The AppKernel class is responsible for actually loading the configuration file of your choice:
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.
The imports key is similar to a PHP include statement and guarantees that the main configuration file
(config.yml) is loaded first. The rest of the file tweaks the default configuration for increased logging
and other settings conducive to a development environment.
Both the prod and test environments follow the same model: each environment imports the base
configuration file and then modifies its configuration values to fit the needs of the specific environment.
This is just a convention, but one that allows you to reuse most of your configuration and customize just
pieces of it between environments.
A bundle is similar to a plugin in other software, but even better. The key difference is that everything
is a bundle in Symfony, including both the core framework functionality and the code written for your
application. Bundles are first-class citizens in Symfony. This gives you the flexibility to use pre-built
features packaged in third-party bundles or to distribute your own bundles. It makes it easy to pick and
choose which features to enable in your application and 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, JavaScript files, tests and anything else. Every aspect of a feature exists in a bundle
and every feature lives in a bundle.
Bundles used in your applications must be enabled by registering them in the registerBundles()
method of the AppKernel class:
With the registerBundles() method, you have total control over which bundles are used by your
application (including the core Symfony bundles).
A bundle can live anywhere as long as it can be autoloaded (via the autoloader configured at app/
autoload.php).
Creating a Bundle
The Symfony Standard Edition comes with a handy task that creates a fully-functional bundle for you.
Of course, creating a bundle by hand is pretty easy as well.
To show you how simple the bundle system is, create a new bundle called AcmeTestBundle and enable
it.
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).
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:
The bundle skeleton generates a basic controller, template and routing resource that can be customized.
You'll learn more about Symfony'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.
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.
third-party bundles: http://knpbundles.com
One of the most common and challenging tasks for any application involves persisting and reading
information to and from a database. Although the Symfony full-stack Framework doesn't integrate any
ORM by default, the Symfony Standard Edition, which is the most widely used distribution, 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 DBAL" cookbook entry.
You can also persist data to MongoDB2 using Doctrine ODM library. For more information, read the
"DoctrineMongoDBBundle3" documentation.
1. http://www.doctrine-project.org/
2. https://www.mongodb.org/
3. https://symfony.com/doc/current/bundles/DoctrineMongoDBBundle/index.html
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:
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 can connect to your database, the following command can automatically generate an
empty test_project database for you:
Setting UTF8 defaults for MySQL is as simple as adding a few lines to your configuration file
(typically my.cnf):
You can also change the defaults for Doctrine so that the generated SQL uses the correct character
set.
We recommend against MySQL's utf8 character set, since it does not support 4-byte unicode
characters, and strings containing them will be truncated. This is fixed by the newer utf8mb4
character set4.
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 simple entity classes for
you. This will ask you interactive questions to help you build any entity:
4. https://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html
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.
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 Documentation5 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.
5. http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html
When using another library or program (e.g. 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:
This command makes sure that all 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).
Keep in mind that Doctrine's entity generator produces simple getters/setters. You should review the
generated methods and add any logic, if necessary, to suit the needs of your application.
6. http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#quoting-reserved-words
7. http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#creating-classes-for-the-database
8. http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#property-mapping
You can also generate all known entities (i.e. any PHP class with Doctrine mapping information) of a
bundle or an entire namespace:
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 executes the SQL
statements needed to update the database schema to where it should be. In other words, if you add
a new property with mapping metadata to Product and run this task, it will execute 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 migrations9, 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 update and track changes to your database schema safely and
reliably.
Whether or not you take advantage of migrations, the doctrine:schema:update command
should only be used during development. It should not be used in a production environment.
Your database now has a fully-functional product table with columns that match the metadata you've
specified.
9. https://symfony.com/doc/current/bundles/DoctrineMigrationsBundle/index.html
If you're following along with this example, you'll need to create a route that points to this action to
see it work.
This article shows working with Doctrine from within a controller by using the getDoctrine()10
method of the controller. This method is a shortcut to get the doctrine service. You can work with
Doctrine anywhere else by injecting that service in the service. See Service Container for more on
creating your own services.
• lines 10-13 In this section, you instantiate and work with the $product object like any other normal
PHP object.
• line 15 This line fetches Doctrine's entity manager object, which is responsible for the process of
persisting objects to, and fetching objects from, the database.
• line 17 The persist($product) call tells Doctrine to "manage" the $product object. This does not cause
a query to be made to the database.
• line 18 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's
data doesn't exist in the database, so the entity manager executes an INSERT query, creating a new
row in the product table.
10. http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/Controller/Controller.html#method_getDoctrine
Whether 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 entity 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 the "DoctrineFixturesBundle11" documentation.
You can achieve the equivalent of this without writing any code by using the @ParamConverter
shortcut. See the FrameworkExtraBundle documentation12 for more details.
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 10-17
$repository = $this->getDoctrine()
->getRepository('AppBundle:Product');
The AppBundle:Product string is a shortcut you can use anywhere in Doctrine instead of the full
class name of the entity (i.e. AppBundle\Entity\Product). As long as your entity lives under the
Entity namespace of your bundle, this will work.
Once you have a repository object, you can access all sorts of helpful methods:
Listing 10-18 1 // query for a single product by its primary key (usually "id")
2 $product = $repository->find($productId);
11. https://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html
12. https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html
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 10-19 1 // query for a single product matching the given name and price
2 $product = $repository->findOneBy(
3 array('name' => 'Keyboard', 'price' => 19.99)
4 );
5
6 // query for multiple products matching the given name, ordered by price
7 $products = $repository->findBy(
8 array('name' => 'Keyboard'),
9 array('price' => 'ASC')
10 );
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.
The icon will turn yellow if there were more than 50 queries on the page. This could indicate that
something is not correct.
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:
Deleting an Object
Deleting an object is very similar, but requires a call to the remove() method of the entity manager:
Listing 10-21
$em->remove($product);
$em->flush();
As you might expect, the remove() method notifies Doctrine that you'd like to remove the given
object from the database. The actual DELETE query, however, isn't actually executed until the flush()
method is called.
Listing 10-22
$product = $repository->find($productId);
$product = $repository->findOneByName('Keyboard');
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 main options: writing pure DQL queries or using Doctrine's
Query Builder.
If you're comfortable with SQL, then DQL should feel very natural. The biggest difference is that you
need to think in terms of selecting PHP objects, instead of rows in a database. For this reason, you select
from the AppBundle:Product entity (an optional shortcut for the AppBundle\Entity\Product
class) and then alias it as p.
Take note of the setParameter() method. When working with Doctrine, it's always a good idea
to set any external values as "placeholders" (:price in the example above) as it prevents SQL
injection attacks.
The getResult() method returns an array of results. To get only one result, you can use
getOneOrNullResult():
Listing 10-24
$product = $query->setMaxResults(1)->getOneOrNullResult();
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 Query Language13
documentation.
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 can be used to get the
result of the query.
For more information on Doctrine's Query Builder, consult Doctrine's Query Builder14 documentation.
13. http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html
14. http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/query-builder.html
Doctrine can generate empty repository classes for all the entities in your application via the same
command used earlier to generate the missing getter and setter methods:
If you opt to create the repository classes yourself, they must extend
Doctrine\ORM\EntityRepository.
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 10-29
$em = $this->getDoctrine()->getManager();
$products = $em->getRepository('AppBundle:Product')
->findAllOrderedByName();
Entity Relationships/Associations
Suppose that each product in your application belongs to exactly one category. In this case, you'll need a
Category class, 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 category
objects through Doctrine, you can let Doctrine create the class for you.
This task generates the Category entity for you, with an id field, a name field and the associated getter
and setter functions.
This many-to-one mapping is critical. It tells Doctrine to use the category_id column on the product
table to relate each record in that table with a record in the category table.
Next, since a single Category object will relate to many Product objects, a products property can
be added to the Category class to hold those associated objects.
While the many-to-one mapping shown earlier was mandatory, this one-to-many mapping is optional.
It is included here to help demonstrate Doctrine's range of relationship management capabailties. Plus,
in the context of this application, it will likely be convenient for each Category object to automatically
own a collection of its related Product objects.
The code in the constructor is important. Rather than being instantiated as a traditional array, the
$products property must be of a type that implements Doctrine's Collection interface. In this
case, an ArrayCollection object is used. 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 metadata used above can reference any entity with a valid namespace,
not just entities defined in the same namespace. To relate to an entity defined in a different class or
bundle, enter a full namespace as the targetEntity.
Now that you've added new properties to both the Product and Category classes, tell Doctrine to
generate the missing getter and setter methods for you:
Ignore the Doctrine metadata for a moment. You now have two classes - Product and Category,
with a natural many-to-one relationship. The Product class holds a single Category object, and the
Category class holds a collection of Product objects. In other words, you've built your classes in a way
that makes sense for your application. The fact that the data needs to be persisted to a database is always
secondary.
Now, review the metadata above the Product entity's $category property. It tells Doctrine that
the related class is Category, and that the id of the related category record should be stored in a
category_id field on the product table.
In other words, the related Category object will be stored in the $category property, but behind the
scenes, Doctrine will persist this relationship by storing the category's id in the category_id column of
the product table.
In this example, you first query for a Product object based on the product's id. This issues a query for
just the product data and hydrates the $product object with that data. Later, when you call $product-
>getCategory()->getName(), Doctrine silently makes a second query to find the Category that's
related to this Product. It prepares the $category object and returns it to you.
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:
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 it 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.
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:
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 config reference.
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.
Now, you can tell Doctrine to execute a method on any of the available lifecycle events. For example,
suppose you want to set a createdAt date column to the current date, only when the entity is first
persisted (i.e. inserted):
Listing 10-42
15. http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html
The above example assumes that you've created and mapped a createdAt property (not shown
here).
Now, right before the entity is first persisted, Doctrine will automatically call this method and the
createdAt field will be set to the current date.
There are several other lifecycle events that you can hook into. For more information on other lifecycle
events and lifecycle callbacks in general, see Doctrine's Lifecycle Events documentation16.
Summary
With Doctrine, you can focus on your objects and how they're used 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.
16. http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html#lifecycle-events
17. http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#property-mapping
18. https://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html
19. https://symfony.com/doc/current/bundles/DoctrineMongoDBBundle/index.html
Propel is an open-source Object-Relational Mapping (ORM) for PHP which implements the ActiveRecord
pattern1. It allows you to access your database using a set of objects, providing a simple API for storing
and retrieving data. Propel uses PDO as an abstraction layer and code generation to remove the burden
of runtime introspection.
A few years ago, Propel was a very popular alternative to Doctrine. However, its popularity has rapidly
declined and that's why the Symfony book no longer includes the Propel documentation. Read the official
PropelBundle documentation2 to learn how to integrate Propel into your Symfony projects.
1. https://en.wikipedia.org/wiki/Active_record_pattern
2. https://github.com/propelorm/PropelBundle/blob/1.4/Resources/doc/index.markdown
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.
It's recommended to use the latest stable PHPUnit version, installed as PHAR.
Each test - whether it's a unit test or a functional test - is a PHP class that should live in the tests/
directory of your application. If you follow this rule, then you can run all of your application's tests with
the following command:
PHPunit is configured by the phpunit.xml.dist file in the root of your Symfony application.
Code coverage can be generated with the --coverage-* options, see the help information that is
shown when using --help for more information.
Unit Tests
A unit test is a test against a single PHP class, also called a unit. If you want to test the overall behavior of
your application, see the section about Functional Tests.
1. https://phpunit.de/manual/current/en/
By convention, the tests/AppBundle directory should replicate the directory of your bundle for
unit tests. So, if you're testing a class in the src/AppBundle/Util/ directory, put the test in the
tests/AppBundle/Util/ directory.
Just like in your real application - autoloading is automatically enabled via the app/autoload.php file
(as configured by default in the phpunit.xml.dist file).
Running tests for a given file or directory is also very easy:
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:
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:
The createClient() method returns a client, which is like a browser that you'll use to crawl your site:
Listing 12-7
$crawler = $client->request('GET', '/post/hello-world');
The request() method (read more about the request method) returns a Crawler2 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().
2. http://api.symfony.com/3.1/Symfony/Component/DomCrawler/Crawler.html
Submitting a form is very similar: select a form button, optionally override some form values and submit
the corresponding 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 12-10
// Assert that the response matches a given CSS selector.
$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 in case that the response is not an XML/HTML document:
Listing 12-11
$this->assertContains(
'Hello World',
$client->getResponse()->getContent()
);
Listing 12-13
$crawler = $client->request('GET', '/post/hello-world');
The request() method takes the HTTP method and a URL as arguments and returns a Crawler
instance.
Hardcoding the request URLs is a best practice for functional tests. If the test generates URLs using
the Symfony router, it won't detect any change made to the application URLs which may impact the
end users.
The server array is the raw values that you'd expect to normally find in the PHP $_SERVER3
superglobal. For example, to set the Content-Type, Referer and X-Requested-With HTTP
headers, you'd pass the following (mind the HTTP_ prefix for non standard headers):
Use the crawler to find DOM elements in the response. These elements can then be used to click on links
and submit forms:
The click() and submit() methods both return a Crawler object. These methods are the best way
to browse your application as it takes care of a lot of things for you, like detecting the HTTP method
from a form and giving you a nice API for uploading files.
You will learn more about the Link and Form objects in the Crawler section below.
The request method can also be used to simulate form submissions directly or perform more complex
requests. Some useful examples:
Listing 12-17 1 // Directly submit a form (but using the Crawler is easier!)
2 $client->request('POST', '/submit', array('name' => 'Fabien'));
3
4 // Submit a raw JSON string in the request body
5 $client->request(
6 'POST',
7 '/submit',
8 array(),
9 array(),
10 array('CONTENT_TYPE' => 'application/json'),
3. http://php.net/manual/en/reserved.variables.server.php
Last but not least, you can force each request to be executed in its own PHP process to avoid any side-
effects when working with several clients in the same script:
Listing 12-18
$client->insulate();
Browsing
The Client supports many operations that can be done in a real browser:
Listing 12-20
$history = $client->getHistory();
$cookieJar = $client->getCookieJar();
You can also get the objects related to the latest request:
If your requests are not insulated, you can also access the Container and the Kernel:
Listing 12-22
$container = $client->getContainer();
$kernel = $client->getKernel();
Listing 12-23
$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 debug:container console task.
If the information you need to check is available from the profiler, use it instead.
Listing 12-24 1 // enable the profiler for the very next request
2 $client->enableProfiler();
3
4 $crawler = $client->request('GET', '/profiler');
5
6 // get the profile
7 $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.
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 12-25
$crawler = $client->followRedirect();
If you want the client to automatically follow all redirects, you can force him with the
followRedirects() method:
Listing 12-26
$client->followRedirects();
If you pass false to the followRedirects() method, the redirects will no longer be followed:
Listing 12-27
$client->followRedirects(false);
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:
filterXpath('h1')
Nodes that match the XPath expression.
eq(1)
Node for the specified index.
first()
First node.
last()
Last node.
siblings()
Siblings.
nextAll()
All following siblings.
previousAll()
All preceding siblings.
parents()
Returns the parent nodes.
children()
Returns children nodes.
reduce($lambda)
Nodes for which the callable does not return false.
Since each of these methods returns a new Crawler instance, you can narrow down your node selection
by chaining the method calls:
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 12-30 1 // Returns the attribute value for the first node
2 $crawler->attr('class');
3
4 // Returns the node value for the first node
5 $crawler->text();
6
7 // Extracts an array of attributes for all nodes
8 // (_text returns the node value)
9 // returns an array for each element in crawler,
10 // each with the value and href
11 $info = $crawler->extract(array('_text', 'href'));
12
13 // Executes a lambda for each node and return an array of results
14 $data = $crawler->each(function ($node, $i) {
15 return $node->attr('href');
16 });
Links
To select links, you can use the traversing methods above or the convenient selectLink() shortcut:
Listing 12-31
$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 12-32
$link = $crawler->selectLink('Click here')->link();
$client->click($link);
Forms
Forms can be selected using their buttons, which can be selected with the selectButton() method,
just like links:
Listing 12-33
$buttonCrawlerNode = $crawler->selectButton('submit');
The selectButton() method can select button tags and submit input tags. It uses several parts of
the buttons to find them:
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 12-34
$form = $buttonCrawlerNode->form();
When calling the form() method, you can also pass an array of field values that overrides the default
ones:
Listing 12-35
$form = $buttonCrawlerNode->form(array(
'name' => 'Fabien',
'my_form[subject]' => 'Symfony rocks!',
));
And if you want to simulate a specific HTTP method for the form, pass it as a second argument:
Listing 12-36
$form = $buttonCrawlerNode->form(array(), 'DELETE');
Listing 12-37
$client->submit($form);
The field values can also be passed as a second argument of the submit() method:
Listing 12-38
$client->submit($form, array(
'name' => 'Fabien',
'my_form[subject]' => 'Symfony rocks!',
));
For more complex situations, use the Form instance as an array to set the value of each field individually:
Listing 12-39
// Change the value of a field
$form['name'] = 'Fabien';
$form['my_form[subject]'] = 'Symfony rocks!';
There is also a nice API to manipulate the values of the fields according to their type:
If you purposefully want to select "invalid" select/radio values, see Selecting Invalid Choice Values.
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 Swift Mailer is configured to not actually deliver emails in the test
environment. You can see this under the swiftmailer configuration option:
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 12-44
$client = static::createClient(array(
'environment' => 'my_test_env',
'debug' => false,
));
If your application behaves according to some HTTP headers, pass them as the second argument of
createClient():
Listing 12-45
$client = static::createClient(array(), array(
'HTTP_HOST' => 'en.example.com',
'HTTP_USER_AGENT' => 'MySuperBrowser/1.0',
));
Listing 12-46
$client->request('GET', '/', array(), array(), array(
'HTTP_HOST' => 'en.example.com',
'HTTP_USER_AGENT' => 'MySuperBrowser/1.0',
));
The test client is available as a service in the container in the test environment (or wherever the
framework.test option is enabled). This means you can override the service entirely if you need to.
PHPUnit Configuration
Each application has its own PHPUnit configuration, stored in the phpunit.xml.dist file. You can
edit this file to change the defaults or create a phpunit.xml file to set up a configuration for your local
machine only.
Store the phpunit.xml.dist file in your code repository and ignore the phpunit.xml file.
By default, only the tests stored in /tests are run via the phpunit command, as configured in the
phpunit.xml.dist file:
Listing 12-47 1 <!-- phpunit.xml.dist -->
2 <phpunit>
3 <!-- ... -->
4 <testsuites>
5 <testsuite name="Project Test Suite">
6 <directory>tests</directory>
7 </testsuite>
8 </testsuites>
9 <!-- ... -->
10 </phpunit>
But you can easily add more directories. For instance, the following configuration adds tests from a
custom lib/tests directory:
To include other directories in the code coverage, also edit the <filter> section:
Learn more
• The chapter about tests in the Symfony Framework Best Practices
• The DomCrawler Component
• The CssSelector Component
• How to Simulate HTTP Authentication in a Functional Test
• How to Test the Interaction of several Clients
• How to Use the Profiler in a Functional Test
• How to Customize the Bootstrap Process before Running Tests
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.
Symfony ships with a Validator1 component that makes this task easy and transparent. This component
is based on the JSR303 Bean Validation specification2.
So far, this is just an ordinary class that serves some purpose inside your application. The goal of
validation is to tell you if 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:
1. https://github.com/symfony/validator
2. http://jcp.org/en/jsr/detail?id=303
Protected and private properties can also be validated, as well as "getter" methods (see Constraint
Targets).
If the $name property is empty, you will see the following error message:
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.
3. http://api.symfony.com/3.1/Symfony/Component/Validator/Validator.html
4. http://api.symfony.com/3.1/Symfony/Component/Validator/ConstraintViolationList.html
Inside the template, you can output the list of errors exactly as needed:
This example uses an AuthorType form class, which is not shown here.
5. http://api.symfony.com/3.1/Symfony/Component/Validator/ConstraintViolation.html
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 Symfony, constraints are similar: they are
assertions that a condition is true. Given a value, a constraint will tell you if that value adheres to the
rules of the constraint.
Supported Constraints
Symfony packages many 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
• IsNull
• IsTrue
• IsFalse
• Type
String Constraints
• Email
• Length
• Url
• Regex
• Ip
• Uuid
Number Constraints
• Range
Comparison Constraints
• EqualTo
• NotEqualTo
Date Constraints
• Date
• DateTime
• Time
Collection Constraints
• Choice
• Collection
• Count
• UniqueEntity
• Language
• Locale
• Country
File Constraints
• File
• Image
Other Constraints
• Callback
• Expression
• 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 called gender that
can be set to either "male", "female" or "other":
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.
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).
Constraint Targets
Constraints can be applied to a class property (e.g. name), a public getter method (e.g. getFullName)
or an entire class. Property constraints are the most common and easy to use. Getter constraints allow
you to specify more complex validation rules. Finally, class constraints are intended for scenarios where
you want to validate a class as a whole.
Properties
Validating class properties is the most basic validation technique. Symfony 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.
Getters
Constraints can also be applied to the return value of a method. Symfony allows you to add a constraint
to any public method whose name starts with "get", "is" or "has". In this guide, 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:
Now, create the isPasswordLegal() method and include the logic you need:
Listing 13-13
public function isPasswordLegal()
{
return $this->firstName !== $this->password;
}
The keen-eyed among you will have noticed that the prefix of the getter ("get", "is" or "has") 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.
User
Equivalent to all constraints of the User object in the Default group. This is always the name of the
class. The difference between this and Default is explained below.
registration
Contains the constraints on the email and password fields only.
Constraints in the Default group of a class are the constraints that have either no explicit group
configured or that are configured to a group equal to the class name or the string Default.
To tell the validator to use a specific group, pass one or more group names as the third argument to the
validate() method:
Listing 13-15
$errors = $validator->validate($author, null, array('registration'));
If no groups are specified, all constraints that belong to the group Default will be applied.
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.
Group Sequence
In some cases, you want to validate your groups by steps. To do this, you can use the GroupSequence
feature. In this case, an object defines a group sequence, which determines the order groups should be
validated.
For example, suppose you have a User class and want to validate that the username and the password
are different only if all other validation passes (in order to avoid multiple error messages).
In this example, it will first validate all constraints in the group User (which is the same as the Default
group). Only if all constraints in that group are valid, the second group, Strict, will be validated.
As you have already seen in the previous section, the Default group and the group containing the
class name (e.g. User) were identical. However, when using Group Sequences, they are no longer
identical. The Default group will now reference the group sequence, instead of all constraints that
do not belong to any group.
This means that you have to use the {ClassName} (e.g. User) group when specifying a group
sequence. When using Default, you get an infinite recursion (as the Default group references the
group sequence, which will contain the Default group which references the same group sequence,
...).
Now, change the User class to implement GroupSequenceProviderInterface6 and add the
getGroupSequence()7, method, which should return an array of groups to use:
Listing 13-18 1 // src/AppBundle/Entity/User.php
2 namespace AppBundle\Entity;
3
4 // ...
5 use Symfony\Component\Validator\GroupSequenceProviderInterface;
6
6. http://api.symfony.com/3.1/Symfony/Component/Validator/GroupSequenceProviderInterface.html
7. http://api.symfony.com/3.1/Symfony/Component/Validator/GroupSequenceProviderInterface.html#method_getGroupSequence
At last, you have to notify the Validator component that your User class provides a sequence of groups
to be validated:
Final Thoughts
The Symfony 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.
8. http://api.symfony.com/3.1/Symfony/Component/Validator/ConstraintViolationList.html
9. http://api.symfony.com/3.1/Symfony/Component/Validator/ConstraintViolation.html
Dealing with HTML forms is one of the most common - and challenging - tasks for a web developer.
Symfony 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 Symfony projects.
For more information, see the Form component documentation on GitHub.
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.
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 Symfony 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. TextType
and DateType), represented by its fully qualified class name. Among other things, it determines which
HTML form tag(s) is rendered for that field.
This example assumes that you submit the form in a "POST" request and to the same URL that it
was displayed in. You will learn later how to change the request method and the target URL of the
form.
That's it! Just three lines are needed to render the complete form:
form_start(form)
Renders the start tag of the form, including the correct enctype attribute when using file uploads.
form_widget(form)
Renders all the fields, which includes the field element itself, a label and any validation error
messages for the field.
form_end(form)
Renders the end tag of the form and any fields that have not yet been rendered, in case you rendered
each field yourself. This is useful for rendering hidden fields and taking advantage of the automatic
CSRF Protection.
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.
Be aware that the createView() method should be called after handleRequest is called.
Otherwise, changes done in the *_SUBMIT events aren't applied to the view (like validation errors).
This controller follows a common pattern for handling forms, and has three possible paths:
1. When initially loading the page in a browser, the form is simply created and rendered.
handleRequest()1 recognizes that the form was not submitted and does nothing.
isSubmitted()2 returns false if the form was not submitted.
2. When the user submits the form, handleRequest()3 recognizes this and immediately writes
the submitted data back into the task and dueDate properties of the $task object. Then this
object is validated. If it is invalid (validation is covered in the next section), isValid()4 returns
false, so the form is rendered together with all validation errors;
1. http://api.symfony.com/3.1/Symfony/Component/Form/FormInterface.html#method_handleRequest
2. http://api.symfony.com/3.1/Symfony/Component/Form/FormInterface.html#method_isSubmitted
3. http://api.symfony.com/3.1/Symfony/Component/Form/FormInterface.html#method_handleRequest
4. http://api.symfony.com/3.1/Symfony/Component/Form/FormInterface.html#method_isValid
Redirecting a user after a successful form submission prevents the user from being able to
hit the "Refresh" button of their browser and re-post the data.
If you need more control over exactly when your form is submitted or which data is passed to it, you can use
the submit()6 for this. Read more about it in the cookbook.
In your controller, use the button's isClicked()7 method for querying if the "Save and add" button
was clicked:
Form Validation
In the previous section, you learned how a form can be submitted with valid or invalid data. In Symfony,
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 14-7
5. http://api.symfony.com/3.1/Symfony/Component/Form/FormInterface.html#method_isValid
6. http://api.symfony.com/3.1/Symfony/Component/Form/FormInterface.html#method_submit
7. http://api.symfony.com/3.1/Symfony/Component/Form/ClickableInterface.html#method_isClicked
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 Symfony and has its own dedicated chapter.
Validation Groups
If your object takes advantage of validation groups, you'll need to specify which validation group(s) your
form should use:
Listing 14-9
$form = $this->createFormBuilder($users, array(
'validation_groups' => array('registration'),
))->add(...);
If you're creating form classes (a good practice), then you'll need to add the following to the
configureOptions() method:
Listing 14-10 1 use Symfony\Component\OptionsResolver\OptionsResolver;
2
3 public function configureOptions(OptionsResolver $resolver)
4 {
5 $resolver->setDefaults(array(
6 'validation_groups' => array('registration'),
7 ));
8 }
Disabling Validation
Sometimes it is useful to suppress the validation of a form altogether. For these cases you can set the
validation_groups option to false:
Listing 14-11 1 use Symfony\Component\OptionsResolver\OptionsResolver;
2
3 public function configureOptions(OptionsResolver $resolver)
4 {
5 $resolver->setDefaults(array(
6 'validation_groups' => false,
7 ));
8 }
Note that when you do that, the form will still run basic integrity checks, for example whether an
uploaded file was too large or whether non-existing fields were submitted. If you want to suppress
validation, you can use the POST_SUBMIT event.
This will call the static method determineValidationGroups() on the Client class after the form
is submitted, but before validation is executed. The Form object is passed as an argument to that method
(see next example). You can also define whole logic inline by using a Closure:
You can find more information about how the validation groups and the default constraints work in the
book section about validation groups.
Then, we configure the button for returning to the previous step to run specific validation groups. In this
example, we want it to suppress validation, so we set its validation_groups option to false:
Now the form will skip your validation constraints. It will still validate basic integrity constraints, such
as checking whether an uploaded file was too large or whether you tried to submit text in a number field.
To see how to use a service to resolve validation_groups dynamically read the How to Dynamically Configure
Validation Groups chapter in the cookbook.
Choice Fields
• ChoiceType
• EntityType
• CountryType
• LanguageType
• LocaleType
• TimezoneType
• CurrencyType
Other Fields
• CheckboxType
• FileType
• RadioType
Field Groups
• CollectionType
• RepeatedType
Hidden Fields
• HiddenType
Buttons
• ButtonType
• ResetType
• SubmitType
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.
Listing 14-17
->add('dueDate', DateType::class, 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.
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.
The label for a field can also be set in the template rendering the form, see below. If you don't need
a label associated to your input, you can disable it by setting its value to false.
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).
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\Length). 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.
max_length
If the field is some sort of text field, then the max_length option can be guessed from the validation
constraints (if Length or Range 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 14-21
->add('task', null, array('attr' => array('maxlength' => 4)))
You already know the form_start() and form_end() functions, but what do the other functions do?
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.
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:
If the auto-generated label for a field isn't quite right, you can explicitly specify it:
Some field types have additional rendering options that can be passed to the widget. These options are
documented with each type, but one common option 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:
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 14-27 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:
This example assumes that you've created a route called target_route that points to the
controller that processes the form.
In Creating Form Classes you will learn how to move the form building code into separate classes. When
using an external form class in the controller, you can pass the action and method as form options:
Finally, you can override the action and method in the template by passing them to the form() or the
form_start() helper:
If the form's method is not GET or POST, but PUT, PATCH or DELETE, Symfony will insert a
hidden field with the name _method that stores this method. The form will be submitted in a
normal POST request, but Symfony's router is capable of detecting the _method parameter and will
interpret it as a PUT, PATCH or DELETE request. Read the cookbook chapter "How to Use HTTP
Methods beyond GET and POST in Routes" for more information.
This new class contains all the directions needed to create the task form. It can be used to quickly build
a form object in the controller:
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.
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:
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:
$form->get('dueDate')->getData();
Listing 14-36
Services and the service container will be handled later on in this book. Things will be more clear after
reading that chapter.
You might want to use a service defined as app.my_service in your form type. Create a constructor to
your form type to receive the service:
Listing 14-38
If, for some reason, you don't have access to your original $task object, you can fetch it from the form:
Listing 14-41
$task = $form->getData();
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.
The Valid Constraint has been added to the property category. This cascades the validation to
the corresponding entity. If you omit this constraint the child entity would not be validated.
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:
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 14-45 1 use Symfony\Component\Form\FormBuilderInterface;
2 use AppBundle\Form\CategoryType;
3
4 public function buildForm(FormBuilderInterface $builder, array $options)
5 {
6 // ...
7
8 $builder->add('category', CategoryType::class);
9 }
The fields from CategoryType can now be rendered alongside those from the TaskType class.
Render the Category fields in the same way as the original Task fields:
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.
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:
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.
For a more extensive discussion, see How to Customize Form Rendering.
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:
There are actually 2 other parts - rows and rest - 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).
The "parent" type of each field type is available in the form type reference for each field type.
8. https://github.com/symfony/symfony/blob/master/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig
9. https://github.com/symfony/symfony/tree/master/src/Symfony/Bridge/Twig
10. https://github.com/symfony/symfony/tree/master/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form
Twig
To automatically include the customized blocks from the fields.html.twig template created earlier
in all templates, modify your application configuration file:
Any blocks inside the fields.html.twig template are now used globally to define form output.
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 app/Resources/views/Form directory
created earlier in all templates, modify your application configuration file:
CSRF Protection
CSRF - or Cross-site request forgery11 - 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_end()
function in your template, which ensures that all un-rendered fields are output.
Since the token is stored in the session, a session is started automatically as soon as you render a
form with CSRF protection.
To disable CSRF protection, set the csrf_protection option to false. Customizations can also be
made globally in your project. For more information, see the form configuration reference section.
The csrf_token_id option is optional but greatly enhances the security of the generated token by
making it different for each form.
CSRF tokens are meant to be different for every user. This is why you need to be cautious if you
try to cache pages with forms including this kind of protection. For more information, see Caching
Pages that Contain CSRF Protected Forms.
11. http://en.wikipedia.org/wiki/Cross-site_request_forgery
Listing 14-53 1 // make sure you've imported the Request namespace above the class
2 use Symfony\Component\HttpFoundation\Request;
3 // ...
4
5 public function contactAction(Request $request)
6 {
7 $defaultData = array('message' => 'Type your message here');
8 $form = $this->createFormBuilder($defaultData)
9 ->add('name', TextType::class)
10 ->add('email', EmailType::class)
11 ->add('message', TextareaType::class)
12 ->add('send', SubmitType::class)
13 ->getForm();
14
15 $form->handleRequest($request);
16
17 if ($form->isValid()) {
18 // data is an array with "name", "email", and "message" keys
19 $data = $form->getData();
20 }
21
22 // ... render the form
23 }
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:
$request->request->get('name');
Listing 14-54
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 component.
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. If your form is mapped to an object (i.e. you're
using the data_class option or passing an object to your form), this is almost always the approach you
want to use. See Validation for more details.
But if the form is not mapped to an object and you instead want to retrieve a simple array of your
submitted data, how can you add constraints to the data of your form?
The answer is to setup the constraints yourself, and attach them to the individual fields. The overall
approach is covered a bit more in the validation chapter, but here's a short example:
Listing 14-55
If you are using validation groups, you need to either reference the Default group when creating
the form, or set the correct group on the constraint you are adding.
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 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.
Symfony's security system is incredibly powerful, but it can also be confusing to set up. In this chapter,
you'll learn how to set up your application's security step-by-step, from configuring your firewall and
how you load users to denying access and fetching the User object. Depending on what you need,
sometimes the initial setup can be tough. But once it's done, Symfony's security system is both flexible
and (hopefully) fun to work with.
Since there's a lot to talk about, this chapter is organized into a few big sections:
1. Initial security.yml setup (authentication);
2. Denying access to your app (authorization);
3. Fetching the current User object.
These are followed by a number of small (but still captivating) sections, like logging out and encoding
user passwords.
The firewalls key is the heart of your security configuration. The dev firewall isn't important, it just
makes sure that Symfony's development tools - which live under URLs like /_profiler and /_wdt
aren't blocked by your security.
All other URLs will be handled by the default firewall (no pattern key means it matches all URLs).
You can think of the firewall like your security system, and so it usually makes sense to have just one
main firewall. But this does not mean that every URL requires authentication - the anonymous key takes
care of this. In fact, if you go to the homepage right now, you'll have access and you'll see that you're
"authenticated" as anon.. Don't be fooled by the "Yes" next to Authenticated, you're just an anonymous
user:
Security is highly configurable and there's a Security Configuration Reference that shows all of the
options with some extra explanation.
Simple! To try this, you need to require the user to be logged in to see a page. To make things interesting,
create a new page at /admin. For example, if you use annotations, create something like this:
Next, add an access_control entry to security.yml that requires the user to be logged in to access
this URL:
You'll learn more about this ROLE_ADMIN thing and denying access later in the 2) Denying Access,
Roles and other Authorization section.
Great! Now, if you go to /admin, you'll see the HTTP basic auth prompt:
But who can you login as? Where do users come from?
Want to use a traditional login form? Great! See How to Build a Traditional Login Form. What other
methods are supported? See the Configuration Reference or build your own.
If your application logs users in via a third-party service such as Google, Facebook or Twitter, check
out the HWIOAuthBundle1 community bundle.
Like with firewalls, you can have multiple providers, but you'll probably only need one. If you do
have multiple, you can configure which one provider to use for your firewall under its provider key
(e.g. provider: in_memory).
See How to Use multiple User Providers for all the details about multiple providers setup.
Try to login using username admin and password kitten. You should see an error!
User providers load user information and put it into a User object. If you load users from the database or
some other source, you'll use your own custom User class. But when you use the "in memory" provider, it
gives you a Symfony\Component\Security\Core\User\User object.
Whatever your User class is, you need to tell Symfony what algorithm was used to encode the passwords.
In this case, the passwords are just plaintext, but in a second, you'll change this to use bcrypt.
If you refresh now, you'll be logged in! The web debug toolbar even tells you who you are and what roles
you have:
1. https://github.com/hwi/HWIOAuthBundle
Of course, your users' passwords now need to be encoded with this exact algorithm. For hardcoded users,
you can use the built-in command:
Everything will now work exactly like before. But if you have dynamic users (e.g. from a database), how
can you programmatically encode the password before inserting them into the database? Don't worry,
see Dynamically Encoding a Password for details.
D) Configuration Done!
Congratulations! You now have a working authentication system that uses HTTP basic auth and loads
users right from the security.yml file.
Your next steps depend on your setup:
• Configure a different way for your users to login, like a login form or something completely custom;
• Load users from a different source, like the database or some other source;
• Learn how to deny access, load the User object and deal with roles in the Authorization section.
In addition to roles (e.g. ROLE_ADMIN), you can protect a resource using other attributes/strings
(e.g. EDIT) and use voters or Symfony's ACL system to give these meaning. This might come in
handy if you need to check if user A can "EDIT" some object B (e.g. a Product with id 5). See Access
Control Lists (ACLs): Securing individual Database Objects.
Roles
When a user logs in, they receive a set of roles (e.g. ROLE_ADMIN). In the example above, these are
hardcoded into security.yml. If you're loading users from the database, these are probably stored on
a column in your table.
All roles you assign to a user must begin with the ROLE_ prefix. Otherwise, they won't be handled by
Symfony's security system in the normal way (i.e. unless you're doing something advanced, assigning
a role like FOO to a user and then checking for FOO as described below will not work).
Roles are simple, and are basically strings that you invent and use as needed. 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.
2. http://php.net/manual/en/function.hash-algos.php
You can also specify a role hierarchy where some roles automatically mean that you also have other roles.
This is great for securing entire sections, but you'll also probably want to secure your individual
controllers as well.
You can define as many URL patterns as you need - each is a regular expression. BUT, only one
will be matched. Symfony will look at each starting at the top, and stop as soon as it finds one
access_control entry that matches the URL.
Listing 15-11 1 # app/config/security.yml
2 security:
3 # ...
4
5 access_control:
6 - { path: ^/admin/users, roles: ROLE_SUPER_ADMIN }
7 - { path: ^/admin, roles: ROLE_ADMIN }
Prepending the path with ^ means that only URLs beginning with the pattern are matched. For example,
a path of simply /admin (without the ^) would match /admin/foo but would also match URLs like
/foo/admin.
In both cases, a special AccessDeniedException3 is thrown, which ultimately triggers a 403 HTTP
response inside Symfony.
That's it! If the user isn't logged in yet, they will be asked to login (e.g. redirected to the login page). If
they are logged in, but do not have the ROLE_ADMIN role, they'll be shown the 403 access denied page
(which you can customize). If they are logged in and have the correct roles, the code will be executed.
Thanks to the SensioFrameworkExtraBundle, you can also secure your controller using annotations:
3. http://api.symfony.com/3.1/Symfony/Component/Security/Core/Exception/AccessDeniedException.html
4. https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/index.html
IS_AUTHENTICATED_FULLY isn't a role, but it kind of acts like one, and every user that has successfully
logged in will have this. In fact, there are three special attributes like this:
• IS_AUTHENTICATED_REMEMBERED:
All logged in users have this, even if they are logged in because of a
"remember me cookie". Even if you don't use the remember me functionality, you can use this to
check if the user is logged in.
• IS_AUTHENTICATED_FULLY: This is similar to IS_AUTHENTICATED_REMEMBERED, but stronger. Users who are logged
in only because of a "remember me cookie" will have IS_AUTHENTICATED_REMEMBERED but will not have
IS_AUTHENTICATED_FULLY.
• IS_AUTHENTICATED_ANONYMOUSLY: All users (even anonymous ones) have this - this is useful when
whitelisting URLs to guarantee access - some details are in How Does the Security access_control
Work?.
For more details on expressions and security, see Security: Complex Access Controls with Expressions.
• Voters allow you to write own business logic (e.g. the user can edit this post because they were
the creator) to determine access. You'll probably want this option - it's flexible enough to solve the
above situation.
• ACLs allow you to create a database structure where you can assign any arbitrary user any access
(e.g. EDIT, VIEW) to any object in your system. Use this if you need an admin user to be able to
grant customized access across your system via some admin interface.
The user will be an object and the class of that object will depend on your user provider.
Now you can call whatever methods are on your User object. For example, if your User object has a
getFirstName() method, you could use that:
Listing 15-18 1 use Symfony\Component\HttpFoundation\Response;
2 // ...
3
4 public function indexAction()
5 {
6 // ...
7
8 return new Response('Well hi there '.$user->getFirstName());
9 }
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:
Next, you'll need to create a route for this URL (but not a controller):
And that's it! By sending a user to /logout (or whatever you configure the path to be), Symfony will
un-authenticate the current user.
Once the user has been logged out, they will be redirected to whatever path is defined by the target
parameter above (e.g. the homepage).
If you need to do something more interesting after logging out, you can specify a logout success
handler by adding a success_handler key and pointing it to a service id of a class that
implements LogoutSuccessHandlerInterface5. See Security Configuration Reference.
Notice that when using http-basic authenticated firewalls, there is no real way to log out : the only
way to log out is to have the browser stop sending your name and password on every request.
Clearing your browser cache or restarting your browser usually helps. Some web developer tools
might be helpful here too.
If, for example, you're storing users in the database, you'll need to encode the users' passwords before
inserting them. No matter what algorithm you configure for your user object, the hashed password can
always be determined in the following way from a controller:
5. http://api.symfony.com/3.1/Symfony/Component/Security/Http/Logout/LogoutSuccessHandlerInterface.html
6. https://en.wikipedia.org/wiki/Cryptographic_hash_function
In order for this to work, just make sure that you have the encoder for your user class (e.g.
AppBundle\Entity\User) configured under the encoders key in app/config/security.yml.
The $encoder object also has an isPasswordValid method, which takes the User object as the first
argument and the plain password to check as the second argument.
When you allow a user to submit a plaintext password (e.g. registration form, change password
form), you must have validation that guarantees that the password is 4096 characters or fewer. Read
more details in How to implement a simple Registration Form.
Hierarchical Roles
Instead of associating many roles to users, you can define role inheritance rules by creating a role
hierarchy:
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).
Stateless Authentication
By default, Symfony 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 Symfony):
If you use a form login, Symfony will create a cookie even if you set stateless to true.
A good security practice is to execute this command regularly to be able to update or replace
compromised dependencies as soon as possible. Internally, this command uses the public security
advisories database7 published by the FriendsOfPHP organization.
The security:check command terminates with a non-zero exit code if any of your dependencies
is affected by a known security vulnerability. Therefore, you can easily integrate it in your build
process.
Final Words
Woh! Nice work! You now know more than the basics of security. The hardest parts are when you have
custom requirements: like a custom authentication strategy (e.g. API tokens), complex authorization
logic and many other things (because security is complex!).
Fortunately, there are a lot of Security Cookbook Articles aimed at describing many of these situations.
Also, see the Security Reference Section. Many of the options don't have specific details, but seeing the
full possible configuration tree may be useful.
Good luck!
7. https://github.com/FriendsOfPHP/security-advisories
8. https://packagist.org/packages/sensio/distribution-bundle
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. Symfony 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.
1. http://www.w3.org/Protocols/rfc2616/rfc2616.html
Types of Caches
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 when caching responses
containing content that is specific to exactly one user (e.g. account information) is discussed.
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.
2. http://2ndscale.com/writings/things-caches-do
3. http://www.mnot.net/cache_docs/
4. https://www.varnish-cache.org/
5. http://wiki.squid-cache.org/SquidFaq/ReverseProxy
The caching kernel will immediately act as a reverse proxy - caching responses from your application and
returning them to the client.
If you're using the framework.http_method_override option to read the HTTP method from a
_method parameter, see the above link for a tweak you need to make.
The cache kernel has a special getLog() method that returns a string representation of what
happened in the cache layer. In the development environment, use it to debug and validate your
cache strategy:
error_log($kernel->getLog());
Listing 16-2
The AppCache object has a sensible default configuration, but it can be finely tuned via a set of options
you can set by overriding the getOptions()6 method:
6. http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/HttpCache/HttpCache.html#method_getOptions
private_headers
Set of request headers that trigger "private" Cache-Control behavior on responses that don't explicitly
state whether the response is public or private via a Cache-Control directive (default: Authorization and
Cookie).
allow_reload
Specifies whether the client can force a cache reload by including a Cache-Control "no-cache" directive
in the request. Set it to true for compliance with RFC 2616 (default: false).
allow_revalidate
Specifies whether the client can force a cache revalidate by including a Cache-Control "max-age=0"
directive in the request. Set it to true for compliance with RFC 2616 (default: false).
stale_while_revalidate
Specifies the default number of seconds (the granularity is the second as the Response TTL precision
is a second) during which the cache can immediately return a stale response while it revalidates it in
the background (default: 2); this setting is overridden by the stale-while-revalidate HTTP Cache-Control
extension (see RFC 5861).
stale_if_error
Specifies the default number of seconds (the granularity is the second) during which the cache can
serve a stale response when an error is encountered (default: 60). This setting is overridden by the
stale-if-error HTTP Cache-Control extension (see RFC 5861).
The performance of the Symfony 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.
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. HTTP caching is the
part of that language that allows clients and servers to exchange information related to caching.
HTTP specifies four response cache headers that are looked at here:
• 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, Validation and
Invalidation section.
Symfony provides an abstraction around the Cache-Control header to make its creation more
manageable:
If you need to set cache headers for many different controller actions, you might want to look into
the FOSHttpCacheBundle7. It provides a way to define cache headers based on the URL pattern and
other request properties.
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
Symfony 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 means 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.
• If no cache header is defined (Cache-Control, Expires, ETag or Last-Modified), Cache-Control is set to no-cache,
meaning that the response will not be cached;
• If Cache-Control is empty (but one of the other cache headers is present), its value is set to private,
must-revalidate;
• But if at least one Cache-Control directive is set, and no public or private directives have been explicitly
added, Symfony adds the private directive automatically (except when s-maxage is set).
7. http://foshttpcachebundle.readthedocs.org/
The goal of both models is to never generate the same response twice by relying on a cache to store and
return "fresh" responses. To achieve long caching times but still provide updated content immediately,
cache invalidation is sometimes used.
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.
Listing 16-6
$date = new DateTime();
$date->modify('+600 seconds');
$response->setExpires($date);
8. http://tools.ietf.org/html/rfc2616#section-13.2
9. http://tools.ietf.org/html/rfc2616#section-13.3
10. http://tools.ietf.org/html/rfc2616
11. http://tools.ietf.org/wg/httpbis/
12. http://tools.ietf.org/html/draft-ietf-httpbis-p4-conditional
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 on the local clock to evaluate the Expires
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."
Listing 16-8 1 // Sets the number of seconds after which the response
2 // should no longer be considered fresh
3 $response->setMaxAge(600);
4
5 // Same as above but only for shared caches
6 $response->setSharedMaxAge(600);
The Cache-Control header would take on the following format (it may have additional directives):
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 if the cached response is still valid or if
it needs to be regenerated. 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 only save CPU if you're able to determine that the cached response is still valid by
doing less work than generating the whole page again (see below for an implementation example).
The 304 status code means "Not Modified". It's important because with this status code the response
does not contain the actual content being requested. Instead, the response is simply a light-weight
set of directions that tells the 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.
The isNotModified()13 method compares the If-None-Match sent with the Request with the
ETag header set on the Response. If the two match, the method automatically sets the Response status
code to 304.
The cache sets the If-None-Match header on the request to the ETag of the original cached
response before sending the request back to the app. This is how the cache and server communicate
with each other and decide whether or not the resource has been updated since it was cached.
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, you'll see how validation can be used more
intelligently to determine the validity of a cache without doing so much work.
Symfony also supports weak ETags by passing true as the second argument to the setEtag()14
method.
13. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/Response.html#method_isNotModified
14. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/Response.html#method_setEtag
The isNotModified()15 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 cache sets the If-Modified-Since header on the request to the Last-Modified of the
original cached response before sending the request back to the app. This is how the cache and server
communicate with each other and decide whether or not the resource has been updated since it was
cached.
15. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/Response.html#method_isNotModified
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()16).
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:
The setVary() method takes a header name or an array of header names for which the response varies.
16. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/Response.html#method_setNotModified
You can also define HTTP caching headers for expiration and validation by using annotations. See
the FrameworkExtraBundle documentation17.
Additionally, most cache-related HTTP headers can be set via the single setCache()18 method:
Cache Invalidation
"There are only two hard things in Computer Science: cache invalidation and naming things." -- Phil
Karlton
Once an URL is cached by a gateway cache, the cache will not ask the application for that content
anymore. This allows the cache to provide fast responses and reduces the load on your application.
However, you risk delivering outdated content. A way out of this dilemma is to use long cache lifetimes,
but to actively notify the gateway cache when content changes. Reverse proxies usually provide a channel
to receive such notifications, typically through special HTTP requests.
While cache invalidation is powerful, avoid it when possible. If you fail to invalidate something,
outdated caches will be served for a potentially long time. Instead, use short cache lifetimes or use
the validation model, and adjust your controllers to perform efficient validation checks as explained
in Optimizing your Code with Validation.
Furthermore, since invalidation is a topic specific to each type of reverse proxy, using this concept
will tie you to a specific reverse proxy or need additional efforts to support different proxies.
17. https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/cache.html
18. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/Response.html#method_setCache
If you want to use cache invalidation, have a look at the FOSHttpCacheBundle19. This bundle
provides services to help with various cache invalidation concepts and also documents the
configuration for a couple of common caching proxies.
If one content corresponds to one URL, the PURGE model works well. You send a request to the cache
proxy with the HTTP method PURGE (using the word "PURGE" is a convention, technically this can be
any string) instead of GET and make the cache proxy detect this and remove the data from the cache
instead of going to the application to get a response.
Here is how you can configure the Symfony reverse proxy to support the PURGE HTTP method:
You must protect the PURGE HTTP method somehow to avoid random people purging your cached
data.
Purge instructs the cache to drop a resource in all its variants (according to the Vary header, see above).
An alternative to purging is refreshing a content. Refreshing means that the caching proxy is instructed
to discard its local cache and fetch the content again. This way, the new content is already available in
the cache. The drawback of refreshing is that variants are not invalidated.
In many applications, the same content bit is used on various pages with different URLs. More flexible
concepts exist for those cases:
• Banning invalidates responses matching regular expressions on the URL or other criteria;
19. http://foshttpcachebundle.readthedocs.org/
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, Symfony makes the process of including them almost
effortless.
Now, suppose you have a page that is relatively static, except for a news ticker at the bottom of the
content. With ESI, you can cache the news ticker independent of the rest of the page.
20. http://www.w3.org/TR/esi-lang
In this example, the full-page cache has a lifetime of ten minutes. Next, 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), Symfony uses the
standard render helper to configure ESI tags:
By using the esi renderer (via the render_esi Twig function), you tell Symfony 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.
As you'll see below, the maxPerPage variable you pass is available as an argument to your controller
(i.e. $maxPerPage). The variables passed through render_esi also become part of the cache key
so that you have unique caches for each combination of variables and values.
When using the default render function (or setting the renderer to inline), Symfony merges the
included page content into the main one before sending the response to the client. But if you use the esi
renderer (i.e. call render_esi) and if Symfony 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, Symfony
will just merge the included page content within the main one as it would have done if you had used
render.
Symfony detects if a gateway cache supports ESI via another Akamai specification that is supported
out of the box by the Symfony reverse proxy.
The embedded action can now specify its own caching rules, entirely independent of the master page.
With ESI, the full page cache will be valid for 600 seconds, but the news component cache will only last
for 60 seconds.
When using a controller reference, the ESI tag should reference the embedded action as an accessible
URL so the gateway cache can fetch it independently of the rest of the page. Symfony takes care of
generating a unique URL for any controller reference and it is able to route them properly thanks to the
FragmentListener21 that must be enabled in your configuration:
Listing 16-23 1 # app/config/config.yml
2 framework:
3 # ...
4 fragments: { path: /_fragment }
One great advantage of the ESI renderer is that you can make your application as dynamic as needed and
at the same time, hit the application as little as possible.
The fragment listener only responds to signed requests. Requests are only signed when using the
fragment renderer and the render_esi Twig function.
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.
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.
Summary
Symfony was designed to follow the proven rules of the road: HTTP. Caching is no exception. Mastering
the Symfony cache system means becoming familiar with the HTTP cache models and using them
effectively. This means that, instead of relying only on Symfony documentation and code examples, you
have access to a world of knowledge related to HTTP caching and gateway caches such as Varnish.
21. http://api.symfony.com/3.1/Symfony/Component/HttpKernel/EventListener/FragmentListener.html
The term "internationalization" (often abbreviated i18n1) refers to the process of abstracting strings
and other locale-specific pieces out of your application 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:
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). The ISO
639-12 language code, an underscore (_), then the ISO 3166-1 alpha-23 country code (e.g. fr_FR for
French/France) is recommended.
In this chapter, you'll learn how to use the Translation component in the Symfony Framework. You can
read the Translation component documentation to learn even more. Overall, the process has several steps:
1. Enable and configure Symfony's translation service;
2. Abstract strings (i.e. "messages") by wrapping them in calls to the Translator ("Basic
Translation");
3. Create translation resources/files 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. https://en.wikipedia.org/wiki/Internationalization_and_localization
2. https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
3. https://en.wikipedia.org/wiki/ISO_3166-1#Current_codes
See Fallback Translation Locales for details on the fallbacks key and what Symfony does when it
doesn't find a translation.
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 you're translating a simple
message from inside a controller:
When this code is executed, Symfony will attempt to translate the message "Symfony is great" based
on the locale of the user. For this to work, you need to tell Symfony how to translate the message
via a "translation resource", which is usually a file that contains a collection of translations for a given
locale. This "dictionary" of translations can be created in several different formats, XLIFF being the
recommended format:
For information on where these files should be located, see Translation Resource/File Names and
Locations.
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 Symfony. You can also translate the message inside your templates.
4. http://api.symfony.com/3.1/Symfony/Component/Translation/Translator.html
5. http://api.symfony.com/3.1/Symfony/Component/Translation/Translator.html#method_trans
• The locale of the current user, which is stored on the request is determined;
• A catalog (e.g. big collection) 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.
• 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, Symfony 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:
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").
For details on how to handle this situation, see Message Placeholders in the components documentation.
For how to do this in templates, see Twig Templates.
Pluralization
Another complication is when you have translations that may or may not be plural, based on some
variable:
To handle this, use the transChoice()6 method or the transchoice tag/filter in your template.
For much more information, see Pluralization in the Translation component documentation.
Translations in Templates
Most of the time, translation occurs in templates. Symfony provides native support for both Twig and
PHP templates.
6. http://api.symfony.com/3.1/Symfony/Component/Translation/Translator.html#method_transChoice
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.
The %var% notation of placeholders is required when translating in Twig templates using the tag.
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 17-8 1 {% trans with {'%name%': 'Fabien'} from "app" %}Hello %name%{% endtrans %}
2
3 {% trans with {'%name%': 'Fabien'} from "app" into "fr" %}Hello %name%{% endtrans %}
4
5 {% transchoice count with {'%name%': 'Fabien'} from "app" %}
6 {0} %name%, there are no apples|{1} %name%, there is one apple|]1,Inf[ %name%, there are %count% apples
7 {% endtranschoice %}
The trans and transchoice filters can be used to translate variable texts and complex expressions:
Using the translation tags or filters have the same effect, but with one subtle difference: automatic
output escaping is only applied to translations using a filter. In other words, if you need to be sure
that your translated message is not output escaped, you must apply the raw filter after the translation
filter:
Note that this only influences the current template, not any "included" template (in order to avoid
side effects).
PHP Templates
The translator service is accessible in PHP templates through the translator helper:
The locations are listed here 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 overridden 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 fall back
to the lower priority message files.
The filename of the translation files is also important: each message file must be named according to the
following path: domain.locale.loader:
• domain: An optional way to organize messages into groups (e.g. admin, navigation or the default
messages) - see Using Message Domains;
• locale: The locale that the translations are for (e.g. en_GB, en, etc);
• loader: How Symfony should load and parse the file (e.g. xlf, php, yml, etc).
The loader can be the name of any registered loader. By default, Symfony provides many loaders,
including:
The choice of which loader to use is entirely up to you and is a matter of taste. The recommended option
is to use xlf for translations. For more options, see Loading Message Catalogs.
You can also store translations in a database, or any other storage by providing a custom class
implementing the LoaderInterface7 interface. See the translation.loader tag for more
information.
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 resources:
When Symfony doesn't find a translation in the given locale, it will add the missing translation to the
log file. For details, see logging.
To set the user's locale, you may want to create a custom event listener so that it's set before any other
parts of the system (i.e. the translator) need it:
7. http://api.symfony.com/3.1/Symfony/Component/Translation/Loader/LoaderInterface.html
Read Making the Locale "Sticky" during a User's Session for more information on making the user's locale
"sticky" to their session.
Setting the locale using $request->setLocale() in the controller is too late to affect the
translator. Either set the locale via a listener (like above), the URL (see next) or call setLocale()
directly on the translator service.
See the The Locale and the URL section below about setting the locale via routing.
When using the special _locale parameter in a route, the matched locale will automatically be set on
the Request and can be retrieved via the getLocale()8 method. In other words, if a user visits the URI
/fr/contact, the locale fr will automatically be set as the locale for the current request.
You can now use the locale to create routes to other translated pages in your application.
Read How to Use Service Container Parameters in your Routes to learn how to avoid hardcoding the
_locale requirement in all your routes.
8. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/Request.html#method_getLocale
Add constraints through 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:
Create a translation file under the validators catalog for the constraint messages, typically in the
Resources/translations/ directory of the bundle.
Listing 17-21 1 <!-- validators.en.xlf -->
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="author.name.not_blank">
7 <source>author.name.not_blank</source>
8 <target>Please enter an author name.</target>
9 </trans-unit>
10 </body>
11 </file>
12 </xliff>
9. http://atlantic18.github.io/DoctrineExtensions/doc/translatable.html
10. https://github.com/KnpLabs/DoctrineBehaviors
The extractors are not able to inspect the messages translated outside templates which means that
translator usages in form labels or inside your controllers won't be detected. Dynamic translations
involving variables or expressions are not detected in templates, which means this example won't be
analyzed:
Suppose your application's default_locale is fr and you have configured en as the fallback locale (see
Configuration and Fallback Translation Locales for how to configure these). And suppose you've already
setup some translations for the fr locale inside an AcmeDemoBundle:
It indicates that the message Symfony2 is great is unused because it is translated, but you haven't
used it anywhere yet.
Now, if you translate the message in one of your templates, you will get this output:
The state is empty which means the message is translated in the fr locale and used in one or more
templates.
If you delete the message Symfony2 is great from your translation file for the fr locale and run the
command, you will get:
You can see that the translations of the message are identical in the fr and en locales which means this
message was probably copied from French to English and maybe you forgot to translate it.
By default all domains are inspected, but it is possible to specify a single domain:
When bundles have a lot of messages, it is useful to display only the unused or only the missing messages,
by using the --only-unused or --only-missing switches:
Summary
With the Symfony 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()11 or transChoice()12
methods (learn about this in Using the Translator);
• Translate each message into multiple locales by creating translation message files. Symfony
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 on the user's session.
11. http://api.symfony.com/3.1/Symfony/Component/Translation/Translator.html#method_trans
12. http://api.symfony.com/3.1/Symfony/Component/Translation/Translator.html#method_transChoice
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.
This chapter is about a special PHP object in Symfony 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. Since all core
Symfony classes use the container, you'll learn how to extend, configure and use any object in Symfony.
In large part, the service container is the biggest contributor to the speed and extensibility of Symfony.
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.
If you want to know a lot more after reading this chapter, check out the DependencyInjection
component documentation.
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!
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 Symfony 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.
Listing 18-1
use AppBundle\Mailer;
This is easy enough. The imaginary Mailer class allows you to configure the method used to deliver the
email messages (e.g. sendmail, smtp, etc). But what if you wanted to use the mailer service somewhere
else? You certainly don't want to repeat the mailer configuration every time you need to use the Mailer
object. What if you needed to change the transport from sendmail to smtp everywhere in the
application? You'd need to hunt down every place you create a Mailer service and change it.
When Symfony 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).
1. https://en.wikipedia.org/wiki/Service-oriented_architecture
When you ask for the app.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 a 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 you'll
learn later how you can configure a service that has multiple instances in the "How to Define Non Shared
Services" cookbook article.
In this example, the controller extends Symfony's base Controller, which gives you access to the
service container itself. You can then use the get method to locate and retrieve the app.mailer
service from the service container.
Service Parameters
The creation of new services (i.e. objects) via the container is pretty straightforward. Parameters make
defining services more organized and flexible:
The end result is exactly the same as before - the difference is only in how you defined the service. By
enclosing the app.mailer.transport string with percent (%) signs, the container knows to look for a
parameter with that name. 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:
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 (this follows shortly), using parameters allows the service to be
easily customized in your application.
The choice of using or not using parameters is up to you. High-quality third-party bundles will always
use parameters as they make the service stored in the container more configurable. For the services in
your application, however, you may not need the flexibility of parameters.
Array Parameters
Parameters can also contain array values. See Array Parameters.
The definition itself hasn't changed, only its location. To make the service container load the definitions
in this resource file, use the imports key in any already loaded resource (e.g. app/config/
services.yml or app/config/config.yml):
Listing 18-8 1 # app/config/services.yml
2 imports:
3 - { resource: services/mailer.yml }
The resource location, for files, is either a relative path from the current file or an absolute path.
Due to the way in which parameters are resolved, you cannot use them to build paths in imports
dynamically. This means that something like the following doesn't work:
When the resources are parsed, the container looks for an extension that can handle the framework
directive. The extension in question, which lives in the FrameworkBundle, is invoked and the service
configuration for the FrameworkBundle is loaded.
The settings under the framework directive (e.g. form: true) indicate that the extension should load
all services related to the Form component. If form was disabled, these services wouldn't be loaded and
Form integration would not be available.
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.
If you want to use dependency injection extensions in your own shared bundles and provide user friendly
configuration, take a look at the "How to Load Service Configuration inside a Bundle" cookbook recipe.
Without using the service container, you can create a new NewsletterManager fairly easily from
inside a controller:
This approach is fine, but what if you decide later that the NewsletterManager class needs a second
or third constructor argument? What if you decide to refactor your 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 you a much more appealing option:
In YAML, the special @app.mailer syntax tells the container to look for a service named app.mailer
and to pass that object into the constructor of NewsletterManager. In this case, however, the
specified service app.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.
But instead of hardcoding this, how could we get this value from the getMailerMethod() of the new
mailer_configuration service? One way is to use an expression:
Listing 18-15 1 # app/config/config.yml
2 services:
3 my_mailer:
4 class: AppBundle\Mailer
5 arguments: ["@=service('mailer_configuration').getMailerMethod()"]
To learn more about the expression language syntax, see The Expression Syntax.
In this context, you have access to 2 functions:
service
Returns a given service (see the example above).
parameter
Returns a specific parameter value (syntax is just like service).
You also have access to the ContainerBuilder2 via a container variable. Here's another example:
Listing 18-17
2. http://api.symfony.com/3.1/Symfony/Component/DependencyInjection/ContainerBuilder.html
Injecting the dependency by the setter method just needs a change of syntax:
The approaches presented in this section are called "constructor injection" and "setter injection". The
Symfony service container also supports "property injection".
Now, just inject the request_stack, which behaves like any normal service:
3. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/RequestStack.html#method_getCurrentRequest
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 object as a
Controller Argument for details.
In Symfony, 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 through the request stack (request_stack).
You can take this a step further by using these services inside services that you've created for your
application. Beginning by modifying the NewsletterManager to use the real Symfony mailer service
(instead of the pretend app.mailer). Also pass the templating engine service to the
NewsletterManager so that it can generate the email content via a template:
Listing 18-25 1 // src/AppBundle/Newsletter/NewsletterManager.php
2 namespace AppBundle\Newsletter;
3
4 use Symfony\Component\Templating\EngineInterface;
5
6 class NewsletterManager
7 {
8 protected $mailer;
9
10 protected $templating;
11
12 public function __construct(
13 \Swift_Mailer $mailer,
14 EngineInterface $templating
15 ) {
16 $this->mailer = $mailer;
17 $this->templating = $templating;
18 }
19
20 // ...
21 }
Be sure that the swiftmailer entry appears in your application configuration. As was mentioned
in Importing Configuration via Container Extensions, the swiftmailer key invokes the service
extension from the SwiftmailerBundle, which registers the mailer service.
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:
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 Symfony or other third-party bundles that your service should be registered
or used in some special way by the bundle.
For a list of all the tags available in the core Symfony Framework, check out The Dependency Injection
Tags. Each of these has a different effect on your service and many tags require additional arguments
(beyond just the name parameter).
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:
By default, only public services are shown, but you can also view private services:
If a private service is only used as an argument to just one other service, it won't be displayed by the
debug:container command, even when using the --show-private option. See Inline Private
Services for more details.
You can get more detailed information about a particular service by specifying its id:
Symfony 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.
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. https://en.wikipedia.org/wiki/List_of_PHP_accelerators
2. http://php.net/manual/en/book.opcache.php
3. http://php.net/manual/en/book.apc.php
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.
4. https://github.com/symfony/symfony-standard/blob/master/app/autoload.php
5. http://api.symfony.com/3.1/Symfony/Component/ClassLoader/ApcClassLoader.html
6. https://github.com/sensiolabs/SensioDistributionBundle/blob/master/Composer/ScriptHandler.php
Listing 19-3
include_once __DIR__.'/../var/bootstrap.php.cache';
Note that there are two disadvantages when using a bootstrap file:
• the file needs to be regenerated whenever any of the original sources change (i.e. when you update
the Symfony source or vendor libraries);
• when debugging, one will need to place break points inside the bootstrap file.
If you're using the Symfony Standard Edition, the bootstrap file is automatically rebuilt after updating the
vendor libraries via the composer install command.