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
- Regras de validação: required/email/unique/exists/file/image/custom
- Classe de validação Form Request: make:request
- Regras de Validação Personalizadas: Regras Closure e Objetos Rule
- Tratamento de Mensagens de Erro: Compartilhamento de View $errors e Respostas JSON de API
- Validação Condicional e Regras "Bail/Sometimes"
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.
// 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
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
// 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:
// Execução bem-sucedida
4. Classe de Validação Form Request
(1) Criar um Form Request
php artisan make:request StoreShopRequest
php artisan make:request UpdateShopRequest
(2) Definindo Regras e Autorização
// 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
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
// 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:
// Execução bem-sucedida
5. Regras de Validação Personalizadas
(1) Regras Closure
// 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
php artisan make:rule ValidCouponCode
// 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
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
// 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:
// Execução bem-sucedida
6. Tratamento de Mensagens de Erro
(1) Exibição de Erros em Páginas Web
<!-- 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
{
"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
// 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:
// Execução bem-sucedida
7. Validação Condicional
(1) Regra Sometimes
// 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
// 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
// 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:
// Execução bem-sucedida
8. Exemplo Completo: O Processo Completo de Verificação do ShopMetrics
// ============================================
// 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
validate() e Form Request?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.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á.{"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.prepareForValidation?validated().$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.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
- A validação do Laravel intercepta dados antes que entrem no sistema, oferecendo uma ampla gama de regras que podem ser combinadas
- A classe Form Request encapsula a lógica de validação, mantendo o controlador limpo
- Regras Closure são adequadas para lógica de uso único, enquanto objetos Rule são adequados para lógica reutilizável
- Redireciona e exibe mensagem de erro ao falhar validação web; a API retorna uma resposta JSON 422
- "sometimes"/nullable: Lida com campos opcionais; "bail": Encerra precocemente
- prepareForValidation: Modifica os dados de entrada antes da validação
📝 Exercícios
-
Exercício Básico (⭐): Crie um
StoreProductRequestpara um produto do ShopMetrics, incluindo regras de validação paraname,sku,price,stockecategory_id, para garantir que o SKU seja único e o preço seja um número positivo. -
Exercício Avançado (⭐⭐): Crie um objeto de regra
ValidCouponCodepara verificar se um cupom não expirou e ainda tem usos restantes. Aplique esta regra a umStoreOrderRequeste teste as respostas para cupons válidos e inválidos. -
Desafio (⭐⭐⭐): Implemente a Regra SufficientStock (para verificar se a quantidade do pedido não excede o estoque) nos campos
items.*.quantitydoStoreOrderRequestpara lidar com problemas de contenção de estoque quando múltiplos itens são pedidos simultaneamente.



