<? phpukraine ДОКУМЕНТАЦІЯ
Пошук по платформі
Документація українською

Переклад офіційної документації українською. Кожен розділ показує стан готовності: недоперекладене позначене відкрито, а не приховане.

ВЕРСІЯ
АКТУАЛЬНА Актуальний реліз. Переклад наздоганяє оригінал — розділи з низьким відсотком позначені у змісті.
ПОГЛИБЛЕНО · ЧАСТКОВО частковий переклад, оновлено 5 вересня 2026

Події — Symfony

ПЕРЕКЛАД НЕПОВНИЙ

Частину підрозділів ще не перекладено — вони показані англійською нижче в тексті або лишились в оригіналі. Готові фрагменти вже перевірені редактором.

У програмуванні подія — це сповіщення, яке надсилається, коли в застосунку стається щось значуще (наприклад, користувач входить у систему, оформлюється замовлення або виникає помилка). Інші частини застосунку можуть слухати ці сповіщення й реагувати на них, виконуючи будь-який фрагмент коду.

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

Цю можливість надає компонент EventDispatcher, який реалізує патерни проєктування Mediator і Observer. Ви можете використовувати його в будь-якому PHP-застосунку, із Symfony чи без неї.

Під час виконання застосунку Symfony спрацьовує безліч сповіщень про події. Більшість із них — це події, повʼязані з ядром (kernel), які диспетчеризуються під час обробки HTTP-запиту. Сторонні бандли й бібліотеки також диспетчеризують події, а ви можете диспетчеризувати власні події зі свого коду.

Встановлення

У застосунках Symfony цей компонент уже встановлено, бо він є залежністю самого фреймворку. В інших застосунках виконайте цю команду, щоб встановити компонент перед використанням:

$ composer require symfony/event-dispatcher

Події та диспетчер

Коли подія диспетчеризується, її ідентифікує унікальна назва (наприклад, kernel.response), яку може слухати будь-яка кількість слухачів. Також створюється обʼєкт події, який передається всім слухачам. Цей обʼєкт часто є підкласом Symfony\Contracts\EventDispatcher\Event з додатковими методами для отримання й навіть перевизначення інформації, повʼязаної з подією. Наприклад, подія kernel.response використовує клас Symfony\Component\HttpKernel\Event\ResponseEvent, який містить методи для отримання й навіть заміни обʼєкта Response.

Symfony диспетчеризує подію kernel.response після того, як обʼєкт Response створено, щоб інші частини системи могли змінити його (наприклад, додати заголовки кешування), перш ніж він буде фактично використаний. Ось як працює процес диспетчеризації події:

  • Слухач (PHP callable) повідомляє центральному обʼєкту диспетчера, що хоче слухати подію kernel.response;

  • У певний момент Symfony каже обʼєкту диспетчера диспетчеризувати подію kernel.response, передаючи разом із нею обʼєкт Event, який має доступ до обʼєкта Response;

  • Диспетчер сповіщає (тобто викликає метод) усіх слухачів події kernel.response, даючи кожному з них змогу внести зміни в обʼєкт Response.

Диспетчер — це центральний обʼєкт системи подій: він веде реєстр слухачів і, коли подія диспетчеризується, сповіщає всіх слухачів, зареєстрованих на цю подію. У застосунках Symfony диспетчер — це сервіс, який ви можете впровадити будь-де завдяки автовайрингу; в інших PHP-застосунках створіть диспетчер самостійно:

// src/Service/OrderService.php
namespace App\Service;

use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;

class OrderService
{
    public function __construct(
        private EventDispatcherInterface $dispatcher,
    ) {
    }

    // ...
}
use Symfony\Component\EventDispatcher\EventDispatcher;

$dispatcher = new EventDispatcher();

Порада. Впроваджуючи диспетчер подій, вказуйте тип одного з його інтерфейсів, а не конкретного класу EventDispatcher. Використовуйте Symfony\Contracts\EventDispatcher\EventDispatcherInterface, коли вам потрібно лише диспетчеризувати події, або Symfony\Component\EventDispatcher\EventDispatcherInterface, якщо вам також потрібно інспектувати слухачів чи керувати ними.

Зверніть увагу. Диспетчер подій Symfony реалізує стандарт PSR-14: Symfony\Contracts\EventDispatcher\EventDispatcherInterface розширює Psr\EventDispatcher\EventDispatcherInterface. Це означає, що ви можете використовувати диспетчер Symfony у будь-якій бібліотеці, яка очікує диспетчер подій PSR-14.

Створення слухача події

Найпоширеніший спосіб слухати подію — зареєструвати слухач події (event listener):

