Files
XC_VM/docs/ru/development/event-system.md
T
Divarion-D 76844fef11 docs: restructure, fix PSR-4 drift, and unify en/ru
Overhaul the Docsify documentation (English + Russian) so it matches the current
codebase and follows one consistent pattern.

Content accuracy (post-migration):
- Rewrite development/autoloader.md to PSR-4 / Composer (the old XC_Autoloader
  scanner, igbinary tmp/cache/autoload_map and registerDirectories are gone).
- PascalCase every source path (src/core -> src/Core, domain/Stream, cli/Commands,
  public/Controllers, Infrastructure/Redis, ...) across all docs.
- Replace the removed autoload.php references with vendor/autoload.php
  (build_system, bootstrap-contexts, error-handling, modules).
- ssl-generation: note that the installer now auto-generates a unique self-signed
  certificate before Nginx starts.

Common pattern (Clean & uniform):
- Strip emoji from headings; remove the in-page Navigation blocks (the Docsify
  sidebar already provides navigation).
- One H1 + intro per doc; uniform "Related files" / "Связанные файлы" section,
  added to the code-centric docs that lacked it.

Structure:
- Remove the empty stray docs/api/; move updates_checklist.md into builds/;
  link the previously-orphaned ucs-integration.md.
- Regroup the sidebars (split the oversized guides group into Developer Guides /
  Security & Access / Integrations; fold builds into Build & Release).

Augment:
- dev-workflow: Local Setup (make dev-tools) + Quality Checks (phpstan, cs, gates).
- build_system: Composer Dependencies section (committed prod-only vendor,
  committed lock, dev tools via composer install, no build-time vendor step).

en/ru parity:
- Apply the same structure, fixes and pattern to docs/ru/ (translated), including
  a new Russian ucs-integration.md. The en and ru file sets are now identical.
2026-06-26 15:56:15 +03:00

204 lines
7.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Система событий
XC_VM использует типизированный диспетчер событий в стиле PSR-14. Все события —
обычные PHP-классы, диспетчируемые и принимаемые по имени класса. Диспетчер
хранится в DI-контейнере под ключом `events`.
---
## EventDispatcher
`EventDispatcher` — синглтон с instance-мостом. Статические методы делегируют
в активный экземпляр, поэтому существующие вызовы работают без изменений.
```php
// bootstrap.php создаёт канонический экземпляр:
$dispatcher = new EventDispatcher();
EventDispatcher::setInstance($dispatcher);
$container->set('events', $dispatcher);
// Оба пути достигают одного хранилища слушателей:
EventDispatcher::dispatch(new MyEvent(...)); // статический вызов
$container->get('events')->dispatch(new MyEvent(...)); // вызов через экземпляр
```
В тестах изолируйте состояние на каждый тест:
```php
protected function setUp(): void {
$dispatcher = new EventDispatcher();
EventDispatcher::setInstance($dispatcher);
}
protected function tearDown(): void {
EventDispatcher::resetInstance();
}
```
---
## Диспетчеризация и прослушивание
```php
// Диспетчеризация
EventDispatcher::dispatch(new StreamStartedEvent($lineId, $streamId));
// Прослушивание
EventDispatcher::listen(StreamStartedEvent::class, function (StreamStartedEvent $e): void {
// обработка
}, priority: 10);
// Удаление слушателя
EventDispatcher::unlisten(StreamStartedEvent::class, $myCallable);
// Проверка
EventDispatcher::hasListeners(StreamStartedEvent::class); // bool
```
**Приоритет** — чем выше целое число, тем раньше вызывается слушатель. По умолчанию `0`.
---
## Регистрация слушателей в модуле
### Вариант 1 — массив getEventSubscribers()
```php
public function getEventSubscribers(): array {
return [
StreamStartedEvent::class => [$this, 'onStreamStarted'],
// с приоритетом:
UserAuthenticatedEvent::class => [[$this, 'onAuth'], 20],
];
}
```
### Вариант 2 — атрибут #[ListensTo]
```php
use ListensTo;
class MyModuleModule extends BaseModule {
#[ListensTo(StreamStartedEvent::class, priority: 20)]
public function onStreamStarted(StreamStartedEvent $e): void {
// обработка
}
// IS_REPEATABLE — несколько атрибутов на одном методе
#[ListensTo(StreamStartedEvent::class)]
#[ListensTo(StreamStoppedEvent::class)]
public function onStreamChange(object $e): void {
// обработка обоих событий
}
}
```
Оба механизма работают одновременно и могут сосуществовать в одном модуле.
`ModuleLoader::bootAll()` выполняет оба прохода для каждого загруженного модуля.
---
## Останавливаемые события (stoppable)
Расширьте `AbstractEvent` и вызовите `$e->stopPropagation()`:
```php
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/` | После установки из маркетплейса | Нет |
| `UserAuthenticatedEvent` | `Events/Auth/` | После успешного входа | Да |
| `UserLoggedOutEvent` | `Events/Auth/` | После выхода | Нет |
| `StreamStartedEvent` | `Events/Stream/` | После старта стрима | Нет |
| `StreamStoppedEvent` | `Events/Stream/` | После остановки стрима | Нет |
| `SettingsChangedEvent` | `Events/Settings/` | После сохранения настроек | Нет |
---
## Создание пользовательского события
Обычный класс с `readonly`-свойствами для неизменяемой полезной нагрузки:
```php
<?php
namespace XcVm\Module\MyModule;
final class MyModuleEvent {
public function __construct(
public readonly int $lineId,
public readonly string $reason,
) {}
}
```
Останавливаемое событие — расширяйте `AbstractEvent`:
```php
<?php
namespace XcVm\Module\MyModule;
use AbstractEvent;
final class MyModuleGatingEvent extends AbstractEvent {
public bool $vetoed = false;
public function __construct(
public readonly int $resourceId,
) {}
}
```
Диспетчеризация из любого места после bootstrap:
```php
EventDispatcher::dispatch(new MyModuleEvent($lineId, 'reason'));
```
---
## Справочник атрибута ListensTo
```php
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
final class ListensTo {
public function __construct(
public readonly string $eventClass,
public readonly int $priority = 0,
) {}
}
```
- `eventClass` — полное имя класса события (FQN)
- `priority` — приоритет слушателя (выше = вызывается раньше; по умолчанию `0`)
- Размещается на публичных методах классов, расширяющих `BaseModule`
- `IS_REPEATABLE` — несколько `#[ListensTo]` на одном методе регистрируются все
- Если `eventClass` не существует во время выполнения — атрибут пропускается (не исключение)
## Связанные файлы
| Файл | Роль |
| --- | --- |
| `src/Core/Events/EventDispatcher.php` | Диспетчер событий PSR-14 |
| `src/Core/Events/ListensTo.php` | Атрибут слушателя |
| `src/Core/Events/` | Классы событий (Auth, Module, Settings, Stream) |