# Подключение и регистрация сердечника Как панель собирается сама по себе при загрузке: один служебный контейнер, как он заполняется и как модули помещают свои маршруты, события, команды, записи cron и элементы навигационной панели в основные реестры. Эта страница является **сквозное повествование и потребительская сторона** одним из основных реестров. То авторская сторона каждой точки расширения задокументирована в другом месте и связана с [Что живет в другом месте](#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`, когда они были загружены) и выдает ошибку, если нет — гарантия **громкий сбой** этот более поздний код может предполагать, что эти службы существуют, а не проверять каждую из них на нулевой уровень. > Записи `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(...)`. 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 Путь 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)` выполняет запрошенную команду. --- ## Что живет в другом месте Чтобы избежать дублирования, информация об авторе и каждой подсистеме размещается на отдельных страницах: |Тема|Страница| | --- | --- | |Какие подсистемы инициализирует каждый контекст; `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` |Веб-точка входа: файлы маршрута + загрузочный блок модуля|