404 Not Found

404 Not Found


nginx

Validação de Formulários do Laravel

Validação é o "posto de controle de segurança" do Laravel—todos os dados que entram no sistema devem passar por validação, e nenhum dado inválido pode passar despercebido.

1. O Que Você Vai Aprender


2. Uma História Real de um Auditor de Segurança

(1) Problema: Entrada do usuário causa inconsistência de dados

Quando Alice se cadastrou no ShopMetrics, ela inseriu "abc" no e-mail, "123" no telefone e deixou o campo de nome da loja em branco—o sistema aceitou tudo, resultando em um banco de dados cheio de dados inválidos. Um usuário malicioso enviou um produto com preço negativo via API para a loja de Bob, resultando em um valor de pedido de -999 USD e causando a queda dos relatórios financeiros. Durante uma auditoria de segurança, Charlie descobriu que 17 endpoints não tinham validação de entrada alguma.

(2) Soluções para Validação do Laravel

A validação do Laravel intercepta os dados antes que entrem no sistema—um único array de regras pode cobrir toda a lógica de validação, e a classe FormRequest mantém os controladores limpos.

PHP
// Regras de validação — 30 segundos para escrever, protege para sempre
$validated = $request->validate([
    'name' => 'required|string|max:255',
    'email' => 'required|email|unique:users',
    'price' => 'required|numeric|min:0',
]);

(3) Resultado

Depois que Alice habilitou a verificação, registros inválidos caíram para zero; os preços dos produtos de Bob nunca mais serão negativos; e Charlie aprovou todos os 17 endpoints na auditoria de segurança.


3. Referência Rápida de Regras de Validação

(1) Regras Comuns

Regra Descrição Exemplo
required Obrigatório 'name' => 'required'
email Formato de E-mail 'email' => 'required|email'
unique:table,column Único 'slug' => 'unique:shops,slug'
exists:table,column Existe 'shop_id' => 'exists:shops,id'
min:n Valor/Comprimento Mínimo 'price' => 'numeric|min:0'
max:n Valor/Comprimento Máximo 'name' => 'string|max:255'
numeric Número 'quantity' => 'required|numeric'
integer Inteiro 'page' => 'integer|min:1'
string String 'name' => 'required|string'
boolean Booleano 'is_active' => 'boolean'
date Data 'published_at' => 'date'
file Arquivo 'logo' => 'file|max:2048'
image Arquivo de imagem 'photo' => 'image|mimes:jpeg,png'
confirmed Confirmação Secundária 'password' => 'confirmed'
regex:pattern Expressões Regulares 'phone' => 'regex:/^[0-9]{10}$/'
in:a,b,c Valor de Enumeração 'status' => 'in:active,suspended'

(2) Processo do Pipeline de Validação

100%
flowchart TD
    A[Entrada da Requisição] --> B[Validar Regras]
    B -->|Passou| C[Dados Sanitizados]
    C --> D[Lógica do Controlador]
    B -->|Falhou| E[Redirecionar de Volta com Erros]
    E --> F["Exibir \$errors na View"]

(1) ▶ Exemplo: Validação de Criação de Produto do ShopMetrics

PHP
// Validação inline no controlador
public function store(Request $request): RedirectResponse
{
    $validated = $request->validate([
        'name' => 'required|string|max:255',
        'sku' => 'required|string|unique:products,sku',
        'price' => 'required|numeric|min:0.01|max:999999.99',
        'stock' => 'required|integer|min:0',
        'category_id' => 'required|exists:categories,id',
        'description' => 'nullable|string|max:5000',
        'is_active' => 'boolean',
    ]);

    $product = Product::create($validated);

    return redirect()->route('products.show', $product)
        ->with('success', 'Produto criado.');
}

Saída:

TEXT
// Execução bem-sucedida

4. Classe de Validação Form Request

(1) Criar um Form Request

BASH
php artisan make:request StoreShopRequest
php artisan make:request UpdateShopRequest

(2) Definindo Regras e Autorização

PHP
// app/Http/Requests/StoreShopRequest.php
class StoreShopRequest extends FormRequest
{
    public function authorize(): bool
    {
        return auth()->check() && auth()->user()->can('create', Shop::class);
    }

