Генерирани фабрики
Nette DI може автоматично да генерира код на фабрики въз основа на интерфейси, което ви спестява писане на код.
Фабриката е клас, който произвежда и конфигурира обекти. Следователно тя им предава и техните зависимости. Моля, не бъркайте с дизайн патърна factory method, който описва специфичен начин за използване на фабрики и не е свързан с тази тема.
Как изглежда такава фабрика, показахме в уводната глава:
class ArticleFactory
{
public function __construct(
private Nette\Database\Connection $db,
) {
}
public function create(): Article
{
return new Article($this->db);
}
}
Nette DI може автоматично да генерира код на фабрики. Всичко, което
трябва да направите, е да създадете интерфейс и Nette DI ще генерира
имплементацията. Интерфейсът трябва да има точно един метод с име
create
и да декларира тип на връщане:
interface ArticleFactory
{
function create(): Article;
}
Така фабриката ArticleFactory
има метод create
, който създава
обекти Article
. Класът Article
може да изглежда
например така:
class Article
{
public function __construct(
private Nette\Database\Connection $db,
) {
}
}
Добавяме фабриката към конфигурационния файл:
services:
- ArticleFactory
Nette DI ще генерира съответната имплементация на фабриката.
В кода, който използва фабриката, изискваме обект по интерфейс и Nette DI ще използва генерираната имплементация:
class UserController
{
public function __construct(
private ArticleFactory $articleFactory,
) {
}
public function foo()
{
// оставяме фабриката да създаде обект
$article = $this->articleFactory->create();
}
}
Параметризирана фабрика
Фабричният метод create
може да приема параметри, които след това
предава на конструктора. Нека добавим например ID на автора на статията
към класа Article
:
class Article
{
public function __construct(
private Nette\Database\Connection $db,
private int $authorId,
) {
}
}
Добавяме параметъра и към фабриката:
interface ArticleFactory
{
function create(int $authorId): Article;
}
Тъй като параметърът в конструктора и параметърът във фабриката имат едно и също име, Nette DI ги предава напълно автоматично.
Разширена дефиниция
Дефиницията може да бъде записана и в многоредов вид, като се
използва ключът implement
:
services:
articleFactory:
implement: ArticleFactory
При писане по този по-дълъг начин е възможно да се посочат
допълнителни аргументи за конструктора в ключа arguments
и
допълнителна конфигурация с помощта на setup
, точно както при
обикновените сървиси.
Пример: ако методът create()
не приемаше параметъра $authorId
,
бихме могли да посочим фиксирана стойност в конфигурацията, която да
бъде предадена на конструктора на Article
:
services:
articleFactory:
implement: ArticleFactory
arguments:
authorId: 123
Или обратно, ако create()
приемаше параметъра $authorId
, но той
не беше част от конструктора и се предаваше чрез метода
Article::setAuthorId()
, щяхме да се обърнем към него в секцията
setup
:
services:
articleFactory:
implement: ArticleFactory
setup:
- setAuthorId($authorId)
Accessor
Освен фабрики, Nette може да генерира и т.нар. аксесори. Това са обекти с
метод get()
, който връща определен сървис от DI контейнера.
Повторното извикване на get()
винаги връща същата инстанция.
Аксесорите осигуряват lazy-loading за зависимостите. Да приемем, че имаме
клас, който записва грешки в специална база данни. Ако този клас
получаваше връзката с базата данни като зависимост чрез конструктора,
връзката винаги трябваше да се създава, въпреки че на практика грешка
се появява само рядко и следователно връзката в повечето случаи би
останала неизползвана. Вместо това класът получава аксесор и едва
когато се извика неговият get()
, се създава обектът на
базата данни:
Как да създадем аксесор? Просто напишете интерфейс и Nette DI ще генерира
имплементацията. Интерфейсът трябва да има точно един метод с име
get
и да декларира тип на връщане:
interface PDOAccessor
{
function get(): PDO;
}
Добавяме аксесора към конфигурационния файл, където е и дефиницията на сървиса, който той ще връща:
services:
- PDOAccessor
- PDO(%dsn%, %user%, %password%)
Тъй като аксесорът връща сървис от тип PDO
и в конфигурацията
има само един такъв сървис, той ще върне точно него. Ако имаше повече
сървиси от този тип, щяхме да посочим връщания сървис по име, напр.
- PDOAccessor(@db1)
.
Множествена фабрика/аксесор
Досега нашите фабрики и аксесори винаги са можели да произвеждат или
връщат само един обект. Въпреки това е много лесно да се създадат и
множествени фабрики, комбинирани с аксесори. Интерфейсът на такъв клас
ще съдържа произволен брой методи с имена create<name>()
и
get<name>()
, напр.:
interface MultiFactory
{
function createArticle(): Article;
function getDb(): PDO;
}
Така че, вместо да предаваме няколко генерирани фабрики и аксесори, предаваме една по-сложна фабрика, която може да прави повече неща.
Алтернативно, вместо няколко метода, може да се използва get()
с
параметър:
interface MultiFactoryAlt
{
function get($name): PDO;
}
Тогава MultiFactory::getArticle()
прави същото като
MultiFactoryAlt::get('article')
. Въпреки това, алтернативният запис има
недостатъка, че не е ясно кои стойности на $name
се поддържат и
логично не е възможно да се разграничат различни върнати стойности за
различни $name
в интерфейса.
Дефиниция чрез списък
По този начин може да се дефинира множествена фабрика в конфигурацията:
services:
- MultiFactory(
article: Article # дефинира createArticle()
db: PDO(%dsn%, %user%, %password%) # дефинира getDb()
)
Или можем да се обърнем към съществуващи сървиси в дефиницията на фабриката чрез референция:
services:
article: Article
- PDO(%dsn%, %user%, %password%)
- MultiFactory(
article: @article # дефинира createArticle()
db: @\PDO # дефинира getDb()
)
Дефиниция с помощта на тагове
Втората възможност е да се използват тагове за дефиницията:
services:
- App\Core\RouterFactory::createRouter
- App\Model\DatabaseAccessor(
db1: @database.db1.explorer
)