第7章 Zend_Controller

目次

7.1. 概要
7.1.1. 導入
7.1.2. リクエストオブジェクト
7.1.3. ルーティング処理
7.1.4. ディスパッチ処理
7.1.5. レスポンスオブジェクト
7.2. さあ、はじめましょう
7.2.1. 導入
7.2.2. サーバの設定
7.2.3. 起動用ファイル
7.2.4. ディレクトリ構造
7.2.5. デフォルトのコントローラ
7.3. サブクラス化
7.3.1. 導入
7.3.2. 規約
7.3.3. フロントコントローラ
7.3.4. リクエストの抽象化
7.3.5. ルータのインターフェイス
7.3.6. ディスパッチャのインターフェイス
7.3.7. アクションコントローラ
7.3.8. レスポンスオブジェクト
7.4. 提供されるサブクラス群
7.4.1. 導入
7.4.2. Zend_Controller_Request_Http
7.4.3. Zend_Controller_Router_Rewrite
7.4.4. Zend_Controller_Response_Http
7.4.5. Zend_Controller_Response_Cli
7.5. アクションコントローラ
7.5.1. 導入
7.5.2. オブジェクトの初期化
7.5.3. ディスパッチ前後のフック
7.5.4. アクセス用メソッド
7.5.5. ユーティリティメソッド
7.6. プラグイン
7.6.1. 導入
7.6.2. プラグインの書き方
7.6.3. プラグインの使用法
7.7. モジュラーディレクトリ構造の規約の使用
7.7.1. 導入
7.7.2. モジュールコントローラディレクトリの指定
7.7.3. モジュールへのルーティング
7.7.4. モジュールあるいはグローバルのデフォルトコントローラ
7.8. MVC での例外
7.8.1. 導入
7.8.2. 例外を処理する方法は?
7.8.3. MVC で遭遇するであろう例外
7.9. 以前のバージョンからの移行
7.9.1. 0.6.0 から 0.8.0 への移行
7.9.2. 0.2.0 以前のバージョンから 0.6.0 への移行

7.1. 概要

7.1.1. 導入

Zend_Controller は、モデル - ビュー - コントローラ (MVC) パターンに基づいたウェブサイトを構築するための基本機能を提供します。

Zend_Controller は軽量で部品化されており、拡張しやすいように設計されています。 この設計は、柔軟性および利用者に対する自由度を確保するための最低限のものであり、 Zend_Controller を使用して構築したシステムは、 ほぼ同じようなコード配置・規約を共有することができます。