// 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 __invoke(ExceptionEvent $event): void
    {
        // ви отримуєте обʼєкт винятку з отриманої події
        $exception = $event->getThrowable();
        $message = sprintf(
            'My Error says: %s with code: %s',
            $exception->getMessage(),
            $exception->getCode()
        );

        // налаштуйте свій обʼєкт відповіді, щоб показати деталі винятку
        $response = new Response();
        $response->setContent($message);
        // повідомлення винятку може містити нефільтрований ввід користувача;
        // встановіть content-type у text, щоб уникнути проблем з XSS
        $response->headers->set('Content-Type', 'text/plain; charset=utf-8');

        // 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);
    }
}

Тепер, коли клас створено, вам треба зареєструвати його як слухач події. У застосунках Symfony визначте клас як сервіс і додайте до нього спеціальний «тег». В інших PHP-застосунках використовуйте метод addListener() диспетчера:

# config/services.yaml
services:
    App\EventListener\ExceptionListener:
        tags: [kernel.event_listener]
// config/services.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

use App\EventListener\ExceptionListener;

return App::config([
    'services' => [
        ExceptionListener::class => [
            'tags' => ['kernel.event_listener'],
        ],
    ],
]);
use App\EventListener\ExceptionListener;
use Symfony\Component\EventDispatcher\EventDispatcher;

$dispatcher = new EventDispatcher();
$listener = new ExceptionListener();
$dispatcher->addListener('kernel.exception', $listener);

Symfony дотримується такої логіки, щоб вирішити, який метод викликати всередині класу слухача події:

  1. Якщо тег kernel.event_listener визначає атрибут method, це імʼя методу, який буде викликано;
  2. Якщо атрибут method не визначено, спробувати викликати магічний метод __invoke() (що робить слухачів подій викликуваними);
  3. Якщо метод __invoke() теж не визначено, кинути виняток.

Зверніть увагу. Є необовʼязковий атрибут тегу kernel.event_listener під назвою priority — додатне або відʼємне ціле число, яке за замовчуванням дорівнює 0 і керує порядком виконання слухачів (що більше число, то раніше виконується слухач). Це корисно, коли потрібно гарантувати, що один слухач виконається перед іншим. Пріоритети внутрішніх слухачів Symfony зазвичай лежать у діапазоні від -256 до 256, але ваші власні слухачі можуть використовувати будь-яке додатне або відʼємне ціле число. Якщо два слухачі мають однаковий пріоритет, вони виконуються в порядку реєстрації. Використовуючи метод addListener(), передайте пріоритет як його необовʼязковий третій аргумент.

Зверніть увагу. Є необовʼязковий атрибут тегу kernel.event_listener під назвою event, який корисний, коли аргумент $event слухача не має типу. Якщо ви його налаштуєте, він змінить тип обʼєкта $event. Для події kernel.exception це Symfony\Component\HttpKernel\Event\ExceptionEvent. Перегляньте довідник подій Symfony, щоб побачити, який тип обʼєкта надає кожна подія.

З цим атрибутом Symfony дотримується такої логіки, щоб вирішити, який метод викликати всередині класу слухача події:

  1. Якщо тег kernel.event_listener визначає атрибут method, це імʼя методу, який буде викликано;
  2. Якщо атрибут method не визначено, спробувати викликати метод, імʼя якого — on + «назва події в PascalCase» (наприклад, метод onKernelException() для події kernel.exception);
  3. Якщо цей метод теж не визначено, спробувати викликати магічний метод __invoke() (що робить слухачів подій викликуваними);
  4. Якщо метод __invoke() теж не визначено, кинути виняток.

Слухачем події може бути будь-який дійсний PHP callable: обʼєкт, що реалізує метод __invoke(), метод обʼєкта, статичний метод класу, глобальна функція тощо. Використовуючи метод addListener(), ви також можете реєструвати замикання як слухачів подій:

use Symfony\Contracts\EventDispatcher\Event;

$dispatcher->addListener('order.placed', function (Event $event): void {
    // буде виконано, коли диспетчеризується подія order.placed
});

Визначення слухачів подій за допомогою PHP-атрибутів

Альтернативний спосіб визначити слухач події — використати PHP-атрибут Symfony\Component\EventDispatcher\Attribute\AsEventListener. Це дає змогу налаштувати слухач усередині його класу, без додавання будь-якої конфігурації в зовнішні файли:

namespace App\EventListener;

use App\Event\CustomEvent;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;

#[AsEventListener]
final class MyListener
{
    public function __invoke(CustomEvent $event): void
    {
        // ...
    }
}

