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\Core\RouterFactory::createRouter
- App\Model\DatabaseAccessor(
db1: @database.db1.explorer
)