The definitive guide of Symfony 1.1

12.4. HTTP 1.1 and Client-Side Caching

The HTTP 1.1 protocol defines a bunch of headers that can be of great use to further speed up an application by controlling the browser's cache system.

The HTTP 1.1 specifications of the World Wide Web Consortium (W3C, [http://www. w3.org/Protocols/rfc2616/rfc2616-sec14.html](http://www. w3.org/Protocols/rfc2616/rfc2616-sec14.html)) describe these headers in detail. If an action has caching enabled, and it uses the with_layout option, it can use one or more of the mechanisms described in the following sections.

Even if some of the browsers of your website's users may not support HTTP 1.1, there is no risk in using the HTTP 1.1 cache features. A browser receiving headers that it doesn't understand simply ignores them, so you are advised to set up the HTTP 1.1 cache mechanisms.

In addition, HTTP 1.1 headers are also understood by proxies and caching servers. Even if a user's browser doesn't understand HTTP 1.1, there can be a proxy in the route of the request to take advantage of it.

12.4.1. Adding an ETag Header to Avoid Sending Unchanged Content

When the ETag feature is enabled, the web server adds to the response a special header containing a signature of the response itself.

ETag: "1A2Z3E4R5T6Y7U"

The user's browser will store this signature, and send it again together with the request the next time it needs the same page. If the new signature shows that the page didn't change since the first request, the server doesn't send the response back. Instead, it just sends a 304: Not modified header. It saves CPU time (if gzipping is enabled for example) and bandwidth (page transfer) for the server, and time (page transfer) for the client. Overall, pages in a cache with an ETag are even faster to load than pages in a cache without an ETag.

In symfony, you enable the ETag feature for the whole application in settings.yml. Here is the default ETag setting:

all:
  .settings:
    etag: on

For actions in a cache with layout, the response is taken directly from the cache/ directory, so the process is even faster.

12.4.2. Adding a Last-Modified Header to Avoid Sending Still Valid Content

When the server sends the response to the browser, it can add a special header to specify when the data contained in the page was last changed:

Last-Modified: Sat, 23 Nov 2006 13:27:31 GMT

Browsers can understand this header and, when requesting the page again, add an If-Modified header accordingly:

If-Modified-Since: Sat, 23 Nov 2006   13:27:31 GMT

The server can then compare the value kept by the client and the one returned by its application. If they match, the server returns a 304: Not modified header, saving bandwidth and CPU time, just as with ETags.

In symfony, you can set the Last-Modified response header just as you would for another header. For instance, you can use it like this in an action:

$this->getResponse()->setHttpHeader('Last-Modified', $this->getResponse()->getDate($timestamp));

This date can be the actual date of the last update of the data used in the page, given from your database or your file system. The getDate() method of the sfResponse object converts a timestamp to a formatted date in the format needed for the Last-Modified header (RFC1123).

12.4.3. Adding Vary Headers to Allow Several Cached Versions of a Page

Another HTTP 1.1 header is Vary. It defines which parameters a page depends on, and is used by browsers and proxies to build cache keys. For example, if the content of a page depends on cookies, you can set its Vary header as follows:

Vary: Cookie

Most often, it is difficult to enable caching on actions because the page may vary according to the cookie, the user language, or something else. If you don't mind expanding the size of your cache, set the Vary header of the response properly. This can be done for the whole application or on a per-action basis, using the cache.yml configuration file or the sfResponse related method as follows:

$this->getResponse()->addVaryHttpHeader('Cookie');
$this->getResponse()->addVaryHttpHeader('User-Agent');
$this->getResponse()->addVaryHttpHeader('Accept-Language');

Symfony will store a different version of the page in the cache for each value of these parameters. This will increase the size of the cache, but whenever the server receives a request matching these headers, the response is taken from the cache instead of being processed. This is a great performance tool for pages that vary only according to request headers.

12.4.4. Adding a Cache-Control Header to Allow Client-Side Caching

Up to now, even by adding headers, the browser keeps sending requests to the server even if it holds a cached version of the page. You can avoid that by adding Cache-Control and Expires headers to the response. These headers are disabled by default in PHP, but symfony can override this behavior to avoid unnecessary requests to your server.

As usual, you trigger this behavior by calling a method of the sfResponse object. In an action, define the maximum time a page should be cached (in seconds):

$this->getResponse()->addCacheControlHttpHeader('max_age=60');

You can also specify under which conditions a page may be cached, so that the provider's cache does not keep a copy of private data (like bank account numbers):

$this->getResponse()->addCacheControlHttpHeader('private=True');

Using Cache-Control HTTP directives, you get the ability to fine-tune the various cache mechanisms between your server and the client's browser. For a detailed review of these directives, see the W3C Cache-Control specifications.

One last header can be set through symfony: the Expires header:

$this->getResponse()->setHttpHeader('Expires', $this->getResponse()->getDate($timestamp));

Caution The major consequence of turning on the Cache-Control mechanism is that your server logs won't show all the requests issued by the users, but only the ones actually received. If the performance gets better, the apparent popularity of the site may decrease in the statistics.