Передача зависимостей
Аргументы, или «зависимости» в терминологии DI, могут быть переданы классам следующими основными способами:
- passing by constructor
- passing by method (called a setter)
- by setting a property
- методом, аннотацией или атрибутом inject
Теперь мы проиллюстрируем различные варианты на конкретных примерах.
Внедрение через конструктор
Зависимости передаются в качестве аргументов конструктору при создании объекта:
Эта форма полезна для обязательных зависимостей, которые абсолютно необходимы классу для функционирования, так как без них экземпляр не может быть создан.
Начиная с версии PHP 8.0, мы можем использовать более короткую форму обозначения (constructor property promotion), которая функционально эквивалентна:
Начиная с версии PHP 8.1, свойство может быть помечено флагом
readonly
, который объявляет, что содержимое свойства не будет
изменяться:
DI контейнер передает зависимости в конструктор автоматически, используя autowiring. Аргументы, которые нельзя передавать таким образом (например, строки, числа, булевы) записать в конфигурации.
Конструкторский ад
Термин ад конструктора относится к ситуации, когда дочерний класс наследуется от родительского класса, конструктор которого требует зависимостей, и дочерний класс тоже требует зависимостей. Он также должен принять и передать зависимости родительского класса:
Проблема возникает, когда мы хотим изменить конструктор класса
BaseClass
, например, когда добавляется новая зависимость. Тогда мы
должны изменить все конструкторы дочерних классов. Что превращает
такую модификацию в ад.
Как предотвратить это? Решение заключается в приоритете композиции над наследованием.
Поэтому давайте спроектируем код по-другому. Мы будем избегать абстрактных
классов Base*
. Вместо того чтобы MyClass
получал некоторую
функциональность, наследуя от BaseClass
, ему будет передаваться эта
функциональность как зависимость:
Внедрение через сеттеры
Зависимости передаются путем вызова метода, который хранит их в
приватном свойстве. Обычно эти методы именуются set*()
, поэтому их
называют сеттерами, но, конечно, они могут называться и по-другому.
Этот метод полезен для необязательных зависимостей, которые не нужны для функционирования класса, поскольку не гарантируется, что объект действительно получит их (т. е. что пользователь вызовет метод).
В то же время, этот метод позволяет неоднократно вызывать сеттер для
изменения зависимости. Если это нежелательно, добавьте проверку в
метод, или, начиная с PHP 8.1, пометьте свойство $cache
флагом
readonly
.
The setter call is defined in the DI container configuration in section setup. Also here the automatic passing of dependencies is used by autowiring:
Внедрение через свойства
Зависимости передаются непосредственно в свойство:
Этот метод считается неприемлемым, поскольку свойство должно быть
объявлено как public
. Следовательно, мы не можем контролировать,
будет ли переданная зависимость действительно иметь указанный тип
(это было верно до версии PHP 7.4), и мы теряем возможность реагировать на
новую назначенную зависимость своим собственным кодом, например,
чтобы предотвратить последующие изменения. В то же время, свойство
становится частью публичного интерфейса класса, что может быть
нежелательно.
Настройка переменной определяется в конфигурации контейнера DI в разделе section setup:
Инъекция
В то время как предыдущие три метода в целом применимы во всех объектно-ориентированных языках, инъектирование методом, аннотацией или атрибутом inject специфично для презентаторов Nette. Они рассматриваются в отдельной главе.
Какой путь выбрать?
- конструктор подходит для обязательных зависимостей, которые необходимы классу для функционирования
- сеттер, с другой стороны, подходит для необязательных зависимостей, или зависимостей, которые могут быть изменены
- публичные переменные не рекомендуются