# Исключения

## Введение

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

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

## Обработка исключений

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

- По умолчанию плагин ErrorHandler зарегистрирован и включен. Этот плагин предназначен для обработки:

  - Ошибок, вызванных отсутствием запрошенного контроллера или действия

  - Ошибок, произошедших внутри котроллеров действий

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

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

- `Zend_Controller_Front::throwExceptions()`

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

      $front->throwExceptions(true);
      try {
          $front->dispatch();
      } catch (Exception $e) {
          // самостоятельная обработка исключения
      }

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

- `Zend_Controller_Response_Abstract::renderExceptions()`

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

- `Zend_Controller_Front::returnResponse()` и `Zend_Controller_Response_Abstract::isException()`.

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

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

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

## Исключения в MVC, с которыми вы можете встретиться

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

Некоторые примеры:

- `Zend_Controller_Dispatcher::dispatch()` по умолчанию будет бросать исключение, если запрошен недействительный контроллер. Есть два способа решения этой проблемы.

  - Установить параметр `useDefaultControllerAlways`.

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

        $front->setParam('useDefaultControllerAlways', true);

        // или

        $dispatcher->setParam('useDefaultControllerAlways', true);

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

  - Исключение, бросаемое методом `dispatch()`, является экземпляром класса Zend_Controller_Dispatcher_Exception, содержащим текст 'Invalid controller specified' (указан недействительный контроллер). Используйте один из методов, описанных в предыдущем разделе, для отлова исключений и перенаправления на общую страницу ошибки или главную страницу.

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

  - Создать подкласс Zend_Controller_Action и переопределить в нем метод `__call()`. Например:

        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');
            }
        }

    Пример выше перехватывает любые вызовы несуществующих методов действий и перенаправляет их на действие по умолчанию в контроллере.

  - Создать подкласс Zend_Controller_Dispatcher и переопределить в нем метод `getAction()` так, чтобы он проводил проверку того, существует ли запрошенное действие. Например:

        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;
            }
        }

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

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

  - Использовать `Zend_Controller_Action::preDispatch()` или `Zend_Controller_Plugin_Abstract::preDispatch()` для определения ошибочно запрошенных действий.

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

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

    Например:

        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;
                }
            }
        }

    В этом примере мы проверяем, доступно ли в контроллере запрошенное действие. Если нет, то производится перенаправление на действие по умолчанию в этом контроллере и сразу же завершается выполнение скрипта.