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
versão: 3.x