Laravel Tutorial · Beyond the Basics · Lesson 27 of 27
The Service Container and Providers
Understand how Laravel builds objects: automatic dependency injection, binding interfaces to implementations, singletons, and swapping implementations in tests.
Automatic injection
Laravel's service container creates objects and their dependencies. When a controller's constructor asks for PaymentGateway $gateway, the container builds it, and its dependencies, recursively. Concrete classes need no setup at all.
Binding interfaces
When code depends on an interface, tell the container which implementation to use, usually in a service provider's register() method (app/Providers/AppServiceProvider.php):
$this->app->bind(PaymentGateway::class, StripeGateway::class); // a new instance each time
$this->app->singleton(ExchangeRates::class, fn () => new ExchangeRates(config('services.rates.key')));
In tests, swap the implementation ($this->app->instance(...) or $this->swap(...)) to use a fake.
<?php
interface PaymentGateway
{
public function charge(int $cents): string;
}
class CardGateway implements PaymentGateway
{
public function charge(int $cents): string { return 'card charged '.$cents; }
}
class FakeGateway implements PaymentGateway
{
public array $charges = [];
public function charge(int $cents): string { $this->charges[] = $cents; return 'fake charge '.$cents; }
}
class Clock
{
public string $id;
public function __construct() { $this->id = bin2hex(random_bytes(3)); }
}
class CheckoutService
{
public function __construct(private PaymentGateway $gateway, private Clock $clock) {} // both injected
public function pay(int $cents): string { return $this->gateway->charge($cents); }
}
app()->bind(PaymentGateway::class, CardGateway::class);
app()->singleton(Clock::class);
echo app(CheckoutService::class)->pay(1500), "\n";
echo 'singleton: same Clock each time? ', app(Clock::class) === app(Clock::class) ? 'yes' : 'no', "\n";
echo 'bind: new CheckoutService each time? ', app(CheckoutService::class) !== app(CheckoutService::class) ? 'yes' : 'no', "\n";
$fake = new FakeGateway;
app()->instance(PaymentGateway::class, $fake); // what a test would do
echo app(CheckoutService::class)->pay(999), ' | recorded: ', json_encode($fake->charges), "\n";
Output
card charged 1500 singleton: same Clock each time? yes bind: new CheckoutService each time? yes fake charge 999 | recorded: [999]
Run this on your computer — see “Get Started” for setup.
Test yourself
Where do you usually bind interfaces to implementations?
In routes/web.phpIn a service provider's register() methodIn .envIn a migration
Show answer
Answer: In a service provider's register() method