Zend_Controller の処理の流れは、 いくつかの部品にわかれて実装されています。 これらの部品のすべてを完璧に理解する必要はありませんが、 処理の概要を知っておくことは有用です。

  • Zend_Controller_Front は、 Zend_Controller の処理全体を取りまとめます。 FrontController パターンであるといえます。 サーバから受け取ったすべてのリクエストを Zend_Controller_Front が処理し、最終的にリクエストを ActionController (Zend_Controller_Action) に委譲します。

  • Zend_Controller_Request_Abstract はリクエスト環境を表し、コントローラ名やアクション名 そしてリクエストパラメータを設定したり取得したりする機能を提供します。 さらに、アクションが Zend_Controller_Dispatcher でディスパッチされたかどうかを追跡する機能もあります。 この抽象リクエストオブジェクトを拡張し、 リクエスト情報をカプセル化します。それによって、 ルータはリクエストの情報を取得して コントローラとアクションを決定できるようになります。

    デフォルトでは Zend_Controller_Request_Http が用いられます。これは、HTTP リクエスト環境全体へのアクセス機能を提供します。

  • Zend_Controller_Router_Interface を使用してルータを定義します。 ルーティングとは、リクエストの内容を調べて、 そのリクエストを処理するコントローラとアクションを決定する処理のことです。 このコントローラとアクション、そしてリクエストパラメータがリクエストオブジェクトに設定され、 Zend_Controller_Dispatcher_Standard で処理されます。 ルーティングが発生するのは一度だけ、 つまり最初にリクエストを受け取ってから 最初のコントローラにディスパッチされるまでの間だけです。

    デフォルトのルータは Zend_Controller_Router_Rewrite です。

    デフォルトのルータである Zend_Controller_Router_Rewrite は、 Zend_Controller_Request_Http で指定された URI を受け取ってそれを分解し、コントローラや アクション、そして URL に含まれたパスのパラメータを取得します。 たとえば、http://localhost/foo/bar/key/value のような URL の場合は foo がコントローラ、 bar がアクション、そしてパラメータ key の値として value を取得します。

    Zend_Controller_Router_Rewrite は、任意のパスにマッチさせることもできます。 詳細は Rewrite Router のドキュメント を参照ください。

  • Zend_Controller_Dispatcher_Interface を使用してディスパッチャを定義します。ディスパッチとは、 コントローラとアクションをリクエストオブジェクトから受け取り、 それを実際のコントローラファイル/クラスとメソッドに対応させる処理のことです。 コントローラやアクションが存在しない場合は、 デフォルトのコントローラやアクションにディスパッチします。

    実際のディスパッチ処理では、 コントローラクラスのインスタンスの作成と そのクラスのアクションメソッドのコールを行います。 ルーティングは一度しか発生しませんが、 それとは異なりディスパッチは繰り返し発生します。 リクエストオブジェクトのディスパッチ状態がリセットされるとループが再開され、 現在リクエストオブジェクトに設定されているアクションがコールされます。 リクエストオブジェクトのディスパッチ状態が (true に) 設定された状態でループが終了すると、処理が終了します。

    デフォルトのディスパッチャは Zend_Controller_Dispatcher_Standard です。 これは、CamelCasedClasses 形式の名前の最後に Controller がついたクラスをコントローラとして使用し、 アクションメソッドの名前は camelCasedMethods 形式 (最後に Action をつける) となります。たとえば SomeFooController::barAction のような場合、コントローラは somefoo、アクションは bar となります。

  • Zend_Controller_Action はコントローラの基底コンポーネントです。 各コントローラはこの Zend_Controller_Action クラスを継承して作成します。このクラスではアクションメソッドを定義します。

  • Zend_Controller_Response_Abstract は基底レスポンスクラスで、アクションコントローラからの応答内容を収集し、 それをレスポンスとして返します。これはヘッダと本文の両方を収集します。 また、__toString() を実装しているので、 それを直接出力してヘッダと本文を一度に送信することができます。

    デフォルトのレスポンスクラスは Zend_Controller_Response_Http で、 これは HTTP 環境での使用に適しています。

Zend_Controller の処理の流れは比較的シンプルです。 Zend_Controller_Front がリクエストを受け取り、 Zend_Controller_Router_Rewrite をコールして、 配送先となるコントローラ (そしてコントローラ内のアクション) を決定します。Zend_Controller_Router_Rewrite は URI を分解し、リクエストからコントローラ名とアクション名を取得します。 その後、Zend_Controller_Front はディスパッチループに突入します。 まず Zend_Controller_Dispatcher_Standard をコールしてそこにリクエストを渡し、 リクエストで指定されたコントローラとアクションを取得します (あるいはデフォルトを使用します)。 コントローラが終了すると、処理は Zend_Controller_Front に戻ります。リクエストのディスパッチ状態がリセットされ、 別のコントローラをディスパッチするように指示された場合は、 ループが続けられて次の配送処理が行われます。 それ以外の場合は、処理がそこで終了します。それ以外の場合は、 処理が終了します。

7.1.2. リクエストオブジェクト

