Nette Documentation Preview

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

<div class=perex>

Аргументы, или в терминологии DI «зависимости», можно передавать в классы следующими основными способами:

* передача через конструктор
* передача через метод (так называемый сеттер)
* установка переменной
* методом, аннотацией или атрибутом *inject*

</div>

Теперь покажем каждый вариант на конкретных примерах.


Передача через конструктор
==========================

Зависимости передаются в момент создания объекта как аргументы конструктора:

```php
class MyClass
{
	private Cache $cache;

	public function __construct(Cache $cache)
	{
		$this->cache = $cache;
	}
}

$obj = new MyClass($cache);
```

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

Начиная с PHP 8.0, мы можем использовать более короткую форму записи ([constructor property promotion |https://blog.nette.org/ru/php-8-0-complete-overview-of-news#toc-constructor-property-promotion]), которая функционально эквивалентна:

```php
// PHP 8.0
class MyClass
{
	public function __construct(
		private Cache $cache,
	) {
	}
}
```

Начиная с PHP 8.1, переменную можно пометить флагом `readonly`, который объявляет, что содержимое переменной больше не изменится:

```php
// PHP 8.1
class MyClass
{
	public function __construct(
		private readonly Cache $cache,
	) {
	}
}
```

DI-контейнер передает зависимости конструктору автоматически с помощью [autowiring |autowiring]. Аргументы, которые таким образом передать нельзя (например, строки, числа, булевы значения), [записываем в конфигурации |services#Аргументы].


Ад конструкторов
----------------

Термин *constructor hell* (ад конструкторов) обозначает ситуацию, когда потомок наследует от родительского класса, конструктор которого требует зависимости, и в то же время потомок требует зависимости. При этом он должен принять и передать также родительские:

```php
abstract class BaseClass
{
	private Cache $cache;

	public function __construct(Cache $cache)
	{
		$this->cache = $cache;
	}
}

final class MyClass extends BaseClass
{
	private Database $db;

	// ⛔ АД КОНСТРУКТОРОВ
	public function __construct(Cache $cache, Database $db)
	{
		parent::__construct($cache);
		$this->db = $db;
	}
}
```

Проблема возникает в момент, когда мы захотим изменить конструктор класса `BaseClass`, например, когда добавится новая зависимость. Тогда необходимо изменить также все конструкторы потомков. Что превращает такое изменение в ад.

Как этого избежать? Решение — **отдавать предпочтение [композиции перед наследованием |faq#Почему композиция предпочтительнее наследования]**.

То есть спроектируем код иначе. Будем избегать [абстрактным |nette:introduction-to-object-oriented-programming#Абстрактные классы] `Base*` классов. Вместо того чтобы `MyClass` получал определенную функциональность путем наследования от `BaseClass`, он получит эту функциональность как зависимость:

```php
final class SomeFunctionality
{
	private Cache $cache;

	public function __construct(Cache $cache)
	{
		$this->cache = $cache;
	}
}

final class MyClass
{
	private SomeFunctionality $sf;
	private Database $db;

	public function __construct(SomeFunctionality $sf, Database $db) // ✅
	{
		$this->sf = $sf;
		$this->db = $db;
	}
}
```


Передача сеттером
=================

Зависимости передаются вызовом метода, который сохраняет их в приватную переменную. Обычное соглашение об именовании этих методов — форма `set*()`, поэтому их называют сеттерами, но они, конечно, могут называться как угодно иначе.

```php
class MyClass
{
	private Cache $cache;

	public function setCache(Cache $cache): void
	{
		$this->cache = $cache;
	}
}

$obj = new MyClass;
$obj->setCache($cache);
```

Этот способ подходит для необязательных зависимостей, которые не являются необходимыми для работы класса, так как не гарантируется, что объект действительно получит зависимость (т. е. что пользователь вызовет метод).

В то же время этот способ позволяет вызывать сеттер повторно и таким образом изменять зависимость. Если это нежелательно, добавим в метод проверку, или с PHP 8.1 пометим свойство `$cache` флагом `readonly`.

```php
class MyClass
{
	private Cache $cache;

	public function setCache(Cache $cache): void
	{
		if (isset($this->cache)) {
			throw new RuntimeException('The dependency has already been set');
		}
		$this->cache = $cache;
	}
}
```

Вызов сеттера определяем в конфигурации DI-контейнера в [ключе setup |services#Setup]. Здесь также используется автоматическая передача зависимостей с помощью autowiring:

```neon
services:
	-	create: MyClass
		setup:
			- setCache
```


Установка переменной
====================

Зависимости передаются записью непосредственно в переменную-член:

```php
class MyClass
{
	public Cache $cache;
}

$obj = new MyClass;
$obj->cache = $cache;
```

Этот способ считается неподходящим, поскольку переменная-член должна быть объявлена как `public`. Следовательно, у нас нет контроля над тем, что переданная зависимость действительно будет данного типа (действовало до PHP 7.4), и мы теряем возможность реагировать на вновь назначенную зависимость собственным кодом, например, предотвратить последующее изменение. В то же время переменная становится частью публичного интерфейса класса, что может быть нежелательно.

Установку переменной определяем в конфигурации DI-контейнера в [секции setup |services#Setup]:

```neon
services:
	-	create: MyClass
		setup:
			- $cache = @\Cache
```


Inject
======

В то время как предыдущие три способа применимы в целом во всех объектно-ориентированных языках, инъекция методом, аннотацией или атрибутом *inject* специфична исключительно для презентеров в Nette. О них рассказывается в [отдельной главе |best-practices:inject-method-attribute].


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

- конструктор подходит для обязательных зависимостей, которые класс непременно нуждается для своей работы
- сеттер, наоборот, подходит для необязательных зависимостей или зависимостей, которые можно будет изменять в дальнейшем
- публичные переменные не подходят

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

Аргументы, или в терминологии DI «зависимости», можно передавать в классы следующими основными способами:

  • передача через конструктор
  • передача через метод (так называемый сеттер)
  • установка переменной
  • методом, аннотацией или атрибутом inject

Теперь покажем каждый вариант на конкретных примерах.

Передача через конструктор

Зависимости передаются в момент создания объекта как аргументы конструктора:

class MyClass
{
	private Cache $cache;

	public function __construct(Cache $cache)
	{
		$this->cache = $cache;
	}
}

$obj = new MyClass($cache);

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

Начиная с PHP 8.0, мы можем использовать более короткую форму записи (constructor property promotion), которая функционально эквивалентна:

// PHP 8.0
class MyClass
{
	public function __construct(
		private Cache $cache,
	) {
	}
}

Начиная с PHP 8.1, переменную можно пометить флагом readonly, который объявляет, что содержимое переменной больше не изменится:

// PHP 8.1
class MyClass
{
	public function __construct(
		private readonly Cache $cache,
	) {
	}
}

DI-контейнер передает зависимости конструктору автоматически с помощью autowiring. Аргументы, которые таким образом передать нельзя (например, строки, числа, булевы значения), записываем в конфигурации.

Ад конструкторов

Термин constructor hell (ад конструкторов) обозначает ситуацию, когда потомок наследует от родительского класса, конструктор которого требует зависимости, и в то же время потомок требует зависимости. При этом он должен принять и передать также родительские:

abstract class BaseClass
{
	private Cache $cache;

	public function __construct(Cache $cache)
	{
		$this->cache = $cache;
	}
}

final class MyClass extends BaseClass
{
	private Database $db;

	// ⛔ АД КОНСТРУКТОРОВ
	public function __construct(Cache $cache, Database $db)
	{
		parent::__construct($cache);
		$this->db = $db;
	}
}

Проблема возникает в момент, когда мы захотим изменить конструктор класса BaseClass, например, когда добавится новая зависимость. Тогда необходимо изменить также все конструкторы потомков. Что превращает такое изменение в ад.

Как этого избежать? Решение — отдавать предпочтение композиции перед наследованием.

То есть спроектируем код иначе. Будем избегать абстрактным Base* классов. Вместо того чтобы MyClass получал определенную функциональность путем наследования от BaseClass, он получит эту функциональность как зависимость:

final class SomeFunctionality
{
	private Cache $cache;

	public function __construct(Cache $cache)
	{
		$this->cache = $cache;
	}
}

final class MyClass
{
	private SomeFunctionality $sf;
	private Database $db;

	public function __construct(SomeFunctionality $sf, Database $db) // ✅
	{
		$this->sf = $sf;
		$this->db = $db;
	}
}

Передача сеттером

Зависимости передаются вызовом метода, который сохраняет их в приватную переменную. Обычное соглашение об именовании этих методов — форма set*(), поэтому их называют сеттерами, но они, конечно, могут называться как угодно иначе.

class MyClass
{
	private Cache $cache;

	public function setCache(Cache $cache): void
	{
		$this->cache = $cache;
	}
}

$obj = new MyClass;
$obj->setCache($cache);

Этот способ подходит для необязательных зависимостей, которые не являются необходимыми для работы класса, так как не гарантируется, что объект действительно получит зависимость (т. е. что пользователь вызовет метод).

В то же время этот способ позволяет вызывать сеттер повторно и таким образом изменять зависимость. Если это нежелательно, добавим в метод проверку, или с PHP 8.1 пометим свойство $cache флагом readonly.

class MyClass
{
	private Cache $cache;

	public function setCache(Cache $cache): void
	{
		if (isset($this->cache)) {
			throw new RuntimeException('The dependency has already been set');
		}
		$this->cache = $cache;
	}
}

Вызов сеттера определяем в конфигурации DI-контейнера в ключе setup. Здесь также используется автоматическая передача зависимостей с помощью autowiring:

services:
	-	create: MyClass
		setup:
			- setCache

Установка переменной

Зависимости передаются записью непосредственно в переменную-член:

class MyClass
{
	public Cache $cache;
}

$obj = new MyClass;
$obj->cache = $cache;

Этот способ считается неподходящим, поскольку переменная-член должна быть объявлена как public. Следовательно, у нас нет контроля над тем, что переданная зависимость действительно будет данного типа (действовало до PHP 7.4), и мы теряем возможность реагировать на вновь назначенную зависимость собственным кодом, например, предотвратить последующее изменение. В то же время переменная становится частью публичного интерфейса класса, что может быть нежелательно.

Установку переменной определяем в конфигурации DI-контейнера в секции setup:

services:
	-	create: MyClass
		setup:
			- $cache = @\Cache

Inject

В то время как предыдущие три способа применимы в целом во всех объектно-ориентированных языках, инъекция методом, аннотацией или атрибутом inject специфична исключительно для презентеров в Nette. О них рассказывается в отдельной главе.

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

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