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
	)
verzió: 3.x