或るリクエストの一生
入力と出力
コンピュータは、基本的に、入力に対して出力を返してくれるものです。
最も一般的なのは、コンソール画面で、 pwd という入力に対し現在のディレクトリが出力されます。
Laravel のあるディレクトリで php artisan --version と入力すれば現在インストールされている Laravel のバージョンが出力されます。
これと同様に、ブラウザを通して https://google.com と入力すると、(なんやかんやあって) Google の画面が出力されます。
また、X(Twitter)にログインした状態で https://x.com/home を入力すると、フォローしている人のつぶやきの一覧が出力されます。
リクエストとレスポンス
このことを、 web サーバー側から見ると、次のように見えます:
- HTTP リクエストを受け取る
- HTTP レスポンスを返す
つまり、サーバー処理とは、 HTTP リクエストを受け取ったときに生まれ、HTTP レスポンスを返すと死ぬ、というライフサイクル(一生)になっているわけです。
しかも、基本的にステートレス(状態を持たない)なので、前世の記憶はありません。
生まれてから死ぬまで、基本的には HTTP リクエストと自分の持っている情報(データベース)のみから、 HTTP レスポンスを作成します。
しかし X などのように、「ログイン状態」を持っているように見える web アプリはたくさんあります。
実はこれも、 HTTP リクエストの中にある情報(API トークンやセッションクッキーなど)と、データベースの情報を組み合わせて、あたかも状態が維持されているように見せかけているだけです。
Laravel を学ぶとき、このリクエストからレスポンスまでの間に、何が起こるのか、ある程度知っておくと、見通しが良いです。
文字列をオブジェクトとして扱う
HTTP リクエストも、 HTTP レスポンスも、文字列です。
しかし、文字列関数だけで、操作するのは大変です。
そこで、 Laravel では(厳密には PSR-7, 15 というガイドラインを採用する多くのフレームワークは)、入力された HTTP リクエストの文字列を Request オブジェクトに変換し、 それをもとに Response オブジェクトを生成し、この Response オブジェクトを HTTP レスポンスの文字列に変換して出力します。
つまり、ライフサイクル(一生)をまとめると、以下のようになります:
- HTTP リクエストを受け取る
- HTTP リクエストから Request オブジェクトを生成する
- Request オブジェクトと内部データから Response オブジェクトを生成する
- Response オブジェクトから HTTP レスポンスを作成する
- HTTP レスポンスを返す
リクエストオブジェクト
リクエストオブジェクトの使い方は、 公式ドキュメント に詳しいです。
とりえあず覚えておくべきことは、以下の通りです:
- ルーティングやコントローラー、ミドルウェアの引数に Request 型の変数を渡すと、自動でその変数がリクエストオブジェクトとして使えるようになる
- 公式ドキュメントにあるような、さまざまなメソッドで、 HTTP リクエストの内容にアクセスできる
特に、上記の 1 は、 DI (依存性注入)という仕組みなのですが、分かりにくいので、最初のうちは「そういうものだ」と暗記してください。
DI (及びそれを管理する DI コンテナ)の実装方法は多様であり、 PSR-11 というガイドラインにおいても、包括的な標準化がされているわけでは無いそうです。
このため、「DI 解説」のような記事をいくら読んでも、 DI の基礎概念の説明と、コンテナを利用する際の仕組みの説明と、その記事で使っているフレームワーク独自実装の説明が、それぞれ食い違っているように見えてしまいがちです。
とりあえず、 DI を使うことでテストしやすくなりますから、使い方を覚えることは必須です。
「ルーティング、コントローラー、ミドルウェアの引数に Request 型の変数を渡すと、自動で Request オブジェクトとして使えるようにしてくれる」という使い方だけを、最初のうちは暗記してしまってください。
Route::get('/', fn(Request $r) => $r->host()); // 'localhost' などが表示される
レスポンスオブジェクト
レスポンスオブジェクトの使い方は、 公式ドキュメント に詳しいです。
とりえあず覚えておくべきことは、以下の通りです:
- ルーティングやコントローラーの返り値の型によって、自動でレスポンスオブジェクトを作成してくれる
response()やview()ヘルパ関数を返すことでも作成できる- Inertia.js を使う場合は、
Inertia::render()メソッドで作成してくれる - ミドルウェア内で、クロージャー型として渡した
$next()の返り値として利用できる
特に、ルーティングやコントローラーで、文字列型を返せば、 text/html として返してくれますし、 array 型で返せば application/json として返してくれます。
<?php
use Illuminate\Http\Response;
class ResponseExampleController extends Controller
{
public function str(): string
{
return 'string'; // text/html の Response オブジェクト
}
public function arr(): array
{
return ['array' => true]; // application/json の Response オブジェクト
}
public function response(): Response
{
return response('{"string": "text"}')->header('Content-Type', 'application/json');
}
public function blade(): Response
{
return view('blade.template', ['response' => true]);
}
public function inertia(): Response
{
return Inertia::render('inertia.template', ['response' => true]);
}
}
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
return $response;
}
ルーティング
上記の通り、ルーティング処理の引数に Request 型を渡せば、リクエストオブジェクトを作成してくれます。
また、ルーティング処理の返り値によって、レスポンスオブジェクトを作成してくれます。
通常は、ルーティングの引数に、コントローラーのメソッドを渡すと思います。
その場合は、リクエストオブジェクトからレスポンスオブジェクトを作成する処理は、 MVC の中で行われます。
また、ルーティングミドルウェアを設定する場合、このルーティングファイルで、利用するミドルウェアを設定します。
ライフサイクル(一生)は、以下のようになります:
- HTTP リクエストを受け取る
- HTTP リクエストから Request オブジェクトを生成する
- ルーティング処理される
- Request オブジェクトと内部データから Response オブジェクトを生成する
- Response オブジェクトから HTTP レスポンスを作成する
- HTTP レスポンスを返す
メイン処理(コントローラー)
コントローラーで定義する関数は、ルーティングと同じ仕組みです。
引数に Request を受け取り、返り値で Response を返すか、型によっては Response オブジェクトを生成します。
ライフサイクル(一生)は、以下のようになります:
- HTTP リクエストを受け取る
- HTTP リクエストから Request オブジェクトを生成する
- ルーティング処理される
- MVC 処理で、Request オブジェクトと内部データから Response オブジェクトを生成する
- Response オブジェクトから HTTP レスポンスを作成する
- HTTP レスポンスを返す
ミドルウェア
ミドルウェア自体の仕組みは、 ミドルウェアの玉ねぎ構造 を参照してください。
処理自体は、メイン処理を玉ねぎの形で包み込むので、ライフサイクルは、以下のようになります:
- HTTP リクエストを受け取る
- HTTP リクエストから Request オブジェクトを生成する
- ルーティング処理される
- ミドルウェア前処理
- MVC 処理で、Request オブジェクトと内部データから Response オブジェクトを生成する
- ミドルウェア後処理
- Response オブジェクトから HTTP レスポンスを作成する
- HTTP レスポンスを返す
まとめ
Laravel が、どんな手順で処理しているのか、おおよそ分かっていれば、迷子になりにくいと思います。
ぜひ、公式ドキュメントなどを参考に、各処理の詳細を調べてみてください。