skip to content

Service Container

Laravel's container binds interfaces to implementations, auto-wires constructors by reflection, and is wired by service providers and fronted by facades. Nearly every Laravel feature leans on it.

on this pageshow

explore

questions

20

In Laravel, when do you need to register a service container binding, and what can auto-wiring build without one?

level: juniorimportance: must knowfreq 78%

answer

  1. reflection reads constructor type hints
  2. concrete classes need no registration
  3. interfaces and abstract classes are not instantiable
  4. Target [...] is not instantiable while building
  5. scalar with no default: unresolvable dependency

basics

~20 s

Laravel's container auto-wires any concrete class by reflecting on its constructor type hints. An interface or abstract class needs a binding, or a binding attribute, to an implementation, and a required scalar with no default needs its value supplied.

solid answer

~40 s

Laravel resolves controllers, middleware, listeners, jobs and anything passed to `make()` through its container. For a concrete class it uses reflection: it reads the constructor's parameters, resolves every class-typed one the same way, and calls `new` with the results, so no registration is needed. It cannot guess an implementation for an interface or abstract class, so resolving one without a binding throws `BindingResolutionException` with the message `Target [...] is not instantiable while building [...]`. A required scalar such as `string $apiKey`, with no default and no nullable type, also fails as an unresolvable dependency. The fix for the interface is a binding in a service provider, `$this->app->bind(ShippingRateCalculator::class, ZoneRateCalculator::class)`; the scalar needs a default or a value supplied another way.

code

php · 15 lines
php
<?php

namespace App\Providers;

use App\Contracts\ShippingRateCalculator;
use App\Services\ZoneRateCalculator;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->bind(ShippingRateCalculator::class, ZoneRateCalculator::class);
    }
}

go deeper

for a junior

Recall that concrete classes resolve with no setup, that an interface needs a bind() call in a provider, and that 'is not instantiable' is the error a missing binding produces.

for a middle

Explain the reflection walk: constructor parameters, recursive resolution of class types, defaults and nullable scalars, and why a required scalar with no default cannot be resolved.

for a senior

Show judgment on what deserves a binding, interfaces at module seams rather than every class, and read a 'while building' chain to locate the missing binding in a deep graph.

for a principal

Weigh auto-wiring convenience against explicit wiring: fewer registrations versus constructor edits silently reshaping the object graph, and where a team should insist on interface bindings.

## What auto-wiring means in Laravel Laravel's **service container** (the `Illuminate\Foundation\Application` object, which extends `Illuminate\Container\Container`) builds objects on demand. Controllers, middleware, event listeners, a job's `handle()` method and anything you pass to `app()` or `make()` go through it. **Auto-wiring** is the container's ability to build a class it has never been told about: it inspects the class with PHP reflection and works out the constructor arguments by itself. Because of auto-wiring, most application code never touches the container directly. You type-hint a class in a constructor and it arrives. ## How the container builds a concrete class When you resolve a class name that has no binding, the container runs roughly these steps: 1. Reflect on the class. If it does not exist, throw `BindingResolutionException` with `Target class [...] does not exist.` 2. Check that it is **instantiable**. An interface, an abstract class or a class with a private constructor is not, and the container throws `Target [...] is not instantiable`, adding `while building [...]` with the chain of classes it was constructing. 3. Read the constructor's parameters. A class with no constructor is simply created with `new`. 4. For each parameter, resolve a value: - a **class or interface type** is resolved recursively through the same process (or its binding, if one exists); - a **scalar or untyped** parameter uses its default value, or `null` if its type is nullable; - a required scalar with neither throws `Unresolvable dependency resolving [...]`. 5. Call the constructor with the resolved values. One subtle rule: a class-typed parameter that **has a default value** and whose class is **not bound** receives the default rather than an auto-wired instance. `?TaxTable $taxes = null` stays `null` until something binds `TaxTable`. ## When a binding becomes necessary | Type hint in the constructor | Without any binding | What you add | |---|---|---| | Concrete class, resolvable dependencies | Built by reflection | Nothing | | Interface or abstract class | `Target [...] is not instantiable` | `bind()` or `singleton()` to an implementation | | Scalar with a default | The default is used | Nothing, unless you want another value | | Non-nullable scalar, no default | `Unresolvable dependency` | A default, a parameter array, or a contextual binding | | Concrete class you want shared | A new object per resolution | `singleton()` or `scoped()` | The two situations the Laravel documentation names are the ones that matter in practice: you type-hint an **interface** your own class implements, or you are writing a **package** whose services other apps resolve. ## Binding an interface to an implementation In a logistics app, a `QuoteController` depends on a `ShippingRateCalculator` interface so the pricing strategy can change without touching the controller: ```php // app/Providers/AppServiceProvider.php, register() $this->app->bind(ShippingRateCalculator::class, ZoneRateCalculator::class); ``` Now every constructor that asks for `ShippingRateCalculator` receives a `ZoneRateCalculator`, itself auto-wired. Swapping the implementation later is a one-line change in the provider. Only the interface needed registering; the concrete calculator and its own dependencies are still built by reflection. ## Common misreadings - **"Every class must be registered."** No. Registering concrete classes that need no special construction adds noise and changes nothing. - **"The container finds implementations for you."** It never scans the codebase. An interface resolves only through an explicit binding or a binding attribute on the interface. - **"Errors appear at boot."** Resolution is lazy. A missing binding surfaces the first time something resolves the class that needs it, which may be one rarely used route. - **"Auto-wiring reads configuration."** It does not look at `.env` or `config/`. Values come from defaults, parameter arrays or bindings. - Reading the **`while building [...]`** list from left to right shows which class asked for the unresolvable type, which is usually the fastest way to find the missing binding in a deep object graph. ## Where the container resolves for you You rarely call the container yourself, because the framework resolves these through it: - **controllers**, including their constructor and the type-hinted parameters of each action; - **middleware** classes and **event listeners**; - a queued **job's `handle()` method**, whose parameters are injected when the worker runs it; - **Artisan commands**, whose `handle()` method can also type-hint services; - anything passed to `app()`, `resolve()` or `App::make()`. Every one of these follows the same rules, so the same missing interface binding fails in a controller, a listener or a job alike. Once you know the rules for one entry point you know them for all of them, and the fix is always the same: a binding for the interface in a service provider, or a concrete type hint where no abstraction is needed.

  • In Laravel, what does a constructor parameter typed ?TaxTable $taxes = null receive when nothing binds TaxTable?
    It receives `null`. When a class-typed parameter has a default and its class is not bound, the container uses the default instead of auto-wiring the class. As soon as a provider binds `TaxTable`, the same parameter is resolved from the container.
  • In Laravel, does binding a concrete class to itself, such as $this->app->bind(ZoneRateCalculator::class), change anything?
    For construction, no: the container would build the class by reflection anyway. The call does make the type count as bound, which matters for code that checks `bound()` and for class-typed parameters with defaults. It becomes useful when you pass a closure to control construction or use `singleton()` to share the object.

