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