@extends('layouts.docs') @section('title', 'Sistema de Validação - OG Framework') @section('body')
Forms & Data
Motor robusto e extensível para validação de dados. Baseado nos padrões Strategy, Rule Pattern e Chain of Responsibility, oferece uma API fluente para validação de requests HTTP, forms e dados de API.
Valida dados contra regras
$passes = validate($data, [
'email' => 'required|email',
'name' => 'required|min_length[2]'
]);
Obtém erros de validação
if (!$passes) {
$errors = validationErrors();
// ['email' => 'O email é inválido']
}
Obtém dados validados
$data = validated();
// ['email' => 'test@ex.com',
// 'name' => 'João']
Classe abstracta para validação automática de HTTP requests:
class AppointmentsCreateRequest extends BaseRequest
{
public function authorize(): bool
{
return true; // Ou lógica de permissão
}
public function rules(): array
{
return [
'subject' => ['required', 'string'],
'start_date' => ['required', 'date_format[Y-m-d]'],
'end_date' => ['required', 'after_or_equal[start_date]'],
// Condicionais
'seller_id' => ['if_exist', 'integer', 'exists[comerciais,codrepres]'],
// Arrays
'contacts' => ['if_exist', 'array'],
'contacts.*' => ['string'],
];
}
}
// Uso no Controller
public function store()
{
$request = new AppointmentsCreateRequest();
return Appointment::create($request->validated());
}
A classe Rule fornece criação fluente de rules complexas:
use Og\Modules\Common\Validation\Rules\Rule;
$rules = [
'email' => [
'required',
'email',
Rule::unique('users', 'email')
->ignore($userId, 'id')
]
];
use Og\Modules\Common\Validation\Rules\Rule;
$rules = [
'category_id' => [
'required',
'exists[categories,id,{active:1}]'
],
'status' => Rule::enum(OrderStatus::class),
'type' => Rule::in(['A', 'B', 'C']),
];
| Rule | Sintaxe | Descrição |
|---|---|---|
| required | required | Campo obrigatório |
| min_length | min_length[5] | Mínimo de caracteres |
| max_length | max_length[255] | Máximo de caracteres |
| in_list | in_list[A,B,C] | Valor na lista |
| is_unique | is_unique[table.col] | Único na tabela |
| exists | exists[table,col] | Registo existe |
| greater_than | greater_than[0] | Maior que valor |
| required_with | required_with[f1,f2] | Obrigatório se campos preenchidos |
| Rule | Sintaxe | Descrição |
|---|---|---|
| before | before[field] | Antes de outra data |
| after | after[field] | Depois de outra data |
| after_or_equal | after_or_equal[field] | Depois ou igual |
| date_format | date_format[Y-m-d] | Formato específico |
| is_future | is_future | Data no futuro |
| is_past | is_past | Data no passado |
| Rule | Sintaxe | Descrição |
|---|---|---|
| Email válido | ||
| url | url | URL válida |
| regex_match | regex_match[/pattern/] | Match de regex |
| valid_json | valid_json | JSON válido |
| alpha_dash | alpha_dash | Letras, números, _, - |
| integer | integer | Deve ser inteiro |
$rules = [
'avatar' => [
'uploaded[avatar]',
'max_size[avatar,1024]', // 1MB
'is_image[avatar]',
'mime_in[avatar,image/jpeg,image/png,image/webp]'
],
'document' => [
'uploaded[document]',
'max_size[document,5120]', // 5MB
'ext_in[document,pdf,doc,docx]'
],
];
Valida apenas se campo existir nos dados
'phone' => ['if_exist', 'string', 'max_length[20]'],
// Campo ausente = válido
// Campo presente = aplica regras
Permite valores vazios, mas valida se preenchido
'bio' => ['permit_empty', 'string', 'max_length[500]'],
// Campo vazio = válido
// Campo com valor = valida regras
Aplica regras apenas se campo existir no dataset
'email' => ['sometimes', 'required', 'email'],
// Campo ausente = não valida
// Campo presente = email obrigatório
Define um valor default quando o campo está ausente/vazio
// Default 0 se vazio
'newsletter' => ['checkbox[0]'],
// Obrigatório como 1
'terms' => ['required', 'checkbox[1]']
// Transforma em: 0 ou 1
Use Rule::when() para escolher rules em tempo de execução:
$isUpdate = $request->has('id');
$rules = [
'email' => Rule::when(
$isUpdate,
// SE update:
['sometimes', 'email', Rule::unique('users', 'email')->ignore($userId, 'id')],
// SENÃO (create):
['required', 'email', Rule::unique('users', 'email')]
),
];
$rules = [
'phone' => Rule::when(
fn () => $data['contact_method'] === 'phone',
['required', 'regex_match[/^\\+\\d+$/]'],
['nullable']
),
'category_id' => Rule::when(
$data['use_default_category'] ?? false,
['nullable'],
['required', 'exists[categories,id,{active:1}]']
),
];
Use after() no BaseRequest para executar validações que dependem de contexto externo
(ex.: permissões, estado em base de dados, integrações). O callback recebe o ValidatorInterface e pode
adicionar erros com setError() / addError().
use Og\Modules\Common\Http\BaseRequest;
use Og\Modules\Common\Validation\Contracts\ValidatorInterface;
use Og\Modules\Common\Validation\Rules\Rule;
class AmsConciergeNoteRequest extends BaseRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'subject' => [
Rule::requiredIf(fn () => $this->request->isPost()),
'string',
'max_length[128]',
],
];
}
public function after(): \Closure
{
return function (ValidatorInterface $validator): void {
if (!$this->request->route('concierge')) {
$validator->setError('concierge', 'Concierge inválido.');
}
};
}
}
class UserRequest extends BaseRequest
{
public function rules(): array
{
return [
'email' => ['required', 'email'],
'password' => ['required', 'min_length[8]'],
];
}
protected function messages(): array
{
return [
'email' => [
'required' => 'O email é obrigatório.',
'email' => 'Forneça um email válido.',
],
'password' => [
'min_length' => 'Mínimo 8 caracteres.',
],
];
}
}
$rules = [
'email' => [
'label' => 'Endereço de Email',
'rules' => ['required', 'email'],
'errors' => [
'required' => 'Preencha o email.',
'email' => 'Formato inválido.',
]
]
];
// Placeholders disponíveis:
// {field} - Nome do campo
// {param} - Parâmetro da rule
// {value} - Valor actual
O sistema suporta validação de estruturas aninhadas e placeholders dinâmicos:
$rules = [
// Acesso aninhado
'user.name' => ['required', 'string'],
'user.profile.bio' => ['string', 'max_length[500]'],
// Wildcard para arrays
'items.*.name' => ['required', 'string'],
'items.*.price' => ['required', 'numeric', 'greater_than[0]'],
// Placeholders dinâmicos - substituídos pelos valores actuais
'min_value' => ['required', 'numeric'],
'max_value' => ['required', 'numeric', 'greater_than[{min_value}]'],
'value' => ['required', 'numeric', 'greater_than[{min_value}]', 'less_than[{max_value}]'],
// O validator substitui {min_value} por 10 e {max_value} por 100
];
Métodos do validator (instância retornada por app(ValidatorInterface::class)):
$v = app(ValidatorInterface::class);
// Definir rules
$v->setRule('email', 'Email', 'required|email');
$v->setRules(['email' => 'required', 'name' => 'string']);
// Carregar dados
$v->withRequest($data);
$v->defineData($data);
// Executar
$passes = $v->validate();
$passes = $v->validate($data); // inline
// Validação rápida de valor
$ok = $v->check($email, ['required', 'email']);
// Erros
$erros = $v->errors();
$has = $v->hasError('email');
$msg = $v->error('email');
// Dados validados
$all = $v->validated();
$one = $v->validated('email');
$some = $v->validated(['email', 'name']);
// Filtros
$only = $v->only(['email'])->validated();
$except = $v->except(['password'])->validated();
// Adicionar erro manual
$v->addError('field', 'Mensagem');
// Callback after (pós-validação)
$v->after(function (ValidatorInterface $validator): void {
$validator->setError('field', 'Mensagem');
});
Crie regras personalizadas implementando as interfaces disponíveis:
class ValidNIF implements RuleInterface
{
public function passes($attr, $value): bool
{
$nif = preg_replace('/\s+/', '', $value);
if (!preg_match('/^[0-9]{9}$/', $nif)) {
return false;
}
// Validar dígito de controlo...
return (int)$nif[8] === $checkDigit;
}
public function message(): string
{
return 'O campo {field} deve ter um NIF válido.';
}
}
// Uso
'nif' => ['required', new ValidNIF()]
Quando a rule precisa enxergar outros campos do payload, implemente DataAwareRule.
O validator injeta o dataset completo antes de chamar passes().
class UniqueInvoice implements RuleInterface, DataAwareRule
{
private array $data = [];
public function defineData(array $data): void
{
$this->data = $data;
}
public function passes($attr, $value): bool
{
// Acesso a outros campos
$companyId = $this->data['company_id'];
$year = $this->data['year'];
return !Invoice::where('company_id', $companyId)
->where('year', $year)
->where('number', $value)
->exists();
}
}
Útil quando você precisa adicionar erro em outro campo, ou consultar estado do validator durante a validação.
O validator injeta a instância via defineValidator().
use Og\Modules\Common\Validation\Contracts\ValidatorAwareRule;
use Og\Modules\Common\Validation\Contracts\ValidatorInterface;
class RequiresInvoiceIdWhenTypeIsInvoice implements RuleInterface, DataAwareRule, ValidatorAwareRule
{
private array $data = [];
private ?ValidatorInterface $validator = null;
public function defineData(array $data): void
{
$this->data = $data;
}
public function defineValidator(ValidatorInterface $validator): void
{
$this->validator = $validator;
}
public function passes($attr, $value): bool
{
$type = $this->data['type'] ?? null;
$invoiceId = $this->data['invoice_id'] ?? null;
if ($type === 'invoice' && empty($invoiceId)) {
$this->validator?->setError('invoice_id', 'Informe a fatura para este tipo.');
}
return true;
}
public function message(): string
{
return '';
}
}
A interface CompilableRules existe para viabilizar regras “tipo forEach() do Laravel”:
permitir que um objeto gere, em runtime, um conjunto de rules a partir do $attribute e do $value
(inclusive para campos resolvidos por wildcard como items.0.price, items.1.price).
Status: contrato presente, mas a integração automática no validator pode ainda não estar finalizada.
Enquanto isso, use wildcards (items.*.field) + Rule::when() e/ou rules custom com DataAwareRule.
use Og\Modules\Common\Validation\Contracts\CompilableRules;
class AmountRules implements CompilableRules
{
public function compile(string $attribute, mixed $value, array $data = null): array
{
$data ??= [];
$rules = ['numeric', 'greater_than[0]'];
if (($data['currency'] ?? 'EUR') === 'EUR') {
$rules[] = 'max_value[999999]';
}
return $rules;
}
}
RuleInterface — Implementa passes() e message()DataAwareRule — Recebe o payload via defineData()ValidatorAwareRule — Recebe o validator via defineValidator()CompilableRules — Gera um array de rules via compile()Rules especializadas para upload de ficheiros:
| Rule | Sintaxe | Descrição |
|---|---|---|
| uploaded | uploaded[field] | Ficheiro foi uploaded com sucesso |
| max_size | max_size[field,2048] | Tamanho máximo em KB |
| mime_in | mime_in[field,image/png,image/jpeg] | Tipos MIME permitidos |
| ext_in | ext_in[field,pdf,doc,docx] | Extensões permitidas |
| max_dims | max_dims[field,1024,768] | Dimensões máximas (imagens) |
| is_image | is_image[field] | Valida se é imagem |
$rules = [
'avatar' => [
'uploaded[avatar]',
'max_size[avatar,1024]', // 1MB
'is_image[avatar]',
'mime_in[avatar,image/jpeg,image/png,image/webp]',
'max_dims[avatar,800,800]' // Max 800x800px
],
'document' => [
'uploaded[document]',
'max_size[document,5120]', // 5MB
'ext_in[document,pdf,doc,docx,xls,xlsx]'
],
'attachments.*' => [ // Array de ficheiros
'uploaded[attachments.*]',
'max_size[attachments.*,2048]'
]
];