Fix Markdown-mangling artifacts the free web engine (yandex) produced in the
committed docs/ru, and regenerate the whole tree cleanly (0 fallbacks):
- Sentinel format @@N@@ -> {N}. MT engines are trained to preserve curly
format-string placeholders, so {N} survives code-heavy lines where @@N@@ (and
ZZZ…ZZZ, which also duplicated its Z) were split/moved — e.g. the stray
"@0@@" in the FAQ and "load balancerZ" in the README are gone.
- Possessive: a trailing English `'s` is consumed INTO the masked span and
dropped on restore. Every sentinel format breaks when a bare `'s` sits right
after it, and Russian has no possessive `'s`.
- Validate + retry: after restore, any leftover brace fragment triggers a retry
(the engine is non-deterministic); after a few failures the line stays English
so a broken token is never emitted.
- Glossary += KeyDB, yt-dlp, Ubuntu, iptables, MAGSCAN.
Regenerated docs/ru (37 files, translators/yandex): no residual sentinels,
mkdocs build --strict clean.
6.9 KiB
Система событий
XC_VM использует типизированный диспетчер событий в стиле PSR-14. Все события являются простыми классами PHP
отправлено и получено по имени. Диспетчер основан на экземпляре и хранится в
Откройте контейнер под ключом events.
Диспетчер событий
EventDispatcher - это синглтон с мостом экземпляра. Статические методы делегируют
активный экземпляр, поэтому существующие сайты вызовов работают без изменений.
// bootstrap.php wires the canonical instance:
$dispatcher = new EventDispatcher();
EventDispatcher::setInstance($dispatcher);
$container->set('events', $dispatcher);
// Both paths reach the same listener store:
EventDispatcher::dispatch(new MyEvent(...)); // static call
$container->get('events')->dispatch(new MyEvent(...)); // instance call
В тестах изолируйте состояние для каждого теста с помощью:
protected function setUp(): void {
$dispatcher = new EventDispatcher();
EventDispatcher::setInstance($dispatcher);
}
protected function tearDown(): void {
EventDispatcher::resetInstance();
}
Диспетчеризация и прослушивание
// Dispatch
EventDispatcher::dispatch(new StreamStartedEvent($lineId, $streamId));
// Listen
EventDispatcher::listen(StreamStartedEvent::class, function (StreamStartedEvent $e): void {
// handle
}, priority: 10);
// Remove a listener
EventDispatcher::unlisten(StreamStartedEvent::class, $myCallable);
// Check
EventDispatcher::hasListeners(StreamStartedEvent::class); // bool
Приоритет — более высокое целое число = вызывается первым. По умолчанию 0.
Регистрация слушателей в модуле
Вариант 1 — Получить массив eventsubscribers()
public function getEventSubscribers(): array {
return [
StreamStartedEvent::class => [$this, 'onStreamStarted'],
StreamStartedEvent::class => [[$this, 'onStreamStarted'], 20], // with priority
];
}
Вариант 2 — атрибут #[ListensTo]
use ListensTo;
class MyModuleModule extends BaseModule {
#[ListensTo(StreamStartedEvent::class, priority: 20)]
public function onStreamStarted(StreamStartedEvent $e): void {
// handle
}
// IS_REPEATABLE — multiple attributes on the same method
#[ListensTo(StreamStartedEvent::class)]
#[ListensTo(StreamStoppedEvent::class)]
public function onStreamChange(object $e): void {
// handle both events
}
}
Оба механизма работают одновременно и могут сосуществовать в одном модуле.
ModuleLoader::bootAll() выполняет оба прохода для каждого загруженного модуля.
Останавливаемые события
Продлить AbstractEvent и вызвать $e->stopPropagation():
class MyGatingEvent extends AbstractEvent {
public bool $allowed = true;
}
EventDispatcher::listen(MyGatingEvent::class, function (MyGatingEvent $e): void {
if (!$this->check()) {
$e->allowed = false;
$e->stopPropagation();
}
}, priority: 100);
Прослушиватели пропускаются, как только isPropagationStopped() возвращает значение true.
Встроенные основные события
| Класс события | Местоположение | Когда отправлено | Останавливаемый |
|---|---|---|---|
ModuleLoadedEvent |
Events/Module/ |
После загрузки файла модуля | Нет |
ModuleBootedEvent |
Events/Module/ |
После вызова boot() |
Нет |
PackageInstalledEvent |
Events/Module/ |
После установки marketplace | Нет |
UserAuthenticatedEvent |
Events/Auth/ |
После успешного входа в систему | Да |
UserLoggedOutEvent |
Events/Auth/ |
После выхода из системы | Нет |
StreamStartedEvent |
Events/Stream/ |
После начала трансляции | Нет |
StreamStoppedEvent |
Events/Stream/ |
После того, как поток прекратился | Нет |
SettingsChangedEvent |
Events/Settings/ |
После сохранения настроек | Нет |
Создание пользовательского события
Простой класс — используйте свойства readonly для неизменяемых полезных нагрузок:
<?php
namespace XcVm\Module\MyModule;
final class MyModuleEvent {
public function __construct(
public readonly int $lineId,
public readonly string $reason,
) {}
}
Останавливаемое событие — продлить AbstractEvent:
<?php
namespace XcVm\Module\MyModule;
use AbstractEvent;
final class MyModuleGatingEvent extends AbstractEvent {
public bool $vetoed = false;
public function __construct(
public readonly int $resourceId,
) {}
}
Отправка из любой точки мира после начальной загрузки:
EventDispatcher::dispatch(new MyModuleEvent($lineId, 'reason'));
Ссылка на атрибут ListensTo
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
final class ListensTo {
public function __construct(
public readonly string $eventClass,
public readonly int $priority = 0,
) {}
}
eventClass— полное название класса для событияpriority— приоритет прослушивателя (более высокий = вызывается первым; по умолчанию0)- Размещается в общедоступных методах классов, расширяющих
BaseModule IS_REPEATABLE— зарегистрировано несколько#[ListensTo]для одного и того же метода- Если
eventClassне существует во время выполнения, атрибут корректно пропускается (без исключений).
Связанные файлы
| Файл | Роль |
|---|---|
src/Core/Events/EventDispatcher.php |
Диспетчер событий PSR-14 |
src/Core/Events/ListensTo.php |
Атрибут слушателя |
src/Core/Events/ |
Классы событий (Авторизация, модуль, Настройки, поток) |