5.3. サブクラス化

5.3.1. 導入

Zend_Controller システムは、拡張性を考慮して作成されています。 拡張方法としては、既存のクラスを継承する以外にも Zend_Controller_Router_Interface および Zend_Controller_Dispatcher_Interface を実装した新しいクラスを作成する方法があります。また、 Zend_Controller_Request_AbstractZend_Controller_Response_Abstract そして Zend_Controller_Action を継承したクラスを作成することもできます。

クラスを拡張する理由としては、以下のようなものが考えられるでしょう。

  • 既存の URI ルーティングが何らかの理由で適用できない場合。 例えば、既存のウェブサイトと統合する予定であるが、 そこで採用している規約が Zend Framework のルーティング機構と合致しないなど。

  • まったく別のルーティングを実装しなければならない場合。 Zend_Controller_Router クラスは URI のみを対象としています。 例えばコンソールアプリケーションや GUI アプリケーションなどの別の形式のプログラムにも MVC パターンを使用したくなることもあるでしょう。 コンソールアプリケーションの場合なら、 独自のルータを実装すればコマンドライン引数によるルーティングを行うこともできます。

  • Zend_Controller_Dispatcher の提供する機能がうまく当てはまらない場合。 デフォルト設定では、コントローラはクラスであり、 アクションはクラスのメソッドであることを前提としています。 しかし、これ以外にもいろいろなやり方があるでしょう。 例えば、ディレクトリをコントローラ、 ディレクトリ内のファイルをアクションと考えることもできます。

  • すべてのコントローラで使用するための機能を追加したい場合。 例えば、デフォルトでは Zend_Controller_ActionZend_View と統合されていません。しかし、 コントローラを継承したクラスでこの機能を持たせることができます。 そうすれば、もとの Zend_Controller_RouterZend_Controller_Dispatcher に手を加えずにすみます。

  • アプリケーションで発生した例外を記録し、 共通のエラーページにリダイレクトさせたい場合。既存の Zend_Controller_Response_Http を拡張し、 __toString() を変更することで、 登録された例外のチェックとその記録、 エラーページへのリダイレクトが可能となります。

システムの主要な部分、特にディスパッチャをオーバーライドする際には十分注意してください。 Zend_Controller を使用する利点のひとつに、 共通の規約に従ったアプリケーションを作成できるということがあります。 デフォルトの振る舞いをあまりにも変更してしまうと、この利点が失われてしまいます。 しかしながら、世の中にはさまざまな需要があるわけですし、 ひとつのソリューションですべて解決できるわけがありません。 そのために、必要なら変更できるようにしています。

5.3.2. 規約

Zend_Controller のクラスを継承する場合は、 その命名や保存場所はできるだけ次の規約に従うようにしてください。 そうすることで、Zend Framework になじみのある他のプログラマが、 あなたのプロジェクトの内容を簡単に理解できるようになります。

5.3.2.1. プレフィックス

Zend Framework に含まれるクラスの名前は、すべて "Zend_" で始まっています。これがプレフィックスです。 あなたが作成するクラスも同じようにしておくことを推奨します。 例えば、もしあなたの所属する会社名が Widget, Inc. なら、 プレフィックスを "Widget_" とします。

5.3.2.2. ディレクトリ構成

Zend_Controller クラスは、 ライブラリディレクトリに以下のように格納されます。

/library
  /Zend
    /Controller
      Action.php
      Dispatcher.php
      Router.php

Zend_Controller クラスを継承する場合は、 作成したクラスを (あなたのプレフィックスのもとで) 同じ構造で格納することを推奨します。こうすることで、 あなたのプロジェクトのコードをレビューする人たちがそれを見つけやすくなるでしょう。

例えば Widget, Inc. のプロジェクトがルータのみを独自に実装した場合は、 次のようになるでしょう。

/library
  /Zend
  /Widget
    /Controller
      Router.php
      README.txt

この例では、Widget/Controller/ ディレクトリが Zend/Controller/ ディレクトリと同じ構造になっていることに注意しましょう。 この場合、このファイルでは Widget_Controller_Router というクラスが提供されます。このクラスは Zend_Controller_Router を継承したものか、あるいは Zend_Controller_Router_Interface を実装した独自のクラスとなります。

また、上の例で Widget/Controller/ ディレクトリに README.txt があることにも注意してください。 あなたのプロジェクトを顧客に提供する際には、 それに関する単体テストや文書も作成するよう、Zend は強く推奨します。 そこまではいかなくても、同じディレクトリにシンプルな README.txt ファイルを置き、 変更点や動作内容を簡単に説明しておくようにしましょう。

5.3.3. リクエストの抽象化

抽象クラス Zend_Controller_Request_Abstract で、いくつかのメソッドを定義しています。

    /**
     * @return string
     */
    public function getControllerName();

    /**
     * @param string $value 
     * @return self
     */
    public function setControllerName($value);

    /**
     * @return string
     */
    public function getActionName();

    /**
     * @param string $value 
     * @return self
     */
    public function setActionName($value);

    /**
     * @return string
     */
    public function getControllerKey();

    /**
     * @param string $key 
     * @return self
     */
    public function setControllerKey($key);

    /**
     * @return string
     */
    public function getActionKey();

    /**
     * @param string $key 
     * @return self
     */
    public function setActionKey($key);

    /**
     * @param string $key 
     * @return mixed
     */
    public function getParam($key);

    /**
     * @param string $key 
     * @param mixed $value 
     * @return self
     */
    public function setParam($key, $value);

    /**
     * @return array
     */
     public function getParams();

    /**
     * @param array $array 
     * @return self
     */
    public function setParams(array $array);

    /**
     * @param boolean $flag 
     * @return self
     */
    public function setDispatched($flag = true);

    /**
     * @return boolean
     */
    public function isDispatched();
}

リクエストオブジェクトは、リクエストのコンテナとなります。 コントローラチェインが必要とするのは、 コントローラ、アクション、オプションパラメータ およびディスパッチ状況を設定したり取得したりする方法だけとなります。 デフォルトでは、リクエストがパラメータを探す際には コントローラあるいはアクションのキーを使用します。 探す順序はコントローラおよびアクションで定義されます。

5.3.4. ルータのインターフェイス

Zend_Controller_Router_Interface で定義されているメソッドはひとつだけです。

<?php

  /**
   * @param  Zend_Controller_Request_Abstract $request
   * @throws Zend_Controller_Router_Exception
   * @return Zend_Controller_Request_Abstract
   */
  public function route(Zend_Controller_Request_Abstract $request);

?>

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

5.3.5. ディスパッチャのインターフェイス

Zend_Controller_Front は、 まず最初にルータをコールして、 リクエスト内で最初にディスパッチできるアクションを決定します。 その後、ディスパッチャループに入ります。

ループ内では、まずリクエストオブジェクトでディスパッチフラグを設定し、 そしてリクエストを処理します (コントローラのインスタンスを作成し、 アクションをコールします)。アクションメソッド (あるいは pre/postDispatch プラグイン) がディスパッチフラグをリセットすると、リクエストオブジェクトの設定内容をもとにして フロントコントローラがループ内の次の処理を実行します。 これにより、すべてのアクションを順に実行していくことができます。

Zend_Controller_Dispatcher_Interface インターフェイスでは、 二つのメソッドが定義されています。

<?php

/**
 * @param  Zend_Controller_Request_Abstract $request
 * @return boolean
 */
public function isDispatchable(Zend_Controller_Request_Abstract $request);

?>

isDispatchable() は、 リクエストがディスパッチ可能かどうかを調べます。 ディスパッチ可能な場合に TRUE、それ以外の場合に FALSE を返します。ディスパッチ可能かどうかを判断する基準は、 インターフェイスを実装したクラスで記述します。 デフォルトの実装である Zend_Controller_Dispatcher では、 コントローラのファイルが存在するかどうか、 そのファイルでクラスが定義されているかどうか、 そしてそのクラスでアクションメソッドが定義されているかどうかを調べます。

<?php

/**
 * @param  Zend_Controller_Request_Abstract $route
 * @return Zend_Controller_Request_Abstract
 */
public function dispatch(Zend_Controller_Request_Abstract $request);

?>

dispatch() は、実際の処理を行うところです。 このメソッドは、コントローラのアクションを実行しなければなりません。 またリクエストオブジェクトを返す必要があります。

5.3.6. アクションコントローラ

アクションコントローラは、アプリケーションのさまざまなアクションを処理します。 この抽象クラスでは次のメソッドを提供します。

    /**
     * @param Zend_Controller_Request_Abstract $request Request object
     * @param Zend_Controller_Response_Abstract $response Response object
     * @param array $args Optional associative array of
     * configuration/environment settings
     */
    public function __construct(Zend_Controller_Request_Abstract $request, Zend_Controller_Response_Abstract $response, array $args = array());

    /**
     * @return void
     */
    public function init();

    /**
     * @return Zend_Controller_Request_Abstract
     */
    public function getRequest();

    /**
     * @param Zend_Controller_Request_Abstract $request 
     * @return self
     */
    public function setRequest(Zend_Controller_Request_Abstract $request);

    /**
     * @return Zend_Controller_Response_Abstract
     */
    public function getResponse();

    /**
     * @param Zend_Controller_Response_Abstract $response 
     * @return self
     */
    public function setResponse(Zend_Controller_Response_Abstract $response);

    /**
     * @return array
     */
    public function getInvokeArgs();

    /**
     * @return mixed
     */
    public function getInvokeArg($name);

    public function preDispatch();

    public function postDispatch();

    /**
     * @param string $methodName
     * @param array $args
     */
    public function __call($methodName, $args);

    /**
     * @param null|Zend_Controller_Request_Abstract $request Optional request 
     * object to use
     * @param null|Zend_Controller_Response_Abstract $response Optional response 
     * object to use
     * @return Zend_Controller_Response_Abstract
     */
    public function run(Zend_Controller_Request_Abstract $request = null, Zend_Controller_Response_Abstract $response = null);

コンストラクタは、リクエストオブジェクトとレスポンスオブジェクトを登録し、 追加の設定引数の配列も登録します。この配列には、 フロントコントローラの setParam() メソッドや setParams() メソッドで登録されたパラメータが含まれます。 登録を済ませたあとで、コンストラクタは処理を init() に渡します。

コンストラクタをオーバーライドすることもできます。ただ、 初期化処理はすべて init() で行うようにし、 リクエストとレスポンスを適切に登録できるようにしておくことを推奨します。

コンストラクタに渡された設定引数へは、 getInvokeArg() および getInvokeArgs() でアクセスできるようになります。 お勧めの方法は、これらの起動時引数として ビューや認証情報、あるいはレジストリオブジェクトを渡すことです。 たとえば次のようになります。

$front = Zend_Controller_Front::getInstance();
$front->setParam('view', new Zend_View())
      ->setControllerDirectory($config->controller->directory);
$response = $front->dispatch();

// サンプルのアクションコントローラ
class FooController extends Zend_Controller_Action
{
    protected $_view = null;

    public function init()
    {
        $this->_view = $this->getInvokeArg('view');
    }
}

アクションがディスパッチされる際の前後に行われる処理が、それぞれ preDispatch() メソッドおよび postDispatch() メソッドです。 デフォルトではこれらのメソッドは空であり、何もしません。

__call() メソッドは、クラス内で登録されていないアクションを処理します。 デフォルトでは、これは未定義のアクションに対して例外をスローします。 これは、デフォルトのアクションメソッドが定義されていない場合にのみ発生します。

アクションメソッドのデフォルトの命名規約は lowercaseAction のようになります。'lowercase' の部分でアクション名を指定し、 'Action' の部分でこれがアクションメソッドであることを指定します。 つまり、http://framework.zend.com/foo/barFooController::barAction() をコールします。

アクションコントローラは、ページコントローラとして使用することもできます。 典型的な使用法は、このようになります。

$controller = new FooController(
    new Zend_Controller_Request_Abstract(),
    new Zend_Controller_Response_Abstract()
);
$controller->run();
[注意] フロントコントローラ/アクションコントローラの使用

ページコントローラ方式ではなく、 フロントコントローラ/ページコントローラ を組み合わせた方式を使用することを推奨します。 これにより、相互運用可能なアプリケーションを書けるようになります。

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

レスポンスオブジェクトは、コールされたアクションからの 内容やヘッダを収集し、それをクライアントに返します。 次のようなメソッドがあります。

    /**
     * @param string $name Header name
     * @param string $value Header value
     * @param boolean $replace Whether or not to replace headers with the same
     * name already registered with the object
     * @return self
     */
    public function setHeader($name, $value, $replace = false);

    /**
     * @return array
     */
    public function getHeaders();

    /**
     * @return void
     */
    public function clearHeaders();

    /**
     * Sends all headers
     * @return void
     */
    public function sendHeaders();

    /**
     * @param string $content
     * @return self
     */
    public function setBody($content);

    /**
     * @param string $content
     * @return self
     */
    public function appendBody($content);

    /**
     * @return string
     */
    public function getBody();

    /**
     * echoes body content
     * @return void
     */
    public function outputBody();

    /**
     * @param Exception $e 
     * @return self
     */
    public function setException(Exception $e);

    /**
     * @return null|Exception
     */
    public function getException();

    /**
     * @return boolean
     */
    public function isException();

    /**
     * @param boolean $flag
     * @return boolean
     */
    public function renderExceptions($flag = null);

    /**
     * @return string
     */
    public function __toString();

setBody() は、ボディ部の内容をすべて置き換えます。 このメソッドの代わりに appendBody() を使用することを推奨します。 __toString() は、内容をレンダリングしてすべてのヘッダを送信します。

レスポンスオブジェクトはまた、 アクションコントローラで発生した例外が最終的に捕捉、登録されるところでもあります (Zend_Controller_Front::throwExceptions() が有効になっている場合を除く)。 isException() は boolean 値を返します。 これは例外が発生したかどうかを表します。 renderExceptions() を使用すると、 例外を捕捉した際に __toString() が例外出力をレンダリングしたのかどうかがわかります。