Autowiring

Az autowiring egy nagyszerű funkció, amely automatikusan átadhatja a szolgáltatásokat a konstruktornak és más metódusoknak, így egyáltalán nem kell megírnunk őket. Ezzel rengeteg időt spórolhatunk meg.

Ez lehetővé teszi, hogy a szolgáltatásdefiníciók írásakor az argumentumok túlnyomó többségét kihagyjuk. Ahelyett:

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

Just write:

services:
	articles: Model\ArticleRepository

Az autowiringet a típusok vezérlik, ezért a ArticleRepository osztályt a következőképpen kell definiálni:

namespace Model;

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

Az autowiring használatához csak egy szolgáltatásnak kell lennie minden egyes típushoz a konténerben. Ha több lenne, az autowiring nem tudná, hogy melyiket kell átadni, és kivételt dobna:

services:
	mainDb: PDO(%dsn%, %user%, %password%)
	tempDb: PDO('sqlite::memory:')
	articles: Model\ArticleRepository # THROWS EXCEPTION, mind a mainDb, mind a tempDb egyezik.

A megoldás az autowiring megkerülése és a szolgáltatás nevének explicit megadása lenne (pl. articles: Model\ArticleRepository(@mainDb)). Kényelmesebb azonban az autowiring letiltása egy szolgáltatásnál, vagy inkább az első szolgáltatásnál.

Letiltott autowiring

A autowired: no opcióval letilthatja a szolgáltatás automatikus bekötését:

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

	tempDb:
		create: PDO('sqlite::memory:')
		autowired: false # eltávolítja a tempDb-t az autowiringből.

	articles: # ezért átadja a mainDb-t a konstruktornak.

A articles szolgáltatás nem dobja ki azt a kivételt, hogy a konstruktornak átadható két megfelelő PDO típusú szolgáltatás (azaz mainDb és tempDb), mert csak a mainDb szolgáltatást látja.

Az autowiring beállítása a Nette-ben másképp működik, mint a Symfony-ban, ahol a autowire: false opció azt mondja ki, hogy az autowiring nem használható a szolgáltatáskonstruktor argumentumaihoz. A Nette-ben az autowiring mindig használatos, akár a konstruktor, akár bármely más metódus argumentumaira. A autowired: false opció azt mondja ki, hogy a szolgáltatáspéldányt nem szabad sehol autowiring használatával átadni.

Előnyben részesített autowiring

Ha több azonos típusú szolgáltatásunk van, és az egyikben van a autowired opció, akkor ez a szolgáltatás lesz az előnyben részesített:

services:
	mainDb:
		create: PDO(%dsn%, %user%, %password%)
		autowired: PDO # előnyben részesíti

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

	articles: Model\ArticleRepository

A articles szolgáltatás nem dobja ki a kivételt, hogy két egyező PDO szolgáltatás van (azaz mainDb és tempDb), hanem az előnyben részesített szolgáltatást, azaz a mainDb szolgáltatást használja.

Szolgáltatások gyűjteménye

Az autowiring egy adott típusú szolgáltatások tömbjét is átadhatja. Mivel a PHP nem tudja natívan jelölni a tömbelemek típusát, a array típus mellett egy phpDoc kommentárt is hozzá kell adni az elem típusával, mint például a ClassName[]:

namespace Model;

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

A DI konténer ekkor automatikusan átadja a megadott típusnak megfelelő szolgáltatások tömbjét. Kihagyja azokat a szolgáltatásokat, amelyeknél az automatikus kapcsolás ki van kapcsolva.

A megjegyzésben szereplő típus lehet a következő formájú is array<int, Class> vagy list<Class>. Ha nem tudja ellenőrizni a phpDoc megjegyzés formáját, akkor közvetlenül a konfigurációban átadhatja a szolgáltatások tömbjét a következővel typed().

Skaláris argumentumok

Az autowiring csak objektumokat és objektumok tömbjeit adhatja át. A skalár argumentumok (pl. karakterláncok, számok, boolék) a konfigurációban írhatók. Alternatív megoldás egy olyan settings-objektum létrehozása, amely objektumként egy skalár értéket (vagy több értéket) foglal magába, amelyet aztán újra át lehet adni az autowiring segítségével.

class MySettings
{
	public function __construct(
		// readonly a PHP 8.1 óta használható.
		public readonly bool $value,
	)
	{}
}

Egy szolgáltatást úgy hozunk létre, hogy hozzáadjuk a konfigurációhoz:

