Symfony2 deployment checklist

Symfony2 is a flexible and powerful framework... that ends to a quite slow execution time. Of course, the prod environment is much faster than you usual dev environment, but that's not enough.

In order to boost your application in production, it is very recommended to install a PHP Accelerator like APC.

On a dedicated server

On Linux

To install APC on a debian-like linux distribution, simply execute:

apt-get install php-apc

Adapt the command to your distribution.

On Windows

Uncomment the concerned line in your php.ini:

extension=php_apc.dll

In all cases

After the installation, you have to activate the extension. This is done by adding this line at the end of your php.ini:

[APC]
apc.enabled=1

On a shared hosting

If you are on a shared hosting, you must not have an SSH access. In that case, the best option is to contact the administrator. Say him it's better for its servers to have a PHP Accelerator installed.

Ensure that your production server is using a customized secret key. Check it in your app/config/parameters.yml file:

secret:  please_use_a_real_secret_here

The default secret is not truly secret, you must change it by a random one.

Before deploying your application, you have to test that your production server is ready to run it.

To test your server you have the choice between three methods:

  1. Manually run php app/check.php and see what to resolve;
  2. Access the config.php with your browser, located in the web directory;
  3. If you don't have access to your server yet (you're planning to buy it), you can still go to the documentation reference page

You don't want the Symfony logo to appear in your visitors' browser. That's why you must replace the default favicon with your own before deployment.

Just replace the file web/favicon.ico.

To make favicons, you can:

  • Use online tools like favicon.cc in order to generate .ico files;
  • Use a PNG icon. In that case you must define a link in your HTML: <link rel="icon" type="image/png" href="yourFavIcon.png">

You can speed up your application by letting Apache handle routes directly, rather than using Symfony2 Router component. There are 2 steps.

1. Dump your routes for Apache

Symfony2 embeds a command to dump all your routes in a format understandable by Apache mod_rewrite:

php app/console router:dump-apache -e=prod --no-debug

And then you can add these rewrite rules in your web/.htaccess, or directly in the directory configuration in your httpd.conf, it's up to you.

RewriteEngine On
# And here the output of the previous command

Of course, be sure to activate the module mod_rewrite in your Apache configuration.

2. Tell the router you're using Apache

Now you have to tell Symfony2 that you want to use another Matcher than the default one. Add this to your config_prod.yml:

parameters:
    router.options.matcher.cache_class: ~ # disable router cache
    router.options.matcher_class: Symfony\Component\Routing\Matcher\ApacheUrlMatcher

That's it!

The component generating the logs of your application is essential for managing your web platform. Symfony2 ships Monolog component dedicated to this task.

The default configuration is OK for the development environment, but it isn't enough for production. The changes to do deal with two aims:

  • Send errors to the website administrator by email (logs of "error" level);
  • Record all authentications, as these are logs of "info" level, that are not logged by default.

Let's configure Monolog thanks to config_prod.yml:

monolog:
    handlers:
        main:
            type:               fingers_crossed
            action_level:       error
            handler:            grouped
        grouped:
            type:               group
            members:            [streamed, swift]
        streamed:
            type:               stream
            path:               "%kernel.logs_dir%/%kernel.environment%.log"
            level:              debug
        swift:
            type:               swift_mailer
            from_email:         FQN@foo.com
            to_email:           webmaster@company.com
            subject:            "OOps"
            level:              debug
        login:
            type:               stream
            path:               "%kernel.logs_dir%/auth.log"
            level:              info
            channels:           security

That's it!

In production environment, it is highly recommended to use Doctrine cache. There are two types of cache.

Query and Metadata Cache

  • The Query Cache aims at caching the transformation of a DQL query to its SQL counterpart. In production, your requests won't change a bit so why not caching them.
  • The Metadata Cache aims at caching the parsed metadata from a few different sources like YAML, XML, Annotations, etc.

Caching these information is done by setting a few parameters in your production configuration file config_prod.yml:

doctrine:
    orm:
        auto_mapping: true
        query_cache_driver:    apc
        metadata_cache_driver: apc

Result Cache

The result of your queries can be cached in order not to query the database again and again. As this is a finer tuning, you can't parameter it application-wide. You can only set the driver as previously:

doctrine:
    orm:
        auto_mapping: true
        result_cache_driver: apc

But then you have to explicitly use or not use this cache in all your majors queries. This is done by setting the name and lifetime of each query cache. See Doctrine Result Cache documentation.

Ensure that Doctrine is actually using your APC cache

You configured APC as a cache driver for Doctrine, that's great. But the problem is that the Dependency Injection Container is generated through the Command Line Interface, when cache is built from there. And if you do not have apc.enable_cli = 1 in your php.ini, then the DIC will use FileCacheReader instead. That's not what we want.

To check if you are really using APC cache, have a look at your app/cache/prod/appProdProjectContainer.php. You should see the following:

protected function getDoctrine_Orm_DefaultEntityManagerService()
{
    $a = $this->get('annotation_reader');
    $b = new \Doctrine\Common\Cache\ApcCache();
    $b->setNamespace('sf2orm_default_5cdc3404d84577b226d7772ca9818908');
    $c = new \Doctrine\Common\Cache\ApcCache();
    $c->setNamespace('sf2orm_default_5cdc3404d84577b226d7772ca9818908');

    // ...

    $g = new \Doctrine\ORM\Configuration();
    $g->setMetadataCacheImpl($b);
    $g->setQueryCacheImpl($c);

    // ...
}

If you can't find \Doctrine\Common\Cache\ApcCache, then you are not using APC.

By default, Composer dumps an autoloader that isn't fully optimized. Indeed, when you have many classes, the autoloader can take up a substantial time..

In production environment, you want the autoloader to be fast. Composer can dump an optimized autoloader that converts PSR-0 packages into classmap ones, which improves performance.

To use the optimized autoloader, just add the --optimize option to the dump-autoload Composer command:

php composer.phar dump-autoload --optimize

Of course, this option makes the command a bit longer. That's why it isn't done by default.

Symfony2 Form Component automatically embeds and validate CSRF tokens for you.

Be sure to customize your secret key, which is used for the token generation, in your app/config/parameters.yml file:

secret:  please_use_a_real_secret_here

Moreover, you could customize the token on a form-by-form basis, which is even better. You can do that by defining an intention option in your forms:

namespace Acme\DemoBundle\Form;

use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class TaskType extends AbstractType
{
    // ...

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            // a unique key to help generate the secret token
            'intention'  => 'task_form',
        ));
    }

    // ...
}

If you are used to the error pages of the "dev" environment, it is hopefully not the case of your future visitors on your production website. So, you must customize the different error pages, in order to integrate them with your own layout.

Hopefully, customizing error pages is really easy. Their views are located in the TwigBundle bundle, that gives you the key to override them by yours. The views you have to create are: Exception/errorXXX.html.twig, where XXX is the number of the encountered error. It is highly recommended to customize at least errors 404 and 500.

To use your own views, there are two possible solutions:

  1. You can create a new bundle, that extends TwigBundle, and put your views in the directory Resources/views/Exception/errorXXX.html.twig. That enable you to reuse them in different projects as it is a reusable bundle;
  2. You can also simply put your views in the directory app/Resources/TwigBundle/views/Exception/errorXXX.html.twig. If your views are specific to the current project, it avoid you to create a new bundle just for that.