Docs›Formulários›Validação
Formulários

Validação

Modelo::rules(), MadValidationException, asModal/asToast/asInline/asDetailForm e o canal de erro por slot [data-field-error].

O MAD valida formulários no servidor via Modelo::rules() + $this->form->validate(), que delega diretamente ao illuminate/validation nativo do Laravel — todas as regras padrão do Laravel funcionam (required, email, unique, exists, numeric, confirmed, image, regex, etc.). Erros lançam MadValidationException, que sabe se apresentar como modal, toast, inline ou response customizada — sem você montar HTML de erro à mão.

Declarando regras no model

Convenção: método estático rules($id = null) no model — recebe o ID do registro em edição (null em criação), útil para regras unique que precisam ignorar o próprio registro:

// app/Models/Produto.php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Produto extends Model
{
    protected $connection = 'business';
    protected $table      = 'produto';

    public static function rules($id = null): array
    {
        return [
            'nome'         => 'required|max:200',
            'cod_barras'   => 'max:50|unique:produto,cod_barras,' . ($id ?? 'NULL') . ',id',
            'valor'        => 'required|numeric|min:0',
            'email'        => 'nullable|email',
            'categoria_id' => 'required|exists:categoria,id',
            'foto'         => 'nullable|image|max:5120', // 5MB
        ];
    }
}
Mensagens em pt-BR por padrão

Mad\Form\MadValidator registra um Illuminate\Translation\Translator próprio com as mensagens padrão do illuminate/validation já traduzidas para pt-BR — você não precisa publicar nem traduzir o pacote de validação do Laravel para ter mensagens em português.

Validando no controller

public function onSave(): MadResponse
{
    try {
        $this->form->validate(Produto::rules($this->registroId));
        // Se chegou aqui, todos os campos passaram — getData() já foi cacheado.

        $produto = Produto::findOrNew($this->registroId);
        $this->form->save($produto);
        $this->registroId = (int) $produto->id;

        return (new MadResponse())->toast('Salvo!', 'success')->closeDrawer();

    } catch (MadValidationException $e) {
        return $e->asModal(); // dialog com a lista de erros

    } catch (\Throwable $e) {
        return MadMessage::error('Erro', $e->getMessage());
    }
}

Apresentando erros

MadValidationException tem quatro métodos de apresentação — todos retornam MadResponse:

catch (MadValidationException $e) {
    return $e->asModal();
}
// Dialog estilizado (MadDialog, no client) com a lista de erros — bom
// default, não depende de o campo estar visível na tela no momento do erro.

asToast()

catch (MadValidationException $e) {
    return $e->asToast();
}
// Toast com só a primeira mensagem de erro — sucinto, bom para forms curtos.

asInline()

catch (MadValidationException $e) {
    return $e->asInline();
}
// Marca cada campo com erro inline (data-field-error) + toast de aviso
// genérico ("Corrija os erros antes de continuar."). Toast é opcional:
// $e->asInline(toast: ''); // omite o toast, só os campos

asDetailForm($name)

// Dentro do processRow() de um detail-form (ver Detail-form):
catch (MadValidationException $e) {
    return $e->asDetailForm('itens'); // erro scoped à linha do detail "itens"
}
MétodoQuando usar
asModal($title = 'Erros encontrados')Default seguro — lista de erros num dialog, não depende do campo estar visível
asToast($prefix = '')Forms curtos — mostra só a primeira mensagem
asInline($toast = '...')Forms com vários campos visíveis simultaneamente — marca cada um com erro
asDetailForm($dfName)Erro de validação dentro do processamento de uma linha de detail-form

Como o erro inline chega ao campo

asInline() (e, na mão, MadResponse::fieldError($campo, $msg)) escreve num slot de erro — o elemento [data-field-error="<campo>"] que todo componente de campo renderiza. Três detalhes desse canal existem porque cada um deles já engoliu mensagens de erro em silêncio:

