Autowiring
Autowiring este o caracteristică excelentă care poate transmite automat serviciile necesare către constructor și alte metode, astfel încât nu trebuie să le scriem deloc. Vă economisește mult timp.
Datorită acestui fapt, putem omite marea majoritate a argumentelor atunci când scriem definiții de servicii. În loc de:
services:
articles: Model\ArticleRepository(@database, @cache.storage)
Este suficient să scrieți:
services:
articles: Model\ArticleRepository
Autowiring se ghidează după tipuri, așa că pentru a funcționa, clasa ArticleRepository
trebuie definită
aproximativ astfel:
namespace Model;
class ArticleRepository
{
public function __construct(\PDO $db, \Nette\Caching\Storage $storage)
{}
}
Pentru a putea utiliza autowiring, trebuie să existe exact un serviciu pentru fiecare tip în container. Dacă ar exista mai multe, autowiring nu ar ști pe care să îl transmită și ar arunca o excepție:
services:
mainDb: PDO(%dsn%, %user%, %password%)
tempDb: PDO('sqlite::memory:')
articles: Model\ArticleRepository # ARUNCĂ EXCEPȚIE, se potrivesc atât mainDb cât și tempDb
Soluția ar fi fie să ocoliți autowiring-ul și să specificați explicit numele serviciului (adică
articles: Model\ArticleRepository(@mainDb)
). Dar este mai convenabil să dezactivați autowiring-ul pentru unul dintre servicii sau să prioritizați primul serviciu.
Dezactivarea autowiring-ului
Putem dezactiva autowiring-ul unui serviciu folosind opțiunea autowired: no
:
services:
mainDb: PDO(%dsn%, %user%, %password%)
tempDb:
create: PDO('sqlite::memory:')
autowired: false # serviciul tempDb este exclus din autowiring
articles: Model\ArticleRepository # prin urmare, transmite mainDb către constructor
Serviciul articles
nu aruncă o excepție că există două servicii potrivite de tip PDO
(adică
mainDb
și tempDb
) care pot fi transmise constructorului, deoarece vede doar serviciul
mainDb
.
Configurarea autowiring-ului în Nette funcționează diferit față de Symfony, unde opțiunea
autowire: false
specifică faptul că autowiring-ul nu trebuie utilizat pentru argumentele constructorului
serviciului respectiv. În Nette, autowiring-ul este întotdeauna utilizat, fie pentru argumentele constructorului, fie pentru
orice altă metodă. Opțiunea autowired: false
specifică faptul că instanța serviciului respectiv nu trebuie
transmisă nicăieri prin autowiring.
Preferința autowiring-ului
Dacă avem mai multe servicii de același tip și pentru unul dintre ele specificăm opțiunea autowired
, acest
serviciu devine preferat:
services:
mainDb:
create: PDO(%dsn%, %user%, %password%)
autowired: PDO # devine preferat
tempDb:
create: PDO('sqlite::memory:')
articles: Model\ArticleRepository
Serviciul articles
nu aruncă o excepție că există două servicii potrivite de tip PDO
(adică
mainDb
și tempDb
), ci folosește serviciul preferat, adică mainDb
.
Array de servicii
Autowiring poate transmite și array-uri de servicii de un anumit tip. Deoarece în PHP nu se poate scrie nativ tipul
elementelor unui array, este necesar, pe lângă tipul array
, să se adauge și un comentariu phpDoc cu tipul
elementului în formatul ClassName[]
:
namespace Model;
class ShipManager
{
/**
* @param Shipper[] $shippers
*/
public function __construct(array $shippers)
{}
}
Containerul DI transmite apoi automat un array de servicii corespunzătoare tipului respectiv. Omită serviciile care au autowiring-ul dezactivat.
Tipul din comentariu poate fi și în formatul array<int, Class>
sau list<Class>
. Dacă
nu puteți influența forma comentariului phpDoc, puteți transmite array-ul de servicii direct în configurație folosind typed()
.
Argumente scalare
Autowiring poate injecta doar obiecte și array-uri de obiecte. Argumentele scalare (de ex. șiruri, numere, booleeni) le scriem în configurație. O alternativă este crearea unui obiect de setări, care încapsulează valoarea scalară (sau mai multe valori) sub formă de obiect, care apoi poate fi transmis din nou prin autowiring.
class MySettings
{
public function __construct(
// readonly poate fi utilizat începând cu PHP 8.1
public readonly bool $value,
)
{}
}
Creați un serviciu din acesta adăugându-l în configurație:
services:
- MySettings('any value')
Toate clasele îl vor solicita apoi prin autowiring.
Restrângerea autowiring-ului
Autowiring-ul serviciilor individuale poate fi restrâns la anumite clase sau interfețe.
În mod normal, autowiring-ul transmite serviciul către fiecare parametru al metodei al cărui tip corespunde serviciului. Restrângerea înseamnă că stabilim condiții pe care tipurile specificate la parametrii metodelor trebuie să le îndeplinească pentru ca serviciul să le fie transmis.
Să ilustrăm acest lucru cu un exemplu:
class ParentClass
{}
class ChildClass extends ParentClass
{}
class ParentDependent
{
function __construct(ParentClass $obj)
{}
}
class ChildDependent
{
function __construct(ChildClass $obj)
{}
}
Dacă le-am înregistra pe toate ca servicii, autowiring-ul ar eșua:
services:
parent: ParentClass
child: ChildClass
parentDep: ParentDependent # ARUNCĂ EXCEPȚIE, se potrivesc serviciile parent și child
childDep: ChildDependent # autowiring transmite serviciul child către constructor
Serviciul parentDep
aruncă excepția Multiple services of type ParentClass found: parent, child
,
deoarece ambele servicii parent
și child
se potrivesc constructorului său, iar autowiring-ul nu poate
decide pe care să îl aleagă.
Prin urmare, pentru serviciul child
, putem restrânge autowiring-ul său la tipul ChildClass
:
services:
parent: ParentClass
child:
create: ChildClass
autowired: ChildClass # se poate scrie și 'autowired: self'
parentDep: ParentDependent # autowiring transmite serviciul parent către constructor
childDep: ChildDependent # autowiring transmite serviciul child către constructor
Acum, serviciul parent
este transmis constructorului serviciului parentDep
, deoarece acum este
singurul obiect potrivit. Autowiring-ul nu mai transmite serviciul child
acolo. Da, serviciul child
este
încă de tip ParentClass
, dar condiția de restrângere dată pentru tipul parametrului nu mai este valabilă,
adică nu este adevărat că ParentClass
este un supratip al ChildClass
.
Pentru serviciul child
, autowired: ChildClass
ar putea fi scris și ca autowired: self
,
deoarece self
este un substituent pentru clasa serviciului curent.
În cheia autowired
este posibil să se specifice și mai multe clase sau interfețe ca un array:
autowired: [BarClass, FooInterface]
Să încercăm să completăm exemplul cu interfețe:
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)
{}
}
Dacă nu restricționăm în niciun fel serviciul child
, acesta se va potrivi constructorilor tuturor claselor
FooDependent
, BarDependent
, ParentDependent
și ChildDependent
, iar
autowiring-ul îl va transmite acolo.
Dar dacă îi restrângem autowiring-ul la ChildClass
folosind autowired: ChildClass
(sau
self
), autowiring-ul îl va transmite doar constructorului ChildDependent
, deoarece necesită un
argument de tip ChildClass
și este adevărat că ChildClass
este de tip
ChildClass
. Niciun alt tip specificat la ceilalți parametri nu este un supratip al ChildClass
, deci
serviciul nu este transmis.
Dacă îl restricționăm la ParentClass
folosind autowired: ParentClass
, autowiring-ul îl va
transmite din nou constructorului ChildDependent
(deoarece ChildClass
necesar este un supratip al
ParentClass
) și, nou, și constructorului ParentDependent
, deoarece tipul necesar
ParentClass
este, de asemenea, potrivit.
Dacă îl restricționăm la FooInterface
, va fi în continuare autowired în ParentDependent
(necesarul ParentClass
este un supratip al FooInterface
) și ChildDependent
, dar în plus
și în constructorul FooDependent
, însă nu în BarDependent
, deoarece BarInterface
nu
este un supratip al FooInterface
.
services:
child:
create: ChildClass
autowired: FooInterface
fooDep: FooDependent # autowiring transmite child către constructor
barDep: BarDependent # ARUNCĂ EXCEPȚIE, niciun serviciu nu se potrivește
parentDep: ParentDependent # autowiring transmite child către constructor
childDep: ChildDependent # autowiring transmite child către constructor