Перейти к содержанию

Первый роутер

За десять минут вы установите библиотеку, напишете слушателя, опишете маршрут и отправите первое событие. В конце будет видно, кто его получил и чем всё закончилось.

Никакой теории по пути. Только шаги, каждый из которых даёт результат. Разбираться, почему всё устроено именно так, будем потом — в разделе Как это устроено.

Что понадобится: PHP 8.1 или новее и Composer.

Шаг 1. Установите библиотеку

composer require selyusize/events-router

Проверьте, что всё на месте:

use Selyusize\EventsRouter\Contract\Exception\ExceptionInterface;

echo interface_exists(ExceptionInterface::class)
    ? 'events-router установлен' . PHP_EOL
    : 'events-router не найден, проверьте composer install' . PHP_EOL;

Должно получиться:

events-router установлен

Шаг 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 оплачен на 1500 ₽
без ошибок

Роутер нашёл маршрут, вытащил из имени события 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"}

Второй слушатель упал, но первый отработал. Ошибка одного слушателя не ломает остальных: она попадает в отчёт и в лог, а рассылка продолжается.

Что дальше

Роутер работает. Дальше — по задаче: