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
versiune: 3.x