Skip to content

Commit c6ff013

Browse files
committed
Made http cache chapter best-practices-compatible and lots of other fixes
1 parent 4840768 commit c6ff013

File tree

1 file changed

+116
-63
lines changed

1 file changed

+116
-63
lines changed

book/http_cache.rst

Lines changed: 116 additions & 63 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
@@ -576,16 +579,22 @@ each ``ETag`` must be unique across all representations of the same resource.
576579

577580
To see a simple implementation, generate the ETag as the md5 of the content::
578581

582+
// src/AppBundle/Controller/DefaultController.php
583+
namespace AppBundle\Controller;
584+
579585
use Symfony\Component\HttpFoundation\Request;
580586

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

588-
return $response;
596+
return $response;
597+
}
589598
}
590599

591600
The :method:`Symfony\\Component\\HttpFoundation\\Response::isNotModified`
@@ -632,28 +641,36 @@ For instance, you can use the latest update date for all the objects needed to
632641
compute the resource representation as the value for the ``Last-Modified``
633642
header value::
634643

644+
// src/AppBundle/Controller/ArticleController.php
645+
namespace AppBundle\Controller;
646+
647+
// ...
635648
use Symfony\Component\HttpFoundation\Request;
649+
use AppBundle\Entity\Article;
636650

637-
public function showAction($articleSlug, Request $request)
651+
class ArticleController extends Controller
638652
{
639-
// ...
653+
public function showAction(Article $article, Request $request)
654+
{
655+
$author = $article->getAuthor();
640656

641-
$articleDate = new \DateTime($article->getUpdatedAt());
642-
$authorDate = new \DateTime($author->getUpdatedAt());
657+
$articleDate = new \DateTime($article->getUpdatedAt());
658+
$authorDate = new \DateTime($author->getUpdatedAt());
643659

644-
$date = $authorDate > $articleDate ? $authorDate : $articleDate;
660+
$date = $authorDate > $articleDate ? $authorDate : $articleDate;
645661

646-
$response->setLastModified($date);
647-
// Set response as public. Otherwise it will be private by default.
648-
$response->setPublic();
662+
$response->setLastModified($date);
663+
// Set response as public. Otherwise it will be private by default.
664+
$response->setPublic();
649665

650-
if ($response->isNotModified($request)) {
651-
return $response;
652-
}
666+
if ($response->isNotModified($request)) {
667+
return $response;
668+
}
653669

654-
// ... do more work to populate the response with the full content
670+
// ... do more work to populate the response with the full content
655671

656-
return $response;
672+
return $response;
673+
}
657674
}
658675

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

702+
// src/AppBundle/Controller/ArticleController.php
703+
namespace AppBundle\Controller;
704+
705+
// ...
685706
use Symfony\Component\HttpFoundation\Response;
686707
use Symfony\Component\HttpFoundation\Request;
687708

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

710-
// do more work here - like retrieving more data
711-
$comments = ...;
733+
// do more work here - like retrieving more data
734+
$comments = ...;
712735

713-
// or render a template with the $response you've already started
714-
return $this->render(
715-
'MyBundle:MyController:article.html.twig',
716-
array('article' => $article, 'comments' => $comments),
717-
$response
718-
);
736+
// or render a template with the $response you've already started
737+
return $this->render('Article/show.html.twig', array(
738+
'article' => $article,
739+
'comments' => $comments
740+
), $response);
741+
}
719742
}
720743

721744
When the ``Response`` is not modified, the ``isNotModified()`` automatically sets
@@ -865,10 +888,10 @@ Here is how you can configure the Symfony reverse proxy to support the
865888

866889
// app/AppCache.php
867890

868-
// ...
869891
use Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache;
870892
use Symfony\Component\HttpFoundation\Request;
871893
use Symfony\Component\HttpFoundation\Response;
894+
// ...
872895

