Провайдери сервісів
Service provider — клас, який реєструє сервіси в DI-контейнері під час bootstrap. У Concept Core це основний спосіб підключити конфіг, HTTP, БД, Twig, валідацію та модулі-компоненти.
Реалізація — League Container Service Providers
(League\Container\ServiceProvider\AbstractServiceProvider).
Див. також: Контейнер сервісів,
конфігурація.
Анатомія провайдера
<?php declare(strict_types=1);
namespace Concept\App\Providers;
use League\Container\ServiceProvider\AbstractServiceProvider;
use League\Container\ServiceProvider\BootableServiceProviderInterface;
final class BillingServiceProvider extends AbstractServiceProvider implements BootableServiceProviderInterface
{
public function provides(string $id): bool
{
return $id === BillingGatewayInterface::class;
}
public function register(): void
{
$container = $this->getContainer();
$container->add(BillingGatewayInterface::class, function () {
return new StripeBillingGateway(/* ... */);
})->setShared(true);
}
public function boot(): void
{
// Eager-логіка одразу після register() цього провайдера
}
}
register() і boot()
register()—$container->add(), bindings, фабрики-
boot()— додаткова логіка одразу післяregister()цього ж провайдера (eager), а не після всіх провайдерів у файлі
boot() має сенс лише в класі з
BootableServiceProviderInterface. З документації League Container:
In much the same way, this method has access to the container itself and can interact with it however you wish, the difference is that the boot method is invoked as soon as you register the service provider with the container meaning that everything in this method is eagerly loaded.
If you wish to apply inflectors or register further service providers from this one, it must be from a bootable service provider like this one, otherwise they will be ignored.
Порядок у bootstrap/providers/app.php критичний: для кожного провайдера
спочатку register(), потім одразу boot(), і лише потім наступний
провайдер у списку. У boot() провайдера N доступні сервіси лише від провайдерів
вище у файлі; провайдери нижче ще не виконували
register().
Тому ComponentsServiceProvider стоїть останнім: у його boot() вже є
ConfigInterface, Router, View registries тощо — і він може
addServiceProvider() для провайдерів компонентів, підключити routes.php
і Twig paths.
provides()
Список id, які надає провайдер. Дозволяє відкладене завантаження (deferred): провайдер підтягується, коли вперше потрібен один із перелічених сервісів.
Телеметрія TelemetryTrait
Провайдери core використовують TelemetryTrait: при створенні ключових сервісів
викликається telemetry()?->mark(FRAMEWORK_SERVICE_AWAKENING, ...), якщо
telemetry.enabled увімкнено. Детальніше — Телеметрія.
Підключення провайдерів
App::registerServiceProviders() підключає масив класів із PHP-файлу:
foreach ($providers as $providerClassName) {
$provider = new $providerClassName();
$this->container->addServiceProvider($provider);
}
Провайдери не реєструються в autowiring — їх інстансують явно (new $providerClassName()).
HTTP — bootstrap/providers/app.php
return [
ConfigServiceProvider::class,
LocaleServiceProvider::class,
TelemetryServiceProvider::class,
ErrorHandlerServiceProvider::class,
HttpServiceProvider::class,
SessionServiceProvider::class,
DataMaskerServiceProvider::class,
LogServiceProvider::class,
ViewRegistryServiceProvider::class,
TwigServiceProvider::class,
ValidationServiceProvider::class,
DatabaseServiceProvider::class,
CastingServiceProvider::class,
DebugLoggerServiceProvider::class,
ComponentsServiceProvider::class,
];
| Провайдер | Що робить |
|---|---|
ConfigServiceProvider | ConfigInterface, .env, override config/{APP_ENV} |
LocaleServiceProvider | LocaleResolverInterface, локаль застосунку |
TelemetryServiceProvider | TelemetryCollector — телеметрія bootstrap/HTTP |
ErrorHandlerServiceProvider | Whoops після config (HTML/JSON/CLI) |
HttpServiceProvider | PSR-7, Router, URL generator, responses, RequestProxy |
SessionServiceProvider | Сесія, flash bag (FlashBagInterface) |
DataMaskerServiceProvider | Маскування чутливих даних у логах |
LogServiceProvider | LoggerInterface, QueryLogger, TelemetryLogHandler |
ViewRegistryServiceProvider | Реєстри шляхів/розширень/контекстів для Twig/Plates |
TwigServiceProvider | ViewInterface (оточення Twig) |
PlatesServiceProvider | ViewInterface (двигун Plates) |
ValidationServiceProvider | Rakit validator, custom rules з config |
DatabaseServiceProvider | Eloquent Capsule, migrations/seeders registries |
CastingServiceProvider | Valinor CasterInterface |
DebugLoggerServiceProvider | Ранній debug-лог до повного logger |
ComponentsServiceProvider | Компоненти: routes, views, CLI, nested providers |
Порядок важливий: config → locale/telemetry → HTTP/view/DB → components останнім.
CLI — bootstrap/providers/console.php
bin/console.php викликає App::create() і окремий список провайдерів
(без View/Validation/Casting за потреби, але з ConsoleServiceProvider):
// bin/console.php
$app->registerServiceProviders([$rootPath . '/bootstrap/providers/console.php']);
$console = $container->get(ConsoleApplication::class);
$console->run();
ConsoleServiceProvider читає config/commands.php і додає команди в
Symfony Console. Команди компонентів — через ComponentsServiceProvider::boot().
Власний провайдер застосунку
- Створіть клас у
App/Providers/MyServiceProvider.php. - У
register()— bindings для інтерфейсів застосунку. - Додайте клас у
bootstrap/providers/app.phpпісля провайдерів, від яких залежите.
// bootstrap/providers/app.php
return [
ConfigServiceProvider::class,
// ...
BillingServiceProvider::class, // після ConfigServiceProvider
ComponentsServiceProvider::class,
];
Якщо провайдеру потрібен ConfigInterface — ставте його після
ConfigServiceProvider. Якщо потрібен Router — після
HttpServiceProvider.
Провайдери компонентів
Компонент оголошує провайдери в методі providers().
ComponentsServiceProvider::boot() підключає їх динамічно:
// AclComponent::providers()
public function providers(): array
{
return [AclServiceProvider::class];
}
// DebugBarComponent::providers()
public function providers(): array
{
return [DebugBarServiceProvider::class];
}
// Concept\Core\Providers\Component\ComponentsServiceProvider
foreach ($registry->providers() as $providerClass) {
$this->getContainer()->addServiceProvider(new $providerClass());
}
Список активних компонентів — config/components.php
(у dev може бути config/dev/components.php з DebugBar).
Клас компонента (AuthAdminComponent, AclComponent тощо)
реалізує ComponentInterface і повертає метадані з власних методів.
Що робить ComponentsServiceProvider::boot()
- Телеметрія
FRAMEWORK_COMPONENT_REGISTEREDдля кожного модуля - Реєстрація console-команд компонентів
- Шляхи міграцій і seeders у registries
- Nested service providers компонентів
requireфайлівComponent/routes.php(змінна$router)- У HTTP (не CLI) — Twig paths, extensions, view contexts з компонентів
Тому маршрути компонента з’являться лише якщо компонент увімкнено в config і
ComponentsServiceProvider виконав boot().
Приклади з core
HttpServiceProvider — register + boot
register(): PSR-7, Router + RouteStrategy + завантаження routes/*.php.
boot(): резолвери сторінок для Illuminate Paginator.
League Route: документація маршрутизації.
DatabaseServiceProvider — чому boot
У register() — DatabaseInterface, Migrator, registries.
У boot() (одразу після register() цього провайдера) — створення
CapsuleManager, підключення з ConfigInterface (Config уже зареєстрований
вище в списку), listeners SQL. Capsule додається в контейнер уже налаштованим.
TwigServiceProvider / PlatesServiceProvider
Реєструє двигун представлень. Twig збирає Environment (кеш, debug, розширення з конфігу, шляхи з реєстрів).
Plates надає альтернативу на чистих PHP-шаблонах.
Після завантаження компонентів реєстри отримують додаткові простори імен, доступні двигуну.
Twig: twig.symfony.com, views — Представлення.
DebugBarServiceProvider (компонент)
Реєструє CustomDebugBar і JavascriptRenderer з колекторами телеметрії.
Підключається лише якщо DebugBarComponent у dev-config компонентів.
Рекомендації
- Один провайдер — одна область (Billing, Notifications), не «GodProvider»
- register — лише bindings; важкі підключення — у
boot() - Залежності через
$container->get()лише всередині провайдера, не в контролерах setShared(true)для сервісів, що мають бути singleton на запит- Не змінюйте порядок core-провайдерів без розуміння залежностей
Чеклист нового модуля
- Компонент у
config/components.php - За потреби —
providers()у класі компонента routes.php,viewPaths(),migrationPaths()— у методах компонента- Класи сервісів резолвляться через autowiring або binding у провайдері модуля
Якщо сервіс «не знаходиться» в контейнері — перевірте, чи провайдер у правильному файлі
(app.php vs console.php), чи виконався boot() компонента,
і чи не потрібен явний add() замість autowiring.