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つ(mainDb
と
tempDb
)存在するという例外をスローしません。なぜなら、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つ(mainDb
と
tempDb
)存在するという例外をスローしませんが、優先されるサービス、つまり
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
という例外をスローします。なぜなら、そのコンストラクタには parent
と child
の両方のサービスが適合し、オートワイヤリングはどちらを選択すべきか決定できないからです。
したがって、child
サービスのオートワイヤリングを ChildClass
型に絞り込むことができます:
services:
parent: ParentClass
child:
create: ChildClass
autowired: ChildClass # 'autowired: self' と書くこともできます
parentDep: ParentDependent # オートワイヤリングは parent サービスをコンストラクタに渡します
childDep: ChildDependent # オートワイヤリングは child サービスをコンストラクタに渡します
これで、parentDep
サービスのコンストラクタには parent
サービスが渡されます。なぜなら、これが現在唯一適合するオブジェクトだからです。child
サービスはもはやオートワイヤリングによって渡されません。はい、child
サービスは依然として ParentClass
型ですが、パラメータの型に指定された絞り込み条件はもはや満たされません。つまり、ParentClass
が ChildClass
のスーパータイプであるという条件は満たされません。
child
サービスでは、autowired: ChildClass
は autowired: 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
サービスを制限しない場合、FooDependent
、BarDependent
、ParentDependent
、ChildDependent
のすべてのクラスのコンストラクタに適合し、オートワイヤリングはそれを渡します。
しかし、そのオートワイヤリングを autowired: ChildClass
(または self
)
を使用して ChildClass
に絞り込むと、オートワイヤリングはそれを ChildDependent
のコンストラクタにのみ渡します。なぜなら、要求される引数の型は ChildClass
であり、ChildClass
は ChildClass
の型であるという条件が満たされるからです。他のパラメータで指定された他の型は
ChildClass
のスーパータイプではないため、サービスは渡されません。
autowired: ParentClass
を使用して ParentClass
に制限すると、オートワイヤリングはそれを再び ChildDependent
のコンストラクタに渡します(要求される ChildClass
は ParentClass
のスーパータイプであるため)。そして、新たに ParentDependent
のコンストラクタにも渡します。なぜなら、要求される型 ParentClass
も適合するからです。
FooInterface
に制限すると、依然として ParentDependent
(要求される
ParentClass
は FooInterface
のスーパータイプ)と ChildDependent
にオートワイヤリングされますが、さらに FooDependent
のコンストラクタにも渡されます。しかし、BarDependent
には渡されません。なぜなら
BarInterface
は FooInterface
のスーパータイプではないからです。
services:
child:
create: ChildClass
autowired: FooInterface
fooDep: FooDependent # オートワイヤリングは child をコンストラクタに渡します
barDep: BarDependent # 例外をスローします。適合するサービスがありません
parentDep: ParentDependent # オートワイヤリングは child をコンストラクタに渡します
childDep: ChildDependent # オートワイヤリングは child をコンストラクタに渡します