<sect1 id="zend.controller.response">
    <title>Объект ответа</title>

    <sect2 id="zend.controller.response.usage">
        <title>Использование</title>

        <para>
            Объект ответа представляет собой логическое продолжение к
            <link linkend="zend.controller.request">объекту запроса</link>. Его
            назначение — сбор содержимого ответа и/или его заголовков, таким
            образом, они могут возвращаться как одно целое. Кроме этого,
            фронт-контроллер будет передавать любые пойманные исключения объекту
            ответа, позволяя разработчику должным образом обрабатывать
            исключения. Эта возможность может быть отключена установкой
            <code>Zend_Controller_Front::throwExceptions(true)</code>:
        </para>

        <programlisting role="php"><![CDATA[
$front->throwExceptions(true);
]]>
        </programlisting>

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

        <programlisting role="php"><![CDATA[
$response->sendResponse();
]]>
        </programlisting>

        <note>
            <para>
                По умолчанию фронт-контроллер вызывает
                <code>sendResponse()</code>, когда завершает обработку запроса,
                и, скорее всего, вам никогда не потребуется вызывать этот метод.
                Тем не менее, если вы хотите производить манипуляции с ответом
                или использовать его в тестировании, то вы можете отменить
                это поведение посредством установки флага
                <code>returnResponse</code> методом
                <code>Zend_Controller_Front::returnResponse(true)</code>:
            </para>

            <programlisting role="php"><![CDATA[
$front->returnResponse(true);
$response = $front->dispatch();

// производим необходимые манипуляции с данными (например,
// журналирование), затем отправляем выходные данные:
$response->sendResponse();
]]>
            </programlisting>
        </note>

        <para>
            Разработчики должны использовать объект ответа в своих контроллерах
            действий. Вместо прямого вывода данных и отправки заголовков
            помещайте их в объект ответа:
        </para>

        <programlisting role="php"><![CDATA[
// Внутри контроллера действий:
// Установка заголовка
$this->getResponse()
    ->setHeader('Content-Type', 'text/html')
    ->appendBody($content);
]]>
        </programlisting>

        <para>
            Этим достигается то, что все заголовки будут отправлены
            одновременно, непосредственно до того, как будет отображено
            содержимое ответа.
        </para>

        <note>
            <para>
                Если используется
                <link
                linkend="zend.controller.action.viewintegration">интеграция
                вида</link>, то вам не нужно сохранять результат рендеринга
                скрипта вида в объект ответа, поскольку
                <code>Zend_Controller_Action::render()</code> делает это по
                умолчанию.
            </para>
        </note>

        <para>
            На тот случай, когда произошло исключение в приложении, проверяйте
            флаг <code>isException()</code> в объекте ответа и
            извлекайте исключение, используя <code>getException()</code>. Кроме
            этого, можно создать собственные объекты ответа, которые производят
            перенаправление на страницу ошибки, журналируют сообщения
            исключений, должным образом оформляют сообщения исключений
            для среды разработки и т.д.
        </para>

        <para>
            Вы можете извлекать объект ответа после вызова метода dispatch()
            фронт-контроллера или указать фронт-контроллеру, чтобы он возвращал
            объект ответа вместо его вывода.
        </para>

        <programlisting role="php"><![CDATA[
// Получение объекта ответа после диспетчеризации:
$front->dispatch();
$response = $front->getResponse();
if ($response->isException()) {
    // Журналирование, отправка сообщений и т.д
}

// Либо метод dispatch() фронт-контроллера возвращает его
$front->returnResponse(true);
$response = $front->dispatch();

// Производим какие-либо манипуляции...

// В конце выводим ответ
$response->sendResponse();
]]>
        </programlisting>

        <para>
            По умолчанию сообщения исключений не отображаются. Это поведение
            может быть отменено вызовом метода <code>renderExceptions()</code>
            или включением через метод <code>throwExceptions()</code>
            возможности генерации исключений фронт-контроллером, как показано
            ниже:
        </para>

        <programlisting role="php"><![CDATA[
$response->renderExceptions(true);
$front->dispatch($request, $response);

// или:
$front->returnResponse(true);
$response = $front->dispatch();
$response->renderExceptions();
$response->sendResponse();

// или:
$front->throwExceptions(true);
$front->dispatch();
]]>
        </programlisting>
    </sect2>

    <sect2 id="zend.controller.response.headers">
        <title>Управление заголовками</title>

        <para>
            Как было замечено ранее, одной из обязанностей объекта ответа
            является сбор и отправка заголовков ответа HTTP. Для этого есть
            различные методы:
        </para>

        <itemizedlist>
            <listitem>
                <para>
                    <code>canSendHeaders()</code> используется  для определения
                    того, были ли заголовки отправлены ранее. Опционально он
                    принимает флаг, указывающий, бросать или нет исключение,
                    если заголовки были уже отправлены. Генерация таких
                    исключений может быть отменена посредством установки
                    свойства <code>headersSentThrowsException</code> в
                    <code>false</code>.
                </para>
            </listitem>

            <listitem>
                <para>
                    <code>setHeader($name, $value, $replace = false)</code>
                    используется для установки отдельного заголовка. По
                    умолчанию он не замещает в объекте существующие под тем же
                    именем заголовки. Но установкой
                    <code>$replace</code> в <code>true</code> можно произвести
                    принудительную замену заголовка.
                </para>

                <para>
                    До установки заголовка он вызывает другой метод
                    <code>canSendHeaders()</code> для проверки того, можно ли
                    выполнить данную операцию, и прверяет, было ли брошено
                    исключение.
                </para>
            </listitem>

            <listitem>
                <para>
                    <code>setRedirect($url, $code = 302)</code> устанавливает
                    HTTP-заголовок &quot;Location&quot; для перенаправления.
                    Если был передан код статуса HTTP, то он будет
                    использоваться при перенаправлении.
                </para>

                <para>
                    Внутри себя он вызывает <code>setHeader()</code> со
                    флагом <code>$replace</code> для обеспечения гарантии того,
                    что отправляется только один такой заголовок.
                </para>
            </listitem>

            <listitem>
                <para>
                    <code>getHeaders()</code> возвращает массив всех заголовков.
                    Каждый элемент массива является массивом со ключами 'name' и
                    'value'.
                </para>
            </listitem>

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

            <listitem>
                <para>
                    <code>setRawHeader()</code> может использоваться для
                    установки заголовков, которые не являются парами
                    ключ/значение, например, заголовок статуса HTTP.
                </para>
            </listitem>

            <listitem>
                <para>
                    <code>getRawHeaders()</code> возвращает все
                    зарегистрированные через <code>setRawHeader()</code>
                    заголовки.
                </para>
            </listitem>

            <listitem>
                <para>
                    <code>clearRawHeaders()</code> удаляет все
                    зарегистрированные через <code>setRawHeader()</code>
                    заголовки.
                </para>
            </listitem>

            <listitem>
                <para>
                    <code>clearAllHeaders()</code> удаляет как обычные
                    заголовки из пар ключ/значение, так и
                    &quot;необработанные&quot; (raw).
                </para>
            </listitem>
        </itemizedlist>

        <para>
            Кроме перечисленных выше методов, есть аксессоры для установки и
            получения кода ответа для текущего запроса,
            <code>setHttpResponseCode()</code> и
            <code>getHttpResponseCode()</code>.
        </para>
    </sect2>

    <sect2 id="zend.controller.response.namedsegments">
        <title>Именованные сегменты</title>

        <para>
            Объект ответа поддерживает именованные сегменты. Это позволяет
            делить содержимое ответа на различные сегменты и упорядочивать эти
            сегменты так, что вывод будет возвращаться в определенном порядке.
            Внутри содержимое тела ответа сохраняется в массиве, и могут
            использоваться различные методы-аксессоры для указания размещения и
            имен в этом массиве.
        </para>

        <para>
            Например, вы можете использовать перехватчик
            <code>preDispatch()</code> для добавления верха страницы в
            объект ответа, затем метод действия должен добавить тело
            страницы, а перехватчик <code>postDispatch()</code> добавляет низ
            страницы:
        </para>

        <programlisting role="php"><![CDATA[
// Предполагается, что класс плагина зарегистрирован во фронт-контроллере
class MyPlugin extends Zend_Controller_Plugin_Abstract
{
    public function preDispatch(Zend_Controller_Request_Abstract $request)
    {
        $response = $this->getResponse();
        $view = new Zend_View();
        $view->setBasePath('../views/scripts');

        $response->prepend('header', $view->render('header.phtml'));
    }

    public function postDispatch(Zend_Controller_Request_Abstract $request)
    {
        $response = $this->getResponse();
        $view = new Zend_View();
        $view->setBasePath('../views/scripts');

        $response->append('footer', $view->render('footer.phtml'));
    }
}

// Пример контроллера действий
class MyController extends Zend_Controller_Action
{
    public function fooAction()
    {
        $this->render();
    }
}
]]>
        </programlisting>

        <para>
            В примере выше вызов <code>/my/foo</code> приведет к тому, что
            конечное содержимое тела объекта ответа будет иметь следующую
            структуру:
        </para>

        <programlisting role="php"><![CDATA[
array(
    'header'  => ..., // содержимое верха страницы
    'default' => ..., // содержимое тела страницы из MyController::fooAction()
    'footer'  => ...  // содержимое низа страницы
);
]]>
        </programlisting>

        <para>
            Рендеринг производится в том порядке, в котором элементы
            представлены в массиве.
        </para>

        <para>
            Для управления именованными сегментами могут использоваться
            различные методы:
        </para>

        <itemizedlist>
            <listitem>
                <para>
                    <code>setBody()</code> и <code>appendBody()</code> позволяют
                    передавать второе значение, <code>$name</code>, обозначающее
                    именованный сегмент. В любом случае, если вы передаете его,
                    он перепишет этот именованный сегмент или создаст его, если
                    он не существует (по умолчанию добавляя в конец массива).
                    Если методу <code>setBody()</code> не был передан
                    именованный сегмент, то будет сброшен весь массив
                    содержимого тела. Если методу <code>appendBody()</code> не
                    было передано имя сегмента, то содержимое будет добавлено в
                    конец сегмента с именем 'default'.
                </para>
            </listitem>

            <listitem>
                <para>
                    <code>prepend($name, $content)</code> будет создавать
                    сегмент с именем <code>$name</code> и помещать его в начало
                    массива. Если сегмент уже существует, то он будет удален до
                    операции добавления (т.е. перезаписан).
                </para>
            </listitem>

            <listitem>
                <para>
                    <code>append($name, $content)</code> будет создавать сегмент
                    с именем <code>$name</code> и помещать его в конец массива.
                    Если сегмент уже существует, то он будет удален до операции
                    добавления.
                </para>
            </listitem>

            <listitem>
                <para>
                    <code>insert($name, $content, $parent = null, $before = false)</code>
                    будет создавать сегмент с именем <code>$name</code>. Если
                    был передано имя сегмента <code>$parent</code> (родитель),
                    то новый сегмент будет помещен до или после этого сегмента
                    (основываясь на значениии <code>$before</code>) в массиве.
                    Если сегмент уже существует, то он будет удален до операции
                    добавления.
                </para>
            </listitem>

            <listitem>
                <para>
                    <code>clearBody($name = null)</code> удалит один
                    сегмент, если был передано его имя <code>$name</code>,
                    иначе будет удален весь массив.
                </para>
            </listitem>

            <listitem>
                <para>
                    <code>getBody($spec = false)</code> может использоваться для
                    получения массива сегментов, если <code>$spec</code> - имя
                    именованного сегмента. Если равен false, то будет возвращена
                    строка, сформированная посредством объединения всех
                    сегментов в порядке следования. Если <code>$spec</code>
                    равен true, то он вернет массив содержимого тела.
                </para>
            </listitem>
        </itemizedlist>
    </sect2>

    <sect2 id="zend.controller.response.exceptions">
        <title>Проверка на исключения в объекте ответа</title>

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

        <itemizedlist>
            <listitem>
                <para>
                    <code>setException(Exception $e)</code> позволяет
                    произвести регистрацию исключения.
                </para>
            </listitem>

            <listitem>
                <para>
                    <code>isException()</code> позволяет определить, было ли
                    зарегистрировано какое-либо исключение.
                </para>
            </listitem>

            <listitem>
                <para>
                    <code>getException()</code> возвращает весь стек
                    исключений.
                </para>
            </listitem>

            <listitem>
                <para>
                    <code>hasExceptionOfType($type)</code> позволяет определить
                    наличие в стеке исключения определенного класса.
                </para>
            </listitem>

            <listitem>
                <para>
                    <code>hasExceptionOfMessage($message)</code> позволяет
                    определить наличие в стеке исключения с заданным
                    сообщением.
                </para>
            </listitem>

            <listitem>
                <para>
                    <code>hasExceptionOfCode($code)</code> позволяет определить
                    наличие в стеке исключения с определенным кодом.
                </para>
            </listitem>

            <listitem>
                <para>
                    <code>getExceptionByType($type)</code> позволяет извлечь все
                    исключения определенного класса из стека.
                    Возвращает false, если не был найдено ни одно
                    исключение, иначе - массив исключений.
                </para>
            </listitem>

            <listitem>
                <para>
                    <code>getExceptionByMessage($message)</code> позволяет
                    извлекать все исключения с заданным сообщением из стека.
                    Возвращает false, если не был найдено ни одно
                    исключение, иначе - массив исключений.
                </para>
            </listitem>

            <listitem>
                <para>
                    <code>getExceptionByCode($code)</code> позволяет извлекать
                    все исключения с определенным кодом из стека.
                    Возвращает false, если не был найдено ни одно
                    исключение, иначе - массив исключений.
                </para>
            </listitem>

            <listitem>
                <para>
                    <code>renderExceptions($flag)</code> позволяет установить
                    флаг, указывающий, должны или нет отправляться исключения
                    вместе с ответом.
                </para>
            </listitem>
        </itemizedlist>
    </sect2>

    <sect2 id="zend.controller.response.subclassing">
        <title>Создание подклассов объекта ответа</title>

        <para>
            Назначением объекта ответа является сбор заголовков и содержимого из
            различных действий и плагинов, и возврат их клиенту. Кроме этого, он
            собирает все возникающие ошибки (исключения) для того, чтобы
            обрабатывать их, возвращать или прятать от конечного пользователя.
        </para>

        <para>
            Базовым классом ответа является
            <code>Zend_Controller_Response_Abstract</code>, все создаваемые
            вами подклассы должны наследовать от него или одного из его
            потомков. Доступные методы были перечислены в предыдущих разделах.
        </para>

        <para>
            Цели, преследуемые при создании подклассов объекта ответа, включают
            в себя изменение способа вывода, основанное на
            окружении запроса (например, не отправлять заголовки для запросов
            CLI или PHP-GTK), добавление функционала для возвращения конечного
            вида, основанного на содержимом, сохраненном в именованном сегменте,
            и т.д.
        </para>
    </sect2>
</sect1>
<!--
vim:se ts=4 sw=4 et:
-->