    public function rules(): array
    {
        return [
            'name' => 'required|string|max:255',
            'slug' => 'required|string|unique:shops,slug',
            'description' => 'nullable|string|max:5000',
            'domain' => 'nullable|url',
            'status' => 'in:active,suspended',
        ];
    }

    public function messages(): array
    {
        return [
            'name.required' => 'O nome da loja é obrigatório.',
            'slug.unique' => 'Este slug de URL já está em uso.',
            'domain.url' => 'Por favor, insira uma URL válida.',
        ];
    }
}

(3) Usando no controlador

PHP
public function store(StoreShopRequest $request): RedirectResponse
{
    // $request->validated() contém apenas dados validados
    $shop = Shop::create($request->validated());
    return redirect()->route('shops.show', $shop);
}
Dimensão Validação Inline Form Request
Localização Dentro do método do controlador Classe separada em arquivo
Reutilização Baixa ✅ Reutilização em múltiplos controladores
Verificação de Autorização Manual ✅ authorize()
Código do Controlador Mais longo Simplificado
Adequado para Verificação simples Verificação complexa/reutilizável

(1) ▶ Exemplo: StoreShopRequest do ShopMetrics

PHP
// app/Http/Requests/StoreShopRequest.php
class StoreShopRequest extends FormRequest
{
    public function authorize(): bool
    {
        return auth()->user()->role === 'tenant_owner';
    }

    public function rules(): array
    {
        return [
            'name' => 'required|string|max:255',
            'slug' => 'required|alpha_dash|unique:shops,slug,NULL,id,tenant_id,' . tenant()->id,
            'description' => 'nullable|string|max:5000',
            'status' => 'sometimes|in:active,suspended',
        ];
    }

    public function messages(): array
    {
        return [
            'slug.unique' => 'Você já tem uma loja com este slug.',
            'slug.alpha_dash' => 'O slug só pode conter letras, números e hifens.',
        ];
    }

    protected function prepareForValidation(): void
    {
        $this->merge([
            'tenant_id' => tenant()->id,
            'slug' => Str::slug($this->slug ?? $this->name),
        ]);
    }
}

Saída:

TEXT
// Execução bem-sucedida

5. Regras de Validação Personalizadas

(1) Regras Closure

PHP
// Regra personalizada inline
$validated = $request->validate([
    'discount' => [
        'required',
        'numeric',
        'min:0',
        function (string $attribute, mixed $value, Closure $fail) {
            if ($value > request('subtotal')) {
                $fail('O desconto não pode exceder o subtotal.');
            }
        },
    ],
]);

(2) Objeto Rule

BASH
php artisan make:rule ValidCouponCode
PHP
// app/Rules/ValidCouponCode.php
class ValidCouponCode implements ValidationRule
{
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        $coupon = Coupon::where('code', $value)
            ->where('expires_at', '>', now())
            ->where('usage_limit', '>', DB::raw('usage_count'))
            ->first();

        if (!$coupon) {
            $fail('Este código de cupom é inválido ou expirou.');
        }
    }
}

// Uso
public function rules(): array
{
    return [
        'coupon_code' => ['nullable', 'string', new ValidCouponCode()],
    ];
}
Método Cenários Adequados Reutilização
Closure Lógica simples de uso único Baixa
Objeto Rule Lógica Complexa/Reutilizável
Método Rule::class Validação relacionada ao banco

(3) Métodos da Classe Rule

PHP
use Illuminate\Validation\Rule;

// Unique ignorando modelo atual
'slug' => Rule::unique('shops', 'slug')->ignore($shop->id),

// In com valores dinâmicos
'status' => Rule::in(['active', 'suspended', 'closed']),

// Exists com consulta adicional
'shop_id' => Rule::exists('shops', 'id')->where(function ($query) {
    $query->where('tenant_id', tenant()->id);
}),

(1) ▶ Exemplo: Regras de Validação de Pedidos do ShopMetrics

PHP
// app/Http/Requests/StoreOrderRequest.php
class StoreOrderRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'items' => 'required|array|min:1',
            'items.*.product_id' => [
                'required',
                'integer',
                Rule::exists('products', 'id')->where('is_active', true),
            ],
            'items.*.quantity' => 'required|integer|min:1|max:100',
            'coupon_code' => ['nullable', 'string', new ValidCouponCode()],
            'discount' => [
                'sometimes',
                'numeric',
                'min:0',
                function ($attribute, $value, $fail) {
                    if ($value > $this->input('subtotal', 0)) {
                        $fail('O desconto não pode exceder o subtotal.');
                    }
                },
            ],
        ];
    }
}

