Сгенерированные фабрики
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
)