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:
| Method | What 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 PaymentServiceProviderGetting 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().