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 createFoo(): Model\Foo;
	function getDb(): PDO;
}
```

Поэтому вместо того, чтобы передавать несколько сгенерированных фабрик и аксессоров, мы собираемся передать ещё одну сложную фабрику, которая может делать больше.

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

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

Затем `MultiFactory::createArticle()` делает то же самое, что и `MultiFactoryAlt::create('article')`. Однако альтернативная нотация имеет тот недостаток, что неясно, какие значения `$name` поддерживаются, и логически невозможно различить разные возвращаемые значения для разных `$name` в интерфейсе.


Определение списка .[#toc-definition-with-a-list]
-------------------------------------------------

А как определить множественную фабрику в конфигурации? Мы создадим три сервиса, которые будут создавать/возвращать, а затем и саму фабрику:

```neon
services:
	article: Article
	- Model\Foo
	- PDO(%dsn%, %user%, %password%)
	- MultiFactory(
		article: @article  # createArticle()
		foo: @Model\Foo    # createFoo()
		db: @\PDO          # 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 createFoo(): Model\Foo;
	function getDb(): PDO;
}

Поэтому вместо того, чтобы передавать несколько сгенерированных фабрик и аксессоров, мы собираемся передать ещё одну сложную фабрику, которая может делать больше.

Как вариант, вместо нескольких методов можно использовать параметры create() и get():

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

Затем MultiFactory::createArticle() делает то же самое, что и MultiFactoryAlt::create('article'). Однако альтернативная нотация имеет тот недостаток, что неясно, какие значения $name поддерживаются, и логически невозможно различить разные возвращаемые значения для разных $name в интерфейсе.

Определение списка

А как определить множественную фабрику в конфигурации? Мы создадим три сервиса, которые будут создавать/возвращать, а затем и саму фабрику:

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

Определения с использованием тегов

Второй вариант — использовать теги для определения:

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