この記事は、 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 で、権限チェックを行っているところで、権限のない場合はリンクを表示させないとかやりたかったマン。