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

Шаблоны топиков

Топик

Топик — имя события из сегментов через точку. Аналог URL для событий: путь /order/42/paid ↔ топик order.42.paid.

shop.order.42.paid
│    │     │  └─ что произошло
│    │     └──── какой именно заказ
│    └────────── о какой сущности
└─────────────── какая система

Правила для имени события: непустые сегменты, без пробелов, без символов *, #, {, }, регистр важен. Нарушение — InvalidEventName.

Сегменты шаблона

В маршруте пишется не точное имя, а шаблон — он описывает целую группу событий.

Сегмент Что совпадает Пример
order ровно этот текст, с учётом регистра order
{order_id} любой один сегмент, значение попадает в параметр 42 → order_id = '42'
{order_id:\d+} один сегмент, целиком подходящий под регулярное выражение 42, но не abc и не 42abc
* любой один сегмент 42, abc
# ноль или больше любых сегментов ничего, 42, 42.payment.failed
use Selyusize\EventsRouter\Topic\TopicPattern;

$cases = [
    ['order.{order_id}.paid', 'order.42.paid'],
    ['order.{order_id:\d+}.paid', 'order.abc.paid'],
    ['order.*.cancelled', 'order.42.cancelled'],
    ['order.*.cancelled', 'order.42.items.cancelled'],
    ['order.#', 'order'],
    ['order.#', 'order.42.payment.failed'],
    ['order.{order_id}.payment.#', 'order.42.payment.failed'],
];

foreach ($cases as [$pattern, $topic]) {
    $params = TopicPattern::fromString($pattern)->match($topic);

    echo str_pad($pattern, 28), str_pad($topic, 26), match (true) {
        $params === null => 'нет',
        $params === [] => 'да',
        default => 'да, ' . json_encode($params),
    }, PHP_EOL;
}
order.{order_id}.paid       order.42.paid             да, {"order_id":"42"}
order.{order_id:\d+}.paid   order.abc.paid            нет
order.*.cancelled           order.42.cancelled        да
order.*.cancelled           order.42.items.cancelled  нет
order.#                     order                     да
order.#                     order.42.payment.failed   да
order.{order_id}.payment.#  order.42.payment.failed   да, {"order_id":"42"}

Параметры

Правило Пример
имя — snake_case: строчные латинские буквы, цифры и _, начинается с буквы {order_id}, {v2}; не {orderId}, не {_id}
ограничение — regex после двоеточия, совпадает с сегментом целиком, ^ и $ не нужны {order_id:\d+}, {code:\d{3}}
Unicode в ограничении поддерживается {name:\p{L}+}
один параметр — один раз на шаблон user.{id}.order.{id} — ошибка
пустой сегмент не совпадает order..paid не подходит под order.{order_id}.paid

Альтернатива в ограничении тоже работает: {status:paid|cancelled}.

Значение параметра — строка. Слушатель получает её через $event->getAttribute('order_id').

Литеральные сегменты библиотека не ограничивает: события Bitrix называются OnAfterUserAdd. Для своих событий пишите snake_case: order.unpaid_reminder.

* и #

Шаблон Совпадает Не совпадает
order.*.cancelled order.42.cancelled order.cancelled, order.42.items.cancelled
order.# order, order.created, order.42.payment.failed shop.order
order.#.failed order.failed, order.42.payment.failed order.42.paid
# любое событие —

# работает как в AMQP: может стоять в любом месте и совпадать с нулём сегментов.

Ограничения

Параметр, * и # занимают весь сегмент. order-{order_id} и ord* писать нельзя. Сегмент, который нужно разобрать, ловится параметром с regex: {order_ref:order-\d+}.

Ошибка в шаблоне видна сразу при объявлении маршрута, а не когда придёт событие:

use Selyusize\EventsRouter\Exception\InvalidTopicPattern;
use Selyusize\EventsRouter\Topic\TopicPattern;

try {
    TopicPattern::fromString('order-{order_id}.paid');
} catch (InvalidTopicPattern $error) {
    echo $error->getMessage(), PHP_EOL;
}
Некорректный шаблон топика "order-{order_id}.paid": параметр в сегменте "order-{order_id}" должен занимать весь сегмент, например order.{order_id}. См. https://selyusize.github.io/events-router/errors/invalid-topic-pattern/

Все варианты — на странице InvalidTopicPattern.