Zend_Controller_Router_Rewrite
は、標準のルータです。
ルーティングとは、URI (ベース URL から取得した URI の一部)
を展開し、どのコントローラのどのアクションが
リクエストを処理するのかを決める処理のことです。
モジュールやコントローラ、アクション、そしてその他のパラメータが
Zend_Controller_Request_Http
オブジェクトにまとめられます。
このオブジェクトを処理するのが Zend_Controller_Dispatcher_Standard
です。
ルーティングが行われるのは一度だけ、すなわちリクエストを最初に受け取ってから
最初のコントローラに処理が渡される際だけです。
Zend_Controller_Router_Rewrite
は、mod_rewrite 風の機能を
PHP だけで実現できるように設計されています。
この処理は Ruby on Rails のルーティングを多少参考にしており、
ウェブサーバの URL 書き換えに関する前提知識を必要としません。
以下の単純な mod_rewrite ルール (のいずれか) で動作するように設計されています。
RewriteEngine on RewriteRule !\.(js|ico|gif|jpg|png|css)$ index.php
あるいは
RewriteEngine on RewriteCond %{SCRIPT_FILENAME} !-f RewriteCond %{SCRIPT_FILENAME} !-d RewriteRule ^(.*)$ index.php/$1
Rewrite ルータを IIS ウェブサーバで使用するには Isapi_Rewrite を Isapi 拡張モジュールとしてインストールします。そして次のようなルールを記述します。
RewriteRule ^[\w/\%]*(?:\.(?!(?:js|ico|gif|jpg|png|css)$)[\w\%]*$)? /index.php [I]
IIS Isapi_Rewrite | |
---|---|
IIS を使用すると、 |
Lighttpd の場合は、次のようなルールを使用します。
url.rewrite-once = ( ".*\.(js|ico|gif|jpg|png|css)$" => "$0", "" => "/index.php")
Rewrite ルータを適切に使用するには、まずそのインスタンスを作成し、 次にユーザ定義のルーティングを追加し、それをコントローラに注入しなければなりません。 以下にコードの例を示します。
<?php /* ルータを作成します */ $router = $ctrl->getRouter(); // デフォルトで rewrite ルータを返します $router->addRoute( 'user', new Zend_Controller_Router_Route('user/:username', array('controller' => 'user', 'action' => 'info')) );
RewriteRouter で最も重要なのが、ユーザ定義のルーティングです。
これは、RewriteRouter の addRoute メソッドをコールして追加します。
このメソッドに、Zend_Controller_Router_Route_Interface
を実装したクラスの新しいインスタンスを渡します。
<?php $router->addRoute('user', new Zend_Controller_Router_Route('user/:username'));
Rewrite ルータには、4 種類の基本的なルーティング方式があります (そのうちのひとつは特別なものです)。
これらのルーティングは、チェインやユーザ定義のルーティング方式を作成する際に何度も使用します。 任意の設定でお好みの数のルーティングを使用することができますが、 Module ルートだけは例外です。これを使用するのは一度だけで、 もっとも汎用的なルート (デフォルト) として使用します。 個々のルーティング方式については、後ほど詳細に説明します。
addRoute への最初のパラメータはルートの名前です。 これを使用して、ルータがルートを処理します。 たとえば URL の生成などに使用します。 二番目のパラメータはルート自身となります。
注意 | |
---|---|
ルート名のもっとも一般的な使用例は、 Zend_View の url ヘルパーです。 <a href="<?= $this->url('user', array('username' => 'martel')) ?>">Martel</a>
これは |
ルーティング処理は、定義されたすべてのルートから リクエスト URI にマッチする定義を探すことによって行います。 マッチするものが見つかれば、ルートのインスタンスから変数の値が返され、 それを Zend_Controller_Request オブジェクトに注入します。 これを、後にディスパッチャやユーザが作成したコントローラで使用します。 マッチするものが見つからない場合は、チェイン内の次のルートを調べます。
定義の順番 | |
---|---|
一番最後にマッチしたルートが適用されるので、 汎用的なルートは最初に定義するようにしましょう。 |
返される値 | |
---|---|
ルーティングの結果返される値は、URL パラメータあるいは
ユーザ定義のルータのデフォルト値です。これらの値は、後ほど
|
ルートで使用される変数のうち、'module'、'controller' および 'action' の 3 つは特別な扱いとなります。これらの特殊変数は、Zend_Controller_Dispatcher がディスパッチ先のコントローラとアクションを決定するために使用されます。
特殊変数 | |
---|---|
これらの特殊変数の名前を変更することもできます。その場合は
|
Zend_Controller_Router_Rewrite がデフォルトのルートとして設定されています。
これは controller/action
形式の URI にマッチします。
さらに、パス要素の最初の部分にモジュール名を指定することができます。つまり
module/controller/action
のような URI も可能です。
また、URI にパラメータを追加した形式、つまり
controller/action/var1/value1/var2/value2
のような URI にもデフォルトで対応しています。
ルータのマッチ処理についての例を示します。
// 以下の設定を前提とします $ctrl->setControllerDirectory( array( 'default' => '/path/to/default/controllers', 'news' => '/path/to/blog/controllers', 'blog' => '/path/to/blog/controllers' ) ); モジュールのみ http://example/news module == news 無効なモジュール名は、コントローラ名として扱われます http://example/foo controller == foo モジュール + コントローラ http://example/blog/archive module == blog controller == archive モジュール + コントローラ + アクション http://example/blog/archive/list module == blog controller == archive action == list モジュール + コントローラ + アクション + パラメータ http://example/blog/archive/list/sort/alpha/date/desc module == blog controller == archive action == list sort == alpha date == desc
デフォルトのルートは、Zend_Controller_Router_Route_Module
オブジェクトを 'default' という名前 (インデックス) で
RewriteRouter に保存したものです。
これは、以下のようにして作成します。
<?php $compat = new Zend_Controller_Router_Route_Module(array(), $dispatcher, $request); $this->addRoute('default', $compat);
このデフォルトルートが不要な場合は、独自の 'デフォルト' ルートで上書きします
(つまり、'default' という名前で保存します)。
あるいは、removeDefaultRoutes()
で削除することもできます。
<?php // すべてのデフォルトルートを削除します $router->removeDefaultRoutes();
Rewrite ルータはサブディレクトリ
(例. http://domain.com/~user/application-root/
>)
内でも使用可能です。この場合、アプリケーションのベース URL
(/~user/application-root
) の自動検出が
Zend_Controller_Request_Http
によって行われ、適切に使用されます。
ベース URL の検出に失敗する場合は、
Zend_Controller_Request_Http
のメソッド setBaseUrl()
を使用してベースパスを上書き指定することができます
(??? を参照ください)。
<?php $request->setBaseUrl(/~user/application-root/);
Zend_Controller_Router_Route
はフレームワークの標準のルートです。
簡単に利用でき、柔軟なルート定義が可能です。各ルートには、まず
(静的および動的な) URL のマッピングが含まれ、
そしてデフォルト値および変数についての制限を指定して初期化します。
とある架空のアプリケーションで、コンテンツの作者情報のページが必要になったとしましょう。
ブラウザで http://domain.com/author/martel
にアクセスした際に、"martel" とかいう人についての情報を見たいわけです。
この機能を実現するためのルートは、次のようになります。
<?php $route = new Zend_Controller_Router_Route( 'author/:username', array( 'controller' => 'profile', 'action' => 'userinfo' ) ); $router->addRoute('user', $route);
Zend_Controller_Router_Route
のコンストラクタの最初のパラメータは、ルートの定義です。
これを URL にマッチさせます。ルート定義は静的な部分と動的な部分で構成され、
それをスラッシュ ('/') で連結します。
静的な部分は単なるテキスト (例. author
) です。
動的な部分を変数と呼び、変数名の前にコロンをつけて
(例. :username
) 表します。
文字の使用法 | |
---|---|
現在の実装では、(スラッシュ以外の) 任意の文字を変数名として使用できます。しかし、 PHP の変数名として使用できる文字だけを用いることを強く推奨します。 このようにしておくことで、 将来実装が変更されたときにバグを引き起こす可能性を抑えられます。 |
この例のルートは、ブラウザで
'http://domain.com/author/martel'
を指した際にマッチします。
この場合、すべての変数の値が Zend_Controller_Request
オブジェクトに注入され、ProfileController からアクセスできるようになります。
この例が返す変数は、以下のようなキーと値のペアを持つ配列となります。
<?php $values = array( 'username' => 'martel', 'controller' => 'profile', 'action' => 'userinfo' );
その後、Zend_Controller_Dispatcher
は
(デフォルトモジュールの) ProfileController
クラスにある
userinfoAction()
メソッドを実行します。変数にアクセスするには、
Zend_Controller_Action::_getParam()
あるいは
Zend_Controller_Request::getParam()
メソッドを使用します。
<?php public function userinfoAction() { $request = $this->getRequest(); $username = $request->getParam('username'); $username = $this->_getParam('username'); }
ルート定義には、特殊文字 (ワイルドカード) を含めることができます。これは '*' 記号で表します。 これを使用して、Module ルートと同様にパラメータを扱う (変数名 => 値 のペアを URI で定義する) ことができます。 次のルートは、Module ルートの挙動をまねたものです。
<?php $route = new Zend_Controller_Router_Route( ':module/:controller/:action/*', array('module' => 'default') ); $router->addRoute('default', $route);
ルートで使用するすべての変数についてデフォルト値を指定することができます。
これは、 Zend_Controller_Router_Route
のコンストラクタの 2 番目のパラメータで指定します。
このパラメータは、変数名をキーとする配列で、
対応する値にそのデフォルト値を指定します。
<?php $route = new Zend_Controller_Router_Route( 'archive/:year', array('year' => 2006) ); $router->addRoute('archive', $route);
上のルートは 'http://domain.com/archive/2005'
および
'http://example.com/archive'
のような URL にマッチします。後者の場合、変数 year にはデフォルト値である
2006 が設定されます。
この例は、year 変数をリクエストオブジェクトに注入することになります。
そしてルーティング情報が存在しない
(コントローラやアクションのパラメータが定義されていない) ので、
アプリケーションはデフォルトのコントローラのデフォルトアクションメソッド
(ともに Zend_Controller_Dispatcher_Abstract
で定義されています)
にディスパッチします。より使いやすくするには、
ルートのデフォルトとしてコントローラとアクションを定義しておく必要があります。
<?php $route = new Zend_Controller_Router_Route( 'archive/:year', array( 'year' => 2006, 'controller' => 'archive', 'action' => 'show' ) ); $router->addRoute('archive', $route);
このルートは、ArchiveController
の
showAction()
を実行します。
Zend_Controller_Router_Route
のコンストラクタの
三番目のパラメータで、変数の制約を指定することができます。
これは、正規表現で指定します。
<?php $route = new Zend_Controller_Router_Route( 'archive/:year', array( 'year' => 2006, 'controller' => 'archive', 'action' => 'show' ), array('year' => '\d+') ); $router->addRoute('archive', $route);
上の例のルートでは、year 変数の値が数値データである場合にのみ
Rewrite ルータにマッチします。つまり
http://domain.com/archive/2345
はマッチしますが
http://example.com/archive/test
はマッチしません。
この場合はチェイン内の次のルートに処理を移します。
これまでの例では、すべて動的なルートを使用していました。 つまり、特定のパターンにマッチするものについてのルートです。 しかし、時には特定のルートを固定してしまい、 わざわざ正規表現エンジンを動かしたくない場合もあるでしょう。 そんなときには静的なルートを使用します。
<?php $route = new Zend_Controller_Router_Route_Static( 'login', array('controller' => 'auth', 'action' => 'login') ); $router->addRoute('login', $route);
上のルートは http://domain.com/login
という URL
にマッチし、AuthController::loginAction()
にディスパッチされます。
デフォルトのルートや静的なルートに加えて、正規表現によるルートも使用可能です。 このルートは他のものに比べてより強力で柔軟なものですが、 多少複雑になってしまいます。そして、より高速になります。
標準のルートと同様、このルートを初期化する際にはルートの定義とデフォルトを指定する必要があります。 サンプルとして、archive ルートを作成してみましょう。 これは先ほど定義したものとほぼ同じですが、今回は Regex ルートを使用しています。
$route = new Zend_Controller_Router_Route_Regex( 'archive/(\d+)', array( 'controller' => 'archive', 'action' => 'show' ) ); $router->addRoute('archive', $route);
定義された正規表現のパターンが、リクエストオブジェクトに注入されます。
上の例では、http://domain.com/archive/2006
がマッチした後の結果の値は次のような配列になります。
$values = array( 1 => '2006', 'controller' => 'archive', 'action' => 'show' );
注意 | |
---|---|
ルータとのマッチングを行う前に、URL の先頭と最後のスラッシュは取り除かれます。
結果として、URL |
注意 | |
---|---|
行頭と行末を表す文字 (それぞれ '^' および '$') が、すべての式の前後に自動的に付加されます。 したがって、これらは正規表現で指定する必要はありません。 |
注意 | |
---|---|
このルートクラスは、区切り文字として |
定義されたサブパターンの内容は、通常通りの方法で取得できます。
public function showAction() { $request = $this->getRequest(); $year = $request->getParam(1); // $year = '2006'; }
注意 | |
---|---|
このキーは、文字列 ('1') ではなく数値の 1 であることに注意しましょう。 |
このルートは、標準のルートとまったく同様に動作するわけではありません。 'year' のデフォルトが設定されていないからです。 また、year のデフォルトを設定してこれをオプション扱いにしたとしても、 最後のスラッシュをどうするかという問題が残ります。 これを解決するには、year 部をスラッシュを含めてオプションにし、 その数値部のみを取得するようにします。
$route = new Zend_Controller_Router_Route_Regex( 'archive(?:/(\d+))?', array( 'year' => '2006', 'controller' => 'archive', 'action' => 'show' ) ); $router->addRoute('archive', $route);
まだ問題が残っていることにおそらくお気づきでしょう。 パラメータとして数値のキーを使用するのはなかなか難しく、 長い目で見れば問題を引き起こす可能性が高くなります。 そこで三番目のパラメータの登場です。 このパラメータは、正規表現サブパターンとパラメータ名のキーを関連付けます。 簡単な例を見てみましょう。
$route = new Zend_Controller_Router_Route_Regex( 'archive/(\d+)', array( 'controller' => 'archive', 'action' => 'show' ), array( 1 => 'year' ) ); $router->addRoute('archive', $route);
この結果は次のようになり、これがリクエストオブジェクトに格納されます。
$values = array( 'year' => '2006', 'controller' => 'archive', 'action' => 'show' );
関連付けは両方の方法で定義でき、任意の環境 (例. Zend_Config) で動作します。 キーには変数名あるいはサブパターン番号のいずれかを含めることができます。
$route = new Zend_Controller_Router_Route_Regex( 'archive/(\d+)', array( ... ), array(1 => 'year') ); // あるいは $route = new Zend_Controller_Router_Route_Regex( 'archive/(\d+)', array( ... ), array('year' => 1) );
注意 | |
---|---|
サブパターンのキーは整数値でなければなりません。 |
リクエストの値から数値キーが消え、代わりに名前がつけられたことに注目しましょう。 もちろん、お望みなら数値での指定と名前での指定を共用することもできます。
$route = new Zend_Controller_Router_Route_Regex( 'archive/(\d+)/page/(\d+)', array( ... ), array('year' => 1) );
この結果、リクエスト内には数値キーと名前つきキーが共存することになります。
たとえば、URL http://domain.com/archive/2006/page/10
は次のような値になります。
$values = array( 'year' => '2006', 2 => 10, 'controller' => 'archive', 'action' => 'show' );
正規表現を簡単に反転させることはできないので、 url ヘルパーやこのクラスのメソッドを使用するには 逆の URL を準備しておく必要があります。 逆方向のパスは sprintf() 形式の文字列で表し、 コンストラクタの四番目のパラメータとして指定します。
$route = new Zend_Controller_Router_Route_Regex( 'archive/(\d+)', array( ... ), array('year' => 1), 'archive/%s' );
これまで説明してきたことは、すべて標準のルートオブジェクトでも可能なことです。
それでは、Regex ルートを使用するメリットはいったい何なのでしょう?
これを使用すると、あらゆる形式の URL を制約なしに定義することができます。
仮に、あなたが blog を持っており
http://domain.com/blog/archive/01-Using_the_Regex_Router.html
のような URL を作成したいと考えたとしましょう。
このパスの最後の要素 01-Using_the_Regex_Router.html
から記事の ID とタイトル/説明 を取得するにはどうしたらいいでしょうか?
標準のルートでは不可能でしょう。Regex ルートを使用した場合は、
次のようにすることができます。
$route = new Zend_Controller_Router_Route_Regex( 'blog/archive/(\d+)-(.+)\.html', array( 'controller' => 'blog', 'action' => 'view' ), array( 1 => 'id', 2 => 'description' ), 'blog/archive/%d-%s.html' ); $router->addRoute('blogArchive', $route);
regex ルートは標準のルートよりはるかに柔軟性があるということが、 ここからもわかります。
新しいルートを追加する際に、
いちいちコードを書き換えるのではなく設定ファイルの変更で対応できると便利でしょう。
そんなときには addConfig()
メソッドを使用します。基本的な使用法は、
まず Zend_Config 互換の設定を作成し、それをコードに読み込み、
そして RewriteRouter に渡すことです。
例として、次のような INI ファイルを考えてみましょう。
[production] routes.archive.route = "archive/:year/*" routes.archive.defaults.controller = archive routes.archive.defaults.action = show routes.archive.defaults.year = 2000 routes.archive.reqs.year = "\d+" routes.news.type = "Zend_Controller_Router_Route_Static" routes.news.route = "news" routes.news.defaults.controller = "news" routes.news.defaults.action = "list" routes.archive.type = "Zend_Controller_Router_Route_Regex" routes.archive.route = "archive/(\d+)" routes.archive.defaults.controller = "archive" routes.archive.defaults.action = "show" routes.archive.map.1 = "year" ; あるいは: routes.archive.map.year = 1
上の INI ファイルを、次のようにして
Zend_Config
オブジェクトに読み込みます。
$config = new Zend_Config_Ini('/path/to/config.ini', 'production'); $router = new Zend_Controller_Router_Rewrite(); $router->addConfig($config, 'routes');
上の例では、INI ファイルの 'routes' セクションを使用してルートを決めるよう、
ルータに指定しています。このセクションの第一レベルのキーがルート名に対応します。
上の例だと 'archive' と 'news' がこれにあたります。
ルートの各エントリには、最低限 'route' エントリとひとつ以上の 'defaults'
エントリが必要となります。また、オプションでひとつ以上の 'reqs'
('required' の略) も指定できます。ここで指定したものが、それぞれ
Zend_Controller_Router_Route_Interface
オブジェクトに対する引数となります。オプションのキー 'type' を使用すると、
特定のルートで使用するルートクラスの型を指定できます。デフォルトでは、これは
Zend_Controller_Router_Route
となります。上の例では、
'news' ルートで
Zend_Controller_Router_Route_Static
を使用するようにしています。
標準の rewrite ルータには、必要となるであろう機能のほとんどが組み込まれています。 もし新しいルータ型を作成する必要があるとすれば、 それは既存のルートに対して新しい機能を追加したり機能を変更したりしたい場合くらいでしょう。
どこかで、既存のものとはまったく異なるルーティング処理が必要となったとしましょう。
そんな場合には Zend_Controller_Router_Interface
を使用します。これは、ルータとして最低限必要なひとつのメソッドのみを定義したインターフェイスです。
method.
<?php interface Zend_Controller_Router_Interface { /** * @param Zend_Controller_Request_Abstract $request * @throws Zend_Controller_Router_Exception * @return Zend_Controller_Request_Abstract */ public function route(Zend_Controller_Request_Abstract $request); } ?>
ルーティング処理は、システムが最初にリクエストを受け取った際に一度だけ行われます。 ルータの役割は、リクエストの内容に応じてコントローラやアクションとオプションパラメータを決定し、 それをリクエストに設定することです。 その後、リクエストオブジェクトがディスパッチャに渡されます。 ルートに対応するディスパッチトークンがない場合は、ルータは何も行いません。