TigerZF
🌐Español

24.10. Plugins

24.10.1. Introducción

La arquitectura del controlador incluye un sistema de plugins que permite que el código de usuario sea invocado cuando ocurren ciertos eventos durante el ciclo de vida del proceso del controlador. El controlador frontal utiliza un broker de plugins como registro de los plugins de usuario, y el broker de plugins garantiza que los métodos de evento se invoquen en cada plugin registrado con el controlador frontal.

Los métodos de evento están definidos en la clase abstracta Zend_Controller_Plugin_Abstract, de la cual heredan las clases de plugin de usuario:

  • routeStartup() se invoca antes de que Zend_Controller_Front llame a el enrutador para evaluar la solicitud contra las rutas registradas.

  • routeShutdown() se invoca después de que el enrutador termina de enrutar la solicitud.

  • dispatchLoopStartup() se invoca antes de que Zend_Controller_Front entre en su bucle de despacho.

  • preDispatch() se invoca antes de que una acción sea despachada por el despachador. Esta llamada de retorno permite un comportamiento de proxy o de filtro. Al modificar la solicitud y restablecer su indicador de despacho (mediante Zend_Controller_Request_Abstract::setDispatched(false)), la acción actual puede ser omitida y/o reemplazada.

  • postDispatch() se invoca después de que una acción es despachada por el despachador. Esta llamada de retorno permite un comportamiento de proxy o de filtro. Al modificar la solicitud y restablecer su indicador de despacho (mediante Zend_Controller_Request_Abstract::setDispatched(false)), se puede especificar una nueva acción para el despacho.

  • dispatchLoopShutdown() se invoca después de que Zend_Controller_Front sale de su bucle de despacho.

24.10.2. Escribiendo plugins

Para escribir una clase de plugin, simplemente incluya y extienda la clase abstracta Zend_Controller_Plugin_Abstract:

class MyPlugin extends Zend_Controller_Plugin_Abstract
{
    // ...
}

Ninguno de los métodos de Zend_Controller_Plugin_Abstract es abstracto, lo que significa que las clases de plugin no están obligadas a implementar ninguno de los métodos de evento disponibles listados arriba. Los desarrolladores de plugins pueden implementar solo los métodos que requieran sus necesidades particulares.

Zend_Controller_Plugin_Abstract también pone a disposición los objetos de solicitud y respuesta para los plugins del controlador a través de los métodos getRequest() y getResponse(), respectivamente.

24.10.3. Usando plugins

Las clases de plugin se registran con Zend_Controller_Front::registerPlugin(), y pueden registrarse en cualquier momento. El siguiente fragmento ilustra cómo se puede usar un plugin en la cadena del controlador:

class MyPlugin extends Zend_Controller_Plugin_Abstract
{
    public function routeStartup(Zend_Controller_Request_Abstract $request)
    {
        $this->getResponse()
             ->appendBody("<p>routeStartup() called</p>\n");
    }

    public function routeShutdown(Zend_Controller_Request_Abstract $request)
    {
        $this->getResponse()
             ->appendBody("<p>routeShutdown() called</p>\n");
    }

    public function dispatchLoopStartup(
        Zend_Controller_Request_Abstract $request)
    {
        $this->getResponse()
             ->appendBody("<p>dispatchLoopStartup() called</p>\n");
    }

    public function preDispatch(Zend_Controller_Request_Abstract $request)
    {
        $this->getResponse()
             ->appendBody("<p>preDispatch() called</p>\n");
    }

    public function postDispatch(Zend_Controller_Request_Abstract $request)
    {
        $this->getResponse()
             ->appendBody("<p>postDispatch() called</p>\n");
    }

    public function dispatchLoopShutdown()
    {
        $this->getResponse()
             ->appendBody("<p>dispatchLoopShutdown() called</p>\n");
    }
}

$front = Zend_Controller_Front::getInstance();
$front->setControllerDirectory('/path/to/controllers')
      ->setRouter(new Zend_Controller_Router_Rewrite())
      ->registerPlugin(new MyPlugin());
$front->dispatch();

Suponiendo que ninguna de las acciones invocadas emite ninguna salida, y solo se invoca una acción, la funcionalidad del plugin anterior aún crearía la siguiente salida:

<p>routeStartup() called</p>
<p>routeShutdown() called</p>
<p>dispatchLoopStartup() called</p>
<p>preDispatch() called</p>
<p>postDispatch() called</p>
<p>dispatchLoopShutdown() called</p>
[Note] Nota

Los plugins pueden registrarse en cualquier momento durante la ejecución del controlador frontal. Sin embargo, si ya ha pasado un evento para el cual el plugin tiene un método de evento registrado, ese método no se activará.

24.10.4. Recuperando y manipulando plugins

