# ContextSwitch および AjaxContext

ContextSwitch アクションヘルパーは、 リクエストに対してさまざまなレスポンスを返す機能を実現するためのものです。 AjaxContext ヘルパーは ContextSwitch をより特化したもので、 レスポンスを XmlHttpRequests で返す機能を提供します。

いずれかを有効にするには、コントローラに対して 「どのアクションがどのコンテキストに対応するのか」 を教えてやる必要があります。 やってきたリクエストがそのアクションで有効なコンテキストである場合、 ヘルパーが行う処理は次のようになります。

- レイアウト機能が有効な場合に、それを無効にする。

- 別のビューサフィックスを設定し、 コンテキストに応じて別のビュースクリプトを効率よく扱えるようにする。

- コンテキストに応じて適切なレスポンスヘッダを送信する。

- オプションで、指定したコールバックを実行して コンテキストの設定や後処理を行う。

たとえば、次のようなコントローラを考えてみましょう。

    class NewsController extends Zend_Controller_Action
    {
        /**
         * トップページは listAction() に転送します
         */
        public function indexAction()
        {
            $this->_forward('list');
        }

        /**
         * ニュースの一覧
         */
        public function listAction()
        {
        }

        /**
         * ニュースの閲覧
         */
        public function viewAction()
        {
        }
    }

ここで、`listAction()` の結果を XML 形式でも返せるようにしたくなったとしましょう。 わざわざ別のアクションを作らなくても、 XML でレスポンスを返すように指示することができます。

    class NewsController extends Zend_Controller_Action
    {
        public function init()
        {
            $contextSwitch = $this->_helper->getHelper('contextSwitch');
            $contextSwitch->addActionContext('list', 'xml')
                          ->initContext();
        }

        // ...
    }

これが何を行っているかというと、

- レスポンスヘッダ 'Content-Type' を '`text/xml`' にします。

- ビューのサフィックスを '`xml.phtml`' (あるいは別のサフィックスをを使っているなら 'xml.[your suffix]') に変更します。

さて、次は新しいビュースクリプト '`news/list.xml.phtml`' を作成しましょう。これが XML の作成とレンダリングを行います。

あるリクエストがコンテキストスイッチを起動するかどうかを判断するために、 このヘルパーはリクエストオブジェクト内のトークンを調べます。 デフォルトでは 'format' というパラメータを調べることになっていますが、 これは変更することもできます。つまり、 ほとんどの場合は、リクエストに 'format' パラメータを追加するだけで コンテキストスイッチを行えるということです。

- URL のパラメータで指定する場合: `/news/list/format/xml` (デフォルトのルーティング方式では、アクションに続けて任意の キー/値 のペアを指定できたことを思い出しましょう)

- `GET` パラメータで指定する場合: `/news/list?format=xml`

ContextSwitch では任意のコンテキストを指定することができます。 つまり (もし存在するなら) サフィックスを自由に変更したり 送信するレスポンスヘッダを任意のものに変更したり、 任意のコールバックで初期化や後処理を行ったりができるということです。

## デフォルトで使用できるコンテキスト

ContextSwitch ヘルパーで 使用できるデフォルトのコンテキストは、json と xml のふたつです。

- JSON。JSON コンテキストは、 'Content-Type' レスポンスヘッダを '`application/json`' に設定し、 ビュースクリプトのサフィックスを '`json.phtml`' とします。

  しかし、デフォルトではビュースクリプトは不要です。 これは、すべてのビュー変数を単純にシリアライズして JSON レスポンスを直接発行するものです。

  自動 JSON シリアライズ機能を使わないようにすることもできます。

      $this->_helper->contextSwitch()->setAutoJsonSerialization(false);

- XML。XML コンテキストは、 'Content-Type' レスポンスヘッダを '`text/xml`' に設定し、 ビュースクリプトのサフィックスを '`xml.phtml`' とします。 このコンテキスト用に、新しいビュースクリプトを作成する必要があります。

## 独自のコンテキストの作成

デフォルトのコンテキストだけでは対応しきれないこともあるでしょう。 たとえば結果を YAML で返したり、PHP のシリアライズ文字列で返したり、 あるいは RSS や ATOM フィードで返したりといったようにです。 ContextSwitch を使用すればそれも可能です。

新たなコンテキストを追加する最も簡単な方法は `addContext()` メソッドを使用することです。 このメソッドの引数は 2 つで、コンテキストの名前と 設定の配列を指定します。設定には、以下のうちのひとつあるいは複数を指定します。

- suffix: ViewRenderer で登録されているデフォルトのビューサフィックスの 前に追加するサフィックス。

