BEAR Blog

Because everything is a resource.

Ray 1.0.0

| Comments

1.0.0

Ray.Di / Ray.Aopのバージョン1.0.0をリリースしました。

これまでも何度か紹介しましたがRay.Diはオブジェクトとオブジェクトの関係の問題を解決するDIフレームワーク、Ray.Aopはアスペクト指向プログラミングフレームワークでGoogleのDIフレームワークGuiceのPHPクローンです。

first commit

最初に作り始めたのはRay.Aopです。これが最初のコミットです。まだPHP5.4はなく、PHP5.3でコードしていました。
https://github.com/koriym/Ray.Aop/tree/2ab2dff8204622fdfaeae0bd608e88010b98b99f

最初に作ろうとしたのはこういうものでした。

サービス(呼び出される方)

1
2
3
4
5
6
7
class Mock
{
    public function getDouble($a)
    {
        return $a * 2;
    }
}

コンシュマー(呼び出す方)

1
echo $mock->getDouble(3); //6

これらの呼び出しコード、呼ばれるメソッドを変更をすることなく帰ってくる値を本来の値の10倍(60)にしすることを考えます。実現するためにはメソッドの実行呼び出しコードと呼ばれるメソッドの間に、「10倍にする」という処理を差し込まなければなりません。

1
2
3
4
5
6
7
8
class tenTimes implements MethodInterceptor
{
    public function invoke(MethodInvocation $invocation)
    {
        $result = $invocation->proceed();
        return $result * 10;
    }
}

特定のメソッドをコールしたときのコールバックを設定する機能などがあれば簡単なのですが、そういう機能はありません。なのでオブジェクトを作成するときにその「横断的処理」を織り込んだオブジェクトを生成する必要があります。具体的にはgetDouble(2)でコールされたららその結果を10倍にする横断的処理が透過的に呼ばれるようなオブジェクトをつくります。

最初はどうやって記述したらよいかさっぱり分からなかったのですが、AOPアラインスのインターフェイスやメソッドリフレクション、マジックメソッドの組み合わせでなんとか(というよりもしかしたら)出来るのではと考えました。

最初のコミットでのコードはこういうものでした。

1
$mock = new Weaver(new Mock, $interceptors);

元のオブジェクト(new Mock)に「10倍に」という処理を横断的処理($interceptors)を織り込んたもの(Weaver)を元のオブジェクト同様に扱います

1
echo $mock->getDouble(3); //60

できました!

オブジェクトに横断的処理を”織り込む”事により、呼び出し側も呼び出される側も無変更で振る舞いを変更することができました。webフレームワークの機能の多くはこれらの横断的処理です。呼ぶ側も呼ばれる側にも無変更で、動的に横断的処理を着脱できると事に大きな可能性を感じました。

失敗

最初のコミットのRay.Aop、これは大失敗というのがすぐに分かります。

1
$mock = new Weaver(new Mock, $interceptors);

このコードではメソッドの指定がなく、Mockが持つ全てのメソッドに「10倍」という横断的処理がくっついて(束縛されて)ました。数字以外を返すメソッドではそもそもエラーになります。これでは使い物になりません。

可能性

しかし同時に、この失敗でやっとAOPの本当の力に気がつくことができました。つまり無指定で全てのメソッドに横断的処理が束縛されるということは、指定した特定メソッドに同時に横断的処理を束縛できるということです。delete*で始まるメソッドには全てログを、admin/で始まるパスでは認証チェックを、などといった横断的処理の束縛がアプリケーション実行コンテキストによって指定条件を決めることができます。1

Ray.Di

AOPがメソッドの呼び出しと呼ばれるメソッドとの関係を規定するものだとすれば、DIはオブエクトとオブジェクトの関係を規定するものです。BEAR.Saturdayの開発/運用経験を通じてオブジェクト間の関係性をオブジェクト自身が解決しないことの有用性は大きく認識してたので、DIフレームワークの導入というのは最優先事項でした。BEAR.SaturdayのDIで多いに参考にしてたのはSolarだったのですが2 今回最も良い設計/実装と思えたのはGoogleのDIフレームワークGuiceでした。それを普通に移植するのではなく、Solarの後継のAura、そのAura.Diをforkして、拡張することにしました。

こんなものが移植できるのか3 甚だ疑問で難しいのではないかと思ったのですが、AOPと共に機能するアノテーションベースで依存ポイントを指定し抽象と具象の接続指定でオブジェクトを構成するその設計と指向は、チャレンジに充分すぎるほどのものではないかと感じながら移植を開始しました。

実装、パフォーマンス、デバック等困難な事も多かったのですが、現在のBEAR.Sundayでかなりヘビーに使えていて今回1.0として長くつけてたbetaを外しました。これでcomposer.jsonで@devや@beta指定する必要がなくなります。

Thx

@madapaja さん @akkie さんには有用なアドバイスをもらい、GitHubもPRもしてもらいました。特に@madapaja さんはブログ記事かいてもらったり、スライドで発表してもらったりしました。また@jingu君にはRoboGuiceとの比較を教えてもらって、Guiceを使った事もない自分には大変助かりました。@hidenorigotoさんにはWeb+DBで「たとえばSymfonyのDIコンポーネントと比較すると、DIの構成と利用の分離の点で一歩進んでます」との賛辞で紹介していただきました。@vectorxenonさんにはCakePHPでの利用してもらいました。

みなさん、ありがとうございます。これからもよろしくお願いします。

  1. 現在BEAR.Sundayのsandoxアプリでは、”テスト”ではスタブデータを、”開発”では全てのin/outを記録するようになっています。 []
  2. 他のPHPフレームワークではほとんどDIが使われていませんでした []
  3. 当時とても大きいものだと誤解してました []