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

Точки расширения модуля

Основные точки расширения, к которым подключается модуль: контейнер DI, потоковое промежуточное программное обеспечение, задачи cron, миграции версий и типизированные события. Чтобы создать модуль, смотрите Создание модуля; для загрузки/жизненного цикла смотрите Жизненный цикл модуля.

Оформление контейнеров и сервизов DI

Сервисы регистрируются в boot() через ServiceContainer. Контейнер поддерживает:

  • set(id, factory) — отложенный синглтон с помощью вызываемого или прямого значения
  • factory(id, callable) — новый экземпляр для каждого get()
  • decorate(id, callable, priority) — завершение существующей службы
// 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:

public function get(string $id): mixed;  // throws NotFoundException if missing
public function has(string $id): bool;

NotFoundException реализует NotFoundExceptionInterface extends ContainerExceptionInterface.


События PSR-14

Модули подписываются на типизированные события с помощью атрибута getEventSubscribers() или #[ListensTo]. Это описано в полном объеме — диспетчеризация, регистрация слушателей, приоритеты, события, которые можно остановить, и встроенный каталог событий - на специальной странице Система событий.


Потоковое промежуточное программное обеспечение

Модули могут внедрять промежуточное программное обеспечение в потоковый конвейер, реализуя StreamMiddlewareProviderInterface (отдельно от ModuleInterface):

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:

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;
    }
}

Регистрация в модуле:

public function registerCommands(CommandRegistry $registry): void {
    $registry->register(new MyCronJob());
}

Объявите запись crontab, переопределив getCronEntries() в классе module:

public function getCronEntries(): array {
    return [
        '*/5 * * * *' => 'cron:my_task',
    ];
}

ModuleLoader::collectCronEntries() объединяет записи всех модулей и StartupCommand / StatusCommand автоматически записывайте их в системный crontab — никаких изменений в основных файлах не требуется.

Формат: ключ = выражение cron, значение = имя консольной команды, зарегистрированное с помощью registerCommands().


Версионные миграции (MigratableInterface)

Два механизма, оба аддитивные. файловая схема, описанный в разделе Структура каталогов модулей (database.sql мастер + database_drop.sql разборка + migrations/<semver>.sql дельты) используется по умолчанию для обычный DDL/seed. MigratableInterface ниже приведен программный путь для обновления шаги, требующие логики PHP (повторное заполнение данных, условные изменения). Модуль может использовать один из них или оба; ModuleManager::updateModule() сначала запускает файл delta, затем вызываемые миграции.

Модули, для обновления которых требуется PHP логическая реализация MigratableInterface:

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 является необязательным