RRelayer
ホーム/ルーティング

API ルート#

ディレクトリに route.php を置くと、そのパスはページではなく JSON/HTTP エンドポイントになります。ファイルは HTTP メソッドをキーにしたハンドラのマップだけ を return し、各ハンドラは関数型ページのファクトリと全く同じ方式でオートワイヤされます。レイアウトや HTML パイプラインは通りません(Next.js の Route Handlers 相当)。

ハンドラは 必ず Response を返します。生データを返したり、先に http_response_code() を立てて暗黙に変換させる経路はありません。Response 以外を返すと、即座に RuntimeException(= 500)で大きく失敗します。ApiResponder はありません。

<?php
// src/Pages/api/users/route.php
declare(strict_types=1);

use App\Service\UserRepository;
use Polidog\Relayer\Http\Request;
use Polidog\Relayer\Http\Response;

return [
    'GET'  => fn (UserRepository $users): Response => Response::json(['users' => $users->all()]),
    'POST' => function (Request $req, UserRepository $users): Response {
        $users->create($req->allPost());

        return Response::json(['ok' => true], 201);
    },
];

規約#

  • キーは HTTP メソッド(大文字小文字は無視)。値はオートワイヤされる

クロージャ。ページと同じリゾルバなので PageContextRequestIdentity・コンテナサービスが型で注入されます。

  • ハンドラの戻り値は 必ず Response。ステータスとヘッダは常に

明示します(推測する経路はありません)。

  • 動的セグメント [param] の値はハンドラ内で $ctx->params['id']

から取得します(PageContext を引数に取る)。

  • 1 ディレクトリはページ route.php のどちらか一方

(両方あるとスキャナがエラーにします)。

  • このファイルは 宣言禁止(毎リクエスト再評価されるため、

マップを return するだけ)。

Response の作り方#

コンストラクタは閉じています。名前付きファクトリで生成します。

ファクトリ用途
Response::json($data, $status = 200, $headers = [])JSON 化(即時エンコード、スラッシュ/ユニコード非エスケープ)+ Content-Type: application/json; charset=utf-8。エンコード不能なら RuntimeException
Response::text($body, $status = 200, $headers = [])プレーンテキスト+ text/plain; charset=utf-8
Response::noContent($status = 204)本文なし・Content-Type なし。「null が 204」の魔法はもうない(明示する)
Response::redirect($location, $status = 302)本文なしの Location リダイレクト
Response::make($body = null, $status = 200, $headers = [])生ボディ用の脱出ハッチ(CSV、空の 201 など。暗黙の Content-Type なし)

インスタンスメソッドでコピーを派生できます。

  • ->withHeader($name, $value) — ヘッダを 1 つ設定/上書き

(大文字小文字は無視)したコピー。

  • ->withoutBody() — ステータス/ヘッダは保持し本文だけ落とした

コピー(自動 HEAD が使うのと同じ)。

呼び出し側が $headersContent-Type を渡せば、ファクトリ既定より優先されます。

自動でやってくれる分(手書き不要)#

ケース挙動
未定義の OPTIONS204Allow(ユーザーコードは走らない)
未定義の HEADGET ハンドラを実行し本文を落として返す
ハンドラの無いメソッド405Allow(JSON 本文)
route.php が消えていた404 {"error":"Not Found"}(JSON 維持)
認証失敗非 null の Identity 引数 / $ctx->requireAuth() が例外 → 自動で JSON 401/403(ページの HTML ログイン 302 ではない)
$ctx->redirect('/x')ハンドラ自身が呼べば Location 応答(認証ゲートではなくハンドラの意図的動作)
$ctx->abort($s) / notFound(){"error":"<理由>"} +当該ステータスの JSON(HTML エラーページではない)

OPTIONS / HEAD を明示的に定義すればそちらが優先されます。認証は自前で判定せず $ctx->requireAuth()Identity 型引数に任せます(認証 参照)。

エラーハンドリング#

フレームワークが捕捉して JSON に変換するのは AuthorizationExceptionRedirectExceptionHttpException$ctx->abort() / $ctx->notFound())の 3 つです。 abort()/notFound()route.php ハンドラ内でも使え、HTML エラーページではなく {"error":"<理由>"} +当該ステータスの JSON になります(認証失敗の JSON 401/403 と同じ API/HTML 境界)。それ以外の例外は JSON 化されず素の 500(本番は display_errors off で空ボディ)になり、JSON 契約が崩れます。リスクのある処理は ハンドラのクロージャ内で try/catch し、Response に変換してください。

return [
    'POST' => function (Request $req, Service $svc): Response {
        try {
            return Response::json(['ok' => true, 'result' => $svc->run($req->allPost())]);
        } catch (\DomainException $e) {
            return Response::json(['error' => $e->getMessage()], 422);
        } catch (\Throwable $e) {
            \error_log((string) $e);                       // ログはサーバ側へ
            return Response::json(['error' => 'internal error'], 500); // 詳細は出さない
        }
    },
];
  • route.phpマップを return するだけ(クラス/関数宣言禁止)

ですが、try/catchハンドラのクロージャ内 なので問題ありません。

  • JSON 化不能な値を Response::json() に渡すと、ハンドラがレスポンスを

組み立てた地点で RuntimeException(= 500)。半端な本文は出ません。

実例#

このサイトの /api/searchsrc/Pages/api/search/route.php で、 RequestDocStore を注入し、全文検索結果を JSON で返します。 1 ディレクトリはページか route.php のどちらか一方なので、HTML 検索ページ(/search)とは別ディレクトリに分けています。

このドキュメントサイト自体の src/Pages/api/search/route.php: Response 注釈で Response::json([...]) を返します。ETag による 304 短絡は CachePolicy::emit() / isNotModified() / sendNotModified() 経由です。 route.php は HTML パイプライン(use-php コンポーネント描画)を通らないため、ページのような Set-Cookie: PHPSESSID が出ず、JSON レスポンスはそのまま CDN キャッシュ対象になります(ページ側を同じ状態にする話は CDN キャッシュ を参照)。

最終更新: 2026-05-19
変更履歴 (2)
  • v0.8.0 破壊的変更 callout を現在形の Response 契約に書き換え、v0.9.0/^0.12.1 注記を削除
  • 新規作成