Основные понятия¶
events-router устроен как HTTP-роутер Slim. Только вместо запроса — событие, а вместо Action — слушатель. Если держать в голове эту аналогию, остальное складывается само.
| events-router | Slim / PSR | Что это |
|---|---|---|
событие, EventInterface |
запрос, ServerRequestInterface |
что произошло: имя-топик, данные, атрибуты |
| топик | путь URL | имя события из сегментов через точку: shop.order.42.paid |
слушатель, ListenerInterface |
Action, RequestHandlerInterface |
реакция на событие |
middleware, MiddlewareInterface |
PSR-15 MiddlewareInterface |
обёртка вокруг слушателя |
| маршрут | маршрут | связка «шаблон топика → слушатель + middleware» |
Главное отличие от HTTP одно: запрос попадает ровно в один Action, а у события может быть сколько угодно слушателей. Почти всё, чем роутер событий отличается от HTTP-роутера, следует из этого.
Событие¶
Событие — это имя, данные и атрибуты.
use Selyusize\EventsRouter\Event;
$event = new Event('shop.order.42.paid', ['amount' => 1500]);
echo $event->getName(), PHP_EOL; // shop.order.42.paid
echo $event->getPayload()['amount'], PHP_EOL; // 1500
// Событие неизменяемо: withAttribute() возвращает копию
$withId = $event->withAttribute('order_id', '42');
echo $withId->getAttribute('order_id'), PHP_EOL; // 42
echo $event->getAttribute('order_id', 'нет'), PHP_EOL; // нет — исходное событие не изменилось
Имя — конкретный топик: что произошло и с чем. Не шаблон, а факт: shop.order.42.paid, а не shop.order.{order_id}.paid.
Payload — данные события: массив, DTO, любой объект. Роутер его не читает и не меняет.
Атрибуты — то, что событие собрало по дороге к слушателю: параметры из шаблона маршрута (order_id из order.{order_id}.paid) и данные, которые добавили middleware.
Событие неизменяемо, как PSR-7 запрос. withAttribute() возвращает копию. Поэтому каждый слушатель получает своё событие со своими параметрами и не может испортить его для остальных. Payload при этом не копируется: если это объект, все копии смотрят на один и тот же объект.
Слушатель¶
Один класс — одна реакция. Метод handle() статичный: роутер вызывает MarkOrderPaid::handle($event) и объектов не создаёт.
use Selyusize\EventsRouter\Contract\Core\EventInterface;
use Selyusize\EventsRouter\Contract\Core\ListenerInterface;
use Selyusize\EventsRouter\Contract\Core\MiddlewareInterface;
use Selyusize\EventsRouter\Event;
final class MarkOrderPaid implements ListenerInterface
{
public static function handle(EventInterface $event): void
{
echo 'Заказ ', $event->getAttribute('order_id'), ' оплачен', PHP_EOL;
}
}
Зависимости слушатель берёт сам, из контейнера. Почему так — в Почему так.
Middleware¶
Middleware оборачивает слушателя. Получает событие и замыкание $next — продолжение цепочки: следующий middleware или сам слушатель. Может что-то сделать до и после, передать дальше изменённое событие или не вызвать $next вовсе — тогда событие до слушателя не дойдёт.
final class EventLogger implements MiddlewareInterface
{
public function process(EventInterface $event, Closure $next): void
{
echo '→ ', $event->getName(), PHP_EOL;
$next($event);
echo '← ', $event->getName(), PHP_EOL;
}
}
Цепочку из middleware и слушателя собирает роутер. Изнутри это выглядит так:
// Так роутер соединяет middleware и слушателя. Вручную это писать не придётся.
(new EventLogger())->process(
(new Event('shop.order.42.paid'))->withAttribute('order_id', '42'),
MarkOrderPaid::handle(...),
);
Маршрут¶
Маршрут связывает шаблон топика, одного слушателя и его middleware. Маршруты собираются в группы с общим префиксом и общими middleware, группы — в один файл.
На одно событие может сработать много маршрутов. Роутер найдёт все и вызовет их слушателей по очереди — в том порядке, в каком маршруты записаны в файле. Синтаксис — в справочнике.