发布于 2015-08-27 16:34:17 | 398 次阅读 | 评论: 0 | 来源: 网络整理
The nature of rich web applications means that they’re dynamic. No matter how efficient your application, each request will always contain more overhead than serving a static file.
And for most Web applications, that’s fine. Symfony is lightning fast, and unless you’re doing some serious heavy-lifting, each request will come back quickly without putting too much stress on your server.
But as your site grows, that overhead can become a problem. The processing that’s normally performed on every request should be done only once. This is exactly what caching aims to accomplish.
The most effective way to improve performance of an application is to cache the full output of a page and then bypass the application entirely on each subsequent request. Of course, this isn’t always possible for highly dynamic websites, or is it? In this chapter, you’ll see how the Symfony cache system works and why this is the best possible approach.
The Symfony cache system is different because it relies on the simplicity and power of the HTTP cache as defined in the HTTP specification. Instead of reinventing a caching methodology, Symfony embraces the standard that defines basic communication on the Web. Once you understand the fundamental HTTP validation and expiration caching models, you’ll be ready to master the Symfony cache system.
For the purposes of learning how to cache with Symfony, the subject is covered in four steps:
Since caching with HTTP isn’t unique to Symfony, many articles already exist on the topic. If you’re new to HTTP caching, Ryan Tomayko’s article Things Caches Do is highly recommended . Another in-depth resource is Mark Nottingham’s Cache Tutorial.
When caching with HTTP, the cache is separated from your application entirely and sits between your application and the client making the request.
The job of the cache is to accept requests from the client and pass them back to your application. The cache will also receive responses back from your 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 HTTP缓存介绍). If the same resource is requested again, the cache sends the cached response to the client, ignoring your application entirely.
This type of cache is known as a HTTP gateway cache and many exist such as Varnish, Squid in reverse proxy mode, and the Symfony reverse proxy.
But a gateway cache isn’t the only type of cache. In fact, the HTTP cache headers sent by your application are consumed and interpreted by up to three different types of caches:
小技巧
Gateway caches are sometimes referred to as reverse proxy caches, surrogate caches, or even HTTP accelerators.
注解
The significance of private versus shared caches will become more obvious when caching responses containing content that is specific to exactly one user (e.g. account information) is discussed.
Each response from your application will likely go through one or both of the first two cache types. These caches are outside of your control but follow the HTTP cache directions set in the response.
Symfony comes with a reverse proxy (also called a gateway cache) written in PHP. Enable it and cacheable responses from your application will start to be cached right away. Installing it is just as easy. Each new Symfony application comes with a pre-configured caching kernel (AppCache
) that wraps the default one (AppKernel
). The caching Kernel is the reverse proxy.
To enable caching, modify the code of a front controller to use the caching kernel:
// web/app.php require_once __DIR__.'/../app/bootstrap.php.cache'; require_once __DIR__.'/../app/AppKernel.php'; require_once __DIR__.'/../app/AppCache.php'; use SymfonyComponentHttpFoundationRequest; $kernel = new AppKernel('prod', false); $kernel->loadClassCache(); // wrap the default AppKernel with the AppCache one $kernel = new AppCache($kernel); $request = Request::createFromGlobals(); $response = $kernel->handle($request); $response->send(); $kernel->terminate($request, $response);
The caching kernel will immediately act as a reverse proxy - caching responses from your application and returning them to the client.
警告
If you’re using the framework.http_method_override option to read the HTTP method from a _method
parameter, see the above link for a tweak you need to make.
小技巧
The cache kernel has a special getLog()
method that returns a string representation of what happened in the cache layer. In the development environment, use it to debug and validate your cache strategy:
error_log($kernel->getLog());
The AppCache
object has a sensible default configuration, but it can be finely tuned via a set of options you can set by overriding the getOptions()
method:
// app/AppCache.php use SymfonyBundleFrameworkBundleHttpCacheHttpCache; class AppCache extends HttpCache { protected function getOptions() { return array( 'debug' => false, 'default_ttl' => 0, 'private_headers' => array('Authorization', 'Cookie'), 'allow_reload' => false, 'allow_revalidate' => false, 'stale_while_revalidate' => 2, 'stale_if_error' => 60, ); } }
小技巧
Unless overridden in getOptions()
, the debug
option will be set to automatically be the debug value of the wrapped AppKernel
.
Here is a list of the main options:
default_ttl
Cache-Control
or Expires
headers override this value (default: 0
).private_headers
Cache-Control
behavior on responses that don’t explicitly state whether the response is public
or private
via a Cache-Control
directive (default: Authorization
and Cookie
).allow_reload
Cache-Control
“no-cache” directive in the request. Set it to true
for compliance with RFC 2616 (default: false
).allow_revalidate
Cache-Control
“max-age=0” directive in the request. Set it to true
for compliance with RFC 2616 (default: false).stale_while_revalidate
2
); this setting is overridden by the stale-while-revalidate
HTTP Cache-Control
extension (see RFC 5861).stale_if_error
60
). This setting is overridden by the stale-if-error
HTTP Cache-Control
extension (see RFC 5861).If debug
is true
, Symfony automatically adds an X-Symfony-Cache
header to the response containing useful information about cache hits and misses.
Changing from one Reverse Proxy to another
The Symfony reverse proxy is a great tool to use when developing your website or when you deploy your website to a shared host where you cannot install anything beyond PHP code. But being written in PHP, it cannot be as fast as a proxy written in C. That’s why it is highly recommended you use Varnish or Squid on your production servers if possible. The good news is that the switch from one proxy server to another is easy and transparent as no code modification is needed in your application. Start easy with the Symfony reverse proxy and upgrade later to Varnish when your traffic increases.
For more information on using Varnish with Symfony, see the How to use Varnish cookbook chapter.
注解
The performance of the Symfony reverse proxy is independent of the complexity of the application. That’s because the application kernel is only booted when the request needs to be forwarded to it.
To take advantage of the available cache layers, your application must be able to communicate which responses are cacheable and the rules that govern when/how that cache should become stale. This is done by setting HTTP cache headers on the response.
小技巧
Keep in mind that “HTTP” is nothing more than the language (a simple text language) that web clients (e.g. browsers) and web servers use to communicate with each other. HTTP caching is the part of that language that allows clients and servers to exchange information related to caching.
HTTP specifies four response cache headers that are looked at here:
Cache-Control
Expires
ETag
Last-Modified
The most important and versatile header is the Cache-Control
header, which is actually a collection of various cache information.
注解
Each of the headers will be explained in full detail in the HTTP Expiration, Validation and Invalidation section.
The Cache-Control
header is unique in that it contains not one, but various pieces of information about the cacheability of a response. Each piece of information is separated by a comma:
Cache-Control: private, max-age=0, must-revalidate Cache-Control: max-age=3600, must-revalidate
Symfony provides an abstraction around the Cache-Control
header to make its creation more manageable:
// ... use SymfonyComponentHttpFoundationResponse; $response = new Response(); // mark the response as either public or private $response->setPublic(); $response->setPrivate(); // set the private or shared max age $response->setMaxAge(600); $response->setSharedMaxAge(600); // set a custom Cache-Control directive $response->headers->addCacheControlDirective('must-revalidate', true);
小技巧
If you need to set cache headers for many different controller actions, you might want to look into the FOSHttpCacheBundle. It provides a way to define cache headers based on the URL pattern and other request properties.
Both gateway and proxy caches are considered “shared” caches as the cached content is shared by more than one user. If a user-specific response were ever mistakenly stored by a shared cache, it might be returned later to any number of different users. Imagine if your account information were cached and then returned to every subsequent user who asked for their account page!
To handle this situation, every response may be set to be public or private:
Symfony conservatively defaults each response to be private. To take advantage of shared caches (like the Symfony reverse proxy), the response will need to be explicitly set as public.
HTTP caching only works for “safe” HTTP methods (like GET and HEAD). Being safe means that you never change the application’s state on the server when serving the request (you can of course log information, cache data, etc). This has two very reasonable consequences:
HTTP 1.1 allows caching anything by default unless there is an explicit Cache-Control
header. In practice, most caches do nothing when requests have a cookie, an authorization header, use a non-safe method (i.e. PUT, POST, DELETE), or when responses have a redirect status code.
Symfony automatically sets a sensible and conservative Cache-Control
header when none is set by the developer by following these rules:
Cache-Control
, Expires
, ETag
or Last-Modified
), Cache-Control
is set to no-cache
, meaning that the response will not be cached;Cache-Control
is empty (but one of the other cache headers is present), its value is set to private, must-revalidate
;Cache-Control
directive is set, and no public
or private
directives have been explicitly added, Symfony adds the private
directive automatically (except when s-maxage
is set).The HTTP specification defines two caching models:
Cache-Control
and/or an Expires
header. Caches that understand expiration will not make the same request until the cached version reaches its expiration time and becomes “stale”;Etag
header) and/or a timestamp (the Last-Modified
header) to check if the page has changed since being cached.The goal of both models is to never generate the same response twice by relying on a cache to store and return “fresh” responses. To achieve long caching times but still provide updated content immediately, cache invalidation is sometimes used.
Reading the HTTP Specification
The HTTP specification defines a simple but powerful language in which clients and servers can communicate. As a web developer, the request-response model of the specification dominates your work. Unfortunately, the actual specification document - RFC 2616 - can be difficult to read.
There is an ongoing effort (HTTP Bis) to rewrite the RFC 2616. It does not describe a new version of HTTP, but mostly clarifies the original HTTP specification. The organization is also improved as the specification is split into seven parts; everything related to HTTP caching can be found in two dedicated parts (P4 - Conditional Requests and P6 - Caching: Browser and intermediary caches).
As a web developer, you are strongly urged to read the specification. Its clarity and power - even more than ten years after its creation - is invaluable. Don’t be put-off by the appearance of the spec - its contents are much more beautiful than its cover.
The expiration model is the more efficient and straightforward of the two caching models and should be used whenever possible. When a response is cached with an expiration, the cache will store the response and return it directly without hitting the application until it expires.
The expiration model can be accomplished using one of two, nearly identical, HTTP headers: Expires
or Cache-Control
.
Expires
头信息设置过期时间¶According to the HTTP specification, “the Expires
header field gives the date/time after which the response is considered stale.” The Expires
header can be set with the setExpires()
Response
method. It takes a DateTime
instance as an argument:
$date = new DateTime(); $date->modify('+600 seconds'); $response->setExpires($date);
The resulting HTTP header will look like this:
Expires: Thu, 01 Mar 2011 16:00:00 GMT
注解
The setExpires()
method automatically converts the date to the GMT timezone as required by the specification.
Note that in HTTP versions before 1.1 the origin server wasn’t required to send the Date
header. Consequently, the cache (e.g. the browser) might need to rely on the local clock to evaluate the Expires
header making the lifetime calculation vulnerable to clock skew. Another limitation of the Expires
header is that the specification states that “HTTP/1.1 servers should not send Expires
dates more than one year in the future.”
Because of the Expires
header limitations, most of the time, you should use the Cache-Control
header instead. Recall that the Cache-Control
header is used to specify many different cache directives. For expiration, there are two directives, max-age
and s-maxage
. The first one is used by all caches, whereas the second one is only taken into account by shared caches:
// Sets the number of seconds after which the response // should no longer be considered fresh $response->setMaxAge(600); // Same as above but only for shared caches $response->setSharedMaxAge(600);
The Cache-Control
header would take on the following format (it may have additional directives):
Cache-Control: max-age=600, s-maxage=600
When a resource needs to be updated as soon as a change is made to the underlying data, the expiration model falls short. With the expiration model, the application won’t be asked to return the updated response until the cache finally becomes stale.
The validation model addresses this issue. Under this model, the cache continues to store responses. The difference is that, for each request, the cache asks the application if the cached response is still valid or if it needs to be regenerated. If the cache is still valid, your application should return a 304 status code and no content. This tells the cache that it’s ok to return the cached response.
Under this model, you only save CPU if you’re able to determine that the cached response is still valid by doing less work than generating the whole page again (see below for an implementation example).
小技巧
The 304 status code means “Not Modified”. It’s important because with this status code the response does not contain the actual content being requested. Instead, the response is simply a light-weight set of directions that tells the cache that it should use its stored version.
Like with expiration, there are two different HTTP headers that can be used to implement the validation model: ETag
and Last-Modified
.
ETag
头信息验证¶The ETag
header is a string header (called the “entity-tag”) that uniquely identifies one representation of the target resource. It’s entirely generated and set by your application so that you can tell, for example, if the /about
resource that’s stored by the cache is up-to-date with what your application would return. An ETag
is like a fingerprint and is used to quickly compare if two different versions of a resource are equivalent. Like fingerprints, each ETag
must be unique across all representations of the same resource.
To see a simple implementation, generate the ETag as the md5 of the content:
// src/AppBundle/Controller/DefaultController.php namespace AppBundleController; use SymfonyComponentHttpFoundationRequest; class DefaultController extends Controller { public function homepageAction(Request $request) { $response = $this->render('static/homepage.html.twig'); $response->setETag(md5($response->getContent())); $response->setPublic(); // make sure the response is public/cacheable $response->isNotModified($request); return $response; } }
The isNotModified()
method compares the If-None-Match
sent with the Request
with the ETag
header set on the Response
. If the two match, the method automatically sets the Response
status code to 304.
注解
The cache sets the If-None-Match
header on the request to the ETag
of the original cached response before sending the request back to the app. This is how the cache and server communicate with each other and decide whether or not the resource has been updated since it was cached.
This algorithm is simple enough and very generic, but you need to create the whole Response
before being able to compute the ETag, which is sub-optimal. In other words, it saves on bandwidth, but not CPU cycles.
In the 通过数据校验优化代码 section, you’ll see how validation can be used more intelligently to determine the validity of a cache without doing so much work.
小技巧
Symfony also supports weak ETags by passing true
as the second argument to the setETag()
method.
Last-Modified
头信息验证¶The Last-Modified
header is the second form of validation. According to the HTTP specification, “The Last-Modified
header field indicates the date and time at which the origin server believes the representation was last modified.” In other words, the application decides whether or not the cached content has been updated based on whether or not it’s been updated since the response was cached.
For instance, you can use the latest update date for all the objects needed to compute the resource representation as the value for the Last-Modified
header value:
// src/AppBundle/Controller/ArticleController.php namespace AppBundleController; // ... use SymfonyComponentHttpFoundationRequest; use AppBundleEntityArticle; class ArticleController extends Controller { public function showAction(Article $article, Request $request) { $author = $article->getAuthor(); $articleDate = new DateTime($article->getUpdatedAt()); $authorDate = new DateTime($author->getUpdatedAt()); $date = $authorDate > $articleDate ? $authorDate : $articleDate; $response->setLastModified($date); // Set response as public. Otherwise it will be private by default. $response->setPublic(); if ($response->isNotModified($request)) { return $response; } // ... do more work to populate the response with the full content return $response; } }
The isNotModified()
method compares the If-Modified-Since
header sent by the request with the Last-Modified
header set on the response. If they are equivalent, the Response
will be set to a 304 status code.
注解
The cache sets the If-Modified-Since
header on the request to the Last-Modified
of the original cached response before sending the request back to the app. This is how the cache and server communicate with each other and decide whether or not the resource has been updated since it was cached.
The main goal of any caching strategy is to lighten the load on the application. Put another way, the less you do in your application to return a 304 response, the better. The Response::isNotModified()
method does exactly that by exposing a simple and efficient pattern:
// src/AppBundle/Controller/ArticleController.php namespace AppBundleController; // ... use SymfonyComponentHttpFoundationResponse; use SymfonyComponentHttpFoundationRequest; class ArticleController extends Controller { public function showAction($articleSlug, Request $request) { // Get the minimum information to compute // the ETag or the Last-Modified value // (based on the Request, data is retrieved from // a database or a key-value store for instance) $article = ...; // create a Response with an ETag and/or a Last-Modified header $response = new Response(); $response->setETag($article->computeETag()); $response->setLastModified($article->getPublishedAt()); // Set response as public. Otherwise it will be private by default. $response->setPublic(); // Check that the Response is not modified for the given Request if ($response->isNotModified($request)) { // return the 304 Response immediately return $response; } // do more work here - like retrieving more data $comments = ...; // or render a template with the $response you've already started return $this->render('article/show.html.twig', array( 'article' => $article, 'comments' => $comments ), $response); } }
When the Response
is not modified, the isNotModified()
automatically sets the response status code to 304
, removes the content, and removes some headers that must not be present for 304
responses (see setNotModified()
).
So far, it’s been assumed that each URI has exactly one representation of the target resource. By default, HTTP caching is done by using the URI of the resource as the cache key. If two people request the same URI of a cacheable resource, the second person will receive the cached version.
Sometimes this isn’t enough and different versions of the same URI need to be cached based on one or more request header values. For instance, if you compress pages when the client supports it, any given URI has two representations: one when the client supports compression, and one when it does not. This determination is done by the value of the Accept-Encoding
request header.
In this case, you need the cache to store both a compressed and uncompressed version of the response for the particular URI and return them based on the request’s Accept-Encoding
value. This is done by using the Vary
response header, which is a comma-separated list of different headers whose values trigger a different representation of the requested resource:
Vary: Accept-Encoding, User-Agent
小技巧
This particular Vary
header would cache different versions of each resource based on the URI and the value of the Accept-Encoding
and User-Agent
request header.
The Response
object offers a clean interface for managing the Vary
header:
// set one vary header $response->setVary('Accept-Encoding'); // set multiple vary headers $response->setVary(array('Accept-Encoding', 'User-Agent'));
The setVary()
method takes a header name or an array of header names for which the response varies.
You can of course use both validation and expiration within the same Response
. As expiration wins over validation, you can easily benefit from the best of both worlds. In other words, by using both expiration and validation, you can instruct the cache to serve the cached content, while checking back at some interval (the expiration) to verify that the content is still valid.
小技巧
You can also define HTTP caching headers for expiration and validation by using annotations. See the FrameworkExtraBundle documentation.
The Response class provides many more methods related to the cache. Here are the most useful ones:
// Marks the Response stale $response->expire(); // Force the response to return a proper 304 response with no content $response->setNotModified();
Additionally, most cache-related HTTP headers can be set via the single setCache()
method:
// Set cache settings in one call $response->setCache(array( 'etag' => $etag, 'last_modified' => $date, 'max_age' => 10, 's_maxage' => 10, 'public' => true, // 'private' => true, ));
“There are only two hard things in Computer Science: cache invalidation and naming things.” – Phil Karlton
Once an URL is cached by a gateway cache, the cache will not ask the application for that content anymore. This allows the cache to provide fast responses and reduces the load on your application. However, you risk delivering outdated content. A way out of this dilemma is to use long cache lifetimes, but to actively notify the gateway cache when content changes. Reverse proxies usually provide a channel to receive such notifications, typically through special HTTP requests.
警告
While cache invalidation is powerful, avoid it when possible. If you fail to invalidate something, outdated caches will be served for a potentially long time. Instead, use short cache lifetimes or use the validation model, and adjust your controllers to perform efficient validation checks as explained in 通过数据校验优化代码.
Furthermore, since invalidation is a topic specific to each type of reverse proxy, using this concept will tie you to a specific reverse proxy or need additional efforts to support different proxies.
Sometimes, however, you need that extra performance you can get when explicitly invalidating. For invalidation, your application needs to detect when content changes and tell the cache to remove the URLs which contain that data from its cache.
小技巧
If you want to use cache invalidation, have a look at the FOSHttpCacheBundle. This bundle provides services to help with various cache invalidation concepts, and also documents the configuration for the a couple of common caching proxies.
If one content corresponds to one URL, the PURGE
model works well. You send a request to the cache proxy with the HTTP method PURGE
(using the word “PURGE” is a convention, technically this can be any string) instead of GET
and make the cache proxy detect this and remove the data from the cache instead of going to the application to get a response.
Here is how you can configure the Symfony reverse proxy to support the PURGE
HTTP method:
// app/AppCache.php use SymfonyBundleFrameworkBundleHttpCacheHttpCache; use SymfonyComponentHttpFoundationRequest; use SymfonyComponentHttpFoundationResponse; // ... class AppCache extends HttpCache { protected function invalidate(Request $request, $catch = false) { if ('PURGE' !== $request->getMethod()) { return parent::invalidate($request, $catch); } if ('127.0.0.1' !== $request->getClientIp()) { return new Response( 'Invalid HTTP method', Response::HTTP_BAD_REQUEST ); } $response = new Response(); if ($this->getStore()->purge($request->getUri())) { $response->setStatusCode(200, 'Purged'); } else { $response->setStatusCode(200, 'Not found'); } return $response; } }
警告
You must protect the PURGE
HTTP method somehow to avoid random people purging your cached data.
Purge instructs the cache to drop a resource in all its variants (according to the Vary
header, see above). An alternative to purging is refreshing a content. Refreshing means that the caching proxy is instructed to discard its local cache and fetch the content again. This way, the new content is already available in the cache. The drawback of refreshing is that variants are not invalidated.
In many applications, the same content bit is used on various pages with different URLs. More flexible concepts exist for those cases:
Gateway caches are a great way to make your website perform better. But they have one limitation: they can only cache whole pages. If you can’t cache whole pages or if parts of a page has “more” dynamic parts, you are out of luck. Fortunately, Symfony provides a solution for these cases, based on a technology called ESI, or Edge Side Includes. Akamai wrote this specification almost 10 years ago and it allows specific parts of a page to have a different caching strategy than the main page.
The ESI specification describes tags you can embed in your pages to communicate with the gateway cache. Only one tag is implemented in Symfony, include
, as this is the only useful one outside of Akamai context:
<!DOCTYPE html> <html> <body> <!-- ... some content --> <!-- Embed the content of another page here --> <esi:include src="http://..." /> <!-- ... more content --> </body> </html>
注解
Notice from the example that each ESI tag has a fully-qualified URL. An ESI tag represents a page fragment that can be fetched via the given URL.
When a request is handled, the gateway cache fetches the entire page from its cache or requests it from the backend application. If the response contains one or more ESI tags, these are processed in the same way. In other words, the gateway cache either retrieves the included page fragment from its cache or requests the page fragment from the backend application again. When all the ESI tags have been resolved, the gateway cache merges each into the main page and sends the final content to the client.
All of this happens transparently at the gateway cache level (i.e. outside of your application). As you’ll see, if you choose to take advantage of ESI tags, Symfony makes the process of including them almost effortless.
First, to use ESI, be sure to enable it in your application configuration:
# app/config/config.yml framework: # ... esi: { enabled: true }
<!-- app/config/config.xml --> <?xml version="1.0" encoding="UTF-8" ?> <container xmlns="http://symfony.com/schema/dic/symfony" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:framework="http://symfony.com/schema/dic/symfony" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> <framework:config> <!-- ... --> <framework:esi enabled="true" /> </framework:config> </container>
// app/config/config.php $container->loadFromExtension('framework', array( // ... 'esi' => array('enabled' => true), ));
Now, suppose you have a page that is relatively static, except for a news ticker at the bottom of the content. With ESI, you can cache the news ticker independent of the rest of the page.
// src/AppBundle/Controller/DefaultController.php // ... class DefaultController extends Controller { public function aboutAction() { $response = $this->render('static/about.html.twig'); // set the shared max age - which also marks the response as public $response->setSharedMaxAge(600); return $response; } }
In this example, the full-page cache has a lifetime of ten minutes. Next, include the news ticker in the template by embedding an action. This is done via the render
helper (See 嵌入Controller for more details).
As the embedded content comes from another page (or controller for that matter), Symfony uses the standard render
helper to configure ESI tags:
{# app/Resources/views/static/about.html.twig #} {# you can use a controller reference #} {{ render_esi(controller('AppBundle:News:latest', { 'maxPerPage': 5 })) }} {# ... or a URL #} {{ render_esi(url('latest_news', { 'maxPerPage': 5 })) }}
<!-- app/Resources/views/static/about.html.php --> // you can use a controller reference use SymfonyComponentHttpKernelControllerControllerReference; <?php echo $view['actions']->render( new ControllerReference( 'AppBundle:News:latest', array('maxPerPage' => 5) ), array('strategy' => 'esi') ) ?> // ... or a URL use SymfonyComponentRoutingGeneratorUrlGeneratorInterface; <?php echo $view['actions']->render( $view['router']->generate( 'latest_news', array('maxPerPage' => 5), UrlGeneratorInterface::ABSOLUTE_URL ), array('strategy' => 'esi'), ) ?>
By using the esi
renderer (via the render_esi
Twig function), you tell Symfony that the action should be rendered as an ESI tag. You might be wondering why you would want to use a helper instead of just writing the ESI tag yourself. That’s because using a helper makes your application work even if there is no gateway cache installed.
小技巧
As you’ll see below, the maxPerPage
variable you pass is available as an argument to your controller (i.e. $maxPerPage
). The variables passed through render_esi
also become part of the cache key so that you have unique caches for each combination of variables and values.
When using the default render
function (or setting the renderer to inline
), Symfony merges the included page content into the main one before sending the response to the client. But if you use the esi
renderer (i.e. call render_esi
) and if Symfony detects that it’s talking to a gateway cache that supports ESI, it generates an ESI include tag. But if there is no gateway cache or if it does not support ESI, Symfony will just merge the included page content within the main one as it would have done if you had used render
.
注解
Symfony detects if a gateway cache supports ESI via another Akamai specification that is supported out of the box by the Symfony reverse proxy.
The embedded action can now specify its own caching rules, entirely independent of the master page.
// src/AppBundle/Controller/NewsController.php namespace AppBundleController; // ... class NewsController extends Controller { public function latestAction($maxPerPage) { // ... $response->setSharedMaxAge(60); return $response; } }
With ESI, the full page cache will be valid for 600 seconds, but the news component cache will only last for 60 seconds.
When using a controller reference, the ESI tag should reference the embedded action as an accessible URL so the gateway cache can fetch it independently of the rest of the page. Symfony takes care of generating a unique URL for any controller reference and it is able to route them properly thanks to the FragmentListener
that must be enabled in your configuration:
# app/config/config.yml framework: # ... fragments: { path: /_fragment }
<!-- app/config/config.xml --> <?xml version="1.0" encoding="UTF-8" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:doctrine="http://symfony.com/schema/dic/framework" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> <!-- ... --> <framework:config> <framework:fragments path="/_fragment" /> </framework:config> </container>
// app/config/config.php $container->loadFromExtension('framework', array( // ... 'fragments' => array('path' => '/_fragment'), ));
One great advantage of the ESI renderer is that you can make your application as dynamic as needed and at the same time, hit the application as little as possible.
小技巧
The listener only responds to local IP addresses or trusted proxies.
注解
Once you start using ESI, remember to always use the s-maxage
directive instead of max-age
. As the browser only ever receives the aggregated resource, it is not aware of the sub-components, and so it will obey the max-age
directive and cache the entire page. And you don’t want that.
The render_esi
helper supports two other useful options:
alt
alt
attribute on the ESI tag, which allows you to specify an alternative URL to be used if the src
cannot be found.ignore_errors
onerror
attribute will be added to the ESI with a value of continue
indicating that, in the event of a failure, the gateway cache will simply remove the ESI tag silently.Symfony was designed to follow the proven rules of the road: HTTP. Caching is no exception. Mastering the Symfony cache system means becoming familiar with the HTTP cache models and using them effectively. This means that, instead of relying only on Symfony documentation and code examples, you have access to a world of knowledge related to HTTP caching and gateway caches such as Varnish.