Internationalisation (i18n) and Localisation (l10n)

View Rendering

When using Zend Framework's MVC layer, chances are you will be using Zend_View. Zend_View is performs well compared to other view or templating engines; since view scripts are written in PHP, you do not incur the overhead of compiling custom markup to PHP, nor do you need to worry that the compiled PHP is not optimized. However, Zend_View presents its own issues: extension is done via overloading (view helpers), and a number of view helpers, while carrying out key functionality do so with a performance cost.

How can I speed up resolution of view helpers?

Most Zend_View "methods" are actually provided via overloading to the helper system. This provides important flexibility to Zend_View; instead of needing to extend Zend_View and provide all the helper methods you may utilize in your application, you can define your helper methods in separate classes and consume them at will as if they were direct methods of Zend_View. This keeps the view object itself relatively thin, and ensures that objects are created only when needed.

Internally, Zend_View uses the PluginLoader to look up helper classes. This means that for each helper you call, Zend_View needs to pass the helper name to the PluginLoader, which then needs to determine the class name, load the class file if necessary, and then return the class name so it may be instantiated. Subsequent uses of the helper are much faster, as Zend_View keeps an internal registry of loaded helpers, but if you use many helpers, the calls add up.

The question, then, is: how can you speed up helper resolution?

Use the PluginLoader include file cache

The simplest, cheapest solution is the same as for general PluginLoader performance: use the PluginLoader include file cache. Anecdotal evidence has shown this technique to provide a 25-30% performance gain on systems without an opcode cache, and a 40-65% gain on systems with an opcode cache.

Extend Zend_View to provide often used helper methods

Another solution for those seeking to tune performance even further is to extend Zend_View to manually add the helper methods they most use in their application. Such helper methods may simply manually instantiate the appropriate helper class and proxy to it, or stuff the full helper implementation into the method.

  1. class My_View extends Zend_View
  2. {
  3.     /**
  4.      * @var array Registry of helper classes used
  5.      */
  6.     protected $_localHelperObjects = array();
  7.  
  8.     /**
  9.      * Proxy to url view helper
  10.      *
  11.      * @param  array $urlOptions Options passed to the assemble method
  12.      *                           of the Route object.
  13.      * @param  mixed $name The name of a Route to use. If null it will
  14.      *                     use the current Route
  15.      * @param  bool $reset Whether or not to reset the route defaults
  16.      *                     with those provided
  17.      * @return string Url for the link href attribute.
  18.      */
  19.     public function url(array $urlOptions = array(), $name = null,
  20.         $reset = false, $encode = true
  21.     ) {
  22.         if (!array_key_exists('url', $this->_localHelperObjects)) {
  23.             $this->_localHelperObjects['url'] = new Zend_View_Helper_Url();
  24.             $this->_localHelperObjects['url']->setView($this);
  25.         }
  26.         $helper = $this->_localHelperObjects['url'];
  27.         return $helper->url($urlOptions, $name, $reset, $encode);
  28.     }
  29.  
  30.     /**
  31.      * Echo a message
  32.      *
  33.      * Direct implementation.
  34.      *
  35.      * @param  string $string
  36.      * @return string
  37.      */
  38.     public function message($string)
  39.     {
  40.         return "<h1>" . $this->escape($message) . "</h1>\n";
  41.     }
  42. }

Either way, this technique will substantially reduce the overhead of the helper system by avoiding calls to the PluginLoader entirely, and either benefiting from autoloading or bypassing it altogether.

How can I speed up view partials?

Those who use partials heavily and who profile their applications will often immediately notice that the partial() view helper incurs a lot of overhead, due to the need to clone the view object. Is it possible to speed this up?

Use partial() only when really necessary

The partial() view helper accepts three arguments:

  • $name: the name of the view script to render

  • $module: the name of the module in which the view script resides; or, if no third argument is provided and this is an array or object, it will be the $model argument.

  • $model: an array or object to pass to the partial representing the clean data to assign to the view.

The power and use of partial() come from the second and third arguments. The $module argument allows partial() to temporarily add a script path for the given module so that the partial view script will resolve to that module; the $model argument allows you to explicitly pass variables for use with the partial view. If you're not passing either argument, use render() instead!

