リクエストハンドラとMVCパターン
リクエストの受け取りからレスポンスまでの流れと、MVCの実装パターン。
RequestContextが中心
RequestContext は、アプリコードが触る標準窓口です。URLパラメータ、リクエスト情報、Cookie、ルーティング結果などを1つにまとめて扱います。
Naifyが要求するのは RequestContextHandlerInterface によるリクエストハンドラ契約であり、Controllerはその代表的な実装形のひとつです。docsでは、責務分離と見通しのよさがちょうどよい実装パターンとしてMVCを中心に説明します。
RequestContextHandlerInterfaceの契約
Naifyが要求するのは RequestContextHandlerInterface の実装です。Controllerはその代表例ですが、責務に応じて別のハンドラ構造を取ることもできます。
namespace Naify\Router;
use Naify\Router\RequestContext;
interface RequestContextHandlerInterface
{
public function handle(RequestContext $context, ?string $action): void;
}
$context:リクエスト情報へのアクセス手段$action:実行するアクションメソッド名(例:"helloAction")- 戻り値は
void。出力はecho等で直接行う
補足: この
RequestContextHandlerInterface はNaify独自のインターフェースであり、PSR-15の RequestHandlerInterface とは別物。ライフサイクル
Controllerのアクション実行は3フェーズで構成されます。
onActionBefore()
→
xxxAction()
→
onActionAfter()
| フェーズ | 役割 | 実装場所 |
|---|---|---|
onActionBefore() | 前処理(認証チェック、共通データ取得など) | BaseController |
xxxAction() | 業務処理(ユースケースの調停) | Feature Controller |
onActionAfter() | 後処理(テンプレート描画、レスポンス送出など) | BaseController |
この順序はBaseControllerの handle() メソッドで固定されます。Feature Controllerは xxxAction() だけを実装すればよく、前後処理のタイミングを気にする必要はありません。
BaseControllerの実装例
アプリ側の実装例:
Naifyフレームワークの機能ではなく、プロジェクト側の実装パターン。
namespace App\Controllers;
use Naify\Router\RequestContext;
use Naify\Router\RequestContextHandlerInterface;
abstract class BaseController implements RequestContextHandlerInterface
{
protected RequestContext $context;
public function handle(RequestContext $context, ?string $action): void
{
$this->context = $context;
$action ??= $context->getUrlParam('action', 'index');
$action .= 'Action';
$this->onActionBefore();
$this->{$action}();
$this->onActionAfter();
}
protected function onActionBefore(): void
{
//共通前処理をここに書く
}
protected function onActionAfter(): void
{
//共通後処理をここに書く
}
}
Feature ControllerはBaseControllerを継承し、アクションメソッドだけを実装します。
namespace App\Controllers;
final class IndexController extends BaseController
{
public function helloAction(): void
{
$name = (string) $this->context->getUrlParam('name', 'World');
echo "Hello, {$name}!";
}
}
複数BaseControllerパターン
BaseControllerは1つに集約せず、用途別に分割します。これにより、認証・出力・権限ルールの混在を防げます。
Controllers/
├── Web/
│ └── BaseController ← HTML出力、CSRF検証
├── Api/
│ └── BaseController ← JSON出力、Bearerトークン検証
└── Authed/
└── BaseController ← セッション認証、ユーザー注入
アプリ側の実装例:
Naifyフレームワークの機能ではなく、プロジェクト側の実装パターン。
Web用BaseController
namespace App\Controllers\Web;
use Naify\Router\RequestContext;
use Naify\Router\RequestContextHandlerInterface;
abstract class BaseController implements RequestContextHandlerInterface
{
protected RequestContext $context;
public function handle(RequestContext $context, ?string $action): void
{
$this->context = $context;
$action ??= $context->getUrlParam('action', 'index');
$action .= 'Action';
$this->onActionBefore();
$this->{$action}();
$this->onActionAfter();
}
protected function onActionBefore(): void
{
// CSRFトークン検証など
}
protected function onActionAfter(): void
{
// View(テンプレート)の描画・出力など
}
}
API用BaseController
namespace App\Controllers\Api;
use Naify\Router\RequestContext;
use Naify\Router\RequestContextHandlerInterface;
abstract class BaseController implements RequestContextHandlerInterface
{
protected RequestContext $context;
protected array $responseData = [];
public function handle(RequestContext $context, ?string $action): void
{
$this->context = $context;
$action ??= $context->getUrlParam('action', 'index');
$action .= 'Action';
$this->onActionBefore();
$this->{$action}();
$this->onActionAfter();
}
protected function onActionBefore(): void
{
// Bearerトークン検証など
}
protected function onActionAfter(): void
{
header('Content-Type: application/json; charset=utf-8');
echo json_encode($this->responseData, JSON_UNESCAPED_UNICODE);
}
}
RequestContextの主要API
コントローラー内でよく使うメソッドの使い方を示します。APIの一覧は リファレンス を参照してください。
パラメータ取得
// GET/POSTパラメータ
$keyword = $this->context->getParam('keyword', '');
// URLパラメータ(ルートの {id} 部分)
$id = (int) $this->context->getUrlParam('id');
// Cookie
$token = $this->context->getCookie('session_token');
リクエスト情報
// HTTPメソッド
$method = $this->context->getMethod(); // "GET", "POST", etc.
//リクエストパス
$path = $this->context->getPath(); // "/users/42"
//サーバー変数
$host = $this->context->getServer('HTTP_HOST');
ルーティング情報
//マッチしたハンドラ名
$handler = $this->context->getRoutingHandler();
//マッチしたアクション名
$action = $this->context->getRoutingAction();
//ルート名
$name = $this->context->getRouteName();