diff --git a/src/classes/attributes/validators/my_validator.php b/src/classes/attributes/validators/my_validator.php new file mode 100644 index 0000000..cff3092 --- /dev/null +++ b/src/classes/attributes/validators/my_validator.php @@ -0,0 +1,214 @@ + + * @copyright (c) 2025, Robert Strutts + * @license MIT + */ + +namespace CodeHydrater\attributes\validators; + +use \Attribute; + +#[Attribute(Attribute::TARGET_PROPERTY)] +class Positive {} + +#[Attribute(Attribute::TARGET_PROPERTY)] +class Required {} + +#[Attribute(Attribute::TARGET_PROPERTY)] +class Email {} + +#[Attribute(Attribute::TARGET_PROPERTY)] +class Min { + public function __construct(public int|float $value) {} +} + +#[Attribute(Attribute::TARGET_PROPERTY)] +class Max { + public function __construct(public int|float $value) {} +} + +#[Attribute(Attribute::TARGET_PROPERTY)] +class GreaterThan { + public function __construct(public int|float $value) {} +} + +#[Attribute(Attribute::TARGET_PROPERTY)] +class LessThan { + public function __construct(public int|float $value) {} +} + +#[Attribute(Attribute::TARGET_PROPERTY)] +class NumberRange { + public function __construct( + public int|float $min, + public int|float $max + ) {} +} + +#[Attribute(Attribute::TARGET_PROPERTY)] +class Between { + public function __construct( + public int|float $min, + public int|float $max + ) {} +} + +#[Attribute(Attribute::TARGET_PROPERTY)] +class Same { + public function __construct(public string $other) {} +} + +#[Attribute(Attribute::TARGET_PROPERTY)] +class Alphanumeric {} + +#[Attribute(Attribute::TARGET_PROPERTY)] +class Secure {} + +#[Attribute(Attribute::TARGET_PROPERTY)] +class ValidEmailDomain {} + +#[Attribute(Attribute::TARGET_PROPERTY)] +class ValidDomain {} + +class my_validator { + + use \CodeHydrater\traits\form_validator; + + private array $errors = []; + + private function handle_errors(string $rule_name, string $name, $value, $params = [], array $messages = []) { + // get the message rules + $rule_messages = array_filter($messages, fn($message) => is_string($message)); + // overwrite the default message + $validation_errors = array_merge(self::DEFAULT_VALIDATION_ERRORS, $rule_messages); + + $fn = 'is_' . $rule_name; + $field = $name; + $data[$name] = $value; + + $callable = self::class . "::{$fn}"; + + $string_params = array_map('strval', $params); + + if (is_callable($callable)) { + $pass = $callable($data, $field, ...$string_params); + if (!$pass) { + $lookfor = $messages[$field][$rule_name] ?? $validation_errors[$rule_name]; + $new_params = array_values($params); + $this->errors[$field] = sprintf( + $lookfor, + $field, + ...$params + ); + } + } + } + + public function validate(object $object, array $messages = []): void { + +$handlers = [ + Positive::class => function (string $name, $value, $attr, array $messages) { + $this->handle_errors("positive", $name, $value, messages: $messages); + }, + + Required::class => function (string $name, $value, $attr, array $messages) { + $this->handle_errors("required", $name, $value, messages: $messages); + }, + + Email::class => function (string $name, $value, $attr, array $messages) { + $this->handle_errors("email", $name, $value, messages: $messages); + }, + + GreaterThan::class => function (string $name, $value, $attr, array $messages) { + $params[] = $attr->value; + $this->handle_errors("greater_than", $name, $value, $params, $messages); + }, + + LessThan::class => function (string $name, $value, $attr, array $messages) { + $params[] = $attr->value; + $this->handle_errors("less_than", $name, $value, $params, $messages); + }, + + Min::class => function (string $name, $value, $attr, array $messages) { + $params[] = $attr->value; + $this->handle_errors("min", $name, $value, $params, $messages); + }, + + Max::class => function (string $name, $value, $attr, array $messages) { + $params[] = $attr->value; + $this->handle_errors("max", $name, $value, $params, $messages); + }, + + NumberRange::class => function (string $name, $value, $attr, array $messages) { + $params[] = $attr->min; + $params[] = $attr->max; + + $this->handle_errors("number_range", $name, $value, $params, $messages); + }, + + Between::class => function (string $name, $value, $attr, array $messages) { + $params[] = $attr->min; + $params[] = $attr->max; + + $this->handle_errors("between", $name, $value, $params, $messages); + }, + + Same::class => function (string $name, $value, $attr, array $messages) { + $params[] = $attr->value; + $this->handle_errors("same", $name, $value, $params, $messages); + }, + + Alphanumeric::class => function (string $name, $value, $attr, array $messages) { + $this->handle_errors("alphanumeric", $name, $value, messages: $messages); + }, + + Secure::class => function (string $name, $value, $attr, array $messages) { + $this->handle_errors("secure", $name, $value, messages: $messages); + }, + + ValidEmailDomain::class => function (string $name, $value, $attr, array $messages) { + $this->handle_errors("valid_email_domain", $name, $value, messages: $messages); + }, + + ValidDomain::class => function (string $name, $value, $attr, array $messages) { + $this->handle_errors("valid_domain", $name, $value, messages: $messages); + }, + +]; + + $ref = new \ReflectionObject($object); + + foreach ($ref->getProperties() as $property) { + $name = $property->getName(); + $property->setAccessible(true); + $value = $property->getValue($object); + + foreach ($property->getAttributes() as $attribute) { + $class = $attribute->getName(); + + if (!isset($handlers[$class])) { + continue; + } + + $message = $handlers[$class]( + $name, + $value, + $attribute->newInstance(), + $messages + ); + + if ($message) { + $this->errors[$name][] = $message; + } + } + } + } + + public function get_errors(): array { + return $this->errors; + } +} diff --git a/src/classes/http/request.php b/src/classes/http/request.php index 70e91cd..45f879f 100644 --- a/src/classes/http/request.php +++ b/src/classes/http/request.php @@ -13,21 +13,14 @@ use CodeHydrater\parameter_bag; class request { use \CodeHydrater\traits\Macroable; - - protected $query_params = []; - protected $post_data = []; - protected $server = []; - protected $cookies = []; - protected $files = []; - protected $headers = []; - + public function __construct( - $query_params = [], - $post_data = [], - $server = [], - $cookies = [], - $files = [], - $headers = [] + protected $query_params = [], + protected $post_data = [], + protected $server = [], + protected $cookies = [], + protected $files = [], + protected $headers = [] ) { $this->query_params = new parameter_bag($query_params); $this->post_data = new parameter_bag($post_data); diff --git a/src/classes/parameter_bag.php b/src/classes/parameter_bag.php index bfb8e30..e706515 100644 --- a/src/classes/parameter_bag.php +++ b/src/classes/parameter_bag.php @@ -15,7 +15,7 @@ namespace CodeHydrater; * @author Robert Strutts */ class parameter_bag { - public function __construct(private array $parameters = []) { } + public function __construct(readonly private array $parameters = []) { } public function get($key, $default = null) { return $this->parameters[$key] ?? $default; diff --git a/src/classes/traits/Macroable.php b/src/classes/traits/Macroable.php index f857a45..27005bd 100644 --- a/src/classes/traits/Macroable.php +++ b/src/classes/traits/Macroable.php @@ -2,31 +2,27 @@ namespace CodeHydrater\traits; -trait Macroable -{ +trait Macroable { protected static array $macros = []; /** * Register a custom macro. */ - public static function macro(string $name, mixed $macro): void - { + public static function macro(string $name, mixed $macro): void { static::$macros[$name] = $macro; } /** * Checks if macro is registered. */ - public static function hasMacro(string $name): bool - { + public static function hasMacro(string $name): bool { return isset(static::$macros[$name]); } /** * Dynamically handle calls to the class. */ - public function __call(string $method, mixed $parameters): mixed - { + public function __call(string $method, mixed $parameters): mixed { if (!static::hasMacro($method)) { throw new \BadMethodCallException("Method {$method} does not exist."); } @@ -43,8 +39,7 @@ trait Macroable /** * Dynamically handle static calls to the class. */ - public static function __callStatic(string $method, mixed $parameters): mixed - { + public static function __callStatic(string $method, mixed $parameters): mixed { if (!static::hasMacro($method)) { throw new \BadMethodCallException("Method {$method} does not exist."); } diff --git a/src/classes/traits/form_validator.php b/src/classes/traits/form_validator.php new file mode 100644 index 0000000..c15bbd5 --- /dev/null +++ b/src/classes/traits/form_validator.php @@ -0,0 +1,154 @@ + + * @copyright (c) 2025, Robert Strutts + * @license MIT + */ + +namespace CodeHydrater\traits; + +use \CodeHydrater\common; + +trait form_validator { + + const DEFAULT_VALIDATION_ERRORS = [ + 'positive' => 'The %s is not a positive number', + 'required' => 'Please enter the %s', + 'email' => 'The %s is not a valid email address', + 'less_than' => 'The %s number must be less than %d', + 'greater_than' => 'The %s number must be greater than %d', + 'number_range' => 'The %s number must be in range of %d to %d', + 'min' => 'The %s must have at least %s characters', + 'max' => 'The %s must have at most %s characters', + 'between' => 'The %s must have between %d and %d characters', + 'same' => 'The %s must match with %s', + 'alphanumeric' => 'The %s should have only letters and numbers', + 'secure' => 'The %s must have between 8 and 64 characters and contain at least one number, one upper case letter, one lower case letter and one special character', + 'valid_email_domain' => 'The %s email address is not active', + 'valid_domain' => 'The %s domain name is not active', + ]; + + private static function is_positive(array $data, string $field): bool { + return (intval($data[$field]) >= 0) ? true : false; + } + + private static function is_required(array $data, string $field): bool { + if (isset($data[$field])) { + if (common::get_count($data[$field])) { + return false; // Should not be an array here + } + if (is_string($data[$field])) { + return (trim($data[$field]) !== ''); + } + if (is_int($data[$field])) { + return true; + } + } + return false; + } + + private static function is_email(array $data, string $field): bool { + if (empty($data[$field])) { + return true; + } + return (filter_var($data[$field], FILTER_VALIDATE_EMAIL) === false) ? false : true; + } + + private static function is_min(array $data, string $field, string $min): bool { + if (!isset($data[$field])) { + return true; + } + + return mb_strlen($data[$field]) >= intval($min); + } + + private static function is_max(array $data, string $field, string $max): bool { + if (!isset($data[$field])) { + return true; + } + + return mb_strlen($data[$field]) <= intval($max); + } + + private static function is_greater_than(array $data, string $field, string $min): bool { + if (!isset($data[$field])) { + return true; + } + + return intval($data[$field]) > intval($min); + } + + private static function is_less_than(array $data, string $field, string $max): bool { + if (!isset($data[$field])) { + return true; + } + + return intval($data[$field]) < intval($max); + } + + private static function is_number_range(array $data, string $field, string $min, string $max): bool { + + if (!isset($data[$field])) { + return true; + } + + $no = intval($data[$field]); + return $no >= intval($min) && $no <= intval($max); + } + + private static function is_between(array $data, string $field, string $min, string $max): bool { + if (!isset($data[$field])) { + return true; + } + + $len = mb_strlen($data[$field]); + return $len >= intval($min) && $len <= intval($max); + } + + private static function is_same(array $data, string $field, string $other): bool { + if (isset($data[$field]) && isset($data[$other])) { + return $data[$field] === $data[$other]; + } + + return false; + } + + private static function is_alphanumeric(array $data, string $field): bool { + if (!isset($data[$field])) { + return true; + } + + return ctype_alnum($data[$field]); + } + + private static function is_secure(array $data, string $field): bool { + if (!isset($data[$field])) { + return false; + } + // Is 8 to 64 CHRs + $pattern = "#.*^(?=.{8,64})(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*\W).*$#"; + return preg_match($pattern, $data[$field]); + } + + private static function is_valid_email_domain(array $data, string $field): bool { + if (!isset($data[$field])) { + return false; + } + + $domain = ltrim(stristr($data[$field], '@'), '@') . '.'; + return checkdnsrr($domain, 'MX'); + } + + private static function is_valid_domain(array $data, string $field): bool { + if (!isset($data[$field])) { + return false; + } + + return checkdnsrr($data[$field], 'A') + || checkdnsrr($data[$field], 'AAAA') + || checkdnsrr($data[$field], 'CNAME'); + } +} \ No newline at end of file diff --git a/src/classes/validator.php b/src/classes/validator.php index b384d7e..1315cad 100644 --- a/src/classes/validator.php +++ b/src/classes/validator.php @@ -10,22 +10,8 @@ declare(strict_types=1); namespace CodeHydrater; class validator { - - const DEFAULT_VALIDATION_ERRORS = [ - 'required' => 'Please enter the %s', - 'email' => 'The %s is not a valid email address', - 'less_than' => 'The %s number must be less than %d', - 'greater_than' => 'The %s number must be greater than %d', - 'number_range' => 'The %s number must be in range of %d to %d', - 'min' => 'The %s must have at least %s characters', - 'max' => 'The %s must have at most %s characters', - 'between' => 'The %s must have between %d and %d characters', - 'same' => 'The %s must match with %s', - 'alphanumeric' => 'The %s should have only letters and numbers', - 'secure' => 'The %s must have between 8 and 64 characters and contain at least one number, one upper case letter, one lower case letter and one special character', - 'valid_email_domain' => 'The %s email address is not active', - 'valid_domain' => 'The %s domain name is not active', - ]; + + use \CodeHydrater\traits\form_validator; private static function make_arrays(array $data, $field): array { $dataset = []; @@ -84,8 +70,9 @@ class validator { if (!$pass) { // get the error message for a specific field and rule if exists // otherwise get the error message from the $validation_errors + $lookfor = $messages[$field][$rule_name] ?? $validation_errors[$rule_name]; $errors[$field] = sprintf( - $messages[$field][$rule_name] ?? $validation_errors[$rule_name], + $lookfor, $field, ...$params ); @@ -97,123 +84,6 @@ class validator { return $errors; } - - private static function is_required(array $data, string $field): bool { - if (isset($data[$field])) { - if (common::get_count($data[$field])) { - return false; // Should not be an array here - } - if (is_string($data[$field])) { - return (trim($data[$field]) !== ''); - } - if (is_int($data[$field])) { - return true; - } - } - return false; - } - - private static function is_email(array $data, string $field): bool { - if (empty($data[$field])) { - return true; - } - return filter_var($data[$field], FILTER_VALIDATE_EMAIL); - } - - private static function is_min(array $data, string $field, string $min): bool { - if (!isset($data[$field])) { - return true; - } - - return mb_strlen($data[$field]) >= intval($min); - } - - private static function is_max(array $data, string $field, string $max): bool { - if (!isset($data[$field])) { - return true; - } - - return mb_strlen($data[$field]) <= intval($max); - } - - private static function is_greater_than(array $data, string $field, string $min): bool { - if (!isset($data[$field])) { - return true; - } - - return intval($data[$field]) > intval($min); - } - - private static function is_less_than(array $data, string $field, string $max): bool { - if (!isset($data[$field])) { - return true; - } - - return intval($data[$field]) < intval($max); - } - - private static function is_number_range(array $data, string $field, string $min, string $max): bool { - if (!isset($data[$field])) { - return true; - } - - $no = intval($data[$field]); - return $no >= intval($min) && $no <= intval($max); - } - - private static function is_between(array $data, string $field, string $min, string $max): bool { - if (!isset($data[$field])) { - return true; - } - - $len = mb_strlen($data[$field]); - return $len >= intval($min) && $len <= intval($max); - } - - private static function is_same(array $data, string $field, string $other): bool { - if (isset($data[$field]) && isset($data[$other])) { - return $data[$field] === $data[$other]; - } - - return false; - } - - private static function is_alphanumeric(array $data, string $field): bool { - if (!isset($data[$field])) { - return true; - } - - return ctype_alnum($data[$field]); - } - - private static function is_secure(array $data, string $field): bool { - if (!isset($data[$field])) { - return false; - } - // Is 8 to 64 CHRs - $pattern = "#.*^(?=.{8,64})(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*\W).*$#"; - return preg_match($pattern, $data[$field]); - } - - private static function is_valid_email_domain(array $data, string $field): bool { - if (!isset($data[$field])) { - return false; - } - - $domain = ltrim(stristr($data[$field], '@'), '@') . '.'; - return checkdnsrr($domain, 'MX'); - } - - private static function is_valid_domain(array $data, string $field): bool { - if (!isset($data[$field])) { - return false; - } - - return checkdnsrr($data[$field], 'A') - || checkdnsrr($data[$field], 'AAAA') - || checkdnsrr($data[$field], 'CNAME'); - } - } /*