В десятой части этого цикла, мы поговорим об одном из преимуществ использования компонентов 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 // framework/src/Framework.php // ... use Symfony\Component\HttpKernel\HttpKernelInterface; class Framework implements HttpKernelInterface { // ... public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) { // ... } } |
Даже если изменения выглядят незначительными, они очень много нам дают! Одна из самых замечательных возможностей: поддержка прозрачного HTTP кэширования.
Класс HttpCache предоставляет полнофункциональный обратный прокси, написанный на PHP. Он реализует интерфейс HttpKernelInterface.
1 2 3 4 5 6 7 8 9 |
// framework/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 |
// framework/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’)), вы можете легко посмотреть объект ответа. Его строковое представление (echo $response;), наряду с основным контентом, покажет вам все заголовки.
Что бы убедиться, что все работает, добавьте в ваш вывод рандомное число и убедитесь, что оно меняется только каждые 10 секунд.
1 |
$response = new Response('Yep, this is a leap year! '.rand()); |
Использование HTTP заголовков позволяет очень гибко настроить кэширование, в том числе, задать обе модели кэширования (модель “окончание срока действия” и модель “валидации”). Если вы не чувствуете себя уверено в этой теме, рекомендую почитать главу про HTTP кэширование в документации Symfony2.
Класс 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, )); // Что равнозначно следующему коду: $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 |
Чтобы HttpCache поддерживал эти теги, нужно передать ему инстанс класса ESI. Класс ESI автоматически парсит свои теги, и осуществляя подзапросы, заменяет их соответствующим контентом.
1 2 3 |
use Symfony\Component\HttpKernel\HttpCache\ESI; $framework = new HttpCache($framework, new Store(__DIR__.'/../cache'), new ESI()); |
При использовании сложных стратегий HTTP кэширования и большого количества ESI тегов, становится довольно сложно понять, где и как страничка должна кэшироваться. Для удобного дебаггинга, вы можете включить режим отладки:
1 |
$framework = new HttpCache($framework, new Store(__DIR__.'/../cache'), new ESI(), array('debug' => true)); |
Режим отладки добавляет заголовок 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 расширений, описанных в RFC 5861.
С добавлением одного интерфейса, наш фреймворк обрел много возможностей, благодаря компоненту HttpKernel. HTTP кэширование — только одна из этих возможностей, но и, пожалуй, самая главная, именно она может заставить ваш фреймворк взлететь!
К содержанию >>
Оригинал статьи на английском языке >>
Исходный код из статьи >>