performance-view.xml 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!-- Reviewed: no -->
  3. <sect1 id="performance.view">
  4. <title>View Rendering</title>
  5. <para>
  6. When using Zend Framework's <acronym>MVC</acronym> layer, chances are you will be using
  7. <classname>Zend_View</classname>. <classname>Zend_View</classname> is performs well
  8. compared to other view or templating engines; since view scripts
  9. are written in <acronym>PHP</acronym>, you do not incur the overhead of compiling custom
  10. markup to <acronym>PHP</acronym>, nor do you need to worry that the compiled
  11. <acronym>PHP</acronym> is not optimized. However, <classname>Zend_View</classname> presents
  12. its own issues: extension is done via overloading (view helpers), and a number of view
  13. helpers, while carrying out key functionality do so with a performance
  14. cost.
  15. </para>
  16. <sect2 id="performance.view.pluginloader">
  17. <title>How can I speed up resolution of view helpers?</title>
  18. <para>
  19. Most <classname>Zend_View</classname> "methods" are actually provided via
  20. overloading to the helper system. This provides important flexibility to
  21. <classname>Zend_View</classname>; instead of needing to extend
  22. <classname>Zend_View</classname> and provide all the helper methods you may
  23. utilize in your application, you can define your helper methods in separate
  24. classes and consume them at will as if they were direct methods of
  25. <classname>Zend_View</classname>. This keeps the view object itself relatively
  26. thin, and ensures that objects are created only when needed.
  27. </para>
  28. <para>
  29. Internally, <classname>Zend_View</classname> uses the <link
  30. linkend="zend.loader.pluginloader">PluginLoader</link> to look
  31. up helper classes. This means that for each helper you call,
  32. <classname>Zend_View</classname> needs to pass the helper name to the
  33. PluginLoader, which then needs to determine the class name, load the
  34. class file if necessary, and then return the class name so it may be
  35. instantiated. Subsequent uses of the helper are much faster, as
  36. <classname>Zend_View</classname> keeps an internal registry of loaded helpers,
  37. but if you use many helpers, the calls add up.
  38. </para>
  39. <para>
  40. The question, then, is: how can you speed up helper resolution?
  41. </para>
  42. <sect3 id="performance.view.pluginloader.cache">
  43. <title>Use the PluginLoader include file cache</title>
  44. <para>
  45. The simplest, cheapest solution is the same as for <link
  46. linkend="performance.classloading.pluginloader">general
  47. PluginLoader performance</link>: <link
  48. linkend="zend.loader.pluginloader.performance.example">use
  49. the PluginLoader include file cache</link>. Anecdotal
  50. evidence has shown this technique to provide a 25-30%
  51. performance gain on systems without an opcode cache, and a
  52. 40-65% gain on systems with an opcode cache.
  53. </para>
  54. </sect3>
  55. <sect3 id="performance.view.pluginloader.extend">
  56. <title>Extend Zend_View to provide often used helper methods</title>
  57. <para>
  58. Another solution for those seeking to tune performance even
  59. further is to extend <classname>Zend_View</classname> to manually add the
  60. helper methods they most use in their application. Such helper
  61. methods may simply manually instantiate the appropriate helper
  62. class and proxy to it, or stuff the full helper implementation
  63. into the method.
  64. </para>
  65. <programlisting language="php"><![CDATA[
  66. class My_View extends Zend_View
  67. {
  68. /**
  69. * @var array Registry of helper classes used
  70. */
  71. protected $_localHelperObjects = array();
  72. /**
  73. * Proxy to url view helper
  74. *
  75. * @param array $urlOptions Options passed to the assemble method
  76. * of the Route object.
  77. * @param mixed $name The name of a Route to use. If null it will
  78. * use the current Route
  79. * @param bool $reset Whether or not to reset the route defaults
  80. * with those provided
  81. * @return string Url for the link href attribute.
  82. */
  83. public function url(array $urlOptions = array(), $name = null,
  84. $reset = false, $encode = true
  85. ) {
  86. if (!array_key_exists('url', $this->_localHelperObjects)) {
  87. $this->_localHelperObjects['url'] = new Zend_View_Helper_Url();
  88. $this->_localHelperObjects['url']->setView($this);
  89. }
  90. $helper = $this->_localHelperObjects['url'];
  91. return $helper->url($urlOptions, $name, $reset, $encode);
  92. }
  93. /**
  94. * Echo a message
  95. *
  96. * Direct implementation.
  97. *
  98. * @param string $string
  99. * @return string
  100. */
  101. public function message($string)
  102. {
  103. return "<h1>" . $this->escape($message) . "</h1>\n";
  104. }
  105. }
  106. ]]></programlisting>
  107. <para>
  108. Either way, this technique will substantially reduce the
  109. overhead of the helper system by avoiding calls to the
  110. PluginLoader entirely, and either benefiting from autoloading or
  111. bypassing it altogether.
  112. </para>
  113. </sect3>
  114. </sect2>
  115. <sect2 id="performance.view.partial">
  116. <title>How can I speed up view partials?</title>
  117. <para>
  118. Those who use partials heavily and who profile their applications
  119. will often immediately notice that the <methodname>partial()</methodname> view
  120. helper incurs a lot of overhead, due to the need to clone the view
  121. object. Is it possible to speed this up?
  122. </para>
  123. <sect3 id="performance.view.partial.render">
  124. <title>Use partial() only when really necessary</title>
  125. <para>
  126. The <methodname>partial()</methodname> view helper accepts three arguments:
  127. </para>
  128. <itemizedlist>
  129. <listitem>
  130. <para>
  131. <varname>$name</varname>: the name of the view script to render
  132. </para>
  133. </listitem>
  134. <listitem>
  135. <para>
  136. <varname>$module</varname>: the name of the module in which the
  137. view script resides; or, if no third argument is provided
  138. and this is an array or object, it will be the
  139. <varname>$model</varname> argument.
  140. </para>
  141. </listitem>
  142. <listitem>
  143. <para>
  144. <varname>$model</varname>: an array or object to pass to the
  145. partial representing the clean data to assign to the view.
  146. </para>
  147. </listitem>
  148. </itemizedlist>
  149. <para>
  150. The power and use of <methodname>partial()</methodname> come from the second
  151. and third arguments. The <varname>$module</varname> argument allows
  152. <methodname>partial()</methodname> to temporarily add a script path for the
  153. given module so that the partial view script will resolve to
  154. that module; the <varname>$model</varname> argument allows you to
  155. explicitly pass variables for use with the partial view.
  156. If you're not passing either argument, <emphasis>use
  157. <methodname>render()</methodname> instead</emphasis>!
  158. </para>
  159. <para>
  160. Basically, unless you are actually passing variables to the
  161. partial and need the clean variable scope, or rendering a view
  162. script from another <acronym>MVC</acronym> module, there is no reason to incur the
  163. overhead of <methodname>partial()</methodname>; instead, use
  164. <classname>Zend_View</classname>'s built-in <methodname>render()</methodname>
  165. method to render the view script.
  166. </para>
  167. </sect3>
  168. </sect2>
  169. <sect2 id="performance.view.action">
  170. <title>How can I speed up calls to the action() view helper?</title>
  171. <para>
  172. Version 1.5.0 introduced the <methodname>action()</methodname> view helper,
  173. which allows you to dispatch an <acronym>MVC</acronym> action and capture its rendered
  174. content. This provides an important step towards the <acronym>DRY</acronym> principle,
  175. and promotes code reuse. However, as those who profile their
  176. applications will quickly realize, it, too, is an expensive
  177. operation. Internally, the <methodname>action()</methodname> view helper needs
  178. to clone new request and response objects, invoke the dispatcher,
  179. invoke the requested controller and action, etc.
  180. </para>
  181. <para>
  182. How can you speed it up?
  183. </para>
  184. <sect3 id="performance.view.action.actionstack">
  185. <title>Use the ActionStack when possible</title>
  186. <para>
  187. Introduced at the same time as the <methodname>action()</methodname> view
  188. helper, the <link
  189. linkend="zend.controller.actionhelpers.actionstack">ActionStack</link>
  190. consists of an action helper and a front controller plugin.
  191. Together, they allow you to push additional actions to invoke
  192. during the dispatch cycle onto a stack. If you are calling
  193. <methodname>action()</methodname> from your layout view scripts, you may
  194. want to instead use the ActionStack, and render your views to
  195. discrete response segments. As an example, you could write a
  196. <methodname>dispatchLoopStartup()</methodname> plugin like the following to
  197. add a login form box to each page:
  198. </para>
  199. <programlisting language="php"><![CDATA[
  200. class LoginPlugin extends Zend_Controller_Plugin_Abstract
  201. {
  202. protected $_stack;
  203. public function dispatchLoopStartup(
  204. Zend_Controller_Request_Abstract $request
  205. ) {
  206. $stack = $this->getStack();
  207. $loginRequest = new Zend_Controller_Request_Simple();
  208. $loginRequest->setControllerName('user')
  209. ->setActionName('index')
  210. ->setParam('responseSegment', 'login');
  211. $stack->pushStack($loginRequest);
  212. }
  213. public function getStack()
  214. {
  215. if (null === $this->_stack) {
  216. $front = Zend_Controller_Front::getInstance();
  217. if (!$front->hasPlugin('Zend_Controller_Plugin_ActionStack')) {
  218. $stack = new Zend_Controller_Plugin_ActionStack();
  219. $front->registerPlugin($stack);
  220. } else {
  221. $stack = $front->getPlugin('ActionStack')
  222. }
  223. $this->_stack = $stack;
  224. }
  225. return $this->_stack;
  226. }
  227. }
  228. ]]></programlisting>
  229. <para>
  230. The <methodname>UserController::indexAction()</methodname> method might then
  231. use the <varname>$responseSegment</varname> parameter to indicate which
  232. response segment to render to. In the layout script, you would
  233. then simply render that response segment:
  234. </para>
  235. <programlisting language="php"><![CDATA[
  236. <?php $this->layout()->login ?>
  237. ]]></programlisting>
  238. <para>
  239. While the ActionStack still requires a dispatch cycle, this is
  240. still cheaper than the <methodname>action()</methodname> view helper as it
  241. does not need to clone objects and reset internal state.
  242. Additionally, it ensures that all pre and post dispatch plugins are
  243. invoked, which may be of particular concern if you are using
  244. front controller plugins for handling <acronym>ACL</acronym>'s to particular
  245. actions.
  246. </para>
  247. </sect3>
  248. <sect3 id="performance.view.action.model">
  249. <title>Favor helpers that query the model over action()</title>
  250. <para>
  251. In most cases, using <methodname>action()</methodname> is simply overkill.
  252. If you have most business logic nested in your models and are
  253. simply querying the model and passing the results to a view
  254. script, it will typically be faster and cleaner to simply write
  255. a view helper that pulls the model, queries it, and does
  256. something with that information.
  257. </para>
  258. <para>
  259. As an example, consider the following controller action and view
  260. script:
  261. </para>
  262. <programlisting language="php"><![CDATA[
  263. class BugController extends Zend_Controller_Action
  264. {
  265. public function listAction()
  266. {
  267. $model = new Bug();
  268. $this->view->bugs = $model->fetchActive();
  269. }
  270. }
  271. // bug/list.phtml:
  272. echo "<ul>\n";
  273. foreach ($this->bugs as $bug) {
  274. printf("<li><b>%s</b>: %s</li>\n",
  275. $this->escape($bug->id),
  276. $this->escape($bug->summary)
  277. );
  278. }
  279. echo "</ul>\n";
  280. ]]></programlisting>
  281. <para>
  282. Using <methodname>action()</methodname>, you would then invoke it with the
  283. following:
  284. </para>
  285. <programlisting language="php"><![CDATA[
  286. <?php $this->action('list', 'bug') ?>
  287. ]]></programlisting>
  288. <para>
  289. This could be refactored to a view helper that looks like the
  290. following:
  291. </para>
  292. <programlisting language="php"><![CDATA[
  293. class My_View_Helper_BugList extends Zend_View_Helper_Abstract
  294. {
  295. public function bugList()
  296. {
  297. $model = new Bug();
  298. $html = "<ul>\n";
  299. foreach ($model->fetchActive() as $bug) {
  300. $html .= sprintf(
  301. "<li><b>%s</b>: %s</li>\n",
  302. $this->view->escape($bug->id),
  303. $this->view->escape($bug->summary)
  304. );
  305. }
  306. $html .= "</ul>\n";
  307. return $html;
  308. }
  309. }
  310. ]]></programlisting>
  311. <para>
  312. You would then invoke the helper as follows:
  313. </para>
  314. <programlisting language="php"><![CDATA[
  315. <?php $this->bugList() ?>
  316. ]]></programlisting>
  317. <para>
  318. This has two benefits: it no longer incurs the overhead of the
  319. <methodname>action()</methodname> view helper, and also presents a more
  320. semantically understandable <acronym>API</acronym>.
  321. </para>
  322. </sect3>
  323. </sect2>
  324. </sect1>
  325. <!--
  326. vim:se ts=4 sw=4 et:
  327. -->