The security process can be understood as a filter by which all requests must pass before executing the action. According to some tests executed in the filter, the processing of the request is modified--for instance, by changing the action executed (default/secure instead of the requested action in the case of the security filter). Symfony extends this idea to filter classes. You can specify any number of filter classes to be executed before the action execution or before the response rendering, and do this for every request. You can see filters as a way to package some code, similar to preExecute() and postExecute(), but at a higher level (for a whole application instead of for a whole module).

6.7.1. The Filter Chain

Symfony actually sees the processing of a request as a chain of filters. When a request is received by the framework, the first filter (which is always the sfRenderingFilter) is executed. At some point, it calls the next filter in the chain, then the next, and so on. When the last filter (which is always sfExecutionFilter) is executed, the previous filter can finish, and so on back to the rendering filter. Figure 6-3 illustrates this idea with a sequence diagram, using an artificially small filter chain (the real one contains more filters).

Sample filter chain

Figure 6.3 Sample filter chain

This process justifies the structure of the filter classes. They all extend the sfFilter class, and contain one execute() method, expecting a $filterChain object as parameter. Somewhere in this method, the filter passes to the next filter in the chain by calling $filterChain->execute(). See Listing 6-28 for an example. So basically, filters are divided into two parts:

  • The code before the call to $filterChain->execute() executes before the action execution.
  • The code after the call to $filterChain->execute() executes after the action execution and before the rendering.

Listing 6-28 - Filter Class Struture

class myFilter extends sfFilter
{
  public function execute ($filterChain)
  {
    // Code to execute before the action execution
    ...

    // Execute next filter in the chain
    $filterChain->execute();

    // Code to execute after the action execution, before the rendering
    ...
  }
}

The default filter chain is defined in an application configuration file called filters.yml, and is shown in Listing 6-29. This file lists the filters that are to be executed for every request.

Listing 6-29 - Default Filter Chain, in frontend/config/filters.yml

rendering: ~
security:  ~

# Generally, you will want to insert your own filters here

cache:     ~
common:    ~
execution: ~

These declarations have no parameter (the tilde character, ~, means "null" in YAML), because they inherit the parameters defined in the symfony core. In the core, symfony defines class and param settings for each of these filters. For instance, Listing 6-30 shows the default parameters for the rendering filter.

Listing 6-30 - Default Parameters of the rendering Filter, in $sf_symfony_lib_dir/config/config/filters.yml

rendering:
  class: sfRenderingFilter   # Filter class
  param:                     # Filter parameters
    type: rendering

By leaving the empty value (~) in the application filters.yml, you tell symfony to apply the filter with the default settings defined in the core.

You can customize the filter chain in various ways:

  • Disable some filters from the chain by adding an enabled: off parameter. For instance, to disable the common filter, which inserts CSS and JavaScript into the head, write:
common:
  enabled: off
  • Do not remove an entry from the filters.yml to disable a filter; symfony would throw an exception in this case.
  • Add your own declarations somewhere in the chain (usually after the security filter) to add a custom filter (as discussed in the next section). Be aware that the rendering filter must be the first entry, and the execution filter must be the last entry of the filter chain.
  • Override the default class and parameters of the default filters (notably to modify the security system and use your own security filter).

6.7.2. Building Your Own Filter

It is pretty simple to build a filter. Create a class definition similar to the one shown in Listing 6-28, and place it in one of the project's lib/ folders to take advantage of the autoloading feature.

As an action can forward or redirect to another action and consequently relaunch the full chain of filters, you might want to restrict the execution of your own filters to the first action call of the request. The isFirstCall() method of the sfFilter class returns a Boolean for this purpose. This call only makes sense before the action execution.

These concepts are clearer with an example. Listing 6-31 shows a filter used to auto-log users with a specific MyWebSite cookie, which is supposedly created by the login action. It is a rudimentary but working way to implement the "remember me" feature offered in login forms.

Listing 6-31 - Sample Filter Class File, Saved in apps/frontend/lib/rememberFilter.class.php

