Skip to content

Commit

Permalink
feature #4779 Update book to comply with best practices, round 3 (Wou…
Browse files Browse the repository at this point in the history
…terJ)

This PR was merged into the 2.3 branch.

Discussion
----------

Update book to comply with best practices, round 3

| Q   | A
| --- | ---
| Doc fix? | yes
| New docs? | yes
| Applies to | all
| Fixed tickets | #4431

Commits
-------

3ab53a6 Fixed some of the comments
c6ff013 Made http cache chapter best-practices-compatible and lots of other fixes
4840768 Made propel chapter best-practices-compatible and lots of other fixes
a272f99 Made testing chapter best-practices-compatible and lots of other fixes
c49f75d Made validation chapter best-practices-compatible
  • Loading branch information
weaverryan committed Mar 13, 2015
2 parents e49a228 + 3ab53a6 commit 8e93786
Show file tree
Hide file tree
Showing 5 changed files with 573 additions and 460 deletions.
2 changes: 2 additions & 0 deletions best_practices/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ they have nothing to do with the application's behavior. In other words, your
application doesn't care about the location of your database or the credentials
to access to it, as long as the database is correctly configured.

.. _best-practices-canonical-parameters:

Canonical Parameters
~~~~~~~~~~~~~~~~~~~~

Expand Down
178 changes: 115 additions & 63 deletions book/http_cache.rst
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,12 @@ kernel::
$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
Expand Down Expand Up @@ -576,16 +579,22 @@ 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 AppBundle\Controller;

use Symfony\Component\HttpFoundation\Request;

public function indexAction(Request $request)
class DefaultController extends Controller
{
$response = $this->render('MyBundle:Main:index.html.twig');
$response->setETag(md5($response->getContent()));
$response->setPublic(); // make sure the response is public/cacheable
$response->isNotModified($request);
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;
return $response;
}
}

The :method:`Symfony\\Component\\HttpFoundation\\Response::isNotModified`
Expand Down Expand Up @@ -632,28 +641,36 @@ 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 AppBundle\Controller;

// ...
use Symfony\Component\HttpFoundation\Request;
use AppBundle\Entity\Article;

public function showAction($articleSlug, Request $request)
class ArticleController extends Controller
{
// ...
public function showAction(Article $article, Request $request)
{
$author = $article->getAuthor();

$articleDate = new \DateTime($article->getUpdatedAt());
$authorDate = new \DateTime($author->getUpdatedAt());
$articleDate = new \DateTime($article->getUpdatedAt());
$authorDate = new \DateTime($author->getUpdatedAt());

$date = $authorDate > $articleDate ? $authorDate : $articleDate;
$date = $authorDate > $articleDate ? $authorDate : $articleDate;

$response->setLastModified($date);
// Set response as public. Otherwise it will be private by default.
$response->setPublic();
$response->setLastModified($date);
// Set response as public. Otherwise it will be private by default.
$response->setPublic();

if ($response->isNotModified($request)) {
return $response;
}
if ($response->isNotModified($request)) {
return $response;
}

// ... do more work to populate the response with the full content
// ... do more work to populate the response with the full content

return $response;
return $response;
}
}

The :method:`Symfony\\Component\\HttpFoundation\\Response::isNotModified`
Expand Down Expand Up @@ -682,40 +699,46 @@ 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 AppBundle\Controller;

// ...
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;

public function showAction($articleSlug, Request $request)
class ArticleController extends Controller
{
// 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;
}
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 = ...;
// do more work here - like retrieving more data
$comments = ...;

// or render a template with the $response you've already started
return $this->render(
'MyBundle:MyController:article.html.twig',
array('article' => $article, 'comments' => $comments),
$response
);
// 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
Expand Down Expand Up @@ -865,10 +888,10 @@ Here is how you can configure the Symfony reverse proxy to support the

// app/AppCache.php

// ...
use Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
// ...

class AppCache extends HttpCache
{
Expand Down Expand Up @@ -930,7 +953,7 @@ 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
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
Expand Down Expand Up @@ -1017,13 +1040,19 @@ independent of the rest of the page.

.. code-block:: php
public function indexAction()
// src/AppBundle/Controller/DefaultController.php
// ...
class DefaultController extends Controller
{
$response = $this->render('MyBundle:MyController:index.html.twig');
// set the shared max age - which also marks the response as public
$response->setSharedMaxAge(600);
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;
return $response;
}
}
In this example, the full-page cache has a lifetime of ten minutes.
Expand All @@ -1038,21 +1067,36 @@ matter), Symfony uses the standard ``render`` helper to configure ESI tags:

.. code-block:: jinja
{# app/Resources/views/static/about.html.twig #}
{# you can use a controller reference #}
{{ render_esi(controller('...:news', { 'maxPerPage': 5 })) }}
{{ render_esi(controller('AppBundle:News:latest', { 'maxPerPage': 5 })) }}
{# ... or a URL #}
{{ render_esi(url('latest_news', { 'maxPerPage': 5 })) }}
.. code-block:: html+php

<!-- app/Resources/views/static/about.html.php -->

// you can use a controller reference
use Symfony\Component\HttpKernel\Controller\ControllerReference;
<?php echo $view['actions']->render(
new \Symfony\Component\HttpKernel\Controller\ControllerReference('...:news', array('maxPerPage' => 5)),
array('strategy' => 'esi'))
?>
new ControllerReference(
'AppBundle:News:latest',
array('maxPerPage' => 5)
),
array('strategy' => 'esi')
) ?>

// ... or a URL
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
<?php echo $view['actions']->render(
$view['router']->generate('latest_news', array('maxPerPage' => 5), true),
$view['router']->generate(
'latest_news',
array('maxPerPage' => 5),
UrlGeneratorInterface::ABSOLUTE_URL
),
array('strategy' => 'esi'),
) ?>

Expand All @@ -1072,7 +1116,7 @@ if there is no gateway cache installed.
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
(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
Expand All @@ -1089,11 +1133,19 @@ of the master page.

.. code-block:: php
public function newsAction($maxPerPage)
// src/AppBundle/Controller/NewsController.php
namespace AppBundle\Controller;
// ...
class NewsController extends Controller
{
// ...
public function latestAction($maxPerPage)
{
// ...
$response->setSharedMaxAge(60);
$response->setSharedMaxAge(60);
return $response;
}
}
With ESI, the full page cache will be valid for 600 seconds, but the news
Expand Down
Loading

0 comments on commit 8e93786

Please sign in to comment.