先週のCakePHP勉強会で、akiyanさんが routes.php に関する発表をやっていて、今までになかったルーティング情報のまとめみたいな資料になっていて多くのBakerが目から鱗状態になったと思います。ボクもそんな中の一人ですが、忘れないうちに復習してみようとおもいます。
内容的には、
・Routesの設定 :: 環境設定 :: CakePHPによる開発 :: マニュアル :: 1.2 Collection :: The Cookbook
・極める routes.php (CakePHP 1.2) : akiyan.com
とかぶりますので、まずはそちらのページを熟読される事をオススメします。
また、routeの確認にはユニットテストが有効です。
shin1x1さんが詳しいエントリーを上げてくださったのでこちらも必読。
CakePHP routes.phpの確認はユニットテストで | Shin x blog
で、ボクはというと実際飛んでいるパラメータを確認するためにもテスト用のコントローラーを作成して確認してみました。
app/controllers/users_controller.php
<?php class UsersController extends AppController { var $autoRender = false; function index( ) { pr($this->params); } function view( ) { pr($this->params); } } ?>
routeだけ確認したので View も Model も使いません。
$uses を呼ばずに、$autoRender = false; でViewにも渡さないようにしています。
アクションの中で pr($this->params) を呼んでいますが、CakePHPが使っているパラメータ配列が確認できて非常に有用ですのでぜひお試しあれ。
以下、続きます。(長いっす)
1)CakePHPのデフォルトルーティング
・3.4.5.1 デフォルトのルーティング
基本中の基本なのでakiyanさんは飛ばしていましたが、そもそもCakePHPでのルーティング(=URLとコントローラー/アクションのマッピング)は下記のようなルールで行われています。
デフォルトのroutesのURLパターン: http://example.com/コントローラ名/アクション名/パラメータ1/パラメータ2/パラメータ3
ですので、/users/view/yager/hoge/fuga としてアクセスすると
Array ( [pass] => Array ( [0] => yager [1] => hoge [2] => fuga ) [named] => Array ( ) [controller] => users [action] => view [plugin] => [form] => Array ( ) [url] => Array ( [url] => users/view/yager/hoge/fuga ) )
となります。
2)名前付きのパラメータ(namedパラメータ)
・3.4.5.2 名前付きのパラメータ
CakePHP1.2では(コントローラー/アクション以外の)パラメータに名前を付けて渡す事ができます。
例えば、1)の例で使ったURLをこのようにアクセスすると、
・/users/view/id:yager/param1:hoge/param2:fuga
Array ( [pass] => Array ( ) [named] => Array ( [id] => yager [param1] => hoge [param2] => fuga ) [controller] => users [action] => view [plugin] => [form] => Array ( ) [url] => Array ( [url] => users/view/id:yager/param1:hoge/param2:fuga ) )
pass に入っていたパラメータが、named の方に入り名前付きの連想配列になって渡っている事が分かります。
「キー」+「セパレータ」+「値」の組み合わせで「id:yager」と渡してやると、$this->passedArgs[‘id’] で取得できます。
3)ある静的URLをコントローラー/アクションにマッピングする
/members/ で /users/index/ を表示するようにしたい。
Router::connect( '/members/', array( 'controller' => 'users', 'action' => 'index' ) );
ルーティングの指定は、app/config/routes.php に Router::connect関数で表現します。
第1引数に制御するURLを、第2引数にroute要素(:controller, :action, :plugin)に対応するデフォルト値を設定します。
4)route要素を使って動的にコントローラー/アクションをマッピングする
3)ででてきたroute要素(:controller, :action, :plugin)はRouter::connect関数の第1引数であるURLに変数として記述する事ができます。
例えば、アクション名を動的なパラメータで表す例です。
Router::connect( '/members/:action/*', array( 'controller' => 'users' ) );
こうしてやると、/members/index/ は /users/index/ に、/members/view/32 は /users/view/32 にルーティングされます。
また、URLには :controller も指定できるため、
Router::connect( '/:action/:controller/*', array() );
とか書いてやると、コントローラー/アクションの順番が逆転するので、UsersController::indexを呼ぶためには /index/users/ にアクセスしないといけません。
まあ、こんなややこしい事やる人いないと思うけど。
こうしてみると、デフォルトルーティングって単純に
Router::connect( '/:controller/:action/*', array('action' => 'index') );
が指定してあるだけなんだなぁという事がわかります。(アクション名が指定されていない時はindexが呼ばれるとか)
5)paramsルーティング
URL中にはroute要素(:controller, :action, :plugin)以外の独自のパラメータも定義する事ができます。akiyanさんは「paramsルーティング」と名付けていましたね。
Router::connect( '/users/:action/:user_id/', array( 'controller' => 'users' ) );
このルーティングで /users/view/yager/ にアクセスすると、
Array ( [pass] => Array ( ) [named] => Array ( ) [action] => view [user_id] => yager [plugin] => [controller] => users [form] => Array ( ) [url] => Array ( [url] => users/view/yager/ ) )
$this->params[‘user_id’] でアクセスできるようになります。
6)Router::connect関数の第2引数でデフォルト値と固定パラメータを定義する
Router::connect関数の第2引数にはroute要素であるコントローラー/アクションの名前を入れたり、5)のparamsのデフォルト値を設定したりできますが、それ以外の独自のパラメータも定義する事ができます。
Router::connect( '/users/:action/:user_id/', array( 'controller' => 'users', 'action' => 'index', 'user_id' => 'default', 'status' => 1 ) );
この設定で、/users/view/yager/にアクセスすると、
Array ( [pass] => Array ( ) [named] => Array ( ) [action] => view [user_id] => yager [plugin] => [controller] => users [status] => 1 [form] => Array ( ) [url] => Array ( [url] => users/view/yager/ ) )
controller/action/user_id のデフォルト値は設定されていますが、URLで省略していないので使われていません。
代わりににURLで指定していない status パラメータが取得できている事がわかります。
独自のパラメータはkey-valueの連想配列でなくても可能です。
Router::connect( '/users/:action/:user_id/', array( 'controller' => 'users', 'action' => 'index', 'user_id' => 'default', 1 ) );
この設定で、/users/view/yager/にアクセスすると、
Array ( [pass] => Array ( [0] => 1 ) [named] => Array ( ) [action] => view [user_id] => yager [plugin] => [controller] => users [form] => Array ( ) [url] => Array ( [url] => users/view/yager/ ) )
パラメータ名が指定されていないので pass の方に入ります。
7)Router::connect関数の第3引数でパラメータ制約をかける
route要素(:controller, :action, :plugin)や独自のparams、あと細かい説明を飛ばしていますがワイルドカード「*」などを使って、かなり柔軟なルーティングが可能ですが、ともすればよけいなURLまでパターンに合ってしまっておかしなアクションが実行されてしまったりします。これを防ぐために、各要素に対して正規表現でパラメータのルール(制限)を書く事ができます。
例えば、5)で書いた下記ルールは、
Router::connect( '/users/:action/:user_id/', array( 'controller' => 'users' ) );
/users/view/yager/ でも /users/view/18/ でもアクセス可能です。
が、一般的には user_id に対応するDBのカラムは integer だったりすると思うので、
Router::connect( '/users/:action/:user_id/', array( 'controller' => 'users' ), array( 'user_id' => '[0-9]+' ) );
第3引数で「数字だけ」という正規表現のルールを追加すると、/users/view/18/ は対象になりますが、/users/view/yager/ は対象外になります。
そこで、上記に追加する形で、
Router::connect( '/users/:action/:user_name/', array( 'controller' => 'users' ), array( 'user_id' => '[A-Za-z0-9]+' ) );
数字以外も許容するルールを書いてやれば、/users/view/yager/ で $this->params[‘user_name’] = ‘yager’ の状態でアクセスできる事になります。
つまり、同じ/コントローラー名/アクション名/パラメータ/ でアクセスしているのに、パラメータの中身によって別の変数に値を入れる事ができるのですね。(便利なのかな、コレ?)
8)プリフィックスルーティング(Prefix Routing)
・3.4.5.5 プリフィックスルーティング(Prefix Routing)
例えば、権限の異なるユーザーに別々のURLから同じコントローラーを呼ぶ時にプリフィックスルーティングが使えます。
app/config/core.php で、
Configure::write('Routing.admin', 'admin');
のコメントアウトをはずしてから、/admin/users/view/yager/ にアクセスすると、
Error: The action admin_view is not defined in controller UsersController
というエラーがでました。
コントローラ(controller)内では、プリフィックスとして admin_ をメソッドの前につけたすべてのアクション(action)が呼び出されます。
via: Routesの設定 :: 環境設定 :: CakePHPによる開発 :: マニュアル :: 1.2 Collection :: The Cookbook
ということなので、admin_viewメソッドを追加してみる。
app/controllers/users_contoroller.php
<?php class UsersController extends AppController { var $autoRender = false; function index( ) { pr($this->params); } function view( ) { pr($this->params); } function admin_view( ) { pr($this->params); } } ?>
この状態で、再度 /admin/users/view/yager/ にアクセスすると、
Array ( [pass] => Array ( [0] => yager ) [named] => Array ( ) [controller] => users [action] => admin_view [plugin] => [prefix] => admin [admin] => 1 [form] => Array ( ) [url] => Array ( [url] => admin/users/view/yager/ ) )
となりました。つまり、下記ルーティングを追加したのと同じ状態ですね。
Router::connect( '/admin/:controller/:action/*', array( 'prefix' => 'admin', 'admin' => true ) );
試しに、core.php の Routing.admin をコメントアウトしてから、上記ルーティングを足すと同じ結果が得られました。
では、複数のprefixを定義してやるとどうなるか。
Configure::write('Routing.admin', 'admin'); Configure::write('Routing.superuser', 'superuser');
この場合、/admin/〜は想像通りでしたが、追加した/superuser/〜は動きませんでした。core.phpで設定できるのは Routing.admin だけみたいですね。
では、実際複数のprefixを作る場合はどうするかというと routes.php に書きます。
Router::connect( '/admin/:controller/:action/*', array( 'prefix' => 'admin', 'admin' => true ) ); Router::connect( '/superuser/:controller/:action/*', array( 'prefix' => 'superuser', 'superuser' => true ) );
あ、あとsuperuser用のアクションも追加しないといけないので、
app/controllers/users_contoroller.php
<?php class UsersController extends AppController { var $autoRender = false; function index( ) { pr($this->params); } function view( ) { pr($this->params); } function admin_view( ) { pr($this->params); } function superuser_view( ) { pr($this->params); } } ?>
この設定で、/superuser/users/view/yager/ にアクセスすると、
Array ( [pass] => Array ( [0] => yager ) [named] => Array ( ) [controller] => users [action] => superuser_view [plugin] => [prefix] => superuser [superuser] => 1 [form] => Array ( ) [url] => Array ( [url] => superuser/users/view/yager/ ) )
という風に、想像通りの結果になりました。
core.php の Routing.admin を使ったプレフィックスルーティングでは必ずadmin_が付いた別のアクションを呼んでしまいますが、routes.phpで書くなら同一アクションにフラグだけ渡すってこともできますね。
Router::connect( '/admin/:controller/:action/*', array( 'admin' => true ) );
まとめ
予想通り、Paginatorとか逆ルーティングまで到達する前にものすごいボリュームになってしまいました。その辺りが気になる方はakiyanさんのスライドをご参照ください。
・極める routes.php (CakePHP 1.2) : akiyan.com
というわけで、続きはakiyanさんにまかせたw