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