PSR-11 — Container interface
Concept
PSR-11 defines the Container interface: a ContainerInterface with two methods — get(string $id): mixed and has(string $id): bool. It standardizes dependency injection containers so that code can retrieve dependencies from any PSR-11-compatible container without knowing which concrete container is being used.
IoC containers: An Inversion of Control (IoC) container is responsible for constructing objects and resolving their dependencies. Instead of new UserService(new UserRepository(new PDO(...))), you tell the container how to build UserService, and it constructs the full dependency graph. This enables swapping implementations, lazy-loading, and lifecycle management (singleton vs transient).
Laravel's service container: The most powerful part of Laravel. Binds interfaces to implementations, auto-wires constructors (reflection-based), manages singletons, scoped bindings, and contextual bindings. Access via app() helper or inject Illuminate\Contracts\Container\Container.
Auto-wiring: Laravel (and most modern containers) use PHP reflection to inspect constructor type hints and automatically inject the right instance. No explicit binding needed for concrete classes — app(UserService::class) works automatically if UserService's constructor only depends on other concrete classes.
Binding types:
bind(abstract, factory): New instance every time.singleton(abstract, factory): Same instance for the entire request lifecycle.instance(abstract, $object): Bind an already-created instance.scoped(abstract, factory): New instance per request (Octane-aware).
Code Example
<?php
// PSR-11 interface
use Psr\Container\ContainerInterface;
function makeService(ContainerInterface $c): MyService
{
return $c->get(MyService::class); // works with ANY PSR-11 container
}
// Laravel service container — binding
use Illuminate\Support\ServiceProvider;
use App\Contracts\PaymentGateway;
use App\Services\StripeGateway;
use App\Services\MockGateway;
class PaymentServiceProvider extends ServiceProvider
{
public function register(): void
{
// Bind interface to implementation
$this->app->bind(PaymentGateway::class, function ($app) {
return new StripeGateway(
config('services.stripe.key'),
config('services.stripe.secret'),
);
});
// Singleton — same instance for the whole request
$this->app->singleton(\App\Services\CacheWarmer::class);
// Contextual binding — when AnalyticsController needs PaymentGateway,
// give it the mock (useful for testing/preview environments)
$this->app->when(\App\Http\Controllers\AnalyticsController::class)
->needs(PaymentGateway::class)
->give(fn() => new MockGateway());
}
}
// Auto-wired injection — Laravel resolves this automatically
class OrderController
{
public function __construct(
private readonly OrderService $orderService,
private readonly PaymentGateway $payment, // resolved via binding above
) {}
}
// Manual resolution
$service = app(OrderService::class);
$service = app()->make(OrderService::class);
// Checking if a binding exists
if (app()->bound(PaymentGateway::class)) {
// ...
}
// Tagged bindings — group related implementations
$this->app->tag([StripeGateway::class, PaypalGateway::class], 'gateways');
$gateways = $this->app->tagged('gateways'); // iterable of all tagged instances