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)
	{}
}

オートワイヤリングを使用できるようにするには、コンテナ内に各型に対してちょうど1つのサービスが存在する必要があります。もし複数存在する場合、オートワイヤリングはどれを渡すべきか分からず、例外をスローします:

services:
	mainDb: PDO(%dsn%, %user%, %password%)
	tempDb: PDO('sqlite::memory:')
	articles: Model\ArticleRepository  # 例外をスローします。mainDb と tempDb の両方が適合します

解決策としては、オートワイヤリングを回避してサービス名を明示的に指定する(例:articles: Model\ArticleRepository(@mainDb))か、より賢い方法として、サービスの1つのオートワイヤリングを無効にするか、最初のサービスを優先することです。

オートワイヤリングの無効化

サービスのオートワイヤリングは、autowired: no オプションを使用して無効にできます:

services:
	mainDb: PDO(%dsn%, %user%, %password%)

	tempDb:
		create: PDO('sqlite::memory:')
		autowired: false               # tempDb サービスはオートワイヤリングから除外されます

	articles: Model\ArticleRepository  # したがって、mainDb をコンストラクタに渡します

articles サービスは、コンストラクタに渡すことができる PDO 型の適合するサービスが2つ(mainDbtempDb)存在するという例外をスローしません。なぜなら、mainDb サービスしか見ていないからです。

Netteでのオートワイヤリングの設定はSymfonyとは異なります。Symfonyでは autowire: false オプションは、特定のサービスのコンストラクタ引数にオートワイヤリングを使用しないことを意味します。 Netteでは、オートワイヤリングは常に、コンストラクタ引数であろうと他のメソッドであろうと使用されます。autowired: false オプションは、特定のサービスのインスタンスがオートワイヤリングによってどこにも渡されるべきではないことを意味します。

オートワイヤリングの優先順位

同じ型のサービスが複数あり、そのうちの1つに autowired オプションを指定すると、そのサービスが優先されます:

services:
	mainDb:
		create: PDO(%dsn%, %user%, %password%)
		autowired: PDO    # 優先されます

	tempDb:
		create: PDO('sqlite::memory:')

	articles: Model\ArticleRepository

articles サービスは、PDO 型の適合するサービスが2つ(mainDbtempDb)存在するという例外をスローしませんが、優先されるサービス、つまり mainDb を使用します。

サービスの配列

オートワイヤリングは、特定の型のサービスの配列も渡すことができます。PHPでは配列の要素の型をネイティブに記述できないため、array 型に加えて、ClassName[] 形式の要素型を持つphpDocコメントを追加する必要があります:

namespace Model;

class ShipManager
{
	/**
	 * @param Shipper[] $shippers
	 */
	public function __construct(array $shippers)
	{}
}

DIコンテナは、指定された型に対応するサービスの配列を自動的に渡します。オートワイヤリングが無効になっているサービスは除外されます。

コメント内の型は array<int, Class> または list<Class> の形式でもかまいません。phpDocコメントの形式を変更できない場合は、typed() を使用して設定で直接サービスの配列を渡すことができます。

スカラー引数

オートワイヤリングは、オブジェクトとオブジェクトの配列のみを注入できます。スカラー引数(例:文字列、数値、ブール値)は設定で記述します。 代替案は、スカラー値(または複数の値)をオブジェクトの形式にカプセル化するsettings-objektを作成することです。その後、このオブジェクトを再びオートワイヤリングで渡すことができます。

class MySettings
{
	public function __construct(
		// readonly は 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  # 例外をスローします。parent と child の両方のサービスが適合します
	childDep: ChildDependent    # オートワイヤリングは child サービスをコンストラクタに渡します

parentDep サービスは Multiple services of type ParentClass found: parent, child という例外をスローします。なぜなら、そのコンストラクタには parentchild の両方のサービスが適合し、オートワイヤリングはどちらを選択すべきか決定できないからです。

したがって、child サービスのオートワイヤリングを ChildClass 型に絞り込むことができます:

services:
	parent: ParentClass
	child:
		create: ChildClass
		autowired: ChildClass   # 'autowired: self' と書くこともできます

	parentDep: ParentDependent  # オートワイヤリングは parent サービスをコンストラクタに渡します
	childDep: ChildDependent    # オートワイヤリングは child サービスをコンストラクタに渡します

これで、parentDep サービスのコンストラクタには parent サービスが渡されます。なぜなら、これが現在唯一適合するオブジェクトだからです。child サービスはもはやオートワイヤリングによって渡されません。はい、child サービスは依然として ParentClass 型ですが、パラメータの型に指定された絞り込み条件はもはや満たされません。つまり、ParentClassChildClassスーパータイプであるという条件は満たされません。

child サービスでは、autowired: ChildClassautowired: self と書くこともできます。なぜなら 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 サービスを制限しない場合、FooDependentBarDependentParentDependentChildDependent のすべてのクラスのコンストラクタに適合し、オートワイヤリングはそれを渡します。

しかし、そのオートワイヤリングを autowired: ChildClass (または self) を使用して ChildClass に絞り込むと、オートワイヤリングはそれを ChildDependent のコンストラクタにのみ渡します。なぜなら、要求される引数の型は ChildClass であり、ChildClassChildClass型であるという条件が満たされるからです。他のパラメータで指定された他の型は ChildClass のスーパータイプではないため、サービスは渡されません。

autowired: ParentClass を使用して ParentClass に制限すると、オートワイヤリングはそれを再び ChildDependent のコンストラクタに渡します(要求される ChildClassParentClass のスーパータイプであるため)。そして、新たに ParentDependent のコンストラクタにも渡します。なぜなら、要求される型 ParentClass も適合するからです。

FooInterface に制限すると、依然として ParentDependent(要求される ParentClassFooInterface のスーパータイプ)と ChildDependent にオートワイヤリングされますが、さらに FooDependent のコンストラクタにも渡されます。しかし、BarDependent には渡されません。なぜなら BarInterfaceFooInterface のスーパータイプではないからです。

services:
	child:
		create: ChildClass
		autowired: FooInterface

	fooDep: FooDependent        # オートワイヤリングは child をコンストラクタに渡します
	barDep: BarDependent        # 例外をスローします。適合するサービスがありません
	parentDep: ParentDependent  # オートワイヤリングは child をコンストラクタに渡します
	childDep: ChildDependent    # オートワイヤリングは child をコンストラクタに渡します