Skip to content

Commit 5d74a67

Browse files
committed
Made http cache chapter best-practices-compatible and lots of other fixes
1 parent 09f01f7 commit 5d74a67

File tree

1 file changed

+121
-65
lines changed

1 file changed

+121
-65
lines changed

book/http_cache.rst

Lines changed: 121 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,12 @@ kernel::
155155
$kernel->loadClassCache();
156156
// wrap the default AppKernel with the AppCache one
157157
$kernel = new AppCache($kernel);
158+
158159
$request = Request::createFromGlobals();
160+
159161
$response = $kernel->handle($request);
160162
$response->send();
163+
161164
$kernel->terminate($request, $response);
162165

163166
The caching kernel will immediately act as a reverse proxy - caching responses
@@ -574,16 +577,22 @@ each ``ETag`` must be unique across all representations of the same resource.
574577

575578
To see a simple implementation, generate the ETag as the md5 of the content::
576579

580+
// src/AppBundle/Controller/DefaultController.php
581+
namespace AppBundle\Controller;
582+
577583
use Symfony\Component\HttpFoundation\Request;
578584

579-
public function indexAction(Request $request)
585+
class DefaultController extends Controller
580586
{
581-
$response = $this->render('MyBundle:Main:index.html.twig');
582-
$response->setETag(md5($response->getContent()));
583-
$response->setPublic(); // make sure the response is public/cacheable
584-
$response->isNotModified($request);
587+
public function homepageAction(Request $request)
588+
{
589+
$response = $this->render('homepage.html.twig');
590+
$response->setETag(md5($response->getContent()));
591+
$response->setPublic(); // make sure the response is public/cacheable
592+
$response->isNotModified($request);
585593

586-
return $response;
594+
return $response;
595+
}
587596
}
588597

589598
The :method:`Symfony\\Component\\HttpFoundation\\Response::isNotModified`
@@ -630,28 +639,36 @@ For instance, you can use the latest update date for all the objects needed to
630639
compute the resource representation as the value for the ``Last-Modified``
631640
header value::
632641

642+
// src/AppBundle/Controller/ArticleController.php
643+
namespace AppBundle\Controller;
644+
645+
// ...
633646
use Symfony\Component\HttpFoundation\Request;
647+
use AppBundle\Entity\Article;
634648

635-
public function showAction($articleSlug, Request $request)
649+
class ArticleController extends Controller
636650
{
637-
// ...
651+
public function showAction(Article $article, Request $request)
652+
{
653+
$author = $article->getAuthor();
638654

639-
$articleDate = new \DateTime($article->getUpdatedAt());
640-
$authorDate = new \DateTime($author->getUpdatedAt());
655+
$articleDate = new \DateTime($article->getUpdatedAt());
656+
$authorDate = new \DateTime($author->getUpdatedAt());
641657

642-
$date = $authorDate > $articleDate ? $authorDate : $articleDate;
658+
$date = $authorDate > $articleDate ? $authorDate : $articleDate;
643659

644-
$response->setLastModified($date);
645-
// Set response as public. Otherwise it will be private by default.
646-
$response->setPublic();
660+
$response->setLastModified($date);
661+
// Set response as public. Otherwise it will be private by default.
662+
$response->setPublic();
647663

648-
if ($response->isNotModified($request)) {
649-
return $response;
650-
}
664+
if ($response->isNotModified($request)) {
665+
return $response;
666+
}
651667

652-
// ... do more work to populate the response with the full content
668+
// ... do more work to populate the response with the full content
653669

654-
return $response;
670+
return $response;
671+
}
655672
}
656673

657674
The :method:`Symfony\\Component\\HttpFoundation\\Response::isNotModified`
@@ -680,40 +697,46 @@ Put another way, the less you do in your application to return a 304 response,
680697
the better. The ``Response::isNotModified()`` method does exactly that by
681698
exposing a simple and efficient pattern::
682699

