Контейнер сервісів
Concept Core будує застосунок навколо DI-контейнера (Dependency Injection): залежності оголошуються один раз, а класи отримують їх через конструктор або фабрики. Реалізація — League Container з підтримкою PSR-11.
Див. також: Service providers, контролери → автовайринг, життєвий цикл запиту.
Створення контейнера
Ланцюжок bootstrap (див. налаштування):
// public/index.php
$app = require bootstrap/app.php;
$app->run();
// bootstrap/app.php
$app = App::create($rootPath, $paths);
$app->registerServiceProviders([$rootPath . '/bootstrap/providers/app.php']);
У App::__construct() одразу реєструється:
ReflectionContainer— автовайринг (delegate)ContainerInterface::class→ сам контейнер (shared)App::class→ екземпляр застосункуPathManager— шляхи зbootstrap/paths.phpWhoops— ранній обробник помилок (до providers)
$this->container = new Container();
$this->container->delegate(new ReflectionContainer(cacheResolutions: true));
$this->container->add(ContainerInterface::class, fn () => $this->container)->setShared(true);
Service providers
Список провайдерів — у bootstrap/providers/app.php.
Кожен клас реалізує League\Container\ServiceProvider\ServiceProviderInterface
і реєструє сервіси в методі register().
return [
ConfigServiceProvider::class,
LocaleServiceProvider::class,
TelemetryServiceProvider::class,
ErrorHandlerServiceProvider::class,
HttpServiceProvider::class,
SessionServiceProvider::class,
// ...
ComponentsServiceProvider::class, // останнім — маршрути й views компонентів
];
Порядок має значення: наприклад, ConfigServiceProvider перед
HttpServiceProvider (роутер читає config/routes),
TwigServiceProvider перед компонентами, які додають Twig paths.
Детально — Провайдери сервісів.
Компоненти можуть додавати провайдери через ComponentsServiceProvider.
register() і boot()
register()— оголошення bindings ($container->add(...))-
boot()— eager-логіка одразу післяregister()цього провайдера (див. Провайдери → register і boot)
Bootable-провайдери в core: HttpServiceProvider, DatabaseServiceProvider,
ErrorHandlerServiceProvider, ComponentsServiceProvider,
DebugLoggerServiceProvider. Порядок у bootstrap/providers/*.php визначає,
які сервіси вже доступні в boot() кожного з них.
Метод provides()
Якщо провайдер оголошує provides(string $id): bool, League Container
може відкладати його завантаження, поки не знадобиться один із перелічених сервісів
(deferred providers).
Реєстрація сервісів (bindings)
Closure-фабрика
Найпоширеніший спосіб у Concept Core:
$container->add(ConfigInterface::class, function () use ($container) {
$pathManager = $container->get(PathManager::class);
// ...
return new Config($nhConfig);
})->setShared(true);
setShared(true) — один екземпляр на запит (singleton у межах контейнера).
Без setShared кожен get() створює новий об’єкт.
Готовий об’єкт
$container->add(PathManager::class, new PathManager($rootPath, $paths));
Інтерфейс → реалізація
У коді залежте від контракту; у провайдері прив’яжіть реалізацію:
$container->add(LoggerInterface::class, function () use ($container) {
return new Logger(/* ... */);
})->setShared(true);
// У класі застосунку
public function __construct(private readonly LoggerInterface $logger) {}
Автовайринг
Якщо для класу немає явного add(), спрацьовує
ReflectionContainer: аналіз конструктора, рекурсивний get() для type-hint.
- Контролери, middleware, FormRequest, сервіси застосунку — зазвичай без ручної реєстрації
- Моделі Eloquent — теж резолвяться, якщо конструктор дозволяє
Обмеження method injection у контролерах — лише FormRequest, PSR-7, параметри URL. Усе інше — у конструктор.
League Container → Dependency Injection
Хто викликає get()
| Місце | Що резолвиться |
|---|---|
League Route + RouteStrategy |
Контролер [Class, 'method'], middleware, FormRequest |
| Service provider | Залежності у closure function () use ($container) |
App::run() |
Router, ServerRequestInterface |
CLI bin/console.php |
ConsoleApplication, команди з контейнера |
RouteStrategy передає контейнер у стратегію маршруту
($strategy->setContainer($container) у HttpServiceProvider).
Документація: Routing,
Middleware.
Оновлення запиту в контейнері
Після збігу маршруту RouteStrategy додає параметри URL у запит і
перезаписує binding (третій аргумент true — replace):
$container->add(ServerRequestInterface::class, $request, true);
Тому UrlGenerator, ViewResponseFactory і ResponseFactory::back()
бачать актуальний запит із атрибутами {id} та SAFE_BACK_URL.
Основні сервіси ядра
Типові binding з провайдерів Concept Core (за контрактом або класом):
| Сервіс | Провайдер |
|---|---|
ConfigInterface | ConfigServiceProvider |
TelemetryCollector | TelemetryServiceProvider |
ServerRequestInterface, Router | HttpServiceProvider |
UrlGeneratorInterface, ResponseFactoryInterface | HttpServiceProvider |
ViewResponseFactoryInterface | HttpServiceProvider |
SessionInterface, FlashBagInterface | SessionServiceProvider |
ViewInterface | TwigServiceProvider |
ValidatorInterface | ValidationServiceProvider |
DatabaseInterface, CapsuleManager | DatabaseServiceProvider |
CasterInterface | CastingServiceProvider |
LoggerInterface | LogServiceProvider |
ComponentRegistry | ComponentsServiceProvider |
Twig environment збирається всередині ViewInterface:
extensions з config/view.php, paths з компонентів — див.
Представлення.
Власний сервіс
- Створіть клас (краще інтерфейс + реалізація).
-
Зареєструйте в
ServiceProviderзастосунку або компонента (providers()у класі компонента). - Інжектуйте тип у конструктор контролера / сервісу.
// App/Providers/BillingServiceProvider.php
public function register(): void
{
$this->getContainer()
->add(BillingGatewayInterface::class, function () {
return new StripeBillingGateway(/* ... */);
})
->setShared(true);
}
Додайте провайдер у bootstrap/providers/app.php або в масив providers компонента.
Антипатерни
Service locator
Не передавайте ContainerInterface у контролери й доменні сервіси, щоб викликати
$container->get() «за потреби». Це приховує залежності.
Повний розбір — Контролери → service locator.
Коли getContainer() доречний
- Усередині service provider (
register()/boot()) - Інфраструктура:
ResponseFactory(URL generator),SeederManager, error handler - Bootstrap, CLI entrypoint, тести з підміною binding
has() і get()
if ($container->has(LoggerInterface::class)) {
$logger = $container->get(LoggerInterface::class);
}
PSR-11: get() кидає NotFoundExceptionInterface, якщо сервіс не зареєстрований.
У провайдерах часто спочатку перевіряють has(ConfigInterface::class), щоб уникнути циклічних залежностей на ранній стадії bootstrap.
Телеметрія bootstrap
Провайдери ядра записують FRAMEWORK_SERVICE_AWAKENING при створенні ключових
сервісів (якщо telemetry.enabled). Читання — через
TelemetryCollector (Телеметрія).
Контейнер — «склад» залежностей; бізнес-логіка лишається в сервісах і контролерах.
Нову інтеграцію (платіжний шлюз, кеш) додавайте binding + інтерфейс, а не виклики
get() з десятка місць.