Symfony-check

Check if your symfony application is ready for deployment

Tags :

  • all
  • high
  • design
  • security
  • performance
"Oops! Page Not Found" error page
  • high
  • design

When the URL entered by the user doesn't match any route or when an sfError404Exception occurs, symfony tries to access to the default/error404 action.

Oops! Page Not Found

To customize the "Oops! Page Not Found" page, add the executeError404() method in apps/frontend/modules/default/actions/actions.class.php:

/**
 * Error page for page not found (404) error
 *
 */
public function executeError404()
{
}

Then create the apps/frontend/modules/default/templates/error404Success.php template.

If you want, you can grab the original file and modify it.

Display the "Oops! Page Not Found" page to test it

In an action that works fine, add on top :

throw new sfError404Exception('Only for test, don\'t forget to remove it!');

Test the related page in the production environement.

If your default module is protected

Make sure to un-secure the error404 action in apps/frontend/modules/default/config/security.yml:

error404:
  is_secure: false

Using an other module

If you want to use an other module, modify apps/frontend/config/settings.yml:

all:
  .actions:
    error_404_module: foo   # To be called when a 404 error is raised
    error_404_action: bar   # Or when the requested URL doesn't match any route

Clear your cache.

php symfony cc

top

"Oops! An Error Occurred" error page
  • design

This is the page displayed in the production environment when symfony catches an error.

Oops! An Error Occurred

Symfony looks for a error.html.php file and let you choose where you prefer to put it:

  1. At mySfProject/apps/frontend/config/error/error.html.php

    Useful if you want to have a different version for each application

  2. At mySfProject/config/error/error.html.php

    The main configuration folder of your project

If you want, you can grab the original file and modify it.

Display the "Oops! An Error Occurred" page to test it

In an action that works fine, add on top:

throw new Exception('Only for test, don\'t forget to remove it!');

Test the related page in the production environement.

top

"Login Required" error page
  • high
  • design

When a non authenticated user tries to access a page defined as secure in security.yml, symfony tries to access to the default/login action.

Login Required

To customize the "Login Required" page, add the executeLogin() method in the apps/frontend/modules/default/actions/actions.class.php file:

/**
 * Warning page for restricted area - requires credentials
 *
 */
public function executeLogin()
{
}

Then create the apps/frontend/modules/default/templates/loginSuccess.php template.

If you want, you can grab the original file and modify it.

Display the "Login Required" page to test it

  1. Pass in the production environment
  2. Logout: Use a logout link, or if you don't have one yet, go to your browser preferences and remove all the cookies related to your application domain.
  3. Try to access to a secure page.

Using an other module

If you want to use an other module, modify apps/frontend/config/settings.yml:

all:
  .actions:
    login_module: foo   # To be called when a non-authenticated user
    login_action: bar   # Tries to access a secure page

Make sure that in your module, the signin action is un-secure. In apps/frontend/modules/foo/config/security.yml:

bar:
  is_secure: false

Clear your cache.

php symfony cc

Read the related symfony documentation

top

"Credentials Required" error page
  • design

When a user doesn't have the credentials required for an action, symfony tries to access to the default/secure action.

Credentials Required

To customize the "Credentials Required" page, add the executeLogin() method in the apps/frontend/modules/default/actions/actions.class.php file:

/**
 * Warning page for restricted area - requires login
 *
 */
public function executeSecure()
{
}

Then create the apps/frontend/modules/default/templates/secureSuccess.php template.

If you want, you can grab the original file and modify it.

Display the "Credentials Required" page to test it

  1. Pass in the production environement
  2. Login, but not as super admin.
  3. In one of your modules, add in apps/frontend/modules/yourModule/config/security.yml:

    all:
      credentials: [ aCredentialYouDontHave ]
    
  4. Clear cache
  5. Try to access to one of the module actions

Using an other module

If you want to use an other module, modify apps/frontend/config/settings.yml:

all:
  .actions:
    secure_module: foo   # To be called when a user doesn't have
    secure_action: bar   # The credentials required for an action

Clear your cache.

php symfony cc

top

"This Module is Unavailable" error page
  • design

When a user requests a module declared as disabled in /apps/frontend/modules/mymodule/config/module.yml, symfony tries to access to the default/disabled action.

This Module is Unavailable

To customize the "This Module is Unavailable" page, add the executeDisabled() method in the apps/frontend/modules/default/actions/actions.class.php file:

/**
 * Module disabled
 *
 */
public function executeDisabled()
{
}

Then create the apps/frontend/modules/default/templates/disabledSuccess.php template.

