BEAR Blog

Because everything is a resource.

Advent Day 7: Ver. 0.Go.Go

| Comments

Day 7

Day 7、7日目の休息ということでDI記事お休みして BEAR.Sudnayの0.5.5リリースの案内です。

BEAR.Sunday version 0.5.5

現在のBEAR.Sundayアプリケーションは三層の構造になっています。

  • Application アプリケーション
  • BEAR.Package アプリケーションパッケージ
  • BEAR.Sunday リソースフレームワーク

BEAR.Sunday

Ray.Di/Aopという生成と振る舞いに制約と機能をもたらすオブジェクトフレームワークに、リソース制約を加えリソースフレームワークとしたものです。

BEAR.Package

必要なテンプレートエンジンやDBライブラリ等の実装選択等の実装選択と、フレームワークが持つ抽象の束縛の集合です。BEAR.Sundayは(コンポーネントの一貫性のプライオリティを下げてでも)多様なライブラリ選択を好みます。アプリケーションアーキテクトはスタッフやプロジェクトを勘案して、アプリケーションパッケージの構成を柔軟に構成することができます。複数のアプリケーションが共有する、チームのソフトウエア基盤です。アプリケーションのコンパイルタイムで必要な処理の多くはこのレイヤーが担当します。

Application アプリケーション

アプリケーションのランタイムコードの記述が中心になります。例えばDBオブジェクトがどのように生成されたかに関与はありません。セットされたDBを使って本質的関心事ではあるビジネスロジックを実現するコードを記述することが中心になります。

ここ最近の作業の中心はBEAR.Packageの分離とBEAR.Packageでのアプリケーションのより良い構成を持つ事でした。

1.0.0 ?

BEAR.Package以外の更新を最小化と、アプリケーション作成を通じてBEAR.Packageを熟成させ1.0.0リリースに繋げたいと思います。
当初予定してた今年中のリリースは難しいですが、来年明けの早い段階でと考えてます。

次のSymfony勉強会 #7 (BEAR.Sundayワークショップ&忘年会) 2012/12/15(Sun)ではこの0.5.5を使います。よろしく御願いします。

Advent Day 6: DI SoC

| Comments

関心の分離としてのDI

オブジェクトは、他のオブジェクトの生成か、使用のいずれかのみを行い、双方を行ってはならない。
生成・使用分離の原則 – Strategic Choice

Eric Raymondは『Art of UNIX Programming』の中で、UNIX®哲学の幾つかを次のように指摘しています。

モジュラー化の原則: 単純な部分を書き、きれいなインターフェースで接続する
分離の原則: ポリシーを機構から分離し、インターフェースをエンジンから分離する
表現の原則: ナレッジをデータの中に入れ込むことによって、プログラム・ロジックは愚かで堅牢となるようにする

こうした考え方は古いものですが、私達は、こうした考えをJava™技術で実現するための新しい方法を発見し続けているのです。そして分離のための技術の最新版、DI(dependency injection: 依存性注入)は、上記の理想を反映したものです。

https://www.ibm.com/developerworks/jp/opensource/library/os-ag-ioc1/

生成・使用分離の原則

生成・使用分離の原則 – 時々聞くこの原則のそもそものソースは何だろうかと時々調べる事がありますが、それに巡り会えたことがありません。ついに見つけたのが流石Strategic Choiceブログなのですが、記事によると名前がなかったので勝手につけました…どうりで中々見つからないわけです。

しかし記事は明快です。

どうして?

この規則に従えば、作業分担が明確化され、結合度が低下する。

オブジェクトの使用は概念レベルを取り扱う。
オブジェクトの生成は具象レベルを取り扱う。
これらレベルの違うものを混ぜて設計すべきではない。
これらを分けて考えることで、それぞれに集中できる。

大変重要です。つまりこれが良く分離されたソースコードでは「概念レベルを取り扱う」ものと「具象レベルを取り扱う」コードが分離されています。

[具象コード]
[具象コード]
[具象コード]

[概念コード]
[概念コード]
[概念コード]

良いコード。これが分離されレイヤーになっています。対してその逆では..

[具象コード]
[具象コード]
[概念コード]
[具象コード]
[具象コード]
[概念コード]

クリアに説明されるとスッキリします。この善し悪しはイメージしやすいのではないでしょうか。ソースの可読性はスコープを狭くして細かく語られることも多いですが、このように大きなスコープでの設計原則が守られてるかという事も大事なのではないかと考えます。

DIの説明を時々料理に例えることがあります。レシピを見て、材料を揃え下ごしらえを済ませてから調理(runtime)に移ります。調理中には途中で材料を選んだり買い物をしなおしたりすることはありません。道具の準備や下ごしらを完了してから調理に移ります。準備と利用は分離され、分業も可能です。

BEAR.Sundayでもruntimeでは原則オブジェクトは用意されたものを利用するだけで、生成に関しての関心は極小化されています。runtimeでオブジェクトの生成方法をアプリケーションワイドなconfigから調べ作成する事は原則ありません。

Advent Day 5: DI Terminology

| Comments

DI用語

Day5はDay4に続かないでDIで使われる用語を色々取り上げてみます。

DI関係の記事を色々見るに言葉がよく分からない事があります。もうあまり使われてる言葉でなかったり、あるいは書かれてる時期や人で使い方やスコープが違うような感じがするときもあります。

よく分からないながらも色々掘り起こして並べてみました。

dependency

依存。オブジェクトに限らず単純な値も含みます。

dependent (dependent consumer)

依存を利用するクライアント

