TigerZF
🌐Español

24.7. Controladores de Acción

24.7.1. Introducción

Zend_Controller_Action es una clase abstracta que puede usar para implementar Controladores de Acción para su uso con el Controlador Frontal al construir un sitio web basado en el patrón Modelo-Vista-Controlador (MVC).

Para usar Zend_Controller_Action, necesitará heredar de ella en sus clases de controlador de acción reales (o heredar de ella para crear su propia clase base para controladores de acción). La operación más básica es heredar de ella, y crear métodos de acción que correspondan a las distintas acciones que desea que el controlador maneje para su sitio. El enrutamiento y despacho de Zend_Controller descubrirá automáticamente cualquier método que termine en 'Action' en su clase como posibles acciones del controlador.

Por ejemplo, supongamos que su clase se define de la siguiente manera:

class FooController extends Zend_Controller_Action
{
    public function barAction()
    {
        // do something
    }

    public function bazAction()
    {
        // do something
    }
}

La clase FooController anterior (controlador foo) define dos acciones, bar y baz.

Hay mucho más que se puede lograr además de esto, como acciones de inicialización personalizadas, acciones por defecto a llamar en caso de que no se especifique ninguna acción (o una acción no válida), enlaces (hooks) de pre- y post-despacho, y una variedad de métodos auxiliares. Este capítulo sirve como una visión general de la funcionalidad del controlador de acción

[Note] Comportamiento por defecto

Por defecto, el controlador frontal habilita el helper de acción ViewRenderer. Este helper se encarga de inyectar el objeto vista en el controlador, así como de renderizar automáticamente las vistas. Puede deshabilitarlo dentro de su controlador de acción mediante uno de los siguientes métodos:

class FooController extends Zend_Controller_Action
{
    public function init()
    {
        // Local to this controller only; affects all actions,
        // as loaded in init:
        $this->_helper->viewRenderer->setNoRender(true);

        // Globally:
        $this->_helper->removeHelper('viewRenderer');

        // Also globally, but would need to be in conjunction with the
        // local version in order to propagate for this controller:
        Zend_Controller_Front::getInstance()
            ->setParam('noViewRenderer', true);
    }
}

initView(), getViewScript(), render(), y renderScript() cada uno delega en el ViewRenderer a menos que el helper no esté en el broker de helpers o que se haya establecido la bandera noViewRenderer.

También puede simplemente deshabilitar el renderizado para una vista individual estableciendo la bandera noRender del ViewRenderer:

class FooController extends Zend_Controller_Action
{
    public function barAction()
    {
        // disable autorendering for this action only:
        $this->_helper->viewRenderer->setNoRender();
    }
}

Las razones principales para deshabilitar el ViewRenderer son si simplemente no necesita un objeto vista o si no está renderizando mediante scripts de vista (por ejemplo, cuando se usa un controlador de acción para servir protocolos de servicios web como SOAP, XML-RPC, o REST). En la mayoría de los casos, nunca necesitará deshabilitar globalmente el ViewRenderer, solo selectivamente dentro de controladores o acciones individuales.

24.7.2. Inicialización de Objetos

Aunque siempre puede sobrescribir el constructor del controlador de acción, no lo recomendamos. Zend_Controller_Action::__construct() realiza algunas tareas importantes, como registrar los objetos de petición y respuesta, así como cualquier argumento de invocación personalizado pasado desde el controlador frontal. Si debe sobrescribir el constructor, asegúrese de llamar a parent::__construct($request, $response, $invokeArgs).

La forma más apropiada de personalizar la instanciación es usar el método init(), que es llamado como la última tarea de __construct(). Por ejemplo, si desea conectarse a una base de datos en la instanciación:

class FooController extends Zend_Controller_Action
{
    public function init()
    {
        $this->db = Zend_Db::factory('Pdo_Mysql', array(
            'host'     => 'myhost',
            'username' => 'user',
            'password' => 'XXXXXXX',
            'dbname'   => 'website'
        ));
    }
}

24.7.3. Enlaces de Pre- y Post-Despacho

Zend_Controller_Action especifica dos métodos que pueden ser llamados para enmarcar una acción solicitada, preDispatch() y postDispatch(). Estos pueden ser útiles de varias maneras: verificar la autenticación y las ACL antes de ejecutar una acción (al llamar a _forward() en preDispatch(), la acción será omitida), por ejemplo, o colocar el contenido generado en una plantilla de todo el sitio (postDispatch()).

[Note] Uso de init() vs. preDispatch()

En la sección anterior, presentamos el método init(), y en esta sección, el método preDispatch(). ¿Cuál es la diferencia entre ellos, y qué acciones tomaría en cada uno?

El método init() está destinado principalmente a extender el constructor. Normalmente, su constructor debería simplemente establecer el estado del objeto, y no realizar demasiada lógica. Esto podría incluir la inicialización de recursos utilizados en el controlador (como modelos, objetos de configuración, etc.), o la asignación de valores obtenidos del controlador frontal, del bootstrap, o de un registro.

