← Laravel: requests, data and application decisions
02 / 10 · 65 MIN

Container, providers and instance lifetimes

Read how the container constructs dependencies, where providers declare services and which objects are reused across resolutions. Choose lifetimes respecting isolation between requests and jobs.

Explicit construction, automatic resolution

When Laravel resolves a controller, it can construct concrete dependencies whose constructors it can also satisfy. A concrete class with no dependencies is the simple case; it needs no manual binding merely to exist. This does not remove the need to reason about the construction graph.

A primitive parameter with no default, an interface with no chosen implementation or a cyclic dependency needs attention. Identify the failing parameter before replacing the whole mechanism with manual instantiation.

Interfaces need a choice

InvoiceSender describes the consumer’s expected contract. The existence of HttpInvoiceSender and FakeInvoiceSender does not establish an automatic preference by name or file order. The example uses bind to declare the implementation.

Laravel 13 also supports binding attributes; exercises assuming no binding explicitly exclude them. Once selected, the implementation’s own dependencies still need resolution. The mapping does not establish availability of the remote HTTP service.

Declare and then boot

In a provider, register declares container bindings. Do not use that phase to prematurely consume services another provider may not yet have registered. boot is called after provider registration and is the appropriate place for initialization such as registering a view composer depending on those services.

Ordering establishes availability of declarations; it does not prove external dependency health. A constructor or boot that contacts a remote system introduces a startup dependency that should be a conscious choice.

Existing on disk is not registration

A manually created provider should appear in bootstrap/providers.php in the skeleton used here. The make:provider command handles registration in the documented flow, but copying a file does not guarantee the same effect. If no binding from a new provider appears, check loading first.

A provider that only declares bindings can be deferred: it implements DeferrableProvider and identifies supplied services in provides. This list is a resolution contract, not a list of routes or users.

Object identity and lifetime

Singleton means subsequent resolutions reuse the same object in that container unless explicitly replaced or forgotten. It does not mean a clone per call or an object automatically shared across PHP processes. In long-lived workers, lifetime can exceed one request or job.

If the object contains mutable tenant data, reuse has different consequences from a stateless service. Define the isolation contract first and only then choose the binding lifetime.

Per-lifecycle reuse and retained references

Scoped reuses an instance within a Laravel-managed request or job lifecycle and flushes it when the next lifecycle starts, as in Octane or a queue worker. The scenario assumes each job resolves context again. It does not turn the object into an authorization boundary or erase references application code has retained in another singleton.

If an old reference remains retained, changing the binding alone does not demonstrate isolation. Review must trace both resolution and retention.

Different consumers, different values

A contextual binding selects an implementation according to the class being constructed. when identifies the consumer, needs identifies the dependency and give supplies the implementation. For ReportClient’s string $endpoint parameter, the example uses giveConfig to obtain an explicit configuration key.

An .env variable is not inferred from any arbitrary parameter name. Compare this contract with two successive global bindings: those express neither alternation nor consumer-specific selection.

<?php
// Excerpt from a registered application provider.
public function register(): void
{
    $this->app->bind(InvoiceSender::class, HttpInvoiceSender::class);
    $this->app->scoped(TenantContext::class, fn () => new TenantContext());
    $this->app->when(ReportClient::class)
        ->needs('$endpoint')
        ->giveConfig('services.reports.endpoint');
}

// Separate consumer excerpt; application classes/imports are omitted.
public function __construct(InvoiceSender $sender, TenantContext $context)
{
    $this->sender = $sender;
    $this->context = $context;
}
IN PRACTICE

The first excerpt belongs to an already registered provider; the second to a container-resolved consumer. Contracts and classes are illustrative application names, not downloadable programs. Predict the implementation used, the primitive value needed and when context should be replaced.

The example does not include tenant authorization or claim to provide it through scoped.

Common pitfalls

Expecting the container to choose among interfaces without binding; resolving another provider’s services too early; forgetting manual registration; treating singleton as global cross-process memory; using scoped while retaining the object in a long-lived singleton.

Related topics: Request lifecycle and configuration · Queues, retries and scheduling · Tests, cache and release diagnosis

Take this idea with you

Resolution needs types, implementations and values. Providers declare bindings before initialization depending on those services. Instance lifetime is part of the contract: reuse within a request should not imply retaining mutable context into the next request.

Create account

Reference: Container automatic resolution · Laravel 13.x; PHP 8.3+