# アクションコントローラ

## 導入

`Zend_Controller_Action` は、 モデル - ビュー - コントローラ (MVC) パターンにもとづいたウェブアプリケーションを作成する際に、 フロントコントローラで使用するアクションコントローラを実装するための抽象クラスです。

`Zend_Controller_Action` を使用するには、 実際のアクションコントローラクラス内でこのクラスのサブクラスを作成する必要があります (あるいは、作成したサブクラスをもとにしてアクションコントローラを作成します)。 基本的な使い方としては、まずサブクラスを作成し、 そしてあなたのサイト上で処理したいさまざまなアクションに対応する アクションメソッドを作成するという流れになります。 `Zend_Controller` は、このクラス内のメソッドで 'Action' という名前で終わるものを見つけると、 ルーティングやディスパッチの際にそれらを自動的にアクションとして扱います。

たとえば、次のようなクラスを見てみましょう。

    class FooController extends Zend_Controller_Action
    {
        public function barAction()
        {
            // 何かをします
        }

        public function bazAction()
        {
            // 何かをします
        }
    }

この FooController クラス (foo コントローラ) では、ふたつのアクション bar および baz が定義されています。

もちろんこれ以外にもたくさんの機能があります。 たとえば初期化アクションを独自に作成したり、 アクションを指定しなかった (あるいは無効なアクションを指定した) 際にコールされるデフォルトのアクションを指定したり、 ディスパッチの前後に実行されるフックを指定したり、 さまざまなヘルパーメソッドを使用したりといったことができます。 この章では、アクションコントローラの機能の概要を説明します。

### デフォルトの挙動

デフォルトでは、フロントコントローラ は ViewRenderer アクションヘルパーを有効にします。このヘルパーは、 ビューオブジェクトをコントローラに注入し、 ビューを自動的にレンダリングします。 アクションコントローラでこれを無効にするには、 以下のいずれかの方法を使用します。

    class FooController extends Zend_Controller_Action
    {
        public function init()
        {
            // このコントローラでのみ無効にします。
            // 初期化時に読み込まれるので、全アクションに影響を及ぼします
            $this->_helper->viewRenderer->setNoRender(true);

            // 全体で無効にします
            $this->_helper->removeHelper('viewRenderer');

            // これも全体で無効にしますが、同時にローカルでも無効にしておく必要があります。
            // これは、ローカルの設定を全体に伝播させる方法です。
            Zend_Controller_Front::getInstance()
                ->setParam('noViewRenderer', true);
        }
    }

`initView()`、`getViewScript()`、 `render()` および `renderScript()` は、それぞれ ViewRenderer へのプロキシとなります。 ただしヘルパーブローカ内にこのヘルパーが登録されていない場合や noViewRenderer フラグが設定されている場合は除きます。

個々のビューのレンダリングを無効にするには、単純に ViewRenderer の noRender フラグを設定することもできます。

    class FooController extends Zend_Controller_Action
    {
        public function barAction()
        {
            // このアクションでのみ自動レンダリングを無効にします
            $this->_helper->viewRenderer->setNoRender();
        }
    }

ViewRenderer を無効にする場面として考えられるのは、 ビューオブジェクトを必要としない場合や ビュースクリプト経由でのレンダリングを行わない場合 (たとえば、アクションコントローラを使用して SOAP や XML-RPC、REST といったウェブサービスプロトコルを扱う場合) です。ViewRenderer をグローバルで無効にすることはまずないでしょう。 無効にするとすれば、個々のコントローラやアクション単位で行うことになります。

## オブジェクトの初期化

アクションコントローラのコンストラクタをオーバーライドすることもできますが、 お勧めしません。`Zend_Controller_Action::__construct()` は、リクエストオブジェクトやレスポンスオブジェクトを登録するなどの重要な作業を行います。 また、フロントコントローラから渡された起動時引数の処理も行います。 コンストラクタをオーバーライドする場合は、必ずその中で `parent::__construct($request, $response, $invokeArgs)` をコールするようにしましょう。

初期化作業をカスタマイズするには、コンストラクタをオーバーライドするよりも `init()` メソッドを使うほうがお勧めです。 これは、`__construct()` の中で最後にコールされます。たとえば、 初期化時にデータベースに接続したいなら次のようにします。

    class FooController extends Zend_Controller_Action
    {
        public function init()
        {
            $this->db = Zend_Db::factory('Pdo_Mysql', array(
                'host'     => 'myhost',
                'username' => 'user',
                'password' => 'XXXXXXX',
                'dbname'   => 'website'
            ));
        }
    }

## ディスパッチ前後のフック

`Zend_Controller_Action` には、 リクエストされたアクションの前後にコールされるふたつのメソッドがあります。それが `preDispatch()` と `postDispatch()` です。 これらはさまざまな場面で活用できます。 たとえばアクションを実行する前に認証情報や ACL を調べたり (`preDispatch()` の中で `_forward()` をコールすると、 そのアクションの処理は飛ばされます)、 作成したコンテンツを (`postDispatch()` で) 全サイト共通のテンプレートに配置したりといったことが考えられます。

### init() 対 preDispatch() の使用法

前述のセクションでは、 `init()` メソッドを紹介しましたが、 このセクションでは `preDispatch()` メソッドを紹介します。 それらの違いは何で、それぞれどのような動作をさせるでしょう？

`init()` メソッドは、主にコンストラクタを拡張することが目的です。 一般的に、コンストラクタはオブジェクトの状態を単純に設定しなければならなくて、 多くのロジックを実行してはいけません。 これは、コントローラで使用されるリソース（例えばモデル、オブジェクトの構成、その他）を初期化すること、 またはフロントコントローラ、ブートストラップ、 またはレジストリから取得される値をアサインすることを含むかもしれません。

`preDispatch()` メソッドは、 オブジェクトまたは環境（例えば、ビュー、アクション・ヘルパー、その他） の状態を設定するために使うこともできます。 しかし、その主な目的は、リクエストされたアクションをディスパッチしなければいけないかどうか 決定することです。 もししないのならば、別のアクションに `_forward()` するか、 または例外を投げなければなりません。

注意: `init()` から実行されると、 実は `_forward()` は正しく動作しません。 それは、２つのメソッドの意図が形になったものです。

## アクセス用メソッド

さまざまなオブジェクトや変数がオブジェクトに登録されており、 それぞれにアクセス用メソッドが用意されています。

- リクエストオブジェクト: `getRequest()` を使用してリクエストオブジェクトを取得し、 それを用いてアクションをコールします。

- レスポンスオブジェクト: `getResponse()` を使用して、最終的なレスポンスの内容を取得します。 典型的な使用法は、このようになります。

      $this->getResponse()->setHeader('Content-Type', 'text/xml');
      $this->getResponse()->appendBody($content);

- 起動時引数: フロントコントローラは、パラメータを ルータやディスパッチャそしてアクションコントローラに送ります。 これらのパラメータを取得するには、 `getInvokeArg($key)` を使用します。あるいは、 すべてのパラメータを取得するには `getInvokeArgs()` を使用します。

- リクエストパラメータ: リクエストオブジェクトは、`_GET` や `_POST` のようなリクエストパラメータのほかに URL のパスで指定したパラメータも収集します。 これらを取得するには、`_getParam($key)` あるいは `_getAllParams()` を使用します。 `_setParam()` を使用して、 リクエストパラメータを設定することもできます。 これは、さらに別のアクションに転送する際などに有用です。

  パラメータが存在するかどうかを調べる (条件分岐の際に使用します) には、 `_hasParam($key)` を使用します。

  `_getParam()` は、オプションの二番目の引数でデフォルト値を指定できます。 もしパラメータが設定されていなかったり空だったりした場合は、このデフォルト値を使用するようになります。 これを用いることで、値を取得する前にいちいち `_hasParam()` をコールする必要がなくなります。

      // id が設定されていない場合のデフォルト値を 1 とします
      $id = $this->_getParam('id', 1);

      // わざわざこのようにする必要はありません
      if ($this->_hasParam('id') {
          $id = $this->_getParam('id');
      } else {
          $id = 1;
      }

## ビューの統合

### Default view integration is via the ViewRenderer

この節の内容は、 ViewRenderer を明示的に無効にしている場合にのみ有効です。 それ以外の場合はこの節を読み飛ばしてください。

`Zend_Controller_Action` では、 ビューの統合のためのちょっとした柔軟な仕組みを提供しています。 これを行うのは `initView()` と `render()` のふたつのメソッドです。前者のメソッドはパブリックプロパティ `$view` の遅延読み込みを行い、 後者のメソッドはアクションの要求にもとづいてビューをレンダリングします。 その際に、ディレクトリ階層をもとにスクリプトのパスを決定します。

### ビューの初期化

`initView()` はビューオブジェクトを初期化します。 `render()` は `initView()` をコールしてビューオブジェクトを取得しますが、 その初期化はいつでも好きなときに行うことができます。 デフォルトでは、取得した結果は `Zend_View` オブジェクトのプロパティ `$view` に格納されますが、 `Zend_View_Interface` を実装したクラスなら何でも好きなものを使用できます。 `$view` がすでに初期化されている場合は、そのプロパティの内容を返します。

デフォルトの実装は、以下のようなディレクトリ階層を前提としています。

    applicationOrModule/
        controllers/
            IndexController.php
        views/
            scripts/
                index/
                    index.phtml
            helpers/
            filters/

言い換えると、ビュースクリプトが `/views/scripts/` ディレクトリ内にあり、かつ `/views/` ディレクトリ内の同一階層に各機能 （ヘルパー、フィルタ）のディレクトリがあるということです。 ビュースクリプトの名前とパスを決定する際の基底ディレクトリとして `/views/scripts/` が用いられます。 その中に、ビュースクリプトを実行するコントローラ名に基づいた名前のディレクトリが作成されます。

### ビューのレンダリング

`render()` のシグネチャは次のとおりです。

    string render(string $action = null,
                  string $name = null,
                  bool $noController = false);

`render()` はビュースクリプトをレンダリングします。 引数を省略した場合は、`[controller]/[action].phtml` が指定されたものとみなします（`.phtml` は `$viewSuffix` プロパティの値です）。 `$action` を指定すると、`/[controller]/` ディレクトリにあるその名前のテンプレートをレンダリングします。 `/[controller]/` ディレクトリを使用しないようにするには、 `$noController` に `TRUE` を指定します。 テンプレートをレンダリングした結果はレスポンスオブジェクトに格納されます。 レスポンスオブジェクトの中の、 特定の名前をつけた部分 に格納したい場合は、 `$name` の値を指定します。

コントローラやアクションの名前には区切り文字 ('_' や '.'、'-') を含めることができるので、 `render()` はスクリプト名を決定する際にこれらの文字を '-' に正規化します。内部的には、 ディスパッチャで設定されている単語やパスの区切り文字を正規化時に用います。 したがって、`/foo.bar/baz-bat` へのリクエストの際に レンダリングされるスクリプトは `foo-bar/baz-bat.phtml` です。 アクションメソッド名が camelCase 方式の場合、 ビュースクリプトのファイル名では単語が '-' で区切られることに注意しましょう。

例を見てみましょう。

    class MyController extends Zend_Controller_Action
    {
        public function fooAction()
        {
            // my/foo.phtml をレンダリングします
            $this->render();

            // my/bar.phtml をレンダリングします
            $this->render('bar');

            // baz.phtml をレンダリングします
            $this->render('baz', null, true);

            // my/login.phtml をレンダリングし、
            // レスポンスオブジェクトの 'form' の部分に返します
            $this->render('login', 'form');

            // site.phtml をレンダリングし、レスポンスオブジェクトの
            // 'page' の部分に返します
            // 'my/' ディレクトリは使用しません
            $this->render('site', 'page', true);
        }

        public function bazBatAction()
        {
            // my/baz-bat.phtml をレンダリングします
            $this->render();
        }
    }

## ユーティリティメソッド

アクセス用メソッドやビューの統合用メソッド以外にも、`Zend_Controller_Action` にはいくつかのユーティリティメソッドが用意されています。 これらを使用して、アクションメソッド (あるいはディスパッチ前後のフックメソッド) でのさまざまな作業を行います。

- `_forward($action, $controller = null, $module = null, array $params = null)`: 別のアクションを実行します。`preDispatch()` の中でコールすると、 リクエストされていたアクションは飛ばされ、 新しいアクションを実行します。それ以外の場合は、 現在のアクションの処理を済ませた後で `_forward()` で指定したアクションを実行します。

- `_redirect($url, array $options = array())`: 別の場所にリダイレクトします。このメソッドには、URL のほかに任意でオプション群を指定します。 デフォルトでは、HTTP 302 リダイレクトを行います。

  オプションは、以下のうちのひとつあるいは複数の組み合わせとなります。

  - exit: 即時に終了するかしないか。 これを指定すると、オープンしたいるセッションをすべて閉じた後にリダイレクトします。

    このオプションをコントローラ全体で有効にするには、 アクセスメソッド `setRedirectExit()` を使用します。

  - prependBase: リクエストオブジェクトに登録されている基底 URL を この URL の先頭に付加するかどうか。

    このオプションをコントローラ全体で有効にするには、 アクセスメソッド `setRedirectPrependBase()` を使用します。

  - code: リダイレクトの際にどの HTTP コードを使用するか。 デフォルトでは HTTP 302 を使用しますが、 301 から 306 までの任意の値を使用できます。

    このオプションをコントローラ全体で有効にするには、 アクセスメソッド `setRedirectCode()` を使用します。

## アクションコントローラのサブクラスの作成

アクションコントローラを作成するには、必ず `Zend_Controller_Action` のサブクラスを作成しなければならないようになっています。 最低限、コントローラがコールするアクションメソッドを定義しなければなりません。

自分のウェブアプリケーション用に便利な機能を実装していく一方で、 同じような前処理やちょっとした処理をあちこちのコントローラで書いているといったことはありませんか？ そのような場合は、`Zend_Controller_Action` を継承した共通基底コントローラクラスを作成し、 共通処理をそこにまとめていくようにしましょう。

### 存在しないアクションの処理

コントローラへのリクエストの際に未定義のアクションメソッドが指定された場合は、 `Zend_Controller_Action::__call()` を実行します。 `__call()` とはもちろん、PHP のマジックメソッドで、メソッドのオーバーロード用に使用するものです。

デフォルトでは、このメソッドは `Zend_Controller_Action_Exception` をスローして、コントローラの中にアクションが見つからなかったことを示します。 メソッド名の最後が 'Action' であった場合は、 おそらく存在しないアクションをリクエストしたのであろうとみなします。 そして、コード 404 で例外を返します。その他のメソッドの場合は コード 500 で例外を返します。 これにより、単にページが見つからないだけなのか アプリケーションエラーなのかをエラーハンドラで区別できるようになります。

もし別の動作をさせたい場合は、これをオーバーライドしましょう。 たとえば、エラーメッセージを表示させたい場合は次のようになります。

    class MyController extends Zend_Controller_Action
    {
        public function __call($method, $args)
        {
            if ('Action' == substr($method, -6)) {
                // アクションメソッドが見つからなかった場合は、
                // エラー用のテンプレートをレンダリングします
                return $this->render('error');
            }

            // その他のメソッドの場合は例外をスローします
            throw new Exception('Invalid method "'
                                . $method
                                . '" called',
                                500);
        }
    }

もうひとつの例として、デフォルトコントローラに転送する処理を見てみましょう。

    class MyController extends Zend_Controller_Action
    {
        public function indexAction()
        {
            $this->render();
        }

        public function __call($method, $args)
        {
            if ('Action' == substr($method, -6)) {
                // アクションメソッドが見つからなかった場合は、
                // index アクションに転送します
                return $this->_forward('index');
            }

            // その他のメソッドの場合は例外をスローします
            throw new Exception('Invalid method "'
                                . $method
                                . '" called',
                                500);
        }
    }

`__call()` をオーバーライドするかわりに、 これまで説明してきた各種フックメソッドをオーバーライドしてコントローラをカスタマイズすることもできます。 たとえば、ビューオブジェクトをレジストリに保存したい場合は、 `initView()` メソッドを次のように書き換えることになるでしょう。

    abstract class My_Base_Controller extends Zend_Controller_Action
    {
        public function initView()
        {
            if (null === $this->view) {
                if (Zend_Registry::isRegistered('view')) {
                    $this->view = Zend_Registry::get('view');
                } else {
                    $this->view = new Zend_View();
                    $this->view->setBasePath(dirname(__FILE__) . '/../views');
                }
            }

            return $this->view;
        }
    }

この章の情報をもとに、それぞれの機能の柔軟性をもとにして アプリケーションやサイトの要求に応じたコントローラを作成していくとよいでしょう。