404 Not Found

404 Not Found


nginx

LaravelリクエストライフサイクルとHTTPカーネル

リクエストライフサイクルはLaravelの「エンジン設計図」です—これを理解すれば, 「フレームワークの使い方がわかる」から「フレームワークを理解している」へと進めます。

1. 学ぶ内容


2. シニア開発者の実話

(1) 悩み:ブラックボックスのデバッグに何時間も無駄に

AliceはShopMetricsで奇妙なバグに遭遇しました:ローカルでは正常に動作していましたが, デプロイ後, あるFacadeがnullを返しました。Facadeが内部でどのクラスを呼び出しているのかも分からず, サービスコンテナがどこでバインドしたのかも確信がありませんでした。4時間かけてコードを掘り下げ, ServiceProviderのregister()メソッドに条件分岐があり, 本番ブランチでバインディングが登録されていなかったことを発見しました。Bobも苦い経験をしていました—ミドルウェアの実行順序を理解していなかったため, 認証ミドルウェアがCORSミドルウェアの後に実行され, リクエストの事前検証で401エラーが発生しました。

(2) ライフサイクル理解による解決策

リクエストライフサイクルを理解すれば, 問題がどの段階で発生しているかを正確に特定できます—ルーティングの不一致なのか, ミドルウェアチェックの失敗なのか, コンテナバインディングの欠落なのか, Providerの読み込み失敗なのか。

PHP
// ライフサイクルを知っていれば, このようにデバッグできる:
// 1. バインディングが存在するか確認
app()->bound('payment.gateway');
// 2. どのProviderが登録したか確認
app()->getBindings()['payment.gateway']['concrete'];
// 3. ミドルウェアの実行順序をトレース
app()->make(\Illuminate\Foundation\Http\Kernel::class)->getMiddlewarePriority();

(3) 成果

コンテナデバッグ技術を使って, AliceはFacadeがnullを返す原因をわずか5分で特定し, Bobはミドルウェアの優先順位を調整してCORS問題を即座に解決しました。ライフサイクルを理解している開発者は, デバッグ効率を10倍に引き上げることができます。


3. リクエストライフサイクル

(1) 完全な流れ

100%
flowchart TD
    A["public/index.php<br/>(エントリポイント)"] --> B["HTTPカーネル<br/>(bootstrap/app.php)"]
    B --> C["サービスプロバイダ<br/>(Register & Boot)"]
    C --> D["ミドルウェアパイプライン<br/>(グローバル + ルート)"]
    D --> E{"ルート一致?"}
    E -->|はい| F["コントローラ/アクション<br/>(ビジネスロジック)"]
    E -->|いいえ| G["フォールバック/404"]
    F --> H["レスポンスオブジェクト"]
    G --> H
    H --> I["クライアントに送信"]
    I --> J["終了処理<br/>(レスポンス後フック)"]

(2) 各段階の詳細解説

段階 ファイル/クラス 責務
エントリポイント public/index.php Composerオートロードの読み込み, アプリケーションインスタンスの作成
カーネル bootstrap/app.php ミドルウェア, 例外処理, ルーティングの設定
Provider登録 config/app.providers コンテナバインディングの登録
Provider起動 Provider boot() 起動ロジックの実行
ミドルウェア グローバル → ルート リクエストとレスポンスのフィルタ/変更
ルート振り分け Router URL照合 → コントローラ実行
終了処理 Terminableミドルウェア レスポンス後のクリーンアップ操作

(1) ▶ サンプル:リクエストライフサイクルの各段階を確認する

BASH
# 登録済みサービスプロバイダを確認
php artisan about --only=providers
# 一覧表示: AppServiceProvider, AuthServiceProvider 等

# ミドルウェアパイプラインを確認
php artisan route:list --columns=method,uri,middleware
# GET /dashboard → auth, tenant.resolve, verified

# コンテナ内のバインド済みサービスを確認
php artisan tinker
# app()->getBindings()
# すべての登録済みコンテナバインディングを一覧表示

出力:

TEXT
# コマンド実行成功

4. HTTPカーネルとコンソールカーネル

Laravelには2つのカーネルがあります:HTTPカーネルはWebリクエストを処理し, コンソールカーネルはArtisanコマンドを処理します。

(1) Laravel 11カーネル設定

