The definitive guide of Symfony 1.2

12.1. Caching the Response

The principle of HTML caching is simple: Part or all of the HTML code that is sent to a user upon a request can be reused for a similar request. This HTML code is stored in a special place (the cache/ folder in symfony), where the front controller will look for it before executing an action. If a cached version is found, it is sent without executing the action, thus greatly speeding up the process. If no cached version is found, the action is executed, and its result (the view) is stored in the cache folder for future requests.

As all the pages may contain dynamic information, the HTML cache is disabled by default. It is up to the site administrator to enable it in order to improve performance.

Symfony handles three different types of HTML caching:

  • Cache of an action (with or without the layout)
  • Cache of a partial, a component, or a component slot
  • Cache of a template fragment

The first two types are handled with YAML configuration files. Template fragment caching is managed by calls to helper functions in the template.

12.1.1. Global Cache Settings

For each application of a project, the HTML cache mechanism can be enabled or disabled (the default), per environment, in the cache setting of the settings.yml file. Listing 12-1 demonstrates enabling the cache.

Listing 12-1 - Activating the Cache, in frontend/config/settings.yml

dev:
  .settings:
    cache:  on

12.1.2. Caching an Action

Actions displaying static information (not depending on database or session-dependent data) or actions reading information from a database but without modifying it (typically, GET requests) are often ideal for caching. Figure 12-1 shows which elements of the page are cached in this case: either the action result (its template) or the action result together with the layout.

Caching an action

Figure 12.1 Caching an action

For instance, consider a user/list action that returns the list of all users of a website. Unless a user is modified, added, or removed (and this matter will be discussed later in the "Removing Items from the Cache" section), this list always displays the same information, so it is a good candidate for caching.

Cache activation and settings, action by action, are defined in a cache.yml file located in the module config/ directory. See Listing 12-2 for an example.

Listing 12-2 - Activating the Cache for an Action, in frontend/modules/user/config/cache.yml

list:
  enabled:     on
  with_layout: false   # Default value
  lifetime:    86400   # Default value

This configuration stipulates that the cache is on for the list action, and that the layout will not be cached with the action (which is the default behavior). It means that even if a cached version of the action exists, the layout (together with its partials and components) is still executed. If the with_layout setting is set to true, the layout is cached with the action and not executed again.

To test the cache settings, call the action in the development environment from your browser.

http://myapp.example.com/frontend_dev.php/user/list

You will notice a border around the action area in the page. The first time, the area has a blue header, showing that it did not come from the cache. Refresh the page, and the action area will have a yellow header, showing that it did come from the cache (with a notable boost in response time). You will learn more about the ways to test and monitor caching later in this chapter.

Note Slots are part of the template, and caching an action will also store the value of the slots defined in this action's template. So the cache works natively for slots.

The caching system also works for pages with arguments. The user module may have, for instance, a show action that expects an id argument to display the details of a user. Modify the cache.yml file to enable the cache for this action as well, as shown in Listing 12-3.

In order to organize your cache.yml, you can regroup the settings for all the actions of a module under the all: key, also shown in Listing 12-3.

Listing 12-3 - A Full cache.yml Example, in frontend/modules/user/config/cache.yml

list:
  enabled:    on
show:
  enabled:    on

all:
  with_layout: false   # Default value
  lifetime:    86400   # Default value

Now, every call to the user/show action with a different id argument creates a new record in the cache. So the cache for this:

http://myapp.example.com/user/show/id/12

will be different than the cache for this:

http://myapp.example.com/user/show/id/25

Caution Actions called with a POST method or with GET parameters are not cached.

The with_layout setting deserves a few more words. It actually determines what kind of data is stored in the cache. For the cache without layout, only the result of the template execution and the action variables are stored in the cache. For the cache with layout, the whole response object is stored. This means that the cache with layout is much faster than the cache without it.

