Generirane tovarne

Nette DI zna samodejno generirati kodo tovarn na podlagi vmesnikov, kar vam prihrani pisanje kode.

Tovarna je razred, ki izdeluje in konfigurira objekte. Posreduje jim torej tudi njihove odvisnosti. Ne zamenjujte prosim z načrtovalskim vzorcem factory method, ki opisuje specifičen način uporabe tovarn in s to temo ni povezan.

Kako taka tovarna izgleda, smo si pokazali v uvodnem poglavju:

class ArticleFactory
{
	public function __construct(
		private Nette\Database\Connection $db,
	) {
	}

	public function create(): Article
	{
		return new Article($this->db);
	}
}

Nette DI zna kodo tovarn samodejno generirati. Vse, kar morate storiti, je ustvariti vmesnik in Nette DI bo generiral implementacijo. Vmesnik mora imeti točno eno metodo z imenom create in deklarirati povratni tip:

interface ArticleFactory
{
	function create(): Article;
}

Torej tovarna ArticleFactory ima metodo create, ki ustvarja objekte Article. Razred Article lahko izgleda na primer takole:

class Article
{
	public function __construct(
		private Nette\Database\Connection $db,
	) {
	}
}

Tovarno dodamo v konfiguracijsko datoteko:

services:
	- ArticleFactory

Nette DI bo generiral ustrezno implementacijo tovarne.

V kodi, ki tovarno uporablja, tako zahtevamo objekt po vmesniku in Nette DI bo uporabil generirano implementacijo:

class UserController
{
	public function __construct(
		private ArticleFactory $articleFactory,
	) {
	}

	public function foo()
	{
		// pustimo tovarni ustvariti objekt
		$article = $this->articleFactory->create();
	}
}

Parametrizirana tovarna

Tovarniška metoda create lahko sprejema parametre, ki jih nato posreduje v konstruktor. Dopolnimo na primer razred Article z ID avtorja članka:

class Article
{
	public function __construct(
		private Nette\Database\Connection $db,
		private int $authorId,
	) {
	}
}

Parameter dodamo tudi v tovarno:

interface ArticleFactory
{
	function create(int $authorId): Article;
}

Zahvaljujoč temu, da se parameter v konstruktorju in parameter v tovarni imenujeta enako, jih Nette DI popolnoma samodejno posreduje.

Napredna definicija

Definicijo lahko zapišemo tudi v večvrstični obliki z uporabo ključa implement:

services:
	articleFactory:
		implement: ArticleFactory

Pri zapisu na ta daljši način je mogoče navesti dodatne argumente za konstruktor v ključu arguments in dopolnilno konfiguracijo z uporabo setup, enako kot pri običajnih storitvah.

Primer: če metoda create() ne bi sprejemala parametra $authorId, bi lahko navedli fiksno vrednost v konfiguraciji, ki bi se posredovala v konstruktor Article:

services:
	articleFactory:
		implement: ArticleFactory
		arguments:
			authorId: 123

Ali obratno, če bi create() parameter $authorId sprejemala, vendar ne bi bil del konstruktorja in bi se posredoval z metodo Article::setAuthorId(), bi se nanj sklicevali v sekciji setup:

services:
	articleFactory:
		implement: ArticleFactory
		setup:
			- setAuthorId($authorId)

Accessor

Nette zna poleg tovarn generirati tudi t.i. accessorje. Gre za objekte z metodo get(), ki vrača določeno storitev iz DI vsebnika. Ponavljajoči klic get() vrača vedno isto instanco.

Accessorji zagotavljajo odvisnostim lazy-loading. Imejmo razred, ki zapisuje napake v posebno podatkovno bazo. Če bi si ta razred pustil povezavo z podatkovno bazo posredovati kot odvisnost prek konstruktorja, bi se morala povezava vedno ustvariti, čeprav se v praksi napaka pojavi le izjemoma in bi torej večinoma povezava ostala neizkoriščena. Namesto tega si razred posreduje accessor in šele ko se pokliče njegov get(), pride do ustvarjanja objekta podatkovne baze:

Kako ustvariti accessor? Zadostuje napisati vmesnik in Nette DI bo generiral implementacijo. Vmesnik mora imeti točno eno metodo z imenom get in deklarirati povratni tip:

interface PDOAccessor
{
	function get(): PDO;
}

Accessor dodamo v konfiguracijsko datoteko, kjer je tudi definicija storitve, ki jo bo vračal:

services:
	- PDOAccessor
	- PDO(%dsn%, %user%, %password%)

Ker accessor vrača storitev tipa PDO in je v konfiguraciji edina taka storitev, bo vračal prav njo. Če bi bilo storitev danega tipa več, določimo vračano storitev z imenom, npr. - PDOAccessor(@db1).

Večkratna tovarna/accessor

Naše tovarne in accessorji so doslej vedno znali izdelovati ali vračati samo en objekt. Lahko pa zelo enostavno ustvarimo tudi večkratne tovarne, kombinirane z accessorji. Vmesnik takega razreda bo vseboval poljubno število metod z imeni create<name>() in get<name>(), npr.:

interface MultiFactory
{
	function createArticle(): Article;
	function getDb(): PDO;
}

Torej namesto da bi si posredovali več generiranih tovarn in accessorjev, posredujemo eno kompleksnejšo tovarno, ki zna več.

Alternativno lahko namesto več metod uporabimo get() s parametrom:

interface MultiFactoryAlt
{
	function get($name): PDO;
}

Potem velja, da MultiFactory::getArticle() počne isto kot MultiFactoryAlt::get('article'). Vendar ima alternativni zapis to slabost, da ni očitno, katere vrednosti $name so podprte in logično tudi ni mogoče v vmesniku ločiti različnih povratnih vrednosti za različne $name.

Definicija s seznamom

Na ta način lahko definiramo večkratno tovarno v konfiguraciji:

services:
	- MultiFactory(
		article: Article                      # definira createArticle()
		db: PDO(%dsn%, %user%, %password%)    # definira getDb()
	)

Ali pa se lahko v definiciji tovarne sklicujemo na obstoječe storitve z referenco:

services:
	article: Article
	- PDO(%dsn%, %user%, %password%)
	- MultiFactory(
		article: @article    # definira createArticle()
		db: @\PDO            # definira getDb()
	)

Definicija z oznakami

Druga možnost je uporaba oznak za definicijo:

services:
	- App\Core\RouterFactory::createRouter
	- App\Model\DatabaseAccessor(
		db1: @database.db1.explorer
	)
različica: 3.x