Компонент HttpKernel: Клас HttpKernel

Дата оновлення перекладу 2022-11-09

Компонент 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) {
    $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)
    {
        $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($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, яка оголошується одразу після виклику контролера. Її ціль - конвертувати зворотнє значення контролера у правильний екземпляр відповіді, але лише якщо це необхідно:

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)
    {
        $response = $event->getControllerResult();

        if (is_string($response)) {
            $event->setResponse(new Response($response));
        }
    }

    public static function getSubscribedEvents()
    {
        return ['kernel.view' => 'onView'];
    }
}

Код простмй, тому що подія kernel.view оголошується лише тоді, коли зворотнє значення контролера не є відповіддю, і тому що установка відповіді у події зупиняє розповсюдження події (наш слухач не може перешкоджати іншим слухачам перегляду).

Не забудье зареєструвати його у фронт-контролері:

1
$dispatcher->addSubscriber(new Simplex\StringResponseListener());

Note

Якщо ви забудете зареєструвати підписаника, HttpKernel видасть виключення з гарним повідомленням: "Контролер має повертати відповідь (Ні, було задано не високосний рік).``.

На даний момент, весь код нашого фреймворку настільки компактний, наскільки це можливо, і в основному складається з комплекту існуючих бібліотек. Розширення залежить від реєстрації слухачів/підписників подій.

Сподіваємося, що у вас тепер є краще розуміння того, чому простий на вигляд HttpKernelInterface настільки потужний. Його реалізація за замовчуванням, HttpKernel, надає вам доступ до багатьох функцій, готових до використання одразу після установки, без зусиль. І так як HttpKernel насправді є кодом, що живить фреймворки Symfony і Silex, ви отримуєте краще з двох світів: користувацький фреймворк, підігнаний під ваші потреби, але заснований на залізобетонній та добре підтримуваній низькорівневій архітектурі, яка доказово працює для багатьох сайтів; кодом, який був перевірений на проблеми безпеки і який доказово добре масштабується.