Skip to content

Laravel request lifecycle, container, providers, and facades

Status: Complete. Last reviewed 2026-08-27.

A Laravel application is an object graph assembled around an Application instance, which is also the service container. For an HTTP request, the framework bootstraps that application, passes an Illuminate\Http\Request through the HTTP kernel and middleware pipeline to the router, converts the route result into a response, and sends that response outward. The container supplies objects while this happens; service providers declare and bootstrap much of that object graph; facades are static-looking proxies to objects in it.

These concepts are related but not interchangeable:

  • The lifecycle describes ordering and boundaries from process entry to termination.
  • The container resolves dependencies and controls the reuse of resolved instances.
  • A service provider contributes bindings and application bootstrapping at defined phases.
  • A facade is one access style for a container-backed service.

“Laravel resolves it” is not a mechanism. A useful explanation names which container binding or concrete constructor is involved, the lifetime of the resolved object, and the lifecycle in which its mutable state can survive.

In a current Laravel 13 application, the web server directs a request to public/index.php. That front controller records a start time, loads Composer’s autoloader, requires bootstrap/app.php, captures the incoming request, and calls the application’s handleRequest() method. bootstrap/app.php constructs and configures the application; it is not the point at which every provider is necessarily booted or every route action executed.

handleRequest() resolves the HTTP kernel and asks it to handle the request. The kernel bootstraps the application if needed. Its bootstrap sequence covers environment loading, configuration, exception handling, facade registration, provider registration, and provider booting. The invariant worth remembering is not every bootstrapper class name, but that the application becomes configured before request dispatch and that provider registration precedes provider booting.

The kernel then sends the request through the global middleware stack. Its final destination dispatches through the router, which applies the matched route’s middleware and invokes the route action or controller. Middleware is a nested pipeline: code before $next($request) executes inward in declared order; code after it executes outward in reverse order. A middleware may return early, so neither downstream middleware nor the route is guaranteed to run.

The route result is normalized to a response. That response unwinds through route middleware and global middleware, the kernel returns it to Application::handleRequest(), and the application sends it. Termination callbacks and terminable middleware run after sending. “After response” does not mean “guaranteed background processing”: it still consumes server resources, can fail, and may delay completion depending on the server/runtime. Durable or retryable work belongs on a queue.

The container maps an abstract identifier—usually a class or interface name—to construction behavior. When asked for an unbound concrete class, it can inspect the constructor with reflection, recursively resolve concrete class dependencies, and instantiate it. This is zero-configuration resolution. It is not automatic implementation selection: an interface has no constructible target, a scalar has no unambiguous source, and a dependency with an unresolvable value still needs a binding or explicit argument.

The common lifetimes are:

  • bind() supplies an instance according to its resolver each time the abstract is resolved.
  • singleton() resolves once and reuses that instance for the life of that application container.
  • scoped() resolves once within a request or job lifecycle and is flushed when a long-running runtime starts the next lifecycle.
  • instance() places an already-created object into the container.

Contextual bindings answer “which implementation for this consumer?” They are appropriate when two consumers legitimately need different implementations of the same contract. Current Laravel also supports contextual attributes for values such as configuration, storage disks, and tagged services. Both approaches make construction policy explicit; neither repairs a contract that mixes unrelated responsibilities.

Dependency injection and the container are also distinct. Constructor injection exposes what an object needs and works without Laravel once objects are constructed. Calling app(SomeService::class) inside business code is service location: it hides the dependency at the call site and couples the code to ambient container state. Framework glue, factories, and providers sometimes need direct container access, but it should be a deliberate boundary choice.

Providers are the framework’s composition modules. Application providers are listed in bootstrap/providers.php in the streamlined structure used by Laravel 11–13; packages may contribute providers through package discovery. Older applications commonly list providers in config/app.php, so file location is version context rather than the concept itself.

register() defines how services can be resolved. Bind interfaces, singletons, scoped services, and related construction policy there. Do not depend on another provider having completed application bootstrapping: Laravel invokes register() across providers before it invokes their boot() methods.

