ルーティングとページ#
ルーターは src/Pages/ をファイルシステムどおりにマッピングします(Next.js App Router 互換の規約)。
| ファイル | 役割 |
|---|---|
page.psx | そのルートを描画(1 ディレクトリに 1 つ) |
layout.psx | 子ページをラップ(ルート→末端へ積み重ね) |
error.psx | エラーページ(abort()/notFound() 受け。404/フォールバック) |
route.php | JSON API エンドポイント |
[param]/ | 動的セグメント($ctx->params['param'] で取得) |
(group)/ | ルートグループ。URL を増やさずファイル整理/個別 layout.psx |
_private/ | 先頭 _ のフォルダ。配下ごとルーティング対象外 |
1 つのディレクトリは ページ か
route.phpのどちらか一方です(両立不可)。
ページの 2 つの書き方#
関数スタイル#
<?php
use App\Service\UserRepository;
use Polidog\Relayer\Router\Component\PageContext;
return function (PageContext $ctx, UserRepository $users): Closure {
$ctx->metadata(['title' => 'Users']);
return fn () => (
<ul>
{array_map(fn ($u) => <li>{$u->name}</li>, $users->all())}
</ul>
);
};2 段(factory がレンダー用クロージャを返す)にすると、描画前にメタデータやキャッシュポリシーを宣言できます。1 段で Element を直接返しても構いません。
クラススタイル#
<?php
namespace App\Pages\Users;
use Polidog\Relayer\Router\Component\PageComponent;
use Polidog\UsePhp\Runtime\Element;
final class UserDetailPage extends PageComponent
{
public function __construct(private readonly UserRepository $users) {}
public function render(): Element
{
$user = $this->users->find($this->getParam('id'));
return <h1>{$user->name}</h1>;
}
}引数は 型で オートワイヤされます。PageContext・Request・Identity (null 許容なら任意、非 null なら認証必須を意味する)、そしてコンテナのサービス。 $_GET / $_POST / $_SERVER を直接読まず、必ず Request を受け取ってください。
動的セグメント#
src/Pages/docs/[slug]/page.psx は /docs/:slug にマッチし、値は $ctx->params['slug'] で取得します(このサイトのドキュメント表示ページが実例)。セグメントは 1 区切りで、スラッシュは含みません。[name] の name は英字/アンダースコア始まりの英数字です。Next.js のような キャッチオール [...slug] や省略可能 [[param]] は非対応(単一セグメントのみ)。複数階層を受けたいときは階層ぶんの [param] を切ります。
ルートグループ (group)/ と除外フォルダ _private/#
ルートグループ — 丸括弧で囲んだフォルダ (name)/ は URL セグメントを増やしません。URL を変えずにファイルを整理したり、一部のルートにだけ別の layout.psx を着せたりできます(括弧名そのものは URL に出ません)。
src/Pages/
(marketing)/
layout.psx ← (marketing) 配下だけのレイアウト
page.psx → "/"
about/page.psx → "/about"
(app)/
layout.psx ← 別レイアウト
dashboard/page.psx → "/dashboard"異なる物理パスが同じ URL へ解決する衝突(例: (a)/about と (b)/about がどちらも /about)は、初回リクエストではなく スキャン/ routes:compile 時にエラーになります。
除外フォルダ — 先頭が _ のフォルダ(_private・_components など)は、そのフォルダと配下すべてがルーティング対象から外れます。URL を持たせたくない断片や下書きを src/Pages/ 配下へ同居させたいときに使います。
404 とエラー#
ルックアップが空のときは http_response_code() を手で立てず、 $ctx->notFound()(=$ctx->abort(404))で「無い」を表明します。 HttpException が投げられ、ルーターが error.psx(または組み込みのエラーページ)を描画します。
return function (PageContext $ctx, UserRepository $users): Closure {
$user = $users->find($ctx->params['id']) ?? $ctx->notFound();
return fn () => <h1>{$user->name}</h1>;
};任意の 4xx/5xx は $ctx->abort(403) のように。3xx は abort() ではなく $ctx->redirect() です(→ サーバーアクション)。 error.psx はルート直下に 1 つ。404 以外はステータス/メッセージを受け取れます(404 は常に統一の Not Found 表示)。
レイアウト#
layout.psx は LayoutComponent を継承したクラスで、$this->getChildren() に子ページが入ります。ネストした各階層のレイアウトがルートから順に積み重なります。レイアウトはコンテナ DI を介さず new で生成されるため、依存のないチャームに留めるのが定石です(このサイトのヘッダ/フッタがそれ)。
ルートの確認とコンパイル#
vendor/bin/relayer routes # 検出された全ルートを一覧
vendor/bin/relayer routes:compile # 本番用スナップショットを書き出しroutes は検出された全ページ・API エンドポイントとメソッドを一覧表示します。
既定ではルーターはリクエストごとに src/Pages/ を走査します。デプロイ時に routes:compile を実行すると var/cache/routes/routes.php(ポータブルで OPcache 済み)へ書き出し、本番はツリー走査の代わりにそれ 1 つを読みます。ファイルの有無だけがゲートで、無ければライブ走査に戻ります(dev は常に最新で stale になりません)。ルートグループによる URL 衝突や、page と route.php の競合は、初回リクエストではなく routes:compile(デプロイ時)で失敗します。
変更履歴 (2)
- バージョン差分表記(vX.Y.Z 追加/破壊的変更/依存バージョン注記)を削除し現在形に整理
- 新規作成