Hoje irei escrever sobre este tema pois percebo que algumas pessoas desenvolvedoras de software apresentam dificuldades em utilizar os conceitos de injeção de dependência (Dependency Injection) em aplicações reais.
O que é Dependency Injection?
Em primeiro lugar, observe este código:
<?php
declare(strict_types=1);
namespace App\Example;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
final class FirstExample
{
/**
* @var Logger
*/
private $logger;
public function __construct()
{
$this->logger = new Logger('My_Logger', [
new StreamHandler('logs/debug.log', Logger::DEBUG),
]);
}
public function something(): void
{
//...
$this->logger->debug('first example debug description');
}
}
$controller = new FirstExample();
$controller->something();
Este código funciona, mas também gera um problema grave! Quando criamos uma instância de Logger
dentro do construtor de FirstExample
, criamos um forte acoplamento entre essas duas classes.
Imagine se precisarmos implementar esse padrão em diversas classes da nossa aplicação, ou pior, e se em determinado momento seja necessário alterar a forma como Logger
deve ser instanciada?
Dá enjoo só de pensar!
Para resolver isso, podemos transferir a criação da instância de Logger
(dependência) para fora da classe FirstExample
e passa-la em sua construção, por exemplo:
<?php
declare(strict_types=1);
namespace App\Example;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
final class SecondExample
{
/**
* @var Logger
*/
private $logger;
public function __construct(Logger $logger)
{
$this->logger = $logger;
}
public function something(): void
{
//...
$this->logger->debug('second example debug description');
}
}
$logger = new Logger('My_Logger', [
new StreamHandler('logs/debug.log', Logger::DEBUG),
]);
$controller = new SecondExample($logger);
$controller->something();
Isso é injeção de dependência, ou seja, quando “injetamos” uma instância de classe dentro de outra, onde a classe “injetada” é a dependência.
Dica de ouro
Existe um principio muito bacana no SOLID que se chama Princípio da Inversão de Dependência que além de outras coisas sugere que nossas classes dependam de abstrações e não de classes concretas.
Como as abstrações (leia-se interfaces) expõem um contrato de utilização “obrigando” as classes que a implementam a segui-lo, podemos utilizar qualquer classe que firme este contrato como dependência, flexibilizando a implementação e reduzindo o acoplamento.
Sabendo disso, podemos refatorar a classe exemplo substituindo Logger
por LoggerInterface
, uma vez que o componente que estamos utilizando (monolog) implementa a PSR3 que define a LoggerInterface
como padrão de implementação de logs para PHP.
Com a refatoração, o código fica assim:
<?php
declare(strict_types=1);
namespace App\Example;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use Psr\Log\LoggerInterface;
final class ThirdExample
{
/**
* @var LoggerInterface
*/
private $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
public function something(): void
{
//...
$this->logger->debug('third example debug description');
}
}
$logger = new Logger('My_Logger', [
new StreamHandler('logs/debug.log', Logger::DEBUG),
]);
$controller = new ThirdExample($logger);
$controller->something();
Como e por que utilizar um container de injeção de dependência?
Um container o quê!?
Em resumo, um container de injeção de dependência serve para entre outras coisas, abstrair o controle sobre quais instâncias devem ser criadas, como será feito (configurando definições, por exemplo) e quando isso é necessário.
Este é o código do nosso exemplo refatorado para a utilização de um container de injeção de dependência:
<?php
declare(strict_types=1);
namespace App\Example;
use DI\ContainerBuilder;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use Psr\Log\LoggerInterface;
final class FourthExample
{
/**
* @var LoggerInterface
*/
private $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
public function something(): void
{
//...
$this->logger->debug('fourth example debug description');
}
}
$builder = new ContainerBuilder();
$builder->addDefinitions([
LoggerInterface::class => new Logger('My_Logger', [
new StreamHandler('logs/debug.log', Logger::DEBUG),
]),
]);
$container = $builder->build();
$exampleController = $container->get('\App\Example\FourthExample');
$exampleController->something();
// OR
$container->call(['\App\Example\FourthExample', 'something'], []);
Na linha 7
estamos declarando a utilização do PHPDI, um container de injeção de dependência muito utilizado em projetos PHP.
O código é basicamente o mesmo que foi utilizado nos últimos exemplos e as mudanças ocorrem de fato a partir da linha 32
, onde criamos uma instância do container. Na linha 34
, através do método addDefinitions()
, definimos as injeções que serão utilizadas em nosso aplicativo.
Na linha 40
, através do método build()
, criamos de fato o nosso container. Na linha 42
, através do método get()
, criamos uma instância de FourthExample
que é atribuída a variável $exampleController
e através dela podemos invocar o mal qualquer método da classe FourthExample
normalmente, sem problemas.
Observação:
Veja que na linha 48
declarei uma forma alternativa de instanciar uma classe através do container utilizando o método call()
, dessa forma, passamos o nome da classe que queremos instanciar e o método que deverá ser invocado seguido de um array com os parâmetros que o método espera receber.
E o que mudou?
Percebam que em nenhum momento precisamos instanciar e/ou “injetar” a(s) dependência(s) necessária(s) para que a classe FourthExample
funcione corretamente, pois o container se encarrega de realizar essa tarefa e essa é uma das grandes sacadas em se utilizar um container de injeção de dependência.
Observação:
Em alguns casos não será necessário configurar as injeções para os nossos aplicativos e para estes casos podemos utilizar um recurso que inclusive, já vem habilitado por padrão com o PHPDI, o autowiring.
Como complemento, sugiro que leiam sobre o recurso de autowiring e como definir injeções manualmente com o componente de injeção de dependência que estamos utilizando.
Criei um projeto que executa todos os exemplos utilizados neste artigo através do terminal. Trata-se de uma aplicação muito básica que gera logs (proposta dos códigos acima), sendo um log para cada exemplo executado. Este é o link do repositório: Repositório no GitHub
Chegamos ao final de mais um artigo e eu espero de verdade que este conteúdo tenha sido útil para você.
Forte abraço e até a próxima!