required('email')->email('email') * ->required('password')->minLength('password', 8); * * if ($validator->fails()) { * return ResponseLib::sendFail($validator->firstError(), [], 'E_VALIDATE')->withStatus(400); * } * * Regras só são aplicadas se o campo não já tiver acumulado erro, evitando * mensagens redundantes para o mesmo campo. */ final class Validator { private array $data; /** @var array erro por campo (primeiro erro encontrado) */ private array $errors = []; public function __construct(array $data) { $this->data = $data; } public function required(string $field, ?string $label = null): self { if ($this->hasError($field)) { return $this; } $value = $this->data[$field] ?? null; if ($value === null || (is_string($value) && trim($value) === '') || $value === []) { $this->addError($field, sprintf('%s is required', $label ?? $field)); } return $this; } public function email(string $field, ?string $label = null): self { if ($this->hasError($field) || !isset($this->data[$field])) { return $this; } $value = trim((string) $this->data[$field]); if (filter_var($value, FILTER_VALIDATE_EMAIL) === false) { $this->addError($field, sprintf('%s must be a valid email', $label ?? $field)); } return $this; } /** * Telefone aceita apenas dígitos (após remover espaços, +, -, () ), * com comprimento entre $min e $max. */ public function phone(string $field, int $min = 8, int $max = 15, ?string $label = null): self { if ($this->hasError($field) || !isset($this->data[$field])) { return $this; } $digits = preg_replace('/\D+/', '', (string) $this->data[$field]); $length = strlen((string) $digits); if ($length < $min || $length > $max) { $this->addError($field, sprintf('%s must have between %d and %d digits', $label ?? $field, $min, $max)); } return $this; } public function minLength(string $field, int $min, ?string $label = null): self { if ($this->hasError($field) || !isset($this->data[$field])) { return $this; } if (mb_strlen((string) $this->data[$field]) < $min) { $this->addError($field, sprintf('%s must be at least %d characters', $label ?? $field, $min)); } return $this; } public function maxLength(string $field, int $max, ?string $label = null): self { if ($this->hasError($field) || !isset($this->data[$field])) { return $this; } if (mb_strlen((string) $this->data[$field]) > $max) { $this->addError($field, sprintf('%s must be at most %d characters', $label ?? $field, $max)); } return $this; } /** * Garante que o valor é um inteiro dentro de [$min, $max] (limites opcionais). */ public function intRange(string $field, ?int $min = null, ?int $max = null, ?string $label = null): self { if ($this->hasError($field) || !isset($this->data[$field])) { return $this; } $value = filter_var($this->data[$field], FILTER_VALIDATE_INT); if ($value === false) { $this->addError($field, sprintf('%s must be an integer', $label ?? $field)); return $this; } if (($min !== null && $value < $min) || ($max !== null && $value > $max)) { $this->addError($field, sprintf('%s is out of allowed range', $label ?? $field)); } return $this; } /** * Garante que o valor está em uma lista de valores permitidos. */ public function in(string $field, array $allowed, ?string $label = null): self { if ($this->hasError($field) || !isset($this->data[$field])) { return $this; } if (!in_array($this->data[$field], $allowed, true)) { $this->addError($field, sprintf('%s has an invalid value', $label ?? $field)); } return $this; } public function fails(): bool { return !empty($this->errors); } /** * @return array */ public function errors(): array { return $this->errors; } public function firstError(): ?string { if (empty($this->errors)) { return null; } return reset($this->errors); } private function hasError(string $field): bool { return isset($this->errors[$field]); } private function addError(string $field, string $message): void { if (!isset($this->errors[$field])) { $this->errors[$field] = $message; } } }