Autowiring
Autowiring é um ótimo recurso que pode passar automaticamente os serviços necessários para o construtor e outros métodos, para que não precisemos escrevê-los. Isso economiza muito tempo.
Graças a isso, podemos omitir a grande maioria dos argumentos ao escrever definições de serviço. Em vez de:
services:
articles: Model\ArticleRepository(@database, @cache.storage)
Basta escrever:
services:
articles: Model\ArticleRepository
O Autowiring é orientado por tipos, então para funcionar, a classe ArticleRepository
deve ser definida
aproximadamente assim:
namespace Model;
class ArticleRepository
{
public function __construct(\PDO $db, \Nette\Caching\Storage $storage)
{}
}
Para poder usar o autowiring, deve haver exatamente um serviço para cada tipo no contêiner. Se houver mais, o autowiring não saberá qual passar e lançará uma exceção:
services:
mainDb: PDO(%dsn%, %user%, %password%)
tempDb: PDO('sqlite::memory:')
articles: Model\ArticleRepository # LANÇARÁ EXCEÇÃO, tanto mainDb quanto tempDb correspondem
A solução seria contornar o autowiring e especificar explicitamente o nome do serviço (ou seja,
articles: Model\ArticleRepository(@mainDb)
). Mas é mais inteligente desativar
o autowiring para um dos serviços, ou dar preferência ao primeiro serviço.
Desativação do autowiring
Podemos desativar o autowiring de um serviço usando a opção autowired: no
:
services:
mainDb: PDO(%dsn%, %user%, %password%)
tempDb:
create: PDO('sqlite::memory:')
autowired: false # o serviço tempDb é excluído do autowiring
articles: Model\ArticleRepository # portanto, passa mainDb para o construtor
O serviço articles
não lançará uma exceção dizendo que existem dois serviços do tipo PDO
correspondentes (ou seja, mainDb
e tempDb
) que podem ser passados para o construtor, porque ele vê
apenas o serviço mainDb
.
A configuração do autowiring no Nette funciona de forma diferente do Symfony, onde a opção
autowire: false
diz que o autowiring não deve ser usado para os argumentos do construtor do serviço fornecido. No
Nette, o autowiring é sempre usado, seja para argumentos do construtor ou para quaisquer outros métodos. A opção
autowired: false
diz que a instância do serviço fornecido não deve ser passada para lugar nenhum usando
autowiring.
Preferência de autowiring
Se tivermos vários serviços do mesmo tipo e especificarmos a opção autowired
para um deles, esse serviço se
torna o preferido:
services:
mainDb:
create: PDO(%dsn%, %user%, %password%)
autowired: PDO # torna-se preferido
tempDb:
create: PDO('sqlite::memory:')
articles: Model\ArticleRepository
O serviço articles
não lançará uma exceção dizendo que existem dois serviços do tipo PDO
correspondentes (ou seja, mainDb
e tempDb
), mas usará o serviço preferido, ou seja,
mainDb
.
Array de serviços
O Autowiring também pode passar arrays de serviços de um determinado tipo. Como não é possível escrever nativamente
o tipo dos itens do array em PHP, é necessário, além do tipo array
, adicionar um comentário phpDoc com o tipo
do item no formato ClassName[]
:
namespace Model;
class ShipManager
{
/**
* @param Shipper[] $shippers
*/
public function __construct(array $shippers)
{}
}
O contêiner DI então passa automaticamente um array de serviços correspondentes ao tipo fornecido. Ele omite serviços que têm o autowiring desativado.
O tipo no comentário também pode estar no formato array<int, Class>
ou list<Class>
. Se
você não pode influenciar a forma do comentário phpDoc, pode passar o array de serviços diretamente na configuração usando
typed()
.
Argumentos escalares
O Autowiring só pode injetar objetos e arrays de objetos. Argumentos escalares (por exemplo, strings, números, booleanos) são escritos na configuração. Uma alternativa é criar um objeto de configurações, que encapsula o valor escalar (ou múltiplos valores) em um objeto, que pode então ser passado novamente usando autowiring.
class MySettings
{
public function __construct(
// readonly pode ser usado a partir do PHP 8.1
public readonly bool $value,
)
{}
}
Você cria um serviço a partir dele adicionando-o à configuração:
services:
- MySettings('any value')
Todas as classes então o solicitarão usando autowiring.
Restringindo o autowiring
Para serviços individuais, o autowiring pode ser restrito a certas classes ou interfaces.
Normalmente, o autowiring passa o serviço para cada parâmetro de método cujo tipo o serviço corresponde. Restringir significa que estabelecemos condições que os tipos especificados nos parâmetros do método devem satisfazer para que o serviço seja passado para eles.
Vamos ilustrar com um exemplo:
class ParentClass
{}
class ChildClass extends ParentClass
{}
class ParentDependent
{
function __construct(ParentClass $obj)
{}
}
class ChildDependent
{
function __construct(ChildClass $obj)
{}
}
Se registrássemos todos eles como serviços, o autowiring falharia:
services:
parent: ParentClass
child: ChildClass
parentDep: ParentDependent # LANÇARÁ EXCEÇÃO, os serviços parent e child correspondem
childDep: ChildDependent # autowiring passa o serviço child para o construtor
O serviço parentDep
lançará a exceção
Multiple services of type ParentClass found: parent, child
, porque ambos os serviços parent
e
child
se encaixam em seu construtor, e o autowiring não pode decidir qual escolher.
Para o serviço child
, podemos, portanto, restringir seu autowiring ao tipo ChildClass
:
services:
parent: ParentClass
child:
create: ChildClass
autowired: ChildClass # também pode escrever 'autowired: self'
parentDep: ParentDependent # autowiring passa o serviço parent para o construtor
childDep: ChildDependent # autowiring passa o serviço child para o construtor
Agora, o serviço parent
é passado para o construtor do serviço parentDep
, porque agora é
o único objeto correspondente. O autowiring não passa mais o serviço child
para lá. Sim, o serviço
child
ainda é do tipo ParentClass
, mas a condição restritiva dada para o tipo do parâmetro não é
mais válida, ou seja, não é verdade que ParentClass
é um supertipo de ChildClass
.
Para o serviço child
, autowired: ChildClass
também poderia ser escrito como
autowired: self
, já que self
é um placeholder para a classe do serviço atual.
Na chave autowired
, também é possível especificar várias classes ou interfaces como um array:
autowired: [BarClass, FooInterface]
Vamos tentar complementar o exemplo com interfaces:
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)
{}
}
Se não restringirmos o serviço child
de forma alguma, ele se encaixará nos construtores de todas as classes
FooDependent
, BarDependent
, ParentDependent
e ChildDependent
, e o autowiring
o passará para lá.
No entanto, se restringirmos seu autowiring a ChildClass
usando autowired: ChildClass
(ou
self
), o autowiring o passará apenas para o construtor de ChildDependent
, porque ele requer um
argumento do tipo ChildClass
e é verdade que ChildClass
é do tipo ChildClass
.
Nenhum outro tipo especificado nos outros parâmetros é um supertipo de ChildClass
, então o serviço não é
passado.
Se o restringirmos a ParentClass
usando autowired: ParentClass
, ele será novamente passado para
o construtor de ChildDependent
(porque o ChildClass
exigido é um supertipo de
ParentClass
) e, agora também para o construtor de ParentDependent
, porque o tipo
ParentClass
exigido também é adequado.
Se o restringirmos a FooInterface
, ele ainda será autowired para ParentDependent
(o
ParentClass
exigido é um supertipo de FooInterface
) e ChildDependent
, mas adicionalmente
também para o construtor de FooDependent
, mas não para BarDependent
, porque BarInterface
não é um supertipo de FooInterface
.
services:
child:
create: ChildClass
autowired: FooInterface
fooDep: FooDependent # autowiring passa child para o construtor
barDep: BarDependent # LANÇARÁ EXCEÇÃO, nenhum serviço corresponde
parentDep: ParentDependent # autowiring passa child para o construtor
childDep: ChildDependent # autowiring passa child para o construtor