События
Событие — сообщение «произошло вот что». Код, который его отправил, не знает, кто отреагирует. Так к действию можно добавить новые последствия — письмо, запись в журнал, пересчёт статистики, — не трогая исходный код. На событиях построен и сам Symfony.
Слушатель события ядра
Пока Symfony обрабатывает запрос, он отправляет несколько событий. Слушатель — класс с атрибутом #[AsEventListener]:
// src/EventListener/ResponseHeaderListener.php
namespace App\EventListener;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
#[AsEventListener]
class ResponseHeaderListener
{
public function __invoke(ResponseEvent $event): void
{
$event->getResponse()->headers->set('X-Frame-Options', 'DENY');
}
}Какое событие слушать, Symfony определяет по типу аргумента. Теперь заголовок добавляется к каждому ответу.
События ядра
| Событие | Когда происходит |
|---|---|
RequestEvent | Запрос получен, контроллер ещё не выбран |
ControllerEvent | Контроллер выбран, но не вызван |
ResponseEvent | Ответ готов к отправке |
ExceptionEvent | Возникло исключение |
TerminateEvent | Ответ уже отправлен |
Своё событие
Событие — обычный класс с данными:
// src/Event/PostPublished.php
namespace App\Event;
use App\Entity\Post;
class PostPublished
{
public function __construct(public readonly Post $post) {}
}Отправляет его диспетчер:
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
public function publish(Post $post, EntityManagerInterface $em, EventDispatcherInterface $dispatcher): Response
{
$post->setPublished(true);
$em->flush();
$dispatcher->dispatch(new PostPublished($post));
return $this->redirectToRoute('blog_show', ['id' => $post->getId()]);
}Слушатели своего события
#[AsEventListener]
class NotifySubscribers
{
public function __construct(private MailerInterface $mailer) {}
public function __invoke(PostPublished $event): void
{
// отправить письма подписчикам о $event->post
}
}Слушателей может быть сколько угодно. Контроллер о них не знает.
Подписчик
Подписчик — класс, который слушает несколько событий и сам перечисляет их:
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\KernelEvents;
class LocaleSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
KernelEvents::REQUEST => ['onRequest', 20],
];
}
public function onRequest(RequestEvent $event): void
{
$request = $event->getRequest();
$request->setLocale($request->getSession()->get('locale', 'ru'));
}
}Число 20 — приоритет: чем оно больше, тем раньше вызывается метод.
События Doctrine
Сущность может реагировать на собственное сохранение:
#[ORM\Entity]
#[ORM\HasLifecycleCallbacks]
class Post
{
#[ORM\PrePersist]
public function setCreatedAtValue(): void
{
$this->createdAt = new \DateTimeImmutable();
}
}Список слушателей
php bin/console debug:event-dispatcher
php bin/console debug:event-dispatcher kernel.requestЧастая ошибка
Слушатели событий ядра выполняются при каждом запросе. Медленная работа в них — запрос к внешнему сервису, тяжёлый расчёт — замедлит весь сайт. Долгие задачи из слушателя отправляют в очередь, а сам слушатель оставляют коротким.