Princípios SOLID: o que são e como aplicá-los no PHP/Laravel (Parte 01 - Responsabilidade Única)
Tags: php, solid, architecture, laravel
Published: 2020-05-21 23:00:01

Hoje vou abordar um dos temas que mais me interessa dentro do mundo do desenvolvimento de software: escrever códigos melhores.

Dentro deste tema costuma-se falar de arquitetura de software, padrões de projeto, design de software, etc.

Nesta série de artigos vou abordar o SOLID que é um princípio de design de software orientado a objeto (OOD). Basicamente, SOLID é um acrônimo para:
```
S - Single Responsibility Principle
O - Open-closed Principle
L - Liskov Substitution Principle
I - Interface Segregation Principle
D - Dependency Inversion Principle
```
Vamos entender como funciona cada um deles e exemplicar usando o PHP. Mais especificamente o Laravel, por ser um _framework_ que adota o MVC por padrão e, com o crescimento da aplicação, tende a ficar com o código/estrutura bem bagunçados se não organizar desde o começo.

Então, neste primeiro artigo vamos abordar o primeiro dos princípios de forma detalhada.

# Single Responsibility Principle
_(Princípio da Responsabilidade Única)_

> Uma classe deve ter uma e apenas uma razão para mudança, significando que uma classe deve ter apenas uma responsabilidade. 

Este é o primeiro, mais simples, e mais importante princípio, pois parte da premissa que toda classe deve ter apenas 1 objetivo quando se trata do fluxo de uma requisição do software.

Vamos dar uma olhada no exemplo abaixo:
```php
class UserRegistrationController extends Controller
{
    public function register(Request $request)
    {
        $attributes = $request->all();
        if($attributes['type'] === 'employee') {
            $this->saveEmployee($attributes);
        }

        if($attributes['type'] === 'contractor') {
            $this->saveContractor($attributes);
        }

        return view('user.register');
    }

    public function saveEmployee($request)
    {
        DB::table('employees')->insert([
            /* Dados do funcionário aqui... */
        ]);
    }

    public function saveContractor($request)
    {
        DB::table('employees')->insert([
            /* Dados do terceirizado aqui... */
        ]);
    }
}
```
Esta classe acima viola o princípio da responsabilidade única pelos motivos a seguir:
* Mistura responsabilidades. Um _Controller_ deve ser responsável por apenas gerenciar _requests_ e _responses_.
* Regra de negócio de ser aplicada em uma camada separada, como a camada de _Service_, por exemplo. (que não tem como padrão no Laravel, mas podemos criá-la manualmente)
* Operações com o banco de dados também devem estar em outra camada. No Laravel, por padrão, usamos o _Model_. Mas no neste artigo vou usar a camada de _Repository_. (mesmo caso do _Service_)

Vamos corrigir a classe:
```php
class UserRegistrationController extends Controller
{
    protected $userService;

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

    public function register(Request $request)
    {
        $userService->register($request);
        
        return view('user.register');

    }
}
```
Aqui deixamos o _Controller_ isolado com a sua única responsabilidade: gerenciar a _request_ e a _response_.

No Laravel, para podermos adicionar uma camada de comunicação com a classe atual, nós usamos a injeção de dependência nativa do _framework_. Basta adicionar a classe desejada como parâmetro no construtor e atribuí-la em uma propriedade da classe. E eu fiz isso adicionando a classe `UserService`.

Vamos entender agora a classe `UserService`:
```php
class UserService
{
    protected $employeeRepository;
    protected $contractorRepository;

    public function __construct(
        EmployeeRepository $employeeRepository,
        ContractorRepository $contractorRepository
    )
    {
        $this->userRepository = $userRepository;
        $this->contractorRepository = $contractorRepository;
    }

    public function register(Request $request)
    {
        $attributes = $request->all();
        if($attributes['type'] === 'employee') {
            $this->employeeRepository->save($attributes);
        }

        if($attributes['type'] === 'contractor') {
            $this->contractorRepository->save($attributes);
        }
    }
}
```
O _Service_ é o local onde colocamos toda a regra de negócio, como é o caso do condicional que usamos para identificar o tipo de usuário.

Aqui também usamos a injeção de dependência, mas importamos 2 classes. Exatamente os _Repositories_ dos tipos de usuário. Como o _Model_ é a representação da tabela do banco dentro da aplicação, e o _Repository_ é reponsável pela operação de banco de cada _Model_, nada mais coerente mantê-los separados.

Vamos entender agora os _Repositories_:
```php
class EmployeeRespository
{
    protected $employee;

    public function __construct(Employee $employee)
    {
        $this->employee = $employee;
    }

    public function save(array $attributes)
    {
        $this->employee->insert($attributes);
    }
}

class ContractorRespository
{
    protected $contractor;

    public function __construct(Employee $contractor)
    {
        $this->contractor = $contractor;
    }

    public function save(array $attributes)
    {
        $this->contractor->insert($attributes);
    }
}
```
Em ambos os _Repositories_ utilizamos a mesma estrutura: injeção de dependência com o respectivos _Models_, e um método `save()`.

Dessa maneira, separamos as responsabilidades em camadas, deixando o código mais legível e com uma arquitetura mais correta.

No próximo artigo vou abordar o segundo princípio: Open-closed Principle (Princípio Aberto-fechado).

Espero que você tenha gostado. Dúvidas e feedbacks são sempre muito bem vindos.

_PS:_ Se você quiser adicionar _Services_ e _Repositories_ na sua aplicação Laravel, é só criar as pastas `app/Services` e `app/Repositories` e adicionar suas classes manualmente.

_PPS:_ Neste exemplo abordei apenas o essencial para explicá-lo, deixando de fora _imports_, _returns_, _validators_, etc.

_PPPS:_ Eu sei que o ideal seria ter um _Model_ `User` e estender para `Employee` e `Contractor`. Mas para o exemplo ficar mais claro, deixei-os separados mesmo.