CakePHP 3 でURL情報からコントローラークラスを取得する

標準

この記事は、 CakePHP Advent Calendar 2018 17日目の記事です。

前日の記事は chinpei215 さんの CakePHP2 のデバッグツールバーをプリチーにする – Qiita でした。


TL;DR: ControllerResolver.php – gist


CakePHP 3.4以降の話。

権限コントロールまわりのアレコレのために、URL情報からコントローラークラスを取得したかったのでやってみました。

ここで言う URL情報 とは、CakePHPの Router::url()HtmlHelper::link() などに渡される配列や文字列のデータです。

$url = [
    'controller' => 'Users',
    'action' => 'index',
];
$url = '/users/add';

CakePHPにおいて配列形式のURL指定の場合、現在のリクエストのコンテキストが影響します。

// `/users/index` へのリクエストを扱っているとき (UsersController::add() での処理中)
$url = ['action' => 'add']; // -> controller='Users', action='add' と解釈
// `/posts/edit/1` へのリクエストを扱っているとき (PostsController::edit() での処理中)
$url = ['action' => 'add']; // -> controller='Posts', action='add' と解釈

このあたりは、 Router::url() がよろしくやってくれます。


さて、URLからコントローラーの取得ですが、CakePHPのコアでもディスパッチャー部分でURLをパースしてコントローラークラスを取得しているので、どのようにやっているか見てみましょう。

CakePHPではリクエストは以下のような流れでやってきます。

  1. webroot/index.php
  2. \App\Application 等で登録したmiddleware
    • RoutingMiddleware で requestオブジェクトにURLをパースした結果(controllerとかactionとか)をセット。
  3. \Cake\Http\ActionDispatcher::dispatch()
  4. \Cake\Http\ActionDispatcher::_invoke()
    • ここでコントローラークラスの実行

リクエストからコントローラーの生成は \Cake\Http\ActionDispatcher::dispatch() で、 \Cake\Http\ContorllerFacotry を利用して行われています。

cakephp/ActionDispatcher.php at 3.7.0 · cakephp/cakephp

\Cake\Http\ContorllerFacotry には、2つの公開メソッド、

  • リクエストからコントローラーオブジェクトを生成する create(ServerRequest $request, Response $response): Controller
  • リクエストからコントローラークラス名を取得する getControllerClass(ServerRequest $request): string

があります。

この \Cake\Http\ContorllerFacotry を使えば、URLからコントローラークラスを取得できそうなので、あとはメソッドに渡す ServerRequest をURLから生成できればよさそうです。

ServerRequestは、コンストラクタにURLの文字列を与えれば生成できますが、RouterクラスによるURLのパース情報を別途与える必要があります。

なので、

  1. URL情報(配列/文字列)を文字列のURLに変換
  2. ServerRequest オブジェクトを生成
  3. Router::parseRequest() でリクエストからルーティング情報取得してリクエストに付与
  4. ContorllerFacotry のメソッドにリクエストオブジェクトを渡してコントローラー取得

のようにしたら、コントローラーを取得できそうです。

コードは以下のようになります。

use Cake\Http\ControllerFactory;
use Cake\Http\Response;
use Cake\Http\ServerRequest;
use Cake\Routing\Router;
// $url は URL情報(配列/文字列)
// URL情報(配列/文字列)を文字列のURLに変換
$urlString = Router::url($url);
// `ServerRequest` オブジェクトを生成
$request = (new ServerRequest($urlString));
// `Router::parseRequest()` でリクエストからルーティング情報取得してリクエストに付与
$params = Router::parseRequest($request);
$request = $request->withAttribute('params', $params);
// コントローラー取得(オブジェクト)
$controller = (new ContorllerFacotry())->create($request, new Response());
// コントローラー取得(コントローラークラス名)
$controller = (new ContorllerFacotry())->getControllerClass($request);

以上のものをクラス化したコードを以下に上げています。

ControllerResolver.php – gist


(自分のユースケースでは、コントローラーオブジェクトが欲しかったので ContorllerFacotry::create() を使用しています。
(本当は、URLからコントローラークラスが取得したいのではなく、そのURLのコントローラーで扱っているモデルクラスが取得したかったマン。
(cakephp/authorization で、権限チェックを行っているところで、権限のない場合はリンクを表示させないとかやりたかったマン。

コメントを残す

Page optimized by WP Minify WordPress Plugin