En ocasiones, puede que necesite anular el registro o recuperar un plugin. Los siguientes métodos del controlador frontal le permiten hacerlo:

  • getPlugin($class) le permite recuperar un plugin por nombre de clase. Si no coincide ningún plugin, devuelve FALSE. Si hay más de un plugin de esa clase registrado, devuelve un array.

  • getPlugins() recupera toda la pila de plugins.

  • unregisterPlugin($plugin) le permite eliminar un plugin de la pila. Puede pasar un objeto de plugin, o el nombre de clase del plugin que desea anular. Si pasa el nombre de clase, se eliminarán todos los plugins de esa clase.

24.10.5. Plugins incluidos en la distribución estándar

Zend Framework incluye un plugin para el manejo de errores en su distribución estándar.

24.10.5.1. ActionStack

El plugin ActionStack le permite gestionar una pila de solicitudes, y opera como un plugin postDispatch. Si ya se detecta un forward (es decir, una llamada a otra acción) en el objeto de solicitud actual, no hace nada. Sin embargo, si no, comprueba su pila y extrae el elemento superior, reenviando a la acción especificada en esa solicitud. La pila se procesa en orden LIFO.

Puede recuperar el plugin desde el controlador frontal en cualquier momento usando Zend_Controller_Front::getPlugin('Zend_Controller_Plugin_ActionStack'). Una vez que tenga el objeto de plugin, dispone de varios mecanismos que puede usar para manipularlo.

  • getRegistry() y setRegistry(). Internamente, ActionStack usa una instancia de Zend_Registry para almacenar la pila. Puede sustituir una instancia de registro diferente o recuperarla con estos accesores.

  • getRegistryKey() y setRegistryKey(). Se pueden usar para indicar qué clave de registro usar al extraer la pila. El valor predeterminado es 'Zend_Controller_Plugin_ActionStack'.

  • getStack() le permite recuperar la pila completa de acciones.

  • pushStack() y popStack() le permiten agregar y extraer de la pila, respectivamente. pushStack() acepta un objeto de solicitud.

Un método adicional, forward(), espera un objeto de solicitud, y establece el estado del objeto de solicitud actual en el controlador frontal al estado del objeto de solicitud proporcionado, y lo marca como no despachado (forzando otra iteración del bucle de despacho).

24.10.5.2. Zend_Controller_Plugin_ErrorHandler

Zend_Controller_Plugin_ErrorHandler proporciona un plugin listo para usar para gestionar las excepciones lanzadas por su aplicación, incluidas aquellas resultantes de controladores o acciones faltantes; es una alternativa a los métodos listados en la sección de excepciones MVC.

Los objetivos principales del plugin son:

  • Interceptar excepciones generadas cuando ninguna ruta coincide

  • Interceptar excepciones generadas debido a controladores o métodos de acción faltantes

  • Interceptar excepciones generadas dentro de los controladores de acción

En otras palabras, el plugin ErrorHandler está diseñado para manejar errores de tipo HTTP 404 (página faltante) y de tipo 500 (error interno). No está pensado para capturar excepciones generadas en otros plugins.

Por defecto, Zend_Controller_Plugin_ErrorHandler reenviará a ErrorController::errorAction() en el módulo predeterminado. Puede establecer valores alternativos usando los distintos accesores disponibles en el plugin:

  • setErrorHandlerModule() establece el módulo de controlador a usar.

  • setErrorHandlerController() establece el controlador a usar.

  • setErrorHandlerAction() establece la acción del controlador a usar.

  • setErrorHandler() toma un array asociativo, que puede contener cualquiera de las claves 'module', 'controller' o 'action', con el que establecerá los valores correspondientes.

Adicionalmente, puede pasar un array asociativo opcional al constructor, que a su vez delegará a setErrorHandler().

Zend_Controller_Plugin_ErrorHandler registra un hook postDispatch() y comprueba si hay excepciones registradas en el objeto de respuesta. Si se encuentra alguna, intenta reenviar a la acción de manejo de errores registrada.

Si ocurre una excepción al despachar el manejador de errores, el plugin indicará al controlador frontal que lance excepciones, y volverá a lanzar la última excepción registrada con el objeto de respuesta.

24.10.5.2.1. Usando el ErrorHandler como manejador de errores 404

Dado que el plugin ErrorHandler captura no solo los errores de la aplicación, sino también los errores en la cadena del controlador que surgen de clases de controlador y/o métodos de acción faltantes, puede utilizarse como manejador de errores 404. Para hacerlo, necesitará que su controlador de errores compruebe el tipo de excepción.

Las excepciones capturadas se registran en un objeto registrado en la solicitud. Para recuperarlo, use Zend_Controller_Action::_getParam('error_handler'):

class ErrorController extends Zend_Controller_Action
{
    public function errorAction()
    {
        $errors = $this->_getParam('error_handler');
    }
}

Una vez que tenga el objeto de error, puede obtener el tipo mediante $errors->type;. Será uno de los siguientes:

  • Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ROUTE, que indica que ninguna ruta coincidió.

  • Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER, que indica que no se encontró el controlador.

  • Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION, que indica que no se encontró la acción solicitada.

  • Zend_Controller_Plugin_ErrorHandler::EXCEPTION_OTHER, que indica otras excepciones.

