View->renderで__toString可能なオブジェクトを返してみる

Viewでの処理結果をシームレスにキャッシュするため、レスポンスオブジェクトに追加するデータを文字列データではなく、__toString可能なオブジェクトにしてみました。PHP的には文字列で格納してゴリゴリ追加していくというのも悪くはないのですが、個人的には関数ライクに処理していきたいので、直接操作するよりも配列等にスタックしていく方が好きです。
いろいろなところでViewが使われていますが、基本的な処理としては、renderで取得した文字列を$response->appendBodyして、レスポンスオブジェクトに追加しています。response側が受け取ったデータをスタックするように変更すればまずはOK.ということで、append appendBody setBodyのところで問答無用にキャストしている部分を修正しました。

<?php
    /**
     * Append content to the body content
     *
     * @param string $content
     * @param null|string $name
     * @return Zend_Controller_Response_Abstract
     */
    public function appendBody($content, $name = null)
    {
        if (null === $name || ! is_string($name)) {
            $name = 'default';
        }
        if (! $content instanceof Flower_View_Unit_Interface ) {
            $append = new Flower_View_Unit_String((string) $content);
        } else {
            $append = $content;
        }
        unset($content);

        if (isset($this->_body[$name])) {
            if ($this->_body[$name] instanceof Flower_View_Unit_Abstract ) {
                $newContent = $this->_body[$name];
            } elseif ($this->_body[$name] instanceof Flower_View_Unit_Interface ) {
                $newContent = new Flower_View_Unit();
                $newContent->append($this->_body[$name]);
            } elseif (is_string($this->_body[$name])) {
                //rare case.
                $newContent = new Flower_View_Unit();
                $newContent->append(new Flower_View_Unit_String($this->_body[$name]));
            }
            $newContent->append($append);
        } else {
            $newContent = $append;
        }
        return $this->append($name, $newContent);
    }

ここで、使っているUnit関連のコンポーネントは、遅延評価可能でスタック可能、文字列化可能なオブジェクトです。
これで受け側はOK。
あとは、View->renderの返り値をUnitで返せばよいということになります。

<?php
    public function run($path)
    {
        $factory = new Flower_View_Unit_ObFactory();
        $factory->captureStart();
        include $path;
        $factory->captureEnd();
        $unit = $factory->makeUnit();
        return $unit;
    }

Zend_Layoutの挙動に注意する

この辺の拡張を行う場合、Zend_LayoutやViewヘルパーの挙動には十分注意する必要があります。Viewヘルパーのうちのいくつかは自前のスタティック変数にデータを保存しているケースがあるので、どの段階でそのデータを保存するのか結果を保存するのかを意識していなければなりません。
Zend_Layoutは、レスポンスオブジェクトを大胆に操作しています。
ビューの構築の最終工程でレスポンスオブジェクトから単一のエントリーを抽出してそれ以外は抹消してデフォルトコンテンツを再構築しています。

<?php
//Zend_Layout_Controller/Plugin/Layout.php

        $response   = $this->getResponse();
        $content    = $response->getBody(true);
        $contentKey = $layout->getContentKey();

        if (isset($content['default'])) {
            $content[$contentKey] = $content['default'];
        }
        if ('default' != $contentKey) {
            unset($content['default']);
        }

        $layout->assign($content);

これで、レスポンスオブジェクトから、デフォルトまたは特定のキーのレスポンスを受け取ってレイアウトオブジェクトにセットします。
次に、レスポンスオブジェクトに結果をsetBodyするので、他のレスポンスセグメントに出力されたデータは消失します。何か使いたいものがある場合は、プレースホルダーを使うのが基本となります。レイアウトオブジェクトにアサインされたデータもプレースホルダーのレジストリに格納されたデータを使っています。

正確なキャッシュを育成するには

正確なキャッシュを作っていくためには、参照透過性が維持できる状態をなるべくキープしてキャッシュ前後での遅延評価を可能にする必要があるのと、参照透過性を壊す要因、スコープ外へのデータ保存や外的要因のコンテンツの変化等の副作用を捕捉しておく必要があります。
最終ラインまでには、まだまだ到達できない感じがしますが、とりあえず動くという状態を維持しながら変化させてみたいと思います。