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.

Create a free account to save this page, take notes and track your progress. Sign in

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.

Binding an interface, singletons, and swapping in 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?

  1. In routes/web.php
  2. In a service provider's register() method
  3. In .env
  4. In a migration
Show answer

Answer: In a service provider's register() method

Was this page helpful?