If you can functionally afford it (that is, if the layout doesn't rely on session-dependent data), you should opt for the cache with layout. Unfortunately, the layout often contains some dynamic elements (for instance, the name of the user who is connected), so action cache without layout is the most common configuration. However, RSS feeds, pop-ups, and pages that don't depend on cookies can be cached with their layout.

12.1.3. Caching a Partial, Component, or Component Slot

Chapter 7 explained how to reuse code fragments across several templates, using the include_partial() helper. A partial is as easy to cache as an action, and its cache activation follows the same rules, as shown in Figure 12-2.

Caching a partial, component, or component slot

Figure 12.2 Caching a partial, component, or component slot

For instance, Listing 12-4 shows how to edit the cache.yml file to enable the cache on a _my_partial.php partial located in the user module. Note that the with_layout setting doesn't make sense in this case.

Listing 12-4 - Caching a Partial, in frontend/modules/user/config/cache.yml

_my_partial:
  enabled:    on
list:
  enabled:    on
...

Now all the templates using this partial won't actually execute the PHP code of the partial, but will use the cached version instead.

<?php include_partial('user/my_partial') ?>

Just as for actions, partial caching is also relevant when the result of the partial depends on parameters. The cache system will store as many versions of a template as there are different values of parameters.

<?php include_partial('user/my_other_partial', array('foo' => 'bar')) ?>

Tip The action cache is more powerful than the partial cache, since when an action is cached, the template is not even executed; if the template contains calls to partials, these calls are not performed. Therefore, partial caching is useful only if you don't use action caching in the calling action or for partials included in the layout.

A little reminder from Chapter 7: A component is a light action put on top of a partial, and a component slot is a component for which the action varies according to the calling actions. These two inclusion types are very similar to partials, and support caching in the same way. For instance, if your global layout includes a component called day with include_component('general/day') in order to show the current date, set the cache.yml file of the general module as follows to enable the cache on this component:

_day:
  enabled: on

When caching a component or a partial, you must decide whether to store a single version for all calling templates or a version for each template. By default, a component is stored independently of the template that calls it. But contextual components, such as a component that displays a different sidebar with each action, should be stored as many times as there are templates calling it. The caching system can handle this case, provided that you set the contextual parameter to true, as follows:

_day:
  contextual: true
  enabled:   on

Note Global components (the ones located in the application templates/ directory) can be cached, provided that you declare their cache settings in the application cache.yml.

12.1.4. Caching a Template Fragment

Action caching applies to only a subset of actions. For the other actions — those that update data or display session-dependent information in the template — there is still room for cache improvement but in a different way. Symfony provides a third cache type, which is dedicated to template fragments and enabled directly inside the template. In this mode, the action is always executed, and the template is split into executed fragments and fragments in the cache, as illustrated in Figure 12-3.

Caching a template fragment

Figure 12.3 Caching a template fragment

For instance, you may have a list of users that shows a link of the last-accessed user, and this information is dynamic. The cache() helper defines the parts of a template that are to be put in the cache. See Listing 12-5 for details on the syntax.

Listing 12-5 - Using the cache() Helper, in frontend/modules/user/templates/listSuccess.php

<!-- Code executed each time  -->
<?php echo link_to('last accessed user', 'user/show?id='.$last_accessed_user_id) ?>

<!-- Cached code  -->
<?php if (!cache('users')): ?>
  <?php foreach ($users as $user): ?>
    <?php echo $user->getName() ?>
  <?php endforeach; ?>
  <?php cache_save() ?>
<?php endif; ?>

Here's how it works:

  • If a cached version of the fragment named 'users' is found, it is used to replace the code between the and the lines.
  • If not, the code between these lines is processed and saved in the cache, identified with the unique fragment name.

The code not included between such lines is always processed and not cached.

Caution The action (list in the example) must not have caching enabled, since this would bypass the whole template execution and ignore the fragment cache declaration.

The speed boost of using the template fragment cache is not as significant as with the action cache, since the action is always executed, the template is partially processed, and the layout is always used for decoration.

You can declare additional fragments in the same template; however, you need to give each of them a unique name so that the symfony cache system can find them afterwards.

As with actions and components, cached fragments can take a lifetime in seconds as a second argument of the call to the cache() helper.

<?php if (!cache('users', 43200)): ?>

The default cache lifetime (86400 seconds, or one day) is used if no parameter is given to the helper.

Tip Another way to make an action cacheable is to insert the variables that make it vary into the action's routing pattern. For instance, if a home page displays the name of the connected user, it cannot be cached unless the URL contains the user nickname. Another example is for internationalized applications: If you want to enable caching on a page that has several translations, the language code must somehow be included in the URL pattern. This trick will multiply the number of pages in the cache, but it can be of great help to speed up heavily interactive applications.

12.1.5. Configuring the Cache Dynamically

The cache.yml file is one way to define cache settings, but it has the inconvenience of being invariant. However, as usual in symfony, you can use plain PHP rather than YAML, and that allows you to configure the cache dynamically.

Why would you want to change the cache settings dynamically? A good example is a page that is different for authenticated users and for anonymous ones, but the URL remains the same. Imagine an article/show page with a rating system for articles. The rating feature is disabled for anonymous users. For those users, rating links trigger the display of a login form. This version of the page can be cached. On the other hand, for authenticated users, clicking a rating link makes a POST request and creates a new rating. This time, the cache must be disabled for the page so that symfony builds it dynamically.

The right place to define dynamic cache settings is in a filter executed before the sfCacheFilter. Indeed, the cache is a filter in symfony, just like the the security features. In order to enable the cache for the article/show page only if the user is not authenticated, create a conditionalCacheFilter in the application lib/ directory, as shown in Listing 12-6.

Listing 12-6 - Configuring the Cache in PHP, in frontend/lib/conditionalCacheFilter.class.php

class conditionalCacheFilter extends sfFilter
{
  public function execute($filterChain)
  {
    $context = $this->getContext();
    if (!$context->getUser()->isAuthenticated())
    {
      foreach ($this->getParameter('pages') as $page)
      {
        $context->getViewCacheManager()->addCache($page['module'], $page['action'], array('lifeTime' => 86400));
      }
    }

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

You must register this filter in the filters.yml file before the sfCacheFilter, as shown in Listing 12-7.

Listing 12-7 - Registering Your Custom Filter, in frontend/config/filters.yml

...
security: ~

conditionalCache:
  class: conditionalCacheFilter
  param:
    pages:
      - { module: article, action: show }

cache: ~
...

Clear the cache (to autoload the new filter class), and the conditional cache is ready. It will enable the cache of the pages defined in the pages parameter only for users who are not authenticated.

The addCache() method of the sfViewCacheManager object expects a module name, an action name, and an associative array with the same parameters as the ones you would define in a cache.yml file. For instance, if you want to define that the article/show action must be cached with the layout and with a lifetime of 3600 seconds, then write the following:

$context->getViewCacheManager()->addCache('article', 'show', array(
  'withLayout' => true,
  'lifeTime'   => 3600,
));

12.1.6. Using the Super Fast Cache

Even a cached page involves some PHP code execution. For such a page, symfony still loads the configuration, builds the response, and so on. If you are really sure that a page is not going to change for a while, you can bypass symfony completely by putting the resulting HTML code directly into the web/ folder. This works thanks to the Apache mod_rewrite settings, provided that your routing rule specifies a pattern ending without a suffix or with .html.

You can do this by hand, page by page, with a simple command-line call:

> curl http://myapp.example.com/user/list.html > web/user/list.html

After that, every time that the user/list action is requested, Apache finds the corresponding list.html page and bypasses symfony completely. The trade-off is that you can't control the page cache with symfony anymore (lifetime, automatic deletion, and so on), but the speed gain is very impressive.

Alternatively, you can use the sfSuperCache symfony plug-in, which automates the process and supports lifetime and cache clearing. Refer to Chapter 17 for more information about plug-ins.