O que é DI Container?

O recipiente 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 recipiente de injeção de dependência para tirar proveito da injeção de dependência (DI para abreviar). Afinal de contas, mesmo no capítulo anterior mostramos exemplos específicos de DI e nenhum recipiente era necessário.

Entretanto, se você precisar gerenciar um grande número de objetos diferentes com muitas dependências, um recipiente de injeção de dependência será realmente útil. O que talvez seja o caso de aplicações web construídas sobre uma estrutura.

No capítulo anterior, introduzimos as classes Article e UserController. Ambas têm algumas dependências, a saber, banco de dados e fábrica ArticleFactory. E para estas classes, vamos agora criar um container. Claro que, para um exemplo tão simples, não faz sentido ter um contêiner. Mas vamos criar um para mostrar como ele parece e funciona.

Aqui está um simples recipiente de código rígido para o exemplo acima:

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 teria este aspecto:

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

Nós apenas perguntamos ao recipiente pelo objeto e não precisamos mais saber nada sobre como criá-lo ou quais são suas dependências; o recipiente sabe tudo isso. As dependências são injetadas automaticamente pelo contêiner. Esse é o seu poder.

Até agora, o contêiner tem tudo codificado de forma rígida. Portanto, damos o próximo passo e adicionamos 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' => '***',
]);

Os leitores astutos podem ter notado um problema. Toda vez que eu recebo um objeto UserController, uma nova instância ArticleFactory e um banco de dados também é criado. Definitivamente, nós não queremos isso.

Assim, acrescentamos um método getService() que retornará as mesmas instâncias repetidas vezes:

class Container
{
	private array $services = [];

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

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

	// ...
}

A primeira chamada para, por exemplo, $container->getService('Database') terá createDatabase() criar um objeto de banco de dados, que será armazenado na matriz $services e devolvido diretamente na próxima chamada.

Também modificamos 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 se refere a qualquer objeto administrado pelo contêiner. Daí o nome do método getService().

Feito. Temos um container DI totalmente funcional! E podemos utilizá-lo:

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

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

Como você pode ver, não é difícil escrever um DIC. É notável que os próprios objetos não sabem que um recipiente os está criando. Assim, é possível criar qualquer objeto em PHP desta forma sem afetar seu código fonte.

A criação e manutenção manual de uma classe de contêineres pode se tornar um pesadelo bastante rápido. Portanto, no próximo capítulo falaremos sobre o Nette DI Container, que pode gerar e se atualizar quase que automaticamente.

versão: 3.x