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).