Factory-uri generate
Nette DI poate genera automat codul factory-urilor pe baza interfețelor, ceea ce vă economisește scrierea codului.
Un factory este o clasă care produce și configurează obiecte. Le transmite deci și dependențele lor. Vă rugăm să nu confundați cu pattern-ul de design factory method, care descrie un mod specific de utilizare a factory-urilor și nu are legătură cu acest subiect.
Cum arată un astfel de factory am arătat în capitolul introductiv:
class ArticleFactory
{
public function __construct(
private Nette\Database\Connection $db,
) {
}
public function create(): Article
{
return new Article($this->db);
}
}
Nette DI poate genera automat codul factory-urilor. Tot ce trebuie să faceți este să creați o interfață și Nette DI va
genera implementarea. Interfața trebuie să aibă exact o metodă numită create
și să declare tipul
returnat:
interface ArticleFactory
{
function create(): Article;
}
Deci, factory-ul ArticleFactory
are o metodă create
, care creează obiecte Article
.
Clasa Article
poate arăta, de exemplu, astfel:
class Article
{
public function __construct(
private Nette\Database\Connection $db,
) {
}
}
Adăugăm factory-ul în fișierul de configurare:
services:
- ArticleFactory
Nette DI va genera implementarea corespunzătoare a factory-ului.
În codul care utilizează factory-ul, solicităm astfel obiectul conform interfeței și Nette DI va utiliza implementarea generată:
class UserController
{
public function __construct(
private ArticleFactory $articleFactory,
) {
}
public function foo()
{
// lăsăm factory-ul să creeze obiectul
$article = $this->articleFactory->create();
}
}
Factory parametrizat
Metoda factory create
poate accepta parametri, pe care îi transmite apoi constructorului. Să completăm, de
exemplu, clasa Article
cu ID-ul autorului articolului:
class Article
{
public function __construct(
private Nette\Database\Connection $db,
private int $authorId,
) {
}
}
Adăugăm parametrul și în factory:
interface ArticleFactory
{
function create(int $authorId): Article;
}
Datorită faptului că parametrul din constructor și parametrul din factory se numesc la fel, Nette DI îi transmite complet automat.
Definiție avansată
Definiția poate fi scrisă și într-o formă multi-linie folosind cheia implement
:
services:
articleFactory:
implement: ArticleFactory
La scrierea în această formă mai lungă, este posibil să se specifice argumente suplimentare pentru constructor în cheia
arguments
și configurație suplimentară folosind setup
, la fel ca la serviciile obișnuite.
Exemplu: dacă metoda create()
nu ar accepta parametrul $authorId
, am putea specifica o valoare
fixă în configurație, care ar fi transmisă constructorului Article
:
services:
articleFactory:
implement: ArticleFactory
arguments:
authorId: 123
Sau invers, dacă create()
ar accepta parametrul $authorId
, dar acesta nu ar face parte din
constructor și s-ar transmite prin metoda Article::setAuthorId()
, ne-am referi la el în secțiunea
setup
:
services:
articleFactory:
implement: ArticleFactory
setup:
- setAuthorId($authorId)
Accessor
Nette poate genera, pe lângă factory-uri, și așa-numiții accesori. Aceștia sunt obiecte cu o metodă get()
,
care returnează un anumit serviciu din containerul DI. Apelarea repetată a get()
returnează mereu aceeași
instanță.
Accesorii oferă lazy-loading pentru dependențe. Să presupunem că avem o clasă care scrie erori într-o bază de date
specială. Dacă această clasă ar primi conexiunea la baza de date ca dependență prin constructor, conexiunea ar trebui
creată întotdeauna, deși în practică eroarea apare doar excepțional și, prin urmare, în majoritatea cazurilor conexiunea
ar rămâne neutilizată. În schimb, clasa primește un accesor și abia atunci când se apelează get()
, se
creează obiectul bazei de date:
Cum se creează un accesor? Este suficient să scrieți o interfață și Nette DI va genera implementarea. Interfața trebuie
să aibă exact o metodă numită get
și să declare tipul returnat:
interface PDOAccessor
{
function get(): PDO;
}
Adăugăm accesorul în fișierul de configurare, unde este definit și serviciul pe care îl va returna:
services:
- PDOAccessor
- PDO(%dsn%, %user%, %password%)
Deoarece accesorul returnează un serviciu de tip PDO
și în configurație există un singur astfel de serviciu,
îl va returna tocmai pe acesta. Dacă ar exista mai multe servicii de tipul respectiv, specificăm serviciul returnat folosind
numele, de ex. - PDOAccessor(@db1)
.
Factory/Accesor multiplu
Factory-urile și accesorii noștri au putut până acum să producă sau să returneze doar un singur obiect. Dar se pot crea
foarte ușor și factory-uri multiple combinate cu accesori. Interfața unei astfel de clase va conține un număr arbitrar de
metode cu numele create<name>()
și get<name>()
, de ex.:
interface MultiFactory
{
function createArticle(): Article;
function getDb(): PDO;
}
Deci, în loc să transmitem mai multe factory-uri și accesori generați, transmitem un factory mai complex care poate face mai multe lucruri.
Alternativ, în loc de mai multe metode, se poate folosi get()
cu un parametru:
interface MultiFactoryAlt
{
function get($name): PDO;
}
Atunci este valabil că MultiFactory::getArticle()
face același lucru ca
MultiFactoryAlt::get('article')
. Cu toate acestea, scrierea alternativă are dezavantajul că nu este evident ce
valori $name
sunt suportate și, logic, nici nu se pot distinge în interfață diferite valori returnate pentru
diferite $name
.
Definiție prin listă
În acest mod se poate defini un factory multiplu în configurație:
services:
- MultiFactory(
article: Article # definește createArticle()
db: PDO(%dsn%, %user%, %password%) # definește getDb()
)
Sau ne putem referi în definiția factory-ului la servicii existente folosind o referință:
services:
article: Article
- PDO(%dsn%, %user%, %password%)
- MultiFactory(
article: @article # definește createArticle()
db: @\PDO # definește getDb()
)
Definiție prin tag-uri
A doua opțiune este utilizarea tag-urilor pentru definire:
services:
- App\Core\RouterFactory::createRouter
- App\Model\DatabaseAccessor(
db1: @database.db1.explorer
)