injector / provider / container

wikiによると”an injector (sometimes referred to as a provider or container)”
依存をdependentに用意する。”DIコンテナ”

dependency resolution

依存の解決、依存の取得方法。

Dependency Lookup1

直接の訳語は「依存の参照」

wikipediaでは通常の処理でDIとは逆。と簡単に説明してます

http://xunitpatterns.com%E3%81%A8%E3%81%84%E3%81%86%E3%82%B5%E3%82%A4%E3%83%88%E3%81%A7%E3%81%AF%E3%81%93%E3%81%AE%E3%82%88%E3%81%86%E3%81%AB%E8%AA%AC%E6%98%8E%E3%81%97%E3%81%A6%E3%81%84%E3%81%BE%E3%81%99%E3%80%82

Dependency Lookup is characterized by the following:

  • Either a Singleton[GOF], a Registry[PEAA] or some kind of Thread-Specific Storage[POSA2],
  • with an interface that fully encapsulated which implementation we are using,
  • with a built-in substitution mechanism for replacing the returned object
    with a Test Double
  • accessed via well-known global name.

Dependency Lookupは2つに分かれるそうです。
http://life.neophi.com/danielr/files/InversionOfControl.pdf

Dependency Pull

“中央”にある依存管理コンテナなどに問い合わせて依存を取得します。

1
$foo = Service::get('foo');

Contextualized Dependency Lookup (CDL)

“渡された”コンテナから取得します。

1
2
3
4
public function do(ConteinerInterface $container)
{
    $this->foo = $container->get('foo');
}

Global Registry

Dependency Pullが用いる中央のレジストリ/コンテナ。

Service Locator

Dependency Lookupと同じ(?)、他にObject Factory, Component Broker, Component Registry等。

Dependency Injection (DI)

1
2
3
4
public function do(FooInterface $foo)
{
    $this->foo = $foo;
}

感想、ちょっと一言

  • dependency lookupは広義では外部を参照するという意味で使われれ、狭義では特定のパターンを指すような感じがします
  • CDLやLookupの言い方はちょっと古くService Locator(SL)で総称されているような感じがします。
  • 一般的に「DIコンテナ」が使われますがwikiにあるように「injector」が表記としてより妥当であると思いました。
  • 用語解説のよい記事になかなか巡り会えませんでした
  1. Service Locator ? []

Advent Day 4: Manual DI

| Comments

DI考察ブログエントリー

@Hirakuさんが自身のブログで「PHPのDIで動的にオブジェクトを確保する考察」というDIのエントリーをポストされてます。

Dependency InjectionがPHPでも流行っているそうです。が、未だによくわからないので、わからないところを自分なりに考察してみます。

PHPのDIで動的にオブジェクトを確保する考察

この記事の中で@Hirakuさんは特定のDIコンテナを使わないで、DIというパターンを考察しています。依存を手動で渡す様々な方法を試し、それらのメリット、デメリットを考察しています。

確かに、DBやLoggerなど、クラスの中で一つだけあれば十分なものも多く、そういうオブジェクトはコンストラクタに渡す形で「外から突っ込む」ことができるでしょう。
しかし、動的にオブジェクトを作るケースだって山ほどあるはずです。「必要なオブジェクトはクラスの生成時に全部できあがっている」なんて状態はそうそうあるもんじゃありません。

この記事の例で言うと基本的には@hidenorigotoさんの意見に+1です。

3つのInject

エントリーの中でランタイムでの動的取得に対して3つの方法をあげられています。

  • factoryをinjectする
  • クラス名をinjectする
  • プロトタイプをinjectする

他にいいやり方があったら教えてください。

…考えてみました。
例えばfactory名をinjectするというのはどうでしょうか?

1
2
3
4
5
6
7
8
function setConfig(ConfigInterface $config)
{
    $this->config = $config;
}
function __construct($configFactory = 'ConfigFactory')
{
    $this->setConfig((new $configFactory)->get());
}

利用コードはクラス名を渡すのと同様です

1
$c = new C('MockConfigFactory');

型の保証が必要でないならsetterメソッドを除去して直接代入します。

また、以下のようなインスタンス生成スクリプトで渡す方法も考えてみました。1

1
2
3
function __construct($configFactoryScript = 'ConfigFactoryScript') {
    $this->config = include INSTANCE_DIR . $configFactoryScript;
}

これらの方法は懸念された問題のうちいくつかを解決しています。factoryによる自由な生成と呼び出しコードの簡素化、必要個数のランタイムでの取得も可能になります。

手動DIの限界

しかしやはり、考察を続けると色々な懸念が出て来ます。これらのインジェクトをシングルトン、あるいは限定個数で行う場合は?また依存にもまた依存が必要です。それらの依存もアプリケーションコンテキストで変化していきます。

以下はBEAR.Sundayのリソースクライアントの生成コードです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$config = new Config(new Annotation(new Definition, new Reader));
$injector = new Injector(new Container(new Forge($config)));
$injector->setModule(new TestModule);
$scheme = new SchemeCollection;
$scheme
->scheme('app')
->host('self')
->toAdapter(new App($injector, 'testworld', 'ResourceObject'));
$scheme
->scheme('page')
->host('self')
->toAdapter(new App($injector, 'testworld', 'Page'));
$scheme->scheme('http')->host('*')->toAdapter(new \BEAR\Resource\Adapter\Http);
$invoker = new Invoker(
    $config,
    new Linker(new Reader)
    new Manager(new HandlerFactory, new ResultFactory, new ResultCollection)
);
$resource = new Resource(
    $new Factory($scheme),
    $invoker, new Request($invoker)
);

