オートワイヤリング
Autowiringは、コンストラクタなどに自動的にサービスを渡すことができる優れた機能で、私たちはサービスを書く必要が全くありません。時間を大幅に節約できます。
これにより、サービス定義を書く際に、大半の引数を省略することができます。代わりに
services:
articles: Model\ArticleRepository(@database, @cache.storage)
と書くだけです。
services:
articles: Model\ArticleRepository
自動配線は型によって駆動されるので、ArticleRepository
クラスは以下のように定義する必要があります。
namespace Model;
class ArticleRepository
{
public function __construct(\PDO $db, \Nette\Caching\Storage $storage)
{}
}
autowiring を使用するには、コンテナ内の各タイプに対して ただ1つのサービス が必要です。それ以上あると、autowiring はどれを渡せばいいのかわからなくなり、例外を投げ出してしまいます。
services:
mainDb: PDO(%dsn%, %user%, %password%)
tempDb: PDO('sqlite::memory:')
articles: Model\ArticleRepository # THROWS EXCEPTION, both mainDb and tempDb matches
解決策としては、autowiring をバイパスしてサービス名を明示的に指定する方法があります
(例:articles: Model\ArticleRepository(@mainDb)
)。しかし、1つのサービス、または最初のサービスの自動配線を無効にする方が便利です。
自動配線を無効にする
autowired: no
オプションを使用することで、サービスの自動配線を無効にすることができます。
services:
mainDb: PDO(%dsn%, %user%, %password%)
tempDb:
create: PDO('sqlite::memory:')
autowired: false # removes tempDb from autowiring
articles: Model\ArticleRepository # therefore passes mainDb to constructor
articles
サービスはmainDb
サービスしか見ていないので、
コンストラクタに渡すことのできるPDO
タイプの一致するサービスが二つある
(つまりmainDb
とtempDb
) という例外を投げません。
Nette で自動配線を設定すると、Symfony のautowire: false
オプションで自動配線をサービスのコンストラクタ引数に使用しないように指定した場合とは異なる動作をします。
Nette
では、コンストラクタの引数であろうと他のメソッドであろうと、自動配線は常に使用されます。autowired: false
オプションは、自動配線を使ってサービスのインスタンスをどこにも渡してはいけないというものです。
望ましい自動配線
同じタイプのサービスが複数あり、そのうちの1つがautowired
のオプションを持っている場合、そのサービスが優先されます。
services:
mainDb:
create: PDO(%dsn%, %user%, %password%)
autowired: PDO # makes it preferred
tempDb:
create: PDO('sqlite::memory:')
articles: Model\ArticleRepository
articles
サービスは、一致するPDO
サービスが2つある (すなわちmainDb
とtempDb
)という例外を投げず、優先されるサービス、すなわちmainDb
を使用します。
サービスのコレクション
Autowiring は、特定の型のサービスの配列を渡すこともできます。PHP
は配列の項目の型をネイティブに表記することができないので、array
の型に加えて、ClassName[]
のように項目の型を指定した phpDoc
コメントを追加する必要があります。
namespace Model;
class ShipManager
{
/**
* @param Shipper[] $shippers
*/
public function __construct(array $shippers)
{}
}
DI コンテナは、指定した型に対応するサービスの配列を自動的に渡します。DI コンテナは、指定した型に対応するサービスの配列を自動的に渡します。自動配線が無効になっているサービスは省略されます。
コメントの型は、以下の形式も可能である。 array<int, Class>
または
list<Class>
.phpDoc のコメントの形式を制御できない場合は、
サービスの配列を直接渡すこともできます。 typed()
.
スカラー引数
Autowiring は、オブジェクトとオブジェクトの配列のみを渡すことができます。スカラー引数(文字列、数値、ブール値など)は設定に書きます。 代替案としては、スカラー値(または複数の値)をオブジェクトとしてカプセル化したsettings-objectを作成し、それを autowiring を使って再度渡すことができます。
class MySettings
{
public function __construct(
// readonly can be used since PHP 8.1
public readonly bool $value,
)
{}
}
サービスを作成するには,コンフィギュレーションに追加します.
services:
- MySettings('any value')
すべてのクラスは自動配線によってそれを要求します。
オートワイヤリングの絞り込み
個々のサービスにおいて、オートワイヤリングは特定のクラスやインターフェースに絞り込むことができます。
通常、自動配線では、サービスが対応する型を持つ各メソッドパラメータにサービスを 渡す。絞り込みとは、メソッドパラメータに指定された型がサービスを渡すために満たすべき条件を指定することである。
例を挙げてみよう。
class ParentClass
{}
class ChildClass extends ParentClass
{}
class ParentDependent
{
function __construct(ParentClass $obj)
{}
}
class ChildDependent
{
function __construct(ChildClass $obj)
{}
}
すべてサービスとして登録すると、自動配線は失敗してしまいます。
services:
parent: ParentClass
child: ChildClass
parentDep: ParentDependent # THROWS EXCEPTION, both parent and child matches
childDep: ChildDependent # passes the service 'child' to the constructor
parentDep
サービスは例外Multiple services of type ParentClass found: parent, child
を投げます。なぜならparent
とchild
の両方がそのコンストラクタに適合し、autowiring
はどちらを選ぶべきかの判断を下すことができないからです。
したがって、サービスchild
の自動配線はChildClass
に絞られます。
services:
parent: ParentClass
child:
create: ChildClass
autowired: ChildClass # alternative: 'autowired: self'
parentDep: ParentDependent # THROWS EXCEPTION, the 'child' can not be autowired
childDep: ChildDependent # passes the service 'child' to the constructor
parentDep
サービスは、現在唯一の一致するオブジェクトであるため、parentDep
サービスのコンストラクタに渡されます。child
サービスは自動配線で渡されなくなりました。はい、child
サービスはまだParentClass
型ですが、パラメータ型に与えられた狭義の条件はもはや適用されません。つまり、ParentClass
is a supertype ofChildClass
はもはや真ではありません。
child
の場合,self
は現在のサービスタイプを意味するので,autowired: ChildClass
はautowired: self
と書くことができる.
autowired
のキーには、いくつかのクラスやインターフェイスを配列として含めることができます。
autowired: [BarClass, FooInterface]
例題にインターフェイスを追加してみましょう。
interface FooInterface
{}
interface BarInterface
{}
class ParentClass implements FooInterface
{}
class ChildClass extends ParentClass implements BarInterface
{}
class FooDependent
{
function __construct(FooInterface $obj)
{}
}
class BarDependent
{
function __construct(BarInterface $obj)
{}
}
class ParentDependent
{
function __construct(ParentClass $obj)
{}
}
class ChildDependent
{
function __construct(ChildClass $obj)
{}
}
child
のサービスを制限しない場合、FooDependent
,BarDependent
,ParentDependent
,ChildDependent
のすべてのクラスのコンストラクタに収まり、 autowiring はそこにそれを渡します。
しかし、autowired: ChildClass
(またはself
) を使ってChildClass
に自動配線を絞ると、ChildDependent
のコンストラクタにしか渡らなくなります。これは、ChildClass
型の引数が必要で、ChildClass
型ChildClass
であるためです。他のパラメータに指定された型はChildClass
のスーパーセットではないので、サービスは渡されません。
autowired: ParentClass
を使ってParentClass
に制限すると、autowiring
はChildDependent
コンストラクタに再び渡します (必要な型ChildClass
はParentClass
のスーパーセットなので)。また、必要な型ParentClass
も一致するのでParentDependent
コンストラクタにも渡します。
FooInterface
に限定すると、ParentDependent
(要求される型ParentClass
はFooInterface
の上位型) とChildDependent
にはまだ自動配線されますが、さらにFooDependent
のコンストラクタには渡されますが、BarInterface
はFooInterface
の上位型ではないので、BarDependent
には渡りません。
services:
child:
create: ChildClass
autowired: FooInterface
fooDep: FooDependent # passes the service child to the constructor
barDep: BarDependent # THROWS EXCEPTION, no service would pass
parentDep: ParentDependent # passes the service child to the constructor
childDep: ChildDependent # passes the service child to the constructor