Ви можете додати кілька атрибутів #[AsEventListener], щоб налаштувати різні методи. Властивість method необовʼязкова, і коли її не визначено, вона за замовчуванням дорівнює on + назва події з великої літери. У прикладі нижче слухач події 'foo' явно не визначає свій метод, тож буде викликано метод onFoo():

namespace App\EventListener;

use App\Event\CustomEvent;
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
    {
        // ...
    }
}

Symfony\Component\EventDispatcher\Attribute\AsEventListener можна також застосовувати безпосередньо до методів:

namespace App\EventListener;

use App\Event\CustomEvent;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;

final class MyMultiListener
{
    #[AsEventListener]
    public function onCustomEvent(CustomEvent $event): void
    {
        // ...
    }

    #[AsEventListener]
    public function onMultipleCustomEvent(CustomEvent|AnotherCustomEvent $event): void
    {
        // ...
    }

    #[AsEventListener(event: 'foo', priority: 42)]
    public function onFoo(): void
    {
        // ...
    }

    #[AsEventListener(event: 'bar')]
    public function onBarEvent(): void
    {
        // ...
    }
}

Зверніть увагу. Зауважте, що атрибут не вимагає задавати параметр event, якщо метод уже вказує тип очікуваної події.

Створення підписника подій

Ще один спосіб слухати події — через підписник подій (event subscriber), тобто клас, який визначає один або кілька методів, що слухають одну чи кілька подій. Головна відмінність від слухачів подій у тому, що підписники завжди знають події, які вони слухають.

Підписники подій реалізують інтерфейс Symfony\Component\EventDispatcher\EventSubscriberInterface, який вимагає єдиного статичного методу під назвою getSubscribedEvents. Цей метод повертає масив, ключі якого — назви подій, а значення — або імʼя методу для виклику, або масив, складений з імені методу й пріоритету.

Якщо різні методи підписника подій слухають одну й ту саму подію, їхній порядок визначається параметром priority. Це значення — додатне або відʼємне ціле число, яке за замовчуванням дорівнює 0. Що більше число, то раніше викликається метод. Пріоритет агрегується для всіх слухачів і підписників, тож ваші методи можуть викликатися до або після методів, визначених в інших слухачах і підписниках.

Наступний приклад показує підписник подій, який визначає кілька методів, що слухають одну й ту саму подію kernel.exception через її клас ExceptionEvent:

// src/EventSubscriber/ExceptionSubscriber.php
namespace App\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;

class ExceptionSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents(): array
    {
        // повертає події, на які підписано, їхні методи й пріоритети
        return [
            ExceptionEvent::class => [
                ['processException', 10],
                ['logException', 0],
                ['notifyException', -10],
            ],
        ];
    }

    public function processException(ExceptionEvent $event): void
    {
        // ...
    }

    public function logException(ExceptionEvent $event): void
    {
        // ...
    }

    public function notifyException(ExceptionEvent $event): void
    {
        // ...
    }
}

У застосунках Symfony це все, що потрібно зробити. Ваш файл services.yaml уже має бути налаштований на завантаження сервісів із теки EventSubscriber, тож Symfony подбає про решту. В інших PHP-застосунках зареєструйте підписника методом Symfony\Component\EventDispatcher\EventDispatcher::addSubscriber:

use App\EventSubscriber\ExceptionSubscriber;

$subscriber = new ExceptionSubscriber();
$dispatcher->addSubscriber($subscriber);

Диспетчер автоматично реєструє підписника для кожної події, яку повертає метод getSubscribedEvents().

Порада. Якщо ваші методи не викликаються при киданні винятку, ще раз перевірте, що ви завантажуєте сервіси з теки EventSubscriber і що увімкнено autoconfigure. Ви також можете вручну додати тег kernel.event_subscriber.

Події запиту, перевірка типів

Одна сторінка може робити кілька запитів (один головний запит, а потім кілька підзапитів — зазвичай коли контролери вбудовуються в шаблони). Для базових подій Symfony вам може знадобитися перевірити, чи подія стосується «головного» запиту, чи «підзапиту»:

// src/EventListener/RequestListener.php
namespace App\EventListener;

use Symfony\Component\HttpKernel\Event\RequestEvent;

class RequestListener
{
    public function onKernelRequest(RequestEvent $event): void
    {
        if (!$event->isMainRequest()) {
            // нічого не робити, якщо це не головний запит
            return;
        }

        // ...
    }
}

Певні речі, як-от перевірка інформації про реальний запит, можуть не потребувати виконання у слухачах підзапитів.

Слухачі чи підписники

