Nette Documentation Preview

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

.[perex]
Nette DI може автоматично генерувати код фабрик на основі інтерфейсу, що позбавляє вас від написання коду.

Фабрика - це клас, який створює та налаштовує об'єкти. Тому він також передає їм їхні залежності. Будь ласка, не плутайте з патерном проектування *factory method*, який описує специфічний спосіб використання фабрик і не має відношення до цієї теми.

Ми показали, як виглядає така фабрика у [вступному розділі |introduction#factory]:

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

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

Усе, що вам потрібно зробити, це створити інтерфейс, а Nette DI згенерує його реалізацію. Інтерфейс повинен мати рівно один метод з ім'ям `create` і оголошувати тип, що повертається:

```php
interface ArticleFactory
{
	function create(): Article;
}
```

Отже, фабрика `ArticleFactory` має метод `create`, який створює об'єкти `Article`. Клас `Article` може виглядати, наприклад, таким чином:

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

Додайте фабрику до файлу конфігурації:

```neon
services:
	- ArticleFactory
```

Nette DI створить відповідну реалізацію фабрики.

Таким чином, у коді, що використовує фабрику, ми запитуємо об'єкт за інтерфейсом, а Nette DI використовує згенеровану реалізацію:

```php
class UserController
{
	public function __construct(
		private ArticleFactory $articleFactory,
	) {
	}

	public function foo()
	{
		// дозволити фабриці створити об'єкт
		$article = $this->articleFactory->create();
	}
}
```


Параметризована фабрика .[#toc-parameterized-factory]
=====================================================

Метод фабрики `create` може приймати параметри, які він потім передає конструктору. Наприклад, давайте додамо ID автора статті в клас `Article`:

```php
class Article
{
	public function __construct(
		private Nette\Database\Connection $db,
		private int $authorId,
	) {
	}
}
```

Ми також додамо параметр до фабрики:

```php
interface ArticleFactory
{
	function create(int $authorId): Article;
}
```

Оскільки параметр у конструкторі та параметр у фабриці мають однакове ім'я, Nette DI передасть їх автоматично.


Розширене визначення .[#toc-advanced-definition]
================================================

Визначення також може бути записано в багаторядковій формі за допомогою ключа `implement`:

```neon
services:
	articleFactory:
		implement: ArticleFactory
```

При написанні таким подовженим способом можна надати додаткові аргументи для конструктора в ключі `arguments` і додаткову конфігурацію за допомогою `setup`, як і для звичайних сервісів.

Приклад: Якби метод `create()` не приймав параметр `$authorId`, ми могли б вказати в конфігурації фіксоване значення, яке передавалося б у конструктор `Article`:

```neon
services:
	articleFactory:
		implement: ArticleFactory
		arguments:
			authorId: 123
```

Або, навпаки, якби `create()` приймав параметр `$authorId`, але він не був частиною конструктора і був переданий методом `Article::setAuthorId()`, ми б звернулися до нього в секції `setup`:

```neon
services:
	articleFactory:
		implement: ArticleFactory
		setup:
			- setAuthorId($authorId)
```


Аксесор .[#toc-accessor]
========================

Крім фабрик, Nette також може генерувати так звані аксесори. Це об'єкти з методом `get()`, який повертає конкретний сервіс з DI-контейнера. Повторні виклики `get()` як і раніше повертають один і той самий екземпляр.

Аксесор забезпечує ледаче завантаження залежностей. Нехай у нас є клас, який записує помилки в спеціальну базу даних. Якби в цьому класі з'єднання з базою даних передавалося конструктором як залежність, то з'єднання доводилося б створювати завжди, хоча на практиці помилка виникає дуже рідко, і тому з'єднання зазвичай залишалося б невикористаним.
Замість цього клас передає метод доступу, і тільки при виклику його `get()` створюється об'єкт бази даних:

Як створити аксесор? Просто напишіть інтерфейс, і Nette DI згенерує реалізацію. Інтерфейс повинен мати рівно один метод з ім'ям `get` і оголосити тип, що повертається:

```php
interface PDOAccessor
{
	function get(): PDO;
}
```

Ми додамо аксесор у файл конфігурації, який також містить визначення сервісу, який він поверне:

```neon
services:
	- PDOAccessor
	- PDO(%dsn%, %user%, %password%)
```

Оскільки метод доступу повертає службу `PDO`, а в конфігурації є тільки один такий сервіс, він поверне його. Якщо сервісів цього типу більше, ми визначаємо сервіс, що повертається, за іменем, наприклад `- PDOAccessor(@db1)`.


Кілька фабрик/аксесорів .[#toc-multifactory-accessor]
=====================================================
Досі наші фабрики та аксесори завжди могли виробляти або повертати лише один об'єкт. Однак дуже легко створити кілька фабрик у поєднанні з аксесорами. Інтерфейс такого класу міститиме будь-яку кількість методів з іменами `create<name>()` и `get<name>()`, наприклад:

```php
interface MultiFactory
{
	function createArticle(): Article;
	function getDb(): PDO;
}
```

Тому замість того, щоб передавати кілька згенерованих фабрик і аксесорів, ми збираємося передати ще одну складну фабрику, яка може робити більше.

Крім того, ви можете використовувати `get()` з параметром замість декількох методів:

```php
interface MultiFactoryAlt
{
	function get($name): PDO;
}
```

У цьому випадку `MultiFactory::getArticle()` робить те ж саме, що і `MultiFactoryAlt::get('article')`. Однак альтернативний синтаксис має кілька недоліків. Незрозуміло, які значення `$name` підтримуються, і неможливо вказати тип повернення в інтерфейсі при використанні декількох різних значень `$name`.


Визначення списку .[#toc-definition-with-a-list]
------------------------------------------------
Цей спосіб можна використовувати для визначення декількох фабрик у конфігурації: .{data-version:3.2.0}

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

Або у визначенні фабрики ми можемо посилатися на існуючі сервіси за допомогою посилання:

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


Визначення з використанням тегів .[#toc-definition-with-tags]
-------------------------------------------------------------

Другий варіант - використовувати [теги |services#Tags] для визначення:

```neon
services:
	- App\Router\RouterFactory::createRouter
	- App\Model\DatabaseAccessor(
		db1: @database.db1.context
	)
```

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

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\Router\RouterFactory::createRouter
	- App\Model\DatabaseAccessor(
		db1: @database.db1.context
	)