1 <?xml version="1.0" encoding="UTF-8"?>
3 <sect1 id="performance.view">
4 <title>View Rendering</title>
7 When using Zend Framework's <acronym>MVC</acronym> layer, chances are you will be using
8 <classname>Zend_View</classname>. <classname>Zend_View</classname> is performs well
9 compared to other view or templating engines; since view scripts
10 are written in <acronym>PHP</acronym>, you do not incur the overhead of compiling custom
11 markup to <acronym>PHP</acronym>, nor do you need to worry that the compiled
12 <acronym>PHP</acronym> is not optimized. However, <classname>Zend_View</classname> presents
13 its own issues: extension is done via overloading (view helpers), and a number of view
14 helpers, while carrying out key functionality do so with a performance
18 <sect2 id="performance.view.pluginloader">
19 <title>How can I speed up resolution of view helpers?</title>
22 Most <classname>Zend_View</classname> "methods" are actually provided via
23 overloading to the helper system. This provides important flexibility to
24 <classname>Zend_View</classname>; instead of needing to extend
25 <classname>Zend_View</classname> and provide all the helper methods you may
26 utilize in your application, you can define your helper methods in separate
27 classes and consume them at will as if they were direct methods of
28 <classname>Zend_View</classname>. This keeps the view object itself relatively
29 thin, and ensures that objects are created only when needed.
33 Internally, <classname>Zend_View</classname> uses the <link
34 linkend="zend.loader.pluginloader">PluginLoader</link> to look
35 up helper classes. This means that for each helper you call,
36 <classname>Zend_View</classname> needs to pass the helper name to the
37 PluginLoader, which then needs to determine the class name, load the
38 class file if necessary, and then return the class name so it may be
39 instantiated. Subsequent uses of the helper are much faster, as
40 <classname>Zend_View</classname> keeps an internal registry of loaded helpers,
41 but if you use many helpers, the calls add up.
45 The question, then, is: how can you speed up helper resolution?
48 <sect3 id="performance.view.pluginloader.cache">
49 <title>Use the PluginLoader include file cache</title>
52 The simplest, cheapest solution is the same as for <link
53 linkend="performance.classloading.pluginloader">general
54 PluginLoader performance</link>: <link
55 linkend="zend.loader.pluginloader.performance.example">use
56 the PluginLoader include file cache</link>. Anecdotal
57 evidence has shown this technique to provide a 25-30%
58 performance gain on systems without an opcode cache, and a
59 40-65% gain on systems with an opcode cache.
63 <sect3 id="performance.view.pluginloader.extend">
64 <title>Extend Zend_View to provide often used helper methods</title>
67 Another solution for those seeking to tune performance even
68 further is to extend <classname>Zend_View</classname> to manually add the
69 helper methods they most use in their application. Such helper
70 methods may simply manually instantiate the appropriate helper
71 class and proxy to it, or stuff the full helper implementation
75 <programlisting language="php"><![CDATA[
76 class My_View extends Zend_View
79 * @var array Registry of helper classes used
81 protected $_localHelperObjects = array();
84 * Proxy to url view helper
86 * @param array $urlOptions Options passed to the assemble method
87 * of the Route object.
88 * @param mixed $name The name of a Route to use. If null it will
89 * use the current Route
90 * @param bool $reset Whether or not to reset the route defaults
92 * @return string Url for the link href attribute.
94 public function url(array $urlOptions = array(), $name = null,
95 $reset = false, $encode = true
97 if (!array_key_exists('url', $this->_localHelperObjects)) {
98 $this->_localHelperObjects['url'] = new Zend_View_Helper_Url();
99 $this->_localHelperObjects['url']->setView($this);
101 $helper = $this->_localHelperObjects['url'];
102 return $helper->url($urlOptions, $name, $reset, $encode);
108 * Direct implementation.
110 * @param string $string
113 public function message($string)
115 return "<h1>" . $this->escape($message) . "</h1>\n";
121 Either way, this technique will substantially reduce the
122 overhead of the helper system by avoiding calls to the
123 PluginLoader entirely, and either benefiting from autoloading or
124 bypassing it altogether.
129 <sect2 id="performance.view.partial">
130 <title>How can I speed up view partials?</title>
133 Those who use partials heavily and who profile their applications
134 will often immediately notice that the <methodname>partial()</methodname> view
135 helper incurs a lot of overhead, due to the need to clone the view
136 object. Is it possible to speed this up?
139 <sect3 id="performance.view.partial.render">
140 <title>Use partial() only when really necessary</title>
143 The <methodname>partial()</methodname> view helper accepts three arguments:
149 <varname>$name</varname>: the name of the view script to render
155 <varname>$module</varname>: the name of the module in which the
156 view script resides; or, if no third argument is provided
157 and this is an array or object, it will be the
158 <varname>$model</varname> argument.
164 <varname>$model</varname>: an array or object to pass to the
165 partial representing the clean data to assign to the view.
171 The power and use of <methodname>partial()</methodname> come from the second
172 and third arguments. The <varname>$module</varname> argument allows
173 <methodname>partial()</methodname> to temporarily add a script path for the
174 given module so that the partial view script will resolve to
175 that module; the <varname>$model</varname> argument allows you to
176 explicitly pass variables for use with the partial view.
177 If you're not passing either argument, <emphasis>use
178 <methodname>render()</methodname> instead</emphasis>!
182 Basically, unless you are actually passing variables to the
183 partial and need the clean variable scope, or rendering a view
184 script from another <acronym>MVC</acronym> module, there is no reason to incur the
185 overhead of <methodname>partial()</methodname>; instead, use
186 <classname>Zend_View</classname>'s built-in <methodname>render()</methodname>
187 method to render the view script.
192 <sect2 id="performance.view.action">
193 <title>How can I speed up calls to the action() view helper?</title>
196 Version 1.5.0 introduced the <methodname>action()</methodname> view helper,
197 which allows you to dispatch an <acronym>MVC</acronym> action and capture its rendered
198 content. This provides an important step towards the <acronym>DRY</acronym> principle,
199 and promotes code reuse. However, as those who profile their
200 applications will quickly realize, it, too, is an expensive
201 operation. Internally, the <methodname>action()</methodname> view helper needs
202 to clone new request and response objects, invoke the dispatcher,
203 invoke the requested controller and action, etc.
207 How can you speed it up?
210 <sect3 id="performance.view.action.actionstack">
211 <title>Use the ActionStack when possible</title>
214 Introduced at the same time as the <methodname>action()</methodname> view
216 linkend="zend.controller.actionhelpers.actionstack">ActionStack</link>
217 consists of an action helper and a front controller plugin.
218 Together, they allow you to push additional actions to invoke
219 during the dispatch cycle onto a stack. If you are calling
220 <methodname>action()</methodname> from your layout view scripts, you may
221 want to instead use the ActionStack, and render your views to
222 discrete response segments. As an example, you could write a
223 <methodname>dispatchLoopStartup()</methodname> plugin like the following to
224 add a login form box to each page:
227 <programlisting language="php"><![CDATA[
228 class LoginPlugin extends Zend_Controller_Plugin_Abstract
232 public function dispatchLoopStartup(
233 Zend_Controller_Request_Abstract $request
235 $stack = $this->getStack();
236 $loginRequest = new Zend_Controller_Request_Simple();
237 $loginRequest->setControllerName('user')
238 ->setActionName('index')
239 ->setParam('responseSegment', 'login');
240 $stack->pushStack($loginRequest);
243 public function getStack()
245 if (null === $this->_stack) {
246 $front = Zend_Controller_Front::getInstance();
247 if (!$front->hasPlugin('Zend_Controller_Plugin_ActionStack')) {
248 $stack = new Zend_Controller_Plugin_ActionStack();
249 $front->registerPlugin($stack);
251 $stack = $front->getPlugin('ActionStack')
253 $this->_stack = $stack;
255 return $this->_stack;
261 The <methodname>UserController::indexAction()</methodname> method might then
262 use the <varname>$responseSegment</varname> parameter to indicate which
263 response segment to render to. In the layout script, you would
264 then simply render that response segment:
267 <programlisting language="php"><![CDATA[
268 <?php $this->layout()->login ?>
272 While the ActionStack still requires a dispatch cycle, this is
273 still cheaper than the <methodname>action()</methodname> view helper as it
274 does not need to clone objects and reset internal state.
275 Additionally, it ensures that all pre and post dispatch plugins are
276 invoked, which may be of particular concern if you are using
277 front controller plugins for handling <acronym>ACL</acronym>'s to particular
282 <sect3 id="performance.view.action.model">
283 <title>Favor helpers that query the model over action()</title>
286 In most cases, using <methodname>action()</methodname> is simply overkill.
287 If you have most business logic nested in your models and are
288 simply querying the model and passing the results to a view
289 script, it will typically be faster and cleaner to simply write
290 a view helper that pulls the model, queries it, and does
291 something with that information.
295 As an example, consider the following controller action and view
299 <programlisting language="php"><![CDATA[
300 class BugController extends Zend_Controller_Action
302 public function listAction()
305 $this->view->bugs = $model->fetchActive();
311 foreach ($this->bugs as $bug) {
312 printf("<li><b>%s</b>: %s</li>\n",
313 $this->escape($bug->id),
314 $this->escape($bug->summary)
321 Using <methodname>action()</methodname>, you would then invoke it with the
325 <programlisting language="php"><![CDATA[
326 <?php $this->action('list', 'bug') ?>
330 This could be refactored to a view helper that looks like the
334 <programlisting language="php"><![CDATA[
335 class My_View_Helper_BugList extends Zend_View_Helper_Abstract
337 public function bugList()
341 foreach ($model->fetchActive() as $bug) {
343 "<li><b>%s</b>: %s</li>\n",
344 $this->view->escape($bug->id),
345 $this->view->escape($bug->summary)
355 You would then invoke the helper as follows:
358 <programlisting language="php"><![CDATA[
359 <?php $this->bugList() ?>
363 This has two benefits: it no longer incurs the overhead of the
364 <methodname>action()</methodname> view helper, and also presents a more
365 semantically understandable <acronym>API</acronym>.