diff --git a/Inp.php b/Inp.php deleted file mode 100755 index c685d73..0000000 --- a/Inp.php +++ /dev/null @@ -1,829 +0,0 @@ -', - '=', - '==', - '>', - '>=', - 'eq', - 'ge', - 'gt', - 'le', - 'lt', - 'ne' - ]; - - private mixed $value; - private int $length = 0; - private DateTime $dateTime; - private ?Luhn $luhn = 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"); - if(is_string($value) || is_numeric($value)) { - $this->length = $this->getLength((string)$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); - } - - /** - * 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(); - } - - /** - * Access luhn validation class - * @return Luhn - */ - public function luhn(): Luhn - { - if (is_null($this->luhn)) { - $this->luhn = new Luhn($this->value); - } - return $this->luhn; - } - - /** - * Will check if value if empty (e.g. "", 0, NULL) = false - * @return bool - */ - public function required(): bool - { - if ($this->length(1) && !empty($this->value)) { - return true; - } - return false; - } - - /** - * Will only check if there is a value (e.g. 0) = true - * @return bool - */ - public function hasValue(): bool - { - return $this->length(1); - } - - /** - * Validate Swedish personal numbers - * @return bool - */ - public function socialNumber(): 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 - { - return $this->luhn()->orgNumber(); - } - - /** - * Validate credit card numbers (THIS needs to be tested) - * @return bool - */ - public function creditCard(): bool - { - return $this->luhn()->creditcard(); - } - - /** - * Validate Swedish vat number - * @return bool - */ - public function vatNumber(): bool - { - return $this->luhn()->vatNumber(); - } - - /** - * 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 email(): bool - { - return (filter_var($this->value, FILTER_VALIDATE_EMAIL) !== false); - } - - /** - * Find in string - * @param string $match keyword to match against - * @param int|null $pos match 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)); - } - - /** - * Check if is a phone number - * @return bool - */ - public function phone(): 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 $arg1 start length - * @param int|null $arg2 end length - * @return bool - * @throws ErrorException - */ - public function zip(int $arg1, ?int $arg2 = 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)); - } - - /** - * Is 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 value int - * 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 string - * @return bool - */ - public function isStr(): bool - { - return $this->isString(); - } - - /** - * 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 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 file - * @return bool - */ - public function isFile(): bool - { - return is_file($this->value); - } - - /** - * Is directory - * @return bool - */ - public function isDir(): bool - { - return is_dir($this->value); - } - - /** - * Is resource - * @return bool - */ - public function isResource(): bool - { - return is_resource($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 number - * @return bool - */ - public function number(): bool - { - return (is_numeric($this->value)); - } - - public function numeric(): bool - { - return $this->number(); - } - - public function numericVal(): bool - { - return $this->number(); - } - - /** - * Value is number positive 20 - * @return bool - */ - public function positive(): bool - { - return ((float)$this->value >= 0); - } - - /** - * Value is number negative -20 - * @return bool - */ - public function negative(): 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 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 - */ - public function max(float $int): bool - { - return ((float)$this->value <= $int); - } - - /** - * 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 - * @return bool - */ - public function length(int $arg1, ?int $arg2 = null): bool - { - if ($this->length >= $arg1 && (($arg2 === null) || $this->length <= $arg2)) { - return true; - } - return false; - } - - /** - * Value string length is equal to ($arg1) - * @param int $arg1 length - * @return bool - */ - public function equalLength(int $arg1): bool - { - if ($this->length === $arg1) { - return true; - } - return false; - } - - /** - * IF value equals to param - * @param $str - * @return bool - */ - public function equal($str): bool - { - return ($this->value === $str); - } - - /** - * IF value is less than to parameter - * @param $num - * @return bool - */ - public function lessThan($num): bool - { - return ($this->value < (float)$num); - } - - /** - * IF value is more than to parameter - * @param $num - * @return bool - */ - public function moreThan($num): bool - { - return ($this->value > (float)$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); - } - - /** - * 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 - { - $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 '!='|'<'|'<='|'<>'|'='|'=='|'>'|'>='|'eq'|'ge'|'gt'|'le'|'lt'|'ne' $operator - * @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 lossyPassword(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 strictPassword(int $length = 1): bool - { - $pattern = '/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&])[A-Za-z\d$@$!%*?&]{' . $length . ',}$/'; - return ((int)preg_match($pattern, $this->value) > 0); - } - - /** - * Is value is string and character between a-z or A-Z - * @param $matchStr - * @return bool - */ - 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 - */ - public function atoZ(): bool - { - return ((int)preg_match("/^[a-zA-Z]+$/", $this->value) > 0); - } - - /** - * Is value is string and character between a-z (LOWERCASE) - * @return bool - */ - public function lowerAtoZ(): bool - { - return ((int)preg_match("/^[a-z]+$/", $this->value) > 0); - } - - /** - * Is value is string and character between A-Z (UPPERCASE) - * @return bool - */ - public function upperAtoZ(): bool - { - return ((int)preg_match("/^[A-Z]+$/", $this->value) > 0); - } - - /** - * Is Hex color code string - * @return bool - */ - public function hex(): 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 date(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 - { - return $this->date($format); - } - - /** - * 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 time(string $format = "H:i"): bool - { - return $this->date($format); - } - - /** - * 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 $arg1 18: user should be 18 or older - * @return bool - * @throws Exception - */ - public function age(int $arg1): bool - { - $now = (int)$this->dateTime->format("Y"); - $dateTime = new DateTime($this->value); - $birth = (int)$dateTime->format("Y"); - $age = ($now - $birth); - return ($age >= $arg1); - } - - /** - * Check if is valid domain - * @param bool $strict stricter = true - * @return bool - */ - public function domain(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 url(): bool - { - return (filter_var($this->value, FILTER_VALIDATE_URL) !== false); - } - - /** - * Check if "Host|domain" has an valid DNS (will check A, AAAA and MX) - * @psalm-suppress UndefinedConstant - * @noinspection PhpComposerExtensionStubsInspection - * @return bool - */ - public function dns(): 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 - { - 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), '.') . '.'; - } - - /** - * 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; - } -} diff --git a/README.md b/README.md index 4e1834c..a8709ba 100755 --- a/README.md +++ b/README.md @@ -1,283 +1,462 @@ # 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 -``` + +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 `Validator` class. There are two ways to do this: ```php -use MaplePHP\Validate\Inp; +use MaplePHP\Validate\Validator; -// Validate option 1 -$inp = new Inp("Lorem ipsum dolor"); +// Option 1: Create an instance +$inp = new Validator("Lorem ipsum dolor"); var_dump($inp->length(1, 200)); // true -// Validate option 2 -$valid = Inp::value("Lorem ipsum dolor")->length(1, 200); +// Option 2: Use the static method for cleaner syntax +$valid = Validator::value("Lorem ipsum dolor")->length(1, 200); +var_dump($valid); // true +``` + +--- + +## Validating Nested Data + +You can traverse nested arrays or objects and validate specific values using dot notation: + +```php +$inp = new Validator([ + "user" => [ + "name" => "John Doe", + "email" => "john.doe@gmail.com", + ] +]); + +$valid = $inp->eq("user.name")->length(1, 200); + var_dump($valid); // true ``` +> 💡 You can also use `validateInData()` for more dynamic validations: +```php +$valid = $inp->validateInData("user.name", "length", [1, 200]); +``` + +--- + +## Using the Chain validations + +The `ValidationChain` class allows you to chain multiple validations on a single value and check the overall result: + +```php +use MaplePHP\Validate\ValidationChain; + +$validPool = new ValidationChain("john.doe@gmail.com"); + +$validPool->isEmail() + ->length(1, 200) + ->endsWith(".com"); + +$isValid = $validPool->isValid(); +// $hasError = $validPool->hasError(); + +var_dump($isValid); // true +``` + +> 🧠 `ValidationChain` is useful when you want to collect and evaluate multiple validation rules at once. + + ## Validations ### Required field ```php -Inp::value("Lorem ipsum dolor")->required(); +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")->equalLength(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 +Validator::value("Lorem ipsum dolor")->isEqualTo("Lorem ipsum dolor"); +``` + +- **Loosely Equals**: Flexible data type validation check if loosely equals to expected value +```php +Validator::value("Lorem ipsum dolor")->isLooselyEqualTo("Lorem ipsum dolor"); ``` -### Check if value equals or not equals another value -- **Equals**: +- **Not equals**: Strict data type validation check if not equals to expected value ```php -Inp::value("Lorem ipsum dolor")->equal("Lorem ipsum dolor"); +Validator::value("Lorem ipsum dolor")->isNotEqualTo("Lorem ipsum"); ``` -- **Not equals**: + +- **Loosely Not equals**: Flexible data type validation check if loosely not equals to expected value ```php -Inp::value("Lorem ipsum dolor")->notEqual("Lorem ipsum"); +Validator::value("Lorem ipsum dolor")->isLooselyNotEqualTo("Lorem ipsum"); ``` + - **More than**: ```php -Inp::value(200)->moreThan(100); +Validator::value(200)->isMoreThan(100); ``` + - **Less than**: ```php -Inp::value(100)->lessThan(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")->email(); +Validator::value("john@gmail.com")->isEmail(); ``` ### Validate if it's a valid phone number Allows numbers and special characters ("-", "+", " "). ```php -Inp::value("+46709676040")->phone(); +Validator::value("+46709676040")->isPhone(); ``` -### Validate Swedish personal number (personnummer) +### Validate Swedish personal number (personnel) ```php -Inp::value("198808213412")->socialNumber(); +Validator::value("198808213412")->isSocialNumber(); ``` ### Validate Swedish organization number ```php -Inp::value("197511043412")->orgNumber(); +Validator::value("197511043412")->isOrgNumber(); ``` ### Validate credit card number ```php -Inp::value("1616523623422334")->creditCard(); +Validator::value("1616523623422334")->isCreditCard(); ``` ### Validate VAT number ```php -Inp::value("SE8272267913")->vatNumber(); +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")->number(); +Validator::value(42)->isNumber(); ``` ### Check if value is positive or negative - **Positive**: ```php -Inp::value("20")->positive(); +Validator::value(20)->isPositive(); ``` - **Negative**: ```php -Inp::value("-20")->negative(); +Validator::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 +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")->lossyPassword(8); +Validator::value("password123")->isLossyPassword(8); ``` - **Strict password** (requires at least one lowercase, uppercase, digit, and special character): ```php -Inp::value("Password#123!")->strictPassword(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 +Validator::value("2022-02-13")->isDate(); +``` +Custom date validation ```php -Inp::value("2022-02-13")->date("Y-m-d"); +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")->dateTime("Y-m-d H:i"); +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")->time("H:i"); +Validator::value("14:15")->isTime(); +``` +Validate hour, minutes and seconds +```php +Validator::value("14:15:58")->isTime(true); ``` ### Check if someone is at least a certain age ```php -Inp::value("1988-05-22")->age(18); +Validator::value("1988-05-22")->isAge(18); ``` ### Check if it's a valid domain name ```php -Inp::value("example.com")->domain(); +Validator::value("example.com")->isDomain(); ``` ### Check if it's a valid URL (http/https is required) ```php -Inp::value("https://example.com/page")->url(); +Validator::value("https://example.com/page")->isUrl(); ``` ### Check if it's a valid DNS entry ```php -Inp::value("example.com")->dns(); +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")->zip(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 +Validator::value(["Apple", "Orange", "Lemon"])->isArray(); ``` -### Validate if value is an array, object, or resource +### Check if array is empty +```php +Validator::value(["Apple", "Orange", "Lemon"])->isArrayEmpty(); +``` + +### Strict data type validation check if value exists in given array +```php +Validator::value(["Apple", "Orange", "Lemon"])->isInArray(); +``` + +### Flexible data type validation check if value exists in given array +```php +Validator::value(["Apple", "Orange", "Lemon"])->isLooselyInArray(); +``` + +### Strict data type validation check if key exists in array +```php +Validator::value(["Apple", "Orange", "Lemon"])->keyExists(); +``` + +### Check if all items in array is truthy +```php +Validator::value(["1", true, "Lemon"])->itemsAreTruthy(); +``` + +### Check if truthy item exist in array +```php +Validator::value(["1", false, "Lemon"])->hasTruthyItem(); +``` + +### Check if array count is equal to length +```php +Validator::value(["Apple", "Orange", "Lemon"])->isCountEqualTo(3); +``` + +### Check if array count is more than the length +```php +Validator::value(["Apple", "Orange", "Lemon"])->isCountMoreThan(1); +``` + +### Check if array count is less than the length +```php +Validator::value(["Apple", "Orange", "Lemon"])->isCountLessThan(4); +``` + +### Check if value is a valid float +```php +Validator::value("Lorem ipsum dolor")->isString(); +``` + +## Validate types + +### Check if value is a valid float +```php +Validator::value("Lorem ipsum dolor")->isString(); +``` +### Check if value is a valid float +```php +Validator::value(3.1415)->isFloat(); +``` +### Check if value is a valid integer +```php +Validator::value(42)->isInt(); +``` +- **Is Boolean**: +```php +Validator::value(true)->isBool(); +``` +- **Is Boolean-like value** (e.g., "yes", "no", "1", "0"): +```php +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(); ``` +## HTTP status code validation -### Validate if value is boolean or interpretable as a boolean -- **Is Boolean**: +#### Strict data type validation check if value is a valid HTTP status code ```php -Inp::value(true)->isBool(); +Validator::value(403)->isHttpStatusCode(); ``` -- **Is Boolean-like value** (e.g., "yes", "no", "1", "0"): + +#### Strict data type validation check if value is HTTP 200 OK ```php -Inp::value("yes")->isBoolVal(); +Validator::value(200)->isHttp200(); ``` +#### Strict data type validation check if value is a 2xx success HTTP code +```php +Validator::value(210)->isHttpSuccess(); +``` + +#### Strict data type validation check if value is a 4xx client error HTTP code +```php +Validator::value(403)->isHttpClientError(); +``` + +#### Strict data type validation check if value is a 5xx server error HTTP code +```php +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/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/src/Inp.php b/src/Inp.php new file mode 100755 index 0000000..17d07bd --- /dev/null +++ b/src/Inp.php @@ -0,0 +1,15 @@ +isResolvableHost(); + } + + 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->isHexColor(); + } + + 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->isGreaterThan($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(); + } + + 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(); + } +} diff --git a/src/ValidationChain.php b/src/ValidationChain.php new file mode 100644 index 0000000..87bae16 --- /dev/null +++ b/src/ValidationChain.php @@ -0,0 +1,363 @@ +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 $this->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 + { + $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|null $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|null $key + * @return $this + */ + public function mapErrorValidationName(?string $key): self + { + $this->validationName = $key; + return $this; + } + + /** + * Access a validation from a Validator instance + * + * @param string $name + * @param array $arguments + * @return bool + * @throws ErrorException + */ + public function validateWith(string $name, array|string ...$arguments): bool + { + $invert = str_starts_with($name, "!"); + if ($invert) { + $name = substr($name, 1); + } + + $inp = new Validator($this->value); + 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 " . + __CLASS__ . ". Use ->validateInData() instead!"); + } + + if ($invert) { + $valid = !$valid; + } + + $name = $this->validationName !== null ? $this->validationName : $name; + $name = ($invert) ? "not" . ucfirst($name) : $name; + + if ($this->key !== null) { + $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 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. + * + * @return bool Returns true if there are validation errors, otherwise false. + */ + public function hasError(): bool + { + return !!$this->getError(); + } + + /** + * 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 + { + return !$this->getError(); + } +} diff --git a/src/Validator.php b/src/Validator.php new file mode 100755 index 0000000..d7a2b19 --- /dev/null +++ b/src/Validator.php @@ -0,0 +1,1308 @@ +', + '=', + '==', + '>', + '>=', + '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 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, mixed ...$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."); + } + 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 ($this->luhn === null) { + $this->luhn = new Luhn($this->value); + } + return $this->luhn; + } + + /** + * Access DNS validations + * + * @return DNS + */ + public function dns(): DNS + { + if ($this->dns === null) { + $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; + } + + /** + * 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 + * + * @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 mixed $needle + * @return bool + */ + public function isInArray(mixed $needle): bool + { + return in_array($needle, $this->value, true); + } + + /** + * Flexible data type validation check if a value exists in a given array + * + * @param mixed $needle + * @return bool + */ + public function isLooselyInArray(mixed $needle): bool + { + return in_array($needle, $this->value); + } + + /** + * 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 (($pos === null && str_contains($this->value, $match)) || + (strpos($this->value, $match) === $pos)); + } + + /** + * Checks if is a phone number + * + * @return bool + */ + public function isPhone(): bool + { + if ($this->getStr === null) { + 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 ($this->getStr === null) { + 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 $this->value === null; + } + + /** + * 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 + */ + public 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; + } + + /** + * 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 + * + * @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 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 + * + * @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 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) + * + * @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) { + return false; + } + return preg_match('/^[a-f0-9]+$/i', $this->value) === 1; + } + + /** + * 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 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 + * + * @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/Validators/DNS.php b/src/Validators/DNS.php new file mode 100755 index 0000000..89b96f6 --- /dev/null +++ b/src/Validators/DNS.php @@ -0,0 +1,299 @@ + '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 the 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 the 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 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, + * 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/Luhn.php b/src/Validators/Luhn.php similarity index 86% rename from Luhn.php rename to src/Validators/Luhn.php index bc6cea7..e580fd0 100755 --- a/Luhn.php +++ b/src/Validators/Luhn.php @@ -1,33 +1,25 @@ 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 +96,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 +111,7 @@ public function creditcard(): bool /** * Get card type + * * @return string|bool */ public function cardPrefix(): string|bool @@ -145,12 +139,13 @@ public function cardPrefix(): string|bool } /** - * Validate Vat + * Validate if is a vat number, will work for multiple countries + * * @return 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(); @@ -162,7 +157,8 @@ public function vatNumber(): bool } /** - * Chech if is a date + * Check if is a date + * * @return boolean */ public function isDate(): bool @@ -175,12 +171,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 +188,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 +210,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 +231,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 +259,7 @@ final protected function part() /** * Get part + * * @param string $key * @return int */ diff --git a/ValidVatFormat.php b/src/Validators/Vat.php similarity index 94% rename from ValidVatFormat.php rename to src/Validators/Vat.php index d1044d3..3c650eb 100755 --- a/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 d3db92a..43cdf1f 100755 --- a/tests/unitary-validate-inp.php +++ b/tests/unitary-validate-inp.php @@ -5,13 +5,15 @@ * 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\Inp; +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 = Inp::value("TestStringValue"); + $strVal = Validator::value("TestStringValue"); $testStrValidates = ["isString", "required", "hasValue"]; foreach ($testStrValidates as $validate) { @@ -20,171 +22,248 @@ ], "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("#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"); - $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("examplethatwillfail.se")->dns(), [ + + $this->add(Validator::value("daniel@creativearmy.se")->isDeliverableEmail(), [ + "equal" => [true], + ], "isDeliverableEmail failed"); + + $this->add(Validator::value("daniel@creativearmy.se")->dns()->isMxRecord(), [ + "equal" => [true], + ], "isMxRecord failed"); + + $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"); + + $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); + }); + + + $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); });