События и слушатели событий
Дата обновления перевода: 2023-01-18
События и слушатели событий
Во время выполнения приложения Symfony, запускается множество уведомлений событий. Ваше приложение может принимать эти уведомления и отвечать на них, путем выполнения какой-либо части кода.
Symfony вызывает несколько событий, связанных с ядром, при обработке HTTP-запроса. Сторонние пакеты могут также запускать события, и вы даже можете запустить пользовательские события из вашего собственного кода.
Все примеры, показанные в этой статье, используют одно и то же событие
KernelEvents::EXCEPTION
в целях последовательности. В вашем приложении
вы можете использовать любое событие и даже смешивать некоторые из них в
одном абоненте.
Создание слушателя событий
Самым распространённым способом принять событие является его регистрация в слушателе событий:
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
// src/EventListener/ExceptionListener.php
namespace App\EventListener;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
class ExceptionListener
{
public function onKernelException(ExceptionEvent $event)
{
// Вы получаете объект исключения из полученного события
$exception = $event->getException();
$message = sprintf(
'My Error says: %s with code: %s',
$exception->getMessage(),
$exception->getCode()
);
// Настройте ваш объект ответа, чтобы он отображал детали исключений
$response = new Response();
$response->setContent($message);
// HttpExceptionInterface - это специальный тип исключения, который
// содержит статус кода и детали заголовка
if ($exception instanceof HttpExceptionInterface) {
$response->setStatusCode($exception->getStatusCode());
$response->headers->replace($exception->getHeaders());
} else {
$response->setStatusCode(Response::HTTP_INTERNAL_SERVER_ERROR);
}
// Отправляет изменённый объект ответа событию
$event->setResponse($response);
}
}
Tip
Каждое событие получает немного разные типы объекта $event
. Для события
kernel.exception
- это GetResponseForExceptionEvent.
Смотрите справочник событий Symfony, чтобы увидеть,
какой тип объекта предоставляет каждое из них.
Теперь, когда класс создан, вам просто нужно зарегистрировать его в качестве
сервиса и уведомить Symfony, что он "слушатель" события kernel.exception
,
путём использования специального "тега":
- YAML
- XML
- PHP
1 2 3 4 5
# config/services.yaml
services:
App\EventListener\ExceptionListener:
tags:
- { name: kernel.event_listener, event: kernel.exception }
Symfony следует этой логике, чтобы решить, какой метод выполнить внутри класса слушателя событий:
- Если тег
kernel.event_listener
определяет атрибутmethod
, то это имя метода, который нужно выполнить; - Если не определён атрибут
method
, попробуйте выполнить метод, имя которого состоит изon
+ "имя события camel-case" (например, методonKernelException()
для событияkernel.exception
); - Если этот метод тоже не определён, попробуйте выполнить волшебный метод
__invoke()
(который делает слушатели событий вызываемыми); - Если метод
_invoke()
тоже не определён, вызовите исключение.
Note
Существует необязательный атрибут для тега kernel.event_listener
под
названием priority
, который по умолчанию равняется 0
и контролирует
порядок выполнения слушателей (чем выше приоритет, тем раньше выполняется
слушатель). Это полезно,когда вам нужно гарантировать, что один слушатель
будет выполнен перед другим. Приоритеы внутренних слушателей Symfony обычно
колеблются в диапазоне от -255
до 255
, но ваши собственные слушатели
могут использовать любое положительное или отрицательное целое число.
Определение слушателей событий с PHP-атрибутами
Альтернативным способом определения слушателя событий является использование PHP-атрибута AsEventListener. Это позволяет сконфигурировать слушателя внутри его класса, без необходимости добавления какой-либо конфигурации во внешних файлах:
1 2 3 4 5 6 7 8 9 10 11 12
namespace App\EventListener;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
#[AsEventListener]
final class MyListener
{
public function __invoke(CustomEvent $event): void
{
// ...
}
}
Вы можете добавлять множество атрибутов #[AsEventListener()]
, чтобы сконфигурировать
разные методы:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
namespace App\EventListener;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
#[AsEventListener(event: CustomEvent::class, method: 'onCustomEvent')]
#[AsEventListener(event: 'foo', priority: 42)]
#[AsEventListener(event: 'bar', method: 'onBarEvent')]
final class MyMultiListener
{
public function onCustomEvent(CustomEvent $event): void
{
// ...
}
public function onFoo(): void
{
// ...
}
public function onBarEvent(): void
{
// ...
}
}
Создание подписчика событий
Еще одним способом принимать события является подписчик событий - класс, который определяет один или более методов, которые слушают одно или более событий. Главное отличие от слушателя событий заключется в том, что подписчики всегда знают, какие события они слушают.
Если разные методы подписчиков событий слушают одно и то же событие, их порядок
определяется параметром priority
. Это значение является положительным или отрицательным
целым числом, которое по умолчанию равно 0
. Чем больше число, тем раньше вызывается
метод. Приоритетность агрегируется для всех слушателей и подписчиков, поэтому ваши методы
могут быть вызвано до или после методов, определенных в других слушателях и событиях.
Чтобы узнать больше о подписчиках событий, прочтите Компонент EventDispatcher.
Следующий пример иллюстрирует подписчика событий, который определяет несколько методов,
которые принимают одно и то же событие kernel.exception
:
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
// src/EventSubscriber/ExceptionSubscriber.php
namespace App\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class ExceptionSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
// вернуть подписанные события, их методы и приоритеты
return array(
KernelEvents::EXCEPTION => array(
array('processException', 10),
array('logException', 0),
array('notifyException', -10),
)
);
}
public function processException(GetResponseForExceptionEvent $event)
{
// ...
}
public function logException(GetResponseForExceptionEvent $event)
{
// ...
}
public function notifyException(GetResponseForExceptionEvent $event)
{
// ...
}
}
Вот и все! Ваш файл services.yaml
должен уже быть настроен так, чтобы загружать
сервисы из каталога EventSubscriber
. Об остальном позаботится Symfony.
Tip
Если ваши методы не вызываются, когда есть исключение, перепроверьте, что
вы загружаете сервисы из
каталога EventSubscriber
и активировали автоконфигурацию .
Вы также можете вручную добавить тег kernel.event_subscriber
.
События запросов, проверка типов
Одна страница может делать несколько запросов (один главный и множество под-запросов - обычно с помощью ). Для главных событий Symfony, вам может понадобиться проверить, относится ли событие к "главному" запросу или "под-запросу":
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// src/EventListener/RequestListener.php
namespace App\EventListener;
use Symfony\Component\HttpKernel\Event\RequestEvent;
class RequestListener
{
public function onKernelRequest(RequestEvent $event)
{
if (!$event->isMainRequest()) {
// ничего не делайте, если это не основной запрос
return;
}
// ...
}
}
Некоторые вещи, как то проверка информации в настоящем запросе, могут не понадобиться в приёмниках под-запросов.
Слушатели или подписчики
Слушатели и подписчики могут быть использованы в одном и том же приложении невнятно. Решение использовать что-либо из них, обычно является делом личного вкуса. Однако, существуют некоторые небольшие преимущества у каждого из них:
- Подписчиков проще использовать повторно так как знание событий хранится в классе, а не в определении сервиса. Это то, почему Symfony использует подписчиков внутренне;
- Слушатели более гибкие так как пакеты могут активировать или деактивировать каждый из них, в зависимости от значений конфигурации.
Псевдонимы событий
При конфигурации слушателей и подписчиков событий через внедрение зависимости, на базовые события Symfony также можно ссылаться по полному имени класса (FQCN) соответствующего класса события:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
// src/EventSubscriber/RequestSubscriber.php
namespace App\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
class RequestSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
RequestEvent::class => 'onKernelRequest',
];
}
public function onKernelRequest(RequestEvent $event)
{
// ...
}
}
Внутренне, FQCN события рассматривается как псевдоним первоначальных имен событий. Так как отображение уже происходит при компиляции сервис-контейнера, слушатели и подписчики событий, использующие FQCN вместо имен событий, будут появляться под первоначальным именем события при исследовании диспетчера событий.
Отображение псведонимов можно расширить для пользовательских событий, зарегистрировав
передачу компилятора AddEventAliasesPass
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// src/Kernel.php
namespace App;
use App\Event\MyCustomEvent;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\EventDispatcher\DependencyInjection\AddEventAliasesPass;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
class Kernel extends BaseKernel
{
protected function build(ContainerBuilder $container)
{
$container->addCompilerPass(new AddEventAliasesPass([
MyCustomEvent::class => 'my_custom_event',
]));
}
}
Передача компилятора всегда будет раширять существующий список псевдонимов. Из-за этого, безопаснее регистрировать несколько экземпляров передач с разными конфигурациями.
Отладка слушателей событий
Вы можете узнать, какие слушатели зарегистрированы в диспетчере событий, используя консоль. Чтобы показать все события и их слушателей, выполните:
1
$ php bin/console debug:event-dispatcher
Вы можете получить зарегистрированных слушателя конкретного события, указав его имя:
1
$ php bin/console debug:event-dispatcher kernel.exception
или получить все, частично соответствующих имени события:
1 2
$ php bin/console debug:event-dispatcher kernel // matches "kernel.exception", "kernel.response" etc.
$ php bin/console debug:event-dispatcher Security // matches "Symfony\Component\Security\Http\Event\CheckPassportEvent"
Система security использует по диспетчеру событий для каждого файерволла.
Используйте опцию --dispatcher
, чтобы получить зарегистрированных слушателей для конкретного
диспетчера событий:
1
$ php bin/console debug:event-dispatcher --dispatcher=security.event_dispatcher.main