The request lifecycle

Laravel · Expert · 15 min

Knowing the lifecycle tells you where to hook in, why config caching matters and how to debug framework behaviour.

Every HTTP request enters through public/index.php. It loads Composer's autoloader, then bootstrap/app.php, which builds the Application (the service container) with Application::configure()->withRouting()->withMiddleware()->withExceptions()->create(). Finally $app->handleRequest(Request::capture()) hands the request to the HTTP kernel.

The kernel first runs its bootstrappers:

  • Load environment variables from .env, unless config is cached.
  • Load configuration (from bootstrap/cache/config.php if you ran config:cache).
  • Set up error and exception handling, register facades.
  • Register all service providers (their register() methods), then boot them (their boot() methods). Deferred providers register only when one of their services is first requested.

The request then passes through the global middleware pipeline (implemented with Illuminate\Pipeline\Pipeline, where each layer is wrapped around the next as a closure). The router matches the route, runs the route's middleware group (for example web: cookies, session, CSRF, bindings), resolves the controller through the container and calls it. The return value is converted into a Symfony Response, which travels back out through the middleware and is sent. Afterwards terminate() runs terminable middleware and defer()red callbacks.

Under PHP-FPM all of this happens for every request, which is why caching config, routes and events, plus OPcache, matter so much. Under Octane the bootstrapping happens once per worker.

Example

<?php
// public/index.php (simplified)
define('LARAVEL_START', microtime(true));
require __DIR__.'/../vendor/autoload.php';
$app = require_once __DIR__.'/../bootstrap/app.php';
$app->handleRequest(Illuminate\Http\Request::capture());

// bootstrap/app.php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\{Exceptions, Middleware};
return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware) {
        $middleware->append(\App\Http\Middleware\AddRequestId::class);
    })
    ->withExceptions(function (Exceptions $exceptions) {
        $exceptions->report(fn (PaymentFailed $e) => Log::channel('payments')->error($e));
    })
    ->create();
// Flow: index.php -> bootstrappers -> providers register() -> boot()
//       -> global middleware -> router -> route middleware -> controller -> back out

Remember

  • public/index.php -> bootstrap/app.php -> kernel bootstrappers -> providers -> middleware -> router
  • All providers register before any boots
  • Under PHP-FPM bootstrapping repeats per request; caches and OPcache cut that cost

Watch out for

  • Doing heavy work (queries, HTTP calls) in a provider's boot method, which then runs on every request.

Check yourself

  1. At what point do service providers' boot() methods run?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: laravel.com/docs/12.x/lifecycle