localdisk

PHP とか Java とか Web とか好きなことを書きます。

PHPでAOPしてみた

PHPでDIはたまに見かけるけどPHPAOPってのはあまりみないですね。S2.PHP5くらいしかない。

で、AOPしたいってだけでS2.PHP5をいれるのもアリでしょうけど、もっとちっちゃく利用したかったのでプロトタイプを作ってみた。

アスペクトクラスを書く

<?php
class Aspect {
    private $clazz;
    private $obj;
    private $intercepters = array();
    public function __construct($obj) {
        if (is_null($obj) || !isset($obj) || !is_object($obj)) {
            throw new Exception('オブジェクトじゃないとだめ');
        }
        $this->obj = $obj;
        $this->clazz = new ReflectionClass($obj);
    }
    public function pointcut($joinpoint, $intercepter) {
        if (!$this->clazz->hasMethod($joinpoint)) {
            throw new Exception('ジョインポイントなし');
        }
        $this->intercepters[$joinpoint] = $intercepter;
    }
    public function run($joinpoint, $args = array()) {
        if (!array_key_exists($joinpoint, $this->intercepters)) {
            throw new Exception('ポイントカットされてない');
        }
        $intercepter = new $this->intercepters[$joinpoint];
        $intercepter->invoke($this->obj, $this->clazz->getMethod($joinpoint), $args);
    }
}

インターセプターを書く

<?php
interface Intercepter {
    public function invoke($object, ReflectionMethod $method, array $args = null);
}
class TestIntercepter implements Intercepter {
    public function invoke($object, ReflectionMethod $method, array $args = null) {
        echo 'before</br>';
        $ret = $method->invoke($object, $args);
        echo '</br>';
        echo 'after';
        return $ret;
    }
}

使い方

<?php
class Test {
    public function testAspect() {
        echo 'aspect';
    }
}
$aspect = new Aspect(new Test());
$aspect->pointcut('testAspect', 'TestIntercepter');
$aspect->run('testAspect');

結果

before
aspect
after

まとめ

もちろんこのままで使い物になりませんが、基本としてこんな感じでよいかと。上記テストクラスのファンクションにアノテーションでポイントカットとインターセプター、あとタイミング(呼び出される前とか後とか両方とか例外が発生した時とか)が指定できるとかなり使いやすくなるんじゃないかと。近いうちに作ってみようかと思います。

DIとかAOPについて思うこと

最近PHPフレームワークでもDIを実装しようとする動きがあるみたいですね。symfonyとか。で、思うんですがユニットテストに重きを置いていないプロジェクトの場合、それほどDIの恩恵を受けることができないと思います。つか多分いらない。あ、大きなアプリには役に立つかもしれません。まだできてない部分にモックコンポーネントを作ってコンテナの設定1つで置き換えるとか。
で、AOPは使い方が限られますが定番の使い方っていうのがあって(ログとったりとかトランザクションとかログインチェックとか)、DIよりわかりやすく使える。とくにどうという結論はありませんが、僕はこう思っていますということで。