BEAR.Sunday

BEAR.Sunday 学習記録(1) DI

More than 1 year has passed since last update.

なんちゃってエンジニアが BEAR.Sunday であれこれ作ってみたい、という勉強の記録です。怪しい記述があったり、微妙に間違っていたり、盛大に間違っていたりすると思いますので参考にされる場合はご注意ください。
また、試行錯誤しながら書き進めているため、理路整然とした記事にはなっておりません。すみません。
PHP のオブジェクト指向はかろうじて理解していますが、PHP5.4 以降の機能や BEAR.Sunday でも使われている DI や AOP はぼんやりと概念は理解した、というレベルです。

BEAR.Sunday でデータベースに接続したい

BEAR.Sunday 面白そう!ってことで、勉強をはじめました。が、難しい。なにせなんちゃってエンジニアなので、DI や AOP といった新しい概念からして理解しないといけないし、Symfony や Zendframework などのフレームワークも詳しいわけではないので、やること多い!

リソース作って、ページから呼んで、ってあたりはOKなのですが、データベースを扱おうと思って、Zendframework2 の Db を組み込もうってところで手が止まります。さあ一体何から手を付ければいいやら。

いろいろググってみた結果、

  • Zendframework2 の Db を組み込んで DB にアクセスするサンプルは見つからない。
  • Doctrine を組み込んで DB にアクセスするサンプルは見つかった。

ってことで、Doctrine の方の参考記事を元に、何をやればいいのか調べていきます。

すると早速ぶち当たるのが、「Provider を用意して」云々というところ。
Provider ?
見ると、Dependency Injection 関係の話の模様。DI は、概念はなんとなーく理解できたものの、実装はよく分かってない、使ったこと無いのですが、どうやら BEAR.Sunday をバリバリ使うには腰を据えて勉強してみないといけないようです。どっちみち、今どきの開発には必須ですよね、たぶん。

BEAR.Sunday の DI

5種類のBinding

まずは、BEAR.Sunday の DI に関するマニュアルを読んでみます。
分かったことは、依存性注入の方法が5種類もあること。さて一体どうやって使い分けるのでしょうか。とりあえず5種類を見ていきます。

  • Linked Binding $this->bind('MovieApp\FinderInterface')->to('MovieApp\Finder');
  • Provider Binding $this->bind('TransactionLogInterface')->toProvider('DatabaseTransactionLogProvider');
  • Named Binding $this->bind('CreditCardProcessorInterface')->annotatedWith('Checkout')->to('CheckoutCreditCardProcessor');
  • Instance Binding $this->bind()->annotatedWith("login_id")->toInstance('bear');
  • Constructor Binding $this->bind('TransactionLog')->toConstructor(['db' => new Database]);

記述する場所は、AbstractModule を継承したクラスの中で、bear/skeleton で生成したひな形を使っている場合は、apps/{Vendor.Application}/src/Module の中ということになるかと思います。

class Module extends \Ray\Di\AbstractModule
{
    public function configure()
    {
        $this->bind('MovieApp\FinderInterface')->to('MovieApp\Finder');
    }
}

使い方もだいたい似ていて、Module の cofigure() メソッドの中で、 $this->bind(Y)->toX(Z) みたいに「XなZをYに束縛する」というコードを書くきまりのようです。

参考記事で使われていた Provider を使った束縛も2つ目に出てきます。

他はどうかって言うと、マニュアルによれば Instance Binding は、「依存のないオブジェクトや配列やスカラー値などの時だけ利用する」とあり、Constructor Binding「外部のクラスなどで@Injectが使えない場合などに」とあって、この2つは限定的なケースでのみ使う方法のようです。

最初の Linked Binding は、まだよくわかっていませんが、一番素直に DI 出来る場合の束縛方法と見ました。

3つ目の Named Binding は、AOP を利用した束縛方法のようです。AOP も初遭遇なので後回し、にしたいと思ったのですが、参考記事で使われていて、これも理解しないとダメのよう。というか、普段そんなにコメント見ないので、使われていることに気づくまでに時間がかかりました。

DI や AOP は、理解して腑に落ちるまで何をやっているのか、どうしてこれで動くのかさっぱりわからん、というコードが多いですね。でも理解できれば見通しが良くなりそうな予感。頑張ります。

Provider Binding のコードを読む

さて、問題の Provider Binding です。
マニュアルでは、以下のように説明されています。

