Capítulo 03: Controllers, Form Requests e Validação de Dados
Especialização em Backend com PHP & Laravel • Laravel 11 & PHP 8.3+ • Eloquent ORM, Sanctum, Filas, Horizon e APIs Corporativas
🗺️ Mapa Conceitual do Tópico
flowchart TD
A["Requisição HTTP"] --> B["FormRequest::authorize()"]
B -->|false| C["403 Forbidden automático"]
B -->|true| D["FormRequest::rules()"]
D -->|falha| E["422 Unprocessable Entity + JSON de erros"]
D -->|sucesso| F["Controller recebe $request->validated()"]
F --> G["Camada de negócio / Model"]
style A fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style B fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style C fill:#ffebee,stroke:#e53935,stroke-width:2px
style D fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style E fill:#ffebee,stroke:#e53935,stroke-width:2px
style F fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style G fill:#f3e5f5,stroke:#9c27b0,stroke-width:2px
🏛️ 1. Por que Form Requests em vez de Validar no Controller
O Laravel permite validar inline com $request->validate([...]), mas em controllers de produção isso rapidamente polui o método de negócio com dezenas de linhas de regras. Uma classe Form Request dedicada resolve isso aplicando o Princípio de Responsabilidade Única: ela concentra autorização (authorize()) e validação (rules()) antes mesmo do corpo do método do controller ser executado — o Laravel intercepta a resolução via injeção de dependência e, se a validação falhar, devolve automaticamente 422 com um JSON de erros, sem nenhum if explícito no controller:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreProdutoRequest extends FormRequest
{
public function authorize(): bool
{
return $this->user()->can('criar', \App\Models\Produto::class);
}
public function rules(): array
{
return [
'nome' => 'required|string|min:3|max:150',
'preco' => 'required|numeric|gt:0',
'sku' => 'required|string|unique:produtos,sku',
];
}
}
public function store(StoreProdutoRequest $request)
{
Produto::create($request->validated()); // apenas campos validados chegam aqui
}
Gotcha crítico: usar $request->all() em vez de $request->validated() reabre a porta para Mass Assignment de campos não previstos (mesmo que $fillable no model ajude, validated() é a primeira linha de defesa, pois nem chega a existir um campo inesperado no array).
🎯 2. Mensagens Customizadas e Regras Condicionais
Mensagens de erro em português e regras que dependem de contexto (criação vs. atualização) evitam duplicar Form Requests inteiros:
public function messages(): array
{
return [
'nome.required' => 'O nome do produto é obrigatório.',
'preco.gt' => 'O preço deve ser superior a zero.',
];
}
Na atualização, a regra unique precisa ignorar o próprio registro (senão o produto falharia a validação contra si mesmo):
use Illuminate\Validation\Rule;
public function rules(): array
{
return [
'sku' => ['required', Rule::unique('produtos')->ignore($this->route('produto'))],
];
}
⚙️ 3. Single Action Controllers
Quando uma ação não se encaixa nas sete rotas RESTful padrão (ex.: um processo de checkout que não é CRUD de um recurso), um controller de ação única com __invoke() evita criar um controller genérico com um método solto e mal nomeado:
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class ProcessarCheckoutController extends Controller
{
public function __invoke(Request $request): JsonResponse
{
return response()->json(['status' => 'CHECKOUT_PROCESSADO'], 201);
}
}
A rota correspondente omite o método, pois __invoke é resolvido implicitamente: Route::post('/checkout', ProcessarCheckoutController::class).