If you want, you can grab the original file and modify it.

Display the "This Module is Unavailable" page to test it

  1. Pass in the production environement
  2. In one of your modules, add in apps/frontend/modules/yourModule/config/module.yml:

    all:
      enabled:     false
    
  3. Clear cache
  4. Try to access to one of the module actions

If your default module is protected

Make sure to un-secure the disabled action in apps/frontend/modules/default/config/security.yml:

disabled:
  is_secure: off

Using an other module

If you want to use an other module, modify apps/frontend/config/settings.yml:

all:
  .actions:
    module_disabled_module: foo   # To be called when a user requests
    module_disabled_action: bar     # A module disabled in the module.yml

Clear your cache.

php symfony cc

top

"Website Temporarily Unavailable" error page
  • high
  • design

A symfony website can be displayed as unavailable:

  • When an application has been disable by php symfony project:disable
  • During the cache clearing, if the check_lock option is checked in settings.yml

In these cases, symfony looks for a unavailable.php file and let you choose where you prefer to put it:

  1. At mySfProject/apps/frontend/config/unavailable.php

    Useful if you want to have a different version for each application

  2. In mySfProject/config/unavailable.php

    The main configuration folder of your project

Website Temporarily Unavailable

If you want, you can grab the original file and modify it.

Don't forget to clear your cache.

Display the "Website Temporarily Unavailable" page to test it

Disable your frontend in dev environement.

  php symfony project:disable frontend dev

top

Favicon
  • design

Modern browsers request a favicon.ico file when a user first browses to your application, to represent the application with an icon in the address bar and bookmarks folder.

Providing such a file will not only make your application's look and feel complete, but it will also prevent a lot of 404 errors from appearing in your server logs.

Some useful links :

top

Cookie names
  • design

The session-handling mechanism uses a cookie on the client side, and this cookie is called "symfony" by default.

In the apps/frontend/config/factories.yml file, add for the all environment:

storage:
  class: sfSessionStorage
  param:
    session_name: my_project

If the sfGuardPlugin is installed, you can modify the "Remember me" cookie name in myProject/config/app.yml:

all:
  sf_guard_plugin:
    remember_cookie_name:   myProjectRememberMe

top

Check the production server
  • high
  • performance

To test if a server is correctly set up to host symfony, Sensio has written a test script. Download it and put it in the web directory.

The script has to be launched in a navigator and through a command line :

php check_configuration.php

Read the related symfony documentation

top

Protect yourself against user uploaded files
  • high
  • security

When it comes to security, the very first rule is that all data sent by users should be validated before being stored on the server.

Experience shows that some developers give poor, little or no attention at all to validating file uploads.

This is mostly due to laziness. However, sometimes the purpose is to build a more flexible form. Example: a CV upload field that accepts any file extension.

This is a huge security mistake.

Why? Because these files by default are stored in the uploads directory which is publicly accessible.

If one of your users succeeds in uploading a php file, such as attack.php through one of your forms, then he will be able to run the script just by using the http//your-sf-project.com/uploads/attack.php uri.

If the aforementioned php file contained malicious code then the hacker could get access to your database settings, user details, delete data etc.

First step: check all of your file upload fields

It is absolutely critical that uploaded files are validated.

Read again the file validator documentation. Do all of your validators have customised mime_types or a mime_categories option ? You should also prevent your forms from accepting the .htaccess mime type.

Second step: disable php execution in the "uploads" directory

If you have access to the httpd.conf file, add the following rule to your virtualhost:

<VirtualHost *:80>

   ...
   ...
   <Directory "/path/to/my/sfProject/web/uploads">
     php_flag engine off
   </Directory>
 </VirtualHost >

If you don’t have access to the httpd.conf of your host, add a new .htaccess file in your /path/to/my/sfProject/web/uploads directory:

php_flag engine off

Third step: Avoid to use the uploads directory when you can

Some uploaded files - like user avatars - need to be publicly accessible and are displayed very often by the server, the uploads directory is the right place for them.

But many other user files are private or rarely displayed. All these files could be stored in the data directory (documentation).

1/ Create a files sub directory: mkdir /path/to/my/sfProject/data/files

2/ Change the path option in your forms:

$this->validatorSchema['driver_licence_pic'] = new sfValidatorFile(array(
  'mime_types' => 'web_images',
  'path'       => sfConfig::get('sf_data_dir').'/files/driver_licence',
));

3/ And add a dedicated route to the file.

top

Language
  • high

By default, symfony puts lang="en" on every single page of your project.

