In Laravel, when do you need to register a service container binding, and what can auto-wiring build without one?
answer
- reflection reads constructor type hints
- concrete classes need no registration
- interfaces and abstract classes are not instantiable
- Target [...] is not instantiable while building
- scalar with no default: unresolvable dependency
basics
~20 sLaravel'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 sLaravel 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
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
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.
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.
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.
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.