Autowiring
Autowiring je odlična lastnost, ki zna samodejno posredovati v konstruktor in druge metode zahtevane storitve, tako da jih sploh ni treba pisati. Prihrani vam veliko časa.
Zahvaljujoč temu lahko izpustimo večino argumentov pri pisanju definicij storitev. Namesto:
services:
articles: Model\ArticleRepository(@database, @cache.storage)
Zadostuje napisati:
services:
articles: Model\ArticleRepository
Autowiring se ravna po tipih, zato mora biti za delovanje razred ArticleRepository
definiran približno
takole:
namespace Model;
class ArticleRepository
{
public function __construct(\PDO $db, \Nette\Caching\Storage $storage)
{}
}
Da bi lahko uporabili autowiring, mora za vsak tip v vsebniku obstajati točno ena storitev. Če bi jih bilo več, autowiring ne bi vedel, katero naj posreduje, in bi vrgel izjemo:
services:
mainDb: PDO(%dsn%, %user%, %password%)
tempDb: PDO('sqlite::memory:')
articles: Model\ArticleRepository # VRŽE IZJEMO, ustrezata mainDb in tempDb
Rešitev bi bila bodisi obiti autowiring in eksplicitno navesti ime storitve (tj.
articles: Model\ArticleRepository(@mainDb)
). Pametneje pa je autowiring ene od storitev izklopiti ali prvo storitev dati prednost.
Izklop autowiringa
Autowiring storitve lahko izklopimo z uporabo možnosti autowired: no
:
services:
mainDb: PDO(%dsn%, %user%, %password%)
tempDb:
create: PDO('sqlite::memory:')
autowired: false # storitev tempDb je izključena iz autowiringa
articles: Model\ArticleRepository # zato posreduje v konstruktor mainDb
Storitev articles
ne bo vrgla izjeme, da obstajata dve ustrezni storitvi tipa PDO
(tj.
mainDb
in tempDb
), ki ju je mogoče posredovati v konstruktor, ker vidi samo storitev
mainDb
.
Konfiguracija autowiringa v Nette deluje drugače kot v Symfonyju, kjer možnost autowire: false
pove, da se autowiring ne sme uporabljati za argumente konstruktorja dane storitve. V Nette se autowiring uporablja vedno, bodisi
za argumente konstruktorja ali katere koli druge metode. Možnost autowired: false
pove, da instanca dane storitve ne
sme biti nikamor posredovana z uporabo autowiringa.
Prednost autowiringa
Če imamo več storitev istega tipa in pri eni od njih navedemo možnost autowired
, postane ta storitev
prednostna:
services:
mainDb:
create: PDO(%dsn%, %user%, %password%)
autowired: PDO # postane prednostna
tempDb:
create: PDO('sqlite::memory:')
articles: Model\ArticleRepository
Storitev articles
ne bo vrgla izjeme, da obstajata dve ustrezni storitvi tipa PDO
(tj.
mainDb
in tempDb
), ampak bo uporabila prednostno storitev, torej mainDb
.
Polje storitev
Autowiring zna posredovati tudi polja storitev določenega tipa. Ker v PHP ni mogoče nativno zapisati tipa elementov polja,
je treba poleg tipa array
dopolniti tudi phpDoc komentar s tipom elementa v obliki ClassName[]
:
namespace Model;
class ShipManager
{
/**
* @param Shipper[] $shippers
*/
public function __construct(array $shippers)
{}
}
DI vsebnik nato samodejno posreduje polje storitev, ki ustrezajo danemu tipu. Izpusti storitve, ki imajo izklopljen autowiring.
Tip v komentarju je lahko tudi v obliki array<int, Class>
ali list<Class>
. Če ne
morete vplivati na obliko phpDoc komentarja, lahko polje storitev posredujete neposredno v konfiguraciji z uporabo typed()
.
Skalarni argumenti
Autowiring zna vstavljati samo objekte in polja objektov. Skalarne argumente (npr. nize, števila, booleane) zapišemo v konfiguraciji. Alternativa je ustvariti settings-objekt, ki skalarno vrednost (ali več vrednosti) zapakira v obliko objekta, ki ga nato lahko spet posredujemo z uporabo autowiringa.
class MySettings
{
public function __construct(
// readonly je mogoče uporabiti od PHP 8.1
public readonly bool $value,
)
{}
}
Iz njega ustvarite storitev z dodajanjem v konfiguracijo:
services:
- MySettings('any value')
Vsi razredi jo nato zahtevajo z uporabo autowiringa.
Omejitev autowiringa
Posameznim storitvam lahko autowiring omejimo samo na določene razrede ali vmesnike.
Običajno autowiring storitev posreduje v vsak parameter metode, katerega tipu storitev ustreza. Omejitev pomeni, da določimo pogoje, ki jim morajo ustrezati tipi, navedeni pri parametrih metod, da jim bo storitev posredovana.
Poglejmo si to na primeru:
class ParentClass
{}
class ChildClass extends ParentClass
{}
class ParentDependent
{
function __construct(ParentClass $obj)
{}
}
class ChildDependent
{
function __construct(ChildClass $obj)
{}
}
Če bi jih vse registrirali kot storitve, bi autowiring spodletel:
services:
parent: ParentClass
child: ChildClass
parentDep: ParentDependent # VRŽE IZJEMO, ustrezata storitvi parent in child
childDep: ChildDependent # autowiring posreduje v konstruktor storitev child
Storitev parentDep
vrže izjemo Multiple services of type ParentClass found: parent, child
, ker
v njen konstruktor ustrezata obe storitvi parent
in child
, in autowiring ne more odločiti, katero naj
izbere.
Pri storitvi child
lahko zato omejimo njen autowiring na tip ChildClass
:
services:
parent: ParentClass
child:
create: ChildClass
autowired: ChildClass # lahko napišemo tudi 'autowired: self'
parentDep: ParentDependent # autowiring posreduje v konstruktor storitev parent
childDep: ChildDependent # autowiring posreduje v konstruktor storitev child
Zdaj se v konstruktor storitve parentDep
posreduje storitev parent
, ker je zdaj to edini ustrezen
objekt. Storitve child
autowiring tja ne posreduje več. Da, storitev child
je še vedno tipa
ParentClass
, vendar ne velja več omejitveni pogoj, dan za tip parametra, tj. ne velja, da je
ParentClass
nadtip ChildClass
.
Pri storitvi child
bi bilo mogoče autowired: ChildClass
zapisati tudi kot
autowired: self
, ker je self
nadomestno ime za razred trenutne storitve.
V ključu autowired
je mogoče navesti tudi več razredov ali vmesnikov kot polje:
autowired: [BarClass, FooInterface]
Poskusimo primer dopolniti še z vmesniki:
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)
{}
}
Če storitve child
nikakor ne omejimo, bo ustrezala konstruktorjem vseh razredov FooDependent
,
BarDependent
, ParentDependent
in ChildDependent
, in autowiring jo bo tja posredoval.
Če pa njen autowiring omejimo na ChildClass
z autowired: ChildClass
(ali self
), jo bo
autowiring posredoval samo v konstruktor ChildDependent
, ker zahteva argument tipa ChildClass
in velja,
da je ChildClass
tipa ChildClass
. Noben drug tip, naveden pri drugih parametrih, ni nadtip
ChildClass
, zato se storitev ne posreduje.
Če jo omejimo na ParentClass
z autowired: ParentClass
, jo bo autowiring spet posredoval
v konstruktor ChildDependent
(ker je zahtevani ChildClass
nadtip ParentClass
) in na novo
tudi v konstruktor ParentDependent
, ker je zahtevani tip ParentClass
prav tako ustrezen.
Če jo omejimo na FooInterface
, bo še vedno avtomatsko povezana v ParentDependent
(zahtevani
ParentClass
je nadtip FooInterface
) in ChildDependent
, poleg tega pa tudi v konstruktor
FooDependent
, vendar ne v BarDependent
, ker BarInterface
ni nadtip
FooInterface
.
services:
child:
create: ChildClass
autowired: FooInterface
fooDep: FooDependent # autowiring posreduje v konstruktor child
barDep: BarDependent # VRŽE IZJEMO, nobena storitev ne ustreza
parentDep: ParentDependent # autowiring posreduje v konstruktor child
childDep: ChildDependent # autowiring posreduje v konstruktor child