data = $data; $this->rules = $rules; $this->messages = $messages; $this->errors = []; foreach ($rules as $field => $fieldRules) { $this->validateField($field, $fieldRules); } return empty($this->errors); } /** * validateField validates a single field against its rules */ private function validateField(string $field, string|array $rules): void { $rules = is_string($rules) ? explode('|', $rules) : $rules; $value = $this->getValue($field); foreach ($rules as $rule) { $this->applyRule($field, $value, $rule); } } /** * getValue gets a value from data using dot notation */ private function getValue(string $field): mixed { $keys = explode('.', $field); $value = $this->data; foreach ($keys as $key) { if (!is_array($value) || !array_key_exists($key, $value)) { return null; } $value = $value[$key]; } return $value; } /** * applyRule applies a single validation rule */ private function applyRule(string $field, mixed $value, string $rule): void { $parts = explode(':', $rule, 2); $ruleName = $parts[0]; $parameters = isset($parts[1]) ? explode(',', $parts[1]) : []; $passes = match($ruleName) { 'required' => $this->validateRequired($value), 'email' => $this->validateEmail($value), 'url' => $this->validateUrl($value), 'alpha' => $this->validateAlpha($value), 'alphaNum' => $this->validateAlphaNum($value), 'numeric' => $this->validateNumeric($value), 'integer' => $this->validateInteger($value), 'float' => $this->validateFloat($value), 'boolean' => $this->validateBoolean($value), 'array' => $this->validateArray($value), 'json' => $this->validateJson($value), 'date' => $this->validateDate($value), 'min' => $this->validateMin($value, $parameters[0] ?? 0), 'max' => $this->validateMax($value, $parameters[0] ?? 0), 'between' => $this->validateBetween($value, $parameters[0] ?? 0, $parameters[1] ?? 0), 'length' => $this->validateLength($value, $parameters[0] ?? 0), 'in' => $this->validateIn($value, $parameters), 'notIn' => $this->validateNotIn($value, $parameters), 'regex' => $this->validateRegex($value, $parameters[0] ?? ''), 'confirmed' => $this->validateConfirmed($field, $value), 'unique' => $this->validateUnique($value, $parameters), 'exists' => $this->validateExists($value, $parameters), default => $this->applyCustomRule($ruleName, $value, $parameters) }; if (!$passes) { $this->addError($field, $ruleName, $parameters); } } /** * Validation rule methods */ private function validateRequired(mixed $value): bool { if ($value === null) return false; if (is_string($value) && trim($value) === '') return false; if (is_array($value) && empty($value)) return false; return true; } private function validateEmail(mixed $value): bool { if (!is_string($value)) return false; return filter_var($value, FILTER_VALIDATE_EMAIL) !== false; } private function validateUrl(mixed $value): bool { if (!is_string($value)) return false; return filter_var($value, FILTER_VALIDATE_URL) !== false; } private function validateAlpha(mixed $value): bool { if (!is_string($value)) return false; return ctype_alpha($value); } private function validateAlphaNum(mixed $value): bool { if (!is_string($value)) return false; return ctype_alnum($value); } private function validateNumeric(mixed $value): bool { return is_numeric($value); } private function validateInteger(mixed $value): bool { return filter_var($value, FILTER_VALIDATE_INT) !== false; } private function validateFloat(mixed $value): bool { return filter_var($value, FILTER_VALIDATE_FLOAT) !== false; } private function validateBoolean(mixed $value): bool { return in_array($value, [true, false, 0, 1, '0', '1', 'true', 'false'], true); } private function validateArray(mixed $value): bool { return is_array($value); } private function validateJson(mixed $value): bool { if (!is_string($value)) return false; json_decode($value); return json_last_error() === JSON_ERROR_NONE; } private function validateDate(mixed $value): bool { if (!is_string($value)) return false; $date = date_parse($value); return $date['error_count'] === 0 && $date['warning_count'] === 0; } private function validateMin(mixed $value, mixed $min): bool { if (is_numeric($value)) return $value >= $min; if (is_string($value)) return strlen($value) >= $min; if (is_array($value)) return count($value) >= $min; return false; } private function validateMax(mixed $value, mixed $max): bool { if (is_numeric($value)) return $value <= $max; if (is_string($value)) return strlen($value) <= $max; if (is_array($value)) return count($value) <= $max; return false; } private function validateBetween(mixed $value, mixed $min, mixed $max): bool { return $this->validateMin($value, $min) && $this->validateMax($value, $max); } private function validateLength(mixed $value, mixed $length): bool { if (is_string($value)) return strlen($value) == $length; if (is_array($value)) return count($value) == $length; return false; } private function validateIn(mixed $value, array $values): bool { return in_array($value, $values, true); } private function validateNotIn(mixed $value, array $values): bool { return !in_array($value, $values, true); } private function validateRegex(mixed $value, string $pattern): bool { if (!is_string($value)) return false; return preg_match($pattern, $value) === 1; } private function validateConfirmed(string $field, mixed $value): bool { $confirmField = $field . '_confirmation'; return $value === $this->getValue($confirmField); } private function validateUnique(mixed $value, array $parameters): bool { // Placeholder for database uniqueness check // Would require database connection in real implementation return true; } private function validateExists(mixed $value, array $parameters): bool { // Placeholder for database existence check // Would require database connection in real implementation return true; } /** * applyCustomRule applies a custom validation rule */ private function applyCustomRule(string $ruleName, mixed $value, array $parameters): bool { if (!isset(self::$customRules[$ruleName])) { return true; // Unknown rules pass by default } return call_user_func(self::$customRules[$ruleName], $value, $parameters, $this->data); } /** * addError adds a validation error */ private function addError(string $field, string $rule, array $parameters = []): void { $message = $this->messages["$field.$rule"] ?? $this->messages[$rule] ?? $this->getDefaultMessage($field, $rule, $parameters); if (!isset($this->errors[$field])) { $this->errors[$field] = []; } $this->errors[$field][] = $message; } /** * getDefaultMessage gets a default error message */ private function getDefaultMessage(string $field, string $rule, array $parameters): string { $fieldName = str_replace('_', ' ', $field); return match($rule) { 'required' => "The {$fieldName} field is required.", 'email' => "The {$fieldName} must be a valid email address.", 'url' => "The {$fieldName} must be a valid URL.", 'alpha' => "The {$fieldName} may only contain letters.", 'alphaNum' => "The {$fieldName} may only contain letters and numbers.", 'numeric' => "The {$fieldName} must be a number.", 'integer' => "The {$fieldName} must be an integer.", 'float' => "The {$fieldName} must be a float.", 'boolean' => "The {$fieldName} must be a boolean.", 'array' => "The {$fieldName} must be an array.", 'json' => "The {$fieldName} must be valid JSON.", 'date' => "The {$fieldName} must be a valid date.", 'min' => "The {$fieldName} must be at least {$parameters[0]}.", 'max' => "The {$fieldName} must not be greater than {$parameters[0]}.", 'between' => "The {$fieldName} must be between {$parameters[0]} and {$parameters[1]}.", 'length' => "The {$fieldName} must be exactly {$parameters[0]} characters.", 'in' => "The selected {$fieldName} is invalid.", 'notIn' => "The selected {$fieldName} is invalid.", 'regex' => "The {$fieldName} format is invalid.", 'confirmed' => "The {$fieldName} confirmation does not match.", 'unique' => "The {$fieldName} has already been taken.", 'exists' => "The selected {$fieldName} is invalid.", default => "The {$fieldName} is invalid." }; } /** * errors returns all validation errors */ public function errors(): array { return $this->errors; } /** * failed checks if validation failed */ public function failed(): bool { return !empty($this->errors); } /** * passed checks if validation passed */ public function passed(): bool { return empty($this->errors); } /** * firstError gets the first error message */ public function firstError(string $field = null): ?string { if ($field) { return $this->errors[$field][0] ?? null; } foreach ($this->errors as $errors) { if (!empty($errors)) { return $errors[0]; } } return null; } /** * extend adds a custom validation rule */ public static function extend(string $name, callable $callback): void { self::$customRules[$name] = $callback; } }