I'm attending CakeFest 2010!

3.4.5 Routesの設定

Routingは、URLをコントローラのアクションに結びつける(map) する機能です。URL の見ばえをよくし(pretty URL)、より柔軟に設定できるよう、CakePHP に備わっています。routes を使うために Apache の mod_rewrite が必ず必要というわけではありませんが、mod_rewrite を使用すると、アドレスバーに表示される URL が整ったものになります。

後に説明するように、CakePHP 1.2 の Routes は機能が拡張され、非常にパワフルです。

3.4.5.1 デフォルトのルーティング

独自のルートを設定する前に、CakePHP のデフォルトのルートについて知っておく必要があります。CakePHP のデフォルトルーティングによって、どんなアプリケーションでもうまく動くようになっています。URL の中に、アクション名を置くことで、アクションに直接アクセスすることができます。コントローラのアクションに URL を使うことでパラメータを渡すこともできます。

    デフォルトのroutesのURLパターン:
    http://example.com/コントローラ名/アクション名/パラメータ1/パラメータ2/パラメータ3

/posts/view という URL は、PostsController の view() アクションにマップされます。 /products/view_clearance は、ProductsController の viewClearance() アクションにマップされます。URL でアクション名が指定されていない場合には、index() メソッドが用いられます。

デフォルトのルーティングでは、URLを使ってアクションにパラメータを渡すこともできます。例えば、/posts/view/25 というリクエストは、PostsController 上で view(25) として呼ぶのと同じことになります。

3.4.5.2 名前付きのパラメータ

CakePHP 1.2 の新機能には、パラメータに名前を付けられる、というものがあります。URL を使ってパラメータに名前を付けて、その値を渡すことができます。/posts/view/title:first+post/category:generalというリクエストでは、PostsController の view() アクションが呼ばれます。このアクションでは、title と category というパラメータの値を、それぞれ、$this->passedArgs[‘title’] と $this->passedArgs[‘category’] として受け取ることができます。

参考例としていくつか挙げてみます。

デフォルトのルーティングを使用した、URL からコントローラへのアクションマッピング:
    
URL: /monkeys/jump
Mapping: MonkeysController->jump();
 
URL: /products
Mapping: ProductsController->index();
 
URL: /tasks/view/45
Mapping: TasksController->view(45);
 
URL: /donations/view/recent/2001
Mapping: DonationsController->view('recent', '2001');

URL: /contents/view/chapter:models/section:associations
Mapping: ContentsController->view();
$this->passedArgs['chapter'] = 'models';
$this->passedArgs['section'] = 'associations';

3.4.5.3 Routes の定義

独自ルートの定義により、アプリケーションを指定した URL で動作させることができるようになります。独自のルーティングは、/app/config/routes.php ファイルで Router::connect() メソッドを使用して定義します。

connect() メソッドは3つのパラメータからなっています。対応させたい URL、独自のルート要素のデフォルトの値、要素と対応させるための正規表現ルールです。

ルート定義の基本的な形式は次のようになります。:

Router::connect(
    'URL',
    array('paramName' => 'デフォルト値'),
    array('paramName' => '正規表現')
)
  1. Router::connect(
  2. 'URL',
  3. array('paramName' => 'デフォルト値'),
  4. array('paramName' => '正規表現')
  5. )

最初のパラメータで、制御する URL をルータに伝えます。URL は、通常のスラッシュで分けられた文字列です。しかし、ワイルドカード(*)や、独自の route 要素(コロンで始まる変数名)を含むことができます。ワイルドカードで対応させたい URL を指定し、route 要素の指定で、コントローラアクションのためのパラメータを集めることができます。

URLを指定した後、connect() の次の2つのパラメータを使って、リクエストが対応した場合に何を行うのかを CakePHP に伝えます。2番目のパラメータは連想配列です。配列のキーは、URL の route 要素にちなんだものか、デフォルト要素(:controller, :action, :plugin)にしてください。配列内の値は、それらのキーに対するデフォルト値です。connect() の3番目のパラメータを考える前に、基本的な例をいくつか見てみましょう。