boot() performs work that needs the registered object graph: registering view composers, macros, routes, listeners, or other application integrations. Dependencies may be injected into boot() because registration is complete. This ordering prevents a provider from observing a half-declared container, but it does not make arbitrary boot side effects safe. A database call or remote request during boot increases startup latency and can stop web requests, CLI commands, and workers from starting.

Deferred providers can postpone provider loading until one of their declared services is requested. They are chiefly useful for providers that only register container bindings. Deferral changes when a provider is loaded, not the lifetime of the service it supplies, and a provider that also needs unconditional boot behavior is a poor candidate.

Cache::get($key) is not a conventional static call on the cache store. Illuminate\Support\Facades\Cache extends the base Facade and identifies a container accessor. The base facade resolves and caches the facade root, then __callStatic() forwards the method and arguments to that object. The underlying object remains replaceable, which is why facade fakes and expectations are possible.

Facades therefore offer terse integration code, not dependency transparency. A class using six facades has at least six ambient collaborators even though its constructor looks empty. Constructor injection makes those dependencies visible, helps express application-owned contracts, and allows ordinary test doubles. A facade can be reasonable in thin Laravel-specific glue or for stable framework services. The decision is about visibility, coupling, and design pressure—not the claim that facades are untestable static methods.

Real-time facades generate the same proxy style for an application class via a Facades\ namespace prefix. They do not change the underlying trade-off; they make a dependency less visible than a constructor parameter.

The most serious lifecycle bugs appear when code assumes a shorter lifetime than the runtime provides. Under traditional PHP-FPM, a worker process may persist, but a typical Laravel application is bootstrapped for each request. Queue workers and Octane deliberately keep a booted application in memory. Octane runs provider register() and boot() once when the worker starts, then reuses the application for later requests.

Consequences include:

  • A singleton captures the first request, authenticated user, tenant, locale, or correlation ID and exposes it to later work.
  • A mutable singleton accumulates per-request data, causing cross-request leakage or unbounded memory growth.
  • A provider reads dynamic request state during boot, so the value is stale or no request exists yet.
  • A worker keeps old code or configuration after deployment because the long-running process was not restarted.
  • A facade’s cached root or a test double survives longer than expected in custom runtime/test setup.

The first diagnostic question is “what is this object’s actual lifetime in this runtime?” Then inspect binding type, construction time, captured constructor arguments, static properties, facade swaps, and worker restart behavior. Reproduce with two sequential requests or jobs using different tenant/user markers; a test that creates a fresh application for each case cannot expose the leak.

Use scoped() for mutable state intended to be shared only within one request or job. Prefer passing request-specific values into operations rather than injecting the entire request into a singleton. A resolver closure can obtain the current request or container when an integration genuinely needs late lookup, but explicit method arguments are easier to reason about in application code.

  1. An Octane worker boots and creates a singleton PricingService.
  2. Its constructor reads the current tenant or captures a Request supplied during the first resolution.
  3. Request A resolves the singleton while tenant A is active and receives correct prices.
  4. Laravel begins a new request, but singletons are not flushed; only scoped instances are.
  5. Request B for tenant B reuses the service holding tenant A.
  6. Logs may show request B’s correlation ID alongside tenant A’s pricing decision, while isolated feature tests remain green.

The repair is not merely “clear the value.” Make tenant context an explicit operation input or bind a tenant-aware collaborator as scoped, add a sequential-request regression test in the long-running runtime, and restart workers during rollout.

Current (Laravel 13): the streamlined application configures middleware, exceptions, and routing from bootstrap/app.php; application providers are in bootstrap/providers.php. Container attributes and scoped bindings are available, and Octane’s reuse boundary must be considered when selecting lifetimes.

Common (Laravel 11–12): the same streamlined structure and core lifecycle mental model apply. Production applications may retain a structure carried forward from older versions.

Legacy: older applications commonly have application HTTP/console kernel classes and provider lists in config/app.php. The files differ, but requests still cross a kernel, providers still register then boot, and facade calls still proxy container services. Diagnose the installed application’s structure instead of reciting a fresh-project file tree.