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 after register() 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,
];
ProviderWhat it does
ConfigServiceProviderConfigInterface, .env, config/{APP_ENV} override
LocaleServiceProviderLocaleResolverInterface, application locale
TelemetryServiceProviderTelemetryCollector — bootstrap/HTTP telemetry
ErrorHandlerServiceProviderWhoops after config (HTML/JSON/CLI)
HttpServiceProviderPSR-7, Router, URL generator, responses, RequestProxy
SessionServiceProviderSession, flash bag (FlashBagInterface)
DataMaskerServiceProviderMasking sensitive data in logs
LogServiceProviderLoggerInterface, QueryLogger, TelemetryLogHandler
ViewRegistryServiceProviderRegistries for Twig/Plates paths/extensions/contexts
TwigServiceProviderViewInterface (Twig environment)
PlatesServiceProviderViewInterface (Plates engine)
ValidationServiceProviderRakit validator, custom rules from config
DatabaseServiceProviderEloquent Capsule, migrations/seeders registries
CastingServiceProviderValinor CasterInterface
DebugLoggerServiceProviderEarly debug log before full logger
ComponentsServiceProviderComponents: 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

  1. Create a class in App/Providers/MyServiceProvider.php.
  2. In register() — bindings for application interfaces.
  3. Add the class to bootstrap/providers/app.php after 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

  1. Telemetry FRAMEWORK_COMPONENT_REGISTERED for each module
  2. Registration of component console commands
  3. Migration and seeder paths in registries
  4. Nested service providers from components
  5. require of Component/routes.php files ($router variable)
  6. 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

  1. Component in config/components.php
  2. If needed — providers() in the component class
  3. routes(), viewPaths(), migrationPaths() — in component class methods
  4. 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.