В конце второй части этой серии я хотел бы поговорить о пользе использования компонентов Symfony2 – это совместимость между всеми фреймворками и приложениями, которые тоже их используют. Давайте сделаем большой шаг навстречу этой цели. Реализуем в нашем фреймворке HttpKernelInterface:
1 2 3 4 5 6 7 8 9 |
namespace Symfony\Component\HttpKernel; interface HttpKernelInterface { /** * @return Response A Response instance */ function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true); } |
HttpKernelInterface это наверно самая важная часть коде в компоненте HttpKernel, я не шучу. Фреймворки и приложения, которые реализуют этот интефейс полностью совместимы. Более того, это позволяет легко использовать кучу классных фич.
Обновим наш фремворк так, чтобы он реализовывал этот интерфейс:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<?php // example.com/src/Framework.php // ... use Symfony\Component\HttpKernel\HttpKernelInterface; class Framework implements HttpKernelInterface { // ... public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) { // ... } } |
Даже если изменения выглядят тривиальными это приносит нам много хорошего! Давайте поговорим об одной из этих классных вещей: прозрачная поддержка HTTP caching.
Класс HttpCache реализует полнофункциональный обратный прокси-сервер, написанный на PHP; он реализует HttpKernelInterface и обертывается другим HttpKernelInterface:
1 2 3 4 5 6 7 8 9 |
// example.com/web/front.php use Symfony\Component\HttpKernel\HttpCache\HttpCache; use Symfony\Component\HttpKernel\HttpCache\Store; $framework = new Simplex\Framework($dispatcher, $matcher, $resolver); $framework = new HttpCache($framework, new Store(__DIR__.'/../cache')); $framework->handle($request)->send(); |
Все! Теперь наш фреймворд поддерживает HTTP кэширование. Некруто ли?
Настройка кэширования производится посредством HTTP заголовков. Например, чтобы ответ закешировался на 10 секунд, необходимо использовать метод Response::setTtl():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// example.com/src/Calendar/Controller/LeapYearController.php public function indexAction(Request $request, $year) { $leapyear = new LeapYear(); if ($leapyear->isLeapYear($year)) { $response = new Response('Yep, this is a leap year!'); } else { $response = new Response('Nope, this is not a leap year.'); } $response->setTtl(10); return $response; } |
Если вы, как я, запустили ваш фреймворк через командную строку, эмитируя запрос Request::create(‘/is_leap_year/2012’, вы можете легко дебажить экземпляр Response, через его строковое представление (echo $response;); эта команда выведет все заголовки вместе с контентом ответа.
Чтобы кэш заработал вы должны создать папку cache и дать ей права на запись сервером ( я давал 777 )
Если папки не будет, то PHP вам сообщит, но если он не сможет туда записать, то он вам об этом не скажет и вы будете долго искать проблему.
Чтобы убедиться в том, что кэширование работает, добавим случайное число в наш ответ и проверим, что не меняется в течении 10 секунд:
1 |
$response = new Response('Yep, this is a leap year! '.rand()); |
При развертывании ваше продакш окружения, продолжайте использовать обратный прокси Symfony2 (отлично подходит для хостинга) или даже лучше, перейдите на более эффективный проски, например Varnish.
Использование HTTP заголовков для управления кэшем вашего приложения очень мощно и позволяет вам тонко настроить вашу стратегию кэширования, т.к. вы можете настроить спецификацию проверки или истечения срока. Если вы плаваете в этой теме, то я вам настоятельно рекомендую прочитать клаву из документации Symfony2 HTTP caching.
Класс Response содержит кучу других методов, позволяющий настроить HTTP кэш. Одной из наиболее мощных является setCache() как абстрактная для большинства стратегий кэширования:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
$date = date_create_from_format('Y-m-d H:i:s', '2005-10-15 10:00:00'); $response->setCache(array( 'public' => true, 'etag' => 'abcde', 'last_modified' => $date, 'max_age' => 10, 's_maxage' => 10, )); // it is equivalent to the following code $response->setPublic(); $response->setEtag('abcde'); $response->setLastModified($date); $response->setMaxAge(10); $response->setSharedMaxAge(10); |
Когда вы используете условное кэширование, метод isNotModified() позволяет вам быстрее отравить ответ:
1 2 3 4 5 6 7 8 |
$response->setETag('whatever_you_compute_as_an_etag'); if ($response->isNotModified($request)) { return $response; } $response->setContent('The computed content of the response'); return $response; |
HTTP кэширование это круто, но что если вы не можете закэшировать страницу целиком? Что если вы кешируете все, кроме сайдбара, который формируется динамически? Edge Side Includes (ESI) – вот оно спасение! Вместо того чтобы генерировать станицу целиком, ESI позволяет вам отметить части страницы, как суб-запросы:
1 2 3 4 5 |
This is the content of your page Is 2012 a leap year? <esi:include src="/leapyear/2012" /> Some other content |
Для поддержки ESI тэгов HttpCache, вы должны передать экземпляр ESI класса. ESI класс автоматически распарсит ESI тэги, сделает суб-запросы и сконвертирует их в соотвествующий контент:
1 2 3 |
use Symfony\Component\HttpKernel\HttpCache\ESI; $framework = new HttpCache($framework, new Store(__DIR__.'/../cache'), new ESI()); |
Для работы с ESI вам необходимо использовать обратное прокси, который поддерживает. Varnish – лучшая альтернатива и это открытое программное обеспечение.
Когда вы используете сложную стратегию кэширования и/или много ESI тэгов, может быть трудно понять почему и где ресурсы должны кэшироваться, а где нет. Чтобы было проще дебажить вы можете включить debug mode:
1 |
$framework = new HttpCache($framework, new Store(__DIR__.'/../cache'), new ESI(), array('debug' => true)); |
Debug mode добавляет заголовок X-Symfony-Cache к каждому ответу, который описывает слой кэша:
1 2 3 |
X-Symfony-Cache: GET /is_leap_year/2012: stale, invalid, store X-Symfony-Cache: GET /is_leap_year/2012: fresh |
HttpCache имеет кучу функций таких как stale-while-revalidate и
stale-if-error.
С добавлением единственного интерфейса, наш фреймворк теперь может использовать кучу фич, реализованных в компоненте HttpKernel; HTTP кэширование только одно из них, но одно из самых важных, которое позволит вашему приложению летать!