シンプルでインスタンス(値)を返すだけの、Providerインターフェイスを実装したプロバイダークラスを作成します。

このプロバイダーの実装は自身にコンストラクターで@Injectとアノテートしている依存があります。 依存を使ってインスタンスを生成してget()メソッドで生成したインスタンスを返します。

このように依存が必要なインスタンスには Provider Bindings を使います。

途中コードが入りますが、……わ、わからん。特に2段目が分かりません。やはりコードを丹念に読まないとダメですね。
最初のコードは、BEAR.Sunday には ProviderInterface というのが用意されていて、 get() のみを持つシンプルなインタフェースです、というだけです。これは簡単。

次の DatabaseTransactionLogProvider クラス。これはわからん。
interface で要求している get() を実装しているのですが、$connection$transactionLog にセットして返しています。

ここまでの疑問点

  • __construct() の引数の $connection はどこから出てきたのか?
  • そもそもこの Provider クラスは何がしたかったのか?いるのか?Linked Bindingではダメなのか
  • __construct() に付いている @Inject って何だ? たぶん、DI のなんとかポイントを示す Annotation なんだろうけど。

Linked Binding と Provider Binding

困ったときは Google 先生。ってことで、「BEAR.Sunday linked binding」でググると、ちょっと古いですが、Google Code 内のドキュメントに記事がありました。

  • Linked Binding: インターフェイス名と実クラス名を束縛します。
  • Provider Binding : オブジェクトのコンストラクションに引数が必要なものや、オブジェクトのコンストラクションが複雑なものはプロバイダーというファクトリークラスをバインドします。

薄々そんな予感はしていたんですが、Provider というのはファクトリ(インスタンスを生成する処理を担うクラス)の一種なのですね。Linked Binding だとクラス名が与えられて単純に __construct() が呼ばれるだけなんだけど、Provider Binding を使えば、もっと複雑なインスタンス生成処理を用意できるということでしょうか。

そうして改めて見直すと、この DatabaseTransactionLogProvider は bind 用に DatabaseTransactionLog インスタンスを生成して $connection をセットする処理を担っているということでしょう。
Linked Binding では、クラス名からインスタンスを生成するだけなので、$connection をセットすることが出来ません。なるほど、納得(た、たぶん)。これで疑問点の2番めは解消。

Linked Binding

ちょっと外れますが、いまひとつ何やっているか良くわかっていなかったマニュアル内の Linked Binding のサンプルコードですが、ここまで来てようやく分かってきました。
このコードには、Finder クラスと Lister クラスがあって、Lister::setFinder() が呼ばれた気配がないのに、Lister に Finder がセットされたことになっています。

$injector = Injector::create([new Module]);
$lister = $injector->getInstance('MovieApp\Lister');
$works = ($lister->finder instanceof MovieApp\Finder);
// It works!

この3行あまりのコードの中の一体どこで!?と思うのですが、

class Lister
{
    public $finder;

    /**
     * @Inject
     */
    public function setFinder(FinderInterface $finder)
    {
        $this->finder = $finder;
    }
}

この、@Inject がキーだったわけです。$injector->getInstance()で、Lister::setFinder()$finder がインジェクトされる、その目印が @Inject というわけです。
このあたり、慣れないと「setFinder() は一体どこで呼ばれたんだ?」と狼狽えてしまいます。

Instance Binding は使えない?

さらに話は逸れますが、BEAR.Sunday のデータベース接続についてググっていくつか記事を読んでいたら、こんなのがありました。BEAR.Sunday 開発者の郡山さんのアカウントのツイートです。
bear.sundayでのDIとAOP

@BEARSunday @atakig 基本的にシリアライズできるものしかRay.DiでDI出来ません。sandboxのDBオブジェクトはAOPでセットしてます。

どういうことか、よく分かっていませんが話の流れからしてどうやら DB の接続用インスタンスを Instance Binding でバインドして注入というのは出来ないみたいです。な、なるほど。

再び Provider Binding

というわけで、再び Provider Binding に戻って、残る疑問点のうちの

__construct() の引数の $connection はどこから出てきたのか?

は、DatabaseTransactionLogProvider::__construct() でアノテーション @Inject が指定されているので、どっかから注入される予定ということだと思います。

とりあえず今回はここまで。データベースへの接続が目標ですが、DI を理解するところで手間取ってしまいましたので、実際の接続は次回ということで。