元記事はこちら。
By Frank
CakePHPのルーティングはとてもパワフルな機能で、URLを綺麗に見せるのに使われています。ドキュメントが用意されているにもかかわらず把握するのが難しいようであると、IRCの#cakephpサポートチャンネルの経験から知りました。この記事ではその状況に一石を投じ、ルーティングの主な機能について解説します。コメント欄からのサポート要請はお断りします。サポートはGoogleグループかIRCのチャンネル#cakephpで得られます。
ルートの適用
ルーティングが使用されるべき一般的なケースは、URLを綺麗に見せるためだけにコントローラが命名される場合でしょう。ほかにも検索エンジンからのリンクがデッドリンクにならないように過去のサイトとマッチするURLにするといった使い方もあります。
誤った考えとして、「ルーティングは適切なコントローラとアクションにリダイレクトする」というものがありますが、これは正確ではありません。ルーティングは「アプリケーションの特定のコントローラとアクションにマップする」ものです。ブラウザからそのURLを入力すると、あなたが指定したURLのままになっているはずです。リダイレクトしていません。
デフォルトのルーティング
CakePHPにはいくつかのデフォルトのルーティング設定があります。いくつかはコアにあり、ほかは APP/config/routes.php にあります。コアにあるものは基本的なURLを動作させるためのものです(たとえば、/controller/action のように)。ここではroutes.phpのルーティング設定を取り上げます。
通常のセットアップでは2つのルーティングがあります。最初のものはドメインのルートにアクセスした場合のメインページへのルーティングです。
<?php Router::connect('/', array('controller' => 'pages', 'action' => 'display', 'home')); ?> |
お分かりのように、ルート(最初のパラメータ)は「/」だけで、ドメインのルートを指します。二番目のパラメータは初期値を設定します。この例ではPagesController::display()(Pagesコントローラのdisplayアクション)にマップされ、最初のパラメータは’home’となります。
routes.phpの2番目のルーティング設定は静的ページを指すものです。*(ワイルドカード)は「/pages/」以降の全ての文字列にマッチします。「/pages/」以降の文字列はdisplayアクションのパラメータとして渡されます。
<?php Router::connect('/pages/*', array('controller' => 'pages', 'action' => 'display')); ?> |
たとえば、ブラウザに「/pages/about」と入力したとすると、CakePHPはPagesController::display()に、最初のパラメータに「about」をセットして実行します。「/pages/about/something/else」というURLを試すと、PagesController::display()に3つのパラメータ(「about」「something」「else」)をセットして実行します。
ルーティングを変更すると、Webサイトのリンクが全て変更されます。一つのファイルを編集すればリンクを制御できる、ということです。HtmlHelperを使用していることと、リンクの生成に配列ベースの設定を行っていることが条件です。この素晴らしい機能は「リバースルーティング」といいます。この記事では扱いませんが、さらなる情報はhttp://debuggable.com/posts/new-router-goodies:480f4dd6-4d40-4405-908d-4cd7cbdd56cb(英語)で得ることができます。
名前付きのルーティングを作成する
最初の部分で、ルーティングはURLの仲介役をすることについて例を示しました。その方法についてさらに述べます。
ここでは、ArticlesControllerに2つのアクション、「index」と「show」を作成したと仮定しましょう。「index」アクションはパラメータをとらず、「show」アクションは$idというパラメータを一つ取ります。以下のようになります。
<?php class ArticlesController extends AppController { public function index() { // Do some magic. } public function show($id = null) { // Do some magic. } } ?> |
さて、デフォルトのルーティング(/articles/show/69)の代わりに、「/writings/show/69」としてみましょう。以下のルート設定で実装できます。
<?php Router::connect('/writings/:action/*', array('controller' => 'articles')); ?> |
「action」という要素の使い方に注目してください。名前付き要素は先頭に「:」を付けます。この名前付き要素はコントローラの Controller::$params に自動的に渡され、Viewクラスに渡されます。通常、配列ベースのURL設定を行うときにセットする変数名がそのまま使用でき、「:action」、「:controller」、「:plugin」などを使うことができます。
自分で作成した名前付き要素を使用することもできます。URLに何でも入れておくことができます。たとえば、ユーザ名をURLに入れておき、「/writings/phally/show/69」とマッチするようにできます。以下のように設定します。
<?php Router::connect('/writings/:username/:action/*', array('controller' => 'articles')); ?> |
このリンクを生成するには、以下のように記述します。
<?php echo $html->link('Article 69', array( 'controller' => 'articles', 'action' => 'show', 'username' => 'phally', 69 )); ?> |
上で示したように、名前付きパラメータを名前付き要素として値を設定しておくことができます。ルートを使用しなければ、このコードは「/articles/show/69/username:phally」というリンクを生成します。
アクションにパラメータを渡す
さて、URLで名前付き要素を使用できるようになり、コントローラのアクションに渡せるようになりました。そこで、ルート設定を賢くして、パラメータの並び順を設定しておきましょう。以下のルート設定を使用します。
<?php Router::connect('/writings/:username/:action/:id/*', array('controller' => 'articles')); ?> |
「:id」を名前付き要素として追加しました。こんなふうにアクションにパラメータを渡すことができ、もうController::$paramsの中を精査しなくてもいいなんて、クールじゃありませんか?ルート設定を以下のように拡張します。
<?php Router::connect( '/writings/:username/:action/:id/*', array( 'controller' => 'articles' ), array( 'pass' => array( 'id', 'username' ) ) ); ?> |
このルート設定で、CakePHPに$Controller->show(69, ‘phally’)のようにアクションを呼び出させることができ、アクションは以下のようになります。
<?php public function show($id = null, $username = null) { // $id == 69; // $username == 'phally'; } ?> |
素晴らしいですね!
ルート設定の順序は重要
PHPは先頭から末尾にかけて読み込むので、ルート設定の順序は重要です。URLにどんな影響が及ぶのか見てみましょう。この部分では先の部分で示したいろいろな設定を例として使用します。usernameやパラメータ渡しについては忘れてください。
これまで扱ったルート設定はArticlesController::show()に対するものだけでした。index()アクションにはまた別の話があります。ルート設定がなければ、以下のコードは「/articles」を生成します。
<?php echo $html->link('Article 69', array( 'controller' => 'articles', 'action' => 'index' ) ); ?> |
しかしルート設定をすると、以下のコードは「/writings」ではなく、「/writings/index」を生成します。修正するには、2つのルート設定が必要です。
<?php Router::connect('/writings', array('controller' => 'articles')); // 重要 Router::connect('/writings/:action/*', array('controller' => 'articles')); // 全てキャッチする ?> |
アクションのデフォルトは「index」なのでこのケースは考慮に入れなくて良いです。パラメータのあるURLは最初のルートにはマッチせず、二番目のものにマッチします。パラメータのないURLは最初のルートにマッチし、二番目のルートへは到達しませんので、マッチしません。二番目のルートはパラメータのないURLはにもマッチしますが、到達しませんので問題ありません。ルートの順序を変更するとどうなるか、見てみましょう。
<?php Router::connect('/writings/:action/*', array('controller' => 'articles')); // 全てキャッチする Router::connect('/writings', array('controller' => 'articles')); // 重要 ?> |
この順序では、いずれのURLも最初のルートにマッチし、生成されるリンクは「/writing/index」となります。
お分かりのように最適な方法は、重要なルートを最初に設定し、そのあとで最初の設定でキャッチできなかったものを全てキャッチするようにURLを設定する、ということです。
マッチする条件をセットアップする
指定したいものだけにマッチするようにルートを設定するには、Router::connect()の第三引数に条件をセットします。ここではまた別の例を取り上げます。静的ページへのリンクで不満が上がる時があり(たとえば、「/pages/about」や「/pages/terms」のように)、「/about」や「/terms」にしたい、というのです。実装には2つの方法があります。一つはDRY(Don’t Repeat Yourself: 同じことを繰り返さない)ではありませんが、全ての静的ページに対してルート設定を行う方法です。
<?php Router::connect('/about', array('controller' => 'pages', 'action' => 'display', 'about')); Router::connect('/terms', array('controller' => 'pages', 'action' => 'display', 'terms')); ?> |
あまり美しくありません。さて、これらを一つのルート設定に置き換え、名前付き要素をこれら2つのキーワードにマッチするように結びつけましょう。
<?php Router::connect( '/:pagename', array( 'controller' => 'pages', 'action' => 'display' ), array( 'pagename' => 'about|terms', 'pass' => array( 'pagename' ) ) ); ?> |
スコープを使って全てキャッチするように設定しました。リンクの生成方法を調整する必要があります。元のコードは以下のようであるとします。
<?php echo $html->link('about', array('controller' => 'pages', 'action' => 'display', 'about')); ?> |
名前付き要素を定義して以下のように調整します。
<?php echo $html->link('about', array('controller' => 'pages', 'action' => 'display', 'pagename' => 'about')); ?> |
条件には上記の例のように簡易なものから複雑な正規表現まで何でも指定できます。CakePHPのドキュメントに良い例があります。
http://book.cakephp.org/ja/view/46/Routesの設定
訳者注:1.3のドキュメントはこちら→http://book.cakephp.org/ja/view/945/ルートの設定
さらに発展的で役立つ例はhttp://dsi.vozibrale.com/articles/view/advanced-routing-with-cakephp-one-example(英語)にあります。
訳者注:翻訳作成時点で上記リンクはつながりませんでした。。。
名前付きパラメータはルーティングを壊す?
もしカスタムの名前付きパラメータを使用しているなら、CakePHPで適切な設定をしない限りルーティングを壊す可能性があります。Router::connectNamed()はその役目を果たします。このメソッドはCakePHPに名前付きパラメータの存在を知らせるものです。デフォルトではページ付けに必要な名前付きパラメータのみ関連付けされています。以下のように名前付きパラメータをセットできます。
<?php Router::connectNamed(array('username', 'email')); ?> |
この方法ではデフォルトのページ付けの設定を上書きしてしまいますので注意してください。デフォルトに追加するには、以下のようにします。
<?php Router::connectNamed(array('username', 'email'), array('default' => true)); ?> |
この設定で、「username」「email」「page」を解析するようになります。さらに情報や例を見たい場合はAPIドキュメントを参照してください。
http://api.cakephp.org/class/router#method-RouterconnectNamed
訳者注:1.3のAPIドキュメントはこちら→http://api13.cakephp.org/class/router#method-RouterconnectNamed
アドミンやプレフィックスルーティング
CakePHP1.3では、アドミンルーティングはプレフィックスルーティングに置き換えられました(ここで説明されています。→http://bakery.cakephp.org/articles/nate/2009/07/14/secrets-of-admin-routing)。URLやアクションで使用するプレフィックスのリストを用意します。同じ名前のプレフィックスの異なるアクションを作成することができます。
<?php Configure::write('Routing.prefixes', array('author', 'moderator', 'admin')); ?> |
<?php class ArticlesController extends AppController { public function author_edit($id = null) { } public function moderator_edit($id = null) { } public function admin_edit($id = null) { } } ?> |
ルートでは「’admin’ => true」「’moderator’ => true」を設定しておくことができます。このセットアップは異なるセクションのレイアウトの変更やユーザ権限をより正確に行うことができます。
ルートのデバッグ
ルートのデバッグを行うには、DebugKitを使用するのが簡単です。この素晴らしいツールで、どのルーティングにマッチしたか、またパラメータの存在チェックが行えます。知りたいことをdebug()で画面に表示するよりはるかに便利です。DebugKitは http://github.com/cakephp/debug_kitにあります。
では、頑張ってください。この記事のコメント欄からのサポート要請はお断りします。CakePHPのドキュメントや、Googleグループや、IRCチャンネルを活用してください。
[p] Phally