- headers: ヘッダ/値 のペアの配列で、レスポンスとともに送信したいもの。

- callbacks: キー 'init' や 'post' を含む配列で、それぞれ コンテキストの初期化や後処理の際に使用する PHP コールバックを指定します。

  初期化コールバックは、ContextSwitch が コンテキストを検出した場合に実行されます。 これを使用して、任意のロジックを実行することができます。 たとえば JSON コンテキストでは、 このコールバックを使用して 自動 JSON シリアライズが有効な場合に ViewRenderer を無効化しています。

  後処理はアクションの `postDispatch()` で発生します。これを使用して、任意のロジックを実行することができます。 たとえば JSON コンテキストでは、このコールバックを使用して 自動 JSON シリアライズ機能が有効か無効かを調べています。 有効な場合はビュー変数を JSON にシリアライズしてレスポンスに送信し、 無効な場合は ViewRenderer を再度有効にします。

コンテキストを操作するメソッドには次のようなものがあります。

- `addContext($context, array $spec)`: 新しいコンテキストを追加する。 そのコンテキストが既に存在する場合は例外をスローします。

- `setContext($context, array $spec)`: 新しいコンテキストを追加、あるいは既存のコンテキストを上書きする。 `addContext()` と同じように指定します。

- `addContexts(array $contexts)`: 複数のコンテキストを一度に追加する。配列 `$contexts` は、コンテキスト/設定 のペアの配列となります。 既に存在するコンテキストを指定した場合は例外をスローします。

- `setContexts(array $contexts)`: 新しいコンテキストを追加、あるいは既存のコンテキストを上書きする。 `addContexts()` と同じように指定します。

- `hasContext($context)`: そのコンテキストが存在する場合に `TRUE`、存在しない場合に `FALSE` を返します。

- `getContext($context)`: 指定した名前のコンテキストを取得する。 `addContext()` で使用する設定とあわせた配列を返します。

- `getContexts()`: すべてのコンテキストを取得する。 コンテキスト/設定 のペアの配列を返します。

- `removeContext($context)`: 指定した名前のコンテキストを削除する。成功した場合に `TRUE`、 そのコンテキストが見つからない場合に `FALSE` を返します。

- `clearContexts()`: すべてのコンテキストを削除する。

## アクションごとのコンテキストの設定

使用するコンテキストの設定には 2 通りの方法があります。 コントローラ内で手動で配列を作成する方法、 そして ContextSwitch のメソッドでそれを作成する方法です。

アクションとコンテキストの関連を追加するメソッドは `addActionContext()` です。 このメソッドには 2 つの引数を指定します。 ひとつはコンテキストを追加したいアクション、 もうひとつはコンテキスト名あるいはコンテキスト名の配列です。 たとえば、次のようなコントローラクラスを考えてみましょう。

    class FooController extends Zend_Controller_Action
    {
        public function listAction()
        {
        }

        public function viewAction()
        {
        }

        public function commentsAction()
        {
        }

        public function updateAction()
        {
        }
    }

ここで、'list' アクションに XML コンテキストを、 そして 'comments' アクションに XML コンテキストと JSON コンテキストを追加してみることにします。これは `init()` メソッドで行います。

    class FooController extends Zend_Controller_Action
    {
        public function init()
        {
            $this->_helper->contextSwitch()
                 ->addActionContext('list', 'xml')
                 ->addActionContext('comments', array('xml', 'json'))
                 ->initContext();
        }
    }

あるいは、単純に配列プロパティ `$contexts` を設定することもできます。

    class FooController extends Zend_Controller_Action
    {
        public $contexts = array(
            'list'     => array('xml'),
            'comments' => array('xml', 'json')
        );

        public function init()
        {
            $this->_helper->contextSwitch()->initContext();
        }
    }

このほうがオーバーヘッドが少なくなりますが、 書き間違える可能性もあります。

コンテキストの関連付けを行うメソッドには次のようなものがあります。

- `addActionContext($action, $context)`: ひとつあるいは複数のコンテキストを、あるアクションで使用できるようにする。 関連付けがすでに設定されている場合は、それに追記します。 `$context` は、単一のコンテキストか コンテキストの配列となります。

  コンテキストとして `TRUE` を指定すると、 すべてのコンテキストをそのアクションで使用できるようにします。

  `$context` に空の値を指定すると、 そのアクションではどのコンテキストも使用できないようにします。

- `setActionContext($action, $context)`: ひとつあるいは複数のコンテキストを、あるアクションで使用できるようにする。 関連付けがすでに設定されている場合は、指定したものでそれを置き換えます。 `$context` は、単一のコンテキストか コンテキストの配列となります。

