Методы и атрибуты inject

Эта статья посвящена разным способам передачи зависимостей в презентеры фреймворка Nette. Мы сравним предпочтительный способ, внедрение через конструктор, с альтернативами в виде методов и атрибутов inject.

Для презентеров, как и для других классов, предпочтительным подходом является передача зависимостей через конструктор. Однако если вы создаёте общего предка, от которого наследуются другие презентеры (например, BasePresenter), и этому предку тоже нужны зависимости, может возникнуть проблема, известная как ад конструкторов. Её можно обойти альтернативными способами, а именно методами и атрибутами inject (раньше аннотациями).

Методы inject*()

Это разновидность передачи зависимостей через сеттеры. Имена таких сеттеров должны начинаться с префикса inject. Nette DI автоматически вызывает методы с такими именами сразу после создания экземпляра презентера и передаёт им все нужные зависимости. Поэтому они должны быть объявлены как public.

Методы inject*() можно воспринимать как расширение конструктора, разбитое на несколько методов. Это позволяет BasePresenter получать свои зависимости отдельным методом, оставляя конструктор свободным для его потомков:

abstract class BasePresenter extends Nette\Application\UI\Presenter
{
	private Foo $foo;

	public function injectBase(Foo $foo): void
	{
		$this->foo = $foo;
	}
}

class MyPresenter extends BasePresenter
{
	private Bar $bar;

	public function __construct(Bar $bar)
	{
		$this->bar = $bar;
	}
}

У презентера может быть сколько угодно методов inject*(), и каждый может принимать сколько угодно параметров. Такой подход хорошо подходит и для случаев, когда презентер составлен из трейтов и каждому трейту нужны свои зависимости.

Атрибуты Inject

Это разновидность внедрения в свойства. Достаточно пометить свойства, в которые нужно внедрить зависимости, и Nette DI автоматически передаст их сразу после создания экземпляра презентера. Чтобы внедрение было возможно, эти свойства должны быть объявлены как public.

Свойства помечаются атрибутом (раньше использовалась аннотация /** @inject */):

use Nette\DI\Attributes\Inject;  // эта строка важна

class MyPresenter extends Nette\Application\UI\Presenter
{
	#[Inject]
	public Cache $cache;
}

Преимуществом этого способа передачи зависимостей была очень краткая запись. Однако с появлением продвижения свойств конструктора использование конструктора часто выглядит проще.

С другой стороны, этот способ страдает теми же недостатками, что и внедрение в свойства вообще: у нас нет контроля над изменениями переменной, а сама переменная становится частью публичного интерфейса класса, что обычно нежелательно.