Если вы используете наш фреймворк, вы наверно хотели бы добавить поддержку настройки отображения ошибок. Сейчас мы поддерживаем 404 и 500 ошибки, но они хардкорно забиты в коде. Сделать так, чтобы можно было настраивать их довольно легко: добавить новое событие и слушателя в диспетчер. Т.е. слушатель должен вызывать обычный контроллер. Но что если контроллер ошибок выбросит исключение? Будет зацикливание. Должен быть более простой способ, не так ли?
Посмотрим на HttpKernel класс. Вместо того, чтобы каждый раз решать одну и туже проблему снова и снова, изобретая каждый раз велосипед, HttpKernel – это общая, расширяемая и гибкая реализация HttpKernelInterface.
Этот класс похож на наш фреймворк: он следит за событиями во время запросов, использует распознователь контроллеров, который передает управления в зависимости от запроса, и, кроме того, заботиться о крайних случаях, обеспечивая обратную связь, когда возникает проблема.
Вот новый код фреймворка:
1 2 3 4 5 6 7 8 9 10 11 |
<?php // example.com/src/Simplex/Framework.php namespace Simplex; use Symfony\Component\HttpKernel\HttpKernel; class Framework extends HttpKernel { } |
И новый фронт-контроллер:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<?php // example.com/web/front.php require_once __DIR__.'/../vendor/.composer/autoload.php'; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing; use Symfony\Component\HttpKernel; use Symfony\Component\EventDispatcher\EventDispatcher; $request = Request::createFromGlobals(); $routes = include __DIR__.'/../src/app.php'; $context = new Routing\RequestContext(); $matcher = new Routing\Matcher\UrlMatcher($routes, $context); $resolver = new HttpKernel\Controller\ControllerResolver(); $dispatcher = new EventDispatcher(); $dispatcher->addSubscriber(new HttpKernel\EventListener\RouterListener($matcher)); $framework = new Simplex\Framework($dispatcher, $resolver); $response = $framework->handle($request); $response->send(); |
RouterListener реализует похожую логику, которая есть в нашем фреймворке: он согласует входящий запрос и заполняет его атрибутами из маршрута.
Наш код сейчас лаконичнее и, что самое удивительно, надежнее и мощнее, чем когда либо. Напрмер, используйте встроенный ExceptionListener чтобы сделать управление ошибками настраиваемым:
1 2 3 4 5 6 |
$errorHandler = function (HttpKernel\Exception\FlattenException $exception) { $msg = 'Something went wrong! ('.$exception->getMessage().')'; return new Response($msg, $exception->getStatusCode()); }; $dispatcher->addSubscriber(new HttpKernel\EventListener\ExceptionListener($errorHandler)); |
ExceptionListener возвращает объект FlattenException вместо Exception чтобы облегчить манимупилорование и отображение исключений. Он может использовать любой валидный контроллер, таким образом вы можете создать класс ErrorController вместо того, чтобы использовать Замыкание:
1 2 |
$listener = new HttpKernel\EventListener\ExceptionListener('Calendar\\Controller\\ErrorController::exceptionAction'); $dispatcher->addSubscriber($listener); |
Контроллер ошибок:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<?php // example.com/src/Calendar/Controller/ErrorController.php namespace Calendar\Controller; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\FlattenException; class ErrorController { public function exceptionAction(FlattenException $exception) { $msg = 'Something went wrong! ('.$exception->getMessage().')'; return new Response($msg, $exception->getStatusCode()); } } |
Вуйаля! Чистый и настраиваемый обработчик ошибок без напряга. И конечно же, если ваш контроллер выкинет исключение, HttpKernel обработает ее.
Во второй части мы говорили о методе Response::prepare(), который гарантирует, Response совместим со HTTP спецификацией. Это хорошая идея всегда вызывать его перед отправкой ответа клиенту; это то, что ResponseListener делает:
1 |
$dispatcher->addSubscriber(new HttpKernel\EventListener\ResponseListener('UTF-8')); |
Это было сшиком просто! Давайте попробуем что-нибудь еще: хотите поддержку потоковых ответов из коробки? Легко, воспользуйтесь StreamedResponseListener:
1 |
$dispatcher->addSubscriber(new HttpKernel\EventListener\StreamedResponseListener()); |
И в ваш контроллер вернеться объект StreamedResponse вместо Response.
Прочитайте главу из документации по Symfony2 Internals, чтобы лучше изучить события и как они могут помочь вам изменить обработку запроса.
Теперь, давайте создадим слушателя, который позволит контроллеру вернуть строку, вместо объекта Response:
1 2 3 4 5 6 7 8 9 10 11 12 |
class LeapYearController { public function indexAction(Request $request, $year) { $leapyear = new LeapYear(); if ($leapyear->isLeapYear($year)) { return 'Yep, this is a leap year! '; } return 'Nope, this is not a leap year.'; } } |
Чтобы реализовать эту фичу, мы собираемся слушать событие kernel.view, которое срабатывает только после вызова контроллера. Наша цель: сконвертировать ответ контроллера в правильный объект Response, но только, если это необходимо:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<?php // example.com/src/Simplex/StringResponseListener.php namespace Simplex; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; use Symfony\Component\HttpFoundation\Response; class StringResponseListener implements EventSubscriberInterface { public function onView(GetResponseForControllerResultEvent $event) { $response = $event->getControllerResult(); if (is_string($response)) { $event->setResponse(new Response($response)); } } public static function getSubscribedEvents() { return array('kernel.view' => 'onView'); } } |
Код довольно прост, т.к. событие kernel.view срабатывает только когда контроллер возвращает строку (наш слушатель не может мешать другим).
Не забудьте зарегистрировать его во фронт-контроллере:
1 |
$dispatcher->addSubscriber(new Simplex\StringResponseListener()); |
Если вы забудете зарегистрировать подписчика, то HttpKernel выкинет исключение: The controller must return a response (Nope, this is not a leap year given)…
Сейчас, наш фреймворк компактный и состоит из комбинации существующих библиотек. Расширяем только когда регистрируем слушателей/подписчиков.
Надедсь, теперь вы понимаете почему простой HttpKernelInterface настолько мощный. Реализация HttpKernel дают вам доступ к кучу крутых фич, прямо из коробки. И т.к. HttpKernel используется в Symfony2 и Silex, у вас теперь комбинация из 2 миров: свой фреймворк, заточенный под ваши нужды, но основанный на незыблемой и хорошо поддерживаемой низкоуровневой структуре, проверенной на множестве сайтах; код, который провер и хорошо масштабируем.