Factory generate

Nette DI può generare automaticamente il codice delle factory basandosi su interfacce, risparmiandoti la scrittura di codice.

Una factory è una classe che produce e configura oggetti. Quindi passa loro anche le loro dipendenze. Si prega di non confondere con il design pattern factory method, che descrive un modo specifico di utilizzare le factory e non è correlato a questo argomento.

Come appare una tale factory lo abbiamo mostrato 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 delle factory. Tutto ciò che devi fare è creare un'interfaccia e Nette DI genererà l'implementazione. L'interfaccia deve avere esattamente un metodo chiamato create e dichiarare il tipo di ritorno:

interface ArticleFactory
{
	function create(): Article;
}

Quindi la factory ArticleFactory ha un metodo create, che crea oggetti Article. La classe Article può apparire ad esempio così:

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

Aggiungiamo la factory al file di configurazione:

services:
	- ArticleFactory

Nette DI genererà l'implementazione corrispondente della factory.

Nel codice che utilizza la factory, richiediamo quindi l'oggetto tramite l'interfaccia e Nette DI utilizzerà l'implementazione generata:

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

	public function foo()
	{
		// facciamo creare l'oggetto alla factory
		$article = $this->articleFactory->create();
	}
}

Factory parametrizzata

Il metodo della factory create può accettare parametri, che poi passa al costruttore. Aggiungiamo ad esempio alla classe Article l'ID dell'autore dell'articolo:

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

Aggiungiamo il parametro anche alla factory:

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

Grazie al fatto che il parametro nel costruttore e il parametro nella factory si chiamano allo stesso modo, Nette DI li passa in modo completamente automatico.

Definizione avanzata

La definizione può essere scritta anche in forma multiriga utilizzando la chiave implement:

services:
	articleFactory:
		implement: ArticleFactory

Scrivendo in questo modo più lungo, è possibile specificare argomenti aggiuntivi per il costruttore nella chiave arguments e configurazioni supplementari tramite setup, proprio come per i servizi normali.

Esempio: se il metodo create() non accettasse il parametro $authorId, potremmo specificare un valore fisso nella configurazione, che verrebbe passato al costruttore di Article:

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

O al contrario, se create() accettasse il parametro $authorId, ma non fosse parte del costruttore e venisse passato tramite il metodo Article::setAuthorId(), ci riferiremmo ad esso nella sezione setup:

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

Accessor

Nette, oltre alle factory, può generare anche i cosiddetti accessor. Si tratta di oggetti con un metodo get(), che restituisce un determinato servizio dal container DI. Chiamate ripetute a get() restituiscono sempre la stessa istanza.

Gli accessor forniscono il lazy-loading alle dipendenze. Supponiamo di avere una classe che scrive errori in un database speciale. Se questa classe ricevesse la connessione al database come dipendenza tramite il costruttore, la connessione dovrebbe sempre essere creata, anche se in pratica un errore si verifica solo eccezionalmente e quindi la maggior parte delle volte la connessione rimarrebbe inutilizzata. Invece, la classe riceve un accessor e solo quando viene chiamato il suo get(), viene creato l'oggetto del database:

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

interface PDOAccessor
{
	function get(): PDO;
}

Aggiungiamo l'accessor al file di configurazione, dove è definita anche la definizione del servizio che restituirà:

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

Poiché l'accessor restituisce un servizio di tipo PDO e nella configurazione c'è un solo servizio di questo tipo, restituirà proprio quello. Se ci fossero più servizi di quel tipo, specificheremmo il servizio restituito tramite il nome, ad es. - PDOAccessor(@db1).

Factory/accessor multipli

Le nostre factory e accessor finora sapevano sempre produrre o restituire solo un oggetto. Ma è possibile creare molto facilmente anche factory multiple combinate con accessor. L'interfaccia di una tale classe conterrà un numero qualsiasi di metodi con nomi create<name>() e get<name>(), ad es.:

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

Quindi, invece di passare diverse factory e accessor generati, passiamo una factory più complessa che sa fare di più.

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

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

Allora vale che MultiFactory::getArticle() fa la stessa cosa di MultiFactoryAlt::get('article'). Tuttavia, la scrittura alternativa ha lo svantaggio che non è chiaro quali valori di $name siano supportati e logicamente non è possibile distinguere nell'interfaccia diversi valori di ritorno per diversi $name.

Definizione tramite elenco

In questo modo è possibile definire una factory multipla nella configurazione:

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

Oppure possiamo riferirci a servizi esistenti nella definizione della factory tramite riferimento:

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

Definizione tramite tag

La seconda possibilità è utilizzare per la definizione i tag:

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