Сгенерированные фабрики

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\Core\RouterFactory::createRouter
	- App\Model\DatabaseAccessor(
		db1: @database.db1.context
	)
версия: 3.x