Передача зависимостей
Аргументы, или «зависимости» в терминологии DI, могут быть переданы классам следующими основными способами:
- passing by constructor
- passing by method (called a setter)
- by setting a property
- методом, аннотацией или атрибутом inject
Первые три метода применимы вообще во всех объектно-ориентированных языках, четвертый специфичен для презентаторов Nette, поэтому он обсуждается в отдельной главе. Сейчас мы подробнее рассмотрим каждый из этих вариантов и покажем их на конкретных примерах.
Внедрение через конструктор
Зависимости передаются в качестве аргументов конструктору при создании объекта:
class MyService
{
public function __construct(
private Cache $cache,
) {
}
}
$service = new MyService($cache);
Эта форма полезна для обязательных зависимостей, которые абсолютно необходимы классу для функционирования, так как без них экземпляр не может быть создан.
Начиная с версии PHP 8.0, мы можем использовать более короткую форму обозначения, которая функционально эквивалентна:
// PHP 8.0
class MyService
{
public function __construct(
private Cache $service,
) {
}
}
Начиная с версии PHP 8.1, свойство может быть помечено флагом
readonly
, который объявляет, что содержимое свойства не будет
изменяться:
// PHP 8.1
class MyService
{
public function __construct(
private readonly Cache $service,
) {
}
}
DI контейнер передает зависимости в конструктор автоматически, используя autowiring. Аргументы, которые нельзя передавать таким образом (например, строки, числа, булевы) записать в конфигурации.
Внедрение через сеттеры
Зависимости передаются путем вызова метода, который хранит их в
приватном свойстве. Обычное соглашение об именовании этих методов
имеет вид set*()
, поэтому они называются сеттерами.
class MyService
{
private Cache $cache;
public function setCache(Cache $service): void
{
$this->cache = $service;
}
}
$service = new MyService;
$service->setCache($cache);
Этот метод полезен для необязательных зависимостей, которые не нужны для функционирования класса, поскольку не гарантируется, что объект действительно получит их (т. е. что пользователь вызовет метод).
В то же время, этот метод позволяет неоднократно вызывать сеттер для
изменения зависимости. Если это нежелательно, добавьте проверку в
метод, или, начиная с PHP 8.1, пометьте свойство $cache
флагом
readonly
.
class MyService
{
private Cache $cache;
public function setCache(Cache $service): void
{
if ($this->cache) {
throw new RuntimeException('The dependency has already been set');
}
$this->cache = $service;
}
}
The setter call is defined in the DI container configuration in section setup. Also here the automatic passing of dependencies is used by autowiring:
services:
-
create: MyService
setup:
- setCache
Внедрение через свойства
Зависимости передаются непосредственно в свойство:
class MyService
{
public Cache $cache;
}
$service = new MyService;
$service->cache = $cache;
Этот метод считается неприемлемым, поскольку свойство должно быть
объявлено как public
. Следовательно, мы не можем контролировать,
будет ли переданная зависимость действительно иметь указанный тип
(это было верно до версии PHP 7.4), и мы теряем возможность реагировать на
новую назначенную зависимость своим собственным кодом, например,
чтобы предотвратить последующие изменения. В то же время, свойство
становится частью публичного интерфейса класса, что может быть
нежелательно.
Настройка переменной определяется в конфигурации контейнера DI в разделе section setup:
services:
-
create: MyService
setup:
- $cache = @\Cache
Какой путь выбрать?
- конструктор подходит для обязательных зависимостей, которые необходимы классу для функционирования
- сеттер, с другой стороны, подходит для необязательных зависимостей, или зависимостей, которые могут быть изменены
- публичные переменные не рекомендуются