Если бы вы использовали наш фреймворк прямо сейчас, вы бы наверняка задумались о поддержке кастомных сообщений об ошибках. Мы уже имеем обработку для ошибок 404 и 500, но способ обработки захардкоден во фреймворке. Сделать их кастомизацию — легче легкого, достаточно определить новое событие, и слушателя, который будет вызывать по этому событию определенный контроллер. Вроде все просто, но, что если, в этом контроллере выбросить исключение? Вы получите бесконечный цикл! Не самый лучший вариант правда?
Посмотрим на класс HttpKernel, который является универсальной, расширенной и гибкой имплементацией интерфейса HttpKernelInterface. Этот класс очень похож на класс фреймворка, который мы пишем. Он управляет событиями в некоторых важных участках кода в процессе обработки запроса. Он использует Controller resolver, для сопоставления контроллера запросу. И в качестве бонуса, он предоставляет отличную обратную связь, при возникновении проблем.
Итак, новый код фреймворка:
1 2 3 4 5 6 7 8 9 10 11 |
<?php // framework/src/Simplex/Framework.php namespace Simplex; use Symfony\Component\HttpKernel\HttpKernel; class Framework extends HttpKernel { } |
Добавим новый front controller
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 // framework/web/front.php require_once __DIR__.'/../vendor/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 — предоставляет ту же логику, что мы использовали в нашем фреймворке: он анализирует входящий запрос, и заполняет атрибуты объекта request параметрами маршрута.
Теперь наш код более компактный, и, внезапно, более надежный и функциональный чем раньше! Например можно использовать встроенный 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); |
Error controller выглядит следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<?php // framework/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 = 'Что-то пошло не так! ('.$exception->getMessage().')'; return new Response($msg, $exception->getStatusCode()); } } |
Вуаля! Удобное и гибкое управление ошибками, без всяких усилий! И конечно, если ваш контроллер выбросит исключение, HttpKernel его легко обработает.
Теперь давайте создадим слушателя, который позволит контроллерам возвращать текстовые строки, вместо полноценного объекта 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 'Да, это високосный год! '; } return 'Нет, это не високосный год.'; } } |
Что бы реализовать эту функцию, нам нужно слушать событие 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 // framework/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 происходит только кода контроллер возвращает значение, не являющееся объектом 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 // framework/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'); } } |
Если вы все же забудете зарегистрировать подписчика, HttpKernel выбросит красивое исключение “The controller must return a response (Да, это високосный год! given).”.
В данный момент, наш фреймворк компактный настолько, насколько это вообще возможно, и в основном, представляет из себя набор компонентов плюс код регистрации слушателей/подписчиков для событий.
Надеюсь, сейчас вам понятно почему с виду простой интерфейс HttpKernelInterface является таким мощным. Реализация HttpKernel дает вам огромное количество отличного функционала, готового для комфортного использования прямо “из коробки”. А учитывая, что HttpKernel это по сути код, который используется в Symfony2 и Silex — вы получаете лучшее из двух миров: гибкий фреймворк заточенный под ваши нужды, но основанный на надежной, поддерживаемой, безопасной, масштабируемой низкоуровневой архитектуре, проверенной огромным количеством проектов.
К содержанию >>
Оригинал статьи на английском языке >>
Исходный код из статьи >>