Router::connect(
    '/pages/*',
    array('controller' => 'pages', 'action' => 'display')
);
  1. Router::connect(
  2. '/pages/*',
  3. array('controller' => 'pages', 'action' => 'display')
  4. );

CakePHP の routes.php ファイルの中(40行目)に、このルートが書かれています。このルートは、/pages/ ではじまるすべての URL にマッチし、それを PagesController();display() メソッドに渡します。例えば、 /pages/products というリクエストは、PagesController->display('products') にマップされます。

Router::connect(
    '/government',
    array('controller' => 'products', 'action' => 'display', 5)
);
  1. Router::connect(
  2. '/government',
  3. array('controller' => 'products', 'action' => 'display', 5)
  4. );

この2つ目の例は、connect() の2番目のパラメータでデフォルトパラメータを定義する方法を示しています。それぞれのお客様向けに異なる製品がある場合などに route を作成することを考えることができるでしょう。この例では、/products/display/5 としなくても、/government という URL でアクセスできるようになります。

一般的な Router の他の使い方は、コントローラの別名(alias)を定義することです。通常の /users/someAction/5 という URL の代わりに、/cooks/someAction/5 でアクセスさせたいとしましょう。このようなルートの設定は、次のようにすることで簡単に実現できます。

Router::connect(
    '/cooks/:action/*', array('controller' => 'users', 'action' => 'index')
);
  1. Router::connect(
  2. '/cooks/:action/*', array('controller' => 'users', 'action' => 'index')
  3. );

この設定は、 Router が /cooks/ で始まる URL を、全て users コントローラに送ることを表しています。

URL が生成されるときも、ルートの設定が適用されます。上述の例にあるルートが最初にマッチした場合、URL として array('controller' => 'users', 'action' => 'someAction', 5) のように指定すると、/cooks/someAction/5 というURL が生成されます。

ルートの中で独自の名前を持つパラメータを使うつもりならば、Router::connectNamed を用いて Router にそれを伝える必要があります。上述の例にあるルートに /cooks/someAction/type:chef のようなURLをマッチさせたい場合は、次のようにします。

Router::connectNamed(array('type'));
Router::connect(
    '/cooks/:action/*', array('controller' => 'users', 'action' => 'index')
);
  1. Router::connectNamed(array('type'));
  2. Router::connect(
  3. '/cooks/:action/*', array('controller' => 'users', 'action' => 'index')
  4. );

さらに柔軟な追加機能として、カスタム route 要素を指定できます。この機能により、コントローラアクション用のパラメータが、URL のどこにあるのかを定義できるようになります。リクエストがあると、このカスタム route 要素は、コントローラの $this->params の中に入ります。これは、パラメータに名前を付けるのとは異なる仕方で扱われます。違いに注意してください。パラメータに名前を付けた(/コントローラ/アクション/名前:値)ものは、$this->passedArgs の中に入りますが、カスタム route 要素のデータは、$this->params の中に入ります。カスタム route 要素を定義する際には、正規表現によって、URL が正しく作られているか、作られていないかを CakePHP が判断できるようにします。

Router::connect(
    '/:controller/:id',
    array('action' => 'view'),
    array('id' => '[0-9]+')
);
  1. Router::connect(
  2. '/:controller/:id',
  3. array('action' => 'view'),
  4. array('id' => '[0-9]+')
  5. );

これは、/コントローラ名/id という形で、どんなコントローラからでもモデルを表示(view)できるようにする例です。connect() に指定されている URL には2つの route 要素があります。controller と :id です。:controller 要素は、CakePHP のデフォルト route 要素です。ルータは URL 内のコントローラ名を見つけることができます。:id 要素はカスタム route 要素なので、connect() の3番目のパラメータ内の正規表現で、どうやってマッチさせるのかを明示する必要があります。こうすることで、CakePHP はアクション名ではないと区別でき、URL 内の ID を見つけることができます。

この route が定義されると、/apples/5 というリクエストは、/apples/view/5 としたのと同じことになります。どちらも、ApplesController の view() メソッドを呼びます。view() メソッド内では、$this->params['id'] を使って渡された ID を知ることができます。

この例も知れば、routing の達人です。

