Nette Documentation Preview

syntax
Autowiring
**********

.[perex]
Autowiring - прекрасная возможность, которая автоматически передаёт нужные сервисы в конструктор и другие методы, так что нам не приходится указывать их явно. Это экономит вам массу времени.

Благодаря ему мы можем опускать подавляющее большинство аргументов при написании определений сервисов. Вместо:

```neon
services:
	articles: Model\ArticleRepository(@database, @cache.storage)
```

Достаточно написать:

```neon
services:
	articles: Model\ArticleRepository
```

Autowiring руководствуется типами, поэтому, чтобы он работал, класс `ArticleRepository` должен быть определён примерно так:

```php
namespace Model;

class ArticleRepository
{
	public function __construct(\PDO $db, \Nette\Caching\Storage $storage)
	{}
}
```

Autowiring никогда не использует имена сервисов. Он руководствуется исключительно системой типов PHP, поэтому знает и то, что класс удовлетворяет интерфейсам, которые он реализует, и классам, от которых он наследуется. Благодаря этому имя сервиса - лишь вспомогательный идентификатор, и его переименование ничего в приложении не сломает.

Чтобы autowiring можно было использовать, в контейнере должен быть **ровно один сервис** каждого типа. Если бы их было больше, autowiring не знал бы, какой передать, и выбросил бы исключение:

```neon
services:
	mainDb: PDO(%dsn%, %user%, %password%)
	tempDb: PDO('sqlite::memory:')
	articles: Model\ArticleRepository  # ВЫБРОСИТ ИСКЛЮЧЕНИЕ, подходят и mainDb, и tempDb
```

