Fabrici generate

Nette DI poate genera automat codul fabricii pe baza interfeței, ceea ce vă scutește de scrierea de cod.

O fabrică este o clasă care creează și configurează obiecte. Prin urmare, le transmite și dependențele acestora. Vă rugăm să nu faceți confuzie cu modelul de proiectare factory method, care descrie un mod specific de utilizare a fabricilor și nu are legătură cu acest subiect.

Am arătat cum arată o astfel de fabrică î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 cod de fabrică. Tot ce trebuie să faceți este să creați o interfață și Nette DI va genera o implementare. Interfața trebuie să aibă exact o metodă numită create și să declare un tip de returnare:

interface ArticleFactory
{
	function create(): Article;
}

Astfel, fabrica ArticleFactory are o metodă create care creează obiecte Article. Clasa Article ar putea arăta, de exemplu, după cum urmează:

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

Adăugați fabrica în fișierul de configurare:

services:
	- ArticleFactory

Nette DI va genera implementarea fabricii corespunzătoare.

Astfel, în codul care utilizează fabrica, solicităm obiectul prin interfață, iar Nette DI utilizează implementarea generată:

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

	public function foo()
	{
		// permiteți fabricii să creeze un obiect
		$article = $this->articleFactory->create();
	}
}

Fabrica parametrizată

Metoda factory create poate accepta parametri pe care îi transmite apoi constructorului. De exemplu, să adăugăm un ID de autor de articol la clasa Article:

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

Vom adăuga, de asemenea, parametrul la fabrică:

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

Deoarece parametrul din constructor și parametrul din fabrică au același nume, Nette DI le va trece automat.

Definiție avansată

Definiția poate fi scrisă și sub formă de mai multe rânduri cu ajutorul tastei implement:

services:
	articleFactory:
		implement: ArticleFactory

Atunci când se scrie în acest mod mai lung, este posibil să se furnizeze argumente suplimentare pentru constructor în cheia arguments și o configurație suplimentară folosind setup, la fel ca pentru serviciile normale.

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

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

Sau, dimpotrivă, dacă create() accepta parametrul $authorId, dar acesta nu făcea parte din constructor și era transmis de metoda Article::setAuthorId(), ne-am putea referi la el în secțiunea setup:

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

Accesor

În afară de fabrici, Nette poate genera și așa numiții accesori. Accesorul este un obiect cu metoda get() care returnează un anumit serviciu de la containerul DI. Mai multe apeluri la get() vor returna întotdeauna aceeași instanță.

Accesorii permit încărcarea leneșă a dependențelor. Să avem o clasă care înregistrează erorile într-o bază de date specială. Dacă conexiunea la baza de date ar fi trecută ca dependență în constructorul său, conexiunea ar trebui să fie mereu creată, deși ar fi folosită doar rareori când apare o eroare, astfel încât conexiunea ar rămâne în mare parte neutilizată. În schimb, clasa poate trece un accesor și, atunci când este apelată metoda get(), numai atunci este creat obiectul bazei de date:

Cum se creează un accesor? Scrieți doar o interfață, iar Nette DI va genera implementarea. Interfața trebuie să aibă exact o metodă numită get și trebuie să declare tipul de returnare:

interface PDOAccessor
{
	function get(): PDO;
}

Adăugați accesorul în fișierul de configurare împreună cu definiția serviciului pe care accesorul îl va returna:

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

Accesorul returnează un serviciu de tip PDO și, deoarece există un singur astfel de serviciu în configurație, accesorul îl va returna. În cazul în care există mai multe servicii configurate de acest tip, puteți specifica care dintre ele trebuie returnat folosind numele său, de exemplu - PDOAccessor(@db1).

Multifactory/Accesor

Până acum, fabricile și accesorii puteau crea sau returna doar un singur obiect. Se poate crea, de asemenea, un multifactory combinat cu un accesor. Interfața unei astfel de clase multifactory poate consta din mai multe metode numite create<name>() și get<name>(), de exemplu:

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

În loc să transmiteți mai multe fabrici și accesori generați, puteți transmite doar o singură multifactorie complexă.

Alternativ, puteți utiliza get() cu un parametru în loc de mai multe metode:

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

În acest caz, MultiFactory::getArticle() face același lucru ca și MultiFactoryAlt::get('article'). Cu toate acestea, sintaxa alternativă are câteva dezavantaje. Nu este clar ce valori $name sunt acceptate, iar tipul de returnare nu poate fi specificat în interfață atunci când se utilizează mai multe valori $name diferite.

Definiție cu o listă

Acest mod poate fi utilizat pentru a defini o fabrică multiplă în configurație:

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

Sau, în definiția fabricii, putem face referire la serviciile existente folosind o referință:

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

Definiție cu etichete

O altă opțiune de definire a unui multifactorial este utilizarea etichetelor:

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