class rememberFilter extends sfFilter
{
  public function execute($filterChain)
  {
    // Execute this filter only once
    if ($this->isFirstCall())
    {
      // Filters don't have direct access to the request and user objects.
      // You will need to use the context object to get them
      $request = $this->getContext()->getRequest();
      $user    = $this->getContext()->getUser();

      if ($request->getCookie('MyWebSite'))
      {
        // sign in
        $user->setAuthenticated(true);
      }
    }

    // Execute next filter
    $filterChain->execute();
  }
}

In some cases, instead of continuing the filter chain execution, you will need to forward to a specific action at the end of a filter. sfFilter doesn't have a forward() method, but sfController does, so you can simply do that by calling the following:

return $this->getContext()->getController()->forward('mymodule', 'myAction');

Note The sfFilter class has an initialize() method, executed when the filter object is created. You can override it in your custom filter if you need to deal with filter parameters (defined in filters.yml, as described next) in your own way.

6.7.3. Filter Activation and Parameters

Creating a filter file is not enough to activate it. You need to add your filter to the filter chain, and for that, you must declare the filter class in the filters.yml, located in the application or in the module config/ directory, as shown in Listing 6-32.

Listing 6-32 - Sample Filter Activation File, Saved in apps/frontend/config/filters.yml

rendering: ~
security:  ~

remember:                 # Filters need a unique name
  class: rememberFilter
  param:
    cookie_name: MyWebSite
    condition:   %APP_ENABLE_REMEMBER_ME%

cache:     ~
common:    ~
execution: ~

When activated, the filter is executed for each request. The filter configuration file can contain one or more parameter definitions under the param key. The filter class has the ability to get the value of these parameters with the getParameter() method. Listing 6-33 demonstrates how to get a filter parameter value.

Listing 6-33 - Getting the Parameter Value, in apps/frontend/lib/rememberFilter.class.php

class rememberFilter extends sfFilter
{
  public function execute ($filterChain)
  {
    // ...

    if ($request->getCookie($this->getParameter('cookie_name')))
    {
      // ...
    }

    // ...
  }
}

The condition parameter is tested by the filter chain to see if the filter must be executed. So your filter declarations can rely on an application configuration, just like the one in Listing 6-32. The remember filter will be executed only if your application app.yml shows this:

all:
  enable_remember_me: on

6.7.4. Sample Filters

The filter feature is useful to repeat code for every action. For instance, if you use a distant analytics system, you probably need to put a code snippet calling a distant tracker script in every page. You could put this code in the global layout, but then it would be active for all of the application. Alternatively, you could place it in a filter, such as the one shown in Listing 6-34, and activate it on a per-module basis.

Listing 6-34 - Google Analytics Filter

class sfGoogleAnalyticsFilter extends sfFilter
{
  public function execute($filterChain)
  {
    // Nothing to do before the action
    $filterChain->execute();

    // Decorate the response with the tracker code
    $googleCode = '
<script src="http://www.google-analytics.com/urchin.js"  type="text/javascript">
</script>
<script type="text/javascript">
  _uacct="UA-'.$this->getParameter('google_id').'";urchinTracker();
</script>';
    $response = $this->getContext()->getResponse();
    $response->setContent(str_ireplace('</body>', $googleCode.'</body>',$response->getContent()));
   }
}

Be aware that this filter is not perfect, as it should not add the tracker on responses that are not HTML.

Another example would be a filter that switches the request to SSL if it is not already, to secure the communication, as shown in Listing 6-35.

Listing 6-35 - Secure Communication Filter

class sfSecureFilter extends sfFilter
{
  public function execute($filterChain)
  {
    $context = $this->getContext();
    $request = $context->getRequest();

    if (!$request->isSecure())
    {
      $secure_url = str_replace('http', 'https', $request->getUri());

      return $context->getController()->redirect($secure_url);
      // We don't continue the filter chain
    }
    else
    {
      // The request is already secure, so we can continue
      $filterChain->execute();
    }
  }
}

Filters are used extensively in plug-ins, as they allow you to extend the features of an application globally. Refer to Chapter 17 to learn more about plug-ins, and see the online wiki (http://trac.symfony-project.org/) for more filter examples.