Service Providers
A service provider is a class that registers services in the DI container during bootstrap. In Concept Core, this is the primary way to wire config, HTTP, database, Twig, validation, and component modules.
Implementation — League Container Service Providers
(League\Container\ServiceProvider\AbstractServiceProvider).
See also: Service container,
configuration.
Provider anatomy
<?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 logic immediately after this provider's register()
}
}
register() and boot()
register()—$container->add(), bindings, factories-
boot()— additional logic immediately afterregister()of this same provider (eager), not after all providers in the file
boot() only makes sense in a class implementing
BootableServiceProviderInterface. From League Container documentation:
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.
Order in bootstrap/providers/app.php is critical: for each provider,
register() runs first, then immediately boot(), and only then the next
provider in the list. In provider N’s boot(), only services from providers
above in the file are available; providers below have not yet run
register().
That is why ComponentsServiceProvider is last: in its boot() it already has
ConfigInterface, Router, view registries, and so on — and it can
addServiceProvider() for component providers, load routes.php,
and Twig paths.
provides()
List of ids the provider supplies. Enables deferred loading: the provider is loaded when one of the listed services is first needed.
Telemetry TelemetryTrait
Core providers use TelemetryTrait: when creating key services,
telemetry()?->mark(FRAMEWORK_SERVICE_AWAKENING, ...) is called if
telemetry.enabled is on. More details — Telemetry.
Registering providers
App::registerServiceProviders() loads an array of classes from a PHP file:
foreach ($providers as $providerClassName) {
$provider = new $providerClassName();
$this->container->addServiceProvider($provider);
}
Providers are not registered in autowiring — they are instantiated explicitly (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,
];
| Provider | What it does |
|---|---|
ConfigServiceProvider | ConfigInterface, .env, config/{APP_ENV} override |
LocaleServiceProvider | LocaleResolverInterface, application locale |
TelemetryServiceProvider | TelemetryCollector — bootstrap/HTTP telemetry |
ErrorHandlerServiceProvider | Whoops after config (HTML/JSON/CLI) |
HttpServiceProvider | PSR-7, Router, URL generator, responses, RequestProxy |
SessionServiceProvider | Session, flash bag (FlashBagInterface) |
DataMaskerServiceProvider | Masking sensitive data in logs |
LogServiceProvider | LoggerInterface, QueryLogger, TelemetryLogHandler |
ViewRegistryServiceProvider | Registries for Twig/Plates paths/extensions/contexts |
TwigServiceProvider | ViewInterface (Twig environment) |
PlatesServiceProvider | ViewInterface (Plates engine) |
ValidationServiceProvider | Rakit validator, custom rules from config |
DatabaseServiceProvider | Eloquent Capsule, migrations/seeders registries |
CastingServiceProvider | Valinor CasterInterface |
DebugLoggerServiceProvider | Early debug log before full logger |
ComponentsServiceProvider | Components: routes, views, CLI, nested providers |
Order matters: config → locale/telemetry → HTTP/view/DB → components last.
CLI — bootstrap/providers/console.php
bin/console.php calls App::create() and a separate provider list
(without View/Validation/Casting as needed, but with ConsoleServiceProvider):
// bin/console.php
$app->registerServiceProviders([$rootPath . '/bootstrap/providers/console.php']);
$console = $container->get(ConsoleApplication::class);
$console->run();
ConsoleServiceProvider reads config/commands.php and adds commands to
Symfony Console. Component commands — via ComponentsServiceProvider::boot().
Application provider
- Create a class in
App/Providers/MyServiceProvider.php. - In
register()— bindings for application interfaces. - Add the class to
bootstrap/providers/app.phpafter providers you depend on.
// bootstrap/providers/app.php
return [
ConfigServiceProvider::class,
// ...
BillingServiceProvider::class, // after ConfigServiceProvider
ComponentsServiceProvider::class,
];
If the provider needs ConfigInterface — place it after
ConfigServiceProvider. If it needs Router — after
HttpServiceProvider.
Component providers
A component declares providers in ComponentInterface::providers().
ComponentsServiceProvider::boot() loads them dynamically:
// DebugBarComponent::providers()
public function providers(): array
{
return [
DebugBarServiceProvider::class,
];
}
// Concept\Core\Providers\Component\ComponentsServiceProvider
foreach ($registry->providers() as $providerClass) {
$this->getContainer()->addServiceProvider(new $providerClass());
}
Active component list — config/components.php
(in dev, config/dev/components.php may include DebugBar).
The component class (AuthAdminComponent) implements ComponentInterface directly.
What ComponentsServiceProvider::boot() does
- Telemetry
FRAMEWORK_COMPONENT_REGISTEREDfor each module - Registration of component console commands
- Migration and seeder paths in registries
- Nested service providers from components
requireofComponent/routes.phpfiles ($routervariable)- In HTTP (not CLI) — Twig paths, extensions, view contexts from components
Component routes appear only if the component is enabled in config and
ComponentsServiceProvider has run boot().
Examples from core
HttpServiceProvider — register + boot
register(): PSR-7, Router + RouteStrategy + loading routes/*.php.
boot(): page resolvers for Illuminate Paginator.
League Route: routing documentation.
DatabaseServiceProvider — why boot
In register() — DatabaseInterface, Migrator, registries.
In boot() (immediately after this provider’s register()) — creation of
CapsuleManager, connection from ConfigInterface (Config is already registered
higher in the list), SQL listeners. Capsule is added to the container already configured.
TwigServiceProvider / PlatesServiceProvider
Registers the view engine. Twig assembles Environment (cache, debug, extensions from config).
Plates provides a native PHP templating alternative.
After component boot, registries gain additional namespaces available to the engine.
Twig: twig.symfony.com, views — Views.
DebugBarServiceProvider (component)
Registers CustomDebugBar and JavascriptRenderer with telemetry collectors.
Loaded only if DebugBarComponent is in dev component config.
Recommendations
- One provider — one area (Billing, Notifications), not a “GodProvider”
- register — bindings only; heavy setup — in
boot() - Dependencies via
$container->get()only inside providers, not in controllers setShared(true)for services that should be singleton per request- Do not change core provider order without understanding dependencies
New module checklist
- Component in
config/components.php - If needed —
providers()in the component class routes(),viewPaths(),migrationPaths()— in component class methods- Service classes resolved via autowiring or binding in the module provider
If a service is “not found” in the container — check whether the provider is in the right file
(app.php vs console.php), whether the component boot() ran,
and whether an explicit add() is needed instead of autowiring.