Puede entonces comprobar los tres primeros tipos, y, en ese caso, mostrar una página 404:

class ErrorController extends Zend_Controller_Action
{
    public function errorAction()
    {
        $errors = $this->_getParam('error_handler');

        switch ($errors->type) {
            case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ROUTE:
            case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER:
            case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION:
                // 404 error -- controller or action not found
                $this->getResponse()
                     ->setRawHeader('HTTP/1.1 404 Not Found');

                // ... get some output to display...
                break;
            default:
                // application error; display error page, but don't
                // change status code
                break;
        }
    }
}

Finalmente, puede recuperar la excepción que activó el manejador de errores obteniendo la propiedad exception del objeto error_handler:

public function errorAction()
{
        $errors = $this->_getParam('error_handler');

        switch ($errors->type) {
            case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ROUTE:
            case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER:
            case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION:
                // 404 error -- controller or action not found
                $this->getResponse()
                     ->setRawHeader('HTTP/1.1 404 Not Found');

                // ... get some output to display...
                break;
            default:
                // application error; display error page, but don't change
                // status code

                // ...

                // Log the exception:
                $exception = $errors->exception;
                $log = new Zend_Log(
                    new Zend_Log_Writer_Stream(
                        '/tmp/applicationException.log'
                    )
                );
                $log->debug($exception->getMessage() . "\n" .
                            $exception->getTraceAsString());
                break;
        }
}
24.10.5.2.2. Manejando salida renderizada previamente

Si despacha múltiples acciones en una solicitud, o si su acción realiza múltiples llamadas a render(), es posible que el objeto de respuesta ya tenga contenido almacenado en él. Esto puede llevar a renderizar una mezcla de contenido esperado y contenido de error.

Si desea renderizar los errores en línea en dichas páginas, no será necesario realizar ningún cambio. Si no desea renderizar dicho contenido, debe limpiar el cuerpo de la respuesta antes de renderizar cualquier vista:

$this->getResponse()->clearBody();
24.10.5.2.3. Ejemplos de uso del plugin

Ejemplo 24.18. Uso estándar

$front = Zend_Controller_Front::getInstance();
$front->registerPlugin(new Zend_Controller_Plugin_ErrorHandler());

Ejemplo 24.19. Estableciendo un manejador de errores diferente

$front = Zend_Controller_Front::getInstance();
$front->registerPlugin(new Zend_Controller_Plugin_ErrorHandler(array(
    'module'     => 'mystuff',
    'controller' => 'static',
    'action'     => 'error'
)));

Ejemplo 24.20. Usando accesores

$plugin = new Zend_Controller_Plugin_ErrorHandler();
$plugin->setErrorHandlerModule('mystuff')
       ->setErrorHandlerController('static')
       ->setErrorHandlerAction('error');

$front = Zend_Controller_Front::getInstance();
$front->registerPlugin($plugin);

24.10.5.2.4. Ejemplo de controlador de errores

Para usar el plugin Error Handler, necesita un controlador de errores. A continuación se muestra un ejemplo simple.

class ErrorController extends Zend_Controller_Action
{
    public function errorAction()
    {
        $errors = $this->_getParam('error_handler');

        switch ($errors->type) {
            case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ROUTE:
            case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER:
            case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION:
                // 404 error -- controller or action not found
                $this->getResponse()->setRawHeader('HTTP/1.1 404 Not Found');

                $content =<<<EOH
<h1>Error!</h1>
<p>The page you requested was not found.</p>
EOH;
                break;
            default:
                // application error
                $content =<<<EOH
<h1>Error!</h1>
<p>An unexpected error occurred. Please try again later.</p>
EOH;
                break;
        }

        // Clear previous content
        $this->getResponse()->clearBody();

        $this->view->content = $content;
    }
}

24.10.5.3. Zend_Controller_Plugin_PutHandler

Zend_Controller_Plugin_PutHandler proporciona un plugin listo para usar para convertir los cuerpos de solicitudes PUT en parámetros de solicitud, tal como con los cuerpos de solicitudes POST. Inspeccionará la solicitud y, si es PUT, usará parse_str para analizar el cuerpo PUT en bruto en un array de parámetros que luego se establece en la solicitud. P. ej.,

PUT /notes/5.xml HTTP/1.1

title=Hello&body=World

Para recibir los parámetros 'title' y 'body' como parámetros de solicitud normales, registre el plugin:

$front = Zend_Controller_Front::getInstance();
$front->registerPlugin(new Zend_Controller_Plugin_PutHandler());

Luego puede acceder a los parámetros del cuerpo PUT por nombre desde la solicitud dentro de su controlador:

...
public function putAction()
{
    $title = $this->getRequest()->getParam('title'); // $title = "Hello"
    $body = $this->getRequest()->getParam('body'); // $body = "World"
}
...