DEV Community

Lucas Cavalcante
Lucas Cavalcante

Posted on

Princípios SOLID: o que são e como aplicá-los no PHP/Laravel (Parte 05 - Inversão de Dependência)

Dando continuidade à série de artigos sobre princípios SOLID, trago hoje o quinto e último dos princípios.

Dependency Inversion Principle

(Princípio da Inversão de Dependência)

Entidades devem depender de abstrações e não de algo concreto. Isso significa que módulos de alto nível não podem depender de módulos de baixo nível, e sim de abstrações.

Como eu citei na parte 04 "abstração é o coração da orientação a objetos. Em PHP, nós conseguimos alcançar um nível de abstração utilizando interfaces e classes abstratas". Ou seja, assim como na parte 04 nós vamos aplicar o nosso exemplo de Employees e Contractor usando interfaces.

Quando trabalhamos com arquitetura em camadas (que no nosso caso é as camadas Controller, Service, Repository e Model) o ideal é que possamos seguir o fluxo da requisição a partir do Controller, e que as camadas mais externas não saibam nada do que acontece nas camadas mais internas.

Por exemplo:

O Controller não pode depender diretamente do Service, assim como o Service não pode depender da camada de Repository.

Mas por quê?

Pois dessa forma essas camadas estariam dependendo de implementações concretas. Ou seja, se eu mudar alguma coisa no meu Service, pode impactar diretamente no Controller e acabar "quebrando" a aplicação. Isso aumenta o acoplamento entre as classes, o que é uma má prática.

E como resolver isso?

Com a inversão de dependência!

Na parte 04, criei 2 interfaces com o objetivo de deixá-las segregadas, cada uma com suas particularidades.

interface UserInterface
{
  public function register(array $attributes, UserRepository $userRepository);
}

interface ContractorInterface
{
  public function calcWorkedHours(array $hours);
}
Enter fullscreen mode Exit fullscreen mode

Para poder fazer a inversão de dependência, eu preciso injetar a interface do Service no Controller. Mas como fazer isso se temos duas interfaces?

A resposta é simples: herança!

Eu vou criar uma interface, e as duas interfaces criadas anteriormente (UserInterface e ContractorInterface) irão estendê-la.

interface ServiceInterface
{
}

interface UserInterface extends ServiceInterface
{
  public function register(array $attributes, UserRepository $userRepository);
}

interface ContractorInterface extends ServiceInterface
{
  public function calcWorkedHours(array $hours);
}
Enter fullscreen mode Exit fullscreen mode

Dessa forma, conseguimos concentrar toda essa camada de abstração em um único ponto.

Agora, vamos ao Controller e vamos injetar essa interface.

class UserRegistrationController extends Controller
{
    protected $userService;

    public function __construct(ServiceInterface $userService)
    {
        $this->userService = $userService;
    }

    // implementação do código
}
Enter fullscreen mode Exit fullscreen mode

Percebam que mesmo o nosso atributo seja nomeado como $userService, ele é do tipo ServiceInterface. E dessa forma evitamos um forte acoplamento entre as camadas concretas, deixando todo serviço a cargo da abstração, que no nosso caso é a ServiceInterface.

Fica aqui o desafio: tente fazer o mesmo processo entre as camadas de Service e Repository. :)

Espero que tenham gostado dessa série de artigos sobre SOLID, e em breve trarei novos conteúdos.

Fui!

Discussion (2)

Collapse
julioolver profile image
Julio Cesar Oliveira

Muito bom! Estou fazendo um projeto do zero, e estou seguindo a estrutura SOLID, vendo vídeos e artigos, como este.

Por fim, segui basicamente este artigo, então só queria dizer que o que mais senti uma certa resistência foi na implementação deste princípio (D), mas depois que entendi, ok, faz total sentido.

Só queria complementar agradecendo pelo seu tempo dedicado para fazer isso e ajudar outros devs, também gostaria de adicionar para talvez alguém que veja e não saiba, é necessário adicionar no AppServiceProvider, no método Register, o bind entre o Serivce e a Interface, pois, caso contrário irá estourar uma Exception de que a interface não é/está instanciada.

Collapse
tiagoschaeffer profile image
Tiago Santos

Desculpe a minha ignorância, mas considerando o artigo (que no contexto exposto faz todo o sentido), antes disso tudo, no router vou precisar instanciar a model com os valores dos atributos; instanciar o repository injetando a instancia da model; instanciar a service injetando o repository para ai injetar a service na controller? isso??
Como faria para instanciar a model antes de ter os devidos atributos setados, já que faria isso na service?
Não entendi direito como ficaria o fluxo dos dados considerando que eles podem vir do client side...

Abraço!