Generált factory-k
A Nette DI képes automatikusan generálni factory kódot interfészek alapján, ami megkíméli Önt a kódírástól.
A factory egy olyan osztály, amely objektumokat gyárt és konfigurál. Tehát átadja nekik a függőségeiket is. Kérjük, ne keverje össze a factory method tervezési mintával, amely a factory-k specifikus felhasználási módját írja le, és nem kapcsolódik ehhez a témához.
Hogy néz ki egy ilyen factory, azt a bevezető fejezetben mutattuk be:
class ArticleFactory
{
public function __construct(
private Nette\Database\Connection $db,
) {
}
public function create(): Article
{
return new Article($this->db);
}
}
A Nette DI képes automatikusan generálni a factory kódot. Mindössze annyit kell tennie, hogy létrehoz egy interfészt,
és a Nette DI legenerálja az implementációt. Az interfésznek pontosan egy create
nevű metódussal kell
rendelkeznie, és deklarálnia kell a visszatérési típust:
interface ArticleFactory
{
function create(): Article;
}
Tehát az ArticleFactory
factorynak van egy create
metódusa, amely Article
objektumokat
hoz létre. Az Article
osztály például így nézhet ki:
class Article
{
public function __construct(
private Nette\Database\Connection $db,
) {
}
}
A factoryt hozzáadjuk a konfigurációs fájlhoz:
services:
- ArticleFactory
A Nette DI legenerálja a factory megfelelő implementációját.
A kódban, amely a factoryt használja, így kérünk egy objektumot az interfész alapján, és a Nette DI a generált implementációt használja:
class UserController
{
public function __construct(
private ArticleFactory $articleFactory,
) {
}
public function foo()
{
// hagyjuk, hogy a factory létrehozza az objektumot
$article = $this->articleFactory->create();
}
}
Paraméterezett factory
A create
factory metódus elfogadhat paramétereket, amelyeket aztán átad a konstruktornak. Egészítsük ki
például az Article
osztályt a cikk szerzőjének ID-jával:
class Article
{
public function __construct(
private Nette\Database\Connection $db,
private int $authorId,
) {
}
}
A paramétert hozzáadjuk a factoryhoz is:
interface ArticleFactory
{
function create(int $authorId): Article;
}
Annak köszönhetően, hogy a konstruktorban és a factoryban lévő paraméter neve ugyanaz, a Nette DI teljesen automatikusan átadja őket.
Haladó definíció
A definíciót többsoros formában is le lehet írni az implement
kulcs használatával:
services:
articleFactory:
implement: ArticleFactory
Ezzel a hosszabb írásmóddal további argumentumokat lehet megadni a konstruktorhoz az arguments
kulcsban, és
kiegészítő konfigurációt a setup
segítségével, ugyanúgy, mint a normál szolgáltatásoknál.
Példa: ha a create()
metódus nem fogadná el a $authorId
paramétert, megadhatnánk egy fix
értéket a konfigurációban, amelyet átadnánk az Article
konstruktorának:
services:
articleFactory:
implement: ArticleFactory
arguments:
authorId: 123
Vagy fordítva, ha a create()
elfogadná a $authorId
paramétert, de az nem lenne része a
konstruktornak, és a Article::setAuthorId()
metódussal adnánk át, akkor a setup
szekcióban
hivatkoznánk rá:
services:
articleFactory:
implement: ArticleFactory
setup:
- setAuthorId($authorId)
Accessor
A Nette a factory-k mellett ún. accessorokat is tud generálni. Ezek olyan objektumok, amelyeknek van egy get()
metódusa, amely egy bizonyos szolgáltatást ad vissza a DI konténerből. A get()
ismételt hívása mindig
ugyanazt a példányt adja vissza.
Az accessorok lazy-loadingot biztosítanak a függőségeknek. Tegyük fel, hogy van egy osztályunk, amely hibákat ír egy
speciális adatbázisba. Ha ez az osztály konstruktorfüggőségként kapná meg az adatbázis-kapcsolatot, a kapcsolatot mindig
létre kellene hozni, bár a gyakorlatban hiba csak kivételesen fordul elő, és így a kapcsolat legtöbbször kihasználatlan
maradna. Ehelyett az osztály átad egy accessort, és csak akkor jön létre az adatbázis objektum, amikor annak
get()
metódusát meghívják:
Hogyan hozzunk létre accessort? Csak írjunk egy interfészt, és a Nette DI legenerálja az implementációt. Az
interfésznek pontosan egy get
nevű metódussal kell rendelkeznie, és deklarálnia kell a visszatérési
típust:
interface PDOAccessor
{
function get(): PDO;
}
Az accessort hozzáadjuk a konfigurációs fájlhoz, ahol a szolgáltatás definíciója is található, amelyet vissza fog adni:
services:
- PDOAccessor
- PDO(%dsn%, %user%, %password%)
Mivel az accessor PDO
típusú szolgáltatást ad vissza, és a konfigurációban csak egy ilyen szolgáltatás
van, pontosan azt fogja visszaadni. Ha több ilyen típusú szolgáltatás lenne, a visszaadott szolgáltatást név szerint
határoznánk meg, pl. - PDOAccessor(@db1)
.
Többszörös factory/accessor
Eddig a factory-ink és accessoraink mindig csak egy objektumot tudtak gyártani vagy visszaadni. De nagyon könnyen
létrehozhatunk többszörös factory-kat is accessorokkal kombinálva. Egy ilyen osztály interfésze tetszőleges számú
create<name>()
és get<name>()
nevű metódust tartalmazhat, pl.:
interface MultiFactory
{
function createArticle(): Article;
function getDb(): PDO;
}
Tehát ahelyett, hogy több generált factoryt és accessort adnánk át, egy komplexebb factoryt adunk át, amely többet tud.
Alternatívaként több metódus helyett használhatjuk a get()
-et paraméterrel:
interface MultiFactoryAlt
{
function get($name): PDO;
}
Ekkor igaz, hogy a MultiFactory::getArticle()
ugyanazt csinálja, mint a
MultiFactoryAlt::get('article')
. Az alternatív írásmódnak azonban az a hátránya, hogy nem egyértelmű, milyen
$name
értékek támogatottak, és logikailag nem lehet megkülönböztetni a különböző visszatérési
értékeket a különböző $name
-ekhez az interfészben.
Definíció listával
Ezzel a módszerrel definiálhatunk többszörös factoryt a konfigurációban:
services:
- MultiFactory(
article: Article # definiálja a createArticle()-t
db: PDO(%dsn%, %user%, %password%) # definiálja a getDb()-t
)
Vagy a factory definíciójában hivatkozhatunk létező szolgáltatásokra referenciával:
services:
article: Article
- PDO(%dsn%, %user%, %password%)
- MultiFactory(
article: @article # definiálja a createArticle()-t
db: @\PDO # definiálja a getDb()-t
)
Definíció tagekkel
A második lehetőség a tageket használni a definícióhoz:
services:
- App\Core\RouterFactory::createRouter
- App\Model\DatabaseAccessor(
db1: @database.db1.explorer
)