こんな複雑なオブジェクトの生成をコンストラクタで毎回記述するわけにもいきません。factoryを利用することになりますが、ではこれらの生成のうち一部だけを違うものに差し替えたりするのはどうしたらいいでしょうか?実際にBEAR.SundayではDevモードの時はInvokerはDevInvokerに変わっています。ContainerもPersistentContainerに変更したいと思っています。

このインスタンスを都度生成するのではなくてシングルトンで供給したい時にはどうしたらいいでしょうか?あるいはこの生成にも依存が必要ならどうしたらいいでしょうか?その依存にもそのまた依存があるはずです。

手動によるDIを実践あるいは深く考察することはDIの理解に有意義な事ですが、ある時点からその限界が見えて来る事に気付きます。テストコードやごく小規模の開発では問題にならないでしょう。しかしこういう手続きの記述で複雑なオブジェクトを構成するのは、柔軟性や結合度、DRY原則で問題が出て来ます。しかしそれは同時にDependency Injectorにどのような役割や機能が求められるかの考察にもなるでしょう。

オブジェクトの生成・構成を手続きの記述ではなくルールに基づいたロジックで行う
ことでこれらの問題を解決します。次回Day 5はその考察を行います。

  1. factoryの方が良さそうですね… []

Advent Day 1: Include

| Comments

include

include

(PHP 4, PHP 5)

include 文は指定されたファイルを読み込み、評価します。

“値の返し方: include に失敗したときには FALSE を返し、警告を発生させます。 成功した場合の返り値は、インクルードしたファイル側で変更していない限りは 1 です。 インクルードしたファイルの中で return を実行すれば、 そのファイルの処理をそこで止めて呼び出し元に処理を戻せます。 “

このようにinculude/requireはクラスや関数を定義するだけでなく、そのファイルの中で生成した変数を渡す事ができます。BEAR.Sundayではこの機能を使ってインスタンスの取得を行っています。

bootstrap時のインスタンス取得

インスタンスの取得には様々な方法があります。

  • new演算子
  • サービスクラスでインスタンス提供メソッド利用(getInstance() / factory()等)
  • Factoryクラス利用
  • オブジェクトコンテナからの取得 (SL)
  • Dependency Injectorによる注入 (DI)
  • 上記includeを利用

アプリケーションのbootstrapの時にはまだSLやDIの機能の準備はできていません。アプリケーションはbootしたばかりで関心の分離もこれからです。このような場合にincludeでのインスタンス取得が有効だと考えました。

SLやDIとの共通点

includeによるインスタンス取得は単純です。DIP原則にも従いませんが「インスタンスの生成をクライアント、およびサービスクラスがが関与しない」というDIがもつ機能との共通点があります。

そのインスタンスはsingletonなのかprototypeなのか、どのようなオブジェクトにより構成されてるのかといオブジェクトの構成知識はクライアントに必要ありません。インスタンス提供メソッドを用意する方法と違い構成が変わってもサービスクラスにも変更はありません。

専用Factoryクラスを用意するとクライアント/サービスクラスに構成知識が不要という点は同じですが、より簡易な記述になり利用も簡単です。

Aura Framework

Aura Frameworkはそれぞれのライブラリのインスタンスはスクリプトで取得することができます。実用的であると同時にインスタンス生成方法の実例にもなっています。例 Aura.SignalAura.Filter

BEAR.Sundayでは開発当初から様々なフレームワークのbest practiceを取り込みたいと考えてますがinclude文によるインスタンスの取得、およびその位置の固定化(scripts/instance.php)もその一環です。

Advent Day 3: Dependency Carrying

| Comments

Dependency Carrying

DI以前に不便や疑問を感じてたところで、DI以降すっきり解決できたものにタイトルのDependency Carryingの問題があります。このDependency Carryingに問題はコードを読みにくくしメンテナンス性も低下させテストも難しくする厄介な問題ですが、開発規模が大きくなってくるとこの問題が出やすい様です。

では”Dependency Carrying”の問題とは何のことでしょうか?

Aというメソッドが他のメソッド(B)を呼ぶ場合に、その呼ばれたメソッド(B)が必要の無いオブジェクト(α)を渡さなければならない場合があります。その呼ばれたメソッド(B)がメソッド©を呼ぶのに必要だからです。つまりCはαに依存しています。メソッドBはαが必要にないのにも関わらず、オブジェクトをcarryするためだけに引数で受け取り、Cに渡します。

ソリューションとしてのDI

使用しないオブジェクトを”carry”するということは双方のメソッドシグネチャーに影響を及ぼします。変更箇所を増やし結合を強め、保守性を低下させます。Aで生まれたオブジェクトαをDが必要としてそのコールフローがA->B->C->DというときB,Cは自ら不要なオブジェクトをcarryします。そして改修によりDがそのオブジェクトを必要としなくなったら?無意味と分かりつつ互換性のためにB,Cのコードを改修する事は難しいでしょう。使われないαは意味も無くcarryされ続けるかもしれません。

この問題の解決にしばしばstaticなシングルトンが使われてきました。しかしもちろん、staticなシングルトンはもちろんそれ自体が他の多くの問題を含みます。

DIは多くの場合この問題を解決します。控えめに言っても大幅に低減します。単に解決するだけでなくもっと洗練された方法で必要なオブジェクトをデリバリーすることができます。

Advent Day 2: DI for Readability

| Comments

Dependency Injection Benefits

