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.
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?
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.
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.
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?
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.
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?
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.
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.