Генерирани фабрики

Nette DI може автоматично да генерира фабричен код въз основа на интерфейса, като ви спестява писането на код.

Фабриката е клас, който създава и конфигурира обекти. Следователно той предава на тях и техните зависимости. Моля, не бъркайте с шаблона за проектиране фабричен метод, който описва специфичен начин за използване на фабрики и не е свързан с тази тема.

Показахме как изглежда една такава фабрика в уводната глава:

class ArticleFactory
{
	public function __construct(
		private Nette\Database\Connection $db,
	) {
	}

	public function create(): Article
	{
		return new Article($this->db);
	}
}

Всичко, което трябва да направите, е да създадете интерфейс и 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 може да приема параметри, които след това предава на конструктора. Например нека добавим идентификатор на автор на статия към класа 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() все още връщат една и съща инстанция.

Този аксесоар осигурява лениво зареждане на зависимостите. Да предположим, че имаме клас, който записва грешки в специална база данни. Ако този клас предаде на конструктора връзка към база данни като зависимост, тя винаги ще трябва да бъде създадена, въпреки че на практика много рядко ще възникне грешка и затова връзката обикновено ще остане неизползвана. Вместо това класът предава метод за достъп и само когато той бъде извикан 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                      # defines createArticle()
		db: PDO(%dsn%, %user%, %password%)    # defines getDb()
	)

Или в дефиницията на фабриката можем да се позовем на съществуващи услуги с помощта на референция:

services:
	article: Article
	- PDO(%dsn%, %user%, %password%)
	- MultiFactory(
		article: @article    # defines createArticle()
		db: @\PDO            # defines getDb()
	)

Дефиниции с помощта на етикети

Вторият вариант е да използвате тагове за дефиниции:

services:
	- App\Router\RouterFactory::createRouter
	- App\Model\DatabaseAccessor(
		db1: @database.db1.context
	)
версия: 3.x