Saída:

TEXT
// Execução bem-sucedida

6. Tratamento de Mensagens de Erro

(1) Exibição de Erros em Páginas Web

HTML
<!-- Exibir todos os erros -->
@if ($errors->any())
    <div class="alert alert-error">
        <ul>
            @foreach ($errors->all() as $error)
                <li>{{ $error }}</li>
            @endforeach
        </ul>
    </div>
@endif

<!-- Exibir erro para campo específico -->
<input type="text" name="name" value="{{ old('name') }}"
       class="{{ $errors->has('name') ? 'border-red-500' : '' }}">
@error('name')
    <p class="text-red-500 text-sm">{{ $message }}</p>
@enderror

(2) Resposta de Erro JSON da API

JSON
{
    "message": "The given data was invalid.",
    "errors": {
        "name": ["The name field is required."],
        "email": ["The email must be a valid email address."]
    }
}
Tipo de Requisição Comportamento ao Falhar Validação Formato Inválido
Formulários Web Redirecionar de Volta ao Formulário + Exibir Erros Variáveis de View $errors
API JSON Retorna 422 + JSON {"errors": {...}}
AJAX Retorna 422 + JSON Mesmo que API

(1) ▶ Exemplo: Tratamento de Erros de Validação na API do ShopMetrics

PHP
// app/Http/Controllers/Api/ShopController.php
public function store(StoreShopRequest $request): JsonResponse
{
    $shop = Shop::create($request->validated());

    return response()->json([
        'message' => 'Loja criada com sucesso.',
        'data' => new ShopResource($shop),
    ], 201);
}

// Cliente recebe 422 ao falhar validação:
// {
//   "message": "The slug has already been taken.",
//   "errors": {
//     "slug": ["The slug has already been taken."]
//   }
// }

Saída:

TEXT
// Execução bem-sucedida

7. Validação Condicional

(1) Regra Sometimes

PHP
// Validar apenas se o campo estiver presente
$validated = $request->validate([
    'name' => 'required|string',
    'notes' => 'sometimes|nullable|string|max:5000',
]);

// Regras condicionais baseadas em outro campo
Validator::make($data, [
    'payment_method' => 'required|in:credit_card,bank_transfer',
    'card_number' => 'required_if:payment_method,credit_card|numeric',
    'bank_account' => 'required_if:payment_method,bank_transfer|numeric',
]);

(2) Regra Bail

PHP
// Parar validação após primeira falha em um campo
$validated = $request->validate([
    'email' => 'bail|required|email|unique:users',
    // Se required falhar, email e unique não serão executados
]);
Regra Função Caso de Uso
sometimes Validar apenas se o campo existir Campo opcional
bail Parar após primeira falha Verificação custosa
required_if Campo obrigatório Método de pagamento → Número do cartão
required_unless Campo é opcional
required_with Obrigatório Confirmar Senha
prohibited_if Proibido condicionalmente
exclude_if Excluir condições Não escrever no validated
nullable Permite null Campo opcional

(1) ▶ Exemplo: Cenário de Validação Condicional do ShopMetrics

PHP
// app/Http/Requests/UpdateSubscriptionRequest.php
class UpdateSubscriptionRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'plan_id' => 'required|exists:plans,id',
            'payment_method' => 'required|in:credit_card,paypal,bank_transfer',
            'card_number' => 'required_if:payment_method,credit_card|string|size:16',
            'card_cvv' => 'required_if:payment_method,credit_card|string|size:3',
            'paypal_email' => 'required_if:payment_method,paypal|email',
            'bank_account' => 'required_if:payment_method,bank_transfer|string',
            'coupon_code' => 'sometimes|nullable|string|max:50',
        ];
    }
}

Saída:

TEXT
// Execução bem-sucedida

8. Exemplo Completo: O Processo Completo de Verificação do ShopMetrics

PHP
// ============================================
// Completo: Validação de Pedidos do ShopMetrics
// Abrange: Form Request, regras personalizadas, condicional, erros
// ============================================

