0

Kernel bootstrapping — HTTP Kernel vs Console Kernel

Advanced5 min read·lv-01-004
laravel-srcinterview

Concept

Laravel has two kernels: the HTTP Kernel (App\Http\Kernel, ultimately Illuminate\Foundation\Http\Kernel) and the Console Kernel (App\Console\Kernel, ultimately Illuminate\Foundation\Console\Kernel). Both implement the same fundamental pattern — they accept input, bootstrap the application, process the input, and return output — but they serve completely different SAPIs.

The HTTP Kernel (Illuminate\Foundation\Http\Kernel) is responsible for web request processing. Its handle(Request $request): Response method is the core execution path. Internally, it calls sendRequestThroughRouter($request), which first calls $this->bootstrap() (running the six bootstrappers) and then pipes the request through the global middleware stack using Illuminate\Pipeline\Pipeline. The middleware stack is defined in $this->middleware (global), $this->middlewareGroups (web, api), and $this->routeMiddleware/$this->middlewareAliases (named).

The bootstrappers run in this exact order, defined in $bootstrappers on the HTTP Kernel:

  1. \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables — reads .env via vlucas/phpdotenv
  2. \Illuminate\Foundation\Bootstrap\LoadConfiguration — reads all config/*.php files
  3. \Illuminate\Foundation\Bootstrap\HandleExceptions — sets error_reporting, registers set_error_handler and set_exception_handler
  4. \Illuminate\Foundation\Bootstrap\RegisterFacades — clears resolved instances and registers the Facade root
  5. \Illuminate\Foundation\Bootstrap\RegisterProviders — calls register() on all service providers
  6. \Illuminate\Foundation\Bootstrap\BootProviders — calls boot() on all registered providers

The Console Kernel (Illuminate\Foundation\Console\Kernel) is the entry point for php artisan. Its handle(InputInterface $input, OutputInterface $output) method bootstraps the application (same six bootstrappers) and then dispatches the Artisan command. It also defines the command schedule via the schedule(Schedule $schedule) method — this is how $schedule->command('emails:send')->daily() works.

A critical architectural difference: the Console Kernel does not run middleware at all. Artisan commands are not HTTP requests, so there is no middleware pipeline. Instead, the Console Kernel resolves the Artisan Application (a Symfony Console\Application subclass), which handles command parsing, option resolution, and dispatching.

In Laravel 11, the App\Http\Kernel class was removed. Middleware is now configured inline in bootstrap/app.php. However, the underlying Illuminate\Foundation\Http\Kernel still exists and functions identically — only the user-facing configuration changed.

Code Example

php
<?php
// Illuminate\Foundation\Http\Kernel — key methods explained

namespace Illuminate\Foundation\Http;

use Illuminate\Contracts\Http\Kernel as KernelContract;

class Kernel implements KernelContract
{
    // Bootstrappers always run in this order
    protected $bootstrappers = [
        \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
        \Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
        \Illuminate\Foundation\Bootstrap\HandleExceptions::class,
        \Illuminate\Foundation\Bootstrap\RegisterFacades::class,
        \Illuminate\Foundation\Bootstrap\RegisterProviders::class,
        \Illuminate\Foundation\Bootstrap\BootProviders::class,
    ];

    public function handle($request)
    {
        // Prevents multiple bootstraps (e.g. in tests calling handle() multiple times)
        $request::enableHttpMethodParameterOverride();

        $response = $this->sendRequestThroughRouter($request);

        $this->requestLifecycleCallbacks($request, $response);

        return $response;
    }

    protected function sendRequestThroughRouter($request)
    {
        // Bind the request instance into the container
        $this->app->instance('request', $request);

        // Clear any previously-resolved Facade instances
        Facade::clearResolvedInstance('request');

        // Run the 6 bootstrappers (only once per process)
        $this->bootstrap();

        // Build the middleware pipeline with the router at the centre
        return (new Pipeline($this->app))
            ->send($request)
            ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
            ->then($this->dispatchToRouter());
    }
}
php
<?php
// App\Http\Kernel (Laravel 10 style) — user customization point
namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    // Global middleware — runs on EVERY request
    protected $middleware = [
        \App\Http\Middleware\TrustProxies::class,
        \Illuminate\Http\Middleware\HandleCors::class,
        \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
        \App\Http\Middleware\TrimStrings::class,
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
    ];

    // Named middleware groups
    protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],
        'api' => [
            \Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],
    ];
}

Interview Q&A

Q: What is the difference between the HTTP Kernel and the Console Kernel in Laravel?

The HTTP Kernel handles web requests via handle(Request): Response. It runs the six bootstrappers and then passes the request through a global middleware pipeline, landing in the Router for dispatch. The Console Kernel handles Artisan commands via handle(InputInterface, OutputInterface): int. It also runs the same bootstrappers, but there is no middleware pipeline — instead it dispatches to the Symfony Console Application which resolves commands by name and passes them parsed arguments/options. The Console Kernel additionally defines the task schedule. Both kernels are resolved from the container, but they are bound to different contracts: Illuminate\Contracts\Http\Kernel vs Illuminate\Contracts\Console\Kernel.


Q: Why do the bootstrappers run in a specific fixed order, and what would break if they were reordered?

The order is load env → load config → handle exceptions → register facades → register providers → boot providers. Each step depends on the previous: LoadConfiguration reads config files that reference env() values, so env must be loaded first. HandleExceptions needs config values like app.debug, so config must be loaded first. RegisterFacades needs the config facade mapping, so config must be loaded. RegisterProviders calls register() which may use Facades, so facades must be registered. BootProviders calls boot() which can depend on other providers' registrations, so all providers must be registered first. Reordering causes BindingResolutionException, undefined constants, or silent mis-configuration where env() calls fall back to defaults because .env hasn't been read yet.


Q: How does $kernel->bootstrap() prevent running the bootstrappers multiple times in a single process?

The Kernel has a $bootstrapped boolean property. bootstrap() checks if ($this->app->hasBeenBootstrapped()) { return; } before running the bootstrappers. This matters in testing: when you call $this->get('/route') multiple times in a single test class, the kernel is reused across requests. Without this guard, LoadConfiguration would re-read all config files on every test request, RegisterProviders would double-register providers, and BootProviders would call boot() twice, causing duplicate event listeners, route registrations, and binding conflicts.