O que é um Contêiner DI?

Um contêiner de injeção de dependência (DIC) é uma classe que pode instanciar e configurar objetos.

Pode surpreendê-lo, mas em muitos casos, você não precisa de um contêiner de injeção de dependência para aproveitar os benefícios da injeção de dependência (DI para abreviar). Afinal, mesmo no capítulo introdutório, mostramos DI com exemplos concretos e nenhum contêiner foi necessário.

No entanto, se você precisar gerenciar um grande número de objetos diferentes com muitas dependências, um contêiner de injeção de dependência será realmente útil. É o caso, por exemplo, de aplicações web construídas sobre um framework.

No capítulo anterior, apresentamos as classes Article e UserController. Ambas têm algumas dependências, nomeadamente o banco de dados e a fábrica ArticleFactory. E para essas classes, agora criaremos um contêiner. Claro, para um exemplo tão simples, não faz sentido ter um contêiner. Mas vamos criá-lo para mostrar como ele se parece e funciona.

Aqui está um contêiner simples hardcoded para o exemplo dado:

class Container
{
	public function createDatabase(): Nette\Database\Connection
	{
		return new Nette\Database\Connection('mysql:', 'root', '***');
	}

	public function createArticleFactory(): ArticleFactory
	{
		return new ArticleFactory($this->createDatabase());
	}

	public function createUserController(): UserController
	{
		return new UserController($this->createArticleFactory());
	}
}

O uso seria o seguinte:

$container = new Container;
$controller = $container->createUserController();

Apenas pedimos ao contêiner o objeto e não precisamos mais saber nada sobre como criá-lo ou quais são suas dependências; o contêiner sabe tudo isso. As dependências são injetadas automaticamente pelo contêiner. Essa é a sua força.

Por enquanto, o contêiner tem todos os dados codificados. Daremos o próximo passo e adicionaremos parâmetros para tornar o contêiner realmente útil:

class Container
{
	public function __construct(
		private array $parameters,
	) {
	}

	public function createDatabase(): Nette\Database\Connection
	{
		return new Nette\Database\Connection(
			$this->parameters['db.dsn'],
			$this->parameters['db.user'],
			$this->parameters['db.password'],
		);
	}

	// ...
}

$container = new Container([
	'db.dsn' => 'mysql:',
	'db.user' => 'root',
	'db.password' => '***',
]);

Leitores atentos podem ter notado um certo problema. Toda vez que obtenho um objeto UserController, uma nova instância de ArticleFactory e do banco de dados também é criada. Definitivamente não queremos isso.

Portanto, adicionaremos um método getService() que sempre retornará as mesmas instâncias:

class Container
{
	private array $services = [];

	public function __construct(
		private array $parameters,
	) {
	}

	public function getService(string $name): object
	{
		if (!isset($this->services[$name])) {
			// getService('Database') chamará createDatabase()
			$method = 'create' . $name;
			$this->services[$name] = $this->$method();
		}
		return $this->services[$name];
	}

	// ...
}

Na primeira chamada, por exemplo, $container->getService('Database'), ele fará com que createDatabase() crie o objeto do banco de dados, que ele armazena no array $services, e na próxima chamada, ele o retorna diretamente.

Também modificaremos o restante do contêiner para usar getService():

class Container
{
	// ...

	public function createArticleFactory(): ArticleFactory
	{
		return new ArticleFactory($this->getService('Database'));
	}

	public function createUserController(): UserController
	{
		return new UserController($this->getService('ArticleFactory'));
	}
}

A propósito, o termo serviço refere-se a qualquer objeto gerenciado pelo contêiner. É por isso que o método se chama getService().

Feito. Temos um contêiner DI totalmente funcional! E podemos usá-lo:

$container = new Container([
	'db.dsn' => 'mysql:',
	'db.user' => 'root',
	'db.password' => '***',
]);

$controller = $container->getService('UserController');
$database = $container->getService('Database');

Como você pode ver, escrever um DIC não é complicado. Vale a pena notar que os próprios objetos não sabem que estão sendo criados por algum contêiner. Assim, é possível criar qualquer objeto em PHP dessa forma sem interferir em seu código-fonte.

Criar e manter manualmente a classe do contêiner pode se tornar rapidamente um pesadelo. Portanto, no próximo capítulo, falaremos sobre o Nette DI Container, que pode se gerar e atualizar quase sozinho.

versão: 3.x