The Service Container
The beating heart of Laravel: how bind, singleton and contextual binding resolve your classes.
Everything in Laravel — controllers, jobs, listeners, middleware — is built by the service container. It is a factory that knows how to construct your classes and automatically inject their dependencies. Understanding it is the difference between using Laravel and understanding Laravel.
Autowiring
Type-hint a class in a constructor and the container builds it for you, recursively resolving its dependencies too. No configuration needed for concrete classes.
Binding
bind()— a fresh instance every time it is resolved.singleton()— built once, then the same instance is shared.instance()— register an object you already have.scoped()— one instance per request/job lifecycle.
// bind an interface to a concrete implementation
$this->app->bind(PaymentGateway::class, StripeGateway::class);
// resolve it
$gateway = app(PaymentGateway::class);Contextual binding
The advanced move: give different classes different implementations of the same interface — a PhotoController gets S3 storage while a InvoiceController gets local disk, both type-hinting Filesystem.
Example
<?php
namespace App\Providers;
use App\Contracts\PaymentGateway;
use App\Services\{StripeGateway, FakeGateway};
use App\Http\Controllers\{CheckoutController, TestController};
use Illuminate\Contracts\Filesystem\Filesystem;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\ServiceProvider;
class PaymentServiceProvider extends ServiceProvider
{
public function register(): void
{
// Shared, stateless client: build once.
$this->app->singleton(PaymentGateway::class, function ($app) {
return new StripeGateway(
secret: config('services.stripe.secret'),
logger: $app->make('log'),
);
});
// Contextual binding: same type-hint, different concretes.
$this->app->when(CheckoutController::class)
->needs(Filesystem::class)
->give(fn () => Storage::disk('s3'));
$this->app->when(TestController::class)
->needs(PaymentGateway::class)
->give(FakeGateway::class);
// Tag several implementations, then resolve them as a group.
$this->app->tag([StripeGateway::class], 'gateways');
}
}
// --- Autowiring in action: nothing to configure for concretes ---
class CheckoutController
{
// Both resolved by the container automatically.
public function __construct(
private PaymentGateway $gateway,
private Filesystem $disk,
) {}
}When to use it
- A developer binds a PaymentGateway interface to a StripeGateway implementation in AppServiceProvider so the concrete driver can be swapped for a different provider without changing controller code.
- A test suite uses $this->app->instance() to replace a real SMS sender with a fake implementation, verifying calls without sending actual messages.
- A package author uses the service container's make() method to resolve dependencies with constructor injection automatically, even for classes not registered explicitly.
More examples
Bind an interface to a concrete class
bind() creates a new instance every time the interface is resolved; singleton() creates it once and reuses that instance throughout the request.
// app/Providers/AppServiceProvider.php
public function register(): void
{
$this->app->bind(
\App\Contracts\PaymentGateway::class,
\App\Services\StripeGateway::class
);
// Singleton: same instance for the lifetime of the request
$this->app->singleton(
\App\Contracts\CacheStore::class,
\App\Services\RedisCacheStore::class
);
}Resolve from the container
Laravel resolves constructor dependencies automatically, injecting the StripeGateway wherever PaymentGateway is type-hinted.
// Via make()
$gateway = app(\App\Contracts\PaymentGateway::class);
// Via type-hint in a controller constructor (auto-resolved)
class CheckoutController extends Controller
{
public function __construct(
private \App\Contracts\PaymentGateway $gateway
) {}
public function charge(Request $request): JsonResponse
{
$result = $this->gateway->charge($request->amount);
return response()->json($result);
}
}Contextual binding for multiple implementations
Contextual binding lets two controllers receive different concrete implementations of the same interface, all resolved automatically by the container.
// Different controllers get different implementations
$this->app->when(CheckoutController::class)
->needs(\App\Contracts\PaymentGateway::class)
->give(\App\Services\StripeGateway::class);
$this->app->when(DonationController::class)
->needs(\App\Contracts\PaymentGateway::class)
->give(\App\Services\PayPalGateway::class);
Discussion