SituaçãoO que o framework faz
Campo sem erro O slot sempre está no DOM (renderizado vazio ou com o hint). Slot criado só quando há erro seria slot inexistente na hora de aplicar a op — a mensagem sumiria.
Campo de mesmo name num detail-form Os seletores do master excluem o sub-form ([data-field-error="x"]:not([data-df-fields] *)) — sem isso o querySelector pegaria o primeiro match do documento e o detail roubaria o erro do master. Erro dentro do detail é dfFieldError($dfName, $campo, $msg) / asDetailForm().
Detail-form em mode="drawer" O sub-form é teleportado para o body pelo drawer e sai de dentro de [data-mad-df-name]. O JS tem fallback global por [data-df-fields][data-df-name="<nome>"] para achar o container teleportado.
Campo dentro de <mad-tab-panel> inativa O slot existe mas está com display:none (x-show) — o usuário só veria o toast. Mad._revealFieldError() ativa a aba que contém o slot. A primeira aba com erro vence (lock de 100ms no root das tabs impede que erros seguintes da mesma resposta fiquem trocando de aba).
Componente próprio precisa render o slot

Se você escreve um componente de campo customizado, renderize o [data-field-error="{{ $name }}"] incondicionalmente (vazio quando não há erro). Sem o elemento no DOM, asInline() aplica a op no vácuo e o usuário só vê o toast genérico. O <mad-field-list> não tem slot por célula — erro de linha se apresenta por asModal()/asToast().

Mensagens customizadas

O 2º argumento de validate() aceita mensagens customizadas no formato 'campo.regra' => 'mensagem' (convenção dot do illuminate/validation):

public static function rules($id = null): array
{
    return [
        'nome'  => 'required|max:200',
        'email' => 'nullable|email',
    ];
}

// Mensagens customizadas: 2º arg de validate() — chave 'campo.regra' (dot,
// convenção illuminate/validation), valor é a mensagem
public function onSave(): MadResponse
{
    try {
        $this->form->validate(Produto::rules($this->registroId), [
            'nome.required' => 'Por favor, informe o nome.',
            'nome.max'      => 'Nome muito longo (máx. 200 caracteres).',
            'email.email'   => 'Email inválido.',
        ]);
        // ...
    } catch (MadValidationException $e) {
        return $e->asModal();
    }
}

Atalho — label inline na chave da regra

A chave da regra aceita 'campo|Label' — o label é extraído automaticamente e usado para compor a mensagem padrão (ex: "O campo Nome do Produto é obrigatório" em vez de "O campo nome..."):

public static function rules($id = null): array
{
    return [
        // chave 'campo|Label' — o label é extraído e usado na mensagem padrão
        'nome|Nome do Produto' => 'required|string|max:255',
        'email'                => 'required|email',
    ];
}

Sem nenhum $attrs explícito, o validate() também tenta usar os labels coletados automaticamente do HTML pelo MadWire (atributo label de cada componente de campo) — o atalho 'campo|Label' só é necessário quando você quer um texto diferente do label visível na tela.

Validação cross-field

Para regras que envolvem múltiplos campos ao mesmo tempo, lance MadValidationException manualmente depois da validação básica:

public function onSave(): MadResponse
{
    try {
        $data = $this->form->getData();

        // Validação básica de cada campo
        $this->form->validate(Produto::rules($this->registroId));

        // Validação cross-field — não cabe numa regra por campo isolada
        if ($data->valor_promo > 0 && $data->valor_promo >= $data->valor) {
            throw new MadValidationException([
                'valor_promo' => 'Valor promocional deve ser menor que o valor base.',
            ]);
        }

        $produto = Produto::findOrNew($this->registroId);
        $this->form->save($produto);

        return (new MadResponse())->toast('Salvo!', 'success')->closeDrawer();

    } catch (MadValidationException $e) {
        return $e->asModal();
    }
}

Uso direto do MadValidator (fora de MadForm)

Em services, jobs ou qualquer contexto sem um MadForm à mão, Mad\Form\MadValidator::validate() funciona isoladamente:

// Uso direto do MadValidator, fora do contexto de MadForm (ex: num service,
// num job, numa API). Retorna array de erros — [] se válido.
$errors = \Mad\Form\MadValidator::validate(
    ['nome' => 'Jo', 'email' => 'invalido'],
    ['nome' => 'required|min:3', 'email' => 'required|email']
);
// ['nome' => 'O campo nome deve ter no mínimo 3 caracteres.', 'email' => 'O campo email deve ser um endereço de e-mail válido.']

Próximos