If your application deals with other languages than english, go to your apps/frontend/templates/layout.php file and change the second line:

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<?php echo $sf_user->getCulture() ?>"↵
  lang="<?php echo $sf_user->getCulture() ?>">

top

Remove "/backend.php/" from your uri
  • design

In the apps/frontend/config/settings.yml file, you can delete the application name from your uri by activing the no_script_name option.

This option is enabled for the first generated application (most often, the frontend), but not for the others.

Why ?

Because without the application name, symfony wouldn't know which one to use when the applications share the same domain.

We have to find an other way to let symfony know which application to use.

A solution can be to use several domains :

  • http://my-app.com for the frontend
  • http://my-app-backend.com for the backend

Personnally, I prefer use some sub domains for each application :

  • http://my-app.com for the frontend
  • http://admin.my-app.com for the backend

If your project is hosted on a dedicated server

You juste have to add a new virtual host in httpd.conf :

<VirtualHost *:80>
  ServerName admin.my-app.com
  DirectoryIndex backend.php
  DocumentRoot "/path/to/the/web/directory"
  <Directory "/path/to/the/web/directory">
    AllowOverride All
    Allow from All
  </Directory>
  Alias /sf /path/to/the/symfony/data/web/sf
  <Directory "/path/to/the/symfony/data/web/sf">
    AllowOverride All
    Allow from All
  </Directory>
</VirtualHost>

If your project is hosted on a shared hosting environment

In that case, you probably can't access to the httpd.conf.
So let's play with the /web/.htaccess. Just after :

<IfModule mod_rewrite.c>
  RewriteEngine On

Add the following lines:

# The admin subdomain returns to the backend
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{HTTP_HOST} ^admin\.my-app\..*
RewriteRule ^(.*)$ backend.php [QSA,L]

Now, you can modify settings.yml

prod:
  .settings:
    no_script_name:  true

Don't forget to clear the cache.

php symfony cc

top

PHP Accelerator
  • high
  • performance

For the production server, you probably want the best performance possible. Installing a PHP accelerator will give you the best improvement for your money.

APC is one of the most popular ones.

On a dedicated server

The set up is as simple as :

pecl install apc

On a shared hosting environment

Sometimes the installation is just not possible. Read the documentation.

If you use the Doctrine ORM

You can enable the cache system by adding the following method to mySfProject/config/ProjectConfiguration.class.php :

/**
 * Configure the Doctrine engine
 **/
public function configureDoctrine(Doctrine_Manager $manager)
{
  $manager->setAttribute(Doctrine::ATTR_QUERY_CACHE, new Doctrine_Cache_Apc());
}

top

Remove .htaccess when possible
  • performance

The .htaccess file is very usefull: it's flexible, it works everywhere and the modifications are processed instantaneously.

But the drawback is that the rules written in it can't be cached by apache, the server has to scan the file for every single request.

If you move the .htaccess rules to your virtual host configuration file, the rules will be cached and apache performance will improve.

Unfortunately, you can't access to the apache configuration files on shared hosts, that's why symfony uses the .htaccess file by default.
So sorry, this tip only works for dedicated server users.

Open your virtual host configuration file

Most often, you'll find the virtual host configurations in the bottom of the httpd.conf, but sometimes, they are moved in some vhosts.conf file.

Your virtual host should look like to something like this:

<VirtualHost *:80>
  ServerName my-symfony-project.com
  DirectoryIndex index.php
  DocumentRoot "/path-to-your-sf-project/web"
  <Directory "/path-to-your-sf-project/web">
    AllowOverride All
    Allow from All
  </Directory>
  Alias /sf /path-to-your-sf-project/lib/vendor/symfony/data/web/sf/
  <Directory "/path-to-your-sf-project/lib/vendor/symfony/data/web/sf/">
    AllowOverride All
    Allow from All
  </Directory>
</VirtualHost>

You need to add your rules to <Directory "/path-to-your-sf-project/web"> :

<Directory "/path-to-your-sf-project/web">
  AllowOverride None
  Allow from All
  Options FollowSymLinks ExecCGI

  RewriteEngine On

  # uncomment the following line, if you are having trouble
  # getting no_script_name to work
  #RewriteBase /

  # we skip all files with .something
  #RewriteCond %{REQUEST_URI} \..+$
  #RewriteCond %{REQUEST_URI} !\.html$
  #RewriteRule .* - [L]

  # we check if the .html version is here (caching)
  RewriteRule ^$ index.html [QSA]
  RewriteRule ^([^.]+)$ $1.html [QSA]
  RewriteCond %{REQUEST_FILENAME} !-f

  # no, so we redirect to our front web controller
  RewriteRule ^(.*)$ index.php [QSA,L]
