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

Рассылка и отчёт

dispatch() отправляет событие всем подходящим слушателям и возвращает отчёт.

use Selyusize\EventsRouter\Contract\Core\EventInterface;
use Selyusize\EventsRouter\Contract\Core\ListenerInterface;
use Selyusize\EventsRouter\Contract\Core\MiddlewareInterface;

final class MarkOrderPaid implements ListenerInterface
{
    public static function handle(EventInterface $event): void
    {
        echo '    MarkOrderPaid: заказ ', $event->getAttribute('order_id'), PHP_EOL;
    }
}

final class AccrueBonuses implements ListenerInterface
{
    public static function handle(EventInterface $event): void
    {
        throw new RuntimeException('сервис бонусов недоступен');
    }
}

final class SendReceipt implements ListenerInterface
{
    public static function handle(EventInterface $event): void
    {
        echo '    SendReceipt: чек на ', $event->getPayload()['amount'], ' ₽', PHP_EOL;
    }
}

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;
    }
}
use Selyusize\EventsRouter\Event;
use Selyusize\EventsRouter\EventRouterFactory;
use Selyusize\EventsRouter\Routing\RouteGroup;

$events = EventRouterFactory::create();   // или EventRouterFactory::create($container)
$events->add(EventLogger::class);

$events->group('order', static function (RouteGroup $group): void {
    $group->listen('{order_id}.paid', MarkOrderPaid::class);
    $group->listen('{order_id}.paid', AccrueBonuses::class);
    $group->listen('{order_id}.paid', SendReceipt::class);
});

$report = $events->dispatch(new Event('order.42.paid', ['amount' => 1500]));

foreach ($report->getListeners() as $listener) {
    echo $listener->getListener(), ': ', $listener->getStatus()->name;
    echo $listener->isFailed() ? ' — ' . $listener->getError()?->getMessage() : '', PHP_EOL;
}
→ order.42.paid
    MarkOrderPaid: заказ 42
    SendReceipt: чек на 1500 ₽
← order.42.paid
MarkOrderPaid: Handled
AccrueBonuses: Failed — сервис бонусов недоступен
SendReceipt: Handled

Как проходит рассылка

dispatch(order.42.paid)
  └─ middleware роутера: $events->add(...)          один раз на событие
       └─ для каждого маршрута в порядке объявления:
            middleware групп → middleware маршрута → слушатель
  1. Роутер находит все подходящие маршруты, как match().
  2. Событие проходит через middleware роутера. Они выполняются один раз на событие, даже если слушателей нет.
  3. Каждый слушатель по очереди, в порядке объявления маршрутов:
    • получает копию события с параметрами своего маршрута в атрибутах (order_id = '42');
    • проходит через свои middleware: групп, затем маршрута.
  4. Ошибка слушателя записывается в отчёт, дальше действует стратегия.
  5. Если включён log_dispatch, рассылка пишется в лог.

Отчёт

DispatchReport:

Метод Что возвращает
getEvent() событие, переданное в dispatch()
getListeners() ListenerReport по каждому подходящему маршруту, в порядке вызова
hasListeners() были ли у события слушатели
hasFailures(), getFailures() упавшие слушатели

ListenerReport:

Метод Что возвращает
getStatus() ListenerStatusEnum: см. таблицу ниже
isFailed() статус Failed
getError() исключение для Failed, иначе null
getRoute(), getListener() сработавший маршрут и класс его слушателя
getEvent() событие, которое получил слушатель, с параметрами маршрута
getDuration() время работы слушателя вместе с его middleware, в секундах

Статусы

ListenerStatusEnum Что случилось
Handled слушатель отработал без исключения
Failed исключение в слушателе или его middleware
Skipped слушатель не вызван: middleware не вызвал $next, рассылку остановили через StoppableEventInterface или стратегией Stop

Стратегии ошибок

Задаются $events->setErrorStrategy(...).

ErrorStrategyEnum Обработчик ошибок Остальные слушатели Исключение из dispatch()
Continue — по умолчанию вызывается вызываются нет
Stop вызывается Skipped нет
Throw не вызывается не вызываются да, исходное

Исключение из middleware роутера и из самого обработчика ошибок выходит из dispatch() при любой стратегии.

Как этим пользоваться — в рецепте Обработать ошибки слушателей.