- `addActionContexts(array $contexts)`: いくつかの アクション/コンテキスト のペアを一度に追加する。 `$contexts` は、アクション/コンテキスト のペアの連想配列です。これは `addActionContext()` へのプロキシとなります。つまり、既に別のペアが登録されている場合は そこに追記します。

- `setActionContexts(array $contexts)`: `addActionContexts()` と同様だが、既存の アクション/コンテキスト のペアは上書きする。

- `hasActionContext($action, $context)`: 特定のアクションにそのコンテキストが存在するかどうかを調べる。

- `getActionContexts($action = null)`: 指定したアクションのすべてのコンテキスト、 あるいはすべての アクション/コンテキスト のペアを返す。

- `removeActionContext($action, $context)`: ひとつあるいは複数のコンテキストを、指定したアクションから削除する。 `$context` は、単一のコンテキストか コンテキストの配列となります。

- `clearActionContexts($action = null)`: すべてのコンテキストを、指定したアクションから削除する。 あるいはすべてのアクションのすべてのコンテキストを削除する。

## コンテキストスイッチの初期化

コンテキストスイッチを初期化するには、アクションコントローラで `initContext()` をコールする必要があります。

    class NewsController extends Zend_Controller_Action
    {
        public function init()
        {
            $this->_helper->contextSwitch()->initContext();
        }
    }

時には、使用するコンテキストを決めてしまいたいこともあるでしょう。 たとえば、コンテキストスイッチが起動したときには XML コンテキストだけを使わせたいという場合などです。 その場合は、そのコンテキストを `initContext()` に渡します。

    $contextSwitch->initContext('xml');

## 追加機能

さまざまなメソッドを使用することで、 ContextSwitch ヘルパーの挙動を変更することができます。 たとえば次のようなメソッドが存在します。

- `setAutoJsonSerialization($flag)`: デフォルトでは、JSON コンテキストはビュー変数をすべてシリアライズし、 JSON 記法にしたものをレスポンスとして返します。 レスポンスを自分で作成したい場合はこれをオフにしなければなりません。 これは、`initContext()` をコールする前に行う必要があります。

      $contextSwitch->setAutoJsonSerialization(false);
      $contextSwitch->initContext();

  このフラグの値を取得するには `getAutoJsonSerialization()` を使用します。

- `setSuffix($context, $suffix, $prependViewRendererSuffix)`: このメソッドは、指定したコンテキストに対して 別のサフィックスを設定します。 3 番目の引数を使用すると、 ViewRenderer のサフィックスの前に 新しいサフィックスをつけるのかどうかを指定することができます。 このフラグはデフォルトで有効になっています。

  サフィックスに空の値を指定すると、 ViewRenderer のサフィックスのみを使用します。

- `addHeader($context, $header, $content)`: 指定したコンテキストにレスポンスヘッダを追加します。 `$header` はヘッダの名前で、 `$content` はそのヘッダに渡す値となります。

  各コンテキストは複数のヘッダを持つことができます。 `addHeader()` は、 そのヘッダをコンテキストのヘッダスタックに追加します。

  指定した `$header` がそのコンテキストに既に存在する場合は、 例外をスローします。

- `setHeader($context, $header, $content)`: `setHeader()` は `addHeader()` とほぼ同じですが、 既存のコンテキストヘッダを上書きします。

- `addHeaders($context, array $headers)`: 指定したコンテキストに一度に複数のヘッダを追加します。 `addHeader()` へのプロキシとして動作するので、 そのヘッダがすでに存在する場合は例外をスローします。 `$headers` は ヘッダ/コンテキスト のペアの配列です。

- `setHeaders($context, array $headers.)`: `addHeaders()` と似ていますが、これは `setHeader()` へのプロキシとして動作し、 既存のヘッダは上書きします。

- `getHeader($context, $header)`: 指定したコンテキストのヘッダの値を取得します。 見つからない場合は `NULL` を返します。

- `removeHeader($context, $header)`: 指定したコンテキストの単一のヘッダを削除します。

- `clearHeaders($context, $header)`: 指定したコンテキストのすべてのヘッダを削除します。

- `setCallback($context, $trigger, $callback)`: 指定したコンテキストにおける指定したトリガーのコールバックを設定します。 トリガーに指定できる値は 'init' あるいは 'post' (それぞれ、コンテキストの初期化時と postDispatch 時を表します) です。 `$callback` は PHP のコールバックとして正しい形式でなければなりません。

