Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

The container and providers

You can write a Request $request parameter in a controller, and Laravel passes the right object by itself. Behind that is the service container: it creates objects and puts them where they are needed. Understanding the container explains how the whole framework is built.

Dependency injection

A class does not create its dependencies itself but receives them in the constructor:

namespace App\Services;
 
class PriceCalculator
{
    public function withTax(int $price): float
    {
        return $price * 1.2;
    }
}
use App\Services\PriceCalculator;
 
class OrderController extends Controller
{
    public function __construct(private PriceCalculator $calculator) {}
 
    public function show(Order $order)
    {
        return $this->calculator->withTax($order->total);
    }
}

Nowhere does it say new PriceCalculator(): the container saw the parameter type and created the object. If PriceCalculator has dependencies of its own, it creates those too.

Why it matters

A class depends not on a specific implementation but on whatever it was given. In a test a fake payment service can stand in for the real one — the class's code does not change.

An interface and its implementation

The container cannot create an interface. You tell it which class to supply:

namespace App\Contracts;
 
interface PaymentGateway
{
    public function charge(int $amount): bool;
}
namespace App\Providers;
 
use App\Contracts\PaymentGateway;
use App\Services\StripeGateway;
use Illuminate\Support\ServiceProvider;
 
class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->bind(PaymentGateway::class, StripeGateway::class);
    }
}

Now any class asking for PaymentGateway receives StripeGateway. To switch payment systems you change one line.

One object for the whole app

bind() creates a new object on every request for it. singleton() creates it once and hands out the same one afterwards:

$this->app->singleton(PaymentGateway::class, function ($app) {
    return new StripeGateway(config('services.stripe.secret'));
});

The function is needed when the constructor requires values from the settings.

Providers

A provider is a class that configures a part of the application. It has two methods:

MethodWhat goes into it
register()Container bindings only
boot()Everything else: access rules, listeners, settings

boot() is called when every provider is already registered, so any service can be used in it.

php artisan make:provider PaymentServiceProvider

Getting an object by hand

Where there are no constructor parameters, take the object with the app() function:

$gateway = app(PaymentGateway::class);

Facades

Cache::get('key') looks like a static call, but it is a facade: it takes the cache object from the container and calls its method. That is why facades are easy to replace in tests too:

use Illuminate\Support\Facades\Cache;
 
Cache::shouldReceive('get')->with('key')->andReturn('value');

Common mistake

The register() method must not use other services: the provider that registers them may not have run yet. Routes, listeners, Gate rules — everything except bindings — belong in boot().

Thanks for reading!

Donate to the site — we work hard for you.