873896
class AppCache extends HttpCache
874897
{
@@ -930,7 +953,7 @@ have one limitation: they can only cache whole pages. If you can't cache
930953
whole pages or if parts of a page has "more" dynamic parts, you are out of
931954
luck. Fortunately, Symfony provides a solution for these cases, based on a
932955
technology called `ESI`_, or Edge Side Includes. Akamai wrote this specification
933-
almost 10 years ago, and it allows specific parts of a page to have a different
956+
almost 10 years ago and it allows specific parts of a page to have a different
934957
caching strategy than the main page.
935958

936959
The ESI specification describes tags you can embed in your pages to communicate
@@ -996,6 +1019,7 @@ First, to use ESI, be sure to enable it in your application configuration:
9961019
http://symfony.com/schema/dic/services/services-1.0.xsd
9971020
http://symfony.com/schema/dic/symfony
9981021
http://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
1022+
">
9991023
10001024
<framework:config>
10011025
<!-- ... -->
@@ -1017,13 +1041,19 @@ independent of the rest of the page.
10171041

10181042
.. code-block:: php
10191043
1020-
public function indexAction()
1044+
// src/AppBundle/Controller/DefaultController.php
1045+
1046+
// ...
1047+
class DefaultController extends Controller
10211048
{
1022-
$response = $this->render('MyBundle:MyController:index.html.twig');
1023-
// set the shared max age - which also marks the response as public
1024-
$response->setSharedMaxAge(600);
1049+
public function aboutAction()
1050+
{
1051+
$response = $this->render('about.html.twig');
1052+
// set the shared max age - which also marks the response as public
1053+
$response->setSharedMaxAge(600);
10251054
1026-
return $response;
1055+
return $response;
1056+
}
10271057
}
10281058
10291059
In this example, the full-page cache has a lifetime of ten minutes.
@@ -1038,21 +1068,36 @@ matter), Symfony uses the standard ``render`` helper to configure ESI tags:
10381068

10391069
.. code-block:: jinja
10401070
1071+
{# app/Resources/views/about.html.twig #}
1072+
10411073
{# you can use a controller reference #}
1042-
{{ render_esi(controller('...:news', { 'maxPerPage': 5 })) }}
1074+
{{ render_esi(controller('AppBundle:News:latest', { 'maxPerPage': 5 })) }}
10431075
10441076
{# ... or a URL #}
10451077
{{ render_esi(url('latest_news', { 'maxPerPage': 5 })) }}
10461078
10471079
.. code-block:: html+php
10481080

1081+
<!-- app/Resources/views/about.html.php -->
1082+
1083+
// you can use a controller reference
1084+
use Symfony\Component\HttpKernel\Controller\ControllerReference;
10491085
<?php echo $view['actions']->render(
1050-
new \Symfony\Component\HttpKernel\Controller\ControllerReference('...:news', array('maxPerPage' => 5)),
1051-
array('strategy' => 'esi'))
1052-
?>
1086+
new ControllerReference(
1087+
'AppBundle:News:latest',
1088+
array('maxPerPage' => 5)
1089+
),
1090+
array('strategy' => 'esi')
1091+
) ?>
10531092

1093+
// ... or a URL
1094+
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
10541095
<?php echo $view['actions']->render(
1055-
$view['router']->generate('latest_news', array('maxPerPage' => 5), true),
1096+
$view['router']->generate(
1097+
'latest_news',
1098+
array('maxPerPage' => 5),
1099+
UrlGeneratorInterface::ABSOLUTE_URL
1100+
),
10561101
array('strategy' => 'esi'),
10571102
) ?>
10581103

@@ -1072,7 +1117,7 @@ if there is no gateway cache installed.
10721117
When using the default ``render`` function (or setting the renderer to
10731118
``inline``), Symfony merges the included page content into the main one
10741119
before sending the response to the client. But if you use the ``esi`` renderer
1075-
(i.e. call ``render_esi``), *and* if Symfony detects that it's talking to a
1120+
(i.e. call ``render_esi``) *and* if Symfony detects that it's talking to a
10761121
gateway cache that supports ESI, it generates an ESI include tag. But if there
10771122
is no gateway cache or if it does not support ESI, Symfony will just merge
10781123
the included page content within the main one as it would have done if you had
@@ -1089,11 +1134,19 @@ of the master page.
10891134

10901135
.. code-block:: php
10911136
1092-
public function newsAction($maxPerPage)
1137+
// src/AppBundle/Controller/NewsController.php
1138+
namespace AppBundle\Controller;
1139+
1140+
// ...
1141+
class NewsController extends Controller
10931142
{
1094-
// ...
1143+
public function latestAction($maxPerPage)
1144+
{
1145+
// ...
1146+
$response->setSharedMaxAge(60);
10951147
1096-
$response->setSharedMaxAge(60);
1148+
return $response;
1149+
}
10971150
}
10981151
10991152
With ESI, the full page cache will be valid for 600 seconds, but the news

0 commit comments

Comments
 (0)