リクエストオブジェクトとは Zend_Controller_Front とルータ、 ディスパッチャそしてコントローラクラスの間でやり取りされる単純なバリューオブジェクトです。 これはコントローラ、アクションそして環境 (HTTP、CLI、PHP-GTK など) に応じたその他のパラメータの内容をまとめたものです。

  • コントローラ名へのアクセスは getControllerName() および setControllerName() によって行います。

  • コントローラ内からコールされるアクションの名前へのアクセスは getActionName() および setActionName() によって行います。

  • アクションに渡されるパラメータは キー/値 の組み合わせからなる連想配列で、 getParams() および setParams() によってアクセスします。 あるいは、その個々のデータには getParam() および setParam() でアクセスします。

リクエストの形式によっては、それ以外のメソッドも使用可能です。 たとえば、デフォルトのリクエストで使用される Zend_Controller_Request_Http では、リクエスト URI やパス情報、$_GET や $_POST パラメータを取得するメソッドなどがあります。

リクエストオブジェクトはフロントコントローラに渡されます。 もしリクエストオブジェクトがなかった場合は、 ディスパッチ処理の最初 (ルーティングが発生する前) にインスタンスが作成されます。これは、 ディスパッチチェインのすべてのオブジェクトに渡されていきます。

さらに、リクエストオブジェクトはテストの際にも有用です。 開発者がリクエストを作成し、コントローラやアクション、 パラメータや URI などを指定してそれをフロントコントローラに渡すことで、 アプリケーションの流れをテストすることができます。 レスポンスオブジェクトと組み合わせて使用すると、 MVC アプリケーションの精密で正確な単体テストが可能となります。

7.1.3. ルーティング処理

はじめてのコントローラを作り始める前に、ルーティング処理が Zend_Controller_Router_Rewrite でどのように実装されているのかを知る必要があるでしょう。 処理手順として、一度だけ発生するルーティングと繰り返し発生するディスパッチがあることを覚えておきましょう。

Zend_Controller_FrontZend_Controller_Router_Rewrite (あるいは別に登録されたルータ) をコールし、 URI をコントローラとアクションに対応させます。 Zend_Controller_Router は URI を受け取り、 チェイン内のルータオブジェクトに渡します。 デフォルトでは、Zend_Controller_Router_Route_Module を使用して入力 URL のマッチを行います。ルータオブジェクトは URL を分解し、コントローラとアクション、そしてパスに含まれた URL パラメータを取得します。そしてルータがそれらをリクエストオブジェクトに格納します。

Zend_Controller_Router_Route_Module は、 非常にシンプルな対応ルールでコントローラ名とアクション名を取得します。

http://framework.zend.com/controller/action/
        

上の例で、最初の部分が常にコントローラの名前となり、 二番目の部分が常にアクションの名前となります。

オプションで、コントローラに渡すパラメータを URI に含めることができます。 これは キー/値 の組み合わせの形式になります。

http://framework.zend.com/controller/action/key1/value1/
        

コントローラやアクションが URI パスに含まれない場合は、 Zend_Controller_Dispatcher_Standard はリクエストオブジェクトのパラメータから値を取得しようとします。 それも見つからなかった場合はデフォルト値を使用します。 デフォルト値は、コントローラもアクションも "index" となります。以下に例を示します。

http://framework.zend.com/roadmap/future/
コントローラ: roadmap
アクション  : future

http://framework.zend.com/roadmap/
コントローラ: roadmap
アクション  : index

http://framework.zend.com/
コントローラ: index
アクション  : index
        
[注意] 柔軟性

より柔軟な機能を使用したい場合は、 Rewrite Router のドキュメント を調べてみましょう。

コントローラの名前、コントローラ内のアクションの名前、 そしてオプションのパラメータの内容が、 リクエストオブジェクトにまとめられます。 Zend_Controller_Front がディスパッチループに入ると、 リクエストオブジェクトが Zend_Controller_Dispatcher_Standard に渡されます。

7.1.4. ディスパッチ処理

