TigerZF
🌐Español

F.5. Renderizado de vistas

Cuando se utiliza la capa MVC de Zend Framework, es muy probable que utilice Zend_View. Zend_View tiene un buen rendimiento en comparación con otros motores de vistas o de plantillas; puesto que los scripts de vista están escritos en PHP, no incurre en la sobrecarga de compilar marcado personalizado a PHP, ni necesita preocuparse de que el PHP compilado no esté optimizado. Sin embargo, Zend_View presenta sus propios problemas: la extensión se realiza mediante sobrecarga (helpers de vista), y varios helpers de vista, aunque llevan a cabo funcionalidad clave, lo hacen con un coste de rendimiento.

F.5.1. ¿Cómo puedo acelerar la resolución de helpers de vista?

La mayoría de los "métodos" de Zend_View se proporcionan en realidad mediante sobrecarga al sistema de helpers. Esto proporciona una flexibilidad importante a Zend_View; en lugar de necesitar extender Zend_View y proporcionar todos los métodos de helper que pueda utilizar en su aplicación, puede definir sus métodos de helper en clases separadas y consumirlos a voluntad como si fueran métodos directos de Zend_View. Esto mantiene el propio objeto de vista relativamente ligero, y garantiza que los objetos se crean solo cuando es necesario.

Internamente, Zend_View utiliza el PluginLoader para buscar las clases de helper. Esto significa que por cada helper que invoca, Zend_View necesita pasar el nombre del helper al PluginLoader, que a su vez necesita determinar el nombre de la clase, cargar el archivo de la clase si es necesario, y luego devolver el nombre de la clase para que pueda instanciarse. Los usos posteriores del helper son mucho más rápidos, ya que Zend_View mantiene un registro interno de los helpers cargados, pero si utiliza muchos helpers, las llamadas se acumulan.

La pregunta, entonces, es: ¿cómo se puede acelerar la resolución de helpers?

F.5.1.1. Utilizar la caché de archivos include del PluginLoader

La solución más simple y económica es la misma que para el rendimiento general del PluginLoader: utilizar la caché de archivos include del PluginLoader. La evidencia anecdótica ha demostrado que esta técnica proporciona una ganancia de rendimiento del 25-30% en sistemas sin una caché de opcode, y una ganancia del 40-65% en sistemas con una caché de opcode.

F.5.1.2. Extender Zend_View para proporcionar los métodos de helper más utilizados

Otra solución para quienes buscan ajustar el rendimiento aún más es extender Zend_View para añadir manualmente los métodos de helper que más utilicen en su aplicación. Dichos métodos de helper pueden simplemente instanciar manualmente la clase de helper apropiada y actuar como proxy hacia ella, o incorporar la implementación completa del helper dentro del método.

class My_View extends Zend_View
{
    /**
     * @var array Registry of helper classes used
     */
    protected $_localHelperObjects = array();

    /**
     * Proxy to url view helper
     *
     * @param  array $urlOptions Options passed to the assemble method
     *                           of the Route object.
     * @param  mixed $name The name of a Route to use. If null it will
     *                     use the current Route
     * @param  bool $reset Whether or not to reset the route defaults
     *                     with those provided
     * @return string Url for the link href attribute.
     */
    public function url(array $urlOptions = array(), $name = null,
        $reset = false, $encode = true
    ) {
        if (!array_key_exists('url', $this->_localHelperObjects)) {
            $this->_localHelperObjects['url'] = new Zend_View_Helper_Url();
            $this->_localHelperObjects['url']->setView($this);
        }
        $helper = $this->_localHelperObjects['url'];
        return $helper->url($urlOptions, $name, $reset, $encode);
    }

    /**
     * Echo a message
     *
     * Direct implementation.
     *
     * @param  string $string
     * @return string
     */
    public function message($string)
    {
        return "<h1>" . $this->escape($message) . "</h1>\n";
    }
}

En cualquier caso, esta técnica reducirá sustancialmente la sobrecarga del sistema de helpers al evitar por completo las llamadas al PluginLoader, y beneficiándose de la autocarga o evitándola por completo.

F.5.2. ¿Cómo puedo acelerar las vistas parciales?

Quienes utilizan mucho los parciales y perfilan sus aplicaciones a menudo notarán inmediatamente que el helper de vista partial() incurre en mucha sobrecarga, debido a la necesidad de clonar el objeto de vista. ¿Es posible acelerar esto?

F.5.2.1. Utilizar partial() solo cuando sea realmente necesario

El helper de vista partial() acepta tres argumentos:

  • $name: el nombre del script de vista a renderizar

  • $module: el nombre del módulo en el que reside el script de vista; o, si no se proporciona un tercer argumento y este es un array u objeto, será el argumento $model.

  • $model: un array u objeto a pasar al parcial que representa los datos limpios a asignar a la vista.

El poder y la utilidad de partial() provienen del segundo y tercer argumento. El argumento $module permite a partial() añadir temporalmente una ruta de scripts para el módulo dado, de modo que el script de vista parcial se resuelva a ese módulo; el argumento $model le permite pasar explícitamente variables para usar con la vista parcial. Si no está pasando ninguno de los dos argumentos, utilice render() en su lugar!

