Компонент 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, вы получаете лучшее из двух миров: пользовательский фреймворк, подогнанный под ваши нужды, но основанный на железобетонной и хорошо поддерживаемой низкоуровневой архитектуре, которая доказанно работает для многих сайтов; кодом, который был проверен на проблемы безопасности и который доказано хорошо масштабируется.