Компонент EventDispatcher
Дата оновлення перекладу 2024-05-02
Компонент EventDispatcher
Компонент EventDispatcher надає інструменти, що дозволяють компонентам вашого додатку спілкуватися одне з одним, оголошуючи події та слухаючи їх.
Вступ
Об'єктно-орієнтований код пройшов довгий шлях, щоб гарантувати розширюваність коду. Створюючи класи, які мають чітко визначені задачі, ви робите ваш код гнучкішим, і розробник може розширювати їх за допомогою підкласів для налаштування цієї поведінки. Але якщо вони хочуть поділитися змінами з іншими розробниками, які також створили свої власні підкласи, спадкування коду більше не буде рішенням.
Уявіть реальний приклад, де ви хочете надати систему плагінів для вашого проекту. Плагін мусить мати можливість додавати методи або робити щось до чи після того, як виконується метод, не втручаючись в роботу інших плагінів. Це складна задачка для одного тільки спадкування, і навіть якщо б множинне спадкування було можливим в PHP, воно має свої недоліки.
Компонент Symfony EventDispatcher реалізує шаблони проектування Mediator та Observer для того, щоб зробити це все можливим та надати вашим проектам можливіть бути дійсно розширюваними.
Візьміть простий приклад з компоненту HttpKernel.
Коли об'єкт Response
вже створено, може бути корисним дозволити іншим елементам
в системі змінювати його (наприклад, додавати деякі кеш-заголовки) до його реального
виокристання. Щоб зробити це можливим, ядро Symfony викликає подію - kernel.response
.
Ось, як вона працює:
- Слухач (PHP-об'єкт) повідомляє центральному об'єкту диспетчеру, що він
хоче слухати подію
kernel.response
; - В якийсь момент, ядро Symfony повідомляє об'єкту диспетчеру оголосити
подію
kernel.response
, передаючи її з об'єктомEvent
, який має доступ до об'єктуResponse
; - Диспетчер повідомляє (тобто, викликає метод) всіх слухачів події
kernel.response
, дозволяючи кожному з них робити зміни в об'єктіResponse
.
Установка
1
$ composer require symfony/event-dispatcher
Note
Якщо ви встановлюєте цей компонент поза додатком Symfony, вам потрібно підключити
файл vendor/autoload.php
у вашому коді для включення механізму автозавантаження
класів, наданих Composer. Детальніше можна прочитати у цій статті.
Застосування
See also
Ця стаття пояснює як використовувати функції EventDispatcher в якості незалежного компоненту в будь-якому додатку PHP. Прочитайте статтю Події та слухачі подій, щоб розуміти, як використовувати його в додатках Symfony.
Події
Коли оголошується подія, вона визначається унікальним ім'ям (наприклад,
kernel.response
), яке може слухати будь-яка кількість слухачів. Також
створюється та передається усім слухачам екземпляр Event.
Як ви побачите пізніше, сам об'єкт Event
часто містить дані про оголошувану подію.
Імена та об'єкти подій
Коли диспетчер повідомляє слухачів, він передає справжній об'єкт Event
цим слухачам. Базовий клас Event
дуже простий: він містить метод для зупинки
розповсюдження події , і більше нічого.
See also
Прочитайте "Обʼєкт Загальної (generic) події", щоб дізнатися більше про цей об'єкт базової події.
Часто, дані про конкретну подію мають бути передані разом з об'єктом
Event
, щоб слухачі мали необхідну їм інформацію. В такому випадку, можна
передати спеціальний підклас, який має додаткові методи для добування та
перевизначення інформації, при оголошенні події. Наприклад, подія kernel.response
використовує FilterResponseEvent,
який містить методи, щоб отримувати і навіть замінювати об'єкт Response
.
Диспетчер
Диспетчер - це центральный об'єкт системи оголошення подій. Зазвичай створюється один диспетчер, який містить реєстр слухачів. Коли подія оголошується через диспетчер, він повідомляє всіх слухачів, зареєстрованих з цією подією:
1 2 3
use Symfony\Component\EventDispatcher\EventDispatcher;
$dispatcher = new EventDispatcher();
З'єднання слухачів
Щоб скористатися перевагами існуючої події, вам необхідно з'єднати слухача з
диспетчером, щоб його можна було повідомити, коли подію буде оголошено. Виклик
методу диспетчеру addListener()
асоціює усі PHP, що викликаються, з подією:
1 2
$listener = new AcmeListener();
$dispatcher->addListener('acme.foo.action', array($listener, 'onFooAction'));
Метод addListener()
має до трьох аргументів:
- Ім'я події (рядок), яку хоче слухати цей слухач;
- PHP, що викликається, яке буде виконане при оголошенні вказаної події;
- Необов'язкове число пріоритету (чим вище - тим важливіше, а отже цього слухача
буде запущено раніше), яке визначає, коли викликається слухач по відношенню до
інших слухачів (за замовчуванням
0
). Якщо два слухача мають однаковий пріоритет, вони виконуються в тому порядку, в якому були додані в диспетчер.
Note
PHP. що викликаєтся - це змінна PHP, яка може бути використана функцією
call_user_func()
, та повертає true
при передачі функції
is_callable()
. Це може бути екземпляр \Closure
, об'єкт, що реалізує
метод __invoke()
(то, чим насправді є замикання), рядок, що представляє
функцію, або масив, що представляє метод об'єкту чи класу.
До цих пір ви бачили, як можна зареєструвати PHP-об'єкти в якості слухачів. Ви можете також зареєструвати PHP Замикання в якості слухачів подій:
1 2 3 4 5
use Symfony\Component\EventDispatcher\Event;
$dispatcher->addListener('acme.foo.action', function (Event $event): void {
// буде виконано при оголошенні події acme.foo.action
});
Коли слухач зареєстрований в диспетчері, він чекає, поки не з'явиться повідомлення про
подію. В прикладі вище, коли оголошується подія acme.foo.action
, диспетчер
викликаж метод AcmeListener::onFooAction()
та передає об'єкт Event
у вигляді
єдиного аргументу:
1 2 3 4 5 6 7 8 9 10 11
use Symfony\Contracts\EventDispatcher\Event;
class AcmeListener
{
// ...
public function onFooAction(Event $event): void
{
// ... зробити щось
}
}
Аргумент $event
- це об'єкт події, який було передано при оголошенні події.
В багатьох випадках, передається спеціальній підклас події з додатковою інформацією.
Ви можете подивитися документацію або реалізацію кожної події, щоб визначити, який
екземпляр передається.
Створення та оголошення події
Окрім реєстраціх в слухачах існуючих подій, ви можете створювати та оголошувати власні події. Це корисно при створенні сторонніх бібліотек, а також, коли ви хочете залишити різні компоненти вашої власної системи гнучкими та відокремленими.
Створення класу подій
Уявіть, що ви хочете створити нову подію - order.placed
- яка оголошується
кожен раз, коли користувач замовляє товар у вашому додатку. При оголошенні цієї
події, ви передаєте користувацький екземпляр події, який має доступ до розміщенного
замовлення. Почніть зі створення цього користувацього класу події та його документування:
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
namespace Acme\Store\Event;
use Acme\Store\Order;
use Symfony\Contracts\EventDispatcher\Event;
/**
* Подія order.placed оголошується кожен раз, коли створюється замовлення
* в системі.
*/
class OrderPlacedEvent extends Event
{
public const NAME = 'order.placed';
protected $order;
public function __construct(Order $order)
{
$this->order = $order;
}
public function getOrder(): Order
{
return $this->order;
}
}
Кожний слухач тепер має доступ до замовлення через метод getOrder()
.
Оголосіть подію
Метод dispatch()
повідомляє всіх слухачів про даний об'єкт. Використовується два аргументи: ім'я
події для оголошення, та екземпляр Event
для передачі кожному слухачу цієї
події:
1 2 3 4 5 6 7 8 9 10
use Acme\Store\Event\OrderPlacedEvent;
use Acme\Store\Order;
// створює або добуває порядок якимось чином
$order = new Order();
// ...
// створює OrderPlacedEvent та оголошує її
$event = new OrderPlacedEvent($order);
$dispatcher->dispatch($event, OrderPlacedEvent::NAME);
Відмітьте, що спеціальний об'єкт OrderPlacedEvent
створюється та передається
методу dispatch()
. Тепер, будь-який слухач події order.placed
отримає
OrderPlacedEvent
.
Note
Якщо вам не потрібно передавати жодних додаткових даних слухачам події, ви
також можете використати клас за замовчуванням Event.
У такому випадку ви можете задокументувати подію та її назву у загальному класі StoreEvents
,
подібно до класу KernelEvents:
1 2 3 4 5 6 7 8 9
namespace App\Event;
class StoreEvents {
/**
* @Event("Symfony\Contracts\EventDispatcher\Event")
*/
public const ORDER_PLACED = 'order.placed';
}
Та використайтек лас Event, щоб оголосити подію:
1 2 3
use Symfony\Contracts\EventDispatcher\Event;
$this->eventDispatcher->dispatch(new Event(), StoreEvents::ORDER_PLACED);
Використання підписників подій
Найбільш розповсюджений спосіб слухати подію - зареєструвати в диспетчері слухача подій. Цей слухач може слухати одну або більше подій та отримує повідомлення кожний раз, коли ці події оголошуються.
Іншим способом слухати події можна через підписника подій. Підписник подій
- це PHP-клас, який здатний повідомити диспетчера, на які саме події йому необхідно
підписатися. Він реалізує інтерфейс
EventSubscriberInterface, який
потребує одного статичного методу під назвою
getSubscribedEvents().
Візьміть наступний приклад підписника, який підписується на події
kernel.response
та order.placed
:
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
namespace Acme\Store\Event;
use Acme\Store\Event\OrderPlacedEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class StoreSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
KernelEvents::RESPONSE => [
['onKernelResponsePre', 10],
['onKernelResponsePost', -10],
],
OrderPlacedEvent::NAME => 'onStoreOrder',
];
}
public function onKernelResponsePre(ResponseEvent $event): void
{
// ...
}
public function onKernelResponsePost(ResponseEvent $event): void
{
// ...
}
public function onStoreOrder(OrderPlacedEvent $event): void
{
// ...
}
}
Це дуже схоже на клас слухача, окрім того, що чам клас може сказати диспетчеру, які події він має слухати. Щоб зареєструвати підписника в диспетчері, використовуйте метод addSubscriber():
1 2 3 4 5
use Acme\Store\Event\StoreSubscriber;
// ...
$subscriber = new StoreSubscriber();
$dispatcher->addSubscriber($subscriber);
Диспетчер автоматично зареєструє підписника для кожної події, що повернена методом
getSubscribedEvents()
. Цей метод повертає масив, індексований за іменами подій,
значення яких є ябо іменем методу для виклику, або масивом, що складається з імені
методу для виклику та пріоритету.
Вищеописаний приклад демонтрує, як зареєструвати декілька методів слухача для однієї
події в підписнику, а також, як передати пріоритет кожного методу слухача. Чим вищий
пріоритет, тим раніше викликається метод. В прикладі вище, коли оголошується подія
kernel.response
, викликаються методи onKernelResponsePre()
та onKernelResponsePost()
,
в такому порядку.
Зупинка потоку / розповсюдження подій
В деяких випадках, слухачу має сенс запобігати виклику будь-яких слухачів. Іншими слоами, слухач має мати можливість сказати диспетчеру зупинити розповсюдження події до майбутніх слухачів (тобто, більше не повідомляти слухачів). Це можна зробити зсередини слухача, за допомогою методу stopPropagation():
1 2 3 4 5 6 7 8
use Acme\Store\Event\OrderPlacedEvent;
public function onStoreOrder(OrderPlacedEvent $event): void
{
// ...
$event->stopPropagation();
}
Тепер, будь-які слухачі order.placed
, які ще не були викликані, не
будуть викликані.
Можливо визначити, чи була подія зупинена, за допомогою методу isPropagationStopped(), який повертає булеве значення:
1 2 3 4 5
// ...
$dispatcher->dispatch($event, 'foo.event');
if ($event->isPropagationStopped()) {
// ...
}
Події та слухачі, що знають про EventDispatcher
EventDispatcher
завжди передає оголошену подію, ім'я події та посилання на себе
самому слухачу. Це може призвести до деяких просунутих застосувань EventDispatcher
,
включно із оголошенням інших подій всередині слухачів, пов'язування подій або навіть
ліниве завантаження слухачів в об'єкті диспетчеру.
Інтроспекція імені події
Екземпляр EventDispatcher
, як і ім'я події, що оголошується, передаються
в якості аргументів слухача:
1 2 3 4 5 6 7 8 9 10
use Symfony\Contracts\EventDispatcher\Event;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
class MyListener
{
public function myEventListener(Event $event, string $eventName, EventDispatcherInterface $dispatcher): void
{
// ... зробити щось з іменем події
}
}
Інші диспетчери
Окрім розповсюдженого EventDispatcher
, компонент надається з деякими
іншими диспетчерами: