<?xml version="1.0" encoding="UTF-8"?>
<!-- Reviewed: no -->
<!-- EN-Revision: 24249 -->
<sect1 id="zend.controller.action">
    <title>アクションコントローラ</title>

    <sect2 id="zend.controller.action.introduction">
        <title>導入</title>

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

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

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

        <programlisting language="php"><![CDATA[
class FooController extends Zend_Controller_Action
{
    public function barAction()
    {
        // 何かをします
    }

    public function bazAction()
    {
        // 何かをします
    }
}
]]></programlisting>

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

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

        <note>
            <title>デフォルトの挙動</title>

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

            <programlisting language="php"><![CDATA[
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);
    }
}
]]></programlisting>

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

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

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

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

    <sect2 id="zend.controller.action.initialization">
        <title>オブジェクトの初期化</title>

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

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

        <programlisting language="php"><![CDATA[
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'
        ));
    }
}
]]></programlisting>
    </sect2>

    <sect2 id="zend.controller.action.prepostdispatch">
        <title>ディスパッチ前後のフック</title>

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

            <para>
                <link linkend="zend.controller.action.initialization">前述のセクション</link>では、
                <methodname>init()</methodname> メソッドを紹介しましたが、
                このセクションでは <methodname>preDispatch()</methodname> メソッドを紹介します。
                それらの違いは何で、それぞれどのような動作をさせるでしょう？
            </para>

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

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

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

    <sect2 id="zend.controller.action.accessors">
        <title>アクセス用メソッド</title>

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

        <itemizedlist>
            <listitem>
                <para>
                    <emphasis>リクエストオブジェクト</emphasis>:
                    <methodname>getRequest()</methodname> を使用してリクエストオブジェクトを取得し、
                    それを用いてアクションをコールします。
                </para>
            </listitem>

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

                <programlisting language="php"><![CDATA[
$this->getResponse()->setHeader('Content-Type', 'text/xml');
$this->getResponse()->appendBody($content);
]]></programlisting>
            </listitem>

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

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

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

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

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

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

    <sect2 id="zend.controller.action.viewintegration">
        <title>ビューの統合</title>

        <note id="zend.controller.action.viewintegration.viewrenderer">
            <title>デフォルトのビュー統合は ViewRenderer を介します</title>

            <para>
                この節の内容は、
                <link linkend="zend.controller.actionhelpers.viewrenderer">ViewRenderer</link>
                を明示的に無効にしている場合にのみ有効です。
                それ以外の場合はこの節を読み飛ばしてください。
            </para>
        </note>

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

        <sect3 id="zend.controller.action.viewintegration.initview">
            <title>ビューの初期化</title>

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

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

            <programlisting language="php"><![CDATA[
applicationOrModule/
    controllers/
        IndexController.php
    views/
        scripts/
            index/
                index.phtml
        helpers/
        filters/
]]></programlisting>

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

        <sect3 id="zend.controller.action.viewintegration.render">
            <title>ビューのレンダリング</title>

            <para>
                <methodname>render()</methodname> のシグネチャは次のとおりです。
            </para>

            <programlisting language="php"><![CDATA[
string render(string $action = null,
              string $name = null,
              bool $noController = false);
]]></programlisting>

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

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

            <para>
                例を見てみましょう。
            </para>

            <programlisting language="php"><![CDATA[
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();
    }
}
]]></programlisting>
        </sect3>
    </sect2>

    <sect2 id="zend.controller.action.utilmethods">
        <title>ユーティリティメソッド</title>

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

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

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

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

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

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

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

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

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

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

    <sect2 id="zend.controller.action.subclassing">
        <title>アクションコントローラのサブクラスの作成</title>

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

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

        <example id="zend.controller.action.subclassing.example-call">
            <title>存在しないアクションの処理</title>

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

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

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

            <programlisting language="php"><![CDATA[
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);
    }
}
]]></programlisting>

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


            <programlisting language="php"><![CDATA[
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);
    }
}
]]></programlisting>
        </example>

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

        <programlisting language="php"><![CDATA[
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;
    }
}
]]></programlisting>

        <para>
            この章の情報をもとに、それぞれの機能の柔軟性をもとにして
            アプリケーションやサイトの要求に応じたコントローラを作成していくとよいでしょう。
        </para>
    </sect2>
</sect1>
<!--
vim:se ts=4 sw=4 et:
-->
