|
| 1 | +Create your own framework... on top of the Symfony2 Components (part 10) |
| 2 | +======================================================================== |
| 3 | + |
| 4 | +In the conclusion of the second part of this series, I've talked about one |
| 5 | +great benefit of using the Symfony2 components: the *interoperability* between |
| 6 | +all frameworks and applications using them. Let's do a big step towards this |
| 7 | +goal by making our framework implement ``HttpKernelInterface``:: |
| 8 | + |
| 9 | + namespace Symfony\Component\HttpKernel; |
| 10 | + |
| 11 | + interface HttpKernelInterface |
| 12 | + { |
| 13 | + /** |
| 14 | + * @return Response A Response instance |
| 15 | + */ |
| 16 | + function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true); |
| 17 | + } |
| 18 | + |
| 19 | +``HttpKernelInterface`` is probably the most important piece of code in the |
| 20 | +HttpKernel component, no kidding. Frameworks and applications that implement |
| 21 | +this interface are fully interoperable. Moreover, a lot of great features will |
| 22 | +come with it for free. |
| 23 | + |
| 24 | +Update your framework so that it implements this interface:: |
| 25 | + |
| 26 | + <?php |
| 27 | + |
| 28 | + // example.com/src/Framework.php |
| 29 | + |
| 30 | + // ... |
| 31 | + |
| 32 | + use Symfony\Component\HttpKernel\HttpKernelInterface; |
| 33 | + |
| 34 | + class Framework implements HttpKernelInterface |
| 35 | + { |
| 36 | + // ... |
| 37 | + |
| 38 | + public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) |
| 39 | + { |
| 40 | + // ... |
| 41 | + } |
| 42 | + } |
| 43 | + |
| 44 | +Even if this change looks trivial, it brings us a lot! Let's talk about one of |
| 45 | +the most impressive one: transparent `HTTP caching`_ support. |
| 46 | + |
| 47 | +The ``HttpCache`` class implements a fully-featured reverse proxy, written in |
| 48 | +PHP; it implements ``HttpKernelInterface`` and wraps another |
| 49 | +``HttpKernelInterface`` instance:: |
| 50 | + |
| 51 | + // example.com/web/front.php |
| 52 | + |
| 53 | + use Symfony\Component\HttpKernel\HttpCache\HttpCache; |
| 54 | + use Symfony\Component\HttpKernel\HttpCache\Store; |
| 55 | + |
| 56 | + $framework = new Simplex\Framework($dispatcher, $matcher, $resolver); |
| 57 | + $framework = new HttpCache($framework, new Store(__DIR__.'/../cache')); |
| 58 | + |
| 59 | + $framework->handle($request)->send(); |
| 60 | + |
| 61 | +That's all it takes to add HTTP caching support to our framework. Isn't it |
| 62 | +amazing? |
| 63 | + |
| 64 | +Configuring the cache needs to be done via HTTP cache headers. For instance, |
| 65 | +to cache a response for 10 seconds, use the ``Response::setTtl()`` method:: |
| 66 | + |
| 67 | + // example.com/src/Calendar/Controller/LeapYearController.php |
| 68 | + |
| 69 | + public function indexAction(Request $request, $year) |
| 70 | + { |
| 71 | + $leapyear = new LeapYear(); |
| 72 | + if ($leapyear->isLeapYear($year)) { |
| 73 | + $response = new Response('Yep, this is a leap year!'); |
| 74 | + } else { |
| 75 | + $response = new Response('Nope, this is not a leap year.'); |
| 76 | + } |
| 77 | + |
| 78 | + $response->setTtl(10); |
| 79 | + |
| 80 | + return $response; |
| 81 | + } |
| 82 | + |
| 83 | +.. tip:: |
| 84 | + |
| 85 | + If, like me, you are running your framework from the command line by |
| 86 | + simulating requests (``Request::create('/is_leap_year/2012')``), you can |
| 87 | + easily debug Response instances by dumping their string representation |
| 88 | + (``echo $response;``) as it displays all headers as well as the response |
| 89 | + content. |
| 90 | + |
| 91 | +To validate that it works correctly, add a random number to the response |
| 92 | +content and check that the number only changes every 10 seconds:: |
| 93 | + |
| 94 | + $response = new Response('Yep, this is a leap year! '.rand()); |
| 95 | + |
| 96 | +.. note:: |
| 97 | + |
| 98 | + When deploying to your production environment, keep using the Symfony2 |
| 99 | + reverse proxy (great for shared hosting) or even better, switch to a more |
| 100 | + efficient reverse proxy like `Varnish`_. |
| 101 | + |
| 102 | +Using HTTP cache headers to manage your application cache is very powerful and |
| 103 | +allows you to finely tuned your caching strategy as you can use both the |
| 104 | +expiration and the validation models of the HTTP specification. If you are not |
| 105 | +comfortable with these concepts, I highly recommend you to read the `HTTP |
| 106 | +caching`_ chapter of the Symfony2 documentation. |
| 107 | + |
| 108 | +The Response class contains many other methods that let's you configure the |
| 109 | +HTTP cache very easily. One of the most powerful is ``setCache()`` as it |
| 110 | +abstracts the most frequently used caching strategies into one simple array:: |
| 111 | + |
| 112 | + $date = date_create_from_format('Y-m-d H:i:s', '2005-10-15 10:00:00'); |
| 113 | + |
| 114 | + $response->setCache(array( |
| 115 | + 'public' => true, |
| 116 | + 'etag' => 'abcde', |
| 117 | + 'last_modified' => $date, |
| 118 | + 'max_age' => 10, |
| 119 | + 's_maxage' => 10, |
| 120 | + )); |
| 121 | + |
| 122 | + // it is equivalent to the following code |
| 123 | + $response->setPublic(); |
| 124 | + $response->setEtag('abcde'); |
| 125 | + $response->setLastModified($date); |
| 126 | + $response->setMaxAge(10); |
| 127 | + $response->setSharedMaxAge(10); |
| 128 | + |
| 129 | +When using the validation model, the ``isNotModified()`` method allows you to |
| 130 | +easily cut on the response time by short-circuiting the response generation as |
| 131 | +early as possible:: |
| 132 | + |
| 133 | + $response->setETag('whatever_you_compute_as_an_etag'); |
| 134 | + |
| 135 | + if ($response->isNotModified($request)) { |
| 136 | + return $response; |
| 137 | + } |
| 138 | + $response->setContent('The computed content of the response'); |
| 139 | + |
| 140 | + return $response; |
| 141 | + |
| 142 | +Using HTTP caching is great, but what if you cannot cache the whole page? What |
| 143 | +if you can cache everything but some sidebar that is more dynamic that the |
| 144 | +rest of the content? Edge Side Includes (`ESI`_) to the rescue! Instead of |
| 145 | +generating the whole content in one go, ESI allows you to mark a region of a |
| 146 | +page as being the content of a sub-request call:: |
| 147 | + |
| 148 | + This is the content of your page |
| 149 | + |
| 150 | + Is 2012 a leap year? <esi:include src="/leapyear/2012" /> |
| 151 | + |
| 152 | + Some other content |
| 153 | + |
| 154 | +For ESI tags to be supported by HttpCache, you need to pass it an instance of |
| 155 | +the ``ESI`` class. The ``ESI`` class automatically parses ESI tags and makes |
| 156 | +sub-requests to convert them to their proper content:: |
| 157 | + |
| 158 | + use Symfony\Component\HttpKernel\HttpCache\ESI; |
| 159 | + |
| 160 | + $framework = new HttpCache($framework, new Store(__DIR__.'/../cache'), new ESI()); |
| 161 | + |
| 162 | +.. note:: |
| 163 | + |
| 164 | + For ESI to work, you need to use a reverse proxy that supports it like the |
| 165 | + Symfony2 implementation. `Varnish`_ is the best alternative and it is |
| 166 | + Open-Source. |
| 167 | + |
| 168 | +When using complex HTTP caching strategies and/or many ESI include tags, it |
| 169 | +can be hard to understand why and when a resource should be cached or not. To |
| 170 | +ease debugging, you can enable the debug mode:: |
| 171 | + |
| 172 | + $framework = new HttpCache($framework, new Store(__DIR__.'/../cache'), new ESI(), array('debug' => true)); |
| 173 | + |
| 174 | +The debug mode adds a ``X-Symfony-Cache`` header to each response that |
| 175 | +describes what the cache layer did: |
| 176 | + |
| 177 | +.. code-block:: text |
| 178 | +
|
| 179 | + X-Symfony-Cache: GET /is_leap_year/2012: stale, invalid, store |
| 180 | +
|
| 181 | + X-Symfony-Cache: GET /is_leap_year/2012: fresh |
| 182 | +
|
| 183 | +HttpCache has many some features like support for the |
| 184 | +``stale-while-revalidate`` and ``stale-if-error`` HTTP Cache-Control |
| 185 | +extensions as defined in RFC 5861. |
| 186 | + |
| 187 | +With the addition of a single interface, our framework can now benefit from |
| 188 | +the many features built into the HttpKernel component; HTTP caching being just |
| 189 | +one of them but an important one as it can make your applications fly! |
| 190 | + |
| 191 | +.. _`HTTP caching`: http://symfony.com/doc/current/book/http_cache.html |
| 192 | +.. _`ESI`: http://en.wikipedia.org/wiki/Edge_Side_Includes |
| 193 | +.. _`Varnish`: https://www.varnish-cache.org/ |
0 commit comments