DEV Community

Lucas Cavalcante
Lucas Cavalcante

Posted on

36 5

Princípios SOLID: o que são e como aplicá-los no PHP/Laravel (Parte 01 - Responsabilidade Única)

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
Enter fullscreen mode Exit fullscreen mode

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:

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... */
        ]);
    }
}
Enter fullscreen mode Exit fullscreen mode

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:

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');

    }
}
Enter fullscreen mode Exit fullscreen mode

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:

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);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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:

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

Heroku

This site is built on Heroku

Join the ranks of developers at Salesforce, Airbase, DEV, and more who deploy their mission critical applications on Heroku. Sign up today and launch your first app!

Get Started

Top comments (2)

Collapse
 
vilailus profile image
Luis "shadowtampa" Gomes

Na camada de repositório valeria a pena utilizar model ao invés de query builder? Pois tive problema com as timestamps já que são exclusivas do eloquent...

Collapse
 
robsonpiere profile image
Robson Piere • Edited

Boa explicação, direto ao ponto.

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay