The service container, providers and facades

Binding and singletons, contextual binding, facade resolution, writing your own provider, and when to inject explicitly instead.

Binding and resolving

// AppServiceProvider::register()
$this->app->bind(PaymentGateway::class, StripeGateway::class);
$this->app->singleton(MetricsClient::class);

// bind a concrete instance built from config
$this->app->singleton(StorageClient::class, fn ($app) => new StorageClient(
    $app['config']->get('services.storage.url'),
    $app['config']->get('services.storage.key'),
));

// contextual binding: one interface, two implementations
$this->app->when(ReportController::class)
    ->needs(Exporter::class)
    ->give(CsvExporter::class);
$this->app->when(AdminController::class)
    ->needs(Exporter::class)
    ->give(PdfExporter::class);

// resolve explicitly
$gateway = app(PaymentGateway::class);
  • bind creates a new instance each resolution; singleton resolves once per request and is reset between requests in Octane unless you add it to the flush list.
  • Zero-configuration resolution works through reflection: if the class has no scalar constructor parameters, Laravel can build it without a binding.
  • Interface to implementation bindings are what make a class testable - without one, the container cannot know which implementation you mean.
💡
Never call app() deep inside business logic. It hides dependencies, breaks static analysis and makes a class impossible to construct in a unit test. Inject through the constructor and let the container resolve at the edge.

Providers and facades

class PaymentServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->mergeConfigFrom(__DIR__.'/../config/payment.php', 'payment');
        $this->app->singleton(PaymentGateway::class, StripeGateway::class);
    }

    public function boot(): void
    {
        $this->publishes([
            __DIR__.'/../config/payment.php' => config_path('payment.php'),
        ], 'payment-config');

        Model::preventLazyLoading(! $this->app->isProduction());
    }
}

// a facade over a custom service
class Payment extends Facade
{
    protected static function getFacadeAccessor(): string
    {
        return PaymentGateway::class;
    }
}

Payment::charge($order, 1999);
MethodRunsUse for
register()Before any provider bootsBindings only - never resolve another service here
boot()After all providers registeredEvents, routes, model config, publishes
deferLazily on first resolutionHeavy providers that are rarely used
terminate()After the response is sentFlushing buffers, closing connections

Deferred providers keep boot cheap, which matters when a request touches ten routes but only one feature. Declare protected $defer = true; and return the provided bindings from provides().

FAQ

When should I create a new provider?
When a coherent slice of your application owns bindings or boot-time wiring - a payment integration, a search client, an admin module. Do not add one per class; that just spreads the same code across more files.
Is resolving from the container in a job safe?
Yes, each job is resolved through the container. What is not safe is holding a mutable singleton across jobs in a long-running worker - reset state at the start of handle() rather than assuming a fresh process.

Events, listeners and notifications Testing with Pest and PHPUnit

Last refreshed 2026-09-18.