Згенеровані фабрики

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 згенерує його реалізацію. Інтерфейс повинен мати рівно один метод з ім'ям 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)

Аксесор

Крім фабрик, 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\Core\RouterFactory::createRouter
	- App\Model\DatabaseAccessor(
		db1: @database.db1.context
	)
версію: 3.x