Jakob Jenkovさんのブログ記事”Dependency Injection Benefits“でDIのメリットを以下の様に述べてます。

  • Reduced Dependencies
  • Reduced Dependency Carrying
  • More Reusable Code
  • More Testable Code
  • More Readable Code

DIの技術的紹介記事は数多くありますが、このうち多くはテストの容易性やモジュラティーの向上による再利用性の高さをメリットとしてあげていますが、この記事ではDIの他のメリットとして可読性の向上を紹介します。

BEAR.Sundayではインターフェイスを通してオブジェクトを受け取ります。インターフェイスとオブジェクトの生成の関係を知るインジェクターがbootstrap時に必要な依存を注入(外部から代入)します。コンストラクタで受け取るものとセッターメソッドによるインジェクションがあります。

コンストラクタインジェクション

1
2
3
4
5
6
7
8
9
10
11
    /**
     * Set resource
     *
     * @param ResourceInterface $resource
     *
     * @Inject
     */
    public function __construct(ResourceInterface $resource)
    {
        $this->resource = $resource;
    }

セッターインジェクション

1
2
3
4
5
6
7
8
9
10
11
    /**
     * Set resource
     *
     * @param ResourceInterface $resource
     *
     * @Inject
     */
    public function setResource(ResourceInterface $resource)
    {
        $this->resource = $resource;
    }

メソッドには@Injectがアノテートされ、外部からの注入が行われる事を示しています。これはコードを読むプログラマにとっても、注入ポイントを知る必要があるインジェクターにとっても文字通りアノテーション(注記)となっています。

外部のインスタンスを取得方法は前回記事のincludeと同じく最小化されています。取得方法のコーディングが必要ないということは、依存の取得が容易であると同時に依存提供の方法や依存内容も変更可能だということを意味します。インターフェイスを通じて受け取る方法は「詳細ではなく抽象に依存せよ」というDIP原則にも従っています。

また依存の利用個所はクラス前半に記述してあるコンストラクタとセッターメソッドに集約されていてそのクラスがどの依存を利用するかをすぐに知るのは容易です。依存を知るためにコードの全てを見る必要がありません。依存は外部から代入され、ランタイム(実行時のコード途中で)で”PULL”されないためです。1

トレイトによるセッターインジェクション

PHP5.4では横断的に利用するメソッドをtraitで集約できます。BEAR.SundayではこれをDIのボイラープレート削減のために使用することができます。これを利用したクラスはこのようになります。

1
2
3
4
5
class A
{
    use ResourceInject;
    use LoggerInject;
    use WebContextInject;

一つの依存が一行で表さ集約されました。コンパクトになりクラスの依存が簡潔に表現されています。

一方、そのインターフェイスはtraitファイルを見なければならなくなりました。この点はSLと同じと割り切るか、出現頻度の高いオブジェクトに限るなどの工夫をするのが良いかも知れません。

