Princípio de responsabilidade única (SRP) com o Laravel

O princípio SRP (princípio de responsabilidade única) é um dos princípios fundamentais para escrever código suportado. Neste artigo, mostrarei como aplicar esse princípio usando o exemplo da linguagem PHP e a estrutura do Laravel.

Freqüentemente, ao descrever o modelo de desenvolvimento do MVC, tarefas excessivamente grandes são atribuídas ao controlador. Obtendo parâmetros, lógica de negócios, autorização e resposta.

Obviamente, em artigos e livros isso é descrito como um exemplo, mas geralmente é percebido como um plano de ação em projetos de trabalho. Essa abordagem inevitavelmente levará a um crescimento descontrolado de classe e complicará bastante o suporte ao código.

Princípio da responsabilidade compartilhada


Por exemplo, tomaremos o tipo bastante grosseiro, mas geralmente atendido, do controlador "grosso".

class OrderController extends Controller
{
    public function create(Request $request)
    {
        $rules = [
            'product_id' => 'required|max:10',
            'count' => 'required|max:10',
        ];
        $validator = Validator::make($request->all(), $rules);
        if ($validator->fails()) {
            return response()->json($validator->errors(), 400);
        }

         //  
        $order = Order::create($request->all());

        //   

        //  sms    

        return response()->json($order, 201);
    }
}

Neste exemplo, pode-se observar que o responsável pelo controle sabe muito sobre "fazer um pedido", também é encarregado de notificar o comprador e fazer a reserva de mercadorias.

Nós nos esforçaremos para garantir que o controlador tenha apenas o controle de todo o processo e nenhuma lógica de negócios.

Para começar, validaremos os parâmetros em uma classe Request separada.

class OrderRequest extends Request
{
    public function rules(): array
    {
         return [
             'product_id' => 'required|max:10',
             'count' => 'required|max:10',
         ];    
    }
}

E mova toda a lógica de negócios para a classe OrderService

public class OrderService
{
    public function create(array $params)
    {
        //  

        //  

        //     sms
        $sms = new RuSms($appId);

        //  ,    
        $sms->send($number, $text);
     }
}

Usando o Service Container, implementaremos nosso serviço no controlador.

Como resultado, temos um controlador "fino".

class OrderController extends Controller
{
    protected $service;
	
    public function __construct(OrderService $service)
    {
	$this->service = $service;	
    }

    public function create(OrderRequest $request)
    {
        $this->service->create($request->all());

        return response()->noContent(201);
    }
}

Já está melhor. Toda a lógica de negócios foi movida para o serviço. O controlador conhece apenas as classes OrderRequest e OrderService - o conjunto mínimo de informações necessárias para realizar seu trabalho.

Mas agora nosso serviço também precisa ser refatorado. Vamos tirar a lógica de enviar sms para uma classe separada.

class SmsRu
{
    protected $appId;

    public function __constructor(string $appId)
    {
        $this->appId = $appId;
    }

    public function send($number, $text): void
    {
          //     
    }
}    

E implementá-lo através do construtor

class OrderService
{
    private $sms;

    public function __construct()
    {
	$this->sms = new SmsRu($appId);	
    }

    public function create(array $params): void
    {
          //  

          //  

          //  ,    
          $this->sms->send($nubmer, $text);
    }
}    

Já é melhor, mas a classe OrderService ainda sabe muito sobre o envio de mensagens. Talvez seja necessário substituir o provedor de mensagens no futuro ou adicionar testes de unidade. Continuamos refatorando adicionando a interface SmsSender e especificando SmsRu por meio do provedor SmsServiceProvider .

interface SmsSenderInterface
{
    public function send($number, $text): void;
}

class SmsServiceProvider implements ServiceProviderInterface
{
    public function register(): void
    {
        $this->app->singleton(SmsSenderInterface::class, function ($app) {
            return new SmsRu($params['app_id']);
        });
    }
}

Agora o serviço também é liberado de detalhes desnecessários

class OrderService
{
    private $sms;

    public function __construct(SmsSenderInterface $sms)
    {
	$this->sms = $sms;	
    }

    public function create(): void
    {
          //  

          //  

          //  ,    
          $this->sms->send($nubmer, $text);
    }
}    

Arquitetura Orientada a Eventos


O envio de mensagens não faz parte do processo básico de criação de pedidos. O pedido será criado independentemente da mensagem enviada; também é possível no futuro a opção de cancelar alertas de SMS será adicionada. Para não sobrecarregar o OrderService com detalhes de notificação desnecessários, você pode usar os Observadores do Laravel . Uma classe que rastreará eventos com um determinado comportamento do nosso modelo de pedidos e atribuirá a ele toda a lógica da notificação do cliente.

class OrderObserver
{
    private $sms;

    public function __construct(SmsSenderInterface $sms)
    {
	$this->sms = $sms;	
    }

    /**
     * Handle the Order "created" event.
     */
    public function created(Order $order)
    {
        $this->sms->send($nubmer, $text);
    }

Não se esqueça de registrar o OrderObserver no AppServiceProvider .

Conclusão


Entendo que estou descrevendo coisas bastante banais neste post, mas queria mostrar como esse princípio é implementado na estrutura do Laravel.

O princípio da SPR é um dos mais importantes e abre o caminho para o resto. É necessário usar, especialmente porque a estrutura do Laravel fornece todas as ferramentas necessárias.

All Articles