Factory-uri generate

Nette DI poate genera automat codul factory-urilor pe baza interfețelor, ceea ce vă economisește scrierea codului.

Un factory este o clasă care produce și configurează obiecte. Le transmite deci și dependențele lor. Vă rugăm să nu confundați cu pattern-ul de design factory method, care descrie un mod specific de utilizare a factory-urilor și nu are legătură cu acest subiect.

Cum arată un astfel de factory am arătat în capitolul introductiv:

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

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

Nette DI poate genera automat codul factory-urilor. Tot ce trebuie să faceți este să creați o interfață și Nette DI va genera implementarea. Interfața trebuie să aibă exact o metodă numită create și să declare tipul returnat:

interface ArticleFactory
{
	function create(): Article;
}

Deci, factory-ul ArticleFactory are o metodă create, care creează obiecte Article. Clasa Article poate arăta, de exemplu, astfel:

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

Adăugăm factory-ul în fișierul de configurare:

services:
	- ArticleFactory

Nette DI va genera implementarea corespunzătoare a factory-ului.

În codul care utilizează factory-ul, solicităm astfel obiectul conform interfeței și Nette DI va utiliza implementarea generată:

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

	public function foo()
	{
		// lăsăm factory-ul să creeze obiectul
		$article = $this->articleFactory->create();
	}
}

Factory parametrizat

Metoda factory create poate accepta parametri, pe care îi transmite apoi constructorului. Să completăm, de exemplu, clasa Article cu ID-ul autorului articolului:

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

Adăugăm parametrul și în factory:

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

Datorită faptului că parametrul din constructor și parametrul din factory se numesc la fel, Nette DI îi transmite complet automat.

Definiție avansată

Definiția poate fi scrisă și într-o formă multi-linie folosind cheia implement:

services:
	articleFactory:
		implement: ArticleFactory

La scrierea în această formă mai lungă, este posibil să se specifice argumente suplimentare pentru constructor în cheia arguments și configurație suplimentară folosind setup, la fel ca la serviciile obișnuite.

Exemplu: dacă metoda create() nu ar accepta parametrul $authorId, am putea specifica o valoare fixă în configurație, care ar fi transmisă constructorului Article:

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

Sau invers, dacă create() ar accepta parametrul $authorId, dar acesta nu ar face parte din constructor și s-ar transmite prin metoda Article::setAuthorId(), ne-am referi la el în secțiunea setup:

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

Accessor

Nette poate genera, pe lângă factory-uri, și așa-numiții accesori. Aceștia sunt obiecte cu o metodă get(), care returnează un anumit serviciu din containerul DI. Apelarea repetată a get() returnează mereu aceeași instanță.

Accesorii oferă lazy-loading pentru dependențe. Să presupunem că avem o clasă care scrie erori într-o bază de date specială. Dacă această clasă ar primi conexiunea la baza de date ca dependență prin constructor, conexiunea ar trebui creată întotdeauna, deși în practică eroarea apare doar excepțional și, prin urmare, în majoritatea cazurilor conexiunea ar rămâne neutilizată. În schimb, clasa primește un accesor și abia atunci când se apelează get(), se creează obiectul bazei de date:

Cum se creează un accesor? Este suficient să scrieți o interfață și Nette DI va genera implementarea. Interfața trebuie să aibă exact o metodă numită get și să declare tipul returnat:

interface PDOAccessor
{
	function get(): PDO;
}

Adăugăm accesorul în fișierul de configurare, unde este definit și serviciul pe care îl va returna:

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

Deoarece accesorul returnează un serviciu de tip PDO și în configurație există un singur astfel de serviciu, îl va returna tocmai pe acesta. Dacă ar exista mai multe servicii de tipul respectiv, specificăm serviciul returnat folosind numele, de ex. - PDOAccessor(@db1).

Factory/Accesor multiplu

Factory-urile și accesorii noștri au putut până acum să producă sau să returneze doar un singur obiect. Dar se pot crea foarte ușor și factory-uri multiple combinate cu accesori. Interfața unei astfel de clase va conține un număr arbitrar de metode cu numele create<name>() și get<name>(), de ex.:

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

Deci, în loc să transmitem mai multe factory-uri și accesori generați, transmitem un factory mai complex care poate face mai multe lucruri.

Alternativ, în loc de mai multe metode, se poate folosi get() cu un parametru:

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

Atunci este valabil că MultiFactory::getArticle() face același lucru ca MultiFactoryAlt::get('article'). Cu toate acestea, scrierea alternativă are dezavantajul că nu este evident ce valori $name sunt suportate și, logic, nici nu se pot distinge în interfață diferite valori returnate pentru diferite $name.

Definiție prin listă

În acest mod se poate defini un factory multiplu în configurație:

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

Sau ne putem referi în definiția factory-ului la servicii existente folosind o referință:

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

Definiție prin tag-uri

A doua opțiune este utilizarea tag-urilor pentru definire:

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