7.4. 提供されるサブクラス群

7.4.1. 導入

Zend Framework では、デフォルトで提供されるクラス以外の選択肢も用意しています。 たとえばリクエストオブジェクト、ルータ、 そしてレスポンスオブジェクトなどについて別の選択肢があります。

7.4.2. Zend_Controller_Request_Http

7.4.2.1. 導入

Zend_Controller_Request_Http は、HTTP 環境で使用するリクエストオブジェクトです Zend_Controller_Request_Http はデフォルトのリクエストクラスであり、 Zend_Controller_Dispatcher で用いられます。

7.4.2.2. リクエストデータへのアクセス

Zend_Controller_Request_Http は、関連する値へのアクセスをカプセル化します。 たとえばコントローラやアクションルータの変数のキー名や値、 URI からパースした追加のパラメータの値などにアクセスできます。 Zend_Controller_Request_Http のプロキシとして動作することで、 スーパーグローバルの値にパブリックメンバとしてアクセスしたり、 現在のベース URL やリクエスト URI を管理することもできます。 スーパーグローバルの値はリクエストオブジェクトに設定することはできません。 そのかわりに setParam/getParam メソッドを使用して、 パラメータを設定あるいは取得します。

[注意] スーバーグローバルデータ

Zend_Controller_Request_Http のパブリックプロパティを使用して スーパーグローバルデータにアクセスする際に注意すべき点は、 プロパティ名 (スーバーグローバル配列のキー) は以下の優先順位でマッチするということです。 1. GET, 2. POST, 3. COOKIE, 4. SERVER, 5. ENV.

特定のスーパーグローバルにアクセスするには、 パブリックメソッドを使用する方法もあります。たとえば、 $_POST['user'] の値を取得するには、リクエストオブジェクト上で getPost('user') をコールします。

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

Zend_Controller_Request_Http は、 サブディレクトリで Zend_Controller_Router_Rewrite を使用することができます。 Zend_Controller_Request_Http は自動的にベース URL を検出し、 それを適切に設定します。

たとえば、index.php をウェブサーバのサブディレクトリ /projects/myapp/index.php においた場合は、ベース URL (rewrite base) は /projects/myapp にしなければなりません。 マッチするルートを見つける前に、この文字列がパスの先頭から取り除かれます。 これにより、すべてのルートに余計な文字を追加する必要がなくなります。 ルート 'user/:username' は、 http://localhost/projects/myapp/user/martel および http://example.com/user/martel の両方にマッチするようになります。

[注意] URL の検出は大文字小文字を区別します

自動的なベース URL の検出処理は大文字小文字を区別します。そのため、 URL とファイルシステムのサブディレクトリ名が確実に一致する必要があります (たとえ Windows マシンであっても同様です)。大文字小文字が一致しなかった場合は noRoute アクションがコールされます。

ベース URL の検出に失敗する場合は、 Zend_Controller_Request_Http クラス、あるいは Zend_Controller_Front クラスの setBaseUrl() メソッドを使用して ベースパスを上書き指定することができます。 一番簡単な方法は Zend_Controller_Front で設定することです。 この設定はリクエストオブジェクトに引き継がれます。 独自のベース URL を設定する例を示します。

/**
 * Zend_Controller_Front で独自のベース URL を指定することによるリクエストのディスパッチ
 */
$router     = new Zend_Controller_Router_Rewrite();
$controller = Zend_Controller_Front::getInstance();
$controller->setControllerDirectory('./application/controllers')
           ->setRouter($router)
           ->setBaseUrl('/projects/myapp'); // ベース URL を指定します!
$response   = $controller->dispatch();

7.4.3. Zend_Controller_Router_Rewrite

7.4.3.1. 導入

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

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

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

7.4.3.2. ルータの使用法

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

/* ルータを作成します */

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

7.4.3.3. 基本的な RewriteRouter の操作法

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

$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>

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

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

[注意] 定義の順番

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

[注意] 返される値

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

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

[注意] 特殊変数

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

7.4.3.4. デフォルトのルート

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

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

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

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

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

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

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

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

7.4.3.6. Zend_Controller_Router_Route

Zend_Controller_Router_Route はフレームワークの標準のルートです。 簡単に利用でき、柔軟なルート定義が可能です。各ルートには、まず (静的および動的な) URL のマッピングが含まれ、 そしてデフォルト値および変数についての制限を指定して初期化します。

とある架空のアプリケーションで、コンテンツの作者情報のページが必要になったとしましょう。 ブラウザで http://domain.com/author/martel にアクセスした際に、"martel" とかいう人についての情報を見たいわけです。 この機能を実現するためのルートは、次のようになります。

$route = new Zend_Controller_Router_Route(
             'author/:username', 
             array('controller' => 'profile', 'action' => 'userinfo')
         );

$router->addRoute('user', $route);

Zend_Controller_Router_Route のコンストラクタの最初のパラメータは、ルートの定義です。 これを URL にマッチさせます。ルート定義は静的な部分と動的な部分で構成され、 それをスラッシュ ('/') で連結します。 動的な部分を変数と予備、変数名の前にコロンをつけて (例. :username) 表します。 静的な部分は単なるテキスト (例. author) です。

