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
)