LaravelリクエストライフサイクルとHTTPカーネル
リクエストライフサイクルはLaravelの「エンジン設計図」です—これを理解すれば, 「フレームワークの使い方がわかる」から「フレームワークを理解している」へと進めます。
1. 学ぶ内容
- リクエストライフサイクル:public/index.php → Kernel → Pipeline → Response
- HTTPカーネルとコンソールカーネルのデュアルカーネルアーキテクチャ
- サービスコンテナのバインディングと解決:bind/singleton/make
- Facades:原理と静的プロキシの仕組み
- Providerの起動順序と遅延Provider
2. シニア開発者の実話
(1) 悩み:ブラックボックスのデバッグに何時間も無駄に
AliceはShopMetricsで奇妙なバグに遭遇しました:ローカルでは正常に動作していましたが, デプロイ後, あるFacadeがnullを返しました。Facadeが内部でどのクラスを呼び出しているのかも分からず, サービスコンテナがどこでバインドしたのかも確信がありませんでした。4時間かけてコードを掘り下げ, ServiceProviderのregister()メソッドに条件分岐があり, 本番ブランチでバインディングが登録されていなかったことを発見しました。Bobも苦い経験をしていました—ミドルウェアの実行順序を理解していなかったため, 認証ミドルウェアがCORSミドルウェアの後に実行され, リクエストの事前検証で401エラーが発生しました。
(2) ライフサイクル理解による解決策
リクエストライフサイクルを理解すれば, 問題がどの段階で発生しているかを正確に特定できます—ルーティングの不一致なのか, ミドルウェアチェックの失敗なのか, コンテナバインディングの欠落なのか, Providerの読み込み失敗なのか。
// ライフサイクルを知っていれば, このようにデバッグできる:
// 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) 完全な流れ
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) ▶ サンプル:リクエストライフサイクルの各段階を確認する
# 登録済みサービスプロバイダを確認
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()
# すべての登録済みコンテナバインディングを一覧表示
出力:
# コマンド実行成功
4. HTTPカーネルとコンソールカーネル
Laravelには2つのカーネルがあります:HTTPカーネルはWebリクエストを処理し, コンソールカーネルはArtisanコマンドを処理します。
(1) Laravel 11カーネル設定
// 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) ▶ サンプル:カスタムグローバルミドルウェア
// 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,
]);
})
出力:
// 実行成功
5. サービスコンテナ
サービスコンテナはLaravelの中核です—依存注入とクラスのライフサイクルを管理します。
(1) バインディング
// 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) 解決
// タイプヒントによる自動解決
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サービスコンテナバインディング
// 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);
}
出力:
// 実行成功
6. Facadesの原理
FacadesはLaravelの「静的プロキシ」です—簡潔な静的呼び出し構文を使用しつつ, 背後ではサービスコンテナに依存して実際のオブジェクトを解決します。
(1) Facadeの仕組み
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と依存注入の比較
// 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();
});
}
出力:
// 実行成功
7. サービスプロバイダの起動仕組み
(1) Providerライフサイクル
リクエスト到着
→ Register段階: すべてのProviderのregister()が呼ばれる (まだbootingではない)
→ Boot段階: すべてのProviderのboot()が呼ばれる (すべてのバインディングが利用可能)
→ アプリケーション準備完了
(2) 遅延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種別 | 読み込みタイミング | 用途 |
|---|---|---|
| 標準Provider | 毎リクエストで読み込み | コア/共通機能 |
| 遅延Provider | 初回使用時に読み込み | 高コスト/低頻度機能 |
(1) ▶ サンプル:ShopMetricsカスタム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
{
// ミドルウェアエイリアスの登録
$this->app->make(\Illuminate\Routing\Router::class)
->aliasMiddleware('tenant', TenantResolve::class);
// ビューコンポーザーの登録
View::composer('dashboard.*', NavigationComposer::class);
// イベントリスナーの登録
Event::listen(OrderPlaced::class, SendOrderNotification::class);
}
}
出力:
// 実行成功
8. 総合例:ShopMetricsリクエストトレーシング
// ============================================
// 総合例: 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ミドルウェア実行 → リクエスト完了
❓ よくある質問
Cache::fake())が, ヘルパーの方が簡潔です (cache())。推奨:簡単なシーンではヘルパー関数を使用し, モックが必要な場合はFacadeを使用してください。registerとbootの違いは何ですか?registerはコンテナバインディングのみを行い, 他のサービスに依存できません。bootはすべてのregisterの完了後に実行され, 安全にバインディングを使用できます。原則:「registerはバインドのみ, bootは起動」。bindを使用 (毎回新しいインスタンスが作成される, 例:PDFジェネレータ)。ステートフルなサービスや初期化コストが高いものにはsingletonを使用 (例:データベース接続, キャッシュマネージャ)。getFacadeAccessor()メソッドが返す文字列を確認し, コンテナで対応するバインディングを検索してください。または, app('cache')で直接インスタンスを取得することもできます。Kernel.phpは削除されましたか?bootstrap/app.phpに統合され, チェーンAPIでミドルウェア, 例外, ルートを設定するようになりました。機能は同じで, 設定がより集中化されただけです。📖 まとめ
- リクエストライフサイクル:index.php → Kernel → Providers → Middleware → Route → Controller → Response
- HTTPカーネルはWebリクエストを処理し, コンソールカーネルはArtisanコマンドを処理します
- サービスコンテナの管理と依存注入:
bind()は新しいインスタンスを作成し,singleton()はインスタンスを再利用します - Facadeは静的プロキシであり, 内部的にコンテナを使って実際のオブジェクトを解決し, モックテストをサポートしています
- Providerのプロセスは2つの段階で構成:register (バインディング)とboot (起動)
- 遅延Provider:遅延読み込みにより不要なサービス起動のオーバーヘッドを削減します
📝 練習問題
-
基本問題 (⭐):
php artisan aboutを使ってShopMetricsのServiceProvider一覧を確認し, どれがフレームワークのデフォルトでどれがカスタムかを特定し, リクエストライフサイクルの簡略フローチャートを描いてください。 -
応用問題 (⭐⭐):
ShopMetricsServiceProviderを作成し,bind()でReportGeneratorInterfaceをPdfReportGeneratorにバインドし, コントローラで依存注入を通じて使用してください。 -
チャレンジ (⭐⭐⭐):遅延Providerを実装してStripe決済ゲートウェイを遅延読み込みしてください。
provides()で宣言されたバインディングを使用し,app()->resolved()で実際に初回使用時にのみ読み込まれることを確認してください。



