この記事は、 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ではリクエストは以下のような流れでやってきます。
- webroot/index.php
\App\Application等で登録したmiddlewareRoutingMiddlewareで requestオブジェクトにURLをパースした結果(controllerとかactionとか)をセット。
\Cake\Http\ActionDispatcher::dispatch()\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のパース情報を別途与える必要があります。
なので、
- URL情報(配列/文字列)を文字列のURLに変換
ServerRequestオブジェクトを生成Router::parseRequest()でリクエストからルーティング情報取得してリクエストに付与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);
以上のものをクラス化したコードを以下に上げています。
(自分のユースケースでは、コントローラーオブジェクトが欲しかったので `ContorllerFacotry::create()` を使用しています。 (本当は、URLからコントローラークラスが取得したいのではなく、そのURLのコントローラーで扱っているモデルクラスが取得したかったマン。 ([cakephp/authorization](https://github.com/cakephp/authorization) で、権限チェックを行っているところで、権限のない場合はリンクを表示させないとかやりたかったマン。