Generované továrny
******************
.[perex]
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í 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 |introduction#Továrna]:
```php
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:
```php
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ě:
```php
class Article
{
public function __construct(
private Nette\Database\Connection $db,
) {
}
}
```
Továrnu přidáme do konfiguračního souboru:
```neon
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:
```php
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 konstruktoru. Doplňme například třídu `Article` o ID autora článku:
```php
class Article
{
public function __construct(
private Nette\Database\Connection $db,
private int $authorId,
) {
}
}
```
Parametr přidáme také do továrny:
```php
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`:
```neon
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`:
```neon
services:
articleFactory:
implement: ArticleFactory
arguments:
authorId: 123
```
Nebo naopak pokud by `create()` parametr `$authorId` přijímala, ale nebyl by součástí konstruktoru a předával se metodou `Article::setAuthorId()`, odkázali bychom se na něj v sekci `setup`:
```neon
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.
Accessory 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ě, takže by spojení povětšinou zůstalo nevyužité. Místo toho si třída nechá předat 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 bezparametrickou metodu s názvem `get` a deklarovat návratový typ:
```php
interface PDOAccessor
{
function get(): PDO;
}
```
Accessor přidáme do konfiguračního souboru, kde je také definice služby, kterou bude vracet:
```neon
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ř.:
```php
interface MultiFactory
{
function createArticle(): Article;
function getDb(): PDO;
}
```
Takže místo toho, abychom 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 parametrem:
```php
interface MultiFactoryAlt
{
function get($name): PDO;
}
```
Pak platí, že `MultiFactory::getDb()` dělá totéž jako `MultiFactoryAlt::get('db')`. 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é typy pro různé hodnoty `$name`.
Místo `get($name)` může rozhraní deklarovat `create($name)`, které při každém volání vrací novou instanci (zatímco `get()` vrací sdílenou). Rozhraní smí obsahovat jen jednu takovou parametrickou metodu. Pokud je návratový typ metody nullable (např. `?PDO`), vrátí pro neznámé `$name` místo vyhození výjimky `null`.
Definice seznamem
-----------------
Vícenásobnou továrnu lze v konfiguraci definovat seznamem, ve kterém jsou služby zapsané přímo: .{data-version:3.2.0}
```neon
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:
```neon
services:
article: Article
- PDO(%dsn%, %user%, %password%)
- MultiFactory(
article: @article # definuje createArticle()
db: @\PDO # definuje getDb()
)
```
Definice pomocí tagů
--------------------
Další možností, jak definovat vícenásobnou továrnu, je využít [tagy |services#Tagy]. Hodnota tagu určuje název odpovídající metody:
```neon
services:
article:
create: Article
tags: {multi: article} # definuje createArticle()
db:
create: PDO(%dsn%, %user%, %password%)
tags: {multi: db} # definuje getDb()
- MultiFactory(tagged: multi)
```
{{maintitle: Generované továrny v Nette DI: kód podle rozhraní}}
{{description: Stačí napsat rozhraní a Nette DI vygeneruje kód továrny za vás. Parametrizované továrny, accessory, vícenásobné továrny a pokročilé definice.}}
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í 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 konstruktoru. 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řijímala, 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.
Accessory 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ě, takže by spojení povětšinou zůstalo nevyužité. Místo toho
si třída nechá předat 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
bezparametrickou 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, abychom 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 parametrem:
interface MultiFactoryAlt
{
function get($name): PDO;
}
Pak platí, že MultiFactory::getDb() dělá totéž jako MultiFactoryAlt::get('db'). 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é typy pro různé hodnoty $name.
Místo get($name) může rozhraní deklarovat create($name), které při každém volání vrací
novou instanci (zatímco get() vrací sdílenou). Rozhraní smí obsahovat jen jednu takovou parametrickou metodu.
Pokud je návratový typ metody nullable (např. ?PDO), vrátí pro neznámé $name místo vyhození
výjimky null.
Definice seznamem
Vícenásobnou továrnu lze v konfiguraci definovat seznamem, ve kterém jsou služby
zapsané přímo:
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ů
Další možností, jak definovat vícenásobnou továrnu, je využít tagy. Hodnota tagu určuje název odpovídající
metody:
services:
article:
create: Article
tags: {multi: article} # definuje createArticle()
db:
create: PDO(%dsn%, %user%, %password%)
tags: {multi: db} # definuje getDb()
- MultiFactory(tagged: multi)