# ContextSwitch and AjaxContext

`ContextSwitch` 动作助手用来使在请求后返回不同的响应格式变得容易。`AjaxContext` 助手是 `ContextSwitch` 的特别版本，用来返回响应到 XmlHttpRequests。

你必须在关于动作能够响应哪个上下文（context）的控制器中打开（enable）其中一个。如果一个进来的请求对给定的动作指出一个有效的上下文，助手将做：

- 如果布局被打开，则关闭它。

- 设置一个备用的视图后缀，为上下文有效地请求一个分离的视图脚本。

- 为期望的上下文发送适当的响应头。

- 可选地调用特别的回调（callback）来设置上下文和/或执行处理后的任务。

例子，考虑下列的控制器：

    class NewsController extends Zend_Controller_Action
    {
        /**
         * Landing page; forwards to listAction()
         */
        public function indexAction()
        {
            $this->_forward('list');
        }

        /**
         * List news items
         */
        public function listAction()
        {
        }

        /**
         * View a news item
         */
        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.[你的后缀]'）。

现在你需要创建一个新的视图脚本 'news/list.xml.phtml'，它将创建和解析 XML。

为决定一个请求是否应该初始化一个上下文开关（context switch），这个助手检查在请求对象理的令牌。缺省地，它寻找一个 'format' 参数，尽管这是可配置的。这意味着，在大多数情况下，为触发一个上下文开关，你可以添加一个 'format' 参数给你的请求：

- 通过 URL 参数：`/news/list/format/xml` （回忆一下，缺省路由模式（schema）允许在动作后跟随任意的键/值对）

- 通过 GET 参数：`/news/list?format=xml`

`ContextSwitch` 允许指定任意的上下文，包括需要修改后缀的、任何应该被发送的响应头和任意的用来初始化和善后的回调（callback）。

## 缺省可用的上下文

缺省地，`ContextSwitch` 助手的两个上下文可用：json 和 xml。

- JSON. JSON 上下文设置 'Content-Type' 响应头为 'application/json'，设置视图脚本后缀为 'json.phtml'。

  缺省地，不需要视图脚本，它将系列化所有的视图变量，立即发出 JSON 响应。

  通过关闭 auto-JSON serialization 可禁止这个行为：

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

- XML. XML 上下文设置 'Content-Type' 响应头为 'text/xml'，设置视图脚本后缀为 'xml.phtml'。你需要为这个上下文创建新的视图脚本。

## 创建定制的上下文

有时候，缺省的上下文不够用，例如你需要返回 YAML，或系列化 PHP、RSS 或 ATOM feed 等等，`ContextSwitch` 正是你需要的东西。

最容易的添加新的上下文的办法是通过 `addContext()` 方法。这个方法带有两个参数：上下文的名称和一个规范（specification）数组，规范应当包含下列中的一个或多个：

- suffix: 当在视图解析器里注册时，预先准备的缺省的视图后缀。

- headers: 作为响应的一部分发送的头/值（header/value）对的数组。

- callbacks: 数组，包含一个或更多键 'init' 或 'post'，指向有效的可用于上下文和善后处理的 PHP 回调（callback）。

  回调的初始化发生在当 `ContextSwitch` 检测到上下文时，你可以用它来执行任意的应该发生的逻辑。作为例子，当 auto-JSON serialization 是 on 的时候，JSON 上下文使用回调来关闭视图解析器（ViewRenderer）。

  善后处理（post processing）发生在动作的 `postDispatch()` 程序期间，可用来执行任意的逻辑。作为例子，JSON 上下文使用回调（callback）来决定是否 auto-JSON serialization 是 on；如果是，它系列化视图变量到 JSON 和 发送响应，如果不是，它重新打开（re-enable）视图解析器（ViewRenderer）。

和上下文交互作用的方法：

- `addContext($context, array $spec)`: 添加新的上下文，如果上下文存在，抛出一个异常。

- `setContext($context, array $spec)`: 添加新的上下文或重写一个已存在的上下文，和 `addContext()` 使用相同的规范（specification）。

- `addContexts(array $contexts)`: 一次添加多个上下文。`$contexts` 应当是上下文/规范（context/specification）对的数组。如果任何一个上下文存在，就抛出异常。

- `setContexts(array $contexts)`: 添加和重写存在的上下文（多于一个），和 `addContexts()` 使用相同的规范。

- `hasContext($context)`: 如果上下文存在，返回 true，否则返回 false。

- `getContext($context)`: 通过名称来获取一个单个的上下文，返回一个遵循用于 `addContext()` 的规范的数组。

- `getContexts()`: 获取所有的上下文，返回上下文/规范对的数组。

- `removeContext($context)`: 通过名称来清除一个单个的上下文，成功返回 true，如果没有发现上下文返回 false。

- `clearContexts()`: 清除所有上下文。

## 为每个动作设置上下文

有两个设置可用的上下文的机制：或者在控制器里手工创建数组，或者使用在 `ContextSwitch` 里的方法来装配它们。

添加动作/上下文关系的基本方法是 `addActionContext()` 。它有两个参数：上下文被添加到的动作和上下文的名称或上下文数组的其中之一。作为例子，考虑下列的控制器类：

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

        public function viewAction()
        {
        }

        public function commentsAction()
        {
        }

        public function updateAction()
        {
        }
    }

