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);
}
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
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),
]);
}
}
E agora olha o que sobrou do controller:
public function store(StorePostRequest $request)
{
$post = Post::create($request->validated());
return redirect()->route('posts.show', $post);
}
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;
}
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.',
];
}
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)