700+
// src/AppBundle/Controller/ArticleController.php
701+
namespace AppBundle\Controller;
702+
703+
// ...
683704
use Symfony\Component\HttpFoundation\Response;
684705
use Symfony\Component\HttpFoundation\Request;
685706

686-
public function showAction($articleSlug, Request $request)
707+
class ArticleController extends Controller
687708
{
688-
// Get the minimum information to compute
689-
// the ETag or the Last-Modified value
690-
// (based on the Request, data is retrieved from
691-
// a database or a key-value store for instance)
692-
$article = ...;
693-
694-
// create a Response with an ETag and/or a Last-Modified header
695-
$response = new Response();
696-
$response->setETag($article->computeETag());
697-
$response->setLastModified($article->getPublishedAt());
698-
699-
// Set response as public. Otherwise it will be private by default.
700-
$response->setPublic();
701-
702-
// Check that the Response is not modified for the given Request
703-
if ($response->isNotModified($request)) {
704-
// return the 304 Response immediately
705-
return $response;
706-
}
709+
public function showAction($articleSlug, Request $request)
710+
{
711+
// Get the minimum information to compute
712+
// the ETag or the Last-Modified value
713+
// (based on the Request, data is retrieved from
714+
// a database or a key-value store for instance)
715+
$article = ...;
716+
717+
// create a Response with an ETag and/or a Last-Modified header
718+
$response = new Response();
719+
$response->setETag($article->computeETag());
720+
$response->setLastModified($article->getPublishedAt());
707721

708-
// do more work here - like retrieving more data
709-
$comments = ...;
722+
// Set response as public. Otherwise it will be private by default.
723+
$response->setPublic();
710724

711-
// or render a template with the $response you've already started
712-
return $this->render(
713-
'MyBundle:MyController:article.html.twig',
714-
array('article' => $article, 'comments' => $comments),
715-
$response
716-
);
725+
// Check that the Response is not modified for the given Request
726+
if ($response->isNotModified($request)) {
727+
// return the 304 Response immediately
728+
return $response;
729+
}
730+
731+
// do more work here - like retrieving more data
732+
$comments = ...;
733+
734+
// or render a template with the $response you've already started
735+
return $this->render('Article/show.html.twig', array(
736+
'article' => $article,
737+
'comments' => $comments
738+
), $response);
739+
}
717740
}
718741

719742
When the ``Response`` is not modified, the ``isNotModified()`` automatically sets
@@ -863,10 +886,10 @@ Here is how you can configure the Symfony reverse proxy to support the
863886

864887
// app/AppCache.php
865888

866-
// ...
867889
use Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache;
868890
use Symfony\Component\HttpFoundation\Request;
869891
use Symfony\Component\HttpFoundation\Response;
892+
// ...
870893

871894
class AppCache extends HttpCache
872895
{
@@ -925,7 +948,7 @@ have one limitation: they can only cache whole pages. If you can't cache
925948
whole pages or if parts of a page has "more" dynamic parts, you are out of
926949
luck. Fortunately, Symfony provides a solution for these cases, based on a
927950
technology called `ESI`_, or Edge Side Includes. Akamai wrote this specification
928-
almost 10 years ago, and it allows specific parts of a page to have a different
951+
almost 10 years ago and it allows specific parts of a page to have a different
929952
caching strategy than the main page.
930953

931954
The ESI specification describes tags you can embed in your pages to communicate
@@ -987,8 +1010,12 @@ First, to use ESI, be sure to enable it in your application configuration:
9871010
<container xmlns="http://symfony.com/schema/dic/symfony"
9881011
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
9891012
xmlns:framework="http://symfony.com/schema/dic/symfony"
990-
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd
991-
http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
1013+
xsi:schemaLocation="
1014+
http://symfony.com/schema/dic/services
1015+
http://symfony.com/schema/dic/services/services-1.0.xsd
1016+
http://symfony.com/schema/dic/symfony
1017+
http://symfony.com/schema/dic/symfony/symfony-1.0.xsd
1018+
">
9921019
9931020
<framework:config>
9941021
<!-- ... -->
@@ -1010,13 +1037,19 @@ independent of the rest of the page.
10101037

10111038
.. code-block:: php
10121039
1013-
public function indexAction()
1040+
// src/AppBundle/Controller/DefaultController.php
1041+
1042+
// ...
1043+
class DefaultController extends Controller
10141044
{
1015-
$response = $this->render('MyBundle:MyController:index.html.twig');
1016-
// set the shared max age - which also marks the response as public
1017-
$response->setSharedMaxAge(600);
1045+
public function aboutAction()
1046+
{
1047+
$response = $this->render('about.html.twig');
1048+
// set the shared max age - which also marks the response as public
1049+
$response->setSharedMaxAge(600);
10181050
1019-
return $response;
1051+
return $response;
1052+
}
10201053
}
10211054
10221055
In this example, the full-page cache has a lifetime of ten minutes.
@@ -1031,21 +1064,36 @@ matter), Symfony uses the standard ``render`` helper to configure ESI tags:
10311064