ディスパッチ処理は、リクエストオブジェクトを受け取り、 そこに含まれる情報 (コントローラ名、アクション名およびオプションのパラメータ) を展開し、コントローラのインスタンスを作成してそのコントローラのアクションをコールします。 コントローラやアクションが見つからない場合は、 デフォルト値を使用します。Zend_Controller_Dispatcher_Standard では、コントローラとアクションのデフォルトはどちらも index です。しかし、 setDefaultController() メソッドや setDefaultAction() メソッドでこれらを変更することもできます。

ディスパッチ処理が発生するのは、フロントコントローラでのループの内部です。 ディスパッチ処理を行う前に、フロントコントローラはルーティングを行い、 ユーザが指定したコントローラとアクション、そして追加のパラメータを取得します。 それからディスパッチループに入り、リクエストを配送します。

ループ内では、まず最初にリクエストオブジェクトのフラグを設定します。 このフラグは、アクションがディスパッチされたことを示すものです。 アクション内や pre/postDispatch プラグインでこのフラグをリセットすると、 ディスパッチループがそのまま継続され、もう一度リクエストを処理しようとします。 リクエスト内のコントローラやアクションを変更してフラグをリセットすることで、 さまざまなリクエストを続けて実行させることができます。

このようなディスパッチ処理を制御する アクションコントローラのメソッドが _forward() です。 このメソッドを pre/postDispatch() やアクションメソッドでコールし、 コントローラやアクション、 そして新しいアクションに送りたい追加のパラメータを指定します。

public function myAction()
{
    // 何かの処理をして...
    // 現在のモジュール内の別のアクション FooController::barAction() に転送します
    $this->_forward('bar', 'foo', null, array('baz' => 'bogus'));
}

7.1.5. レスポンスオブジェクト

レスポンスオブジェクトは、リクエストオブジェクトと対になるものです。 その目的は、コンテンツやヘッダを収集し、それを返すことです。 さらに、フロントコントローラで捕捉した例外はすべてレスポンスオブジェクトに渡されます。 これにより、例外の処理がやりやすくなります。 この挙動を変更するには Zend_Controller_Front::throwExceptions(true) を設定します。

$front->throwExceptions(true);

ヘッダを含むレスポンス出力を送信するには、 sendOutput() を使用します。

$response->sendOutput();

開発者は、アクションコントローラの中でレスポンスオブジェクトを使用しなければなりません。 出力を直接レンダリングしたり直接ヘッダを送信したりするのではなく、 それらをレスポンスオブジェクトに格納するようにします。

// アクションコントローラのアクション内で、
// ヘッダを設定します
$this->getResponse()
    ->setHeader('Content-Type', 'text/html')
    ->appendBody($content);

こうすることで、すべてのヘッダを一度に送信し、 その後でコンテンツを表示することができます。

アプリケーションで例外が発生したかどうかを調べるには、 レスポンスオブジェクトの isException() フラグを調べます。例外を取得するには getException() を使用します。さらに、独自のレスポンスオブジェクトを作成して、 エラーページへのリダイレクトや例外メッセージのログ出力、 例外をわかりやすく表示する (開発用) などを行うことができます。

レスポンスオブジェクトは、フロントコントローラの dispatch() から受け取ることになります。あるいは、 出力のレンダリングを行わない状態のレスポンスオブジェクトを フロントコントローラから受け取ることもできます。

// dispatch の後に取得します
$front->dispatch();
$response = $front->getResponse();
if ($response->isException()) {
    // ログへの記録、メール送信など...
}

// あるいは、フロントコントローラの dispatch() の返り値を使用します
$front->returnResponse(true);
$response = $front->dispatch();

// 何かの処理...

// 最後に結果を表示します
$response->sendResponse();

デフォルトでは、例外メッセージは表示されません。 この挙動をオーバーライドするには renderExceptions() メソッドを使用するか、あるいは上で示したようにフロントコントローラで throwExceptions() を有効にします。

$response->renderExceptions(true);
$front->dispatch($request, $response);

// あるいは
$front->returnResponse(true);
$response = $front->dispatch();
$response->renderExceptions();
$response->sendOutput();

// あるいは
$front->throwExceptions(true);
$front->dispatch();