Zend_View_Filterでデコレーター実装する。
Zend_View_Filterというのは、Zend_View_Abstract内の記述。実際には、プラグインパスを与えてる際のクラスプレフィックスとしてZend_View_Filter_がデフォルトになっているだけで、Zend_Viewのディレクトリにはフィルターは登録されていない。(いずれ、登録されるのかもしれない。)
Zend_Viewにはフィルター機能がある。フィルタークラスを指定すると、出力直前にフィルターを通してくれる。これを利用すると、デコレーター的に作成済みコンテンツをさらにラップするという使い方ができる。
フィルター側でsetViewメソッドを実装しておくと、setViewでビューオブジェクトを貰える。(Zend_View_Abstractの_getPlugin)
フィルターのなかで単純に$this->_view->render()を呼んだりすると無限ループになる。逆に言えば、viewの中に、スクリプトスタックを持っておいて、popしながらrenderしていけば、デコレーターフィルターチェーンを実装できる。
<?php require_once ('Zend/Filter/Interface.php'); class Flower_View_Filter_ScriptStack implements Zend_Filter_Interface { protected $_view; protected $_content; public function filter ($value) { $script = is_array($this->_view->scriptStack) ? array_pop($this->_view->scriptStack) : false; if ($script) { $this->_content = $value; $value = $this->_view->render($script); $this->_content = ''; } return $value; } public function setView(Zend_View $view) { $this->_view = $view; } public function getContent() { return $this->_content; } }
※フィルターの持続が長いようで、若干無駄があったので、修正した。
これで、現ビューオブジェクトにセット済みのパラメーターを用いたフィルター(実質的にはデコレーター)が可能になる。
ビュースクリプトからフィルター中のコンテンツを取得するため、ビューオブジェクトを汚染してもアレなので、getFilter経由でとらないといけないのは仕方がないところか。
あとは、細かいデコレーターというよりも、全体をセクションで括るなどのラッパー的なデコレーターになるので、細かいカスタマイズや複雑な構造をレンダリングするというデコレーター特性についてはまた別に考えることにする。
※このフィルタの問題点
単純にラップする目的としてはよいのだが、ActionStackや_forwardと絡めたときには、フィルターへのスタックが消えてしまうので、期待する動作にはならない。また、アクション毎に指定できればいいのだが、renderされるタイミングとの兼ね合いが問題になる。
AコントローラーでScriptStackに与えてBコントローラーに_forwardすると、コントローラーAのviewRendererはパスされて、コントローラーBのviewRenderer時にスクリプトスタックが消費される。そのため、チェーンして、AからB/Cへも同じスクリプトを充てたいという時には無理がある。ActionStackでも同じことが言えて、最初のコントローラーまでしか伝搬させることができない。
この点については、ActionStackの拡張版や、横断的に処理できるプラグインにスクリプトスタックを与えて、preDispatchで再生させるというコンセプトはありうる。そこまでやると脱線気味なので本質的な解決方法を模索するべきだろうが・・・