Nette Documentation Preview

syntax
Генерируемые фабрики
********************

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

Фабрика - это класс, отвечающий за создание объектов и передачу их зависимостей. Не путайте это с шаблоном проектирования *factory method*, который описывает конкретный способ использования фабрик и с этой темой не связан.

Как выглядит такая фабрика, мы показали во [вводной главе |introduction#Фабрика]:

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

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

Nette DI умеет порождать код фабрики автоматически. Вам достаточно создать интерфейс, а реализацию сгенерирует 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();
	}
}
```


Фабрика с параметрами
=====================

Метод фабрики `create` может принимать параметры, которые он затем передаёт в конструктор. Например, добавим в класс `Article` идентификатор автора статьи:

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

Добавим параметр и в фабрику:

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

Поскольку имя параметра в конструкторе (`$authorId`) совпадает с именем параметра в методе фабрики, Nette DI передаёт его автоматически.


Расширенное определение
=======================

Определение можно записать и в многострочной форме с помощью ключа `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)
```


Аксессор
========

Помимо фабрик Nette умеет порождать и так называемые аксессоры. Это объекты с методом `get()`, возвращающим определённый сервис из DI-контейнера. Повторные вызовы `get()` всегда возвращают один и тот же экземпляр.

Аксессоры дают отложенную загрузку зависимостей. Представьте класс, который записывает ошибки в отдельную базу данных. Если бы этот класс получал соединение с базой через внедрение в конструктор, соединение устанавливалось бы всегда, даже если ошибки случаются редко и соединение почти всё время не используется. Вместо этого класс может получить аксессор. Объект базы данных (соединение) создаётся только тогда, когда метод `get()` аксессора вызывается впервые.

Как создать аксессор? Достаточно написать интерфейс, а реализацию сгенерирует Nette DI. У интерфейса должен быть ровно один метод с именем `get`, без параметров и с объявленным типом возвращаемого значения:

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

Добавьте аксессор в конфигурационный файл вместе с определением сервиса, который он должен возвращать:

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

Поскольку аксессор возвращает сервис `PDO`, а такой сервис в конфигурации определён только один, аксессор вернёт именно его. Если сервисов такого типа несколько, укажите по имени, какой из них должен вернуть аксессор, например `- PDOAccessor(@db1)`.


Мультифабрика и мультиаксессор
==============================

До сих пор наши фабрики и аксессоры умели создавать или возвращать только один тип объектов. Однако вы легко можете создать мультифабрики, сочетающие возможности фабрик и аксессоров. Интерфейс такой составляющей может содержать несколько методов с именами `create<Name>()` и `get<Name>()`, например:

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

Так что вместо внедрения нескольких отдельных фабрик и аксессоров вы можете внедрить одну более всеобъемлющую составляющую.

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

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

Тогда `MultiFactory::getDb()` делает то же самое, что `MultiFactoryAlt::get('db')`. Однако у этой альтернативной записи есть недостаток: из сигнатуры интерфейса не видно явно, какие значения `$name` поддерживаются. Кроме того, в интерфейсе нельзя задать разные типы возвращаемых значений для разных значений `$name`.

Вместо `get($name)` интерфейс может объявить `create($name)`, который при каждом вызове возвращает новый экземпляр (тогда как `get()` возвращает общий). В интерфейсе может быть только один такой метод с параметром. Если тип возвращаемого значения метода nullable (например, `?PDO`), для неизвестного `$name` он возвращает `null` вместо выбрасывания исключения.


Определение списком
-------------------
Мультифабрику можно определить в конфигурации списком, записав сервисы прямо в нём: .{data-version:3.2.0}

```neon
services:
	- MultiFactory(
		article: Article()                    # задаёт createArticle()
		db: PDO(%dsn%, %user%, %password%)    # задаёт getDb()
	)
```

Как вариант, в определении мультифабрики можно сослаться на существующие сервисы через ссылки:

```neon
services:
	article: Article
	- PDO(%dsn%, %user%, %password%)
	- MultiFactory(
		article: @article    # задаёт createArticle()
		db: @\PDO            # задаёт getDb()
	)
```


Определение тегами
------------------

Ещё один способ определить мультифабрику - использовать [теги |services#Теги]. Значение тега определяет имя соответствующего метода:

```neon
services:
	article:
		create: Article
		tags: {multi: article}     # задаёт createArticle()
	db:
		create: PDO(%dsn%, %user%, %password%)
		tags: {multi: db}          # задаёт getDb()

	- MultiFactory(tagged: multi)
```

Генерируемые фабрики

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 умеет порождать код фабрики автоматически. Вам достаточно создать интерфейс, а реализацию сгенерирует 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;
}

Поскольку имя параметра в конструкторе ($authorId) совпадает с именем параметра в методе фабрики, 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::getDb() делает то же самое, что MultiFactoryAlt::get('db'). Однако у этой альтернативной записи есть недостаток: из сигнатуры интерфейса не видно явно, какие значения $name поддерживаются. Кроме того, в интерфейсе нельзя задать разные типы возвращаемых значений для разных значений $name.

Вместо get($name) интерфейс может объявить create($name), который при каждом вызове возвращает новый экземпляр (тогда как get() возвращает общий). В интерфейсе может быть только один такой метод с параметром. Если тип возвращаемого значения метода nullable (например, ?PDO), для неизвестного $name он возвращает null вместо выбрасывания исключения.

Определение списком

Мультифабрику можно определить в конфигурации списком, записав сервисы прямо в нём:

services:
	- MultiFactory(
		article: Article()                    # задаёт createArticle()
		db: PDO(%dsn%, %user%, %password%)    # задаёт getDb()
	)

Как вариант, в определении мультифабрики можно сослаться на существующие сервисы через ссылки:

services:
	article: Article
	- PDO(%dsn%, %user%, %password%)
	- MultiFactory(
		article: @article    # задаёт createArticle()
		db: @\PDO            # задаёт getDb()
	)

Определение тегами

Ещё один способ определить мультифабрику – использовать теги. Значение тега определяет имя соответствующего метода:

services:
	article:
		create: Article
		tags: {multi: article}     # задаёт createArticle()
	db:
		create: PDO(%dsn%, %user%, %password%)
		tags: {multi: db}          # задаёт getDb()

	- MultiFactory(tagged: multi)