services:
	- MySettings('any value')

Ezt követően minden osztály automatikus bekötés útján fogja kérni.

Az autowiring szűkítése

Az egyes szolgáltatások esetében az autowiring szűkíthető bizonyos osztályokra vagy interfészekre.

Normális esetben az autowiring minden olyan metódusparaméterhez átadja a szolgáltatást, amelynek típusának a szolgáltatás megfelel. A szűkítés azt jelenti, hogy megadjuk azokat a feltételeket, amelyeknek a metódusparaméterekhez megadott típusoknak meg kell felelniük ahhoz, hogy a szolgáltatás átadásra kerüljön hozzájuk.

Vegyünk egy példát:

class ParentClass
{}

class ChildClass extends ParentClass
{}

class ParentDependent
{
	function __construct(ParentClass $obj)
	{}
}

class ChildDependent
{
	function __construct(ChildClass $obj)
	{}
}

Ha az összeset szolgáltatásként regisztrálnánk, az autowiring nem sikerülne:

services:
	parent: ParentClass
	child: ChildClass
	parentDep:  # THROWS EXCEPTION, mind a szülő, mind a gyermek egyezik
	childDep: ChildDependent # átadja a 'child' szolgáltatást a konstruktornak.

A parentDep szolgáltatás a Multiple services of type ParentClass found: parent, child kivételt dobja, mert a parent és a child is belefér a konstruktorába, és az autowiring nem tudja eldönteni, hogy melyiket válassza.

A child szolgáltatás esetében ezért az autowiringet a ChildClass szolgáltatásra tudjuk leszűkíteni:

services:
	parent: ParentClass
	child:
		create: ChildClass
		autowired: ChildClass   # alternatíva: 'autowired: self'

	parentDep: ParentDependent  # THROWS EXCEPTION, a 'child' nem lehet autowired
	childDep: ChildDependent    # átadja a 'child' szolgáltatást a konstruktornak.

A parentDep szolgáltatást most átadjuk a parentDep szolgáltatás konstruktorának, mivel ez az egyetlen megfelelő objektum. A child szolgáltatást már nem adjuk át autowiringgel. Igen, a child szolgáltatás még mindig a ParentClass típusú, de a paramétertípusra megadott szűkítő feltétel már nem érvényes, azaz már nem igaz, hogy a ParentClass a ChildClass szupertípusa.

A child esetében a autowired: ChildClass -t írhatnánk autowired: self -nek, mivel a self az aktuális szolgáltatás típusát jelenti.

A autowired kulcs több osztályt és interfészt is tartalmazhat tömbként:

autowired: [BarClass, FooInterface]

Próbáljuk meg hozzáadni interfészeket a példához:

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)
	{}
}

Ha nem korlátozzuk a child szolgáltatást, akkor az összes FooDependent, BarDependent, ParentDependent és ChildDependent osztály konstruktorába befér, és az autowiring átadja azt.

Ha azonban az autowiringjét a ChildClass -ra szűkítjük a autowired: ChildClass (vagy a self) segítségével, az autowiring csak a ChildDependent konstruktorába passzolja, mert az ChildClass típusú argumentumot igényel, és a ChildClass típusú ChildClass. A többi paraméterhez megadott típus nem a ChildClass szuperhalmaza, így a szolgáltatás nem kerül átadásra.

Ha a ParentClass -ra korlátozzuk a autowired: ParentClass segítségével, akkor az autowiring ismét átadja a ChildDependent konstruktornak (mivel a szükséges típus ChildClass a ParentClass szuperszettje ) és a ParentDependent konstruktornak is, mivel a szükséges típus ParentClass szintén megfelel.

Ha a FooInterface-ra korlátozzuk, akkor is autowire-elni fog a ParentDependent (a szükséges típus ParentClass a FooInterface szupertípusa ) és a ChildDependent, de ezen kívül a FooDependent konstruktornak is, de a BarDependent-nak nem, mivel a BarInterface nem szupertípusa a FooInterface-nak.

services:
	child:
		create: ChildClass
		autowired: FooInterface

	fooDep: FooDependent       # átadja a szolgáltatás gyermekét a konstruktornak
	barDep: BarDependent       # THROWS EXCEPTION, nincs szolgáltatás átadása.
	parentDep: ParentDependent # átadja a szolgáltatás gyermekét a konstruktornak.
	childDep: ChildDependent   # átadja a szolgáltatás gyermekét a konstruktornak.