skip to content

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%

answer

  1. implements DeferrableProvider
  2. provides(): the abstracts it binds
  3. manifest in bootstrap/cache/services.php
  4. make() loads the provider on demand
  5. manifest rebuilt only when provider list changes

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.

solid answer

~50 s

When the provider list is compiled, each provider implementing `Illuminate\Contracts\Support\DeferrableProvider` is not registered; instead every abstract returned by its `provides()` is mapped to it in the manifest, `bootstrap/cache/services.php`. The application's `make()` checks that map first: resolving a listed service registers the provider, and boots it at once if the app has already booted. If `provides()` omits a binding, nothing loads the provider for it, so resolving that abstract throws `Target [...] is not instantiable` for an interface, or silently auto-wires an unconfigured object for a concrete class. The manifest is rebuilt only when the list of providers changes, so after editing `provides()` on an existing provider you run `php artisan clear-compiled` or `optimize:clear`. Deferral suits providers that only bind; listeners, routes or macros in their `boot()` would not exist until a provided service is resolved.

code

bash · 2 lines
bash
php artisan clear-compiled   # deletes bootstrap/cache/services.php and packages.php
php artisan optimize:clear   # also runs clear-compiled among the other cache clears

go deeper

for a junior

Recall that a deferred provider implements DeferrableProvider, lists its bindings in provides(), and loads only when one of them is resolved.

for a middle

Explain the services.php manifest, how make() triggers registration, and why bound() already reports deferred services as bound.

for a senior

Diagnose missing provides() entries and stale manifests, and refuse deferral for providers that register listeners, routes or macros.

for a principal

Judge whether deferral's startup savings justify the upkeep of provides() lists across modules, and where profiling should decide.

## What deferral saves Every eager provider is instantiated, registered and booted on every request, even when the request never uses its services. A **deferred provider** skips all of that until one of its services is actually needed. Laravel's own `MailServiceProvider` is deferred: an app that sends no mail in a given request never registers the mail services. To defer a provider, implement `Illuminate\Contracts\Support\DeferrableProvider` and return the abstracts it binds from `provides()`: ```php class InvoicePdfServiceProvider extends ServiceProvider implements DeferrableProvider { public function register(): void { $this->app->singleton(PdfRenderer::class, fn ($app) => new PdfRenderer($app['config']['pdf'])); } public function provides(): array { return [PdfRenderer::class]; } } ``` ## How the manifest works 1. On startup, `ProviderRepository` loads `bootstrap/cache/services.php` if it exists. 2. If the file is missing, or its recorded provider list differs from the current list, it **recompiles**: it instantiates every provider and asks `isDeferred()`. 3. Eager providers go into an `eager` list. For each deferred provider, every entry of `provides()` becomes a key in a `deferred` map pointing at the provider, and its `when()` events are recorded. 4. Eager providers are registered; the `deferred` map is handed to the application. The recompile check compares **only the list of provider class names**. Changing what an existing provider returns from `provides()` does not trigger a rebuild. ## Loading on demand - `Application::make()` first calls a check that, if the abstract is in the deferred map and not already cached, **registers** the owning provider. - If the application has already booted, `register()` boots the provider immediately; if not, its boot is scheduled with the other booting work. - `Application::bound()` returns `true` for a deferred service even before its provider loads. - A provider can also name events in `when()`; firing one registers the provider. - `loadDeferredProviders()` registers every remaining deferred provider at once. ## Traps | Mistake | Symptom | Fix | |---|---|---| | `provides()` omits an interface the provider binds | `Target [...] is not instantiable` when it is resolved | List every abstract the provider binds | | `provides()` omits a concrete class it configures | An unconfigured object is auto-wired, no error | Same; the missing entry is silent | | `provides()` edited on an existing provider | Old map still used from the manifest | `php artisan clear-compiled` or `optimize:clear` | | Listeners, routes or macros in `boot()` | Missing until some provided service is resolved | Keep those in an eager provider | | Provider resolves its own services in another provider's `register()` | Deferral defeated, provider loads on every request | Resolve lazily in closures or `boot()` | ## Checking what is deferred The manifest is plain PHP that returns an array, so it can be opened and read directly. It has four keys: - `providers`: the full provider list the manifest was compiled from, used for the recompile check; - `eager`: providers registered on every request; - `deferred`: a map from each provided abstract to its provider class; - `when`: for each deferred provider, the events that load it. If a service you expect to be deferred appears nowhere in `deferred`, either the provider does not implement `DeferrableProvider` or the manifest is stale. Deleting the file with `clear-compiled` is safe; it is rebuilt on the next request or command. ## When not to defer - The provider registers **behaviour**: event listeners, routes, gates, view composers, Blade directives, macros. Those must exist on every request. - The provider's services are used on nearly every request anyway, so deferral saves nothing. - The provider binds many abstracts and keeping `provides()` in sync is error-prone; one missing entry is a production bug. Deferral is an optimisation for providers whose only job is to bind services that many requests never touch. The documentation states the rule directly: defer a provider only if it is **only** registering bindings in the container.

  • In Laravel, what does Application::bound() return for a service owned by a deferred provider that has not loaded yet?
    `true`. The application's `bound()` treats any key in the deferred-services map as bound, so code that checks `bound()` before resolving works the same whether or not the provider has loaded. Resolving it then registers the provider.
  • In Laravel, what does a deferred provider's when() method do?
    It returns a list of event names. When the manifest is loaded, Laravel registers listeners for those events that register the provider, so a deferred provider can be loaded by an event as well as by resolving one of its services. The default implementation returns an empty array.

saying these in an interview costs you the question

  • A deferred provider's boot() runs at startup like any other provider's.
  • Laravel works out a deferred provider's services by scanning its register() method.
  • Editing provides() takes effect immediately because the manifest is rebuilt on every change.
  • Any provider can be deferred safely, including ones that register event listeners.
  • Resolving a service missing from provides() still loads the provider automatically.