-
Notifications
You must be signed in to change notification settings - Fork 0
03 HTTP and Web
Woof では、Web アプリケーションの入力である HTTP リクエストと、出力である HTTP レスポンスを、すべて洗練されたオブジェクトとして扱います。
この章では、リクエストの受け取りからルーティング、高度なヘッダー解析、そして多彩なレスポンスの返し方までを解説します。
Woof は「純粋関数」としての見通しの良さを保つため、複雑なルーティングエンジンを内蔵していません。ルーティングは単に、 Request と Environment を受け取り、適切な Controller を返す 1 つの関数 (クロージャ) として定義します。
このアプローチにより、特定の URL にマッチしなかった場合 (404 Not Found) の制御なども非常に美しく、かつ明確に記述できます。
$router = function (Request $request, WebEnvironment $env): Controller {
$path = $request->getUri()->getPath();
if ($path === "/") {
return new HomeController();
}
if ($path === "/api/users") {
return new UserApiController();
}
// どの条件にもマッチしなかった場合は 404 用の Controller を返す
return new NotFoundController();
};
$controller = $router($request, $env);
$response = $controller->handle($request, $env);クライアントから送信された情報は、すべて不変な Request オブジェクトにカプセル化されています。
Woof の強力な点は、複雑な HTTP ヘッダーを単なる文字列としてではなく、意味を持ったオブジェクトとしてパース (解析) できることです。
ブラウザが送信する Accept-Language や Accept などのヘッダーには、優先度を示す q 値 (Quality Values) が含まれています。
Woof はこれを自動で解釈し、優先度順にソートして扱うことができます。
use Woof\Http\QualityValues;
// "ja,en-US;q=0.7,en;q=0.3" のような文字列を解析して取得
$acceptLanguage = $request->getHeader("Accept-Language");
// ヘッダーが QualityValues としてパースされているか確認
if ($acceptLanguage instanceof QualityValues) {
// getValue() は q値の優先度順にソートされた配列を返します
$values = $acceptLanguage->getValue();
// 最も優先度の高い言語を取得します (この例では "ja")
$primaryLanguage = $values[0] ?? null;
}キャッシュ制御などで用いられる HTTP 特有の日付フォーマットも HttpDate オブジェクトとして安全に扱うことができます。
use Woof\Http\HttpDate;
$ifModifiedSince = $request->getHeader("If-Modified-Since");
// ヘッダーが HttpDate としてパースされているか確認
if ($ifModifiedSince instanceof HttpDate) {
// HttpDate の getValue() は int 型 (Unix タイムスタンプ) を返します
$timestamp = $ifModifiedSince->getValue();
// そのまま int 同士の比較に使用したり、date() 関数に渡して書式化したりできます
if ($timestamp < time()) {
// ...
}
}Controller の処理結果は、最終的に Response オブジェクトとして出力されます。
レスポンスボディ (コンテンツの中身) には、用途に合わせて様々な専用クラスをセットすることができます。
- TextBody: プレーンテキストや HTML 文字列を直接返す場合に使用します。
- JsonBody: 配列やオブジェクトを JSON 形式で返す API などに使用します。
- FileBody: サーバー上のファイルをブラウザで表示させたり、ダウンロードさせたりする場合に使用します。
use Woof\Web\Operator;
use Woof\Http\Response\JsonBody;
use Woof\Http\Response\FileBody;
// JSON API のレスポンス例
return (new Operator($request, $env))
->setBody(new JsonBody(["status" => "success", "id" => 123]))
->build();
// 画像ファイルをブラウザ上で表示させる例
return (new Operator($request, $env))
->setBody(new FileBody("/path/to/image.png", "image/png"))
->build();
// PDF ファイルを強制的にダウンロードさせる例
return (new Operator($request, $env))
->setBody(new FileBody("/path/to/document.pdf", "application/pdf"))
->setAttachmentFilename("report.pdf") // ここで Content-Disposition ヘッダーが自動的にセットされます
->build();HTML テンプレートエンジンなどを利用して動的な画面を描画する場合は
View インターフェースを実装したクラスを作成し、 Operator の setView() メソッドに渡します。
ここで、Woof における 極めて重要な実装のゴールデンルール を再確認します。
「View クラスのコンストラクタには、純粋な値 (不変な状態) だけを渡すこと」
データベースと直接通信するような ORM クラス (例: $userModel) をそのまま View に渡してはいけません。
必ず、Controller 側で必要なデータ (ユーザー名 $userName や、表示フラグ $isAdmin など) を取り出し、それらの「純粋な値」だけを View に渡してください。
これにより、View が外部の環境から完全に切り離されるため、入力と出力が 1:1 になる美しい単体テストの設計が可能になります。