- `setCallbacks($context, array $callbacks)`: 指定したコンテキストに複数のコールバックを設定します。 `$callbacks` は トリガー/コールバック のペアとなります。実際のところ、登録できるコールバックは ほとんどふたつだけで、初期化用のものと後処理用のものです。

- `getCallback($context, $trigger)`: 指定したコンテキストにおける指定したトリガーのコールバックを取得します。

- `getCallbacks($context)`: 指定したコンテキストにおけるすべてのコールバックを取得します。 トリガー/コールバック のペアを返します。

- `removeCallback($context, $trigger)`: 指定したコンテキストにおける指定したトリガーのコールバックを削除します。

- `clearCallbacks($context)`: 指定したコンテキストにおけるすべてのコールバックを削除します。

- `setContextParam($name)`: コンテキストスイッチが要求されたかどうかを調べるための リクエストパラメータを設定します。デフォルトは 'format' ですが、このアクセサを使用することで変更することができます。

  `getContextParam()` で、現在の値を取得することができます。

- `setAutoDisableLayout($flag)`: デフォルトでは、コンテキストスイッチが発生したときには レイアウト機能が無効になります。これは、 レイアウト機能は通常は普通のレスポンスの時に使用するものであって それ以外のコンテキストでは無意味だからです。 しかし、時にはレイアウト機能を使いたいこともあるでしょう (新しいコンテキスト用のレイアウトがある場合など)。 その場合は、`setAutoDisableLayout()` に `FALSE` を渡します。これは、 `initContext()` をコールするより 前に 行わなければなりません。

  このフラグの現在の値を取得するには、アクセサ `getAutoDisableLayout()` を使用します。

- `getCurrentContext()` を使うと、 現在のコンテキストを取得することができます。 コンテキストスイッチが発生していない場合や `initContext()` の起動前にコールした場合は `NULL` を返します。

## AjaxContext の機能

AjaxContext ヘルパーは ContextSwitch を継承したものです。 ContextSwitch の機能はすべて使用することができます。 しかし、いくつか重要な違いがあります。

まず、コンテキストを決めるアクションコントローラのプロパティは `$ajaxable` となります。これにより、 AJAX 用と通常の HTTP リクエスト用で別のコンテキストを使用できるようになります。 AjaxContext の *ActionContext()* 系のメソッドは、このプロパティに書き込みます。

次に、これは XmlHttpRequest が発生した場合にのみ起動します。 リクエストオブジェクトの `isXmlHttpRequest()` メソッドで判断します。したがって、たとえコンテキストパラメータ ('format') をリクエストで渡したとしても、そのリクエストが XmlHttpRequest でない場合はコンテキストスイッチが発生しません。

3 番目に、AjaxContext は HTML コンテキストを追加します。 このコンテキストでは、サフィックスを '`ajax.phtml`' として通常のリクエストのコンテキストと区別しています。 追加のヘッダは返しません。

### Ajax リクエストに対してアクションに応答させる

この例では、アクション 'view'、'form' および 'process' に対する AJAX リクエストにレスポンスを返させるようにしています。 最初のふたつ 'view' および 'form' では、HTML コード片を返してページを更新させます。最後の 'process' については JSON を返しています。

    class CommentController extends Zend_Controller_Action
    {
        public function init()
        {
            $ajaxContext = $this->_helper->getHelper('AjaxContext');
            $ajaxContext->addActionContext('view', 'html')
                        ->addActionContext('form', 'html')
                        ->addActionContext('process', 'json')
                        ->initContext();
        }

        public function viewAction()
        {
            // 単一のコメントを表示します
            // AjaxContext の場合は comment/view.ajax.phtml
            // を使用します
        }

        public function formAction()
        {
            // 新規コメントの追加フォームをレンダリングします
            // AjaxContext の場合は comment/form.ajax.phtml
            // を使用します
        }

        public function processAction()
        {
            // 新規コメントを処理します
            // 結果を JSON で返します。結果をビュー変数に格納するだけで、
            // JSON でそれを返してくれます
        }
    }

クライアント側では、AJAX ライブラリからエンドポイント '`/comment/view`'、'`/comment/form`' そして '`/comment/process`' へリクエストを送ることになります。 その際に、'format' パラメータを '`/comment/view/format/html`'、'`/comment/form/format/html`' そして '`/comment/process/format/json`' のように指定します (あるいはクエリ文字列で "?format=json" のようにしてもかまいません)。

ライブラリ側で 'X-Requested-With: XmlHttpRequest' ヘッダが設定されていれば、 このアクションは適切な形式でレスポンスを返します。