Factory generate
Nette DI sa generare automaticamente il codice delle factory a partire dalle interfacce, risparmiandovi di scrivere codice.
Una factory è una classe che si occupa di creare oggetti e di passarne le dipendenze. Non confondetela con il design pattern factory method, che descrive un modo specifico di usare le factory e non ha nulla a che vedere con questo argomento.
Abbiamo mostrato che aspetto ha una factory del genere nel capitolo introduttivo:
class ArticleFactory
{
public function __construct(
private Nette\Database\Connection $db,
) {
}
public function create(): Article
{
return new Article($this->db);
}
}
Nette DI sa generare automaticamente il codice della factory. Vi basta creare un'interfaccia e Nette DI ne genererà
l'implementazione. L'interfaccia deve avere esattamente un metodo chiamato create e dichiarare un tipo di
ritorno:
interface ArticleFactory
{
function create(): Article;
}
La factory ArticleFactory ha quindi un metodo create che crea oggetti Article. La classe
Article potrebbe avere per esempio questo aspetto:
class Article
{
public function __construct(
private Nette\Database\Connection $db,
) {
}
}
Aggiungete la factory al file di configurazione:
services:
- ArticleFactory
Nette DI genererà la corrispondente implementazione della factory.
Nel codice che usa la factory, chiedete l'oggetto tramite la sua interfaccia e Nette DI vi fornirà l'implementazione generata:
class UserController
{
public function __construct(
private ArticleFactory $articleFactory,
) {
}
public function foo()
{
// lascia che sia la factory a creare l'oggetto
$article = $this->articleFactory->create();
}
}
Factory con parametri
Il metodo create della factory può accettare parametri, che passa poi al costruttore. Aggiungiamo per 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;
}
Poiché il nome del parametro nel costruttore ($authorId) coincide con il nome del parametro nel metodo della
factory, Nette DI lo passa automaticamente.
Definizione avanzata
La definizione si può scrivere anche su più righe, con la chiave implement:
services:
articleFactory:
implement: ArticleFactory
Questo formato più lungo permette di indicare argomenti aggiuntivi per il costruttore tramite la chiave arguments
e ulteriori configurazioni tramite setup, come nelle normali definizioni dei servizi.
Esempio: se il metodo create() non accettasse il parametro $authorId, potremmo indicare nella
configurazione un valore fisso da passare al costruttore di Article:
services:
articleFactory:
implement: ArticleFactory
arguments:
authorId: 123
Al contrario, se create() accettasse $authorId ma questo non facesse parte del costruttore e venisse
invece passato tramite un metodo come Article::setAuthorId(), faremmo riferimento al parametro nella sezione
setup:
services:
articleFactory:
implement: ArticleFactory
setup:
- setAuthorId($authorId)
Accessor
Oltre alle factory, Nette sa generare anche i cosiddetti accessor. Sono oggetti con un metodo get() che
restituisce un determinato servizio dal container DI. Chiamate ripetute a get() restituiscono sempre la stessa
istanza.
Gli accessor offrono il caricamento pigro delle dipendenze. Immaginate una classe che registra gli errori in un database
dedicato. Se questa classe ricevesse la connessione al database tramite dependency injection nel costruttore, la connessione
verrebbe sempre stabilita, anche se gli errori sono rari e la connessione resta quasi sempre inutilizzata. La classe può invece
ricevere un accessor. L'oggetto database (la connessione) viene creato solo quando il metodo get() dell'accessor
viene chiamato la prima volta.
Come si crea un accessor? Basta scrivere un'interfaccia e Nette DI ne genererà l'implementazione. L'interfaccia deve avere
esattamente un metodo chiamato get, senza parametri e con un tipo di ritorno dichiarato:
interface PDOAccessor
{
function get(): PDO;
}
Aggiungete l'accessor al file di configurazione, insieme alla definizione del servizio che deve restituire:
services:
- PDOAccessor
- PDO(%dsn%, %user%, %password%)
Poiché l'accessor restituisce un servizio PDO e nella configurazione ne è definito uno solo di quel tipo,
l'accessor restituirà quel servizio. Se esistessero più servizi di quel tipo, indicate per nome quale deve restituire
l'accessor, per esempio - PDOAccessor(@db1).
Multifactory/accessor
Finora le nostre factory e i nostri accessor potevano creare o restituire un solo tipo di oggetto. Potete però creare
facilmente delle multifactory, che uniscono le caratteristiche delle factory e degli accessor. L'interfaccia di un componente del
genere può contenere più metodi chiamati create<Nome>() e get<Nome>(), per esempio:
interface MultiFactory
{
function createArticle(): Article;
function getDb(): PDO;
}
Invece di iniettare più factory e accessor separati, potete quindi iniettare un unico componente più completo.
In alternativa ai metodi multipli si può usare get() con un parametro:
interface MultiFactoryAlt
{
function get($name): PDO;
}
MultiFactory::getDb() fa allora la stessa cosa di MultiFactoryAlt::get('db'). Questa notazione
alternativa ha però lo svantaggio che i valori supportati per $name non sono esplicitamente chiari dalla firma
dell'interfaccia. Inoltre nell'interfaccia non potete definire tipi di ritorno diversi per valori diversi di
$name.
Al posto di get($name) l'interfaccia può dichiarare create($name), che restituisce una nuova istanza
a ogni chiamata (mentre get() ne restituisce una condivisa). L'interfaccia può contenere un solo metodo con
parametro di questo tipo. Se il tipo di ritorno del metodo è nullable (per esempio ?PDO), per un $name
sconosciuto restituisce null invece di sollevare un'eccezione.
Definizione con un elenco
Potete definire una multifactory nella configurazione con un elenco, scrivendo i servizi in linea:
services:
- MultiFactory(
article: Article() # definisce createArticle()
db: PDO(%dsn%, %user%, %password%) # definisce getDb()
)
In alternativa, nella definizione della multifactory potete fare riferimento a servizi esistenti tramite i riferimenti:
services:
article: Article
- PDO(%dsn%, %user%, %password%)
- MultiFactory(
article: @article # definisce createArticle()
db: @\PDO # definisce getDb()
)
Definizione con i tag
Un altro modo di definire una multifactory è usare i tag. Il valore del tag determina il nome del metodo corrispondente:
services:
article:
create: Article
tags: {multi: article} # definisce createArticle()
db:
create: PDO(%dsn%, %user%, %password%)
tags: {multi: db} # definisce getDb()
- MultiFactory(tagged: multi)