From b7d16d472887677632f97c3dd2ce906aa097d061 Mon Sep 17 00:00:00 2001 From: Daniel Ronkainen Date: Tue, 1 Apr 2025 22:13:46 +0200 Subject: [PATCH 01/21] Add validation pool class Instance for multiple validations --- Inp.php | 2 +- ValidatePool.php | 135 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 ValidatePool.php diff --git a/Inp.php b/Inp.php index c685d73..da416cd 100755 --- a/Inp.php +++ b/Inp.php @@ -570,7 +570,7 @@ public function validVersion(bool $strict = false): bool /** * Validate/compare if a version is equal/more/equalMore/less... e.g than withVersion * @param string $withVersion - * @param '!='|'<'|'<='|'<>'|'='|'=='|'>'|'>='|'eq'|'ge'|'gt'|'le'|'lt'|'ne' $operator + * @param string $operator '!='|'<'|'<='|'<>'|'='|'=='|'>'|'>='|'eq'|'ge'|'gt'|'le'|'lt'|'ne' * @return bool */ public function versionCompare(string $withVersion, string $operator = "=="): bool diff --git a/ValidatePool.php b/ValidatePool.php new file mode 100644 index 0000000..d223072 --- /dev/null +++ b/ValidatePool.php @@ -0,0 +1,135 @@ +value = $value; + } + + /** + * Magic method to handle dynamic method calls on the object. + * + * @param string $name The name of the method being called. + * @param array $arguments The arguments passed to the method. + * @return self Returns the current instance of the class. + * @throws \ErrorException + */ + public function __call(string $name, array $arguments): self + { + $inp = new Inp($this->value); + if(!method_exists($inp, $name)) { + throw new \BadMethodCallException("Method $name does not exist in class " . Inp::class . "."); + } + $this->error[$name] = !$inp->$name(...$arguments); + return $this; + } + + /** + * Retrieves the errors recorded during the validation process. + * + * @return array Returns an associative array of errors where the key is the method name + * and the value is the arguments passed to the method. + */ + public function getError(): array + { + return $this->error; + } + + /** + * Checks if there are any errors recorded in the validation process. + * + * @return bool Returns true if there are validation errors, otherwise false. + */ + public function hasError(): bool + { + $this->error = array_filter($this->error); + return !!$this->error; + } + + /** + * Checks if the current state is valid based on the presence of an error. + * + * @return bool Returns true if there is no error, otherwise false. + */ + public function isValid(): bool + { + $this->error = array_filter($this->error); + return !$this->error; + } +} \ No newline at end of file From 40046e5b66e7140543f1e6c801ef45960762c8b1 Mon Sep 17 00:00:00 2001 From: Daniel Ronkainen Date: Wed, 2 Apr 2025 23:04:49 +0200 Subject: [PATCH 02/21] Improve consistency --- ValidatePool.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/ValidatePool.php b/ValidatePool.php index d223072..b5d0c83 100644 --- a/ValidatePool.php +++ b/ValidatePool.php @@ -77,6 +77,11 @@ class ValidatePool private mixed $value; private array $error = []; + /** + * Constructor for the ValidatePool class. + * + * @param mixed $value The initial value to be validated. + */ public function __construct(mixed $value) { $this->value = $value; @@ -108,6 +113,7 @@ public function __call(string $name, array $arguments): self */ public function getError(): array { + $this->error = array_filter($this->error); return $this->error; } @@ -118,8 +124,7 @@ public function getError(): array */ public function hasError(): bool { - $this->error = array_filter($this->error); - return !!$this->error; + return !!$this->getError(); } /** @@ -129,7 +134,6 @@ public function hasError(): bool */ public function isValid(): bool { - $this->error = array_filter($this->error); - return !$this->error; + return !$this->getError(); } } \ No newline at end of file From f6b6bf00ab6ba65a38ed46a1d824e6308db92160 Mon Sep 17 00:00:00 2001 From: Daniel Ronkainen Date: Mon, 14 Apr 2025 19:55:13 +0200 Subject: [PATCH 03/21] Add travers object and array value validation support --- Inp.php | 86 ++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 17 ++++++++++ ValidatePool.php | 44 ++++++++++++++++++++++--- 3 files changed, 143 insertions(+), 4 deletions(-) diff --git a/Inp.php b/Inp.php index da416cd..6e18dc4 100755 --- a/Inp.php +++ b/Inp.php @@ -80,6 +80,46 @@ public static function value(mixed $value): self return new self($value); } + /** + * Makes it possible to travers to a value in array or object + * + * @param string $key + * @param bool $immutable + * @return self + * @throws ErrorException + */ + public function traverse(string $key, bool $immutable = true): self + { + $value = $this->value; + if(is_array($this->value) || is_object($this->value)) { + $value = $this->traversDataFromStr($this->value, $key); + if(!$immutable && $value !== false) { + $this->value = $value; + return $this; + } + } + return self::value($value); + } + + /** + * This will make it possible to validate arrays and object with one line + * + * @example validateInData(user.name, 'length', [1, 200]); + * @param string $key + * @param string $validate + * @param array $args + * @return mixed + * @throws ErrorException + */ + public function validateInData(string $key, string $validate, array $args = []): bool + { + $inp = $this->traverse($key, false); + if(!method_exists($inp, $validate)) { + throw new \BadMethodCallException("Method '{$validate}' does not exist in " . __CLASS__ . " class."); + } + return $inp->{$validate}(...$args); + } + /** * Get value string length * @param string $value @@ -92,6 +132,17 @@ public function getLength(string $value): int return (int)$mb->strlen(); } + /** + * Get the current value + * _The value can be changes with travers method and this lets you peak at the new one_ + * + * @return mixed + */ + public function getValue(): mixed + { + return $this->value; + } + /** * Access luhn validation class * @return Luhn @@ -469,6 +520,16 @@ public function length(int $arg1, ?int $arg2 = null): bool return false; } + /** + * Check int value is equal to int value + * @param int $value + * @return bool + */ + public function count(int $value): bool + { + return (int)$this->value === $value; + } + /** * Value string length is equal to ($arg1) * @param int $arg1 length @@ -826,4 +887,29 @@ public function allOf(array $arr): bool } return true; } + + // Move Helper function to new file later on + + /** + * Will make it possible to traverse validation + * + * This is a helper function that + * @param array $array + * @param string $key + * @return mixed + */ + private function traversDataFromStr(array $array, string $key): mixed + { + $new = $array; + $exp = explode(".", $key); + foreach ($exp as $index) { + $data = is_object($new) ? ($new->{$index} ?? null) : ($new[$index] ?? null); + if(is_null($data)) { + $new = false; + break; + } + $new = $data; + } + return $new; + } } diff --git a/README.md b/README.md index 4e1834c..00d264b 100755 --- a/README.md +++ b/README.md @@ -21,6 +21,23 @@ $valid = Inp::value("Lorem ipsum dolor")->length(1, 200); var_dump($valid); // true ``` +## Travers +It is possible to traverse validate items inside arrays and objects. +```php +$inp = new Inp([ + "user" => [ + "name" => "John Doe", + "email" => "john.doe@gmail.com", + ] +]); +$valid = $inp->traverse("user.name")->length(1, 200); +// This below is the same as above but can be used for other purposes +// $valid = $inp->validateInData("user.name", "length", [1, 200]); + +var_dump($valid); // true +``` + + ## Validations ### Required field diff --git a/ValidatePool.php b/ValidatePool.php index b5d0c83..3dc4522 100644 --- a/ValidatePool.php +++ b/ValidatePool.php @@ -77,6 +77,8 @@ class ValidatePool private mixed $value; private array $error = []; + private ?Inp $inp = null; + /** * Constructor for the ValidatePool class. * @@ -87,6 +89,17 @@ public function __construct(mixed $value) $this->value = $value; } + /** + * Get the current value + * _The value can be changes with travers method and this lets you peak at the new one_ + * + * @return mixed + */ + public function getValue(): mixed + { + return !is_null($this->inp) ? $this->inp->getValue() : $this->value; + } + /** * Magic method to handle dynamic method calls on the object. * @@ -97,12 +110,35 @@ public function __construct(mixed $value) */ public function __call(string $name, array $arguments): self { - $inp = new Inp($this->value); - if(!method_exists($inp, $name)) { + $this->validateWith($name, $arguments); + return $this; + } + + /** + * Access a validation from Inp instance + * + * @param string $name + * @param array $arguments + * @return bool + * @throws \ErrorException + */ + public function validateWith(string $name, array $arguments = []): bool + { + $invert = str_starts_with($name, "!"); + if ($invert) { + $name = substr($name, 1); + } + + $this->inp = new Inp($this->value); + if(!method_exists($this->inp, $name)) { throw new \BadMethodCallException("Method $name does not exist in class " . Inp::class . "."); } - $this->error[$name] = !$inp->$name(...$arguments); - return $this; + $valid = $this->inp->$name(...$arguments); + if($invert) { + $valid = !$valid; + } + $this->error[$name] = !$valid; + return $valid; } /** From 78cff6e858fcdc8d09cec6e5ed326ef41b0f5ca1 Mon Sep 17 00:00:00 2001 From: Daniel Ronkainen Date: Tue, 15 Apr 2025 22:53:36 +0200 Subject: [PATCH 04/21] Add more validations --- Inp.php | 197 ++++++++++++++++++++++++++++++++++++++--------- README.md | 102 ++++++++++++++++++------ ValidatePool.php | 11 ++- 3 files changed, 249 insertions(+), 61 deletions(-) diff --git a/Inp.php b/Inp.php index 6e18dc4..8faedc6 100755 --- a/Inp.php +++ b/Inp.php @@ -14,6 +14,7 @@ use MaplePHP\DTO\MB; use MaplePHP\Validate\Interfaces\InpInterface; use MaplePHP\DTO\Format\Str; +use MaplePHP\DTO\Format\Arr; use DateTime; class Inp implements InpInterface @@ -45,14 +46,18 @@ class Inp implements InpInterface /** * Start instance * @param mixed $value the input value - * @throws ErrorException */ public function __construct(mixed $value) { $this->value = $value; $this->dateTime = new DateTime("now"); - if(is_string($value) || is_numeric($value)) { - $this->length = $this->getLength((string)$value); + $this->init(); + } + + private function init(): void + { + if(is_string($this->value) || is_numeric($this->value)) { + $this->length = $this->getLength((string)$this->value); $this->getStr = new Str($this->value); } } @@ -95,6 +100,7 @@ public function traverse(string $key, bool $immutable = true): self $value = $this->traversDataFromStr($this->value, $key); if(!$immutable && $value !== false) { $this->value = $value; + $this->init(); return $this; } } @@ -443,17 +449,13 @@ public function isReadable(): bool * Value is number * @return bool */ - public function number(): bool + public function isNumber(): bool { return (is_numeric($this->value)); } - public function numeric(): bool - { - return $this->number(); - } - - public function numericVal(): bool + // Alias + public function number(): bool { return $this->number(); } @@ -462,20 +464,30 @@ public function numericVal(): bool * Value is number positive 20 * @return bool */ - public function positive(): bool + public function isPositive(): bool { return ((float)$this->value >= 0); } + public function positive(): bool + { + return $this->isPositive(); + } + /** * Value is number negative -20 * @return bool */ - public function negative(): bool + public function isNegative(): bool { return ((float)$this->value < 0); } + public function negative(): bool + { + return $this->isNegative(); + } + /** * Value is minimum float|int value * @param float $int @@ -520,57 +532,173 @@ public function length(int $arg1, ?int $arg2 = null): bool return false; } + /** + * Check if array is empty + * + * @return bool + */ + public function isArrayEmpty(): bool + { + return ($this->isArray() && count($this->value) === 0); + } + + /** + * Check if all items in array is truthy + * + * @param string|int|float $key + * @return bool + */ + public function itemsAreTruthy(string|int|float $key): bool + { + if($this->isArray()) { + $count = Arr::value($this->value) + ->filter(fn ($item) => $item->flatten()->{$key}->toBool()) + ->count(); + return ($count === count($this->value)); + } + return false; + } + + /** + * Check if truthy item exist in array + * + * @param string|int|float $key + * @return bool + */ + public function hasTruthyItem(string|int|float $key): bool + { + if($this->isArray()) { + $count = Arr::value($this->value) + ->filter(fn ($item) => $item->flatten()->{$key}->toBool()) + ->count(); + return ($count > 0); + } + return false; + } + + /** + * Validate array length equal to + * + * @param int $length + * @return bool + */ + public function isCountEqualTo(int $length): bool + { + return ($this->isArray() && count($this->value) === $length); + } + + /** + * Validate array length is more than + * + * @param int $length + * @return bool + */ + public function isCountMoreThan(int $length): bool + { + return ($this->isArray() && count($this->value) > $length); + } + + /** + * Validate array length is less than + * + * @param int $length + * @return bool + */ + public function isCountLessThan(int $length): bool + { + return ($this->isArray() && count($this->value) < $length); + } + + /** * Check int value is equal to int value * @param int $value * @return bool */ - public function count(int $value): bool + public function toIntEqual(int $value): bool { return (int)$this->value === $value; } /** - * Value string length is equal to ($arg1) - * @param int $arg1 length + * Value string length is equal to ($length) + * @param int $length * @return bool */ - public function equalLength(int $arg1): bool + public function isLengthEqualTo(int $length): bool { - if ($this->length === $arg1) { + if ($this->length === $length) { return true; } return false; } + public function equalLength(int $length): bool + { + return $this->isLengthEqualTo($length); + } + /** * IF value equals to param - * @param $str + * @param mixed $value * @return bool */ - public function equal($str): bool + public function isEqualTo(mixed $value): bool { - return ($this->value === $str); + return ($this->value === $value); + } + + // Alias + public function equal(mixed $value): bool + { + return $this->isEqualTo($value); + } + + /** + * IF value equals to param + * @param mixed $value + * @return bool + */ + public function isNotEqualTo(mixed $value): bool + { + return ($this->value !== $value); + } + + public function notEqual(mixed $value): bool + { + return $this->isNotEqualTo($value); } /** * IF value is less than to parameter - * @param $num + * @param float|int $num * @return bool */ - public function lessThan($num): bool + public function isLessThan(float|int $num): bool { - return ($this->value < (float)$num); + return ($this->value < $num); + } + + // Shortcut + public function lessThan(float|int $num): bool + { + return $this->isLessThan($num); } /** * IF value is more than to parameter - * @param $num + * @param float|int $num * @return bool */ - public function moreThan($num): bool + public function isMoreThan(float|int $num): bool + { + return ($this->value > $num); + } + + // Alias + public function moreThan(float|int $num): bool { - return ($this->value > (float)$num); + return $this->isMoreThan($num); } /** @@ -606,28 +734,23 @@ public function endsWith(string $needle): bool return str_ends_with($this->value, $needle); } - /** - * IF value equals to param - * @param $str - * @return bool - */ - public function notEqual($str): bool - { - return ($this->value !== $str); - } - /** * Check is a valid version number * @param bool $strict (validate as a semantic Versioning, e.g. 1.0.0) * @return bool */ - public function validVersion(bool $strict = false): bool + public function isValidVersion(bool $strict = false): bool { $strictMatch = (!$strict || preg_match("/^(\d?\d)\.(\d?\d)\.(\d?\d)$/", (string)$this->value)); $compare = version_compare((string)$this->value, '0.0.1', '>='); return ($strictMatch && $compare !== false && $compare >= 0); } + public function validVersion(bool $strict = false): bool + { + return $this->isValidVersion(); + } + /** * Validate/compare if a version is equal/more/equalMore/less... e.g than withVersion * @param string $withVersion diff --git a/README.md b/README.md index 00d264b..c805d92 100755 --- a/README.md +++ b/README.md @@ -62,25 +62,25 @@ Inp::value("Lorem ipsum dolor")->length(1, 160); ### Check if string has an exact length ```php -Inp::value("Lorem ipsum dolor")->equalLength(10); +Inp::value("Lorem ipsum dolor")->isLengthEqualTo(10); ``` ### Check if value equals or not equals another value - **Equals**: ```php -Inp::value("Lorem ipsum dolor")->equal("Lorem ipsum dolor"); +Inp::value("Lorem ipsum dolor")->isEqualTo("Lorem ipsum dolor"); ``` - **Not equals**: ```php -Inp::value("Lorem ipsum dolor")->notEqual("Lorem ipsum"); +Inp::value("Lorem ipsum dolor")->isNotEqualTo("Lorem ipsum"); ``` - **More than**: ```php -Inp::value(200)->moreThan(100); +Inp::value(200)->isMoreThan(100); ``` - **Less than**: ```php -Inp::value(100)->lessThan(200); +Inp::value(100)->isLessThan(200); ``` - **Contains**: ```php @@ -128,32 +128,33 @@ Inp::value("SE8272267913")->vatNumber(); ### Check if value is a valid float ```php -Inp::value("3.1415")->isFloat(); +Inp::value(3.1415)->isFloat(); ``` ### Check if value is a valid integer ```php -Inp::value("42")->isInt(); +Inp::value(42)->isInt(); ``` ### Check if value is a valid number (numeric) ```php -Inp::value("42")->number(); +Inp::value(42)->isNumber(); ``` ### Check if value is positive or negative - **Positive**: ```php -Inp::value("20")->positive(); +Inp::value(20)->isPositive(); ``` - **Negative**: ```php -Inp::value("-20")->negative(); +Inp::value(-20)->isNegative(); ``` ### Check if value is a valid version number ```php -Inp::value("1.0.0")->validVersion(true); // strict semantic versioning +// True === validate as a semantic Versioning, e.g. 1.0.0 +Inp::value("1.0.0")->isValidVersion(true); ``` ### Compare version with another version @@ -253,33 +254,65 @@ Inp::value("12345")->zip(5); Inp::value("abc")->pregMatch("a-zA-Z"); ``` -### Validate if value is an array, object, or resource -- **Array**: +## Validate Arrays + +### Check if is an array ```php -Inp::value([1, 2, 3])->isArray(); +Inp::value(["Apple", "Orange", "Lemon"])->isArray(); ``` -- **Object**: + +### Check if array is empty ```php -Inp::value($obj)->isObject(); +Inp::value(["Apple", "Orange", "Lemon"])->isArrayEmpty(); ``` -- **Resource**: + +### Check if all items in array is truthy ```php -Inp::value($resource)->isResource(); +Inp::value(["1", true, "Lemon"])->itemsAreTruthy(); ``` -- **Json**: +### Check if truthy item exist in array ```php -Inp::value($jsonStr)->isJson(); +Inp::value(["1", false, "Lemon"])->hasTruthyItem(); ``` -- **HTML Document**: + + +### Check if array count is equal to length ```php -Inp::value($jsonStr)->isFullHtml(); +Inp::value(["Apple", "Orange", "Lemon"])->isCountEqualTo(3); ``` +### Check if array count is more than the length +```php +Inp::value(["Apple", "Orange", "Lemon"])->isCountMoreThan(1); +``` + +### Check if array count is less than the length +```php +Inp::value(["Apple", "Orange", "Lemon"])->isCountLessThan(4); +``` + + +### Check if value is a valid float +```php +Inp::value("Lorem ipsum dolor")->isString(); +``` +## Validate types -### Validate if value is boolean or interpretable as a boolean +### Check if value is a valid float +```php +Inp::value("Lorem ipsum dolor")->isString(); +``` +### Check if value is a valid float +```php +Inp::value(3.1415)->isFloat(); +``` +### Check if value is a valid integer +```php +Inp::value(42)->isInt(); +``` - **Is Boolean**: ```php Inp::value(true)->isBool(); @@ -288,6 +321,29 @@ Inp::value(true)->isBool(); ```php Inp::value("yes")->isBoolVal(); ``` +- **Array**: +```php +Inp::value([1, 2, 3])->isArray(); +``` +- **Object**: +```php +Inp::value($obj)->isObject(); +``` +- **Resource**: +```php +Inp::value($resource)->isResource(); +``` + +- **Json**: +```php +Inp::value($jsonStr)->isJson(); +``` + +- **HTML Document**: +```php +Inp::value($jsonStr)->isFullHtml(); +``` + ### Validate using multiple methods (one or all must match) - **Validate if one method passes**: diff --git a/ValidatePool.php b/ValidatePool.php index 3dc4522..e2947ff 100644 --- a/ValidatePool.php +++ b/ValidatePool.php @@ -6,6 +6,8 @@ * @method self withValue(mixed $value) * @method self value(mixed $value) * @method self getLength(string $value) + * @method self traverse(string $key, bool $immutable = true) + * @method self validateInData(string $key, string $validate, array $args = []) * @method self luhn() * @method self required() * @method self hasValue() @@ -122,7 +124,7 @@ public function __call(string $name, array $arguments): self * @return bool * @throws \ErrorException */ - public function validateWith(string $name, array $arguments = []): bool + public function validateWith(string $name, array $arguments = []): bool|self { $invert = str_starts_with($name, "!"); if ($invert) { @@ -134,6 +136,13 @@ public function validateWith(string $name, array $arguments = []): bool throw new \BadMethodCallException("Method $name does not exist in class " . Inp::class . "."); } $valid = $this->inp->$name(...$arguments); + + // If using traverse method in Inp + if($valid instanceof Inp) { + throw new \BadMethodCallException("The method ->$name() is not supported with " . + __CLASS__ . ". Use ->validateInData() instead!"); + } + if($invert) { $valid = !$valid; } From a5d87e22bb7e2b791aa4762d2a6d3e78ac82051c Mon Sep 17 00:00:00 2001 From: Daniel Ronkainen Date: Wed, 16 Apr 2025 21:34:25 +0200 Subject: [PATCH 05/21] Structure and quality improvements --- README.md | 63 +++++--- composer.json | 2 +- Inp.php => src/Inp.php | 132 +++++---------- .../Interfaces}/InpInterface.php | 0 Luhn.php => src/Luhn.php | 52 +++--- src/Traits/InpAliases.php | 152 ++++++++++++++++++ ValidVatFormat.php => src/ValidVatFormat.php | 0 ValidatePool.php => src/ValidatePool.php | 13 +- 8 files changed, 268 insertions(+), 146 deletions(-) rename Inp.php => src/Inp.php (90%) rename {Interfaces => src/Interfaces}/InpInterface.php (100%) rename Luhn.php => src/Luhn.php (87%) create mode 100644 src/Traits/InpAliases.php rename ValidVatFormat.php => src/ValidVatFormat.php (100%) rename ValidatePool.php => src/ValidatePool.php (94%) diff --git a/README.md b/README.md index c805d92..f77f894 100755 --- a/README.md +++ b/README.md @@ -21,8 +21,8 @@ $valid = Inp::value("Lorem ipsum dolor")->length(1, 200); var_dump($valid); // true ``` -## Travers -It is possible to traverse validate items inside arrays and objects. +## Traverse +It is possible to traverse and validate items inside a collection ```php $inp = new Inp([ "user" => [ @@ -37,12 +37,25 @@ $valid = $inp->traverse("user.name")->length(1, 200); var_dump($valid); // true ``` +## Validation Pool +Validation Pool is like `Inp` except it helps you making multiple validations in same instance. +```php +$validPool = new ValidatePool("john.doe@gmail.com"); +$validPool->isEmail() + ->length(1, 200) + ->endsWith(".com"); +$isValid = $validPool->isValid(); +// $hasError = $validPool->hasError(); + +var_dump($isValid); // true +``` + ## Validations ### Required field ```php -Inp::value("Lorem ipsum dolor")->required(); +Inp::value("Lorem ipsum dolor")->isRequired(); ``` ### Check if there is any value (even if it's 0) @@ -97,33 +110,33 @@ Inp::value("Lorem ipsum dolor")->endsWith("dolor"); ### Validate if it's a valid email ```php -Inp::value("john@gmail.com")->email(); +Inp::value("john@gmail.com")->isEmail(); ``` ### Validate if it's a valid phone number Allows numbers and special characters ("-", "+", " "). ```php -Inp::value("+46709676040")->phone(); +Inp::value("+46709676040")->isPhone(); ``` -### Validate Swedish personal number (personnummer) +### Validate Swedish personal number (personnel) ```php -Inp::value("198808213412")->socialNumber(); +Inp::value("198808213412")->isSocialNumber(); ``` ### Validate Swedish organization number ```php -Inp::value("197511043412")->orgNumber(); +Inp::value("197511043412")->isOrgNumber(); ``` ### Validate credit card number ```php -Inp::value("1616523623422334")->creditCard(); +Inp::value("1616523623422334")->isCreditCard(); ``` ### Validate VAT number ```php -Inp::value("SE8272267913")->vatNumber(); +Inp::value("SE8272267913")->isVatNumber(); ``` ### Check if value is a valid float @@ -165,11 +178,11 @@ Inp::value("1.0.0")->versionCompare("2.0.0", '>='); ### Validate password (lossy or strict) - **Lossy password (minimum character set)**: ```php -Inp::value("password123")->lossyPassword(8); +Inp::value("password123")->isLossyPassword(8); ``` - **Strict password** (requires at least one lowercase, uppercase, digit, and special character): ```php -Inp::value("Password#123!")->strictPassword(8); +Inp::value("Password#123!")->isStrictPassword(8); ``` ### Validate if value is string and contains only A-Z @@ -192,38 +205,48 @@ Inp::value("#000000")->hex(); ``` ### Check if it's a valid date +As default you can validate against a date format like this "Y-m-d" ```php -Inp::value("2022-02-13")->date("Y-m-d"); +Inp::value("2022-02-13")->isDate(); +``` +Custom date validation +```php +Inp::value("2022/02/13 14:15")->isDate(Y/m/d H:i); ``` ### Check if it's a valid date and time ```php -Inp::value("2022-02-13 14:15")->dateTime("Y-m-d H:i"); +Inp::value("2022-02-13 14:15:58")->isDateWithTime(); ``` ### Check if it's a valid time +Validate hour and minutes +```php +Inp::value("14:15")->isTime(); +``` +Validate hour, minutes and seconds ```php -Inp::value("14:15")->time("H:i"); +Inp::value("14:15:58")->isTime(true); ``` ### Check if someone is at least a certain age ```php -Inp::value("1988-05-22")->age(18); +Inp::value("1988-05-22")->isAge(18); ``` ### Check if it's a valid domain name ```php -Inp::value("example.com")->domain(); +Inp::value("example.com")->isDomain(); ``` ### Check if it's a valid URL (http/https is required) ```php -Inp::value("https://example.com/page")->url(); +Inp::value("https://example.com/page")->isUrl(); ``` ### Check if it's a valid DNS entry ```php -Inp::value("example.com")->dns(); +Inp::value("example.com")->isDns(); ``` ### Validate file and directory properties @@ -246,7 +269,7 @@ Inp::value("/path/to/file.txt")->isReadable(); ### Validate ZIP code (with custom length) ```php -Inp::value("12345")->zip(5); +Inp::value("12345")->isZip(5); ``` ### Validate if value matches a pattern (regex) diff --git a/composer.json b/composer.json index 5758562..74d5710 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,7 @@ }, "autoload": { "psr-4": { - "MaplePHP\\Validate\\": "" + "MaplePHP\\Validate\\": "src" } }, "minimum-stability": "dev" diff --git a/Inp.php b/src/Inp.php similarity index 90% rename from Inp.php rename to src/Inp.php index 8faedc6..719bed1 100755 --- a/Inp.php +++ b/src/Inp.php @@ -1,5 +1,4 @@ init(); } + /** + * Used to reset length in traverse with "mutable" flag + * @return void + * @throws ErrorException + */ private function init(): void { if(is_string($this->value) || is_numeric($this->value)) { @@ -165,7 +171,7 @@ public function luhn(): Luhn * Will check if value if empty (e.g. "", 0, NULL) = false * @return bool */ - public function required(): bool + public function isRequired(): bool { if ($this->length(1) && !empty($this->value)) { return true; @@ -183,28 +189,19 @@ public function hasValue(): bool } /** - * Validate Swedish personal numbers + * Validate Swedish personal numbers (personalNumber) * @return bool */ - public function socialNumber(): bool + public function isSocialNumber(): bool { return $this->luhn()->personnummer(); } - /** - * Validate Swedish personal numbers - * @return bool - */ - public function personalNumber(): bool - { - return $this->socialNumber(); - } - /** * Validate Swedish org numbers * @return bool */ - public function orgNumber(): bool + public function isOrgNumber(): bool { return $this->luhn()->orgNumber(); } @@ -213,16 +210,16 @@ public function orgNumber(): bool * Validate credit card numbers (THIS needs to be tested) * @return bool */ - public function creditCard(): bool + public function isCreditCard(): bool { - return $this->luhn()->creditcard(); + return $this->luhn()->creditCard(); } /** * Validate Swedish vat number * @return bool */ - public function vatNumber(): bool + public function isVatNumber(): bool { return $this->luhn()->vatNumber(); } @@ -233,7 +230,7 @@ public function vatNumber(): bool * manually with the method @dns but in most cases this will not be necessary. * @return bool */ - public function email(): bool + public function isEmail(): bool { return (filter_var($this->value, FILTER_VALIDATE_EMAIL) !== false); } @@ -254,7 +251,7 @@ public function findInString(string $match, ?int $pos = null): bool * Check if is a phone number * @return bool */ - public function phone(): bool + public function isPhone(): bool { if (is_null($this->getStr)) { return false; @@ -267,19 +264,19 @@ public function phone(): bool /** * Check if is valid ZIP - * @param int $arg1 start length - * @param int|null $arg2 end length + * @param int $minLength start length + * @param int|null $maxLength end length * @return bool * @throws ErrorException */ - public function zip(int $arg1, ?int $arg2 = null): bool + public function isZip(int $minLength, ?int $maxLength = null): bool { if (is_null($this->getStr)) { return false; } $this->value = (string)$this->getStr->replace([" ", "-", "—", "–"], ["", "", "", ""]); $this->length = $this->getLength($this->value); - return ($this->isInt() && $this->length($arg1, $arg2)); + return ($this->isInt() && $this->length($minLength, $maxLength)); } /** @@ -454,12 +451,6 @@ public function isNumber(): bool return (is_numeric($this->value)); } - // Alias - public function number(): bool - { - return $this->number(); - } - /** * Value is number positive 20 * @return bool @@ -469,11 +460,6 @@ public function isPositive(): bool return ((float)$this->value >= 0); } - public function positive(): bool - { - return $this->isPositive(); - } - /** * Value is number negative -20 * @return bool @@ -483,11 +469,6 @@ public function isNegative(): bool return ((float)$this->value < 0); } - public function negative(): bool - { - return $this->isNegative(); - } - /** * Value is minimum float|int value * @param float $int @@ -609,7 +590,6 @@ public function isCountLessThan(int $length): bool return ($this->isArray() && count($this->value) < $length); } - /** * Check int value is equal to int value * @param int $value @@ -633,11 +613,6 @@ public function isLengthEqualTo(int $length): bool return false; } - public function equalLength(int $length): bool - { - return $this->isLengthEqualTo($length); - } - /** * IF value equals to param * @param mixed $value @@ -648,12 +623,6 @@ public function isEqualTo(mixed $value): bool return ($this->value === $value); } - // Alias - public function equal(mixed $value): bool - { - return $this->isEqualTo($value); - } - /** * IF value equals to param * @param mixed $value @@ -664,11 +633,6 @@ public function isNotEqualTo(mixed $value): bool return ($this->value !== $value); } - public function notEqual(mixed $value): bool - { - return $this->isNotEqualTo($value); - } - /** * IF value is less than to parameter * @param float|int $num @@ -679,12 +643,6 @@ public function isLessThan(float|int $num): bool return ($this->value < $num); } - // Shortcut - public function lessThan(float|int $num): bool - { - return $this->isLessThan($num); - } - /** * IF value is more than to parameter * @param float|int $num @@ -695,12 +653,6 @@ public function isMoreThan(float|int $num): bool return ($this->value > $num); } - // Alias - public function moreThan(float|int $num): bool - { - return $this->isMoreThan($num); - } - /** * Checks if a string contains a given substring * @@ -746,11 +698,6 @@ public function isValidVersion(bool $strict = false): bool return ($strictMatch && $compare !== false && $compare >= 0); } - public function validVersion(bool $strict = false): bool - { - return $this->isValidVersion(); - } - /** * Validate/compare if a version is equal/more/equalMore/less... e.g than withVersion * @param string $withVersion @@ -772,7 +719,7 @@ public function versionCompare(string $withVersion, string $operator = "=="): bo * @param integer $length Minimum length * @return bool */ - public function lossyPassword(int $length = 1): bool + public function isLossyPassword(int $length = 1): bool { return ((int)preg_match('/^[a-zA-Z\d$@$!%*?&]{' . $length . ',}$/', $this->value) > 0); } @@ -789,7 +736,7 @@ public function lossyPassword(int $length = 1): bool * @param integer $length Minimum length * @return bool */ - public function strictPassword(int $length = 1): bool + public function isStrictPassword(int $length = 1): bool { $pattern = '/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&])[A-Za-z\d$@$!%*?&]{' . $length . ',}$/'; return ((int)preg_match($pattern, $this->value) > 0); @@ -805,7 +752,6 @@ public function pregMatch($matchStr): bool return ((int)preg_match("/^[" . $matchStr . "]+$/", $this->value) > 0); } - /** * Is value is string and character between a-z or A-Z * @return bool @@ -837,7 +783,7 @@ public function upperAtoZ(): bool * Is Hex color code string * @return bool */ - public function hex(): bool + public function isHex(): bool { return ((int)preg_match('/^#([0-9A-F]{3}){1,2}$/i', $this->value) > 0); } @@ -847,30 +793,28 @@ public function hex(): bool * @param string $format validate after this date format (default Y-m-d) * @return bool */ - public function date(string $format = "Y-m-d"): bool + public function isDate(string $format = "Y-m-d"): bool { return (DateTime::createFromFormat($format, $this->value) !== false); } - /** * Check if is a date and time - * @param string $format validate after this date format (default Y-m-d H:i) * @return bool */ - public function dateTime(string $format = "Y-m-d H:i"): bool + public function isDateWithTime(): bool { - return $this->date($format); + return $this->date("Y-m-d H:i:s"); } /** * Check if is a date and time - * @param string $format validate after this date format (default Y-m-d H:i) + * @param bool $withSeconds * @return bool */ - public function time(string $format = "H:i"): bool + public function isTime(bool $withSeconds = false): bool { - return $this->date($format); + return $this->date("H:i" . ($withSeconds ? ":s" : "")); } /** @@ -894,17 +838,17 @@ public function dateRange(string $format = "Y-m-d H:i"): array|false /** * Check "minimum" age (value format should be validated date "Y-m-d") - * @param int $arg1 18: user should be 18 or older + * @param int $checkAge * @return bool - * @throws Exception + * @throws \DateMalformedStringException */ - public function age(int $arg1): bool + public function isAge(int $checkAge): bool { $now = (int)$this->dateTime->format("Y"); $dateTime = new DateTime($this->value); $birth = (int)$dateTime->format("Y"); $age = ($now - $birth); - return ($age >= $arg1); + return ($age >= $checkAge); } /** @@ -912,7 +856,7 @@ public function age(int $arg1): bool * @param bool $strict stricter = true * @return bool */ - public function domain(bool $strict = true): bool + public function isDomain(bool $strict = true): bool { $strict = ($strict) ? FILTER_FLAG_HOSTNAME : 0; return (filter_var((string)$this->value, FILTER_VALIDATE_DOMAIN, $strict) !== false); @@ -922,7 +866,7 @@ public function domain(bool $strict = true): bool * Check if is valid URL (http|https is required) * @return bool */ - public function url(): bool + public function isUrl(): bool { return (filter_var($this->value, FILTER_VALIDATE_URL) !== false); } @@ -933,7 +877,7 @@ public function url(): bool * @noinspection PhpComposerExtensionStubsInspection * @return bool */ - public function dns(): bool + public function isDns(): bool { $AResult = true; $host = $this->getHost($this->value); diff --git a/Interfaces/InpInterface.php b/src/Interfaces/InpInterface.php similarity index 100% rename from Interfaces/InpInterface.php rename to src/Interfaces/InpInterface.php diff --git a/Luhn.php b/src/Luhn.php similarity index 87% rename from Luhn.php rename to src/Luhn.php index bc6cea7..10cd4df 100755 --- a/Luhn.php +++ b/src/Luhn.php @@ -1,7 +1,6 @@ string); - $this->string = preg_replace('/[^A-Z\d]/', '', strtoupper($number)); $this->number = preg_replace('/\D/', '', $number); - //$this->length = (is_string($this->number)) ? strlen($this->number) : 0; } /** @@ -104,10 +95,11 @@ function bankgiro() { */ /** - * Is valid creditcard number + * Is valid credit card number + * * @return bool */ - public function creditcard(): bool + public function creditCard(): bool { if ($this->cardPrefix()) { $sum = $this->luhn($this->number); @@ -118,6 +110,7 @@ public function creditcard(): bool /** * Get card type + * * @return string|bool */ public function cardPrefix(): string|bool @@ -145,7 +138,8 @@ public function cardPrefix(): string|bool } /** - * Validate Vat + * Validate if is a vat number, will work for multiple countries + * * @return bool */ public function vatNumber(): bool @@ -162,7 +156,8 @@ public function vatNumber(): bool } /** - * Chech if is a date + * Check if is a date + * * @return boolean */ public function isDate(): bool @@ -175,12 +170,14 @@ public function isDate(): bool } /** - * Check if is a coordinaion number + * Check if is a coordination number + * * If you are going to live and work here but don’t meet the requirements * for registering in the Swedish Population Register. + * * @return bool */ - public function isCoordinationNumber() + public function isCoordinationNumber(): bool { return checkdate( $this->getPart('month'), @@ -190,13 +187,14 @@ public function isCoordinationNumber() } /** - * The Luhn algorithm. - * @param string str + * The Luhn algorithm + * + * @param string $number * @return float */ - final protected function luhn($number): float + final protected function luhn(string $number): float { - $_val = $sum = 0; + $sum = 0; for ($i = 0; $i < strlen($number); $i++) { $_val = (int)$number[$i]; $_val *= 2 - ($i % 2); @@ -211,12 +209,13 @@ final protected function luhn($number): float /** * Parse Swedish social security numbers and get the parts + * * @return array */ - final protected function part() + final protected function part(): array { $match = []; - $reg = '/^(\d{2}){0,1}(\d{2})(\d{2})(\d{2})([\+\-\s]?)(\d{3})(\d)$/'; + $reg = '/^(\d{2})?(\d{2})(\d{2})(\d{2})([+\-\s]?)(\d{3})(\d)$/'; preg_match($reg, $this->number, $match); if (count($match) !== 8) { return []; @@ -231,7 +230,7 @@ final protected function part() $check = $match[7]; if (!in_array($sep, ['-', '+'])) { - if (empty($century) || date('Y') - intval(strval($century) . strval($year)) < 100) { + if (empty($century) || date('Y') - intval($century . $year) < 100) { $sep = '-'; } else { $sep = '+'; @@ -259,6 +258,7 @@ final protected function part() /** * Get part + * * @param string $key * @return int */ diff --git a/src/Traits/InpAliases.php b/src/Traits/InpAliases.php new file mode 100644 index 0000000..4541b15 --- /dev/null +++ b/src/Traits/InpAliases.php @@ -0,0 +1,152 @@ +isDns(); + } + + public function url(): bool + { + return $this->isUrl(); + } + + public function domain(bool $strict = true): bool + { + return $this->isDomain($strict); + } + + public function age(int $checkAge): bool + { + return $this->isAge($checkAge); + } + + public function time(): bool + { + return $this->isTime(); + } + + public function dateTime(): bool + { + return $this->dateTime(); + } + + public function date(string $format = "Y-m-d"): bool + { + return $this->isDate(); + } + + public function hex(): bool + { + return $this->isHex(); + } + + public function strictPassword(int $length = 1): bool + { + return $this->isStrictPassword($length); + } + + public function lossyPassword(int $length = 1): bool + { + return $this->isLossyPassword($length); + } + + public function validVersion(bool $strict = false): bool + { + return $this->isValidVersion(); + } + + public function moreThan(float|int $num): bool + { + return $this->isMoreThan($num); + } + + public function lessThan(float|int $num): bool + { + return $this->isLessThan($num); + } + + public function notEqual(mixed $value): bool + { + return $this->isNotEqualTo($value); + } + + public function equal(mixed $value): bool + { + return $this->isEqualTo($value); + } + + public function equalLength(int $length): bool + { + return $this->isLengthEqualTo($length); + } + + public function negative(): bool + { + return $this->isNegative(); + } + + public function positive(): bool + { + return $this->isPositive(); + } + + public function number(): bool + { + return $this->isNumber(); + } + + public function zip(int $minLength, ?int $maxLength = null): bool + { + return $this->isZip($minLength, $maxLength); + } + + public function phone(): bool + { + return $this->isPhone(); + } + + public function email(): bool + { + return $this->isEmail(); + } + + public function vatNumber(): bool + { + return $this->isVatNumber(); + } + + public function creditCard(): bool + { + return $this->isCreditCard(); + } + + public function orgNumber(): bool + { + return $this->isOrgNumber(); + } + + public function socialNumber(): bool + { + return $this->isSocialNumber(); + } + + public function personalNumber(): bool + { + return $this->socialNumber(); + } + + public function required(): bool + { + return $this->isRequired(); + } +} \ No newline at end of file diff --git a/ValidVatFormat.php b/src/ValidVatFormat.php similarity index 100% rename from ValidVatFormat.php rename to src/ValidVatFormat.php diff --git a/ValidatePool.php b/src/ValidatePool.php similarity index 94% rename from ValidatePool.php rename to src/ValidatePool.php index e2947ff..9bf3dd3 100644 --- a/ValidatePool.php +++ b/src/ValidatePool.php @@ -2,6 +2,9 @@ namespace MaplePHP\Validate; +use BadMethodCallException; +use ErrorException; + /** * @method self withValue(mixed $value) * @method self value(mixed $value) @@ -108,7 +111,7 @@ public function getValue(): mixed * @param string $name The name of the method being called. * @param array $arguments The arguments passed to the method. * @return self Returns the current instance of the class. - * @throws \ErrorException + * @throws ErrorException */ public function __call(string $name, array $arguments): self { @@ -122,9 +125,9 @@ public function __call(string $name, array $arguments): self * @param string $name * @param array $arguments * @return bool - * @throws \ErrorException + * @throws ErrorException */ - public function validateWith(string $name, array $arguments = []): bool|self + public function validateWith(string $name, array $arguments = []): bool { $invert = str_starts_with($name, "!"); if ($invert) { @@ -133,13 +136,13 @@ public function validateWith(string $name, array $arguments = []): bool|self $this->inp = new Inp($this->value); if(!method_exists($this->inp, $name)) { - throw new \BadMethodCallException("Method $name does not exist in class " . Inp::class . "."); + throw new BadMethodCallException("Method $name does not exist in class " . Inp::class . "."); } $valid = $this->inp->$name(...$arguments); // If using traverse method in Inp if($valid instanceof Inp) { - throw new \BadMethodCallException("The method ->$name() is not supported with " . + throw new BadMethodCallException("The method ->$name() is not supported with " . __CLASS__ . ". Use ->validateInData() instead!"); } From ecac136d423ce4482c91cb368e5e45338e88b7ef Mon Sep 17 00:00:00 2001 From: Daniel Ronkainen Date: Wed, 16 Apr 2025 21:47:49 +0200 Subject: [PATCH 06/21] Structure and quality improvements --- README.md | 52 ++++++++++++++++++++++++++++++++++++++++------------ src/Inp.php | 31 ++++--------------------------- 2 files changed, 44 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index f77f894..0075aa6 100755 --- a/README.md +++ b/README.md @@ -1,28 +1,42 @@ # MaplePHP - Validation MaplePHP - Validation is a lightweight and powerful PHP library designed to simplify the validation of various data inputs. Whether you're verifying if a value is a valid email or phone number, ensuring string lengths, or performing more advanced checks like credit card numbers and dates, MaplePHP - Validation offers a comprehensive and intuitive approach. With its wide range of built-in validators and simple syntax, it makes handling complex validation tasks easier, leading to cleaner and more reliable code. +Absolutely! Here's a revised version of your documentation that improves clarity, grammar, formatting, and consistency while preserving your original intentions. I’ve also added brief explanations where helpful and improved phrasing for better readability. + +--- + ## Installation -``` + +Install the library via Composer: + +```bash composer require maplephp/validate ``` -## Initiation -You will always initiate an instance with the static method **_val** followed by a value you want to validate. +--- + +## Getting Started + +You can validate values by instantiating the `Inp` class. There are two ways to do this: ```php use MaplePHP\Validate\Inp; -// Validate option 1 +// Option 1: Create an instance $inp = new Inp("Lorem ipsum dolor"); var_dump($inp->length(1, 200)); // true -// Validate option 2 +// Option 2: Use the static method for cleaner syntax $valid = Inp::value("Lorem ipsum dolor")->length(1, 200); var_dump($valid); // true ``` -## Traverse -It is possible to traverse and validate items inside a collection +--- + +## Validating Nested Data + +You can traverse nested arrays or objects and validate specific values using dot notation: + ```php $inp = new Inp([ "user" => [ @@ -30,26 +44,40 @@ $inp = new Inp([ "email" => "john.doe@gmail.com", ] ]); -$valid = $inp->traverse("user.name")->length(1, 200); -// This below is the same as above but can be used for other purposes -// $valid = $inp->validateInData("user.name", "length", [1, 200]); + +$valid = $inp->eq("user.name")->length(1, 200); var_dump($valid); // true ``` -## Validation Pool -Validation Pool is like `Inp` except it helps you making multiple validations in same instance. +> 💡 You can also use `validateInData()` for more dynamic validations: ```php +$valid = $inp->validateInData("user.name", "length", [1, 200]); +``` + +--- + +## Using the Validation Pool + +The `ValidatePool` class allows you to chain multiple validations on a single value and check the overall result: + +```php +use MaplePHP\Validate\ValidatePool; + $validPool = new ValidatePool("john.doe@gmail.com"); + $validPool->isEmail() ->length(1, 200) ->endsWith(".com"); + $isValid = $validPool->isValid(); // $hasError = $validPool->hasError(); var_dump($isValid); // true ``` +> 🧠 `ValidatePool` is useful when you want to collect and evaluate multiple validation rules at once. + ## Validations diff --git a/src/Inp.php b/src/Inp.php index 719bed1..6e4d45b 100755 --- a/src/Inp.php +++ b/src/Inp.php @@ -9,6 +9,7 @@ namespace MaplePHP\Validate; use ErrorException; +use MaplePHP\DTO\Traverse; use MaplePHP\Validate\Interfaces\InpInterface; use MaplePHP\Validate\Traits\InpAliases; use MaplePHP\DTO\MB; @@ -99,11 +100,11 @@ public static function value(mixed $value): self * @return self * @throws ErrorException */ - public function traverse(string $key, bool $immutable = true): self + public function eq(string $key, bool $immutable = true): self { $value = $this->value; if(is_array($this->value) || is_object($this->value)) { - $value = $this->traversDataFromStr($this->value, $key); + $value = Traverse::value($this->value)->eq($key)->get(); if(!$immutable && $value !== false) { $this->value = $value; $this->init(); @@ -125,7 +126,7 @@ public function traverse(string $key, bool $immutable = true): self */ public function validateInData(string $key, string $validate, array $args = []): bool { - $inp = $this->traverse($key, false); + $inp = $this->eq($key, false); if(!method_exists($inp, $validate)) { throw new \BadMethodCallException("Method '{$validate}' does not exist in " . __CLASS__ . " class."); } @@ -955,28 +956,4 @@ public function allOf(array $arr): bool return true; } - // Move Helper function to new file later on - - /** - * Will make it possible to traverse validation - * - * This is a helper function that - * @param array $array - * @param string $key - * @return mixed - */ - private function traversDataFromStr(array $array, string $key): mixed - { - $new = $array; - $exp = explode(".", $key); - foreach ($exp as $index) { - $data = is_object($new) ? ($new->{$index} ?? null) : ($new[$index] ?? null); - if(is_null($data)) { - $new = false; - break; - } - $new = $data; - } - return $new; - } } From f1ff4ecf1ac680366367a1d24414071cd3e5f99f Mon Sep 17 00:00:00 2001 From: Daniel Ronkainen Date: Wed, 16 Apr 2025 21:49:00 +0200 Subject: [PATCH 07/21] Structure and quality improvements --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 0075aa6..b6a03d7 100755 --- a/README.md +++ b/README.md @@ -1,8 +1,5 @@ # MaplePHP - Validation MaplePHP - Validation is a lightweight and powerful PHP library designed to simplify the validation of various data inputs. Whether you're verifying if a value is a valid email or phone number, ensuring string lengths, or performing more advanced checks like credit card numbers and dates, MaplePHP - Validation offers a comprehensive and intuitive approach. With its wide range of built-in validators and simple syntax, it makes handling complex validation tasks easier, leading to cleaner and more reliable code. - -Absolutely! Here's a revised version of your documentation that improves clarity, grammar, formatting, and consistency while preserving your original intentions. I’ve also added brief explanations where helpful and improved phrasing for better readability. - --- ## Installation From afdd7633008c2a108543dc051773d7faffe78128 Mon Sep 17 00:00:00 2001 From: Daniel Ronkainen Date: Wed, 16 Apr 2025 21:49:21 +0200 Subject: [PATCH 08/21] Structure and quality improvements --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b6a03d7..466d646 100755 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # MaplePHP - Validation + MaplePHP - Validation is a lightweight and powerful PHP library designed to simplify the validation of various data inputs. Whether you're verifying if a value is a valid email or phone number, ensuring string lengths, or performing more advanced checks like credit card numbers and dates, MaplePHP - Validation offers a comprehensive and intuitive approach. With its wide range of built-in validators and simple syntax, it makes handling complex validation tasks easier, leading to cleaner and more reliable code. ---- ## Installation From 8a1f4ddb8a8917426cddad87332702857b77aa2a Mon Sep 17 00:00:00 2001 From: Daniel Ronkainen Date: Thu, 17 Apr 2025 21:23:48 +0200 Subject: [PATCH 09/21] Add more validations --- README.md | 70 +++++++++++++++++++-- src/Inp.php | 172 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 231 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 466d646..9be2ca3 100755 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ MaplePHP - Validation is a lightweight and powerful PHP library designed to simplify the validation of various data inputs. Whether you're verifying if a value is a valid email or phone number, ensuring string lengths, or performing more advanced checks like credit card numbers and dates, MaplePHP - Validation offers a comprehensive and intuitive approach. With its wide range of built-in validators and simple syntax, it makes handling complex validation tasks easier, leading to cleaner and more reliable code. +--- + ## Installation Install the library via Composer: @@ -103,31 +105,47 @@ Inp::value("Lorem ipsum dolor")->length(1, 160); Inp::value("Lorem ipsum dolor")->isLengthEqualTo(10); ``` -### Check if value equals or not equals another value -- **Equals**: +### Check if value equals exactly to or not equals another value +- **Equals**: Strict data type validation check if equals to expected value ```php Inp::value("Lorem ipsum dolor")->isEqualTo("Lorem ipsum dolor"); ``` -- **Not equals**: + +- **Loosely Equals**: Flexible data type validation check if loosely equals to expected value +```php +Inp::value("Lorem ipsum dolor")->isLooselyEqualTo("Lorem ipsum dolor"); +``` + +- **Not equals**: Strict data type validation check if not equals to expected value ```php Inp::value("Lorem ipsum dolor")->isNotEqualTo("Lorem ipsum"); ``` + +- **Loosely Not equals**: Flexible data type validation check if loosely not equals to expected value +```php +Inp::value("Lorem ipsum dolor")->isLooselyNotEqualTo("Lorem ipsum"); +``` + - **More than**: ```php Inp::value(200)->isMoreThan(100); ``` + - **Less than**: ```php Inp::value(100)->isLessThan(200); ``` + - **Contains**: ```php Inp::value("Lorem ipsum dolor")->contains("ipsum"); ``` + - **Starts with**: ```php Inp::value("Lorem ipsum dolor")->startsWith("Lorem"); ``` + - **Ends with**: ```php Inp::value("Lorem ipsum dolor")->endsWith("dolor"); @@ -314,6 +332,21 @@ Inp::value(["Apple", "Orange", "Lemon"])->isArray(); Inp::value(["Apple", "Orange", "Lemon"])->isArrayEmpty(); ``` +### Strict data type validation check if value exists in given array +```php +Inp::value(["Apple", "Orange", "Lemon"])->isInArray(); +``` + +### Flexible data type validation check if value exists in given array +```php +Inp::value(["Apple", "Orange", "Lemon"])->isLooselyInArray(); +``` + +### Strict data type validation check if key exists in array +```php +Inp::value(["Apple", "Orange", "Lemon"])->keyExists(); +``` + ### Check if all items in array is truthy ```php Inp::value(["1", true, "Lemon"])->itemsAreTruthy(); @@ -324,8 +357,6 @@ Inp::value(["1", true, "Lemon"])->itemsAreTruthy(); Inp::value(["1", false, "Lemon"])->hasTruthyItem(); ``` - - ### Check if array count is equal to length ```php Inp::value(["Apple", "Orange", "Lemon"])->isCountEqualTo(3); @@ -341,7 +372,6 @@ Inp::value(["Apple", "Orange", "Lemon"])->isCountMoreThan(1); Inp::value(["Apple", "Orange", "Lemon"])->isCountLessThan(4); ``` - ### Check if value is a valid float ```php Inp::value("Lorem ipsum dolor")->isString(); @@ -393,6 +423,34 @@ Inp::value($jsonStr)->isFullHtml(); ``` +## HTTP status code validation + +#### Strict data type validation check if value is a valid HTTP status code +```php +Inp::value(403)->isHttpStatusCode(); +``` + +#### Strict data type validation check if value is HTTP 200 OK +```php +Inp::value(200)->isHttp200(); +``` + +#### Strict data type validation check if value is a 2xx success HTTP code +```php +Inp::value(210)->isHttpSuccess(); +``` + +#### Strict data type validation check if value is a 4xx client error HTTP code +```php +Inp::value(403)->isHttpClientError(); +``` + +#### Strict data type validation check if value is a 5xx server error HTTP code +```php +Inp::value(500)->isHttpServerError(); +``` + + ### Validate using multiple methods (one or all must match) - **Validate if one method passes**: ```php diff --git a/src/Inp.php b/src/Inp.php index 6e4d45b..ad571ef 100755 --- a/src/Inp.php +++ b/src/Inp.php @@ -180,6 +180,79 @@ public function isRequired(): bool return false; } + /** + * Strict data type validation check if is false + * + * @return bool + */ + public function isTrue(): bool + { + return $this->value === true; + } + + /** + * Flexible data type validation check if is truthy + * + * @return bool + */ + public function isisTruthy(): bool + { + return filter_var($this->value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) === true; + } + + /** + * Strict data type validation check if is false + * + * @return bool + */ + public function isFalse(): bool + { + return $this->value === false; + } + + /** + * Flexible data type validation check if is falsy + * + * @return bool + */ + public function isFalsy(): bool + { + return filter_var($this->value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) === false; + } + + /** + * Strict data type validation check if value exists in given array + * + * @param array $haystack + * @return bool + */ + public function isInArray(array $haystack): bool + { + return in_array($this->value, $haystack, true); + } + + /** + * Flexible data type validation check if value exists in given array + * + * @param array $haystack + * @return bool + */ + public function isLooselyInArray(array $haystack): bool + { + return in_array($this->value, $haystack); + } + + /** + * Strict data type validation check if key exists in array + * + * @param string|int $key + * @return bool + */ + public function keyExists(string|int $key): bool + { + return is_array($this->value) && array_key_exists($key, $this->value); + } + /** * Will only check if there is a value (e.g. 0) = true * @return bool @@ -615,17 +688,31 @@ public function isLengthEqualTo(int $length): bool } /** - * IF value equals to param - * @param mixed $value + * Strict data type validation check if equals to expected value + * + * @param mixed $expected + * @return bool + */ + public function isEqualTo(mixed $expected): bool + { + return $this->value === $expected; + } + + /** + * Flexible data type validation check if loosely equals to expected value + * + * @param mixed $expected * @return bool */ - public function isEqualTo(mixed $value): bool + public function isLooselyEqualTo(mixed $expected): bool { - return ($this->value === $value); + return $this->value == $expected; } + /** - * IF value equals to param + * Strict data type validation check if not equals to expected value + * * @param mixed $value * @return bool */ @@ -634,6 +721,17 @@ public function isNotEqualTo(mixed $value): bool return ($this->value !== $value); } + /** + * Flexible data type validation check if loosely not equals to expected value + * + * @param mixed $value + * @return bool + */ + public function isLooselyNotEqualTo(mixed $value): bool + { + return ($this->value !== $value); + } + /** * IF value is less than to parameter * @param float|int $num @@ -921,6 +1019,70 @@ private function getHost(string $host): string return rtrim(idn_to_ascii($host, IDNA_DEFAULT, $variant), '.') . '.'; } + /** + * Strict data type validation check if value is a valid HTTP status code + * + * @return bool + */ + public function isHttpStatusCode(): bool + { + $validCodes = [ + 100, 101, 102, 103, + 200, 201, 202, 203, 204, 205, 206, 207, 208, 226, + 300, 301, 302, 303, 304, 305, 307, 308, + 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, + 410, 411, 412, 413, 414, 415, 416, 417, 418, 421, + 422, 423, 424, 425, 426, 428, 429, 431, 451, + 500, 501, 502, 503, 504, 505, 506, 507, 508, 510, 511 + ]; + return in_array((int)$this->value, $validCodes, true); + } + + /** + * Strict data type validation check if value is HTTP 200 OK + * + * @return bool + */ + public function isHttp200(): bool + { + return (int)$this->value === 200; + } + + /** + * Strict data type validation check if value is a 2xx success HTTP code + * + * @return bool + */ + public function isHttpSuccess(): bool + { + $intVal = (int)$this->value; + return $intVal >= 200 && $intVal < 300; + } + + + /** + * Strict data type validation check if value is a 4xx client error HTTP code + * + * @return bool + */ + public function isHttpClientError(): bool + { + $intVal = (int)$this->value; + return $intVal >= 400 && $intVal < 500; + } + + /** + * Strict data type validation check if value is a 5xx server error HTTP code + * + * @return bool + */ + public function isHttpServerError(): bool + { + $intVal = (int)$this->value; + return $intVal >= 500 && $intVal < 600; + } + + /** * Validate multiple. Will return true if "one" matches * @param array $arr From ef1fd01497c5555df9c172aaef7a584e429df866 Mon Sep 17 00:00:00 2001 From: Daniel Ronkainen Date: Thu, 17 Apr 2025 23:05:13 +0200 Subject: [PATCH 10/21] Map custom error key mapping --- src/ValidatePool.php | 41 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/src/ValidatePool.php b/src/ValidatePool.php index 9bf3dd3..e25a465 100644 --- a/src/ValidatePool.php +++ b/src/ValidatePool.php @@ -80,6 +80,8 @@ class ValidatePool { private mixed $value; + private ?string $key = null; + private ?string $validationName = null; private array $error = []; private ?Inp $inp = null; @@ -102,7 +104,7 @@ public function __construct(mixed $value) */ public function getValue(): mixed { - return !is_null($this->inp) ? $this->inp->getValue() : $this->value; + return $this->value; } /** @@ -119,6 +121,18 @@ public function __call(string $name, array $arguments): self return $this; } + public function mapErrorToKey(string $key): self + { + $this->key = $key; + return $this; + } + + public function mapErrorValidationName(string $key): self + { + $this->validationName = $key; + return $this; + } + /** * Access a validation from Inp instance * @@ -149,22 +163,45 @@ public function validateWith(string $name, array $arguments = []): bool if($invert) { $valid = !$valid; } - $this->error[$name] = !$valid; + + $name = !is_null($this->validationName) ? $this->validationName : $name; + if(!is_null($this->key)) { + $this->error[$this->key][$name] = !$valid; + } else { + $this->error[][$name] = !$valid; + } + + $this->validationName = $this->key = null; return $valid; } /** * Retrieves the errors recorded during the validation process. * + * NOTICE: Every error item that has true is true that it found an error! + * * @return array Returns an associative array of errors where the key is the method name * and the value is the arguments passed to the method. */ public function getError(): array { + $this->error = array_map('array_filter', $this->error); $this->error = array_filter($this->error); return $this->error; } + public function getNormalizedError(): array + { + $new = []; + $error = $this->getError(); + foreach($error as $keyA => $arr) { + foreach($arr as $keyB => $bool) { + $new[$keyA][$keyB] = !$bool; + } + } + return $new; + } + /** * Checks if there are any errors recorded in the validation process. * From 70ac64c88bd8f4ced22fe1d1dd5994cc98dd8168 Mon Sep 17 00:00:00 2001 From: Daniel Ronkainen Date: Tue, 22 Apr 2025 21:57:21 +0200 Subject: [PATCH 11/21] Add DNS validations --- src/DNS.php | 306 ++++++++++++++++++++++++++++++++++++++ src/Inp.php | 286 +++++++++++++++++------------------ src/Traits/InpAliases.php | 39 ++++- 3 files changed, 486 insertions(+), 145 deletions(-) create mode 100755 src/DNS.php diff --git a/src/DNS.php b/src/DNS.php new file mode 100755 index 0000000..ffd685f --- /dev/null +++ b/src/DNS.php @@ -0,0 +1,306 @@ + 'DNS_A', // Host Address + DNS_CNAME => 'DNS_CNAME', // Canonical Name Record + DNS_HINFO => 'DNS_HINFO', // Host Information + DNS_CAA => 'DNS_CAA', // Certification Authority Authorization + DNS_MX => 'DNS_MX', // Mail Exchange Record + DNS_NS => 'DNS_NS', // Name Server Record + DNS_PTR => 'DNS_PTR', // Pointer Record + DNS_SOA => 'DNS_SOA', // Start of Authority Record + DNS_TXT => 'DNS_TXT', // Text Record + DNS_AAAA => 'DNS_AAAA', // IPv6 Address Record + DNS_SRV => 'DNS_SRV', // Service Locator + DNS_NAPTR => 'DNS_NAPTR', // Naming Authority Pointer + DNS_A6 => 'DNS_A6', // IPv6 Address Record (deprecated) + DNS_ALL => 'DNS_ALL', // All Records + DNS_ANY => 'DNS_ANY', // Any Records + ]; + + private string $host; + + public function __construct(string $host) + { + $this->host = $this->getHost($host); + } + + /** + * Check if the host is resolvable via DNS (MX, A, or AAAA record). + * + * @return bool + */ + public function isResolvableHost(): bool + { + return $this->isMxRecord() || $this->isAddressRecord(); + } + + /** + * Check if the host has a valid A or AAAA DNS record. + * + * @return bool + */ + public function isAddressRecord(): bool + { + return checkdnsrr($this->host, 'A') || checkdnsrr($this->host, 'AAAA'); + } + + /** + * Check if value is a valid mx host record + * + * @return bool + */ + public function isMxRecord(): bool + { + // Argument 2 is MX by default + return checkdnsrr($this->host); + } + + + /** + * Check if value is a valid CNAME record + * + * A CNAME record is used to alias a domain to another domain. + * + * @return bool + */ + public function isCnameRecord(): bool + { + return checkdnsrr($this->host, 'CNAME'); + } + + + /** + * Check if the host contains an 'A' record in the DNS. + * + * An 'A' record maps a domain name to an IPv4 address, + * allowing the domain to be resolved to its corresponding IP. + * + * @return bool Returns true if an 'A' record exists, false otherwise. + */ + public function isARecord(): bool + { + return checkdnsrr($this->host, 'A'); + } + + + /** + * Checks if the host contains an 'AAAA' record in the DNS. + * + * An 'AAAA' record maps a domain name to an IPv6 address, + * allowing the domain to be resolved to its corresponding IPv6 address. + * + * @return bool Returns true if an 'AAAA' record exists, false otherwise. + */ + public function isAaaaRecord(): bool + { + return checkdnsrr($this->host, 'AAAA'); + } + + + /** + * Check if the host contains an 'NS' record in the DNS. + * + * An 'NS' (Name Server) record delegates a domain or subdomain to a set of name servers. + * + * @return bool Returns true if an 'NS' record exists, false otherwise. + */ + public function isNsRecord(): bool + { + return checkdnsrr($this->host, 'NS'); + } + + + /** + * Check if the host contains a 'SOA' record in the DNS. + * + * A 'SOA' (Start of Authority) record provides information about the + * domain's administrative information, such as the primary DNS server, + * the email of the domain administrator, and other essential info. + * + * @return bool Returns true if a 'SOA' record exists, false otherwise. + */ + public function isSoaRecord(): bool + { + return checkdnsrr($this->host, 'SOA'); + } + + + /** + * Check if the host contains a 'TXT' record in the DNS. + * + * A 'TXT' record is primarily used to store human-readable data related to the domain, + * such as SPF records, or other text-based information. + * + * @return bool Returns true if a 'TXT' record exists, false otherwise. + */ + public function isTxtRecord(): bool + { + return checkdnsrr($this->host, 'TXT'); + } + + + /** + * Check if the host contains an 'SRV' record in the DNS. + * + * An 'SRV' record is used to define the location (hostname and port) + * of servers for specified services. + * + * @return bool Returns true if an 'SRV' record exists, false otherwise. + */ + public function isSrvRecord(): bool + { + return checkdnsrr($this->host, 'SRV'); + } + + + /** + * Check if the host contains a 'NAPTR' record in the DNS. + * + * A 'NAPTR' (Naming Authority Pointer) record is used for applications in + * Internet telephony to map phone numbers to domain names or define other service-specific rules. + * + * @return bool Returns true if a 'NAPTR' record exists, false otherwise. + */ + public function isNaptrRecord(): bool + { + return checkdnsrr($this->host, 'NAPTR'); + } + + + /** + * Check if the host contains an 'A6' record in the DNS. + * + * An 'A6' record was used in earlier IPv6 implementations to map a domain name to an IPv6 address, + * but it has been deprecated in favor of 'AAAA' records. + * + * @return bool Returns true if an 'A6' record exists, false otherwise. + */ + public function isA6Record(): bool + { + return checkdnsrr($this->host, 'A6'); + } + + + /** + * Check if the host contains any valid DNS record of any type. + * + * This method checks for the existence of any DNS record for the given host + * without specifying any particular record type. + * + * @return bool Returns true if any valid DNS record exists, false otherwise. + */ + public function isAnyRecord(): bool + { + return checkdnsrr($this->host, 'ANY'); + } + + + /** + * Check if the host contains all DNS records. + * + * This method checks for the existence of all possible DNS records associated + * with the given host. It supports various record types like A, AAAA, CNAME, NS, + * SOA, and others. + * + * @return bool Returns true if all DNS records exist, false otherwise. + */ + public function isAllRecord(): bool + { + return checkdnsrr($this->host, 'ALL'); + } + + + /** + * Check if the host contains a 'CAA' record in the DNS. + * + * A 'CAA' (Certification Authority Authorization) record is used to specify + * which certificate authorities (CAs) are allowed to issue certificates for a domain. + * + * @return bool Returns true if a 'CAA' record exists, false otherwise. + */ + public function isCaaRecord(): bool + { + return checkdnsrr($this->host, 'CAA'); + } + + + /** + * Check if the host contains a 'PTR' record in the DNS. + * + * A 'PTR' (Pointer) record is used for reverse DNS lookups, + * often translating an IP address to a hostname. + * + * @return bool Returns true if a 'PTR' record exists, false otherwise. + */ + public function isPtrRecord(): bool + { + return checkdnsrr($this->host, 'PTR'); + } + + + /** + * Check if the host contains an 'HINFO' record in the DNS. + * + * An 'HINFO' (Host Information) record provides information about the hardware + * and operating system of a host. This type of record is rarely used due to + * security concerns. + * + * @return bool Returns true if an 'HINFO' record exists, false otherwise. + */ + public function isHinfoRecord(): bool + { + return checkdnsrr($this->host, 'HINFO'); + } + + /** + * Fetch DNS Resource Records associated with a hostname + * + * This method retrieves DNS records of a specific type for the host + * and returns them as an array if found. Supported DNS types include: + * DNS_A, DNS_CNAME, DNS_HINFO, DNS_CAA, DNS_MX, DNS_NS, DNS_PTR, DNS_SOA, + * DNS_TXT, DNS_AAAA, DNS_SRV, DNS_NAPTR, DNS_A6, DNS_ALL, or DNS_ANY. + * + * @param int $type The DNS record type to match. + * @return array|false Returns an array of matched DNS records, or false if none are found. + * + * @throws \InvalidArgumentException If an invalid or unsupported DNS type is provided. + */ + public function getDnsRecordForType(int $type): array|false + { + if(!isset(self::ALOWED_DNS_TYPES[$type])) { + throw new \InvalidArgumentException('Invalid DNS type. Use one of ' . implode(', ', self::ALOWED_DNS_TYPES) . '.'); + } + $result = dns_get_record($this->host, $type); + if (is_array($result) && count($result) > 0) { + return $result; + } + return false; + } + + /** + * Get hosts (used for DNS checks) + * + * @noinspection PhpComposerExtensionStubsInspection + * @param string $host + * @return string + */ + protected function getHost(string $host): string + { + if (!defined('INTL_IDNA_VARIANT_2003')) { + define('INTL_IDNA_VARIANT_2003', 0); + } + $variant = (defined('INTL_IDNA_VARIANT_UTS46')) ? INTL_IDNA_VARIANT_UTS46 : INTL_IDNA_VARIANT_2003; + return rtrim(idn_to_ascii($host, IDNA_DEFAULT, $variant), '.') . '.'; + } +} diff --git a/src/Inp.php b/src/Inp.php index ad571ef..b5b05fc 100755 --- a/src/Inp.php +++ b/src/Inp.php @@ -41,6 +41,7 @@ class Inp implements InpInterface private int $length = 0; private DateTime $dateTime; private ?Luhn $luhn = null; + private ?DNS $dns = null; private ?Str $getStr = null; @@ -168,6 +169,19 @@ public function luhn(): Luhn return $this->luhn; } + /** + * Access DNS validations + * + * @return DNS + */ + public function dns(): DNS + { + if(is_null($this->dns)) { + $this->dns = new DNS($this->value); + } + return $this->dns; + } + /** * Will check if value if empty (e.g. "", 0, NULL) = false * @return bool @@ -195,7 +209,7 @@ public function isTrue(): bool * * @return bool */ - public function isisTruthy(): bool + public function isTruthy(): bool { return filter_var($this->value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) === true; } @@ -254,7 +268,7 @@ public function keyExists(string|int $key): bool } /** - * Will only check if there is a value (e.g. 0) = true + * Will only check if there is a value * @return bool */ public function hasValue(): bool @@ -302,6 +316,7 @@ public function isVatNumber(): bool * Validate email * Loosely check if is email. By loosely I mean it will not check if valid DNS. You can check this * manually with the method @dns but in most cases this will not be necessary. + * * @return bool */ public function isEmail(): bool @@ -309,8 +324,55 @@ public function isEmail(): bool return (filter_var($this->value, FILTER_VALIDATE_EMAIL) !== false); } + + /** + * Validate if the email is deliverable + * This checks if the email is syntactically valid and has a valid MX record. + * + * @return bool + */ + public function isDeliverableEmail(): bool + { + return ($this->isEmail() && $this->dns()->isMxRecord()); + } + + + /** + * Checks if a string contains a given substring + * + * @param string $needle + * @return bool + */ + public function contains(string $needle): bool + { + return str_contains($this->value, $needle); + } + + /** + * Checks if a string starts with a given substring + * + * @param string $needle + * @return bool + */ + public function startsWith(string $needle): bool + { + return str_starts_with($this->value, $needle); + } + + /** + * Checks if a string ends with a given substring + * + * @param string $needle + * @return bool + */ + public function endsWith(string $needle): bool + { + return str_ends_with($this->value, $needle); + } + /** * Find in string + * * @param string $match keyword to match against * @param int|null $pos match start position if you want * @return bool @@ -338,6 +400,7 @@ public function isPhone(): bool /** * Check if is valid ZIP + * * @param int $minLength start length * @param int|null $maxLength end length * @return bool @@ -382,15 +445,6 @@ public function isString(): bool return is_string($this->value); } - /** - * Is value string - * @return bool - */ - public function isStr(): bool - { - return $this->isString(); - } - /** * Is value array * @return bool @@ -418,6 +472,15 @@ public function isBool(): bool return (is_bool($this->value)); } + /** + * Is resource + * @return bool + */ + public function isResource(): bool + { + return is_resource($this->value); + } + /** * Is a valid json string * @return bool @@ -448,7 +511,6 @@ public function isFullHtml(): bool return $htmlTag && $headTag && $bodyTag; } - /** * Check if the value itself can be Interpreted as a bool value * E.g. If value === ([on, off], [yes, no], [1, 0] or [true, false]) @@ -472,30 +534,30 @@ public function isNull(): bool } /** - * Is file + * Is directory * @return bool */ - public function isFile(): bool + public function isDir(): bool { - return is_file($this->value); + return is_dir($this->value); } /** - * Is directory + * Is file * @return bool */ - public function isDir(): bool + public function isFile(): bool { - return is_dir($this->value); + return is_file($this->value); } /** - * Is resource + * Check if is file or directory * @return bool */ - public function isResource(): bool + function isFileOrDirectory(): bool { - return is_resource($this->value); + return file_exists($this->value); } /** @@ -517,16 +579,28 @@ public function isReadable(): bool } /** - * Value is number + * Value is strictly a number (int or float). + * * @return bool */ public function isNumber(): bool { - return (is_numeric($this->value)); + return $this->isFloat() || $this->isInt(); + } + + /** + * Value is loosely numeric (e.g. numeric strings, scientific notation). + * + * @return bool + */ + public function isNumbery(): bool + { + return is_numeric($this->value); } /** * Value is number positive 20 + * * @return bool */ public function isPositive(): bool @@ -536,6 +610,7 @@ public function isPositive(): bool /** * Value is number negative -20 + * * @return bool */ public function isNegative(): bool @@ -545,6 +620,7 @@ public function isNegative(): bool /** * Value is minimum float|int value + * * @param float $int * @return bool */ @@ -553,18 +629,9 @@ public function min(float $int): bool return ((float)$this->value >= $int); } - /** - * Value is minimum float|int value (Same as "@min()" but can be used to add another error message) - * @param float $int - * @return bool - */ - public function minAlt(float $int): bool - { - return $this->min($int); - } - /** * Value is maximum float|int value + * * @param float $int * @return bool */ @@ -574,14 +641,15 @@ public function max(float $int): bool } /** - * Value string length is more than start ($arg1) or between start ($arg1) and end ($arg2) - * @param int $arg1 start length - * @param int|null $arg2 end length + * Check if string length is more than start ($min), or between ($min) and ($max) + * + * @param int $min start length + * @param int|null $max end length * @return bool */ - public function length(int $arg1, ?int $arg2 = null): bool + public function length(int $min, ?int $max = null): bool { - if ($this->length >= $arg1 && (($arg2 === null) || $this->length <= $arg2)) { + if ($this->length >= $min && (($max === null) || $this->length <= $max)) { return true; } return false; @@ -752,39 +820,6 @@ public function isMoreThan(float|int $num): bool return ($this->value > $num); } - /** - * Checks if a string contains a given substring - * - * @param string $needle - * @return bool - */ - public function contains(string $needle): bool - { - return str_contains($this->value, $needle); - } - - /** - * Checks if a string starts with a given substring - * - * @param string $needle - * @return bool - */ - public function startsWith(string $needle): bool - { - return str_starts_with($this->value, $needle); - } - - /** - * Checks if a string ends with a given substring - * - * @param string $needle - * @return bool - */ - public function endsWith(string $needle): bool - { - return str_ends_with($this->value, $needle); - } - /** * Check is a valid version number * @param bool $strict (validate as a semantic Versioning, e.g. 1.0.0) @@ -842,40 +877,44 @@ public function isStrictPassword(int $length = 1): bool } /** - * Is value is string and character between a-z or A-Z - * @param $matchStr + * Check if the value contains only characters matching the given pattern. + * + * @param string $charRange A character range (e.g., 'a-z', 'A-Z0-9') * @return bool */ - public function pregMatch($matchStr): bool + public function isMatchingPattern(string $charRange): bool { - return ((int)preg_match("/^[" . $matchStr . "]+$/", $this->value) > 0); + return (bool)preg_match("/^[$charRange]+$/", $this->value); } /** - * Is value is string and character between a-z or A-Z + * Check if the value contains only alphabetic characters (a–z or A–Z). + * * @return bool */ - public function atoZ(): bool + public function isAlpha(): bool { - return ((int)preg_match("/^[a-zA-Z]+$/", $this->value) > 0); + return (bool)preg_match("/^[a-zA-Z]+$/", $this->value); } /** - * Is value is string and character between a-z (LOWERCASE) + * Check if the value contains only lowercase letters (a–z). + * * @return bool */ - public function lowerAtoZ(): bool + public function isLowerAlpha(): bool { - return ((int)preg_match("/^[a-z]+$/", $this->value) > 0); + return (bool)preg_match("/^[a-z]+$/", $this->value); } /** - * Is value is string and character between A-Z (UPPERCASE) + * Check if the value contains only uppercase letters (A–Z). + * * @return bool */ - public function upperAtoZ(): bool + public function isUpperAlpha(): bool { - return ((int)preg_match("/^[A-Z]+$/", $this->value) > 0); + return (bool)preg_match("/^[A-Z]+$/", $this->value); } /** @@ -916,25 +955,6 @@ public function isTime(bool $withSeconds = false): bool return $this->date("H:i" . ($withSeconds ? ":s" : "")); } - /** - * Check if is a date and a "valid range" - * @param string $format validate after this date format (default Y-m-d H:i) - * @return array|false E.g. array(T1, T2); T1 = start and T2 = end - */ - public function dateRange(string $format = "Y-m-d H:i"): array|false - { - $exp = explode(" - ", $this->value); - if (count($exp) === 2) { - $time1 = trim($exp[0]); - $time2 = trim($exp[1]); - $val1 = DateTime::createFromFormat($format, $time1); - $val2 = DateTime::createFromFormat($format, $time2); - return (($val1 && $val2 && ($val1->getTimestamp() <= $val2->getTimestamp())) ? - ["t1" => $time1, "t2" => $time2] : false); - } - return false; - } - /** * Check "minimum" age (value format should be validated date "Y-m-d") * @param int $checkAge @@ -976,47 +996,9 @@ public function isUrl(): bool * @noinspection PhpComposerExtensionStubsInspection * @return bool */ - public function isDns(): bool - { - $AResult = true; - $host = $this->getHost($this->value); - $MXResult = checkdnsrr($host); // Argument 2 is MX by default - if (!$MXResult) { - $AResult = checkdnsrr($host, 'A') || checkdnsrr($host, 'AAAA'); - } - return ($MXResult || $AResult); - } - - /** - * Match DNS record by search for TYPE and matching VALUE - * @param int $type (DNS_A, DNS_CNAME, DNS_HINFO, DNS_CAA, DNS_MX, DNS_NS, DNS_PTR, DNS_SOA, - * DNS_TXT, DNS_AAAA, DNS_SRV, DNS_NAPTR, DNS_A6, DNS_ALL or DNS_ANY) - * @noinspection PhpComposerExtensionStubsInspection - * @return array|false - */ - public function matchDNS(int $type): array|false - { - $host = $this->getHost($this->value); - $result = dns_get_record($host, $type); - if (is_array($result) && count($result) > 0) { - return $result; - } - return false; - } - - /** - * Get hosts (used for DNS checks) - * @noinspection PhpComposerExtensionStubsInspection - * @param string $host - * @return string - */ - private function getHost(string $host): string + public function isResolvableHost(): bool { - if (!defined('INTL_IDNA_VARIANT_2003')) { - define('INTL_IDNA_VARIANT_2003', 0); - } - $variant = (defined('INTL_IDNA_VARIANT_UTS46')) ? INTL_IDNA_VARIANT_UTS46 : INTL_IDNA_VARIANT_2003; - return rtrim(idn_to_ascii($host, IDNA_DEFAULT, $variant), '.') . '.'; + return $this->dns()->isResolvableHost(); } /** @@ -1118,4 +1100,22 @@ public function allOf(array $arr): bool return true; } + /** + * Check if is a date and a "valid range" + * @param string $format validate after this date format (default Y-m-d H:i) + * @return array|false E.g. array(T1, T2); T1 = start and T2 = end + */ + public function dateRange(string $format = "Y-m-d H:i"): array|false + { + $exp = explode(" - ", $this->value); + if (count($exp) === 2) { + $time1 = trim($exp[0]); + $time2 = trim($exp[1]); + $val1 = DateTime::createFromFormat($format, $time1); + $val2 = DateTime::createFromFormat($format, $time2); + return (($val1 && $val2 && ($val1->getTimestamp() <= $val2->getTimestamp())) ? + ["t1" => $time1, "t2" => $time2] : false); + } + return false; + } } diff --git a/src/Traits/InpAliases.php b/src/Traits/InpAliases.php index 4541b15..07a0281 100644 --- a/src/Traits/InpAliases.php +++ b/src/Traits/InpAliases.php @@ -10,9 +10,9 @@ trait InpAliases { - public function dns(): bool + public function isDns(): bool { - return $this->isDns(); + return $this->isResolvableHost(); } public function url(): bool @@ -149,4 +149,39 @@ public function required(): bool { return $this->isRequired(); } + + public function isStr(): bool + { + return $this->isString(); + } + + public function minimum(float $int): bool + { + return $this->min($int); + } + + public function maximum(float $int): bool + { + return $this->max($int); + } + + public function pregMatch(string $match): bool + { + return $this->isMatchingPattern($match); + } + + public function atoZ(): bool + { + return $this->isAlpha(); + } + + public function lowerAtoZ(): bool + { + return $this->isLowerAlpha(); + } + + public function upperAtoZ(): bool + { + return $this->isUpperAlpha(); + } } \ No newline at end of file From 12af9c9b8de2e7c832768ee980f7bf9f8e7c6d1c Mon Sep 17 00:00:00 2001 From: Daniel Ronkainen Date: Sun, 27 Apr 2025 21:09:54 +0200 Subject: [PATCH 12/21] Minor code quality improvements --- src/DNS.php | 18 +---- src/Inp.php | 141 +++++++++++++++++++++++---------- tests/unitary-validate-inp.php | 12 ++- 3 files changed, 114 insertions(+), 57 deletions(-) diff --git a/src/DNS.php b/src/DNS.php index ffd685f..2c878ba 100755 --- a/src/DNS.php +++ b/src/DNS.php @@ -3,7 +3,7 @@ * @Package: MaplePHP - Luhn algorithm * @Author: Daniel Ronkainen * @Licence: Apache-2.0 license, Copyright © Daniel Ronkainen - Don't delete this comment, its part of the license. + Don't delete this comment, it's part of the license. */ namespace MaplePHP\Validate; @@ -56,7 +56,7 @@ public function isAddressRecord(): bool } /** - * Check if value is a valid mx host record + * Check if the value is a valid mx host record * * @return bool */ @@ -68,7 +68,7 @@ public function isMxRecord(): bool /** - * Check if value is a valid CNAME record + * Check if the value is a valid CNAME record * * A CNAME record is used to alias a domain to another domain. * @@ -119,10 +119,9 @@ public function isNsRecord(): bool { return checkdnsrr($this->host, 'NS'); } - /** - * Check if the host contains a 'SOA' record in the DNS. + * Check if the host contains an 'SOA' record in the DNS. * * A 'SOA' (Start of Authority) record provides information about the * domain's administrative information, such as the primary DNS server, @@ -134,7 +133,6 @@ public function isSoaRecord(): bool { return checkdnsrr($this->host, 'SOA'); } - /** * Check if the host contains a 'TXT' record in the DNS. @@ -148,7 +146,6 @@ public function isTxtRecord(): bool { return checkdnsrr($this->host, 'TXT'); } - /** * Check if the host contains an 'SRV' record in the DNS. @@ -162,7 +159,6 @@ public function isSrvRecord(): bool { return checkdnsrr($this->host, 'SRV'); } - /** * Check if the host contains a 'NAPTR' record in the DNS. @@ -176,7 +172,6 @@ public function isNaptrRecord(): bool { return checkdnsrr($this->host, 'NAPTR'); } - /** * Check if the host contains an 'A6' record in the DNS. @@ -190,7 +185,6 @@ public function isA6Record(): bool { return checkdnsrr($this->host, 'A6'); } - /** * Check if the host contains any valid DNS record of any type. @@ -204,7 +198,6 @@ public function isAnyRecord(): bool { return checkdnsrr($this->host, 'ANY'); } - /** * Check if the host contains all DNS records. @@ -219,7 +212,6 @@ public function isAllRecord(): bool { return checkdnsrr($this->host, 'ALL'); } - /** * Check if the host contains a 'CAA' record in the DNS. @@ -233,7 +225,6 @@ public function isCaaRecord(): bool { return checkdnsrr($this->host, 'CAA'); } - /** * Check if the host contains a 'PTR' record in the DNS. @@ -248,7 +239,6 @@ public function isPtrRecord(): bool return checkdnsrr($this->host, 'PTR'); } - /** * Check if the host contains an 'HINFO' record in the DNS. * diff --git a/src/Inp.php b/src/Inp.php index b5b05fc..d9fbf50 100755 --- a/src/Inp.php +++ b/src/Inp.php @@ -3,12 +3,15 @@ * @Package: MaplePHP - Input validation library * @Author: Daniel Ronkainen * @Licence: Apache-2.0 license, Copyright © Daniel Ronkainen - Don't delete this comment, its part of the license. + Don't delete this comment, it's part of the license. */ namespace MaplePHP\Validate; +use BadMethodCallException; +use DOMDocument; use ErrorException; +use Exception; use MaplePHP\DTO\Traverse; use MaplePHP\Validate\Interfaces\InpInterface; use MaplePHP\Validate\Traits\InpAliases; @@ -47,6 +50,7 @@ class Inp implements InpInterface /** * Start instance + * * @param mixed $value the input value * @throws ErrorException */ @@ -58,7 +62,8 @@ public function __construct(mixed $value) } /** - * Used to reset length in traverse with "mutable" flag + * Used to reset length in traverse with the "mutable" flag + * * @return void * @throws ErrorException */ @@ -72,6 +77,7 @@ private function init(): void /** * Immutable: Validate against new value + * * @param mixed $value * @return InpInterface */ @@ -84,6 +90,7 @@ public function withValue(mixed $value): InpInterface /** * Start instance + * * @param string $value the input value * @return self * @throws ErrorException @@ -94,7 +101,7 @@ public static function value(mixed $value): self } /** - * Makes it possible to travers to a value in array or object + * Makes it possible to traverse to a value in array or object * * @param string $key * @param bool $immutable @@ -119,6 +126,7 @@ public function eq(string $key, bool $immutable = true): self * This will make it possible to validate arrays and object with one line * * @example validateInData(user.name, 'length', [1, 200]); + * * @param string $key * @param string $validate * @param array $args @@ -129,13 +137,14 @@ public function validateInData(string $key, string $validate, array $args = []): { $inp = $this->eq($key, false); if(!method_exists($inp, $validate)) { - throw new \BadMethodCallException("Method '{$validate}' does not exist in " . __CLASS__ . " class."); + throw new BadMethodCallException("Method '$validate' does not exist in " . __CLASS__ . " class."); } return $inp->{$validate}(...$args); } /** * Get value string length + * * @param string $value * @return int * @throws ErrorException @@ -159,6 +168,7 @@ public function getValue(): mixed /** * Access luhn validation class + * * @return Luhn */ public function luhn(): Luhn @@ -177,13 +187,19 @@ public function luhn(): Luhn public function dns(): DNS { if(is_null($this->dns)) { - $this->dns = new DNS($this->value); + $value = $this->value; + $position = Traverse::value($this->value)->strPosition("@")->toInt(); + if($position > 0) { + $value = Traverse::value($this->value)->strSubstr($position + 1)->toString(); + } + $this->dns = new DNS($value); } return $this->dns; } /** - * Will check if value if empty (e.g. "", 0, NULL) = false + * Will check if the value is empty (e.g. "", 0, NULL) = false + * * @return bool */ public function isRequired(): bool @@ -235,7 +251,7 @@ public function isFalsy(): bool } /** - * Strict data type validation check if value exists in given array + * Strict data type validation check if a value exists in a given array * * @param array $haystack * @return bool @@ -246,7 +262,7 @@ public function isInArray(array $haystack): bool } /** - * Flexible data type validation check if value exists in given array + * Flexible data type validation check if a value exists in a given array * * @param array $haystack * @return bool @@ -257,18 +273,19 @@ public function isLooselyInArray(array $haystack): bool } /** - * Strict data type validation check if key exists in array + * Strict data type validation check if the key exists in an array * - * @param string|int $key + * @param string|int|float $key * @return bool */ - public function keyExists(string|int $key): bool + public function keyExists(string|int|float $key): bool { return is_array($this->value) && array_key_exists($key, $this->value); } /** * Will only check if there is a value + * * @return bool */ public function hasValue(): bool @@ -278,6 +295,7 @@ public function hasValue(): bool /** * Validate Swedish personal numbers (personalNumber) + * * @return bool */ public function isSocialNumber(): bool @@ -287,6 +305,7 @@ public function isSocialNumber(): bool /** * Validate Swedish org numbers + * * @return bool */ public function isOrgNumber(): bool @@ -296,6 +315,7 @@ public function isOrgNumber(): bool /** * Validate credit card numbers (THIS needs to be tested) + * * @return bool */ public function isCreditCard(): bool @@ -305,6 +325,7 @@ public function isCreditCard(): bool /** * Validate Swedish vat number + * * @return bool */ public function isVatNumber(): bool @@ -314,7 +335,7 @@ public function isVatNumber(): bool /** * Validate email - * Loosely check if is email. By loosely I mean it will not check if valid DNS. You can check this + * Loosely checks if is email. By loosely I mean it will not check if valid DNS. You can check this * manually with the method @dns but in most cases this will not be necessary. * * @return bool @@ -374,7 +395,7 @@ public function endsWith(string $needle): bool * Find in string * * @param string $match keyword to match against - * @param int|null $pos match start position if you want + * @param int|null $pos match the start position if you want * @return bool */ public function findInString(string $match, ?int $pos = null): bool @@ -384,7 +405,8 @@ public function findInString(string $match, ?int $pos = null): bool } /** - * Check if is a phone number + * Checks if is a phone number + * * @return bool */ public function isPhone(): bool @@ -417,8 +439,9 @@ public function isZip(int $minLength, ?int $maxLength = null): bool } /** - * Is value float + * If value float * Will validate whether a string is a valid float (User input is always a string) + * * @return bool */ public function isFloat(): bool @@ -427,8 +450,9 @@ public function isFloat(): bool } /** - * Is value int + * Is the value an int value * Will validate whether a string is a valid integer (User input is always a string) + * * @return bool */ public function isInt(): bool @@ -438,6 +462,7 @@ public function isInt(): bool /** * Is value string + * * @return bool */ public function isString(): bool @@ -447,6 +472,7 @@ public function isString(): bool /** * Is value array + * * @return bool */ public function isArray(): bool @@ -456,6 +482,7 @@ public function isArray(): bool /** * Is value object + * * @return bool */ public function isObject(): bool @@ -465,6 +492,7 @@ public function isObject(): bool /** * Is value bool + * * @return bool */ public function isBool(): bool @@ -473,7 +501,8 @@ public function isBool(): bool } /** - * Is resource + * Is the value a resource value? + * * @return bool */ public function isResource(): bool @@ -483,6 +512,7 @@ public function isResource(): bool /** * Is a valid json string + * * @return bool */ public function isJson(): bool @@ -492,13 +522,14 @@ public function isJson(): bool } /** - * Validate a string as html, check that it contains doctype, html, head and body + * Validate a string as HTML, check that it contains doctype, HTML, head, and body + * * @return bool */ public function isFullHtml(): bool { libxml_use_internal_errors(true); - $dom = new \DOMDocument(); + $dom = new DOMDocument(); if (!is_string($this->value) || !$dom->loadHTML($this->value, LIBXML_NOERROR | LIBXML_NOWARNING)) { return false; // Invalid HTML syntax } @@ -513,7 +544,8 @@ public function isFullHtml(): bool /** * Check if the value itself can be Interpreted as a bool value - * E.g. If value === ([on, off], [yes, no], [1, 0] or [true, false]) + * E.g., If value === ([on, off], [yes, no], [1, 0] or [true, false]) + * * @return bool */ public function isBoolVal(): bool @@ -526,6 +558,7 @@ public function isBoolVal(): bool /** * Is null + * * @return bool */ public function isNull(): bool @@ -535,6 +568,7 @@ public function isNull(): bool /** * Is directory + * * @return bool */ public function isDir(): bool @@ -544,6 +578,7 @@ public function isDir(): bool /** * Is file + * * @return bool */ public function isFile(): bool @@ -553,6 +588,7 @@ public function isFile(): bool /** * Check if is file or directory + * * @return bool */ function isFileOrDirectory(): bool @@ -562,6 +598,7 @@ function isFileOrDirectory(): bool /** * Is writable + * * @return bool */ public function isWritable(): bool @@ -571,6 +608,7 @@ public function isWritable(): bool /** * Is readable + * * @return bool */ public function isReadable(): bool @@ -589,7 +627,7 @@ public function isNumber(): bool } /** - * Value is loosely numeric (e.g. numeric strings, scientific notation). + * Value is loosely numeric (e.g., numeric strings, scientific notation). * * @return bool */ @@ -609,7 +647,7 @@ public function isPositive(): bool } /** - * Value is number negative -20 + * Value is the number negative -20 * * @return bool */ @@ -641,7 +679,7 @@ public function max(float $int): bool } /** - * Check if string length is more than start ($min), or between ($min) and ($max) + * Check if the string length is more than start ($min), or between ($min) and ($max) * * @param int $min start length * @param int|null $max end length @@ -656,7 +694,7 @@ public function length(int $min, ?int $max = null): bool } /** - * Check if array is empty + * Check if an array is empty * * @return bool */ @@ -666,7 +704,7 @@ public function isArrayEmpty(): bool } /** - * Check if all items in array is truthy + * Check if all items in an array are truthy * * @param string|int|float $key * @return bool @@ -683,7 +721,7 @@ public function itemsAreTruthy(string|int|float $key): bool } /** - * Check if truthy item exist in array + * Check if a truthy item exists in an array * * @param string|int|float $key * @return bool @@ -734,6 +772,7 @@ public function isCountLessThan(int $length): bool /** * Check int value is equal to int value + * * @param int $value * @return bool */ @@ -744,6 +783,7 @@ public function toIntEqual(int $value): bool /** * Value string length is equal to ($length) + * * @param int $length * @return bool */ @@ -756,7 +796,7 @@ public function isLengthEqualTo(int $length): bool } /** - * Strict data type validation check if equals to expected value + * Strict data type validation check if equals to the expected value * * @param mixed $expected * @return bool @@ -767,7 +807,7 @@ public function isEqualTo(mixed $expected): bool } /** - * Flexible data type validation check if loosely equals to expected value + * Flexible data type validation check if loosely equals to the expected value * * @param mixed $expected * @return bool @@ -802,6 +842,7 @@ public function isLooselyNotEqualTo(mixed $value): bool /** * IF value is less than to parameter + * * @param float|int $num * @return bool */ @@ -812,6 +853,7 @@ public function isLessThan(float|int $num): bool /** * IF value is more than to parameter + * * @param float|int $num * @return bool */ @@ -822,6 +864,7 @@ public function isMoreThan(float|int $num): bool /** * Check is a valid version number + * * @param bool $strict (validate as a semantic Versioning, e.g. 1.0.0) * @return bool */ @@ -833,7 +876,8 @@ public function isValidVersion(bool $strict = false): bool } /** - * Validate/compare if a version is equal/more/equalMore/less... e.g than withVersion + * Validate/compare if a version is equal/more/equalMore/less... e.g., than withVersion + * * @param string $withVersion * @param string $operator '!='|'<'|'<='|'<>'|'='|'=='|'>'|'>='|'eq'|'ge'|'gt'|'le'|'lt'|'ne' * @return bool @@ -850,6 +894,7 @@ public function versionCompare(string $withVersion, string $operator = "=="): bo * Lossy password - Will return false if a character inputted is not allowed * [a-zA-Z\d$@$!%*?&] - Matches "any" letter (uppercase or lowercase), digit, or special character * from the allowed set of special characters + * * @param integer $length Minimum length * @return bool */ @@ -867,6 +912,7 @@ public function isLossyPassword(int $length = 1): bool * [A-Za-z\d$@$!%*?&]{1,} - matches 1 or more characters consisting of letters, digits, * and the allowed special characters * I do tho recommend that you validate the length with @length(8, 60) method! + * * @param integer $length Minimum length * @return bool */ @@ -888,7 +934,7 @@ public function isMatchingPattern(string $charRange): bool } /** - * Check if the value contains only alphabetic characters (a–z or A–Z). + * Check if the value contains only alphabetic characters (a - z or A–Z). * * @return bool */ @@ -898,7 +944,7 @@ public function isAlpha(): bool } /** - * Check if the value contains only lowercase letters (a–z). + * Check if the value contains only lowercase letters (a - z). * * @return bool */ @@ -908,7 +954,7 @@ public function isLowerAlpha(): bool } /** - * Check if the value contains only uppercase letters (A–Z). + * Check if the value contains only uppercase letters (A - Z). * * @return bool */ @@ -918,7 +964,8 @@ public function isUpperAlpha(): bool } /** - * Is Hex color code string + * Is Hex color code string? + * * @return bool */ public function isHex(): bool @@ -928,6 +975,7 @@ public function isHex(): bool /** * Check if is a date + * * @param string $format validate after this date format (default Y-m-d) * @return bool */ @@ -938,6 +986,7 @@ public function isDate(string $format = "Y-m-d"): bool /** * Check if is a date and time + * * @return bool */ public function isDateWithTime(): bool @@ -947,6 +996,7 @@ public function isDateWithTime(): bool /** * Check if is a date and time + * * @param bool $withSeconds * @return bool */ @@ -956,10 +1006,11 @@ public function isTime(bool $withSeconds = false): bool } /** - * Check "minimum" age (value format should be validated date "Y-m-d") + * Check "minimum" age (a value format should be validated date "Y-m-d") + * * @param int $checkAge * @return bool - * @throws \DateMalformedStringException + * @throws Exception */ public function isAge(int $checkAge): bool { @@ -972,6 +1023,7 @@ public function isAge(int $checkAge): bool /** * Check if is valid domain + * * @param bool $strict stricter = true * @return bool */ @@ -983,6 +1035,7 @@ public function isDomain(bool $strict = true): bool /** * Check if is valid URL (http|https is required) + * * @return bool */ public function isUrl(): bool @@ -991,7 +1044,8 @@ public function isUrl(): bool } /** - * Check if "Host|domain" has an valid DNS (will check A, AAAA and MX) + * Check if "Host|domain" has a valid DNS (will check A, AAAA, and MX) + * * @psalm-suppress UndefinedConstant * @noinspection PhpComposerExtensionStubsInspection * @return bool @@ -1002,7 +1056,7 @@ public function isResolvableHost(): bool } /** - * Strict data type validation check if value is a valid HTTP status code + * Strict data type validation check if the value is a valid HTTP status code * * @return bool */ @@ -1021,7 +1075,7 @@ public function isHttpStatusCode(): bool } /** - * Strict data type validation check if value is HTTP 200 OK + * Strict data type validation check if the value is HTTP 200 OK * * @return bool */ @@ -1031,7 +1085,7 @@ public function isHttp200(): bool } /** - * Strict data type validation check if value is a 2xx success HTTP code + * Strict data type validation check if the value is a 2xx success HTTP code * * @return bool */ @@ -1043,7 +1097,7 @@ public function isHttpSuccess(): bool /** - * Strict data type validation check if value is a 4xx client error HTTP code + * Strict data type validation check if the value is a 4xx client error HTTP code * * @return bool */ @@ -1054,7 +1108,7 @@ public function isHttpClientError(): bool } /** - * Strict data type validation check if value is a 5xx server error HTTP code + * Strict data type validation check if the value is a 5xx server error HTTP code * * @return bool */ @@ -1067,6 +1121,7 @@ public function isHttpServerError(): bool /** * Validate multiple. Will return true if "one" matches + * * @param array $arr * @return bool * @throws ErrorException @@ -1085,6 +1140,7 @@ public function oneOf(array $arr): bool /** * Validate multiple. Will return true if "all" matches + * * @param array $arr * @return bool * @throws ErrorException @@ -1102,8 +1158,9 @@ public function allOf(array $arr): bool /** * Check if is a date and a "valid range" + * * @param string $format validate after this date format (default Y-m-d H:i) - * @return array|false E.g. array(T1, T2); T1 = start and T2 = end + * @return array|false E.g., array(T1, T2); T1 = start, and T2 = end */ public function dateRange(string $format = "Y-m-d H:i"): array|false { diff --git a/tests/unitary-validate-inp.php b/tests/unitary-validate-inp.php index d3db92a..c33e1a6 100755 --- a/tests/unitary-validate-inp.php +++ b/tests/unitary-validate-inp.php @@ -153,7 +153,16 @@ "equal" => [true], ], "Expect url to be true"); - $this->add(Inp::value("examplethatwillfail.se")->dns(), [ + + $this->add(Inp::value("daniel@creativearmy.se")->isDeliverableEmail(), [ + "equal" => [true], + ], "wdwq required to be true"); + + $this->add(Inp::value("daniel@creativearmy.se")->dns()->isMxRecord(), [ + "equal" => [true], + ], "wdwq required to be true"); + + $this->add(Inp::value("examplethatwillfail.se")->dns()->isAddressRecord(), [ "equal" => [false], ], "Expect dns to be false"); @@ -187,4 +196,5 @@ "equal" => [true], ], "Expect required to be true"); + }); From b4a8ef3c4d09cdd686e89e5e8d24e15c5c7740c7 Mon Sep 17 00:00:00 2001 From: Daniel Ronkainen Date: Mon, 28 Apr 2025 22:17:18 +0200 Subject: [PATCH 13/21] Improve naming and structure --- README.md | 170 +-- src/Inp.php | 1174 +--------------- src/{ValidatePool.php => ValidationChain.php} | 46 +- src/Validator.php | 1180 +++++++++++++++++ src/{ => Validators}/DNS.php | 10 +- src/{ => Validators}/Luhn.php | 4 +- .../Vat.php} | 6 +- tests/unitary-validate-inp.php | 88 +- 8 files changed, 1348 insertions(+), 1330 deletions(-) rename src/{ValidatePool.php => ValidationChain.php} (93%) create mode 100755 src/Validator.php rename src/{ => Validators}/DNS.php (96%) rename src/{ => Validators}/Luhn.php (98%) rename src/{ValidVatFormat.php => Validators/Vat.php} (94%) diff --git a/README.md b/README.md index 9be2ca3..87b68ce 100755 --- a/README.md +++ b/README.md @@ -16,17 +16,17 @@ composer require maplephp/validate ## Getting Started -You can validate values by instantiating the `Inp` class. There are two ways to do this: +You can validate values by instantiating the `Validator` class. There are two ways to do this: ```php -use MaplePHP\Validate\Inp; +use MaplePHP\Validate\Validator; // Option 1: Create an instance -$inp = new Inp("Lorem ipsum dolor"); +$inp = new Validator("Lorem ipsum dolor"); var_dump($inp->length(1, 200)); // true // Option 2: Use the static method for cleaner syntax -$valid = Inp::value("Lorem ipsum dolor")->length(1, 200); +$valid = Validator::value("Lorem ipsum dolor")->length(1, 200); var_dump($valid); // true ``` @@ -37,7 +37,7 @@ var_dump($valid); // true You can traverse nested arrays or objects and validate specific values using dot notation: ```php -$inp = new Inp([ +$inp = new Validator([ "user" => [ "name" => "John Doe", "email" => "john.doe@gmail.com", @@ -58,12 +58,12 @@ $valid = $inp->validateInData("user.name", "length", [1, 200]); ## Using the Validation Pool -The `ValidatePool` class allows you to chain multiple validations on a single value and check the overall result: +The `ValidationChain` class allows you to chain multiple validations on a single value and check the overall result: ```php -use MaplePHP\Validate\ValidatePool; +use MaplePHP\Validate\ValidationChain; -$validPool = new ValidatePool("john.doe@gmail.com"); +$validPool = new ValidationChain("john.doe@gmail.com"); $validPool->isEmail() ->length(1, 200) @@ -75,351 +75,351 @@ $isValid = $validPool->isValid(); var_dump($isValid); // true ``` -> 🧠 `ValidatePool` is useful when you want to collect and evaluate multiple validation rules at once. +> 🧠 `ValidationChain` is useful when you want to collect and evaluate multiple validation rules at once. ## Validations ### Required field ```php -Inp::value("Lorem ipsum dolor")->isRequired(); +Validator::value("Lorem ipsum dolor")->isRequired(); ``` ### Check if there is any value (even if it's 0) ```php -Inp::value(0)->hasValue(); +Validator::value(0)->hasValue(); ``` ### Check string length (min, max) - **Min only**: ```php -Inp::value("Lorem ipsum dolor")->length(1); +Validator::value("Lorem ipsum dolor")->length(1); ``` - **Min and Max**: ```php -Inp::value("Lorem ipsum dolor")->length(1, 160); +Validator::value("Lorem ipsum dolor")->length(1, 160); ``` ### Check if string has an exact length ```php -Inp::value("Lorem ipsum dolor")->isLengthEqualTo(10); +Validator::value("Lorem ipsum dolor")->isLengthEqualTo(10); ``` ### Check if value equals exactly to or not equals another value - **Equals**: Strict data type validation check if equals to expected value ```php -Inp::value("Lorem ipsum dolor")->isEqualTo("Lorem ipsum dolor"); +Validator::value("Lorem ipsum dolor")->isEqualTo("Lorem ipsum dolor"); ``` - **Loosely Equals**: Flexible data type validation check if loosely equals to expected value ```php -Inp::value("Lorem ipsum dolor")->isLooselyEqualTo("Lorem ipsum dolor"); +Validator::value("Lorem ipsum dolor")->isLooselyEqualTo("Lorem ipsum dolor"); ``` - **Not equals**: Strict data type validation check if not equals to expected value ```php -Inp::value("Lorem ipsum dolor")->isNotEqualTo("Lorem ipsum"); +Validator::value("Lorem ipsum dolor")->isNotEqualTo("Lorem ipsum"); ``` - **Loosely Not equals**: Flexible data type validation check if loosely not equals to expected value ```php -Inp::value("Lorem ipsum dolor")->isLooselyNotEqualTo("Lorem ipsum"); +Validator::value("Lorem ipsum dolor")->isLooselyNotEqualTo("Lorem ipsum"); ``` - **More than**: ```php -Inp::value(200)->isMoreThan(100); +Validator::value(200)->isMoreThan(100); ``` - **Less than**: ```php -Inp::value(100)->isLessThan(200); +Validator::value(100)->isLessThan(200); ``` - **Contains**: ```php -Inp::value("Lorem ipsum dolor")->contains("ipsum"); +Validator::value("Lorem ipsum dolor")->contains("ipsum"); ``` - **Starts with**: ```php -Inp::value("Lorem ipsum dolor")->startsWith("Lorem"); +Validator::value("Lorem ipsum dolor")->startsWith("Lorem"); ``` - **Ends with**: ```php -Inp::value("Lorem ipsum dolor")->endsWith("dolor"); +Validator::value("Lorem ipsum dolor")->endsWith("dolor"); ``` ### Validate if it's a valid email ```php -Inp::value("john@gmail.com")->isEmail(); +Validator::value("john@gmail.com")->isEmail(); ``` ### Validate if it's a valid phone number Allows numbers and special characters ("-", "+", " "). ```php -Inp::value("+46709676040")->isPhone(); +Validator::value("+46709676040")->isPhone(); ``` ### Validate Swedish personal number (personnel) ```php -Inp::value("198808213412")->isSocialNumber(); +Validator::value("198808213412")->isSocialNumber(); ``` ### Validate Swedish organization number ```php -Inp::value("197511043412")->isOrgNumber(); +Validator::value("197511043412")->isOrgNumber(); ``` ### Validate credit card number ```php -Inp::value("1616523623422334")->isCreditCard(); +Validator::value("1616523623422334")->isCreditCard(); ``` ### Validate VAT number ```php -Inp::value("SE8272267913")->isVatNumber(); +Validator::value("SE8272267913")->isVatNumber(); ``` ### Check if value is a valid float ```php -Inp::value(3.1415)->isFloat(); +Validator::value(3.1415)->isFloat(); ``` ### Check if value is a valid integer ```php -Inp::value(42)->isInt(); +Validator::value(42)->isInt(); ``` ### Check if value is a valid number (numeric) ```php -Inp::value(42)->isNumber(); +Validator::value(42)->isNumber(); ``` ### Check if value is positive or negative - **Positive**: ```php -Inp::value(20)->isPositive(); +Validator::value(20)->isPositive(); ``` - **Negative**: ```php -Inp::value(-20)->isNegative(); +Validator::value(-20)->isNegative(); ``` ### Check if value is a valid version number ```php // True === validate as a semantic Versioning, e.g. 1.0.0 -Inp::value("1.0.0")->isValidVersion(true); +Validator::value("1.0.0")->isValidVersion(true); ``` ### Compare version with another version ```php -Inp::value("1.0.0")->versionCompare("2.0.0", '>='); +Validator::value("1.0.0")->versionCompare("2.0.0", '>='); ``` ### Validate password (lossy or strict) - **Lossy password (minimum character set)**: ```php -Inp::value("password123")->isLossyPassword(8); +Validator::value("password123")->isLossyPassword(8); ``` - **Strict password** (requires at least one lowercase, uppercase, digit, and special character): ```php -Inp::value("Password#123!")->isStrictPassword(8); +Validator::value("Password#123!")->isStrictPassword(8); ``` ### Validate if value is string and contains only A-Z - **Both cases**: ```php -Inp::value("HelloWorld")->atoZ(); +Validator::value("HelloWorld")->atoZ(); ``` - **Lowercase only**: ```php -Inp::value("helloworld")->lowerAtoZ(); +Validator::value("helloworld")->lowerAtoZ(); ``` - **Uppercase only**: ```php -Inp::value("HELLOWORLD")->upperAtoZ(); +Validator::value("HELLOWORLD")->upperAtoZ(); ``` ### Check if it's a valid hex color code ```php -Inp::value("#000000")->hex(); +Validator::value("#000000")->hex(); ``` ### Check if it's a valid date As default you can validate against a date format like this "Y-m-d" ```php -Inp::value("2022-02-13")->isDate(); +Validator::value("2022-02-13")->isDate(); ``` Custom date validation ```php -Inp::value("2022/02/13 14:15")->isDate(Y/m/d H:i); +Validator::value("2022/02/13 14:15")->isDate(Y/m/d H:i); ``` ### Check if it's a valid date and time ```php -Inp::value("2022-02-13 14:15:58")->isDateWithTime(); +Validator::value("2022-02-13 14:15:58")->isDateWithTime(); ``` ### Check if it's a valid time Validate hour and minutes ```php -Inp::value("14:15")->isTime(); +Validator::value("14:15")->isTime(); ``` Validate hour, minutes and seconds ```php -Inp::value("14:15:58")->isTime(true); +Validator::value("14:15:58")->isTime(true); ``` ### Check if someone is at least a certain age ```php -Inp::value("1988-05-22")->isAge(18); +Validator::value("1988-05-22")->isAge(18); ``` ### Check if it's a valid domain name ```php -Inp::value("example.com")->isDomain(); +Validator::value("example.com")->isDomain(); ``` ### Check if it's a valid URL (http/https is required) ```php -Inp::value("https://example.com/page")->isUrl(); +Validator::value("https://example.com/page")->isUrl(); ``` ### Check if it's a valid DNS entry ```php -Inp::value("example.com")->isDns(); +Validator::value("example.com")->isDns(); ``` ### Validate file and directory properties - **Check if it's a valid file**: ```php -Inp::value("/path/to/file.txt")->isFile(); +Validator::value("/path/to/file.txt")->isFile(); ``` - **Check if it's a directory**: ```php -Inp::value("/path/to/directory")->isDir(); +Validator::value("/path/to/directory")->isDir(); ``` - **Check if it's writable**: ```php -Inp::value("/path/to/file.txt")->isWritable(); +Validator::value("/path/to/file.txt")->isWritable(); ``` - **Check if it's readable**: ```php -Inp::value("/path/to/file.txt")->isReadable(); +Validator::value("/path/to/file.txt")->isReadable(); ``` ### Validate ZIP code (with custom length) ```php -Inp::value("12345")->isZip(5); +Validator::value("12345")->isZip(5); ``` ### Validate if value matches a pattern (regex) ```php -Inp::value("abc")->pregMatch("a-zA-Z"); +Validator::value("abc")->pregMatch("a-zA-Z"); ``` ## Validate Arrays ### Check if is an array ```php -Inp::value(["Apple", "Orange", "Lemon"])->isArray(); +Validator::value(["Apple", "Orange", "Lemon"])->isArray(); ``` ### Check if array is empty ```php -Inp::value(["Apple", "Orange", "Lemon"])->isArrayEmpty(); +Validator::value(["Apple", "Orange", "Lemon"])->isArrayEmpty(); ``` ### Strict data type validation check if value exists in given array ```php -Inp::value(["Apple", "Orange", "Lemon"])->isInArray(); +Validator::value(["Apple", "Orange", "Lemon"])->isInArray(); ``` ### Flexible data type validation check if value exists in given array ```php -Inp::value(["Apple", "Orange", "Lemon"])->isLooselyInArray(); +Validator::value(["Apple", "Orange", "Lemon"])->isLooselyInArray(); ``` ### Strict data type validation check if key exists in array ```php -Inp::value(["Apple", "Orange", "Lemon"])->keyExists(); +Validator::value(["Apple", "Orange", "Lemon"])->keyExists(); ``` ### Check if all items in array is truthy ```php -Inp::value(["1", true, "Lemon"])->itemsAreTruthy(); +Validator::value(["1", true, "Lemon"])->itemsAreTruthy(); ``` ### Check if truthy item exist in array ```php -Inp::value(["1", false, "Lemon"])->hasTruthyItem(); +Validator::value(["1", false, "Lemon"])->hasTruthyItem(); ``` ### Check if array count is equal to length ```php -Inp::value(["Apple", "Orange", "Lemon"])->isCountEqualTo(3); +Validator::value(["Apple", "Orange", "Lemon"])->isCountEqualTo(3); ``` ### Check if array count is more than the length ```php -Inp::value(["Apple", "Orange", "Lemon"])->isCountMoreThan(1); +Validator::value(["Apple", "Orange", "Lemon"])->isCountMoreThan(1); ``` ### Check if array count is less than the length ```php -Inp::value(["Apple", "Orange", "Lemon"])->isCountLessThan(4); +Validator::value(["Apple", "Orange", "Lemon"])->isCountLessThan(4); ``` ### Check if value is a valid float ```php -Inp::value("Lorem ipsum dolor")->isString(); +Validator::value("Lorem ipsum dolor")->isString(); ``` ## Validate types ### Check if value is a valid float ```php -Inp::value("Lorem ipsum dolor")->isString(); +Validator::value("Lorem ipsum dolor")->isString(); ``` ### Check if value is a valid float ```php -Inp::value(3.1415)->isFloat(); +Validator::value(3.1415)->isFloat(); ``` ### Check if value is a valid integer ```php -Inp::value(42)->isInt(); +Validator::value(42)->isInt(); ``` - **Is Boolean**: ```php -Inp::value(true)->isBool(); +Validator::value(true)->isBool(); ``` - **Is Boolean-like value** (e.g., "yes", "no", "1", "0"): ```php -Inp::value("yes")->isBoolVal(); +Validator::value("yes")->isBoolVal(); ``` - **Array**: ```php -Inp::value([1, 2, 3])->isArray(); +Validator::value([1, 2, 3])->isArray(); ``` - **Object**: ```php -Inp::value($obj)->isObject(); +Validator::value($obj)->isObject(); ``` - **Resource**: ```php -Inp::value($resource)->isResource(); +Validator::value($resource)->isResource(); ``` - **Json**: ```php -Inp::value($jsonStr)->isJson(); +Validator::value($jsonStr)->isJson(); ``` - **HTML Document**: ```php -Inp::value($jsonStr)->isFullHtml(); +Validator::value($jsonStr)->isFullHtml(); ``` @@ -427,36 +427,36 @@ Inp::value($jsonStr)->isFullHtml(); #### Strict data type validation check if value is a valid HTTP status code ```php -Inp::value(403)->isHttpStatusCode(); +Validator::value(403)->isHttpStatusCode(); ``` #### Strict data type validation check if value is HTTP 200 OK ```php -Inp::value(200)->isHttp200(); +Validator::value(200)->isHttp200(); ``` #### Strict data type validation check if value is a 2xx success HTTP code ```php -Inp::value(210)->isHttpSuccess(); +Validator::value(210)->isHttpSuccess(); ``` #### Strict data type validation check if value is a 4xx client error HTTP code ```php -Inp::value(403)->isHttpClientError(); +Validator::value(403)->isHttpClientError(); ``` #### Strict data type validation check if value is a 5xx server error HTTP code ```php -Inp::value(500)->isHttpServerError(); +Validator::value(500)->isHttpServerError(); ``` ### Validate using multiple methods (one or all must match) - **Validate if one method passes**: ```php -Inp::value("12345")->oneOf(['isInt' => []]); +Validator::value("12345")->oneOf(['isInt' => []]); ``` - **Validate if all methods pass**: ```php -Inp::value("12345")->allOf(['isInt' => [], 'length' => [5]]); +Validator::value("12345")->allOf(['isInt' => [], 'length' => [5]]); ``` \ No newline at end of file diff --git a/src/Inp.php b/src/Inp.php index d9fbf50..23037c8 100755 --- a/src/Inp.php +++ b/src/Inp.php @@ -1,1178 +1,14 @@ ', - '=', - '==', - '>', - '>=', - 'eq', - 'ge', - 'gt', - 'le', - 'lt', - 'ne' - ]; - - private mixed $value; - private int $length = 0; - private DateTime $dateTime; - private ?Luhn $luhn = null; - private ?DNS $dns = null; - private ?Str $getStr = null; - - - /** - * Start instance - * - * @param mixed $value the input value - * @throws ErrorException - */ - public function __construct(mixed $value) - { - $this->value = $value; - $this->dateTime = new DateTime("now"); - $this->init(); - } - - /** - * Used to reset length in traverse with the "mutable" flag - * - * @return void - * @throws ErrorException - */ - private function init(): void - { - if(is_string($this->value) || is_numeric($this->value)) { - $this->length = $this->getLength((string)$this->value); - $this->getStr = new Str($this->value); - } - } - - /** - * Immutable: Validate against new value - * - * @param mixed $value - * @return InpInterface - */ - public function withValue(mixed $value): InpInterface - { - $inst = clone $this; - $inst->value = $value; - return $inst; - } - - /** - * Start instance - * - * @param string $value the input value - * @return self - * @throws ErrorException - */ - public static function value(mixed $value): self - { - return new self($value); - } - - /** - * Makes it possible to traverse to a value in array or object - * - * @param string $key - * @param bool $immutable - * @return self - * @throws ErrorException - */ - public function eq(string $key, bool $immutable = true): self - { - $value = $this->value; - if(is_array($this->value) || is_object($this->value)) { - $value = Traverse::value($this->value)->eq($key)->get(); - if(!$immutable && $value !== false) { - $this->value = $value; - $this->init(); - return $this; - } - } - return self::value($value); - } - - /** - * This will make it possible to validate arrays and object with one line - * - * @example validateInData(user.name, 'length', [1, 200]); - * - * @param string $key - * @param string $validate - * @param array $args - * @return mixed - * @throws ErrorException - */ - public function validateInData(string $key, string $validate, array $args = []): bool - { - $inp = $this->eq($key, false); - if(!method_exists($inp, $validate)) { - throw new BadMethodCallException("Method '$validate' does not exist in " . __CLASS__ . " class."); - } - return $inp->{$validate}(...$args); - } - - /** - * Get value string length - * - * @param string $value - * @return int - * @throws ErrorException - */ - public function getLength(string $value): int - { - $mb = new MB($value); - return (int)$mb->strlen(); - } - - /** - * Get the current value - * _The value can be changes with travers method and this lets you peak at the new one_ - * - * @return mixed - */ - public function getValue(): mixed - { - return $this->value; - } - - /** - * Access luhn validation class - * - * @return Luhn - */ - public function luhn(): Luhn - { - if (is_null($this->luhn)) { - $this->luhn = new Luhn($this->value); - } - return $this->luhn; - } - - /** - * Access DNS validations - * - * @return DNS - */ - public function dns(): DNS - { - if(is_null($this->dns)) { - $value = $this->value; - $position = Traverse::value($this->value)->strPosition("@")->toInt(); - if($position > 0) { - $value = Traverse::value($this->value)->strSubstr($position + 1)->toString(); - } - $this->dns = new DNS($value); - } - return $this->dns; - } - - /** - * Will check if the value is empty (e.g. "", 0, NULL) = false - * - * @return bool - */ - public function isRequired(): bool - { - if ($this->length(1) && !empty($this->value)) { - return true; - } - return false; - } - - /** - * Strict data type validation check if is false - * - * @return bool - */ - public function isTrue(): bool - { - return $this->value === true; - } - - /** - * Flexible data type validation check if is truthy - * - * @return bool - */ - public function isTruthy(): bool - { - return filter_var($this->value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) === true; - } - - /** - * Strict data type validation check if is false - * - * @return bool - */ - public function isFalse(): bool - { - return $this->value === false; - } - - /** - * Flexible data type validation check if is falsy - * - * @return bool - */ - public function isFalsy(): bool - { - return filter_var($this->value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) === false; - } - - /** - * Strict data type validation check if a value exists in a given array - * - * @param array $haystack - * @return bool - */ - public function isInArray(array $haystack): bool - { - return in_array($this->value, $haystack, true); - } - - /** - * Flexible data type validation check if a value exists in a given array - * - * @param array $haystack - * @return bool - */ - public function isLooselyInArray(array $haystack): bool - { - return in_array($this->value, $haystack); - } - - /** - * Strict data type validation check if the key exists in an array - * - * @param string|int|float $key - * @return bool - */ - public function keyExists(string|int|float $key): bool - { - return is_array($this->value) && array_key_exists($key, $this->value); - } - - /** - * Will only check if there is a value - * - * @return bool - */ - public function hasValue(): bool - { - return $this->length(1); - } - - /** - * Validate Swedish personal numbers (personalNumber) - * - * @return bool - */ - public function isSocialNumber(): bool - { - return $this->luhn()->personnummer(); - } - - /** - * Validate Swedish org numbers - * - * @return bool - */ - public function isOrgNumber(): bool - { - return $this->luhn()->orgNumber(); - } - - /** - * Validate credit card numbers (THIS needs to be tested) - * - * @return bool - */ - public function isCreditCard(): bool - { - return $this->luhn()->creditCard(); - } - - /** - * Validate Swedish vat number - * - * @return bool - */ - public function isVatNumber(): bool - { - return $this->luhn()->vatNumber(); - } - - /** - * Validate email - * Loosely checks if is email. By loosely I mean it will not check if valid DNS. You can check this - * manually with the method @dns but in most cases this will not be necessary. - * - * @return bool - */ - public function isEmail(): bool - { - return (filter_var($this->value, FILTER_VALIDATE_EMAIL) !== false); - } - - - /** - * Validate if the email is deliverable - * This checks if the email is syntactically valid and has a valid MX record. - * - * @return bool - */ - public function isDeliverableEmail(): bool - { - return ($this->isEmail() && $this->dns()->isMxRecord()); - } - - - /** - * Checks if a string contains a given substring - * - * @param string $needle - * @return bool - */ - public function contains(string $needle): bool - { - return str_contains($this->value, $needle); - } - - /** - * Checks if a string starts with a given substring - * - * @param string $needle - * @return bool - */ - public function startsWith(string $needle): bool - { - return str_starts_with($this->value, $needle); - } - - /** - * Checks if a string ends with a given substring - * - * @param string $needle - * @return bool - */ - public function endsWith(string $needle): bool - { - return str_ends_with($this->value, $needle); - } - - /** - * Find in string - * - * @param string $match keyword to match against - * @param int|null $pos match the start position if you want - * @return bool - */ - public function findInString(string $match, ?int $pos = null): bool - { - return ((is_null($pos) && str_contains($this->value, $match)) || - (strpos($this->value, $match) === $pos)); - } - - /** - * Checks if is a phone number - * - * @return bool - */ - public function isPhone(): bool - { - if (is_null($this->getStr)) { - return false; - } - $val = (string)$this->getStr->replace([" ", "-", "—", "–", "(", ")"], ["", "", "", "", "", ""]); - $match = preg_match('/^[0-9]{7,14}+$/', $val); - $strict = preg_match('/^\+[0-9]{1,2}[0-9]{6,13}$/', $val); - return ($strict || $match); - } - - /** - * Check if is valid ZIP - * - * @param int $minLength start length - * @param int|null $maxLength end length - * @return bool - * @throws ErrorException - */ - public function isZip(int $minLength, ?int $maxLength = null): bool - { - if (is_null($this->getStr)) { - return false; - } - $this->value = (string)$this->getStr->replace([" ", "-", "—", "–"], ["", "", "", ""]); - $this->length = $this->getLength($this->value); - return ($this->isInt() && $this->length($minLength, $maxLength)); - } - - /** - * If value float - * Will validate whether a string is a valid float (User input is always a string) - * - * @return bool - */ - public function isFloat(): bool - { - return (filter_var($this->value, FILTER_VALIDATE_FLOAT) !== false); - } - - /** - * Is the value an int value - * Will validate whether a string is a valid integer (User input is always a string) - * - * @return bool - */ - public function isInt(): bool - { - return (filter_var($this->value, FILTER_VALIDATE_INT) !== false); - } - - /** - * Is value string - * - * @return bool - */ - public function isString(): bool - { - return is_string($this->value); - } - - /** - * Is value array - * - * @return bool - */ - public function isArray(): bool - { - return is_array($this->value); - } - - /** - * Is value object - * - * @return bool - */ - public function isObject(): bool - { - return is_object($this->value); - } - - /** - * Is value bool - * - * @return bool - */ - public function isBool(): bool - { - return (is_bool($this->value)); - } - - /** - * Is the value a resource value? - * - * @return bool - */ - public function isResource(): bool - { - return is_resource($this->value); - } - - /** - * Is a valid json string - * - * @return bool - */ - public function isJson(): bool - { - json_decode($this->value); - return json_last_error() === JSON_ERROR_NONE; - } - - /** - * Validate a string as HTML, check that it contains doctype, HTML, head, and body - * - * @return bool - */ - public function isFullHtml(): bool - { - libxml_use_internal_errors(true); - $dom = new DOMDocument(); - if (!is_string($this->value) || !$dom->loadHTML($this->value, LIBXML_NOERROR | LIBXML_NOWARNING)) { - return false; // Invalid HTML syntax - } - if (!$dom->doctype || strtolower($dom->doctype->name) !== "html") { - return false; - } - $htmlTag = $dom->getElementsByTagName("html")->length > 0; - $headTag = $dom->getElementsByTagName("head")->length > 0; - $bodyTag = $dom->getElementsByTagName("body")->length > 0; - return $htmlTag && $headTag && $bodyTag; - } - - /** - * Check if the value itself can be Interpreted as a bool value - * E.g., If value === ([on, off], [yes, no], [1, 0] or [true, false]) - * - * @return bool - */ - public function isBoolVal(): bool - { - $val = strtolower(trim((string)$this->value)); - $true = ($val === "on" || $val === "yes" || $val === "1" || $val === "true"); - $false = ($val === "off" || $val === "no" || $val === "0" || $val === "false"); - return ($true || $false); - } - - /** - * Is null - * - * @return bool - */ - public function isNull(): bool - { - return is_null($this->value); - } - - /** - * Is directory - * - * @return bool - */ - public function isDir(): bool - { - return is_dir($this->value); - } - - /** - * Is file - * - * @return bool - */ - public function isFile(): bool - { - return is_file($this->value); - } - - /** - * Check if is file or directory - * - * @return bool - */ - function isFileOrDirectory(): bool - { - return file_exists($this->value); - } - - /** - * Is writable - * - * @return bool - */ - public function isWritable(): bool - { - return is_writable($this->value); - } - - /** - * Is readable - * - * @return bool - */ - public function isReadable(): bool - { - return is_readable($this->value); - } - - /** - * Value is strictly a number (int or float). - * - * @return bool - */ - public function isNumber(): bool - { - return $this->isFloat() || $this->isInt(); - } - - /** - * Value is loosely numeric (e.g., numeric strings, scientific notation). - * - * @return bool - */ - public function isNumbery(): bool - { - return is_numeric($this->value); - } - - /** - * Value is number positive 20 - * - * @return bool - */ - public function isPositive(): bool - { - return ((float)$this->value >= 0); - } - - /** - * Value is the number negative -20 - * - * @return bool - */ - public function isNegative(): bool - { - return ((float)$this->value < 0); - } - - /** - * Value is minimum float|int value - * - * @param float $int - * @return bool - */ - public function min(float $int): bool - { - return ((float)$this->value >= $int); - } - - /** - * Value is maximum float|int value - * - * @param float $int - * @return bool - */ - public function max(float $int): bool - { - return ((float)$this->value <= $int); - } - - /** - * Check if the string length is more than start ($min), or between ($min) and ($max) - * - * @param int $min start length - * @param int|null $max end length - * @return bool - */ - public function length(int $min, ?int $max = null): bool - { - if ($this->length >= $min && (($max === null) || $this->length <= $max)) { - return true; - } - return false; - } - - /** - * Check if an array is empty - * - * @return bool - */ - public function isArrayEmpty(): bool - { - return ($this->isArray() && count($this->value) === 0); - } - - /** - * Check if all items in an array are truthy - * - * @param string|int|float $key - * @return bool - */ - public function itemsAreTruthy(string|int|float $key): bool - { - if($this->isArray()) { - $count = Arr::value($this->value) - ->filter(fn ($item) => $item->flatten()->{$key}->toBool()) - ->count(); - return ($count === count($this->value)); - } - return false; - } - - /** - * Check if a truthy item exists in an array - * - * @param string|int|float $key - * @return bool - */ - public function hasTruthyItem(string|int|float $key): bool - { - if($this->isArray()) { - $count = Arr::value($this->value) - ->filter(fn ($item) => $item->flatten()->{$key}->toBool()) - ->count(); - return ($count > 0); - } - return false; - } - - /** - * Validate array length equal to - * - * @param int $length - * @return bool - */ - public function isCountEqualTo(int $length): bool - { - return ($this->isArray() && count($this->value) === $length); - } - - /** - * Validate array length is more than - * - * @param int $length - * @return bool - */ - public function isCountMoreThan(int $length): bool - { - return ($this->isArray() && count($this->value) > $length); - } - - /** - * Validate array length is less than - * - * @param int $length - * @return bool - */ - public function isCountLessThan(int $length): bool - { - return ($this->isArray() && count($this->value) < $length); - } - - /** - * Check int value is equal to int value - * - * @param int $value - * @return bool - */ - public function toIntEqual(int $value): bool - { - return (int)$this->value === $value; - } - - /** - * Value string length is equal to ($length) - * - * @param int $length - * @return bool - */ - public function isLengthEqualTo(int $length): bool - { - if ($this->length === $length) { - return true; - } - return false; - } - - /** - * Strict data type validation check if equals to the expected value - * - * @param mixed $expected - * @return bool - */ - public function isEqualTo(mixed $expected): bool - { - return $this->value === $expected; - } - - /** - * Flexible data type validation check if loosely equals to the expected value - * - * @param mixed $expected - * @return bool - */ - public function isLooselyEqualTo(mixed $expected): bool - { - return $this->value == $expected; - } - - - /** - * Strict data type validation check if not equals to expected value - * - * @param mixed $value - * @return bool - */ - public function isNotEqualTo(mixed $value): bool - { - return ($this->value !== $value); - } - - /** - * Flexible data type validation check if loosely not equals to expected value - * - * @param mixed $value - * @return bool - */ - public function isLooselyNotEqualTo(mixed $value): bool - { - return ($this->value !== $value); - } - - /** - * IF value is less than to parameter - * - * @param float|int $num - * @return bool - */ - public function isLessThan(float|int $num): bool - { - return ($this->value < $num); - } - - /** - * IF value is more than to parameter - * - * @param float|int $num - * @return bool - */ - public function isMoreThan(float|int $num): bool - { - return ($this->value > $num); - } - - /** - * Check is a valid version number - * - * @param bool $strict (validate as a semantic Versioning, e.g. 1.0.0) - * @return bool - */ - public function isValidVersion(bool $strict = false): bool - { - $strictMatch = (!$strict || preg_match("/^(\d?\d)\.(\d?\d)\.(\d?\d)$/", (string)$this->value)); - $compare = version_compare((string)$this->value, '0.0.1', '>='); - return ($strictMatch && $compare !== false && $compare >= 0); - } - - /** - * Validate/compare if a version is equal/more/equalMore/less... e.g., than withVersion - * - * @param string $withVersion - * @param string $operator '!='|'<'|'<='|'<>'|'='|'=='|'>'|'>='|'eq'|'ge'|'gt'|'le'|'lt'|'ne' - * @return bool - */ - public function versionCompare(string $withVersion, string $operator = "=="): bool - { - if (in_array($operator, self::WHITELIST_OPERATORS)) { - return version_compare((string)$this->value, $withVersion, $operator); - } - return false; - } - - /** - * Lossy password - Will return false if a character inputted is not allowed - * [a-zA-Z\d$@$!%*?&] - Matches "any" letter (uppercase or lowercase), digit, or special character - * from the allowed set of special characters - * - * @param integer $length Minimum length - * @return bool - */ - public function isLossyPassword(int $length = 1): bool - { - return ((int)preg_match('/^[a-zA-Z\d$@$!%*?&]{' . $length . ',}$/', $this->value) > 0); - } - - /** - * Strict password - * (?=.*[a-z]) - at least one lowercase letter - * (?=.*[A-Z]) - at least one uppercase letter - * (?=.*\d) - at least one digit - * (?=.*[$@$!%*?&]) - at least one special character from the set: $, @, #, !, %, *, ?, & - * [A-Za-z\d$@$!%*?&]{1,} - matches 1 or more characters consisting of letters, digits, - * and the allowed special characters - * I do tho recommend that you validate the length with @length(8, 60) method! - * - * @param integer $length Minimum length - * @return bool - */ - public function isStrictPassword(int $length = 1): bool - { - $pattern = '/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&])[A-Za-z\d$@$!%*?&]{' . $length . ',}$/'; - return ((int)preg_match($pattern, $this->value) > 0); - } - - /** - * Check if the value contains only characters matching the given pattern. - * - * @param string $charRange A character range (e.g., 'a-z', 'A-Z0-9') - * @return bool - */ - public function isMatchingPattern(string $charRange): bool - { - return (bool)preg_match("/^[$charRange]+$/", $this->value); - } - - /** - * Check if the value contains only alphabetic characters (a - z or A–Z). - * - * @return bool - */ - public function isAlpha(): bool - { - return (bool)preg_match("/^[a-zA-Z]+$/", $this->value); - } - - /** - * Check if the value contains only lowercase letters (a - z). - * - * @return bool - */ - public function isLowerAlpha(): bool - { - return (bool)preg_match("/^[a-z]+$/", $this->value); - } - - /** - * Check if the value contains only uppercase letters (A - Z). - * - * @return bool - */ - public function isUpperAlpha(): bool - { - return (bool)preg_match("/^[A-Z]+$/", $this->value); - } - - /** - * Is Hex color code string? - * - * @return bool - */ - public function isHex(): bool - { - return ((int)preg_match('/^#([0-9A-F]{3}){1,2}$/i', $this->value) > 0); - } - - /** - * Check if is a date - * - * @param string $format validate after this date format (default Y-m-d) - * @return bool - */ - public function isDate(string $format = "Y-m-d"): bool - { - return (DateTime::createFromFormat($format, $this->value) !== false); - } - - /** - * Check if is a date and time - * - * @return bool - */ - public function isDateWithTime(): bool - { - return $this->date("Y-m-d H:i:s"); - } - - /** - * Check if is a date and time - * - * @param bool $withSeconds - * @return bool - */ - public function isTime(bool $withSeconds = false): bool - { - return $this->date("H:i" . ($withSeconds ? ":s" : "")); - } - - /** - * Check "minimum" age (a value format should be validated date "Y-m-d") - * - * @param int $checkAge - * @return bool - * @throws Exception - */ - public function isAge(int $checkAge): bool - { - $now = (int)$this->dateTime->format("Y"); - $dateTime = new DateTime($this->value); - $birth = (int)$dateTime->format("Y"); - $age = ($now - $birth); - return ($age >= $checkAge); - } - - /** - * Check if is valid domain - * - * @param bool $strict stricter = true - * @return bool - */ - public function isDomain(bool $strict = true): bool - { - $strict = ($strict) ? FILTER_FLAG_HOSTNAME : 0; - return (filter_var((string)$this->value, FILTER_VALIDATE_DOMAIN, $strict) !== false); - } - - /** - * Check if is valid URL (http|https is required) - * - * @return bool - */ - public function isUrl(): bool - { - return (filter_var($this->value, FILTER_VALIDATE_URL) !== false); - } - - /** - * Check if "Host|domain" has a valid DNS (will check A, AAAA, and MX) - * - * @psalm-suppress UndefinedConstant - * @noinspection PhpComposerExtensionStubsInspection - * @return bool - */ - public function isResolvableHost(): bool - { - return $this->dns()->isResolvableHost(); - } - - /** - * Strict data type validation check if the value is a valid HTTP status code - * - * @return bool - */ - public function isHttpStatusCode(): bool - { - $validCodes = [ - 100, 101, 102, 103, - 200, 201, 202, 203, 204, 205, 206, 207, 208, 226, - 300, 301, 302, 303, 304, 305, 307, 308, - 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, - 410, 411, 412, 413, 414, 415, 416, 417, 418, 421, - 422, 423, 424, 425, 426, 428, 429, 431, 451, - 500, 501, 502, 503, 504, 505, 506, 507, 508, 510, 511 - ]; - return in_array((int)$this->value, $validCodes, true); - } - - /** - * Strict data type validation check if the value is HTTP 200 OK - * - * @return bool - */ - public function isHttp200(): bool - { - return (int)$this->value === 200; - } - - /** - * Strict data type validation check if the value is a 2xx success HTTP code - * - * @return bool - */ - public function isHttpSuccess(): bool - { - $intVal = (int)$this->value; - return $intVal >= 200 && $intVal < 300; - } - - - /** - * Strict data type validation check if the value is a 4xx client error HTTP code - * - * @return bool - */ - public function isHttpClientError(): bool - { - $intVal = (int)$this->value; - return $intVal >= 400 && $intVal < 500; - } - - /** - * Strict data type validation check if the value is a 5xx server error HTTP code - * - * @return bool - */ - public function isHttpServerError(): bool - { - $intVal = (int)$this->value; - return $intVal >= 500 && $intVal < 600; - } - - - /** - * Validate multiple. Will return true if "one" matches - * - * @param array $arr - * @return bool - * @throws ErrorException - */ - public function oneOf(array $arr): bool - { - $valid = false; - foreach ($arr as $method => $args) { - $inst = new self($this->value); - if(call_user_func_array([$inst, $method], $args)) { - $valid = true; - } - } - return $valid; - } - - /** - * Validate multiple. Will return true if "all" matches - * - * @param array $arr - * @return bool - * @throws ErrorException - */ - public function allOf(array $arr): bool - { - foreach ($arr as $method => $args) { - $inst = new self($this->value); - if(!call_user_func_array([$inst, $method], $args)) { - return false; - } - } - return true; - } - - /** - * Check if is a date and a "valid range" - * - * @param string $format validate after this date format (default Y-m-d H:i) - * @return array|false E.g., array(T1, T2); T1 = start, and T2 = end - */ - public function dateRange(string $format = "Y-m-d H:i"): array|false - { - $exp = explode(" - ", $this->value); - if (count($exp) === 2) { - $time1 = trim($exp[0]); - $time2 = trim($exp[1]); - $val1 = DateTime::createFromFormat($format, $time1); - $val2 = DateTime::createFromFormat($format, $time2); - return (($val1 && $val2 && ($val1->getTimestamp() <= $val2->getTimestamp())) ? - ["t1" => $time1, "t2" => $time2] : false); - } - return false; - } -} +} \ No newline at end of file diff --git a/src/ValidatePool.php b/src/ValidationChain.php similarity index 93% rename from src/ValidatePool.php rename to src/ValidationChain.php index e25a465..1eaf882 100644 --- a/src/ValidatePool.php +++ b/src/ValidationChain.php @@ -77,17 +77,15 @@ * @method self oneOf(array $arr) * @method self allOf(array $arr) */ -class ValidatePool +class ValidationChain { private mixed $value; private ?string $key = null; private ?string $validationName = null; private array $error = []; - private ?Inp $inp = null; - /** - * Constructor for the ValidatePool class. + * Constructor for the ValidationChain class. * * @param mixed $value The initial value to be validated. */ @@ -134,7 +132,7 @@ public function mapErrorValidationName(string $key): self } /** - * Access a validation from Inp instance + * Access a validation from Validator instance * * @param string $name * @param array $arguments @@ -148,14 +146,14 @@ public function validateWith(string $name, array $arguments = []): bool $name = substr($name, 1); } - $this->inp = new Inp($this->value); - if(!method_exists($this->inp, $name)) { - throw new BadMethodCallException("Method $name does not exist in class " . Inp::class . "."); + $inp = new Validator($this->value); + if(!method_exists($inp, $name)) { + throw new BadMethodCallException("Method $name does not exist in class " . Validator::class . "."); } - $valid = $this->inp->$name(...$arguments); + $valid = $inp->$name(...$arguments); - // If using traverse method in Inp - if($valid instanceof Inp) { + // If using traverse method in Validator + if($valid instanceof Validator) { throw new BadMethodCallException("The method ->$name() is not supported with " . __CLASS__ . ". Use ->validateInData() instead!"); } @@ -190,18 +188,6 @@ public function getError(): array return $this->error; } - public function getNormalizedError(): array - { - $new = []; - $error = $this->getError(); - foreach($error as $keyA => $arr) { - foreach($arr as $keyB => $bool) { - $new[$keyA][$keyB] = !$bool; - } - } - return $new; - } - /** * Checks if there are any errors recorded in the validation process. * @@ -221,4 +207,18 @@ public function isValid(): bool { return !$this->getError(); } + + /* + public function getNormalizedError(): array + { + $new = []; + $error = $this->getError(); + foreach($error as $keyA => $arr) { + foreach($arr as $keyB => $bool) { + $new[$keyA][$keyB] = !$bool; + } + } + return $new; + } + */ } \ No newline at end of file diff --git a/src/Validator.php b/src/Validator.php new file mode 100755 index 0000000..7a11271 --- /dev/null +++ b/src/Validator.php @@ -0,0 +1,1180 @@ +', + '=', + '==', + '>', + '>=', + 'eq', + 'ge', + 'gt', + 'le', + 'lt', + 'ne' + ]; + + private mixed $value; + private int $length = 0; + private DateTime $dateTime; + private ?Luhn $luhn = null; + private ?DNS $dns = null; + private ?Str $getStr = null; + + + /** + * Start instance + * + * @param mixed $value the input value + * @throws ErrorException + */ + public function __construct(mixed $value) + { + $this->value = $value; + $this->dateTime = new DateTime("now"); + $this->init(); + } + + /** + * Used to reset length in traverse with the "mutable" flag + * + * @return void + * @throws ErrorException + */ + private function init(): void + { + if(is_string($this->value) || is_numeric($this->value)) { + $this->length = $this->getLength((string)$this->value); + $this->getStr = new Str($this->value); + } + } + + /** + * Immutable: Validate against new value + * + * @param mixed $value + * @return InpInterface + */ + public function withValue(mixed $value): InpInterface + { + $inst = clone $this; + $inst->value = $value; + return $inst; + } + + /** + * Start instance + * + * @param string $value the input value + * @return self + * @throws ErrorException + */ + public static function value(mixed $value): self + { + return new self($value); + } + + /** + * Makes it possible to traverse to a value in array or object + * + * @param string $key + * @param bool $immutable + * @return self + * @throws ErrorException + */ + public function eq(string $key, bool $immutable = true): self + { + $value = $this->value; + if(is_array($this->value) || is_object($this->value)) { + $value = Traverse::value($this->value)->eq($key)->get(); + if(!$immutable && $value !== false) { + $this->value = $value; + $this->init(); + return $this; + } + } + return self::value($value); + } + + /** + * This will make it possible to validate arrays and object with one line + * + * @example validateInData(user.name, 'length', [1, 200]); + * + * @param string $key + * @param string $validate + * @param array $args + * @return mixed + * @throws ErrorException + */ + public function validateInData(string $key, string $validate, array $args = []): bool + { + $inp = $this->eq($key, false); + if(!method_exists($inp, $validate)) { + throw new BadMethodCallException("Method '$validate' does not exist in " . __CLASS__ . " class."); + } + return $inp->{$validate}(...$args); + } + + /** + * Get value string length + * + * @param string $value + * @return int + * @throws ErrorException + */ + public function getLength(string $value): int + { + $mb = new MB($value); + return (int)$mb->strlen(); + } + + /** + * Get the current value + * _The value can be changes with travers method and this lets you peak at the new one_ + * + * @return mixed + */ + public function getValue(): mixed + { + return $this->value; + } + + /** + * Access luhn validation class + * + * @return Luhn + */ + public function luhn(): Luhn + { + if (is_null($this->luhn)) { + $this->luhn = new Luhn($this->value); + } + return $this->luhn; + } + + /** + * Access DNS validations + * + * @return DNS + */ + public function dns(): DNS + { + if(is_null($this->dns)) { + $value = $this->value; + $position = Traverse::value($this->value)->strPosition("@")->toInt(); + if($position > 0) { + $value = Traverse::value($this->value)->strSubstr($position + 1)->toString(); + } + $this->dns = new DNS($value); + } + return $this->dns; + } + + /** + * Will check if the value is empty (e.g. "", 0, NULL) = false + * + * @return bool + */ + public function isRequired(): bool + { + if ($this->length(1) && !empty($this->value)) { + return true; + } + return false; + } + + /** + * Strict data type validation check if is false + * + * @return bool + */ + public function isTrue(): bool + { + return $this->value === true; + } + + /** + * Flexible data type validation check if is truthy + * + * @return bool + */ + public function isTruthy(): bool + { + return filter_var($this->value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) === true; + } + + /** + * Strict data type validation check if is false + * + * @return bool + */ + public function isFalse(): bool + { + return $this->value === false; + } + + /** + * Flexible data type validation check if is falsy + * + * @return bool + */ + public function isFalsy(): bool + { + return filter_var($this->value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) === false; + } + + /** + * Strict data type validation check if a value exists in a given array + * + * @param array $haystack + * @return bool + */ + public function isInArray(array $haystack): bool + { + return in_array($this->value, $haystack, true); + } + + /** + * Flexible data type validation check if a value exists in a given array + * + * @param array $haystack + * @return bool + */ + public function isLooselyInArray(array $haystack): bool + { + return in_array($this->value, $haystack); + } + + /** + * Strict data type validation check if the key exists in an array + * + * @param string|int|float $key + * @return bool + */ + public function keyExists(string|int|float $key): bool + { + return is_array($this->value) && array_key_exists($key, $this->value); + } + + /** + * Will only check if there is a value + * + * @return bool + */ + public function hasValue(): bool + { + return $this->length(1); + } + + /** + * Validate Swedish personal numbers (personalNumber) + * + * @return bool + */ + public function isSocialNumber(): bool + { + return $this->luhn()->personnummer(); + } + + /** + * Validate Swedish org numbers + * + * @return bool + */ + public function isOrgNumber(): bool + { + return $this->luhn()->orgNumber(); + } + + /** + * Validate credit card numbers (THIS needs to be tested) + * + * @return bool + */ + public function isCreditCard(): bool + { + return $this->luhn()->creditCard(); + } + + /** + * Validate Swedish vat number + * + * @return bool + */ + public function isVatNumber(): bool + { + return $this->luhn()->vatNumber(); + } + + /** + * Validate email + * Loosely checks if is email. By loosely I mean it will not check if valid DNS. You can check this + * manually with the method @dns but in most cases this will not be necessary. + * + * @return bool + */ + public function isEmail(): bool + { + return (filter_var($this->value, FILTER_VALIDATE_EMAIL) !== false); + } + + + /** + * Validate if the email is deliverable + * This checks if the email is syntactically valid and has a valid MX record. + * + * @return bool + */ + public function isDeliverableEmail(): bool + { + return ($this->isEmail() && $this->dns()->isMxRecord()); + } + + + /** + * Checks if a string contains a given substring + * + * @param string $needle + * @return bool + */ + public function contains(string $needle): bool + { + return str_contains($this->value, $needle); + } + + /** + * Checks if a string starts with a given substring + * + * @param string $needle + * @return bool + */ + public function startsWith(string $needle): bool + { + return str_starts_with($this->value, $needle); + } + + /** + * Checks if a string ends with a given substring + * + * @param string $needle + * @return bool + */ + public function endsWith(string $needle): bool + { + return str_ends_with($this->value, $needle); + } + + /** + * Find in string + * + * @param string $match keyword to match against + * @param int|null $pos match the start position if you want + * @return bool + */ + public function findInString(string $match, ?int $pos = null): bool + { + return ((is_null($pos) && str_contains($this->value, $match)) || + (strpos($this->value, $match) === $pos)); + } + + /** + * Checks if is a phone number + * + * @return bool + */ + public function isPhone(): bool + { + if (is_null($this->getStr)) { + return false; + } + $val = (string)$this->getStr->replace([" ", "-", "—", "–", "(", ")"], ["", "", "", "", "", ""]); + $match = preg_match('/^[0-9]{7,14}+$/', $val); + $strict = preg_match('/^\+[0-9]{1,2}[0-9]{6,13}$/', $val); + return ($strict || $match); + } + + /** + * Check if is valid ZIP + * + * @param int $minLength start length + * @param int|null $maxLength end length + * @return bool + * @throws ErrorException + */ + public function isZip(int $minLength, ?int $maxLength = null): bool + { + if (is_null($this->getStr)) { + return false; + } + $this->value = (string)$this->getStr->replace([" ", "-", "—", "–"], ["", "", "", ""]); + $this->length = $this->getLength($this->value); + return ($this->isInt() && $this->length($minLength, $maxLength)); + } + + /** + * If value float + * Will validate whether a string is a valid float (User input is always a string) + * + * @return bool + */ + public function isFloat(): bool + { + return (filter_var($this->value, FILTER_VALIDATE_FLOAT) !== false); + } + + /** + * Is the value an int value + * Will validate whether a string is a valid integer (User input is always a string) + * + * @return bool + */ + public function isInt(): bool + { + return (filter_var($this->value, FILTER_VALIDATE_INT) !== false); + } + + /** + * Is value string + * + * @return bool + */ + public function isString(): bool + { + return is_string($this->value); + } + + /** + * Is value array + * + * @return bool + */ + public function isArray(): bool + { + return is_array($this->value); + } + + /** + * Is value object + * + * @return bool + */ + public function isObject(): bool + { + return is_object($this->value); + } + + /** + * Is value bool + * + * @return bool + */ + public function isBool(): bool + { + return (is_bool($this->value)); + } + + /** + * Is the value a resource value? + * + * @return bool + */ + public function isResource(): bool + { + return is_resource($this->value); + } + + /** + * Is a valid json string + * + * @return bool + */ + public function isJson(): bool + { + json_decode($this->value); + return json_last_error() === JSON_ERROR_NONE; + } + + /** + * Validate a string as HTML, check that it contains doctype, HTML, head, and body + * + * @return bool + */ + public function isFullHtml(): bool + { + libxml_use_internal_errors(true); + $dom = new DOMDocument(); + if (!is_string($this->value) || !$dom->loadHTML($this->value, LIBXML_NOERROR | LIBXML_NOWARNING)) { + return false; // Invalid HTML syntax + } + if (!$dom->doctype || strtolower($dom->doctype->name) !== "html") { + return false; + } + $htmlTag = $dom->getElementsByTagName("html")->length > 0; + $headTag = $dom->getElementsByTagName("head")->length > 0; + $bodyTag = $dom->getElementsByTagName("body")->length > 0; + return $htmlTag && $headTag && $bodyTag; + } + + /** + * Check if the value itself can be Interpreted as a bool value + * E.g., If value === ([on, off], [yes, no], [1, 0] or [true, false]) + * + * @return bool + */ + public function isBoolVal(): bool + { + $val = strtolower(trim((string)$this->value)); + $true = ($val === "on" || $val === "yes" || $val === "1" || $val === "true"); + $false = ($val === "off" || $val === "no" || $val === "0" || $val === "false"); + return ($true || $false); + } + + /** + * Is null + * + * @return bool + */ + public function isNull(): bool + { + return is_null($this->value); + } + + /** + * Is directory + * + * @return bool + */ + public function isDir(): bool + { + return is_dir($this->value); + } + + /** + * Is file + * + * @return bool + */ + public function isFile(): bool + { + return is_file($this->value); + } + + /** + * Check if is file or directory + * + * @return bool + */ + function isFileOrDirectory(): bool + { + return file_exists($this->value); + } + + /** + * Is writable + * + * @return bool + */ + public function isWritable(): bool + { + return is_writable($this->value); + } + + /** + * Is readable + * + * @return bool + */ + public function isReadable(): bool + { + return is_readable($this->value); + } + + /** + * Value is strictly a number (int or float). + * + * @return bool + */ + public function isNumber(): bool + { + return $this->isFloat() || $this->isInt(); + } + + /** + * Value is loosely numeric (e.g., numeric strings, scientific notation). + * + * @return bool + */ + public function isNumbery(): bool + { + return is_numeric($this->value); + } + + /** + * Value is number positive 20 + * + * @return bool + */ + public function isPositive(): bool + { + return ((float)$this->value >= 0); + } + + /** + * Value is the number negative -20 + * + * @return bool + */ + public function isNegative(): bool + { + return ((float)$this->value < 0); + } + + /** + * Value is minimum float|int value + * + * @param float $int + * @return bool + */ + public function min(float $int): bool + { + return ((float)$this->value >= $int); + } + + /** + * Value is maximum float|int value + * + * @param float $int + * @return bool + */ + public function max(float $int): bool + { + return ((float)$this->value <= $int); + } + + /** + * Check if the string length is more than start ($min), or between ($min) and ($max) + * + * @param int $min start length + * @param int|null $max end length + * @return bool + */ + public function length(int $min, ?int $max = null): bool + { + if ($this->length >= $min && (($max === null) || $this->length <= $max)) { + return true; + } + return false; + } + + /** + * Check if an array is empty + * + * @return bool + */ + public function isArrayEmpty(): bool + { + return ($this->isArray() && count($this->value) === 0); + } + + /** + * Check if all items in an array are truthy + * + * @param string|int|float $key + * @return bool + */ + public function itemsAreTruthy(string|int|float $key): bool + { + if($this->isArray()) { + $count = Arr::value($this->value) + ->filter(fn ($item) => $item->flatten()->{$key}->toBool()) + ->count(); + return ($count === count($this->value)); + } + return false; + } + + /** + * Check if a truthy item exists in an array + * + * @param string|int|float $key + * @return bool + */ + public function hasTruthyItem(string|int|float $key): bool + { + if($this->isArray()) { + $count = Arr::value($this->value) + ->filter(fn ($item) => $item->flatten()->{$key}->toBool()) + ->count(); + return ($count > 0); + } + return false; + } + + /** + * Validate array length equal to + * + * @param int $length + * @return bool + */ + public function isCountEqualTo(int $length): bool + { + return ($this->isArray() && count($this->value) === $length); + } + + /** + * Validate array length is more than + * + * @param int $length + * @return bool + */ + public function isCountMoreThan(int $length): bool + { + return ($this->isArray() && count($this->value) > $length); + } + + /** + * Validate array length is less than + * + * @param int $length + * @return bool + */ + public function isCountLessThan(int $length): bool + { + return ($this->isArray() && count($this->value) < $length); + } + + /** + * Check int value is equal to int value + * + * @param int $value + * @return bool + */ + public function toIntEqual(int $value): bool + { + return (int)$this->value === $value; + } + + /** + * Value string length is equal to ($length) + * + * @param int $length + * @return bool + */ + public function isLengthEqualTo(int $length): bool + { + if ($this->length === $length) { + return true; + } + return false; + } + + /** + * Strict data type validation check if equals to the expected value + * + * @param mixed $expected + * @return bool + */ + public function isEqualTo(mixed $expected): bool + { + return $this->value === $expected; + } + + /** + * Flexible data type validation check if loosely equals to the expected value + * + * @param mixed $expected + * @return bool + */ + public function isLooselyEqualTo(mixed $expected): bool + { + return $this->value == $expected; + } + + + /** + * Strict data type validation check if not equals to expected value + * + * @param mixed $value + * @return bool + */ + public function isNotEqualTo(mixed $value): bool + { + return ($this->value !== $value); + } + + /** + * Flexible data type validation check if loosely not equals to expected value + * + * @param mixed $value + * @return bool + */ + public function isLooselyNotEqualTo(mixed $value): bool + { + return ($this->value !== $value); + } + + /** + * IF value is less than to parameter + * + * @param float|int $num + * @return bool + */ + public function isLessThan(float|int $num): bool + { + return ($this->value < $num); + } + + /** + * IF value is more than to parameter + * + * @param float|int $num + * @return bool + */ + public function isMoreThan(float|int $num): bool + { + return ($this->value > $num); + } + + /** + * Check is a valid version number + * + * @param bool $strict (validate as a semantic Versioning, e.g. 1.0.0) + * @return bool + */ + public function isValidVersion(bool $strict = false): bool + { + $strictMatch = (!$strict || preg_match("/^(\d?\d)\.(\d?\d)\.(\d?\d)$/", (string)$this->value)); + $compare = version_compare((string)$this->value, '0.0.1', '>='); + return ($strictMatch && $compare !== false && $compare >= 0); + } + + /** + * Validate/compare if a version is equal/more/equalMore/less... e.g., than withVersion + * + * @param string $withVersion + * @param string $operator '!='|'<'|'<='|'<>'|'='|'=='|'>'|'>='|'eq'|'ge'|'gt'|'le'|'lt'|'ne' + * @return bool + */ + public function versionCompare(string $withVersion, string $operator = "=="): bool + { + if (in_array($operator, self::WHITELIST_OPERATORS)) { + return version_compare((string)$this->value, $withVersion, $operator); + } + return false; + } + + /** + * Lossy password - Will return false if a character inputted is not allowed + * [a-zA-Z\d$@$!%*?&] - Matches "any" letter (uppercase or lowercase), digit, or special character + * from the allowed set of special characters + * + * @param integer $length Minimum length + * @return bool + */ + public function isLossyPassword(int $length = 1): bool + { + return ((int)preg_match('/^[a-zA-Z\d$@$!%*?&]{' . $length . ',}$/', $this->value) > 0); + } + + /** + * Strict password + * (?=.*[a-z]) - at least one lowercase letter + * (?=.*[A-Z]) - at least one uppercase letter + * (?=.*\d) - at least one digit + * (?=.*[$@$!%*?&]) - at least one special character from the set: $, @, #, !, %, *, ?, & + * [A-Za-z\d$@$!%*?&]{1,} - matches 1 or more characters consisting of letters, digits, + * and the allowed special characters + * I do tho recommend that you validate the length with @length(8, 60) method! + * + * @param integer $length Minimum length + * @return bool + */ + public function isStrictPassword(int $length = 1): bool + { + $pattern = '/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&])[A-Za-z\d$@$!%*?&]{' . $length . ',}$/'; + return ((int)preg_match($pattern, $this->value) > 0); + } + + /** + * Check if the value contains only characters matching the given pattern. + * + * @param string $charRange A character range (e.g., 'a-z', 'A-Z0-9') + * @return bool + */ + public function isMatchingPattern(string $charRange): bool + { + return (bool)preg_match("/^[$charRange]+$/", $this->value); + } + + /** + * Check if the value contains only alphabetic characters (a - z or A–Z). + * + * @return bool + */ + public function isAlpha(): bool + { + return (bool)preg_match("/^[a-zA-Z]+$/", $this->value); + } + + /** + * Check if the value contains only lowercase letters (a - z). + * + * @return bool + */ + public function isLowerAlpha(): bool + { + return (bool)preg_match("/^[a-z]+$/", $this->value); + } + + /** + * Check if the value contains only uppercase letters (A - Z). + * + * @return bool + */ + public function isUpperAlpha(): bool + { + return (bool)preg_match("/^[A-Z]+$/", $this->value); + } + + /** + * Is Hex color code string? + * + * @return bool + */ + public function isHex(): bool + { + return ((int)preg_match('/^#([0-9A-F]{3}){1,2}$/i', $this->value) > 0); + } + + /** + * Check if is a date + * + * @param string $format validate after this date format (default Y-m-d) + * @return bool + */ + public function isDate(string $format = "Y-m-d"): bool + { + return (DateTime::createFromFormat($format, $this->value) !== false); + } + + /** + * Check if is a date and time + * + * @return bool + */ + public function isDateWithTime(): bool + { + return $this->date("Y-m-d H:i:s"); + } + + /** + * Check if is a date and time + * + * @param bool $withSeconds + * @return bool + */ + public function isTime(bool $withSeconds = false): bool + { + return $this->date("H:i" . ($withSeconds ? ":s" : "")); + } + + /** + * Check "minimum" age (a value format should be validated date "Y-m-d") + * + * @param int $checkAge + * @return bool + * @throws Exception + */ + public function isAge(int $checkAge): bool + { + $now = (int)$this->dateTime->format("Y"); + $dateTime = new DateTime($this->value); + $birth = (int)$dateTime->format("Y"); + $age = ($now - $birth); + return ($age >= $checkAge); + } + + /** + * Check if is valid domain + * + * @param bool $strict stricter = true + * @return bool + */ + public function isDomain(bool $strict = true): bool + { + $strict = ($strict) ? FILTER_FLAG_HOSTNAME : 0; + return (filter_var((string)$this->value, FILTER_VALIDATE_DOMAIN, $strict) !== false); + } + + /** + * Check if is valid URL (http|https is required) + * + * @return bool + */ + public function isUrl(): bool + { + return (filter_var($this->value, FILTER_VALIDATE_URL) !== false); + } + + /** + * Check if "Host|domain" has a valid DNS (will check A, AAAA, and MX) + * + * @psalm-suppress UndefinedConstant + * @noinspection PhpComposerExtensionStubsInspection + * @return bool + */ + public function isResolvableHost(): bool + { + return $this->dns()->isResolvableHost(); + } + + /** + * Strict data type validation check if the value is a valid HTTP status code + * + * @return bool + */ + public function isHttpStatusCode(): bool + { + $validCodes = [ + 100, 101, 102, 103, + 200, 201, 202, 203, 204, 205, 206, 207, 208, 226, + 300, 301, 302, 303, 304, 305, 307, 308, + 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, + 410, 411, 412, 413, 414, 415, 416, 417, 418, 421, + 422, 423, 424, 425, 426, 428, 429, 431, 451, + 500, 501, 502, 503, 504, 505, 506, 507, 508, 510, 511 + ]; + return in_array((int)$this->value, $validCodes, true); + } + + /** + * Strict data type validation check if the value is HTTP 200 OK + * + * @return bool + */ + public function isHttp200(): bool + { + return (int)$this->value === 200; + } + + /** + * Strict data type validation check if the value is a 2xx success HTTP code + * + * @return bool + */ + public function isHttpSuccess(): bool + { + $intVal = (int)$this->value; + return $intVal >= 200 && $intVal < 300; + } + + + /** + * Strict data type validation check if the value is a 4xx client error HTTP code + * + * @return bool + */ + public function isHttpClientError(): bool + { + $intVal = (int)$this->value; + return $intVal >= 400 && $intVal < 500; + } + + /** + * Strict data type validation check if the value is a 5xx server error HTTP code + * + * @return bool + */ + public function isHttpServerError(): bool + { + $intVal = (int)$this->value; + return $intVal >= 500 && $intVal < 600; + } + + + /** + * Validate multiple. Will return true if "one" matches + * + * @param array $arr + * @return bool + * @throws ErrorException + */ + public function oneOf(array $arr): bool + { + $valid = false; + foreach ($arr as $method => $args) { + $inst = new self($this->value); + if(call_user_func_array([$inst, $method], $args)) { + $valid = true; + } + } + return $valid; + } + + /** + * Validate multiple. Will return true if "all" matches + * + * @param array $arr + * @return bool + * @throws ErrorException + */ + public function allOf(array $arr): bool + { + foreach ($arr as $method => $args) { + $inst = new self($this->value); + if(!call_user_func_array([$inst, $method], $args)) { + return false; + } + } + return true; + } + + /** + * Check if is a date and a "valid range" + * + * @param string $format validate after this date format (default Y-m-d H:i) + * @return array|false E.g., array(T1, T2); T1 = start, and T2 = end + */ + public function dateRange(string $format = "Y-m-d H:i"): array|false + { + $exp = explode(" - ", $this->value); + if (count($exp) === 2) { + $time1 = trim($exp[0]); + $time2 = trim($exp[1]); + $val1 = DateTime::createFromFormat($format, $time1); + $val2 = DateTime::createFromFormat($format, $time2); + return (($val1 && $val2 && ($val1->getTimestamp() <= $val2->getTimestamp())) ? + ["t1" => $time1, "t2" => $time2] : false); + } + return false; + } +} diff --git a/src/DNS.php b/src/Validators/DNS.php similarity index 96% rename from src/DNS.php rename to src/Validators/DNS.php index 2c878ba..5de5065 100755 --- a/src/DNS.php +++ b/src/Validators/DNS.php @@ -6,7 +6,9 @@ Don't delete this comment, it's part of the license. */ -namespace MaplePHP\Validate; +namespace MaplePHP\Validate\Validators; + +use InvalidArgumentException; class DNS { @@ -264,12 +266,12 @@ public function isHinfoRecord(): bool * @param int $type The DNS record type to match. * @return array|false Returns an array of matched DNS records, or false if none are found. * - * @throws \InvalidArgumentException If an invalid or unsupported DNS type is provided. + * @throws InvalidArgumentException If an invalid or unsupported DNS type is provided. */ public function getDnsRecordForType(int $type): array|false { if(!isset(self::ALOWED_DNS_TYPES[$type])) { - throw new \InvalidArgumentException('Invalid DNS type. Use one of ' . implode(', ', self::ALOWED_DNS_TYPES) . '.'); + throw new InvalidArgumentException('Invalid DNS type. Use one of ' . implode(', ', self::ALOWED_DNS_TYPES) . '.'); } $result = dns_get_record($this->host, $type); if (is_array($result) && count($result) > 0) { @@ -291,6 +293,6 @@ protected function getHost(string $host): string define('INTL_IDNA_VARIANT_2003', 0); } $variant = (defined('INTL_IDNA_VARIANT_UTS46')) ? INTL_IDNA_VARIANT_UTS46 : INTL_IDNA_VARIANT_2003; - return rtrim(idn_to_ascii($host, IDNA_DEFAULT, $variant), '.') . '.'; + return rtrim(idn_to_ascii($host, IDNA_DEFAULT, $variant), '.') . 'src'; } } diff --git a/src/Luhn.php b/src/Validators/Luhn.php similarity index 98% rename from src/Luhn.php rename to src/Validators/Luhn.php index 10cd4df..f346445 100755 --- a/src/Luhn.php +++ b/src/Validators/Luhn.php @@ -6,7 +6,7 @@ Don't delete this comment, its part of the license. */ -namespace MaplePHP\Validate; +namespace MaplePHP\Validate\Validators; class Luhn { @@ -144,7 +144,7 @@ public function cardPrefix(): string|bool */ public function vatNumber(): bool { - $vat = new ValidVatFormat($this->string); + $vat = new Vat($this->string); if ($vat->validate()) { if ($vat->getCountryCode() === "SE") { return $this->orgNumber(); diff --git a/src/ValidVatFormat.php b/src/Validators/Vat.php similarity index 94% rename from src/ValidVatFormat.php rename to src/Validators/Vat.php index d1044d3..3c650eb 100755 --- a/src/ValidVatFormat.php +++ b/src/Validators/Vat.php @@ -4,12 +4,12 @@ * @Package: MaplePHP - Validate vat number * @Author: Daniel Ronkainen * @Licence: Apache-2.0 license, Copyright © Daniel Ronkainen - Don't delete this comment, its part of the license. + Don't delete this comment, it's part of the license. */ -namespace MaplePHP\Validate; +namespace MaplePHP\Validate\Validators; -class ValidVatFormat +class Vat { /** * Regular expression per country code diff --git a/tests/unitary-validate-inp.php b/tests/unitary-validate-inp.php index c33e1a6..effb5ec 100755 --- a/tests/unitary-validate-inp.php +++ b/tests/unitary-validate-inp.php @@ -6,12 +6,12 @@ */ use MaplePHP\Unitary\Unit; -use MaplePHP\Validate\Inp; +use MaplePHP\Validate\Validator; $unit = new Unit(); $unit->case("MaplePHP input validate test", function() { - $strVal = Inp::value("TestStringValue"); + $strVal = Validator::value("TestStringValue"); $testStrValidates = ["isString", "required", "hasValue"]; foreach ($testStrValidates as $validate) { @@ -20,179 +20,179 @@ ], "Expect {$validate} to be true"); } - $this->add(Inp::value("8808218329")->socialNumber(), [ + $this->add(Validator::value("8808218329")->socialNumber(), [ "equal" => [false], ], "Expect socialNumber to be false"); - $this->add(Inp::value("4030000010001234")->creditCard(), [ + $this->add(Validator::value("4030000010001234")->creditCard(), [ "equal" => [true], ], "Expect creditCard to be true"); - $this->add(Inp::value("john.doe-gmail.com")->email(), [ + $this->add(Validator::value("john.doe-gmail.com")->email(), [ "equal" => [false], ], "Expect creditCard to be false"); - $this->add(Inp::value("Hello world!")->findInString("world"), [ + $this->add(Validator::value("Hello world!")->findInString("world"), [ "equal" => [true], ], "Expect findInString to be true"); - $this->add(Inp::value("+46 (0) 702-83 27 12")->phone(), [ + $this->add(Validator::value("+46 (0) 702-83 27 12")->phone(), [ "equal" => [true], ], "Expect phone to be true"); - $this->add(Inp::value("252522")->zip(5), [ + $this->add(Validator::value("252522")->zip(5), [ "equal" => [true], ], "Expect zip to be true"); $testDataTypeValidations = ['isString', 'isInt', 'isFloat', 'isArray', 'isObject', 'isBool']; - $this->add(Inp::value("Is string")->isString(), [ + $this->add(Validator::value("Is string")->isString(), [ "equal" => [true], ], "Expect isString to be true"); - $this->add(Inp::value(true)->isInt(), [ + $this->add(Validator::value(true)->isInt(), [ "equal" => [true], ], "Expect isInt to be true"); - $this->add(Inp::value(22.12)->isFloat(), [ + $this->add(Validator::value(22.12)->isFloat(), [ "equal" => [true], ], "Expect isFloat to be true"); - $this->add(Inp::value([1, 2, 3])->isArray(), [ + $this->add(Validator::value([1, 2, 3])->isArray(), [ "equal" => [true], ], "Expect isArray to be true"); - $this->add(Inp::value(new stdClass())->isObject(), [ + $this->add(Validator::value(new stdClass())->isObject(), [ "equal" => [true], ], "Expect isObject to be true"); - $this->add(Inp::value(false)->isBool(), [ + $this->add(Validator::value(false)->isBool(), [ "equal" => [true], ], "Expect isBool to be true"); - $this->add(Inp::value("222.33")->number(), [ + $this->add(Validator::value("222.33")->number(), [ "equal" => [true], ], "Expect number to be true"); - $this->add(Inp::value(100)->positive(), [ + $this->add(Validator::value(100)->positive(), [ "equal" => [true], ], "Expect positive to be true"); - $this->add(Inp::value(-100)->negative(), [ + $this->add(Validator::value(-100)->negative(), [ "equal" => [true], ], "Expect negative to be true"); - $this->add(Inp::value(10)->min(10), [ + $this->add(Validator::value(10)->min(10), [ "equal" => [true], ], "Expect min to be true"); - $this->add(Inp::value(10)->max(10), [ + $this->add(Validator::value(10)->max(10), [ "equal" => [true], ], "Expect max to be true"); - $this->add(Inp::value("Lorem ipsum")->length(1, 11), [ + $this->add(Validator::value("Lorem ipsum")->length(1, 11), [ "equal" => [true], ], "Expect length to be true"); - $this->add(Inp::value("22222")->equalLength(5), [ + $this->add(Validator::value("22222")->equalLength(5), [ "equal" => [true], ], "Expect equalLength to be true"); - $this->add(Inp::value("hello")->equal("hello"), [ + $this->add(Validator::value("hello")->equal("hello"), [ "equal" => [true], ], "Expect equal to be true"); - $this->add(Inp::value("world")->notEqual("hello"), [ + $this->add(Validator::value("world")->notEqual("hello"), [ "equal" => [true], ], "Expect notEqual to be true"); - $this->add(Inp::value("1.2.3")->validVersion(true), [ + $this->add(Validator::value("1.2.3")->validVersion(true), [ "equal" => [true], ], "Expect validVersion to be true"); - $this->add(Inp::value("1.2.0")->versionCompare("1.2.0"), [ + $this->add(Validator::value("1.2.0")->versionCompare("1.2.0"), [ "equal" => [true], ], "Expect versionCompare to be true"); - $this->add(Inp::value("MyStrongPass")->lossyPassword(), [ + $this->add(Validator::value("MyStrongPass")->lossyPassword(), [ "equal" => [true], ], "Expect lossyPassword to be true"); - $this->add(Inp::value("My@StrongPass12")->strictPassword(), [ + $this->add(Validator::value("My@StrongPass12")->strictPassword(), [ "equal" => [true], ], "Expect strictPassword to be true"); - $this->add(Inp::value("HelloWorld")->atoZ(), [ + $this->add(Validator::value("HelloWorld")->atoZ(), [ "equal" => [true], ], "Expect atoZ to be true"); - $this->add(Inp::value("welloworld")->lowerAtoZ(), [ + $this->add(Validator::value("welloworld")->lowerAtoZ(), [ "equal" => [true], ], "Expect lowerAtoZ to be true"); - $this->add(Inp::value("HELLOWORLD")->upperAtoZ(), [ + $this->add(Validator::value("HELLOWORLD")->upperAtoZ(), [ "equal" => [true], ], "Expect upperAtoZ to be true"); - $this->add(Inp::value("#F1F1F1")->hex(), [ + $this->add(Validator::value("#F1F1F1")->hex(), [ "equal" => [true], ], "Expect hex to be true"); - $this->add(Inp::value("1922-03-01")->date(), [ + $this->add(Validator::value("1922-03-01")->date(), [ "equal" => [true], ], "Expect date to be true"); - $this->add(Inp::value("1988-08-21")->age(18), [ + $this->add(Validator::value("1988-08-21")->age(18), [ "equal" => [true], ], "Expect age to be true"); - $this->add(Inp::value("example.se")->domain(), [ + $this->add(Validator::value("example.se")->domain(), [ "equal" => [true], ], "Expect domain to be true"); - $this->add(Inp::value("https://example.se")->url(), [ + $this->add(Validator::value("https://example.se")->url(), [ "equal" => [true], ], "Expect url to be true"); - $this->add(Inp::value("daniel@creativearmy.se")->isDeliverableEmail(), [ + $this->add(Validator::value("daniel@creativearmy.se")->isDeliverableEmail(), [ "equal" => [true], ], "wdwq required to be true"); - $this->add(Inp::value("daniel@creativearmy.se")->dns()->isMxRecord(), [ + $this->add(Validator::value("daniel@creativearmy.se")->dns()->isMxRecord(), [ "equal" => [true], ], "wdwq required to be true"); - $this->add(Inp::value("examplethatwillfail.se")->dns()->isAddressRecord(), [ + $this->add(Validator::value("examplethatwillfail.se")->dns()->isAddressRecord(), [ "equal" => [false], ], "Expect dns to be false"); - $this->add(Inp::value("Lorem ipsum")->oneOf([ + $this->add(Validator::value("Lorem ipsum")->oneOf([ "length" => [120, 200], "isString" => [] ]), [ "equal" => [true], ], "Expect oneOf to be true"); - $this->add(Inp::value("Lorem ipsum")->allOf([ + $this->add(Validator::value("Lorem ipsum")->allOf([ "length" => [1, 200], "isString" => [] ]), [ "equal" => [true], ], "Expect allOf to be true"); - $this->add(Inp::value("required")->required(), [ + $this->add(Validator::value("required")->required(), [ "equal" => [true], ], "Expect required to be true"); - $this->add(Inp::value("required")->required(), [ + $this->add(Validator::value("required")->required(), [ "equal" => [true], ], "Expect required to be true"); - $this->add(Inp::value("required")->required(), [ + $this->add(Validator::value("required")->required(), [ "equal" => [true], ], "Expect required to be true"); - $this->add(Inp::value("required")->required(), [ + $this->add(Validator::value("required")->required(), [ "equal" => [true], ], "Expect required to be true"); From 5aa11a41ff67d0742805d2e8a5e1b98039e71fda Mon Sep 17 00:00:00 2001 From: Daniel Ronkainen Date: Mon, 28 Apr 2025 22:37:49 +0200 Subject: [PATCH 14/21] Add proxy validations blocks to chain class --- README.md | 2 +- src/ValidationChain.php | 120 +++++++++++++++++++-------------- tests/unitary-validate-inp.php | 2 +- 3 files changed, 71 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index 87b68ce..a8709ba 100755 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ $valid = $inp->validateInData("user.name", "length", [1, 200]); --- -## Using the Validation Pool +## Using the Chain validations The `ValidationChain` class allows you to chain multiple validations on a single value and check the overall result: diff --git a/src/ValidationChain.php b/src/ValidationChain.php index 1eaf882..7b5bb50 100644 --- a/src/ValidationChain.php +++ b/src/ValidationChain.php @@ -7,75 +7,93 @@ /** * @method self withValue(mixed $value) - * @method self value(mixed $value) + * @method self eq(string $key, bool $immutable = '1') + * @method self validateInData(string $key, string $validate, array $args = '') * @method self getLength(string $value) - * @method self traverse(string $key, bool $immutable = true) - * @method self validateInData(string $key, string $validate, array $args = []) * @method self luhn() - * @method self required() + * @method self dns() + * @method self isRequired() + * @method self isTrue() + * @method self isTruthy() + * @method self isFalse() + * @method self isFalsy() + * @method self isInArray(array $haystack) + * @method self isLooselyInArray(array $haystack) + * @method self keyExists(string|int|float $key) * @method self hasValue() - * @method self socialNumber() - * @method self personalNumber() - * @method self orgNumber() - * @method self creditCard() - * @method self vatNumber() - * @method self email() - * @method self findInString(string $match, ?int $pos) - * @method self phone() - * @method self zip(int $arg1, ?int $arg2) + * @method self isSocialNumber() + * @method self isOrgNumber() + * @method self isCreditCard() + * @method self isVatNumber() + * @method self isEmail() + * @method self isDeliverableEmail() + * @method self contains(string $needle) + * @method self startsWith(string $needle) + * @method self endsWith(string $needle) + * @method self findInString(string $match, ?int $pos = '') + * @method self isPhone() + * @method self isZip(int $minLength, ?int $maxLength = '') * @method self isFloat() * @method self isInt() * @method self isString() - * @method self isStr() * @method self isArray() * @method self isObject() * @method self isBool() + * @method self isResource() * @method self isJson() * @method self isFullHtml() * @method self isBoolVal() * @method self isNull() - * @method self isFile() * @method self isDir() - * @method self isResource() + * @method self isFile() + * @method self isFileOrDirectory() * @method self isWritable() * @method self isReadable() - * @method self number() - * @method self numeric() - * @method self numericVal() - * @method self positive() - * @method self negative() + * @method self isNumber() + * @method self isNumbery() + * @method self isPositive() + * @method self isNegative() * @method self min(float $int) - * @method self minAlt(float $int) * @method self max(float $int) - * @method self length(int $arg1, ?int $arg2) - * @method self equalLength(int $arg1) - * @method self equal($str) - * @method self lessThan($num) - * @method self moreThan($num) - * @method self contains(string $needle) - * @method self startsWith(string $needle) - * @method self endsWith(string $needle) - * @method self notEqual($str) - * @method self validVersion(bool $strict) - * @method self versionCompare(string $withVersion, string $operator) - * @method self lossyPassword(int $length) - * @method self strictPassword(int $length) - * @method self pregMatch($matchStr) - * @method self atoZ() - * @method self lowerAtoZ() - * @method self upperAtoZ() - * @method self hex() - * @method self date(string $format) - * @method self dateTime(string $format) - * @method self time(string $format) - * @method self dateRange(string $format) - * @method self age(int $arg1) - * @method self domain(bool $strict) - * @method self url() - * @method self dns() - * @method self matchDNS(int $type) + * @method self length(int $min, ?int $max = '') + * @method self isArrayEmpty() + * @method self itemsAreTruthy(string|int|float $key) + * @method self hasTruthyItem(string|int|float $key) + * @method self isCountEqualTo(int $length) + * @method self isCountMoreThan(int $length) + * @method self isCountLessThan(int $length) + * @method self toIntEqual(int $value) + * @method self isLengthEqualTo(int $length) + * @method self isEqualTo(mixed $expected) + * @method self isLooselyEqualTo(mixed $expected) + * @method self isNotEqualTo(mixed $value) + * @method self isLooselyNotEqualTo(mixed $value) + * @method self isLessThan(int|float $num) + * @method self isMoreThan(int|float $num) + * @method self isValidVersion(bool $strict = '') + * @method self versionCompare(string $withVersion, string $operator = '==') + * @method self isLossyPassword(int $length = '1') + * @method self isStrictPassword(int $length = '1') + * @method self isMatchingPattern(string $charRange) + * @method self isAlpha() + * @method self isLowerAlpha() + * @method self isUpperAlpha() + * @method self isHex() + * @method self isDate(string $format = 'Y-m-d') + * @method self isDateWithTime() + * @method self isTime(bool $withSeconds = '') + * @method self isAge(int $checkAge) + * @method self isDomain(bool $strict = '1') + * @method self isUrl() + * @method self isResolvableHost() + * @method self isHttpStatusCode() + * @method self isHttp200() + * @method self isHttpSuccess() + * @method self isHttpClientError() + * @method self isHttpServerError() * @method self oneOf(array $arr) * @method self allOf(array $arr) + * @method self dateRange(string $format = 'Y-m-d H:i') */ class ValidationChain { @@ -132,7 +150,7 @@ public function mapErrorValidationName(string $key): self } /** - * Access a validation from Validator instance + * Access a validation from a Validator instance * * @param string $name * @param array $arguments @@ -152,7 +170,7 @@ public function validateWith(string $name, array $arguments = []): bool } $valid = $inp->$name(...$arguments); - // If using traverse method in Validator + // If using the traverse method in Validator if($valid instanceof Validator) { throw new BadMethodCallException("The method ->$name() is not supported with " . __CLASS__ . ". Use ->validateInData() instead!"); diff --git a/tests/unitary-validate-inp.php b/tests/unitary-validate-inp.php index effb5ec..512d365 100755 --- a/tests/unitary-validate-inp.php +++ b/tests/unitary-validate-inp.php @@ -196,5 +196,5 @@ "equal" => [true], ], "Expect required to be true"); - + //echo $this->listAllProxyMethods(Validator::class, isolateClass: true); }); From e41306024baf13a5924dc6bcf06e933518c16af5 Mon Sep 17 00:00:00 2001 From: Daniel Ronkainen Date: Wed, 30 Apr 2025 23:34:28 +0200 Subject: [PATCH 15/21] Add validation chain with validation invertion --- src/ValidationChain.php | 144 ++++++++++++++++++++++++++++----- src/Validator.php | 7 +- src/Validators/DNS.php | 2 +- tests/unitary-validate-inp.php | 31 ++++++- 4 files changed, 161 insertions(+), 23 deletions(-) diff --git a/src/ValidationChain.php b/src/ValidationChain.php index 7b5bb50..2fcf49d 100644 --- a/src/ValidationChain.php +++ b/src/ValidationChain.php @@ -4,14 +4,17 @@ use BadMethodCallException; use ErrorException; +use MaplePHP\DTO\Traverse; +use MaplePHP\Validate\Validators\DNS; +use MaplePHP\Validate\Validators\Luhn; /** * @method self withValue(mixed $value) * @method self eq(string $key, bool $immutable = '1') * @method self validateInData(string $key, string $validate, array $args = '') * @method self getLength(string $value) - * @method self luhn() - * @method self dns() + * @method Luhn luhn() + * @method DNS dns() * @method self isRequired() * @method self isTrue() * @method self isTruthy() @@ -94,6 +97,94 @@ * @method self oneOf(array $arr) * @method self allOf(array $arr) * @method self dateRange(string $format = 'Y-m-d H:i') + * @method self notWithValue(mixed $value) + * @method self notEq(string $key, bool $immutable = '1') + * @method self notValidateInData(string $key, string $validate, array $args = '') + * @method self notGetLength(string $value) + * @method Luhn notLuhn() + * @method DNS notDns() + * @method self notIsRequired() + * @method self notIsTrue() + * @method self notIsTruthy() + * @method self notIsFalse() + * @method self notIsFalsy() + * @method self notIsInArray(array $haystack) + * @method self notIsLooselyInArray(array $haystack) + * @method self notKeyExists(string|int|float $key) + * @method self notHasValue() + * @method self notIsSocialNumber() + * @method self notIsOrgNumber() + * @method self notIsCreditCard() + * @method self notIsVatNumber() + * @method self notIsEmail() + * @method self notIsDeliverableEmail() + * @method self notContains(string $needle) + * @method self notStartsWith(string $needle) + * @method self notEndsWith(string $needle) + * @method self notFindInString(string $match, ?int $pos = '') + * @method self notIsPhone() + * @method self notIsZip(int $minLength, ?int $maxLength = '') + * @method self notIsFloat() + * @method self notIsInt() + * @method self notIsString() + * @method self notIsArray() + * @method self notIsObject() + * @method self notIsBool() + * @method self notIsResource() + * @method self notIsJson() + * @method self notIsFullHtml() + * @method self notIsBoolVal() + * @method self notIsNull() + * @method self notIsDir() + * @method self notIsFile() + * @method self notIsFileOrDirectory() + * @method self notIsWritable() + * @method self notIsReadable() + * @method self notIsNumber() + * @method self notIsNumbery() + * @method self notIsPositive() + * @method self notIsNegative() + * @method self notMin(float $int) + * @method self notMax(float $int) + * @method self notLength(int $min, ?int $max = '') + * @method self notIsArrayEmpty() + * @method self notItemsAreTruthy(string|int|float $key) + * @method self notHasTruthyItem(string|int|float $key) + * @method self notIsCountEqualTo(int $length) + * @method self notIsCountMoreThan(int $length) + * @method self notIsCountLessThan(int $length) + * @method self notToIntEqual(int $value) + * @method self notIsLengthEqualTo(int $length) + * @method self notIsEqualTo(mixed $expected) + * @method self notIsLooselyEqualTo(mixed $expected) + * @method self notIsNotEqualTo(mixed $value) + * @method self notIsLooselyNotEqualTo(mixed $value) + * @method self notIsLessThan(int|float $num) + * @method self notIsMoreThan(int|float $num) + * @method self notIsValidVersion(bool $strict = '') + * @method self notVersionCompare(string $withVersion, string $operator = '==') + * @method self notIsLossyPassword(int $length = '1') + * @method self notIsStrictPassword(int $length = '1') + * @method self notIsMatchingPattern(string $charRange) + * @method self notIsAlpha() + * @method self notIsLowerAlpha() + * @method self notIsUpperAlpha() + * @method self notIsHex() + * @method self notIsDate(string $format = 'Y-m-d') + * @method self notIsDateWithTime() + * @method self notIsTime(bool $withSeconds = '') + * @method self notIsAge(int $checkAge) + * @method self notIsDomain(bool $strict = '1') + * @method self notIsUrl() + * @method self notIsResolvableHost() + * @method self notIsHttpStatusCode() + * @method self notIsHttp200() + * @method self notIsHttpSuccess() + * @method self notIsHttpClientError() + * @method self notIsHttpServerError() + * @method self notOneOf(array $arr) + * @method self notAllOf(array $arr) + * @method self notDateRange(string $format = 'Y-m-d H:i') */ class ValidationChain { @@ -133,16 +224,36 @@ public function getValue(): mixed */ public function __call(string $name, array $arguments): self { + $newName = Traverse::value($name) + ->strCamelCaseToArr() + ->shift($rest) + ->implode() + ->toString(); + if($rest === "not") { + $name = "!" . $newName; + } $this->validateWith($name, $arguments); return $this; } + /** + * You can add a name to error keys + * + * @param string $key + * @return $this + */ public function mapErrorToKey(string $key): self { $this->key = $key; return $this; } + /** + * You can overwrite the expected validation name on error + * + * @param string $key + * @return $this + */ public function mapErrorValidationName(string $key): self { $this->validationName = $key; @@ -157,7 +268,7 @@ public function mapErrorValidationName(string $key): self * @return bool * @throws ErrorException */ - public function validateWith(string $name, array $arguments = []): bool + public function validateWith(string $name, array|string ...$arguments): bool { $invert = str_starts_with($name, "!"); if ($invert) { @@ -168,8 +279,13 @@ public function validateWith(string $name, array $arguments = []): bool if(!method_exists($inp, $name)) { throw new BadMethodCallException("Method $name does not exist in class " . Validator::class . "."); } + + if(isset($arguments[0][0])) { + $arguments = Traverse::value($arguments)->flatten()->toArray(); + } $valid = $inp->$name(...$arguments); + // If using the traverse method in Validator if($valid instanceof Validator) { throw new BadMethodCallException("The method ->$name() is not supported with " . @@ -199,13 +315,19 @@ public function validateWith(string $name, array $arguments = []): bool * @return array Returns an associative array of errors where the key is the method name * and the value is the arguments passed to the method. */ - public function getError(): array + public function getFailedValidations(): array { $this->error = array_map('array_filter', $this->error); $this->error = array_filter($this->error); return $this->error; } + // Alias for "getFailedValidations" (used in Unitary) + public function getError(): array + { + return $this->getFailedValidations(); + } + /** * Checks if there are any errors recorded in the validation process. * @@ -225,18 +347,4 @@ public function isValid(): bool { return !$this->getError(); } - - /* - public function getNormalizedError(): array - { - $new = []; - $error = $this->getError(); - foreach($error as $keyA => $arr) { - foreach($arr as $keyB => $bool) { - $new[$keyA][$keyB] = !$bool; - } - } - return $new; - } - */ } \ No newline at end of file diff --git a/src/Validator.php b/src/Validator.php index 7a11271..fbad5c0 100755 --- a/src/Validator.php +++ b/src/Validator.php @@ -135,8 +135,13 @@ public function eq(string $key, bool $immutable = true): self * @return mixed * @throws ErrorException */ - public function validateInData(string $key, string $validate, array $args = []): bool + public function validateInData(string $key, string $validate, array|string ...$args): bool { + + if(isset($args[0][0])) { + $args = Traverse::value($args)->flatten()->toArray(); + } + $inp = $this->eq($key, false); if(!method_exists($inp, $validate)) { throw new BadMethodCallException("Method '$validate' does not exist in " . __CLASS__ . " class."); diff --git a/src/Validators/DNS.php b/src/Validators/DNS.php index 5de5065..2927d4a 100755 --- a/src/Validators/DNS.php +++ b/src/Validators/DNS.php @@ -293,6 +293,6 @@ protected function getHost(string $host): string define('INTL_IDNA_VARIANT_2003', 0); } $variant = (defined('INTL_IDNA_VARIANT_UTS46')) ? INTL_IDNA_VARIANT_UTS46 : INTL_IDNA_VARIANT_2003; - return rtrim(idn_to_ascii($host, IDNA_DEFAULT, $variant), '.') . 'src'; + return rtrim(idn_to_ascii($host, IDNA_DEFAULT, $variant), '.'); } } diff --git a/tests/unitary-validate-inp.php b/tests/unitary-validate-inp.php index 512d365..7a2c930 100755 --- a/tests/unitary-validate-inp.php +++ b/tests/unitary-validate-inp.php @@ -5,11 +5,13 @@ * when used in MaplePHP framework you can skip the "bash code" at top and the "autoload file"! */ +use MaplePHP\Unitary\TestCase; use MaplePHP\Unitary\Unit; +use MaplePHP\Validate\ValidationChain; use MaplePHP\Validate\Validator; $unit = new Unit(); -$unit->case("MaplePHP input validate test", function() { +$unit->group("MaplePHP input validate test", function(TestCase $inst) { $strVal = Validator::value("TestStringValue"); $testStrValidates = ["isString", "required", "hasValue"]; @@ -156,11 +158,11 @@ $this->add(Validator::value("daniel@creativearmy.se")->isDeliverableEmail(), [ "equal" => [true], - ], "wdwq required to be true"); + ], "isDeliverableEmail failed"); $this->add(Validator::value("daniel@creativearmy.se")->dns()->isMxRecord(), [ "equal" => [true], - ], "wdwq required to be true"); + ], "isMxRecord failed"); $this->add(Validator::value("examplethatwillfail.se")->dns()->isAddressRecord(), [ "equal" => [false], @@ -196,5 +198,28 @@ "equal" => [true], ], "Expect required to be true"); + + $validPool = new ValidationChain("john.doe@gmail.com"); + + $validPool->isEmail() + ->length(1, 16) + ->isEmail() + ->notIsPhone() + ->endsWith(".net"); + + + $inst->validate($validPool->isValid(), function(ValidationChain $inst) { + $inst->isFalse(); + }); + + $inst->validate($validPool->hasError(), function(ValidationChain $inst) { + $inst->isTrue(); + }); + + $inst->validate(count($validPool->getFailedValidations()), function(ValidationChain $inst) { + $inst->isEqualTo(2); + }); + //echo $this->listAllProxyMethods(Validator::class, isolateClass: true); + //echo $this->listAllProxyMethods(Validator::class, "not", isolateClass: true); }); From da38a4a272a581f8ac59fa42a44f10d0478871ea Mon Sep 17 00:00:00 2001 From: Daniel Ronkainen Date: Sun, 4 May 2025 00:41:59 +0200 Subject: [PATCH 16/21] Code quality improvents --- src/Inp.php | 3 ++- src/Traits/InpAliases.php | 7 ++++--- src/ValidationChain.php | 22 +++++++++++----------- src/Validator.php | 32 ++++++++++++++++---------------- src/Validators/DNS.php | 15 ++++++++------- src/Validators/Luhn.php | 1 + 6 files changed, 42 insertions(+), 38 deletions(-) diff --git a/src/Inp.php b/src/Inp.php index 23037c8..17d07bd 100755 --- a/src/Inp.php +++ b/src/Inp.php @@ -1,4 +1,5 @@ isResolvableHost(); @@ -184,4 +185,4 @@ public function upperAtoZ(): bool { return $this->isUpperAlpha(); } -} \ No newline at end of file +} diff --git a/src/ValidationChain.php b/src/ValidationChain.php index 2fcf49d..ab2e030 100644 --- a/src/ValidationChain.php +++ b/src/ValidationChain.php @@ -229,7 +229,7 @@ public function __call(string $name, array $arguments): self ->shift($rest) ->implode() ->toString(); - if($rest === "not") { + if ($rest === "not") { $name = "!" . $newName; } $this->validateWith($name, $arguments); @@ -239,10 +239,10 @@ public function __call(string $name, array $arguments): self /** * You can add a name to error keys * - * @param string $key + * @param string|null $key * @return $this */ - public function mapErrorToKey(string $key): self + public function mapErrorToKey(?string $key): self { $this->key = $key; return $this; @@ -251,10 +251,10 @@ public function mapErrorToKey(string $key): self /** * You can overwrite the expected validation name on error * - * @param string $key + * @param string|null $key * @return $this */ - public function mapErrorValidationName(string $key): self + public function mapErrorValidationName(?string $key): self { $this->validationName = $key; return $this; @@ -276,28 +276,28 @@ public function validateWith(string $name, array|string ...$arguments): bool } $inp = new Validator($this->value); - if(!method_exists($inp, $name)) { + if (!method_exists($inp, $name)) { throw new BadMethodCallException("Method $name does not exist in class " . Validator::class . "."); } - if(isset($arguments[0][0])) { + if (isset($arguments[0][0])) { $arguments = Traverse::value($arguments)->flatten()->toArray(); } $valid = $inp->$name(...$arguments); // If using the traverse method in Validator - if($valid instanceof Validator) { + if ($valid instanceof Validator) { throw new BadMethodCallException("The method ->$name() is not supported with " . __CLASS__ . ". Use ->validateInData() instead!"); } - if($invert) { + if ($invert) { $valid = !$valid; } $name = !is_null($this->validationName) ? $this->validationName : $name; - if(!is_null($this->key)) { + if (!is_null($this->key)) { $this->error[$this->key][$name] = !$valid; } else { $this->error[][$name] = !$valid; @@ -347,4 +347,4 @@ public function isValid(): bool { return !$this->getError(); } -} \ No newline at end of file +} diff --git a/src/Validator.php b/src/Validator.php index fbad5c0..0ffc99e 100755 --- a/src/Validator.php +++ b/src/Validator.php @@ -1,4 +1,5 @@ value) || is_numeric($this->value)) { + if (is_string($this->value) || is_numeric($this->value)) { $this->length = $this->getLength((string)$this->value); $this->getStr = new Str($this->value); } @@ -113,9 +114,9 @@ public static function value(mixed $value): self public function eq(string $key, bool $immutable = true): self { $value = $this->value; - if(is_array($this->value) || is_object($this->value)) { + if (is_array($this->value) || is_object($this->value)) { $value = Traverse::value($this->value)->eq($key)->get(); - if(!$immutable && $value !== false) { + if (!$immutable && $value !== false) { $this->value = $value; $this->init(); return $this; @@ -131,19 +132,18 @@ public function eq(string $key, bool $immutable = true): self * * @param string $key * @param string $validate - * @param array $args + * @param mixed $args The MIXED value to be passed to the validate method + * @return bool * @return mixed * @throws ErrorException */ - public function validateInData(string $key, string $validate, array|string ...$args): bool + public function validateInData(string $key, string $validate, mixed ...$args): bool { - - if(isset($args[0][0])) { + if (isset($args[0][0])) { $args = Traverse::value($args)->flatten()->toArray(); } - $inp = $this->eq($key, false); - if(!method_exists($inp, $validate)) { + if (!method_exists($inp, $validate)) { throw new BadMethodCallException("Method '$validate' does not exist in " . __CLASS__ . " class."); } return $inp->{$validate}(...$args); @@ -193,10 +193,10 @@ public function luhn(): Luhn */ public function dns(): DNS { - if(is_null($this->dns)) { + if (is_null($this->dns)) { $value = $this->value; $position = Traverse::value($this->value)->strPosition("@")->toInt(); - if($position > 0) { + if ($position > 0) { $value = Traverse::value($this->value)->strSubstr($position + 1)->toString(); } $this->dns = new DNS($value); @@ -598,7 +598,7 @@ public function isFile(): bool * * @return bool */ - function isFileOrDirectory(): bool + public function isFileOrDirectory(): bool { return file_exists($this->value); } @@ -718,7 +718,7 @@ public function isArrayEmpty(): bool */ public function itemsAreTruthy(string|int|float $key): bool { - if($this->isArray()) { + if ($this->isArray()) { $count = Arr::value($this->value) ->filter(fn ($item) => $item->flatten()->{$key}->toBool()) ->count(); @@ -735,7 +735,7 @@ public function itemsAreTruthy(string|int|float $key): bool */ public function hasTruthyItem(string|int|float $key): bool { - if($this->isArray()) { + if ($this->isArray()) { $count = Arr::value($this->value) ->filter(fn ($item) => $item->flatten()->{$key}->toBool()) ->count(); @@ -1138,7 +1138,7 @@ public function oneOf(array $arr): bool $valid = false; foreach ($arr as $method => $args) { $inst = new self($this->value); - if(call_user_func_array([$inst, $method], $args)) { + if (call_user_func_array([$inst, $method], $args)) { $valid = true; } } @@ -1156,7 +1156,7 @@ public function allOf(array $arr): bool { foreach ($arr as $method => $args) { $inst = new self($this->value); - if(!call_user_func_array([$inst, $method], $args)) { + if (!call_user_func_array([$inst, $method], $args)) { return false; } } diff --git a/src/Validators/DNS.php b/src/Validators/DNS.php index 2927d4a..89b96f6 100755 --- a/src/Validators/DNS.php +++ b/src/Validators/DNS.php @@ -1,4 +1,5 @@ 'DNS_A', // Host Address DNS_CNAME => 'DNS_CNAME', // Canonical Name Record DNS_HINFO => 'DNS_HINFO', // Host Information @@ -29,7 +30,7 @@ class DNS DNS_ALL => 'DNS_ALL', // All Records DNS_ANY => 'DNS_ANY', // Any Records ]; - + private string $host; public function __construct(string $host) @@ -80,7 +81,7 @@ public function isCnameRecord(): bool { return checkdnsrr($this->host, 'CNAME'); } - + /** * Check if the host contains an 'A' record in the DNS. @@ -94,7 +95,7 @@ public function isARecord(): bool { return checkdnsrr($this->host, 'A'); } - + /** * Checks if the host contains an 'AAAA' record in the DNS. @@ -270,7 +271,7 @@ public function isHinfoRecord(): bool */ public function getDnsRecordForType(int $type): array|false { - if(!isset(self::ALOWED_DNS_TYPES[$type])) { + if (!isset(self::ALOWED_DNS_TYPES[$type])) { throw new InvalidArgumentException('Invalid DNS type. Use one of ' . implode(', ', self::ALOWED_DNS_TYPES) . '.'); } $result = dns_get_record($this->host, $type); @@ -279,10 +280,10 @@ public function getDnsRecordForType(int $type): array|false } return false; } - + /** * Get hosts (used for DNS checks) - * + * * @noinspection PhpComposerExtensionStubsInspection * @param string $host * @return string diff --git a/src/Validators/Luhn.php b/src/Validators/Luhn.php index f346445..e580fd0 100755 --- a/src/Validators/Luhn.php +++ b/src/Validators/Luhn.php @@ -1,4 +1,5 @@ Date: Sun, 4 May 2025 16:46:13 +0200 Subject: [PATCH 17/21] Add instance check validation --- src/ValidationChain.php | 5 +++-- src/Validator.php | 23 +++++++++++++++++------ 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/ValidationChain.php b/src/ValidationChain.php index ab2e030..0916189 100644 --- a/src/ValidationChain.php +++ b/src/ValidationChain.php @@ -20,8 +20,8 @@ * @method self isTruthy() * @method self isFalse() * @method self isFalsy() - * @method self isInArray(array $haystack) - * @method self isLooselyInArray(array $haystack) + * @method self isInArray(mixed $needle) + * @method self isLooselyInArray(mixed $needle) * @method self keyExists(string|int|float $key) * @method self hasValue() * @method self isSocialNumber() @@ -68,6 +68,7 @@ * @method self toIntEqual(int $value) * @method self isLengthEqualTo(int $length) * @method self isEqualTo(mixed $expected) + * @method self isInstanceOf(object|string $instance) * @method self isLooselyEqualTo(mixed $expected) * @method self isNotEqualTo(mixed $value) * @method self isLooselyNotEqualTo(mixed $value) diff --git a/src/Validator.php b/src/Validator.php index 0ffc99e..36686f5 100755 --- a/src/Validator.php +++ b/src/Validator.php @@ -260,23 +260,23 @@ public function isFalsy(): bool /** * Strict data type validation check if a value exists in a given array * - * @param array $haystack + * @param mixed $needle * @return bool */ - public function isInArray(array $haystack): bool + public function isInArray(mixed $needle): bool { - return in_array($this->value, $haystack, true); + return in_array($needle, $this->value, true); } /** * Flexible data type validation check if a value exists in a given array * - * @param array $haystack + * @param mixed $needle * @return bool */ - public function isLooselyInArray(array $haystack): bool + public function isLooselyInArray(mixed $needle): bool { - return in_array($this->value, $haystack); + return in_array($needle, $this->value); } /** @@ -812,6 +812,17 @@ public function isEqualTo(mixed $expected): bool { return $this->value === $expected; } + + /** + * Strict data type validation check if value is an instance of the specified class or interface + * + * @param object|string $instance The class or interface name to check against + * @return bool + */ + public function isInstanceOf(object|string $instance): bool + { + return $this->value instanceof $instance; + } /** * Flexible data type validation check if loosely equals to the expected value From 9038cc1aa5d9cc804459902205a5aafff69c6808 Mon Sep 17 00:00:00 2001 From: Daniel Ronkainen Date: Fri, 9 May 2025 23:26:36 +0200 Subject: [PATCH 18/21] Imporve semantics refactor: replace function null checks with strict comparisons --- src/Traits/InpAliases.php | 4 +-- src/ValidationChain.php | 12 ++++--- src/Validator.php | 58 +++++++++++++++++++++++++++++----- tests/unitary-validate-inp.php | 10 ++++++ 4 files changed, 69 insertions(+), 15 deletions(-) diff --git a/src/Traits/InpAliases.php b/src/Traits/InpAliases.php index f994dd5..6dc0bcc 100644 --- a/src/Traits/InpAliases.php +++ b/src/Traits/InpAliases.php @@ -48,7 +48,7 @@ public function date(string $format = "Y-m-d"): bool public function hex(): bool { - return $this->isHex(); + return $this->isHexColor(); } public function strictPassword(int $length = 1): bool @@ -68,7 +68,7 @@ public function validVersion(bool $strict = false): bool public function moreThan(float|int $num): bool { - return $this->isMoreThan($num); + return $this->isGreaterThan($num); } public function lessThan(float|int $num): bool diff --git a/src/ValidationChain.php b/src/ValidationChain.php index 0916189..2d026de 100644 --- a/src/ValidationChain.php +++ b/src/ValidationChain.php @@ -16,6 +16,7 @@ * @method Luhn luhn() * @method DNS dns() * @method self isRequired() + * @method self hasResponse() * @method self isTrue() * @method self isTruthy() * @method self isFalse() @@ -73,7 +74,7 @@ * @method self isNotEqualTo(mixed $value) * @method self isLooselyNotEqualTo(mixed $value) * @method self isLessThan(int|float $num) - * @method self isMoreThan(int|float $num) + * @method self isGreaterThan(int|float $num) * @method self isValidVersion(bool $strict = '') * @method self versionCompare(string $withVersion, string $operator = '==') * @method self isLossyPassword(int $length = '1') @@ -105,6 +106,7 @@ * @method Luhn notLuhn() * @method DNS notDns() * @method self notIsRequired() + * @method self notHasResponse() * @method self notIsTrue() * @method self notIsTruthy() * @method self notIsFalse() @@ -157,11 +159,12 @@ * @method self notToIntEqual(int $value) * @method self notIsLengthEqualTo(int $length) * @method self notIsEqualTo(mixed $expected) + * @method self notIsInstanceOf(object|string $instance) * @method self notIsLooselyEqualTo(mixed $expected) * @method self notIsNotEqualTo(mixed $value) * @method self notIsLooselyNotEqualTo(mixed $value) * @method self notIsLessThan(int|float $num) - * @method self notIsMoreThan(int|float $num) + * @method self notIsGreaterThan(int|float $num) * @method self notIsValidVersion(bool $strict = '') * @method self notVersionCompare(string $withVersion, string $operator = '==') * @method self notIsLossyPassword(int $length = '1') @@ -286,7 +289,6 @@ public function validateWith(string $name, array|string ...$arguments): bool } $valid = $inp->$name(...$arguments); - // If using the traverse method in Validator if ($valid instanceof Validator) { throw new BadMethodCallException("The method ->$name() is not supported with " . @@ -297,8 +299,8 @@ public function validateWith(string $name, array|string ...$arguments): bool $valid = !$valid; } - $name = !is_null($this->validationName) ? $this->validationName : $name; - if (!is_null($this->key)) { + $name = $this->validationName !== null ? $this->validationName : $name; + if ($this->key !== null) { $this->error[$this->key][$name] = !$valid; } else { $this->error[][$name] = !$valid; diff --git a/src/Validator.php b/src/Validator.php index 36686f5..05edcb1 100755 --- a/src/Validator.php +++ b/src/Validator.php @@ -180,7 +180,7 @@ public function getValue(): mixed */ public function luhn(): Luhn { - if (is_null($this->luhn)) { + if ($this->luhn === null) { $this->luhn = new Luhn($this->value); } return $this->luhn; @@ -193,7 +193,7 @@ public function luhn(): Luhn */ public function dns(): DNS { - if (is_null($this->dns)) { + if ($this->dns === null) { $value = $this->value; $position = Traverse::value($this->value)->strPosition("@")->toInt(); if ($position > 0) { @@ -217,6 +217,16 @@ public function isRequired(): bool return false; } + /** + * Will only check if there is a value + * + * @return bool + */ + public function hasResponse(): bool + { + return $this->length(1); + } + /** * Strict data type validation check if is false * @@ -407,7 +417,7 @@ public function endsWith(string $needle): bool */ public function findInString(string $match, ?int $pos = null): bool { - return ((is_null($pos) && str_contains($this->value, $match)) || + return (($pos === null && str_contains($this->value, $match)) || (strpos($this->value, $match) === $pos)); } @@ -418,7 +428,7 @@ public function findInString(string $match, ?int $pos = null): bool */ public function isPhone(): bool { - if (is_null($this->getStr)) { + if ($this->getStr === null) { return false; } $val = (string)$this->getStr->replace([" ", "-", "—", "–", "(", ")"], ["", "", "", "", "", ""]); @@ -437,7 +447,7 @@ public function isPhone(): bool */ public function isZip(int $minLength, ?int $maxLength = null): bool { - if (is_null($this->getStr)) { + if ($this->getStr === null) { return false; } $this->value = (string)$this->getStr->replace([" ", "-", "—", "–"], ["", "", "", ""]); @@ -570,7 +580,7 @@ public function isBoolVal(): bool */ public function isNull(): bool { - return is_null($this->value); + return $this->value === null; } /** @@ -875,11 +885,33 @@ public function isLessThan(float|int $num): bool * @param float|int $num * @return bool */ - public function isMoreThan(float|int $num): bool + public function isGreaterThan(float|int $num): bool { return ($this->value > $num); } + /** + * Check if the value is at least to specified number in parameter + * + * @param float|int $num + * @return bool + */ + public function isAtLeast(float|int $num): bool + { + return ($this->value >= $num); + } + + /** + * Check if the value is at most to specified number in parameter + * + * @param float|int $num + * @return bool + */ + public function isAtMost(float|int $num): bool + { + return ($this->value <= $num); + } + /** * Check is a valid version number * @@ -986,11 +1018,21 @@ public function isUpperAlpha(): bool * * @return bool */ - public function isHex(): bool + public function isHexColor(): bool { return ((int)preg_match('/^#([0-9A-F]{3}){1,2}$/i', $this->value) > 0); } + + public function isHexString(?int $length = null): bool + { + if ($length !== null && strlen($this->value) !== $length) { + return false; + } + return preg_match('/^[a-f0-9]+$/i', $this->value) === 1; + } + + /** * Check if is a date * diff --git a/tests/unitary-validate-inp.php b/tests/unitary-validate-inp.php index 7a2c930..6fd9c95 100755 --- a/tests/unitary-validate-inp.php +++ b/tests/unitary-validate-inp.php @@ -26,6 +26,16 @@ "equal" => [false], ], "Expect socialNumber to be false"); + + $this->add(Validator::value("#CCC")->isHexColor(), [ + "equal" => [true], + ], "Expect isHexColor to be true"); + + $this->add(Validator::value("#F1F1F1")->isHexColor(), [ + "equal" => [true], + ], "Expect isHexColor to be true"); + + $this->add(Validator::value("4030000010001234")->creditCard(), [ "equal" => [true], ], "Expect creditCard to be true"); From 03e98242793b8149b55c69948a1e395f0181147a Mon Sep 17 00:00:00 2001 From: Daniel Ronkainen Date: Fri, 9 May 2025 23:31:24 +0200 Subject: [PATCH 19/21] Add validation annotation --- src/ValidationChain.php | 6 ++++-- src/Validator.php | 7 +++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/ValidationChain.php b/src/ValidationChain.php index 2d026de..e8a7695 100644 --- a/src/ValidationChain.php +++ b/src/ValidationChain.php @@ -83,7 +83,8 @@ * @method self isAlpha() * @method self isLowerAlpha() * @method self isUpperAlpha() - * @method self isHex() + * @method self isHexColor() + * @method self isHexString(?int $length = null) * @method self isDate(string $format = 'Y-m-d') * @method self isDateWithTime() * @method self isTime(bool $withSeconds = '') @@ -173,7 +174,8 @@ * @method self notIsAlpha() * @method self notIsLowerAlpha() * @method self notIsUpperAlpha() - * @method self notIsHex() + * @method self notIsHexColor() + * @method self notIsHexString(?int $length = null) * @method self notIsDate(string $format = 'Y-m-d') * @method self notIsDateWithTime() * @method self notIsTime(bool $withSeconds = '') diff --git a/src/Validator.php b/src/Validator.php index 05edcb1..364b833 100755 --- a/src/Validator.php +++ b/src/Validator.php @@ -1024,6 +1024,13 @@ public function isHexColor(): bool } + /** + * Check if the value is a valid hexadecimal string + * Validates that the string contains only hexadecimal characters (0-9, a-f, A-F) + * + * @param int|null $length Optional specific length the hex string must match + * @return bool Returns true if the string is valid hex, false otherwise + */ public function isHexString(?int $length = null): bool { if ($length !== null && strlen($this->value) !== $length) { From 7ebc81cb8b823dfe88e916667133a31ce2aa9d14 Mon Sep 17 00:00:00 2001 From: Daniel Ronkainen Date: Wed, 14 May 2025 21:06:36 +0200 Subject: [PATCH 20/21] Add more validations --- src/ValidationChain.php | 6 +++ src/Validator.php | 67 +++++++++++++++++++++++++++++++++- tests/unitary-validate-inp.php | 34 +++++++++++++++++ 3 files changed, 105 insertions(+), 2 deletions(-) diff --git a/src/ValidationChain.php b/src/ValidationChain.php index e8a7695..e177d15 100644 --- a/src/ValidationChain.php +++ b/src/ValidationChain.php @@ -97,6 +97,9 @@ * @method self isHttpSuccess() * @method self isHttpClientError() * @method self isHttpServerError() + * @method self isRequestMethod() + * @method self hasKey(string|int|float $key) + * @method self hasQueryParam(string $queryParamKey, ?string $queryParamValue = null) * @method self oneOf(array $arr) * @method self allOf(array $arr) * @method self dateRange(string $format = 'Y-m-d H:i') @@ -188,6 +191,9 @@ * @method self notIsHttpSuccess() * @method self notIsHttpClientError() * @method self notIsHttpServerError() + * @method self notIsRequestMethod() + * @method self notHasKey(string|int|float $key) + * @method self notHasQueryParam(string $queryParamKey, ?string $queryParamValue = null) * @method self notOneOf(array $arr) * @method self notAllOf(array $arr) * @method self notDateRange(string $format = 'Y-m-d H:i') diff --git a/src/Validator.php b/src/Validator.php index 364b833..d7a2b19 100755 --- a/src/Validator.php +++ b/src/Validator.php @@ -1023,7 +1023,6 @@ public function isHexColor(): bool return ((int)preg_match('/^#([0-9A-F]{3}){1,2}$/i', $this->value) > 0); } - /** * Check if the value is a valid hexadecimal string * Validates that the string contains only hexadecimal characters (0-9, a-f, A-F) @@ -1039,7 +1038,6 @@ public function isHexString(?int $length = null): bool return preg_match('/^[a-f0-9]+$/i', $this->value) === 1; } - /** * Check if is a date * @@ -1185,6 +1183,71 @@ public function isHttpServerError(): bool return $intVal >= 500 && $intVal < 600; } + /** + * Validate if the given string is a valid HTTP request method + * Valid methods are: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS + * + * @return bool Returns true if the method is valid, false otherwise + */ + public function isRequestMethod(): bool + { + return in_array(strtoupper($this->value), ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']); + } + + /** + * Check if a specific key exists in the current value (array/object) + * Uses Traverse to check key existence without flattening + * + * @param string|int|float $key The key to check for + * @return bool Returns true if the key exists, false otherwise + */ + public function hasKey(string|int|float $key): bool + { + $obj = new Traverse($this->value); + return $obj->eq($key)->toBool(); + } + + /** + * Check if a specific key exists in a flattened array /object structure + * Uses Traverse to check key existence after flattening the hierarchical structure + * + * @param string|int|float $key The key to check for in the flattened structure + * @return bool Returns true if the key exists in a flattened structure, false otherwise + */ + public function hasFlattenKey(string|int|float $key): bool + { + $obj = new Traverse($this->value); + return $obj->flattenWithKeys()->eq($key)->toBool(); + } + + /** + * Check if a query parameter exists in the URL query string and optionally match its value + * + * @param string $queryParamKey The name of the query parameter to check + * @param string|null $queryParamValue Optional value to match against the query parameter + * @return bool Returns true if parameter exists and matches value (if provided), false otherwise + */ + public function hasQueryParam(string $queryParamKey, ?string $queryParamValue = null): bool + { + $obj = new Traverse($this->value); + $obj = $obj->parseStr()->eq($queryParamKey); + return $obj->toBool() && ($queryParamValue === null || $obj->toString() === $queryParamValue); + } + + /** + * Check if a query parameter exists and strictly matches a specific value + * + * @param string $queryParamKey The name of the query parameter to check + * @param mixed $queryParamValue The value to match against the query parameter + * @return bool Returns true if parameter exists and strictly matches the value + */ + public function isQueryParam(string $queryParamKey, mixed $queryParamValue): bool + { + if (!is_string($this->value)) { + parse_str($this->value, $params); + } + return isset($params[$queryParamKey]) && $params[$queryParamKey] === $queryParamValue; + } /** * Validate multiple. Will return true if "one" matches diff --git a/tests/unitary-validate-inp.php b/tests/unitary-validate-inp.php index 6fd9c95..43cdf1f 100755 --- a/tests/unitary-validate-inp.php +++ b/tests/unitary-validate-inp.php @@ -230,6 +230,40 @@ $inst->isEqualTo(2); }); + + $inst->validate(Validator::value("GET")->isRequestMethod(), function(ValidationChain $inst) { + $inst->istrue(); + }); + + $inst->validate(Validator::value("POST")->isRequestMethod(), function(ValidationChain $inst) { + $inst->istrue(); + }); + + $inst->validate(Validator::value("PUT")->isRequestMethod(), function(ValidationChain $inst) { + $inst->istrue(); + }); + + $inst->validate(Validator::value("DELETE")->isRequestMethod(), function(ValidationChain $inst) { + $inst->istrue(); + }); + + $inst->validate(Validator::value("PATCH")->isRequestMethod(), function(ValidationChain $inst) { + $inst->istrue(); + }); + + $inst->validate(Validator::value("HEAD")->isRequestMethod(), function(ValidationChain $inst) { + $inst->istrue(); + }); + + $inst->validate(Validator::value("OPTIONS")->isRequestMethod(), function(ValidationChain $inst) { + $inst->istrue(); + }); + + $inst->validate(Validator::value("options")->isRequestMethod(), function(ValidationChain $inst) { + $inst->istrue(); + }); + + //echo $this->listAllProxyMethods(Validator::class, isolateClass: true); //echo $this->listAllProxyMethods(Validator::class, "not", isolateClass: true); }); From b25722fec8baba293b96d8d3e4b2e123b6592311 Mon Sep 17 00:00:00 2001 From: Daniel Ronkainen Date: Sat, 17 May 2025 13:27:34 +0200 Subject: [PATCH 21/21] Pass inversion name to error data --- src/ValidationChain.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ValidationChain.php b/src/ValidationChain.php index e177d15..87bae16 100644 --- a/src/ValidationChain.php +++ b/src/ValidationChain.php @@ -308,6 +308,8 @@ public function validateWith(string $name, array|string ...$arguments): bool } $name = $this->validationName !== null ? $this->validationName : $name; + $name = ($invert) ? "not" . ucfirst($name) : $name; + if ($this->key !== null) { $this->error[$this->key][$name] = !$valid; } else {