假如我们想添加 XML 上下文到 'list'动作、XML 和 JSON 上下文到 'comments' 动作，可以在 `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 的空值将关闭（disable）所有给定动作的上下文。

- `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()` 来获取 flag 的值。

- `setSuffix($context, $suffix, $prependViewRendererSuffix)`: 用这个方法，你可以为给定的上下文指定一个不同的后缀。第三个参数用来指示是否用新的后缀预先准备当前视图解析器，这个 flag 缺省为打开。

  传递空值给后缀将导致只有视图解析器的后缀被使用。

- `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)`: 为给定的上下文在给定的触发器设置回调（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)`: 缺省地，当上下文开关出现，布局是关闭的；这是因为一般布局将只用来返回正常的相应，对备用的（alternate）上下文没有意义。然而，如果想使用布局（也许你对新的上下文有个布局），你可以通过传递一个 false 的值给 `setAutoDisableLayout()` 来改变它的行为。这应当 在 调用 `initContext()` 之前 来做。

  用访问器 `getAutoDisableLayout()` 来获取这个 flag 的值。

- `getCurrentContext()` 可用来确定什么上下文被检测到，如果有的话。如果没有上下文开关发生，或者在调用 `initContext()` 之前调用它，则返回 null。

## AjaxContext 函数

`AjaxContext` 助手继承 `ContextSwitch`，所有 `ContextSwitch` 函数列表对它也有效，只是有些小小的不同。

首先，它为确定上下文使用不同的动作控制器属性 － `$ajaxable`。这样你就可以对 AJAX 和 普通的 HTTP 请求使用不同的上下文。`AjaxContext` 的各种各样 `*ActionContext*()` 方法将写到这个属性。

其次，由请求对象的 `isXmlHttpRequest()` 方法来确定，它只有在 XmlHttpRequest 出现后才触发。这样，如果上下文参数 （'format'）在请求中传递，但请求没有做成 XmlHttpRequest，不会触发上下文开关。

第三，`AjaxContext` 添加另外的上下文 － HTML。在这个上下文中，为了区别这个上下文和普通的请求，它设置后缀为 'ajax.phtml'。没有另外的头返回。

### 允许动作响应 Ajax 的请求

在下面的例子中，我们允许对动作 'view'、 'form' 和 'process' 的请求响应 AJAX 的请求。在头两个例子 'view' 和 'form'，返回不更新页面的 HTML 片段；在最后的例子，返回 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()
        {
            // Pull a single comment to view.
            // When AjaxContext detected, uses the comment/view.ajax.phtml
            // view script.
        }

        public function formAction()
        {
            // Render the "add new comment" form.
            // When AjaxContext detected, uses the comment/form.ajax.phtml
            // view script.
        }

        public function processAction()
        {
            // Process a new comment
            // Return the results as JSON; simply assign the results as
            // view variables, and JSON will be returned.
        }
    }

在客户端，AJAX 库将请求终点 '/comment/view'、 '/comment/form' 和 '/comment/process'，并传递 'format' 参数：'/comment/view/format/html'、'/comment/form/format/html' 和 '/comment/process/format/json'。（或者你可以通过查询字符串（ query string） 传递参数，如："?format=json"）

假定你的库传递 'X-Requested-With:XmlHttpRequest'头，这些动作将返回适当的响应格式。