| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372 |
- <?xml version="1.0" encoding="UTF-8"?>
- <!-- Reviewed: no -->
- <sect1 id="performance.view">
- <title>View Rendering</title>
- <para>
- When using Zend Framework's <acronym>MVC</acronym> layer, chances are you will be using
- <classname>Zend_View</classname>. <classname>Zend_View</classname> is performs well
- compared to other view or templating engines; since view scripts
- are written in <acronym>PHP</acronym>, you do not incur the overhead of compiling custom
- markup to <acronym>PHP</acronym>, nor do you need to worry that the compiled
- <acronym>PHP</acronym> is not optimized. However, <classname>Zend_View</classname> 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 <classname>Zend_View</classname> "methods" are actually provided via
- overloading to the helper system. This provides important flexibility to
- <classname>Zend_View</classname>; instead of needing to extend
- <classname>Zend_View</classname> 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
- <classname>Zend_View</classname>. This keeps the view object itself relatively
- thin, and ensures that objects are created only when needed.
- </para>
- <para>
- Internally, <classname>Zend_View</classname> uses the <link
- linkend="zend.loader.pluginloader">PluginLoader</link> to look
- up helper classes. This means that for each helper you call,
- <classname>Zend_View</classname> 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
- <classname>Zend_View</classname> 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 <classname>Zend_View</classname> 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 language="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($this);
- }
- $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 <methodname>partial()</methodname> 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 <methodname>partial()</methodname> view helper accepts three arguments:
- </para>
- <itemizedlist>
- <listitem>
- <para>
- <varname>$name</varname>: the name of the view script to render
- </para>
- </listitem>
- <listitem>
- <para>
- <varname>$module</varname>: 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
- <varname>$model</varname> argument.
- </para>
- </listitem>
- <listitem>
- <para>
- <varname>$model</varname>: 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 <methodname>partial()</methodname> come from the second
- and third arguments. The <varname>$module</varname> argument allows
- <methodname>partial()</methodname> to temporarily add a script path for the
- given module so that the partial view script will resolve to
- that module; the <varname>$model</varname> argument allows you to
- explicitly pass variables for use with the partial view.
- If you're not passing either argument, <emphasis>use
- <methodname>render()</methodname> 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 <acronym>MVC</acronym> module, there is no reason to incur the
- overhead of <methodname>partial()</methodname>; instead, use
- <classname>Zend_View</classname>'s built-in <methodname>render()</methodname>
- 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 <methodname>action()</methodname> view helper,
- which allows you to dispatch an <acronym>MVC</acronym> action and capture its rendered
- content. This provides an important step towards the <acronym>DRY</acronym> principle,
- and promotes code reuse. However, as those who profile their
- applications will quickly realize, it, too, is an expensive
- operation. Internally, the <methodname>action()</methodname> 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 <methodname>action()</methodname> 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
- <methodname>action()</methodname> 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
- <methodname>dispatchLoopStartup()</methodname> plugin like the following to
- add a login form box to each page:
- </para>
- <programlisting language="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 <methodname>UserController::indexAction()</methodname> method might then
- use the <varname>$responseSegment</varname> parameter to indicate which
- response segment to render to. In the layout script, you would
- then simply render that response segment:
- </para>
- <programlisting language="php"><![CDATA[
- <?php $this->layout()->login ?>
- ]]></programlisting>
- <para>
- While the ActionStack still requires a dispatch cycle, this is
- still cheaper than the <methodname>action()</methodname> 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 <acronym>ACL</acronym>'s 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 <methodname>action()</methodname> 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 language="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 <methodname>action()</methodname>, you would then invoke it with the
- following:
- </para>
- <programlisting language="php"><![CDATA[
- <?php $this->action('list', 'bug') ?>
- ]]></programlisting>
- <para>
- This could be refactored to a view helper that looks like the
- following:
- </para>
- <programlisting language="php"><![CDATA[
- class My_View_Helper_BugList extends Zend_View_Helper_Abstract
- {
- public function bugList()
- {
- $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 language="php"><![CDATA[
- <?php $this->bugList() ?>
- ]]></programlisting>
- <para>
- This has two benefits: it no longer incurs the overhead of the
- <methodname>action()</methodname> view helper, and also presents a more
- semantically understandable <acronym>API</acronym>.
- </para>
- </sect3>
- </sect2>
- </sect1>
- <!--
- vim:se ts=4 sw=4 et:
- -->
|