Router::connect(
    '/:controller/:year/:month/:day',
    array('action' => 'index', 'day' => null),
    array(
        'year' => '[12][0-9]{3}',
        'month' => '0[1-9]|1[012]',
        'day' => '0[1-9]|[12][0-9]|3[01]'
    )
);
  1. Router::connect(
  2. '/:controller/:year/:month/:day',
  3. array('action' => 'index', 'day' => null),
  4. array(
  5. 'year' => '[12][0-9]{3}',
  6. 'month' => '0[1-9]|1[012]',
  7. 'day' => '0[1-9]|[12][0-9]|3[01]'
  8. )
  9. );

ややこしいものですが、route が非常に強力であることを示す例です。この URL では、4つの要素があります。最初のものはよく知っているもので、まずコントローラ名が来ることを CakePHP に伝えます。

次に、いくつかのデフォルト値を指定します。コントローラ名に関係なく、index() アクションが呼ばれるようにします。そして、day パラメーター(URL の4つ目の要素)のデフォルト値を null として、これがオプションであることを設定します。

最後に、年(year)、月(month)、日(day)の数値にマッチする正規表現を指定します。

設定すると、この route は、/articles/2007/02/01、/posts/2004/11/16、/products/2001/05 (day パラメータは、オプション)といった URL にマッチするようになります。対応するコントローラの index() アクションに渡し、$this->params の中にはカスタム指定した日付パラメータを入れます。

3.4.5.4 アクションにパラメータを渡す

アクション(action)が次のように定義されていて、$this->params['id'] の代わりに $articleID を使用して引数を受け取りたいと仮定します。ただ、Router::connect() の第3引数に特別な配列(array)を追加するだけです。

// some_controller.php
function view($articleID = null, $slug = null) {
    // ここに何かしらコードを書く
}

// routes.php
Router::connect(
    // E.g. /blog/3-CakePHP_Rocks
    '/blog/:id-:slug',
    array('controller' => 'blog', 'action' => 'view'),
    array(
        // これは単にアクション(action)で ":id" を $articleID にマッピングするため、順番は重要です。
        'pass' => array('id', 'slug'),
        'id' => '[0-9]+'
    )
)
  1. // some_controller.php
  2. function view($articleID = null, $slug = null) {
  3. // ここに何かしらコードを書く
  4. }
  5. // routes.php
  6. Router::connect(
  7. // E.g. /blog/3-CakePHP_Rocks
  8. '/blog/:id-:slug',
  9. array('controller' => 'blog', 'action' => 'view'),
  10. array(
  11. // これは単にアクション(action)で ":id" を $articleID にマッピングするため、順番は重要です。
  12. 'pass' => array('id', 'slug'),
  13. 'id' => '[0-9]+'
  14. )
  15. )

これで逆のルーティング機能が追加されたので、以下のように url 配列に渡すことができます。ルートで定義されているので、 Cake は URL をどのように作成したらよいかがわかっています。

// view.ctp
// これは /blog/3-CakePHP_Rocks へのリンクを返すでしょう
<?= $html->link('CakePHP Rocks', array(
    'controller' => 'blog',
    'action' => 'view',
    'id' => 3,
    'slug' => Inflector::slug('CakePHP Rocks')
)) ?>
  1. // view.ctp
  2. // これは /blog/3-CakePHP_Rocks へのリンクを返すでしょう
  3. <?= $html->link('CakePHP Rocks', array(
  4. 'controller' => 'blog',
  5. 'action' => 'view',
  6. 'id' => 3,
  7. 'slug' => Inflector::slug('CakePHP Rocks')
  8. )) ?>

3.4.5.5 プレフィックスルーティング(Prefix Routing)

たいていのアプリケーションには、権限のあるユーザだけが情報を変更できる管理画面が必要です。そのために /admin/users/edit/5 のような特別な URL を準備することがよくあります。CakePHP では、コア設定ファイル内の Routing.admin に管理パスを設定して admin ルーティングを有効にできます。

Configure::write('Routing.admin', 'admin');
  1. Configure::write('Routing.admin', 'admin');

コントローラ(controller)内では、プレフィックスとして admin_ をメソッドの前につけたすべてのアクション(action)が呼び出されます。users を例にすると、/admin/users/edit/5 というURLへのアクセスで、UsersControlleradmin_edit メソッドが、引数5で呼び出されます。このビューファイルはapp/views/users/admin_edit.ctpとなります。

URL /admin を、pagesコントローラの admin_indexアクションにマップするには次のような route を使います。

Router::connect('/admin', array('controller' => 'pages', 'action' => 'index', 'admin' => true)); 
  1. Router::connect('/admin', array('controller' => 'pages', 'action' => 'index', 'admin' => true));

複数のプレフィックスを使うように Router を設定することもできます。

Router::connect('/profiles/:controller/:action/*', array('prefix' => 'profiles', 'profiles' => true)); 
  1. Router::connect('/profiles/:controller/:action/*', array('prefix' => 'profiles', 'profiles' => true));

profiles に対するすべての呼び出しは、profiles_ プレフィックスのついたメソッドを探して呼び出します。usersを例にとると、/profiles/users/edit/5 などの構造を持つURLは、UsersControllerprofiles_edit メソッドを呼び出します。

:controllerと似ている:prefixという名前のエレメントを使って、ワイルドカードプレフィックスルーティングをすることができます。以下のルートを使えば、apiコントローラのuser_createアクションに、/api/user/createというURLをマッピングすることができます。

Router::connect('/api/:prefix/:action', array('controller' => 'api'));
  1. Router::connect('/api/:prefix/:action', array('controller' => 'api'));

その他、apiコントローラのquestion_createアクションに/api/question/create、apiコントローラのquestion_editアクションに/api/question/editをマッピングすることもできます。

また、重要な点として、HTML ヘルパーを使用してリンクを作成するなら、プレフィックスを用いた呼び出しの管理も楽になります。HTML ヘルパーを使ったリンクの例は次のようになります。

echo $html->link('Edit your profile', array('profiles' => true, 'controller' => 'users', 'action' => 'edit', 'id' => 5)); 
  1. echo $html->link('Edit your profile', array('profiles' => true, 'controller' => 'users', 'action' => 'edit', 'id' => 5));

この方法を使うと、複数のプレフィックスルートを定義することができ、柔軟な URL 構造を持つアプリケーションを作成できます。

3.4.5.6 Plugin routing

このセクションには保留されている変更があります. More information about translations

Plugin routing uses the plugin key. You can create links that point to a plugin by adding the plugin key to your url array.

echo $html->link('New todo', array('plugin' => 'todo', 'controller' => 'todo_items', 'action' => 'create'));
  1. echo $html->link('New todo', array('plugin' => 'todo', 'controller' => 'todo_items', 'action' => 'create'));

Conversely if the active request is a plugin request and you want to create a link that has no plugin you can do the following.

echo $html->link('New todo', array('plugin' => null, 'controller' => 'users', 'action' => 'profile'));
  1. echo $html->link('New todo', array('plugin' => null, 'controller' => 'users', 'action' => 'profile'));

By setting plugin => null you tell the Router that you want to create a link that is not part of a plugin.

3.4.5.7 File extensions

このセクションには保留されている変更があります. More information about translations

To handle different file extensions with your routes, you need one extra line in your routes config file:

Router::parseExtensions('html', 'rss');
  1. Router::parseExtensions('html', 'rss');

This will tell the router to remove any matching file extensions, and then parse what remains.

If you want to create a URL such as /page/title-of-page.html you would create your route as illustrated below:

	Router::connect(
		'/page/:title',
		array('controller' => 'pages', 'action' => 'view'),
		array(
			'pass' => array('title')
		)
	);	
  1. Router::connect(
  2. '/page/:title',
  3. array('controller' => 'pages', 'action' => 'view'),
  4. array(
  5. 'pass' => array('title')
  6. )
  7. );

Then to create links which map back to the routes simply use:

$html->link('Link title', array('controller' => 'pages', 'action' => 'view', 'title' => Inflector::slug('text to slug', '-'), 'ext' => 'html'))
  1. $html->link('Link title', array('controller' => 'pages', 'action' => 'view', 'title' => Inflector::slug('text to slug', '-'), 'ext' => 'html'))