Слухачів і підписників можна використовувати в одному застосунку взаємозамінно. Вибір між ними зазвичай справа особистого смаку. Проте кожен із них має незначні переваги:

  • Підписників легше повторно використовувати, бо знання про події зберігається в класі, а не у визначенні сервісу. Саме тому Symfony використовує підписників усередині себе;
  • Слухачі гнучкіші, бо бандли можуть умовно вмикати чи вимикати кожен із них залежно від якогось значення конфігурації.

Створення й диспетчеризація власних подій

Крім реєстрації слухачів для наявних подій, ви можете створювати й диспетчеризувати власні події. Це корисно при створенні сторонніх бібліотек, а також коли ви хочете зберегти різні компоненти власної системи гнучкими й слабко звʼязаними.

Створення класу події

Припустімо, ви хочете створити нову подію, яка диспетчеризується щоразу, коли клієнт замовляє товар у вашому застосунку. Диспетчеризуючи цю подію, ви передаватимете власний екземпляр події, що має доступ до оформленого замовлення. Почніть зі створення цього класу власної події та його документування:

// src/Event/OrderPlacedEvent.php
namespace App\Event;

use App\Entity\Order;
use Symfony\Contracts\EventDispatcher\Event;

/**
 * This event is dispatched each time an order
 * is placed in the system.
 */
final class OrderPlacedEvent extends Event
{
    public function __construct(
        private Order $order,
    ) {
    }

    public function getOrder(): Order
    {
        return $this->order;
    }
}

Тепер кожен слухач має доступ до замовлення через метод getOrder().

Диспетчеризація події

Метод Symfony\Contracts\EventDispatcher\EventDispatcherInterface::dispatch сповіщає всіх слухачів заданої події. Він приймає два аргументи: екземпляр події, який передається кожному слухачу цієї події, і, за потреби, назву події для диспетчеризації. Якщо назву не визначено, як назва події використовується повністю кваліфіковане імʼя класу (FQCN) екземпляра події:

// src/Service/OrderService.php
namespace App\Service;

use App\Entity\Order;
use App\Event\OrderPlacedEvent;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;

class OrderService
{
    public function __construct(
        private EventDispatcherInterface $dispatcher,
    ) {
    }

    public function placeOrder(): void
    {
        // замовлення якимось чином створюється або отримується
        $order = new Order();
        // ...

        // створює OrderPlacedEvent і диспетчеризує її
        $event = new OrderPlacedEvent($order);
        $this->dispatcher->dispatch($event);
    }
}
use App\Entity\Order;
use App\Event\OrderPlacedEvent;
use Symfony\Component\EventDispatcher\EventDispatcher;

$dispatcher = new EventDispatcher();

// замовлення якимось чином створюється або отримується
$order = new Order();
// ...

// створює OrderPlacedEvent і диспетчеризує її
$event = new OrderPlacedEvent($order);
$dispatcher->dispatch($event);

Зверніть увагу, що створюється спеціальний обʼєкт OrderPlacedEvent і передається в метод dispatch(). Тепер будь-який слухач події OrderPlacedEvent::class отримає OrderPlacedEvent.

Метод dispatch() повертає той самий обʼєкт події після того, як усіх слухачів було викликано. Це дає змогу прочитати будь-яку інформацію, яку слухачі могли додати чи змінити в події:

$event = $dispatcher->dispatch(new OrderPlacedEvent($order));

// слухачі могли змінити замовлення (наприклад, застосувати якусь знижку)
$order = $event->getOrder();

Зверніть увагу. Якщо вам не потрібно передавати слухачам подій жодних додаткових даних, ви можете також використати стандартний клас Symfony\Contracts\EventDispatcher\Event. У такому разі визначте й задокументуйте назву події як константу в якомусь класі, подібно до класу Symfony\Component\HttpKernel\KernelEvents, який використовує Symfony:

// src/Event/StoreEvents.php
namespace App\Event;

final class StoreEvents
{
    /**
     * This event is dispatched each time an order
     * is placed in the system.
     */
    public const ORDER_PLACED = 'order.placed';
}

Далі передайте назву події другим аргументом методу dispatch():

use App\Event\StoreEvents;
use Symfony\Contracts\EventDispatcher\Event;

$dispatcher->dispatch(new Event(), StoreEvents::ORDER_PLACED);

Узагальнений обʼєкт події

Створення власного класу події, як показано в попередніх розділах, — рекомендований спосіб передавати дані слухачам: імʼя класу ідентифікує подію, а його методи визначають точні дані, що передаються слухачам.

