Avtomatsko napeljevanje

Autowiring je odlična funkcija, ki lahko samodejno posreduje storitve konstruktorju in drugim metodam, tako da nam jih sploh ni treba pisati. S tem prihranimo veliko časa.

Tako lahko pri pisanju definicij storitev preskočimo veliko večino argumentov. Namesto:

services:
	articles: Model\ArticleRepository(@database, @cache.storage)

napišite:

services:
	articles: Model\ArticleRepository

Zato je treba razred ArticleRepository opredeliti na naslednji način:

namespace Model;

class ArticleRepository
{
	public function __construct(\PDO $db, \Nette\Caching\Storage $storage)
	{}
}

Za uporabo samodejnega napeljevanja mora biti za vsak tip v vsebniku poudarjena samo ena storitev. Če bi jih bilo več, autowiring ne bi vedel, katero naj posreduje, in bi zavrgel izjemo:

services:
	mainDb: PDO(%dsn%, %user%, %password%)
	tempDb: PDO('sqlite::memory:')
	articles: Model\ArticleRepository  # IZJAVA, tako mainDb kot tempDb se ujemata

Rešitev bi bila, da se izognemo samodejnemu usmerjanju in izrecno navedemo ime storitve (tj. articles: Model\ArticleRepository(@mainDb)). Vendar pa je bolj priročno, da onemogočimo samodejno posredovanje ene storitve ali raje prve storitve.

Onemogočeno samodejno napeljevanje

Samodejno ožičenje storitev lahko onemogočite z uporabo možnosti autowired: no:

services:
	mainDb: PDO(%dsn%, %user%, %password%)

	tempDb:
		create: PDO('sqlite::memory:')
		autowired: false                 # odstrani tempDb iz samodejnega napeljevanja

	articles: Model\ArticleRepository    # zato konstruktorju posreduje mainDb

Storitev articles ne vrže izjeme, da obstajata dve ustrezni storitvi tipa PDO (tj. mainDb in tempDb), ki ju je mogoče posredovati konstruktorju, saj vidi samo storitev mainDb.

Konfiguracija samodejnega navajanja v Nette deluje drugače kot v Symfonyju, kjer možnost autowire: false pravi, da se samodejno navajanje ne sme uporabljati za argumente konstruktorja storitve. V Nette se autowiring vedno uporablja, ne glede na to, ali gre za argumente konstruktorja ali katere koli druge metode. Možnost autowired: false pravi, da se primerek storitve ne sme nikamor posredovati z uporabo samodejnega vodenja.

Prednostno samodejno vodenje

Če imamo več storitev iste vrste in ima ena od njih možnost autowired, ta storitev postane prednostna:

services:
	mainDb:
		create: PDO(%dsn%, %user%, %password%)
		autowired: PDO    # daje prednost

	tempDb:
		create: PDO('sqlite::memory:')

	articles: Model\ArticleRepository

Storitev articles ne vrže izjeme, da obstajata dve ustrezni storitvi PDO (tj. mainDb in tempDb), temveč uporabi prednostno storitev, tj. mainDb.

Zbirka storitev

Samodejno napeljevanje lahko posreduje tudi niz storitev določene vrste. Ker PHP ne more nativno zapisati vrste elementov polja, je treba poleg vrste array dodati še komentar phpDoc z vrsto elementa, kot je ClassName[]:

namespace Model;

class ShipManager
{
	/**
	 * @param Shipper[] $shippers
	 */
	public function __construct(array $shippers)
	{}
}

Vsebnik DI nato samodejno posreduje polje storitev, ki ustrezajo dani vrsti. Izpusti storitve, ki imajo izklopljeno samodejno napeljavo.

Vrsta v komentarju je lahko tudi v obliki array<int, Class> ali list<Class>. Če ne morete nadzorovati oblike komentarja phpDoc, lahko posredujete polje storitev neposredno v konfiguraciji z uporabo typed().

Skalarni argumenti

