@@ -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
163166The 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
577580To 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('static/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
591600The: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
632641compute the resource representation as the value for the ``Last-Modified ``
633642header 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
659676The: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,
682699the better. The ``Response::isNotModified() `` method does exactly that by
683700exposing 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
721744When 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
930953whole pages or if parts of a page has "more" dynamic parts, you are out of
931954luck. Fortunately, Symfony provides a solution for these cases, based on a
932955technology 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
934957caching strategy than the main page.
935958
936959The ESI specification describes tags you can embed in your pages to communicate
@@ -1017,13 +1040,19 @@ independent of the rest of the page.
10171040
10181041..code-block ::php
10191042
1020- public function indexAction()
1043+ // src/AppBundle/Controller/DefaultController.php
1044+
1045+ // ...
1046+ class DefaultController extends Controller
10211047 {
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);
1048+ public function aboutAction()
1049+ {
1050+ $response = $this->render('static/about.html.twig');
1051+ // set the shared max age - which also marks the response as public
1052+ $response->setSharedMaxAge(600);
10251053
1026- return $response;
1054+ return $response;
1055+ }
10271056 }
10281057
10291058 In this example, the full-page cache has a lifetime of ten minutes.
@@ -1038,21 +1067,36 @@ matter), Symfony uses the standard ``render`` helper to configure ESI tags:
10381067
10391068 ..code-block ::jinja
10401069
1070+ {# app/Resources/views/static/about.html.twig #}
1071+
10411072 {# you can use a controller reference #}
1042- {{ render_esi(controller('...:news ', { 'maxPerPage': 5 })) }}
1073+ {{ render_esi(controller('AppBundle:News:latest ', { 'maxPerPage': 5 })) }}
10431074
10441075 {# ... or a URL #}
10451076 {{ render_esi(url('latest_news', { 'maxPerPage': 5 })) }}
10461077
10471078 ..code-block ::html+php
10481079
1080+ <!-- app/Resources/views/static/about.html.php -->
1081+
1082+ // you can use a controller reference
1083+ use Symfony\C omponent\H ttpKernel\C ontroller\C ontrollerReference;
10491084 <?php echo $view['actions']->render(
1050- new\S ymfony\C omponent\H ttpKernel\C ontroller\C ontrollerReference('...:news', array('maxPerPage' => 5)),
1051- array('strategy' => 'esi'))
1052- ?>
1085+ new ControllerReference(
1086+ 'AppBundle:News: latest',
1087+ array('maxPerPage' => 5)
1088+ ),
1089+ array('strategy' => 'esi')
1090+ ) ?>
10531091
1092+ // ... or a URL
1093+ use Symfony\C omponent\R outing\G enerator\U rlGeneratorInterface;
10541094 <?php echo $view['actions']->render(
1055- $view['router']->generate('latest_news', array('maxPerPage' => 5), true),
1095+ $view['router']->generate(
1096+ 'latest_news',
1097+ array('maxPerPage' => 5),
1098+ UrlGeneratorInterface::ABSOLUTE_URL
1099+ ),
10561100 array('strategy' => 'esi'),
10571101 ) ?>
10581102
@@ -1072,7 +1116,7 @@ if there is no gateway cache installed.
10721116When using the default ``render `` function (or setting the renderer to
10731117``inline ``), Symfony merges the included page content into the main one
10741118before 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
1119+ (i.e. call ``render_esi ``) *and * if Symfony detects that it's talking to a
10761120gateway cache that supports ESI, it generates an ESI include tag. But if there
10771121is no gateway cache or if it does not support ESI, Symfony will just merge
10781122the included page content within the main one as it would have done if you had
@@ -1089,11 +1133,19 @@ of the master page.
10891133
10901134..code-block ::php
10911135
1092- public function newsAction($maxPerPage)
1136+ // src/AppBundle/Controller/NewsController.php
1137+ namespace AppBundle\Controller;
1138+
1139+ // ...
1140+ class NewsController extends Controller
10931141 {
1094- // ...
1142+ public function latestAction($maxPerPage)
1143+ {
1144+ // ...
1145+ $response->setSharedMaxAge(60);
10951146
1096- $response->setSharedMaxAge(60);
1147+ return $response;
1148+ }
10971149 }
10981150
10991151 With ESI, the full page cache will be valid for 600 seconds, but the news