Autowiring
Autowiring je skvělá vlastnost, která umí automaticky předávat do konstruktoru a dalších metod požadované služby, takže je nemusíme vůbec psát. Ušetří vám spoustu času.
Díky tomu můžeme vynechat naprostou většinu argumentů při psaní definic služeb. Místo:
services:
articles: Model\ArticleRepository(@database, @cache.storage)
Stačí napsat:
services:
articles: Model\ArticleRepository
Autowiring se řídí podle typů, takže aby fungoval, musí být třída ArticleRepository
definována
asi takto:
namespace Model;
class ArticleRepository
{
public function __construct(\PDO $db, \Nette\Caching\Storage $storage)
{}
}
Aby bylo možné použit autowiring, musí pro každý typ být v kontejneru právě jedna služba. Pokud by jich bylo víc, autowiring by nevěděl, kterou z nich předat a vyhodil by výjimku:
services:
mainDb: PDO(%dsn%, %user%, %password%)
tempDb: PDO('sqlite::memory:')
articles: Model\ArticleRepository # VYHODÍ VÝJIMKU, vyhovuje mainDb i tempDb
Řešením by bylo buď autowiring obejít a explicitně uvést název služby (tj
articles: Model\ArticleRepository(@mainDb)
). Šikovnější ale je autowirování jedné ze služeb vypnout, nebo první službu upřednostnit.
Vypnutí autowiringu
Autowirování služby můžeme vypnout pomocí volby autowired: no
:
services:
mainDb: PDO(%dsn%, %user%, %password%)
tempDb:
create: PDO('sqlite::memory:')
autowired: false # služba tempDb je vyřazena z autowiringu
articles: Model\ArticleRepository # tudíž předá do kontruktoru mainDb
Služba articles
nevyhodí výjimku, že existují dvě vyhovující služby typu PDO
(tj.
mainDb
a tempDb
), které lze do konstruktoru předat, protože vidí jen službu
mainDb
.
Konfigurace autowiringu v Nette funguje odlišně než v Symfony, kde volba autowire: false
říká, že se nemá autowiring používat pro argumenty konstruktoru dané služby. V Nette se autowiring používá vždy, ať
už pro argumenty konstruktoru, nebo kterékoliv jiné metody. Volba autowired: false
říká, že instance dané
služba nemá být pomocí autowiringu nikam předávána.
Preference autowiringu
Pokud máme více služeb stejného typu a u jedné z nich uvedeme volbu autowired
, stává se tato služba
preferovanou:
services:
mainDb:
create: PDO(%dsn%, %user%, %password%)
autowired: PDO # stává se preferovanou
tempDb:
create: PDO('sqlite::memory:')
articles: Model\ArticleRepository
Služba articles
nevyhodí výjimku, že existují dvě vyhovující služby typu PDO
(tj.
mainDb
a tempDb
), ale použije preferovanou službu, tedy mainDb
.
Pole služeb
Autowiring umí předávat i pole služeb určitého typu. Protože v PHP nelze nativně zapsat typ položek pole, je třeba
kromě typu array
doplnit i phpDoc komentář s typem položky ve tvaru ClassName[]
:
namespace Model;
class ShipManager
{
/**
* @param Shipper[] $shippers
*/
public function __construct(array $shippers)
{}
}
DI kontejner pak automaticky předá pole služeb odpovídajících danému typu. Vynechá služby, které mají vypnutý autowiring.
Typ v komentáři může být také ve tvaru array<int, Class>
nebo list<Class>
. Pokud
nemůžete ovlivnit podobu phpDoc komentáře, můžete předat pole služeb přímo v konfiguraci pomocí typed()
.
Skalární argumenty
Autowiring umí dosazovat pouze objekty a pole objektů. Skalární argumenty (např. řetězce, čísla, booleany) zapíšeme v konfiguraci. Alternativnou je vytvořit settings-objekt, který skalární hodnotu (nebo více hodnot) zapouzdří do podoby objektu, a ten pak lze opět předávat pomocí autowiringu.
class MySettings
{
public function __construct(
// readonly je možné použít od PHP 8.1
public readonly bool $value,
)
{}
}
Vytvoříte z něj službu přidáním do konfigurace:
services:
- MySettings('any value')
Všechny třídy si jej poté vyžádají pomocí autowiringu.
Zúžení autowiringu
Jednotlivým službám lze autowiring zúžit jen na určité třídy nebo rozhraní.
Normálně autowiring službu předá do každého parametru metody, jehož typu služba odpovídá. Zúžení znamená, že stanovíme podmínky, kterým musí typy uvedené u parametrů metod vyhovovat, aby jim byla služba předaná.
Ukážeme si to na příkladu:
class ParentClass
{}
class ChildClass extends ParentClass
{}
class ParentDependent
{
function __construct(ParentClass $obj)
{}
}
class ChildDependent
{
function __construct(ChildClass $obj)
{}
}
Pokud bychom je všechny zaregistrovali jako služby, tak by autowiring selhal:
services:
parent: ParentClass
child: ChildClass
parentDep: ParentDependent # VYHODÍ VÝJIMKU, vyhovují služby parent i child
childDep: ChildDependent # autowiring předá do konstruktoru službu child
Služba parentDep
vyhodí výjimku Multiple services of type ParentClass found: parent, child
,
protože do jejího kontruktoru pasují obě služby parent
i child
, a autowiring nemůže rozhodnout,
kterou z nich zvolit.
U služby child
můžeme proto zúžit její autowirování na typ ChildClass
:
services:
parent: ParentClass
child:
create: ChildClass
autowired: ChildClass # lze napsat i 'autowired: self'
parentDep: ParentDependent # autowiring předá do konstruktoru službu parent
childDep: ChildDependent # autowiring předá do konstruktoru službu child
Nyní se do kontruktoru služby parentDep
předá služba parent
, protože teď je to jediný
vyhovující objekt. Službu child
už tam autowiring nepředá. Ano, služba child
je stále typu
ParentClass
, ale už neplatí zužující podmínka daná pro typ parametru, tj. neplatí, že
ParentClass
je nadtyp ChildClass
.
U služby child
by bylo možné autowired: ChildClass
zapsat také jako
autowired: self
, jelikož self
je zástupné označení pro třídu aktuální služby.
V klíči autowired
je možné uvést i několik tříd nebo interfaců jako pole:
autowired: [BarClass, FooInterface]
Zkusme příklad doplnit ještě o rozhraní:
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)
{}
}
Když službu child
nijak neomezíme, bude pasovat do konstruktorů všech tříd FooDependent
,
BarDependent
, ParentDependent
i ChildDependent
a autowiring ji tam předá.
Pokud její autowiring ale zúžíme na ChildClass
pomocí autowired: ChildClass
(nebo
self
), předá ji autowiring pouze do konstruktoru ChildDependent
, protože vyžaduje argument typu
ChildClass
a platí, že ChildClass
je typu ChildClass
. Žádný další typ
uvedený u dalších parametrů není nadtypem ChildClass
, takže se služba nepředá.
Pokud jej omezíme na ParentClass
pomocí autowired: ParentClass
, předá ji autowiring opět do
konstruktoru ChildDependent
(protože vyžadovaný ChildClass
je nadtyp ParentClass
a nově
i do konstruktoru ParentDependent
, protože vyžadovaný typ ParentClass
je taktéž vyhovující.
Pokud jej omezíme na FooInterface
, bude stále autowirovaná do ParentDependent
(vyžadovaný
ParentClass
je nadtyp FooInterface
) a ChildDependent
, ale navíc i do konstruktoru
FooDependent
, nikoliv však do BarDependent
, neboť BarInterface
není nadtyp
FooInterface
.
services:
child:
create: ChildClass
autowired: FooInterface
fooDep: FooDependent # autowiring předá do konstruktoru child
barDep: BarDependent # VYHODÍ VÝJIMKU, žádná služba nevyhovuje
parentDep: ParentDependent # autowiring předá do konstruktoru child
childDep: ChildDependent # autowiring předá do konstruktoru child