saying these in an interview costs you the question

  • Every class must be registered in a service provider before it can be injected.
  • The container finds an interface's implementation by scanning for classes that implement it.
  • An unbound interface in a required constructor parameter is injected as null.
  • Auto-wiring reads .env or config files to fill scalar constructor arguments.
  • Missing bindings are reported when the application boots, before any resolution.
open as a page

In Laravel, what is the difference between using the Cache facade, the cache() helper and an injected Illuminate\Contracts\Cache\Repository?

level: juniorimportance: must knowfreq 66%

basics

~20 s

All three reach the same container-managed cache service, so behaviour matches. The facade and cache() helper call it from inside method bodies; an injected Repository contract declares the dependency in the constructor, which is explicit and framework-portable.

open as a page

In a Laravel service provider, what belongs in register() and what belongs in boot(), and why does the order matter?

level: juniorimportance: must knowfreq 75%

basics

~20 s

register() should only bind services into the container; boot() configures the app with them, such as macros, view composers, gates and event listeners. Laravel runs every provider's register() before any boot(), so boot() can rely on every registered service.

open as a page

In Laravel's service container, how do bind(), singleton(), scoped() and instance() differ in what repeated resolutions return?

level: middleimportance: must knowfreq 72%

basics

~20 s

bind() builds a new object on every resolution; singleton() builds once, lazily, and returns that object afterwards; scoped() is a singleton forgotten when a queue job or Octane request starts; instance() stores an object you already built.

open as a page

In Laravel, how does a call like Cache::get('key') reach a real object when the Cache facade class defines no static get method?

level: middleimportance: must knowfreq 72%

basics

~10 s

Cache extends Illuminate\Support\Facades\Facade, whose __callStatic() catches the missing static call, resolves the container binding named by getFacadeAccessor() ('cache', a CacheManager) and calls get() on that object.

open as a page

In a Laravel accounting app, how would you give LedgerExporter and PayrollExporter different storage disks for the same Filesystem type hint using when()->needs()->give()?

level: seniorimportance: must knowfreq 50%

basics

~10 s

Register $this->app->when(LedgerExporter::class)->needs(Filesystem::class)->give(fn () => Storage::disk('ledger-archive')) and a second rule for PayrollExporter. The container applies each rule only while building that class, so each exporter gets its own disk.

open as a page

In a Laravel controller or service, how do contextual attributes such as #[CurrentUser], #[Config] and #[DB] fill a parameter?

level: juniorimportance: should knowfreq 30%

basics

~20 s

A contextual attribute on a parameter tells the container how to produce that one value: #[Config('app.timezone')] reads a config key, #[CurrentUser] injects the authenticated user and #[DB('reporting')] a named connection. They work in constructors, controller actions and container-called methods.

open as a page

In Laravel, what does the container's call() method do, and how does it fill the parameters of the method it invokes?

level: middleimportance: should knowfreq 38%

basics

~20 s

App::call() invokes any callable and injects its parameters: values you pass by name win, class-typed parameters are resolved from the container, and the rest take their defaults. Laravel uses it to run a job's handle(), an Artisan command's handle() and scheduled closures.

open as a page

