PHP

9 out, 2019

Dependency Injection na vida real com PHP

Publicidade

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!