Первый роутер¶
За десять минут вы установите библиотеку, напишете слушателя, опишете маршрут и отправите первое событие. В конце будет видно, кто его получил и чем всё закончилось.
Никакой теории по пути. Только шаги, каждый из которых даёт результат. Разбираться, почему всё устроено именно так, будем потом — в разделе Как это устроено.
Что понадобится: PHP 8.1 или новее и Composer.
Шаг 1. Установите библиотеку¶
Проверьте, что всё на месте:
use Selyusize\EventsRouter\Contract\Exception\ExceptionInterface;
echo interface_exists(ExceptionInterface::class)
? 'events-router установлен' . PHP_EOL
: 'events-router не найден, проверьте composer install' . PHP_EOL;
Должно получиться:
Шаг 2. Напишите слушателя¶
Слушатель — это реакция на событие. Один класс, один статичный метод handle():
use Selyusize\EventsRouter\Contract\Core\EventInterface;
use Selyusize\EventsRouter\Contract\Core\ListenerInterface;
final class MarkOrderPaid implements ListenerInterface
{
public static function handle(EventInterface $event): void
{
echo 'Заказ ', $event->getAttribute('order_id'), ' оплачен на ', $event->getPayload()['amount'], ' ₽', PHP_EOL;
}
}
$event->getAttribute('order_id') — номер заказа. Откуда он возьмётся, увидите на следующем шаге.
Шаг 3. Опишите маршрут¶
Маршруты лежат в отдельном файле, как HTTP-маршруты в Slim. Создайте events.php:
use Selyusize\EventsRouter\EventRouter;
use Selyusize\EventsRouter\Routing\RouteGroup;
return static function (EventRouter $events): void {
$events->setPrefix('shop');
$events->group('order', static function (RouteGroup $group): void {
$group->listen('{order_id}.paid', MarkOrderPaid::class);
});
};
Этот маршрут ловит все события вида shop.order.<что угодно>.paid. То, что стоит на месте {order_id}, попадёт к слушателю в атрибут order_id.
Шаг 4. Отправьте событие¶
Создайте роутер, подключите маршруты и разошлите событие:
use Selyusize\EventsRouter\Event;
use Selyusize\EventsRouter\EventRouterFactory;
$events = EventRouterFactory::create(container: $container); // любой PSR-11 или null — тогда PHP-DI
$events->loadRoutes(require __DIR__ . '/events.php');
$report = $events->dispatch(new Event('shop.order.42.paid', ['amount' => 1500]));
echo $report->hasFailures() ? 'есть ошибки' : 'без ошибок', PHP_EOL;
Результат:
Роутер нашёл маршрут, вытащил из имени события 42, вызвал слушателя и вернул отчёт. hasFailures() говорит, что все слушатели отработали без исключений.
Шаг 5. Посмотрите, что происходило¶
Отчёт хорош в коде, но в работающем приложении хочется видеть историю. Включите лог рассылки — он пишет каждое событие и каждого вызванного слушателя:
use Selyusize\EventsRouter\Event;
use Selyusize\EventsRouter\EventRouterFactory;
$events = EventRouterFactory::create(config: [
'log_path' => $logs . '/{level}/{date}.log', // ошибки и рассылки — в разные папки, файл на каждый день
'log_dispatch' => true, // писать каждую рассылку
]);
$events->listen('shop.order.{order_id}.paid', MarkOrderPaid::class);
$events->listen('shop.order.{order_id}.paid', AccrueBonuses::class);
$events->dispatch(new Event('shop.order.42.paid', ['amount' => 1500]));
В логе появятся две записи. Первая — о рассылке, вторая — об упавшем слушателе:
info/2026-10-10.log
[2026-10-10 14:03:12] INFO events-router: shop.order.42.paid, слушателей: 2, 0.4 мс {"listeners":[{"listener":"MarkOrderPaid","status":"Handled","ms":0.1,"attributes":{"order_id":"42"}},{"listener":"AccrueBonuses","status":"Failed","ms":0.1,"attributes":{"order_id":"42"},"error":"RuntimeException: сервис бонусов недоступен"}]}
error/2026-10-10.log
[2026-10-10 14:03:12] ERROR events-router: слушатель AccrueBonuses упал на событии shop.order.42.paid: RuntimeException: сервис бонусов недоступен в … {"exception":{"class":"RuntimeException","message":"сервис бонусов недоступен","file":"…","trace":"…"},"event":"shop.order.42.paid","attributes":{"order_id":"42"},"listener":"AccrueBonuses"}
Второй слушатель упал, но первый отработал. Ошибка одного слушателя не ломает остальных: она попадает в отчёт и в лог, а рассылка продолжается.
Что дальше¶
Роутер работает. Дальше — по задаче:
- больше маршрутов, группы и middleware — Маршруты;
- что можно писать в шаблоне топика — Шаблоны топиков;
- зависимости для слушателей — Подключить контейнер;
- куда и как писать лог — Настроить лог;
- что делать с ошибками слушателей — Обработать ошибки.