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