Files
XC_VM/docs/ru/development/core-wiring.md
T

206 lines
21 KiB
Markdown
Raw Normal View History

# Подключение и регистрация сердечника
2026-09-10 17:31:22 +03:00
Как панель собирается сама по себе при загрузке: один служебный контейнер, как он заполняется и как
модули помещают свои маршруты, события, команды, записи cron и элементы навигационной панели в основные реестры.
Эта страница является **сквозное повествование и потребительская сторона** одним из основных реестров. То
авторская сторона каждой точки расширения задокументирована в другом месте и связана с
2026-09-10 17:31:22 +03:00
[Что живет в другом месте](#what-lives-elsewhere) — на этой странице это не повторяется.
---
## Один контейнер
Все зависит от одного процесса в масштабах всего процесса `ServiceContainer`
(`src/Core/Container/ServiceContainer.php`), синглтон PSR-11 `ContainerInterface`, полученный с помощью
`ServiceContainer::getInstance()`. `XC_Bootstrap::boot()` создает его, заполняет его, и каждый последующий
потребитель (`XC_Bootstrap::getContainer()`, модуль `boot()`, разрешение обработчика маршрута) считывает то же самое
пример. Тесты сбрасывают его с помощью `ServiceContainer::resetInstance()`.
Для основных служб есть значение **автоматическое обнаружение поставщика услуг отсутствует**: зарегистрирован канонический набор
обязательно с помощью bootstrap (см. ниже), а модули добавляют свои собственные сервисы в свои
`boot()`. То, что вы видите зарегистрированным, в точности соответствует коду `set()` — ничто не подключено с помощью
сканирование условных обозначений или аннотаций.
---
## Заполнение контейнера при загрузке
Это основа системы. `XC_Bootstrap::boot()` регистрация услуг осуществляется в два этапа.
**Early (in `boot()` itself), before any subsystem loads:**
|Ключ|Ценность|Источник|
| --- | --- | --- |
| `context` |активное строковое значение `BootContext`| `src/bootstrap.php` |
| `options` |массив `$options`, переданный в `boot()`| `src/bootstrap.php` |
| `config` |`ConfigReader::getAll()` (проанализировано `config.ini`)| `src/bootstrap.php` |
**Набор канонических служб — `XC_Bootstrap::populateContainer()`** (выполняется для каждого контекста, за исключением
`Minimal`, т.е. как только база данных будет запущена):
|Ключ|Ценность|Записи|
| --- | --- | --- |
| `db` |дескриптор `Database`|защищенный (см. ниже)|
| `settings` | `SettingsManager::getAll()` |защищенный|
| `servers` | `ServerRepository::getAll()` | |
| `bouquets` | `BouquetService::getAll()` | |
| `categories` | `CategoryService::getFromDatabase()` | |
| `redis` | `RedisManager::instance()` |только тогда, когда для этого контекста был загружен Redis|
| `translator` | `Translator::class` | |
| `events` |новый экземпляр `EventDispatcher`|также подключен к статическому фасаду (см. [События](#events))|
Сразу после этого **`XC_Bootstrap::assertContainerHealth()`** жестко требует, чтобы `events` было
присутствует (плюс `db`/`redis`, когда они были загружены) и выдает ошибку, если нет — гарантия **громкий сбой**
2026-09-10 17:31:22 +03:00
этот более поздний код может предполагать, что эти службы существуют, а не проверять каждую из них на нулевой уровень.
> Записи `context`/`config`/`settings`/`servers`/`bouquets`/`categories` представляют собой простые данные
> снимки, сделанные при загрузке, а не на ленивых заводах — они считываются, а не пересчитываются, на протяжении всего срока службы устройства.
> запрос.
---
## Ссылка на сервисный контейнер
Все установщики могут быть объединены в цепочку (`return $this`).
|Метод|Цель|
| --- | --- |
| `set(id, value)` |Зарегистрируйте сервис. Значение **`Closure`** становится **ленивая фабрика синглетов** (сначала вызывается один раз для `get()`, затем кэшируется); все остальное — скаляр, объект или массив `[Class, 'method']` — сохраняется как готовое значение.|
| `factory(id, callable)` |Зарегистрируйте фабрику, которая возвращает значение **новый экземпляр для каждого `get()`** (без кэширования).|
| `register(array)` |Скопируйте `set()` с карты `id => value`.|
| `get(id)` |Разрешить службу. Возвращает кэшированный синглтон, если он присутствует; в противном случае запускает фабрику один раз, кэширует ее и применяет декораторы. Защищает от циклических фабрик и выдает `CircularDependencyException`; выдает `NotFoundException` для неизвестного идентификатора.|
| `getOrDefault(id, default)` |Как `get()`, но возвращает `default` вместо того, чтобы выбрасывать при отсутствии.|
|`has(id)` / `keys()` / `remove(id)` / `dump()`|Самоанализ и разрушение.|
| `decorate(id, decorator, priority)` |Завершите работу с существующей службой, наивысший приоритет которой был применен последним. **Запрещенный** в защищенных службах `['db', 'settings', 'config', 'auth']` — выберите один из вариантов оформления. Смотрите [Пункты расширения модуля](module-extension-points.md).|
|`tag(id, tag)` / `getTagged(tag)`|Сгруппируйте службы под одной меткой для пакетного поиска.|
> **Метки в настоящее время не используются основной проводкой.** `tag()`/`getTagged()` существует, но путь загрузки
> собирает вклады модулей путем проверки `instanceof` по списку загруженных модулей (см.
> [`bootAll`](#moduleloaderbootall-the-orchestrator)), **нет** по тегу. Исходный документированный блок все еще
> описывает теги как механизм сбора данных для подписчиков/cron/маршрутов, который описывает
> предполагаемый дизайн, а не текущий код. Не полагайтесь на коллекцию на основе тегов, пока она не будет создана на самом деле.
> реализованный.
Resolving a `[Class, 'method']` handler (used by the Router) goes **through the container**, so
классы-обработчики разграничиваются при регистрации, а в противном случае возвращаются к `new`.
---
## `ModuleLoader::bootAll` — организатор
После того, как `ModuleLoader::loadAll()` обнаружил, отфильтровал и **топологически отсортированный** отобрал модули
(see [Module Lifecycle](module-lifecycle.md)), `bootAll()` is the single place that pushes each
вклад модуля в основные реестры. Он проверяет каждое значение **подинтерфейс** на `instanceof`
таким образом, модуль реализует только те перехватчики, которые ему нужны (`ModuleInterface` - это их совокупность).
Порядок для каждого модуля внутри `bootAll(ServiceContainer $container, ?Router $router, ?StreamPipeline $pipeline)`:
1. **Сначала основная навигационная панель, один раз** — `(new CoreNavbarProvider())->registerNavbar(...)` перед любым модулем, поэтому узлы основного меню существуют как родительские.
2. `ServiceProviderInterface` → `boot($container)` **затем** `registerEventSubscribers()` — службы регистрируются до подключения слушателей этого модуля.
3. `StreamMiddlewareProviderInterface` → `registerStreamMiddleware($pipeline)` — **только в том случае, если было передано значение `$pipeline`**.
4. `RouteProviderInterface` → `registerRoutes($router)` — **только в том случае, если было передано значение `$router`** (`$router !== null`).
5. `NavbarProviderInterface` → `registerNavbar(...)`.
2026-09-10 17:31:22 +03:00
6. `TopbarProviderInterface` → `registerTopbar(...)` — кнопки действий для каждой страницы (объединены в `Topbar::config`).
7. `TableProviderInterface` → `registerTables(...)` — обработчики таблиц на сервере (просмотрены с помощью `TableController`).
8. `PermissionProviderInterface` → `registerPermissions(...)` — ключи дополнительных разрешений (объединены в `PermissionReference`).
9. `QuickToolsProviderInterface` → `registerQuickTools(...)` — Кнопка быстрого доступа + обработчик.
Шаги 6-9 - это панель администратора, принадлежащая модулю: их реестры (`TopbarRegistry`,
`TableRegistry`, `PermissionRegistry`, `QuickToolsRegistry`) находятся `reset()` в самом
запускается с `bootAll` и отображается позже в верхней панели/таблице/разрешениях/быстрых инструментах
код. Они выполняются независимо от `$router` (только реестры, никаких маршрутов), поэтому модуль
таблица доступна даже по пути REST API, который загружает модули без маршрутизатора.
Два вклада равны **отдельные проходы, не являющиеся частью `bootAll`**:
- `registerAllCommands($registry)` — Команды CLI, каждый модуль которых заключен в try / catch, поэтому один сломанный модуль не может заблокировать весь CLI.
- `collectCronEntries()` — строки crontab, собранные из `CronProviderInterface::getCronEntries()`, используемые командами запуска/состояния.
Потому что маршруты и потоковое промежуточное программное обеспечение стробируются на основе необязательных аргументов `$router`/`$pipeline`,
**одно и то же значение `bootAll()` выполняет различную работу в зависимости от точки входа** — интерфейс командной строки не передает ни,
таким образом, к нему подключены только подписчики services + event (и безвредная навигационная панель).
---
## События
`EventDispatcher` (`src/Core/Events/EventDispatcher.php`) - это синглтон со статическим фасадом.
`populateContainer()` выполняет `new EventDispatcher()` → `EventDispatcher::setInstance($d)` →
`$container->set('events', $d)`, таким образом, запись контейнера `events` и статический
`EventDispatcher::dispatch()/listen()` общий доступ к хранилищу прослушивателей **один**. Модули регистрируют прослушиватели
во время шага 2 из `bootAll`, описанного выше, с помощью атрибута `getEventSubscribers()` или `#[ListensTo]`.
Полная информация - регистрационные формы, приоритеты, мероприятия, которые можно отменить, встроенный каталог мероприятий — приведена ниже.
в [системе событий](event-system.md); на этой странице описывается только *где в последовательности загрузки* прослушиватели
подключись к сети.
---
## Регистрация команд CLI
2026-09-10 17:31:22 +03:00
Путь CLI (`src/console.php`) создает свой набор команд в два этапа:
1. **Автоматическое обнаружение ядра.** `new CommandRegistry()`, затем глобус `Cli/Commands/*.php` и
`Cli/CronJobs/*.php`, сопоставьте каждый каталог с его пространством имен и с помощью отражения `register()` каждый
**неабстрактный** класс, реализующий `CommandInterface`. Таким образом, добавление основной команды - это просто
удаление класса в одном из этих каталогов — никакой ручной регистрации.
2. **Команды модуля.** `ModuleLoader::registerAllCommands($registry)` вызывает каждый
`CommandProviderInterface::registerCommands()`.
`CommandRegistry` (`src/Cli/CommandRegistry.php`) - это простая карта `name → CommandInterface`:
`register()`, `dispatch($argv)` (обрабатывает `--list`/`--help`, группирует справку по префиксу `group:` в
название команды), `get($name)`, `getAll()`.
---
## Сквозная загрузка — администрирование (web)
`src/Public/index.php`:
1. `XC_Bootstrap::boot(BootContext::Admin)` — создает контейнер; устанавливает `context`/`options`/`config`; загружает константы; выполняет проверку флуда/хостинга; `bootAdmin()` (сессия, база данных, `LegacyInitializer`, Redis, API администратора/реселлера, транслятор, обработчик завершения работы, константы состояния); **`populateContainer()`**; **`assertContainerHealth()`**.
2. `Router::getInstance()`, затем `require` основные файлы маршрутов `routes/{scope}.php` (+ `routes/api.php`).
3. Блок загрузки модуля: `router->beginModuleRegistration()` → `new ModuleLoader; loadAll(); bootAll($container, $router)` → `router->endModuleRegistration()` → `drainRouteCollisions()`. Режим регистрации модуля выполняет **выигрывают основные маршруты** по любому маршруту модуля с одинаковым путем; коллизии фиксируются, а не перезаписываются автоматически.
4. `Router::dispatch()` / `dispatchApi()` обрабатывает запрос, разрешая `[Class, 'method']` обработчики через контейнер.
Смотрите [Контексты начальной загрузки](bootstrap-contexts.md), чтобы узнать, какие именно подсистемы инициализируются в каждом контексте, и [Обработка HTTP-запросов](http-request-handling.md) для API маршрутизатора и диспетчеризации.
---
## Сквозная загрузка — CLI
`src/console.php`:
1. `require bootstrap.php`; `XC_Bootstrap::boot(BootContext::Cli)` — DB, `LegacyInitializer`, необязательно Redis, заголовок процесса, затем **такой же** `populateContainer()` (таким образом, `events` и friends также существуют в CLI).
2. `new CommandRegistry()`; автоматическое обнаружение ядра `Cli/Commands` + `Cli/CronJobs` (глобус + отражение) → `register()`.
3. `new ModuleLoader; loadAll(); registerAllCommands($registry)` (команды модуля, **до** `bootAll`), затем `bootAll(getContainer())` **без маршрутизатора и трубопровода** — таким образом, маршруты и потоковое промежуточное программное обеспечение пропускаются; подключаются только службы модуля + подписчики событий.
4. `registry->dispatch($argv)` выполняет запрошенную команду.
---
## Что живет в другом месте
Чтобы избежать дублирования, информация об авторе и каждой подсистеме размещается на отдельных страницах:
|Тема|Страница|
| --- | --- |
2026-09-10 17:31:22 +03:00
|Какие подсистемы инициализирует каждый контекст; `boot()` параметры; идемпотентность|[Контексты начальной загрузки](bootstrap-contexts.md)|
|Формы регистрации на мероприятия, приоритеты, мероприятия, которые можно отменить, каталог мероприятий|[Система событий](event-system.md)|
|API маршрутизатора, `begin/endModuleRegistration`, диспетчеризация, разрешение обработчика|[Обработка HTTP-запросов](http-request-handling.md)|
|Конструктор элементов навигационной панели, правила видимости, рендеринг|[Рендеринг навигационной панели](navbar-rendering.md)|
|Обнаружение модулей, фильтрация env, топосортировка, включение/ выключение, установка / обновление|[Жизненный цикл модуля](module-lifecycle.md)|
|DI—оформление, потоковое промежуточное программное обеспечение, cron, миграции - хуки автора модуля|[Точки расширения модуля](module-extension-points.md)|
|Написание модуля (манифест, контракт класса, макет каталога)|[Разработка модуля](module-authoring.md)|
## Связанные файлы
|Файл|Роль|
| --- | --- |
| `src/Core/Container/ServiceContainer.php` |Контейнер DI: `set`/`factory`/`get`/`decorate`/`tag`|
| `src/bootstrap.php` |`XC_Bootstrap::boot`, `populateContainer`, `assertContainerHealth`|
| `src/Core/Module/ModuleLoader.php` |`bootAll`, `registerAllCommands`, `collectCronEntries`|
| `src/Core/Events/EventDispatcher.php` |Диспетчер событий + статический фасад, объединенный в `events`|
| `src/Cli/CommandRegistry.php` |Карта команд CLI + `dispatch`|
| `src/console.php` |Точка входа в интерфейс командной строки: автоматическое обнаружение основной команды + загрузка модуля|
| `src/Public/index.php` |Веб-точка входа: файлы маршрута + загрузочный блок модуля|