Cablare automată
Autowiring este o caracteristică grozavă care poate trece automat servicii către constructor și alte metode, astfel încât nu trebuie să le scriem deloc. Aceasta vă economisește mult timp.
Acest lucru ne permite să sărim peste marea majoritate a argumentelor atunci când scriem definițiile serviciilor. În loc de:
services:
articles: Model\ArticleRepository(@database, @cache.storage)
Scrieți doar:
services:
articles: Model\ArticleRepository
Cablarea automată este determinată de tipuri, astfel încât clasa ArticleRepository
trebuie să fie definită
după cum urmează:
namespace Model;
class ArticleRepository
{
public function __construct(\PDO $db, \Nette\Caching\Storage $storage)
{}
}
Pentru a utiliza autowiring, trebuie să existe un singur serviciu pentru fiecare tip din container. Dacă ar exista mai multe, autowiring nu ar ști pe care să îl treacă și ar arunca o excepție:
services:
mainDb: PDO(%dsn%, %user%, %password%)
tempDb: PDO('sqlite::memory:')
articles: Model\ArticleRepository # THROWS EXCEPTION, atât mainDb cât și tempDb se potrivesc
Soluția ar fi fie să se ocolească autowiring și să se precizeze explicit numele serviciului (de exemplu,
articles: Model\ArticleRepository(@mainDb)
). Cu toate acestea, este mai convenabil să se dezactiveze autowiring-ul pentru un singur serviciu, sau să se prefere primul serviciu.
Autowiring dezactivat
Puteți dezactiva cablarea automată a serviciilor utilizând opțiunea autowired: no
:
services:
mainDb: PDO(%dsn%, %user%, %password%)
tempDb:
create: PDO('sqlite::memory:')
autowired: false # elimină tempDb din autowiring
articles: Model\ArticleRepository # prin urmare, trece mainDb la constructor
Serviciul articles
nu aruncă excepția că există două servicii corespunzătoare de tip PDO
(de
exemplu, mainDb
și tempDb
) care pot fi transmise constructorului, deoarece acesta vede doar serviciul
mainDb
.
Configurarea autowiring-ului în Nette funcționează diferit față de Symfony, unde opțiunea
autowire: false
spune că autowiring-ul nu trebuie folosit pentru argumentele constructorului serviciului. În Nette,
autowiring este întotdeauna folosit, fie pentru argumentele constructorului, fie pentru orice altă metodă. Opțiunea
autowired: false
spune că instanța serviciului nu trebuie să fie transmisă nicăieri folosind autowiring.
Autowiring preferat
În cazul în care avem mai multe servicii de același tip și unul dintre ele are opțiunea autowired
, acest
serviciu devine cel preferat:
services:
mainDb:
create: PDO(%dsn%, %user%, %password%)
autowired: PDO # îl face preferat
tempDb:
create: PDO('sqlite::memory:')
articles: Model\ArticleRepository
Serviciul articles
nu aruncă excepția că există două servicii PDO
corespunzătoare (adică
mainDb
și tempDb
), ci utilizează serviciul preferat, adică mainDb
.
Colecția de servicii
Autowiring poate transmite, de asemenea, o serie de servicii de un anumit tip. Deoarece PHP nu poate nota în mod nativ tipul
de elemente ale tabloului, pe lângă tipul array
, trebuie adăugat un comentariu phpDoc cu tipul de element, cum ar
fi ClassName[]
:
namespace Model;
class ShipManager
{
/**
* @param Shipper[] $shippers
*/
public function __construct(array $shippers)
{}
}
Recipientul DI transmite apoi automat un array de servicii care corespund tipului dat. Acesta va omite serviciile care au cablarea automată dezactivată.
Tipul din comentariu poate fi, de asemenea, de forma array<int, Class>
sau list<Class>
.
Dacă nu puteți controla forma comentariului phpDoc, puteți trece o matrice de servicii direct în configurație folosind typed()
.
Argumente scalare
Autowiring poate transmite numai obiecte și array-uri de obiecte. Argumentele scalare (de exemplu, șiruri de caractere, numere, booleeni) se scriu în configurare. O alternativă este crearea unui obiect de configurare care încapsulează o valoare scalară (sau mai multe valori) sub forma unui obiect, care poate fi apoi transmis din nou cu ajutorul autowiring-ului.
class MySettings
{
public function __construct(
// readonly poate fi utilizat începând cu PHP 8.1
public readonly bool $value,
)
{}
}
Creați un serviciu adăugându-l la configurație:
services:
- MySettings('any value')
Toate clasele îl vor solicita apoi prin cablare automată.
Restrângerea cablării automate
Pentru servicii individuale, autocablarea poate fi restrânsă la anumite clase sau interfețe.
În mod normal, autowiring-ul trece serviciul la fiecare parametru al metodei căruia îi corespunde tipul serviciului. Restrângerea înseamnă că se specifică condițiile pe care tipurile specificate pentru parametrii metodei trebuie să le îndeplinească pentru ca serviciul să le fie transmis.
Să luăm un exemplu:
class ParentClass
{}
class ChildClass extends ParentClass
{}
class ParentDependent
{
function __construct(ParentClass $obj)
{}
}
class ChildDependent
{
function __construct(ChildClass $obj)
{}
}
Dacă le-am înregistrat pe toate ca servicii, cablarea automată ar eșua:
services:
parent: ParentClass
child: ChildClass
parentDep: ParentDependent # EXCEPȚIE DE ÎNTÂRZIERE, atât părintele cât și copilul se potrivesc
childDep: ChildDependent # trece serviciul "child" în constructor
Serviciul parentDep
aruncă excepția Multiple services of type ParentClass found: parent, child
,
deoarece atât parent
, cât și child
se potrivesc în constructorul său, iar autowiring nu poate lua
o decizie cu privire la care să o aleagă.
Prin urmare, pentru serviciul child
, putem restrânge cablarea automată la ChildClass
:
services:
parent: ParentClass
child:
create: ChildClass
autowired: ChildClass # alternative: 'autowired: self'
parentDep: ParentDependent # Se produce o EXCEPȚIE, 'child' nu poate fi autowired.
childDep: ChildDependent # transmite serviciul "child" la constructor
Serviciul parentDep
este transmis acum constructorului serviciului parentDep
, deoarece este acum
singurul obiect corespunzător. Serviciul child
nu mai este transmis prin autocablare. Da, serviciul
child
este în continuare de tipul ParentClass
, dar condiția de restrângere dată pentru tipul de
parametru nu se mai aplică, adică nu mai este adevărat că ParentClass
este un supratip al
ChildClass
.
În cazul child
, autowired: ChildClass
ar putea fi scris ca autowired: self
, deoarece
self
înseamnă tipul de serviciu curent.
Cheia autowired
poate include mai multe clase și interfețe ca matrice:
autowired: [BarClass, FooInterface]
Să încercăm să adăugăm interfețe la exemplu:
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)
{}
}
Atunci când nu limităm serviciul child
, acesta se va potrivi în constructorii tuturor claselor
FooDependent
, BarDependent
, ParentDependent
și ChildDependent
și autowiring
îl va trece acolo.
Cu toate acestea, dacă limităm autowiring-ul la ChildClass
folosind autowired: ChildClass
(sau
self
), autowiring-ul îl trece doar la constructorul ChildDependent
, deoarece necesită un argument de
tip ChildClass
și ChildClass
este de tip ChildClass
. Niciun alt tip specificat
pentru ceilalți parametri nu este un supraansamblu al ChildClass
, astfel încât serviciul nu este transmis.
Dacă îl restricționăm la ParentClass
folosind autowired: ParentClass
, autowiring îl va trece din
nou la constructorul ChildDependent
(deoarece tipul necesar ChildClass
este un supraansamblu al
ParentClass
) și la constructorul ParentDependent
de asemenea, deoarece tipul necesar al
ParentClass
este de asemenea corespunzător.
Dacă îl restricționăm la FooInterface
, acesta va trece în continuare automat la ParentDependent
(tipul necesar ParentClass
este un supratip al FooInterface
) și la ChildDependent
, dar și
la constructorul FooDependent
, dar nu și la BarDependent
, deoarece BarInterface
nu este un
supratip al FooInterface
.
services:
child:
create: ChildClass
autowired: FooInterface
fooDep: FooDependent # transmite copilul serviciului către constructor
barDep: BarDependent # EXCEPȚIE, niciun serviciu nu ar trece
parentDep: ParentDependent # trece serviciul copil la constructor
childDep: ChildDependent # trece serviciul copil la constructor