Basically, unless you are actually passing variables to the partial and need the clean variable scope, or rendering a view script from another MVC module, there is no reason to incur the overhead of partial(); instead, use Zend_View's built-in render() method to render the view script.

How can I speed up calls to the action() view helper?

Version 1.5.0 introduced the action() view helper, which allows you to dispatch an MVC action and capture its rendered content. This provides an important step towards the DRY principle, and promotes code reuse. However, as those who profile their applications will quickly realize, it, too, is an expensive operation. Internally, the action() view helper needs to clone new request and response objects, invoke the dispatcher, invoke the requested controller and action, etc.

How can you speed it up?

Use the ActionStack when possible

Introduced at the same time as the action() view helper, the ActionStack consists of an action helper and a front controller plugin. Together, they allow you to push additional actions to invoke during the dispatch cycle onto a stack. If you are calling action() from your layout view scripts, you may want to instead use the ActionStack, and render your views to discrete response segments. As an example, you could write a dispatchLoopStartup() plugin like the following to add a login form box to each page:

  1. class LoginPlugin extends Zend_Controller_Plugin_Abstract
  2. {
  3.     protected $_stack;
  4.  
  5.     public function dispatchLoopStartup(
  6.         Zend_Controller_Request_Abstract $request
  7.     ) {
  8.         $stack = $this->getStack();
  9.         $loginRequest = new Zend_Controller_Request_Simple();
  10.         $loginRequest->setControllerName('user')
  11.                      ->setActionName('index')
  12.                      ->setParam('responseSegment', 'login');
  13.         $stack->pushStack($loginRequest);
  14.     }
  15.  
  16.     public function getStack()
  17.     {
  18.         if (null === $this->_stack) {
  19.             $front = Zend_Controller_Front::getInstance();
  20.             if (!$front->hasPlugin('Zend_Controller_Plugin_ActionStack')) {
  21.                 $stack = new Zend_Controller_Plugin_ActionStack();
  22.                 $front->registerPlugin($stack);
  23.             } else {
  24.                 $stack = $front->getPlugin('ActionStack')
  25.             }
  26.             $this->_stack = $stack;
  27.         }
  28.         return $this->_stack;
  29.     }
  30. }

The UserController::indexAction() method might then use the $responseSegment parameter to indicate which response segment to render to. In the layout script, you would then simply render that response segment:

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

While the ActionStack still requires a dispatch cycle, this is still cheaper than the action() view helper as it does not need to clone objects and reset internal state. Additionally, it ensures that all pre and post dispatch plugins are invoked, which may be of particular concern if you are using front controller plugins for handling ACL's to particular actions.

Favor helpers that query the model over action()

In most cases, using action() is simply overkill. If you have most business logic nested in your models and are simply querying the model and passing the results to a view script, it will typically be faster and cleaner to simply write a view helper that pulls the model, queries it, and does something with that information.

As an example, consider the following controller action and view script:

  1. class BugController extends Zend_Controller_Action
  2. {
  3.     public function listAction()
  4.     {
  5.         $model = new Bug();
  6.         $this->view->bugs = $model->fetchActive();
  7.     }
  8. }
  9.  
  10. // bug/list.phtml:
  11. echo "<ul>\n";
  12. foreach ($this->bugs as $bug) {
  13.     printf("<li><b>%s</b>: %s</li>\n",
  14.         $this->escape($bug->id),
  15.         $this->escape($bug->summary)
  16.     );
  17. }
  18. echo "</ul>\n";

Using action(), you would then invoke it with the following:

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

This could be refactored to a view helper that looks like the following:

  1. class My_View_Helper_BugList extends Zend_View_Helper_Abstract
  2. {
  3.     public function bugList()
  4.     {
  5.         $model = new Bug();
  6.         $html  = "<ul>\n";
  7.         foreach ($model->fetchActive() as $bug) {
  8.             $html .= sprintf(
  9.                 "<li><b>%s</b>: %s</li>\n",
  10.                 $this->view->escape($bug->id),
  11.                 $this->view->escape($bug->summary)
  12.             );
  13.         }
  14.         $html .= "</ul>\n";
  15.         return $html;
  16.     }
  17. }

You would then invoke the helper as follows:

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

This has two benefits: it no longer incurs the overhead of the action() view helper, and also presents a more semantically understandable API.


Internationalisation (i18n) and Localisation (l10n)