[注意] 文字の使用法

現在の実装では、(スラッシュ以外の) 任意の文字を変数名として使用できます。しかし、 PHP の変数名として使用できる文字だけを用いることを強く推奨します。 このようにしておくことで、 将来実装が変更されたときにバグを引き起こす可能性を抑えられます。

この例のルートは、ブラウザで 'http://domain.com/author/martel' を指した際にマッチします。 この場合、すべての変数の値が Zend_Controller_Request オブジェクトに注入され、ProfileController からアクセスできるようになります。 この例が返す変数は、以下のようなキーと値のペアを持つ配列となります。

$values = array(
  'username' => 'martel',
  'controller' => 'profile',
  'action' => 'userinfo'
);

その後、Zend_Controller_Dispatcher は (デフォルトモジュールの) ProfileController クラスにある userinfoAction メソッドを実行します。変数にアクセスするには、 Zend_Controller_Action::_getParam あるいは Zend_Controller_Request::getParam メソッドを使用します。

public function userinfoAction() 
{
    $request = $this->getRequest();
    $username = $request->getParam('username');
    
    $username = $this->_getParam('username');
}

ルート定義には、特殊文字 (ワイルドカード) を含めることができます。これは '*' 記号で表します。 これを使用して、Module ルートと同様にパラメータを扱う (変数名 => 値 のペアを URI で定義する) ことができます。 次のルートは、Module ルートの挙動をまねたものです。

$route = new Zend_Controller_Router_Route(':controller/:action/*');
$router->addRoute('default', $route);
7.4.3.6.1. 変数のデフォルト

ルートで使用するすべての変数についてデフォルト値を指定することができます。 これは、 Zend_Controller_Router_Route のコンストラクタの 2 番目のパラメータで指定します。 このパラメータは、変数名をキーとする配列で、 対応する値にそのデフォルト値を指定します。

$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 で定義されています) にディスパッチします。より使いやすくするには、 ルートのデフォルトとしてコントローラとアクションを定義しておく必要があります。

$route = new Zend_Controller_Router_Route(
             'archive/:year', 
             array(
                 'year' => 2006, 
                 'controller' => 'archive',
                 'action' => 'show'
             )
         );
$router->addRoute('archive', $route);

このルートは、ArchiveController の showAction を実行します。

7.4.3.6.2. 変数の制約

Zend_Controller_Router_Route のコンストラクタの 三番目のパラメータで、変数の制約を指定することができます。 これは、正規表現で指定します。

$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 はマッチしません。 この場合はチェイン内の次のルートに処理を移します。

7.4.3.7. Zend_Controller_Router_Route_Static

これまでの例では、すべて動的なルートを使用していました。 つまり、特定のパターンにマッチするものについてのルートです。 しかし、時には特定のルートを固定してしまい、 わざわざ正規表現エンジンを動かしたくない場合もあるでしょう。 そんなときには静的なルートを使用します。

$route = new Zend_Controller_Router_Route_Static(
             'login', 
             array('controller' => 'auth', 'action' => 'login')
         );
$router->addRoute('login', $route);

上のルートは http://domain.com/login という URL にマッチします。

7.4.3.8. Zend_Controller_Router_Route_Regex

デフォルトのルートや静的なルートに加えて、正規表現によるルートも使用可能です。 このルートは他のものに比べてより強力で柔軟なものですが、 多少複雑になってしまいます。そして、より高速になります。

標準のルートと同様、このルートを初期化する際にはルートの定義とデフォルトを指定する必要があります。 サンプルとして、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 http://domain.com/foo/bar/ は正規表現 foo/bar にマッチすることになります。

[注意] 注意

行頭と行末を表す文字 (それぞれ '^' および '$') が、すべての式の前後に自動的に付加されます。 したがって、これらは正規表現で指定する必要はありません。

[注意] 注意

このルートクラスは、区切り文字として # を使用します。 つまり、ルート定義の中にハッシュ文字 ('#') がある場合は、それをエスケープする必要があるということです。 しかし、この文字 (アンカー) は通常はウェブサーバに渡されることはないので、 エスケープが必要になることはまずないでしょう。

定義されたサブパターンの内容は、通常通りの方法で取得できます。

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 を作成したいと考えたとしましょう。 これまでの内容を組み合わせると、答えはこのようになります。

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

7.4.3.9. 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 を使用するようにしています。

7.4.4. Zend_Controller_Response_Http

Zend_Controller_Response_Http は、 HTTP 環境での使用に適したレスポンスオブジェクトです。 ヘッダの設定/取得/消去の機能があります。また、 __toString() メソッドを使用して、 レスポンス本体の前に全ヘッダを一括送信することもできます。

setHeader() は、二つの引数を受け取ります。 最初がヘッダの型で、次がヘッダの値です。三番目のオプションのパラメータを true にすると、同じ型の既存のヘッダを強制的に上書きします。

7.4.5. Zend_Controller_Response_Cli

Zend_Controller_Response_Cli は、 CLI 環境での使用に適したレスポンスオブジェクトです。 ヘッダの処理などの機能は持たず、 __toString() が起動された際には単純に本文のみを返します。