Generované továrny
Nette DI umí automaticky generovat kód továren na základě rozhraní, což vám ušetří psaní kódu.
Továrna je třída, která vyrábí a konfiguruje objekty. Předává jim tedy i jejich závislosti. Nezaměňujte prosím s návrhovým vzorem factory method, který popisuje specifický způsob využití továren a s tímto tématem nesouvisí.
Jak taková továrna vypadá jsme si ukázali v úvodní kapitole:
class ArticleFactory
{
public function __construct(
private Nette\Database\Connection $db,
) {
}
public function create(): Article
{
return new Article($this->db);
}
}
Nette DI umí kód továren automaticky generovat. Vše, co musíte udělat, je vytvořit rozhraní a Nette DI vygeneruje
implementaci. Rozhraní musí mít přesně jednu metodu s názvem create
a deklarovat návratový typ:
interface ArticleFactory
{
function create(): Article;
}
Tedy továrna ArticleFactory
má metodu create
, která vytváří objekty Article
.
Třída Article
může vypadat třeba následovně:
class Article
{
public function __construct(
private Nette\Database\Connection $db,
) {
}
}
Továrnu přidáme do konfiguračního souboru:
services:
- ArticleFactory
Nette DI vygeneruje odpovídající implementaci továrny.
V kódu, který továrnu používá, si tak vyžádáme objekt podle rozhraní a Nette DI použije vygenerovanou implementaci:
class UserController
{
public function __construct(
private ArticleFactory $articleFactory,
) {
}
public function foo()
{
// necháme továrnu vytvořit objekt
$article = $this->articleFactory->create();
}
}
Parametrizovaná továrna
Tovární metoda create
může přijímat parametry, které poté předá do konstrukturu. Doplňme například
třídu Article
o ID autora článku:
class Article
{
public function __construct(
private Nette\Database\Connection $db,
private int $authorId,
) {
}
}
Parametr přidáme také do továrny:
interface ArticleFactory
{
function create(int $authorId): Article;
}
Díky tomu, že se parametr v konstruktoru i parametr v továrně jmenují stejně, Nette DI je zcela automaticky předá.
Pokročilá definice
Definici lze zapsat i ve víceřádkové podobě za použití klíče implement
:
services:
articleFactory:
implement: ArticleFactory
Při zápisu tímto delším způsobem je možné uvést další argumenty pro konstruktor v klíči arguments
a
doplňující konfiguraci pomocí setup
, stejně, jako u běžných služeb.
Příklad: pokud by metoda create()
nepřijímala parametr $authorId
, mohli bychom uvést pevnou
hodnotu v konfiguraci, která by se předávala do konstruktoru Article
:
services:
articleFactory:
implement: ArticleFactory
arguments:
authorId: 123
Nebo naopak pokud by create()
parametr $authorId
přijimala, ale nebyl by součástí konstruktoru a
předával se metodou Article::setAuthorId()
, odkázali bychom se na něj v sekci setup
:
services:
articleFactory:
implement: ArticleFactory
setup:
- setAuthorId($authorId)
Accessor
Nette umí krom továren generovat i tzv. accessory. Jde o objekty s metodou get()
, která vrací určitou
službu z DI kontejneru. Opakované volání get()
vrací stále tutéž instanci.
Accessor poskytují závislostem lazy-loading. Mějme třídu, která zapisuje chyby do speciální databáze. Když by si tato
třída nechávala připojení k databázi předávat jako závislost konstruktorem, muselo by se připojení vždycky vytvořit,
ačkoliv v praxi se chyba objeví jen výjimečně a tedy povětšinou by zůstalo spojení nevyužité. Místo toho si tak
třída předá accessor a teprve když se zavolá jeho get()
, dojde k vytvoření objektu databáze:
Jak accessor vytvořit? Stačí napsat rozhraní a Nette DI vygeneruje implementaci. Rozhraní musí mít přesně jednu metodu
s názvem get
a deklarovat návratový typ:
interface PDOAccessor
{
function get(): PDO;
}
Accessor přidáme do konfiguračního souboru, kde je také definice služby, kterou bude vracet:
services:
- PDOAccessor
- PDO(%dsn%, %user%, %password%)
Protože accessor vrací službu typu PDO
a v konfiguraci je jediná taková služba, bude vracet právě ji.
Pokud by služeb daného typu bylo víc, určíme vracenou službu pomocí názvu, např. - PDOAccessor(@db1)
.
Vícenásobná továrna/accessor
Naše továrny a accessory uměly zatím vždy vyrábět nebo vracet jen jeden objekt. Lze ale velmi snadno vytvořit
i vícenásobné továrny kombinované s accessory. Rozhraní takové třídy bude obsahovat libovolný počet metod s názvy
create<name>()
a get<name>()
, např.:
interface MultiFactory
{
function createArticle(): Article;
function getDb(): PDO;
}
Takže místo toho, abych si předávali několik generovaných továren a accessorů, předáme jednu komplexnější továrnu, která toho umí víc.
Alternativně lze místo několika metod použít get()
s parameterem:
interface MultiFactoryAlt
{
function get($name): PDO;
}
Pak platí, že MultiFactory::getArticle()
dělá totéž jako MultiFactoryAlt::get('article')
.
Nicméně alternativní zápis má tu nevýhodu, že není zřejmé, jaké hodnoty $name
jsou podporované a logicky
také nelze v rozhraní odlišit různé návratové hodnoty pro různé $name
.
Definice seznamem
Tímto způsobem lze definovat vícenásobnou továrnu v konfiguraci:
services:
- MultiFactory(
article: Article # definuje createArticle()
db: PDO(%dsn%, %user%, %password%) # definuje getDb()
)
Nebo se můžeme v definici továrny odkázat na existující služby pomocí reference:
services:
article: Article
- PDO(%dsn%, %user%, %password%)
- MultiFactory(
article: @article # definuje createArticle()
db: @\PDO # definuje getDb()
)
Definice pomocí tagů
Druhou možností je využít k definici tagy:
services:
- App\Core\RouterFactory::createRouter
- App\Model\DatabaseAccessor(
db1: @database.db1.explorer
)