Компонент HttpKernel: Класс HttpKernel
Дата обновления перевода 2024-07-19
Компонент HttpKernel: Класс HttpKernel
Если бы вы использовали наш фреймворк прямо сейчас, то вам скорее всего нужно было бы добавить поддержку пользовательских сообщений ошибок. У нас есть поддержка ошибок 404 и 500, но ответы жёстко закодированы в самом фреймворке. Сделать их более настраиваемыми достаточно просто: выполните диспетчеризацию нового события и слушайте его. Сделать это правильно - значит, что слушатель должен вызвать обычный контроллер. Но что, если контроллер ошибок выдаёт исключение? Вы получите бесконечный цикл. Должен быть способ проще, правда?
Введите класс HttpKernel
. Вместо того, чтобы решаь одну и ту же проблему по
кругу, вместо повторного изобретения колеса каждый раз, класс HttpKernel
-
это общая, расширяемая и гибкая реализация HttpKernelInterface
.
Этот класс очень похож на класс фреймворка, который мы уже написали: он диспетчеризирует события в стратегических точка во время обработки запроса, использует разрешитель контроллера для выбора контроллера для диспетчеризации запроса, и, в качестве дополнительного бонуса, он заботится о пограничных случаях и предоставляет отличную обратную связь при возникновении проблемы.
Вот новый код фреймворка:
1 2 3 4 5 6 7 8
// 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 27
// example.com/web/front.php
require_once __DIR__.'/../vendor/autoload.php';
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel;
use Symfony\Component\Routing;
$request = Request::createFromGlobals();
$requestStack = new RequestStack();
$routes = include __DIR__.'/../src/app.php';
$context = new Routing\RequestContext();
$matcher = new Routing\Matcher\UrlMatcher($routes, $context);
$controllerResolver = new HttpKernel\Controller\ControllerResolver();
$argumentResolver = new HttpKernel\Controller\ArgumentResolver();
$dispatcher = new EventDispatcher();
$dispatcher->addSubscriber(new HttpKernel\EventListener\RouterListener($matcher, $requestStack));
$framework = new Simplex\Framework($dispatcher, $controllerResolver, $requestStack, $argumentResolver);
$response = $framework->handle($request);
$response->send();
RouterListener
- это реализация той же логики, что была у нас в фреймворке:
он сопоставляет входящий запрос и наполняет атрибуты запроса параметрами маршрута.
Теперь наш код намного более ёмкий и на удивление более прочный и мощнее, чем
когда-либо. Например, используйте встроенный ErrorListener
, чтобы сделать
управление ошибкамми конфигурируемым:
1 2 3 4 5 6
$errorHandler = function (Symfony\Component\ErrorHandler\Exception\FlattenException $exception): Response {
$msg = 'Something went wrong! ('.$exception->getMessage().')';
return new Response($msg, $exception->getStatusCode());
};
$dispatcher->addSubscriber(new HttpKernel\EventListener\ErrorListener($errorHandler));
ErrorListener
даёт вам экземпляр FlattenException
вместо выданного
экземпляра Exception
или Error
, чтобы облегчить управление и отображение
исключений. Он может взять любой валидный контроллер в качестве обработчика
исключений, так что вы можете создать класс ErrorController вместо использования
Замыкания:
1 2 3 4
$listener = new HttpKernel\EventListener\ErrorListener(
'Calendar\Controller\ErrorController::exception'
);
$dispatcher->addSubscriber($listener);
Контроллер ошибок выглядит следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// example.com/src/Calendar/Controller/ErrorController.php
namespace Calendar\Controller;
use Symfony\Component\ErrorHandler\Exception\FlattenException;
use Symfony\Component\HttpFoundation\Response;
class ErrorController
{
public function exception(FlattenException $exception): Response
{
$msg = 'Something went wrong! ('.$exception->getMessage().')';
return new Response($msg, $exception->getStatusCode());
}
}
Вуаля! Чистое и настраиваемое управление ошибками без усилий. И, конечно же,
если ваш ErrorController
выдаст исключение, HttpKernel хорошо с ним справится.
Во второй главе мы говорили о методе Response::prepare()
, который гарантирует,
что Ответ всегда соответствует HTTP-спецификации. Наверное, это хорошая идея, всегда
вызывать его прямо перед отправкой Ответа клиенту; вот, что делает ResponseListener
:
1
$dispatcher->addSubscriber(new HttpKernel\EventListener\ResponseListener('UTF-8'));
Если вы хотите сразу после установки получить поддержку потоковых ответов,
подпишитесь на StreamedResponseListener
:
1
$dispatcher->addSubscriber(new HttpKernel\EventListener\StreamedResponseListener());
А в вашем контроллере, верните экземпляр StreamedResponse
вместо экземпляра Response
.
Tip
Прочтите справочник Встроенные события Symfony, чтобы узнать больше о событиях, диспетчеризированных HttpKernel, и о том, как они позволяют вам изменить поток запроса.
Теперь, давайте создадим слушатель, который позволяет контроллеру вернуть строку, вместо полного объекта Ответа:
1 2 3 4 5 6 7 8 9 10 11 12
class LeapYearController
{
public function index(int $year): string
{
$leapYear = new LeapYear();
if ($leapYear->isLeapYear($year)) {
return 'Yep, this is a leap year! ';
}
return 'Nope, this is not a leap year.';
}
}
Чтобы реализовать эту функцию, мы будет слушать событие kernel.view
, которое
запускается сразу после вызова контроллера. Его цель - конвертировать возвратное
значение контроллера в правильный экземпляр Ответа, но только, если это необходимо:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
// example.com/src/Simplex/StringResponseListener.php
namespace Simplex;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\ViewEvent;
class StringResponseListener implements EventSubscriberInterface
{
public function onView(ViewEvent $event): void
{
$response = $event->getControllerResult();
if (is_string($response)) {
$event->setResponse(new Response($response));
}
}
public static function getSubscribedEvents(): array
{
return ['kernel.view' => 'onView'];
}
}
Код простой, потому что событие kernel.view
запускается только, когда возвратное
значение контроллера не является Ответом, и потому что установка ответа в событии
останавливает распространение события (наш слушатель не может препятствовать другим
слушателям просмотра).
Не забудьте зарегистрировать его во фронт контроллере:
1
$dispatcher->addSubscriber(new Simplex\StringResponseListener());
Note
Если вы забудете зарегистрировать подписчика, HttpKernel выдаст исключение
с красивым сообщением: Контроллер должен возвращать ответ (Нет, был задан
не високосный год).
.
На данный момент, весь код нашего фреймворка настолько компактен, насколько это возможно, и в основном состоит из комплекта существующих библиотек. Расширение зависит от регистрации слушателей/подписчиков событий.
Надеемся, что у вас теперь есть лучшее понимание того, почему просто выглядящий
HttpKernelInterface
настолько мощный. Его реализация по умолчанию, HttpKernel
,
предоставляет вам доступ ко многим крутым функциям, готовым к использованию сразу
после установки, без усилий. И так как HttpKernel на самрм деле является кодом, питающим
фреймворки Symfony и Silex, вы получаете лучшее из двух миров: пользовательский фреймворк,
подогнанный под ваши нужды, но основанный на железобетонной и хорошо поддерживаемой
низкоуровневой архитектуре, которая доказанно работает для многих сайтов; кодом, который
был проверен на проблемы безопасности и который доказано хорошо масштабируется.