👌

Laravelのリクエストライフサイクルを理解する🔥

に公開

はじめに

「ブラウザでURLを入力してからページが表示されるまで、Laravelの中で何が起きているの?」

最初はなんとなく動かしているだけでも、この流れを理解しておくと、バグの原因がどこにあるか特定しやすくなったり、ミドルウェアをどこに書けばいいか判断できたりするようになります。
Udemyの講座などではこのあたりを詳しく解説している講座に私は出会っていないため、まとめておこうと思いました(^^)

この記事では、Laravelのリクエストライフサイクルを順番に丁寧に解説していきます。


全体の流れ

まず全体像を把握しておきましょう。

リクエスト(ブラウザでURLを入力)

public/index.php

カーネル(Kernel)

グローバルミドルウェア(前処理)

ルーター

ミドルウェアA(前処理)

ミドルウェアB(前処理)

コントローラーロジック

ビューのレンダリング&レスポンスオブジェクト生成

ミドルウェアB(後処理)

ミドルウェアA(後処理)

グローバルミドルウェア(後処理)

レスポンスをブラウザに返す

terminate(後片付け)

一つひとつ見ていきます。


① リクエストがやってくる

ブラウザで https://example.com/posts と入力したとします。このとき、WebサーバーはまずそのURLに対応するファイルが public/ フォルダの中に実在するかどうかを確認します。

/posts というファイルは存在しないので、WebサーバーはすべてのリクエストをLaravelの入口である public/index.php に流します。

この仕組みは public/.htaccess(Apache用)に書かれています。

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [L]

「ファイルが存在しない場合はindex.phpに流す」というルールです。/css/app.css のような実在するファイルはそのまま返されます。


② public/index.php

すべてのリクエストはここを通ります。コードを見てみましょう。

<?php

require __DIR__.'/../vendor/autoload.php';

$app = require_once __DIR__.'/../bootstrap/app.php';

$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);

$response->send();

$kernel->terminate($request, $response);

index.php 自身はほとんど何もしていません。役割ごとに分解するとこうなります。

オートローダーの読み込み

require __DIR__.'/../vendor/autoload.php';

__DIR__ はそのファイルが置かれているディレクトリの絶対パスを返すPHPのマジック定数です。これを読み込むことで、use 文を書くだけでクラスが自動で読み込まれるようになります。

リクエストオブジェクトの生成

$request = Illuminate\Http\Request::capture()

PHPのグローバル変数をLaravelのRequestオブジェクトに変換します。

// 変換前(PHPのグローバル変数)
$_GET['name']
$_POST['email']
$_SERVER['REMOTE_ADDR']

// 変換後(LaravelのRequestオブジェクト)
$request->query('name')
$request->input('email')
$request->ip()

③ カーネル(Kernel)

カーネルはリクエストを受け取って、ミドルウェアとコントローラーを順番に通して、レスポンスを生成する司令塔です。

$response = $kernel->handle($request);

handle() を呼ぶだけで、グローバルミドルウェアの適用、ルーティング、コントローラーの実行まで全部やってくれます。


④ グローバルミドルウェア(前処理)

カーネルはまず、すべてのリクエストに適用されるグローバルミドルウェアを実行します。ここはまだルーティングが解決される前の段階です。

グローバルミドルウェアの例としては、メンテナンスモードのチェック、CORS設定、リクエストのログ記録などがあります。

public function handle(Request $request, Closure $next)
{
    // ルーティング前に全リクエストに対して実行したい処理
    Log::info('リクエスト受信', ['url' => $request->fullUrl()]);

    return $next($request);
}

⑤ ルーター

グローバルミドルウェアを通過した後、ルーターが「このURLはどのコントローラーのどのメソッドを呼ぶか」を解決します。

routes/web.php に書いたルート定義がここで使われます。

Route::middleware('auth')->group(function () {
    Route::get('/posts', [PostController::class, 'index']);
});

ルーターがコントローラーを特定したことで、そのルートに紐づいているミドルウェア(ここでは auth)が次に実行されます。


⑥ ミドルウェア(前処理)

ルートに紐づいたミドルウェアが順番に実行されます。

ミドルウェアの中で重要なのが $next($request) です。これは「次の処理にバトンを渡す」役割を持っています。

public function handle(Request $request, Closure $next)
{
    // 前処理(コントローラーの処理前に実行)
    if (!auth()->check()) {
        return redirect('/login');
    }

    $response = $next($request); // コントローラーに進む

    // 後処理(コントローラーの処理後に実行)
    $response->headers->set('X-Custom-Header', 'value');

    return $response;
}

$next($request) を呼ぶ前が前処理、呼んだ後が後処理です。

複数のミドルウェアがある場合、玉ねぎのようにバトンが渡されます。

MiddlewareA の前処理

MiddlewareB の前処理

コントローラー

MiddlewareB の後処理

MiddlewareA の後処理

⑦ コントローラーロジック&ビューのレンダリング

ミドルウェアをすべて通過してやっとコントローラーが実行されます。ビューのレンダリングもコントローラーの中で行われます。

class PostController extends Controller
{
    public function index()
    {
        $posts = Post::all();
        return view('posts.index', compact('posts')); // ビューのレンダリング
    }
}

view() の時点でHTMLが生成され、レスポンスオブジェクトとして返されます。


⑧ ミドルウェアの後処理

コントローラーが処理を終えると、今度はミドルウェアが逆順に後処理を実行します。

コントローラー

MiddlewareB の後処理($next() より後のコード)

MiddlewareA の後処理($next() より後のコード)

グローバルミドルウェアの後処理

逆順なのは、後から適用されたミドルウェアの後処理を先に終わらせる必要があるからです。


⑨ レスポンスをブラウザに返す

$response->send();

ここでHTMLがブラウザに返されます。ユーザーはこのタイミングでページを受け取ります。


⑩ terminate(後片付け)

$kernel->terminate($request, $response);

レスポンスを返した後に実行される後片付け処理です。ユーザーはすでにページを受け取っているので、この処理時間はユーザー体験に影響しません。

ミドルウェアに terminate() メソッドを定義すると自動で呼ばれます。

class LogRequests
{
    public function handle(Request $request, Closure $next)
    {
        return $next($request);
    }

    // レスポンス送信後に実行される
    public function terminate(Request $request, $response)
    {
        Log::info('レスポンス送信完了', [
            'status' => $response->getStatusCode(),
        ]);
    }
}

terminate() は必須ではありません。後処理が必要なミドルウェアだけに定義すればOKです。

複数のミドルウェアに terminate() が定義されている場合は、リクエスト時とは逆順に実行されます。

レスポンス送信

MiddlewareB の terminate()

MiddlewareA の terminate()

まとめ

Laravelのリクエストライフサイクルをまとめると以下のようになります。

リクエスト

public/index.php(入口)

カーネル(司令塔)

グローバルミドルウェア(全リクエストへの処理)

ルーター(コントローラーの特定)

ルートミドルウェア(前処理)

コントローラー&ビューのレンダリング

ルートミドルウェア(後処理・逆順)

グローバルミドルウェア(後処理)

レスポンスをブラウザへ

terminate(後片付け・逆順)

この流れを理解しておくと、「認証チェックはどこに書くべきか」「ログはどのタイミングで記録するか」といった判断がスムーズにできるようになります。ぜひ実際のコードを読むときに意識してみてください。

Discussion