DEV Community

Denis Augusto
Denis Augusto

Posted on

Seu controller tá fazendo o trabalho do Form Request (e ele não devia)

Abre o seu controller. Vai, eu espero

Tem um método store() aí dentro com umas 40 linhas? Começando com um $request->validate([...]) gigante, seguido de um monte de if, depois a regra de negócio, depois o retorno?

Pois é. Seu controller tá fazendo um trabalho que não é dele.

A função de um controller é simples: receber a requisição, chamar quem resolve o problema, devolver a resposta. Ele é o garçom, não o cozinheiro. E ainda assim a gente vive enfiando validação, autorização e tratamento de dados ali no meio, até virar aquele monstrinho de 80 linhas que ninguém quer abrir na sexta-feira.

Já viu (ou escreveu) isso? Senta aí que tem solução, e ela já vem de fábrica no Laravel.

O controller-faz-tudo

Olha esse clássico:

public function store(Request $request)
{
    $validated = $request->validate([
        'titulo'    => 'required|string|max:255',
        'conteudo'  => 'required|string|min:10',
        'categoria' => 'required|exists:categorias,id',
    ]);

    if (! $request->user()->can('criar-post')) {
        abort(403);
    }

    $validated['slug'] = Str::slug($validated['titulo']);

    $post = Post::create($validated);

    return redirect()->route('posts.show', $post);
}
Enter fullscreen mode Exit fullscreen mode

Funciona? Funciona. Mas repara quantas responsabilidades diferentes tão espremidas aí: validação, autorização, transformação de dado e a regra de negócio de verdade. Tudo no mesmo lugar.

Agora imagina esse mesmo bloco repetido no update(). E na versão da API. Copiado, colado, com pequenas diferenças. De novo a velha história.

O controller tem UMA função

Existe um princípio chamado Single Responsibility: cada classe deveria ter um motivo só pra mudar. O controller muda quando o fluxo da requisição muda — não quando uma regra de validação muda, não quando uma permissão muda.

Validação é um conhecimento. Autorização é outro. Cada um merece morar na sua própria casa, e o Laravel já construiu essa casa pra você: o Form Request.

A solução: tira o peso do controller

Gera com um comando:

php artisan make:request StorePostRequest
Enter fullscreen mode Exit fullscreen mode

Isso cria app/Http/Requests/StorePostRequest.php. Joga tudo que não é função de controller pra dentro dele:

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Str;

class StorePostRequest extends FormRequest
{
    // Autorização: este usuário pode fazer isso?
    public function authorize(): bool
    {
        return $this->user()->can('criar-post');
    }

    // Validação: os dados estão corretos?
    public function rules(): array
    {
        return [
            'titulo'    => ['required', 'string', 'max:255'],
            'conteudo'  => ['required', 'string', 'min:10'],
            'categoria' => ['required', 'exists:categorias,id'],
        ];
    }

    // Preparação: ajusta os dados ANTES de validar
    protected function prepareForValidation(): void
    {
        $this->merge([
            'slug' => Str::slug($this->titulo),
        ]);
    }
}
Enter fullscreen mode Exit fullscreen mode

E agora olha o que sobrou do controller:

public function store(StorePostRequest $request)
{
    $post = Post::create($request->validated());

    return redirect()->route('posts.show', $post);
}
Enter fullscreen mode Exit fullscreen mode

Duas linhas. Duas. O controller voltou a ser garçom.

O pulo do gato é o type-hint: ao trocar Request por StorePostRequest, o Laravel roda a autorização e a validação automaticamente antes do método executar. Falhou? Nem entra no store(). O usuário já recebe o erro certo, redirecionado de volta, com as mensagens preenchidas.

Form Request não é só rules()

Esse é o ponto que quase ninguém usa direito. O Form Request tem três super-poderes, não um:

Autorização — o authorize() decide quem pode. Retornou false, vira 403 sozinho:

public function authorize(): bool
{
    return $this->user()->id === $this->route('post')->user_id;
}
Enter fullscreen mode Exit fullscreen mode

Mensagens customizadas — sem espalhar texto pelo projeto:

public function messages(): array
{
    return [
        'titulo.required' => 'O título é obrigatório, capricha nele.',
        'conteudo.min'    => 'Conteúdo muito curto, conta mais história.',
    ];
}
Enter fullscreen mode Exit fullscreen mode

Preparação de dados — o prepareForValidation() ajusta a entrada antes da regra rodar. Ótimo pra normalizar CPF, gerar slug, limpar máscara de telefone.

Tudo isso saiu do controller e foi morar onde faz sentido.

Pausa: e quando NÃO usar?

Porque sim, dá pra exagerar. Form Request vira muleta quando você cria um pra toda e qualquer rota, mesmo as triviais.

Tem um endpoint que valida só um campo? Um filtro de busca com um q opcional? Criar uma classe inteira pra isso é cerimônia demais. Um $request->validate(['q' => 'nullable|string']) ali mesmo resolve e é mais legível.

A régua é simples: se a validação é curta, usada num lugar só e não tem autorização envolvida, deixa inline. Se ela cresce, se repete, ou se mistura validação com permissão — chama o Form Request. Ele existe pra tirar peso, não pra adicionar burocracia.

E mais um aviso: Form Request não é lugar de regra de negócio. Calcular preço, disparar evento, mexer em outras tabelas — isso é trabalho da camada de serviço, não da validação. Não troque um controller inchado por um Form Request inchado.

Bônus: olha quantos controllers seus pedem isso

Pega seu projeto e procura por $request->validate(. Quantos resultados? Cada um daqueles, especialmente os que se repetem entre store e update, é um Form Request esperando pra nascer.

Começa pelo controller mais bagunçado que você tem. Extrai um. Vê como ele encolhe. Vicia.

Antes de você fechar a aba

Pergunta sincera: qual é o controller mais inchado do seu projeto hoje? Aquele que você sente um arrepio só de abrir.

Comenta aí embaixo o tamanho do método store() mais cabeludo que você já viu (ou escreveu, sem vergonha). E se esse post te deu vontade de fazer um refactor agora, salva ele e manda pro colega que ainda valida tudo no controller.

Seu controller magro agradece. 😄

Top comments (0)