Компонент HttpKernel
Дата обновления перевода 2024-07-05
Компонент HttpKernel
Компонент HttpKernel предоставляет структурный процесс для преобразоваия
Request
вResponse
используя компонент EventDispatcher. Он достаточно гибкий, чтобы создавать полный фреймворк (Symfony), микро-фреймворк (Silex) или продвинутую CMS-систему (Drupal).
Установка
1
$ composer require symfony/http-kernel
Note
Если вы устанавливаете этот компонент вне приложения Symfony, вам нужно
подключить файл vendor/autoload.php
в вашем коде для включения механизма
автозагрузки классов, предоставляемых Composer. Детальнее читайте в
этой статье.
Рабочий процесс запроса
See also
Эта статья объясняет как использовать функции HttpKernel как независимого компонента в любом приложении PHP. В приложениях Symfony уже всё настроено и готово к использованию. Прочитайте статьи Контроллер и События и слушатели событий для понимания как использовать эти функции при создании контроллеров и определения событий в приложениях Symfony.
Каждое веб-взаимодействие HTTP начинается с запроса и заканчивается ответом. Ваша работа, как разработчика, - создать PHP-код, который читает информацию запроса (например, URL) и создаёт и возвращает ответ (например, страницу HTML или строку JSON). Вот упрощённый обзор рабочего процесса запроса в приложениях Symfony:
- Пользователь запрашивает источник в браузере;
- Браузер отправляет запрос серверу;
- Symfony даёт приложению объект Запрос;
- Приложение генерирует объект Ответ, используя данные объекта Запрос;
- Сервер отправляет запрос обратно браузеру;
- Браузер отображает источник пользователю.
Обычно, какой-то фреймворк или система строятся для обработки всех повторяющихся задач (например, маршрутизации, безопасности и т.д.), чтобы разработчик мог с лёгкостью построить каждую страницу приложения. То как именно эти системы строятся, очень отличается. Компонент HttpKernel предоставляет интерфейс, который формализует процесс начала запроса и создание соответствующего ответа. Компонент должен быть сердцем любого приложения фреймворка, независимо от того, насколько изменена архитектура этой системы:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
namespace Symfony\Component\HttpKernel;
use Symfony\Component\HttpFoundation\Request;
interface HttpKernelInterface
{
// ...
/**
* @return Response A Response instance
*/
public function handle(
Request $request,
int $type = self::MAIN_REQUEST,
bool $catch = true
): Response;
}
Внутренне, метод HttpKernel::handle() - твёрдая реализация HttpKernelInterface::handle() - определяет рабочий процесс, который начинается с Request и заканчивается Response.
Точные детали этого рабочего процесса - ключ к помниманию того, как работает ядро (и фреймворк Symfony или любая другая библиотека, которая использует ядро).
HttpKernel: Управляемое событиями
Метод HttpKernel::handle()
работает внутренне, запуская события. Это делает
метод как гибким, так и немного абстрактным, так как вся "работа" фреймворка /
приложения, построенная с помощью HttpKernel на самом деле производится слушателями
событий.
Чтобы помочь объяснить этот процесс, данный документ рассматривает каждый шаг процесса и объясняет, как работает одна особенная реализация HttpKernel - фреймворк Symfony.
Изначально, использование 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 28 29
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Controller\ArgumentResolver;
use Symfony\Component\HttpKernel\Controller\ControllerResolver;
use Symfony\Component\HttpKernel\HttpKernel;
// создать объект Request
$request = Request::createFromGlobals();
$dispatcher = new EventDispatcher();
// ... добавить какие-то слушатели событий
// создать ваш контроллер и разрешитель аргументов
$controllerResolver = new ControllerResolver();
$argumentResolver = new ArgumentResolver();
// инстанциировать ядро
$kernel = new HttpKernel($dispatcher, $controllerResolver, new RequestStack(), $argumentResolver);
// на самом деле запустить ядро, которое превращает запрос в ответ,
// запуская события, вызывая контроллер и возвращая ответ
$response = $kernel->handle($request);
// отправить загловки и отразить содержание
$response->send();
// запустить событие kernel.terminate
$kernel->terminate($request, $response);
См. "полный рабочий пример ", чтобы увидеть более конкретную релизацию.
Чтобы узнать общую информацию о добавлении слушателей к событиям ниже, см. Создание слушателя событий .
See also
Существует ряд отличных туториалов об использовании компонента HttpKernel и других компонентов Symfony для создания вашего собственного фреймворка. См. Вступление.
1) Событие kernel.request
Типичные цели: Добавлять больше информации в Request
, инициализировать
части системы, или возвращать Response
, если это возможно (например, слой
безопасности, отказывающий в доступе).
Информационная таблица событий ядра
Первое событие, которое запускается внутре HttpKernel::handle
- это kernel.request
, которое может иметь множество разных слушателей.
Слушатели этого события могут сильно отличаться. Некоторые слушатели - например,
слушатель безопасности - могут иметь достаточно информации, чтобы создать объект
Response
незамедлительно. Например, если слушатель безопасности определил, что
пользователь не имеет доступа, этот слушатель может вернуть
RedirectResponse к странице входа или
ответ Отказ в доступе 403.
Если Response
возвращается на этом этапе, то процесс переходит напрямую к
событию kernel.response .
Другие слушатели просто инициализируют что-то или добавляют больше информации в
запрос. Например, слушатель может определить и установить локаль в объекте Request
.
Другой распространённый слушатель - маршрутизатор. Слушатель маршрутизатора может
обрабатывать Request
и определять контроллер, который должен быть отображён
(см. следующий раздел). На самом деле, объект Request
имеет набор
"атрибутов ", которая является идеальным
местом для хранения дополнительных данных о запросе,относящихся к запросу. Это
означает, что если ваш слушатель маршрутизатора каким-то образом определяет
контроллер, он может хранить его в атрибутах Request
(что может быть использовано
вашим разрешителем контроллера).
В конце-концов, цель слушателя kernel.request
- либо создать и вернуть Response
напрямую, либо добавить информацию к Request
(например, установить локаль или
установить какую-то другую информацию в атрибутах Request
).
Note
При установке ответа для события kernel.request
, распространение останавливается.
Это означает, что слушатели с более низким приоритетом, не будут выполняться.
2) Разрешение контроллера
Предполагая, что ни один слушатель kernel.request
не смог создать Response
,
следующий шаг в HttpKernel - определить и подготовить (т.е. разрешить) контроллер.
Контроллер - это часть кода конечного приложения, которая отвечает за создание и
возвращение Response
для конкретной страницы. Единственное требование - чтобы
PHP был вызываемым, т.е. функцией, методом, объектом или Closure
.
Но то как вы определяете конкретный контроллер для запроса, зависит полностью
от вашего приложения. Это работа "разрешителя контроллера" - класса, реализующего
ControllerResolverInterface,
и являющегося одним из аргументов конструктора HttpKernel
.
Ваша работа заключается в создании класса, реализующего интерфейс, и заполнении
его метода: getController()
. На самом деле, одна реализация по умолчанию
уже существует, и вы можете использовать её напрямую или научиться у:
ControllerResolver. Эта
реализация объясняется больше в сноске ниже:
1 2 3 4 5 6 7 8
namespace Symfony\Component\HttpKernel\Controller;
use Symfony\Component\HttpFoundation\Request;
interface ControllerResolverInterface
{
public function getController(Request $request): callable|false;
}
Внутренне, метод HttpKernel::handle()
вначале вызывает
getController()
в разрешителе контроллера. Этот метод передаётся в Request
и отвечает за определение
и возвращение PHP-вызываемого (контроллера), основанного на информации запроса.
3) Событие kernel.controller
Типичные цели: Инициализировать что-то или изменить контроллер прямо перед его выполнением.
Информационная таблица событий ядра
После того, как было определено вызываемое контроллера, HttpKernel::handle()
запускает событие kernel.controller
. Слушатели этого события могут инициализировать
какую-то часть системы, которую нужно инициализировать после определения некоторых
вещей (например, контроллера, информации маршрутизации), но перед выполнением контроллера.
Чтобы увидеть примеры, смотрите раздел Symfony ниже.
Ещё один типичный случай применения этого события - извлекать атрибуты из контроллера, используя метод getAttributes(). См. раздел Symfony ниже, чтобы увидеть примеры.
Слушатели этого события могут также изменять вызываемое контроллера полностью, вызвав ControllerEvent::setController в объекте события, который передаётся слушателям этого события.
4) Получение аргументов контроллера
Далее, HttpKernel::handle()
вызывает
ArgumentResolverInterface::getArguments().
Помните, что контроллер, возвращённый в getController()
- вызываемое. Цель
getArguments()
0 вернуть массив аргументов, который должен быть передан этому
конроллеру. То, как именно это будет сделано, зависит только от вас, хотя встроенный
ArgumentResolver является хорошим
примером.
На этом этапе, ядро имеет PHP-вызываемое (контроллер) и массив аргументов, который должен быть передан при выполении этого вызываемого.
5) Вызов контроллера
Следущий шаг HttpKernel::handle()
- выполнение контроллера.
Работа контроллера - построить ответ для заданного источника. Это может быть HTML страница, строка JSON или что-либо другое. В отличие от любой другой части процесса до этого времени, этот шаг реализуется "конечным-разработчиком" для каждой страницы, которая строится.
Обычно, контроллер возвращает объект Response
. Если это так, то работа ядра
уже почти закончена! В этом случае, следующий шаг - событие
kernel.response .
Но если контроллер возвращает что-либо, кроме Response
, то ядру предстоит ещё
немного работы - kernel.view (так как
конечная цель всегда - сгенерировать объект Response
).
Note
Контроллер должен вернуть что-то. Если контроллер возвращает null
,
то незамедлительно будет вызвано исключение.
6) Событие kernel.view
Типичные цели: Преобразовать возвратное значение не-Response
из контроллера в
Response
Информационная таблица событий ядра
Если контроллер не возвращает объект Response
, то ядро запскает другое событие
- kernel.view
. Работа слушателя этого события - вернуть значение контроллера
(например, массив данных или объект), чтобы создать Response
.
Это может быть полезно, если вы хотите исползовать слой "просмотра": вместо
возвращения Response
из контроллера, вы возвращаете даные, которые представляют
страницу. Слушатель этого события потом может использовать эти данные, чтобы
созать Response
в правильном формате (например, HTML, JSON, и др.).
На этом этапе, если ни один слушатель не установил в событии ответ, вызывается
исключение: либо контроллер, либо один из слушателей просмотра должен всегда
возвращать Response
.
Note
При установке ответа для события kernel.view
, распространение останавливается.
Это означает, что слушатели с более низким приоритетом не будут выполнены.
7) Событие kernel.response
Типичные цели: Изменить объект Response
прямо перед его отправкой
Информационная таблица событий ядра
Конечной целью ядра является преобразование Request
в Response
. Response
может быть создан во время события kernel.request ,
возвращён из контроллера , или
возвращён одним из слушателей события kernel.view .
Независимо от того, кто создаёт Response
, другое событие - kernel.response
,
запускается сразу после этого. Типичный слушатель этого события изменит объект
Response
каким-то образом, например, изменит заголовки, добавит cookie, или
даже изменит содержимое самого Response
(например, внедрив JavaScript до конца
тега </body>
HTML-ответа).
После того, как запущено это событие, финальный объект Response
возвращается
из handle(). В наиболее частых
случаях применения, после этого вы вызываете метод
send(), который отправляет
заголовки и отображает содержимое Response
.
8) Событие kernel.terminate
Типичные цели: Выполнить какие-то "тяжёлые" действия после отправки ответа пользователю
Информационная таблица событий ядра
Финальным событием процесса HttpKernel является kernel.terminate
и оно
уникально, так как происходит после метода HttpKernel::handle()
, и
после того, как ответ отправлен пользователю. Вспомните из примеров выше,
в таком случае код, использующий ядро, заканчивается так:
1 2 3 4 5
// отправляет заголовки и отражает содержимое
$response->send();
// запускает событие kernel.terminate
$kernel->terminate($request, $response);
Как вы видите, вызывав $kernel->terminate
после отправки ответа, вы запустите
событие kernel.terminate
, где вы можете вполнить некоторые действия, которые
вы откладывали, чтобы вернуть ответ клиенту максимально быстро (например, отправка
электронных писем).
Caution
Внутренне, HttpKernel использует PHP функцию fastcgi_finish_request.
Это означает, что в этот момент, только API сервер PHP FPM может отправлять
ответ клиенту, в то время как процесс PHP сервера всё ещё выполняет какие-то
задачи. Со всему другими API сервера, слушатели kernel.terminate
всё равно
выполняются, но ответ не отправляется клиенту, пока они все не будут выполнены.
Note
Использование события kernel.terminate
необязательно, и должно быть вызывано
только если ваше ядро реализует TerminableInterface.
Обработка исключений: событие kernel.exception
Типичные цели: Обработать какое-то исключение и создать подходящий
Response
для возврата исключению
Информационная таблица событий ядра
Если в какой-то момент внутри HttpKernel::handle()
вызывается исключение,
то вызывается другое событие - kernel.exception
. Внутренне, тело функции
handle()
обёрнуто в блок "попробуй поймай". Когда вызывается исключение,
запускается событие kernel.exception
, чтобы ваша система могла как-то ответить
на исключение.
Каждому слушателю этого события передаётся объект
ExceptionEvent,
который вы можете использовать для доступа к изначальному исключению через
метод getThrowable().
Типичный слушатель этого события проверит наличие определённого типа исключений
и создаст соответствующую ошибку Response
.
Например, чтобы сгенерировать страницу 404, вы можете вызвать специальный тип
исключения, а потом добавить слушатель этого события, который выглядит как исключение,
и создаёт и возвращает 404 Response
. На самом деле, компонент HttpKernel поставляется
с ErrorListener, который,
если вы решите его использовать, сделает это и многое другое по умолчаню (см. сноску
ниже, чтобы узнать больше).
ExceptionEvent раскрывает
метод isKernelTerminating(),
который вы можете использовать для определения того, завершается ли ядро
в момент возникновения исключения.
7.1
Метод isKernelTerminating() был представлен в Symfony 7.1.
Note
При установке ответа для события kernel.exception
, распространение
останавливается. Это означает, что слушатели с более низким приоритетом
не будут выполнены.
Создание слушателя событий
Как вы видели, вы можете создавать и присоединять слушателей событий к любым событиям,
запущенным во время цикла HttpKernel::handle()
. Обычно слушатель - это PHP-класс
с выполняющимся методом, но он может быть чем угодно. Чтобы узнать больше о создании
и присоединении слушателей событий, см. Компонент EventDispatcher.
Имя каждого события "ядра" определяется в виде константы в классе KernelEvents. Кроме того, каждому слушателю событий передаётся один аргумент, который является подклассом KernelEvent. Этот объект содержит информацию о текущем состоянии системы, и каждое событие имеет свой собственный объект события:
??? | ????????? KernelEvents |
????????, ?????????? ????????? |
---|---|---|
kernel.request | KernelEvents::REQUEST |
GetResponseEvent |
kernel.controller | KernelEvents::CONTROLLER |
FilterControllerEvent |
kernel.controller_arguments | KernelEvents::CONTROLLER_ARGUMENTS |
ControllerArgumentsEvent |
kernel.view | KernelEvents::VIEW |
GetResponseForControllerResultEvent |
kernel.response | KernelEvents::RESPONSE |
FilterResponseEvent |
kernel.finish_request | KernelEvents::FINISH_REQUEST |
FinishRequestEvent |
kernel.terminate | KernelEvents::TERMINATE |
PostResponseEvent |
kernel.exception | KernelEvents::EXCEPTION |
GetResponseForExceptionEvent |
Полный рабочий пример
При использовании компонента HttpKernel, вы вольны присоединять любые слушатели к базовым событиям, использовать любой разрешитель контроллера, который реализует ControllerResolverInterface и использовать любой разрешитель аргументов, который реализует ArgumentResolverInterface. Однако, компонент 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 28 29 30 31 32 33 34 35 36 37 38
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Controller\ArgumentResolver;
use Symfony\Component\HttpKernel\Controller\ControllerResolver;
use Symfony\Component\HttpKernel\EventListener\RouterListener;
use Symfony\Component\HttpKernel\HttpKernel;
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
$routes = new RouteCollection();
$routes->add('hello', new Route('/hello/{name}', [
'_controller' => function (Request $request): Response {
return new Response(
sprintf("Hello %s", $request->get('name'))
);
}]
));
$request = Request::createFromGlobals();
$matcher = new UrlMatcher($routes, new RequestContext());
$dispatcher = new EventDispatcher();
$dispatcher->addSubscriber(new RouterListener($matcher, new RequestStack()));
$controllerResolver = new ControllerResolver();
$argumentResolver = new ArgumentResolver();
$kernel = new HttpKernel($dispatcher, $controllerResolver, new RequestStack(), $argumentResolver);
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);
Подзапросы
В дополенение в "главному" запросу, который отправляется в HttpKernel::handle()
,
вы можете также отправить так называемы "подзапрос". Подзапрос выглядит и ведёт себя
так же, как любой другой запрос, но бычно слушит для отображения одной маленькой
части страницы вместо целой. Вы чаще всего будете делать подзапросы из вашего контроллера
(или, возможно, из шаблона, который отображается вашим контроллером).
Чтобы выполнить подзапрос, используйте HttpKernel::handle()
, но измените
второй аргумент следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
// ...
// создать какой-то другой запрос вручную, как это необходимо
$request = new Request();
// например, установить its _controller вручную
$request->attributes->set('_controller', '...');
$response = $kernel->handle($request, HttpKernelInterface::SUB_REQUEST);
// сделать что-то с ответом
Это создаёт ещё один полный цикл запрос-ответ, где новый Request
преобразуется
в Response
. Единственное отличие внутренне в том, что некоторые слушатели
(например, безопасности) могут действовать только в главном запросе. Каждому слушателю
передаётся некоторый подкласс KernelEvent,
метод isMainRequest() которого
может быть использован для проверки, является ли текущий запрос "главным" или "под-" запросом.
Например, слушатель, которому нужно действовать только по главному запросу, может выглядеть так:
1 2 3 4 5 6 7 8 9 10 11
use Symfony\Component\HttpKernel\Event\RequestEvent;
// ...
public function onKernelRequest(RequestEvent $event): void
{
if (!$event->isMainRequest()) {
return;
}
// ...
}
Note
По умолчанию значение атрибута запроса _format
равно html
. Если ваш
подзапрос возвращает другой формат (например, json
), вы можете задать его,
явно определив атрибут _format
в запросе:
1
$request->attributes->set('_format', 'json');
Расположение источников
Компонент HttpKernel отвечает за механизм пакета, используемый в приложениях Symfony. Ключевая функция пакетов заключается в том, что они позволяют переопределять любой источник, используемый приложением (файлы конфигурации, шаблоны, контроллеры, файлы переводов и др.).
Этот механизм переопределения работает, так как на источники ссылаются не по
их физическому пути, а по логическому. Например, на файл services.xml
, хранящийся
в каталоге Resources/config/
пакета по имени FooBundle ссылаются так:
@FooBundle/Resources/config/services.xml
. Этот логический путь будет работать,
когда приложение переопределит этот файл, и даже если вы измените каталог FooBundle.
Компонент HttpKernel предоставляет метод под названием locateResource(), который может быть использован для преобразования логических путей в физические:
1
$path = $kernel->locateResource('@FooBundle/Resources/config/services.xml');