Проте для швидких прототипів або дуже простих потреб ви можете використати клас Symfony\Component\EventDispatcher\GenericEvent замість створення окремого власного класу на кожну подію. Він зберігає довільний субʼєкт події й необовʼязкові аргументи, які також доступні через інтерфейс ArrayAccess:

use Symfony\Component\EventDispatcher\GenericEvent;

$event = new GenericEvent($order, ['type' => 'online', 'counter' => 0]);
$dispatcher->dispatch($event, 'order.placed');

class OrderListener
{
    public function handle(GenericEvent $event): void
    {
        if ($event->getSubject() instanceof Order) {
            // ...
        }

        if (isset($event['type']) && 'online' === $event['type']) {
            // ...
        }

        $event['counter']++;
    }
}

Крім getSubject(), клас визначає кілька методів для роботи з аргументами події: getArgument(), getArguments(), setArgument(), setArguments() і hasArgument().

Зупинка потоку/поширення події

У деяких випадках слухачу може бути доцільно завадити виклику будь-яких інших слухачів. Іншими словами, слухач має мати змогу сказати диспетчеру зупинити все поширення події до наступних слухачів (тобто більше не сповіщати жодного слухача). Цього можна досягти зсередини слухача за допомогою методу Symfony\Contracts\EventDispatcher\Event::stopPropagation:

use App\Event\OrderPlacedEvent;

public function onPlacedOrder(OrderPlacedEvent $event): void
{
    // ...

    $event->stopPropagation();
}

Тепер будь-які слухачі OrderPlacedEvent::class, яких ще не було викликано, не будуть викликані.

Можна визначити, чи подію було зупинено, за допомогою методу Symfony\Contracts\EventDispatcher\Event::isPropagationStopped, який повертає булеве значення:

// ...
$dispatcher->dispatch($event);
if ($event->isPropagationStopped()) {
    // ...
}

Інтроспекція назви події

Крім обʼєкта події, диспетчер передає слухачам назву диспетчеризованої події й посилання на самого себе. Додайте ці необовʼязкові аргументи до сигнатури слухача, коли вони вам потрібні (наприклад, щоб повторно використати той самий слухач для кількох подій або щоб диспетчеризувати інші події зсередини слухача):

use Symfony\Contracts\EventDispatcher\Event;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;

class MyListener
{
    public function myEventListener(Event $event, string $eventName, EventDispatcherInterface $dispatcher): void
    {
        // ... зробити щось із назвою події
    }
}

Інспектування й видалення слухачів

Symfony\Component\EventDispatcher\EventDispatcherInterface визначає кілька методів для отримання інформації про зареєстрованих слухачів і для їх видалення:

use App\Event\OrderPlacedEvent;

// перевіряє, чи має якась подія зареєстрованих слухачів (якщо ви не
// передаєте жодного аргументу, перевіряються всі події одразу)
$dispatcher->hasListeners(OrderPlacedEvent::class);

// отримує слухачів якоїсь події, відсортованих від найвищого до
// найнижчого пріоритету (якщо ви не передаєте жодного аргументу,
// повертаються слухачі всіх подій)
$listeners = $dispatcher->getListeners(OrderPlacedEvent::class);

// отримує пріоритет якогось слухача (повертає null, якщо слухача
// не зареєстровано для цієї події)
$priority = $dispatcher->getListenerPriority(OrderPlacedEvent::class, $listener);

// видаляє слухача або підписника з диспетчера
$dispatcher->removeListener(OrderPlacedEvent::class, $listener);
$dispatcher->removeSubscriber($subscriber);

Ці методи визначені в інтерфейсі компонента, але не в Symfony\Contracts\EventDispatcher\EventDispatcherInterface. У застосунках Symfony вказуйте тип аргументу сервісу як інтерфейс компонента, щоб використовувати ці методи.

Псевдоніми подій

При налаштуванні слухачів і підписників подій через впровадження залежностей на базові події Symfony можна також посилатися за повністю кваліфікованим імʼям класу (FQCN) відповідного класу події:

// 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): void
    {
        // ...
    }
}

Усередині FQCN подій трактуються як псевдоніми оригінальних назв подій. Оскільки зіставлення відбувається вже під час компіляції контейнера сервісів, слухачі й підписники подій, що використовують FQCN замість назв подій, зʼявлятимуться під оригінальною назвою події при інспектуванні диспетчера подій.

Це зіставлення псевдонімів можна розширити для власних подій, зареєструвавши compiler pass AddEventAliasesPass:

// 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): void
    {
        $container->addCompilerPass(new AddEventAliasesPass([
            MyCustomEvent::class => 'my_custom_event',
        ]));
    }
}

