ビューのレンダリング
Zend FrameworkのMVCレイヤを使うときには、
Zend_Viewを使うようになる機会があります。
Zend_Viewは、
他のビューやテンプレートエンジンに比べて良く機能します。
ビュースクリプトはPHPで記述されているので、
PHPにカスタムで加えたコンパイルのオーバーヘッドを招きませんし、
コンパイルされたPHPが最適されていないかどうか心配する必要もありません。
しかしながら、Zend_Viewには特有の問題があります:
エクステンションはオーバーロード経由で実行されます(ビューヘルパ)。
ビューヘルパの多くは重要な機能を担っていますが、
性能面でのコストもあります。
どのようにしたらビューヘルパの解決を速く出来ますか?
ほとんどのZend_View "メソッド"は、
実際ヘルパ方式でオーバーロード経由で提供されています。
これによりZend_Viewに重要な柔軟性が与えられています;
Zend_Viewを拡張してアプリケーションで利用するであろうすべてのヘルパメソッドを提供する必要の代わりに、
分離されたクラスにヘルパメソッドを定義して、
まるでZend_Viewそのもののメソッドであるかのように使い切ることができます。
このことによりビューオブジェクト自身は比較的身軽に保たれ、
オブジェクトが必要なときだけ生成されることが保証されます。
Internally, Zend_View uses the PluginLoader to look
up helper classes. This means that for each helper you call,
Zend_View 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
Zend_View keeps an internal registry of loaded helpers,
but if you use many helpers, the calls add up.
それでは質問です: どのようにしたらヘルパの解決を速く出来ますか?
ファイルキャッシュを含むPluginLoaderを使う
The simplest, cheapest solution is the same as for general
PluginLoader performance: use
the PluginLoader include file cache. 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.
よく使われるヘルパメソッドを提供するようにZend_Viewを拡張する
Another solution for those seeking to tune performance even
further is to extend Zend_View 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.
_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 "
" . $this->escape($message) . "
\n";
}
}
]]>
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.
How can I speed up view partials?
Those who use partials heavily and who profile their applications
will often immediately notice that the partial() view
helper incurs a lot of overhead, due to the need to clone the view
object. Is it possible to speed this up?
Use partial() only when really necessary
The partial() view helper accepts three arguments:
$name: the name of the view script to render
$module: 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
$model argument.
$model: an array or object to pass to the
partial representing the clean data to assign to the view.
The power and use of partial() come from the second
and third arguments. The $module argument allows
partial() to temporarily add a script path for the
given module so that the partial view script will resolve to
that module; the $model argument allows you to
explicitly pass variables for use with the partial view.
If you're not passing either argument, use
render() instead!
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 partial(); instead, use
Zend_View's built-in render() method
to render the view script.
どのようにしたらアクションメソッドのビューヘルパの呼び出しを速く出来ますか?
Version 1.5.0 introduced the action() 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 action() view helper needs
to clone new request and response objects, invoke the dispatcher,
invoke the requested controller and action, etc.
How can you speed it up?
可能な場合はActionStackを使う
Introduced at the same time as the action() view
helper, the ActionStack
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
action() 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
dispatchLoopStartup() plugin like the following to
add a login form box to each page:
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;
}
}
]]>
The UserController::indexAction() method might then
use the responseSegment parameter to indicate which
response segment to render to. In the layout script, you would
then simply render that response segment:
layout()->login ?>
]]>
While the ActionStack still requires a dispatch cycle, this is
still cheaper than the action() 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.
Favor helpers that query the model over action()
In most cases, using action() 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.
ひとつの例として、下記のようなコントローラーアクションと
ビュースクリプトを考えてみましょう:
view->bugs = $model->fetchActive();
}
}
// bug/list.phtml:
echo "
\n";
]]>
Using action(), you would then invoke it with the
following:
action('list', 'bug') ?>
]]>
This could be refactored to a view helper that looks like the
following:
\n";
foreach ($model->fetchActive() as $bug) {
$html .= sprintf(
"
%s: %s
\n",
$this->view->escape($bug->id),
$this->view->escape($bug->summary)
);
}
$html .= "\n";
return $html;
}
}
]]>
You would then invoke the helper as follows:
bugList() ?>
]]>
This has two benefits: it no longer incurs the overhead of the
action() view helper, and also presents a more
semantically understandable API.