# 標準のルータ

## 導入

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|html)$ index.php

あるいは (推奨)

    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} -s [OR]
    RewriteCond %{REQUEST_FILENAME} -l [OR]
    RewriteCond %{REQUEST_FILENAME} -d
    RewriteRule ^.*$ - [NC,L]
    RewriteRule ^.*$ index.php [NC,L]

Rewrite ルータを IIS ウェブサーバ (バージョン <= 7.0) で使用するには Isapi_Rewrite を Isapi 拡張モジュールとしてインストールします。そして次のようなルールを記述します。

    RewriteRule ^[\w/\%]*(?:\.(?!(?:js|ico|gif|jpg|png|css|html)$)[\w\%]*$)? /index.php [I]

IIS Isapi_Rewrite

IIS を使用すると、`$_SERVER['REQUEST_URI']` が存在しないか空の文字列に設定されます。このような場合、 Zend_Controller_Request_Http は `$_SERVER['HTTP_X_REWRITE_URL']` の値を使用します。これは Isapi_Rewrite 拡張モジュールが設定します。

IIS 7.0 ではネイティブの URL リライトモジュールが登場しました。 次のように設定して使います。

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
         <system.webServer>
             <rewrite>
                 <rules>
                     <rule name="Imported Rule 1" stopProcessing="true">
                         <match url="^.*$" />
                         <conditions logicalGrouping="MatchAny">
                             <add input="{REQUEST_FILENAME}"
                                 matchType="IsFile" pattern=""
                                 ignoreCase="false" />
                             <add input="{REQUEST_FILENAME}"
                                 matchType="IsDirectory"
                                 pattern="" ignoreCase="false" />
                         </conditions>
                         <action type="None" />
                     </rule>
                     <rule name="Imported Rule 2" stopProcessing="true">
                         <match url="^.*$" />
                         <action type="Rewrite" url="index.php" />
                     </rule>
                 </rules>
             </rewrite>
         </system.webServer>
    </configuration>

Lighttpd の場合は、次のようなルールを使用します。

    url.rewrite-once = (
        ".*\?(.*)$" => "/index.php?$1",
        ".*\.(js|ico|gif|jpg|png|css|html)$" => "$0",
        "" => "/index.php"
    )

## ルータの使用法

Rewrite ルータを適切に使用するには、まずそのインスタンスを作成し、 次にユーザ定義のルーティングを追加し、それをコントローラに注入しなければなりません。 以下にコードの例を示します。

    // ルータを作成します

    $router = $ctrl->getRouter(); // デフォルトで rewrite ルータを返します
    $router->addRoute(
        'user',
        new Zend_Controller_Router_Route('user/:username',
                                         array('controller' => 'user',
                                               'action' => 'info'))
    );

## 基本的な RewriteRouter の操作法

RewriteRouter で最も重要なのが、ユーザ定義のルーティングです。 これは、RewriteRouter の addRoute メソッドをコールして追加します。 このメソッドに、Zend_Controller_Router_Route_Interface を実装したクラスの新しいインスタンスを渡します。

    $router->addRoute('user',
                      new Zend_Controller_Router_Route('user/:username'));

Rewrite ルータには、6 種類の基本的なルーティング方式があります (そのうちのひとつは特別なものです)。

- Zend_Controller_Router_Route

- Zend_Controller_Router_Route_Static

- Zend_Controller_Router_Route_Regex

- Zend_Controller_Router_Route_Hostname

- Zend_Controller_Router_Route_Chain

- Zend_Controller_Router_Rewrite *

これらのルーティングは、チェインやユーザ定義のルーティング方式を作成する際に何度も使用します。 任意の設定でお好みの数のルーティングを使用できますが、 Module ルートだけは例外です。これを使用するのは一度だけで、 もっとも汎用的なルート (デフォルト) として使用します。 個々のルーティング方式については、後ほど詳細に説明します。

addRoute への最初のパラメータはルートの名前です。 これを使用して、ルータがルートを処理します。 たとえば URL の生成などに使用します。 二番目のパラメータはルート自身となります。