Compiler pass завжди розширює наявний список псевдонімів. Через це безпечно реєструвати кілька екземплярів цього pass із різними конфігураціями.

Реєстрація слухачів у самостійних застосунках

Якщо ваш застосунок не використовує фреймворк Symfony, але використовує контейнер сервісів, ви все одно можете застосовувати теги kernel.event_listener і kernel.event_subscriber, показані в попередніх розділах. Щоб їх увімкнути, зареєструйте в білдері контейнера compiler pass під назвою RegisterListenersPass:

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
use Symfony\Component\EventDispatcher\EventDispatcher;

$container = new ContainerBuilder(new ParameterBag());
// зареєструвати compiler pass, який обробляє теги сервісів
// 'kernel.event_listener' і 'kernel.event_subscriber'
$container->addCompilerPass(new RegisterListenersPass());

$container->register('event_dispatcher', EventDispatcher::class);

// реєструє слухач події
$container->register('listener_service_id', \AcmeListener::class)
    ->addTag('kernel.event_listener', [
        'event' => 'acme.foo.action',
        'method' => 'onFooAction',
    ]);

// реєструє підписника подій
$container->register('subscriber_service_id', \AcmeSubscriber::class)
    ->addTag('kernel.event_subscriber');

RegisterListenersPass розвʼязує імена класів-псевдонімів, що, наприклад, дає змогу посилатися на подію через повністю кваліфіковане імʼя класу (FQCN) класу події. Pass читатиме зіставлення псевдонімів зі спеціального параметра контейнера. Цей параметр можна розширити, зареєструвавши compiler pass AddEventAliasesPass:

use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\EventDispatcher\DependencyInjection\AddEventAliasesPass;
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
use Symfony\Component\EventDispatcher\EventDispatcher;

$container = new ContainerBuilder(new ParameterBag());
$container->addCompilerPass(new AddEventAliasesPass([
    \AcmeFooActionEvent::class => 'acme.foo.action',
]));
$container->addCompilerPass(new RegisterListenersPass(), PassConfig::TYPE_BEFORE_REMOVING);

$container->register('event_dispatcher', EventDispatcher::class);

// реєструє слухач події
$container->register('listener_service_id', \AcmeListener::class)
    ->addTag('kernel.event_listener', [
        // буде перетворено на 'acme.foo.action' за допомогою RegisterListenersPass.
        'event' => \AcmeFooActionEvent::class,
        'method' => 'onFooAction',
    ]);

Зверніть увагу. Зауважте, що AddEventAliasesPass має оброблятися перед RegisterListenersPass.

Pass слухачів припускає, що id сервісу диспетчера подій — event_dispatcher, що слухачі подій позначені тегом kernel.event_listener, що підписники подій позначені тегом kernel.event_subscriber і що зіставлення псевдонімів зберігається як параметр event_dispatcher.event_aliases.

Налагодження слухачів подій

Ви можете дізнатися, які слухачі зареєстровані в диспетчері подій, за допомогою консолі. Щоб показати всі події та їхніх слухачів, виконайте:

$ php bin/console debug:event-dispatcher

Ви можете отримати зареєстрованих слухачів для конкретної події, вказавши її назву:

$ php bin/console debug:event-dispatcher kernel.exception

або ви можете отримати все, що частково збігається з назвою події:

$ 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"

Система безпеки використовує окремий диспетчер подій на кожен firewall. Використовуйте опцію --dispatcher, щоб отримати зареєстрованих слухачів для конкретного диспетчера подій:

$ php bin/console debug:event-dispatcher --dispatcher=security.event_dispatcher.main

Трасувальний диспетчер подій

Можливості налагодження, показані в попередньому розділі, працюють завдяки класу Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher. Цей диспетчер подій обгортає будь-який інший диспетчер, щоб записувати, які зі слухачів подій ним викликаються. У самостійних застосунках ви можете використовувати цей клас безпосередньо, передавши обгорнутий диспетчер і екземпляр Symfony\Component\Stopwatch\Stopwatch у його конструктор:

use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\Stopwatch\Stopwatch;

// диспетчер подій, який треба налагодити
$dispatcher = new EventDispatcher();

$traceableEventDispatcher = new TraceableEventDispatcher(
    $dispatcher,
    new Stopwatch()
);

Використовуйте трасувальний диспетчер як будь-який інший диспетчер, щоб реєструвати слухачів подій і диспетчеризувати події. Коли ваш застосунок обробив усі події, використайте методи Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher::getCalledListeners і Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher::getNotCalledListeners, щоб отримати інформацію про слухачів подій:

// ...

$calledListeners = $traceableEventDispatcher->getCalledListeners();
$notCalledListeners = $traceableEventDispatcher->getNotCalledListeners();

