オートワイヤリング

オートワイヤリングは、必要なサービスをコンストラクタやほかのメソッドに自動的に渡してくれる素晴らしい機能で、明示的に指定する必要がなくなります。多くの時間を節約できます。

おかげで、サービスの定義を書くときに大部分の引数を省けます。次のように書く代わりに、

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

オートワイヤリングはサービスの名前を決して使いません。PHP の型システムだけに導かれるので、クラスが実装するインターフェースや継承するクラスも満たすと分かります。おかげでサービスの名前は補助的な識別子にすぎず、名前を変えてもアプリケーションの何も壊れません。

オートワイヤリングを使うには、コンテナの中に各型のサービスがちょうどひとつなければなりません。複数あると、オートワイヤリングはどれを渡すべきか分からず例外を投げます。

services:
	mainDb: PDO(%dsn%, %user%, %password%)
	tempDb: PDO('sqlite::memory:')
	articles: Model\ArticleRepository  # 例外を投げます。mainDb と tempDb の両方が当てはまります

ひとつの解は、オートワイヤリングを迂回してサービス名を明示的に指定すること(たとえば articles: Model\ArticleRepository(@mainDb))です。とはいえ、いずれかのサービスのオートワイヤリングを無効にするか、ひとつのサービスをほかより優先するほうが便利です。

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

autowired: false オプションを使うと、サービスのオートワイヤリングを無効にできます。

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

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

	articles: Model\ArticleRepository  # なのでコンストラクタには mainDb が渡されます

articles サービスは、コンストラクタに使える PDO サービスが 2 つ(mainDbtempDb)あるという例外を投げません。mainDb サービスだけを考慮するからです。

オートワイヤリングは、di › excluded設定オプションを使って型ごとにまとめて無効にすることもできます。ここには、決してオートワイヤリングされるべきでない型(とその子孫)を並べます。

Nette のオートワイヤリングの設定は Symfony とは違います。Symfony では autowire: false は、そのサービスのコンストラクタの引数にオートワイヤリングを使わない、という意味です。Nette では、オートワイヤリングはコンストラクタの引数と、コンテナ経由で呼ばれるほかのメソッド(セッターインジェクションなど)に当てはまります。autowired: false オプションは、そのサービスのインスタンスがほかのサービスの依存関係として自動的に渡されるのをコンテナに禁じます。

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

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

services:
	mainDb:
		create: PDO(%dsn%, %user%, %password%)
		autowired: PDO    # 優先されるようになります

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

	articles: Model\ArticleRepository

articles サービスは、当てはまる PDO サービスが複数(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()を使って設定でサービスの配列を直接渡せます。

スカラーの引数

オートワイヤリングが働くのはオブジェクトとオブジェクトの配列だけです。スカラーの引数(文字列、数値、真偽値など)は設定で指定しなければなりません。別の方法として、そのスカラー値(や複数の値)を包む設定オブジェクトを作ることもできます。そのオブジェクトはオートワイヤリングで渡せます。

class MySettings
{
	public function __construct(
		// readonly は PHP 8.1 以降で使えます
		public readonly bool $value,
	)
	{}
}

設定に足してサービスとして登録します。

services:
	- MySettings('any value')

これでほかのクラスがオートワイヤリングで要求できます。

省略可能な依存関係

コンストラクタやメソッドのパラメータに既定値があり、求める型のサービスがコンテナにない場合、オートワイヤリングは例外を投げず、その引数を単に飛ばすので既定値が使われます。省略可能な依存関係はこうして宣言します。

class Foo
{
	public function __construct(
		private ?Logger $logger = null,
	) {}
}

これに対し、既定値のないパラメータでは、サービスがないと常に例外になります。

オートワイヤリングの絞り込み

個々のサービスについて、オートワイヤリングを特定のクラスやインターフェースに絞り込めます。

ふつうオートワイヤリングは、そのサービスが型として当てはまるすべてのメソッドのパラメータにサービスを渡します。絞り込みとは、サービスが渡されるためにメソッドのパラメータの型が満たすべき条件を設けることです。

例を見てみましょう。

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: child, parent という例外を投げます。parentchild の両方がそのコンストラクタに当てはまり、オートワイヤリングがどちらを選ぶか決められないからです。

そこで child サービスについて、オートワイヤリングを ChildClass 型に絞り込めます。

services:
	parent: ParentClass
	child:
		create: ChildClass
		autowired: ChildClass   # 'autowired: self' とも書けます

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

これで parentDep サービスのコンストラクタには parent サービスが渡されます。当てはまるオブジェクトがそれだけになったからです。child サービスはもうそこへオートワイヤリングされません。たしかに child サービスは今も ParentClass 型ですが、絞り込みの条件 autowired: ChildClass により、明示的に ChildClass(またはその派生型)と型付けされたパラメータにしか渡されません。ParentDependentParentClass を求めるので、child サービスはそこでのオートワイヤリングの候補から外れます。

child サービスの autowired: ChildClassautowired: self とも書けます。self は現在のサービスのクラスを表すプレースホルダーだからです。

autowired キーには、複数のクラスやインターフェースを配列として指定することもできます。

autowired: [ParentClass, 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: ParentClassParentClass に制限すると、オートワイヤリングは再び 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 サービスをコンストラクタに渡します
バージョン: 3.x