</Directory>

Remove the my_project/web/.htaccess file

Unless you added extra rules in it, the file is now useless. If you don't remove the htaccess, apache will still parse the file on every request

Test the modifications and restart Apache

Apache provides the configtest tool to check that apache configuration files are ok.

On many linux servers, you can access to apache commands by /etc/init.d/apachectl makeMeSandwich.
Rights errors just need the sudo prefix to be fixed: sudo /etc/init.d/apachectl makeMeSandwich

/etc/init.d/apachectl apachectl configtest

If there's no error:

/etc/init.d/apachectl apachectl restart

Document this in a README file

Your project really should have at least one README file where this kind of modifications are reported.

This tip was given by Jérôme Macias on the symfony-check thread.

top

Log errors
  • high
  • security

Error logs are a precious help to find what goes wrong on your production server.

In apps/frontend/config/settings.yml, activate the option:

prod:
  .settings:
    logging_enabled:    true

Then, in apps/frontend/config/factories.yml:

prod:
  logger:
    class: sfAggregateLogger
    param:
      level: err
      loggers:
        sf_file_debug:
          class: sfFileLogger
          param:
            level: err
            file: %SF_LOG_DIR%/%SF_APP%_%SF_ENVIRONMENT%.log

Clear your cache.

php symfony cc

Don't forget to set up a cron dedicated to the log rotation.

Read the related symfony documentation.

top

Fix your deployments
  • high
  • security

You shall not pass!

Test environment scripts SHALL NOT PASS

To prevent that only production related scripts are published on the production server, symfony provides you two tools : the php symfony project:clear-controllers command and the myproject/config/rsync_exclude.txt file.

The rsync_exclude.txt file filters by default the scripts related to the dev environment. But if you use some other ones, they have to be added:

/web/*_cache.php
/web/*_test.php

These files aren't very dangerous, but they also should be filtered :

Thumbs.db
.DS_Store

Production database must be protected

You really should add theses files to rsync_exclude.txt :

/config/databases.yml
/data/sql/schema.sql

Force some files to be transfered

You can create a myproject/config/rsync_include.txt file that will be able to force the transfer of files or directories.

Read the related symfony documentation

top

Template escaping
  • security

Useless for sf1.3/1.4 projects

By default, the output escaping is disabled. In the /apps/frontend/config/settings.yml file, add:

all:
  .settings:
    # Output escaping settings
    escaping_strategy:      true
    escaping_method:        ESC_SPECIALCHARS

Read the related symfony documentation

top

Form protection
  • security

Useless for sf1.3/1.4 project

symfony has the ability to protect each form of your project against CSRF attacks.

To enable this feature, you have to prompt a csrf key in apps/frontend/config/settings.yml :

all:
  .settings:
    csrf_secret:       chooseYouOwnSecretKey

This solution inject in each form a token unique for a given user and for a given form. Be carefull, forms that use the csrf protection can no longer be cached.

Read the related symfony documentation

top

Display an unavailable message during maintenance operations
  • high
  • design

The bigger your website becomes, the slower some symfony tasks will be.

At some point, these operations will substantially slow down your application and disturb your users.

Displaying a clean maintenance screen is usually a better choice than providing a degrated navigation experience to your users.

To redirect automatically the users to the customized unavailable.php page when you launch tasks like php symfony cc, modify the apps/frontend/config/settings.yml file:

all:
  .settings:
    check_lock:    true

top

Routing optimization
  • performance

The symfony routing system is one of the killer features of the framework.

<p>Please have a look on <php echo link_to('our products', 'product/index') ?>.</p>

Indicate in your template which module/action you want and then let routing.yml do all the job.

You can use the default rule:

default:
  url:   /:module/:action/*

Or add a dedicated one:

product_list:
  url:     /our-products
  param:   { module: product, action: index }

Clear your cache and all the related links and uri will be modified, all over the application.

It feels like magic, but it has a cost.

Each time the url request product/index is found in a template, symfony has to scan all the rules defined in routing.yml and guess which one can handle this request.
The default rule ? An other one ?

This search is done for all the links...

If your project have many pages and many routing rules, you can speed up your application by using rule labels instead of a module/action pairs in your templates:

<p>Please have a look on <php echo link_to('our products', '@product_list') ?>.</p>

This solution will speed up the routing, but the drawback is that internal links can become a little less self-evident.

It's up to you.

Read the related symfony documentation

top

Add a comment

Suggestions, corrections and comments are very welcome.

Show me the code

Symfony-Check sources are available on Google code