<?xml version="1.0" encoding="UTF-8"?>
<!-- Reviewed: no -->
<sect1 id="zend.controller.exceptions">
    <title>Исключения</title>

    <sect2 id="zend.controller.exceptions.introduction">
        <title>Введение</title>

        <para>
            Компоненты MVC в Zend Framework используют фронт-контроллер, это
            означает, что все запросы к сайту проходят через единственную
            точку входа. Поэтому все исключения, брошенные внутри компонент, в
            конечном итоге доходят до фронт-контроллера, что позволяет
            разработчикам обрабатывать их в одном месте.
        </para>

        <para>
            Сообщения исключений и данные обратной трассировки часто содержат
            важную системную информацию, такую, как текст запросов SQL,
            местонахождение файлов и т.п. Поэтому в целях защиты вашего сайта
            <classname>Zend_Controller_Front</classname> по умолчанию отлавливает все
            исключения и регистрирует их в объектах ответа; в свою очередь,
            объект ответа по умолчанию не отображает сообщения исключений.
        </para>
    </sect2>

    <sect2 id="zend.controller.exceptions.handling">
        <title>Обработка исключений</title>

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

        <itemizedlist>
            <listitem>
                <para>
                    По умолчанию <link
                    linkend="zend.controller.plugins.standard.errorhandler">плагин
                    ErrorHandler</link> зарегистрирован и включен. Этот плагин
                    предназначен для обработки:
                </para>

                <itemizedlist>
                    <listitem><para>
                        Ошибок, вызванных отсутствием запрошенного контроллера
                        или действия
                    </para></listitem>

                    <listitem><para>
                        Ошибок, произошедших внутри котроллеров действий
                    </para></listitem>
                </itemizedlist>

                <para>
                    Он действует как плагин <code>postDispatch()</code> и
                    производит проверку, есть ли исключение диспетчера,
                    контроллера действий и др. Если обнаружено
                    исключение, то производится переход на контроллер-обработчик
                    ошибок.
                </para>

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

            <listitem>
                <para>
                    <code>Zend_Controller_Front::throwExceptions()</code>
                </para>

                <para>
                    Посредством передачи булевого значения <constant>TRUE</constant>
                    этому методу вы говорите фронт-контроллеру, что будете
                    самостоятельно обрабатывать исключения вместо того, чтобы
                    они собирались объектом ответа или обрабатывались плагином
                    ErrorHandler. Например:
                </para>

                <programlisting language="php"><![CDATA[
$front->throwExceptions(true);
try {
    $front->dispatch();
} catch (Exception $e) {
    // самостоятельная обработка исключения
}
]]></programlisting>

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

            <listitem>
                <para>
                    <code>Zend_Controller_Response_Abstract::renderExceptions()</code>
                </para>

                <para>
                    Путем передачи булевого значения <constant>TRUE</constant>
                    этому методу, вы говорите объекту ответа, что он должен
                    вместе с заголовком и содержимым ответа выводить сообщения
                    исключений и данные обратной трассировки. В этом случае
                    будут отображены любые исключения, сгенерированные вашим
                    приложением. Это рекомендуется только для непроизводственной
                    среды.
                </para>
            </listitem>

            <listitem>
                <para>
                    <code>Zend_Controller_Front::returnResponse()</code> и
                    <code>Zend_Controller_Response_Abstract::isException()</code>.
                </para>

                <para>
                    После передачи булевого значения <constant>TRUE</constant> методу
                    <code>Zend_Controller_Front::returnResponse()</code> метод
                    <code>Zend_Controller_Front::dispatch()</code> будет не
                    выводить ответ, а возвращать его. Имея объект ответа, вы
                    можете проверить, были ли отловлены исключения, используя
                    его метод <code>isException()</code>, и извлекая их через
                    метод <code>getException()</code>. Например:
                </para>

                <programlisting language="php"><![CDATA[
$front->returnResponse(true);
$response = $front->dispatch();
if ($response->isException()) {
    $exceptions = $response->getException();
    // обработка исключений ...
} else {
    $response->sendHeaders();
    $response->outputBody();
}
]]></programlisting>

                <para>
                    Основное преимущество этого метода по сравнению с
                    <code>Zend_Controller_Front::throwExceptions()</code>
                    состоит в том, что он позволяет управлять выводом ответа
                    после обработки исключений. Это позволит отлавливать любые
                    исключения в цепочке контроллеров, в отличие от плагина
                    ErrorHandler.
                </para>
            </listitem>
        </itemizedlist>
    </sect2>

    <sect2 id="zend.controller.exceptions.internal">
        <title>Исключения в MVC, с которыми вы можете встретиться</title>

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

        <para>
            Некоторые примеры:
        </para>

        <itemizedlist>
            <listitem>
                <para>
                    <code>Zend_Controller_Dispatcher::dispatch()</code> по
                    умолчанию будет бросать исключение, если запрошен
                    недействительный контроллер. Есть два способа решения этой
                    проблемы.
                </para>

                <itemizedlist>
                    <listitem>
                        <para>
                            Установить параметр
                            <code>useDefaultControllerAlways</code>.
                        </para>

                        <para>
                            Добавить в своем фронт-контроллере или диспетчере
                            следующую директиву:
                        </para>

                        <programlisting language="php"><![CDATA[
$front->setParam('useDefaultControllerAlways', true);

// или

$dispatcher->setParam('useDefaultControllerAlways', true);
]]></programlisting>

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

                    <listitem>
                        <para>
                            Исключение, бросаемое методом
                            <code>dispatch()</code>, является экземпляром класса
                            <classname>Zend_Controller_Dispatcher_Exception</classname>,
                            содержащим текст 'Invalid controller specified'
                            (указан недействительный контроллер).
                            Используйте один из методов, описанных в
                            <link
                            linkend="zend.controller.exceptions.handling">предыдущем разделе</link>,
                            для отлова исключений и перенаправления на общую
                            страницу ошибки или главную страницу.
                        </para>
                    </listitem>
                </itemizedlist>
            </listitem>

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

                <itemizedlist>
                    <listitem>
                        <para>
                            Создать подкласс <classname>Zend_Controller_Action</classname>
                            и переопределить в нем метод <code>__call()</code>.
                            Например:
                        </para>

                        <programlisting language="php"><![CDATA[
class My_Controller_Action extends Zend_Controller_Action
{
    public function __call($method, $args)
    {
        if ('Action' == substr($method, -6)) {
            $controller = $this->getRequest()->getControllerName();
            $url = '/' . $controller . '/index';
            return $this->_redirect($url);
        }

        throw new Exception('Invalid method');
    }
}
]]></programlisting>
                        <para>
                            Пример выше перехватывает любые вызовы
                            несуществующих методов действий и перенаправляет их
                            на действие по умолчанию в контроллере.
                        </para>
                    </listitem>

                    <listitem>
                        <para>
                            Создать подкласс
                            <classname>Zend_Controller_Dispatcher</classname> и
                            переопределить в нем метод <code>getAction()</code>
                            так, чтобы он проводил проверку того, существует ли
                            запрошенное действие. Например:
                        </para>

                        <programlisting language="php"><![CDATA[
class My_Controller_Dispatcher extends Zend_Controller_Dispatcher
{
    public function getAction($request)
    {
        $action = $request->getActionName();
        if (empty($action)) {
            $action = $this->getDefaultAction();
            $request->setActionName($action);
            $action = $this->formatActionName($action);
        } else {
            $controller = $this->getController();
            $action     = $this->formatActionName($action);
            if (!method_exists($controller, $action)) {
                $action = $this->getDefaultAction();
                $request->setActionName($action);
                $action = $this->formatActionName($action);
            }
        }

        return $action;
    }
}
]]></programlisting>

                        <para>
                            Код выше проверяет существование запрошенного
                            действия в классе контроллера; если это действие не
                            существует, то оно заменяется на действие по
                            умолчанию.
                        </para>

                        <para>
                            Этот способ хорош тем, что можно прозрачно заменить
                            действие до финальной диспетчеризации. Тем не менее,
                            он также подразумевает, что опечатки в URL могут
                            обрабатываться как корректные, что не хорошо с точки
                            зрения поисковой оптимизации.
                        </para>
                    </listitem>

                    <listitem>
                        <para>
                            Использовать
                            <code>Zend_Controller_Action::preDispatch()</code>
                            или
                            <code>Zend_Controller_Plugin_Abstract::preDispatch()</code>
                            для определения ошибочно запрошенных действий.
                        </para>

                        <para>
                            Создав подкласс <classname>Zend_Controller_Action</classname>
                            и модифицировав метод <code>preDispatch()</code>, вы
                            можете изменить все
                            свои контроллеры так, чтобы они переходили
                            к другому действию или производили
                            HTTP-перенаправление до того, как будет выполнено
                            текущее действие. Код для этого похож на код,
                            приведенный выше, с переопределением
                            <code>__call()</code>.
                        </para>

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

                        <para>
                            Например:
                        </para>

                        <programlisting language="php"><![CDATA[
class My_Controller_PreDispatchPlugin extends Zend_Controller_Plugin_Abstract
{
    public function preDispatch(Zend_Controller_Request_Abstract $request)
    {
        $front      = Zend_Controller_Front::getInstance();
        $dispatcher = $front->getDispatcher();
        $class      = $dispatcher->getControllerClass($request);
        if (!$controller) {
            $class = $dispatcher->getDefaultControllerClass($request);
        }

        $r      = new ReflectionClass($class);
        $action = $dispatcher->getActionMethod($request);

        if (!$r->hasMethod($action)) {
            $defaultAction  = $dispatcher->getDefaultAction();
            $controllerName = $request->getControllerName();
            $response       = $front->getResponse();
            $response->setRedirect('/' . $controllerName
                                  . '/' . $defaultAction);
            $response->sendHeaders();
            exit;
        }
    }
}
]]></programlisting>

                        <para>
                            В этом примере мы проверяем, доступно ли в
                            контроллере запрошенное действие. Если нет, то
                            производится перенаправление на действие по
                            умолчанию в этом контроллере и сразу же завершается
                            выполнение скрипта.
                        </para>
                    </listitem>
                </itemizedlist>
            </listitem>
        </itemizedlist>
    </sect2>
</sect1>
<!--
vim:se ts=4 sw=4 et:
-->