// app/Rules/SufficientStock.php
class SufficientStock implements ValidationRule
{
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        $productId = request()->input(str_replace('.quantity', '.product_id', $attribute));
        $product = Product::find($productId);
        if ($product && $value > $product->stock) {
            $fail("Apenas {$product->stock} unidades disponíveis para {$product->name}.");
        }
    }
}

// app/Http/Requests/StoreOrderRequest.php
class StoreOrderRequest extends FormRequest
{
    public function authorize(): bool
    {
        return auth()->check();
    }

    public function rules(): array
    {
        return [
            'items' => 'required|array|min:1|max:50',
            'items.*.product_id' => [
                'required',
                'integer',
                Rule::exists('products', 'id')->where('is_active', true),
            ],
            'items.*.quantity' => [
                'required',
                'integer',
                'min:1',
                'max:100',
                new SufficientStock(),
            ],
            'coupon_code' => 'sometimes|nullable|string|max:50',
            'notes' => 'sometimes|nullable|string|max:1000',
            'shipping_address.line1' => 'required|string|max:255',
            'shipping_address.city' => 'required|string|max:100',
            'shipping_address.zip' => 'required|string|max:20',
            'shipping_address.country' => 'required|string|size:2',
        ];
    }

    public function messages(): array
    {
        return [
            'items.required' => 'Seu carrinho está vazio.',
            'items.min' => 'Adicione pelo menos um item para fazer um pedido.',
            'items.*.product_id.exists' => 'Um dos produtos selecionados não está disponível.',
            'shipping_address.line1.required' => 'O endereço da rua é obrigatório.',
        ];
    }
}

// Controlador fica limpo
public function store(StoreOrderRequest $request): RedirectResponse
{
    $order = $this->orderService->createFromRequest($request);
    return redirect()->route('orders.show', $order)
        ->with('success', 'Pedido realizado com sucesso!');
}

❓ Perguntas Frequentes

P Como escolher entre validate() e Form Request?
R Para validação simples (3-5 regras), use validate() diretamente; para validação complexa (10+ regras, reutilização ou verificações de autorização), use Form Request. Form Request é a melhor prática.
P Como excluir o registro atual ao atualizar uma regra "unique"?
R Use o formato Rule::unique('shops', 'slug')->ignore($shop->id) ou unique:shops,slug,{shop}. A validação de atualização deve excluir o registro atual; caso contrário, a validação sempre falhará.
P Qual formato é retornado quando a autenticação da API falha?
R O Laravel retorna automaticamente um código de status 422 + JSON: {"message":"...","errors":{"field":["mensagem de erro"]}}. O frontend deve analisar o objeto errors com base no código de status 422 para exibir erros em nível de campo.
P Qual é a finalidade de prepareForValidation?
R Ele modifica os dados de entrada antes da validação—por exemplo, gerando automaticamente um slug, formatando um número de telefone ou adicionando valores padrão. Os dados modificados são então usados no processo de validação e na saída de validated().
P Como testar regras de validação durante os testes?
R Use $this->postJson() para enviar dados inválidos; verifique se um erro 422 e a mensagem de erro $response->assertJsonValidationErrors('name') são retornados. Você também pode usar Validator::make() para testar as regras diretamente.
P Muitas regras de validação afetam o desempenho?
R As regras unique e exists consultam o banco de dados, o que incorre em overhead de desempenho. Para APIs de alta frequência, considere usar bail para encerrar a operação precocemente ou armazenar em cache os resultados de validação. Na maioria dos cenários, o desempenho não é um problema.

📖 Resumo


📝 Exercícios

  1. Exercício Básico (⭐): Crie um StoreProductRequest para um produto do ShopMetrics, incluindo regras de validação para name, sku, price, stock e category_id, para garantir que o SKU seja único e o preço seja um número positivo.

  2. Exercício Avançado (⭐⭐): Crie um objeto de regra ValidCouponCode para verificar se um cupom não expirou e ainda tem usos restantes. Aplique esta regra a um StoreOrderRequest e teste as respostas para cupons válidos e inválidos.

  3. Desafio (⭐⭐⭐): Implemente a Regra SufficientStock (para verificar se a quantidade do pedido não excede o estoque) nos campos items.*.quantity do StoreOrderRequest para lidar com problemas de contenção de estoque quando múltiplos itens são pedidos simultaneamente.

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%