Передача зависимостей

Аргументы, или «зависимости» в терминологии 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

Какой путь выбрать?

  • конструктор подходит для обязательных зависимостей, которые необходимы классу для функционирования
  • сеттер, с другой стороны, подходит для необязательных зависимостей, или зависимостей, которые могут быть изменены
  • публичные переменные не рекомендуются