Незмінний диспетчер подій

Symfony\Component\EventDispatcher\ImmutableEventDispatcher — це заблокований або «заморожений» диспетчер подій. Це проксі іншого диспетчера, і він не дозволяє реєструвати нових слухачів чи підписників.

Щоб його використати, спершу створіть звичайний диспетчер і зареєструйте якихось слухачів або підписників. Далі обгорніть його незмінним диспетчером:

use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\ImmutableEventDispatcher;
use Symfony\Contracts\EventDispatcher\Event;

$dispatcher = new EventDispatcher();
$dispatcher->addListener('order.placed', function (Event $event): void {
    // ...
});

// ...

$immutableDispatcher = new ImmutableEventDispatcher($dispatcher);

Якщо ваш код спробує викликати будь-який із методів, що змінюють незмінний диспетчер (наприклад, addListener()), буде кинуто BadMethodCallException.

Як налаштувати фільтри «до» і «після»

У розробці вебзастосунків доволі поширена потреба виконати певну логіку безпосередньо перед або одразу після дій вашого контролера — як фільтри чи хуки.

Деякі вебфреймворки визначають методи на кшталт preExecute() і postExecute(), але в Symfony такого немає. Хороша новина в тому, що є набагато кращий спосіб втрутитися в процес Request -> Response за допомогою диспетчера подій.

Приклад валідації токена

Уявіть, що вам потрібно розробити API, де деякі контролери публічні, а інші обмежені одним чи кількома клієнтами. Для цих приватних можливостей ви можете видати клієнтам токен, яким вони себе ідентифікують.

Отже, перед виконанням дії вашого контролера вам потрібно перевірити, чи є ця дія обмеженою. Якщо так, вам потрібно валідувати наданий токен.

Зверніть увагу. Зауважте, що для простоти в цьому рецепті токени визначатимуться в конфігурації, і не використовуватимуться ані налаштування бази даних, ані автентифікація через компонент Security.

Фільтри «до» з подією kernel.controller

Спершу визначте якусь конфігурацію токенів як параметри:

# config/services.yaml
parameters:
    tokens:
        client1: pass1
        client2: pass2
// config/services.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

return App::config([
    'parameters' => [
        'tokens' => [
            'client1' => 'pass1',
            'client2' => 'pass2',
        ],
    ],
]);

Позначення контролерів, які треба перевіряти

Слухач kernel.controller (він же KernelEvents::CONTROLLER) отримує сповіщення на кожен запит, безпосередньо перед виконанням контролера. Тож спершу вам потрібен якийсь спосіб визначити, чи контролер, що відповідає запиту, потребує валідації токена.

Чистий і простий спосіб — створити порожній інтерфейс і змусити контролери реалізувати його:

namespace App\Controller;

interface TokenAuthenticatedController
{
    // ...
}

Контролер, який реалізує цей інтерфейс, виглядає так:

namespace App\Controller;

use App\Controller\TokenAuthenticatedController;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;

class FooController extends AbstractController implements TokenAuthenticatedController
{
    // дія, яка потребує автентифікації
    public function bar(): Response
    {
        // ...
    }
}

Створення підписника подій

Далі вам потрібно створити підписника подій, який міститиме логіку, що має виконуватися перед вашими контролерами. Якщо ви не знайомі з підписниками подій, ви можете дізнатися більше про те, як їх використовувати:

// src/EventSubscriber/TokenSubscriber.php
namespace App\EventSubscriber;

use App\Controller\TokenAuthenticatedController;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ControllerEvent;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\KernelEvents;

class TokenSubscriber implements EventSubscriberInterface
{
    public function __construct(
        #[Autowire(param: 'tokens')]
        private array $tokens
    ) {
    }

    public function onKernelController(ControllerEvent $event): void
    {
        $controller = $event->getController();

        // коли клас контролера визначає кілька методів-дій, контролер
        // повертається як [$controllerInstance, 'methodName']
        if (is_array($controller)) {
            $controller = $controller[0];
        }

        if ($controller instanceof TokenAuthenticatedController) {
            $token = $event->getRequest()->query->get('token');
            if (!in_array($token, $this->tokens)) {
                throw new AccessDeniedHttpException('This action needs a valid token!');
            }
        }
    }

    public static function getSubscribedEvents(): array
    {
        return [
            KernelEvents::CONTROLLER => 'onKernelController',
        ];
    }
}

