TigerZF
🌐Español

24.12. Excepciones MVC

24.12.1. Introducción

Los componentes MVC de Zend Framework utilizan un Front Controller, lo que significa que todas las peticiones a un sitio dado pasarán por un único punto de entrada. Como resultado, todas las excepciones burbujean hasta el Front Controller finalmente, lo que permite al desarrollador manejarlas en una única ubicación.

Sin embargo, los mensajes de excepción y la información de traza a menudo contienen información sensible del sistema, como sentencias SQL, ubicaciones de archivos, y más. Para ayudar a proteger su sitio, por defecto Zend_Controller_Front captura todas las excepciones y las registra con el objeto de respuesta; a su vez, por defecto, el objeto de respuesta no muestra los mensajes de excepción.

24.12.2. Manejo de excepciones

Ya existen varios mecanismos incorporados en los componentes MVC para permitirle manejar excepciones.

  • Por defecto, el plugin manejador de errores está registrado y activo. Este plugin fue diseñado para manejar:

    • Errores debidos a controladores o acciones faltantes

    • Errores que ocurren dentro de controladores de acción

    Opera como un plugin postDispatch(), y comprueba si ha ocurrido una excepción del despachador, del controlador de acción, u otra. Si es así, reenvía a un controlador manejador de errores.

    Este manejador cubrirá la mayoría de las situaciones excepcionales, y manejará con elegancia los controladores y acciones faltantes.

  • Zend_Controller_Front::throwExceptions()

    Al pasar un valor booleano TRUE a este método, puede indicar al front controller que, en lugar de agregar excepciones en el objeto de respuesta o usar el plugin manejador de errores, prefiere manejarlas usted mismo. Como ejemplo:

    $front->throwExceptions(true);
    try {
        $front->dispatch();
    } catch (Exception $e) {
        // handle exceptions yourself
    }
    

    Este método es probablemente la forma más fácil de agregar manejo personalizado de excepciones que cubra toda la gama de posibles excepciones en su aplicación de front controller.

  • Zend_Controller_Response_Abstract::renderExceptions()

    Al pasar un valor booleano TRUE a este método, indica al objeto de respuesta que debe renderizar un mensaje de excepción y traza al renderizarse a sí mismo. En este escenario, cualquier excepción generada por su aplicación se mostrará. Esto solo se recomienda para entornos que no son de producción.

  • Zend_Controller_Front::returnResponse() y Zend_Controller_Response_Abstract::isException().

    Al pasar un valor booleano TRUE a Zend_Controller_Front::returnResponse(), Zend_Controller_Front::dispatch() no renderizará la respuesta, sino que la devolverá. Una vez que tenga la respuesta, puede entonces comprobar si se atraparon excepciones usando su método isException(), y recuperar las excepciones mediante el método getException(). Como ejemplo:

    $front->returnResponse(true);
    $response = $front->dispatch();
    if ($response->isException()) {
        $exceptions = $response->getException();
        // handle exceptions ...
    } else {
        $response->sendHeaders();
        $response->outputBody();
    }
    

    La principal ventaja que ofrece este método sobre Zend_Controller_Front::throwExceptions() es permitirle renderizar condicionalmente la respuesta después de manejar la excepción. Esto capturará cualquier excepción en la cadena de controladores, a diferencia del plugin manejador de errores.

24.12.3. Excepciones MVC que puede encontrar

Los diversos componentes MVC --petición, enrutador, despachador, controlador de acción y objetos de respuesta-- pueden cada uno lanzar excepciones en ciertas ocasiones. Algunas excepciones pueden anularse condicionalmente, y otras se usan para indicar que el desarrollador puede necesitar considerar la estructura de su aplicación.

Como algunos ejemplos:

  • Zend_Controller_Dispatcher::dispatch(), por defecto, lanzará una excepción si se solicita un controlador inválido. Hay dos formas recomendadas de tratar esto.

    • Establecer el parámetro useDefaultControllerAlways.

      En su front controller, o en su despachador, agregue la siguiente directiva:

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

      Cuando se establece este indicador, el despachador usará el controlador y la acción por defecto en lugar de lanzar una excepción. La desventaja de este método es que cualquier error tipográfico que un usuario cometa al acceder a su sitio aún se resolverá y mostrará su página de inicio, lo que puede causar estragos con la optimización para motores de búsqueda.

    • La excepción lanzada por dispatch() es una Zend_Controller_Dispatcher_Exception que contiene el texto 'Invalid controller specified'. Use uno de los métodos descritos en la sección anterior para capturar la excepción, y luego redirigir a una página de error genérica o a la página de inicio.

  • Zend_Controller_Action::__call() lanzará una Zend_Controller_Action_Exception si no puede despachar una acción inexistente a un método. Lo más probable es que quiera usar alguna acción por defecto en el controlador en casos como este. Las formas de lograr esto incluyen:

    • Crear una subclase de Zend_Controller_Action y sobrescribir el método __call(). Como ejemplo:

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

      El ejemplo anterior intercepta cualquier método de acción indefinido que se llame y lo redirige a la acción por defecto en el controlador.

    • Crear una subclase de Zend_Controller_Dispatcher y sobrescribir el método getAction() para verificar que la acción existe. Como ejemplo:

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

      El código anterior comprueba que la acción solicitada exista en la clase controladora; si no, se restablece la acción a la acción por defecto.

      Este método es conveniente porque puede alterar de forma transparente la acción antes del despacho final. Sin embargo, también significa que los errores tipográficos en la URL aún pueden despacharse correctamente, lo cual no es ideal para la optimización en motores de búsqueda.

    • Use Zend_Controller_Action::preDispatch() o Zend_Controller_Plugin_Abstract::preDispatch() para identificar acciones inválidas.

      Al crear una subclase de Zend_Controller_Action y modificar preDispatch(), puede modificar todos sus controladores para reenviar a otra acción o redirigir antes de realmente despachar la acción. El código para esto será similar al código para sobrescribir __call(), arriba.

      Alternativamente, puede comprobar esta información en un plugin global. Esto tiene la ventaja de ser independiente del controlador de acción; si su aplicación consiste en una variedad de controladores de acción, y no todos heredan de la misma clase, este método puede añadir consistencia en el manejo de sus diversas clases.

      Como ejemplo:

      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 (!$class) {
                  $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;
              }
          }
      }
      

      En este ejemplo, comprobamos si la acción solicitada está disponible en el controlador. Si no, redirigimos a la acción por defecto en el controlador, y salimos de la ejecución del script inmediatamente.