SPL SplObserverとSplSubjectでオブザーバーパターン

hnwさんのエントリーで「見直されるべきPHP5の組み込みイテレータ」というお話がありました。RegexIterator AppendIterator NoRewindIterator SplFileObjectについて説明していただいていました。イテレーターに振る舞いを追加するところが面白いです。

組み込みイテレータの中には、他にも気になるクラスやメソッドがたくさんあります。他に面白いものを見つけた人は是非教えてください。また、多数のクラスやインターフェースが関係しているので、これはデザインパターンで言うと何かな、と考えるのも面白いと思います。

http://d.hatena.ne.jp/hnw/20090523

確かに、面白いんです。それに、これが「面白い」じゃなくて、「普通」になってくれたらなぁという気はします。
ひとくちにSPLといってもライブラリやフレームワーク内での頻出度に差が出てくるので、下手にロジックが隠ぺいされてしまうと、知らない人には何がなんだかわからなくなってしまいます。使用例が増えて欲しいですね。
最近ではArrayObjectやArrayIterator、DirectoryIteratorなんかは頻出なので、SPLを意識するまでもなく話が通じます。でも、たとえばSplObserverとかSplSubject の話だとややレアじゃないですか?僕が知らなかっただけかな。
以前にRecursiveIteratorIteratorでエントリーを書いたことがありますが、手作り関数で再帰を構築しているケースよりは、RecursiveIteratorIteratorのようなやり方の方が慣れると安心感があります。fizzbuzz問題をなるべく冗長に解いてみた - noopな日々はArrayObjectを使っているのですが、これはなくてもいいけど使ったっていう、アンチパターンかもしれない。
ディレクトリ操作、ファイル操作、反復出力、オブジェクトコンテナなど、個別に実装していると関数が乱舞してしまうケースでも、イテレーターだと、共通の拡張方法が用意されているので振る舞いをコントロールしやすいです。PHPではなく他のオブジェクト指向言語ですでに磨かれた枯れた技術でもあるので安心感もあります。

SplObserverとSplSubject

hnwさんのエントリーで「SPL入門 - SPLで学ぶPHP5のオブジェクト指向 -」(PDF)が紹介されていたので読んでみたのですが、P25でSplObserverとSplSubjectの存在と使い方を知ったので、試してみようと思います。オブザーバーパターンでサブジェクト側はオブザーブするオブジェクトをリストで保持します。リフレクションでもオブジェクトIDは取得できないのですが、そこでは、オブザーバーの保存にはspl_object_hashをキーにするといいよという話が書いてありました。ふむふむ。
ただ、せっかくなので、オブジェクトをプールするなら、SplObjectStorageがあるのでそっちを使ってサンプルを作ってみました。
極簡単なサンプルなので、具体実装部分はカラですがアイテムの変更をロガーで記録するというイメージです。

<?php
class logger implements SplObserver {

    public function update(SplSubject $subject)
    {
        // execute related action
        $this->_logger->log(serialize($subject));
    }
}
class item implements SplSubject {
    protected $_id;
    
    protected $_observers = null;

    protected $_event;

    public function __construct($id)
    {
        $this->_id = $id;
        $this->_observers = new SplObjectStorage();
    }
    public function attach(SplObserver $observer)
    {
        $this->_observers->attach($observer);
    }
    public function detach(SplObserver $observer)
    {
        $this->_observers->detach($observer);
    }
    public function notify()
    {
        $observers = $this->_observers;
        $observers->rewind();
        while ($observers->valid() && $this->_cancel !== true) {
            $observers->current()->update($this);
            $observers->next();
        }
    }

    public function action($params)
    {
        //some action
        $this->_event = $params;
        $this->notify();
    }
}

$item = new item;
$item->attach(new logger);;
$item->action(array('contents' => 'hoge'));

ちょっと、雑に書きすぎかという気はしなくもないですが・・・
SplSubjectインターフェースは attach detach notifyの3メソッド、SplObserverインターフェースはupdateメソッドだけが定義されています。それ以外が実装クラスでのアレンジです。
HTTP_Request2 | うえちょこ@ぼろぐ、こちらで紹介されているPEARのhttp_requestもSplObserverとSplSubjectを使っているようです。SplObserver側のソースが例示されていますが、SplSubject側も見ておきたいですね。
今回オブジェクトリストの保存にSplObjectStorageを使いましたが、内部では===でオブジェクトを比較するだけのなんてことないコンテナなんですが、IDの比較などを考えるとオブジェクトを格納するには便利そうです。