Symfony Cookbook 3.1
Symfony Cookbook 3.1
Version: 3.1
generated on July 28, 2016
The Cookbook (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
Then, enable the bundle in the AppKernel.php file of your Symfony application:
Finally, add the following minimal configuration to enable Assetic support in your application:
PDF brought to you by Chapter 1: How to Use Assetic for Asset Management | 7
But with Assetic, you can manipulate these assets however you want (or load them from anywhere) before
serving them. This means you can:
Assets
Using Assetic provides many advantages over directly serving the files. The files do not need to be stored
where they are served from and can be drawn from various sources such as from within a bundle.
You can use Assetic to process CSS stylesheets, JavaScript files and images. The philosophy behind
adding either is basically the same, but with a slightly different syntax.
If your application templates use the default block names from the Symfony Standard Edition, the
javascripts tag will most commonly live in the javascripts block:
You can also include CSS stylesheets: see Including CSS Stylesheets.
In this example, all files in the Resources/public/js/ directory of the AppBundle will be loaded and
served from a different location. The actual rendered tag might simply look like:
PDF brought to you by Chapter 1: How to Use Assetic for Asset Management | 8
If your application templates use the default block names from the Symfony Standard Edition, the
stylesheets tag will most commonly live in the stylesheets block:
But because Assetic changes the paths to your assets, this will break any background images (or other
paths) that uses relative paths, unless you use the cssrewrite filter.
Notice that in the original example that included JavaScript files, you referred to the files using
a path like @AppBundle/Resources/public/file.js, but that in this example, you referred
to the CSS files using their actual, publicly-accessible path: bundles/app/css. You can use
either, except that there is a known issue that causes the cssrewrite filter to fail when using the
@AppBundle syntax for CSS stylesheets.
Including Images
To include an image you can use the image tag.
You can also use Assetic for image optimization. More information in How to Use Assetic for Image
Optimization with Twig Functions.
Instead of using Assetic to include images, you may consider using the LiipImagineBundle1
community bundle, which allows to compress and manipulate images (rotate, resize, watermark,
etc.) before serving them.
1. https://github.com/liip/LiipImagineBundle
PDF brought to you by Chapter 1: How to Use Assetic for Asset Management | 9
When using the cssrewrite filter, don't refer to your CSS files using the @AppBundle syntax. See
the note in the above section for details.
Combining Assets
One feature of Assetic is that it will combine many files into one. This helps to reduce the number of
HTTP requests, which is great for front-end performance. It also allows you to maintain the files more
easily by splitting them into manageable parts. This can help with re-usability as you can easily split
project-specific files from those which can be used in other applications, but still serve them as a single
file:
In the dev environment, each file is still served individually, so that you can debug problems more easily.
However, in the prod environment (or more specifically, when the debug flag is false), this will be
rendered as a single script tag, which contains the contents of all of the JavaScript files.
If you're new to Assetic and try to use your application in the prod environment (by using the
app.php controller), you'll likely see that all of your CSS and JS breaks. Don't worry! This is on
purpose. For details on using Assetic in the prod environment, see Dumping Asset Files.
And combining files doesn't only apply to your files. You can also use Assetic to combine third party
assets, such as jQuery, with your own into a single file:
PDF brought to you by Chapter 1: How to Use Assetic for Asset Management | 10
After you have defined the named assets, you can reference them in your templates with the
@named_asset notation:
Listing 1-14 1 {% javascripts
2 '@jquery_and_ui'
3 '@AppBundle/Resources/public/js/*' %}
4 <script src="{{ asset_url }}"></script>
5 {% endjavascripts %}
Filters
Once they're managed by Assetic, you can apply filters to your assets before they are served. This includes
filters that compress the output of your assets for smaller file sizes (and better frontend optimization).
Other filters can compile CoffeeScript files to JavaScript and process SASS into CSS. In fact, Assetic has a
long list of available filters.
Many of the filters do not do the work directly, but use existing third-party libraries to do the heavy-
lifting. This means that you'll often need to install a third-party library to use a filter. The great advantage
of using Assetic to invoke these libraries (as opposed to using them directly) is that instead of having to
run them manually after you work on the files, Assetic will take care of this for you and remove this step
altogether from your development and deployment processes.
To use a filter, you first need to specify it in the Assetic configuration. Adding a filter here doesn't mean
it's being used - it just means that it's available to use (you'll use the filter below).
For example to use the UglifyJS JavaScript minifier the following configuration should be defined:
Now, to actually use the filter on a group of JavaScript files, add it into your template:
A more detailed guide about configuring and using Assetic filters as well as details of Assetic's debug
mode can be found in How to Minify CSS/JS Files (Using UglifyJS and UglifyCSS).
PDF brought to you by Chapter 1: How to Use Assetic for Asset Management | 11
Moreover, that file does not actually exist, nor is it dynamically rendered by Symfony (as the asset files
are in the dev environment). This is on purpose - letting Symfony generate these files dynamically in a
production environment is just too slow.
Instead, each time you use your application in the prod environment (and therefore, each time you
deploy), you should run the following command:
This will physically generate and write each file that you need (e.g. /js/abcd123.js). If you update
any of your assets, you'll need to run this again to regenerate the file.
Next, since Symfony is no longer generating these assets for you, you'll need to dump them manually. To
do so, run the following command:
PDF brought to you by Chapter 1: How to Use Assetic for Asset Management | 12
The assetic:watch command was introduced in AsseticBundle 2.4. In prior versions, you had to use
the --watch option of the assetic:dump command for the same behavior.
Since running this command in the dev environment may generate a bunch of files, it's usually a good
idea to point your generated asset files to some isolated directory (e.g. /js/compiled), to keep things
organized:
PDF brought to you by Chapter 1: How to Use Assetic for Asset Management | 13
Starting from Symfony 2.8, Assetic is no longer included by default in the Symfony Standard Edition.
Refer to this article to learn how to install and enable Assetic in your Symfony application.
The official Symfony Best Practices recommend to use Assetic to manage web assets, unless you are
comfortable with JavaScript-based front-end tools.
Even if those JavaScript-based solutions are the most suitable ones from a technical point of view, using
pure PHP alternative libraries can be useful in some scenarios:
• If you can't install or use npm and the other JavaScript solutions;
• If you prefer to limit the amount of different technologies used in your applications;
• If you want to simplify application deployment.
In this article, you'll learn how to combine and minimize CSS and JavaScript files and how to compile
Sass files using PHP-only libraries with Assetic.
PDF brought to you by Chapter 2: Combining, Compiling and Minimizing Web Assets with PHP Libraries | 14
The value of the formatter option is the fully qualified class name of the formatter used by the filter to
produce the compiled CSS file. Using the compressed formatter will minimize the resulting file, regardless
of whether the original files are regular CSS files or SCSS files.
Next, update your Twig template to add the {% stylesheets %} tag defined by Assetic:
PDF brought to you by Chapter 2: Combining, Compiling and Minimizing Web Assets with PHP Libraries | 15
Next, update the code of your Twig template to add the {% javascripts %} tag defined by Assetic:
This simple configuration combines all the JavaScript files, minimizes the contents and saves the output
in the web/js/app.js file, which is the one that is served to your visitors.
The leading ? character in the jsqueeze filter name tells Assetic to only apply the filter when not in
debug mode. In practice, this means that you'll see unminified files while developing and minimized files
in the prod environment.
PDF brought to you by Chapter 2: Combining, Compiling and Minimizing Web Assets with PHP Libraries | 16
Starting from Symfony 2.8, Assetic is no longer included by default in the Symfony Standard Edition.
Refer to this article to learn how to install and enable Assetic in your Symfony application.
Install UglifyJS
UglifyJS is available as a Node.js3 module. First, you need to install Node.js4 and then, decide the
installation method: global or local.
Global Installation
The global installation method makes all your projects use the very same UglifyJS version, which
simplifies its maintenance. Open your command console and execute the following command (you may
need to run it as a root user):
Now you can execute the global uglifyjs command anywhere on your system:
1. https://github.com/mishoo/UglifyJS
2. https://github.com/fmarcia/UglifyCSS
3. https://nodejs.org/
4. https://nodejs.org/
PDF brought to you by Chapter 3: How to Minify CSS/JS Files (Using UglifyJS and UglifyCSS) | 17
Local Installation
It's also possible to install UglifyJS inside your project only, which is useful when your project requires
a specific UglifyJS version. To do this, install it without the -g option and specify the path where to put
the module:
It is recommended that you install UglifyJS in your app/Resources folder and add the node_modules
folder to version control. Alternatively, you can create an npm package.json5 file and specify your
dependencies there.
Now you can execute the uglifyjs command that lives in the node_modules directory:
The path where UglifyJS is installed may vary depending on your system. To find out where npm
stores the bin folder, execute the following command:
It should output a folder on your system, inside which you should find the UglifyJS executable.
If you installed UglifyJS locally, you can find the bin folder inside the node_modules folder. It's
called .bin in this case.
5. http://browsenpm.org/package.json
PDF brought to you by Chapter 3: How to Minify CSS/JS Files (Using UglifyJS and UglifyCSS) | 18
The above example assumes that you have a bundle called AppBundle and your JavaScript files
are in the Resources/public/js directory under your bundle. However you can include your
JavaScript files no matter where they are.
With the addition of the uglifyjs2 filter to the asset tags above, you should now see minified
JavaScripts coming over the wire much faster.
To try this out, switch to your prod environment (app.php). But before you do, don't forget to clear
your cache and dump your assetic assets.
Instead of adding the filters to the asset tags, you can also configure which filters to apply for each
file in your application configuration file. See Filtering Based on a File Extension for more details.
PDF brought to you by Chapter 3: How to Minify CSS/JS Files (Using UglifyJS and UglifyCSS) | 19
To use the filter for your CSS files, add the filter to the Assetic stylesheets helper:
Just like with the uglifyjs2 filter, if you prefix the filter name with ? (i.e. ?uglifycss), the
minification will only happen when you're not in debug mode.
PDF brought to you by Chapter 3: How to Minify CSS/JS Files (Using UglifyJS and UglifyCSS) | 20
The YUI Compressor is no longer maintained by Yahoo1. That's why you are strongly advised to
avoid using YUI utilities unless strictly necessary. Read How to Minify CSS/JS Files (Using UglifyJS
and UglifyCSS) for a modern and up-to-date alternative.
Starting from Symfony 2.8, Assetic is no longer included by default in the Symfony Standard Edition.
Refer to this article to learn how to install and enable Assetic in your Symfony application.
Yahoo! provides an excellent utility for minifying JavaScripts and stylesheets so they travel over the wire
faster, the YUI Compressor2. Thanks to Assetic, you can take advantage of this tool very easily.
1. http://yuiblog.com/blog/2013/01/24/yui-compressor-has-a-new-owner/
2. http://yui.github.io/yuicompressor/
3. https://github.com/yui/yuicompressor/releases
PDF brought to you by Chapter 4: How to Minify JavaScripts and Stylesheets with YUI Compressor | 21
Windows users need to remember to update config to proper Java location. In Windows7 x64 bit by
default it's C:\Program Files (x86)\Java\jre6\bin\java.exe.
You now have access to two new Assetic filters in your application: yui_css and yui_js. These will
use the YUI Compressor to minify stylesheets and JavaScripts, respectively.
The above example assumes that you have a bundle called AppBundle and your JavaScript files are
in the Resources/public/js directory under your bundle. This isn't important however - you
can include your JavaScript files no matter where they are.
With the addition of the yui_js filter to the asset tags above, you should now see minified JavaScripts
coming over the wire much faster. The same process can be repeated to minify your stylesheets.
Instead of adding the filter to the asset tags, you can also globally enable it by adding the apply_to
attribute to the filter configuration, for example in the yui_js filter apply_to: "\.js$". To
only have the filter applied in production, add this to the config_prod file rather than the common
config file. For details on applying filters by file extension, see Filtering Based on a File Extension.
PDF brought to you by Chapter 4: How to Minify JavaScripts and Stylesheets with YUI Compressor | 22
Starting from Symfony 2.8, Assetic is no longer included by default in the Symfony Standard Edition.
Refer to this article to learn how to install and enable Assetic in your Symfony application.
Among its many filters, Assetic has four filters which can be used for on-the-fly image optimization. This
allows you to get the benefits of smaller file sizes without having to use an image editor to process each
image. The results are cached and can be dumped for production so there is no performance hit for your
end users.
Using Jpegoptim
Jpegoptim1 is a utility for optimizing JPEG files. To use it with Assetic, make sure to have it already
installed on your system and then, configure its location using the bin option of the jpegoptim filter:
1. http://www.kokkonen.net/tjko/projects.html
PDF brought to you by Chapter 5: How to Use Assetic for Image Optimization with Twig Functions | 23
You can also specify the output directory for images in the Assetic configuration file:
For uploaded images, you can compress and manipulate them using the LiipImagineBundle2
community bundle.
PDF brought to you by Chapter 5: How to Use Assetic for Image Optimization with Twig Functions | 24
PDF brought to you by Chapter 5: How to Use Assetic for Image Optimization with Twig Functions | 25
Starting from Symfony 2.8, Assetic is no longer included by default in the Symfony Standard Edition.
Refer to this article to learn how to install and enable Assetic in your Symfony application.
Assetic filters can be applied to individual files, groups of files or even, as you'll see here, files that have
a specific extension. To show you how to handle each option, suppose that you want to use Assetic's
CoffeeScript filter, which compiles CoffeeScript files into JavaScript.
The main configuration is just the paths to coffee, node and node_modules. An example
configuration might look like this:
This is all that's needed to compile this CoffeeScript file and serve it as the compiled JavaScript.
PDF brought to you by Chapter 6: How to Apply an Assetic Filter to a specific File Extension | 26
Both files will now be served up as a single file compiled into regular JavaScript.
With this option, you no longer need to specify the coffee filter in the template. You can also list
regular JavaScript files, all of which will be combined and rendered as a single JavaScript file (with only
the .coffee files being run through the CoffeeScript filter):
PDF brought to you by Chapter 6: How to Apply an Assetic Filter to a specific File Extension | 27
Most bundles provide their own installation instructions. However, the basic steps for installing a bundle
are the same:
Looking for bundles? Try searching at KnpBundles.com4: the unofficial archive of Symfony Bundles.
1. https://getcomposer.org/doc/00-intro.md
2. https://github.com/FriendsOfSymfony/FOSUserBundle
3. https://packagist.org
4. http://knpbundles.com/
In a few rare cases, you may want a bundle to be only enabled in the development environment. For
example, the DoctrineFixturesBundle helps to load dummy data - something you probably only want to
do while developing. To only load this bundle in the dev and test environments, register the bundle in
this way:
5. https://getcomposer.org/doc/03-cli.md#require
Instead of the full bundle name, you can also pass the short name used as the root of the bundle's
configuration:
Other Setup
At this point, check the README file of your brand new bundle to see what to do next. Have fun!
This article is all about how to structure your reusable bundles so that they're easy to configure and
extend. Many of these recommendations do not apply to application bundles because you'll want to keep
those as simple as possible. For application bundles, just follow the practices shown throughout the book
and cookbook.
The best practices for application-specific bundles are discussed in The Symfony Framework Best Practices.
Bundle Name
A bundle is also a PHP namespace. The namespace must follow the PSR-01 or PSR-42 interoperability
standards for PHP namespaces and class names: it starts with a vendor segment, followed by zero or more
category segments, and it ends with the namespace short name, which must end with a Bundle suffix.
A namespace becomes a bundle as soon as you add a bundle class to it. The bundle class name must
follow these simple rules:
1. http://www.php-fig.org/psr/psr-0/
2. http://www.php-fig.org/psr/psr-4/
Acme\BlogBundle AcmeBlogBundle
By convention, the getName() method of the bundle class should return the class name.
If you share your bundle publicly, you must use the bundle class name as the name of the repository
(AcmeBlogBundle and not BlogBundle for instance).
Symfony core Bundles do not prefix the Bundle class with Symfony and always add a Bundle sub-
namespace; for example: FrameworkBundle3.
Each bundle has an alias, which is the lower-cased short version of the bundle name using underscores
(acme_blog for AcmeBlogBundle). This alias is used to enforce uniqueness within a project and for
defining bundle's configuration options (see below for some usage examples).
Directory Structure
The basic directory structure of an AcmeBlogBundle must read as follows:
The following files are mandatory, because they ensure a structure convention that automated tools
can rely on:
• AcmeBlogBundle.php:
This is the class that transforms a plain directory into a Symfony bundle (change
this to your bundle's name);
• README.md: This file contains the basic description of the bundle and it usually shows some basic
examples and links to its full documentation (it can use any of the markup formats supported by
GitHub, such as README.rst);
• LICENSE: The full contents of the license used by the code. Most third-party bundles are published
under the MIT license, but you can choose any license4;
• Resources/doc/index.rst: The root file for the Bundle documentation.
The depth of sub-directories should be kept to the minimum for most used classes and files (two levels
maximum).
3. http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/FrameworkBundle.html
4. http://choosealicense.com/
[1] See How to Provide Model Classes for several Doctrine Implementations for how to handle the mapping
with a compiler pass.
Classes
The bundle directory structure is used as the namespace hierarchy. For instance, a
ContentController controller is stored in Acme/BlogBundle/Controller/
ContentController.php and the fully qualified class name is
Acme\BlogBundle\Controller\ContentController.
All classes and files must follow the Symfony coding standards.
Some classes should be seen as facades and should be as short as possible, like Commands, Helpers,
Listeners and Controllers.
Classes that connect to the event dispatcher should be suffixed with Listener.
Exception classes should be stored in an Exception sub-namespace.
Vendors
A bundle must not embed third-party PHP libraries. It should rely on the standard Symfony autoloading
instead.
A bundle should not embed third-party libraries written in JavaScript, CSS or any other language.
Tests
A bundle should come with a test suite written with PHPUnit and stored under the Tests/ directory.
Tests should follow the following principles:
A test suite must not contain AllTests.php scripts, but must rely on the existence of a
phpunit.xml.dist file.
Documentation
All classes and functions must come with full PHPDoc.
Extensive documentation should also be provided in the reStructuredText format, under the
Resources/doc/ directory; the Resources/doc/index.rst file is the only mandatory file and
must be the entry point for the documentation.
Installation Instructions
In order to ease the installation of third-party bundles, consider using the following standardized
instructions in your README.md file.
The example above assumes that you are installing the latest stable version of the bundle, where you don't
have to provide the package version number (e.g. composer require friendsofsymfony/user-
bundle). If the installation instructions refer to some past bundle version or to some unstable version,
include the version constraint (e.g. composer require friendsofsymfony/user-bundle
"~2.0@dev").
Optionally, you can add more installation steps (Step 3, Step 4, etc.) to explain other required installation
tasks, such as registering routes or dumping assets.
Routing
If the bundle provides routes, they must be prefixed with the bundle alias. For example, if your bundle is
called AcmeBlogBundle, all its routes must be prefixed with acme_blog_.
Templates
If a bundle provides templates, they must use Twig. A bundle must not provide a main layout, except if
it provides a full working application.
Translation Files
If a bundle provides message translations, they must be defined in the XLIFF format; the domain should
be named after the bundle name (acme_blog).
A bundle must not override existing messages from another bundle.
Configuration
To provide more flexibility, a bundle can provide configurable settings by using the Symfony built-in
mechanisms.
For simple configuration settings, rely on the default parameters entry of the Symfony configuration.
Symfony parameters are simple key/value pairs; a value being any valid PHP value. Each parameter name
should start with the bundle alias, though this is just a best-practice suggestion. The rest of the parameter
name will use a period (.) to separate different parts (e.g. acme_blog.author.email).
The end user can provide values in any configuration file:
Listing 8-4
$container->getParameter('acme_blog.author.email');
Even if this mechanism is simple enough, you should consider using the more advanced semantic bundle
configuration.
Services
If the bundle defines services, they must be prefixed with the bundle alias. For example, AcmeBlogBundle
services must be prefixed with acme_blog.
In addition, services not meant to be used by the application directly, should be defined as private.
You can learn much more about service loading in bundles reading this article: How to Load Service
Configuration inside a Bundle.
Composer Metadata
The composer.json file should include at least the following metadata:
name
Consists of the vendor and the short bundle name. If you are releasing the bundle on your own
instead of on behalf of a company, use your personal name (e.g. johnsmith/blog-bundle). The bundle
short name excludes the vendor name and separates each word with an hyphen. For example:
AcmeBlogBundle is transformed into blog-bundle and AcmeSocialConnectBundle is transformed into social-
connect-bundle.
description
A brief explanation of the purpose of the bundle.
type
Use the symfony-bundle value.
license
MIT is the preferred license for Symfony bundles, but you can use any other license.
autoload
This information is used by Symfony to load the classes of the bundle. The PSR-46 autoload
standard is recommended for modern bundles, but PSR-07 standard is also supported.
In order to make it easier for developers to find your bundle, register it on Packagist8, the official
repository for Composer packages.
5. http://semver.org/
6. http://www.php-fig.org/psr/psr-4/
7. http://www.php-fig.org/psr/psr-0/
8. https://packagist.org/
When working with third-party bundles, you'll probably come across a situation where you want to
override a file in that third-party bundle with a file in one of your own bundles. Symfony gives you a very
convenient way to override things like controllers, templates, and other files in a bundle's Resources/
directory.
For example, suppose that you're installing the FOSUserBundle1, but you want to override its base
layout.html.twig template, as well as one of its controllers. Suppose also that you have your own
UserBundle where you want the overridden files to live. Start by registering the FOSUserBundle as the
"parent" of your bundle:
By making this simple change, you can now override several parts of the FOSUserBundle simply by
creating a file with the same name.
Despite the method name, there is no parent/child relationship between the bundles, it is just a way
to extend and override an existing bundle.
1. https://github.com/friendsofsymfony/fosuserbundle
PDF brought to you by Chapter 9: How to Use Bundle Inheritance to Override Parts of a Bundle | 37
Overriding controllers in this way only works if the bundle refers to the controller using the standard
FOSUserBundle:Registration:register syntax in routes and templates. This is the best
practice.
The overriding of resources only works when you refer to resources with the @FOSUserBundle/
Resources/config/routing/security.xml method. If you refer to resources without using
the @BundleName shortcut, they can't be overridden in this way.
Translation and validation files do not work in the same way as described above. Read "Translations"
if you want to learn how to override translations and see "Validation Metadata" for tricks to override
the validation.
PDF brought to you by Chapter 9: How to Use Bundle Inheritance to Override Parts of a Bundle | 38
This document is a quick reference for how to override different parts of third-party bundles.
Templates
For information on overriding templates, see
Routing
Routing is never automatically imported in Symfony. If you want to include the routes from any
bundle, then they must be manually imported from somewhere in your application (e.g. app/config/
routing.yml).
The easiest way to "override" a bundle's routing is to never import it at all. Instead of importing a
third-party bundle's routing, simply copy that routing file into your application, modify it, and import it
instead.
Controllers
Assuming the third-party bundle involved uses non-service controllers (which is almost always the case),
you can easily override controllers via bundle inheritance. For more information, see How to Use Bundle
Inheritance to Override Parts of a Bundle. If the controller is a service, see the next section on how to
override it.
PDF brought to you by Chapter 10: How to Override any Part of a Bundle | 39
Secondly, if the class is not available as a parameter, you want to make sure the class is always overridden
when your bundle is used or if you need to modify something beyond just the class name, you should use
a compiler pass:
In this example you fetch the service definition of the original service, and set its class name to your own
class.
See How to Work with Compiler Passes in Bundles for information on how to use compiler passes. If you
want to do something beyond just overriding the class, like adding a method call, you can only use the
compiler pass method.
Forms
Form types are referred to by their fully-qualified class name:
Listing 10-3
$builder->add('name', CustomType::class);
1. http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/inheritance-mapping.html#overrides
PDF brought to you by Chapter 10: How to Override any Part of a Bundle | 40
Validation Metadata
Symfony loads all validation configuration files from every bundle and combines them into one validation
metadata tree. This means you are able to add new constraints to a property, but you cannot override
them.
To override this, the 3rd party bundle needs to have configuration for validation groups. For instance,
the FOSUserBundle has this configuration. To create your own validation, add the constraints to a new
validation group:
Now, update the FOSUserBundle configuration, so it uses your validation groups instead of the original
ones.
Translations
Translations are not related to bundles, but to domains. That means that you can override the
translations from any translation file, as long as it is in the correct domain.
The last translation file always wins. That means that you need to make sure that the bundle
containing your translations is loaded after any bundle whose translations you're overriding. This is
done in AppKernel.
Translation files are also not aware of bundle inheritance. If you want to override translations
from the parent bundle, be sure that the parent bundle is loaded before the child bundle in the
AppKernel class.
The file that always wins is the one that is placed in app/Resources/translations, as those
files are always loaded last.
PDF brought to you by Chapter 10: How to Override any Part of a Bundle | 41
The Symfony Standard Edition comes with a complete demo that lives inside a bundle called
AcmeDemoBundle. It is a great boilerplate to refer to while starting a project, but you'll probably want to
eventually remove it.
This article uses the AcmeDemoBundle as an example, but you can use these steps to remove any
bundle.
If you don't know the location of a bundle, you can use the getPath()1 method to get the path of
the bundle:
dump($this->container->get('kernel')->getBundle('AcmeDemoBundle')->getPath());
Listing 11-2
die();
Some bundles rely on other bundles, if you remove one of the two, the other will probably not work. Be
sure that no other bundles, third party or self-made, rely on the bundle you are about to remove.
1. http://api.symfony.com/3.1/Symfony/Component/HttpKernel/Bundle/BundleInterface.html#method_getPath
If a third party bundle relies on another bundle, you can find that bundle mentioned in the
composer.json file included in the bundle directory.
In Symfony, you'll find yourself using many services. These services can be registered in the app/
config/ directory of your application. But when you want to decouple the bundle for use in other
projects, you want to include the service configuration in the bundle itself. This article will teach you how
to do that.
The Extension class should implement the ExtensionInterface1, but usually you would simply
extend the Extension2 class:
1. http://api.symfony.com/3.1/Symfony/Component/DependencyInjection/Extension/ExtensionInterface.html
2. http://api.symfony.com/3.1/Symfony/Component/DependencyInjection/Extension/Extension.html
PDF brought to you by Chapter 12: How to Load Service Configuration inside a Bundle | 45
Since the new Extension class name doesn't follow the naming conventions, you should also override
Extension::getAlias()4 to return the correct DI alias. The DI alias is the name used to refer
to the bundle in the container (e.g. in the app/config/config.yml file). By default, this is done
by removing the Extension suffix and converting the class name to underscores (e.g.
AcmeHelloExtension's DI alias is acme_hello).
3. http://api.symfony.com/3.1/Symfony/Component/HttpKernel/Bundle/Bundle.html#method_build
4. http://api.symfony.com/3.1/Symfony/Component/DependencyInjection/Extension/Extension.html#method_getAlias
PDF brought to you by Chapter 12: How to Load Service Configuration inside a Bundle | 46
The IniFileLoader can only be used to load parameters and it can only load them as strings.
If you removed the default file with service definitions (i.e. app/config/services.yml), make
sure to also remove it from the imports key in app/config/config.yml.
If some class extends from other classes, all its parents are automatically included in the list of classes
to compile.
• When classes contain annotations, such as controllers with @Route annotations and entities with @ORM
or @Assert annotations, because the file location retrieved from PHP reflection changes;
• When classes use the __DIR__ and __FILE__ constants, because their values will change when loading
these classes from the classes.php file.
PDF brought to you by Chapter 12: How to Load Service Configuration inside a Bundle | 47
If you open your application configuration file (usually app/config/config.yml), you'll see a
number of different configuration sections, such as framework, twig and doctrine. Each of these
configures a specific bundle, allowing you to define options at a high level and then let the bundle make
all the low-level, complex changes based on your settings.
For example, the following configuration tells the FrameworkBundle to enable the form integration,
which involves the definition of quite a few services as well as integration of other related components:
PDF brought to you by Chapter 13: How to Create Friendly Configuration for a Bundle | 48
Read more about the extension in How to Load Service Configuration inside a Bundle.
If a bundle provides an Extension class, then you should not generally override any service container
parameters from that bundle. The idea is that if an Extension class is present, every setting that
should be configurable should be present in the configuration made available by that class. In
other words, the extension class defines all the public configuration settings for which backward
compatibility will be maintained.
For parameter handling within a dependency injection container see Using Parameters within a Dependency
Injection Class.
Notice that this is an array of arrays, not just a single flat array of the configuration values. This is
intentional, as it allows Symfony to parse several configuration resources. For example, if acme_social
appears in another configuration file - say config_dev.yml - with different values beneath it, the
incoming array might look like this:
PDF brought to you by Chapter 13: How to Create Friendly Configuration for a Bundle | 49
The Configuration class can be much more complicated than shown here, supporting "prototype" nodes,
advanced validation, XML-specific normalization and advanced merging. You can read more about this in
the Config component documentation. You can also see it in action by checking out some core Configuration
classes, such as the one from the FrameworkBundle Configuration1 or the TwigBundle Configuration2.
This class can now be used in your load() method to merge configurations and force validation (e.g. if
an additional option was passed, an exception will be thrown):
The processConfiguration() method uses the configuration tree you've defined in the
Configuration class to validate, normalize and merge all the configuration arrays together.
1. https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
2. https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php
PDF brought to you by Chapter 13: How to Create Friendly Configuration for a Bundle | 50
This class uses the getConfiguration() method to get the Configuration instance. You should
override it if your Configuration class is not called Configuration or if it is not placed in the same
namespace as the extension.
3. http://api.symfony.com/3.1/Symfony/Component/HttpKernel/DependencyInjection/ConfigurableExtension.html
4. http://php.net/manual/en/function.isset.php
PDF brought to you by Chapter 13: How to Create Friendly Configuration for a Bundle | 51
Supporting XML
Symfony allows people to provide the configuration in three different formats: Yaml, XML and PHP.
Both Yaml and PHP use the same syntax and are supported by default when using the Config component.
Supporting XML requires you to do some more things. But when sharing your bundle with others, it is
recommended that you follow these steps.
5. http://api.symfony.com/3.1/Symfony/Component/HttpKernel/DependencyInjection/Extension.html#method_getConfiguration
6. https://en.wikipedia.org/wiki/XML_namespace
7. http://api.symfony.com/3.1/Symfony/Component/DependencyInjection/Extension/Extension.html#method_getNamespace
8. https://en.wikipedia.org/wiki/XML_schema
9. http://api.symfony.com/3.1/Symfony/Component/DependencyInjection/Extension/ExtensionInterface.html#method_getXsdValidationBasePath
PDF brought to you by Chapter 13: How to Create Friendly Configuration for a Bundle | 52
Assuming the XSD file is called hello-1.0.xsd, the schema location will be
http://acme_company.com/schema/dic/hello/hello-1.0.xsd:
Listing 13-11 1 <!-- app/config/config.xml -->
2 <?xml version="1.0" ?>
3
4 <container xmlns="http://symfony.com/schema/dic/services"
5 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
6 xmlns:acme-hello="http://acme_company.com/schema/dic/hello"
7 xsi:schemaLocation="http://acme_company.com/schema/dic/hello
8 http://acme_company.com/schema/dic/hello/hello-1.0.xsd">
9
10 <acme-hello:config>
11 <!-- ... -->
12 </acme-hello:config>
13
14 <!-- ... -->
15 </container>
PDF brought to you by Chapter 13: How to Create Friendly Configuration for a Bundle | 53
When building reusable and extensible applications, developers are often faced with a choice: either
create a single large bundle or multiple smaller bundles. Creating a single bundle has the drawback that
it's impossible for users to choose to remove functionality they are not using. Creating multiple bundles
has the drawback that configuration becomes more tedious and settings often need to be repeated for
various bundles.
Using the below approach, it is possible to remove the disadvantage of the multiple bundle approach by
enabling a single Extension to prepend the settings for any bundle. It can use the settings defined in the
app/config/config.yml to prepend settings just as if they had been written explicitly by the user in
the application configuration.
For example, this could be used to configure the entity manager name to use in multiple bundles. Or it
can be used to enable an optional feature that depends on another bundle being loaded as well.
To give an Extension the power to do this, it needs to implement PrependExtensionInterface1:
1. http://api.symfony.com/3.1/Symfony/Component/DependencyInjection/Extension/PrependExtensionInterface.html
PDF brought to you by Chapter 14: How to Simplify Configuration of multiple Bundles | 54
The above would be the equivalent of writing the following into the app/config/config.yml in case
AcmeGoodbyeBundle is not registered and the entity_manager_name setting for acme_hello is set
to non_default:
2. http://api.symfony.com/3.1/Symfony/Component/DependencyInjection/Extension/PrependExtensionInterface.html#method_prepend
3. http://api.symfony.com/3.1/Symfony/Component/DependencyInjection/ContainerBuilder.html
4. http://api.symfony.com/3.1/Symfony/Component/DependencyInjection/Extension/ExtensionInterface.html#method_load
5. http://api.symfony.com/3.1/Symfony/Component/DependencyInjection/ContainerBuilder.html#method_prependExtensionConfig
6. http://api.symfony.com/3.1/Symfony/Component/DependencyInjection/ContainerBuilder.html
PDF brought to you by Chapter 14: How to Simplify Configuration of multiple Bundles | 55
Because Symfony's cache uses the standard HTTP cache headers, the Symfony Reverse Proxy can easily
be replaced with any other reverse proxy. Varnish1 is a powerful, open-source, HTTP accelerator capable
of serving cached content fast and including support for Edge Side Includes.
If you do not have access to your Varnish configuration, you can instead configure Symfony to distrust
the Forwarded header as detailed in the cookbook.
1. https://www.varnish-cache.org
PDF brought to you by Chapter 15: How to Use Varnish to Speed up my Website | 56
If content is not different for every user, but depends on the roles of a user, a solution is to separate
the cache per group. This pattern is implemented and explained by the FOSHttpCacheBundle3 under
the name User Context4.
2. https://www.varnish-cache.org/trac/wiki/VCLExampleRemovingSomeCookies
3. http://foshttpcachebundle.readthedocs.org/
4. http://foshttpcachebundle.readthedocs.org/en/latest/features/user-context.html
PDF brought to you by Chapter 15: How to Use Varnish to Speed up my Website | 57
You can see the default behavior of Varnish in the form of a VCL file: default.vcl5 for Varnish 3,
builtin.vcl6 for Varnish 4.
Varnish only supports the src attribute for ESI tags (onerror and alt attributes are ignored).
First, configure Varnish so that it advertises its ESI support by adding a Surrogate-Capability
header to requests forwarded to the backend application:
The abc part of the header isn't important unless you have multiple "surrogates" that need to
advertise their capabilities. See Surrogate-Capability Header8 for details.
Then, optimize Varnish so that it only parses the response contents when there is at least one ESI tag by
checking the Surrogate-Control header that Symfony adds automatically:
5. https://github.com/varnish/Varnish-Cache/blob/3.0/bin/varnishd/default.vcl
6. https://github.com/varnish/Varnish-Cache/blob/4.1/bin/varnishd/builtin.vcl
7. http://www.w3.org/TR/edge-arch
8. http://www.w3.org/TR/edge-arch
PDF brought to you by Chapter 15: How to Use Varnish to Speed up my Website | 58
If you followed the advice about ensuring a consistent caching behavior, those VCL functions already
exist. Just append the code to the end of the function, they won't interfere with each other.
Cache Invalidation
If you want to cache content that changes frequently and still serve the most recent version to users, you
need to invalidate that content. While cache invalidation9 allows you to purge content from your proxy
before it has expired, it adds complexity to your caching setup.
The open source FOSHttpCacheBundle10 takes the pain out of cache invalidation by helping you to
organize your caching and invalidation setup.
The documentation of the FOSHttpCacheBundle11 explains how to configure Varnish and other
reverse proxies for cache invalidation.
9. http://tools.ietf.org/html/rfc2616#section-13.10
10. http://foshttpcachebundle.readthedocs.org/
11. http://foshttpcachebundle.readthedocs.org/
PDF brought to you by Chapter 15: How to Use Varnish to Speed up my Website | 59
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 them.
For more information about how CSRF protection works in Symfony, please check CSRF Protection.
How to Cache Most of the Page and still be able to Use CSRF Protection
To cache a page that contains a CSRF token, you can use more advanced caching techniques like ESI
fragments, where you cache the full page and embedding the form inside an ESI tag with no cache at all.
Another option would be to load the form via an uncached AJAX request, but cache the rest of the HTML
response.
Or you can even load just the CSRF token with an AJAX request and replace the form field value with it.
PDF brought to you by Chapter 16: Caching Pages that Contain CSRF Protected Forms | 60
Composer1 is the package manager used by modern PHP applications. Use Composer to manage
dependencies in your Symfony applications and to install Symfony Components in your PHP projects.
It's recommended to install Composer globally in your system as explained in the following sections.
Learn more
Read the Composer documentation4 to learn more about its usage and features.
1. https://getcomposer.org/
2. https://getcomposer.org/download
3. https://getcomposer.org/Composer-Setup.exe
4. https://getcomposer.org/doc/00-intro.md
Every application is the combination of code and a set of configuration that dictates how that code should
function. The configuration may define the database being used, if something should be cached or how
verbose logging should be.
In Symfony, the idea of "environments" is the idea that the same codebase can be run using multiple
different configurations. For example, the dev environment should use configuration that makes
development easy and friendly, while the prod environment should use a set of configuration optimized
for speed.
This works via a simple standard that's used by default inside the AppKernel class:
PDF brought to you by Chapter 18: How to Master and Create new Environments | 62
To share common configuration, each environment's configuration file simply first imports from a
central configuration file (config.yml). The remainder of the file can then deviate from the default
configuration by overriding individual parameters. For example, by default, the web_profiler toolbar
is disabled. However, in the dev environment, the toolbar is activated by modifying the value of the
toolbar option in the config_dev.yml configuration file:
Listing 18-3 1 # app/config/config_dev.yml
2 imports:
3 - { resource: config.yml }
4
5 web_profiler:
6 toolbar: true
7 # ...
The given URLs assume that your web server is configured to use the web/ directory of the
application as its root. Read more in Installing Symfony.
If you open up one of these files, you'll quickly see that the environment used by each is explicitly set:
The prod key specifies that this application will run in the prod environment. A Symfony application
can be executed in any environment by using this code and changing the environment string.
The test environment is used when writing functional tests and is not accessible in the browser
directly via a front controller. In other words, unlike the other environments, there is no
app_test.php front controller file.
PDF brought to you by Chapter 18: How to Master and Create new Environments | 63
In addition to the --env and --debug options, the behavior of Symfony commands can also be
controlled with environment variables. The Symfony console application checks the existence and value
of these environment variables before executing any command:
SYMFONY_ENV
Sets the execution environment of the command to the value of this variable (dev, prod, test, etc.);
SYMFONY_DEBUG
If 0, debug mode is disabled. Otherwise, debug mode is enabled.
These environment variables are very useful for production servers because they allow you to ensure that
commands always run in the prod environment without having to add any command option.
PDF brought to you by Chapter 18: How to Master and Create new Environments | 64
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:
And with this simple addition, the application now supports a new environment called benchmark.
This new configuration file imports the configuration from the prod environment and modifies it. This
guarantees that the new environment is identical to the prod environment, except for any changes
explicitly made here.
Because you'll want this environment to be accessible via a browser, you should also create a front
controller for it. Copy the web/app.php file to web/app_benchmark.php and edit the environment
to be benchmark:
Listing 18-11
http://localhost/app_benchmark.php
Some environments, like the dev environment, are never meant to be accessed on any deployed
server by the public. This is because certain environments, for debugging purposes, may give too
much information about the application or underlying infrastructure. To be sure these environments
aren't accessible, the front controller is usually protected from external IP addresses via the following
code at the top of the controller:
if (!in_array(@$_SERVER['REMOTE_ADDR'], array('127.0.0.1', '::1'))) {
Listing 18-12
die('You are not allowed to access this file. Check '.basename(__FILE__).' for more information.');
}
PDF brought to you by Chapter 18: How to Master and Create new Environments | 65
Sometimes, when debugging, it may be helpful to inspect a cached file to understand how something
is working. When doing so, remember to look in the directory of the environment you're using (most
commonly dev while developing and debugging). While it can vary, the var/cache/dev directory
includes the following:
appDevDebugProjectContainer.php
The cached "service container" that represents the cached application configuration.
appDevUrlGenerator.php
The PHP class generated from the routing configuration and used when generating URLs.
appDevUrlMatcher.php
The PHP class used for route matching - look here to see the compiled regular expression logic used
to match incoming URLs to different routes.
twig/
This directory contains all the cached Twig templates.
You can easily change the directory location and name. For more information read the article How
to Override Symfony's default Directory Structure.
Going further
Read the article on How to Set external Parameters in the Service Container.
PDF brought to you by Chapter 18: How to Master and Create new Environments | 66
A traditional Symfony app contains a sensible directory structure, various configuration files and an
AppKernel with several bundles already-registered. This is a fully-featured app that's ready to go.
But did you know, you can create a fully-functional Symfony application in as little as one file? This is
possible thanks to the new MicroKernelTrait1. This allows you to start with a tiny application, and
then add features and structure as you need to.
Next, create an index.php file that creates a kernel class and executes it:
1. http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.html
PDF brought to you by Chapter 19: Building your own Framework with the MicroKernelTrait | 67
That's it! To test it, you can start the built-in web server:
configureRoutes(RouteCollectionBuilder $routes)
Your job in this method is to add routes to the application. The RouteCollectionBuilder has methods
that make adding routes in PHP more fun. You can also load external routing files (shown below).
PDF brought to you by Chapter 19: Building your own Framework with the MicroKernelTrait | 68
Now, suppose you want to use Twig and load routes via annotations. For annotation routing, you need
SensioFrameworkExtraBundle. This comes with a normal Symfony project. But in this case, you need to
download it:
Instead of putting everything in index.php, create a new app/AppKernel.php to hold the kernel.
Now it looks like this:
PDF brought to you by Chapter 19: Building your own Framework with the MicroKernelTrait | 69
Unlike the previous kernel, this loads an external app/config/config.yml file, because the
configuration started to get bigger:
This also loads annotation routes from an src/App/Controller/ directory, which has one file in it:
Template files should live in the Resources/views directory of whatever directory your kernel lives
in. Since AppKernel lives in app/, this template lives at app/Resources/views/micro/
random.html.twig.
Finally, you need a front controller to boot and run the application. Create a web/index.php:
PDF brought to you by Chapter 19: Building your own Framework with the MicroKernelTrait | 70
That's it! This /random/10 URL will work, Twig will render, and you'll even get the web debug toolbar
to show up at the bottom. The final structure looks like this:
Hey, that looks a lot like a traditional Symfony application! You're right: the MicroKernelTrait is still
Symfony: but you can control your structure and features quite easily.
PDF brought to you by Chapter 19: Building your own Framework with the MicroKernelTrait | 71
Symfony automatically ships with a default directory structure. You can easily override this directory
structure to create your own. The default directory structure is:
PDF brought to you by Chapter 20: How to Override Symfony's default Directory Structure | 72
In this code, $this->environment is the current environment (i.e. dev). In this case you have
changed the location of the cache directory to var/{environment}/cache.
You should keep the cache directory different for each environment, otherwise some unexpected
behavior may happen. Each environment generates its own cached configuration files, and so each
needs its own directory to store those cache files.
Listing 20-4
require_once __DIR__.'/../path/to/app/autoload.php';
You also need to change the extra.symfony-web-dir option in the composer.json file:
Listing 20-5 1 {
2 "...": "...",
3 "extra": {
4 "...": "...",
5 "symfony-web-dir": "my_new_web_dir"
6 }
7 }
PDF brought to you by Chapter 20: How to Override Symfony's default Directory Structure | 73
If you use the AsseticBundle, you need to configure the read_from option to point to the correct
web directory:
Now you just need to clear the cache and dump the assets again and your application should work:
Listing 20-8 1 {
2 "config": {
3 "bin-dir": "bin",
4 "vendor-dir": "/some/dir/vendor"
5 },
6 }
Listing 20-9
// app/autoload.php
// ...
$loader = require '/some/dir/vendor/autoload.php';
This modification can be of interest if you are working in a virtual environment and cannot use NFS
- for example, if you're running a Symfony application using Vagrant/VirtualBox in a guest operating
system.
PDF brought to you by Chapter 20: How to Override Symfony's default Directory Structure | 74
You have seen how to use configuration parameters within Symfony service containers. There are special
cases such as when you want, for instance, to use the %kernel.debug% parameter to make the services
in your bundle enter debug mode. For this case there is more work to do in order to make the system
understand the parameter value. By default, your parameter %kernel.debug% will be treated as a
simple string. Consider the following example:
PDF brought to you by Chapter 21: Using Parameters within a Dependency Injection Class | 75
The string %kernel.debug% passed here as an argument handles the interpreting job to the
container which in turn does the evaluation. Both ways accomplish similar goals. AsseticBundle will
not use %kernel.debug% but rather the new %assetic.debug% parameter.
PDF brought to you by Chapter 21: Using Parameters within a Dependency Injection Class | 76
The section How to Master and Create new Environments explained the basics on how Symfony uses
environments to run your application with different configuration settings. This section will explain a bit
more in-depth what happens when your application is bootstrapped. To hook into this process, you need
to understand three parts that work together:
Usually, you will not need to define your own front controller or AppKernel class as the Symfony
Standard Edition1 provides sensible default implementations.
This documentation section is provided to explain what is going on behind the scenes.
1. https://github.com/symfony/symfony-standard
2. https://en.wikipedia.org/wiki/Front_Controller_pattern
3. https://github.com/symfony/symfony-standard
4. https://github.com/symfony/symfony-standard/blob/master/web/app.php
5. https://github.com/symfony/symfony-standard/blob/master/web/app_dev.php
PDF brought to you by Chapter 22: Understanding how the Front Controller, Kernel and Environments Work together | 77
As you can see, this URL contains the PHP script to be used as the front controller. You can use
that to easily switch the front controller or use a custom one by placing it in the web/ directory (e.g.
app_cache.php).
When using Apache and the RewriteRule shipped with the Symfony Standard Edition7, you can omit the
filename from the URL and the RewriteRule will use app.php as the default one.
Pretty much every other web server should be able to achieve a behavior similar to that of the
RewriteRule described above. Check your server documentation for details or see Configuring a Web
Server.
Make sure you appropriately secure your front controllers against unauthorized access. For example,
you don't want to make a debugging environment available to arbitrary users in your production
environment.
Technically, the bin/console8 script used when running Symfony on the command line is also a front
controller, only that is not used for web, but for command line requests.
registerContainerConfiguration()15
It loads the application configuration.
6. https://en.wikipedia.org/wiki/Decorator_pattern
7. https://github.com/symfony/symfony-standard/blob/master/web/.htaccess
8. https://github.com/symfony/symfony-standard/blob/master/bin/console
9. http://api.symfony.com/3.1/Symfony/Component/HttpKernel/Kernel.html
10. http://api.symfony.com/3.1/Symfony/Component/HttpKernel/HttpKernelInterface.html#method_handle
11. http://api.symfony.com/3.1/Symfony/Component/HttpKernel/KernelInterface.html
12. http://api.symfony.com/3.1/Symfony/Component/HttpKernel/Kernel.html
13. https://en.wikipedia.org/wiki/Template_method_pattern
14. http://api.symfony.com/3.1/Symfony/Component/HttpKernel/KernelInterface.html#method_registerBundles
15. http://api.symfony.com/3.1/Symfony/Component/HttpKernel/KernelInterface.html#method_registerContainerConfiguration
PDF brought to you by Chapter 22: Understanding how the Front Controller, Kernel and Environments Work together | 78
The name and location of the AppKernel is not fixed. When putting multiple Kernels into a single
application, it might therefore make sense to add additional sub-directories, for example app/
admin/AdminKernel.php and app/api/ApiKernel.php. All that matters is that your front
controller is able to create an instance of the appropriate kernel.
Having different AppKernels might be useful to enable different front controllers (on potentially
different servers) to run parts of your application independently (for example, the admin UI, the front-
end UI and database migrations).
There's a lot more the AppKernel can be used for, for example overriding the default directory
structure. But odds are high that you don't need to change things like this on the fly by having several
AppKernel implementations.
The Environments
As just AppKernel has to implement another method -
mentioned, the
registerContainerConfiguration()19. This method is responsible for loading the application's
configuration from the right environment.
Environments have been covered extensively in the previous chapter, and you probably remember that
the Symfony Standard Edition comes with three of them - dev, prod and test.
More technically, these names are nothing more than strings passed from the front controller to the
AppKernel's constructor. This name can then be used in the
registerContainerConfiguration()20 method to decide which configuration files to load.
The Symfony Standard Edition's AppKernel21 class implements this method by simply loading the app/
config/config_*environment*.yml file. You are, of course, free to implement this method
differently if you need a more sophisticated way of loading your configuration.
16. https://github.com/symfony/symfony-standard/blob/master/app/AppKernel.php
17. http://api.symfony.com/3.1/Symfony/Component/HttpKernel/Kernel.html#method___construct
18. http://api.symfony.com/3.1/Symfony/Component/HttpKernel/Kernel.html#method_getEnvironment
19. http://api.symfony.com/3.1/Symfony/Component/HttpKernel/KernelInterface.html#method_registerContainerConfiguration
20. http://api.symfony.com/3.1/Symfony/Component/HttpKernel/KernelInterface.html#method_registerContainerConfiguration
21. https://github.com/symfony/symfony-standard/blob/master/app/AppKernel.php
PDF brought to you by Chapter 22: Understanding how the Front Controller, Kernel and Environments Work together | 79
In the chapter How to Master and Create new Environments, you learned how to manage your application
configuration. At times, it may benefit your application to store certain credentials outside of your project
code. Database configuration is one such example. The flexibility of the Symfony service container allows
you to easily do this.
Environment Variables
Symfony will grab any environment variable prefixed with SYMFONY__ and set it as a parameter in the
service container. Some transformations are applied to the resulting parameter name:
For example, if you're using Apache, environment variables can be set using the following VirtualHost
configuration:
PDF brought to you by Chapter 23: How to Set external Parameters in the Service Container | 80
Now that you have declared an environment variable, it will be present in the PHP $_SERVER global
variable. Symfony then automatically sets all $_SERVER variables prefixed with SYMFONY__ as
parameters in the service container.
You can now reference these parameters wherever you need them.
Constants
The container also has support for setting PHP constants as parameters. See Constants as Parameters for
more details.
Miscellaneous Configuration
The imports directive can be used to pull in parameters stored elsewhere. Importing a PHP file gives
you the flexibility to add whatever is needed in the container. The following imports a file named
parameters.php.
Listing 23-4 1 # app/config/config.yml
2 imports:
3 - { resource: parameters.php }
A resource file can be one of many types. PHP, XML, YAML, INI, and closure resources are all
supported by the imports directive.
In parameters.php, tell the service container the parameters that you wish to set. This is useful when
important configuration is in a non-standard format. The example below includes a Drupal database
configuration in the Symfony service container.
1. http://httpd.apache.org/docs/current/env.html
PDF brought to you by Chapter 23: How to Set external Parameters in the Service Container | 81
Using the Apache Router is no longer considered a good practice. The small increase obtained
in the application routing performance is not worth the hassle of continuously updating the routes
configuration.
The Apache Router will be removed in Symfony 3 and it's highly recommended to not use it in your
applications.
Symfony, while fast out of the box, also provides various ways to increase that speed with a little bit of
tweaking. One of these ways is by letting Apache handle routes directly, rather than using Symfony for
this task.
Apache router was deprecated in Symfony 2.5 and removed in Symfony 3.0. Since the PHP
implementation of the Router was improved, performance gains were no longer significant (while it's
very hard to replicate the same behavior).
PDF brought to you by Chapter 24: How to Use the Apache Router | 82
The preferred way to develop your Symfony application is to use PHP's internal web server. However,
when using an older PHP version or when running the application in the production environment, you'll
need to use a fully-featured web server. This article describes several ways to use Symfony with Apache
or Nginx.
When using Apache, you can configure PHP as an Apache module or with FastCGI using PHP FPM.
FastCGI also is the preferred way to use PHP with Nginx.
If your system supports the APACHE_LOG_DIR variable, you may want to use
${APACHE_LOG_DIR}/ instead of hardcoding /var/log/apache2/.
Use the following optimized configuration to disable .htaccess support and increase web server
performance:
If you are using php-cgi, Apache does not pass HTTP basic username and password to PHP by
default. To work around this limitation, you should use the following configuration snippet:
For advanced Apache configuration options, read the official Apache documentation1.
1. http://httpd.apache.org/docs/
2. https://bz.apache.org/bugzilla/show_bug.cgi?id=54101
If you prefer to use a Unix socket, you have to use the -socket option instead:
Nginx
The minimum configuration to get your application running under Nginx is:
3. http://www.fastcgi.com/mod_fastcgi/docs/mod_fastcgi.html#FastCgiExternalServer
This executes only app.php, app_dev.php and config.php in the web directory. All other files
ending in ".php" will be denied.
If you have other PHP files in your web directory that need to be executed, be sure to include them
in the location block above.
For advanced Nginx configuration options, read the official Nginx documentation4.
4. http://wiki.nginx.org/Symfony
The default Symfony Standard Edition defines three execution environments called dev, prod and test.
An environment simply represents a way to execute the same codebase with different configurations.
In order to select the configuration file to load for each environment, Symfony executes the
registerContainerConfiguration() method of the AppKernel class:
Listing 26-1 1 // app/AppKernel.php
2 use Symfony\Component\HttpKernel\Kernel;
3 use Symfony\Component\Config\Loader\LoaderInterface;
4
5 class AppKernel extends Kernel
6 {
7 // ...
8
9 public function registerContainerConfiguration(LoaderInterface $loader)
10 {
11 $loader->load($this->getRootDir().'/config/config_'.$this->getEnvironment().'.yml');
12 }
13 }
This method loads the app/config/config_dev.yml file for the dev environment and so on. In
turn, this file loads the common configuration file located at app/config/config.yml. Therefore,
the configuration files of the default Symfony Standard Edition follow this structure:
Then, make sure that each config.yml file loads the rest of the configuration files, including the
common files. For instance, this would be the imports needed for the app/config/dev/config.yml
file:
1. http://api.symfony.com/3.1/Symfony/Component/HttpKernel/KernelInterface.html#method_registerContainerConfiguration
Following the same technique explained in the previous section, make sure to import the appropriate
configuration files from each main file (common.yml, dev.yml and prod.yml).
Advanced Techniques
Symfony loads configuration files using the Config component, which provides some advanced features.
The IniFileLoader parses the file contents using the parse_ini_file2 function. Therefore,
you can only set parameters to string values. Use one of the other loaders if you want to use other
data types (e.g. boolean, integer, etc.).
If you use any other configuration format, you have to define your own loader class extending it from
FileLoader3. When the configuration values are dynamic, you can use the PHP configuration file
to execute your own logic. In addition, you can define your own services to load configurations from
databases or web services.
Most of the time, local developers won't have the same files that exist on the production servers. For that
reason, the Config component provides the ignore_errors option to silently discard errors when the
loaded file doesn't exist:
As you've seen, there are lots of ways to organize your configuration files. You can choose one of these or
even create your own custom way of organizing the files. Don't feel limited by the Standard Edition that
comes with Symfony. For even more customization, see "How to Override Symfony's default Directory
Structure".
2. http://php.net/manual/en/function.parse-ini-file.php
3. http://api.symfony.com/3.1/Symfony/Component/DependencyInjection/Loader/FileLoader.html
The Console page of the Components section (The Console Component) covers how to create a console
command. This cookbook article covers the differences when creating console commands within the
Symfony Framework.
1. http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/Command/ContainerAwareCommand.html
2. http://api.symfony.com/3.1/Symfony/Component/Console/Command/Command.html
In the specific case above, the name parameter and the --yell option are not mandatory for the
command to work, but are shown so you can see how to customize them when calling the command.
To be able to use the fully set up service container for your console tests you can extend your test from
KernelTestCase5:
Listing 27-5 1 use Symfony\Component\Console\Tester\CommandTester;
2 use Symfony\Bundle\FrameworkBundle\Console\Application;
3 use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
4 use AppBundle\Command\GreetCommand;
5
6 class ListCommandTest extends KernelTestCase
7 {
8 public function testExecute()
9 {
10 $kernel = $this->createKernel();
11 $kernel->boot();
12
13 $application = new Application($kernel);
14 $application->add(new GreetCommand());
15
16 $command = $application->find('demo:greet');
17 $commandTester = new CommandTester($command);
18 $commandTester->execute(
19 array(
20 'name' => 'Fabien',
21 '--yell' => true,
3. http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/Console/Application.html
4. http://api.symfony.com/3.1/Symfony/Component/Console/Application.html
5. http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.html
The Using Console Commands, Shortcuts and Built-in Commands page of the components documentation
looks at the global console options. When you use the console as part of the full-stack framework, some
additional global options are available as well.
By default, console commands run in the dev environment and you may want to change this for
some commands. For example, you may want to run some commands in the prod environment for
performance reasons. Also, the result of some commands will be different depending on the environment.
For example, the cache:clear command will clear and warm the cache for the specified environment
only. To clear and warm the prod cache you need to run:
or the equivalent:
In addition to changing the environment, you can also choose to disable debug mode. This can be useful
where you want to run commands in the dev environment but avoid the performance hit of collecting
debug data:
One of the most boring tasks when creating console commands is to deal with the styling of the
command's input and output. Displaying titles and tables or asking questions to the user involves a lot of
repetitive code.
Consider for example the code used to display the title of the following command:
Displaying a simple title requires three lines of code, to change the font color, underline the contents and
leave an additional blank line after the title. Dealing with styles is required for well-designed commands,
but it complicates their code unnecessarily.
In order to reduce that boilerplate code, Symfony commands can optionally use the Symfony Style
Guide. These styles are implemented as a set of helper methods which allow to create semantic
commands and forget about their styling.
Helper Methods
The SymfonyStyle2 class defines some helper methods that cover the most common interactions
performed by console commands.
Titling Methods
title()3
It displays the given string as the command title. This method is meant to be used only once in a
given command, but nothing prevents you to use it repeatedly:
Listing 29-3
$io->title('Lorem ipsum dolor sit amet');
section()4
It displays the given string as the title of some command section. This is only needed in complex
commands which want to better separate their contents:
1. http://api.symfony.com/3.1/Symfony/Component/Console/Style/SymfonyStyle.html
2. http://api.symfony.com/3.1/Symfony/Component/Console/Style/SymfonyStyle.html
3. http://api.symfony.com/3.1/Symfony/Component/Console/Style/SymfonyStyle.html#method_title
4. http://api.symfony.com/3.1/Symfony/Component/Console/Style/SymfonyStyle.html#method_section
It displays the given string or array of strings as regular text. This is useful to render help messages
and instructions for the user running the command:
listing()6
table()7
newLine()8
It displays a blank line in the command output. Although it may seem useful, most of the times you
won't need it at all. The reason is that every helper already adds their own blank lines, so you don't
have to care about the vertical spacing:
5. http://api.symfony.com/3.1/Symfony/Component/Console/Style/SymfonyStyle.html#method_text
6. http://api.symfony.com/3.1/Symfony/Component/Console/Style/SymfonyStyle.html#method_listing
7. http://api.symfony.com/3.1/Symfony/Component/Console/Style/SymfonyStyle.html#method_table
8. http://api.symfony.com/3.1/Symfony/Component/Console/Style/SymfonyStyle.html#method_newLine
PDF brought to you by Chapter 29: How to Style a Console Command | 100
It displays the given string or array of strings as a highlighted admonition. Use this helper sparingly
to avoid cluttering command's output:
caution()10
Similar to the note() helper, but the contents are more prominently highlighted. The resulting
contents resemble an error message, so you should avoid using this helper unless strictly necessary:
It displays a progress bar with a number of steps equal to the argument passed to the method (don't
pass any value if the length of the progress bar is unknown):
progressAdvance()12
It makes the progress bar advance the given number of steps (or 1 step if no argument is passed):
9. http://api.symfony.com/3.1/Symfony/Component/Console/Style/SymfonyStyle.html#method_note
10. http://api.symfony.com/3.1/Symfony/Component/Console/Style/SymfonyStyle.html#method_caution
11. http://api.symfony.com/3.1/Symfony/Component/Console/Style/SymfonyStyle.html#method_progressStart
12. http://api.symfony.com/3.1/Symfony/Component/Console/Style/SymfonyStyle.html#method_progressAdvance
PDF brought to you by Chapter 29: How to Style a Console Command | 101
It finishes the progress bar (filling up all the remaining steps when its length is known):
Listing 29-13
$io->progressFinish();
Listing 29-14
$io->ask('What is your name?');
You can pass the default value as the second argument so the user can simply hit the <Enter> key
to select that value:
Listing 29-15
$io->ask('Where are you from?', 'United States');
In case you need to validate the given value, pass a callback validator as the third argument:
askHidden()15
It's very similar to the ask() method but the user's input will be hidden and it cannot define a
default value. Use it when asking for sensitive information:
confirm()16
It asks a Yes/No question to the user and it only returns true or false:
Listing 29-18
$io->confirm('Restart the web server?');
You can pass the default value as the second argument so the user can simply hit the <Enter> key
to select that value:
Listing 29-19
$io->confirm('Restart the web server?', true);
choice()17
It asks a question whose answer is constrained to the given list of valid answers:
Listing 29-20
$io->choice('Select the queue to analyze', array('queue1', 'queue2', 'queue3'));
13. http://api.symfony.com/3.1/Symfony/Component/Console/Style/SymfonyStyle.html#method_progressFinish
14. http://api.symfony.com/3.1/Symfony/Component/Console/Style/SymfonyStyle.html#method_ask
15. http://api.symfony.com/3.1/Symfony/Component/Console/Style/SymfonyStyle.html#method_askHidden
16. http://api.symfony.com/3.1/Symfony/Component/Console/Style/SymfonyStyle.html#method_confirm
17. http://api.symfony.com/3.1/Symfony/Component/Console/Style/SymfonyStyle.html#method_choice
PDF brought to you by Chapter 29: How to Style a Console Command | 102
Listing 29-21
$io->choice('Select the queue to analyze', array('queue1', 'queue2', 'queue3'), 'queue1');
Result Methods
success()18
It displays the given string or array of strings highlighted as a successful message (with a green
background and the [OK] label). It's meant to be used once to display the final result of executing
the given command, but you can use it repeatedly during the execution of the command:
warning()19
It displays the given string or array of strings highlighted as a warning message (with a red
background and the [WARNING] label). It's meant to be used once to display the final result of
executing the given command, but you can use it repeatedly during the execution of the command:
error()20
It displays the given string or array of strings highlighted as an error message (with a red background
and the [ERROR] label). It's meant to be used once to display the final result of executing the given
command, but you can use it repeatedly during the execution of the command:
18. http://api.symfony.com/3.1/Symfony/Component/Console/Style/SymfonyStyle.html#method_success
19. http://api.symfony.com/3.1/Symfony/Component/Console/Style/SymfonyStyle.html#method_warning
20. http://api.symfony.com/3.1/Symfony/Component/Console/Style/SymfonyStyle.html#method_error
PDF brought to you by Chapter 29: How to Style a Console Command | 103
Then, instantiate this custom class instead of the default SymfonyStyle in your commands. Thanks
to the StyleInterface you won't need to change the code of your commands to change their
appearance:
21. http://api.symfony.com/3.1/Symfony/Component/Console/Style/StyleInterface.html
PDF brought to you by Chapter 29: How to Style a Console Command | 104
The Console component documentation covers how to create a console command. This cookbook article
covers how to use a console command directly from your controller.
You may have the need to execute some function that is only available in a console command. Usually,
you should refactor the command and move some logic into a service that can be reused in the controller.
However, when the command is part of a third-party library, you wouldn't want to modify or duplicate
their code. Instead, you can execute the command directly.
In comparison with a direct call from the console, calling a command from a controller has a slight
performance impact because of the request stack overhead.
Imagine you want to send spooled Swift Mailer messages by using the swiftmailer:spool:send command.
Run this command from inside your controller via:
PDF brought to you by Chapter 30: How to Call a Command from a Controller | 105
The AnsiToHtmlConverter can also be registered as a Twig Extension2, and supports optional
themes.
1. https://github.com/sensiolabs/ansi-to-html
2. https://github.com/sensiolabs/ansi-to-html#twig-integration
PDF brought to you by Chapter 30: How to Call a Command from a Controller | 106
Unfortunately, the command line context does not know about your VirtualHost or domain name.
This means that if you generate absolute URLs within a console command you'll probably end up with
something like http://localhost/foo/bar which is not very useful.
To fix this, you need to configure the "request context", which is a fancy way of saying that you need to
configure your environment so that it knows what URL it should use when generating URLs.
There are two ways of configuring the request context: at the application level and per Command.
PDF brought to you by Chapter 31: How to Generate URLs from the Console | 107
PDF brought to you by Chapter 31: How to Generate URLs from the Console | 108
The Console component doesn't provide any logging capabilities out of the box. Normally, you run
console commands manually and observe the output, which is why logging is not provided. However,
there are cases when you might need logging. For example, if you are running console commands
unattended, such as from cron jobs or deployment scripts, it may be easier to use Symfony's logging
capabilities instead of configuring other tools to gather console output and process it. This can be
especially handful if you already have some existing setup for aggregating and analyzing Symfony logs.
There are basically two logging cases you would need:
1. http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/Command/ContainerAwareCommand.html
PDF brought to you by Chapter 32: How to Enable Logging in Console Commands | 109
Depending on the environment in which you run your command (and your logging setup), you should
see the logged entries in var/logs/dev.log or var/logs/prod.log.
PDF brought to you by Chapter 32: How to Enable Logging in Console Commands | 110
In the code above, when any command throws an exception, the listener will receive an event. You
can simply log it by passing the logger service via the service configuration. Your method receives a
ConsoleExceptionEvent2 object, which has methods to get information about the event and the
exception.
2. http://api.symfony.com/3.1/Symfony/Component/Console/Event/ConsoleExceptionEvent.html
PDF brought to you by Chapter 32: How to Enable Logging in Console Commands | 111
PDF brought to you by Chapter 32: How to Enable Logging in Console Commands | 112
By default, Symfony will take a look in the Command directory of each bundle and automatically register
your commands. If a command extends the ContainerAwareCommand1, Symfony will even inject the
container. While making life easier, this has some limitations:
To solve these problems, you can register your command as a service and tag it with
console.command:
Listing 33-1 1 # app/config/config.yml
2 services:
3 app.command.my_command:
4 class: AppBundle\Command\MyCommand
5 tags:
6 - { name: console.command }
• a hardcoded string;
• a container parameter (e.g. something from parameters.yml);
• a value computed by a service (e.g. a repository).
By extending ContainerAwareCommand, only the first is possible, because you can't access the
container inside the configure() method. Instead, inject any parameter or service you need into the
1. http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/Command/ContainerAwareCommand.html
PDF brought to you by Chapter 33: How to Define Commands as Services | 113
Now, just update the arguments of your service configuration like normal to inject the
command.default_name parameter:
Listing 33-3 1 # app/config/config.yml
2 parameters:
3 command.default_name: Javier
4
5 services:
6 app.command.my_command:
7 class: AppBundle\Command\MyCommand
8 arguments: ["%command.default_name%"]
9 tags:
10 - { name: console.command }
Be careful not to actually do any work in configure (e.g. make database queries), as your code will
be run, even if you're using the console to execute a different command.
PDF brought to you by Chapter 33: How to Define Commands as Services | 114
In Symfony applications, all errors are treated as exceptions, no matter if they are just a 404 Not Found
error or a fatal error triggered by throwing some exception in your code.
In the development environment, Symfony catches all the exceptions and displays a special exception
page with lots of debug information to help you quickly discover the root problem:
Since these pages contain a lot of sensitive internal information, Symfony won't display them in the
production environment. Instead, it'll show a simple and generic error page:
PDF brought to you by Chapter 34: How to Customize Error Pages | 115
1. http://api.symfony.com/3.1/Symfony/Bundle/TwigBundle/Controller/ExceptionController.html
PDF brought to you by Chapter 34: How to Customize Error Pages | 116
In case you need them, the ExceptionController passes some information to the error template
via the status_code and status_text variables that store the HTTP status code and message
respectively.
You can customize the status code by implementing HttpExceptionInterface2 and its required
getStatusCode() method. Otherwise, the status_code will default to 500.
The exception pages shown in the development environment can be customized in the same way
as error pages. Create a new exception.html.twig template for the standard HTML exception
page or exception.json.twig for the JSON exception page.
If you're coming from an older version of Symfony, you might need to add this to your
routing_dev.yml file. If you're starting from scratch, the Symfony Standard Edition3 already contains
it for you.
With this route added, you can use URLs like
Listing 34-4
2. http://api.symfony.com/3.1/Symfony/Component/HttpKernel/Exception/HttpExceptionInterface.html
3. https://github.com/symfony/symfony-standard/
PDF brought to you by Chapter 34: How to Customize Error Pages | 117
to preview the error page for a given status code as HTML or for a given status code and format.
logger
A DebugLoggerInterface6 instance which may be null in some circumstances.
Instead of creating a new exception controller from scratch you can, of course, also extend the default
ExceptionController7. In that case, you might want to override one or both of the showAction()
and findTemplate() methods. The latter one locates the template to be used.
In case of extending the ExceptionController8 you may configure a service to pass the Twig
environment and the debug flag to the constructor.
And then configure twig.exception_controller using the controller as services syntax (e.g.
app.exception_controller:showAction).
The error page preview also works for your own controllers set up this way.
4. http://api.symfony.com/3.1/Symfony/Component/HttpKernel/EventListener/ExceptionListener.html
5. http://api.symfony.com/3.1//Symfony/Component/Debug/Exception/FlattenException.html
6. http://api.symfony.com/3.1//Symfony/Component/HttpKernel/Log/DebugLoggerInterface.html
7. http://api.symfony.com/3.1/Symfony/Bundle/TwigBundle/Controller/ExceptionController.html
8. http://api.symfony.com/3.1/Symfony/Bundle/TwigBundle/Controller/ExceptionController.html
PDF brought to you by Chapter 34: How to Customize Error Pages | 118
This approach allows you to create centralized and layered error handling: instead of catching (and
handling) the same exceptions in various controllers time and again, you can have just one (or several)
listeners deal with them.
See ExceptionListener11 class code for a real example of an advanced listener of this type.
This listener handles various security-related exceptions that are thrown in your application (like
AccessDeniedException12) and takes measures like redirecting the user to the login page,
logging them out and other things.
9. http://api.symfony.com/3.1/Symfony/Component/HttpKernel/HttpKernel.html
10. http://api.symfony.com/3.1/Symfony/Component/HttpKernel/Event/GetResponseForExceptionEvent.html
11. http://api.symfony.com/3.1/Symfony/Component/Security/Http/Firewall/ExceptionListener.html
12. http://api.symfony.com/3.1/Symfony/Component/Security/Core/Exception/AccessDeniedException.html
PDF brought to you by Chapter 34: How to Customize Error Pages | 119
Defining controllers as services is not officially recommended by Symfony. They are used by
some developers for very specific use cases, such as DDD (domain-driven design) and Hexagonal
Architecture applications.
In the book, you've learned how easily a controller can be used when it extends the base Controller1
class. While this works fine, controllers can also be specified as services. Even if you don't specify your
controllers as services, you might see them being used in some open-source Symfony bundles, so it may
be useful to understand both approaches.
These are the main advantages of defining controllers as services:
• The entire controller and any service passed to it can be modified via the service container
configuration. This is useful when developing reusable bundles;
• Your controllers are more "sandboxed". By looking at the constructor arguments, it's easy to see
what types of things this controller may or may not do;
• Since dependencies must be injected manually, it's more obvious when your controller is becoming
too big (i.e. if you have many constructor arguments).
• It takes more work to create the controllers because they don't have automatic access to the services
or to the base controller shortcuts;
• The constructor of the controllers can rapidly become too complex because you must inject every
single dependency needed by them;
• The code of the controllers is more verbose because you can't use the shortcuts of the base controller
and you must replace them with some lines of code.
The recommendation from the best practices is also valid for controllers defined as services: avoid putting
your business logic into the controllers. Instead, inject services that do the bulk of the work.
1. http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/Controller/Controller.html
PDF brought to you by Chapter 35: How to Define Controllers as Services | 120
You cannot drop the Action part of the method name when using this syntax.
You can also route to the service by using the same notation when defining the route _controller
value:
You can also use annotations to configure routing using a controller defined as a service. Make sure
you specify the service ID in the @Route annotation. See the FrameworkExtraBundle documentation2
for details.
If your controller implements the __invoke() method, you can simply refer to the service id
(app.hello_controller).
2. https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/routing.html#controller-as-service
PDF brought to you by Chapter 35: How to Define Controllers as Services | 121
If you look at the source code for the render function in Symfony's base Controller class4, you'll see that
this method actually uses the templating service:
Listing 35-6
public function render($view, array $parameters = array(), Response $response = null)
{
return $this->container->get('templating')->renderResponse($view, $parameters, $response);
}
In a controller that's defined as a service, you can instead inject the templating service and use it
directly:
The service definition also needs modifying to specify the constructor argument:
3. https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php
4. https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php
PDF brought to you by Chapter 35: How to Define Controllers as Services | 122
Rather than fetching the templating service from the container, you can inject only the exact service(s)
that you need directly into the controller.
This does not mean that you cannot extend these controllers from your own base controller. The
move away from the standard base controller is because its helper methods rely on having the
container available which is not the case for controllers that are defined as services. It may be a
good idea to extract common code into a service that's injected rather than place that code into a
base controller that you extend. Both approaches are valid, exactly how you want to organize your
reusable code is up to you.
createNotFoundException()7
5. http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/Controller/Controller.html#method_createForm
6. http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/Controller/Controller.html#method_createFormBuilder
7. http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/Controller/Controller.html#method_createNotFoundException
8. http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/Controller/Controller.html#method_forward
9. http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/Controller/Controller.html#method_generateUrl
10. http://api.symfony.com/3.1/Symfony/Component/Routing/Generator/UrlGeneratorInterface.html
PDF brought to you by Chapter 35: How to Define Controllers as Services | 123
redirect()14
getRequest has been deprecated. Instead, have an argument to your controller action method
called Request $request. The order of the parameters is not important, but the typehint must
be provided.
11. http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/Controller/Controller.html#method_getDoctrine
12. http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/Controller/Controller.html#method_getUser
13. http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/Controller/Controller.html#method_isGranted
14. http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/Controller/Controller.html#method_redirect
15. http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/Controller/Controller.html#method_render
16. http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/Controller/Controller.html#method_renderView
17. http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/Controller/Controller.html#method_stream
PDF brought to you by Chapter 35: How to Define Controllers as Services | 124
Instead of handling file uploading yourself, you may consider using the VichUploaderBundle1
community bundle. This bundle provides all the common operations (such as file renaming, saving
and deleting) and it's tightly integrated with Doctrine ORM, MongoDB ODM, PHPCR ODM and
Propel.
Imagine that you have a Product entity in your application and you want to add a PDF brochure for
each product. To do so, add a new property called brochure in the Product entity:
1. https://github.com/dustin10/VichUploaderBundle
Now, update the template that renders the form to display the new brochure field (the exact template
code to add depends on the method used by your application to customize form rendering):
Finally, you need to update the code of the controller that handles the form:
Now, create the brochures_directory parameter that was used in the controller to specify the
directory in which the brochures should be stored:
There are some important things to consider in the code of the above controller:
1. When the form is uploaded, the brochure property contains the whole PDF file contents. Since
this property stores just the file name, you must set its new value before persisting the changes
of the entity;
2. In Symfony applications, uploaded files are objects of the UploadedFile2 class. This class provides
methods for the most common operations when dealing with uploaded files;
3. A well-known security best practice is to never trust the input provided by users. This also
applies to the files uploaded by your visitors. The UploadedFile class provides methods to get the
original file extension (getExtension()3), the original file size (getClientSize()4) and the original file
name (getClientOriginalName()5). However, they are considered not safe because a malicious user
could tamper that information. That's why it's always better to generate a unique name and use
the guessExtension()6 method to let Symfony guess the right extension according to the file MIME
type;
You can use the following code to link to the PDF brochure of a product:
2. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/File/UploadedFile.html
3. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/File/UploadedFile.html#method_getExtension
4. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/File/UploadedFile.html#method_getClientSize
5. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/File/UploadedFile.html#method_getClientOriginalName
6. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/File/UploadedFile.html#method_guessExtension
7. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/File/File.html
This listeners is now automatically executed when persisting a new Product entity. This way, you can
remove everything related to uploading from the controller.
This listener can also create the File instance based on the path when fetching entities from the
database:
After adding these lines, configure the listener to also listen for the postLoad event.
New in version 3.1: The ArgumentResolver and value resolvers were introduced in Symfony 3.1.
In the book, you've learned that you can get the Request1 object via an argument in your controller.
This argument has to be type-hinted by the Request class in order to be recognized. This is done via the
ArgumentResolver2. By creating and registering custom argument value resolvers, you can extend this
functionality.
RequestValueResolver4
Injects the current Request if type-hinted with Request or a class extending Request.
DefaultValueResolver5
Will set the default value of the argument if present and the argument is optional.
VariadicValueResolver6
Verifies if the request data is an array and will add all of them to the argument list. When the action
is called, the last (variadic) argument will contain all the values of this array.
Prior to Symfony 3.1, this logic was resolved within the ControllerResolver. The old
functionality is rewritten to the aforementioned value resolvers.
1. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/Request.html
2. http://api.symfony.com/3.1/Symfony/Component/HttpKernel/Controller/ArgumentResolver.html
3. http://api.symfony.com/3.1/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestAttributeValueResolver.html
4. http://api.symfony.com/3.1/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestValueResolver.html
5. http://api.symfony.com/3.1/Symfony/Component/HttpKernel/Controller/ArgumentResolver/DefaultValueResolver.html
6. http://api.symfony.com/3.1/Symfony/Component/HttpKernel/Controller/ArgumentResolver/VariadicValueResolver.html
PDF brought to you by Chapter 37: Extending Action Argument Resolving | 131
Somehow you will have to get the User object and inject it into the controller. This can be done by
implementing the ArgumentValueResolverInterface7. This interface specifies that you have to
implement two methods:
supports()
This method is used to check whether the value resolver supports the given argument. resolve() will
only be executed when this returns true.
resolve()
This method will resolve the actual value for the argument. Once the value is resolved, you must
yield8 the value to the ArgumentResolver.
Both methods get the Request object, which is the current request, and an ArgumentMetadata9
instance. This object contains all information retrieved from the method signature for the current
argument.
Now that you know what to do, you can implement this interface. To get the current User, you need the
current security token. This token can be retrieved from the token storage:
7. http://api.symfony.com/3.1/Symfony/Component/HttpKernel/Controller/ArgumentValueResolverInterface.html
8. http://php.net/manual/en/language.generators.syntax.php
9. http://api.symfony.com/3.1/Symfony/Component/HttpKernel/ControllerMetadata/ArgumentMetadata.html
PDF brought to you by Chapter 37: Extending Action Argument Resolving | 132
In order to get the actual User object in your argument, the given value must fulfill the following
requirements:
When all those requirements are met and true is returned, the ArgumentResolver calls resolve()
with the same values as it called supports().
That's it! Now all you have to do is add the configuration for the service container. This can be done by
tagging the service with controller.argument_resolver and adding a priority.
While adding a priority is optional, it's recommended to add one to make sure the expected value is
injected. The RequestAttributeValueResolver has a priority of 100. As this one is responsible for
fetching attributes from the Request, it's recommended to trigger your custom value resolver with a
lower priority. This makes sure the argument resolvers are not triggered when the attribute is present.
For instance, when passing the user along a subrequests.
As you can see in the UserValueResolver::supports() method, the user may not be available
(e.g. when the controller is not behind a firewall). In these cases, the resolver will not be executed. If
no argument value is resolved, an exception will be thrown.
To prevent this, you can add a default value in the controller (e.g. User $user = null). The
DefaultValueResolver is executed as the last resolver and will use the default value if no value
was already resolved.
PDF brought to you by Chapter 37: Extending Action Argument Resolving | 133
When you work on a Symfony project on your local machine, you should use the dev environment
(app_dev.php front controller). This environment configuration is optimized for two main purposes:
• Give the developer accurate feedback whenever something goes wrong (web debug toolbar, nice
exception pages, profiler, ...);
• Be as similar as possible as the production environment to avoid problems when deploying the
project.
To make your debugger happier, disable the loading of all PHP class caches by removing the call to
loadClassCache():
PDF brought to you by Chapter 38: How to Optimize your Development Environment for Debugging | 134
If you disable the PHP caches, don't forget to revert after your debugging session.
Some IDEs do not like the fact that some classes are stored in different locations. To avoid problems, you
can tell your IDE to ignore the PHP cache file.
PDF brought to you by Chapter 38: How to Optimize your Development Environment for Debugging | 135
Deploying can be a complex and varied task depending on the setup and the requirements of
your application. This article is not a step-by-step guide, but is a general list of the most common
requirements and ideas for deployment.
• Tagging a particular version of your code as a release in your source control repository;
• Creating a temporary staging area to build your updated setup "offline";
• Running any tests available to ensure code and/or server stability;
• Removal of any unnecessary files from the web/ directory to keep your production environment
clean;
• Clearing of external cache systems (like Memcached1 or Redis2).
1. http://memcached.org/
2. http://redis.io/
PDF brought to you by Chapter 39: How to Deploy a Symfony Application | 136
sf2debpkg8
Helps you build a native Debian package for your Symfony project.
Magallanes9
This Capistrano-like deployment tool is built in PHP, and may be easier for PHP developers to
extend for their needs.
Fabric10
This Python-based library provides a basic suite of operations for executing local or remote shell
commands and uploading/downloading files.
Deployer11
This is another native PHP rewrite of Capistrano, with some ready recipes for Symfony.
Bundles
There are some bundles that add deployment features12 directly into your Symfony console.
Basic scripting
You can of course use shell, Ant13 or any other build tool to script the deploying of your project.
3. http://capistranorb.com/
4. https://github.com/capistrano/symfony/
5. http://capistranorb.com/
6. https://github.com/capistrano/symfony/
7. http://capifony.org/
8. https://github.com/liip/sf2debpkg
9. https://github.com/andres-montanez/Magallanes
10. http://www.fabfile.org/
11. http://deployer.org/
12. http://knpbundles.com/search?q=deploy
13. http://blog.sznapka.pl/deploying-symfony2-applications-with-ant
PDF brought to you by Chapter 39: How to Deploy a Symfony Application | 137
• Microsoft Azure
• Heroku
• Platform.sh
A) Check Requirements
Check if your server meets the requirements by running:
If you get a "class not found" error during this step, you may need to run export
SYMFONY_ENV=prod before running this command so that the post-install-cmd scripts run in
the prod environment.
PDF brought to you by Chapter 39: How to Deploy a Symfony Application | 138
PDF brought to you by Chapter 39: How to Deploy a Symfony Application | 139
This step by step cookbook describes how to deploy a small Symfony web application to the Microsoft
Azure Website cloud platform. It will explain how to set up a new Azure website including configuring
the right PHP version and global environment variables. The document also shows how to you can
leverage Git and Composer to deploy your Symfony application to the cloud.
1. https://signup.live.com/signup.aspx
2. https://manage.windowsazure.com
PDF brought to you by Chapter 40: Deploying to Microsoft Azure Website Cloud | 140
For the URL, enter the URL that you would like to use for your Symfony application, then pick Create
new web hosting plan in the region you want. By default, a free 20 MB SQL database is selected in the
database dropdown list. In this tutorial, the Symfony app will connect to a MySQL database. Pick the
Create a new MySQL database option in the dropdown list. You can keep the DefaultConnection
string name. Finally, check the box Publish from source control to enable a Git repository and go to
the next step.
Agree to the terms and conditions and click on the right arrow to continue.
PDF brought to you by Chapter 40: Deploying to Microsoft Azure Website Cloud | 141
Congratulations! Your Azure Website is now up and running. You can check it by browsing to the
Website url you configured in the first step. You should see the following display in your web browser:
PDF brought to you by Chapter 40: Deploying to Microsoft Azure Website Cloud | 142
Your Azure Website is ready! But to run a Symfony site, you need to configure just a few additional
things.
PDF brought to you by Chapter 40: Deploying to Microsoft Azure Website Cloud | 143
Choosing a more recent PHP version can greatly improve runtime performance. PHP 5.5 ships with
a new built-in PHP accelerator called OPCache that replaces APC. On an Azure Website, OPCache
is already enabled and there is no need to install and set up APC.
The following screenshot shows the output of a phpinfo3 script run from an Azure Website to
verify that PHP 5.5 is running with OPCache enabled.
3. http://php.net/manual/en/function.phpinfo.php
PDF brought to you by Chapter 40: Deploying to Microsoft Azure Website Cloud | 144
This cookbook has a section dedicated to explaining how to configure your Azure Website Git
repository and how to push the commits to be deployed. See Deploying from Git. You can also learn
more about configuring PHP internal settings on the official PHP MSDN documentation4 page.
The Microsoft Azure team is currently working on enabling the intl PHP extension by default. In
the near future, the following steps will no longer be necessary.
To get the php_intl.dll file under your site/wwwroot directory, simply access the online Kudu
tool by browsing to the following URL:
Kudu is a set of tools to manage your application. It comes with a file explorer, a command line prompt,
a log stream and a configuration settings summary page. Of course, this section can only be accessed if
you're logged in to your main Azure Website account.
4. http://blogs.msdn.com/b/silverlining/archive/2012/07/10/configuring-php-in-windows-azure-websites-with-user-ini-files.aspx
PDF brought to you by Chapter 40: Deploying to Microsoft Azure Website Cloud | 145
To complete the activation of the php_intl.dll extension, you must tell Azure Website to load it
from the newly created ext directory. This can be done by registering a global PHP_EXTENSIONS
environment variable from the Configure tab of the main Azure Website Control panel.
In the app settings section, register the PHP_EXTENSIONS environment variable with the value
ext\php_intl.dll as shown in the screenshot below:
PDF brought to you by Chapter 40: Deploying to Microsoft Azure Website Cloud | 146
Great! The PHP environment setup is now complete. Next, you'll learn how to configure the Git
repository and push code to production. You'll also learn how to install and configure the Symfony app
after it's deployed.
Get your Git from the git-scm.com6 website and follow the instructions to install and configure it on
your local machine.
In the Azure Website Control panel, browse the Deployment tab to get the Git repository URL where
you should push your code:
5. http://php.net/manual/en/function.phpinfo.php
6. http://git-scm.com/download
PDF brought to you by Chapter 40: Deploying to Microsoft Azure Website Cloud | 147
The .gitignore file asks Git not to track any of the files and directories that match these patterns. This
means these files won't be deployed to the Azure Website.
Now, from the command line on your local machine, type the following at the root of your Symfony
project:
Don't forget to replace the values enclosed by < and > with your custom settings displayed in the
Deployment tab of your Azure Website panel. The git remote command connects the Azure Website
remote Git repository and assigns an alias to it with the name azure. The second git push command
pushes all your commits to the remote master branch of your remote azure Git repository.
The deployment with Git should produce an output similar to the screenshot below:
PDF brought to you by Chapter 40: Deploying to Microsoft Azure Website Cloud | 148
The curl command retrieves and downloads the Composer command line tool and installs it at the
root of the site/wwwroot directory. Then, running the Composer install command downloads and
installs all necessary third-party libraries.
This may take a while depending on the number of third-party dependencies you've configured in your
composer.json file.
The -d switch allows you to quickly override/add any php.ini settings. In this command, we are
forcing PHP to use the intl extension, because it is not enabled by default in Azure Website at
the moment. Soon, this -d option will no longer be needed since Microsoft will enable the intl
extension by default.
At the end of the composer install command, you will be prompted to fill in the values of some
Symfony settings like database credentials, locale, mailer credentials, CSRF token protection, etc. These
parameters come from the app/config/parameters.yml.dist file.
PDF brought to you by Chapter 40: Deploying to Microsoft Azure Website Cloud | 149
The displayed MySQL database settings should be something similar to the code below. Of course, each
value depends on what you've already configured.
Switch back to the console and answer the prompted questions and provide the following answers. Don't
forget to adapt the values below with your real values from the MySQL connection string.
Don't forget to answer all the questions. It's important to set a unique random string for the secret
variable. For the mailer configuration, Azure Website doesn't provide a built-in mailer service. You
PDF brought to you by Chapter 40: Deploying to Microsoft Azure Website Cloud | 150
Your Symfony application is now configured and should be almost operational. The final step is to build
the database schema. This can easily be done with the command line interface if you're using Doctrine.
In the online Console tool of the Kudu application, run the following command to mount the tables into
your MySQL database.
This command builds the tables and indexes for your MySQL database. If your Symfony application is
more complex than a basic Symfony Standard Edition, you may have additional commands to execute
for setup (see How to Deploy a Symfony Application).
Make sure that your application is running by browsing the app.php front controller with your web
browser and the following URL:
If Symfony is correctly installed, you should see the front page of your Symfony application showing.
PDF brought to you by Chapter 40: Deploying to Microsoft Azure Website Cloud | 151
As you can see, the latest rule RewriteRequestsToPublic is responsible for rewriting any URLs to
the web/app.php front controller which allows you to skip the web/ folder in the URL. The first rule
called BlockAccessToPublic matches all URL patterns that contain the web/ folder and serves a 403
Forbidden HTTP response instead. This example is based on Benjamin Eberlei's sample you can find
on GitHub in the SymfonyAzureEdition7 bundle.
Deploy this file under the site/wwwroot directory of the Azure Website and browse to your application
without the web/app.php segment in the URL.
Conclusion
Nice work! You've now deployed your Symfony application to the Microsoft Azure Website Cloud
platform. You also saw that Symfony can be easily configured and executed on a Microsoft IIS web server.
The process is simple and easy to implement. And as a bonus, Microsoft is continuing to reduce the
number of steps needed so that deployment becomes even easier.
7. https://github.com/beberlei/symfony-azure-edition/
PDF brought to you by Chapter 40: Deploying to Microsoft Azure Website Cloud | 152
This step by step cookbook describes how to deploy a Symfony web application to the Heroku cloud
platform. Its contents are based on the original article1 published by Heroku.
Setting up
To set up a new Heroku website, first sign up with Heroku2 or sign in with your credentials. Then
download and install the Heroku Toolbelt3 on your local computer.
You can also check out the getting Started with PHP on Heroku4 guide to gain more familiarity with the
specifics of working with PHP applications on Heroku.
1. https://devcenter.heroku.com/articles/getting-started-with-symfony2
2. https://signup.heroku.com/signup/dc
3. https://devcenter.heroku.com/articles/getting-started-with-php#set-up
4. https://devcenter.heroku.com/articles/getting-started-with-php
5. https://devcenter.heroku.com/articles/dynos#ephemeral-filesystem
6. https://devcenter.heroku.com/articles/logplex
Once the application is deployed, run heroku logs --tail to keep the stream of logs from Heroku
open in your terminal.
You are now ready to deploy the application as explained in the next section.
1) Create a Procfile
By default, Heroku will launch an Apache web server together with PHP to serve applications. However,
two special circumstances apply to Symfony applications:
1. The document root is in the web/ directory and not in the root directory of the application;
2. The Composer bin-dir, where vendor binaries (and thus Heroku's own boot scripts) are placed,
is bin/ , and not the default vendor/bin.
Vendor binaries are usually installed to vendor/bin by Composer, but sometimes (e.g. when
running a Symfony Standard Edition project!), the location will be different. If in doubt, you can
always run composer config bin-dir to figure out the right location.
Create a new file called Procfile (without any extension) at the root directory of the application and
add just the following content:
If you prefer to use Nginx, which is also available on Heroku, you can create a configuration file for
it and point to it from your Procfile as described in the Heroku documentation7:
7. https://devcenter.heroku.com/articles/custom-php-settings#nginx
Be aware that dependencies from composer.json listed in the require-dev section are never
installed during a deploy on Heroku. This may cause problems if your Symfony environment relies
on such packages. The solution is to move these packages from require-dev to the require
section.
In this case, you need to confirm by typing yes and hitting <Enter> key - ideally after you've verified
that the RSA key fingerprint is correct10.
Then, deploy your application executing this command:
8. https://getcomposer.org/doc/articles/scripts.md
9. https://devcenter.heroku.com/articles/config-vars
10. https://devcenter.heroku.com/articles/git-repository-ssh-fingerprints
And that's it! If you now open your browser, either by manually pointing it to the URL heroku create
gave you, or by using the Heroku Toolbelt, the application will respond:
If you take your first steps on Heroku using a fresh installation of the Symfony Standard Edition,
you may run into a 404 page not found error. This is because the route for / is defined by the
AcmeDemoBundle, but the AcmeDemoBundle is only loaded in the dev environment (check out
your AppKernel class). Try opening /app/example from the AppBundle.
11. https://devcenter.heroku.com/articles/php-support#custom-compile-step
12. https://getcomposer.org/doc/articles/scripts.md
13. https://getcomposer.org/doc/articles/scripts.md#writing-custom-commands
Listing 41-10 1 {
2 "scripts": {
3 "compile": [
4 "rm web/app_dev.php"
5 ]
6 }
7 }
This is also very useful to build assets on the production system, e.g. with Assetic:
Listing 41-11 1 {
2 "scripts": {
3 "compile": [
4 "bin/console assetic:dump"
5 ]
6 }
7 }
Node.js Dependencies
Building assets may depend on node packages, e.g. uglifyjs or uglifycss for asset minification.
Installing node packages during the deploy requires a node installation. But currently, Heroku
compiles your app using the PHP buildpack, which is auto-detected by the presence of a
composer.json file, and does not include a node installation. Because the Node.js buildpack has
a higher precedence than the PHP buildpack (see Heroku buildpacks14), adding a package.json
listing your node dependencies makes Heroku opt for the Node.js buildpack instead:
Listing 41-12 1 {
2 "name": "myApp",
3 "engines": {
4 "node": "0.12.x"
5 },
6 "dependencies": {
7 "uglifycss": "*",
8 "uglify-js": "*"
9 }
10 }
With the next deploy, Heroku compiles your app using the Node.js buildpack and your npm
packages become installed. On the other hand, your composer.json is now ignored. To compile
your app with both buildpacks, Node.js and PHP, you need to use both buildpacks. To override
buildpack auto-detection, you need to explicitly set the buildpack:
With the next deploy, you can benefit from both buildpacks. This setup also enables your Heroku
environment to make use of node based automatic build tools like Grunt15 or gulp16.
14. https://devcenter.heroku.com/articles/buildpacks
15. http://gruntjs.com
16. http://gulpjs.com
This step-by-step cookbook describes how to deploy a Symfony web application to Platform.sh1. You can
read more about using Symfony with Platform.sh on the official Platform.sh documentation2.
1. https://platform.sh
2. https://docs.platform.sh/toolstacks/symfony/symfony-getting-started
3. https://marketplace.commerceguys.com/platform/buy-now
4. https://docs.platform.sh/reference/configuration-files
For best practices, you should also add a .platform folder at the root of your Git repository which
contains the following files:
An example of these configurations can be found on GitHub5. The list of available services6 can be found
on the Platform.sh documentation.
5. https://github.com/platformsh/platformsh-examples
6. https://docs.platform.sh/reference/configuration-files/#configure-services
PROJECT-ID
Unique identifier of your project. Something like kjh43kbobssae
CLUSTER
Server location where your project is deployed. It can be eu or us
That's it! Your application is being deployed on Platform.sh and you'll soon be able to access it in your
browser.
Every code change that you do from now on will be pushed to Git in order to redeploy your environment
on Platform.sh.
More information about migrating your database and files7 can be found on the Platform.sh
documentation.
7. https://docs.platform.sh/toolstacks/php/symfony/migrate-existing-site/
8. https://marketplace.commerceguys.com/platform/buy-now
This step-by-step cookbook describes how to deploy a Symfony web application to fortrabbit1. You can
read more about using Symfony with fortrabbit on the official fortrabbit Symfony install guide2.
Setting up fortrabbit
Before getting started, you should have done a few things on the fortrabbit side:
• Sign up3;
• Add an SSH key to your Account (to deploy via Git);
• Create an App.
Configure Logging
Per default Symfony logs to a file. Modify the app/config/config_prod.yml file to redirect it to
error_log4:
Listing 43-1 1 # app/config/config_prod.yml
2 monolog:
3 # ...
4 handlers:
5 nested:
6 type: error_log
1. https://www.fortrabbit.com
2. https://help.fortrabbit.com/install-symfony
3. https://dashboard.fortrabbit.com
4. http://php.net/manual/en/function.error-log.php
Make sure this file is imported into the main config file:
Document Root
The document root is configuable for every custom domain you setup for your App. The default is
/htdocs, but for Symfony you probably want to change it to /htdocs/web. You also do so in the
fortrabbit Dashboard under Domain settings.
Deploying to fortrabbit
It is assumed that your codebase is under version-control with Git and dependencies are managed with
Composer (locally).
Every time you push to fortrabbit composer install runs before your code gets deployed. To finetune the
deployment behavior put a fortrabbit.yml5. deployment file (optional) in the project root.
Add fortrabbit as a (additional) Git remote and add your configuration changes:
5. https://help.fortrabbit.com/deployment-file-v2
The first git push takes much longer as all composer dependencies get downloaded. All
subsequent deploys are done within seconds.
That's it! Your application is being deployed on fortrabbit. More information about database migrations
and tunneling6 can be found in the fortrabbit documentation.
6. https://help.fortrabbit.com/install-symfony-2#toc-migrate-amp-other-database-commands
Doctrine2 is very flexible, and the community has already created a series of useful Doctrine extensions
to help you with common entity-related tasks.
One library in particular - the DoctrineExtensions1 library - provides integration functionality for
Sluggable2, Translatable3, Timestampable4, Loggable5, Tree6 and Sortable7 behaviors.
The usage for each of these extensions is explained in that repository.
However, to install/activate each extension you must register and activate an Event Listener. To do this,
you have two options:
1. Use the StofDoctrineExtensionsBundle8, which integrates the above library.
2. Implement this services directly by following the documentation for integration with Symfony:
Install Gedmo Doctrine2 extensions in Symfony29
1. https://github.com/Atlantic18/DoctrineExtensions
2. https://github.com/Atlantic18/DoctrineExtensions/blob/master/doc/sluggable.md
3. https://github.com/Atlantic18/DoctrineExtensions/blob/master/doc/translatable.md
4. https://github.com/Atlantic18/DoctrineExtensions/blob/master/doc/timestampable.md
5. https://github.com/Atlantic18/DoctrineExtensions/blob/master/doc/loggable.md
6. https://github.com/Atlantic18/DoctrineExtensions/blob/master/doc/tree.md
7. https://github.com/Atlantic18/DoctrineExtensions/blob/master/doc/sortable.md
8. https://github.com/stof/StofDoctrineExtensionsBundle
9. https://github.com/Atlantic18/DoctrineExtensions/blob/master/doc/symfony2.md
PDF brought to you by Chapter 44: How to use Doctrine Extensions: Timestampable, Sluggable, Translatable, etc. | 166
Doctrine packages a rich event system that fires events when almost anything happens inside the
system. For you, this means that you can create arbitrary services and tell Doctrine to notify those
objects whenever a certain action (e.g. prePersist) happens within Doctrine. This could be useful, for
example, to create an independent search index whenever an object in your database is saved.
Doctrine defines two types of objects that can listen to Doctrine events: listeners and subscribers. Both
are very similar, but listeners are a bit more straightforward. For more, see The Event System1 on
Doctrine's website.
The Doctrine website also explains all existing events that can be listened to.
1. http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html
PDF brought to you by Chapter 45: How to Register Event Listeners and Subscribers | 167
In each event, you have access to a LifecycleEventArgs object, which gives you access to both the
entity object of the event and the entity manager itself.
One important thing to notice is that a listener will be listening for all entities in your application. So, if
you're interested in only handling a specific type of entity (e.g. a Product entity but not a BlogPost
entity), you should check for the entity's class type in your method (as shown above).
In Doctrine 2.4, a feature called Entity Listeners was introduced. It is a lifecycle listener class used
for an entity. You can read about it in the Doctrine Documentation2.
2. http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html#entity-listeners
PDF brought to you by Chapter 45: How to Register Event Listeners and Subscribers | 168
Doctrine event subscribers can not return a flexible array of methods to call for the events like the
Symfony event subscriber can. Doctrine event subscribers must return a simple array of the event
names they subscribe to. Doctrine will then expect methods on the subscriber with the same name
as each subscribed event, just as when using an event listener.
For a full reference, see chapter The Event System3 in the Doctrine documentation.
3. http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html
PDF brought to you by Chapter 45: How to Register Event Listeners and Subscribers | 169
This article is about the Doctrine DBAL. Typically, you'll work with the higher level Doctrine ORM
layer, which simply uses the DBAL behind the scenes to actually communicate with the database.
To read more about the Doctrine ORM, see "Databases and Doctrine".
The Doctrine1 Database Abstraction Layer (DBAL) is an abstraction layer that sits on top of PDO2 and
offers an intuitive and flexible API for communicating with the most popular relational databases. In
other words, the DBAL library makes it easy to execute queries and perform other database actions.
Read the official Doctrine DBAL Documentation3 to learn all the details and capabilities of Doctrine's
DBAL library.
For full DBAL configuration options, or to learn how to configure multiple connections, see Doctrine
DBAL Configuration.
You can then access the Doctrine DBAL connection by accessing the database_connection service:
1. http://www.doctrine-project.org
2. http://www.php.net/pdo
3. http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/index.html
PDF brought to you by Chapter 46: How to Use Doctrine DBAL | 170
4. http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/types.html#custom-mapping-types
PDF brought to you by Chapter 46: How to Use Doctrine DBAL | 171
When starting work on a brand new project that uses a database, two different situations comes
naturally. In most cases, the database model is designed and built from scratch. Sometimes, however,
you'll start with an existing and probably unchangeable database model. Fortunately, Doctrine comes
with a bunch of tools to help generate model classes from your existing database.
As the Doctrine tools documentation1 says, reverse engineering is a one-time process to get started on
a project. Doctrine is able to convert approximately 70-80% of the necessary mapping information
based on fields, indexes and foreign key constraints. Doctrine can't discover inverse associations,
inheritance types, entities with foreign keys as primary keys or semantical operations on associations
such as cascade or lifecycle events. Some additional work on the generated entities will be necessary
afterwards to design each to fit your domain model specificities.
This tutorial assumes you're using a simple blog application with the following two tables: blog_post
and blog_comment. A comment record is linked to a post record thanks to a foreign key constraint.
1. http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/tools.html#reverse-engineering
PDF brought to you by Chapter 47: How to Generate Entities from an Existing Database | 172
Before diving into the recipe, be sure your database connection parameters are correctly setup in the
app/config/parameters.yml file (or wherever your database configuration is kept) and that you
have initialized a bundle that will host your future entity class. In this tutorial it's assumed that an
AcmeBlogBundle exists and is located under the src/Acme/BlogBundle folder.
The first step towards building entity classes from an existing database is to ask Doctrine to introspect
the database and generate the corresponding metadata files. Metadata files describe the entity class to
generate based on table fields.
This command line tool asks Doctrine to introspect the database and generate the XML metadata
files under the src/Acme/BlogBundle/Resources/config/doctrine folder of your bundle. This
generates two files: BlogPost.orm.xml and BlogComment.orm.xml.
It's also possible to generate the metadata files in YAML format by changing the last argument to
yml.
Once the metadata files are generated, you can ask Doctrine to build related entity classes by executing
the following two commands.
The first command generates entity classes with annotation mappings. But if you want to use YAML or
XML mapping instead of annotations, you should execute the second command only.
If you want to use annotations, you must remove the XML (or YAML) files after running these two
commands. This is necessary as it is not possible to mix mapping configuration formats
For example, the newly created BlogComment entity class looks as follow:
PDF brought to you by Chapter 47: How to Generate Entities from an Existing Database | 173
As you can see, Doctrine converts all table fields to pure private and annotated class properties. The most
impressive thing is that it also discovered the relationship with the BlogPost entity class based on the
foreign key constraint. Consequently, you can find a private $post property mapped with a BlogPost
entity in the BlogComment entity class.
If you want to have a one-to-many relationship, you will need to add it manually into the entity or
to the generated XML or YAML files. Add a section on the specific entities for one-to-many defining
the inversedBy and the mappedBy pieces.
PDF brought to you by Chapter 47: How to Generate Entities from an Existing Database | 174
You can use multiple Doctrine entity managers or connections in a Symfony application. This is
necessary if you are using different databases or even vendors with entirely different sets of entities. In
other words, one entity manager that connects to one database will handle some entities while another
entity manager that connects to another database might handle the rest.
Using multiple entity managers is pretty easy, but more advanced and not usually required. Be sure
you actually need multiple entity managers before adding in this layer of complexity.
The following configuration code shows how you can configure two entity managers:
PDF brought to you by Chapter 48: How to Work with multiple Entity Managers and Connections | 175
In this case, you've defined two entity managers and called them default and customer. The
default entity manager manages entities in the AppBundle and AcmeStoreBundle, while the
customer entity manager manages entities in the AcmeCustomerBundle. You've also defined two
connections, one for each entity manager.
When working with multiple connections and entity managers, you should be explicit about which
configuration you want. If you do omit the name of the connection or entity manager, the default
(i.e. default) is used.
If you do omit the entity manager's name when asking for it, the default entity manager (i.e. default) is
returned:
You can now use Doctrine just as you did before - using the default entity manager to persist and fetch
entities that it manages and the customer entity manager to persist and fetch its entities.
The same applies to repository calls:
PDF brought to you by Chapter 48: How to Work with multiple Entity Managers and Connections | 176
PDF brought to you by Chapter 48: How to Work with multiple Entity Managers and Connections | 177
Doctrine allows you to specify custom DQL functions. For more information on this topic, read
Doctrine's cookbook article "DQL User Defined Functions1".
In Symfony, you can register your custom DQL functions as follows:
1. http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/cookbook/dql-user-defined-functions.html
PDF brought to you by Chapter 49: How to Register custom DQL Functions | 178
One of the goals of bundles is to create discreet bundles of functionality that do not have many (if any)
dependencies, allowing you to use that functionality in other applications without including unnecessary
items.
Doctrine 2.2 includes a new utility called the ResolveTargetEntityListener, that functions by
intercepting certain calls inside Doctrine and rewriting targetEntity parameters in your metadata
mapping at runtime. It means that in your bundle you are able to use an interface or abstract class in your
mappings and expect correct mapping to a concrete entity at runtime.
This functionality allows you to define relationships between different entities without making them hard
dependencies.
Background
Suppose you have an InvoiceBundle which provides invoicing functionality and a CustomerBundle that
contains customer management tools. You want to keep these separated, because they can be used in
other systems without each other, but for your application you want to use them together.
In this case, you have an Invoice entity with a relationship to a non-existent object, an
InvoiceSubjectInterface. The goal is to get the ResolveTargetEntityListener to replace
any mention of the interface with a real object that implements that interface.
Set up
This article uses the following two basic entities (which are incomplete for brevity) to explain how to set
up and use the ResolveTargetEntityListener.
A Customer entity:
Listing 50-1
PDF brought to you by Chapter 50: How to Define Relationships with Abstract Classes and Interfaces | 179
An Invoice entity:
An InvoiceSubjectInterface:
Next, you need to configure the listener, which tells the DoctrineBundle about the replacement:
Listing 50-4
PDF brought to you by Chapter 50: How to Define Relationships with Abstract Classes and Interfaces | 180
Final Thoughts
With the ResolveTargetEntityListener, you are able to decouple your bundles, keeping them
usable by themselves, but still being able to define relationships between different objects. By using this
method, your bundles will end up being easier to maintain independently.
PDF brought to you by Chapter 50: How to Define Relationships with Abstract Classes and Interfaces | 181
When building a bundle that could be used not only with Doctrine ORM but also the CouchDB ODM,
MongoDB ODM or PHPCR ODM, you should still only write one model class. The Doctrine bundles
provide a compiler pass to register the mappings for your model classes.
For non-reusable bundles, the easiest option is to put your model classes in the default locations:
Entity for the Doctrine ORM or Document for one of the ODMs. For reusable bundles, rather
than duplicate model classes just to get the auto-mapping, use the compiler pass.
In your bundle class, write the following code to register the compiler pass. This one is written for the
CmfRoutingBundle, so parts of it will need to be adapted for your case:
PDF brought to you by Chapter 51: How to Provide Model Classes for several Doctrine Implementations | 182
Note the class_exists1 check. This is crucial, as you do not want your bundle to have a hard
dependency on all Doctrine bundles but let the user decide which to use.
The compiler pass provides factory methods for all drivers provided by Doctrine: Annotations, XML,
Yaml, PHP and StaticPHP. The arguments are:
1. http://php.net/manual/en/function.class-exists.php
PDF brought to you by Chapter 51: How to Provide Model Classes for several Doctrine Implementations | 183
Note that you do not need to provide a namespace alias unless your users are expected to ask
Doctrine for the base classes.
Now place your mapping file into /Resources/config/doctrine-base with the fully qualified
class name, separated by . instead of \, for example Other.Namespace.Model.Name.orm.xml.
You may not mix the two as otherwise the SymfonyFileLocator will get confused.
Adjust accordingly for the other Doctrine implementations.
PDF brought to you by Chapter 51: How to Provide Model Classes for several Doctrine Implementations | 184
Creating a registration form is pretty easy - it really means just creating a form that will update some
User model object (a Doctrine entity in this example) and then save it.
The popular FOSUserBundle1 provides a registration form, reset password form and other user
management functionality.
If you don't already have a User entity and a working login system, first start with How to Load Security
Users from the Database (the Entity Provider).
Your User entity will probably at least have the following fields:
username
This will be used for logging in, unless you instead want your user to login via email (in that case,
this field is unnecessary).
email
A nice piece of information to collect. You can also allow users to login via email.
password
The encoded password.
plainPassword
This field is not persisted: (notice no @ORM\Column above it). It temporarily stores the plain password
from the registration form. This field can be validated and is then used to populate the password field.
With some validation added, your class may look something like this:
1. https://github.com/FriendsOfSymfony/FOSUserBundle
PDF brought to you by Chapter 52: How to Implement a Simple Registration Form | 185
PDF brought to you by Chapter 52: How to Implement a Simple Registration Form | 186
The UserInterface2 requires a few other methods and your security.yml file needs to be
configured properly to work with the User entity. For a more complete example, see the Entity Provider
article.
2. http://api.symfony.com/3.1/Symfony/Component/Security/Core/User/UserInterface.html
3. https://symfony.com/blog/cve-2013-5750-security-issue-in-fosuserbundle-login-form
PDF brought to you by Chapter 52: How to Implement a Simple Registration Form | 187
There are just three fields: email, username and plainPassword (repeated to confirm the entered
password).
To explore more things about the Form component, read the chapter about forms in the book.
PDF brought to you by Chapter 52: How to Implement a Simple Registration Form | 188
To define the algorithm used to encode the password in step 3 configure the encoder in the security
configuration:
In this case the recommended bcrypt algorithm is used. To learn more about how to encode the users
password have a look into the security chapter.
If you decide to NOT use annotation routing (shown above), then you'll need to create a route to
this controller:
Listing 52-8
PDF brought to you by Chapter 52: How to Implement a Simple Registration Form | 189
Next, just update the providers section of your security.yml file so that Symfony knows how to
load your users via the email property on login. See Using a Custom Query to Load the User.
The constraints option is also used, which allows us to add validation, even though there is no
termsAccepted property on User.
PDF brought to you by Chapter 52: How to Implement a Simple Registration Form | 190
The default Symfony session storage writes the session information to files. Most medium to large
websites use a database to store the session values instead of files, because databases are easier to use and
scale in a multiple web server environment.
Symfony has a built-in solution for database session storage called PdoSessionHandler1. To use it,
you just need to change some parameters in the main configuration file:
1. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.html
PDF brought to you by Chapter 53: How to Use PdoSessionHandler to Store Sessions in the Database | 191
MySQL
PDF brought to you by Chapter 53: How to Use PdoSessionHandler to Store Sessions in the Database | 192
PostgreSQL
If the session data doesn't fit in the data column, it might get truncated by the database engine. To
make matters worse, when the session data gets corrupted, PHP ignores the data without giving a
warning.
If the application stores large amounts of session data, this problem can be solved by increasing the
column size (use BLOB or even MEDIUMBLOB). When using MySQL as the database engine, you can
also enable the strict SQL mode2 to get noticed when such an error happens.
2. https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html
PDF brought to you by Chapter 53: How to Use PdoSessionHandler to Store Sessions in the Database | 193
The default Symfony session storage writes the session information to files. Some medium to large
websites use a NoSQL database called MongoDB to store the session values instead of files, because
databases are easier to use and scale in a multi-webserver environment.
Symfony has a built-in solution for NoSQL database session storage called
MongoDbSessionHandler1. MongoDB is an open-source document database that provides high
performance, high availability and automatic scaling. This article assumes that you have already installed
and configured a MongoDB server2. To use it, you just need to change/add some parameters in the main
configuration file:
The parameters used above should be defined somewhere in your application, often in your main
parameters configuration:
Listing 54-2
1. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.html
2. http://docs.mongodb.org/manual/installation/
PDF brought to you by Chapter 54: How to Use MongoDbSessionHandler to Store Sessions in a MongoDB Database | 194
3. http://docs.mongodb.org/v2.2/tutorial/getting-started-with-the-mongo-shell/
PDF brought to you by Chapter 54: How to Use MongoDbSessionHandler to Store Sessions in a MongoDB Database | 195
The Doctrine2 ORM integration offers several console commands under the doctrine namespace. To
view the command list you can use the list command:
A list of available commands will print out. You can find out more information about any of these
commands (or any Symfony command) by running the help command. For example, to get details
about the doctrine:database:create task, run:
Sending emails is a classic task for any web application and one that has special complications and
potential pitfalls. Instead of recreating the wheel, one solution to send emails is to use the
SwiftmailerBundle, which leverages the power of the Swift Mailer1 library. This bundle comes with the
Symfony Standard Edition.
Configuration
To use Swift Mailer, you'll need to configure it for your mail server.
Instead of setting up/using your own mail server, you may want to use a hosted mail provider such
as Mandrill2, SendGrid3, Amazon SES4 or others. These give you an SMTP server, username and
password (sometimes called keys) that can be used with the Swift Mailer configuration.
These values (e.g. %mailer_transport%), are reading from the parameters that are set in the
parameters.yml file. You can modify the values in that file, or set the values directly here.
The following configuration attributes are available:
1. http://swiftmailer.org/
2. https://mandrill.com/
3. https://sendgrid.com/
4. http://aws.amazon.com/ses/
• type (how to queue the messages, file or memory is supported, see How to Spool Emails)
• path (where to store the messages)
Sending Emails
The Swift Mailer library works by creating, configuring and then sending Swift_Message objects. The
"mailer" is responsible for the actual delivery of the message and is accessible via the mailer service.
Overall, sending an email is pretty straightforward:
To keep things decoupled, the email body has been stored in a template and rendered with the
renderView() method. The registration.html.twig template might look something like this:
Listing 56-3 1 {# app/Resources/views/Emails/registration.html.twig #}
2 <h3>You did it! You registered!</h3>
3
4 Hi {{ name }}! You're successfully registered.
5
6 {# example, assuming you have a route named "login" #}
7 To login, go to: <a href="{{ url('login') }}">...</a>.
8
9 Thanks!
10
11 {# Makes an absolute URL to the /images/logo.png file #}
12 <img src="{{ absolute_url(asset('images/logo.png')) }}">
Several other cookbook articles are available related to sending emails in Symfony:
5. http://swiftmailer.org/docs/messages.html
During development, instead of using a regular SMTP server to send emails, you might find using Gmail
easier and more practical. The SwiftmailerBundle makes it really easy.
In the development configuration file, change the transport setting to gmail and set the username
and password to the Google credentials:
Option Value
encryption ssl
PDF brought to you by Chapter 57: How to Use Gmail to Send Emails | 200
host smtp.gmail.com
If your application uses tls encryption or oauth authentication, you must override the default options
by defining the encryption and auth_mode parameters.
If your Gmail account uses 2-Step-Verification, you must generate an App password1 and use it as the
value of the mailer_password parameter. You must also ensure that you allow less secure apps to
access your Gmail account2.
See the Swiftmailer configuration reference for more details.
1. https://support.google.com/accounts/answer/185833
2. https://support.google.com/accounts/answer/6010255
PDF brought to you by Chapter 57: How to Use Gmail to Send Emails | 201
Requirements for sending emails from a production system differ from your development setup as you
don't want to be limited in the number of emails, the sending rate or the sender address. Thus, using
Gmail or similar services is not an option. If setting up and maintaining your own reliable mail server
causes you a headache there's a simple solution: Leverage the cloud to send your emails.
This cookbook shows how easy it is to integrate Amazon's Simple Email Service (SES)1 into Symfony.
You can use the same technique for other mail services, as most of the time there is nothing more to
it than configuring an SMTP endpoint for Swift Mailer.
In the Symfony configuration, change the Swift Mailer settings transport, host, port and
encryption according to the information provided in the SES console2. Create your individual SMTP
credentials in the SES console and complete the configuration with the provided username and
password:
Listing 58-1 1 # app/config/config.yml
2 swiftmailer:
3 transport: smtp
4 host: email-smtp.us-east-1.amazonaws.com
5 port: 587 # different ports are available, see SES console
6 encryption: tls # TLS encryption is required
7 username: AWS_SES_SMTP_USERNAME # to be created in the SES console
8 password: AWS_SES_SMTP_PASSWORD # to be created in the SES console
The port and encryption keys are not present in the Symfony Standard Edition configuration by
default, but you can simply add them as needed.
And that's it, you're ready to start sending emails through the cloud!
1. http://aws.amazon.com/ses
2. https://console.aws.amazon.com/ses
PDF brought to you by Chapter 58: How to Use the Cloud to Send Emails | 202
3. http://aws.amazon.com
PDF brought to you by Chapter 58: How to Use the Cloud to Send Emails | 203
When developing an application which sends email, you will often not want to actually send the email
to the specified recipient during development. If you are using the SwiftmailerBundle with Symfony,
you can easily achieve this through configuration settings without having to make any changes to
your application's code at all. There are two main choices when it comes to handling email during
development: (a) disabling the sending of email altogether or (b) sending all email to a specific address
(with optional exceptions).
Disabling Sending
You can disable sending email by setting the disable_delivery option to true. This is the default in
the test environment in the Standard distribution. If you do this in the test specific config then email
will not be sent when you run tests, but will continue to be sent in the prod and dev environments:
If you'd also like to disable deliver in the dev environment, simply add this same configuration to the
config_dev.yml file.
Listing 59-3
PDF brought to you by Chapter 59: How to Work with Emails during Development | 204
In the dev environment, the email will instead be sent to [email protected]. Swift Mailer will add
an extra header to the email, X-Swift-To, containing the replaced address, so you can still see who it
would have been sent to.
In addition to the to addresses, this will also stop the email being sent to any CC and BCC addresses
set for it. Swift Mailer will add additional headers to the email with the overridden addresses in them.
These are X-Swift-Cc and X-Swift-Bcc for the CC and BCC addresses respectively.
In the above example all email messages will be redirected to [email protected] and messages sent
to the [email protected] address or to any email address belonging to the domain
specialdomain.com will also be delivered as normal.
PDF brought to you by Chapter 59: How to Work with Emails during Development | 205
Alternatively, you can open the profiler after the redirect and search by the submit URL used on
the previous request (e.g. /contact/handle). The profiler's search feature allows you to load the
profiler information for any past requests.
PDF brought to you by Chapter 59: How to Work with Emails during Development | 206
When you are using the SwiftmailerBundle to send an email from a Symfony application, it will default
to sending the email immediately. You may, however, want to avoid the performance hit of the
communication between Swift Mailer and the email transport, which could cause the user to wait for
the next page to load while the email is sending. This can be avoided by choosing to "spool" the emails
instead of sending them directly. This means that Swift Mailer does not attempt to send the email but
instead saves the message to somewhere such as a file. Another process can then read from the spool and
take care of sending the emails in the spool. Currently only spooling to file or memory is supported by
Swift Mailer.
If you want to store the spool somewhere with your project directory, remember that you can use
the %kernel.root_dir% parameter to reference the project's root:
Now, when your app sends an email, it will not actually be sent but instead added to the spool. Sending
the messages from the spool is done separately. There is a console command to send the messages in the
spool:
Of course you will not want to run this manually in reality. Instead, the console command should be
triggered by a cron job or scheduled task and run at a regular interval.
Sending emails with Symfony is pretty straightforward thanks to the SwiftmailerBundle, which leverages
the power of the Swift Mailer1 library.
To functionally test that an email was sent, and even assert the email subject, content or any other
headers, you can use the Symfony Profiler.
Start with an easy controller action that sends an email:
Don't forget to enable the profiler as explained in How to Use the Profiler in a Functional Test.
In your functional test, use the swiftmailer collector on the profiler to get information about the
messages sent on the previous request:
1. http://swiftmailer.org/
PDF brought to you by Chapter 61: How to Test that an Email is Sent in a Functional Test | 209
PDF brought to you by Chapter 61: How to Test that an Email is Sent in a Functional Test | 210
During the execution of a Symfony application, lots of event notifications are triggered. Your application
can listen to these notifications and respond to them by executing any piece of code.
Internal events provided by Symfony itself are defined in the KernelEvents1 class. Third-party bundles
and libraries also trigger lots of events and your own application can trigger custom events.
All the examples shown in this article use the same KernelEvents::EXCEPTION event for consistency
purposes. In your own application, you can use any event and even mix several of them in the same
subscriber.
1. http://api.symfony.com/3.1/Symfony/Component/HttpKernel/KernelEvents.html
PDF brought to you by Chapter 62: How to Create Event Listeners and Subscribers | 211
Each event receives a slightly different type of $event object. For the kernel.exception event,
it is GetResponseForExceptionEvent2. To see what type of object each event listener receives,
see KernelEvents3 or the documentation about the specific event you're listening to.
Now that the class is created, you just need to register it as a service and notify Symfony that it is a
"listener" on the kernel.exception event by using a special "tag":
There is an optional tag attribute called method which defines which method to execute when the
event is triggered. By default the name of the method is on + "camel-cased event name". If the event
is kernel.exception the method executed by default is onKernelException().
The other optional tag attribute is called priority, which defaults to 0 and it controls the order
in which listeners are executed (the highest the priority, the earlier a listener is executed). This is
useful when you need to guarantee that one listener is executed before another. The priorities of
the internal Symfony listeners usually range from -255 to 255 but your own listeners can use any
positive or negative integer.
2. http://api.symfony.com/3.1/Symfony/Component/HttpKernel/Event/GetResponseForExceptionEvent.html
3. http://api.symfony.com/3.1/Symfony/Component/HttpKernel/KernelEvents.html
PDF brought to you by Chapter 62: How to Create Event Listeners and Subscribers | 212
Now, you just need to register the class as a service and add the kernel.event_subscriber tag to
tell Symfony that this is an event subscriber:
PDF brought to you by Chapter 62: How to Create Event Listeners and Subscribers | 213
Certain things, like checking information on the real request, may not need to be done on the sub-request
listeners.
Listeners or Subscribers
Listeners and subscribers can be used in the same application indistinctly. The decision to use either of
them is usually a matter of personal taste. However, there are some minor advantages for each of them:
• Subscribers are easier to reuse because the knowledge of the events is kept in the class rather than
in the service definition. This is the reason why Symfony uses subscribers internally;
• Listeners are more flexible because bundles can enable or disable each of them conditionally
depending on some configuration value.
You can get registered listeners for a particular event by specifying its name:
PDF brought to you by Chapter 62: How to Create Event Listeners and Subscribers | 214
It is quite common in web application development to need some logic to be executed just before or just
after your controller actions acting as filters or hooks.
Some web frameworks define methods like preExecute() and postExecute(), but there is no such
thing in Symfony. The good news is that there is a much better way to interfere with the Request ->
Response process using the EventDispatcher component.
Please note that for simplicity in this recipe, tokens will be defined in config and neither database
setup nor authentication via the Security component will be used.
PDF brought to you by Chapter 63: How to Set Up Before and After Filters | 215
PDF brought to you by Chapter 63: How to Set Up Before and After Filters | 216
Now, add another method to this class - onKernelResponse - that looks for this flag on the request
object and sets a custom header on the response if it's found:
Listing 63-7
PDF brought to you by Chapter 63: How to Set Up Before and After Filters | 217
Finally, a second "tag" is needed in the service definition to notify Symfony that the onKernelResponse
event should be notified for the kernel.response event:
That's it! The TokenListener is now notified before every controller is executed
(onKernelController) and after every controller returns a response (onKernelResponse). By
making specific controllers implement the TokenAuthenticatedController interface, your listener
knows which controllers it should take action on. And by storing a value in the request's "attributes" bag,
the onKernelResponse method knows to add the extra header. Have fun!
PDF brought to you by Chapter 63: How to Set Up Before and After Filters | 218
To allow multiple classes to add methods to another one, you can define the magic __call() method
in the class you want to be extended like this:
This uses a special HandleUndefinedMethodEvent that should also be created. This is a generic class
that could be reused each time you need to use this pattern of class extension:
PDF brought to you by Chapter 64: How to Extend a Class without Using Inheritance | 219
Next, create a class that will listen to the foo.method_is_not_found event and add the method
bar():
Listing 64-3 1 class Bar
2 {
3 public function onFooMethodIsNotFound(HandleUndefinedMethodEvent $event)
4 {
5 // only respond to the calls to the 'bar' method
6 if ('bar' != $event->getMethod()) {
7 // allow another listener to take care of this unknown method
8 return;
9 }
10
11 // the subject object (the foo instance)
12 $foo = $event->getSubject();
13
14 // the bar method arguments
15 $arguments = $event->getArguments();
16
17 // ... do something
18
19 // set the return value
20 $event->setReturnValue($someValue);
21 }
22 }
PDF brought to you by Chapter 64: How to Extend a Class without Using Inheritance | 220
PDF brought to you by Chapter 64: How to Extend a Class without Using Inheritance | 221
In this example, two events are thrown: foo.pre_send, before the method is executed, and
foo.post_send after the method is executed. Each uses a custom Event class to communicate
information to the listeners of the two events. These event classes would need to be created by you and
should allow, in this example, the variables $foo, $bar and $ret to be retrieved and set by the listeners.
PDF brought to you by Chapter 65: How to Customize a Method Behavior without Using Inheritance | 222
PDF brought to you by Chapter 65: How to Customize a Method Behavior without Using Inheritance | 223
Symfony comes with a powerful ExpressionLanguage component. It allows you to add highly customized
logic inside configuration.
The Symfony Framework leverages expressions out of the box in the following ways:
• Configuring services;
• Route matching conditions;
• Checking security (explained below) and access controls with allow_if;
• Validation.
For more information about how to create and work with expressions, see The Expression Syntax.
In this example, if the current user has ROLE_ADMIN or if the current user object's isSuperAdmin()
method returns true, then access will be granted (note: your User object may not have an
isSuperAdmin method, that method is invented for this example).
1. http://api.symfony.com/3.1/Symfony/Component/ExpressionLanguage/Expression.html
PDF brought to you by Chapter 66: How to use Expressions in Security, Routing, Services, and Validation | 224
roles
The array of roles the user has, including from the role hierarchy but not including the
IS_AUTHENTICATED_* attributes (see the functions below).
object
The object (if any) that's passed as the second argument to isGranted.
token
The token object.
trust_resolver
The AuthenticationTrustResolverInterface2, object: you'll probably use the is_* functions below instead.
is_anonymous
Equal to using IS_AUTHENTICATED_ANONYMOUSLY with the isGranted function.
is_remember_me
Similar, but not equal to IS_AUTHENTICATED_REMEMBERED, see below.
is_fully_authenticated
Similar, but not equal to IS_AUTHENTICATED_FULLY, see below.
has_role
Checks to see if the user has the given role - equivalent to an expression like 'ROLE_ADMIN' in roles.
2. http://api.symfony.com/3.1/Symfony/Component/Security/Core/Authentication/AuthenticationTrustResolverInterface.html
PDF brought to you by Chapter 66: How to use Expressions in Security, Routing, Services, and Validation | 225
Here, $access1 and $access2 will be the same value. Unlike the behavior of
IS_AUTHENTICATED_REMEMBERED and IS_AUTHENTICATED_FULLY, the is_remember_me
function only returns true if the user is authenticated via a remember-me cookie and
is_fully_authenticated only returns true if the user has actually logged in during this session
(i.e. is full-fledged).
PDF brought to you by Chapter 66: How to use Expressions in Security, Routing, Services, and Validation | 226
Symfony gives you a wide variety of ways to customize how a form is rendered. In this guide, you'll learn
how to customize every possible part of your form with as little effort as possible whether you use Twig
or PHP as your templating engine.
You can also render each of the three parts of the field individually:
In both cases, the form label, errors and HTML widget are rendered by using a set of markup that ships
standard with Symfony. For example, both of the above templates would render:
To quickly prototype and test a form, you can render the entire form with just one line:
PDF brought to you by Chapter 67: How to Customize Form Rendering | 227
The remainder of this recipe will explain how every part of the form's markup can be modified at
several different levels. For more information about form rendering in general, see Rendering a Form in a
Template.
When you use the Bootstrap form themes and render the fields manually, calling form_label()
for a checkbox/radio field doesn't show anything. Due to Bootstrap internals, the label is already
shown by form_widget().
In the next section you will learn how to customize a theme by overriding some or all of its fragments.
For example, when the widget of an integer type field is rendered, an input number field is generated
renders:
Internally, Symfony uses the integer_widget fragment to render the field. This is because the field
type is integer and you're rendering its widget (as opposed to its label or errors).
1. https://github.com/symfony/symfony/blob/master/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig
2. https://github.com/symfony/symfony/blob/master/src/Symfony/Bridge/Twig/Resources/views/Form/form_table_layout.html.twig
3. https://github.com/symfony/symfony/blob/master/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig
4. http://getbootstrap.com/
5. https://github.com/symfony/symfony/blob/master/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_horizontal_layout.html.twig
6. https://github.com/symfony/symfony/blob/master/src/Symfony/Bridge/Twig/Resources/views/Form/foundation_5_layout.html.twig
7. http://foundation.zurb.com/
PDF brought to you by Chapter 67: How to Customize Form Rendering | 228
As you can see, this fragment itself renders another fragment - form_widget_simple:
The point is, the fragments dictate the HTML output of each part of a form. To customize the form
output, you just need to identify and override the correct fragment. A set of these form fragment
customizations is known as a form "theme". When rendering a form, you can choose which form
theme(s) you want to apply.
In Twig a theme is a single template file and the fragments are the blocks defined in this file.
In PHP a theme is a folder and the fragments are individual template files in this folder.
Form Theming
To see the power of form theming, suppose you want to wrap every input number field with a div tag.
The key to doing this is to customize the integer_widget fragment.
8. https://github.com/symfony/symfony/blob/master/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig
PDF brought to you by Chapter 67: How to Customize Form Rendering | 229
Both methods have the same effect but are better in different situations.
By using the special {% form_theme form _self %} tag, Twig looks inside the same template for
any overridden form blocks. Assuming the form.age field is an integer type field, when its widget is
rendered, the customized integer_widget block will be used.
The disadvantage of this method is that the customized form block can't be reused when rendering other
forms in other templates. In other words, this method is most useful when making form customizations
that are specific to a single form in your application. If you want to reuse a form customization across
several (or all) forms in your application, read on to the next section.
PDF brought to you by Chapter 67: How to Customize Form Rendering | 230
When the form.age widget is rendered, Symfony will use the integer_widget block from the new
template and the input tag will be wrapped in the div element specified in the customized block.
Multiple Templates
A form can also be customized by applying several templates. To do this, pass the name of all the
templates as an array using the with keyword:
The templates can also be located in different bundles, use the functional name to reference these
templates, e.g. AcmeFormExtraBundle:form:fields.html.twig.
Child Forms
You can also apply a form theme to a specific child of your form:
This is useful when you want to have a custom theme for a nested form that's different than the one of
your main form. Just specify both your themes:
Now that you've created the customized form template, you need to tell Symfony to use it. Inside the
template where you're actually rendering your form, tell Symfony to use the theme via the setTheme
helper method:
PDF brought to you by Chapter 67: How to Customize Form Rendering | 231
When the form.age widget is rendered, Symfony will use the customized
integer_widget.html.php template and the input tag will be wrapped in the div element.
If you want to apply a theme to a specific child form, pass it to the setTheme method:
The :form syntax is based on the functional names for templates: Bundle:Directory. As the
form directory lives in the app/Resources/views directory, the Bundle part is empty, resulting
in :form.
Now, when the blocks from form_div_layout.html.twig10 are imported, the integer_widget block is
called base_integer_widget. This means that when you redefine the integer_widget block, you
can reference the default markup via base_integer_widget:
9. https://github.com/symfony/symfony/blob/master/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig
10. https://github.com/symfony/symfony/blob/master/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig
PDF brought to you by Chapter 67: How to Customize Form Rendering | 232
It is not possible to reference the base block when using PHP as the templating engine. You have to
manually copy the content from the base block to your new template file.
Twig
By using the
following configuration, any customized form blocks inside the form/
fields.html.twig template will be used globally when a form is rendered.
Listing 67-21 1 # app/config/config.yml
2 twig:
3 form_themes:
4 - 'form/fields.html.twig'
5 # ...
By default, Twig uses a div layout when rendering forms. Some people, however, may prefer to render
forms in a table layout. Use the form_table_layout.html.twig resource to use such a layout:
If you only want to make the change in one template, add the following line to your template file rather
than adding the template as a resource:
Note that the form variable in the above code is the form view variable that you passed to your template.
PHP
By using the following configuration, any customized form fragments inside the app/Resources/
views/Form folder will be used globally when a form is rendered.
Listing 67-24 1 # app/config/config.yml
2 framework:
3 templating:
4 form:
5 resources:
6 - 'AppBundle:Form'
7 # ...
By default, the PHP engine uses a div layout when rendering forms. Some people, however, may prefer to
render forms in a table layout. Use the FrameworkBundle:FormTable resource to use such a layout:
PDF brought to you by Chapter 67: How to Customize Form Rendering | 233
If you only want to make the change in one template, add the following line to your template file rather
than adding the template as a resource:
Note that the $form variable in the above code is the form view variable that you passed to your
template.
Here, the _product_name_widget fragment defines the template to use for the field whose id is
product_name (and name is product[name]).
The product portion of the field is the form name, which may be set manually or generated
automatically based on your form type name (e.g. ProductType equates to product). If you're
not sure what your form name is, just view the source of your generated form.
If you want to change the product or name portion of the block name _product_name_widget
you can set the block_name option in your form type:
You can also override the markup for an entire field row using the same method:
PDF brought to you by Chapter 67: How to Customize Form Rendering | 234
Not only can you override the rendered widget, but you can also change the complete form row or the
label as well. For the tasks field given above, the block names would be the following:
widget _tasks_entry_widget
row _tasks_entry_row
The Form component only handles how the validation errors are rendered, and not the actual
validation error messages. The error messages themselves are determined by the validation
constraints you apply to your objects. For more information, see the chapter on validation.
There are many different ways to customize how errors are rendered when a form is submitted with
errors. The error messages for a field are rendered when you use the form_errors helper:
PDF brought to you by Chapter 67: How to Customize Form Rendering | 235
To override how errors are rendered for all fields, simply copy, paste and customize the form_errors
fragment.
You can also customize the error output for just one specific field type. To customize only the markup
used for these errors, follow the same directions as above but put the contents in a relative _errors
block (or file in case of PHP templates). For example: text_errors (or text_errors.html.php).
See Form Fragment Naming to find out which specific block or file you have to customize.
Certain errors that are more global to your form (i.e. not specific to just one field) are rendered separately,
usually at the top of your form:
To customize only the markup used for these errors, follow the same directions as above, but now check
if the compound variable is set to true. If it is true, it means that what's being currently rendered is a
collection of fields (e.g. a whole form), and not just an individual field.
PDF brought to you by Chapter 67: How to Customize Form Rendering | 236
In Twig, if you're making the form customization inside a separate template, use the following:
When using PHP as a templating engine you have to copy the content from the original template:
PDF brought to you by Chapter 67: How to Customize Form Rendering | 237
In Twig, if you're making the form customization inside a separate template, use the following:
When using PHP as a templating engine you have to copy the content from the original template:
PDF brought to you by Chapter 67: How to Customize Form Rendering | 238
The array passed as the second argument contains form "variables". For more details about this concept
in Twig, see More about Form Variables.
PDF brought to you by Chapter 67: How to Customize Form Rendering | 239
Data transformers are used to translate the data for a field into a format that can be displayed in a form
(and back on submit). They're already used internally for many field types. For example, the DateType
field can be rendered as a yyyy-MM-dd-formatted input textbox. Internally, a data transformer converts
the starting DateTime value of the field into the yyyy-MM-dd string to render the form, and then back
into a DateTime object on submit.
When a form field has the inherit_data option set, Data Transformers won't be applied to that
field.
PDF brought to you by Chapter 68: How to Use Data Transformers | 240
Internally the tags are stored as an array, but displayed to the user as a simple comma seperated string
to make them easier to edit.
This is a perfect time to attach a custom data transformer to the tags field. The easiest way to do this is
with the CallbackTransformer1 class:
The CallbackTransformer takes two callback functions as arguments. The first transforms the
original value into a format that'll be used to render the field. The second does the reverse: it transforms
the submitted value back into the format you'll use in your code.
You can also add the transformer, right when adding the field by changing the format slightly:
1. http://api.symfony.com/3.1/Symfony/Component/Form/CallbackTransformer.html
2. http://api.symfony.com/3.1/Symfony/Component/Form/DataTransformerInterface.html
PDF brought to you by Chapter 68: How to Use Data Transformers | 241
Good start! But if you stopped here and submitted the form, the Task's issue property would be a string
(e.g. "55"). How can you transform this into an Issue entity on submit?
PDF brought to you by Chapter 68: How to Use Data Transformers | 242
Just like in the first example, a transformer has two directions. The transform() method is responsible
for converting the data used in your code to a format that can be rendered in your form (e.g. an
Issue object to its id, a string). The reverseTransform() method does the reverse: it converts the
submitted value back into the format you want (e.g. convert the id back to the Issue object).
To cause a validation error, throw a TransformationFailedException3. But the message you pass
to this exception won't be shown to the user. You'll set that message with the invalid_message option
(see below).
When null is passed to the transform() method, your transformer should return an equivalent
value of the type it is transforming to (e.g. an empty string, 0 for integers or 0.0 for floats).
3. http://api.symfony.com/3.1/Symfony/Component/Form/Exception/TransformationFailedException.html
PDF brought to you by Chapter 68: How to Use Data Transformers | 243
For more information about defining form types as services, read register your form type as a service.
Listing 68-8
// e.g. in a controller somewhere
$form = $this->createForm(TaskType::class, $task);
// ...
PDF brought to you by Chapter 68: How to Use Data Transformers | 244
Be careful when adding your transformers. For example, the following is wrong, as the transformer
would be applied to the entire form, instead of just this field:
Listing 68-9
// THIS IS WRONG - TRANSFORMER WILL BE APPLIED TO THE ENTIRE FORM
// see above example for correct code
$builder->add('issue', TextType::class)
->addModelTransformer($transformer);
Great! This will act and render like a text field (getParent()), but will automatically have the data
transformer and a nice default value for the invalid_message option.
Next, register your type as a service and tag it with form.type so that it's recognized as a custom field
type:
PDF brought to you by Chapter 68: How to Use Data Transformers | 245
Now, whenever you need to use your special issue_selector field type, it's quite easy:
PDF brought to you by Chapter 68: How to Use Data Transformers | 246
View transformers:
As a general rule, the normalized data should contain as much information as possible.
PDF brought to you by Chapter 68: How to Use Data Transformers | 247
Often times, a form can't be created statically. In this entry, you'll learn how to customize your form
based on three common use-cases:
1. Customizing your Form Based on the Underlying Data
Example: you have a "Product" form and need to modify/add/remove a field
based on the data on the underlying Product being edited.
PDF brought to you by Chapter 69: How to Dynamically Modify Forms Using Form Events | 248
If this particular section of code isn't already familiar to you, you probably need to take a step back
and first review the Forms chapter before proceeding.
Assume for a moment that this form utilizes an imaginary "Product" class that has only two properties
("name" and "price"). The form generated from this class will look the exact same regardless if a new
Product is being created or if an existing product is being edited (e.g. a product fetched from the
database).
Suppose now, that you don't want the user to be able to change the name value once the object has been
created. To do this, you can rely on Symfony's EventDispatcher component system to analyze the data on
the object and modify the form based on the Product object's data. In this entry, you'll learn how to add
this level of flexibility to your forms.
The goal is to create a name field only if the underlying Product object is new (e.g. hasn't been persisted
to the database). Based on that, the event listener might look like the following:
PDF brought to you by Chapter 69: How to Dynamically Modify Forms Using Form Events | 249
Now the logic for creating the name field resides in it own subscriber class:
1. http://api.symfony.com/3.1/Symfony/Component/Form/FormEvents.html
2. http://api.symfony.com/3.1/Symfony/Component/Form/FormEvents.html
PDF brought to you by Chapter 69: How to Dynamically Modify Forms Using Form Events | 250
The problem is now to get the current user and create a choice field that contains only this user's friends.
Luckily it is pretty easy to inject a service inside of the form. This can be done in the constructor:
You might wonder, now that you have access to the User (through the token storage), why not just
use it directly in buildForm and omit the event listener? This is because doing so in the buildForm
method would result in the whole form type being modified and not just this one form instance. This
may not usually be a problem, but technically a single form type could be used on a single request to
create many forms or fields.
PDF brought to you by Chapter 69: How to Dynamically Modify Forms Using Form Events | 251
PDF brought to you by Chapter 69: How to Dynamically Modify Forms Using Form Events | 252
In a controller that extends the Controller3 class, you can simply call:
You can also easily embed the form type into another form:
3. http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/Controller/Controller.html
PDF brought to you by Chapter 69: How to Dynamically Modify Forms Using Form Events | 253
When you're building this form to display to the user for the first time, then this example works perfectly.
However, things get more difficult when you handle the form submission. This is because the
PRE_SET_DATA event tells us the data that you're starting with (e.g. an empty SportMeetup object),
not the submitted data.
On a form, we can usually listen to the following events:
• PRE_SET_DATA
• POST_SET_DATA
• PRE_SUBMIT
• SUBMIT
• POST_SUBMIT
The key is to add a POST_SUBMIT listener to the field that your new field depends on. If you add a
POST_SUBMIT listener to a form child (e.g. sport), and add new children to the parent form, the Form
component will detect the new field automatically and map it to the submitted client data.
The type would now look like:
PDF brought to you by Chapter 69: How to Dynamically Modify Forms Using Form Events | 254
You can see that you need to listen on these two events and have different callbacks only because in two
different scenarios, the data that you can use is available in different events. Other than that, the listeners
always perform exactly the same things on a given form.
One piece that is still missing is the client-side updating of your form after the sport is selected. This
should be handled by making an AJAX call back to your application. Assume that you have a sport
meetup creation controller:
PDF brought to you by Chapter 69: How to Dynamically Modify Forms Using Form Events | 255
The associated template uses some JavaScript to update the position form field according to the
current selection in the sport field:
The major benefit of submitting the whole form to just extract the updated position field is that no
additional server-side code is needed; all the code from above to generate the submitted form can be
reused.
4. http://api.symfony.com/3.1/Symfony/Component/Form/Extension/Validator/EventListener/ValidationListener.html
PDF brought to you by Chapter 69: How to Dynamically Modify Forms Using Form Events | 256
By doing this, you may accidentally disable something more than just form validation, since the
POST_SUBMIT event may have other listeners.
PDF brought to you by Chapter 69: How to Dynamically Modify Forms Using Form Events | 257
In this entry, you'll learn how to create a form that embeds a collection of many other forms. This could
be useful, for example, if you had a Task class and you wanted to edit/create/remove many Tag objects
related to that Task, right inside the same form.
In this entry, it's loosely assumed that you're using Doctrine as your database store. But if you're not
using Doctrine (e.g. Propel or just a database connection), it's all very similar. There are only a few
parts of this tutorial that really care about "persistence".
If you are using Doctrine, you'll need to add the Doctrine metadata, including the ManyToMany
association mapping definition on the Task's tags property.
First, suppose that each Task belongs to multiple Tag objects. Start by creating a simple Task class:
PDF brought to you by Chapter 70: How to Embed a Collection of Forms | 258
The ArrayCollection is specific to Doctrine and is basically the same as using an array (but it
must be an ArrayCollection if you're using Doctrine).
Now, create a Tag class. As you saw above, a Task can have many Tag objects:
Then, create a form class so that a Tag object can be modified by the user:
With this, you have enough to render a tag form by itself. But since the end goal is to allow the tags of a
Task to be modified right inside the task form itself, create a form for the Task class.
Notice that you embed a collection of TagType forms using the CollectionType field:
PDF brought to you by Chapter 70: How to Embed a Collection of Forms | 259
The corresponding template is now able to render both the description field for the task form as
well as all the TagType forms for any tags that are already related to this Task. In the above controller,
I added some dummy code so that you can see this in action (since a Task has zero tags when first
created).
Listing 70-6
PDF brought to you by Chapter 70: How to Embed a Collection of Forms | 260
When the user submits the form, the submitted data for the tags field are used to construct an
ArrayCollection of Tag objects, which is then set on the tag field of the Task instance.
The tags collection is accessible naturally via $task->getTags() and can be persisted to the database
or used however you need.
So far, this works great, but this doesn't allow you to dynamically add new tags or delete existing tags.
So, while editing existing tags will work great, your user can't actually add any new tags yet.
In this entry, you embed only one collection, but you are not limited to this. You can also embed
nested collection as many levels down as you like. But if you use Xdebug in your development setup,
you may receive a Maximum function nesting level of '100' reached, aborting!
error. This is due to the xdebug.max_nesting_level PHP setting, which defaults to 100.
This directive limits recursion to 100 calls which may not be enough for rendering the form in the
template if you render the whole form at once (e.g form_widget(form)). To fix this you can set
this directive to a higher value (either via a php.ini file or via ini_set1, for example in app/
autoload.php) or render each form field by hand using form_row.
1. http://php.net/manual/en/function.ini-set.php
PDF brought to you by Chapter 70: How to Embed a Collection of Forms | 261
In addition to telling the field to accept any number of submitted objects, the allow_add also makes a
"prototype" variable available to you. This "prototype" is a little "template" that contains all the HTML
to be able to render any new "tag" forms. To render it, make the following change to your template:
If you render your whole "tags" sub-form at once (e.g. form_row(form.tags)), then the
prototype is automatically available on the outer div as the data-prototype attribute, similar to
what you see above.
The form.tags.vars.prototype is a form element that looks and feels just like the individual
form_widget(tag) elements inside your for loop. This means that you can call form_widget,
form_row or form_label on it. You could even choose to render only one of its fields (e.g. the
name field):
On the rendered page, the result will look something like this:
The goal of this section will be to use JavaScript to read this attribute and dynamically add new tag forms
when the user clicks a "Add a tag" link. To make things simple, this example uses jQuery and assumes
you have it included somewhere on your page.
Add a script tag somewhere on your page so you can start writing some JavaScript.
First, add a link to the bottom of the "tags" list via JavaScript. Second, bind to the "click" event of that
link so you can add a new tag form (addTagForm will be show next):
PDF brought to you by Chapter 70: How to Embed a Collection of Forms | 262
The addTagForm function's job will be to use the data-prototype attribute to dynamically add
a new form when this link is clicked. The data-prototype HTML contains the tag text input
element with a name of task[tags][__name__][name] and id of task_tags___name___name.
The __name__ is a little "placeholder", which you'll replace with a unique, incrementing number (e.g.
task[tags][3][name]).
The actual code needed to make this all work can vary quite a bit, but here's one example:
It is better to separate your JavaScript in real JavaScript files than to write it inside the HTML as is
done here.
Now, each time a user clicks the Add a tag link, a new sub form will appear on the page. When the
form is submitted, any new tag forms will be converted into new Tag objects and added to the tags
property of the Task object.
If you want to customize the HTML code in the prototype, read How to Customize a Collection Prototype.
To make handling these new tags easier, add an "adder" and a "remover" method for the tags in the Task
class:
2. http://jsfiddle.net/847Kf/4/
PDF brought to you by Chapter 70: How to Embed a Collection of Forms | 263
Next, add a by_reference option to the tags field and set it to false:
With these two changes, when the form is submitted, each new Tag object is added to the Task class
by calling the addTag method. Before this change, they were added internally by the form by calling
$task->getTags()->add($tag). That was just fine, but forcing the use of the "adder" method
makes handling these new Tag objects easier (especially if you're using Doctrine, which you will learn
about next!).
You have to create both addTag and removeTag methods, otherwise the form will still use
setTag even if by_reference is false. You'll learn more about the removeTag method later in
this article.
PDF brought to you by Chapter 70: How to Embed a Collection of Forms | 264
A new entity was found through the relationship AppBundle\Entity\Task#tags that was not
configured to cascade persist operations for entity...
To fix this, you may choose to "cascade" the persist operation automatically from the Task object to
any related tags. To do this, add the cascade option to your ManyToMany metadata:
A second potential issue deals with the Owning Side and Inverse Side3 of Doctrine relationships. In
this example, if the "owning" side of the relationship is "Task", then persistence will work fine as the
tags are properly added to the Task. However, if the owning side is on "Tag", then you'll need to do
a little bit more work to ensure that the correct side of the relationship is modified.
The trick is to make sure that the single "Task" is set on each "Tag". One easy way to do this is to
add some extra logic to addTag(), which is called by the form type since by_reference is set to
false:
If you have a one-to-many relationship, then the workaround is similar, except that you can simply
call setTask from inside addTag.
3. http://docs.doctrine-project.org/en/latest/reference/unitofwork-associations.html
PDF brought to you by Chapter 70: How to Embed a Collection of Forms | 265
Now, you need to put some code into the removeTag method of Task:
Template Modifications
The allow_delete option has one consequence: if an item of a collection isn't sent on submission, the
related data is removed from the collection on the server. The solution is thus to remove the form element
from the DOM.
First, add a "delete this tag" link to each tag form:
Listing 70-21
PDF brought to you by Chapter 70: How to Embed a Collection of Forms | 266
When a tag form is removed from the DOM and submitted, the removed Tag object will not be included
in the collection passed to setTags. Depending on your persistence layer, this may or may not be
enough to actually remove the relationship between the removed Tag and Task object.
PDF brought to you by Chapter 70: How to Embed a Collection of Forms | 267
As you can see, adding and removing the elements correctly can be tricky. Unless you have a many-
to-many relationship where Task is the "owning" side, you'll need to do extra work to make sure that
PDF brought to you by Chapter 70: How to Embed a Collection of Forms | 268
PDF brought to you by Chapter 70: How to Embed a Collection of Forms | 269
Symfony comes with a bunch of core field types available for building forms. However there are situations
where you may want to create a custom form field type for a specific purpose. This recipe assumes
you need a field definition that holds a person's gender, based on the existing choice field. This section
explains how the field is defined, how you can customize its layout and finally, how you can register it
for use in your application.
1. http://api.symfony.com/3.1/Symfony/Component/Form/AbstractType.html
PDF brought to you by Chapter 71: How to Create a Custom Form Field Type | 270
The location of this file is not important - the Form\Type directory is just a convention.
Here, the return value of the getParent function indicates that you're extending the ChoiceType field.
This means that, by default, you inherit all of the logic and rendering of that field type. To see some of
the logic, check out the ChoiceType2 class. There are three methods that are particularly important:
buildForm()
Each field type has a buildForm method, which is where you configure and build any field(s). Notice
that this is the same method you use to setup your forms, and it works the same here.
buildView()
This method is used to set any extra variables you'll need when rendering your field in a template.
For example, in ChoiceType3, a multiple variable is set and used in the template to set (or not set) the
multiple attribute on the select field. See Creating a Template for the Field for more details.
configureOptions()
This defines options for your form type that can be used in buildForm() and buildView(). There are a lot
of options common to all fields (see FormType Field), but you can create any others that you need
here.
If you're creating a field that consists of many fields, then be sure to set your "parent" type as form
or something that extends form. Also, if you need to modify the "view" of any of your child types
from your parent type, use the finishView() method.
The goal of this field was to extend the choice type to enable selection of a gender. This is achieved by
fixing the choices to a list of possible genders.
The first part of the prefix (e.g. gender) comes from the class name (GenderType -> gender).
This can be controlled by overriding getBlockPrefix() in GenderType.
When the name of your form class matches any of the built-in field types, your form might
not be rendered correctly. A form type named AppBundle\Form\PasswordType will have the
same block name as the built-in PasswordType and won't be rendered correctly. Override the
getBlockPrefix() method to return a unique block prefix (e.g. app_password) to avoid
collisions.
In this case, since the parent field is ChoiceType, you don't need to do any work as the custom field
type will automatically be rendered like a ChoiceType. But for the sake of this example, suppose that
2. https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php
3. https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php
PDF brought to you by Chapter 71: How to Create a Custom Form Field Type | 271
Make sure the correct widget prefix is used. In this example the name should be gender_widget
(see What are Form Themes?). Further, the main config file should point to the custom form
template so that it's used when rendering all forms.
When using Twig this is:
For the PHP templating engine, your configuration should look like this:
PDF brought to you by Chapter 71: How to Create a Custom Form Field Type | 272
But this only works because the GenderType is very simple. What if the gender codes were stored
in configuration or in a database? The next section explains how more complex field types solve this
problem.
To use the parameter, define your custom field type as a service, injecting the genders parameter value
as the first argument to its to-be-created __construct function:
Make sure the services file is being imported. See Importing Configuration with imports for details.
First, add a __construct method to GenderType, which receives the gender configuration:
PDF brought to you by Chapter 71: How to Create a Custom Form Field Type | 273
Great! The GenderType is now fueled by the configuration parameters and registered as a service.
Because you used the form.type alias in its configuration, your service will be used instead of creating
a new GenderType. In other words, your controller does not need to change, it still looks like this:
Have fun!
PDF brought to you by Chapter 71: How to Create a Custom Form Field Type | 274
Custom form field types are great when you need field types with a specific purpose, such as a gender
selector, or a VAT number input.
But sometimes, you don't really need to add new field types - you want to add features on top of existing
types. This is where form type extensions come in.
Form type extensions have 2 main use-cases:
1. You want to add a specific feature to a single type (such as adding a "download" feature to
the FileType field type);
2. You want to add a generic feature to several types (such as adding a "help" text to every
"input text"-like type).
It might be possible to achieve your goal with custom form rendering or custom form field types. But
using form type extensions can be cleaner (by limiting the amount of business logic in templates) and
more flexible (you can add several type extensions to a single form type).
Form type extensions can achieve most of what custom field types can do, but instead of being field types
of their own, they plug into existing types.
Imagine that you manage a Media entity, and that each media is associated to a file. Your Media form
uses a file type, but when editing the entity, you would like to see its image automatically rendered next
to the file input.
You could of course do this by customizing how this field is rendered in a template. But field type
extensions allow you to do this in a nice DRY fashion.
PDF brought to you by Chapter 72: How to Create a Form Type Extension | 275
The only method you must implement is the getExtendedType function. It is used to indicate the
name of the form type that will be extended by your extension.
The value you return in the getExtendedType method corresponds to the fully qualified class
name of the form type class you wish to extend.
In addition to the getExtendedType function, you will probably want to override one of the following
methods:
• buildForm()
• buildView()
• configureOptions()
• finishView()
For more information on what those methods do, you can refer to the Creating Custom Field Types
cookbook article.
The extended_type key of the tag is the type of field that this extension should be applied to. In your
case, as you want to extend the Symfony\Component\Form\Extension\Core\Type\FileType
field type, you will use that as the extended_type.
1. http://api.symfony.com/3.1/Symfony/Component/Form/FormTypeExtensionInterface.html
2. http://api.symfony.com/3.1/Symfony/Component/Form/AbstractTypeExtension.html
PDF brought to you by Chapter 72: How to Create a Form Type Extension | 276
Your form type extension class will need to do two things in order to extend the FileType::class
form type:
1. Override the configureOptions method in order to add an image_path option;
2. Override the buildView methods in order to pass the image URL to the view.
The logic is the following: when adding a form field of type FileType::class, you will be able to
specify a new option: image_path. This option will tell the file field how to get the actual image URL
in order to display it in the view:
PDF brought to you by Chapter 72: How to Create a Form Type Extension | 277
You will need to change your config file or explicitly specify how you want your form to be themed in
order for Symfony to use your overridden block. See What are Form Themes? for more information.
PDF brought to you by Chapter 72: How to Create a Form Type Extension | 278
When displaying the form, if the underlying model has already been associated with an image, you will
see it displayed next to the file input.
PDF brought to you by Chapter 72: How to Create a Form Type Extension | 279
The inherit_data form field option can be very useful when you have some duplicated fields in
different entities. For example, imagine you have two entities, a Company and a Customer:
As you can see, each entity shares a few of the same fields: address, zipcode, city, country.
Start with building two forms for these entities, CompanyType and CustomerType:
Listing 73-3
PDF brought to you by Chapter 73: How to Reduce Code Duplication with "inherit_data" | 280
Instead of including the duplicated fields address, zipcode, city and country in both of these
forms, create a third form called LocationType for that:
The location form has an interesting option set, namely inherit_data. This option lets the form
inherit its data from its parent form. If embedded in the company form, the fields of the location form
PDF brought to you by Chapter 73: How to Reduce Code Duplication with "inherit_data" | 281
Instead of setting the inherit_data option inside LocationType, you can also (just like with
any option) pass it in the third argument of $builder->add().
Finally, make this work by adding the location form to your two original forms:
That's it! You have extracted duplicated field definitions to a separate location form that you can reuse
wherever you need it.
Forms with the inherit_data option set cannot have *_SET_DATA event listeners.
PDF brought to you by Chapter 73: How to Reduce Code Duplication with "inherit_data" | 282
The Form component consists of 3 core objects: a form type (implementing FormTypeInterface1),
the Form2 and the FormView3.
The only class that is usually manipulated by programmers is the form type class which serves as a form
blueprint. It is used to generate the Form and the FormView. You could test it directly by mocking its
interactions with the factory but it would be complex. It is better to pass it to FormFactory like it is done
in a real application. It is simple to bootstrap and you can trust the Symfony components enough to use
them as a testing base.
There is already a class that you can benefit from for simple FormTypes testing: TypeTestCase4. It is
used to test the core types and you can use it to test your types too.
Depending on the way you installed your Symfony or Symfony Form component the tests may not
be downloaded. Use the --prefer-source option with Composer if this is the case.
The Basics
The simplest TypeTestCase implementation looks like the following:
1. http://api.symfony.com/3.1/Symfony/Component/Form/FormTypeInterface.html
2. http://api.symfony.com/3.1/Symfony/Component/Form/Form.html
3. http://api.symfony.com/3.1/Symfony/Component/Form/FormView.html
4. http://api.symfony.com/3.1/Symfony/Component/Form/Test/TypeTestCase.html
PDF brought to you by Chapter 74: How to Unit Test your Forms | 283
Listing 74-2
$form = $this->factory->create(TestedType::class);
This test checks that none of your data transformers used by the form failed. The isSynchronized()5
method is only set to false if a data transformer throws an exception:
Listing 74-3
$form->submit($formData);
$this->assertTrue($form->isSynchronized());
Don't test the validation: it is applied by a listener that is not active in the test case and it relies on
validation configuration. Instead, unit test your custom constraints directly.
Next, verify the submission and mapping of the form. The test below checks if all the fields are correctly
specified:
Listing 74-4
$this->assertEquals($object, $form->getData());
Finally, check the creation of the FormView. You should check if all widgets you want to display are
available in the children property:
5. http://api.symfony.com/3.1/Symfony/Component/Form/FormInterface.html#method_isSynchronized
PDF brought to you by Chapter 74: How to Unit Test your Forms | 284
6. http://api.symfony.com/3.1/Symfony/Component/Form/PreloadedExtension.html
7. http://api.symfony.com/3.1/Symfony/Component/OptionsResolver/Exception/InvalidOptionsException.html
8. http://api.symfony.com/3.1/Symfony/Component/Form/Test/TypeTestCase.html#method_getExtensions
PDF brought to you by Chapter 74: How to Unit Test your Forms | 285
9. https://phpunit.de/manual/current/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.data-providers
PDF brought to you by Chapter 74: How to Unit Test your Forms | 286
The code above will run your test three times with 3 different sets of data. This allows for decoupling the
test fixtures from the tests and easily testing against multiple sets of data.
You can also pass another argument, such as a boolean if the form has to be synchronized with the given
set of data or not etc.
PDF brought to you by Chapter 74: How to Unit Test your Forms | 287
The empty_data option allows you to specify an empty data set for your form class. This empty data set
would be used if you submit your form, but haven't called setData() on your form or passed in data
when you created your form. For example:
By default, empty_data is set to null. Or, if you have specified a data_class option for your form
class, it will default to a new instance of that class. That instance will be created by calling the constructor
with no arguments.
If you want to override this default behavior, there are two ways to do this.
PDF brought to you by Chapter 75: How to Configure empty Data for a Form Class | 288
You can instantiate your class however you want. In this example, you pass some dependency into the
BlogType then use that to instantiate the Blog class. The point is, you can set empty_data to the
exact "new" object that you want to use.
In order to pass arguments to the BlogType constructor, you'll need to register it as a service and
tag with form.type.
PDF brought to you by Chapter 75: How to Configure empty Data for a Form Class | 289
PDF brought to you by Chapter 76: How to Use the submit() Function to Handle Form Submissions | 290
Forms consisting of nested fields expect an array in submit()3. You can also submit individual
fields by calling submit()4 directly on the field:
$form->get('firstName')->submit('Fabien');
Listing 76-3
When submitting a form via a "PATCH" request, you may want to update only a few submitted
fields. To achieve this, you may pass an optional second boolean parameter to submit(). Passing
false will remove any missing fields within the form object. Otherwise, the mising fields will be set
to null.
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_submit
3. http://api.symfony.com/3.1/Symfony/Component/Form/FormInterface.html#method_submit
4. http://api.symfony.com/3.1/Symfony/Component/Form/FormInterface.html#method_submit
PDF brought to you by Chapter 76: How to Use the submit() Function to Handle Form Submissions | 291
As of Symfony 2.3, the virtual option is renamed to inherit_data. You can read everything
about the new option in "How to Reduce Code Duplication with "inherit_data"".
PDF brought to you by Chapter 77: How to Use the virtual Form Field Option | 292
Symfony and all its packages are perfectly managed by Composer. Bower is a dependency management
tool for front-end dependencies, like Bootstrap or jQuery. As Symfony is purely a back-end framework,
it can't help you much with Bower. Fortunately, it is very easy to use!
Installing Bower
Bower1 is built on top of Node.js2. Make sure you have that installed and then run:
After this command has finished, run bower in your terminal to find out if it's installed correctly.
If you don't want to have NodeJS on your computer, you can also use BowerPHP3 (an unofficial PHP
port of Bower). Beware that this is currently in beta status. If you're using BowerPHP, use bowerphp
instead of bower in the examples.
Listing 78-2 1 {
2 "directory": "web/assets/vendor/"
3 }
1. http://bower.io
2. https://nodejs.org
3. http://bowerphp.org/
PDF brought to you by Chapter 78: Using Bower with Symfony | 293
This will install Bootstrap and its dependencies in web/assets/vendor/ (or whatever directory you
configured in .bowerrc).
For more details on how to use Bower, check out Bower documentation7.
Great job! Your site is now using Bootstrap. You can now easily upgrade bootstrap to the latest version
and manage other front-end dependencies too.
4. http://gulpjs.com/
5. http://gruntjs.com/
6. http://getbootstrap.com/
7. http://bower.io/
PDF brought to you by Chapter 78: Using Bower with Symfony | 294
8. http://addyosmani.com/blog/checking-in-front-end-dependencies/
9. https://github.com/bower/bower/pull/1748
PDF brought to you by Chapter 78: Using Bower with Symfony | 295
In this article, you'll learn how to install and use new Symfony versions before they are released as stable
versions.
Once the command finishes its execution, you'll have a new Symfony project created in the
my_project/ directory and based on the most recent code found in the 2.7 branch.
If you want to test a beta version, use beta as the value of the stability option:
Listing 79-3 1 {
2 "require": {
PDF brought to you by Chapter 79: How to Install or Upgrade to the Latest, Unreleased Symfony Version | 296
Finally, open a command console, enter your project directory and execute the following command to
update your project dependencies:
If you prefer to test a Symfony beta version, replace the "2.7.*@dev" constraint by "2.7.0-beta1"
to install a specific beta number or 2.7.*@beta to get the most recent beta version.
After upgrading the Symfony version, read the Symfony Upgrading Guide to learn how you should
proceed to update your application's code in case the new Symfony version has deprecated some of its
features.
If you use Git to manage the project's code, it's a good practice to create a new branch to test the
new Symfony version. This solution avoids introducing any issue in your application and allows you
to test the new version with total confidence:
PDF brought to you by Chapter 79: How to Install or Upgrade to the Latest, Unreleased Symfony Version | 297
Monolog1 is a logging library for PHP used by Symfony. It is inspired by the Python LogBook library.
Usage
To log a message simply get the logger service from the container in your controller:
The logger service has different methods for different logging levels. See LoggerInterface2 for details on
which methods are available.
When injecting the logger in a service you can use a custom channel control which "channel" the
logger will log to.
The basic handler is the StreamHandler which writes logs in a stream (by default in the var/logs/
prod.log in the prod environment and var/logs/dev.log in the dev environment).
1. https://github.com/Seldaek/monolog
2. https://github.com/php-fig/log/blob/master/Psr/Log/LoggerInterface.php
PDF brought to you by Chapter 80: How to Use Monolog to Write Logs | 298
The above configuration defines a stack of handlers which will be called in the order they are defined.
The handler named "file" will not be included in the stack itself as it is used as a nested handler of
the fingers_crossed handler.
If you want to change the config of MonologBundle in another config file you need to redefine the
whole stack. It cannot be merged because the order matters and a merge does not allow to control
the order.
PDF brought to you by Chapter 80: How to Use Monolog to Write Logs | 299
Listing 80-6
3. https://fedorahosted.org/logrotate/
PDF brought to you by Chapter 80: How to Use Monolog to Write Logs | 300
If you use several handlers, you can also register a processor at the handler level or at the channel
level instead of registering it globally (see the following sections).
PDF brought to you by Chapter 80: How to Use Monolog to Write Logs | 301
PDF brought to you by Chapter 80: How to Use Monolog to Write Logs | 302
Monolog1 can be configured to send an email when an error occurs with an application. The configuration
for this requires a few nested handlers in order to avoid receiving too many emails. This configuration
looks complicated at first but each handler is fairly straightforward when it is broken down.
The mail handler is a fingers_crossed handler which means that it is only triggered when the action
level, in this case critical is reached. The critical level is only triggered for 5xx HTTP code errors.
If this level is reached once, the fingers_crossed handler will log all messages regardless of their level.
The handler setting means that the output is then passed onto the deduplicated handler.
1. https://github.com/Seldaek/monolog
PDF brought to you by Chapter 81: How to Configure Monolog to Email Errors | 303
The deduplicated handler simply keeps all the messages for a request and then passes them onto the
nested handler in one go, but only if the records are unique over a given period of time (60 seconds by
default). If the records are duplicates they are simply discarded. Adding this handler reduces the amount
of notifications to a manageable level, specially in critical failure scenarios.
The messages are then passed to the swift handler. This is the handler that actually deals with emailing
you the error. The settings for this are straightforward, the to and from addresses, the formatter, the
content type and the subject.
You can combine these handlers with other handlers so that the errors still get logged on the server as
well as the emails being sent:
This uses the group handler to send the messages to the two group members, the deduplicated and
the stream handlers. The messages will now be both written to the log file and emailed.
PDF brought to you by Chapter 81: How to Configure Monolog to Email Errors | 304
It is possible to use the console to print messages for certain verbosity levels using the
OutputInterface1 instance that is passed when a command gets executed.
Alternatively, you can use the standalone PSR-3 logger provided with the console component.
When a lot of logging has to happen, it's cumbersome to print information depending on the verbosity
settings (-v, -vv, -vvv) because the calls need to be wrapped in conditions. The code quickly gets
verbose or dirty. For example:
Instead of using these semantic methods to test for each of the verbosity levels, the MonologBridge2
provides a ConsoleHandler3 that listens to console events and writes log messages to the console output
depending on the current log level and the console verbosity.
The example above could then be rewritten as:
1. http://api.symfony.com/3.1/Symfony/Component/Console/Output/OutputInterface.html
2. https://github.com/symfony/MonologBridge
3. https://github.com/symfony/MonologBridge/blob/master/Handler/ConsoleHandler.php
PDF brought to you by Chapter 82: How to Configure Monolog to Display Console Messages | 305
Depending on the verbosity level that the command is run in and the user's configuration (see below),
these messages may or may not be displayed to the console. If they are displayed, they are timestamped
and colored appropriately. Additionally, error logs are written to the error output (php://stderr). There
is no need to conditionally handle the verbosity settings anymore.
The Monolog console handler is enabled in the Monolog configuration.
With the verbosity_levels option you can adapt the mapping between verbosity and log level.
In the given example it will also show notices in normal verbosity mode (instead of warnings only).
Additionally, it will only use messages logged with the custom my_channel channel and it changes the
display style via a custom formatter (see the MonologBundle reference for more information):
PDF brought to you by Chapter 82: How to Configure Monolog to Display Console Messages | 306
Sometimes your logs become flooded with unwanted 404 HTTP errors, for example, when an attacker
scans your app for some well-known application paths (e.g. /phpmyadmin). When using a
fingers_crossed handler, you can exclude logging these 404 errors based on a regular expression in
the MonologBundle configuration:
PDF brought to you by Chapter 83: How to Configure Monolog to Exclude 404 Errors from the Log | 307
The Symfony Framework organizes log messages into channels. By default, there are several channels,
including doctrine, event, security, request and more. The channel is printed in the log message
and can also be used to direct different channels to different places/files.
By default, Symfony logs every message into a single file (regardless of the channel).
Each channel corresponds to a logger service (monolog.logger.XXX) in the container (use the
debug:container command to see a full list) and those are injected into different services.
The channels configuration only works for top level handlers. Handlers that are nested inside a
group, buffer, filter, fingers crossed or other such handler will ignore this configuration and will
process every message passed to them.
PDF brought to you by Chapter 84: How to Log Messages to different Files | 308
With this, you can now send log messages to the foo channel by using the automatically registered logger
service monolog.logger.foo.
PDF brought to you by Chapter 84: How to Log Messages to different Files | 309
The Symfony Profiler delegates data collection to some special classes called data collectors. Symfony
comes bundled with a few of them, but you can easily create your own.
The getName()2 method returns the name of the data collector and must be unique in the application.
This value is also used to access the information later on (see How to Use the Profiler in a Functional Test
for instance).
The collect()3 method is responsible for storing the collected data in local properties.
Most of the time, it is convenient to extend DataCollector4 and populate the $this->data property
(it takes care of serializing the $this->data property). Imagine you create a new data collector that
collects the method and accepted content types from the request:
1. http://api.symfony.com/3.1/Symfony/Component/HttpKernel/DataCollector/DataCollectorInterface.html
2. http://api.symfony.com/3.1/Symfony/Component/HttpKernel/DataCollector/DataCollectorInterface.html#method_getName
3. http://api.symfony.com/3.1/Symfony/Component/HttpKernel/DataCollector/DataCollectorInterface.html#method_collect
4. http://api.symfony.com/3.1/Symfony/Component/HttpKernel/DataCollector/DataCollector.html
PDF brought to you by Chapter 85: How to Create a custom Data Collector | 310
The getters are added to give the template access to the collected information.
As the profiler serializes data collector instances, you should not store objects that cannot be
serialized (like PDO objects) or you need to provide your own serialize() method.
PDF brought to you by Chapter 85: How to Create a custom Data Collector | 311
Built-in collector templates define all their images as embedded base64-encoded images. This makes
them work everywhere without having to mess with web assets links:
Another solution is to define the images as SVG files. In addition to being resolution-independent,
these images can be easily embedded in the Twig template or included from an external file to reuse
them in several templates:
You are encouraged to use the latter technique for your own toolbar panels.
If the toolbar panel includes extended web profiler information, the Twig template must also define
additional blocks:
PDF brought to you by Chapter 85: How to Create a custom Data Collector | 312
The menu and panel blocks are the only required blocks to define the contents displayed in the web
profiler panel associated with this data collector. All blocks have access to the collector object.
Finally, to enable the data collector template, add a template attribute to the data_collector tag in
your service configuration:
The id attribute must match the value returned by the getName() method.
The position of each panel in the toolbar is determined by the priority defined by each collector. Most
built-in collectors use 255 as their priority. If you want your collector to be displayed before them, use a
higher value:
PDF brought to you by Chapter 85: How to Create a custom Data Collector | 313
The Symfony profiler is only activated in the development environment to not hurt your application
performance. However, sometimes it may be useful to conditionally enable the profiler in the production
environment to assist you in debugging issues. This behavior is implemented with the Request
Matchers.
You can also set a path option to define the path on which the profiler should be enabled. For instance,
setting it to ^/admin/ will enable the profiler only for the URLs which start with /admin/.
1. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/RequestMatcher.html
PDF brought to you by Chapter 86: How to Use Matchers to Enable the Profiler Conditionally | 314
Then, configure a new service and set it as private because the application won't use it directly:
Once the service is registered, the only thing left to do is configure the profiler to use this service as the
matcher:
2. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/RequestMatcherInterface.html
3. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/RequestMatcherInterface.html#method_matches
PDF brought to you by Chapter 86: How to Use Matchers to Enable the Profiler Conditionally | 315
In Symfony versions prior to 3.0, profiles could be stored in files, databases, services like Redis and
Memcache, etc. Starting from Symfony 3.0, the only storage mechanism with built-in support is the
filesystem.
By default the profile stores the collected data in the %kernel.cache_dir%/profiler/ directory.
If you want to use another location to store the profiles, define the dsn option of the
framework.profiler:
Listing 87-1 1 # app/config/config.yml
2 framework:
3 profiler:
4 dsn: 'file:/tmp/symfony/profiler'
You can also create your own profile storage service implementing the
:class:Symfony\Component\HttpKernel\Profiler\ProfilerStorageInterface and
overriding the profiler.storage service.
PDF brought to you by Chapter 87: Switching the Profiler Storage | 316
Most of the times, the profiler information is accessed and analyzed using its web-based visualizer.
However, you can also retrieve profiling information programmatically thanks to the methods provided
by the profiler service.
When the response object is available, use the loadProfileFromResponse()1 method to access to
its associated profile:
Listing 88-1
// ... $profiler is the 'profiler' service
$profile = $profiler->loadProfileFromResponse($response);
When the profiler stores data about a request, it also associates a token with it; this token is available in
the X-Debug-Token HTTP header of the response. Using this token, you can access the profile of any
past response thanks to the loadProfile()2 method:
Listing 88-2
$token = $response->headers->get('X-Debug-Token');
$profile = $container->get('profiler')->loadProfile($token);
When the profiler is enabled but not the web debug toolbar, inspect the page with your browser's
developer tools to get the value of the X-Debug-Token HTTP header.
The profiler service also provides the find()3 method to look for tokens based on some criteria:
1. http://api.symfony.com/3.1/Symfony/Component/HttpKernel/Profiler/Profiler.html#method_loadProfileFromResponse
2. http://api.symfony.com/3.1/Symfony/Component/HttpKernel/Profiler/Profiler.html#method_loadProfile
3. http://api.symfony.com/3.1/Symfony/Component/HttpKernel/Profiler/Profiler.html#method_find
PDF brought to you by Chapter 88: How to Access Profiling Data Programmatically | 317
PDF brought to you by Chapter 88: How to Access Profiling Data Programmatically | 318
The PSR-7 bridge converts HttpFoundation objects from and to objects implementing HTTP
message interfaces defined by the PSR-71.
Installation
You can install the component in 2 different ways:
The bridge also needs a PSR-7 implementation to allow converting HttpFoundation objects to PSR-7
objects. It provides native support for Zend Diactoros3. Use Composer (zendframework/zend-diactoros on
Packagist4) or refer to the project documentation to install it.
Usage
Converting from HttpFoundation Objects to PSR-7
The bridge provides an interface of a factory called HttpMessageFactoryInterface5 that builds
objects implementing PSR-7 interfaces from HttpFoundation objects. It also provide a default
implementation using Zend Diactoros internally.
1. http://www.php-fig.org/psr/psr-7/
2. https://packagist.org/packages/symfony/psr-http-message-bridge
3. https://github.com/zendframework/zend-diactoros
4. https://packagist.org/packages/zendframework/zend-diactoros
5. http://api.symfony.com/3.1/Symfony/Bridge/PsrHttpMessage/HttpMessageFactoryInterface.html
6. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/Request.html
7. http://api.symfony.com/3.1/Zend/Diactoros/ServerRequest.html
8. http://api.symfony.com/3.1/Psr/Http/Message/ServerRequestInterface.html
9. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/Response.html
10. http://api.symfony.com/3.1/Zend/Diactoros/Response.html
11. http://api.symfony.com/3.1/Psr/Http/Message/ResponseInterface.html
12. http://api.symfony.com/3.1/Symfony/Bridge/PsrHttpMessage/HttpFoundationFactoryInterface.html
13. http://api.symfony.com/3.1/Psr/Http/Message/ServerRequestInterface.html
14. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/Request.html
15. http://api.symfony.com/3.1/Psr/Http/Message/ResponseInterface.html
16. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/Response.html
When you deploy your application, you may be behind a load balancer (e.g. an AWS Elastic Load
Balancer) or a reverse proxy (e.g. Varnish for caching).
For the most part, this doesn't cause any problems with Symfony. But, when a request passes through a
proxy, certain request information is sent using either the standard Forwarded header or non-standard
special X-Forwarded-* headers. For example, instead of reading the REMOTE_ADDR header (which will
now be the IP address of your reverse proxy), the user's true IP will be stored in a standard Forwarded:
for="..." header or a non standard X-Forwarded-For header.
If you don't configure Symfony to look for these headers, you'll get incorrect information about the
client's IP address, whether or not the client is connecting via HTTPS, the client's port and the hostname
being requested.
Solution: trusted_proxies
This is no problem, but you do need to tell Symfony what is happening and which reverse proxy IP
addresses will be doing this type of thing:
In this example, you're saying that your reverse proxy (or proxies) has the IP address 192.0.0.1 or
matches the range of IP addresses that use the CIDR notation 10.0.0.0/8. For more details, see the
framework.trusted_proxies option.
You are also saying that you trust that the proxy does not send conflicting headers, e.g. sending both X-
Forwarded-For and Forwarded in the same request.
That's it! Symfony will now look for the correct headers to get information like the client's IP address,
host, port and whether the request is using HTTPS.
PDF brought to you by Chapter 90: How to Configure Symfony to Work behind a Load Balancer or a Reverse Proxy | 321
3. Ensure that the trusted_proxies setting in your app/config/config.yml is not set or it will
overwrite the setTrustedProxies call above.
That's it! It's critical that you prevent traffic from all non-trusted sources. If you allow outside traffic, they
could "spoof" their true IP address and other information.
My Reverse Proxy Sends X-Forwarded-For but Does not Filter the Forwarded
Header
Many popular proxy implementations do not yet support the Forwarded header and do not filter it by
default. Ideally, you would configure this in your proxy. If this is not possible, you can tell Symfony to
distrust the Forwarded header, while still trusting your proxy's X-Forwarded-For header.
This is done inside of your front controller:
Configuring the proxy server trust is very important, as not doing so will allow malicious users to "spoof"
their IP address.
1. http://docs.aws.amazon.com/ElasticLoadBalancing/latest/DeveloperGuide/using-elb-security-groups.html
2. http://tools.ietf.org/html/rfc7239
PDF brought to you by Chapter 90: How to Configure Symfony to Work behind a Load Balancer or a Reverse Proxy | 322
Every Request has a "format" (e.g. html, json), which is used to determine what type of content to
return in the Response. In fact, the request format, accessible via getRequestFormat()1, is used to
set the MIME type of the Content-Type header on the Response object. Internally, Symfony contains
a map of the most common formats (e.g. html, json) and their associated MIME types (e.g. text/
html, application/json). Of course, additional format-MIME type entries can easily be added. This
document will show how you can add the jsonp format and corresponding MIME type.
You can also associate multiple mime types to a format, but please note that the preferred one must
be the first as it will be used as the content type:
1. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/Request.html#method_getRequestFormat
PDF brought to you by Chapter 91: How to Register a new Request Format and Mime Type | 323
Sometimes, you want to secure some routes and be sure that they are always accessed via the HTTPS
protocol. The Routing component allows you to enforce the URI scheme via schemes:
The above configuration forces the secure route to always use HTTPS.
When generating the secure URL, and if the current scheme is HTTP, Symfony will automatically
generate an absolute URL with HTTPS as the scheme:
The requirement is also enforced for incoming requests. If you try to access the /secure path with
HTTP, you will automatically be redirected to the same URL, but with the HTTPS scheme.
The above example uses https for the scheme, but you can also force a URL to always use http.
The Security component provides another way to enforce HTTP or HTTPS via the
requires_channel setting. This alternative method is better suited to secure an "area" of your
website (all URLs under /admin) or when you want to secure URLs defined in a third party bundle
(see How to Force HTTPS or HTTP for different URLs for more details).
PDF brought to you by Chapter 92: How to Force Routes to always Use HTTPS or HTTP | 324
Sometimes, you need to compose URLs with parameters that can contain a slash /. For example, take
the classic /hello/{username} route. By default, /hello/Fabien will match this route but not
/hello/Fabien/Kris. This is because Symfony uses this character as separator between route parts.
This guide covers how you can modify a route so that /hello/Fabien/Kris matches the /hello/
{username} route, where {username} equals Fabien/Kris.
That's it! Now, the {username} parameter can contain the / character.
PDF brought to you by Chapter 93: How to Allow a "/" Character in a Route Parameter | 325
Sometimes, a URL needs to redirect to another URL. You can do that by creating a new controller action
whose only task is to redirect, but using the RedirectController1 of the FrameworkBundle is even
easier.
You can redirect to a specific path (e.g. /about) or to a specific route using its name (e.g. homepage).
In this example, you configured a route for the / path and let the RedirectController redirect it to
/app. The permanent switch tells the action to issue a 301 HTTP status code instead of the default
302 HTTP status code.
1. http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/Controller/RedirectController.html
2. http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/Controller/RedirectController.html#method_urlRedirectAction
PDF brought to you by Chapter 94: How to Configure a Redirect without a custom Controller | 326
Because you are redirecting to a route instead of a path, the required option is called route in the
redirect action, instead of path in the urlRedirect action.
3. http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/Controller/RedirectController.html#method_redirectAction
PDF brought to you by Chapter 94: How to Configure a Redirect without a custom Controller | 327
The HTTP method of a request is one of the requirements that can be checked when seeing if it matches
a route. This is introduced in the routing chapter of the book "Routing" with examples using GET and
POST. You can also use other HTTP verbs in this way. For example, if you have a blog post entry then
you could use the same URL path to show it, make changes to it and delete it by matching on GET, PUT
and DELETE.
PDF brought to you by Chapter 95: How to Use HTTP Methods beyond GET and POST in Routes | 328
PDF brought to you by Chapter 95: How to Use HTTP Methods beyond GET and POST in Routes | 329
Sometimes you may find it useful to make some parts of your routes globally configurable. For instance,
if you build an internationalized site, you'll probably start with one or two locales. Surely you'll add a
requirement to your routes to prevent a user from matching a locale other than the locales you support.
You could hardcode your _locale requirement in all your routes, but a better solution is to use a
configurable service container parameter right inside your routing configuration:
You can now control and set the app.locales parameter somewhere in your container:
You can also use a parameter to define your route path (or part of your path):
Just like in normal service container configuration files, if you actually need a % in your route,
you can escape the percent sign by doubling it, e.g. /score-50%%, which would resolve to
/score-50%.
However, as the % characters included in any URL are automatically encoded, the resulting URL of
this example would be /score-50%25 (%25 is the result of encoding the % character).
PDF brought to you by Chapter 96: How to Use Service Container Parameters in your Routes | 330
PDF brought to you by Chapter 96: How to Use Service Container Parameters in your Routes | 331
There are many bundles out there that use their own route loaders to accomplish cases like those
described above, for instance FOSRestBundle2, JMSI18nRoutingBundle3, KnpRadBundle4 and
SonataAdminBundle5.
Loading Routes
The routes in a Symfony application are loaded by the DelegatingLoader6. This loader uses several
other loaders (delegates) to load resources of different types, for instance YAML files or @Route and
@Method annotations in controller files. The specialized loaders implement LoaderInterface7 and
therefore have two important methods: supports()8 and load()9.
Take these lines from the routing.yml in the Symfony Standard Edition:
1. https://github.com/FriendsOfSymfony/FOSRestBundle
2. https://github.com/FriendsOfSymfony/FOSRestBundle
3. https://github.com/schmittjoh/JMSI18nRoutingBundle
4. https://github.com/KnpLabs/KnpRadBundle
5. https://github.com/sonata-project/SonataAdminBundle
6. http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/Routing/DelegatingLoader.html
7. http://api.symfony.com/3.1/Symfony/Component/Config/Loader/LoaderInterface.html
8. http://api.symfony.com/3.1/Symfony/Component/Config/Loader/LoaderInterface.html#method_supports
9. http://api.symfony.com/3.1/Symfony/Component/Config/Loader/LoaderInterface.html#method_load
PDF brought to you by Chapter 97: How to Create a custom Route Loader | 332
When the main loader parses this, it tries all registered delegate loaders and calls their supports()10
method with the given resource (@AppBundle/Controller/) and type (annotation) as arguments.
When one of the loader returns true, its load()11 method will be called, which should return a
RouteCollection12 containing Route13 objects.
Routes loaded this way will be cached by the Router the same way as when they are defined in one
of the default formats (e.g. XML, YML, PHP file).
10. http://api.symfony.com/3.1/Symfony/Component/Config/Loader/LoaderInterface.html#method_supports
11. http://api.symfony.com/3.1/Symfony/Component/Config/Loader/LoaderInterface.html#method_load
12. http://api.symfony.com/3.1/Symfony/Component/Routing/RouteCollection.html
13. http://api.symfony.com/3.1/Symfony/Component/Routing/Route.html
14. http://api.symfony.com/3.1/Symfony/Component/Config/Loader/LoaderInterface.html
15. http://api.symfony.com/3.1/Symfony/Component/Config/Loader/Loader.html
16. http://api.symfony.com/3.1/Symfony/Component/Config/Loader/LoaderInterface.html
PDF brought to you by Chapter 97: How to Create a custom Route Loader | 333
Make sure the controller you specify really exists. In this case you have to create an extraAction
method in the ExtraController of the AppBundle:
Notice the tag routing.loader. All services with this tag will be marked as potential route loaders
and added as specialized route loaders to the routing.loader service, which is an instance of
DelegatingLoader17.
17. http://api.symfony.com/3.1/Symfony/Bundle/FrameworkBundle/Routing/DelegatingLoader.html
PDF brought to you by Chapter 97: How to Create a custom Route Loader | 334
The routes defined using custom route loaders will be automatically cached by the framework. So
whenever you change something in the loader class itself, don't forget to clear the cache.
The resource name and type of the imported routing configuration can be anything that would
normally be supported by the routing configuration loader (YAML, XML, PHP, annotation, etc.).
18. http://api.symfony.com/3.1/Symfony/Component/Config/Loader/Loader.html
19. http://api.symfony.com/3.1/Symfony/Component/Config/Loader/LoaderResolver.html
20. http://api.symfony.com/3.1/Symfony/Component/Config/Loader/LoaderInterface.html#method_supports
21. http://api.symfony.com/3.1/Symfony/Component/Config/Loader/LoaderInterface.html#method_load
22. http://api.symfony.com/3.1/Symfony/Component/Config/Loader/Loader.html#method_import
PDF brought to you by Chapter 97: How to Create a custom Route Loader | 335
The goal of this cookbook is to demonstrate how to redirect URLs with a trailing slash to the same URL
without a trailing slash (for example /en/blog/ to /en/blog).
Create a controller that will match any URL with a trailing slash, remove the trailing slash (keeping query
parameters if any) and redirect to the new URL with a 301 response status code:
After that, create a route to this controller that's matched whenever a URL with a trailing slash is
requested. Be sure to put this route last in your system, as explained below:
PDF brought to you by Chapter 98: Redirect URLs with a Trailing Slash | 336
Redirecting a POST request does not work well in old browsers. A 302 on a POST request would
send a GET request after the redirection for legacy reasons. For that reason, the route here only
matches GET requests.
Make sure to include this route in your routing configuration at the very end of your route listing.
Otherwise, you risk redirecting real routes (including Symfony core routes) that actually do have a
trailing slash in their path.
PDF brought to you by Chapter 98: Redirect URLs with a Trailing Slash | 337
Parameters inside the defaults collection don't necessarily have to match a placeholder in the route
path. In fact, you can use the defaults array to specify extra parameters that will then be accessible as
arguments to your controller, and as attributes of the Request object:
Now, you can access this extra parameter in your controller, as an argument to the controller method:
Listing 99-2
public function indexAction($page, $title)
{
// ...
}
As you can see, the $title variable was never defined inside the route path, but you can still access
its value from inside your controller, through the method's argument, or from the Request object's
attributes bag.
PDF brought to you by Chapter 99: How to Pass Extra Information from a Route to a Controller | 338
The core Symfony Routing System is excellent at handling complex sets of routes. A highly optimized
routing cache is dumped during deployments.
However, when working with large amounts of data that each need a nice readable URL (e.g. for search
engine optimization purposes), the routing can get slowed down. Additionally, if routes need to be edited
by users, the route cache would need to be rebuilt frequently.
For these cases, the DynamicRouter offers an alternative approach:
When all routes are known during deploy time and the number is not too high, using a custom route
loader is the preferred way to add more routes. When working with just one type of objects, a slug
parameter on the object and the @ParamConverter annotation work fine (see FrameworkExtraBundle1)
.
The DynamicRouter is useful when you need Route objects with the full feature set of Symfony. Each
route can define a specific controller so you can decouple the URL structure from your application logic.
The DynamicRouter comes with built-in support for Doctrine ORM and Doctrine PHPCR-ODM but
offers the ContentRepositoryInterface to write a custom loader, e.g. for another database type or
a REST API or anything else.
The DynamicRouter is explained in the Symfony CMF documentation2.
1. https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html
2. http://symfony.com/doc/master/cmf/book/routing.html
PDF brought to you by Chapter 100: Looking up Routes from a Database: Symfony CMF DynamicRouter | 339
If you need a login form and are storing users in some sort of a database, then you should consider
using FOSUserBundle1, which helps you build your User object and gives you many routes and
controllers for common tasks like login, registration and forgot password.
In this entry, you'll build a traditional login form. Of course, when the user logs in, you can load your
users from anywhere - like the database. See B) Configuring how Users are Loaded for details.
First, enable form login under your firewall:
The login_path and check_path can also be route names (but cannot have mandatory wildcards
- e.g. /login/{foo} where foo has no default value).
Now, when the security system initiates the authentication process, it will redirect the user to the
login form /login. Implementing this login form visually is your job. First, create a new
SecurityController inside a bundle:
Listing 101-2 1 // src/AppBundle/Controller/SecurityController.php
2 namespace AppBundle\Controller;
3
4 use Symfony\Bundle\FrameworkBundle\Controller\Controller;
5
6 class SecurityController extends Controller
1. https://github.com/FriendsOfSymfony/FOSUserBundle
PDF brought to you by Chapter 101: How to Build a Traditional Login Form | 340
Next, configure the route that you earlier used under your form_login configuration (login):
Great! Next, add the logic to loginAction that will display the login form:
Don't let this controller confuse you. As you'll see in a moment, when the user submits the form, the
security system automatically handles the form submission for you. If the user had submitted an invalid
username or password, this controller reads the form submission error from the security system so that it
can be displayed back to the user.
In other words, your job is to display the login form and any login errors that may have occurred, but the
security system itself takes care of checking the submitted username and password and authenticating
the user.
Finally, create the template:
PDF brought to you by Chapter 101: How to Build a Traditional Login Form | 341
The form can look like anything, but has a few requirements:
• The form must POST to the login route, since that's what you configured under the form_login key in
security.yml.
• The username must have the name _username and the password must have the name _password.
Actually, all of this can be configured under the form_login key. See Form Login Configuration
for more details.
This login form is currently not protected against CSRF attacks. Read Using CSRF Protection in the
Login Form on how to protect your login form.
And that's it! When you submit the form, the security system will automatically check the user's
credentials and either authenticate the user or send the user back to the login form where the error can
be displayed.
To review the whole process:
1. The user tries to access a resource that is protected;
2. The firewall initiates the authentication process by redirecting the user to the login form (/login);
3. The /login page renders login form via the route and controller created in this example;
4. The user submits the login form to /login;
5. The security system intercepts the request, checks the user's submitted credentials,
authenticates the user if they are correct, and sends the user back to the login form if they are
not.
2. http://api.symfony.com/3.1/Symfony/Component/Security/Core/Exception/AuthenticationException.html
PDF brought to you by Chapter 101: How to Build a Traditional Login Form | 342
Adding an access control that matches /login/* and requires no authentication fixes the problem:
Also, if your firewall does not allow for anonymous users (no anonymous key), you'll need to create a
special firewall that allows anonymous users for the login page:
PDF brought to you by Chapter 101: How to Build a Traditional Login Form | 343
PDF brought to you by Chapter 101: How to Build a Traditional Login Form | 344
• The ldap user provider, using the LdapUserProvider1 class. Like all other user providers, it can be used
with any authentication provider.
• The form_login_ldap authentication provider, for authenticating against an LDAP server using a login
form. Like all other authentication providers, it can be used with any user provider.
• The http_basic_ldap authentication provider, for authenticating against an LDAP server using HTTP
Basic. Like all other authentication providers, it can be used with any user provider.
• Checking a user's password and fetching user information against an LDAP server. This can be
done using both the LDAP user provider and either the LDAP form login or LDAP HTTP Basic
authentication providers.
• Checking a user's password against an LDAP server while fetching user information from another
source (database using FOSUserBundle, for example).
• Loading user information from an LDAP server, while using another authentication strategy (token-
based pre-authentication, for example).
1. http://api.symfony.com/3.1/Symfony/Component/Security/Core/User/LdapUserProvider.html
PDF brought to you by Chapter 102: Authenticating against an LDAP server | 345
service
type: string default: ldap
This is the name of your configured LDAP client. You can freely chose the name, but it must be unique
in your application and it cannot start with a number or contain white spaces.
base_dn
type: string default: null
This is the base DN for the directory
search_dn
type: string default: null
This is your read-only user's DN, which will be used to authenticate against the LDAP server in order to
fetch the user's information.
PDF brought to you by Chapter 102: Authenticating against an LDAP server | 346
default_roles
type: array default: []
This is the default role you wish to give to a user fetched from the LDAP server. If you do not configure
this key, your users won't have any roles, and will not be considered as authenticated fully.
uid_key
type: string default: sAMAccountName
This is the entry's key to use as its UID. Depends on your LDAP server implementation. Commonly used
values are:
• sAMAccountName
• userPrincipalName
• uid
filter
type: string default: ({uid_key}={username})
This key lets you configure which LDAP query will be used. The {uid_key} string will be replaced by
the value of the uid_key configuration value (by default, sAMAccountName), and the {username}
string will be replaced by the username you are trying to load.
For example, with a uid_key of uid, and if you are trying to load the user fabpot, the final string will
be: (uid=fabpot).
Of course, the username will be escaped, in order to prevent LDAP injection2.
The syntax for the filter key is defined by RFC45153.
service
type: string default: ldap
This is the name of your configured LDAP client. You can freely chose the name, but it must be unique
in your application and it cannot start with a number or contain white spaces.
2. http://projects.webappsec.org/w/page/13246947/LDAP%20Injection
3. http://www.faqs.org/rfcs/rfc4515.html
PDF brought to you by Chapter 102: Authenticating against an LDAP server | 347
PDF brought to you by Chapter 102: Authenticating against an LDAP server | 348
Symfony's security system can load security users from anywhere - like a database, via Active Directory
or an OAuth server. This article will show you how to load your users from the database via a Doctrine
entity.
Introduction
Before you start, you should check out FOSUserBundle1. This external bundle allows you to load
users from the database (like you'll learn here) and gives you built-in routes & controllers for things
like login, registration and forgot password. But, if you need to heavily customize your user system
or if you want to learn how things work, this tutorial is even better.
1. https://github.com/FriendsOfSymfony/FOSUserBundle
PDF brought to you by Chapter 103: How to Load Security Users from the Database (the Entity Provider) | 349
PDF brought to you by Chapter 103: How to Load Security Users from the Database (the Entity Provider) | 350
To make things shorter, some of the getter and setter methods aren't shown. But you can generate these
by running:
2. http://api.symfony.com/3.1/Symfony/Component/Security/Core/User/UserInterface.html
3. http://api.symfony.com/3.1/Symfony/Component/Security/Core/User/UserInterface.html#method_getRoles
4. http://api.symfony.com/3.1/Symfony/Component/Security/Core/User/UserInterface.html#method_getPassword
5. http://api.symfony.com/3.1/Symfony/Component/Security/Core/User/UserInterface.html#method_getSalt
6. http://api.symfony.com/3.1/Symfony/Component/Security/Core/User/UserInterface.html#method_getUsername
7. http://api.symfony.com/3.1/Symfony/Component/Security/Core/User/UserInterface.html#method_eraseCredentials
8. http://api.symfony.com/3.1/Symfony/Component/Security/Core/User/UserInterface.html
PDF brought to you by Chapter 103: How to Load Security Users from the Database (the Entity Provider) | 351
First, the encoders section tells Symfony to expect that the passwords in the database will be encoded
using bcrypt. Second, the providers section creates a "user provider" called our_db_provider
that knows to query from your AppBundle:User entity by the username property. The name
our_db_provider isn't important: it just needs to match the value of the provider key under
your firewall. Or, if you don't set the provider key under your firewall, the first "user provider" is
automatically used.
9. https://symfony.com/doc/master/bundles/DoctrineFixturesBundle/index.html
PDF brought to you by Chapter 103: How to Load Security Users from the Database (the Entity Provider) | 352
10. http://api.symfony.com/3.1/Symfony/Component/Security/Core/User/AdvancedUserInterface.html
11. http://api.symfony.com/3.1/Symfony/Component/Security/Core/User/UserInterface.html
PDF brought to you by Chapter 103: How to Load Security Users from the Database (the Entity Provider) | 353
If any of these return false, the user won't be allowed to login. You can choose to have persisted
properties for all of these, or whatever you need (in this example, only isActive pulls from the
database).
So what's the difference between the methods? Each returns a slightly different error message (and these
can be translated when you render them in your login template to customize them further).
If you use AdvancedUserInterface, you also need to add any of the properties used by these
methods (like isActive) to the serialize() and unserialize() methods. If you don't do
this, your user may not be deserialized correctly from the session on each request.
Congrats! Your database-loading security system is all setup! Next, add a true login form instead of HTTP
Basic or keep reading for other topics.
Don't forget to add the repository class to the mapping definition of your entity.
To finish this, just remove the property key from the user provider in security.yml:
12. http://api.symfony.com/3.1/Symfony/Component/Security/Core/User/AdvancedUserInterface.html
13. http://api.symfony.com/3.1/Symfony/Component/Security/Core/User/AdvancedUserInterface.html#method_isAccountNonExpired
14. http://api.symfony.com/3.1/Symfony/Component/Security/Core/User/AdvancedUserInterface.html#method_isAccountNonLocked
15. http://api.symfony.com/3.1/Symfony/Component/Security/Core/User/AdvancedUserInterface.html#method_isCredentialsNonExpired
16. http://api.symfony.com/3.1/Symfony/Component/Security/Core/User/AdvancedUserInterface.html#method_isEnabled
17. http://api.symfony.com/3.1/Symfony/Bridge/Doctrine/Security/User/UserLoaderInterface.html
PDF brought to you by Chapter 103: How to Load Security Users from the Database (the Entity Provider) | 354
This tells Symfony to not query automatically for the User. Instead, when someone logs in, the
loadUserByUsername() method on UserRepository will be called.
18. http://php.net/manual/en/class.serializable.php
19. http://api.symfony.com/3.1/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.html#method_refreshUser
20. http://api.symfony.com/3.1/Symfony/Component/Security/Core/User/EquatableInterface.html
PDF brought to you by Chapter 103: How to Load Security Users from the Database (the Entity Provider) | 355
Whether you need to build a traditional login form, an API token authentication system or you need to
integrate with some proprietary single-sign-on system, the Guard component can make it easy... and fun!
In this example, you'll build an API token authentication system and learn how to work with Guard.
PDF brought to you by Chapter 104: How to Create a Custom Authentication System with Guard | 356
This User doesn't have a password, but you can add a password property if you also want to allow
this user to login with a password (e.g. via a login form).
Your User class doesn't need to be stored in Doctrine: do whatever you need. Next, make sure you've
configured a "user provider" for the user:
• How to Load Security Users from the Database (the Entity Provider)
• How to Create a custom User Provider
1. http://api.symfony.com/3.1/Symfony/Component/Security/Guard/GuardAuthenticatorInterface.html
2. http://api.symfony.com/3.1/Symfony/Component/Security/Guard/AbstractGuardAuthenticator.html
PDF brought to you by Chapter 104: How to Create a Custom Authentication System with Guard | 357
PDF brought to you by Chapter 104: How to Create a Custom Authentication System with Guard | 358
Nice work! Each method is explained below: The Guard Authenticator Methods.
You did it! You now have a fully-working API token authentication system. If your homepage required
ROLE_USER, then you could test it under different conditions:
Listing 104-6 1 # test with no token
2 curl http://localhost:8000/
3 # {"message":"Authentication Required"}
PDF brought to you by Chapter 104: How to Create a Custom Authentication System with Guard | 359
supportsRememberMe
If you want to support "remember me" functionality, return true from this method. You will still
need to active remember_me under your firewall for it to work. Since this is a stateless API, you do not
want to support "remember me" functionality in this example.
3. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/Response.html
4. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/Response.html
5. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/Response.html
PDF brought to you by Chapter 104: How to Create a Custom Authentication System with Guard | 360
In this case, since "ILuvAPIs" is a ridiculous API key, you could include an easter egg to return a custom
message if someone tries this:
6. http://api.symfony.com/3.1/Symfony/Component/Security/Core/Exception/CustomUserMessageAuthenticationException.html
PDF brought to you by Chapter 104: How to Create a Custom Authentication System with Guard | 361
PDF brought to you by Chapter 104: How to Create a Custom Authentication System with Guard | 362
Once a user is authenticated, their credentials are typically stored in the session. This means that when
the session ends they will be logged out and have to provide their login details again next time they wish
to access the application. You can allow users to choose to stay logged in for longer than the session lasts
using a cookie with the remember_me firewall option:
PDF brought to you by Chapter 105: How to Add "Remember Me" Login Functionality | 363
domain (default
value: null)
The domain where the cookie associated with this feature is used. By default cookies use the current
domain obtained from $_SERVER.
The user will then automatically be logged in on subsequent visits while the cookie remains valid.
PDF brought to you by Chapter 105: How to Add "Remember Me" Login Functionality | 364
IS_AUTHENTICATED_REMEMBERED
Automatically assigned to a user who was authenticated via a remember me cookie.
IS_AUTHENTICATED_FULLY
Automatically assigned to a user that has provided their login details during the current session.
You can use these to control access beyond the explicitly assigned roles.
If you have the IS_AUTHENTICATED_REMEMBERED role, then you also have the
IS_AUTHENTICATED_ANONYMOUSLY role. If you have the IS_AUTHENTICATED_FULLY role,
then you also have the other two roles. In other words, these roles represent three levels of increasing
"strength" of authentication.
You can use these additional roles for finer grained control over access to parts of a site. For example,
you may want your user to be able to view their account at /account when authenticated by cookie but
to have to provide their login details to be able to edit the account details. You can do this by securing
specific controller actions using these roles. The edit action in the controller could be secured using the
service context.
In the following example, the action is only allowed if the user has the IS_AUTHENTICATED_FULLY
role.
If your application is based on the Symfony Standard Edition, you can also secure your controller using
annotations:
PDF brought to you by Chapter 105: How to Add "Remember Me" Login Functionality | 365
If you also had an access control in your security configuration that required the user to have a
ROLE_USER role in order to access any of the account area, then you'd have the following situation:
• If a non-authenticated (or anonymously authenticated user) tries to access the account area,
the user will be asked to authenticate.
• Once the user has entered their username and password, assuming the user receives the
ROLE_USER role per your configuration, the user will have the IS_AUTHENTICATED_FULLY role and be able
to access any page in the account section, including the editAction controller.
• If the user's session ends, when the user returns to the site, they will be able to access every
account page - except for the edit page - without being forced to re-authenticate. However,
when they try to access the editAction controller, they will be forced to re-authenticate, since
they are not, yet, fully authenticated.
For more information on securing services or methods in this way, see How to Secure any Service or
Method in your Application.
PDF brought to you by Chapter 105: How to Add "Remember Me" Login Functionality | 366
Sometimes, it's useful to be able to switch from one user to another without having to log out and log
in again (for instance when you are debugging or trying to understand a bug a user sees that you can't
reproduce).
User impersonation is not compatible with pre authenticated firewalls. The reason is that
impersonation requires the authentication state to be maintained server-side, but pre-authenticated
information (SSL_CLIENT_S_DN_Email, REMOTE_USER or other) is sent in each request.
Impersonating the user can be easily done by activating the switch_user firewall listener:
To switch to another user, just add a query string with the _switch_user parameter and the username
as the value to the current URL:
To switch back to the original user, use the special _exit username:
During impersonation, the user is provided with a special role called ROLE_PREVIOUS_ADMIN. In a
template, for instance, this role can be used to show a link to exit impersonation:
Of course, this feature needs to be made available to a small group of users. By default, access is restricted
to users having the ROLE_ALLOWED_TO_SWITCH role. The name of this role can be modified via the
role setting. For extra security, you can also change the query parameter name via the parameter
setting:
Events
The firewall dispatches the security.switch_user event right after the impersonation is completed.
The SwitchUserEvent1 is passed to the listener, and you can use this to get the user that you are now
impersonating.
The cookbook article about Making the Locale "Sticky" during a User's Session does not update the locale
when you impersonate a user. The following code sample will show how to change the sticky locale:
The listener implementation assumes your User entity has a getLocale() method.
1. http://api.symfony.com/3.1/Symfony/Component/Security/Http/Event/SwitchUserEvent.html
Using a form login for authentication is a common, and flexible, method for handling authentication in
Symfony. Pretty much every aspect of the form login can be customized. The full, default configuration
is shown in the next section.
As mentioned, by default the user is redirected back to the page originally requested. Sometimes, this
can cause problems, like if a background Ajax request "appears" to be the last visited URL, causing
the user to be redirected there. For information on controlling this behavior, see How to Change the
default Target Path Behavior.
PDF brought to you by Chapter 107: How to Customize your Form Login | 370
Now, when no URL is set in the session, users will be sent to the default_security_target route.
PDF brought to you by Chapter 107: How to Customize your Form Login | 371
Now, the user will be redirected to the value of the hidden form field. The value attribute can be a relative
path, absolute URL, or a route name. You can even change the name of the hidden form field by changing
the target_path_parameter option to another value.
PDF brought to you by Chapter 107: How to Customize your Form Login | 372
Part of Symfony's standard authentication process depends on "user providers". When a user submits a
username and password, the authentication layer asks the configured user provider to return a user object
for a given username. Symfony then checks whether the password of this user is correct and generates a
security token so the user stays authenticated during the current session. Out of the box, Symfony has
four user providers: in_memory, entity, ldap and chain. In this entry you'll see how you can create
your own user provider, which could be useful if your users are accessed via a custom database, a file, or
- as shown in this example - a web service.
1. http://api.symfony.com/3.1/Symfony/Component/Security/Core/User/UserInterface.html
2. http://api.symfony.com/3.1/Symfony/Component/Security/Core/User/UserInterface.html#method_getRoles
3. http://api.symfony.com/3.1/Symfony/Component/Security/Core/User/UserInterface.html#method_getPassword
4. http://api.symfony.com/3.1/Symfony/Component/Security/Core/User/UserInterface.html#method_getSalt
5. http://api.symfony.com/3.1/Symfony/Component/Security/Core/User/UserInterface.html#method_getUsername
6. http://api.symfony.com/3.1/Symfony/Component/Security/Core/User/UserInterface.html#method_eraseCredentials
7. http://api.symfony.com/3.1/Symfony/Component/Security/Core/User/EquatableInterface.html
8. http://api.symfony.com/3.1/Symfony/Component/Security/Core/User/EquatableInterface.html#method_isEqualTo
PDF brought to you by Chapter 108: How to Create a custom User Provider | 373
If you have more information about your users - like a "first name" - then you can add a firstName field
to hold that data.
PDF brought to you by Chapter 108: How to Create a custom User Provider | 374
9. http://api.symfony.com/3.1/Symfony/Component/Security/Core/User/UserProviderInterface.html
10. http://api.symfony.com/3.1/Symfony/Component/Security/Core/User/UserProviderInterface.html
PDF brought to you by Chapter 108: How to Create a custom User Provider | 375
The real implementation of the user provider will probably have some dependencies or configuration
options or other services. Add these as arguments in the service definition.
Make sure the services file is being imported. See Importing Configuration with imports for details.
Modify security.yml
Everything comes together in your security configuration. Add the user provider to the list of providers
in the "security" section. Choose a name for the user provider (e.g. "webservice") and mention the id of
the service you just defined.
Symfony also needs to know how to encode passwords that are supplied by website users, e.g. by filling
in a login form. You can do this by adding a line to the "encoders" section in your security configuration:
The value here should correspond with however the passwords were originally encoded when creating
your users (however those users were created). When a user submits their password, it's encoded using
this algorithm and the result is compared to the hashed password returned by your getPassword()
method.
PDF brought to you by Chapter 108: How to Create a custom User Provider | 376
If your external users have their passwords salted via a different method, then you'll need to do
a bit more work so that Symfony properly encodes the password. That is beyond the scope of
this entry, but would include sub-classing MessageDigestPasswordEncoder and overriding the
mergePasswordAndSalt method.
Additionally, you can configure the details of the algorithm used to hash passwords. In this example,
the application sets explicitly the cost of the bcrypt hashing:
PDF brought to you by Chapter 108: How to Create a custom User Provider | 377
Check out How to Create a Custom Authentication System with Guard for a simpler and more flexible
way to accomplish custom authentication tasks like this.
Imagine you want to allow access to your website only between 2pm and 4pm UTC. In this entry, you'll
learn how to do this for a login form (i.e. where your user submits their username and password).
1. http://api.symfony.com/3.1/Symfony/Component/Security/Http/Authentication/SimpleFormAuthenticatorInterface.html
PDF brought to you by Chapter 109: How to Create a Custom Form Password Authenticator | 378
How it Works
Great! Now you just need to setup some Configuration. But first, you can find out more about what each
method in this class does.
1) createToken
When Symfony begins handling a request, createToken() is called, where you create a
TokenInterface2 object that contains whatever information you need in authenticateToken() to
authenticate the user (e.g. the username and password).
Whatever token object you create here will be passed to you later in authenticateToken().
2. http://api.symfony.com/3.1/Symfony/Component/Security/Core/Authentication/Token/TokenInterface.html
PDF brought to you by Chapter 109: How to Create a Custom Form Password Authenticator | 379
3) authenticateToken
If supportsToken returns true, Symfony will now call authenticateToken(). Your job here is to
check that the token is allowed to log in by first getting the User object via the user provider and then,
by checking the password and the current time.
The "flow" of how you get the User object and determine whether or not the token is valid (e.g.
checking the password), may vary based on your requirements.
Ultimately, your job is to return a new token object that is "authenticated" (i.e. it has at least 1 role set on
it) and which has the User object inside of it.
Inside this method, the password encoder is needed to check the password's validity:
Listing 109-2
$passwordValid = $this->encoder->isPasswordValid($user, $token->getCredentials());
This is a service that is already available in Symfony and it uses the password algorithm that is configured
in the security configuration (e.g. security.yml) under the encoders key. Below, you'll see how to
inject that into the TimeAuthenticator.
Configuration
Now, configure your TimeAuthenticator as a service:
Then, activate it in the firewalls section of the security configuration using the simple_form key:
PDF brought to you by Chapter 109: How to Create a Custom Form Password Authenticator | 380
PDF brought to you by Chapter 109: How to Create a Custom Form Password Authenticator | 381
Check out How to Create a Custom Authentication System with Guard for a simpler and more flexible
way to accomplish custom authentication tasks like this.
Nowadays, it's quite usual to authenticate the user via an API key (when developing a web service for
instance). The API key is provided for every request and is passed as a query string parameter or via an
HTTP header.
1. http://api.symfony.com/3.1/Symfony/Component/Security/Http/Authentication/SimplePreAuthenticatorInterface.html
PDF brought to you by Chapter 110: How to Authenticate Users with API Keys | 382
Once you've configured everything, you'll be able to authenticate by adding an apikey parameter to
the query string, like http://example.com/api/
foo?apikey=37b51d194a7513e45b56f6524f2d51f2.
The authentication process has several steps, and your implementation will probably differ:
1. createToken
Early in the request cycle, Symfony calls createToken(). Your job here is to create a token object that
contains all of the information from the request that you need to authenticate the user (e.g. the apikey
PDF brought to you by Chapter 110: How to Authenticate Users with API Keys | 383
In case you return null from your createToken() method, be sure to enable anonymous in you
firewall. This way you'll be able to get an AnonymousToken.
2. supportsToken
After Symfony calls createToken(), it will then call supportsToken() on your class (and any other
authentication listeners) to figure out who should handle the token. This is just a way to allow several
authentication mechanisms to be used for the same firewall (that way, you can for instance first try to
authenticate the user via a certificate or an API key and fall back to a form login).
Mostly, you just need to make sure that this method returns true for a token that has been created by
createToken(). Your logic should probably look exactly like this example.
3. authenticateToken
If supportsToken() returns true, Symfony will now call authenticateToken(). One key part is
the $userProvider, which is an external class that helps you load information about the user. You'll
learn more about this next.
In this specific example, the following things happen in authenticateToken():
1. First, you use the $userProvider to somehow look up the $username that corresponds to the $apiKey;
2. Second, you use the $userProvider again to load or create a User object for the $username;
3. Finally, you create an authenticated token (i.e. a token with at least one role) that has the proper
roles and the User object attached to it.
The goal is ultimately to use the $apiKey to find or create a User object. How you do this (e.g. query a
database) and the exact class for your User object may vary. Those differences will be most obvious in
your user provider.
2. http://api.symfony.com/3.1/Symfony/Component/Security/Core/Exception/BadCredentialsException.html
PDF brought to you by Chapter 110: How to Authenticate Users with API Keys | 384
Read the dedicated article to learn how to create a custom user provider.
The logic inside getUsernameForApiKey() is up to you. You may somehow transform the API key
(e.g. 37b51d) into a username (e.g. jondoe) by looking up some information in a "token" database
table.
The same is true for loadUserByUsername(). In this example, Symfony's core User3 class is simply
created. This makes sense if you don't need to store any extra information on your User object (e.g.
firstName). But if you do, you may instead have your own user class which you create and populate
here by querying a database. This would allow you to have custom data on the User object.
Finally, just make sure that supportsClass() returns true for User objects with the same class as
whatever user you return in loadUserByUsername().
If your authentication is stateless like in this example (i.e. you expect the user to send the API key
with every request and so you don't save the login to the session), then you can simply throw the
UnsupportedUserException exception in refreshUser().
3. http://api.symfony.com/3.1/Symfony/Component/Security/Core/User/User.html
PDF brought to you by Chapter 110: How to Authenticate Users with API Keys | 385
Configuration
Once you have your ApiKeyAuthenticator all setup, you need to register it as a service and use it in
your security configuration (e.g. security.yml). First, register it as a service.
Now, activate it and your custom user provider (see How to Create a custom User Provider) in the
firewalls section of your security configuration using the simple_preauth and provider keys
respectively:
4. http://api.symfony.com/3.1/Symfony/Component/Security/Http/Authentication/AuthenticationFailureHandlerInterface.html
PDF brought to you by Chapter 110: How to Authenticate Users with API Keys | 386
That's it! Now, your ApiKeyAuthenticator should be called at the beginning of each request and
your authentication process will take place.
The stateless configuration parameter prevents Symfony from trying to store the authentication
information in the session, which isn't necessary since the client will send the apikey on each request. If
you do need to store authentication in the session, keep reading!
Even though the token is being stored in the session, the credentials - in this case the API key (i.e.
$token->getCredentials()) - are not stored in the session for security reasons. To take advantage
of the session, update ApiKeyAuthenticator to see if the stored token has a valid User object that
can be used:
PDF brought to you by Chapter 110: How to Authenticate Users with API Keys | 387
The second step is the important one: Symfony calls refreshUser() and passes you the user object
that was serialized in the session. If your users are stored in the database, then you may want to re-query
for a fresh version of the user to make sure it's not out-of-date. But regardless of your requirements,
refreshUser() should now return the User object:
Listing 110-10 1 // src/AppBundle/Security/ApiKeyUserProvider.php
2
3 // ...
4 class ApiKeyUserProvider implements UserProviderInterface
5 {
6 // ...
7
8 public function refreshUser(UserInterface $user)
9 {
PDF brought to you by Chapter 110: How to Authenticate Users with API Keys | 388
You'll also want to make sure that your User object is being serialized correctly. If your User object
has private properties, PHP can't serialize those. In this case, you may get back a User object that has
a null value for each property. For an example, see How to Load Security Users from the Database
(the Entity Provider).
This uses the handy HttpUtils5 class to check if the current URL matches the URL you're looking for.
In this case, the URL (/login/check) has been hardcoded in the class, but you could also inject it as
the second constructor argument.
Next, just update your service configuration to inject the security.http_utils service:
5. http://api.symfony.com/3.1/Symfony/Component/Security/Http/HttpUtils.html
PDF brought to you by Chapter 110: How to Authenticate Users with API Keys | 389
PDF brought to you by Chapter 110: How to Authenticate Users with API Keys | 390
Creating a custom authentication system is hard, and this entry will walk you through that process.
But depending on your needs, you may be able to solve your problem in a simpler, or via a
community bundle:
If you have read the chapter on Security, you understand the distinction Symfony makes between
authentication and authorization in the implementation of security. This chapter discusses the core
classes involved in the authentication process, and how to implement a custom authentication provider.
Because authentication and authorization are separate concepts, this extension will be user-provider
agnostic, and will function with your application's user providers, may they be based in memory, a
database, or wherever else you choose to store them.
Meet WSSE
The following chapter demonstrates how to create a custom authentication provider for WSSE
authentication. The security protocol for WSSE provides several security benefits:
1. Username / Password encryption
2. Safe guarding against replay attacks
3. No web server configuration required
WSSE is very useful for the securing of web services, may they be SOAP or REST.
1. https://github.com/hwi/HWIOAuthBundle
PDF brought to you by Chapter 111: How to Create a custom Authentication Provider | 391
WSSE also supports application key validation, which is useful for web services, but is outside the
scope of this chapter.
The Token
The role of the token in the Symfony security context is an important one. A token represents the user
authentication data present in the request. Once a request is authenticated, the token retains the user's
data, and delivers this data across the security context. First, you'll create your token class. This will allow
the passing of all relevant information to your authentication provider.
The WsseUserToken class extends the Security component's AbstractToken4 class, which
provides basic token functionality. Implement the TokenInterface5 on any class to use as a token.
The Listener
Next, you need a listener to listen on the firewall. The listener is responsible for fielding requests
to the firewall and calling the authentication provider. A listener must be an instance of
2. http://www.xml.com/pub/a/2003/12/17/dive.html
3. https://en.wikipedia.org/wiki/Cryptographic_nonce
4. http://api.symfony.com/3.1/Symfony/Component/Security/Core/Authentication/Token/AbstractToken.html
5. http://api.symfony.com/3.1/Symfony/Component/Security/Core/Authentication/Token/TokenInterface.html
PDF brought to you by Chapter 111: How to Create a custom Authentication Provider | 392
6. http://api.symfony.com/3.1/Symfony/Component/Security/Http/Firewall/ListenerInterface.html
7. http://api.symfony.com/3.1/Symfony/Component/HttpKernel/Event/GetResponseEvent.html
PDF brought to you by Chapter 111: How to Create a custom Authentication Provider | 393
A class not used above, the AbstractAuthenticationListener9 class, is a very useful base
class which provides commonly needed functionality for security extensions. This includes
maintaining the token in the session, providing success / failure handlers, login form URLs, and
more. As WSSE does not require maintaining authentication sessions or login forms, it won't be used
for this example.
Returning prematurely from the listener is relevant only if you want to chain authentication
providers (for example to allow anonymous users). If you want to forbid access to anonymous users
and have a nice 403 error, you should set the status code of the response before returning.
8. http://api.symfony.com/3.1/Symfony/Component/Security/Core/Exception/AuthenticationException.html
9. http://api.symfony.com/3.1/Symfony/Component/Security/Http/Firewall/AbstractAuthenticationListener.html
PDF brought to you by Chapter 111: How to Create a custom Authentication Provider | 394
While the hash_equals11 function was introduced in PHP 5.6, you are safe to use it with any
PHP version in your Symfony application. In PHP versions prior to 5.6, Symfony Polyfill12 (which is
included in Symfony) will define the function for you.
The Factory
You have created a custom token, custom listener, and custom provider. Now you need to tie them all
together. How do you make a unique provider available for every firewall? The answer is by using a
10. http://api.symfony.com/3.1/Symfony/Component/Security/Core/Authentication/Provider/AuthenticationProviderInterface.html
11. http://php.net/manual/en/function.hash-equals.php
12. https://github.com/symfony/polyfill
PDF brought to you by Chapter 111: How to Create a custom Authentication Provider | 395
getPosition
Returns when the provider should be called. This can be one of pre_auth, form, http or remember_me.
getKey
Method which defines the configuration key used to reference the provider in the firewall
configuration.
addConfiguration
Method which is used to define the configuration options underneath the configuration key in your
security configuration. Setting configuration options are explained later in this chapter.
13. http://api.symfony.com/3.1/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SecurityFactoryInterface.html
14. http://api.symfony.com/3.1/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SecurityFactoryInterface.html
PDF brought to you by Chapter 111: How to Create a custom Authentication Provider | 396
Now that you have created a factory class, the wsse key can be used as a firewall in your security
configuration.
You may be wondering "why do you need a special factory class to add listeners and providers to
the dependency injection container?". This is a very good question. The reason is you can use your
firewall multiple times, to secure multiple parts of your application. Because of this, each time your
firewall is used, a new service is created in the DI container. The factory is what creates these new
services.
Configuration
It's time to see your authentication provider in action. You will need to do a few things in order to
make this work. The first thing is to add the services above to the DI container. Your factory class above
makes reference to service ids that do not exist yet: wsse.security.authentication.provider
and wsse.security.authentication.listener. It's time to define those services.
Now that your services are defined, tell your security context about your factory in your bundle class:
You are finished! You can now define parts of your app as under WSSE protection.
Listing 111-7
15. http://api.symfony.com/3.1/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.html
PDF brought to you by Chapter 111: How to Create a custom Authentication Provider | 397
Congratulations! You have written your very own custom security authentication provider!
A little Extra
How about making your WSSE authentication provider a bit more exciting? The possibilities are endless.
Why don't you start by adding some sparkle to that shine?
Configuration
You can add custom options under the wsse key in your security configuration. For instance, the time
allowed before expiring the Created header item, by default, is 5 minutes. Make this configurable, so
different firewalls can have different timeout lengths.
You will first need to edit WsseFactory and define the new option in the addConfiguration
method.
Now, in the create method of the factory, the $config argument will contain a lifetime key,
set to 5 minutes (300 seconds) unless otherwise set in the configuration. Pass this argument to your
authentication provider in order to put it to use.
PDF brought to you by Chapter 111: How to Create a custom Authentication Provider | 398
The lifetime of each WSSE request is now configurable, and can be set to any desirable value per firewall.
The rest is up to you! Any relevant configuration items can be defined in the factory and consumed or
passed to the other classes in the container.
PDF brought to you by Chapter 111: How to Create a custom Authentication Provider | 399
A lot of authentication modules are already provided by some web servers, including Apache. These
modules generally set some environment variables that can be used to determine which user is accessing
your application. Out of the box, Symfony supports most authentication mechanisms. These requests
are called pre authenticated requests because the user is already authenticated when reaching your
application.
User impersonation is not compatible with pre-authenticated firewalls. The reason is that
impersonation requires the authentication state to be maintained server-side, but pre-authenticated
information (SSL_CLIENT_S_DN_Email, REMOTE_USER or other) is sent in each request.
By default, the firewall provides the SSL_CLIENT_S_DN_Email variable to the user provider, and sets
the SSL_CLIENT_S_DN as credentials in the PreAuthenticatedToken1. You can override these by
setting the user and the credentials keys in the x509 firewall configuration respectively.
1. http://api.symfony.com/3.1/Symfony/Component/Security/Core/Authentication/Token/PreAuthenticatedToken.html
PDF brought to you by Chapter 112: Using pre Authenticated Security Firewalls | 400
The firewall will then provide the REMOTE_USER environment variable to your user provider. You can
change the variable name used by setting the user key in the remote_user firewall configuration.
Just like for X509 authentication, you will need to configure a "user provider". See the previous note
for more information.
PDF brought to you by Chapter 112: Using pre Authenticated Security Firewalls | 401
By default, the Security component retains the information of the last request URI in a session variable
named _security.main.target_path (with main being the name of the firewall, defined in
security.yml). Upon a successful login, the user is redirected to this path, as to help them continue
from the last known page they visited.
In some situations, this is not ideal. For example, when the last request URI was an XMLHttpRequest
which returned a non-HTML or partial HTML response, the user is redirected back to a page which the
browser cannot render.
To get around this behavior, you would simply need to extend the ExceptionListener class and
override the default method named setTargetPath().
First, override the security.exception_listener.class parameter in your configuration file.
This can be done from your main configuration file (in app/config) or from a configuration file being
imported from a bundle:
PDF brought to you by Chapter 113: How to Change the default Target Path Behavior | 402
PDF brought to you by Chapter 113: How to Change the default Target Path Behavior | 403
When using a login form, you should make sure that you are protected against CSRF (Cross-site request
forgery1). The Security component already has built-in support for CSRF. In this article you'll learn how
you can use it in your login form.
Login CSRF attacks are a bit less well-known. See Forging Login Requests2 if you're curious about
more details.
The Security component can be configured further, but this is all information it needs to be able to use
CSRF in the login form.
1. https://en.wikipedia.org/wiki/Cross-site_request_forgery
2. https://en.wikipedia.org/wiki/Cross-site_request_forgery#Forging_login_requests
PDF brought to you by Chapter 114: Using CSRF Protection in the Login Form | 404
After this, you have protected your login form against CSRF attacks.
You can change the name of the field by setting csrf_parameter and change the token ID by
setting csrf_token_id in your configuration:
PDF brought to you by Chapter 114: Using CSRF Protection in the Login Form | 405
Usually, the same password encoder is used for all users by configuring it to apply to all instances of a
specific class:
Another option is to use a "named" encoder and then select which encoder you want to use dynamically.
In the previous example, you've set the sha512 algorithm for Acme\UserBundle\Entity\User. This
may be secure enough for a regular user, but what if you want your admins to have a stronger algorithm,
for example bcrypt. This can be done with named encoders:
This creates an encoder named harsh. In order for a User instance to use it, the class must implement
EncoderAwareInterface1. The interface requires one method - getEncoderName - which should
return the name of the encoder to use:
1. http://api.symfony.com/3.1/Symfony/Component/Security/Core/Encoder/EncoderAwareInterface.html
PDF brought to you by Chapter 115: How to Choose the Password Encoder Algorithm Dynamically | 406
PDF brought to you by Chapter 115: How to Choose the Password Encoder Algorithm Dynamically | 407
Each authentication mechanism (e.g. HTTP Authentication, form login, etc) uses exactly one user
provider, and will use the first declared user provider by default. But what if you want to specify a few
users via configuration and the rest of your users in the database? This is possible by creating a new
provider that chains the two together:
Now, all authentication mechanisms will use the chain_provider, since it's the first specified. The
chain_provider will, in turn, try to load the user from both the in_memory and user_db providers.
You can also configure the firewall or individual authentication mechanisms to use a specific provider.
Again, unless a provider is specified explicitly, the first provider is always used:
In this example, if a user tries to log in via HTTP authentication, the authentication system will use the
in_memory user provider. But if the user tries to log in via the form login, the user_db provider will be
used (since it's the default for the firewall as a whole).
PDF brought to you by Chapter 116: How to Use multiple User Providers | 408
PDF brought to you by Chapter 116: How to Use multiple User Providers | 409
The Guard authentication component allows you to easily use many different authenticators at a time.
An entry point is a service id (of one of your authenticators) whose start() method is called to start
the authentication process.
There is one limitation with this approach - you have to use exactly one entry point.
Listing 117-2
PDF brought to you by Chapter 117: How to Use Multiple Guard Authenticators | 410
PDF brought to you by Chapter 117: How to Use Multiple Guard Authenticators | 411
When using the Security component, you can create firewalls that match certain request options. In most
cases, matching against the URL is sufficient, but in special cases you can further restrict the initialization
of a firewall against other options of the request.
You can use any of these restrictions individually or mix them together to get your desired firewall
configuration.
Restricting by Pattern
This is the default restriction and restricts a firewall to only be initialized if the request URL matches the
configured pattern.
The pattern is a regular expression. In this example, the firewall will only be activated if the URL starts
(due to the ^ regex character) with /admin. If the URL does not match this pattern, the firewall will not
be activated and subsequent firewalls will have the opportunity to be matched for this request.
Restricting by Host
If matching against the pattern only is not enough, the request can also be matched against host.
When the configuration option host is set, the firewall will be restricted to only initialize if the host from
the request matches against the configuration.
PDF brought to you by Chapter 118: How to Restrict Firewalls to a Specific Request | 412
The host (like the pattern) is a regular expression. In this example, the firewall will only be activated
if the host is equal exactly (due to the ^ and $ regex characters) to the hostname admin.example.com.
If the hostname does not match this pattern, the firewall will not be activated and subsequent firewalls
will have the opportunity to be matched for this request.
In this example, the firewall will only be activated if the HTTP method of the request is either GET or
POST. If the method is not in the array of the allowed methods, the firewall will not be activated and
subsequent firewalls will again have the opportunity to be matched for this request.
PDF brought to you by Chapter 118: How to Restrict Firewalls to a Specific Request | 413
As of Symfony 2.5, more possibilities to restrict firewalls have been added. You can read everything about
all the possibilities (including host) in "How to Restrict Firewalls to a Specific Request".
PDF brought to you by Chapter 119: How to Restrict Firewalls to a Specific Host | 414
During the authentication of a user, additional checks might be required to verify if the identified user is
allowed to log in. By defining a custom user checker, you can define per firewall which checker should
be used.
1. http://api.symfony.com/3.1/Symfony/Component/Security/Core/UserCheckerInterface.html
2. http://api.symfony.com/3.1/Symfony/Component/Security/Core/Exception/AccountStatusException.html
PDF brought to you by Chapter 120: How to Create and Enable Custom User Checkers | 415
All that's left to do is add the checker to the desired firewall where the value is the service id of your user
checker:
Additional Configurations
It's possible to have a different user checker per firewall.
Internally the user checkers are aliased per firewall. For secured_area the alias
security.user_checker.secured_area would point to app.user_checker.
PDF brought to you by Chapter 120: How to Create and Enable Custom User Checkers | 416
In Symfony, you can check the permission to access data by using the ACL module, which is a bit
overwhelming for many applications. A much easier solution is to work with custom voters, which are
like simple conditional statements.
Take a look at the authorization chapter for an even deeper understanding on voters.
1. http://api.symfony.com/3.1/Symfony/Component/Security/Core/Authorization/Voter/VoterInterface.html
2. http://api.symfony.com/3.1/Symfony/Component/Security/Core/Authorization/Voter/Voter.html
PDF brought to you by Chapter 121: How to Use Voters to Check User Permissions | 417
The denyAccessUnlessGranted() method (and also, the simpler isGranted() method) calls out
to the "voter" system. Right now, no voters will vote on whether or not the user can "view" or "edit" a
Post. But you can create your own voter that decides this using whatever logic you want.
The denyAccessUnlessGranted() function and the isGranted() functions are both just
shortcuts to call isGranted() on the security.authorization_checker service.
PDF brought to you by Chapter 121: How to Use Voters to Check User Permissions | 418
PDF brought to you by Chapter 121: How to Use Voters to Check User Permissions | 419
You're done! Now, when you call isGranted() with view/edit and a Post object, your voter will be
executed and you can control access.
3. http://api.symfony.com/3.1/Symfony/Component/Security/Core/Authorization/AccessDecisionManager.html
PDF brought to you by Chapter 121: How to Use Voters to Check User Permissions | 420
That's it! Calling decide() on the AccessDecisionManager is essentially the same as calling
isGranted() from a controller or other places (it's just a little lower-level, which is necessary for a
voter).
consensus
This grants access if there are more voters granting access than denying;
unanimous
This only grants access once all voters grant access.
In the above scenario, both voters should grant access in order to grant access to the user to read the post.
In this case, the default strategy is no longer valid and unanimous should be used instead. You can set
this in the security configuration:
PDF brought to you by Chapter 121: How to Use Voters to Check User Permissions | 421
In complex applications, you will often face the problem that access decisions cannot only be based
on the person (Token) who is requesting access, but also involve a domain object that access is being
requested for. This is where the ACL system comes in.
Alternatives to ACLs
Using ACL's isn't trivial, and for simpler use cases, it may be overkill. If your permission logic
could be described by just writing some code (e.g. to check if a Blog is owned by the current
User), then consider using voters. A voter is passed the object being voted on, which you can use to
make complex decisions and effectively implement your own ACL. Enforcing authorization (e.g. the
isGranted part) will look similar to what you see in this entry, but your voter class will handle the
logic behind the scenes, instead of the ACL system.
Imagine you are designing a blog system where your users can comment on your posts. Now, you want a
user to be able to edit their own comments, but not those of other users; besides, you yourself want to be
able to edit all comments. In this scenario, Comment would be the domain object that you want to restrict
access to. You could take several approaches to accomplish this using Symfony, two basic approaches are
(non-exhaustive):
• Enforce security in your business methods: Basically, that means keeping a reference inside each
Comment to all users who have access, and then compare these users to the provided Token.
• Enforce security with roles: In this approach, you would add a role for each Comment object, i.e.
ROLE_COMMENT_1, ROLE_COMMENT_2, etc.
Both approaches are perfectly valid. However, they couple your authorization logic to your business code
which makes it less reusable elsewhere, and also increases the difficulty of unit testing. Besides, you could
run into performance issues if many users would have access to a single domain object.
Fortunately, there is a better way, which you will find out about now.
PDF brought to you by Chapter 122: How to Use Access Control Lists (ACLs) | 422
The ACL system requires a connection from either Doctrine DBAL (usable by default) or Doctrine
MongoDB (usable with MongoDBAclBundle1). However, that does not mean that you have to use
Doctrine ORM or ODM for mapping your domain objects. You can use whatever mapper you like
for your objects, be it Doctrine ORM, MongoDB ODM, Propel, raw SQL, etc. The choice is yours.
After the connection is configured, you have to import the database structure. Fortunately, there is a task
for this. Simply run the following command:
Getting Started
Coming back to the small example from the beginning, you can now implement ACL for it.
Once the ACL is created, you can grant access to objects by creating an Access Control Entry (ACE) to
solidify the relationship between the entity and your user.
1. https://github.com/IamPersistent/MongoDBAclBundle
PDF brought to you by Chapter 122: How to Use Access Control Lists (ACLs) | 423
There are a couple of important implementation decisions in this code snippet. For now, I only want to
highlight two:
First, you may have noticed that ->createAcl() does not accept domain objects directly, but only
implementations of the ObjectIdentityInterface. This additional step of indirection allows you to
work with ACLs even when you have no actual domain object instance at hand. This will be extremely
helpful if you want to check permissions for a large number of objects without actually hydrating these
objects.
The other interesting part is the ->insertObjectAce() call. In the example, you are granting the user
who is currently logged in owner access to the Comment. The MaskBuilder::MASK_OWNER is a pre-
defined integer bitmask; don't worry the mask builder will abstract away most of the technical details,
but using this technique you can store many different permissions in one database row which gives a
considerable boost in performance.
The order in which ACEs are checked is significant. As a general rule, you should place more specific
entries at the beginning.
Checking Access
In this example, you check whether the user has the EDIT permission. Internally, Symfony maps the
permission to several integer bitmasks, and checks whether the user has any of them.
PDF brought to you by Chapter 122: How to Use Access Control Lists (ACLs) | 424
Cumulative Permissions
In the first example above, you only granted the user the OWNER base permission. While this effectively
also allows the user to perform any operation such as view, edit, etc. on the domain object, there are
cases where you may want to grant these permissions explicitly.
The MaskBuilder can be used for creating bit masks easily by combining several base permissions:
This integer bitmask can then be used to grant a user the base permissions you added above:
The user is now allowed to view, edit, delete, and un-delete objects.
PDF brought to you by Chapter 122: How to Use Access Control Lists (ACLs) | 425
The aim of this chapter is to give a more in-depth view of the ACL system, and also explain some of the
design decisions behind it.
Design Concepts
Symfony's object instance security capabilities are based on the concept of an Access Control List. Every
domain object instance has its own ACL. The ACL instance holds a detailed list of Access Control
Entries (ACEs) which are used to make access decisions. Symfony's ACL system focuses on two main
objectives:
• providing a way to efficiently retrieve a large amount of ACLs/ACEs for your domain objects, and
to modify them;
• providing a way to easily make decisions of whether a person is allowed to perform an action on a
domain object or not.
As indicated by the first point, one of the main capabilities of Symfony's ACL system is a high-
performance way of retrieving ACLs/ACEs. This is extremely important since each ACL might have
several ACEs, and inherit from another ACL in a tree-like fashion. Therefore, no ORM is leveraged,
instead the default implementation interacts with your connection directly using Doctrine's DBAL.
Object Identities
The ACL system is completely decoupled from your domain objects. They don't even have to be stored
in the same database, or on the same server. In order to achieve this decoupling, in the ACL system your
objects are represented through object identity objects. Every time you want to retrieve the ACL for a
domain object, the ACL system will first create an object identity from your domain object, and then pass
this object identity to the ACL provider for further processing.
Security Identities
This is analog to the object identity, but represents a user, or a role in your application. Each role, or user
has its own security identity.
PDF brought to you by Chapter 123: How to Use advanced ACL Concepts | 426
• acl_security_identities: This table records all security identities (SID) which hold ACEs. The default
implementation ships with two security identities: RoleSecurityIdentity2 and UserSecurityIdentity3.
• acl_classes: This table maps class names to a unique ID which can be referenced from other tables.
• acl_object_identities: Each row in this table represents a single domain object instance.
• acl_object_identity_ancestors: This table allows all the ancestors of an ACL to be determined in a
very efficient way.
• acl_entries: This table contains all ACEs. This is typically the table with the most rows. It can
contain tens of millions without significantly impacting performance.
• Class-Scope: These entries apply to all objects with the same class.
• Object-Scope: This was the scope solely used in the previous chapter, and it only applies to one
specific object.
Sometimes, you will find the need to apply an ACE only to a specific field of the object. Suppose you
want the ID only to be viewable by an administrator, but not by your customer service. To solve this
common problem, two more sub-scopes have been added:
• Class-Field-Scope: These entries apply to all objects with the same class, but only to a specific field
of the objects.
• Object-Field-Scope: These entries apply to a specific object, and only to a specific field of that
object.
Pre-Authorization Decisions
For pre-authorization decisions, that is decisions made before any secure method (or secure action) is
invoked, the proven AccessDecisionManager service is used. The AccessDecisionManager is also used
for reaching authorization decisions based on roles. Just like roles, the ACL system adds several new
attributes which may be used to check for different permissions.
1. http://api.symfony.com/3.1/Symfony/Component/Security/Acl/Dbal/MutableAclProvider.html#method_updateUserSecurityIdentity
2. http://api.symfony.com/3.1/Symfony/Component/Security/Acl/Domain/RoleSecurityIdentity.html
3. http://api.symfony.com/3.1/Symfony/Component/Security/Acl/Domain/UserSecurityIdentity.html
PDF brought to you by Chapter 123: How to Use advanced ACL Concepts | 427
Extensibility
The above permission map is by no means static, and theoretically could be completely replaced at will.
However, it should cover most problems you encounter, and for interoperability with other bundles, you
are encouraged to stick to the meaning envisaged for them.
PDF brought to you by Chapter 123: How to Use advanced ACL Concepts | 428
4. https://github.com/schmittjoh/JMSSecurityExtraBundle
5. http://api.symfony.com/3.1/Symfony/Component/Security/Acl/Domain/PermissionGrantingStrategy.html
PDF brought to you by Chapter 123: How to Use advanced ACL Concepts | 429
You can force areas of your site to use the HTTPS protocol in the security config. This is done through
the access_control rules using the requires_channel option. For example, if you want to force
all URLs starting with /secure to use HTTPS then you could use the following configuration:
The login form itself needs to allow anonymous access, otherwise users will be unable to authenticate.
To force it to use HTTPS you can still use access_control rules by using the
IS_AUTHENTICATED_ANONYMOUSLY role:
Listing 124-2 1 # app/config/security.yml
2 security:
3 # ...
4
5 access_control:
6 - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: https }
It is also possible to specify using HTTPS in the routing configuration, see How to Force Routes to always
Use HTTPS or HTTP for more details.
PDF brought to you by Chapter 124: How to Force HTTPS or HTTP for different URLs | 430
In the security chapter, you can see how to secure a controller by requesting the
security.authorization_checker service from the Service Container and checking the current
user's role:
You can also secure any service by injecting the security.authorization_checker service into it.
For a general introduction to injecting dependencies into services see the Service Container chapter of the
book. For example, suppose you have a NewsletterManager class that sends out emails and you want
to restrict its use to only users who have some ROLE_NEWSLETTER_ADMIN role. Before you add security,
the class looks something like this:
Your goal is to check the user's role when the sendNewsletter() method is called. The first step
towards this is to inject the security.authorization_checker service into the object. Since it
PDF brought to you by Chapter 125: How to Secure any Service or Method in your Application | 431
The injected service can then be used to perform the security check when the sendNewsletter()
method is called:
If the current user does not have the ROLE_NEWSLETTER_ADMIN, they will be prompted to log in.
PDF brought to you by Chapter 125: How to Secure any Service or Method in your Application | 432
You can then achieve the same results as above using an annotation:
The annotations work because a proxy class is created for your class which performs the security
checks. This means that, whilst you can use annotations on public and protected methods, you
cannot use them with private methods or methods marked final.
The JMSSecurityExtraBundle also allows you to secure the parameters and return values of methods. For
more information, see the JMSSecurityExtraBundle2 documentation.
The disadvantage of this method is that, if activated, the initial page load may be very slow
depending on how many services you have defined.
1. https://github.com/schmittjoh/JMSSecurityExtraBundle
2. https://github.com/schmittjoh/JMSSecurityExtraBundle
PDF brought to you by Chapter 125: How to Secure any Service or Method in your Application | 433
For each incoming request, Symfony checks each access_control entry to find one that matches the
current request. As soon as it finds a matching access_control entry, it stops - only the first matching
access_control is used to enforce access.
Each access_control has several options that configure two different things:
1. should the incoming request match this access control entry
2. once it matches, should some sort of access restriction be enforced:
1. Matching Options
Symfony creates an instance of RequestMatcher1 for each access_control entry, which determines
whether or not a given access control should be used on this request. The following access_control
options are used for matching:
• path
• ip or ips
• host
• methods
For each incoming request, Symfony will decide which access_control to use based on the URI,
the client's IP address, the incoming host name, and the request method. Remember, the first rule that
1. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/RequestMatcher.html
PDF brought to you by Chapter 126: How Does the Security access_control Work? | 434
2. Access Enforcement
Once Symfony has decided which access_control entry matches (if any), it then enforces access
restrictions based on the roles, allow_if and requires_channel options:
• If the user does not have the given role(s), then access is denied (internally, an
role
is thrown);
AccessDeniedException2
• allow_if If the expression returns false, then access is denied;
• requires_channel If the incoming request's channel (e.g. http) does not match this value (e.g. https), the
user will be redirected (e.g. redirected from http to https, or vice versa).
If access is denied, the system will try to authenticate the user if not already (e.g. redirect the user to
the login page). If the user is already logged in, the 403 "access denied" error page will be shown. See
How to Customize Error Pages for more information.
2. http://api.symfony.com/3.1/Symfony/Component/Security/Core/Exception/AccessDeniedException.html
PDF brought to you by Chapter 126: How Does the Security access_control Work? | 435
As you'll read in the explanation below the example, the ips option does not restrict to a specific IP
address. Instead, using the ips key means that the access_control entry will only match this IP
address, and users accessing it from a different IP address will continue down the access_control
list.
Here is an example of how you configure some example /internal* URL pattern so that it is only
accessible by requests from the local server itself:
Here is how it works when the path is /internal/something coming from the external IP address
10.0.0.1:
• The first access control rule is ignored as the path matches but the IP address does not match either
of the IPs listed;
• The second access control rule is enabled (the only restriction being the path) and so it matches.
If you make sure that no users ever have ROLE_NO_ACCESS, then access is denied (ROLE_NO_ACCESS can be
anything that does not match an existing role, it just serves as a trick to always deny access).
But if the same request comes from 127.0.0.1 or ::1 (the IPv6 loopback address):
• Now, the first access control rule is enabled as both the path and the ip match: access is allowed as
the user always has the IS_AUTHENTICATED_ANONYMOUSLY role.
• The second access rule is not examined as the first rule matched.
Securing by an Expression
Once an access_control entry is matched, you can deny access via the roles key or use more
complex logic with an expression in the allow_if key:
In this case, when the user tries to access any URL starting with /_internal/secure, they will only
be granted access if the IP address is 127.0.0.1 or if the user has the ROLE_ADMIN role.
Inside the expression, you have access to a number of different variables and functions including
request, which is the Symfony Request3 object (see Request).
3. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/Request.html
PDF brought to you by Chapter 126: How Does the Security access_control Work? | 436
PDF brought to you by Chapter 126: How Does the Security access_control Work? | 437
Serializing and deserializing to and from objects and different formats (e.g. JSON or XML) is a very
complex topic. Symfony comes with a Serializer Component, which gives you some tools that you can
leverage for your solution.
In fact, before you start, get familiar with the serializer, normalizers and encoders by reading the Serializer
Component.
PDF brought to you by Chapter 127: How to Use the Serializer | 438
Next, add the @Groups annotations to your class and choose which groups to use when serializing:
In addition to the @Groups annotation, the Serializer component also supports Yaml or XML files. These
files are automatically loaded when being stored in one of the following locations:
1. http://api.symfony.com/3.1/Symfony/Component/Serializer/Encoder/JsonEncoder.html
2. http://api.symfony.com/3.1/Symfony/Component/Serializer/Encoder/XmlEncoder.html
3. http://api.symfony.com/3.1/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.html
4. https://github.com/krakjoe/apcu
PDF brought to you by Chapter 127: How to Use the Serializer | 439
5. https://github.com/api-platform/core
6. http://json-ld.org
7. http://hydra-cg.com
PDF brought to you by Chapter 127: How to Use the Serializer | 440
In the service container, all services are shared by default. This means that each time you retrieve the
service, you'll get the same instance. This is often the behavior you want, but in some cases, you might
want to always get a new instance.
In order to always get a new instance, set the shared setting to false in your service definition:
PDF brought to you by Chapter 128: How to Define Non Shared Services | 441
Compiler passes give you an opportunity to manipulate other service definitions that have been registered
with the service container. You can read about how to create them in the components section "Execute
Code During Compilation".
When using separate compiler passes, you need to register them in the build() method of the bundle
class (this is not needed when implementing the process() method in the extension):
One of the most common use-cases of compiler passes is to work with tagged services (read more about
tags in the components section "Working with Tagged Services"). If you are using custom tags in a bundle
then by convention, tag names consist of the name of the bundle (lowercase, underscores as separators),
followed by a dot and finally the "real" name. For example, if you want to introduce some sort of
"mail_transport" tag in your AppBundle, you should call it app.mail_transport.
PDF brought to you by Chapter 129: How to Work with Compiler Passes in Bundles | 442
The session proxy mechanism has a variety of uses and this article demonstrates two common uses.
Rather than using the regular session handler, you can create a custom save handler just by defining a
class that extends the SessionHandlerProxy1 class.
Then, define a new service related to the custom session handler:
Finally, use the framework.session.handler_id configuration option to tell Symfony to use your
own session handler instead of the default one:
Keep reading the next sections to learn how to use the session handlers in practice to solve two common
use cases: encrypt session information and define readonly guest sessions.
1. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/Session/Storage/Proxy/SessionHandlerProxy.html
Symfony stores the locale setting in the Request, which means that this setting is not available in
subsequent requests. In this article, you'll learn how to store the locale in the session, so that it'll be the
same for every subsequent request.
Creating a LocaleListener
To simulate that the locale is stored in a session, you need to create and register a new event listener. The
listener will look something like this. Typically, _locale is used as a routing parameter to signify the
locale, though it doesn't really matter how you determine the desired locale from the request:
PDF brought to you by Chapter 131: Making the Locale "Sticky" during a User's Session | 445
That's it! Now celebrate by changing the user's locale and seeing that it's sticky throughout the request.
Remember, to get the user's locale, always use the Request::getLocale1 method:
1. http://api.symfony.com/3.1/Symfony/Component/HttpFoundation/Request.html#method_getLocale
PDF brought to you by Chapter 131: Making the Locale "Sticky" during a User's Session | 446
In order to update the language immediately after a user has changed their language preferences, you
need to update the session after an update to the User entity.
PDF brought to you by Chapter 131: Making the Locale "Sticky" during a User's Session | 447
By default, the Symfony Standard Edition uses the global php.ini values for
session.save_handler and session.save_path to determine where to store session data. This
is because of the following configuration:
With this configuration, changing where your session metadata is stored is entirely up to your php.ini
configuration.
However, if you have the following configuration, Symfony will store the session data in files in the cache
directory %kernel.cache_dir%/sessions. This means that when you clear the cache, any current
sessions will also be deleted:
Using a different directory to save session data is one method to ensure that your current sessions aren't
lost when you clear Symfony's cache.
Using a different session save handler is an excellent (yet more complex) method of session
management available within Symfony. See Configuring Sessions and Save Handlers for a discussion
of session save handlers. There are also entries in the cookbook about storing sessions in a relational
database or a NoSQL database.
To change the directory in which Symfony saves session data, you only need change the framework
configuration. In this example, you will change the session directory to app/sessions:
Listing 132-3
PDF brought to you by Chapter 132: Configuring the Directory where Session Files are Saved | 448
PDF brought to you by Chapter 132: Configuring the Directory where Session Files are Saved | 449
If you're integrating the Symfony full-stack Framework into a legacy application that starts the session
with session_start(), you may still be able to use Symfony's session management by using the PHP
Bridge session.
If the application has it's own PHP save handler, you can specify null for the handler_id:
Otherwise, if the problem is simply that you cannot avoid the application starting the session with
session_start(), you can still make use of a Symfony based session save handler by specifying the
save handler as in the example below:
If the legacy application requires its own session save handler, do not override this. Instead set
handler_id: ~. Note that a save handler cannot be changed once the session has been started.
If the application starts the session before Symfony is initialized, the save handler will have already
been set. In this case, you will need handler_id: ~. Only override the save handler if you are sure
the legacy application can use the Symfony save handler without side effects and that the session has
not been started before Symfony is initialized.
PDF brought to you by Chapter 133: Bridge a legacy Application with Symfony Sessions | 450
The default behavior of PHP session is to persist the session regardless of whether the session data has
changed or not. In Symfony, each time the session is accessed, metadata is recorded (session created/last
used) which can be used to determine session age and idle time.
If for performance reasons you wish to limit the frequency at which the session persists, this feature can
adjust the granularity of the metadata updates and persist the session less often while still maintaining
relatively accurate metadata. If other session data is changed, the session will always persist.
You can tell Symfony not to update the metadata "session last updated" time until a certain amount
of time has passed, by setting framework.session.metadata_update_threshold to a value in
seconds greater than zero:
PHP default's behavior is to save the session whether it has been changed or not. When using
framework.session.metadata_update_threshold Symfony will wrap the session handler
(configured at framework.session.handler_id) into the WriteCheckSessionHandler. This
will prevent any session write if the session was not modified.
Be aware that if the session is not written at every request, it may be garbage collected sooner than
usual. This means that your users may be logged out sooner than expected.
PDF brought to you by Chapter 134: Limit Session Metadata Writes | 451
Sessions are automatically started whenever you read, write or even check for the existence of data in the
session. This means that if you need to avoid creating a session cookie for some users, it can be difficult:
you must completely avoid accessing the session.
For example, one common problem in this situation involves checking for flash messages, which are
stored in the session. The following code would guarantee that a session is always started:
Even if the user is not logged in and even if you haven't created any flash messages, just calling the
get() (or even has()) method of the flashBag will start a session. This may hurt your application
performance because all users will receive a session cookie. To avoid this behavior, add a check before
trying to access the flash messages:
PDF brought to you by Chapter 135: Avoid Starting Sessions for Anonymous Users | 452
Sometimes you want a variable to be accessible to all the templates you use. This is possible inside your
app/config/config.yml file:
Listing 136-1 1 # app/config/config.yml
2 twig:
3 # ...
4 globals:
5 ga_tracking: UA-xxxxx-x
PDF brought to you by Chapter 136: How to Inject Variables into all Templates (i.e. global Variables) | 453
The service is not loaded lazily. In other words, as soon as Twig is loaded, your service is instantiated,
even if you never use that global variable.
To define a service as a global Twig variable, prefix the string with @. This should feel familiar, as it's the
same syntax you use in service configuration.
PDF brought to you by Chapter 136: How to Inject Variables into all Templates (i.e. global Variables) | 454
PDF brought to you by Chapter 137: How to Use and Register Namespaced Twig Paths | 455
Now, you can use the same @theme namespace to refer to any template located in the previous three
directories:
PDF brought to you by Chapter 137: How to Use and Register Namespaced Twig Paths | 456
Symfony defaults to Twig for its template engine, but you can still use plain PHP code if you want. Both
templating engines are supported equally in Symfony. Symfony adds some nice features on top of PHP to
make writing templates with PHP more powerful.
You can now render a PHP template instead of a Twig one simply by using the .php extension in the
template name instead of .twig. The controller below renders the index.html.php template:
1. https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/view
PDF brought to you by Chapter 138: How to Use PHP instead of Twig for Templates | 457
Enabling the php and twig template engines simultaneously is allowed, but it will produce an
undesirable side effect in your application: the @ notation for Twig namespaces will no longer be
supported for the render() method:
Decorating Templates
More often than not, templates in a project share common elements, like the well-known header and
footer. In Symfony, this problem is thought about differently: a template can be decorated by another
one.
The index.html.php template is decorated by layout.html.php, thanks to the extend() call:
The AppBundle::layout.html.php notation sounds familiar, doesn't it? It is the same notation
used to reference a template. The :: part simply means that the controller element is empty, so the
corresponding file is directly stored under views/.
Now, have a look at the layout.html.php file:
PDF brought to you by Chapter 138: How to Use PHP instead of Twig for Templates | 458
The base layout already has the code to output the title in the header:
The output() method inserts the content of a slot and optionally takes a default value if the slot is not
defined. And _content is just a special slot that contains the rendered child template.
For large slots, there is also an extended syntax:
PDF brought to you by Chapter 138: How to Use PHP instead of Twig for Templates | 459
The render() method evaluates and returns the content of another template (this is the exact same
method as the one used in the controller).
Here, the AppBundle:Hello:fancy string refers to the fancy action of the Hello controller:
But where is the $view['actions'] array element defined? Like $view['slots'], it's called a
template helper, and the next section tells you more about those.
PDF brought to you by Chapter 138: How to Use PHP instead of Twig for Templates | 460
Listing 138-16 1 <a href="<?php echo $view['router']->path('hello', array('name' => 'Thomas')) ?>">
2 Greet Thomas!
3 </a>
The path() method takes the route name and an array of parameters as arguments. The route name is
the main key under which routes are referenced and the parameters are the values of the placeholders
defined in the route pattern:
Listing 138-18 1 <link href="<?php echo $view['assets']->getUrl('css/blog.css') ?>" rel="stylesheet" type="text/css" />
2
3 <img src="<?php echo $view['assets']->getUrl('images/logo.png') ?>" />
The assets helper's main purpose is to make your application more portable. Thanks to this helper,
you can move the application root directory anywhere under your web root directory without changing
anything in your template's code.
Profiling Templates
By using the stopwatch helper, you are able to time parts of your template and display it on the timeline
of the WebProfilerBundle:
<?php $view['stopwatch']->start('foo') ?>
Listing 138-19
... things that get timed
<?php $view['stopwatch']->stop('foo') ?>
If you use the same name more than once in your template, the times are grouped on the same line
in the timeline.
Output Escaping
When using PHP templates, escape variables whenever they are displayed to the user:
Listing 138-20
<?php echo $view->escape($var) ?>
By default, the escape() method assumes that the variable is outputted within an HTML context. The
second argument lets you change the context. For instance, to output something in a JavaScript script,
use the js context:
Listing 138-21
<?php echo $view->escape($var, 'js') ?>
PDF brought to you by Chapter 138: How to Use PHP instead of Twig for Templates | 461
The main motivation for writing an extension is to move often used code into a reusable class like adding
support for internationalization. An extension can define tags, filters, tests, operators, global variables,
functions, and node visitors.
Creating an extension also makes for a better separation of code that is executed at compilation time and
code needed at runtime. As such, it makes your code faster.
Before writing your own extensions, have a look at the Twig official extension repository1.
To get your custom functionality you must first create a Twig Extension class. As an example you'll create
a price filter to format a given number into price:
1. https://github.com/twigphp/Twig-extensions
2. http://twig.sensiolabs.org/doc/advanced_legacy.html#creating-an-extension
PDF brought to you by Chapter 139: How to Write a custom Twig Extension | 462
Along with custom filters, you can also add custom functions and register global variables.
Learning further
For a more in-depth look into Twig Extensions, please take a look at the Twig extensions documentation3.
3. http://twig.sensiolabs.org/doc/advanced.html#creating-an-extension
PDF brought to you by Chapter 139: How to Write a custom Twig Extension | 463
Usually, when you need to create a page, you need to create a controller and render a template from
within that controller. But if you're rendering a simple template that doesn't need any data passed
into it, you can avoid creating the controller entirely, by using the built-in
FrameworkBundle:Template:template controller.
For example, suppose you want to render a static/privacy.html.twig template, which doesn't
require that any variables are passed to it. You can do this without creating a controller:
Listing 140-3
PDF brought to you by Chapter 140: How to Render a Template without a custom Controller | 464
The maxAge and sharedAge values are used to modify the Response object created in the controller.
For more information on caching, see HTTP Cache.
There is also a private variable (not shown here). By default, the Response will be made public, as long
as maxAge or sharedAge are passed. If set to true, the Response will be marked as private.
PDF brought to you by Chapter 140: How to Render a Template without a custom Controller | 465
If your application needs HTTP authentication, pass the username and password as server variables to
createClient():
Listing 141-1
$client = static::createClient(array(), array(
'PHP_AUTH_USER' => 'username',
'PHP_AUTH_PW' => 'pa$$word',
));
Listing 141-2
$client->request('DELETE', '/post/12', array(), array(), array(
'PHP_AUTH_USER' => 'username',
'PHP_AUTH_PW' => 'pa$$word',
));
When your application is using a form_login, you can simplify your tests by allowing your test
configuration to make use of HTTP authentication. This way you can use the above to authenticate
in tests, but still have your users log in via the normal form_login. The trick is to include the
http_basic key in your firewall, along with the form_login key:
Listing 141-3 1 # app/config/config_test.yml
2 security:
3 firewalls:
4 your_firewall_name:
5 http_basic: ~
PDF brought to you by Chapter 141: How to Simulate HTTP Authentication in a Functional Test | 466
Authenticating requests in functional tests might slow down the suite. It could become an issue especially
when form_login is used, since it requires additional requests to fill in and submit the form.
One of the solutions is to configure your firewall to use http_basic in the test environment as
explained in How to Simulate HTTP Authentication in a Functional Test. Another way would be to create
a token yourself and store it in a session. While doing this, you have to make sure that an appropriate
cookie is sent with a request. The following example demonstrates this technique:
PDF brought to you by Chapter 142: How to Simulate Authentication with a Token in a Functional Test | 467
The technique described in How to Simulate HTTP Authentication in a Functional Test is cleaner and
therefore the preferred way.
PDF brought to you by Chapter 142: How to Simulate Authentication with a Token in a Functional Test | 468
If you need to simulate an interaction between different clients (think of a chat for instance), create
several clients:
This works except when your code maintains a global state or if it depends on a third-party library that
has some kind of global state. In such a case, you can insulate your clients:
Insulated clients transparently execute their requests in a dedicated and clean PHP process, thus avoiding
any side-effects.
As an insulated client is slower, you can keep one client in the main process, and insulate the other
ones.
PDF brought to you by Chapter 143: How to Test the Interaction of several Clients | 469
It's highly recommended that a functional test only tests the Response. But if you write functional tests
that monitor your production servers, you might want to write tests on the profiling data as it gives you
a great way to check various things and enforce some metrics.
The Symfony Profiler gathers a lot of data for each request. Use this data to check the number of database
calls, the time spent in the framework, etc. But before writing assertions, enable the profiler and check
that the profiler is indeed available (it is enabled by default in the test environment):
If a test fails because of profiling data (too many DB queries for instance), you might want to use the Web
Profiler to analyze the request after the tests finish. It's easy to achieve if you embed the token in the error
message:
PDF brought to you by Chapter 144: How to Use the Profiler in a Functional Test | 470
The profiler store can be different depending on the environment (especially if you use the SQLite
store, which is the default configured one).
The profiler information is available even if you insulate the client or if you use an HTTP layer for
your tests.
Read the API for built-in data collectors to learn more about their interfaces.
In this way only tests that call $client->enableProfiler() will collect data.
PDF brought to you by Chapter 144: How to Use the Profiler in a Functional Test | 471
If your code interacts with the database, e.g. reads data from or stores data into it, you need to adjust
your tests to take this into account. There are many ways how to deal with this. In a unit test, you can
create a mock for a Repository and use it to return expected objects. In a functional test, you may need
to prepare a test database with predefined values to ensure that your test always has the same data to
work with.
If you want to test your queries directly, see How to Test Doctrine Repositories.
It is possible (and a good idea) to inject your repository directly by registering your repository as a
factory service. This is a little bit more work to setup, but makes testing easier as you only need to
mock the repository.
PDF brought to you by Chapter 145: How to Test Code that Interacts with the Database | 472
Since the ObjectManager gets injected into the class through the constructor, it's easy to pass a mock
object within a test:
In this example, you are building the mocks from the inside out, first creating the employee which gets
returned by the Repository, which itself gets returned by the EntityManager. This way, no real class
is involved in testing.
PDF brought to you by Chapter 145: How to Test Code that Interacts with the Database | 473
Make sure that your database runs on localhost and has the defined database and user credentials set up.
PDF brought to you by Chapter 145: How to Test Code that Interacts with the Database | 474
Unit testing Doctrine repositories in a Symfony project is not recommended. When you're dealing with
a repository, you're really dealing with something that's meant to be tested against a real database
connection.
Fortunately, you can easily test your queries against a real database, as described below.
Functional Testing
If you need to actually execute a query, you will need to boot the kernel to get a valid connection. In this
case, you'll extend the KernelTestCase, which makes all of this quite easy:
PDF brought to you by Chapter 146: How to Test Doctrine Repositories | 475
PDF brought to you by Chapter 146: How to Test Doctrine Repositories | 476
Sometimes when running tests, you need to do additional bootstrap work before running those tests. For
example, if you're running a functional test and have introduced a new translation resource, then you will
need to clear your cache before running those tests. This cookbook covers how to do that.
First, add the following file:
Now, you can define in your phpunit.xml.dist file which environment you want the cache to be
cleared:
This now becomes an environment variable (i.e. $_ENV) that's available in the custom bootstrap file
(tests.bootstrap.php).
PDF brought to you by Chapter 147: How to Customize the Bootstrap Process before Running Tests | 477
When a new patch version is released (only the last number changed), it is a release that only contains
bug fixes. This means that upgrading to a new patch version is really easy:
That's it! You should not encounter any backwards-compatibility breaks or need to change anything else
in your code. That's because when you started your project, your composer.json included Symfony
using a constraint like 2.6.*, where only the last version number will change when you update.
It is recommended to update to a new patch version as soon as possible, as important bugs and
security leaks may be fixed in these new releases.
Beware, if you have some unspecific version constraints2 in your composer.json (e.g. dev-
master), this could upgrade some non-Symfony libraries to new versions that contain backwards-
compatibility breaking changes.
1. https://getcomposer.org/doc/01-basic-usage.md#package-versions
2. https://getcomposer.org/doc/01-basic-usage.md#package-versions
PDF brought to you by Chapter 148: Upgrading a Patch Version (e.g. 2.6.0 to 2.6.1) | 478
If you're upgrading a minor version (where the middle number changes), then you should not encounter
significant backwards compatibility changes. For details, see the Symfony backwards compatibility
promise.
However, some backwards-compatibility breaks are possible and you'll learn in a second how to prepare
for them.
There are two steps to upgrading a minor version:
1. Update the Symfony library via Composer;
2. Update your code to work with the new version.
Listing 149-1 1 {
2 "...": "...",
3
4 "require": {
5 "symfony/symfony": "2.6.*",
6 },
7 "...": "...",
8 }
Dependency Errors
If you get a dependency error, it may simply mean that you need to upgrade other Symfony dependencies
too. In that case, try the following command:
PDF brought to you by Chapter 149: Upgrading a Minor Version (e.g. 2.5.3 to 2.6.1) | 479
Beware, if you have some unspecific version constraints2 in your composer.json (e.g. dev-
master), this could upgrade some non-Symfony libraries to new versions that contain backwards-
compatibility breaking changes.
1. https://getcomposer.org/doc/01-basic-usage.md#package-versions
2. https://getcomposer.org/doc/01-basic-usage.md#package-versions
3. https://github.com/symfony/symfony/blob/2.7/UPGRADE-2.7.md
4. https://github.com/symfony/symfony
PDF brought to you by Chapter 149: Upgrading a Minor Version (e.g. 2.5.3 to 2.6.1) | 480
Every few years, Symfony releases a new major version release (the first number changes). These releases
are the trickiest to upgrade, as they are allowed to contain BC breaks. However, Symfony tries to make
this upgrade process as smooth as possible.
This means that you can update most of your code before the major release is actually released. This is
called making your code future compatible.
There are a couple of steps to upgrading a major version:
1. Make your code deprecation free;
2. Update to the new major version via Composer;
3. Update your code to work with the new version.
PDF brought to you by Chapter 150: Upgrading a Major Version (e.g. 2.7.0 to 3.0.0) | 481
Deprecations in PHPUnit
When you run your tests using PHPUnit, no deprecation notices are shown. To help you here, Symfony
provides a PHPUnit bridge. This bridge will show you a nice summary of all deprecation notices at the
end of the test report.
All you need to do is install the PHPUnit bridge:
Once you fixed them all, the command ends with 0 (success) and you're done!
PDF brought to you by Chapter 150: Upgrading a Major Version (e.g. 2.7.0 to 3.0.0) | 482
Dependency Errors
If you get a dependency error, it may simply mean that you need to upgrade other Symfony dependencies
too. In that case, try the following command:
This updates symfony/symfony and all packages that it depends on, which will include several other
packages. By using tight version constraints in composer.json, you can control what versions each
library upgrades to.
If this still doesn't work, your composer.json file may specify a version for a library that is not
compatible with the newer Symfony version. In that case, updating that library to a newer version in
composer.json may solve the issue.
Or, you may have deeper issues where different libraries depend on conflicting versions of other libraries.
Check your error message to debug.
PDF brought to you by Chapter 150: Upgrading a Major Version (e.g. 2.7.0 to 3.0.0) | 483
Beware, if you have some unspecific version constraints2 in your composer.json (e.g. dev-
master), this could upgrade some non-Symfony libraries to new versions that contain backwards-
compatibility breaking changes.
1. https://getcomposer.org/doc/01-basic-usage.md#package-versions
2. https://getcomposer.org/doc/01-basic-usage.md#package-versions
PDF brought to you by Chapter 150: Upgrading a Major Version (e.g. 2.7.0 to 3.0.0) | 484
Symfony 3 was released on November 2015. Although this version doesn't contain any new feature, it
removes all the backwards compatibility layers included in the previous 2.8 version. If your bundle uses
any deprecated feature and it's published as a third-party bundle, applications upgrading to Symfony 3
will no longer be able to use it.
These constraints prevent the bundle from using Symfony 3 components, so it makes it impossible to
install it in a Symfony 3 based application. This issue is very easy to solve thanks to the flexibility of
Composer dependencies constraints. Just replace ~2.N by ~2.N|~3.0 (or ^2.N by ^2.N|~3.0).
The above example can be updated to work with Symfony 3 as follows:
Listing 151-2 1 {
2 "require": {
3 "symfony/framework-bundle": "~2.7|~3.0",
4 "symfony/finder": "~2.7|~3.0",
5 "symfony/validator": "~2.7|~3.0"
6 }
7 }
PDF brought to you by Chapter 151: Upgrading a Third-Party Bundle for a Major Symfony Version | 485
Then, run your test suite and look for the deprecation list displayed after the PHPUnit test report:
Fix the reported deprecations, run the test suite again and repeat the process until no deprecation usage
is reported.
Useful Resources
There are several resources that can help you detect, understand and fix the use of deprecated features:
Official Symfony Guide to Upgrade from 2.x to 3.02
The full list of changes required to upgrade to Symfony 3.0 and grouped by component.
SensioLabs DeprecationDetector3
It runs a static code analysis against your project's source code to find usages of deprecated methods,
classes and interfaces. It works for any PHP application, but it includes special detectors for
Symfony applications, where it can also detect usages of deprecated services.
1. https://github.com/symfony/phpunit-bridge
2. https://github.com/symfony/symfony/blob/2.8/UPGRADE-3.0.md
3. https://github.com/sensiolabs-de/deprecation-detector
4. https://github.com/umpirsky/Symfony-Upgrade-Fixer
PDF brought to you by Chapter 151: Upgrading a Third-Party Bundle for a Major Symfony Version | 486
If your operating system doesn't support symbolic links, you'll need to copy your local bundle directory
into the appropriate directory inside vendor/.
script: phpunit
Updating your Code to Support Symfony 2.x and 3.x at the Same Time
The real challenge of adding Symfony 3 support for your bundles is when you want to support both
Symfony 2.x and 3.x simultaneously using the same code. There are some edge cases where you'll need
to deal with the API differences.
Before diving into the specifics of the most common edge cases, the general recommendation is to not
rely on the Symfony Kernel version to decide which code to use:
PDF brought to you by Chapter 151: Upgrading a Third-Party Bundle for a Major Symfony Version | 487
Instead of checking the Symfony Kernel version, check the version of the specific component. For
example, the OptionsResolver API changed in its 2.6 version by adding a setDefined() method. The
recommended check in this case would be:
PDF brought to you by Chapter 151: Upgrading a Third-Party Bundle for a Major Symfony Version | 488
You can create a custom constraint by extending the base constraint class, Constraint1. As an example
you're going to create a simple validator that checks if a string contains only alphanumeric characters.
The @Annotation annotation is necessary for this new constraint in order to make it available for
use in classes via annotations. Options for your constraint are represented as public properties on
the constraint class.
1. http://api.symfony.com/3.1/Symfony/Component/Validator/Constraint.html
2. http://api.symfony.com/3.1/Symfony/Component/Validator/Constraint.html
PDF brought to you by Chapter 152: How to Create a custom Validation Constraint | 489
In other words, if you create a custom Constraint (e.g. MyConstraint), Symfony will automatically
look for another class, MyConstraintValidator when actually performing the validation.
The validator class is also simple, and only has one required method validate():
Inside validate, you don't need to return a value. Instead, you add violations to the validator's
context property and a value will be considered valid if it causes no violations. The buildViolation
method takes the error message as its argument and returns an instance of
ConstraintViolationBuilderInterface3. The addViolation method call finally adds the
violation to the context.
3. http://api.symfony.com/3.1/Symfony/Component/Validator/Violation/ConstraintViolationBuilderInterface.html
PDF brought to you by Chapter 152: How to Create a custom Validation Constraint | 490
If your constraint contains options, then they should be public properties on the custom Constraint class
you created earlier. These options can be configured like options on core Symfony constraints.
As mentioned above, Symfony will automatically look for a class named after the constraint, with
Validator appended. You can override this in your constraint class:
Listing 152-6
public function validatedBy()
{
return 'Fully\Qualified\ConstraintValidator\Class\Name'; // or 'alias_name' if provided
}
Make sure to use the 'alias_name' when you have configured your validator as a service. Otherwise your
validator class will be simply instantiated without your dependencies.
With this, the validator validate() method gets an object as its first argument:
Note that a class constraint validator is applied to the class itself, and not to the property:
PDF brought to you by Chapter 152: How to Create a custom Validation Constraint | 491
PDF brought to you by Chapter 152: How to Create a custom Validation Constraint | 492
Sometimes, you may want to display constraint validation error messages differently based on some rules.
For example, you have a registration form for new users where they enter some personal information and
choose their authentication credentials. They would have to choose a username and a secure password,
but providing bank account information would be optional. Nonetheless, you want to make sure that
these optional fields, if entered, are still valid, but display their errors differently.
The process to achieve this behavior consists of two steps:
1. Apply different error levels to the validation constraints;
2. Customize your error messages depending on the configured error level.
PDF brought to you by Chapter 153: How to Handle Different Error Levels | 493
For example, you can leverage this to customize the form_errors block so that the severity is added as
an additional HTML class:
For more information on customizing form rendering, see How to Customize Form Rendering.
1. http://api.symfony.com/3.1/Symfony/Component/Validator/ConstraintViolation.html#method_getConstraint
PDF brought to you by Chapter 153: How to Handle Different Error Levels | 494
Sometimes you need advanced logic to determine the validation groups. If they can't be determined by
a simple callback, you can use a service. Create a service that implements __invoke which accepts a
FormInterface as a parameter.
Listing 154-1 1 // src/AppBundle/Validation/ValidationGroupResolver.php
2 namespace AppBundle\Validation;
3
4 use Symfony\Component\Form\FormInterface;
5
6 class ValidationGroupResolver
7 {
8 private $service1;
9
10 private $service2;
11
12 public function __construct($service1, $service2)
13 {
14 $this->service1 = $service1;
15 $this->service2 = $service2;
16 }
17
18 /**
19 * @param FormInterface $form
20 * @return array
21 */
22 public function __invoke(FormInterface $form)
23 {
24 $groups = array();
25
26 // ... determine which groups to apply and return an array
27
28 return $groups;
29 }
30 }
Then in your form, inject the resolver and set it as the validation_groups.
Listing 154-2
PDF brought to you by Chapter 154: How to Dynamically Configure Validation Groups | 495
This will result in the form validator invoking your group resolver to set the validation groups returned
when validating.
PDF brought to you by Chapter 154: How to Dynamically Configure Validation Groups | 496
Since PHP 5.4 the CLI SAPI comes with a built-in web server1. It can be used to run your PHP applications
locally during development, for testing or for application demonstrations. This way, you don't have to
bother configuring a full-featured web server such as Apache or Nginx.
The built-in web server is meant to be run in a controlled environment. It is not designed to be used
on public networks.
This starts the web server at localhost:8000 in the background that serves your Symfony application.
By default, the web server listens on port 8000 on the loopback device. You can change the socket passing
an IP address and a port as a command-line argument:
You can use the --force option to force the web server start if the process wasn't correctly stopped
(without using the server:stop command).
1. http://www.php.net/manual/en/features.commandline.webserver.php
PDF brought to you by Chapter 155: How to Use PHP's built-in Web Server | 497
The first command shows if your Symfony application will be server through localhost:8000,
the second one does the same for 192.168.0.1:8080.
Some systems do not support the server:start command, in these cases you can execute the
server:run command. This command behaves slightly different. Instead of starting the server in
the background, it will block the current terminal until you terminate it (this is usually done by
pressing Ctrl and C).
You should NEVER listen to all interfaces on a computer that is directly accessible from the
Internet. The built-in web server is not designed to be used on public networks.
Command Options
The built-in web server expects a "router" script (read about the "router" script on php.net2) as an
argument. Symfony already passes such a router script when the command is executed in the prod or
in the dev environment. Use the --router option in any other environment or to use another router
script:
If your application's document root differs from the standard directory layout, you have to pass the
correct location using the --docroot option:
2. http://php.net/manual/en/features.commandline.webserver.php#example-411
PDF brought to you by Chapter 155: How to Use PHP's built-in Web Server | 498
PDF brought to you by Chapter 155: How to Use PHP's built-in Web Server | 499
Setting up a controller to act as a SOAP server is simple with a couple tools. You must, of course, have
the PHP SOAP1 extension installed. As the PHP SOAP extension can not currently generate a WSDL, you
must either create one from scratch or use a 3rd party generator.
There are several SOAP server implementations available for use with PHP. Zend SOAP2 and
NuSOAP3 are two examples. Although the PHP SOAP extension is used in these examples, the
general idea should still be applicable to other implementations.
SOAP works by exposing the methods of a PHP object to an external entity (i.e. the person using the
SOAP service). To start, create a class - HelloService - which represents the functionality that you'll
expose in your SOAP service. In this case, the SOAP service will allow the client to call a method called
hello, which happens to send an email:
Listing 156-1 1 // src/Acme/SoapBundle/Services/HelloService.php
2 namespace Acme\SoapBundle\Services;
3
4 class HelloService
5 {
6 private $mailer;
7
8 public function __construct(\Swift_Mailer $mailer)
9 {
10 $this->mailer = $mailer;
11 }
12
13 public function hello($name)
14 {
15
16 $message = \Swift_Message::newInstance()
17 ->setTo('[email protected]')
1. http://php.net/manual/en/book.soap.php
2. http://framework.zend.com/manual/current/en/modules/zend.soap.server.html
3. http://sourceforge.net/projects/nusoap
PDF brought to you by Chapter 156: How to Create a SOAP Web Service in a Symfony Controller | 500
Next, you can train Symfony to be able to create an instance of this class. Since the class sends an email,
it's been designed to accept a Swift_Mailer instance. Using the Service Container, you can configure
Symfony to construct a HelloService object properly:
Take note of the calls to ob_start() and ob_get_clean(). These methods control output buffering4
which allows you to "trap" the echoed output of $server->handle(). This is necessary because
Symfony expects your controller to return a Response object with the output as its "content". You must
also remember to set the "Content-Type" header to "text/xml", as this is what the client will expect. So,
you use ob_start() to start buffering the STDOUT and use ob_get_clean() to dump the echoed
output into the content of the Response and clear the output buffer. Finally, you're ready to return the
Response.
Below is an example calling the service using a NuSOAP5 client. This example assumes that the
indexAction in the controller above is accessible via the route /soap:
Listing 156-4
$client = new \Soapclient('http://example.com/app.php/soap?wsdl');
4. http://php.net/manual/en/book.outcontrol.php
5. http://sourceforge.net/projects/nusoap
PDF brought to you by Chapter 156: How to Create a SOAP Web Service in a Symfony Controller | 501
PDF brought to you by Chapter 156: How to Create a SOAP Web Service in a Symfony Controller | 502
Though this entry is specifically about Git, the same generic principles will apply if you're storing
your project in Subversion.
Once you've read through Create your First Page in Symfony and become familiar with using Symfony,
you'll no-doubt be ready to start your own project. In this cookbook article, you'll learn the best way to
start a new Symfony project that's stored using the Git1 source control management system.
1. http://git-scm.com/
PDF brought to you by Chapter 157: How to Create and Store a Symfony Project in Git | 503
You may also want to create a .gitignore file that can be used system-wide. This allows
you to exclude files/folders for all your projects that are created by your IDE or operating
system. For details, see GitHub .gitignore2.
At this point, you have a fully-functional Symfony project that's correctly committed to Git. You can
immediately begin development, committing the new changes to your Git repository.
You can continue to follow along with the Create your First Page in Symfony chapter to learn more about
how to configure and develop inside your application.
The Symfony Standard Edition comes with some example functionality. To remove the sample code,
follow the instructions in the "How to Remove the AcmeDemoBundle" article.
If you want to add a new package to your application, run the composer require command:
2. https://help.github.com/articles/ignoring-files
3. https://getcomposer.org/
PDF brought to you by Chapter 157: How to Create and Store a Symfony Project in Git | 504
Upgrading Symfony
Since Symfony is just a group of third-party libraries and third-party libraries are entirely controlled
through composer.json and composer.lock, upgrading Symfony means simply upgrading
each of these files to match their state in the latest Symfony Standard Edition.
Of course, if you've added new entries to composer.json, be sure to replace only the original parts
(i.e. be sure not to also delete any of your custom entries).
4. https://getcomposer.org/
5. https://github.com/
6. https://bitbucket.org/
7. https://en.wikipedia.org/wiki/Comparison_of_open-source_software_hosting_facilities
8. http://git-scm.com/book/en/Git-Basics-Getting-a-Git-Repository
9. https://github.com/sitaramc/gitolite
PDF brought to you by Chapter 157: How to Create and Store a Symfony Project in Git | 505
This entry is specifically about Subversion, and based on principles found in How to Create and Store
a Symfony Project in Git.
Once you've read through Create your First Page in Symfony and become familiar with using Symfony,
you'll no-doubt be ready to start your own project. The preferred method to manage Symfony projects is
using Git1 but some prefer to use Subversion2 which is totally fine!. In this cookbook article, you'll learn
how to manage your project using SVN3 in a similar manner you would do with Git4.
This is a method to tracking your Symfony project in a Subversion repository. There are several ways
to do and this one is simply one that works.
1. http://git-scm.com/
2. http://subversion.apache.org/
3. http://subversion.apache.org/
4. http://git-scm.com/
PDF brought to you by Chapter 158: How to Create and Store a Symfony Project in Subversion | 506
3. Now, set the ignore rules. Not everything should be stored in your Subversion repository. Some
files (like the cache) are generated and others (like the database configuration) are meant to be
customized on each machine. This makes use of the svn:ignore property, so that specific files
can be ignored.
4. The rest of the files can now be added and committed to the project:
That's it! Since the app/config/parameters.yml file is ignored, you can store machine-specific
settings like database passwords here without committing them. The parameters.yml.dist file is
committed, but is not read by Symfony. And by adding any new keys you need to both files, new
developers can quickly clone the project, copy this file to parameters.yml, customize it, and start
developing.
At this point, you have a fully-functional Symfony project stored in your Subversion repository. The
development can start with commits in the Subversion repository.
You can continue to follow along with the Create your First Page in Symfony chapter to learn more about
how to configure and develop inside your application.
5. http://svnbook.red-bean.com/
6. http://code.google.com/hosting/
PDF brought to you by Chapter 158: How to Create and Store a Symfony Project in Subversion | 507
If you want to add a new package to your application, run the composer require command:
Upgrading Symfony
Since Symfony is just a group of third-party libraries and third-party libraries are entirely controlled
through composer.json and composer.lock, upgrading Symfony means simply upgrading
each of these files to match their state in the latest Symfony Standard Edition.
Of course, if you've added new entries to composer.json, be sure to replace only the original parts
(i.e. be sure not to also delete any of your custom entries).
7. https://getcomposer.org/
8. https://getcomposer.org/
PDF brought to you by Chapter 158: How to Create and Store a Symfony Project in Subversion | 508
• Self hosting: create your own repository and access it either through the filesystem or the network.
To help in this task you can read Version Control with Subversion.
• Third party hosting: there are a lot of serious free hosting solutions available like GitHub11, Google
code12, SourceForge13 or Gna14. Some of them offer Git hosting as well.
9. http://git-scm.com/
10. http://subversion.apache.org/
11. https://github.com/
12. http://code.google.com/hosting/
13. http://sourceforge.net/
14. http://gna.org/
PDF brought to you by Chapter 158: How to Create and Store a Symfony Project in Subversion | 509
In order to develop a Symfony application, you might want to use a virtual development environment
instead of the built-in server or WAMP/LAMP. Homestead1 is an easy-to-use Vagrant2 box to get a virtual
environment up and running quickly.
Due to the amount of filesystem operations in Symfony (e.g. updating cache files and writing to log
files), Symfony can slow down significantly. To improve the speed, consider overriding the cache and
log directories to a location outside the NFS share (for instance, by using sys_get_temp_dir3).
You can read this blog post4 for more tips to speed up Symfony on Vagrant.
1. http://laravel.com/docs/homestead
2. https://www.vagrantup.com/
3. http://php.net/manual/en/function.sys-get-temp-dir.php
4. http://www.whitewashing.de/2013/08/19/speedup_symfony2_on_vagrant_boxes.html
5. http://laravel.com/docs/homestead#installation-and-setup
PDF brought to you by Chapter 159: Using Symfony with Homestead/Vagrant | 510
The type option tells Homestead to use the Symfony nginx configuration.
At last, edit the hosts file on your local machine to map symfony-demo.dev to 192.168.10.10
(which is the IP used by Homestead):
Listing 159-3
# /etc/hosts (unix) or C:\Windows\System32\drivers\etc\hosts (Windows)
192.168.10.10 symfony-demo.dev
Now, navigate to http://symfony-demo.dev in your web browser and enjoy developing your
Symfony application!
To learn more features of Homestead, including Blackfire Profiler integration, automatic creation of MySQL
databases and more, read the Daily Usage6 section of the Homestead documentation.
6. http://laravel.com/docs/5.1/homestead#daily-usage
PDF brought to you by Chapter 159: Using Symfony with Homestead/Vagrant | 511