10321065
.. code-block:: jinja
10331066
1067+
{# app/Resources/views/about.html.twig #}
1068+
10341069
{# you can use a controller reference #}
1035-
{{ render_esi(controller('...:news', { 'maxPerPage': 5 })) }}
1070+
{{ render_esi(controller('AppBundle:News:latest', { 'maxPerPage': 5 })) }}
10361071
10371072
{# ... or a URL #}
10381073
{{ render_esi(url('latest_news', { 'maxPerPage': 5 })) }}
10391074
10401075
.. code-block:: html+php
10411076

1077+
<!-- app/Resources/views/about.html.php -->
1078+
1079+
// you can use a controller reference
1080+
use Symfony\Component\HttpKernel\Controller\ControllerReference;
10421081
<?php echo $view['actions']->render(
1043-
new \Symfony\Component\HttpKernel\Controller\ControllerReference('...:news', array('maxPerPage' => 5)),
1044-
array('strategy' => 'esi'))
1045-
?>
1082+
new ControllerReference(
1083+
'AppBundle:News:latest',
1084+
array('maxPerPage' => 5)
1085+
),
1086+
array('strategy' => 'esi')
1087+
) ?>
10461088

1089+
// ... or a URL
1090+
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
10471091
<?php echo $view['actions']->render(
1048-
$view['router']->generate('latest_news', array('maxPerPage' => 5), true),
1092+
$view['router']->generate(
1093+
'latest_news',
1094+
array('maxPerPage' => 5),
1095+
UrlGeneratorInterface::ABSOLUTE_URL
1096+
),
10491097
array('strategy' => 'esi'),
10501098
) ?>
10511099

@@ -1065,7 +1113,7 @@ if there is no gateway cache installed.
10651113
When using the default ``render`` function (or setting the renderer to
10661114
``inline``), Symfony merges the included page content into the main one
10671115
before sending the response to the client. But if you use the ``esi`` renderer
1068-
(i.e. call ``render_esi``), *and* if Symfony detects that it's talking to a
1116+
(i.e. call ``render_esi``) *and* if Symfony detects that it's talking to a
10691117
gateway cache that supports ESI, it generates an ESI include tag. But if there
10701118
is no gateway cache or if it does not support ESI, Symfony will just merge
10711119
the included page content within the main one as it would have done if you had
@@ -1082,11 +1130,19 @@ of the master page.
10821130

10831131
.. code-block:: php
10841132
1085-
public function newsAction($maxPerPage)
1133+
// src/AppBundle/Controller/NewsController.php
1134+
namespace AppBundle\Controller;
1135+
1136+
// ...
1137+
class NewsController extends Controller
10861138
{
1087-
// ...
1139+
public function latestAction($maxPerPage)
1140+
{
1141+
// ...
1142+
$response->setSharedMaxAge(60);
10881143
1089-
$response->setSharedMaxAge(60);
1144+
return $response;
1145+
}
10901146
}
10911147
10921148
With ESI, the full page cache will be valid for 600 seconds, but the news

0 commit comments

Comments
 (0)