ルート名のもっとも一般的な使用例は、 Zend_View の url ヘルパーです。

    <a href=
    "<?php echo $this->url(array('username' => 'martel'), 'user') ?>">Martel</a>

これは `user/martel` へのリンクとなります。

ルーティング処理は、定義されたすべてのルートから リクエスト URI にマッチする定義を探すことによって行います。 マッチするものが見つかれば、ルートのインスタンスから変数の値が返され、 それを Zend_Controller_Request オブジェクトに注入します。 これを、後にディスパッチャやユーザが作成したコントローラで使用します。 マッチするものが見つからない場合は、チェイン内の次のルートを調べます。

どのルートがマッチしたかを知りたい場合は getCurrentRouteName() メソッドを使用します。 これは、ルートをルータに登録する際に使用した識別子を返します。 ルートオブジェクトそのものを取得したい場合は getCurrentRoute() を使用します。

定義の順番

一番最後にマッチしたルートが適用されるので、 汎用的なルートは最初に定義するようにしましょう。

返される値

ルーティングの結果返される値は、URL パラメータあるいは ユーザ定義のルータのデフォルト値です。これらの値は、後ほど Zend_Controller_Request::getParam() あるいは Zend_Controller_Action::_getParam() メソッドでアクセスできます。

ルートで使用される変数のうち、'module'、'controller' および 'action' の 3 つは特別な扱いとなります。これらの特殊変数は、Zend_Controller_Dispatcher がディスパッチ先のコントローラとアクションを決定するために使用されます。

特殊変数

これらの特殊変数の名前を変更することもできます。その場合は Zend_Controller_Request_Http の setControllerKey() メソッドや setActionKey() メソッドを使用します。

## デフォルトのルート

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/news/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 に保存したものです。 これは、以下のようにして作成します。

    $compat = new Zend_Controller_Router_Route_Module(array(),
                                                      $dispatcher,
                                                      $request);
    $this->addRoute('default', $compat);

このデフォルトルートが不要な場合は、独自の 'デフォルト' ルートで上書きします (つまり、'default' という名前で保存します)。 あるいは、removeDefaultRoutes() で削除することもできます。

    // すべてのデフォルトルートを削除します
    $router->removeDefaultRoutes();

## ベース URL およびサブディレクトリ

Rewrite ルータはサブディレクトリ (例. `http://domain.com/user/application-root/`) 内でも使用可能です。この場合、アプリケーションのベース URL (`/user/application-root`) の自動検出が Zend_Controller_Request_Http によって行われ、適切に使用されます。

ベース URL の検出に失敗する場合は、 Zend_Controller_Request_Http のメソッド setBaseUrl() を使用してベースパスを上書き指定できます (ベース URL およびサブディレクトリを参照ください)。

    $request->setBaseUrl('/~user/application-root/');

## グローバルパラメータ

グローバルパラメータをルータ内で設定できます。 これは setGlobalParam() によってルートに自動的に適用されます。 グローバルパラメータが設定されているにもかかわらず 直接メソッドによっても設定された場合は、 ユーザが設定したパラメータのほうがグローバルパラメータより優先されます。 グローバルパラメータは、このように設定します。

    $router->setGlobalParam('lang', 'en');

## ルートの型

## RewriteRouter での Zend_Config の使用法

新しいルートを追加する際に、 いちいちコードを書き換えるのではなく設定ファイルの変更で対応できると便利でしょう。 そんなときには 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 を使用します。これは、ルータとして最低限必要なひとつのメソッドのみを定義したインターフェイスです。

    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);
    }

ルーティング処理は、システムが最初にリクエストを受け取った際に一度だけ行われます。 ルータの役割は、リクエストの内容に応じてコントローラやアクションとオプションパラメータを決定し、 それをリクエストに設定することです。 その後、リクエストオブジェクトがディスパッチャに渡されます。 ルートに対応するディスパッチトークンがない場合は、ルータは何も行いません。