リクエストハンドラと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();