<sect3 id="zend.controller.actionhelpers.contextswitch">
    <title>ContextSwitch и AjaxContext</title>

    <para>
        Помощник действий <code>ContextSwitch</code> предназначен для облегчения
        возврата ответов в различных форматах.
        Помощник <code>AjaxContext</code> является специализированной версией
        <code>ContextSwitch</code>, которая облегчает возврат ответа на запросы,
        произведенные посредством XmlHttpRequest.
    </para>

    <para>
        Для того, чтобы включить использование какого-либо из этих помощников,
        нужно указать в своем контроллере, какие действия могут отвечать в каком
        контексте. Если входящий запрос означает допустимый контекст для данного
        действия, то помощник производит следующие действия:
    </para>

    <itemizedlist>
        <listitem><para>
            Выключает макеты, если они включены.
        </para></listitem>

        <listitem><para>
            Устанавливает альтернативный суффикс вида, это позволяет эффективно
            разделять скрипты видов для различных контекстов.
        </para></listitem>

        <listitem><para>
            Отправляет HTTP-заголовки ответа, требуемые в данном контексте.
        </para></listitem>

        <listitem><para>
            Опционально вызывает предопределенные функции обратного вызова для
            установки контекста и/или пост-обработки.
        </para></listitem>
    </itemizedlist>

    <para>
        В качестве примера рассмотрим следующий контроллер:
    </para>

    <programlisting role="php"><![CDATA[
class NewsController extends Zend_Controller_Action
{
    /**
     * Начальная страница, производится переход к listAction()
     */
    public function indexAction()
    {
        $this->_forward('list');
    }

    /**
     * Выводит список новостей
     */
    public function listAction()
    {
    }

    /**
     * Просмотр одной новости
     */
    public function viewAction()
    {
    }
}
]]>
    </programlisting>

    <para>
        Предположим, нам нужно, чтобы действие <code>listAction()</code> было
        доступно и в формате XML. Вместо того, чтобы создавать новое действие,
        мы можем указать, что действие <code>listAction()</code> может
        возвращать и ответ в формате XML:
    </para>

    <programlisting role="php"><![CDATA[
class NewsController extends Zend_Controller_Action
{
    public function init()
    {
        $contextSwitch = $this->_helper->getHelper('contextSwitch');
        $contextSwitch->addActionContext('list', 'xml')
                      ->initContext();
    }

    // ...
}
]]>
    </programlisting>

    <para>
        Этим будет:
    </para>

    <itemizedlist>
        <listitem><para>
                Установлен заголовок ответа 'Content-Type' со значением
                'text/xml'.
        </para></listitem>

        <listitem><para>
                Изменено значение суффикса вида на 'xml.phtml' (или
                'xml.[your suffix]', если вы используете другой суффикс вида).
        </para></listitem>
    </itemizedlist>

    <para>
        Теперь нужно создать новый скрипт вида - 'news/list.xml.phtml',
        в котором формируется код XML.
    </para>

    <para>
        Для определения того, нужно ли переключение контекста,
        помощник проверяет метку в объекте запроса. По умолчанию он
        проверяет параметр 'format', но это поведение может быть изменено.
        Это значит, что в большинстве случаев для того, чтобы инициировать
        переключение контекста, достаточно добавить параметр 'format' в
        запрос одним из двух способов:
    </para>

    <itemizedlist>
        <listitem><para>
                Через параметр URL: <code>/news/list/format/xml</code>
                (напоминаем, используемый по умолчанию механизм маршрутизации
                разрешает добавление произвольных пар ключ/значение после
                имени действия в URL)
        </para></listitem>

        <listitem><para>
                Через параметр GET: <code>/news/list?format=xml</code>
        </para></listitem>
    </itemizedlist>

    <para>
        <code>ContextSwitch</code> позволяет задавать любой контекст, включая
        используемый суффикс вида, отправляемые заголовки ответа и функции
        обратного вызова для инициализации и пост-обработки.
    </para>

    <sect4 id="zend.controller.actionhelpers.contextswitch.contexts">
        <title>Доступные по умолчанию контексты</title>

        <para>
            По умолчанию через помощник <code>ContextSwitch</code> используются
            два контекста: json и xml.
        </para>

        <itemizedlist>
            <listitem>
                <para>
                    <emphasis>JSON</emphasis>. Контекст JSON устанавливает
                    заголовок ответа 'Content-Type' в значение
                    'application/json' и суффикс скрипта вида в значение
                    'json.phtml'.
                </para>

                <para>
                    Но по умолчанию использование скриптов вида не обязательно.
                    Контекст просто сериализует все переменные вида и сразу
                    возвращает ответ в формате JSON.
                </para>

                <para>
                    Это поведение может быть отменено путем отключения
                    автоматической сериализации JSON:
                </para>

                <programlisting role="php"><![CDATA[
$this->_helper->contextSwitch()->setAutoJsonSerialization(false);
]]>
                </programlisting>
            </listitem>

            <listitem>
                <para>
                    <emphasis>XML</emphasis>. Контекст XML устанавливает
                    заголовок ответа 'Content-Type' в значение 'text/xml' и
                    суффикс скрипта вида в значение 'xml.phtml'. Для этого
                    контекста нужно создавать скрипты вида.
                </para>
            </listitem>
        </itemizedlist>
    </sect4>

    <sect4 id="zend.controller.actionhelpers.contextswitch.custom">
        <title>Создание своего контекста</title>

        <para>
            Иногда доступных по умолчанию контекстов может быть недостаточно.
            Например, нужно возвращать данные в формате YAML, сериализованный
            PHP, ленты RSS, ATOM и т.д. <code>ContextSwitch</code> позволяет
            добавлять новые контексты.
        </para>

        <para>
            Наиболее простым способом добавления нового контекста является
            использование метода <code>addContext()</code>. Этот метод
            принимает два аргумента - имя контекста и массив спецификации.
            Массив должен включать в себя один или более элементов
            из следующих:
        </para>

        <itemizedlist>
            <listitem>
                <para><emphasis>suffix</emphasis>: суффикс, который должен
                добавляться перед суффиксом, зарегистрированным во
                ViewRenderer.</para>
            </listitem>

            <listitem>
                <para><emphasis>headers</emphasis>: массив пар
                заголовок/значение, которые требуется отправлять в
                ответе.</para>
            </listitem>

            <listitem>
                <para><emphasis>callbacks</emphasis>: массив, который содержит
                ключи 'init' и 'post' (один из них или оба).
                Ключи должны указывать на действующие функции обратного вызова,
                которые могут использоваться для инициализации и пост-обработки
                контекста.</para>

                <para>Вызов функции инициализации производится сразу после
                того, как контекст определен помощником
                <code>ContextSwitch</code>. Вы можете использовать его для
                выполнения произвольной логики. Например, контекст JSON
                использует функцию обратного вызова для отключения
                ViewRenderer, если включена автоматическая сериализация
                JSON.</para>

                <para>Вызов функции пост-обработки производится во время
                операции <code>postDispatch()</code> и может использоваться для
                выполнения произвольной логики. Например, контекст JSON
                использует функцию обратного вызова для определения того,
                включена ли автоматическая сериализация JSON; если включена, то
                помощник сериализует переменные вида в JSON и отправляет ответ,
                иначе включается ViewRenderer.</para>
            </listitem>
        </itemizedlist>

        <para>
            Для взаимодействия с контекстом есть несколько методов:
        </para>

        <itemizedlist>
            <listitem><para>
                <code>addContext($context, array $spec)</code>: добавляет новый
                контекст. Бросает исключение, если контекст уже существует.
            </para></listitem>

            <listitem><para>
                <code>setContext($context, array $spec)</code>: добавляет новый
                контекст или переопределяет существующий. Использует ту же
                спецификацию, что и <code>addContext()</code>.
            </para></listitem>

            <listitem><para>
                <code>addContexts(array $contexts)</code>: добавляет несколько
                контекстов одновременно. Массив <code>$contexts</code> должен
                содержать пары контекст/спецификация. Если какой-либо из
                контекстов уже существует, то бросается исключение.
            </para></listitem>

            <listitem><para>
                <code>setContexts(array $contexts)</code>: добавляет новые
                контексты и переопределяет существующие. Использует ту же
                спецификацию, что и <code>addContexts()</code>.
            </para></listitem>

            <listitem><para>
                <code>hasContext($context)</code>: если контекст с этим именем
                уже существует, то метод возвращает true, иначе false.
            </para></listitem>

            <listitem><para>
                <code>getContext($context)</code>: возвращает контекст по его
                имени в виде массива, следующего спецификации, используемой
                в <code>addContext()</code>.
            </para></listitem>

            <listitem><para>
                <code>getContexts()</code>: возвращает все контексты в виде
                массива пар контекст/спецификация.
            </para></listitem>

            <listitem><para>
                <code>removeContext($context)</code>: удаляет контекст
                по его имени. Возвращает true в случае успеха, false - если
                контекст не найден.
            </para></listitem>

            <listitem><para>
                <code>clearContexts()</code>: удаляет все контексты.
            </para></listitem>
        </itemizedlist>
    </sect4>

    <sect4 id="zend.controller.actionhelpers.contextswitch.actions">
        <title>Установка контекстов для действий</title>

        <para>
            Есть два способа установки доступных для действий контекстов.
            Вы можете либо вручную создавать массивы в своем контроллере, либо
            использовать несколько методов в <code>ContextSwitch</code> для
            "сборки" таких массивов.
        </para>

        <para>
            <code>addActionContext()</code> является основным методом для
            добавления связей действие/контекст. Он принимает два аргумента:
            действие, к которому добавляется контекст, и имя контекста (либо
            массив контекстов). Для примера рассмотрим следующий класс
            контроллера:
        </para>

        <programlisting role="php"><![CDATA[
class FooController extends Zend_Controller_Action
{
    public function listAction()
    {
    }

    public function viewAction()
    {
    }

    public function commentsAction()
    {
    }

    public function updateAction()
    {
    }
}
]]>
        </programlisting>

        <para>
            Предположим, что мы хотим добавить контекст XML к действию 'list',
            а к действию 'comments' - контексты XML и JSON. В этом случае мы
            можем добавить следующий код в метод <code>init()</code>:
        </para>

        <programlisting role="php"><![CDATA[
class FooController extends Zend_Controller_Action
{
    public function init()
    {
        $this->_helper->contextSwitch()
             ->addActionContext('list', 'xml')
             ->addActionContext('comments', array('xml', 'json'))
             ->initContext();
    }
}
]]>
        </programlisting>

        <para>
            Мы можем также просто определить свойство <code>$contexts</code>
            в контроллере:
        </para>

        <programlisting role="php"><![CDATA[
class FooController extends Zend_Controller_Action
{
    public $contexts = array(
        'list'     => array('xml'),
        'comments' => array('xml', 'json')
    );

    public function init()
    {
        $this->_helper->contextSwitch()->initContext();
    }
}
]]>
        </programlisting>

        <para>
            Этот способ менее трудоемкий, но заключает в себе больше
            потенциальных ошибок.
        </para>

        <para>
            Следующие методы могут использоваться для построения связей
            действие/контекст:
        </para>

        <itemizedlist>
            <listitem>
                <para>
                    <code>addActionContext($action, $context)</code>: помечает
                    один или более контекстов как доступные для действия
                    $action. Если связи уже существуют, то производится
                    добавление к ним. <code>$context</code> может быть как
                    одним контекстом, так и массивом контекстов.
                </para>

                <para>
                    Если <code>$context</code> имеет значение <code>true</code>,
                    то все доступные в <code>ContextSwitch</code> контексты
                    помечаются как доступные для действия.
                </para>

                <para>
                    Пустое значение аргумента <code>$context</code> отключит
                    все контексты для данного действия.
                </para>
            </listitem>

            <listitem><para>
                    <code>setActionContext($action, $context)</code>: помечает
                    один или более контекстов как доступные для действия
                    $action. Если связи уже существуют, то метод заменяет их
                    указанными.
                    <code>$context</code> может быть как одним контекстом, так и
                    массивом контекстов.
            </para></listitem>

            <listitem><para>
                    <code>addActionContexts(array $contexts)</code>: добавляет
                    одновременно несколько пар действие/контекст.
                    <code>$contexts</code> должен быть ассоциативным массивом
                    пар действие/контекст. Использует метод
                    <code>addActionContext()</code>, это означает, что если
                    связи для данного действия уже существуют, то указанные в
                    <code>$contexts</code> связи добавляются к ним.
            </para></listitem>

            <listitem><para>
                    <code>setActionContexts(array $contexts)</code>: действует
                    аналогично <code>addActionContexts()</code>, но
                    переопределяет пары действие/контекст, если они уже
                    существуют.
            </para></listitem>

            <listitem><para>
                    <code>hasActionContext($action, $context)</code>:
                    используется для определения того, имеет ли действие $action
                    данный контекст.
            </para></listitem>

            <listitem><para>
                    <code>getActionContexts($action = null)</code>: возвращает
                    все контексты для действия $action или все пары
                    действие/контекст.
            </para></listitem>

            <listitem><para>
                    <code>removeActionContext($action, $context)</code>:
                    удаляет один или более контекстов из действия $action.
                    <code>$context</code> может быть как
                    одним контекстом, так и массивом контекстов.
            </para></listitem>

            <listitem><para>
                    <code>clearActionContexts($action = null)</code>: удаляет
                    все контексты из действия $action или из всех
                    действий с контекстами.
            </para></listitem>
        </itemizedlist>
    </sect4>

    <sect4 id="zend.controller.actionhelpers.contextswitch.initcontext">
        <title>Инициализация переключения контекста</title>

        <para>
            Для того, чтобы инициализировать переключение контекста, необходимо
            вызвать метод <code>initContext()</code> в контроллере действий:
        </para>

        <programlisting role="php"><![CDATA[
class NewsController extends Zend_Controller_Action
{
    public function init()
    {
        $this->_helper->contextSwitch()->initContext();
    }
}
]]>
        </programlisting>

        <para>
            В некоторых случаях может потребоваться принудительное использование
            контекста - например, использовать только контекст XML, если
            переключение контекста включено. Вы можете осуществить это
            передачей контекста методу <code>initContext()</code>:
        </para>

        <programlisting role="php"><![CDATA[
$contextSwitch->initContext('xml');
]]>
        </programlisting>
    </sect4>

    <sect4 id="zend.controller.actionhelpers.contextswitch.misc">
        <title>Дополнительный функционал</title>

        <para>
            Для управления помощником <code>ContextSwitch</code> могут
            использоваться различные методы. Эти методы включают в себя:
        </para>

        <itemizedlist>
            <listitem>
                <para>
                    <code>setAutoJsonSerialization($flag)</code>: По умолчанию
                    контекст JSON будет сериализовать все переменные вида
                    согласно нотации JSON и возвращать их в качестве ответа.
                    Если вы хотите самостоятельно формировать ответ, то должны
                    отключить автоматическую сериализацию через данный метод;
                    это нужно делать до вызова <code>initContext()</code>.
                </para>

                <programlisting role="php"><![CDATA[
$contextSwitch->setAutoJsonSerialization(false);
$contextSwitch->initContext();
]]>
                </programlisting>

                <para>
                    Вы можете получить значение этого флага через метод
                    <code>getAutoJsonSerialization()</code>.
                </para>
            </listitem>

            <listitem>
                <para>
                    <code>setSuffix($context, $suffix,
                        $prependViewRendererSuffix)</code>: Используя этот
                    метод, вы можете задать другой суффикс для использования в
                    контексте $context. Третий аргумент используется для
                    указания того, добавлять ли или нет суффикс из
                    помощника ViewRenderer к новому суффиксу; этот флаг
                    установлен по умолчанию.
                </para>

                <para>
                    Передача пустого значения в качестве суффикса приведет к
                    тому, что будет использоваться только суффикс из
                    ViewRenderer.
                </para>
            </listitem>

            <listitem>
                <para>
                    <code>addHeader($context, $header, $content)</code>:
                    Добавляет заголовок ответа для контекста $context.
                    <code>$header</code> является именем заголовка, а
                    <code>$content</code> - значением для этого заголовка.
                </para>

                <para>
                    Любой контекст может иметь несколько заголовков.
                    <code>addHeader()</code> добавляет дополнительные
                    заголовки в стек заголовков для данного контекста.
                </para>

                <para>
                    Если переданный заголовок уже существует для данного
                    контекста, то бросается исключение.
                </para>
            </listitem>

            <listitem>
                <para>
                    <code>setHeader($context, $header, $content)</code>:
                    <code>setHeader()</code> действует аналогично
                    <code>addHeader()</code> за исключением того, что позволяет
                    переопределять существующие для данного контекста заголовки.
                </para>
            </listitem>

            <listitem>
                <para>
                    <code>addHeaders($context, array $headers)</code>:
                    Добавляет несколько заголовков к контексту $context.
                    Использует <code>addHeader()</code>, поэтому
                    если заголовок уже существует, то бросается исключение.
                    <code>$headers</code> является массивом пар
                    заголовок/значение.
                </para>
            </listitem>

            <listitem>
                <para>
                    <code>setHeaders($context, array $headers.)</code>:
                    действует аналогично <code>addHeaders()</code> за
                    исключением того, что использует <code>setHeader()</code>,
                    что позволяет перезаписывать существующие заголовки.
                </para>
            </listitem>

            <listitem>
                <para>
                    <code>getHeader($context, $header)</code>: возвращает
                    значение заголовка для данного контекста. Если заголовок
                    не найден, то возвращается null.
                </para>
            </listitem>

            <listitem>
                <para>
                    <code>removeHeader($context, $header)</code>: удаляет
                    один заголовок для данного контекста.
                </para>
            </listitem>

            <listitem>
                <para>
                    <code>clearHeaders($context)</code>: удаляет все
                    заголовки для данного контекста.
                </para>
            </listitem>

            <listitem>
                <para>
                    <code>setCallback($context, $trigger, $callback)</code>:
                    устанавливает функцию обратного вызова для триггера $trigger
                    и контекста $context. Триггерами могут быть 'init' или
                    'post' (выбор триггера определяет, когда будет вызвана
                    функция - во время инициализации контекста или операции
                    <code>postDispatch()</code>). <code>$callback</code> должен
                    быть действующей PHP-функцией обратного вызова.
                </para>
            </listitem>

            <listitem>
                <para>
                    <code>setCallbacks($context, array $callbacks)</code>:
                    устанавливает несколько функций обратного вызова для
                    данного контекста. <code>$callbacks</code> должен быть
                    массивом пар триггер/функция обратного вызова. В
                    действительности можно зарегистрировать максимум две
                    функции обратного вызова, одна для инициализации, другая для
                    пост-обработки.
                </para>
            </listitem>

            <listitem>
                <para>
                    <code>getCallback($context, $trigger)</code>: возвращает
                    функцию обратного вызова для триггера $trigger и контекста
                    $context.
                </para>
            </listitem>

            <listitem>
                <para>
                    <code>getCallbacks($context)</code>: возвращает все функции
                    обратного вызова в виде массива пар триггер/функция
                    обратного вызова.
                </para>
            </listitem>

            <listitem>
                <para>
                    <code>removeCallback($context, $trigger)</code>: удаляет
                    функцию обратного вызова для триггера $trigger и контекста
                    $context.
                </para>
            </listitem>

            <listitem>
                <para>
                    <code>clearCallbacks($context)</code>: удаляет все функции
                    обратного вызова для данного контекста.
                </para>
            </listitem>

            <listitem>
                <para>
                    <code>setContextParam($name)</code>: устанавливает параметр
                    запроса, используемый для переключения контекста. По
                    умолчанию установлено значение 'format', но его можно
                    изменить с помощью данного аксессора.
                </para>

                <para>
                    <code>getContextParam()</code> может использоваться для
                    получения текущего значения.
                </para>
            </listitem>

            <listitem>
                <para>
                    <code>setAutoDisableLayout($flag)</code>: По умолчанию
                    макеты отключаются, когда производится переключение
                    контекста. Это потому, что макеты, как правило, используются
                    только с обычным выводом и их использование не имеет смысла
                    для альтернативного контекста. Тем не менее, если вы хотите
                    использовать макеты, то можете изменить это поведение,
                    передав значение <code>false</code> методу
                    <code>setAutoDisableLayout()</code>. Вы должны делать это
                    <emphasis>до</emphasis> вызова <code>initContext()</code>.
                </para>

                <para>
                    Для получения значения этого флага используйте аксессор
                    <code>getAutoDisableLayout()</code>.
                </para>
            </listitem>

            <listitem>
                <para>
                    <code>getCurrentContext()</code> может использоваться
                    для определения того, какой контекст был установлен.
                    Возвращает null, если не было переключения контекста,
                    или метод был вызван до <code>initContext()</code>.
                </para>
            </listitem>
        </itemizedlist>
    </sect4>

    <sect4 id="zend.controller.actionhelpers.contextswitch.ajaxcontext">
        <title>Функционал AjaxContext</title>

        <para>
            Помощник <code>AjaxContext</code> наследует от
            <code>ContextSwitch</code>, поэтому весь функционал, описанный
            для <code>ContextSwitch</code>, доступен и для него. Но есть
            несколько ключевых отличий.
        </para>

        <para>
            Во-первых, он использует другое свойство контроллера для
            определения контекстов, а именно <code>$ajaxable</code>. Поэтому вы
            можете одновременно использовать различные контексты для AJAX и
            обычных HTTP-запросов. Все методы
            <code>*ActionContext*()</code> помощника <code>AjaxContext</code>
            будут записывать в это свойство.
        </para>

        <para>
            Во-вторых, он будет запускаться только тогда, когда производится
            запрос XmlHttpRequest. Для определения того, был ли произведен
            запрос XmlHttpRequest, используется метод
            <code>isXmlHttpRequest()</code> объекта запроса.
            Таким образом, если параметр контекста ('format') был передан в
            запросе, но сам запрос был произведен не через
            XmlHttpRequest, то не будет произведено переключение контекста.
        </para>

        <para>
            И в-третьих, <code>AjaxContext</code> добавляет дополнительный
            контекст - HTML. В этом контексте он устанавливает суффикс в
            значение 'ajax.phtml' для отделения этого контекста от обычных
            запросов. Дополнительные заголовки не возвращаются.
        </para>

        <example id="zend.controller.actionhelpers.contextswitch.ajaxcontext.example">
            <title>Установка действий для ответов на AJAX-запросы</title>

            <para>
                В следующем примере мы указываем, что действия 'view', 'form' и
                'process' могут отвечать на AJAX-запросы. В действиях
                'view' и 'form' мы будем возвращать куски HTML-кода для
                обновления страницы, в действии 'process' - данные в формате
                JSON.
            </para>

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

            <para>
                На клиентской стороне посредством AJAX-библиотеки производятся
                запросы к конечным точкам '/comment/view',
                '/comment/form', '/comment/process' и передается параметр
                'format': '/comment/view/format/html',
                '/comment/form/format/html', '/comment/process/format/json'
                (можно также передавать параметр
                через строку запроса, например: "?format=json").
            </para>

            <para>
                Если ваша AJAX-библиотека передает заголовок 'X-Requested-With:
                XmlHttpRequest', то эти действия будут возвращать ответ в
                требуемом формате.
            </para>
        </example>
    </sect4>
</sect3>
<!--
vim:se ts=4 sw=4 et:
-->
