Dependências de passagem
Argumentos, ou “dependências” na terminologia DI, podem ser passados às aulas das seguintes maneiras principais:
- passando por construtor
- passando por método (chamado setter)
- definindo uma propriedade
por método, anotação ou atributo *injectar
Vamos agora ilustrar as diferentes variantes com exemplos concretos.
Injeção do construtor
As dependências são passadas como argumentos para o construtor quando o objeto é criado:
class MyClass
{
private Cache $cache;
public function __construct(Cache $cache)
{
$this->cache = $cache;
}
}
$obj = new MyClass($cache);
Este formulário é útil para as dependências obrigatórias que a classe precisa absolutamente funcionar, pois sem elas a instância não pode ser criada.
Desde o PHP 8.0, podemos usar uma forma mais curta de notação que é funcionalmente equivalente (constructor property promotion):
// PHP 8.0
class MyClass
{
public function __construct(
private Cache $cache,
) {
}
}
A partir do PHP 8.1, uma propriedade pode ser marcada com uma bandeira readonly
que declara que o conteúdo da
propriedade não mudará:
// PHP 8.1
class MyClass
{
public function __construct(
private readonly Cache $cache,
) {
}
}
Recipiente DI passa automaticamente as dependências para o construtor usando a fiação automática. Argumentos que não podem ser passados desta forma (por exemplo, strings, números, booleans) escrevem na configuração.
Construtor Inferno
O termo inferno construtor refere-se a uma situação em que uma criança herda de uma classe de pais cujo construtor requer dependências, e a criança requer dependências também. Também deve assumir e transmitir as dependências dos pais:
abstract class BaseClass
{
private Cache $cache;
public function __construct(Cache $cache)
{
$this->cache = $cache;
}
}
final class MyClass extends BaseClass
{
private Database $db;
// ⛔ CONSTRUCTOR HELL
public function __construct(Cache $cache, Database $db)
{
parent::__construct($cache);
$this->db = $db;
}
}
O problema ocorre quando queremos mudar o construtor da classe BaseClass
, por exemplo, quando uma nova
dependência é acrescentada. Então temos que modificar todos os construtores das crianças também. O que faz de tal
modificação um inferno.
Como evitar isso? A solução é priorizar a composição em vez da herança.
Portanto, vamos projetar o código de forma diferente. Evitaremos classes Base*
abstratas. Em vez de
MyClass
obter alguma funcionalidade herdando de BaseClass
, essa funcionalidade será passada como uma
dependência:
final class SomeFunctionality
{
private Cache $cache;
public function __construct(Cache $cache)
{
$this->cache = $cache;
}
}
final class MyClass
{
private SomeFunctionality $sf;
private Database $db;
public function __construct(SomeFunctionality $sf, Database $db) // ✅
{
$this->sf = $sf;
$this->db = $db;
}
}
Injeção de setter
As dependências são passadas chamando um método que as armazena em uma propriedade privada. A convenção usual de nomes
para estes métodos é a forma set*()
, razão pela qual eles são chamados de setters, mas é claro que eles podem
ser chamados de qualquer outra coisa.
class MyClass
{
private Cache $cache;
public function setCache(Cache $cache): void
{
$this->cache = $cache;
}
}
$obj = new MyClass;
$obj->setCache($cache);
Este método é útil para dependências opcionais que não são necessárias para a função de classe, uma vez que não é garantido que o objeto realmente as receberá (ou seja, que o usuário chamará o método).
Ao mesmo tempo, este método permite que o setter seja chamado repetidamente para mudar a dependência. Se isto não for
desejável, acrescente uma verificação ao método, ou a partir do PHP 8.1, marque a propriedade $cache
com a
bandeira readonly
.
class MyClass
{
private Cache $cache;
public function setCache(Cache $cache): void
{
if ($this->cache) {
throw new RuntimeException('The dependency has already been set');
}
$this->cache = $cache;
}
}
A chamada do setter é definida na configuração do recipiente DI na configuração da seção. Também aqui a passagem automática de dependências é usada por cabeamento automático:
services:
- create: MyClass
setup:
- setCache
Injeção de propriedade
As dependências são passadas diretamente para a propriedade:
class MyClass
{
public Cache $cache;
}
$obj = new MyClass;
$obj->cache = $cache;
Este método é considerado inadequado porque a propriedade deve ser declarada como public
. Assim, não temos
controle sobre se a dependência passada será realmente do tipo especificado (isto era verdade antes do PHP 7.4) e perdemos a
capacidade de reagir à dependência recém-atribuída com nosso próprio código, por exemplo, para evitar mudanças
subseqüentes. Ao mesmo tempo, a propriedade se torna parte da interface pública da classe, o que pode não ser desejável.
A configuração da variável é definida na configuração do recipiente DI na configuração da seção:
services:
- create: MyClass
setup:
- $cache = @\Cache
Injetar
Enquanto os três métodos anteriores são geralmente válidos em todos os idiomas orientados a objetos, a injeção por método, a anotação ou o atributo injet é específico para os apresentadores Nette. Eles são discutidos em um capítulo separado.
Qual a maneira de escolher?
- construtor é adequado para as dependências obrigatórias que a classe precisa para funcionar
- o setter, por outro lado, é adequado para dependências opcionais, ou dependências que podem ser alteradas
- variáveis públicas não são recomendadas