PHP
// bootstrap/app.php — Laravel 11単一ファイル設定
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();
観点 HTTPカーネル コンソールカーネル
エントリ public/index.php artisanファイル
処理対象 HTTPリクエスト コンソール入力
ミドルウェア グローバル + ルート なし
出力 HTTPレスポンス コンソール出力
環境 Webリクエスト CLIコマンド

(1) ▶ サンプル:カスタムグローバルミドルウェア

PHP
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
    // グローバルミドルウェアを追加 (すべてのリクエストで実行)
    $middleware->append([
        \App\Http\Middleware\SetLocale::class,
    ]);

    // デフォルトのグローバルミドルウェアを削除
    $middleware->remove([
        \Illuminate\Foundation\Http\Middleware\TrimStrings::class,
    ]);

    // ルートミドルウェアのエイリアスを登録
    $middleware->alias([
        'tenant' => \App\Http\Middleware\TenantResolve::class,
        'role' => \App\Http\Middleware\CheckRole::class,
    ]);
})

出力:

TEXT
// 実行成功

5. サービスコンテナ

サービスコンテナはLaravelの中核です—依存注入とクラスのライフサイクルを管理します。

(1) バインディング

PHP
// app/Providers/AppServiceProvider.php
public function register(): void
{
    // インターフェースを実装にバインド
    $this->app->bind(
        PaymentGatewayInterface::class,
        StripeGateway::class,
    );

    // クロージャでバインド (完全制御)
    $this->app->bind('analytics.service', function ($app) {
        return new AnalyticsService(
            $app->make(CacheManager::class),
            $app['config']->get('analytics.ttl'),
        );
    });

    // シングルトン — 毎回同じインスタンス
    $this->app->singleton(ShopMetricsConfig::class, function ($app) {
        return new ShopMetricsConfig(
            $app['config']->get('shopmetrics'),
        );
    });
}
バインディング方法 呼び出しごと 目的
bind() 新しいインスタンスを作成 ステートレスサービス
singleton() インスタンスを再利用 ステートフル/コストの高いオブジェクト
scoped() リクエストごとに新しいインスタンスを作成 リクエストレベルのシングルトン
instance() 既存のインスタンスを使用 既に作成済みのオブジェクト

(2) 解決

PHP
// タイプヒントによる自動解決
class OrderController extends Controller
{
    public function __construct(
        private PaymentGatewayInterface $gateway, // 自動解決される
    ) {}
}

// 手動解決
$gateway = app(PaymentGatewayInterface::class);
$gateway = app()->make(PaymentGatewayInterface::class);
$analytics = resolve('analytics.service');

(1) ▶ サンプル:ShopMetricsサービスコンテナバインディング

PHP
// 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);
}

出力:

TEXT
// 実行成功

6. Facadesの原理

FacadesはLaravelの「静的プロキシ」です—簡潔な静的呼び出し構文を使用しつつ, 背後ではサービスコンテナに依存して実際のオブジェクトを解決します。

(1) Facadeの仕組み

100%
flowchart LR
    A["Cache::get('key')"] --> B["Cache Facade<br/>(静的呼び出し)"]
    B --> C["Facade::__callStatic()"]
    C --> D["コンテナから解決<br/>(キャッシュマネージャ)"]
    D --> E["CacheManager->get('key')"]

(2) 一般的なFacadeパターン一覧

Facade 実際のクラス コンテナバインディングキー
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と依存注入の比較

観点 Facade 依存注入
構文 静的呼び出し コンストラクタ/メソッド注入
テスト容易性 モック可能 (Cache::fake()) モック可能 (手動バインディング)
IDEサポート プラグイン/サポートファイルが必要 ネイティブタイプヒント
簡潔さ ✅ 1行で呼び出し ❌ コンストラクタ宣言が必要
推奨シーン 簡単な操作/コントローラ サービスクラス/コンストラクタ

(1) ▶ サンプル:Facadeと依存注入の比較

PHP
// Facadeを使用 — 簡潔
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();
    });
}

// DIを使用 — 明示的, テストしやすい
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();
    });
}

出力:

TEXT
// 実行成功

7. サービスプロバイダの起動仕組み

(1) Providerライフサイクル

TEXT
リクエスト到着
  → Register段階: すべてのProviderのregister()が呼ばれる (まだbootingではない)
  → Boot段階: すべてのProviderのboot()が呼ばれる (すべてのバインディングが利用可能)
  → アプリケーション準備完了

(2) 遅延Provider

PHP
// 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種別 読み込みタイミング 用途
標準Provider 毎リクエストで読み込み コア/共通機能
遅延Provider 初回使用時に読み込み 高コスト/低頻度機能