In Laravel, how do app(), resolve() and App::make() relate, and why does passing parameters to make() skip a singleton's cached instance?

level: middleimportance: should knowfreq 42%

basics

~10 s

app(X), resolve(X), App::make(X) and $this->app->make(X) all run the application container's make(). Passing a parameter array forces a one-off build: the container ignores the cached singleton and does not cache the new object.

open as a page

In Laravel, how do the #[Bind], #[Singleton] and #[Scoped] attributes declare a binding on the interface itself, and when does a provider binding override them?

level: middleimportance: should knowfreq 28%

basics

~20 s

#[Bind(RedisEventPusher::class)] on an interface tells the container which class to build when the interface is resolved, with no provider code; #[Singleton] or #[Scoped] beside it sets the lifetime. An explicit provider binding for the interface is checked first and wins.

open as a page

In Laravel 13, how does bootstrap/providers.php decide which service providers load, and what does php artisan make:provider change in it?

level: middleimportance: should knowfreq 40%

basics

~20 s

bootstrap/providers.php returns the array of the app's own provider classes; the skeleton lists only AppServiceProvider. php artisan make:provider creates the class and adds it to that file. Framework defaults and auto-discovered package providers load before these.

open as a page

In a Laravel newsletter app, why does resolving ClickTracker inside NewsletterServiceProvider::register() to build a Mailable macro fail, and how is it fixed?

level: middleimportance: should knowfreq 45%

basics

~20 s

Providers register one at a time, so when NewsletterServiceProvider::register() runs, the provider that binds ClickTracker may not have run yet and resolving it throws. Define the macro in boot() and resolve ClickTracker inside the macro closure.

open as a page

In a Laravel CRM code review, a service calls the Cache, Http and Log facades inside its methods — do facades hide dependencies, and what would you change?

level: seniorimportance: should knowfreq 55%

basics

~10 s

Yes: facade calls keep collaborators out of the constructor, so the class looks dependency-free and its facade calls fail outside a booted app. Inject Illuminate\Contracts\Cache\Repository, Illuminate\Http\Client\Factory and Psr\Log\LoggerInterface to make them explicit.

open as a page

A custom Laravel facade proxies a service registered with bind(), yet every facade call gets the same object — why, and when does that reuse reset?

level: seniorimportance: should knowfreq 30%

basics

~20 s

The Facade base class stores the first resolved object in a static $resolvedInstance map while static::$cached is true, so bind()'s fresh-instance rule applies only once. Bootstrap, the queue worker's per-job reset and clearResolvedInstance() empty that map.

open as a page

In Laravel, how does a DeferrableProvider with provides() postpone loading, and what breaks when provides() leaves out a binding the provider registers?

level: seniorimportance: should knowfreq 30%

basics

~20 s

A provider implementing DeferrableProvider is skipped at startup; Laravel records its provides() list in bootstrap/cache/services.php and registers it only when one of those services is resolved. A binding missing from provides() never triggers loading, so resolving it fails or auto-wires.

open as a page

In Laravel's container, what do beforeResolving(), resolving() and afterResolving() callbacks do, and why does a resolving() callback fire only once for a singleton?

level: middleimportance: nice to knowfreq 18%

basics

~20 s

resolving() and afterResolving() run callbacks on each newly built object, matched by type; beforeResolving() runs before any build. A cached singleton is returned before the resolving callbacks fire, so they run only at its first build.

open as a page

In Laravel, how does a real-time facade imported as use Facades\App\Crm\LeadScorer; work, and what does it cost you?

level: middleimportance: nice to knowfreq 24%

basics

~20 s

Prefixing an import with Facades\ makes Laravel's AliasLoader generate a facade class whose getFacadeAccessor() returns the rest of the name, so static calls resolve that class from the container. It saves injection but hides the dependency.

open as a page

In Laravel, when do the application's booting() and booted() callbacks and a provider's own booted() callback run, and what are they used for?

level: middleimportance: nice to knowfreq 15%

basics

~10 s

$app->booting() callbacks run before any provider's boot(); a provider's own booting() and booted() callbacks wrap that provider's boot(); $app->booted() callbacks run after every provider has booted, or immediately if added after boot.

open as a page

In a Laravel logistics app, how would you collect every carrier's rate calculator with tag() and tagged(), and decorate each one with extend()?

level: seniorimportance: nice to knowfreq 22%

basics

~10 s

Bind each calculator, group them with $this->app->tag([...], 'rate-calculators'), and build the quote service from $app->tagged('rate-calculators'), a lazy iterable. Wrap a calculator with $this->app->extend(Calculator::class, fn ($c) => new CachedRates($c)), which decorates it whenever it resolves.

open as a page

In Laravel 13, what does the #[BindWhen] container attribute do, why does it need PHP 8.5, and when is its condition evaluated?

level: seniorimportance: nice to knowfreq 12%

basics

~20 s

#[BindWhen(BetaEventPusher::class, static fn () => ...)] on an interface binds that class only when the closure returns true. Closures in attribute arguments need PHP 8.5. The condition runs at resolution, and once it matches the result is registered as an ordinary binding.

open as a page