Fabbriche generate

Nette DI può generare automaticamente il codice di fabbrica basato sull'interfaccia, evitando così di scrivere codice.

Un factory è una classe che crea e configura gli oggetti. Pertanto, passa anche le loro dipendenze. Non bisogna confondersi con il design pattern metodo di fabbrica, che descrive un modo specifico di usare i factory e non è correlato a questo argomento.

Abbiamo mostrato l'aspetto di una fabbrica di questo tipo nel capitolo introduttivo:

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

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

Nette DI può generare automaticamente il codice del factory. Tutto ciò che si deve fare è creare un'interfaccia e Nette DI genererà un'implementazione. L'interfaccia deve avere esattamente un metodo chiamato create e dichiarare un tipo di ritorno:

interface ArticleFactory
{
	function create(): Article;
}

Quindi il factory ArticleFactory ha un metodo create che crea oggetti Article. La classe Article potrebbe avere il seguente aspetto, ad esempio:

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

Aggiungere il factory al file di configurazione:

services:
	- ArticleFactory

Nette DI genererà l'implementazione del factory corrispondente.

Pertanto, nel codice che utilizza il factory, si richiede l'oggetto per interfaccia e Nette DI utilizza l'implementazione generata:

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

	public function foo()
	{
		// lasciamo che il factory crei un oggetto
		$article = $this->articleFactory->create();
	}
}

Fabbrica parametrizzata

Il metodo factory create può accettare parametri, che poi passa al costruttore. Per esempio, aggiungiamo l'ID dell'autore di un articolo alla classe Article:

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

Aggiungeremo anche il parametro al factory:

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

Poiché il parametro nel costruttore e quello nel factory hanno lo stesso nome, Nette DI li passerà automaticamente.

Definizione avanzata

La definizione può essere scritta anche in forma multilinea utilizzando il tasto implement:

services:
	articleFactory:
		implement: ArticleFactory

Quando si scrive in questo modo più lungo, è possibile fornire argomenti aggiuntivi per il costruttore nella chiave arguments e configurazioni aggiuntive usando setup, proprio come per i servizi normali.

Esempio: se il metodo create() non accettasse il parametro $authorId, si potrebbe specificare nella configurazione un valore fisso da passare al costruttore Article:

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

Oppure, al contrario, se create() accettasse il parametro $authorId, ma non facesse parte del costruttore e fosse passato dal metodo Article::setAuthorId(), faremmo riferimento ad esso nella sezione setup:

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

Accessore

Oltre ai factory, Nette può anche generare i cosiddetti accessor. L'accessor è un oggetto con il metodo get() che restituisce un particolare servizio dal contenitore DI. Più chiamate a get() restituiranno sempre la stessa istanza.

Gli accessor portano il lazy-loading alle dipendenze. Abbiamo una classe che registra gli errori in un database speciale. Se la connessione al database fosse passata come dipendenza nel suo costruttore, la connessione dovrebbe essere sempre creata, anche se verrebbe usata solo raramente, quando appare un errore, quindi la connessione rimarrebbe per lo più inutilizzata. Invece, la classe può passare un accessor e quando viene chiamato il suo metodo get(), solo allora viene creato l'oggetto database:

Come creare un accessor? Scrivete solo un'interfaccia e Nette DI genererà l'implementazione. L'interfaccia deve avere esattamente un metodo chiamato get e deve dichiarare il tipo di ritorno:

interface PDOAccessor
{
	function get(): PDO;
}

Aggiungete l'accessor al file di configurazione insieme alla definizione del servizio che l'accessor restituirà:

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

L'accessor restituisce un servizio di tipo PDO e poiché esiste un solo servizio di questo tipo nella configurazione, l'accessor lo restituirà. Con più servizi configurati di quel tipo, si può specificare quale deve essere restituito usando il suo nome, per esempio - PDOAccessor(@db1).

Multifactory/Accessor

Finora, i factory e gli accessor potevano creare o restituire un solo oggetto. È possibile creare anche una multifactory combinata con un accessor. L'interfaccia di questa classe multifactory può essere composta da più metodi chiamati create<name>() e get<name>()ad esempio:

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

Invece di passare più factory e accessor generati, si può passare un solo multifactory complesso.

In alternativa, si può usare get() con un parametro invece di più metodi:

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

In questo caso, MultiFactory::getArticle() fa la stessa cosa di MultiFactoryAlt::get('article'). Tuttavia, la sintassi alternativa presenta alcuni svantaggi. Non è chiaro quali valori di $name siano supportati e il tipo di ritorno non può essere specificato nell'interfaccia quando si usano più valori diversi di $name.

Definizione con un elenco

Questo modo può essere usato per definire una fabbrica multipla nella configurazione:

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

Oppure, nella definizione del factory, si può fare riferimento a servizi esistenti usando un riferimento:

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

Definizione con tag

Un'altra opzione per definire una multifactory è quella di usare i tag:

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