Samodejno napeljevanje lahko posreduje samo predmete in polja predmetov. Skalarne argumente (npr. nize, številke, logične vrednosti) zapišite v konfiguracijo. Druga možnost je, da ustvarite objekt settings-object, ki skalarno vrednost (ali več vrednosti) uokviri kot objekt, ki ga lahko nato ponovno posredujete z uporabo autowiringa.

class MySettings
{
	public function __construct(
		// readonly se lahko uporablja od PHP 8.1
		public readonly bool $value,
	)
	{}
}

Storitev ustvarite tako, da jo dodate v konfiguracijo:

services:
	- MySettings('any value')

Vsi razredi jo bodo nato zahtevali prek samodejnega povezovanja.

Oženje samodejnega napeljevanja

Pri posameznih storitvah lahko samodejno napeljavo zožite na določene razrede ali vmesnike.

Običajno samodejno usmerjanje posreduje storitev vsakemu parametru metode, katere tipu ustreza storitev. Oženje pomeni, da določimo pogoje, ki jih morajo tipi, določeni za parametre metod, izpolnjevati, da se jim posreduje storitev.

Poglejmo primer:

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 bila samodejna povezava neuspešna:

services:
	parent: ParentClass
	child: ChildClass
	parentDep: ParentDependent  # IZJEMA, ujemata se tako starš kot otrok
	childDep: ChildDependent    # konstruktorju posreduje storitev 'child'.

Storitev parentDep vrže izjemo Multiple services of type ParentClass found: parent, child, ker sta tako parent kot child vključena v njen konstruktor in se samodejna vključitev ne more odločiti, katero bo izbrala.

Pri storitvi child lahko torej njeno samodejno vodenje zožimo na ChildClass:

services:
	parent: ParentClass
	child:
		create: ChildClass
		autowired: ChildClass   # alternative: 'autowired: self'

	parentDep: ParentDependent  # IZJAVA, 'otrok' ne more biti samodejno ožičen
	childDep: ChildDependent    # posreduje storitev "child" konstruktorju

Storitev parentDep je zdaj posredovana konstruktorju storitve parentDep, saj je zdaj edini ustrezni objekt. Storitev child ni več posredovana s samodejnim vodenjem. Da, storitev child je še vedno tipa ParentClass, vendar ne velja več pogoj za zožitev, ki je podan za tip parametra, tj. ne drži več, da je ParentClass nadtip ChildClass.

V primeru child bi lahko autowired: ChildClass zapisali kot autowired: self, saj self pomeni trenutni tip storitve.

Ključ autowired lahko vključuje več razredov in vmesnikov kot polje:

autowired: [BarClass, FooInterface]

Poskusimo v primer dodati vmesnike:

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 ne omejimo, se bo prilegala konstruktorjem vseh razredov FooDependent, BarDependent, ParentDependent in ChildDependent in jo bo tja posredoval autowiring.

Če pa njegovo autowiring omejimo na ChildClass z uporabo autowired: ChildClass (ali self), ga autowiring prenese le v konstruktor ChildDependent, saj zahteva argument tipa ChildClass, ChildClass pa je tipa ChildClass. Noben tip, ki je naveden za druge parametre, ni nadmnožica ChildClass, zato se storitev ne posreduje.

Če jo omejimo na ParentClass z uporabo autowired: ParentClass, jo bo samodejno posredovanje spet posredovalo konstruktorju ChildDependent (ker je zahtevani tip ChildClass nadmnožica ParentClass) in tudi konstruktorju ParentDependent, ker je zahtevani tip ParentClass prav tako ustrezen.

Če ga omejimo na FooInterface, se bo še vedno samodejno usmeril na ParentDependent (zahtevani tip ParentClass je nadtip FooInterface) in ChildDependent, dodatno pa še na konstruktor FooDependent, ne pa na BarDependent, saj BarInterface ni nadtip FooInterface.

services:
	child:
		create: ChildClass
		autowired: FooInterface

	fooDep: FooDependent        # posreduje otroka storitve konstruktorju
	barDep: BarDependent        # IZJAVA, nobena storitev ne bi bila posredovana
	parentDep: ParentDependent  # posreduje otroka storitve konstruktorju
	childDep: ChildDependent    # posreduje otroka storitve konstruktorju
različica: 3.x