Cablagem automática

A fiação automática é uma grande característica que pode passar automaticamente os serviços para o construtor e outros métodos, portanto, não precisamos escrevê-los de forma alguma. Isso economiza muito tempo.

Isto nos permite saltar a grande maioria dos argumentos ao escrever definições de serviços. Ao invés de:

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

Basta escrever:

services:
	articles: Model\ArticleRepository

A fiação automática é feita por tipos, portanto ArticleRepository classe deve ser definida como segue:

namespace Model;

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

Para utilizar a fiação automática, deve haver ** apenas um serviço** para cada tipo no contêiner. Se houvesse mais, o cabeamento automático não saberia qual deles passar e jogar fora uma exceção:

services:
	mainDb: PDO(%dsn%, %user%, %password%)
	tempDb: PDO('sqlite::memory:')
	articles: Model\ArticleRepository  # EXCEPÇÃO, tanto a mainDb como a tempDb combinam

A solução seria contornar a fiação automática e declarar explicitamente o nome do serviço (ou seja, articles: Model\ArticleRepository(@mainDb)). Entretanto, é mais conveniente desativar a fiação automática de um serviço, ou o primeiro serviço prefere.

Desligamento automático

Você pode desativar o serviço de cabeamento automático usando a opção autowired: no:

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

	tempDb:
		create: PDO('sqlite::memory:')
		autowired: false # remove a tempDb da autowiring

	articles: Model\ArticleRepository    # portanto passa o mainDb para o construtor

O serviço articles não lança a exceção de que existem dois serviços correspondentes do tipo PDO (ou seja, mainDb e tempDb) que podem ser passados ao construtor, pois ele só vê o serviço mainDb.

A configuração da fiação automática em Nette funciona de forma diferente da Symfony, onde a opção autowire: false diz que a fiação automática não deve ser usada para argumentos de construção de serviços. Em Nette, a fiação automática é sempre usada, seja para argumentos do construtor ou para qualquer outro método. A opção autowired: false diz que a instância de serviço não deve ser aprovada em nenhum lugar usando a fiação automática.

Fiação automática preferencial

Se tivermos mais serviços do mesmo tipo e um deles tiver a opção autowired, este serviço se torna o preferido:

services:
	mainDb:
		create: PDO(%dsn%, %user%, %password%)
		autowired: PDO    # faz com que seja preferível

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

	articles: Model\ArticleRepository

O serviço articles não lança a exceção de que existem dois serviços correspondentes PDO (ou seja, mainDb e tempDb), mas utiliza o serviço preferido, ou seja, mainDb.

Coleção de serviços

A fiação automática também pode passar uma série de serviços de um determinado tipo. Uma vez que o PHP não pode nativamente anotar o tipo de itens de array, além do tipo array, um comentário phpDoc com o tipo de item como ClassName[] deve ser adicionado:

namespace Model;

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

O recipiente DI passa então automaticamente uma série de serviços que correspondem ao tipo dado. Ele irá omitir serviços que tenham a fiação automática desligada.

O tipo no comentário também pode ter o formato array<int, Class> ou list<Class>. Se não for possível controlar a forma do comentário do phpDoc, você poderá passar uma matriz de serviços diretamente na configuração usando typed().

Argumentos escalares

A cablagem automática só pode passar objetos e matrizes de objetos. Argumentos escalares (por exemplo, cordas, números, booleanos) escrevem em configuração. Uma alternativa é criar um objeto-objeto de configuração que encapsula um valor escalar (ou múltiplos valores) como um objeto, que pode então ser passado novamente usando a fiação automática.

class MySettings
{
	public function __construct(
		// somente leitura pode ser usado desde PHP 8.1
		public readonly bool $value,
	)
	{}
}

Você cria um serviço ao adicioná-lo à configuração:

services:
	- MySettings('any value')

Todas as classes o solicitarão então por meio de fiação automática.

Estreitamento da fiação automática

Para serviços individuais, a fiação automática pode ser restringida a classes ou interfaces específicas.

Normalmente, a cablagem automática passa o serviço para cada parâmetro do método a cujo tipo o serviço corresponde. Estreitamento significa que especificamos as condições que os tipos especificados para os parâmetros do método devem satisfazer para que o serviço lhes seja passado.

Vejamos um exemplo:

class ParentClass
{}

class ChildClass extends ParentClass
{}

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

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

Se todos eles fossem registrados como serviços, a fiação automática falharia:

services:
	parent: ParentClass
	child: ChildClass
	parentDep: ParentDependent  # THROWS EXCEPTION, both parent and child matches
	childDep: ChildDependent    # passa o serviço 'criança' para o construtor

O serviço parentDep lança a exceção Multiple services of type ParentClass found: parent, child porque tanto parent quanto child cabem em seu construtor e a fiação automática não pode tomar uma decisão sobre qual escolher.

Para o serviço child, podemos, portanto, reduzir sua fiação automática para ChildClass:

services:
	parent: ParentClass
	child:
		create: ChildClass
		autowired: ChildClass  # alternativa para a classe infantil: 'autowired: autowired'

	paiDep: ParentDependent    # THROWS EXCEPTION, the 'child' can not be autowired
	childDep: ChildDependent   # passa o serviço 'criança' para o construtor

O serviço parentDep é agora passado para o construtor de serviços parentDep, uma vez que agora é o único objeto correspondente. O serviço child não é mais passado por auto-cablagem. Sim, o serviço child ainda é do tipo ParentClass, mas a condição de estreitamento dada para o tipo de parâmetro não se aplica mais, ou seja, não é mais verdade que ParentClass * é um supertipo* de ChildClass.

No caso do child, autowired: ChildClass poderia ser escrito como autowired: self, pois o self significa tipo de serviço atual.

A chave autowired pode incluir várias classes e interfaces como matriz:

autowired: [BarClass, FooInterface]

Vamos tentar acrescentar interfaces ao exemplo:

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)
	{}
}

Quando não limitarmos o serviço child, ele caberá nos construtores de todas as classes FooDependent, BarDependent, ParentDependent e ChildDependent e a fiação automática o passará por lá.

No entanto, se limitarmos sua fiação automática a ChildClass usando autowired: ChildClass (ou self), a fiação automática só passa para o construtor ChildDependent, pois requer um argumento do tipo ChildClass e ChildClass é do tipo ChildClass. Nenhum outro tipo especificado para os outros parâmetros é um superconjunto de ChildClass, portanto, o serviço não é passado.

Se restringirmos a ParentClass usando autowired: ParentClass, a fiação automática passará novamente para o construtor ChildDependent (já que o tipo requerido ChildClass é um superconjunto de ParentClass) e para o construtor ParentDependent também, já que o tipo requerido de ParentClass também é compatível.

Se o restringirmos a FooInterface, ele ainda fará a ligação automática para ParentDependent (o tipo exigido ParentClass é um supertipo de FooInterface) e ChildDependent, mas além do construtor FooDependent, mas não para BarDependent, já que BarInterface não é um supertipo de FooInterface.

services:
	child:
		create: ChildClass
		autowired: FooInterface

	fooDep: FooDependent        # passa a criança de serviço para o construtor
	barDep: BarDependent        # EXCEPÇÃO DE GARANTIAS, nenhum serviço passaria
	parentDep: ParentDependent  # passa a criança de serviço para o construtor
	childDep: ChildDependent    # passa a criança de serviço para o construtor
versão: 3.x