  1. この点はSLより優れています []

早期束縛、遅延束縛

| Comments

束縛(バインディング)

束縛またはバインディング(英: Binding)は、情報工学において、より大きく複雑で頻繁に使われる何かへの単純な参照の生成を意味する。単純な参照を大きなものを反復する代わりに使うことができる。束縛とはそのような参照を指す。また、そこから転じて、何らかの「関連付け」を束縛またはバインディングと称する。 –wikipedia 束縛 (情報工学)

BEAR.Sundayではコンパイルタイムとランタイムの明確な区別があります。ブートストラップで実行に必要なアプリケーションオブジェクトやページリソースオブジェクトを作るのがコンパイルです。

オブジェクトの依存がDIで解決され、リクエスト毎に変わらないオブジェクトの結びつきはここで決定されます。ブートストラップ時に束縛するので早期束縛、あるいは束縛が固定されているので静的束縛と呼びます1 が同じものです。(英語ではそれぞれEager Binding, Static Bindining

ランタイムではコンパイルで作られたオブジェクトのリクエスト毎に変わる処理が実行されます。コンパイルで決定できない依存はここでAOPを使って解決されます。利用メソッドが呼ばれる直前のギリギリのタイミングで遅延束縛、あるいは動的束縛(英語ではそれぞれLazy Binding, Dynamic Binding)と呼びます。

  1. Dependency Injectorがインジェクト =早期束縛
  2. AOPのアスペクトがインジェクト =遅延束縛

基本的に早期束縛を優先して、早期束縛できない時のみ遅延束縛を選ぶようにします。ブートストラップで依存を解決する早期束縛はオブジェクトとオブジェクトの結びつきを固定化させコード実行をより少なくします。速度もより高速です。

早期束縛(DI)できないサービスオブジェクト

以下のものはDIできません。AOPのアスペクトでインジェクトするか、またはプロバイダーというわれるマイクロファクトリーで都度生成します。

  1. シリアライズできないもの(PDO、クロージャ、リフレクションなどの組み込みオブジェクト等)
  2. コンストラクタが毎リクエスト生成を前提としてるもの(現在時刻をコンストラクタでプロパティに代入してるmonolog等)
  3. ランタイムでないと決定できないオブジェクト

DBオブジェクトのインジェクトはこのうち1番目と3番目にあたります。PHPはリソース変数がシリアライズできません、それに複数のリクエストメソッドを1つのクラスで表すリソースオブジェクトは、実際にメソッドが呼ばれないとどのDBオブジェクトをインジェクトするか(マスター/スレーブ)が決定できません。DBオブジェクトはアスペクト(横断段的処理)としてメソッドに束縛されたインターセプターでインジェクトします。

DBインジェクター

以下はDBインジェクターのコードです。これはリクエストメソッドの直前にコールされるインターセプターです。
AOPについての初歩的なことはマニュアルのはじめてのアスペクトをご覧下さい。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
<?php
/**
 * This file is part of the BEAR.Sunday package
 *
 * @license http://opensource.org/licenses/bsd-license.php BSD
 */
namespace BEAR\Package\Interceptor;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Logging\SQLLogger;
use Ray\Aop\MethodInterceptor;
use Ray\Aop\MethodInvocation;
use Doctrine\Common\Annotations\AnnotationReader as Reader;
use Ray\Di\Di\Inject;
use Ray\Di\Di\Named;
/**
 * Cache interceptor
 *
 * @package    BEAR.Sunday
 * @subpackage Intercetor
 */
final class DbInjector implements MethodInterceptor
{
    /**
     * @var Reader
     */
    private $reader;
    /**
     * DSN for master
     *
     * @var array
     */
    private $masterDb;
    /**
     * DSN for slave
     *
     * @var array
     */
    private $slaveDb;
    /**
     * Set annotation reader
     *
     * @param Reader $reader
     *
     * @return void
     * @Inject
     */
    public function setReader(Reader $reader)
    {
        $this->reader = $reader;
    }
    /**
     * Constructor
     *
     * @param  array $masterDb
     * @@param array $slaveDb
     *
     * @Inject
     * @Named("masterDb=master_db,slaveDb=slave_db")
     */
    public function __construct(array $masterDb, array $slaveDb)
    {
        $this->masterDb = $masterDb;
        $this->slaveDb = $slaveDb;
    }
    /**
     * (non-PHPdoc)
     * @see Ray\Aop.MethodInterceptor::invoke()
     */
    public function invoke(MethodInvocation $invocation)
    {
        $object = $invocation->getThis();
        $method = $invocation->getMethod();
        $connectionParams = ($method->name !== 'onGet') ? $this->slaveDb : $this->masterDb;
        $pagerAnnotation = $this->reader->getMethodAnnotation($method, 'BEAR\Sunday\Annotation\DbPager');
        if ($pagerAnnotation) {
            $connectionParams['wrapperClass'] = 'BEAR\Package\Module\Database\DoctrineDbalModule\Connection';
            $db = DriverManager::getConnection($connectionParams);
            $db->setMaxPerPage($pagerAnnotation->limit);
        } else {
            $db = DriverManager::getConnection($connectionParams);
        }
        /* @var $db \BEAR\Package\Module\Database\DoctrineDbalModule\Connection */
        $object->setDb($db);
        $result = $invocation->proceed();
        if ($pagerAnnotation) {
            $pagerData = $db->getPager();
            if ($pagerData) {
                $object->headers['pager'] = $pagerData;
            }
        }
        return $result;
    }
}

このDBインジェクターはメソッドに応じてMaster/Slave DBを選択しています。つまりDBオブジェクトを利用するにはクラスに@Dbとマークするだけで、メソッドがコールされた直前のタイミングでマスタースレーブが自動選択されDBオブジェクトがインジェクトされます。

1
2
$connectionParams =
 ($method->name !== 'onGet') ? $this->slaveDb : $this->masterDb;

またもし`@DbPager`とメソッドがアノテートされてるとDBPager用のオブジェクトを作成しています。

1
2
3
4
        if ($pagerAnnotation) {
            $connectionParams['wrapperClass'] = 'BEAR\Package\Module\Database\DoctrineDbalModule\Connection';
            $db = DriverManager::getConnection($connectionParams);
            $db->setMaxPerPage($pagerAnnotation->limit);

DI/AOP開発プロセス

この記事ではこのDBインジェクターにロガーを追加します。既存のオブジェクトに機能を追加する時にどのようなプロセスが必要か明らかにします。

セッターメソッド

DBインジェクターインターセプターでアノテーションリーダーがインジェクトされているようにロガーを受け取るコードを記述します。

1
2
3
4
5
6
7
8
9
10
11
    /**
     * Set SqlLogger
     *
     * @param \Doctrine\DBAL\Logging\SQLLogger $sqlLogger
     *
     * @Inject(optional = true)
     */
    public function setSqlLogger(SQLLogger $sqlLogger)
    {
        $this->sqlLogger = $sqlLogger;
    }

このインジェクトを必須にしないでオプションにするために@Inject(optional = true)としています。SQLLoggerインターフェイスへの束縛が行われていれば(DI設定がされていれば)インジェクトされるし、なければされません。ログの要不要に応じて、例えば開発かプロダクションでその束縛を変更することができます。

インターフェイスと実装をモジュールで束縛

ここで開発時のみにロガーを利用するこtにします。DevModuleにこの記述を加えます。

1
$this->bind('Doctrine\DBAL\Logging\SQLLogger')->to('Doctrine\DBAL\Logging\EchoSQLLogger');

これでDoctrine\DBAL\Logging\SQLLoggerインターフェイスのオブジェクトを受け取るセッターにDoctrine\DBAL\Logging\EchoSQLLoggerオブジェクトがインジェクトされます。

特定メソッドとインターセプターの束縛

DBインジェクターは以下のようなコードでクラスに@Dbとアノテートしていて’on’で始まる全てのメソッドに束縛されてます。(このコードはBEAR\Package\Module\Database\DoctrineDbalModuleで見つける事ができます)

1
2
3
4
5
6
7
8
9
    private function installDbInjector()
    {
        $dbInjector = $this->requestInjection('\BEAR\Package\Interceptor\DbInjector');
        $this->bindInterceptor(
            $this->matcher->annotatedWith('BEAR\Sunday\Annotation\Db'),
            $this->matcher->startWith('on'),
            [$dbInjector]
        );
    }

アノテーション

アノテーションはDoctrine\Commonsのアノテーションライブラリを利用しています。
@Dbアノテーションはこのようなクラスです。このアノテーションは@Target(“CLASS”)とアノテートされ、クラスのみアノテートすることができます。メソッドには記述できません。2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
/**
 * This file is part of the BEAR.Sunday package
 *
 * @package BEAR.Sunday
 * @license http://opensource.org/licenses/bsd-license.php BSD
 */
namespace BEAR\Sunday\Annotation;
/**
 * Db
 *
 * @Annotation
 * @Target("CLASS")
 *
 * @package    BEAR.Sunday
 * @subpackage Annotation
 */
final class Db implements Annotation
{
}

インターセプターで利用

これでインターセプターのセッターメソッドはロガーを受け取りました。この依存を受け取る方法は依存逆転原則に従ってインターフェイスに依存しています。モジュールでの束縛を変更しても利用オブジェクトには変更がありません。

利用コードではもしロガーがあればセットするというコードを記述しました。

1
2
3
        if ($this->sqlLogger instanceof SQLLogger) {
            $db->getConfiguration()->setSQLLogger($this->sqlLogger);
        }

結論

これでDBオブジェクトにロガーがセットされて、SQLを実行するたびにSQLが表示されるようになりました。しかしEchoSQLLoggerはその名の通り、SQLを即時にechoするもので洗練されたログ機構とはとてもいえません。DebugStackというログをスタックとして記録するクラスを代わりに束縛し、ZF\Logや他のログライブラリがその情報を利用するようなコードが必要でしょう。

この記事では2つの束縛の違いと横断的処理であるインターセプターも依存を受け取る事が出来る事、それをオプションにしたりモードに応じてインジェクト内容を変えれる事、インターセプターとメソッドの束縛、受け取った依存の利用という一連の流れを説明しました。

依存逆転原則に従って抽象に依存したオブジェクトを静的にDIで繋げ、AOPで横断的処理の束縛を動的にできる例をみました。関心は分離されその結合は疎です。BEAR.Sundayのフレームワーク機能はほぼ全てこの原則と手順で構成され、BEAR.Sundayアプリケーションも同様です。

共通基底クラスや共通規約、固定的なメソッドやプロパティを使う事なくオブジェクトの構成を同一原則で行っています。そのBEAR.Sundayのオブジェクトを構成する原則もBEAR.Sunday固有のものでなく、ソフトウエア技術として一般性を持ち支持を受けている原理・法則で構成されたものです。

  1. BEAR.Sundayではこの束縛がリクエストを超えて再利用されます []
  2. これらの規則はDoctrineアノテーションによるものです []

PHP:Dis Is It(2012), BEAR.Sunday WS, and WOW !

| Comments

PHP:Dis Is It(2012)

PHPMatsuri 2012という国内最大のPHPハッカソンイベントで「PHP:Dis Is It(2012)」というタイトルで講演を行いました。

これは丁度一年前にPHP Apocalypseというイベントで行ったPHP:Dis Is Itとほぼ同じものですが、言い足りなかった事を加えたり伝え方をより工夫したりしたものです。最初の講演でice breakerになればいいと思ってPVも加えました。

一年前の資料を再度目を通しスピーチを練り直す作業は、去年の自分の考えが変化したりないかを問い直す事でもあります。PVでも紹介したように現在のPHPは変化の延長にあり、やはりこの視点でのPHP評価というものにはより確信が持てました。PVはその確信をうまく反映できて、なかなか楽しいものになったと思うのですがいかがだったでしょうか。

※ これは”This is it”です!

質疑応答 ?

前回と違うのは講演後に質疑応答があった事です。この内容で質問!?って感じだったのですが、何故か次々に手が挙がりました(?)しかも質問は講演と直接関係ないことばかりで(!?)「自己紹介」にも質問がありました。(!?!?)

「アセンブラ、Cと来て何故PHPか?」と聞かれ、「言語よりプロダクトや働き方・関わり方にフォーカスしてる」と答えましたが、後で良く考えてみたらもっと単純でその時々に自分が一番カッコイイと思ってるのをずっと追いかけてるだけだと思います。「ゲーム制作」って今でもかっこいいモノだと信じてますが、「90年代前半のファミコン&ゲームボーイのコンシュマー黎明期、後半の2Dから3Dへのパラダイムチェンジの時期」は本当に特別な時期で全てが輝いていてカッコよかったです。1

また自分が16か17の時に始めて知ったテッド・ネルソンのハイパーテキストの概念2 がWWWという実装になって世界を繋ぐのを20世紀後半にリアルタイムで見た時、やっぱりカッコイイと思いました。アラン・ケイのダイナブックのような夢が実現するとは思いませんでした。

CodeAsDocumentation

| Comments

コードがドキュメントだ

なぜコードが重要なドキュメントなのかというと、 詳細かつ正確なドキュメントはコードしかないからだ — Martin Fowler

アプリケーションシーケンス

どのようなシーケンス12 によりフレームワーク/アプリケーションが実行されるかはフレームワークの設計思想を強く反映していて、柔軟度にも違いがあります。ほぼ固定的なものから、イベントシステムで柔軟性を持たせたもの、それらはアプリケーションテンプレートのようなWebフレームワークと、ライブラリ指向のフルスタックフレームワークで異なってきます。

しかし、しばしばこのシーケンスは分かりにくいものです。

BEAR.Sundayではこのシーケンスをアプリケーションの構成だと考え、完全にアプリケーションドメインのものにならないかと考えました。基本シークエンスをスクリプトにより簡潔に表現し可読性を高め、自由に編集できるようにします。特定の処理を追加したり、割込ませるためにはそのスクリプトを直接編集します。3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/** @global $mode application configuration mode */
// Clear
require dirname(__DIR__) . '/scripts/clear.php';
// Application instance with loader
$mode = 'Prod';
$app = require dirname(__DIR__) . '/scripts/instance.php';
// Dispatch
list($method, $pagePath, $query) = $app->router->match($GLOBALS);
// Request
try {
    $page = $app
    ->resource
    ->$method
    ->uri('page://self/' . $pagePath)
    ->withQuery($query)
    ->eager
    ->request();
} catch (NotFound $e) {
    $code = 404;
    goto ERROR;
} catch (BadRequest $e) {
    $code = 400;
    goto ERROR;
} catch (Exception $e) {
    $code = 503;
    error_log((string)$e);
    goto ERROR;
}
// Transfer
OK: {
    $app->response->setResource($page)->render()->prepare()->send();
    exit(0);
}
ERROR: {
    http_response_code($code);
    require dirname(__DIR__) . "/http/{$code}.php";
    exit(1);
}

これがアプリケーションスクリプトです。通常のOOPを使ったオブジェクトの世界ではなく、シェルスクリプトのように処理手順が記述してあるスクリプトの世界です。

アプリケーションオブジェクトはアプリケーションスクリプトが使う全てのサービス(オブジェクト)を保持したオブジェクトです。スクリプトで取得して1つの$appという変数に代入しています。アプリケーションスクリプトでそのサービスを使って全体のフローを簡潔に構成します。

ページリソースで構成の意図を

アプリケーションスクリプトではページリソースのURIを組み立て、ページリソースリクエストを行いその結果を出力しています。

ページリソースは自らをページとして構成するのが役割ですが、その構成意図はコードで表されます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Posts extends Page
{
    use ResourceInject;
    public $body = [
        'posts' => ''
    ];
    /**
     * @Cache
     */
    public function onGet($id)
    {
        $this['posts'] = $this->resource
            ->get
            ->uri('app://self/blog/posts')
            ->withQuery(['id' => $id])
            ->request();
        return $this;
    }
}

セットすべきコンテンツ

$bodyプロパティはこのページでコンテンツ’posts’を用意する必要があるのを表しています。

指定可能なGETクエリー

GETリクエストにはidクエリーの指定が必須であることも表されています。

コンテンツとリソースをリンク

メソッド内では、コンテンツpostsapp://self/blog/posts?id=$idリソースにするという意図が示されてます。

app://self/blog/postsが何を示すのかはこのコードには現れてないのに注意してください。これが特定のPHPクラスを指すのか、特定ファイルの内容なのか、あるいはリモートアクセスの結果なのか、ここでは指定されていません。app://self/blog/postsというURIで表されるリソースを自身のpostsコンテンツにするという意図が表されているだけです。4

このメソッドには@Cacheというアノテーションがありますが、キャッシュを行うという意図が表されてるだけです。実際にどのようなキャッシュアダプターがどのようにキャッシュを行うかはこのコードには現れません。

これは単に設定ファイルをアノテーションにしただけではないのに注意してください。アプリケーションはbootstrap時にこの意図をみて、このメソッドとキャッシュの実装アスペクトを織り込みます。

フォームの仕様

以下はPOSTメソッドはフォームからPOSTサブミットのリクエストインターフェイスです。

1
2
3
4
5
6
7
    public function onPost(
        $name,
        $gender,
        $age = null,
        $hobby = null,
        $lang = 'PHP'
    ) {

ここではPOSTメソッドでサブミットされるフォームに対してのリクエストインターフェイスがメソッドシグネチャーで表されています。$nameと$genderは入力必須、その他はオプションで$langを入力しなければ”PHP”になります。

リクエストサービスオブジェクトから値を取り出す方法と比べて、PHPのメソッドとHTTPリクエストがシームレスに統合され、コードがPOSTフォームのドキュメンテーションになっています。5

リソースのリンク

1
2
3
4
5
6
    public $links = [
        'payment' => [
            Link::HREF => 'app://self/restbucks/payment{?id}',
            Link::TEMPLATED => true
        ],
    ];

リソースの関係はAPIドキュメント内だけにあるのではなく、コードにも存在しています。この例では注文IDと支払URIの関係がリンクで表されています。

アプリケーションリソース

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    /**
     * @Time
     * @Transactional
     * @CacheUpdate
     */
    public function onPost($title, $body)
    {
        $values = [
            'title' => $title,
            'body' => $body,
            'created' => $this->time
        ];
        $this->db->insert($this->table, $values);
        //
        $lastId = $this->db->lastInsertId('id');
        $this->code = Code::CREATED;
        $this->links['new_post'] = [Link::HREF => "app://self/posts/post?id={$lastId}"];
        $this->links['page_new_post'] = [Link::HREF => "page://self/blog/posts/post?id={$lastId}"];
        return $this;
    }

このアプリケーションリソースメソッドではデータベースオブジェクトを利用してSQLを発行しますが、ここではアノテーションが実装意図を表すドキュメンテーションになっています。

@Transactionalとアノテートするとこのメソッド内のクエリーはトランザクションとして扱われますが、@Cacheと同様、アノテーションそのものがトランザクションの機能をもっているわけではありません。@Transactionalというアノテート(注記)が(意図を表すドキュメンテーションとして扱われ)アプリケーションでトランザクションインターセプターと合成されています。

構造、意図、実装

アプリケーションの構造がアプリケーションスクリプトで表され、アプリケーションを構成するページリソースでは構成の意図が表されています。そのページリソースを構成するためのアプリケーションリソースでは、その構成のための実装が行われますが横断的実装はアスペクトで表され可能な限り下位のレイヤーで実装を行おうとします。

上位のレイヤーでは構成意図を、下位のレイヤーでその実装を行うことでアプリケーションに解決空間の構成と実装のレイヤリングをフレームワークとして与えようとします。

アプリケーションオブジェクト

アプリケーションスクリプトからデーターベース操作を行う下位レイヤーまで、レイヤーを上から下に見て来ましたが、最期にそのトップのアプリケーションオブジェクトをみてみましょう。

アプリケーションというオブジェクトグラフの頂点になるこのインスタンスはアプリケーションスクリプトが使う全てのサービス(オブジェクト)を保持するのがその最大責務です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
final class App implements Context
{
    /** application dir path @var string */
    const DIR = __DIR__;
    public $injector;
    public $resource;
    public $logger;
    public $response;
    public $exceptionHandler;
    public $router;
    public $globals;
    /**
     * Constructor
     *
     * @param \Ray\Di\InjectorInterface                        $injector         Dependency Injector
     * @param \BEAR\Resource\ResourceInterface                 $resource         Resource client
     * @param \BEAR\Sunday\Exception\ExceptionHandlerInterface $exceptionHandler Exception handler
     * @param \BEAR\Sunday\Application\Logger                  $logger           Application logger
     * @param \BEAR\Sunday\Web\ResponseInterface               $response         Web / Console response
     * @param \BEAR\Sunday\Web\RouterInterface                 $router           Resource cache adapter
     * @param \BEAR\Sunday\Web\GlobalsInterface                $globals          GLOBALS value
     *
     * @Inject
     */
    public function __construct(
        InjectorInterface $injector,
        ResourceInterface $resource,
        ExceptionHandlerInterface $exceptionHandler,
        ApplicationLogger $logger,
        ResponseInterface $response,
        RouterInterface $router,
        GlobalsInterface $globals
    ) {
        $this->injector = $injector;
        $this->resource = $resource;
        $this->response = $response;
        $this->exceptionHandler = $exceptionHandler;
        $this->logger = $logger;
        $this->router = $router;
        $this->globals = $globals;
    }
}

オブジェクトの生成と実行が完全に分離されたDIシステムを持つBEAR.Sundayでは、アプリケーションスクリプトが利用するサービスの全ては、このアプリケーションのコードで表されます。

アプリケーションの構成に必要な全てのサービスがここに記述されているはずで、要不要が生じればこのコードに反映されることになります。

Readability, Simplicity and Self-documenting

簡潔で、可読性が高く、自己記述的であること – これはBEAR.Sundayが開発当初から一貫して指向してきたことです。

これは単に表記の問題ではありません。解決しようとする問題の本質を明らかにして、本質的関心だけを浮かび上がる、そういう記述を指向しているということです。そのためにはスコープを適切に持ち、関心を分離し付随的な関心を削ぎ落とす事が必要でこれには多くの前提が必要です。

生成と利用を完全に分離したDIや、横断的関心事を注記6に応じて織り込むAOP、アプリケーションプロトコル/プラットフォームであるHTTPとフレームワークとの融合を即するRESTオブジェクト、これらのその前提です。これらDI/AOP/RESTを道具として指向するのではなく、簡潔で可読性が高く自己記述的なアプリケーションコーディングの前提、オブジェクトフレームワークとして機能させようとしています。

これまでの開発で最も多くのスクラップ&ビルドを繰り返しよりよい解を求めて試行錯誤を繰り返し注力したたのはフレームワークの機能ではなく、アプリケーション記述のあり方です。7 完成が近づくにつれ、その点の関して完成度が高まったと考え、マーティンファウラーのCodeAsDocumentationというタイトルで記事にしました。

次の11/03 22:00からPHP Matsuri2012というイベントでBEAR.Sunday初のワークショップを行う事になりました。当日は簡単なリソース作成を通じて、今回の記事のようなことやDI/AOP、それにOOPや設計の話が出来ればと思ってます。よろしくお願いします。

  1. あらかじめ定められた順序または手続きに従って制御の各段階を逐次進めていく制御 []
  2. http://ja.wikipedia.org/wiki/%E3%82%B7%E3%83%BC%E3%82%B1%E3%83%B3%E3%82%B9%E5%88%B6%E5%BE%A1 []
  3. CakePHP のシーケンス図を作ってみたという記事を読みました。CakePHP2のアプリケーション/フレームワークのシーケンスが分かるという記事なのですが、面白いと思ったと同時に現在のフレームワークのシーケンスの分かりにくさを表している事でもないかと考えました。 []
  4. もしかしたらThriftで繋がれたC++モジュールが何かの実行を行ってくれるのかもしれません []
  5. webに”型”がないようにweb言語であるPHPにスカラータイプヒントがありません []
  6. アノテーション []
  7. これはアプリケーションドメインなのでアプリケーションアーキテクトが責任を持ちますが、そのスタンダードを示す事が大切だと考えより注力しました。 []