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()
	{
		// let the factory create an object
		$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()
	{
		// let the factory create an object
		$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
	)