El método preDispatch() también se puede utilizar para establecer el estado del objeto o del entorno (por ejemplo, vista, helper de acción, etc.), pero su propósito principal es tomar decisiones sobre si la acción solicitada debe ser despachada o no. Si no, entonces debería usar _forward() hacia otra acción, o lanzar una excepción.

Nota: _forward() en realidad no funcionará correctamente cuando se ejecute desde init(), lo cual es una formalización de las intenciones de los dos métodos.

24.7.4. Accesores

Varios objetos y variables están registrados con el objeto, y cada uno tiene métodos accesores.

  • Objeto Request: getRequest() puede utilizarse para recuperar el objeto de petición usado para llamar a la acción.

  • Objeto Response: getResponse() puede utilizarse para recuperar el objeto de respuesta que agrega la respuesta final. Algunas llamadas típicas podrían ser como sigue:

    $this->getResponse()->setHeader('Content-Type', 'text/xml');
    $this->getResponse()->appendBody($content);
    
  • Argumentos de Invocación: el controlador frontal puede introducir parámetros en el router, el despachador y el controlador de acción. Para recuperarlos, use getInvokeArg($key); alternativamente, obtenga la lista completa usando getInvokeArgs().

  • Parámetros de la petición: El objeto de petición agrega los parámetros de la petición, como cualquier parámetro _GET o _POST, o parámetros de usuario especificados en la información de ruta de la URL. Para recuperarlos, use _getParam($key) o _getAllParams(). También puede establecer parámetros de la petición usando _setParam(); esto es útil cuando se reenvía a acciones adicionales.

    Para comprobar si un parámetro existe o no (útil para ramificaciones lógicas), use _hasParam($key).

    [Note] Nota

    _getParam() puede tomar un segundo argumento opcional que contiene un valor por defecto para usar si el parámetro no está establecido o está vacío. Usarlo elimina la necesidad de llamar a _hasParam() antes de recuperar un valor:

    // Use default value of 1 if id is not set
    $id = $this->_getParam('id', 1);
    
    // Instead of:
    if ($this->_hasParam('id') {
        $id = $this->_getParam('id');
    } else {
        $id = 1;
    }
    

24.7.5. Integración de Vistas

[Note] La integración de vistas por defecto es a través del ViewRenderer

El contenido de esta sección solo es válido cuando ha deshabilitado explícitamente el ViewRenderer. De lo contrario, puede saltarse esta sección sin problema.

Zend_Controller_Action proporciona un mecanismo rudimentario y flexible para la integración de vistas. Dos métodos logran esto, initView() y render(); el primer método carga de forma diferida (lazy-load) la propiedad pública $view, y el segundo renderiza una vista basada en la acción solicitada actual, usando la jerarquía de directorios para determinar la ruta del script.

24.7.5.1. Inicialización de la Vista

initView() inicializa el objeto vista. render() llama a initView() para recuperar el objeto vista, pero puede ser inicializado en cualquier momento; por defecto puebla la propiedad $view con un objeto Zend_View, pero se puede usar cualquier clase que implemente Zend_View_Interface. Si $view ya está inicializada, simplemente devuelve esa propiedad.

La implementación por defecto asume la siguiente estructura de directorios:

applicationOrModule/
    controllers/
        IndexController.php
    views/
        scripts/
            index/
                index.phtml
        helpers/
        filters/

En otras palabras, se asume que los scripts de vista están en el subdirectorio /views/scripts/, y se asume que el subdirectorio /views/ contiene funcionalidad hermana (helpers, filtros). Al determinar el nombre y la ruta del script de vista, el directorio /views/scripts/ se usará como ruta base, con directorios nombrados según los controladores individuales proporcionando una jerarquía de scripts de vista.

24.7.5.2. Renderizado de Vistas

render() tiene la siguiente firma:

string render(string $action = null,
              string $name = null,
              bool $noController = false);

render() renderiza un script de vista. Si no se pasan argumentos, asume que el script solicitado es [controller]/[action].phtml (donde .phtml es el valor de la propiedad $viewSuffix). Pasar un valor para $action renderizará esa plantilla en el subdirectorio /[controller]/. Para omitir el uso del subdirectorio /[controller]/, pase un valor TRUE para $noController. Finalmente, las plantillas se renderizan en el objeto respuesta; si desea renderizar en un segmento con nombre específico en el objeto respuesta, pase un valor para $name.

[Note] Nota

Dado que los nombres de controlador y acción pueden contener caracteres delimitadores de palabras como '_', '.', y '-', render() los normaliza a '-' al determinar el nombre del script. Internamente, usa los delimitadores de palabra y ruta del despachador para realizar esta normalización. Así, una petición a /foo.bar/baz-bat renderizará el script foo-bar/baz-bat.phtml. Si su método de acción contiene camelCasing, recuerde que esto resultará en palabras separadas por '-' al determinar el nombre del archivo del script de vista.

Algunos ejemplos:

class MyController extends Zend_Controller_Action
{
    public function fooAction()
    {
        // Renders my/foo.phtml
        $this->render();

        // Renders my/bar.phtml
        $this->render('bar');

        // Renders baz.phtml
        $this->render('baz', null, true);

        // Renders my/login.phtml to the 'form' segment of the
        // response object
        $this->render('login', 'form');

        // Renders site.phtml to the 'page' segment of the response
        // object; does not use the 'my/' subirectory
        $this->render('site', 'page', true);
    }

    public function bazBatAction()
    {
        // Renders my/baz-bat.phtml
        $this->render();
    }
}

24.7.6. Métodos Utilitarios

Además de los accesores y los métodos de integración de vistas, Zend_Controller_Action tiene varios métodos utilitarios para realizar tareas comunes desde dentro de sus métodos de acción (o desde pre- y post-despacho).

  • _forward($action, $controller = null, $module = null, array $params = null): realiza otra acción. Si se llama en preDispatch(), la acción actualmente solicitada será omitida en favor de la nueva. De lo contrario, después de que la acción actual sea procesada, la acción solicitada en _forward() será ejecutada.

  • _redirect($url, array $options = array()): redirige a otra ubicación. Este método toma una URL y un conjunto opcional de opciones. Por defecto, realiza una redirección HTTP 302.

    Las opciones pueden incluir una o más de las siguientes:

    • exit: si se debe salir inmediatamente o no. Si se solicita, cerrará limpiamente cualquier sesión abierta y realizará la redirección.

      Puede establecer esta opción globalmente dentro del controlador usando el accesor setRedirectExit().

    • prependBase: si se debe anteponer o no la URL base registrada con el objeto de petición a la URL proporcionada.

      Puede establecer esta opción globalmente dentro del controlador usando el accesor setRedirectPrependBase().

    • code: qué código HTTP utilizar en la redirección. Por defecto, se utiliza un HTTP 302; se puede usar cualquier código entre 301 y 306.

      Puede establecer esta opción globalmente dentro del controlador usando el accesor setRedirectCode().

24.7.7. Heredando del Controlador de Acción

Por diseño, Zend_Controller_Action debe heredarse para crear un controlador de acción. Como mínimo, necesitará definir métodos de acción que el controlador pueda llamar.

Además de crear funcionalidad útil para sus aplicaciones web, también puede encontrar que está repitiendo gran parte de la misma configuración o métodos utilitarios en sus distintos controladores; si es así, crear una clase de controlador base común que herede de Zend_Controller_Action podría resolver dicha redundancia.

Ejemplo 24.1. Manejo de Acciones Inexistentes

Si se hace una petición a un controlador que incluye un método de acción no definido, se invocará Zend_Controller_Action::__call(). __call() es, por supuesto, el método mágico de PHP para la sobrecarga de métodos.

Por defecto, este método lanza una Zend_Controller_Action_Exception indicando que el método solicitado no se encontró en el controlador. Si el método solicitado termina en 'Action', se asume que se solicitó una acción y no existe; dichos errores resultan en una excepción con código 404. Todos los demás métodos resultan en una excepción con código 500. Esto le permite diferenciar fácilmente entre página no encontrada y errores de aplicación en su manejador de errores.

Debería sobrescribir esta funcionalidad si desea realizar otras operaciones. Por ejemplo, si desea mostrar un mensaje de error, podría escribir algo como esto:

class MyController extends Zend_Controller_Action
{
    public function __call($method, $args)
    {
        if ('Action' == substr($method, -6)) {
            // If the action method was not found, render the error
            // template
            return $this->render('error');
        }

        // all other methods throw an exception
        throw new Exception('Invalid method "'
                            . $method
                            . '" called',
                            500);
    }
}

Otra posibilidad es que quiera reenviar a una página de controlador por defecto:

class MyController extends Zend_Controller_Action
{
    public function indexAction()
    {
        $this->render();
    }

    public function __call($method, $args)
    {
        if ('Action' == substr($method, -6)) {
            // If the action method was not found, forward to the
            // index action
            return $this->_forward('index');
        }

        // all other methods throw an exception
        throw new Exception('Invalid method "'
                            . $method
                            . '" called',
                            500);
    }
}

Además de sobrescribir __call(), cada uno de los métodos de inicialización, utilitarios, accesores, vista y de enlace de despacho mencionados anteriormente en este capítulo pueden ser sobrescritos para personalizar sus controladores. Como ejemplo, si está almacenando su objeto vista en un registro, podría querer modificar su método initView() con un código similar al siguiente:

abstract class My_Base_Controller extends Zend_Controller_Action
{
    public function initView()
    {
        if (null === $this->view) {
            if (Zend_Registry::isRegistered('view')) {
                $this->view = Zend_Registry::get('view');
            } else {
                $this->view = new Zend_View();
                $this->view->setBasePath(dirname(__FILE__) . '/../views');
            }
        }

        return $this->view;
    }
}

Esperamos que, a partir de la información de este capítulo, pueda ver la flexibilidad de este componente en particular y cómo puede adaptarlo a las necesidades de su aplicación o sitio.