Ответ
Ядро Symfony (HttpKernel) генерирует ряд событий на протяжении жизненного цикла обработки HTTP-запроса. Эти события позволяют внедрять кастомную логику на разных этапах. Основные события определены в классе SymfonyComponentHttpKernelKernelEvents.
Полный список основных событий ядра:
-
KernelEvents::REQUEST(kernel.request):- Когда: Сразу после получения
Requestот фронт-контроллера (index.php), но до определения роута и контроллера. - Зачем: Для глобальной инициализации, локализации, ранней проверки прав доступа (например, брандмауэр безопасности слушает это событие).
- Когда: Сразу после получения
-
KernelEvents::CONTROLLER(kernel.controller):- Когда: После определения контроллера и аргументов, но непосредственно перед его вызовом.
- Зачем: Для модификации контроллера (например, подмена на другой) или его аргументов (например, десериализация тела запроса в объект).
-
KernelEvents::CONTROLLER_ARGUMENTS(kernel.controller_arguments):- Когда: После разрешения всех аргументов контроллера, но перед вызовом.
- Зачем: Для последней проверки или модификации массива аргументов.
-
KernelEvents::VIEW(kernel.view):- Когда: Если контроллер вернул значение, которое не является объектом
Response. - Зачем: Чтобы преобразовать результат контроллера (массив, объект) в
Response. За это отвечает, например,SymfonyBundleFrameworkBundleControllerAbstractController.
- Когда: Если контроллер вернул значение, которое не является объектом
-
KernelEvents::RESPONSE(kernel.response):- Когда: После того как у ядра есть объект
Response(полученный от контроллера или сформированный вVIEW), но перед его отправкой. - Зачем: Для модификации ответа — добавление заголовков, сжатие GZIP, инъекция контента.
- Когда: После того как у ядра есть объект
-
KernelEvents::FINISH_REQUEST(kernel.finish_request):- Когда: Сразу после
RESPONSE, когда основной запрос завершен. Используется для очистки контекстозависимых данных (например, локали вRequestContext).
- Когда: Сразу после
-
KernelEvents::TERMINATE(kernel.terminate):- Когда: После отправки ответа клиенту (соединение закрыто).
- Зачем: Для выполнения задач, которые не должны задерживать отправку ответа: тяжелое логирование, отправка email, обработка очередей.
-
KernelEvents::EXCEPTION(kernel.exception):- Когда: При возникновении любого необработанного исключения в процессе работы ядра.
- Зачем: Для кастомной обработки ошибок — преобразование исключения в пользовательскую страницу ошибки, логирование, отправку уведомлений.
Пример слушателя для логирования исключений:
# config/services.yaml
services:
AppEventListenerExceptionLoggerListener:
tags:
- { name: kernel.event_listener, event: kernel.exception, priority: 100 }
// src/EventListener/ExceptionLoggerListener.php
use PsrLogLoggerInterface;
use SymfonyComponentHttpKernelEventExceptionEvent;
class ExceptionLoggerListener
{
public function __construct(private LoggerInterface $logger) {}
public function onKernelException(ExceptionEvent $event): void
{
$exception = $event->getThrowable();
$this->logger->error('Unhandled exception', [
'exception' => $exception,
'url' => $event->getRequest()->getUri(),
]);
// Можно НЕ останавливать распространение события,
// чтобы стандартный обработчик ошибок Symfony тоже сработал.
}
} Ответ 18+ 🔞
А, ну это ж классика, ёпта! Сидишь такой, пишешь на Symfony, и тут бац — надо вклиниться в самую жопу процесса, пока запрос летит от пользователя до ответа. Ну, ядро-то не дурак, оно тебе целую пачку событий накидывает, как будто говорит: «На, мудила, хуярь свою логику куда хочешь, только не сломай». Главное — знать, где и что происходит, а то впендюришь слушатель не туда и будешь потом охуевать, почему ничего не работает.
Вот смотри, есть у них там класс KernelEvents. В нём все эти события, как будто контрольные точки в какой-нибудь долбанной игре. Разберём по полочкам, а то волнение ебать, как всё сложно.
-
KernelEvents::REQUEST(kernel.request)
Срабатывает самым первым, прямо после того как фронт-контроллер (index.php) отдал ядру объект запроса. Контроллер ещё не найден, роутер даже не нюхал этот запрос. Идеальное место, чтобы навесить какую-нибудь глобальную хуйню: проверить IP, подсунуть локаль пользователю, или, как это делает компонент Security — поставить брандмауэр и спросить «А ты кто такой, сука?». -
KernelEvents::CONTROLLER(kernel.controller)
А вот тут уже роутер отработал, нашёл контроллер и аргументы к нему. Но сам вызов контроллера ещё не произошёл! Можно, блядь, подсунуть свинью — заменить контроллер на свой, или поковыряться в аргументах. Типа последний шанс всё испортить перед стартом. -
KernelEvents::CONTROLLER_ARGUMENTS(kernel.controller_arguments)
Это уже почти то же самое, что и предыдущее, но чуть позже — когда все аргументы для контроллера уже полностью готовы и упакованы в массив. Можно сделать финальный чек или подмену, если совсем припёрло. -
KernelEvents::VIEW(kernel.view)
О, это интересно! Срабатывает, если твой контроллер вернул какую-то хуйню, но не объектResponse. Ну, там, массив данных или свой какой-то объект. Задача этого события — превратить эту хрень в нормальный HTTP-ответ. AbstractController из FrameworkBundle как раз этим и занимается — ловит результат и через какой-нибудь сериализатор делает из него JSON или что ты там хочешь. -
KernelEvents::RESPONSE(kernel.response)
Ответ уже есть! Неважно, откуда он взялся — напрямую из контроллера или родился в событииVIEW. Но он ещё не улетел к пользователю. Самое время его пошаманить: добавить хидеры, сжать gzip'ом, впихнуть какую-нибудь аналитику в тело. Главное — не сломать, а то пользователь получит пиздатый кракозябр вместо страницы. -
KernelEvents::FINISH_REQUEST(kernel.finish_request)
Всё, основной запрос обработан, ответ готов к отправке. Событие нужно в основном для уборки — почистить за собой контекстные данные, которые привязаны к этому конкретному запросу (например, сбросить локаль в каком-нибудь сервисе). Чтобы следующий запрос не начался с грязного наследства. -
KernelEvents::TERMINATE(kernel.terminate)
А вот это, блядь, мощная фича! Срабатывает уже ПОСЛЕ того, как ответ полностью отправился клиенту и соединение закрыто. Понимаешь? Пользователь уже получил свою страницу и доволен, а ты в это время можешь делать какую-то долгую, тяжёлую хуйню: логировать в медленное хранилище, кидать задачи в очередь, рассылать письма. Это не задержит ответ ни на миллисекунду. Красота, да? Терпения ноль ебать ждать, когда это всё выполнится в основном потоке. -
KernelEvents::EXCEPTION(kernel.exception)
Ну и куда же без этого, ёпта! Если в любом месте, на любом из предыдущих этапов, вылетело необработанное исключение — летишь сюда. Твоя последняя надежда всё починить, прежде чем пользователю прилетит белая страница с ошибкой 500. Можно залогировать ошибку, отправить алерт в телегу, или сгенерировать красивую кастомную страничку «Извините, всё пошло по пизде».
Вот тебе пример, как бы я сделал слушателя для логирования исключений:
Сначала настраиваю сервис в YAML. Всё стандартно, блядь.
# config/services.yaml
services:
AppEventListenerExceptionLoggerListener:
tags:
- { name: kernel.event_listener, event: kernel.exception, priority: 100 }
А вот сам слушатель. Беру логгер, и если что-то пошло не так — пишу всё в него, вместе с URL, где это случилось. Главное — не остановить событие, пусть стандартный обработчик Symfony тоже отработает и покажет пользователю вменяемую ошибку, а не голый exception.
// src/EventListener/ExceptionLoggerListener.php
use PsrLogLoggerInterface;
use SymfonyComponentHttpKernelEventExceptionEvent;
class ExceptionLoggerListener
{
public function __construct(private LoggerInterface $logger) {}
public function onKernelException(ExceptionEvent $event): void
{
$exception = $event->getThrowable();
$this->logger->error('Unhandled exception', [
'exception' => $exception,
'url' => $event->getRequest()->getUri(),
]);
// Можно НЕ останавливать распространение события,
// чтобы стандартный обработчик ошибок Symfony тоже сработал.
}
}
Вот и вся магия, чувак. Разложил по полочкам, как нахуй надо. Главное — не путать, где TERMINATE, а где FINISH_REQUEST, а то будешь потом сидеть и думать, почему твои письма отправляются по пять минут.