Autowiring
Az Autowiring egy nagyszerű funkció, amely automatikusan átadja a szükséges szolgáltatásokat a konstruktornak és más metódusoknak, így egyáltalán nem kell őket megírnunk. Rengeteg időt takarít meg Önnek.
Ennek köszönhetően a szolgáltatásdefiníciók írásakor a legtöbb argumentumot elhagyhatjuk. Helyette:
services:
articles: Model\ArticleRepository(@database, @cache.storage)
Elég ennyit írni:
services:
articles: Model\ArticleRepository
Az autowiring típusok alapján működik, tehát ahhoz, hogy működjön, az ArticleRepository osztályt valahogy
így kell definiálni:
namespace Model;
class ArticleRepository
{
public function __construct(\PDO $db, \Nette\Caching\Storage $storage)
{}
}
Az autowiring használatához minden típushoz pontosan egy szolgáltatásnak kell lennie a konténerben. Ha több lenne belőlük, az autowiring nem tudná, melyiket adja át, és kivételt dobna:
services:
mainDb: PDO(%dsn%, %user%, %password%)
tempDb: PDO('sqlite::memory:')
articles: Model\ArticleRepository # KIVÉTELT DOB, a mainDb és a tempDb is megfelel
A megoldás az lenne, ha vagy megkerülnénk az autowiringot, és explicit módon megadnánk a szolgáltatás nevét (azaz
articles: Model\ArticleRepository(@mainDb)). De ügyesebb az egyik szolgáltatás autowiringját kikapcsolni, vagy az első szolgáltatást előnyben
részesíteni.
Autowiring kikapcsolása
Egy szolgáltatás autowiringját kikapcsolhatjuk az autowired: no opcióval:
services:
mainDb: PDO(%dsn%, %user%, %password%)
tempDb:
create: PDO('sqlite::memory:')
autowired: false # a tempDb szolgáltatás ki van zárva az autowiringból
articles: Model\ArticleRepository # tehát a konstruktorba a mainDb-t adja át
Az articles szolgáltatás nem dob kivételt, hogy két megfelelő PDO típusú szolgáltatás
létezik (azaz mainDb és tempDb), amelyeket át lehet adni a konstruktorba, mert csak a
mainDb szolgáltatást látja.
Az autowiring konfigurációja a Nette-ben másképp működik, mint a Symfony-ban, ahol az
autowire: false opció azt mondja, hogy ne használja az autowiringot az adott szolgáltatás konstruktorának
argumentumaihoz. A Nette-ben az autowiring mindig használatos, akár a konstruktor argumentumaihoz, akár bármely más
metódushoz. Az autowired: false opció azt mondja, hogy az adott szolgáltatás példányát ne adják át sehova
autowiring segítségével.
Autowiring preferencia
Ha több azonos típusú szolgáltatásunk van, és az egyiknél megadjuk az autowired opciót, ez a
szolgáltatás preferálttá válik:
services:
mainDb:
create: PDO(%dsn%, %user%, %password%)
autowired: PDO # preferálttá válik
tempDb:
create: PDO('sqlite::memory:')
articles: Model\ArticleRepository
Az articles szolgáltatás nem dob kivételt, hogy két megfelelő PDO típusú szolgáltatás
létezik (azaz mainDb és tempDb), hanem a preferált szolgáltatást használja, tehát a
mainDb-t.
Szolgáltatások tömbje
Az autowiring képes átadni egy adott típusú szolgáltatások tömbjét is. Mivel PHP-ban natívan nem lehet megadni a tömb
elemeinek típusát, a array típus mellett egy phpDoc kommentet is hozzá kell adni az elem típusával
ClassName[] formában:
namespace Model;
class ShipManager
{
/**
* @param Shipper[] $shippers
*/
public function __construct(array $shippers)
{}
}
A DI konténer ezután automatikusan átadja az adott típusnak megfelelő szolgáltatások tömbjét. Kihagyja azokat a szolgáltatásokat, amelyeknek ki van kapcsolva az autowiringja.
A kommentben szereplő típus lehet array<int, Class> vagy list<Class> formájú is. Ha
nem tudja befolyásolni a phpDoc komment formáját, átadhatja a szolgáltatások tömbjét közvetlenül a konfigurációban a
typed()
segítségével.
Skalár argumentumok
Az autowiring csak objektumokat és objektumok tömbjeit tudja beilleszteni. A skalár argumentumokat (pl. stringek, számok, logikai értékek) a konfigurációban írjuk le. Alternatíva egy settings-objektum létrehozása, amely a skalár értéket (vagy több értéket) objektum formájába csomagolja, és ezt aztán újra át lehet adni autowiring segítségével.
class MySettings
{
public function __construct(
// a readonly PHP 8.1-től használható
public readonly bool $value,
)
{}
}
Szolgáltatást hozhat létre belőle a konfigurációhoz való hozzáadással:
services:
- MySettings('any value')
Ezután minden osztály autowiring segítségével kérheti azt.
Autowiring szűkítése
Az egyes szolgáltatások autowiringját le lehet szűkíteni csak bizonyos osztályokra vagy interfészekre.
Normális esetben az autowiring átadja a szolgáltatást minden olyan metódusparaméternek, amelynek típusa megfelel a szolgáltatásnak. A szűkítés azt jelenti, hogy feltételeket szabunk, amelyeknek a metódusparamétereknél megadott típusoknak meg kell felelniük ahhoz, hogy a szolgáltatást átadják nekik.
Nézzünk egy példát:
class ParentClass
{}
class ChildClass extends ParentClass
{}
class ParentDependent
{
function __construct(ParentClass $obj)
{}
}
class ChildDependent
{
function __construct(ChildClass $obj)
{}
}
Ha mindegyiket szolgáltatásként regisztrálnánk, az autowiring meghiúsulna:
services:
parent: ParentClass
child: ChildClass
parentDep: ParentDependent # KIVÉTELT DOB, a parent és a child szolgáltatás is megfelel
childDep: ChildDependent # az autowiring a child szolgáltatást adja át a konstruktorba
A parentDep szolgáltatás Multiple services of type ParentClass found: parent, child kivételt dob,
mert a konstruktorába mind a parent, mind a child szolgáltatás illeszkedik, és az autowiring nem
tudja eldönteni, melyiket válassza.
Ezért a child szolgáltatásnál leszűkíthetjük az autowiringját a ChildClass típusra:
services:
parent: ParentClass
child:
create: ChildClass
autowired: ChildClass # 'autowired: self'-et is lehet írni
parentDep: ParentDependent # az autowiring a parent szolgáltatást adja át a konstruktorba
childDep: ChildDependent # az autowiring a child szolgáltatást adja át a konstruktorba
Most a parentDep szolgáltatás konstruktorába a parent szolgáltatás kerül átadásra, mert most
ez az egyetlen megfelelő objektum. A child szolgáltatást az autowiring már nem adja át oda. Igen, a
child szolgáltatás továbbra is ParentClass típusú, de már nem teljesül a paraméter típusára
vonatkozó szűkítő feltétel, azaz nem igaz, hogy a ParentClass felülírja a
ChildClass-t.
A child szolgáltatásnál az autowired: ChildClass-t autowired: self-ként is lehetne
írni, mivel a self az aktuális szolgáltatás osztályának helyettesítő jelölése.
Az autowired kulcsban több osztályt vagy interfészt is meg lehet adni tömbként:
autowired: [BarClass, FooInterface]
Próbáljuk meg a példát kiegészíteni egy interfésszel:
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 a child szolgáltatást semmilyen módon nem korlátozzuk, akkor illeszkedni fog az összes
FooDependent, BarDependent, ParentDependent és ChildDependent osztály
konstruktorába, és az autowiring oda fogja átadni.
Ha azonban az autowiringját leszűkítjük a ChildClass-ra az autowired: ChildClass (vagy
self) segítségével, az autowiring csak a ChildDependent konstruktorába adja át, mert az
ChildClass típusú argumentumot igényel, és igaz, hogy a ChildClass típusa
ChildClass. A többi paraméternél megadott további típusok egyike sem felülírja a ChildClass-t,
így a szolgáltatás nem kerül átadásra.
Ha a ParentClass-ra korlátozzuk az autowired: ParentClass segítségével, az autowiring ismét
átadja a ChildDependent konstruktorába (mert a szükséges ChildClass felülírja a
ParentClass-t), és újonnan a ParentDependent konstruktorába is, mert a szükséges
ParentClass típus szintén megfelelő.
Ha a FooInterface-re korlátozzuk, akkor továbbra is autowire-olva lesz a ParentDependent-be (a
szükséges ParentClass felülírja a FooInterface-t) és a ChildDependent-be, de
ráadásul a FooDependent konstruktorába is, viszont nem a BarDependent-be, mert a
BarInterface nem felülírja a FooInterface-t.
services:
child:
create: ChildClass
autowired: FooInterface
fooDep: FooDependent # az autowiring a child-ot adja át a konstruktorba
barDep: BarDependent # KIVÉTELT DOB, egyetlen szolgáltatás sem felel meg
parentDep: ParentDependent # az autowiring a child-ot adja át a konstruktorba
childDep: ChildDependent # az autowiring a child-ot adja át a konstruktorba