mirror of
https://github.com/Vateron-Media/XC_VM.git
synced 2026-10-19 12:02:59 +02:00
190 lines
8.7 KiB
Markdown
190 lines
8.7 KiB
Markdown
# Точки расширения модуля
|
||||
|
|
|
|||
|
|
Основные точки расширения, к которым подключается модуль: контейнер DI, потоковое промежуточное программное обеспечение, задачи cron, миграции версий и типизированные события. Чтобы создать модуль, смотрите [Создание модуля](module-authoring.md); для загрузки/жизненного цикла смотрите [Жизненный цикл модуля](module-lifecycle.md).
|
|||
|
|
|
|||
|
|
## Оформление контейнеров и сервизов DI
|
|||
|
|
|
|||
|
|
|
|||
|
|
Сервисы регистрируются в `boot()` через `ServiceContainer`. Контейнер поддерживает:
|
|||
|
|
|
|||
|
|
- **`set(id, factory)`** — отложенный синглтон с помощью вызываемого или прямого значения
|
|||
|
|
- **`factory(id, callable)`** — новый экземпляр для каждого `get()`
|
|||
|
|
- **`decorate(id, callable, priority)`** — завершение существующей службы
|
|||
|
|
|
|||
|
|
```php
|
|||
|
|
// Decorate a service (adds behaviour around the original)
|
|||
|
|
$container->decorate('stream.encoder', function (mixed $inner, ServiceContainer $c): MyEncoder {
|
|||
|
|
return new MyEncoder($inner, $c->get('settings'));
|
|||
|
|
}, priority: 20);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Декораторы объединены в цепочки по приоритету (самый высокий и самый внешний). Защищенные сервисы
|
|||
|
|
(`db`, `settings`, `config`, `auth`) не удается оформить — любая попытка приводит к результату `RuntimeException`.
|
|||
|
|
|
|||
|
|
### Соответствие требованиям стандарта PSR-11
|
|||
|
|
|
|||
|
|
`ServiceContainer` реализует `ContainerInterface`:
|
|||
|
|
|
|||
|
|
```php
|
|||
|
|
public function get(string $id): mixed; // throws NotFoundException if missing
|
|||
|
|
public function has(string $id): bool;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`NotFoundException` реализует `NotFoundExceptionInterface extends ContainerExceptionInterface`.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## События PSR-14
|
|||
|
|
|
|||
|
|
Модули подписываются на типизированные события с помощью атрибута `getEventSubscribers()` или `#[ListensTo]`. Это описано в полном объеме — диспетчеризация, регистрация слушателей, приоритеты, события, которые можно остановить, и встроенный каталог событий - на специальной странице [Система событий](event-system.md).
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Потоковое промежуточное программное обеспечение
|
|||
|
|
|
|||
|
|
|
|||
|
|
Модули могут внедрять промежуточное программное обеспечение в потоковый конвейер, реализуя
|
|||
|
|
`StreamMiddlewareProviderInterface` (отдельно от `ModuleInterface`):
|
|||
|
|
|
|||
|
|
```php
|
|||
|
|
class MyStreamMiddleware implements StreamMiddlewareInterface {
|
|||
|
|
|
|||
|
|
public function getPriority(): int {
|
|||
|
|
return 50;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public function handle(StreamContext $ctx, callable $next): StreamContext {
|
|||
|
|
// before — read or set attributes
|
|||
|
|
$ctx->set('my.key', 'value');
|
|||
|
|
$ctx = $next($ctx);
|
|||
|
|
// after
|
|||
|
|
return $ctx;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`StreamContext` - это набор атрибутов (`get`, `set`, `has`, `abort`, `isAborted`). `StreamPipeline`
|
|||
|
|
выполняет промежуточное программное обеспечение, отсортированное по убыванию `getPriority()`.
|
|||
|
|
|
|||
|
|
### Приоритеты трубопровода
|
|||
|
|
|
|||
|
|
|Диапазон|Владелец|
|
|||
|
|
| ---------- | ----------------- |
|
|||
|
|
| `80–100` |Ядро (авторизация, разрешение, ограничение подключения)|
|
|||
|
|
| `0–79` |Модули|
|
|||
|
|
|
|||
|
|
### Зарезервированные слоты на панели навигации
|
|||
|
|
|
|||
|
|
|Родительский узел|Гнезда для модулей|
|
|||
|
|
| ------------------- | ------------------ |
|
|||
|
|
| `management.service_setup` |`order` ≥ 60|
|
|||
|
|
| `management.logs` |`order` ≥ 170|
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Задача Cron
|
|||
|
|
|
|||
|
|
|
|||
|
|
**Логика Cron** (`MyCron.php`) — только бизнес-логика, без подключения к интерфейсу командной строки.
|
|||
|
|
|
|||
|
|
**Обертка от CronJob** (`MyCronJob.php`) — реализует `CommandInterface`, использует `CronTrait`:
|
|||
|
|
|
|||
|
|
```php
|
|||
|
|
class MyCronJob implements CommandInterface {
|
|||
|
|
use CronTrait;
|
|||
|
|
|
|||
|
|
public function getName(): string { return 'cron:my_task'; }
|
|||
|
|
public function getDescription(): string { return 'Cron: my task'; }
|
|||
|
|
|
|||
|
|
public function execute(array $rArgs): int {
|
|||
|
|
if (!$this->assertRunAsXcVm()) {
|
|||
|
|
return 1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
require INCLUDES_PATH . 'admin.php';
|
|||
|
|
require_once __DIR__ . '/MyCron.php';
|
|||
|
|
|
|||
|
|
$this->initCron('XC_VM[MyTask]');
|
|||
|
|
MyCron::run();
|
|||
|
|
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Регистрация в модуле:
|
|||
|
|
|
|||
|
|
```php
|
|||
|
|
public function registerCommands(CommandRegistry $registry): void {
|
|||
|
|
$registry->register(new MyCronJob());
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Объявите запись crontab, переопределив `getCronEntries()` в классе module:
|
|||
|
|
|
|||
|
|
```php
|
|||
|
|
public function getCronEntries(): array {
|
|||
|
|
return [
|
|||
|
|
'*/5 * * * *' => 'cron:my_task',
|
|||
|
|
];
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`ModuleLoader::collectCronEntries()` объединяет записи всех модулей и `StartupCommand` /
|
|||
|
|
`StatusCommand` автоматически записывайте их в системный crontab — никаких изменений в основных файлах не требуется.
|
|||
|
|
|
|||
|
|
**Формат:** ключ = выражение cron, значение = имя консольной команды, зарегистрированное с помощью `registerCommands()`.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Версионные миграции (MigratableInterface)
|
|||
|
|
|
|||
|
|
|
|||
|
|
> **Два механизма, оба аддитивные.** **файловая схема**, описанный в разделе
|
|||
|
|
> [Структура каталогов модулей](module-authoring.md#module-directory-structure) (`database.sql` мастер +
|
|||
|
|
> `database_drop.sql` разборка + `migrations/<semver>.sql` дельты) используется по умолчанию для
|
|||
|
|
> обычный DDL/seed. `MigratableInterface` ниже приведен **программный** путь для обновления
|
|||
|
|
> шаги, требующие логики PHP (повторное заполнение данных, условные изменения). Модуль может использовать
|
|||
|
|
> один из них или оба; `ModuleManager::updateModule()` сначала запускает файл delta, затем
|
|||
|
|
> вызываемые миграции.
|
|||
|
|
|
|||
|
|
Модули, для обновления которых требуется PHP логическая реализация `MigratableInterface`:
|
|||
|
|
|
|||
|
|
```php
|
|||
|
|
namespace XcVm\Module\MyModule;
|
|||
|
|
|
|||
|
|
use BaseModule;
|
|||
|
|
use MigratableInterface;
|
|||
|
|
use ServiceContainer;
|
|||
|
|
|
|||
|
|
class MyModuleModule extends BaseModule implements MigratableInterface {
|
|||
|
|
|
|||
|
|
public function getMigrations(): array {
|
|||
|
|
return [
|
|||
|
|
'1.1.0' => function (): void {
|
|||
|
|
// runs when upgrading from any version < 1.1.0 to >= 1.1.0
|
|||
|
|
global $db;
|
|||
|
|
$db->query("ALTER TABLE xc_my_table ADD COLUMN new_col INT DEFAULT 0");
|
|||
|
|
},
|
|||
|
|
'1.2.0' => function (): void {
|
|||
|
|
// runs when upgrading from < 1.2.0 to >= 1.2.0
|
|||
|
|
},
|
|||
|
|
];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`ModuleManager::updateModule()` считывает `installed_version` из хранилища переопределений, фильтрует
|
|||
|
|
сопоставляет только записи `> fromVersion && <= toVersion`, сортирует по полу и запускает каждую из них.
|
|||
|
|
вызываемый в своей собственной транзакции базы данных. `installModule()` записи `installed_version` после
|
|||
|
|
успешная установка; `uninstallModule()` удаляет ее.
|
|||
|
|
|
|||
|
|
**Key rules:**
|
|||
|
|
|
|||
|
|
- Ключи - это полустрочные строки (`'1.1.0'`, `'2.0.0'`) — `version_compare` используется упорядочение
|
|||
|
|
- Каждая миграция выполняется в рамках своей собственной транзакции — сбой откатывает только этот шаг
|
|||
|
|
- `BaseModule` предоставляет значение по умолчанию `getMigrations(): array { return []; }`, поэтому реализация
|
|||
|
|
`MigratableInterface` является необязательным
|
|||
|
|
|
|||
|
|
---
|