(1) ▶ サンプル:ShopMetricsカスタムServiceProvider

PHP
// 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
    {
        // ミドルウェアエイリアスの登録
        $this->app->make(\Illuminate\Routing\Router::class)
            ->aliasMiddleware('tenant', TenantResolve::class);

        // ビューコンポーザーの登録
        View::composer('dashboard.*', NavigationComposer::class);

        // イベントリスナーの登録
        Event::listen(OrderPlaced::class, SendOrderNotification::class);
    }
}

出力:

TEXT
// 実行成功

8. 総合例:ShopMetricsリクエストトレーシング

PHP
// ============================================
// 総合例: ShopMetricsリクエストのトレース
// 対象: ライフサイクル, コンテナ, Facade, Provider
// ============================================

// 1. public/index.php — エントリポイント
// $app = require_once __DIR__.'/../bootstrap/app.php';
// $app->handleRequest();

// 2. bootstrap/app.php — カーネル設定
// return Application::configure(basePath: dirname(__DIR__))
//     ->withRouting(web: ..., api: ...)
//     ->withMiddleware(fn ($m) => $m->append([SetLocale::class]))
//     ->create();

// 3. AppServiceProvider — バインディングの登録
// public function register(): void
// {
//     $this->app->singleton(TenantManager::class);
//     $this->app->bind(PaymentGatewayInterface::class, StripeGateway::class);
// }

// 4. ルート: GET /{tenant}/dashboard
// Route::middleware(['auth', 'tenant'])->get('/{tenant}/dashboard', [DashboardController::class, 'index']);

// 5. コントローラ — DIとFacadeを使用
class DashboardController extends Controller
{
    public function __construct(
        private TenantManager $tenantManager, // DIで解決される
    ) {}

    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. レスポンス送信 → Terminableミドルウェア実行 → リクエスト完了

❓ よくある質問

Q Facadeとヘルパー関数はどちらが良いですか?
A どちらも内部的には同じサービスを呼び出しています。Facadeはテスト時にモックできます (Cache::fake())が, ヘルパーの方が簡潔です (cache())。推奨:簡単なシーンではヘルパー関数を使用し, モックが必要な場合はFacadeを使用してください。
Q registerbootの違いは何ですか?
A registerはコンテナバインディングのみを行い, 他のサービスに依存できません。bootはすべてのregisterの完了後に実行され, 安全にバインディングを使用できます。原則:「registerはバインドのみ, bootは起動」。
Q シングルトンとバインドはいつ使い分けるべきですか?
A ステートレスなサービスにはbindを使用 (毎回新しいインスタンスが作成される, 例:PDFジェネレータ)。ステートフルなサービスや初期化コストが高いものにはsingletonを使用 (例:データベース接続, キャッシュマネージャ)。
Q 遅延Providerを使用すると最初のリクエストが遅くなりますか?
A 初回読み込み時にはわずかなオーバーヘッドがありますが, 全体的に後続リクエストの起動時間を短縮できます。ある機能の90%のリクエストで使用されない場合, 遅延が正しい選択です。
Q 特定のFacadeに関連する実際のクラスを確認するにはどうすればよいですか?
A FacadeクラスのgetFacadeAccessor()メソッドが返す文字列を確認し, コンテナで対応するバインディングを検索してください。または, app('cache')で直接インスタンスを取得することもできます。
Q Laravel 11でKernel.phpは削除されましたか?
A はい, Laravel 11ではHTTPカーネルとコンソールカーネルがbootstrap/app.phpに統合され, チェーンAPIでミドルウェア, 例外, ルートを設定するようになりました。機能は同じで, 設定がより集中化されただけです。

📖 まとめ


📝 練習問題

  1. 基本問題 (⭐):php artisan aboutを使ってShopMetricsのServiceProvider一覧を確認し, どれがフレームワークのデフォルトでどれがカスタムかを特定し, リクエストライフサイクルの簡略フローチャートを描いてください。

  2. 応用問題 (⭐⭐):ShopMetricsServiceProviderを作成し, bind()ReportGeneratorInterfacePdfReportGeneratorにバインドし, コントローラで依存注入を通じて使用してください。

  3. チャレンジ (⭐⭐⭐):遅延Providerを実装してStripe決済ゲートウェイを遅延読み込みしてください。provides()で宣言されたバインディングを使用し, app()->resolved()で実際に初回使用時にのみ読み込まれることを確認してください。

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%