The definitive guide of Symfony 1.2

18.4. Tweaking the Cache

Chapter 12 already described how to cache parts of a response or all of it. The response cache results in a major performance improvement, and it should be one of your first optimization considerations. If you want to make the most out of the cache system, read further, for this section unveils a few tricks you might not have thought of.

18.4.1. Clearing Selective Parts of the Cache

During application development, you have to clear the cache in various situations:

  • When you create a new class: Adding a class to an autoloading directory (one of the project's lib/ folders) is not enough to have symfony find it automatically in non-development environments. You must clear the autoloading configuration cache so that symfony browses again all the directories of the autoload.yml file and references the location of autoloadable classes — including the new ones.
  • When you change the configuration in production: The configuration is parsed only during the first request in production. Further requests use the cached version instead. So a change in the configuration in the production environment (or any environment where debug is turned off) doesn't take effect until you clear the cached version of the file.
  • When you modify a template in an environment where the template cache is enabled: The valid cached templates are always used instead of existing templates in production, so a template change is ignored until the template cache is cleared or outdated.
  • When you update an application with the project:deploy command: This case usually covers the three previous modifications.

The problem with clearing the whole cache is that the next request will take quite long to process, because the configuration cache needs to be regenerated. Besides, the templates that were not modified will be cleared from the cache as well, losing the benefit of previous requests.

That means it's a good idea to clear only the cache files that really need to be regenerated. Use the options of the cache:clear task to define a subset of cache files to clear, as demonstrated in Listing 18-14.

Listing 18-14 - Clearing Only Selective Parts of the Cache

// Clear only the cache of the frontend application
> php symfony cache:clear frontend

// Clear only the HTML cache of the frontend application
> php symfony cache:clear frontend template

// Clear only the configuration cache of the frontend application
> php symfony cache:clear frontend config

You can also remove files by hand in the cache/ directory, or clear template cache files selectively from the action with the $cacheManager->remove() method, as described in Chapter 12.

All these techniques will minimize the negative performance impact of any of the changes listed previously.

Tip When you upgrade symfony, the cache is automatically cleared, without manual intervention (if you set the check_symfony_version parameter to true in settings.yml).

18.4.2. Generating Cached Pages

When you deploy a new application to production, the template cache is empty. You must wait for users to visit a page once for this page to be put in the cache. In critical deployments, the overhead of page processing is not acceptable, and the benefits of caching must be available as soon as the first request is issued.

The solution is to automatically browse the pages of your application in the staging environment (where the configuration is similar to the one in production) to have the template cache generated, then to transfer the application with the cache to production.

To browse the pages automatically, one option is to create a shell script that looks through a list of external URLs with a browser (curl for instance). But there is a better and faster solution: a symfony batch using the sfBrowser object, already discussed in Chapter 15. That's an internal browser written in PHP (and used by sfTestBrowser for functional tests). It takes an external URL and returns a response, but the interesting thing is that it triggers the template cache just like a regular browser. As it only initializes symfony once and doesn't pass by the HTTP transport layer, this method is a lot faster.

Listing 18-15 shows an example batch script used to generate template cache files in a staging environment. Launch it by calling php batch/generate_cache.php.

Listing 18-15 - Generating the Template Cache, in batch/generate_cache.php

require_once(dirname(__FILE__).'/../config/ProjectConfiguration.class.php');
$configuration = ProjectConfiguration::getApplicationConfiguration('frontend', 'staging', false);
sfContext::createInstance($configuration);

// Array of URLs to browse
$uris = array(
  '/foo/index',
  '/foo/bar/id/1',
  '/foo/bar/id/2',
  ...
);

$b = new sfBrowser();
foreach ($uris as $uri)
{
  $b->get($uri);
}

18.4.3. Using a Database Storage System for Caching

The default storage system for the template cache in symfony is the file system: Fragments of HTML or serialized response objects are stored under the cache/ directory of a project. Symfony proposes an alternative way to store cache: a SQLite database. Such a database is a simple file that PHP natively knows how to query very efficiently.

To tell symfony to use SQLite storage instead of file system storage for the template cache, open the factories.yml file and edit the view_cache entry as follows:

view_cache:
  class: sfSQLiteCache
  param:
    database: %SF_TEMPLATE_CACHE_DIR%/cache.db

The benefits of using SQLite storage for the template cache are faster read and write operations when the number of cache elements is important. If your application makes heavy use of caching, the template cache files end up scattered in a deep file structure; in this case, switching to SQLite storage will increase performance. In addition, clearing the cache on file system storage may require a lot of files to be removed from the disk; this operation may last a few seconds, during which your application is unavailable. With a SQLite storage system, the cache clearing process results in a single file operation: the deletion of the SQLite database file. Whatever the number of cache elements currently stored, the operation is instantaneous.

18.4.4. Bypassing Symfony

Perhaps the best way to speed symfony up is to bypass it completely . . . this is said only partly in jest. Some pages don't change and don't need to be reprocessed by the framework at each request. The template cache is already here to speed up the delivery of such pages, but it still relies on symfony.

A couple of tricks described in Chapter 12 allow you to bypass symfony completely for some pages. The first one involves the use of HTTP 1.1 headers for asking the proxies and client browsers to cache the page themselves, so that they don't request it again the next time the page is needed. The second one is the super fast cache (automated by the sfSuperCachePlugin plug-in), which consists of storing a copy of the response in the web/ directory and modifying the rewriting rules so that Apache first looks for a cached version before handing a request to symfony.

Both these methods are very effective, and even if they only apply to static pages, they will take the burden of handling these pages off from symfony, and the server will then be fully available to deal with complex requests.

18.4.5. Caching the Result of a Function Call

If a function doesn't rely on context-sensitive values nor on randomness, calling it twice with the same parameters should return the same result. That means the second call could very well be avoided if the result had been stored the first time. That's exactly what the sfFunctionCache class does. This class has a call() method, which expects a callable and an array of parameters as its arguments. When called, this method creates an md5 hash with all its arguments and looks in the cache for a key named by this hash. If such a key is found, the function returns the result stored in the cache. If not, the sfFunctionCache executes the function, stores the result in the cache, and returns it. So the second execution of Listing 18-16 will be faster than the first one.

Listing 18-16 - Caching the Result of a Function

$cache = new sfFileCache(array('cache_dir' => sfConfig::get('sf_cache_dir').'/function'));
$fc = new sfFunctionCache($cache);
$result1 = $fc->call('cos', array(M_PI));
$result2 = $fc->call('preg_replace', array('/\s\s+/', ' ', $input));

The sfFunctionCache constructor expects a cache object. The first argument of the call() method must be a callable, so it can be a function name, an array of a class name and static method name, or an array of an object name and public method name. As for the other argument of the call() method, it's an array of arguments that will be passed to the callable.

Caution If you use a file based cache object as in the example, it's better to give a cache directory under the cache/ directory, as it will be cleanup automatically by the cache:clear task. If you store the function cache somewhere else, it will not be cleared automatically when you clear the cache through the command line.

18.4.6. Caching Data in the Server

PHP accelerators provide special functions to store data in memory so that you can reuse it across requests. The problem is that they all have a different syntax, and each has its own specific way of performing this task. The symfony cache classes abstract all these differences and works with whatever accelerator you are using. See its syntax in Listing 18-17.

Listing 18-17 - Using a PHP accelerator to cache data

$cache = new sfAPCCache();

// Storing data in the cache
$cache->set($name, $value, $lifetime);

// Retrieving data
$value = $cache->get($name);

// Checking if a piece of data exists in the cache
$value_exists = $cache->has($name);

// Clear the cache
$cache->clear();

The set() method returns false if the caching didn't work. The cached value can be anything (a string, an array, an object); the sfProcessCache class will deal with the serialization. The get() method returns null if the required variable doesn't exist in the cache.

Tip If you want to go further into memory caching, make sure you take a look at the sfMemcacheCache class. It provides the same interface as the other cache classes and it can help decrease the database load on load-balanced applications.