Passando Dependências
Argumentos, ou na terminologia de DI “dependências”, podem ser passados para classes das seguintes maneiras principais:
- passagem pelo construtor
- passagem por método (chamado setter)
- configuração de variável
- método, anotação ou atributo inject
Agora mostraremos cada variante com exemplos concretos.
Passagem pelo construtor
As dependências são passadas no momento da criação do objeto como argumentos do construtor:
class MyClass
{
private Cache $cache;
public function __construct(Cache $cache)
{
$this->cache = $cache;
}
}
$obj = new MyClass($cache);
Esta forma é adequada para dependências obrigatórias que a classe necessita essencialmente para sua função, pois sem elas a instância não poderá ser criada.
A partir do PHP 8.0, podemos usar uma forma mais curta de notação (constructor property promotion), que é funcionalmente equivalente:
// PHP 8.0
class MyClass
{
public function __construct(
private Cache $cache,
) {
}
}
A partir do PHP 8.1, a variável pode ser marcada com o sinalizador readonly
, que declara que o conteúdo da
variável não mudará mais:
// PHP 8.1
class MyClass
{
public function __construct(
private readonly Cache $cache,
) {
}
}
O contêiner de DI passa as dependências para o construtor automaticamente usando autowiring. Argumentos que não podem ser passados dessa forma (por exemplo, strings, números, booleanos) escrevemos na configuração.
Constructor hell
O termo constructor hell descreve a situação em que um descendente herda de uma classe pai cujo construtor requer dependências, e ao mesmo tempo o descendente requer dependências. Ele também deve receber e passar as dependências do pai:
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 surge no momento em que queremos alterar o construtor da classe BaseClass
, por exemplo, quando uma
nova dependência é adicionada. Então, é necessário modificar também todos os construtores dos descendentes. O que torna tal
modificação um inferno.
Como evitar isso? A solução é dar preferência à composição em vez de herança.
Ou seja, projetaremos o código de forma diferente. Evitaremos classes abstratas Base*
.
Em vez de MyClass
obter certas funcionalidades herdando de BaseClass
, essa funcionalidade será passada
como 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;
}
}
Passagem por setter
As dependências são passadas chamando um método que as armazena em uma variável privada. A convenção usual de
nomenclatura para esses métodos é a forma set*()
, por isso são chamados de setters, mas podem, é claro, ter
qualquer outro nome.
class MyClass
{
private Cache $cache;
public function setCache(Cache $cache): void
{
$this->cache = $cache;
}
}
$obj = new MyClass;
$obj->setCache($cache);
Este método é adequado para dependências opcionais que não são essenciais para a função da classe, pois não há garantia de que o objeto realmente receberá a dependência (ou seja, que o usuário chamará o método).
Ao mesmo tempo, este método permite chamar o setter repetidamente e, assim, alterar a dependência. Se isso não for
desejado, adicionamos uma verificação ao método ou, a partir do PHP 8.1, marcamos a propriedade $cache
com
o sinalizador readonly
.
class MyClass
{
private Cache $cache;
public function setCache(Cache $cache): void
{
if (isset($this->cache)) {
throw new RuntimeException('A dependência já foi definida');
}
$this->cache = $cache;
}
}
A chamada do setter é definida na configuração do contêiner de DI na chave setup. Aqui também se utiliza a passagem automática de dependências por autowiring:
services:
- create: MyClass
setup:
- setCache
Configuração de variável
As dependências são passadas escrevendo diretamente na variável de membro:
class MyClass
{
public Cache $cache;
}
$obj = new MyClass;
$obj->cache = $cache;
Este método é considerado inadequado porque a variável de membro deve ser declarada como public
. E, portanto,
não temos controle sobre se a dependência passada será realmente do tipo especificado (válido antes do PHP 7.4) e perdemos a
capacidade de reagir à dependência recém-atribuída com código próprio, por exemplo, para impedir alterações subsequentes.
Ao mesmo tempo, a variável 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 contêiner de DI na seção setup:
services:
- create: MyClass
setup:
- $cache = @\Cache
Inject
Enquanto os três métodos anteriores se aplicam geralmente em todas as linguagens orientadas a objetos, a injeção por método, anotação ou atributo inject é específica puramente para presenters no Nette. Eles são discutidos em um capítulo separado.
Qual método escolher?
- o construtor é adequado para dependências obrigatórias que a classe necessita essencialmente para sua função
- o setter, por outro lado, é adequado para dependências opcionais, ou dependências que podem ser alteradas posteriormente
- variáveis públicas não são adequadas