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);bindcreates a new instance each resolution;singletonresolves 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);| Method | Runs | Use for |
|---|---|---|
register() | Before any provider boots | Bindings only - never resolve another service here |
boot() | After all providers registered | Events, routes, model config, publishes |
defer | Lazily on first resolution | Heavy providers that are rarely used |
terminate() | After the response is sent | Flushing 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.Related
Events, listeners and notifications Testing with Pest and PHPUnit
Last refreshed 2026-09-18.