"Esse campo aqui só é obrigatório às vezes"
Toda regra de validação começa simples. required, email, min:8, beleza. Aí o PO chega e solta a frase mágica:
"Ah, mas o CNPJ só é obrigatório se a pessoa for pessoa jurídica."
E lá se foi a simplicidade. De repente sua validação depende de outro campo, e você faz o que todo mundo faz na primeira vez: joga um if no controller, valida um pedaço, depois valida outro pedaço, e o método store() vira um monstro de ramificação.
Já passou por isso? A boa notícia é que o Laravel tem ferramenta pra praticamente todo tipo de "depende". Você só precisa parar de resolver tudo na mão.
O problema: validação picada no controller
O jeito ingênuo é validar em pedaços, dependendo do que veio:
public function store(Request $request)
{
$regras = [
'tipo' => 'required|in:pf,pj',
'nome' => 'required|string',
];
// e aqui começa a bagunça
if ($request->tipo === 'pj') {
$regras['cnpj'] = 'required|string|size:14';
} else {
$regras['cpf'] = 'required|string|size:11';
}
if ($request->quer_newsletter) {
$regras['email'] = 'required|email';
}
$request->validate($regras);
// ...
}
Funciona? Funciona. Mas repara no que aconteceu: sua regra de negócio agora tá espalhada em construção manual de array, o controller sabe demais sobre validação, e testar isso é um inferno. Cada if novo é mais uma ramificação pra dar errado.
A solução 1: required_if e amigos
Pra dependências simples entre campos, o Laravel já tem regras prontas. Você quase nunca precisa de if pra isso:
public function rules(): array
{
return [
'tipo' => 'required|in:pf,pj',
'cnpj' => 'required_if:tipo,pj|nullable|size:14',
'cpf' => 'required_if:tipo,pf|nullable|size:11',
'email' => 'required_if:quer_newsletter,true|email',
];
}
Tem uma família inteira dessas: required_unless, required_with, required_without, required_with_all, exclude_if, prohibited_if... Antes de escrever um if, vale conferir se já não existe uma regra que faz exatamente o que você quer. Na maioria dos casos, existe.
A solução 2: Rule::when pra condição no meio da regra
Quando a condição não é "um campo depende de outro", mas sim algo do contexto (o usuário logado, um plano, uma feature flag), o Rule::when deixa a coisa declarativa dentro do próprio array:
use Illuminate\Validation\Rule;
public function rules(): array
{
return [
'titulo' => 'required|string',
// só valida como obrigatório+único se for admin
'slug' => Rule::when(
$this->user()->isAdmin(),
['required', 'unique:posts,slug'],
['nullable'] // senão, opcional
),
];
}
Leu de cima pra baixo e entendeu a regra inteira, sem sair caçando um if três métodos acima.
A solução 3: sometimes() pra lógica que não cabe no array
E quando a condição é complexa de verdade, tipo "valida o desconto só se ele veio E o pedido passa de 500 reais"? Aí entra o método sometimes() do Validator, ideal no withValidator() de um Form Request:
use Illuminate\Validation\Validator;
public function withValidator(Validator $validator): void
{
$validator->sometimes('desconto', 'numeric|max:50', function ($input) {
return $input->cupom && $input->total > 500;
});
}
A regra do desconto só é aplicada se aquele closure retornar true. Toda a lógica maluca fica isolada num lugar só, com nome, testável, e longe do caminho principal.
Como usar na prática
Validação de array/coleção — aquele formulário com N itens repetidos (um carrinho, uma lista de convidados). Usa o .*:
public function rules(): array
{
return [
'itens' => 'required|array|min:1',
'itens.*.produto_id' => 'required|exists:produtos,id',
'itens.*.quantidade' => 'required|integer|min:1',
];
}
Uma regra só valida os N itens do array. Nada de foreach validando na mão.
Mensagem customizada por condição — combina com messages() no Form Request pra falar a língua do usuário: "Informe o CNPJ para pessoa jurídica" em vez do genérico.
A pegadinha: nullable vs a ausência da regra
Aqui cai muita gente. Quando você usa required_if, quase sempre precisa combinar com nullable no mesmo campo. Sem isso, se o campo vier vazio (mas presente no request), outras regras como size:14 ou email ainda vão tentar validar a string vazia e reclamar.
// ❌ se vier cnpj='' num PF, o size:14 reclama
'cnpj' => 'required_if:tipo,pj|size:14',
// ✅ vazio é permitido quando não obrigatório
'cnpj' => 'required_if:tipo,pj|nullable|size:14',
nullable diz "pode ser nulo/vazio, e nesse caso ignora as outras regras". É o par natural do required_if. Guarda isso que economiza uma hora de debug.
Bônus: e agora?
Abre o Form Request (ou controller) mais cheio de if do seu projeto e faz o exercício: cada if que existe só pra montar array de regra provavelmente tem um required_if, Rule::when ou sometimes equivalente esperando pra substituir ele.
E se você ainda valida direto no controller com $request->validate(), esse é o empurrãozinho pra migrar tudo pra um Form Request. Todo esse arsenal fica muito mais organizado lá dentro.
Antes de você fechar a aba
A regra de ouro: se você tá construindo array de validação com if, o Laravel provavelmente já tem uma forma declarativa de fazer isso. Vale abrir a doc de validação e passar o olho na lista de regras — tem umas 20 que quase ninguém usa e resolvem casos que a gente insiste em fazer na mão.
Qual regra de validação condicional você mais usa? Ou tem algum caso cabeludo que não soube resolver? Joga nos comentários que a gente destrincha junto. 👇
Top comments (0)