Laravel Request Lifecycle and the HTTP Kernel
The request lifecycle is the "engine blueprint" of Laravel—understand it, and you'll go from "knowing how to use the framework" to "understanding the framework."
1. What You'll Learn
- Request lifecycle: public/index.php → Kernel → Pipeline → Response
- Dual-kernel architecture with HTTP Kernel and Console Kernel
- Service Container Binding and Resolution: bind/singleton/make
- Facades: Principles and Static Proxy Mechanisms
- Provider Startup Order and Deferred Providers
2. A True Story of a Senior Developer
(1) Pain Point: Black-box debugging wastes hours
Alice encountered a strange bug in ShopMetrics: everything worked fine locally, but after deployment, a certain Facade returned null. She didn't know which class the Facade was calling internally, nor was she sure where the service container had bound it. It took her four hours of digging through the code to discover that the register() method of a ServiceProvider contained a conditional check, and the binding hadn't been registered in the production branch. Bob had also learned the hard way—he didn't understand the execution order of middleware, which caused the authentication middleware to run after the CORS middleware, resulting in a 401 error during the pre-validation of requests.
(2) Understanding the Life Cycle Solution
Once you understand the request lifecycle, you can pinpoint exactly at which stage any issue occurs—whether it's a routing mismatch, a failed middleware check, a missing container binding, or a Provider that failed to load.
// Knowing the lifecycle helps you debug like this:
// 1. Check if the binding exists
app()->bound('payment.gateway');
// 2. Check which provider registered it
app()->getBindings()['payment.gateway']['concrete'];
// 3. Trace middleware execution order
app()->make(\Illuminate\Foundation\Http\Kernel::class)->getMiddlewarePriority();
(3) Revenue
Using container debugging techniques, Alice pinpointed the cause of the Facade returning null in just 5 minutes, and Bob resolved the CORS issue immediately after adjusting the middleware priority. Developers who understand the lifecycle can boost their debugging efficiency tenfold.
3. Request Lifecycle
(1) Complete Process
flowchart TD
A["public/index.php<br/>(Entry Point)"] --> B["HTTP Kernel<br/>(bootstrap/app.php)"]
B --> C["Service Providers<br/>(Register & Boot)"]
C --> D["Middleware Pipeline<br/>(Global + Route)"]
D --> E{"Route Matched?"}
E -->|Yes| F["Controller/Action<br/>(Business Logic)"]
E -->|No| G["Fallback/404"]
F --> H["Response Object"]
G --> H
H --> I["Send to Client"]
I --> J["Terminate<br/>(Post-response hooks)"]
(2) Detailed Explanation of Each Stage
| Phase | File/Class | Responsibilities |
|---|---|---|
| Entry Point | public/index.php |
Load Composer autoload, create application instance |
| Kernel | bootstrap/app.php |
Configure middleware, exception handling, and routing |
| Provider Registration | config/app.providers |
Register Container Binding |
| Provider Startup | Provider boot() |
Execute startup logic |
| Middleware | Global → Route | Filter/Modify Requests and Responses |
| Route Distribution | Router | Match URL → Execute Controller |
| Termination | Terminable Middleware | Perform cleanup operations after a response |
(1) ▶ Example: Viewing the Stages of the Request Lifecycle
# Check registered service providers
php artisan about --only=providers
# Listed: AppServiceProvider, AuthServiceProvider, etc.
# Check middleware pipeline
php artisan route:list --columns=method,uri,middleware
# GET /dashboard → auth, tenant.resolve, verified
# Check bound services in container
php artisan tinker
# app()->getBindings()
# Lists all registered container bindings
Output:
# Command executed successfully
4. HTTP Kernel and Console Kernel
Laravel has two kernels: the HTTP kernel handles web requests, and the Console kernel handles Artisan commands.
(1) Laravel 11 Kernel Configuration
// bootstrap/app.php — Laravel 11 single-file configuration
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
api: __DIR__.'/../routes/api.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
$middleware->appendToGuestList([
'/api/*',
]);
$middleware->alias([
'tenant.resolve' => \App\Http\Middleware\TenantResolve::class,
]);
})
->withExceptions(function (Exceptions $exceptions) {
//
})->create();
| Dimension | HTTP Kernel | Console Kernel |
|---|---|---|
| Entry | public/index.php |
artisan File |
| Object Being Processed | HTTP Request | Console Input |
| Middleware | Global + Route | None |
| Output | HTTP Response | Console Output |
| Environment | Web Requests | CLI Commands |
(1) ▶ Example: Custom Global Middleware
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
// Add global middleware (runs on every request)
$middleware->append([
\App\Http\Middleware\SetLocale::class,
]);
// Remove a default global middleware
$middleware->remove([
\Illuminate\Foundation\Http\Middleware\TrimStrings::class,
]);
// Register alias for route middleware
$middleware->alias([
'tenant' => \App\Http\Middleware\TenantResolve::class,
'role' => \App\Http\Middleware\CheckRole::class,
]);
})
Output:
// Execution Successful
5. Service Containers
The service container is at the heart of Laravel—it manages dependency injection and the lifecycle of classes.
(1) Binding
// app/Providers/AppServiceProvider.php
public function register(): void
{
// Bind interface to implementation
$this->app->bind(
PaymentGatewayInterface::class,
StripeGateway::class,
);
// Bind with closure (full control)
$this->app->bind('analytics.service', function ($app) {
return new AnalyticsService(
$app->make(CacheManager::class),
$app['config']->get('analytics.ttl'),
);
});
// Singleton — same instance every time
$this->app->singleton(ShopMetricsConfig::class, function ($app) {
return new ShopMetricsConfig(
$app['config']->get('shopmetrics'),
);
});
}
| Binding Method | Per Call | Purpose |
|---|---|---|
bind() |
Create a New Instance | Stateless Service |
singleton() |
Reuse Examples | Stateful/Costly Objects |
scoped() |
Create a new instance for each request | Request-level singleton |
instance() |
Use an existing instance | Created objects |
(2) Analysis
// Automatic resolution via type-hint
class OrderController extends Controller
{
public function __construct(
private PaymentGatewayInterface $gateway, // auto-resolved
) {}
}
// Manual resolution
$gateway = app(PaymentGatewayInterface::class);
$gateway = app()->make(PaymentGatewayInterface::class);
$analytics = resolve('analytics.service');
(1) ▶ Example: ShopMetrics Service Container Binding
// app/Providers/AppServiceProvider.php
public function register(): void
{
$this->app->bind(
\App\Contracts\ReportGeneratorInterface::class,
\App\Services\PdfReportGenerator::class,
);
$this->app->singleton(
\App\Services\TenantManager::class,
fn ($app) => new TenantManager(
$app->make(\App\Models\Tenant::class),
),
);
$this->app->when(ShopController::class)
->needs(\App\Contracts\FileStorageInterface::class)
->give(\App\Services\S3StorageService::class);
}
Output:
// Execution Successful
6. The Facades Principle
Facades are Laravel's "static proxies"—they use concise static call syntax and rely on the service container to resolve the actual objects behind the scenes.
(1) How the Facade Works
flowchart LR
A["Cache::get('key')"] --> B["Cache Facade<br/>(static call)"]
B --> C["Facade::__callStatic()"]
C --> D["Resolve from Container<br/>(cache manager)"]
D --> E["CacheManager->get('key')"]
(2) Table of Common Facade Patterns
| Facade | Actual Class | Container Binding Key |
|---|---|---|
Cache |
CacheManager |
cache |
DB |
DatabaseManager |
db |
Event |
Dispatcher |
events |
Log |
LogManager |
log |
Mail |
MailManager |
mail |
Queue |
QueueManager |
queue |
Route |
Router |
router |
Storage |
FileManager |
filesystem |
(3) Facade vs. Dependency Injection
| Dimension | Facade | Dependency Injection |
|---|---|---|
| Syntax | Static Call | Constructor/Method Injection |
| Testability | Mockable (Cache::fake()) |
Mockable (manual binding) |
| IDE Support | Plug-ins/Support Files Required | Native Type Hints |
| Simplicity | ✅ One-line call | ❌ Requires a constructor declaration |
| Recommended Scenarios | Simple Operations/Controllers | Service Classes/Constructors |
(1) ▶ Example: Facade vs. Dependency Injection
// Using Facade — concise
use Illuminate\Support\Facades\Cache;
public function getShopStats(int $shopId): array
{
return Cache::remember("shop.stats.{$shopId}", 3600, function () use ($shopId) {
return Shop::findOrFail($shopId)->getStats();
});
}
// Using DI — explicit, easier to test
public function __construct(
private CacheManager $cache,
) {}
public function getShopStats(int $shopId): array
{
return $this->cache->remember("shop.stats.{$shopId}", 3600, function () use ($shopId) {
return Shop::findOrFail($shopId)->getStats();
});
}
Output:
// Execution Successful
7. Service Provider Startup Mechanism
(1) Provider Lifecycle
Request arrives
→ Register Phase: all providers' register() called (no booting yet)
→ Boot Phase: all providers' boot() called (all bindings available)
→ Application ready
(2) Deferred Provider
// app/Providers/PaymentServiceProvider.php
class PaymentServiceProvider extends ServiceProvider
{
protected bool $defer = true;
public function register(): void
{
$this->app->singleton(StripeGateway::class, function ($app) {
return new StripeGateway(config('services.stripe'));
});
}
public function provides(): array
{
return [StripeGateway::class];
}
}
| Provider Type | When to Load | Use Cases |
|---|---|---|
| Standard Provider | Loaded on every request | Core/Common Features |
| Deferred Provider | Load on first use | Expensive/low-frequency features |
(1) ▶ Example: ShopMetrics Custom ServiceProvider
// app/Providers/ShopMetricsServiceProvider.php
class ShopMetricsServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->bind(
ReportGeneratorInterface::class,
PdfReportGenerator::class,
);
$this->app->singleton(TenantManager::class);
}
public function boot(): void
{
// Register middleware alias
$this->app->make(\Illuminate\Routing\Router::class)
->aliasMiddleware('tenant', TenantResolve::class);
// Register view composer
View::composer('dashboard.*', NavigationComposer::class);
// Register event listeners
Event::listen(OrderPlaced::class, SendOrderNotification::class);
}
}
Output:
// Execution Successful
8. Comprehensive Example: ShopMetrics Request Tracing
// ============================================
// Comprehensive: Trace a ShopMetrics request
// Covers: lifecycle, container, facade, provider
// ============================================
// 1. public/index.php — Entry point
// $app = require_once __DIR__.'/../bootstrap/app.php';
// $app->handleRequest();
// 2. bootstrap/app.php — Kernel configuration
// return Application::configure(basePath: dirname(__DIR__))
// ->withRouting(web: ..., api: ...)
// ->withMiddleware(fn ($m) => $m->append([SetLocale::class]))
// ->create();
// 3. AppServiceProvider — Register bindings
// public function register(): void
// {
// $this->app->singleton(TenantManager::class);
// $this->app->bind(PaymentGatewayInterface::class, StripeGateway::class);
// }
// 4. Route: GET /{tenant}/dashboard
// Route::middleware(['auth', 'tenant'])->get('/{tenant}/dashboard', [DashboardController::class, 'index']);
// 5. Controller — Use DI and Facades
class DashboardController extends Controller
{
public function __construct(
private TenantManager $tenantManager, // DI resolved
) {}
public function index(Request $request): View
{
$tenant = $this->tenantManager->current();
$stats = Cache::remember("dashboard.{$tenant->id}", 300, function () use ($tenant) {
return [
'revenue' => $tenant->shops()->sum('revenue'),
'orders' => $tenant->orders()->count(),
'shops' => $tenant->shops()->active()->count(),
];
});
return view('dashboard.index', compact('tenant', 'stats'));
}
}
// 6. Response sent → Terminable middleware runs → Request complete
❓ FAQ
Cache::fake()), while a helper is more concise (cache()). Recommendation: Use helper functions for simple scenarios, and use Facade when mocking is required.register and boot?register only performs container binding and cannot depend on other services; boot runs after all register operations are complete and can safely use any bindings. Follow the principle: "register only binds; boot starts the container."getFacadeAccessor() method of the Facade class, then look up the corresponding binding in the container. Alternatively, use app('cache') to retrieve the instance directly.Kernel.php?bootstrap/app.php, using a chained API to configure middleware, exceptions, and routes. The functionality remains the same; it's just that the configuration is more centralized.📖 Summary
- Request lifecycle: index.php → Kernel → Providers → Middleware → Route → Controller → Response
- The HTTP Kernel handles web requests, and the Console Kernel handles Artisan commands
- Service container management and dependency injection:
bind()creates a new instance, whilesingleton()reuses the instance - Facade is a static proxy; it uses a container to resolve the actual object under the hood and supports mock testing.
- The Provider process consists of two phases: register (binding) and boot (startup)
- Deferred Provider: Deferred loading reduces unnecessary service startup overhead
📝 Exercises
-
Basic Question (⭐): Use
php artisan aboutto view the ShopMetrics Service Provider list, identify which ones are framework defaults and which are custom, and draw a simplified flowchart of the request lifecycle. -
Advanced Exercise (⭐⭐): Create a
ShopMetricsServiceProvider, usebind()to bindReportGeneratorInterfacetoPdfReportGenerator, and then use it in the controller via dependency injection. -
Challenge (⭐⭐⭐): Implement a Deferred Provider to lazy-load the Stripe payment gateway. Use the binding declared in
provides()and verify viaapp()->resolved()that it is indeed loaded only upon first use.