Básicamente, a menos que realmente esté pasando variables al parcial y necesite el ámbito de variable limpio, o esté renderizando un script de vista de otro módulo MVC, no hay razón para incurrir en la sobrecarga de partial(); en su lugar, utilice el método integrado render() de Zend_View para renderizar el script de vista.

F.5.3. ¿Cómo puedo acelerar las llamadas al helper de vista action()?

La versión 1.5.0 introdujo el helper de vista action(), que le permite despachar una acción MVC y capturar su contenido renderizado. Esto proporciona un paso importante hacia el principio DRY, y promueve la reutilización de código. Sin embargo, como quienes perfilan sus aplicaciones se darán cuenta rápidamente, también es una operación costosa. Internamente, el helper de vista action() necesita clonar nuevos objetos de petición y respuesta, invocar el despachador, invocar el controlador y la acción solicitados, etc.

¿Cómo se puede acelerar?

F.5.3.1. Utilizar el ActionStack cuando sea posible

Introducido al mismo tiempo que el helper de vista action(), el ActionStack consiste en un helper de acción y un plugin de controlador frontal. Juntos, le permiten insertar acciones adicionales para invocar durante el ciclo de despacho en una pila. Si está llamando a action() desde sus scripts de vista de layout, quizá quiera utilizar en su lugar el ActionStack, y renderizar sus vistas a segmentos de respuesta discretos. Como ejemplo, podría escribir un plugin dispatchLoopStartup() como el siguiente para añadir un cuadro de formulario de inicio de sesión a cada página:

class LoginPlugin extends Zend_Controller_Plugin_Abstract
{
    protected $_stack;

    public function dispatchLoopStartup(
        Zend_Controller_Request_Abstract $request
    ) {
        $stack = $this->getStack();
        $loginRequest = new Zend_Controller_Request_Simple();
        $loginRequest->setControllerName('user')
                     ->setActionName('index')
                     ->setParam('responseSegment', 'login');
        $stack->pushStack($loginRequest);
    }

    public function getStack()
    {
        if (null === $this->_stack) {
            $front = Zend_Controller_Front::getInstance();
            if (!$front->hasPlugin('Zend_Controller_Plugin_ActionStack')) {
                $stack = new Zend_Controller_Plugin_ActionStack();
                $front->registerPlugin($stack);
            } else {
                $stack = $front->getPlugin('ActionStack')
            }
            $this->_stack = $stack;
        }
        return $this->_stack;
    }
}

El método UserController::indexAction() podría entonces utilizar el parámetro $responseSegment para indicar a qué segmento de respuesta renderizar. En el script de layout, entonces simplemente renderizaría ese segmento de respuesta:

<?php $this->layout()->login ?>

Aunque el ActionStack todavía requiere un ciclo de despacho, esto sigue siendo más económico que el helper de vista action(), ya que no necesita clonar objetos ni reiniciar el estado interno. Además, garantiza que se invoquen todos los plugins de pre y post despacho, lo cual puede ser de particular interés si está utilizando plugins de controlador frontal para gestionar ACL's de determinadas acciones.

F.5.3.2. Preferir helpers que consultan el modelo en lugar de action()

En la mayoría de los casos, utilizar action() es simplemente excesivo. Si tiene la mayor parte de la lógica de negocio anidada en sus modelos y simplemente consulta el modelo y pasa los resultados a un script de vista, típicamente será más rápido y limpio simplemente escribir un helper de vista que obtenga el modelo, lo consulte, y haga algo con esa información.

Como ejemplo, considere la siguiente acción de controlador y el siguiente script de vista:

class BugController extends Zend_Controller_Action
{
    public function listAction()
    {
        $model = new Bug();
        $this->view->bugs = $model->fetchActive();
    }
}

// bug/list.phtml:
echo "<ul>\n";
foreach ($this->bugs as $bug) {
    printf("<li><b>%s</b>: %s</li>\n",
        $this->escape($bug->id),
        $this->escape($bug->summary)
    );
}
echo "</ul>\n";

Utilizando action(), lo invocaría entonces con lo siguiente:

<?php $this->action('list', 'bug') ?>

Esto podría refactorizarse a un helper de vista que se vea de la siguiente manera:

class My_View_Helper_BugList extends Zend_View_Helper_Abstract
{
    public function bugList()
    {
        $model = new Bug();
        $html  = "<ul>\n";
        foreach ($model->fetchActive() as $bug) {
            $html .= sprintf(
                "<li><b>%s</b>: %s</li>\n",
                $this->view->escape($bug->id),
                $this->view->escape($bug->summary)
            );
        }
        $html .= "</ul>\n";
        return $html;
    }
}

A continuación, invocaría el helper de la siguiente manera:

<?php $this->bugList() ?>

Esto tiene dos beneficios: ya no incurre en la sobrecarga del helper de vista action(), y también presenta una API más comprensible semánticamente.