7.12. Исключения

7.12.1. Введение

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

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

7.12.2. Как я могу обрабатывать исключения?

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

  • Zend_Controller_Front::throwExceptions()

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

    <?php
    $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(). Например:

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

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

7.12.3. Исключения MVC

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

Например:

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

    • Установить параметр 'useDefaultControllerAlways'.

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

      <?php
      $front->setParam('useDefaultControllerAlways', true);
      
      // или
      $dispatcher->setParam('useDefaultControllerAlways', true);
      

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

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

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

    • Создать подкласс Zend_Controller_Action и переопределить в нем метод Zend_Controller_Action. Например:

      <?php
      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() так, чтобы он производил проверку того, существует ли запрошенное действие. Например:

      <?php
      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(), вы можете изменить все свои контроллеры так, чтобы они перенаправляли (forward) к другому действию или производили HTTP-перенаправление прежде действительной обработки действия. Код для этого выглядит подобно тому, что был приведен выше, с переопределением __call().

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

      Например:

      <?php
      class My_Controller_PreDispatchPlugin extends Zend_Controller_Plugin_Abstract
      {
          public function preDispatch(Zend_Controller_Request_Abstract $request)
          {
              $dispatcher = Zend_Controller_Front::getInstance()->getDispatcher();
              $controller = $dispatcher->getController($request);
              if (!$controller) {
                  $controller = $dispatcher->getDefaultControllerName($request);
              }
              $action     = $dispatcher->getAction($request);
      
              if (!method_exists($controller, $action)) {
                  $defaultAction = $dispatcher->getDefaultAction();
                  $controllerName = $request->getControllerName();
                  $response = Zend_Controller_Front::getInstance()->getResponse();
                  $response->setRedirect('/' . $controllerName . '/' . $defaultAction);
                  $response->sendHeaders();
                  exit;
              }
          }
      }
      

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