| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358 |
- <sect1 id="performance.view">
- <title>View Rendering</title>
- <para>
- When using Zend Framework's MVC layer, chances are you will be using
- <code>Zend_View</code>. <code>Zend_View</code> is relatively performant
- when 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
- unoptimized. However, <code>Zend_View</code> 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.
- </para>
- <sect2 id="performance.view.pluginloader">
- <title>How can I speed up resolution of view helpers?</title>
- <para>
- Most <code>Zend_View</code> "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.
- </para>
- <para>
- Internally, <code>Zend_View</code> uses the <link
- linkend="zend.loader.pluginloader">PluginLoader</link> to look
- up helper classes. This means that for each helper you call,
- <code>Zend_View</code> 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
- <code>Zend_View</code> keeps an internal registry of loaded helpers,
- but if you use many helpers, the calls add up.
- </para>
- <para>
- The question, then, is: how can you speed up helper resolution?
- </para>
- <sect3 id="performance.view.pluginloader.cache">
- <title>Use the PluginLoader include file cache</title>
- <para>
- The simplest, cheapest solution is the same as for <link
- linkend="performance.classloading.pluginloader">general
- PluginLoader performance</link>: <link
- linkend="zend.loader.pluginloader.performance.example">use
- the PluginLoader include file cache</link>. 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.
- </para>
- </sect3>
- <sect3 id="performance.view.pluginloader.extend">
- <title>Extend Zend_View to provide often used helper methods</title>
- <para>
- Another solution for those seeking to tune performance even
- further is to extend <code>Zend_View</code> 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.
- </para>
- <programlisting role="php"><![CDATA[
- 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($view);
- }
- $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";
- }
- }
- ]]></programlisting>
- <para>
- 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.
- </para>
- </sect3>
- </sect2>
- <sect2 id="performance.view.partial">
- <title>How can I speed up view partials?</title>
- <para>
- Those who use partials heavily and who profile their applications
- will often immediately notice that the <code>partial()</code> view
- helper incurs a lot of overhead, due to the need to clone the view
- object. Is it possible to speed this up?
- </para>
- <sect3 id="performance.view.partial.render">
- <title>Use partial() only when really necessary</title>
- <para>
- The <code>partial()</code> view helper accepts three arguments:
- </para>
- <itemizedlist>
- <listitem><para>
- <code>$name</code>: the name of the view script to render
- </para></listitem>
- <listitem><para>
- <code>$module</code>: 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
- <code>$model</code> argument.
- </para></listitem>
- <listitem><para>
- <code>$model</code>: an array or object to pass to the
- partial representing the clean data to assign to the view.
- </para></listitem>
- </itemizedlist>
- <para>
- The power and use of <code>partial()</code> come from the second
- and third arguments. The <code>$module</code> argument allows
- <code>partial()</code> to temporarily add a script path for the
- given module so that the partial view script will resolve to
- that module; the <code>$model</code> argument allows you to
- explicitly pass variables for use with the partial view.
- If you're not passing either argument, <emphasis>use
- <code>render()</code> instead</emphasis>!
- </para>
- <para>
- 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 <code>partial()</code>; instead, use
- <code>Zend_View</code>'s built-in <code>render()</code> method
- to render the view script.
- </para>
- </sect3>
- </sect2>
- <sect2 id="performance.view.action">
- <title>How can I speed up calls to the action() view helper?</title>
- <para>
- Version 1.5.0 introduced the <code>action()</code> 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 <code>action()</code> view helper needs
- to clone new request and response objects, invoke the dispatcher,
- invoke the requested controller and action, etc.
- </para>
- <para>
- How can you speed it up?
- </para>
- <sect3 id="performance.view.action.actionstack">
- <title>Use the ActionStack when possible</title>
- <para>
- Introduced at the same time as the <code>action()</code> view
- helper, the <link
- linkend="zend.controller.actionhelpers.actionstack">ActionStack</link>
- 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
- <code>action()</code> 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
- <code>dispatchLoopStartup()</code> plugin like the following to
- add a login form box to each page:
- </para>
- <programlisting role="php"><![CDATA[
- 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;
- }
- }
- ]]></programlisting>
- <para>
- The <code>UserController::indexAction()</code> method might then
- use the <code>responseSegment</code> parameter to indicate which
- response segment to render to. In the layout script, you would
- then simply render that response segment:
- </para>
- <programlisting role="php"><![CDATA[
- <?= $this->layout()->login ?>
- ]]></programlisting>
- <para>
- While the ActionStack still requires a dispatch cycle, this is
- still cheaper than the <code>action()</code> view helper as it
- does not need to clone objects and reset internal state.
- Additionally, it ensures that all pre/post dispatch plugins are
- invoked, which may be of particular concern if you are using
- front controller plugins for handling ACLs to particular
- actions.
- </para>
- </sect3>
- <sect3 id="performance.view.action.model">
- <title>Favor helpers that query the model over action()</title>
- <para>
- In most cases, using <code>action()</code> 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.
- </para>
- <para>
- As an example, consider the following controller action and view
- script:
- </para>
- <programlisting role="php"><![CDATA[
- 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";
- ]]></programlisting>
- <para>
- Using <code>action()</code>, you would then invoke it with the
- following:
- </para>
- <programlisting role="php"><![CDATA[
- <?= $this->action('list', 'bug') ?>
- ]]></programlisting>
- <para>
- This could be refactored to a view helper that looks like the
- following:
- </para>
- <programlisting role="php"><![CDATA[
- class My_View_Helper_BugList extends Zend_View_Helper_Abstract
- {
- public function direct()
- {
- $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;
- }
- }
- ]]></programlisting>
- <para>
- You would then invoke the helper as follows:
- </para>
- <programlisting role="php"><![CDATA[
- <?= $this->bugList() ?>
- ]]></programlisting>
- <para>
- This has two benefits: it no longer incurs the overhead of the
- <code>action()</code> view helper, and also presents a more
- semantically understandable API.
- </para>
- </sect3>
- </sect2>
- </sect1>
- <!--
- vim:se ts=4 sw=4 et:
- -->
|