Ось і все! Ваш файл services.yaml уже має бути налаштований на завантаження сервісів із теки EventSubscriber. Symfony подбає про решту. Ваш метод onKernelController() класу TokenSubscriber виконуватиметься на кожному запиті. Якщо контролер, який ось-ось буде виконано, реалізує TokenAuthenticatedController, застосовується автентифікація за токеном. Це дає вам змогу мати фільтр «до» на будь-якому контролері, який ви забажаєте.

Порада. Якщо ваш підписник не викликається на кожному запиті, ще раз перевірте, що ви завантажуєте сервіси з теки EventSubscriber і що увімкнено autoconfigure. Ви також можете вручну додати тег kernel.event_subscriber.

Фільтри «після» з подією kernel.response

Крім «хука», який виконується перед вашим контролером, ви можете також додати хук, що виконується після вашого контролера. Для цього прикладу уявіть, що ви хочете додати хеш sha1 (із сіллю, що використовує цей токен) до всіх відповідей, які пройшли цю автентифікацію за токеном.

Ще одна базова подія Symfony — під назвою kernel.response (вона ж KernelEvents::RESPONSE) — сповіщається на кожному запиті, але після того, як контролер повернув обʼєкт Response. Щоб створити слухач «після», створіть клас слухача й зареєструйте його як сервіс на цю подію.

Наприклад, візьміть TokenSubscriber із попереднього прикладу й спершу запишіть токен автентифікації в атрибути запиту. Це слугуватиме базовою позначкою, що цей запит пройшов автентифікацію за токеном:

public function onKernelController(ControllerEvent $event): void
{
    // ...

    if ($controller instanceof TokenAuthenticatedController) {
        $token = $event->getRequest()->query->get('token');
        if (!in_array($token, $this->tokens)) {
            throw new AccessDeniedHttpException('This action needs a valid token!');
        }

        // позначити запит як такий, що пройшов автентифікацію за токеном
        $event->getRequest()->attributes->set('auth_token', $token);
    }
}

Тепер налаштуйте підписника слухати ще одну подію й додайте onKernelResponse(). Він шукатиме позначку auth_token на обʼєкті запиту й встановлюватиме власний заголовок у відповіді, якщо її знайдено:

// додайте новий use-вираз на початку вашого файлу
use Symfony\Component\HttpKernel\Event\ResponseEvent;

public function onKernelResponse(ResponseEvent $event): void
{
    // перевірити, чи onKernelController позначив це як запит з «автентифікацією» за токеном
    if (!$token = $event->getRequest()->attributes->get('auth_token')) {
        return;
    }

    $response = $event->getResponse();

    // створити хеш і встановити його як заголовок відповіді
    $hash = sha1($response->getContent().$token);
    $response->headers->set('X-CONTENT-HASH', $hash);
}

public static function getSubscribedEvents(): array
{
    return [
        KernelEvents::CONTROLLER => 'onKernelController',
        KernelEvents::RESPONSE => 'onKernelResponse',
    ];
}

Ось і все! TokenSubscriber тепер сповіщається перед виконанням кожного контролера (onKernelController()) і після того, як кожен контролер повертає відповідь (onKernelResponse()). Змушуючи конкретні контролери реалізувати інтерфейс TokenAuthenticatedController, ваш слухач знає, щодо яких контролерів він має діяти. А завдяки збереженню значення в сумці «attributes» запиту метод onKernelResponse() знає, що треба додати додатковий заголовок. Гарної роботи!

Як змінити поведінку методу без використання успадкування

Якщо ви хочете зробити щось безпосередньо перед або одразу після виклику методу, ви можете диспетчеризувати подію відповідно на початку або в кінці методу:

use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;

class CustomMailer
{
    public function __construct(
        private EventDispatcherInterface $dispatcher,
    ) {
    }

Решта підрозділу «How to Customize a Method Behavior without Using Inheritance» (продовження класу CustomMailer, приклади BeforeSendMailEvent/AfterSendMailEvent та їхніх слухачів), а також наступні підрозділи сторінки — «Learn More», «How to Work with Kernel Events», «How to Register Event Listeners in the Service Container» та решта посилань і приміток — залишаються неперекладеними.

ЯК ЦЯ СТОРІНКА ВИГЛЯДАЄ В ПОШУКУ
phpukraine.com/docs/symfony/events
Події — Symfony документація українською
Події у Symfony 8.1: переклад офіційної документації українською. Оновлено 5 вересня 2026. Приклади коду, пояснення та посилання на питання зі співбесід.
Стан перекладу

Перекладаємо з офіційної документації, розділ за розділом, і не ховаємо недоперекладене. Помітили неточність у терміні чи реченні: напишіть, виправимо.

79%
Готовності
5
У роботі
0
Ще не перекладено
Глосарій термінів