[GENERIC] Zend_Translate:
[zend.git] / documentation / manual / en / ref / performance-view.xml
blob782d9d2caab150a1f187720f64e9e7cb31bcfc11
1 <?xml version="1.0" encoding="UTF-8"?>
2 <!-- Reviewed: no -->
3 <sect1 id="performance.view">
4     <title>View Rendering</title>
6     <para>
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
15         cost.
16     </para>
18     <sect2 id="performance.view.pluginloader">
19         <title>How can I speed up resolution of view helpers?</title>
21         <para>
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.
30         </para>
32         <para>
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.
42         </para>
44         <para>
45             The question, then, is: how can you speed up helper resolution?
46         </para>
48         <sect3 id="performance.view.pluginloader.cache">
49             <title>Use the PluginLoader include file cache</title>
51             <para>
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.
60             </para>
61         </sect3>
63         <sect3 id="performance.view.pluginloader.extend">
64             <title>Extend Zend_View to provide often used helper methods</title>
66             <para>
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
72                 into the method.
73             </para>
75             <programlisting language="php"><![CDATA[
76 class My_View extends Zend_View
78     /**
79      * @var array Registry of helper classes used
80      */
81     protected $_localHelperObjects = array();
83     /**
84      * Proxy to url view helper
85      *
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
91      *                     with those provided
92      * @return string Url for the link href attribute.
93      */
94     public function url(array $urlOptions = array(), $name = null,
95         $reset = false, $encode = true
96     ) {
97         if (!array_key_exists('url', $this->_localHelperObjects)) {
98             $this->_localHelperObjects['url'] = new Zend_View_Helper_Url();
99             $this->_localHelperObjects['url']->setView($this);
100         }
101         $helper = $this->_localHelperObjects['url'];
102         return $helper->url($urlOptions, $name, $reset, $encode);
103     }
105     /**
106      * Echo a message
107      *
108      * Direct implementation.
109      *
110      * @param  string $string
111      * @return string
112      */
113     public function message($string)
114     {
115         return "<h1>" . $this->escape($message) . "</h1>\n";
116     }
118 ]]></programlisting>
120             <para>
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.
125             </para>
126         </sect3>
127     </sect2>
129     <sect2 id="performance.view.partial">
130         <title>How can I speed up view partials?</title>
132         <para>
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?
137         </para>
139         <sect3 id="performance.view.partial.render">
140             <title>Use partial() only when really necessary</title>
142             <para>
143                 The <methodname>partial()</methodname> view helper accepts three arguments:
144             </para>
146             <itemizedlist>
147                 <listitem>
148                     <para>
149                         <varname>$name</varname>: the name of the view script to render
150                     </para>
151                 </listitem>
153                 <listitem>
154                     <para>
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.
159                     </para>
160                 </listitem>
162                 <listitem>
163                     <para>
164                         <varname>$model</varname>: an array or object to pass to the
165                         partial representing the clean data to assign to the view.
166                     </para>
167                 </listitem>
168             </itemizedlist>
170             <para>
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>!
179             </para>
181             <para>
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.
188             </para>
189         </sect3>
190     </sect2>
192     <sect2 id="performance.view.action">
193         <title>How can I speed up calls to the action() view helper?</title>
195         <para>
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.
204         </para>
206         <para>
207             How can you speed it up?
208         </para>
210         <sect3 id="performance.view.action.actionstack">
211             <title>Use the ActionStack when possible</title>
213             <para>
214                 Introduced at the same time as the <methodname>action()</methodname> view
215                 helper, the <link
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:
225             </para>
227             <programlisting language="php"><![CDATA[
228 class LoginPlugin extends Zend_Controller_Plugin_Abstract
230     protected $_stack;
232     public function dispatchLoopStartup(
233         Zend_Controller_Request_Abstract $request
234     ) {
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);
241     }
243     public function getStack()
244     {
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);
250             } else {
251                 $stack = $front->getPlugin('ActionStack')
252             }
253             $this->_stack = $stack;
254         }
255         return $this->_stack;
256     }
258 ]]></programlisting>
260             <para>
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:
265             </para>
267             <programlisting language="php"><![CDATA[
268 <?php $this->layout()->login ?>
269 ]]></programlisting>
271             <para>
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
278                 actions.
279             </para>
280         </sect3>
282         <sect3 id="performance.view.action.model">
283             <title>Favor helpers that query the model over action()</title>
285             <para>
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.
292             </para>
294             <para>
295                 As an example, consider the following controller action and view
296                 script:
297             </para>
299             <programlisting language="php"><![CDATA[
300 class BugController extends Zend_Controller_Action
302     public function listAction()
303     {
304         $model = new Bug();
305         $this->view->bugs = $model->fetchActive();
306     }
309 // bug/list.phtml:
310 echo "<ul>\n";
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)
315     );
317 echo "</ul>\n";
318 ]]></programlisting>
320             <para>
321                 Using <methodname>action()</methodname>, you would then invoke it with the
322                 following:
323             </para>
325             <programlisting language="php"><![CDATA[
326 <?php $this->action('list', 'bug') ?>
327 ]]></programlisting>
329             <para>
330                 This could be refactored to a view helper that looks like the
331                 following:
332             </para>
334             <programlisting language="php"><![CDATA[
335 class My_View_Helper_BugList extends Zend_View_Helper_Abstract
337     public function bugList()
338     {
339         $model = new Bug();
340         $html  = "<ul>\n";
341         foreach ($model->fetchActive() as $bug) {
342             $html .= sprintf(
343                 "<li><b>%s</b>: %s</li>\n",
344                 $this->view->escape($bug->id),
345                 $this->view->escape($bug->summary)
346             );
347         }
348         $html .= "</ul>\n";
349         return $html;
350     }
352 ]]></programlisting>
354             <para>
355                 You would then invoke the helper as follows:
356             </para>
358             <programlisting language="php"><![CDATA[
359 <?php $this->bugList() ?>
360 ]]></programlisting>
362             <para>
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>.
366             </para>
367         </sect3>
368     </sect2>
369 </sect1>
370 <!--
371 vim:se ts=4 sw=4 et: