ZF2のDi関連で

Zend\Db\Adapter\AdapterとProfilerAwareInterfaceについて

ZF2.1でZend\Db\AdapterにProfilerAwareInterfaceが追加されました。
使い方にもよりますが、Zend\Di\Diで自動化している場合、2.0系の設定でそのまま使うと、

'Invalid instantiator of type "NULL" for "Zend\\Db\\Adapter\\Profiler\\ProfilerInterface".

という例外が発生することがあります。
簡単な解決としては、Profilerを加えてしまうというのがあります。
2.0系での設定例としては、下記のような感じでできますが、

<?php
return array(
'di' => array (
    'instance' => array(
        'Zend\Db\Adapter\Adapter' => array(
            'parameters' => array(
                'driver' => array(
                    'dbname' => 'test',
                    //その他パラメーター
                ),
            ),
        ),
    ),
),
);

2.1からはProfilerAwareInterfaceが追加されたので、パラメーターとしてProfilerを渡します。

<?php
return array(
'di' => array (
    'instance' => array(
        'Zend\Db\Adapter\Adapter' => array(
            'parameters' => array(
                'driver' => array(
                    'dbname' => 'test',
                    //その他パラメーター
                ),
                'profiler' => 'Zend\Db\Adapter\Profiler\Profiler',
            ),
        ),
    ),
),
);

'profiler' => 'Zend\Db\Adapter\Profiler\Profiler',
の行ですが、
ProfilerAwareInterfaceがsetProfilerで$profilerをパラメーターとして定義しているため、'profiler'という名前で、ProfilerInterfaceを実装しているクラス名を指定します。これにより、自動的にProfilerインスタンスを抽入してくれます。
「Profilerがいらないとき、困らない?」というスタンスと「AwareInterfaceは依存の表明だから、必須だ」というスタンスがありえますね。以前にMLで議論されていた気がしますが、とりあえず両方に対応できるようにしておくのがいいかもしれません。
(追記) AwareInterfaceは依存の表明でprofilerがsetterインジェクションで自動的に抽入されます。一方、〜〜CapableInterfaceは特定の機能をgetterでgetできることを表すようです。他にもパターンがあるのでどこかで一覧ができているといいですよね。

Zend\Diをデバッグする

デバッグするといっても基本的にはDi定義を参照してそれに対する動作を確認することに尽きます。
たとえば、リフレクションで指定されたZend\Db\Adapter\Adapterの定義を確認してみます。

<?php
Zend\Di\Display\Console::export($di);
Zend\Di\Display\Console::export($di, array('Zend\Db\Adapter\Adapter'));

結果

    Parameters For Class: Zend\Db\Adapter\Adapter
      driver [type: scalar, required, injection-method: __construct fq-name: Zend\Db\Adapter\Adapter::__construct:0]
      platform [type: Zend\Db\Adapter\Platform\PlatformInterface, not required, injection-method: __construct fq-name: Zend\Db\Adapter\Adapter::__construct:1]
      queryResultPrototype [type: Zend\Db\ResultSet\ResultSetInterface, not required, injection-method: __construct fq-name: Zend\Db\Adapter\Adapter::__construct:2]
      profiler [type: Zend\Db\Adapter\Profiler\ProfilerInterface, not required, injection-method: __construct fq-name: Zend\Db\Adapter\Adapter::__construct:3]
      profiler [type: Zend\Db\Adapter\Profiler\ProfilerInterface, required, injection-method: setProfiler fq-name: Zend\Db\Adapter\Adapter::setProfiler:0]

Diの設定として、driver platform queryResultPrototype profilerが使えることを意味しています。
この中で、profilerが2行あり、not requiredとrequiredがあります。
この違いは、最初のprofilerはコンストラクタのオプションで、次行のprofilerはserProfilerで使うprofilerです。
さて、この出力結果から、最初に書いたProfilerAwareInterfaceに関する挙動が出てくると判断できればよいのですが、もう少し詳しく見てみる必要がありそうです。
これらパラメーターとinjectionMethodの組み合わせのうち必須とマークされたものかどうかをチェックしてみます。
上記の出力では不十分で、CompilerDefinitionを使って設定をファイルに書き出してチェックするか、自前で出力することになります。
大雑把ですが、こんな感じ。

<?php
class RuntimeDefinitionExporter extends RuntimeDefinition {
    public function export()
    {
        return var_export($this->classes);
    }
}

Zend\Db\Adapter\Adapterの出力結果は下記になります。

