HTTP Cache
The nature of rich web applications means that they're dynamic. No matterhow efficient your application, each request will always contain more overheadthan serving a static file. Usually, that's fine. But when you need your requeststo be lightning fast, you need HTTP caching.
Caching on the Shoulders of Giants
With HTTP Caching, you cache the full output of a page (i.e. the response) and bypassyour application entirely on subsequent requests. Caching entire responsesisn't always possible for highly dynamic sites, or is it? WithEdge Side Includes (ESI), you can use the power of HTTP cachingon only fragments of your site.
The Symfony cache system is different because it relies on the simplicityand power of the HTTP cache as defined in RFC 7234 - Caching. Instead ofreinventing a caching methodology, Symfony embraces the standard that definesbasic communication on the Web. Once you understand the fundamental HTTPvalidation and expiration caching models, you'll be ready to master the Symfonycache system.
Since caching with HTTP isn't unique to Symfony, many articles already existon the topic. If you're new to HTTP caching, Ryan Tomayko's articleThings Caches Do is highly recommended. Another in-depth resource is MarkNottingham's Cache Tutorial.
Caching with a Gateway Cache
When caching with HTTP, the cache is separated from your application entirelyand sits between your application and the client making the request.
The job of the cache is to accept requests from the client and pass themback to your application. The cache will also receive responses back fromyour application and forward them on to the client. The cache is the "middle-man"of the request-response communication between the client and your application.
Along the way, the cache will store each response that is deemed "cacheable"(See Making your Responses HTTP Cacheable). If the same resource is requested again,the cache sends the cached response to the client, ignoring your applicationentirely.
This type of cache is known as an HTTP gateway cache and many exist suchas Varnish, Squid in reverse proxy mode, and the Symfony reverse proxy.
Tip
Gateway caches are sometimes referred to as reverse proxy caches,surrogate caches, or even HTTP accelerators.
Symfony Reverse Proxy
Symfony comes with a reverse proxy (i.e. gateway cache) written in PHP.It's not a fully-featured reverse proxy cache like Varnish,but is a great way to start.
Tip
For details on setting up Varnish, see How to Use Varnish to Speed up my Website.
To enable the proxy, first create a caching kernel:
- // src/CacheKernel.php
- namespace App;
- use Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache;
- class CacheKernel extends HttpCache
- {
- }
Modify the code of your front controller to wrap the default kernel into thecaching kernel:
- // public/index.php
- + use App\CacheKernel;
- use App\Kernel;
- // ...
- $kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
- + // Wrap the default Kernel with the CacheKernel one in 'prod' environment
- + if ('prod' === $kernel->getEnvironment()) {
- + $kernel = new CacheKernel($kernel);
- + }
- $request = Request::createFromGlobals();
- // ...
The caching kernel will immediately act as a reverse proxy: caching responsesfrom your application and returning them to the client.
Caution
If you're using the framework.http_method_overrideoption to read the HTTP method from a _method
parameter, see theabove link for a tweak you need to make.
Tip
The cache kernel has a special getLog()
method that returns a stringrepresentation of what happened in the cache layer. In the developmentenvironment, use it to debug and validate your cache strategy:
- error_log($kernel->getLog());
The CacheKernel
object has a sensible default configuration, but it can befinely tuned via a set of options you can set by overriding thegetOptions()
method:
- // src/CacheKernel.php
- namespace App;
- use Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache;
- class CacheKernel extends HttpCache
- {
- protected function getOptions()
- {
- return [
- 'default_ttl' => 0,
- // ...
- ];
- }
- }
For a full list of the options and their meaning, see theHttpCache::__construct() documentation
.
When you're in debug mode (the second argument of Kernel
constructor in thefront controller is true
), Symfony automatically adds an X-Symfony-Cache
header to the response. You can also use the trace_level
configoption and set it to either none
, short
or full
toadd this information.
short
will add the information for the master request only.It's written in a concise way that makes it easy to record theinformation in your server log files. For example, in Apache you canuse %{X-Symfony-Cache}o
in LogFormat
format statements.This information can be used to extract general information aboutcache efficiency of your routes.
Tip
You can change the name of the header used for the traceinformation using the trace_header
config option.
New in version 4.3: The trace_level
and trace_header
configuration optionswere introduced in Symfony 4.3.
Changing from one Reverse Proxy to another
The Symfony reverse proxy is a great tool to use when developing yourwebsite or when you deploy your website to a shared host where you cannotinstall anything beyond PHP code. But being written in PHP, it cannotbe as fast as a proxy written in C.
Fortunately, since all reverse proxies are effectively the same, you shouldbe able to switch to something more robust - like Varnish - without any problems.See How to use Varnish
Making your Responses HTTP Cacheable
Once you've added a reverse proxy cache (e.g. like the Symfony reverse proxy or Varnish),you're ready to cache your responses. To do that, you need to communicate to yourcache which responses are cacheable and for how long. This is done by setting HTTPcache headers on the response.
HTTP specifies four response cache headers that you can set to enable caching:
Cache-Control
Expires
ETag
Last-Modified
These four headers are used to help cache your responses via two different models:Expiration CachingUsed to cache your entire response for a specific amount of time (e.g. 24 hours).Simple, but cache invalidation is more difficult.
- Validation CachingMore complex: used to cache your response, but allows you to dynamically invalidateit as soon as your content changes.
Reading the HTTP Specification
All of the HTTP headers you'll read about are not invented by Symfony! They'repart of an HTTP specification that's used by sites all over the web. To dig deeperinto HTTP Caching, check out the documents RFC 7234 - Caching andRFC 7232 - Conditional Requests.
As a web developer, you are strongly urged to read the specification. Itsclarity and power - even more than fifteen years after its creation - isinvaluable. Don't be put-off by the appearance of the spec - its contentsare much more beautiful than its cover!
Expiration Caching
The easiest way to cache a response is by caching it for a specific amount of time:
- // src/Controller/BlogController.php
- use Symfony\Component\HttpFoundation\Response;
- // ...
- public function index()
- {
- // somehow create a Response object, like by rendering a template
- $response = $this->render('blog/index.html.twig', []);
- // cache for 3600 seconds
- $response->setSharedMaxAge(3600);
- // (optional) set a custom Cache-Control directive
- $response->headers->addCacheControlDirective('must-revalidate', true);
- return $response;
- }
Thanks to this new code, your HTTP response will have the following header:
- Cache-Control: public, s-maxage=3600, must-revalidate
This tells your HTTP reverse proxy to cache this response for 3600 seconds. If anyone_requests this URL again before 3600 seconds, your application _won't be hit at all.If you're using the Symfony reverse proxy, look at the X-Symfony-Cache
headerfor debugging information about cache hits and misses.
Tip
The URI of the request is used as the cache key (unless you vary).
This provides great performance and is simple to use. But, cache _invalidation_is not supported. If your content change, you'll need to wait until your cacheexpires for the page to update.
Tip
Actually, you can manually invalidate your cache, but it's not part of theHTTP Caching spec. See Cache Invalidation.
If you need to set cache headers for many different controller actions, check outFOSHttpCacheBundle. It provides a way to define cache headers based on the URLpattern and other request properties.
Finally, for more information about expiration caching, see HTTP Cache Expiration.
Validation Caching
With expiration caching, you say "cache for 3600 seconds!". But, when someoneupdates cached content, you won't see that content on your site until the cacheexpires.
If you need to see updated content immediately, you either need toinvalidate your cache or use the validationcaching model.
For details, see HTTP Cache Validation.
Safe Methods: Only caching GET or HEAD requests
HTTP caching only works for "safe" HTTP methods (like GET and HEAD). This meanstwo things:
- Don't try to cache PUT or DELETE requests. It won't work and with good reason.These methods are meant to be used when mutating the state of your application(e.g. deleting a blog post). Caching them would prevent certain requests from hittingand mutating your application.
- POST requests are generally considered uncacheable, but they can be cachedwhen they include explicit freshness information. However, POST caching is notwidely implemented, so you should avoid it if possible.
- You should never change the state of your application (e.g. update a blog post)when responding to a GET or HEAD request. If those requests are cached, futurerequests may not actually hit your server.
More Response Methods
The Response class provides many more methods related to the cache. Here arethe most useful ones:
- // marks the Response stale
- $response->expire();
- // forces the response to return a proper 304 response with no content
- $response->setNotModified();
Additionally, most cache-related HTTP headers can be set via the singlesetCache()
method:
- // sets cache settings in one call
- $response->setCache([
- 'etag' => $etag,
- 'last_modified' => $date,
- 'max_age' => 10,
- 's_maxage' => 10,
- 'public' => true,
- // 'private' => true,
- ]);
Cache Invalidation
Cache invalidation is not part of the HTTP specification. Still, it can be reallyuseful to delete various HTTP cache entries as soon as some content on your siteis updated.
For details, see Cache Invalidation.
Using Edge Side Includes
When pages contain dynamic parts, you may not be able to cache entire pages,but only parts of it. Read Working with Edge Side Includes to find out how to configuredifferent cache strategies for specific parts of your page.
HTTP Caching and User Sessions
Whenever the session is started during a request, Symfony turns the responseinto a private non-cacheable response. This is the best default behavior to notcache private user information (e.g. a shopping cart, a user profile details,etc.) and expose it to other visitors.
However, even requests making use of the session can be cached under somecircumstances. For example, information related to some user group could becached for all the users belonging to that group. Handling these advancedcaching scenarios is out of the scope of Symfony, but they can be solved withthe FOSHttpCacheBundle.
In order to disable the default Symfony behavior that makes requests using thesession uncacheable, add the following internal header to your response andSymfony won't modify it:
- use Symfony\Component\HttpKernel\EventListener\AbstractSessionListener;
- $response->headers->set(AbstractSessionListener::NO_AUTO_CACHE_CONTROL_HEADER, 'true');
Summary
Symfony was designed to follow the proven rules of the road: HTTP. Cachingis no exception. Mastering the Symfony cache system means becoming familiarwith the HTTP cache models and using them effectively. This means that, insteadof relying only on Symfony documentation and code examples, you have accessto a world of knowledge related to HTTP caching and gateway caches such asVarnish.