Одно из решений - обойти autowiring и явно указать имя сервиса (например, `articles: Model\ArticleRepository(@mainDb)`). Однако удобнее либо [отключить |#Отключение autowiring] autowiring для одного из сервисов, либо [сделать |#Предпочтительный сервис для autowiring] один сервис предпочтительным перед остальными.


Отключение autowiring
---------------------

Мы можем отключить autowiring для сервиса параметром `autowired: false`:

```neon
services:
	mainDb: PDO(%dsn%, %user%, %password%)

	tempDb:
		create: PDO('sqlite::memory:')
		autowired: false               # сервис tempDb исключён из autowiring

	articles: Model\ArticleRepository  # поэтому в конструктор передаётся mainDb
```

Сервис `articles` не выбросит исключение о том, что для конструктора доступны два подходящих сервиса `PDO` (`mainDb` и `tempDb`), потому что он видит только сервис `mainDb`.

Autowiring можно отключить и глобально для целых типов параметром конфигурации [`di › excluded` |configuration#DI], где перечисляются типы (и их потомки), которые никогда не должны попадать в autowiring.

.[note]
Настройка autowiring в Nette отличается от Symfony. В Symfony `autowire: false` означает, что autowiring не нужно использовать для аргументов конструктора сервиса. В Nette autowiring относится к аргументам конструктора и любых других методов, вызываемых через контейнер (например, при внедрении через сеттер). Параметр `autowired: false` не даёт контейнеру автоматически передавать экземпляр этого сервиса как зависимость другим сервисам.


Предпочтительный сервис для autowiring
--------------------------------------

Если у нас несколько сервисов одного типа и для одного из них мы задаём параметр `autowired`, этот сервис становится предпочтительным:

```neon
services:
	mainDb:
		create: PDO(%dsn%, %user%, %password%)
		autowired: PDO    # становится предпочтительным

	tempDb:
		create: PDO('sqlite::memory:')

	articles: Model\ArticleRepository
```

Сервис `articles` не выбросит исключение о нескольких подходящих сервисах `PDO` (`mainDb` и `tempDb`), а использует предпочтительный, то есть `mainDb`.


Набор сервисов
--------------

Autowiring умеет передавать и массивы сервисов определённого типа. Поскольку PHP не поддерживает указание типа элементов массива в объявлениях типов, вам нужно дополнить объявление типа `array` комментарием phpDoc с типом элементов, например `ClassName[]`:

```php
namespace Model;

class ShipManager
{
	/**
	 * @param Shipper[] $shippers
	 */
	public function __construct(array $shippers)
	{}
}
```

DI-контейнер тогда автоматически передаст массив сервисов соответствующего типа. Он пропускает сервисы, у которых [autowiring отключён |#Отключение autowiring], и никогда не включает создаваемый в данный момент сервис в его собственный набор. В отличие от передачи отдельного сервиса, [сужение |#Сужение autowiring] autowiring до определённого типа или пометка сервиса как [предпочтительного |#Предпочтительный сервис для autowiring] здесь не действуют: в массиве всегда оказываются все сервисы заданного типа.

Тип в комментарии может иметь и вид `array<int, Class>` или `list<Class>`. Если вы не можете управлять видом комментария phpDoc, вы можете передать массив сервисов прямо в конфигурации через [`typed()` |services#Специальные функции].


Скалярные аргументы
-------------------

Autowiring работает только для объектов и массивов объектов. Скалярные аргументы (например, строки, числа, логические значения) нужно [указывать в конфигурации |services#Аргументы]. Альтернатива - создать [объект настроек|best-practices:passing-settings-to-presenters], упаковывающий скалярное значение (или несколько значений). Такой объект затем можно передавать через autowiring.

```php
class MySettings
{
	public function __construct(
		// readonly можно использовать начиная с PHP 8.1
		public readonly bool $value,
	)
	{}
}
```

Вы регистрируете его как сервис, добавив в конфигурацию:

```neon
services:
	- MySettings('any value')
```

Остальные классы затем могут запросить его через autowiring.


Необязательные зависимости
--------------------------

Если у параметра конструктора или метода есть значение по умолчанию, а сервиса нужного типа в контейнере нет, autowiring не выбрасывает исключение: он просто пропускает аргумент, и используется значение по умолчанию. Так объявляются необязательные зависимости:

```php
class Foo
{
	public function __construct(
		private ?Logger $logger = null,
	) {}
}
```

Напротив, для параметра без значения по умолчанию отсутствие сервиса всегда приводит к исключению.


Сужение autowiring
------------------

Для отдельных сервисов autowiring можно сузить до определённых классов или интерфейсов.

Обычно autowiring передаёт сервис в каждый параметр метода, типу которого сервис соответствует. Сужение означает, что мы устанавливаем условия, которым должны отвечать типы, указанные у параметров метода, чтобы сервис туда передавался.

Возьмём пример:

```php
class ParentClass
{}

class ChildClass extends ParentClass
{}

class ParentDependent
{
	function __construct(ParentClass $obj)
	{}
}

class ChildDependent
{
	function __construct(ChildClass $obj)
	{}
}
```

Если бы мы зарегистрировали их все как сервисы, autowiring не справился бы:

```neon
services:
	parent: ParentClass
	child: ChildClass
	parentDep: ParentDependent  # ВЫБРОСИТ ИСКЛЮЧЕНИЕ, подходят и parent, и child
	childDep: ChildDependent    # autowiring передаёт в конструктор сервис child
```

Сервис `parentDep` выбрасывает исключение `Multiple services of type ParentClass found: child, parent`, потому что в его конструктор подходят и сервис `parent`, и сервис `child`, а autowiring не может решить, какой выбрать.

Поэтому для сервиса `child` мы можем сузить autowiring до типа `ChildClass`:

```neon
services:
	parent: ParentClass
	child:
		create: ChildClass
		autowired: ChildClass   # можно записать и как 'autowired: self'

	parentDep: ParentDependent  # autowiring передаёт в конструктор сервис parent
	childDep: ChildDependent    # autowiring передаёт в конструктор сервис child
```

Теперь в конструктор сервиса `parentDep` передаётся сервис `parent`, потому что он стал единственным подходящим объектом. Сервис `child` autowiring туда больше не передаёт. Да, сервис `child` по-прежнему имеет тип `ParentClass`, но условие сужения `autowired: ChildClass` означает, что он будет передаваться только в параметры, явно объявленные как `ChildClass` (или его подтипы). Поскольку `ParentDependent` требует `ParentClass`, сервис `child` больше не считается там кандидатом на autowiring.

Для сервиса `child` запись `autowired: ChildClass` можно было бы заменить на `autowired: self`, потому что `self` - это подстановка для класса текущего сервиса.

В ключе `autowired` можно указать и несколько классов или интерфейсов массивом:

```neon
autowired: [ParentClass, FooInterface]
```

Попробуем добавить в пример интерфейсы:

```php
interface FooInterface
{}

interface BarInterface
{}

class ParentClass implements FooInterface
{}

class ChildClass extends ParentClass implements BarInterface
{}

class FooDependent
{
	function __construct(FooInterface $obj)
	{}
}

class BarDependent
{
	function __construct(BarInterface $obj)
	{}
}

class ParentDependent
{
	function __construct(ParentClass $obj)
	{}
}

class ChildDependent
{
	function __construct(ChildClass $obj)
	{}
}
```

Если мы никак не ограничим сервис `child`, он подойдёт в конструкторы всех классов `FooDependent`, `BarDependent`, `ParentDependent` и `ChildDependent`, и autowiring передаст его туда.

Однако если мы сузим его autowiring до `ChildClass` через `autowired: ChildClass` (или `self`), autowiring передаст его только в конструктор `ChildDependent`, потому что тот требует аргумент типа `ChildClass`, а `ChildClass` *имеет тип* `ChildClass`. Ни у одного другого параметра требуемый тип не является `ChildClass` или его подтипом, поэтому в них сервис не передаётся.

Если мы ограничим его до `ParentClass` через `autowired: ParentClass`, autowiring снова передаст его в конструктор `ChildDependent` (потому что требуемый `ChildClass` - подтип `ParentClass`), а теперь ещё и в конструктор `ParentDependent`, потому что требуемый тип `ParentClass` тоже подходит.

Если мы ограничим его до `FooInterface`, он по-прежнему будет передаваться в `ParentDependent` (требуемый `ParentClass` - подтип `FooInterface`) и `ChildDependent`, а дополнительно и в конструктор `FooDependent`, но не в `BarDependent`, потому что `BarInterface` не является подтипом `FooInterface`.

```neon
services:
	child:
		create: ChildClass
		autowired: FooInterface

	fooDep: FooDependent        # autowiring передаёт в конструктор сервис child
	barDep: BarDependent        # ВЫБРОСИТ ИСКЛЮЧЕНИЕ, ни один сервис не подходит
	parentDep: ParentDependent  # autowiring передаёт в конструктор сервис child
	childDep: ChildDependent    # autowiring передаёт в конструктор сервис child
```

Autowiring

Autowiring – прекрасная возможность, которая автоматически передаёт нужные сервисы в конструктор и другие методы, так что нам не приходится указывать их явно. Это экономит вам массу времени.

Благодаря ему мы можем опускать подавляющее большинство аргументов при написании определений сервисов. Вместо:

services:
	articles: Model\ArticleRepository(@database, @cache.storage)

Достаточно написать:

services:
	articles: Model\ArticleRepository

Autowiring руководствуется типами, поэтому, чтобы он работал, класс ArticleRepository должен быть определён примерно так:

namespace Model;

class ArticleRepository
{
	public function __construct(\PDO $db, \Nette\Caching\Storage $storage)
	{}
}

Autowiring никогда не использует имена сервисов. Он руководствуется исключительно системой типов PHP, поэтому знает и то, что класс удовлетворяет интерфейсам, которые он реализует, и классам, от которых он наследуется. Благодаря этому имя сервиса – лишь вспомогательный идентификатор, и его переименование ничего в приложении не сломает.

Чтобы autowiring можно было использовать, в контейнере должен быть ровно один сервис каждого типа. Если бы их было больше, autowiring не знал бы, какой передать, и выбросил бы исключение:

services:
	mainDb: PDO(%dsn%, %user%, %password%)
	tempDb: PDO('sqlite::memory:')
	articles: Model\ArticleRepository  # ВЫБРОСИТ ИСКЛЮЧЕНИЕ, подходят и mainDb, и tempDb

Одно из решений – обойти autowiring и явно указать имя сервиса (например, articles: Model\ArticleRepository(@mainDb)). Однако удобнее либо отключить autowiring для одного из сервисов, либо сделать один сервис предпочтительным перед остальными.

Отключение autowiring

Мы можем отключить autowiring для сервиса параметром autowired: false:

services:
	mainDb: PDO(%dsn%, %user%, %password%)

	tempDb:
		create: PDO('sqlite::memory:')
		autowired: false               # сервис tempDb исключён из autowiring

	articles: Model\ArticleRepository  # поэтому в конструктор передаётся mainDb

Сервис articles не выбросит исключение о том, что для конструктора доступны два подходящих сервиса PDO (mainDb и tempDb), потому что он видит только сервис mainDb.

Autowiring можно отключить и глобально для целых типов параметром конфигурации di › excluded, где перечисляются типы (и их потомки), которые никогда не должны попадать в autowiring.

Настройка autowiring в Nette отличается от Symfony. В Symfony autowire: false означает, что autowiring не нужно использовать для аргументов конструктора сервиса. В Nette autowiring относится к аргументам конструктора и любых других методов, вызываемых через контейнер (например, при внедрении через сеттер). Параметр autowired: false не даёт контейнеру автоматически передавать экземпляр этого сервиса как зависимость другим сервисам.

Предпочтительный сервис для autowiring

Если у нас несколько сервисов одного типа и для одного из них мы задаём параметр autowired, этот сервис становится предпочтительным:

services:
	mainDb:
		create: PDO(%dsn%, %user%, %password%)
		autowired: PDO    # становится предпочтительным

	tempDb:
		create: PDO('sqlite::memory:')

	articles: Model\ArticleRepository

Сервис articles не выбросит исключение о нескольких подходящих сервисах PDO (mainDb и tempDb), а использует предпочтительный, то есть mainDb.

Набор сервисов

Autowiring умеет передавать и массивы сервисов определённого типа. Поскольку PHP не поддерживает указание типа элементов массива в объявлениях типов, вам нужно дополнить объявление типа array комментарием phpDoc с типом элементов, например ClassName[]:

namespace Model;

class ShipManager
{
	/**
	 * @param Shipper[] $shippers
	 */
	public function __construct(array $shippers)
	{}
}

DI-контейнер тогда автоматически передаст массив сервисов соответствующего типа. Он пропускает сервисы, у которых autowiring отключён, и никогда не включает создаваемый в данный момент сервис в его собственный набор. В отличие от передачи отдельного сервиса, сужение autowiring до определённого типа или пометка сервиса как предпочтительного здесь не действуют: в массиве всегда оказываются все сервисы заданного типа.

Тип в комментарии может иметь и вид array<int, Class> или list<Class>. Если вы не можете управлять видом комментария phpDoc, вы можете передать массив сервисов прямо в конфигурации через typed().

Скалярные аргументы

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

class MySettings
{
	public function __construct(
		// readonly можно использовать начиная с PHP 8.1
		public readonly bool $value,
	)
	{}
}

Вы регистрируете его как сервис, добавив в конфигурацию:

services:
	- MySettings('any value')

Остальные классы затем могут запросить его через autowiring.

Необязательные зависимости

Если у параметра конструктора или метода есть значение по умолчанию, а сервиса нужного типа в контейнере нет, autowiring не выбрасывает исключение: он просто пропускает аргумент, и используется значение по умолчанию. Так объявляются необязательные зависимости:

class Foo
{
	public function __construct(
		private ?Logger $logger = null,
	) {}
}

Напротив, для параметра без значения по умолчанию отсутствие сервиса всегда приводит к исключению.

Сужение autowiring

Для отдельных сервисов autowiring можно сузить до определённых классов или интерфейсов.

Обычно autowiring передаёт сервис в каждый параметр метода, типу которого сервис соответствует. Сужение означает, что мы устанавливаем условия, которым должны отвечать типы, указанные у параметров метода, чтобы сервис туда передавался.

Возьмём пример:

class ParentClass
{}

class ChildClass extends ParentClass
{}

class ParentDependent
{
	function __construct(ParentClass $obj)
	{}
}

class ChildDependent
{
	function __construct(ChildClass $obj)
	{}
}

Если бы мы зарегистрировали их все как сервисы, autowiring не справился бы:

services:
	parent: ParentClass
	child: ChildClass
	parentDep: ParentDependent  # ВЫБРОСИТ ИСКЛЮЧЕНИЕ, подходят и parent, и child
	childDep: ChildDependent    # autowiring передаёт в конструктор сервис child

Сервис parentDep выбрасывает исключение Multiple services of type ParentClass found: child, parent, потому что в его конструктор подходят и сервис parent, и сервис child, а autowiring не может решить, какой выбрать.

Поэтому для сервиса child мы можем сузить autowiring до типа ChildClass:

services:
	parent: ParentClass
	child:
		create: ChildClass
		autowired: ChildClass   # можно записать и как 'autowired: self'

	parentDep: ParentDependent  # autowiring передаёт в конструктор сервис parent
	childDep: ChildDependent    # autowiring передаёт в конструктор сервис child

Теперь в конструктор сервиса parentDep передаётся сервис parent, потому что он стал единственным подходящим объектом. Сервис child autowiring туда больше не передаёт. Да, сервис child по-прежнему имеет тип ParentClass, но условие сужения autowired: ChildClass означает, что он будет передаваться только в параметры, явно объявленные как ChildClass (или его подтипы). Поскольку ParentDependent требует ParentClass, сервис child больше не считается там кандидатом на autowiring.

Для сервиса child запись autowired: ChildClass можно было бы заменить на autowired: self, потому что self – это подстановка для класса текущего сервиса.

В ключе autowired можно указать и несколько классов или интерфейсов массивом:

autowired: [ParentClass, FooInterface]

Попробуем добавить в пример интерфейсы:

interface FooInterface
{}

interface BarInterface
{}

class ParentClass implements FooInterface
{}

class ChildClass extends ParentClass implements BarInterface
{}

class FooDependent
{
	function __construct(FooInterface $obj)
	{}
}

class BarDependent
{
	function __construct(BarInterface $obj)
	{}
}

class ParentDependent
{
	function __construct(ParentClass $obj)
	{}
}

class ChildDependent
{
	function __construct(ChildClass $obj)
	{}
}

Если мы никак не ограничим сервис child, он подойдёт в конструкторы всех классов FooDependent, BarDependent, ParentDependent и ChildDependent, и autowiring передаст его туда.

Однако если мы сузим его autowiring до ChildClass через autowired: ChildClass (или self), autowiring передаст его только в конструктор ChildDependent, потому что тот требует аргумент типа ChildClass, а ChildClass имеет тип ChildClass. Ни у одного другого параметра требуемый тип не является ChildClass или его подтипом, поэтому в них сервис не передаётся.

Если мы ограничим его до ParentClass через autowired: ParentClass, autowiring снова передаст его в конструктор ChildDependent (потому что требуемый ChildClass – подтип ParentClass), а теперь ещё и в конструктор ParentDependent, потому что требуемый тип ParentClass тоже подходит.

Если мы ограничим его до FooInterface, он по-прежнему будет передаваться в ParentDependent (требуемый ParentClass – подтип FooInterface) и ChildDependent, а дополнительно и в конструктор FooDependent, но не в BarDependent, потому что BarInterface не является подтипом FooInterface.

services:
	child:
		create: ChildClass
		autowired: FooInterface

	fooDep: FooDependent        # autowiring передаёт в конструктор сервис child
	barDep: BarDependent        # ВЫБРОСИТ ИСКЛЮЧЕНИЕ, ни один сервис не подходит
	parentDep: ParentDependent  # autowiring передаёт в конструктор сервис child
	childDep: ChildDependent    # autowiring передаёт в конструктор сервис child