<?php
return array (
  'Zend\\Db\\Adapter\\Adapter' => 
  array (
    'supertypes' => 
    array (
      0 => 'Zend\\Db\\Adapter\\AdapterInterface',
      1 => 'Zend\\Db\\Adapter\\Profiler\\ProfilerAwareInterface',
    ),
    'instantiator' => '__construct',
    'methods' => 
    array (
      '__construct' => true,
      'setProfiler' => true,
    ),
    'parameters' => 
    array (
      '__construct' => 
      array (
        'Zend\\Db\\Adapter\\Adapter::__construct:0' => 
        array (
          0 => 'driver',
          1 => NULL,
          2 => true,
          3 => NULL,
        ),
        'Zend\\Db\\Adapter\\Adapter::__construct:1' => 
        array (
          0 => 'platform',
          1 => 'Zend\\Db\\Adapter\\Platform\\PlatformInterface',
          2 => false,
          3 => NULL,
        ),
        'Zend\\Db\\Adapter\\Adapter::__construct:2' => 
        array (
          0 => 'queryResultPrototype',
          1 => 'Zend\\Db\\ResultSet\\ResultSetInterface',
          2 => false,
          3 => NULL,
        ),
        'Zend\\Db\\Adapter\\Adapter::__construct:3' => 
        array (
          0 => 'profiler',
          1 => 'Zend\\Db\\Adapter\\Profiler\\ProfilerInterface',
          2 => false,
          3 => NULL,
        ),
      ),
      'setProfiler' => 
      array (
        'Zend\\Db\\Adapter\\Adapter::setProfiler:0' => 
        array (
          0 => 'profiler',
          1 => 'Zend\\Db\\Adapter\\Profiler\\ProfilerInterface',
          2 => true,
          3 => NULL,
        ),
      ),
    ),
  ),
);

これを見れば、はっきりします。
まず、Zend\Di\Display\Console::exportの出力結果で、profilerが2度出現した理由は、定義配列中のparameters内で、メソッド毎の解析結果からprofilerという名前でコンストラクタとセッターの2箇所の定義が生じていることがわかります。
コンストラクタのprofilerはfalse (= not required) setProfilerのprofilerはtrue ( = required)と表示された理由がここにあります。
次に、Zend\Diが依存の自動解決を試みるかどうかですが、これはmethodsにあるメソッドがtrueなら必須であり、falseなら任意です。
Instantiatorや〜〜AwareInterface中のsetterは必須としてRuntimeDefinitionが解析します。単純なsetterは任意となります。
したがって、最初の問いに戻りますが、
Zend\Db\Adapter\AdapterがProfilerAwareInterfaceを実装しているとき、profilerはAwareInterfaceによる強制で解決される必要があるとすれば、設定中に書かれていれば、そのProfilerがセットされ、Profilerの指定が何もなければ例外が発生することになります。
では、AwareInterfaceではあるが、Profilerは不要と判断している場合はどうでしょうか。
この場合は少し遠回りになりますが、Definitionを調整してDefinitionListに加える方法がよいのではないかと思っています。
調整箇所は

    'methods' => 
    array (
      '__construct' => true,
      'setProfiler' => false, // true,
    ),

という具合に、trueをfalseに変更します。
そこで、ClassDefinitionの定義をdiに加えます。

<?php
return array(
    'di' => array(
        'definition' => array(
            'class' => array(
                'Zend\Db\Adapter\Adapter' =>  array(
                    'methods' => array(
                        'setProfiler' => array(
                            'profiler' => array(
                                'name' => 'profiler',
                                'type' => 'Zend\Db\Adapter\Profiler\ProfilerInterface',
                                'required' => false,
                            ),
                        ), 
                    ),
                ),
            ),
        ),
    ),
),

ClassDefinitionを定義してもRuntimeDefinitionは走ります。もし、RuntimeDefinitionを走らせたくないときは、モジュールのbootstrap時にでも、ArrayDefinitionでそれを読み込んでDefinitionListに加えるか、DiFactoryの修正版を作り、service_listener_optionsでDependencyInjectorキーに割り当てればOKでしょう。

まとめると、FooAwareInterfaceでは、Interface上で定義されたsetterは依存の表明となり、Diコンテナが強制的に抽入を試みます。Zend\Db\Adapter\Adapterの場合は、parametersでprofilerを指定します。
抽入を任意にしたい場合は、ClassDefinitionを追加し、methods定義でrequiredをfalseに設定します。

※追記
http://d.hatena.ne.jp/n314/20130417/1366226869
こちらで、addTypePreferenceによるインターフェースへのデフォルト実装と、全体でインターフェースインジェクションを停止する方法が紹介されています。
Profilerのように横断的に共通するものについてはaddTypePreferenceがいいですね。