├── src ├── Rules.php ├── Debug │ ├── ValidationCollection.php │ ├── icons │ │ └── validation.svg │ └── ValidationCollector.php ├── BaseValidator.php ├── BaseRules.php ├── Traits │ ├── FilesValidator.php │ └── Validator.php ├── Languages │ ├── pt-br │ │ └── validation.php │ ├── en │ │ └── validation.php │ └── es │ │ └── validation.php ├── FilesValidator.php ├── Validation.php └── Validator.php ├── README.md ├── LICENSE ├── .phpstorm.meta.php └── composer.json /src/Rules.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Validation; 11 | 12 | /** 13 | * Class Rules. 14 | * 15 | * @package validation 16 | */ 17 | class Rules extends BaseRules 18 | { 19 | use Traits\Validator; 20 | use Traits\FilesValidator; 21 | } 22 | -------------------------------------------------------------------------------- /src/Debug/ValidationCollection.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Validation\Debug; 11 | 12 | use Framework\Debug\Collection; 13 | 14 | /** 15 | * Class ValidationCollection. 16 | * 17 | * @package validation 18 | */ 19 | class ValidationCollection extends Collection 20 | { 21 | protected string $iconPath = __DIR__ . '/icons/validation.svg'; 22 | } 23 | -------------------------------------------------------------------------------- /src/Debug/icons/validation.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/BaseValidator.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Validation; 11 | 12 | use Framework\Helpers\ArraySimple; 13 | 14 | /** 15 | * Class BaseValidator. 16 | * 17 | * @package validation 18 | */ 19 | abstract class BaseValidator 20 | { 21 | /** 22 | * Get field value from data. 23 | * 24 | * @param string $field 25 | * @param array $data 26 | * 27 | * @return string|null 28 | */ 29 | protected static function getData(string $field, array $data) : ?string 30 | { 31 | $data = ArraySimple::value($field, $data); 32 | return \is_scalar($data) ? (string) $data : null; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Aplus Framework Validation Library 2 | 3 | # Aplus Framework Validation Library 4 | 5 | - [Home](https://aplus-framework.com/packages/validation) 6 | - [User Guide](https://docs.aplus-framework.com/guides/libraries/validation/index.html) 7 | - [API Documentation](https://docs.aplus-framework.com/packages/validation.html) 8 | 9 | [![tests](https://github.com/aplus-framework/validation/actions/workflows/tests.yml/badge.svg)](https://github.com/aplus-framework/validation/actions/workflows/tests.yml) 10 | [![coverage](https://coveralls.io/repos/github/aplus-framework/validation/badge.svg?branch=master)](https://coveralls.io/github/aplus-framework/validation?branch=master) 11 | [![packagist](https://img.shields.io/packagist/v/aplus/validation)](https://packagist.org/packages/aplus/validation) 12 | [![open-source](https://img.shields.io/badge/open--source-sponsor-magenta)](https://aplus-framework.com/sponsor) 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Natan Felles 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.phpstorm.meta.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PHPSTORM_META; 11 | 12 | registerArgumentsSet( 13 | 'rules', 14 | 'alpha', 15 | 'alphaNumber', 16 | 'array', 17 | 'base64', 18 | 'between', 19 | 'blank', 20 | 'bool', 21 | 'datetime', 22 | 'dim', 23 | 'email', 24 | 'empty', 25 | 'equals', 26 | 'ext', 27 | 'float', 28 | 'greater', 29 | 'greaterOrEqual', 30 | 'hex', 31 | 'hexColor', 32 | 'image', 33 | 'in', 34 | 'int', 35 | 'ip', 36 | 'isset', 37 | 'json', 38 | 'latin', 39 | 'length', 40 | 'less', 41 | 'lessOrEqual', 42 | 'maxDim', 43 | 'maxLength', 44 | 'maxSize', 45 | 'md5', 46 | 'mimes', 47 | 'minDim', 48 | 'minLength', 49 | 'notBetween', 50 | 'notEquals', 51 | 'notIn', 52 | 'notRegex', 53 | 'null', 54 | 'number', 55 | 'object', 56 | 'optional', 57 | 'regex', 58 | 'required', 59 | 'specialChar', 60 | 'string', 61 | 'timezone', 62 | 'uploaded', 63 | 'url', 64 | 'uuid', 65 | ); 66 | expectedArguments( 67 | \Framework\Validation\Validation::getMessage(), 68 | 1, 69 | argumentsSet('rules') 70 | ); 71 | expectedArguments( 72 | \Framework\Validation\Validation::isRuleAvailable(), 73 | 0, 74 | argumentsSet('rules') 75 | ); 76 | expectedArguments( 77 | \Framework\Validation\Validation::setMessage(), 78 | 1, 79 | argumentsSet('rules') 80 | ); 81 | expectedArguments( 82 | \Framework\Validation\Validation::setRule(), 83 | 1, 84 | argumentsSet('rules') 85 | ); 86 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aplus/validation", 3 | "description": "Aplus Framework Validation Library", 4 | "license": "MIT", 5 | "type": "library", 6 | "keywords": [ 7 | "validation", 8 | "validator", 9 | "files-validation" 10 | ], 11 | "authors": [ 12 | { 13 | "name": "Natan Felles", 14 | "email": "natanfelles@gmail.com", 15 | "homepage": "https://natanfelles.github.io" 16 | } 17 | ], 18 | "homepage": "https://aplus-framework.com/packages/validation", 19 | "support": { 20 | "email": "support@aplus-framework.com", 21 | "issues": "https://github.com/aplus-framework/validation/issues", 22 | "forum": "https://aplus-framework.com/forum", 23 | "source": "https://github.com/aplus-framework/validation", 24 | "docs": "https://docs.aplus-framework.com/guides/libraries/validation/" 25 | }, 26 | "funding": [ 27 | { 28 | "type": "Aplus Sponsor", 29 | "url": "https://aplus-framework.com/sponsor" 30 | } 31 | ], 32 | "require": { 33 | "php": ">=8.3", 34 | "ext-fileinfo": "*", 35 | "ext-intl": "*", 36 | "ext-json": "*", 37 | "aplus/debug": "^4.3", 38 | "aplus/helpers": "^4.0", 39 | "aplus/language": "^4.0" 40 | }, 41 | "require-dev": { 42 | "ext-xdebug": "*", 43 | "aplus/coding-standard": "^2.8", 44 | "ergebnis/composer-normalize": "^2.25", 45 | "jetbrains/phpstorm-attributes": "^1.0", 46 | "phpmd/phpmd": "^2.13", 47 | "phpstan/phpstan": "^1.9", 48 | "phpunit/phpunit": "^10.5" 49 | }, 50 | "minimum-stability": "dev", 51 | "prefer-stable": true, 52 | "autoload": { 53 | "psr-4": { 54 | "Framework\\Validation\\": "src/" 55 | } 56 | }, 57 | "autoload-dev": { 58 | "psr-4": { 59 | "Tests\\Validation\\": "tests/" 60 | } 61 | }, 62 | "config": { 63 | "allow-plugins": { 64 | "ergebnis/composer-normalize": true 65 | }, 66 | "optimize-autoloader": true, 67 | "preferred-install": "dist", 68 | "sort-packages": true 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/BaseRules.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Validation; 11 | 12 | use Error; 13 | use Stringable; 14 | 15 | /** 16 | * Class BaseRules. 17 | * 18 | * @property-read array $rules 19 | * 20 | * @package validation 21 | */ 22 | abstract class BaseRules implements Stringable 23 | { 24 | /** 25 | * @var array 26 | */ 27 | protected array $rules = []; 28 | 29 | /** 30 | * @since 2.3 31 | */ 32 | final public function __construct() 33 | { 34 | } 35 | 36 | public function __toString() : string 37 | { 38 | return \implode('|', $this->rules); 39 | } 40 | 41 | public function __get(string $property) : mixed 42 | { 43 | if ($property === 'rules') { 44 | return $this->rules; 45 | } 46 | throw new Error( 47 | 'Cannot access property ' . static::class . '::$' . $property 48 | ); 49 | } 50 | 51 | /** 52 | * Set field as optional. 53 | * 54 | * If field is undefined, validation passes. 55 | * 56 | * @return static 57 | */ 58 | public function optional() : static 59 | { 60 | $this->rules[] = 'optional'; 61 | return $this; 62 | } 63 | 64 | /** 65 | * If the field has a blank string, the validation passes. 66 | * 67 | * @since 2.2 68 | * 69 | * @return static 70 | */ 71 | public function blank() : static 72 | { 73 | $this->rules[] = 'blank'; 74 | return $this; 75 | } 76 | 77 | /** 78 | * If the field value is null, the validation passes. 79 | * 80 | * @since 2.2 81 | * 82 | * @return static 83 | */ 84 | public function null() : static 85 | { 86 | $this->rules[] = 'null'; 87 | return $this; 88 | } 89 | 90 | /** 91 | * If the field has an empty value, the validation passes. 92 | * 93 | * @since 2.2 94 | * 95 | * @return static 96 | */ 97 | public function empty() : static 98 | { 99 | $this->rules[] = 'empty'; 100 | return $this; 101 | } 102 | 103 | protected function esc(string $value) : string 104 | { 105 | return \strtr($value, [',' => '\,']); 106 | } 107 | 108 | /** 109 | * @param array $values 110 | * 111 | * @return string 112 | */ 113 | protected function implode(array $values) : string 114 | { 115 | foreach ($values as &$value) { 116 | $value = $this->esc((string) $value); 117 | } 118 | return \implode(',', $values); 119 | } 120 | 121 | public static function create() : static 122 | { 123 | return new static(); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/Traits/FilesValidator.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Validation\Traits; 11 | 12 | /** 13 | * Trait FilesValidator. 14 | * 15 | * @package validation 16 | */ 17 | trait FilesValidator 18 | { 19 | /** 20 | * Validates file is uploaded. 21 | * 22 | * @return static 23 | */ 24 | public function uploaded() : static 25 | { 26 | $this->rules[] = 'uploaded'; 27 | return $this; 28 | } 29 | 30 | /** 31 | * Validates file size. 32 | * 33 | * @param int $kilobytes 34 | * 35 | * @return static 36 | */ 37 | public function maxSize(int $kilobytes) : static 38 | { 39 | $this->rules[] = 'maxSize:' . $kilobytes; 40 | return $this; 41 | } 42 | 43 | /** 44 | * Validates file accepted MIME types. 45 | * 46 | * @param string ...$allowedTypes 47 | * 48 | * @return static 49 | */ 50 | public function mimes(string ...$allowedTypes) : static 51 | { 52 | $this->rules[] = 'mimes:' . $this->implode($allowedTypes); 53 | return $this; 54 | } 55 | 56 | /** 57 | * Validates file accepted extensions. 58 | * 59 | * NOTE: For greater security use the {@see FilesValidator::mimes()} method 60 | * to filter the file type. 61 | * 62 | * @param string ...$allowedExtensions 63 | * 64 | * @return static 65 | */ 66 | public function ext(string ...$allowedExtensions) : static 67 | { 68 | $this->rules[] = 'ext:' . $this->implode($allowedExtensions); 69 | return $this; 70 | } 71 | 72 | /** 73 | * Validates file is an image. 74 | * 75 | * @return static 76 | */ 77 | public function image() : static 78 | { 79 | $this->rules[] = 'image'; 80 | return $this; 81 | } 82 | 83 | /** 84 | * Validates image max dimensions. 85 | * 86 | * @param int $width 87 | * @param int $height 88 | * 89 | * @return static 90 | */ 91 | public function maxDim(int $width, int $height) : static 92 | { 93 | $this->rules[] = 'maxDim:' . $width . ',' . $height; 94 | return $this; 95 | } 96 | 97 | /** 98 | * Validates image min dimensions. 99 | * 100 | * @param int $width 101 | * @param int $height 102 | * 103 | * @return static 104 | */ 105 | public function minDim(int $width, int $height) : static 106 | { 107 | $this->rules[] = 'minDim:' . $width . ',' . $height; 108 | return $this; 109 | } 110 | 111 | /** 112 | * Validates image dimensions. 113 | * 114 | * @param int $width 115 | * @param int $height 116 | * 117 | * @return static 118 | */ 119 | public function dim(int $width, int $height) : static 120 | { 121 | $this->rules[] = 'dim:' . $width . ',' . $height; 122 | return $this; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/Languages/pt-br/validation.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | return [ 11 | 'alpha' => 'O campo {field} requer apenas caracteres alfabéticos.', 12 | 'alphaNumber' => 'O campo {field} requer apenas caracteres alfabéticos e numéricos.', 13 | 'number' => 'O campo {field} requer apenas caracteres numéricos.', 14 | 'uuid' => 'O campo {field} requer um UUID válido.', 15 | 'timezone' => 'O campo {field} requer uma timezone válida.', 16 | 'base64' => 'O campo {field} requer uma string base64 válida.', 17 | 'md5' => 'O campo {field} requer um hash MD5 válido.', 18 | 'hex' => 'O campo {field} requer uma string hexadecimal válida.', 19 | 'hexColor' => 'O campo {field} requer uma cor hexadecimal válida.', 20 | 'json' => 'O campo {field} requer uma string JSON válida.', 21 | 'regex' => 'O campo {field} não corresponde ao padrão requerido.', 22 | 'notRegex' => 'O campo {field} corresponde a um padrão inválido.', 23 | 'email' => 'O campo {field} requer um endereço de e-mail válido.', 24 | 'greater' => 'O campo {field} deve ser maior que {0}.', 25 | 'greaterOrEqual' => 'O campo {field} deve ser maior ou igual a {0}.', 26 | 'less' => 'O campo {field} deve ser menor que {0}.', 27 | 'lessOrEqual' => 'O campo {field} deve ser menor ou igual a {0}.', 28 | 'in' => 'O campo {field} não possui um valor permitido.', 29 | 'notIn' => 'O campo {field} possui um valor não permitido.', 30 | 'ip' => 'O campo {field} requer um endereço de IP válido.', 31 | 'url' => 'O campo {field} requer um endereço de URL válido.', 32 | 'datetime' => 'O campo {field} não corresponde ao formato de datetime requerido.', 33 | 'between' => 'O campo {field} deve estar entre {0} e {1}.', 34 | 'notBetween' => 'O campo {field} não pode estar entre {0} e {1}.', 35 | 'equals' => 'O campo {field} deve ser igual ao campo {0}.', 36 | 'notEquals' => 'O campo {field} não pode ser igual ao campo {0}.', 37 | 'maxLength' => 'O campo {field} requer {0} ou menos caracteres no tamanho.', 38 | 'minLength' => 'O campo {field} requer {0} ou mais caracteres no tamanho.', 39 | 'length' => 'O campo {field} requer exatamente {0} caracteres no tamanho.', 40 | 'required' => 'O campo {field} é requerido.', 41 | 'isset' => 'O campo {field} deve ser enviado.', 42 | 'latin' => 'O campo {field} requer apenas caracteres latinos.', 43 | 'uploaded' => 'O campo {field} requer que um arquivo seja carregado.', 44 | 'maxSize' => 'O campo {field} requer um arquivo que não exceda o tamanho máximo de {0} kilobytes.', 45 | 'ext' => 'O campo {field} requer um arquivo com uma extensão aceita: {args}.', 46 | 'mimes' => 'O campo {field} requer um arquivo com um tipo MIME aceito: {args}.', 47 | 'image' => 'O campo {field} requer uma imagem.', 48 | 'maxDim' => 'O campo {field} requer uma imagem que não exceda as dimensões máximas de {0} de largura e {1} de altura.', 49 | 'minDim' => 'O campo {field} requer uma imagem que possua as dimensões mínimas de {0} de largura e {1} de altura.', 50 | 'dim' => 'O campo {field} requer uma imagem com as dimensões exatas de {0} de largura e {1} de altura.', 51 | 'specialChar' => 'O campo {field} requer caracteres especiais.', 52 | 'optional' => 'O campo {field} é opcional. Se não estiver definido, a validação passa.', 53 | 'blank' => 'O campo {field} pode ter uma string em branco para passar.', 54 | 'null' => 'O campo {field} pode ter um valor nulo para passar.', 55 | 'empty' => 'O campo {field} pode ter um valor vazio para passar.', 56 | 'array' => 'O campo {field} requer uma array.', 57 | 'bool' => 'O campo {field} requer um valor booleano.', 58 | 'float' => 'O campo {field} requer um número de ponto flutuante.', 59 | 'int' => 'O campo {field} requer um número inteiro.', 60 | 'object' => 'O campo {field} requer um objeto.', 61 | 'string' => 'O campo {field} requer uma string.', 62 | 'slug' => 'O campo {field} requer uma slug válida.', 63 | ]; 64 | -------------------------------------------------------------------------------- /src/Languages/en/validation.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | return [ 11 | 'alpha' => 'The {field} field requires only alphabetic characters.', 12 | 'alphaNumber' => 'The {field} field requires only alphabetic and numeric characters.', 13 | 'number' => 'The {field} field requires only numeric characters.', 14 | 'uuid' => 'The {field} field requires a valid UUID.', 15 | 'timezone' => 'The {field} field requires a valid timezone.', 16 | 'base64' => 'The {field} field requires a valid base64 string.', 17 | 'md5' => 'The {field} field requires a valid MD5 hash.', 18 | 'hex' => 'The {field} field requires a valid hexadecimal string.', 19 | 'hexColor' => 'The {field} field requires a valid hexadecimal color.', 20 | 'json' => 'The {field} field requires a valid JSON string.', 21 | 'regex' => 'The {field} field does not matches the required pattern.', 22 | 'notRegex' => 'The {field} field matches a invalid pattern.', 23 | 'email' => 'The {field} field requires a valid email address.', 24 | 'greater' => 'The {field} field must be greater than {0}.', 25 | 'greaterOrEqual' => 'The {field} field must be greater than or equal to {0}.', 26 | 'less' => 'The {field} field must be less than {0}.', 27 | 'lessOrEqual' => 'The {field} field must be less than or equal to {0}.', 28 | 'in' => 'The {field} field does not have an allowed value.', 29 | 'notIn' => 'The {field} field has a disallowed value.', 30 | 'ip' => 'The {field} field requires a valid IP address.', 31 | 'url' => 'The {field} field requires a valid URL address.', 32 | 'datetime' => 'The {field} field does not match the required datetime format.', 33 | 'between' => 'The {field} field must be between {0} and {1}.', 34 | 'notBetween' => 'The {field} field can not be between {0} and {1}.', 35 | 'equals' => 'The {field} field must be equals the {0} field.', 36 | 'notEquals' => 'The {field} field can not be equals the {0} field.', 37 | 'maxLength' => 'The {field} field requires {0} or less characters in length.', 38 | 'minLength' => 'The {field} field requires {0} or more characters in length.', 39 | 'length' => 'The {field} field requires exactly {0} characters in length.', 40 | 'required' => 'The {field} field is required.', 41 | 'isset' => 'The {field} field must be sent.', 42 | 'latin' => 'The {field} field requires only latin characters.', 43 | 'uploaded' => 'The {field} field requires a file to be uploaded.', 44 | 'maxSize' => 'The {field} field requires a file that does not exceed the maximum size of {0} kilobytes.', 45 | 'ext' => 'The {field} field requires a file with an accepted extension: {args}.', 46 | 'mimes' => 'The {field} field requires a file with an accepted MIME type: {args}.', 47 | 'image' => 'The {field} field requires an image.', 48 | 'maxDim' => 'The {field} field requires an image that does not exceed the maximum dimensions of {0} in width and {1} in height.', 49 | 'minDim' => 'The {field} field requires an image having the minimum dimensions of {0} in width and {1} in height.', 50 | 'dim' => 'The {field} field requires an image with the exact dimensions of {0} in width and {1} in height.', 51 | 'specialChar' => 'The {field} field requires special characters.', 52 | 'optional' => 'The {field} field is optional. If undefined, validation passes.', 53 | 'blank' => 'The {field} field can have a blank string to pass.', 54 | 'null' => 'The {field} field can have a null value to pass.', 55 | 'empty' => 'The {field} field can have an empty value to pass.', 56 | 'array' => 'The {field} field requires an array.', 57 | 'bool' => 'The {field} field requires a boolean value.', 58 | 'float' => 'The {field} field requires a floating point number.', 59 | 'int' => 'The {field} field requires an integer.', 60 | 'object' => 'The {field} field requires an object.', 61 | 'string' => 'The {field} field requires a string.', 62 | 'slug' => 'The {field} field requires a valid slug.', 63 | ]; 64 | -------------------------------------------------------------------------------- /src/Languages/es/validation.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | return [ 11 | 'alpha' => 'El campo {field} sólo requiere caracteres alfabéticos.', 12 | 'alphaNumber' => 'El campo {field} sólo requiere caracteres alfabéticos y numéricos.', 13 | 'number' => 'El campo {field} sólo requiere caracteres numéricos.', 14 | 'uuid' => 'El campo {field} requiere un identificador UUID válido.', 15 | 'timezone' => 'El campo {field} requiere una zona horaria válida.', 16 | 'base64' => 'El campo {field} requiere una codificación base64 válida.', 17 | 'md5' => 'El campo {field} requiere un hash MD5 válido.', 18 | 'hex' => 'El campo {field} requiere una numeración hexadecimal válida.', 19 | 'hexColor' => 'El campo {field} requiere un color hexadecimal válido.', 20 | 'json' => 'El campo {field} requiere un formato JSON válido.', 21 | 'regex' => 'El campo {field} no coincide con el estándar requerido.', 22 | 'notRegex' => 'El campo {field} coincide con un estándar no válido.', 23 | 'email' => 'El campo {field} requiere una dirección de correo electrónico válida.', 24 | 'greater' => 'El campo {field} debe ser mayor que {0}.', 25 | 'greaterOrEqual' => 'El campo {field} debe ser mayor o igual que {0}.', 26 | 'less' => 'El campo {field} debe ser menor que {0}.', 27 | 'lessOrEqual' => 'El campo {field} debe ser menor o igual que {0}.', 28 | 'in' => 'El campo {field} no tiene un valor permitido.', 29 | 'notIn' => 'El campo {field} tiene un valor no permitido.', 30 | 'ip' => 'El campo {field} requiere una dirección de IP válida.', 31 | 'url' => 'El campo {field} requiere una dirección URL válida.', 32 | 'datetime' => 'El campo {field} no coincide con el formato fecha/hora requerido.', 33 | 'between' => 'El campo {field} debe estar entre {0} y {1}.', 34 | 'notBetween' => 'El campo {field} no puede estar entre {0} y {1}.', 35 | 'equals' => 'El campo {field} debe ser igual al campo {0}.', 36 | 'notEquals' => 'El campo {field} no puede ser igual al campo {0}.', 37 | 'maxLength' => 'El campo {field} requiere {0} o menos cantidad de caracteres.', 38 | 'minLength' => 'El campo {field} requiere {0} o más cantidad de caracteres.', 39 | 'length' => 'El campo {field} requiere exactamente {0} caracteres.', 40 | 'required' => 'El campo {field} es requerido.', 41 | 'isset' => 'El campo {field} debe enviarse.', 42 | 'latin' => 'El campo {field} sólo requiere caracteres latinos.', 43 | 'uploaded' => 'El campo {field} requiere que se cargue un archivo.', 44 | 'maxSize' => 'El campo {field} requiere un archivo que no exceda el tamaño máximo de {0} kilobytes.', 45 | 'ext' => 'El campo {field} requiere un archivo con una extensión aceptada: {args}.', 46 | 'mimes' => 'El campo {field} requiere un archivo con un tipo MIME aceptado: {args}.', 47 | 'image' => 'El campo {field} requiere una imagen.', 48 | 'maxDim' => 'El campo {field} requiere una imagen que no exceda las dimensiones máximas de {0} de ancho y {1} de alto. ', 49 | 'minDim' => 'El campo {field} requiere una imagen que tenga las dimensiones mínimas de {0} de ancho y {1} de alto.', 50 | 'dim' => 'El campo {field} requiere una imagen con las dimensiones exactas de {0} de ancho y {1} de alto.', 51 | 'specialChar' => 'El campo {field} requiere caracteres especiales.', 52 | 'optional' => 'El campo {field} es opcional. Si no se establece, la validación pasa.', 53 | 'blank' => 'El campo {field} puede tener un texto en blanco para pasar.', 54 | 'null' => 'El campo {field} puede tener un valor nulo para pasar.', 55 | 'empty' => 'El campo {field} puede tener un valor vacío para pasar.', 56 | 'array' => 'El campo {field} requiere una matriz.', 57 | 'bool' => 'El campo {field} requiere un valor lógico.', 58 | 'float' => 'El campo {field} requiere un número flotante.', 59 | 'int' => 'El campo {field} requiere un número entero.', 60 | 'object' => 'El campo {field} requiere un objeto.', 61 | 'string' => 'El campo {field} requiere un texto.', 62 | 'slug' => 'El campo {field} requiere una slug válida.', 63 | ]; 64 | -------------------------------------------------------------------------------- /src/FilesValidator.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Validation; 11 | 12 | use Framework\Helpers\ArraySimple; 13 | 14 | /** 15 | * Class FilesValidator. 16 | * 17 | * @package validation 18 | */ 19 | class FilesValidator 20 | { 21 | /** 22 | * @var array 23 | */ 24 | protected static array $files; 25 | 26 | /** 27 | * Get $_FILES in a re-organized way. 28 | * 29 | * @return array 30 | */ 31 | protected static function getOrganizedFiles() : array 32 | { 33 | return static::$files ?? (static::$files = ArraySimple::files()); 34 | } 35 | 36 | /** 37 | * @param string $field 38 | * 39 | * @return array|null 40 | */ 41 | protected static function getFile(string $field) : ?array 42 | { 43 | $files = static::getOrganizedFiles(); 44 | return ArraySimple::value($field, $files); 45 | } 46 | 47 | /** 48 | * Validates file is uploaded. 49 | * 50 | * @param string $field 51 | * @param array $data 52 | * 53 | * @return bool 54 | */ 55 | public static function uploaded(string $field, array $data = []) : bool 56 | { 57 | $file = static::getFile($field); 58 | if ($file === null) { 59 | return false; 60 | } 61 | return $file['error'] === \UPLOAD_ERR_OK && \is_uploaded_file($file['tmp_name']); 62 | } 63 | 64 | /** 65 | * Validates file size. 66 | * 67 | * @param string $field 68 | * @param array $data 69 | * @param int $kilobytes 70 | * 71 | * @return bool 72 | */ 73 | public static function maxSize(string $field, array $data, int $kilobytes) : bool 74 | { 75 | $uploaded = static::uploaded($field); 76 | if (!$uploaded) { 77 | return false; 78 | } 79 | $file = static::getFile($field); 80 | return $file['size'] <= ($kilobytes * 1024); 81 | } 82 | 83 | /** 84 | * Validates file accepted MIME types. 85 | * 86 | * @param string $field 87 | * @param array $data 88 | * @param string ...$allowedTypes 89 | * 90 | * @return bool 91 | */ 92 | public static function mimes(string $field, array $data, string ...$allowedTypes) : bool 93 | { 94 | $uploaded = static::uploaded($field); 95 | if (!$uploaded) { 96 | return false; 97 | } 98 | $file = static::getFile($field); 99 | $mimeType = \mime_content_type($file['tmp_name']); 100 | return \in_array($mimeType, $allowedTypes, true); 101 | } 102 | 103 | /** 104 | * Validates file accepted extensions. 105 | * 106 | * NOTE: For greater security use the {@see FilesValidator::mimes()} method 107 | * to filter the file type. 108 | * 109 | * @param string $field 110 | * @param array $data 111 | * @param string ...$allowedExtensions 112 | * 113 | * @return bool 114 | */ 115 | public static function ext(string $field, array $data, string ...$allowedExtensions) : bool 116 | { 117 | $uploaded = static::uploaded($field); 118 | if (!$uploaded) { 119 | return false; 120 | } 121 | $file = static::getFile($field); 122 | foreach ($allowedExtensions as $extension) { 123 | if (\str_ends_with($file['name'], '.' . $extension)) { 124 | return true; 125 | } 126 | } 127 | return false; 128 | } 129 | 130 | /** 131 | * Validates file is an image. 132 | * 133 | * @param string $field 134 | * @param array $data 135 | * 136 | * @return bool 137 | */ 138 | public static function image(string $field, array $data = []) : bool 139 | { 140 | $uploaded = static::uploaded($field); 141 | if (!$uploaded) { 142 | return false; 143 | } 144 | $file = static::getFile($field); 145 | $mime = \mime_content_type($file['tmp_name']) ?: 'application/octet-stream'; 146 | return \str_starts_with($mime, 'image/'); 147 | } 148 | 149 | /** 150 | * Validates image max dimensions. 151 | * 152 | * @param string $field 153 | * @param array $data 154 | * @param int $width 155 | * @param int $height 156 | * 157 | * @return bool 158 | */ 159 | public static function maxDim(string $field, array $data, int $width, int $height) : bool 160 | { 161 | $isImage = static::image($field); 162 | if (!$isImage) { 163 | return false; 164 | } 165 | $file = static::getFile($field); 166 | $sizes = \getimagesize($file['tmp_name']); 167 | return !($sizes === false || $sizes[0] > $width || $sizes[1] > $height); 168 | } 169 | 170 | /** 171 | * Validates image min dimensions. 172 | * 173 | * @param string $field 174 | * @param array $data 175 | * @param int $width 176 | * @param int $height 177 | * 178 | * @return bool 179 | */ 180 | public static function minDim(string $field, array $data, int $width, int $height) : bool 181 | { 182 | $isImage = static::image($field); 183 | if (!$isImage) { 184 | return false; 185 | } 186 | $file = static::getFile($field); 187 | $sizes = \getimagesize($file['tmp_name']); 188 | return !($sizes === false || $sizes[0] < $width || $sizes[1] < $height); 189 | } 190 | 191 | /** 192 | * Validates image dimensions. 193 | * 194 | * @param string $field 195 | * @param array $data 196 | * @param int $width 197 | * @param int $height 198 | * 199 | * @return bool 200 | */ 201 | public static function dim(string $field, array $data, int $width, int $height) : bool 202 | { 203 | $isImage = static::image($field); 204 | if (!$isImage) { 205 | return false; 206 | } 207 | $file = static::getFile($field); 208 | $sizes = \getimagesize($file['tmp_name']); 209 | return !($sizes === false || $sizes[0] !== $width || $sizes[1] !== $height); 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /src/Debug/ValidationCollector.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Validation\Debug; 11 | 12 | use Framework\Debug\Collector; 13 | use Framework\Debug\Debugger; 14 | use Framework\Validation\Validation; 15 | use ReflectionMethod; 16 | 17 | /** 18 | * Class ValidationCollector. 19 | * 20 | * @package validation 21 | */ 22 | class ValidationCollector extends Collector 23 | { 24 | protected Validation $validation; 25 | /** 26 | * @var array> 27 | */ 28 | protected array $validatorsRules; 29 | 30 | public function setValidation(Validation $validation) : static 31 | { 32 | $this->validation = $validation; 33 | return $this; 34 | } 35 | 36 | public function getActivities() : array 37 | { 38 | $activities = []; 39 | foreach ($this->getData() as $index => $data) { 40 | $activities[] = [ 41 | 'collector' => $this->getName(), 42 | 'class' => static::class, 43 | 'description' => 'Run validation ' . ($index + 1), 44 | 'start' => $data['start'], 45 | 'end' => $data['end'], 46 | ]; 47 | } 48 | return $activities; 49 | } 50 | 51 | public function setErrorInDebugData(string $field, string $error, int $index = -1) : static 52 | { 53 | $data = $this->getData(); 54 | if ($index === -1) { 55 | $index = \array_key_last($data); 56 | if ($index === null) { 57 | return $this; 58 | } 59 | } 60 | $data[$index]['errors'][$field] = $error; 61 | $this->data = $data; 62 | return $this; 63 | } 64 | 65 | public function getContents() : string 66 | { 67 | if (!isset($this->validation)) { 68 | return '

A Validation instance has not been set in this collector.

'; 69 | } 70 | \ob_start(); ?> 71 | renderValidations() ?> 72 | validatorsRules = $this->getValidatorsRules(); ?> 73 |

Ruleset

74 | renderRuleset() ?> 75 |

Validators Rules

76 | renderValidatorsRules(); 78 | return \ob_get_clean(); // @phpstan-ignore-line 79 | } 80 | 81 | protected function renderValidations() : string 82 | { 83 | if (!$this->hasData()) { 84 | return '

Validation did not run.

'; 85 | } 86 | $count = \count($this->getData()); 87 | \ob_start(); ?> 88 |

Validation ran time. 89 |

90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | getData() as $index => $item): 104 | $count = \count($item['errors']); 105 | $errors = []; 106 | foreach ($item['errors'] as $field => $error) : 107 | $errors[] = [ 108 | 'field' => $field, 109 | 'error' => $error, 110 | ]; 111 | endforeach; ?> 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 127 | 128 |
#TypeErrors CountError FieldError MessageTime
129 | validation->getRules()) { 135 | return '

No rules have been set.

'; 136 | } 137 | \ob_start(); ?> 138 |

The following rules have been set:

139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | validation->getRuleset() as $index => $set): ?> 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 |
#FieldLabelRulePossible Error Message
sRule($set['rules'][0]['rule'], $this->validatorsRules) ?>
sRule($set['rules'][$i]['rule'], $this->validatorsRules) ?>
168 | 175 |

There are validatorsRules) ?> rules available:

176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | validatorsRules as $rule => $data) : ?> 187 | 188 | 189 | 194 | 200 | 201 | 202 | 203 | 204 |
RuleParamsMessage PatternValidator
190 | 191 |
192 | 193 |
195 |
validation->getLanguage()->render('validation', $rule)
198 |                                 ) ?>
199 |
205 | > 211 | */ 212 | protected function getValidatorsRules() : array 213 | { 214 | $rules = []; 215 | foreach (\array_reverse($this->validation->getValidators()) as $validator) { 216 | foreach (\get_class_methods($validator) as $method) { 217 | $method = (string) $method; 218 | if (\is_callable([$validator, $method])) { 219 | $params = []; 220 | $reflection = new ReflectionMethod($validator, $method); 221 | foreach ($reflection->getParameters() as $parameter) { 222 | $name = $parameter->getName(); 223 | if (\in_array($name, ['field', 'data'], true)) { 224 | continue; 225 | } 226 | $value = ''; 227 | if ($parameter->isDefaultValueAvailable()) { 228 | $value = $parameter->getDefaultValue(); 229 | $type = \get_debug_type($value); 230 | if ($type === 'string') { 231 | $value = "'" . \strtr($value, [ 232 | "'" => "\\'", 233 | ]) . "'"; 234 | } elseif ($type === 'null') { 235 | $value = 'null'; 236 | } 237 | $value = ' = ' . $value; 238 | } 239 | $name = '$' . $name; 240 | if ($parameter->isVariadic()) { 241 | $name = '...' . $name; 242 | } 243 | $params[] = $name . $value; 244 | } 245 | $rules[$method] = [ 246 | 'validator' => \is_string($validator) ? $validator : $validator::class, 247 | 'params' => \implode(', ', $params), 248 | ]; 249 | } 250 | } 251 | } 252 | $rules['optional'] = [ 253 | 'validator' => '', 254 | 'params' => '', 255 | ]; 256 | $rules['blank'] = [ 257 | 'validator' => '', 258 | 'params' => '', 259 | ]; 260 | $rules['null'] = [ 261 | 'validator' => '', 262 | 'params' => '', 263 | ]; 264 | $rules['empty'] = [ 265 | 'validator' => '', 266 | 'params' => '', 267 | ]; 268 | \ksort($rules); 269 | return $rules; 270 | } 271 | 272 | /** 273 | * @param string $rule 274 | * @param array $validatorsRules 275 | * 276 | * @return string 277 | */ 278 | protected function sRule(string $rule, array $validatorsRules) : string 279 | { 280 | if ($rule === 'optional' 281 | || \array_key_exists(\explode(':', $rule)[0], $validatorsRules) 282 | ) { 283 | return \htmlentities($rule); 284 | } 285 | return '' . \htmlentities($rule) . ''; 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /src/Traits/Validator.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Validation\Traits; 11 | 12 | use JetBrains\PhpStorm\Language; 13 | 14 | /** 15 | * Trait Validator. 16 | * 17 | * @package validation 18 | */ 19 | trait Validator 20 | { 21 | /** 22 | * Validates alphabetic characters. 23 | * 24 | * @return static 25 | */ 26 | public function alpha() : static 27 | { 28 | $this->rules[] = 'alpha'; 29 | return $this; 30 | } 31 | 32 | /** 33 | * Validates a number. 34 | * 35 | * @return static 36 | */ 37 | public function number() : static 38 | { 39 | $this->rules[] = 'number'; 40 | return $this; 41 | } 42 | 43 | /** 44 | * Validates a number or alphabetic characters. 45 | * 46 | * @return static 47 | */ 48 | public function alphaNumber() : static 49 | { 50 | $this->rules[] = 'alphaNumber'; 51 | return $this; 52 | } 53 | 54 | /** 55 | * Validates a UUID. 56 | * 57 | * @return static 58 | */ 59 | public function uuid() : static 60 | { 61 | $this->rules[] = 'uuid'; 62 | return $this; 63 | } 64 | 65 | /** 66 | * Validates a timezone. 67 | * 68 | * @return static 69 | */ 70 | public function timezone() : static 71 | { 72 | $this->rules[] = 'timezone'; 73 | return $this; 74 | } 75 | 76 | /** 77 | * Validates a base64 string. 78 | * 79 | * @return static 80 | */ 81 | public function base64() : static 82 | { 83 | $this->rules[] = 'base64'; 84 | return $this; 85 | } 86 | 87 | /** 88 | * Validates a md5 hash. 89 | * 90 | * @return static 91 | */ 92 | public function md5() : static 93 | { 94 | $this->rules[] = 'md5'; 95 | return $this; 96 | } 97 | 98 | /** 99 | * Validates a hexadecimal string. 100 | * 101 | * @return static 102 | */ 103 | public function hex() : static 104 | { 105 | $this->rules[] = 'hex'; 106 | return $this; 107 | } 108 | 109 | /** 110 | * Validates a hexadecimal color. 111 | * 112 | * @return static 113 | */ 114 | public function hexColor() : static 115 | { 116 | $this->rules[] = 'hexColor'; 117 | return $this; 118 | } 119 | 120 | /** 121 | * Validates a JSON string. 122 | * 123 | * @return static 124 | */ 125 | public function json() : static 126 | { 127 | $this->rules[] = 'json'; 128 | return $this; 129 | } 130 | 131 | /** 132 | * Validates a Regex pattern. 133 | * 134 | * @param string $pattern 135 | * 136 | * @return static 137 | */ 138 | public function regex(#[Language('RegExp')] string $pattern) : static 139 | { 140 | $this->rules[] = 'regex:' . $this->esc($pattern); 141 | return $this; 142 | } 143 | 144 | /** 145 | * Validates a Regex no matching pattern. 146 | * 147 | * @param string $pattern 148 | * 149 | * @return static 150 | */ 151 | public function notRegex(#[Language('RegExp')] string $pattern) : static 152 | { 153 | $this->rules[] = 'notRegex:' . $this->esc($pattern); 154 | return $this; 155 | } 156 | 157 | /** 158 | * Validate field has value equals other field. 159 | * 160 | * @param string $equalsField 161 | * 162 | * @return static 163 | */ 164 | public function equals(string $equalsField) : static 165 | { 166 | $this->rules[] = 'equals:' . $this->esc($equalsField); 167 | return $this; 168 | } 169 | 170 | /** 171 | * Validate field has not value equals other field. 172 | * 173 | * @param string $diffField 174 | * 175 | * @return static 176 | */ 177 | public function notEquals(string $diffField) : static 178 | { 179 | $this->rules[] = 'notEquals:' . $this->esc($diffField); 180 | return $this; 181 | } 182 | 183 | /** 184 | * Validate field between min and max values. 185 | * 186 | * @param int|string $min 187 | * @param int|string $max 188 | * 189 | * @return static 190 | */ 191 | public function between(int | string $min, int | string $max) : static 192 | { 193 | $this->rules[] = 'between:' . $this->esc((string) $min) . ',' . $this->esc((string) $max); 194 | return $this; 195 | } 196 | 197 | /** 198 | * Validate field not between min and max values. 199 | * 200 | * @param int|string $min 201 | * @param int|string $max 202 | * 203 | * @return static 204 | */ 205 | public function notBetween(int | string $min, int | string $max) : static 206 | { 207 | $this->rules[] = 'notBetween:' . $this->esc((string) $min) . ',' . $this->esc((string) $max); 208 | return $this; 209 | } 210 | 211 | /** 212 | * Validate field is in list. 213 | * 214 | * @param string $in 215 | * @param string ...$others 216 | * 217 | * @return static 218 | */ 219 | public function in(string $in, string ...$others) : static 220 | { 221 | $this->rules[] = 'in:' . $this->implode([$in, ...$others]); 222 | return $this; 223 | } 224 | 225 | /** 226 | * Validate field is not in list. 227 | * 228 | * @param string $notIn 229 | * @param string ...$others 230 | * 231 | * @return static 232 | */ 233 | public function notIn(string $notIn, string ...$others) : static 234 | { 235 | $this->rules[] = 'notIn:' . $this->implode([$notIn, ...$others]); 236 | return $this; 237 | } 238 | 239 | /** 240 | * Validates an IP. 241 | * 242 | * @param int $version 4, 6 or 0 to both 243 | * 244 | * @return static 245 | */ 246 | public function ip(int $version = 0) : static 247 | { 248 | $this->rules[] = 'ip:' . $version; 249 | return $this; 250 | } 251 | 252 | /** 253 | * Validates an URL. 254 | * 255 | * @return static 256 | */ 257 | public function url() : static 258 | { 259 | $this->rules[] = 'url'; 260 | return $this; 261 | } 262 | 263 | /** 264 | * Validates a datetime format. 265 | * 266 | * @param string $format 267 | * 268 | * @return static 269 | */ 270 | public function datetime(string $format = 'Y-m-d H:i:s') : static 271 | { 272 | $this->rules[] = 'datetime:' . $this->esc($format); 273 | return $this; 274 | } 275 | 276 | /** 277 | * Validates a email. 278 | * 279 | * @return static 280 | */ 281 | public function email() : static 282 | { 283 | $this->rules[] = 'email'; 284 | return $this; 285 | } 286 | 287 | /** 288 | * Validates is greater than. 289 | * 290 | * @param int|string $greaterThan 291 | * 292 | * @return static 293 | */ 294 | public function greater(int | string $greaterThan) : static 295 | { 296 | $this->rules[] = 'greater:' . $this->esc((string) $greaterThan); 297 | return $this; 298 | } 299 | 300 | /** 301 | * Validates is greater than or equal to. 302 | * 303 | * @param int|string $greaterThanOrEqualTo 304 | * 305 | * @return static 306 | */ 307 | public function greaterOrEqual(int | string $greaterThanOrEqualTo) : static 308 | { 309 | $this->rules[] = 'greaterOrEqual:' . $this->esc((string) $greaterThanOrEqualTo); 310 | return $this; 311 | } 312 | 313 | /** 314 | * Validates is less than. 315 | * 316 | * @param int|string $lessThan 317 | * 318 | * @return static 319 | */ 320 | public function less(int | string $lessThan) : static 321 | { 322 | $this->rules[] = 'less:' . $this->esc((string) $lessThan); 323 | return $this; 324 | } 325 | 326 | /** 327 | * Validates is less than or equal to. 328 | * 329 | * @param int|string $lessThanOrEqualTo 330 | * 331 | * @return static 332 | */ 333 | public function lessOrEqual(int | string $lessThanOrEqualTo) : static 334 | { 335 | $this->rules[] = 'lessOrEqual:' . $this->esc((string) $lessThanOrEqualTo); 336 | return $this; 337 | } 338 | 339 | /** 340 | * Validates a latin text. 341 | * 342 | * @return static 343 | */ 344 | public function latin() : static 345 | { 346 | $this->rules[] = 'latin'; 347 | return $this; 348 | } 349 | 350 | /** 351 | * Validates max length. 352 | * 353 | * @param int $maxLength 354 | * 355 | * @return static 356 | */ 357 | public function maxLength(int $maxLength) : static 358 | { 359 | $this->rules[] = 'maxLength:' . $maxLength; 360 | return $this; 361 | } 362 | 363 | /** 364 | * Validates min length. 365 | * 366 | * @param int $minLength 367 | * 368 | * @return static 369 | */ 370 | public function minLength(int $minLength) : static 371 | { 372 | $this->rules[] = 'minLength:' . $minLength; 373 | return $this; 374 | } 375 | 376 | /** 377 | * Validates exact length. 378 | * 379 | * @param int $length 380 | * 381 | * @return static 382 | */ 383 | public function length(int $length) : static 384 | { 385 | $this->rules[] = 'length:' . $length; 386 | return $this; 387 | } 388 | 389 | /** 390 | * Validates required value. 391 | * 392 | * @return static 393 | */ 394 | public function required() : static 395 | { 396 | $this->rules[] = 'required'; 397 | return $this; 398 | } 399 | 400 | /** 401 | * Validates field is set. 402 | * 403 | * @return static 404 | */ 405 | public function isset() : static 406 | { 407 | $this->rules[] = 'isset'; 408 | return $this; 409 | } 410 | 411 | /** 412 | * Validates array. 413 | * 414 | * @since 2.2 415 | * 416 | * @return static 417 | */ 418 | public function array() : static 419 | { 420 | $this->rules[] = 'array'; 421 | return $this; 422 | } 423 | 424 | /** 425 | * Validates boolean. 426 | * 427 | * @since 2.2 428 | * 429 | * @return static 430 | */ 431 | public function bool() : static 432 | { 433 | $this->rules[] = 'bool'; 434 | return $this; 435 | } 436 | 437 | /** 438 | * Validates float. 439 | * 440 | * @since 2.2 441 | * 442 | * @return static 443 | */ 444 | public function float() : static 445 | { 446 | $this->rules[] = 'float'; 447 | return $this; 448 | } 449 | 450 | /** 451 | * Validates integer. 452 | * 453 | * @since 2.2 454 | * 455 | * @return static 456 | */ 457 | public function int() : static 458 | { 459 | $this->rules[] = 'int'; 460 | return $this; 461 | } 462 | 463 | /** 464 | * Validates object. 465 | * 466 | * @since 2.2 467 | * 468 | * @return static 469 | */ 470 | public function object() : static 471 | { 472 | $this->rules[] = 'object'; 473 | return $this; 474 | } 475 | 476 | /** 477 | * Validates string. 478 | * 479 | * @since 2.2 480 | * 481 | * @return static 482 | */ 483 | public function string() : static 484 | { 485 | $this->rules[] = 'string'; 486 | return $this; 487 | } 488 | 489 | /** 490 | * Validates slug. 491 | * 492 | * @since 2.6 493 | * 494 | * @return static 495 | */ 496 | public function slug() : static 497 | { 498 | $this->rules[] = 'slug'; 499 | return $this; 500 | } 501 | 502 | /** 503 | * Validates special characters. 504 | * 505 | * @see https://owasp.org/www-community/password-special-characters 506 | * 507 | * @param int $quantity 508 | * @param string $characters 509 | * 510 | * @return static 511 | */ 512 | public function specialChar( 513 | int $quantity = 1, 514 | string $characters = '!"#$%&\'()*+,-./:;=<>?@[\]^_`{|}~' 515 | ) : static { 516 | $this->rules[] = 'specialChar:' . $quantity . ',' . $this->esc($characters); 517 | return $this; 518 | } 519 | } 520 | -------------------------------------------------------------------------------- /src/Validation.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Validation; 11 | 12 | use Framework\Helpers\ArraySimple; 13 | use Framework\Language\Language; 14 | use Framework\Validation\Debug\ValidationCollector; 15 | use InvalidArgumentException; 16 | use JetBrains\PhpStorm\Pure; 17 | 18 | /** 19 | * Class Validation. 20 | * 21 | * @package validation 22 | */ 23 | class Validation 24 | { 25 | /** 26 | * The labels used to replace field names. 27 | * 28 | * @var array The field names as keys and the labels as values 29 | */ 30 | protected array $labels = []; 31 | /** 32 | * The Validator rules. 33 | * 34 | * @var array|string>>> The 35 | * field names as keys and the rules and arguments as values 36 | */ 37 | protected array $rules = []; 38 | /** 39 | * The last errors. 40 | * 41 | * @var array|string>>> The 42 | * field names as keys and the rule and arguments as values 43 | */ 44 | protected array $errors = []; 45 | /** 46 | * Custom error messages. 47 | * 48 | * @var array> The field name as keys and an 49 | * associative array of rule names as keys and messages as values 50 | */ 51 | protected array $messages = []; 52 | /** 53 | * The current Validators. 54 | * 55 | * @var array Values are the Validators FQCN or 56 | * instances 57 | */ 58 | protected array $validators = []; 59 | /** 60 | * The Language instance. 61 | * 62 | * @var Language 63 | */ 64 | protected Language $language; 65 | protected ValidationCollector $debugCollector; 66 | 67 | /** 68 | * Validation constructor. 69 | * 70 | * @param array|null $validators 71 | * @param Language|null $language 72 | */ 73 | public function __construct(?array $validators = null, ?Language $language = null) 74 | { 75 | $defaultValidators = [ 76 | Validator::class, 77 | FilesValidator::class, 78 | ]; 79 | $this->validators = empty($validators) 80 | ? $defaultValidators 81 | : \array_reverse($validators); 82 | if ($language) { 83 | $this->setLanguage($language); 84 | } 85 | } 86 | 87 | public function setLanguage(?Language $language = null) : static 88 | { 89 | if ($language === null) { 90 | $language = new Language(); 91 | } 92 | $language->addDirectory(__DIR__ . '/Languages'); 93 | $this->language = $language; 94 | return $this; 95 | } 96 | 97 | public function getLanguage() : Language 98 | { 99 | if (!isset($this->language)) { 100 | $this->setLanguage(); 101 | } 102 | return $this->language; 103 | } 104 | 105 | /** 106 | * @return array 107 | */ 108 | public function getValidators() : array 109 | { 110 | return $this->validators; 111 | } 112 | 113 | /** 114 | * Reset the validation. 115 | * 116 | * @return static 117 | */ 118 | public function reset() : static 119 | { 120 | $this->labels = []; 121 | $this->rules = []; 122 | $this->errors = []; 123 | $this->messages = []; 124 | return $this; 125 | } 126 | 127 | /** 128 | * Set label for a field. 129 | * 130 | * @param string $field 131 | * @param string $label 132 | * 133 | * @return static 134 | */ 135 | public function setLabel(string $field, string $label) : static 136 | { 137 | $this->labels[$field] = $label; 138 | return $this; 139 | } 140 | 141 | /** 142 | * Get the label for a given field. 143 | * 144 | * @param string $field 145 | * 146 | * @return string|null 147 | */ 148 | #[Pure] 149 | public function getLabel(string $field) : ?string 150 | { 151 | return $this->labels[$field] ?? null; 152 | } 153 | 154 | /** 155 | * Get a list of all labels. 156 | * 157 | * @return array 158 | */ 159 | #[Pure] 160 | public function getLabels() : array 161 | { 162 | return $this->labels; 163 | } 164 | 165 | /** 166 | * Set fields labels. 167 | * 168 | * @param array $labels An associative array with fields as 169 | * keys and label as values 170 | * 171 | * @return static 172 | */ 173 | public function setLabels(array $labels) : static 174 | { 175 | $this->labels = []; 176 | foreach ($labels as $field => $label) { 177 | $this->setLabel($field, $label); 178 | } 179 | return $this; 180 | } 181 | 182 | /** 183 | * @return array 184 | */ 185 | public function getRuleset() : array 186 | { 187 | $result = []; 188 | foreach ($this->getRules() as $field => $rules) { 189 | $label = $this->getLabel($field); 190 | $tmp = [ 191 | 'field' => $field, 192 | 'label' => $label, 193 | 'rules' => [], 194 | ]; 195 | foreach ($rules as $rule) { 196 | $rule['args'] = $this->escapeArgs($rule['args']); // @phpstan-ignore-line 197 | $args = \implode(',', $rule['args']); 198 | $ruleString = $rule['rule'] . ($args === '' ? '' : ':' . $args); // @phpstan-ignore-line 199 | $tmp['rules'][] = [ 200 | 'rule' => $ruleString, 201 | 'message' => $this->getFilledMessage( 202 | $field, 203 | $rule['rule'], // @phpstan-ignore-line 204 | \array_merge( 205 | ['field' => $label ?? $field], 206 | $rule['args'] 207 | ) 208 | ), 209 | ]; 210 | } 211 | $result[] = $tmp; 212 | } 213 | return $result; 214 | } 215 | 216 | /** 217 | * @param array $args 218 | * 219 | * @return array 220 | */ 221 | protected function escapeArgs(array $args) : array 222 | { 223 | foreach ($args as &$arg) { 224 | $arg = \strtr($arg, [',' => '\,']); 225 | } 226 | return $args; 227 | } 228 | 229 | /** 230 | * @param string $rule 231 | * 232 | * @return array|string> 233 | */ 234 | protected function parseRule(string $rule) : array 235 | { 236 | $args = []; 237 | if (\str_contains($rule, ':')) { 238 | [$rule, $args] = \explode(':', $rule, 2); 239 | $args = (array) \preg_split('#(? ',']); 242 | } 243 | } 244 | return ['rule' => $rule, 'args' => $args]; // @phpstan-ignore-line 245 | } 246 | 247 | /** 248 | * @param string $rules 249 | * 250 | * @return array|string>> 251 | */ 252 | #[Pure] 253 | protected function extractRules(string $rules) : array 254 | { 255 | $result = []; 256 | $rules = (array) \preg_split('#(? '|']); 259 | $result[] = $this->parseRule($rule); 260 | } 261 | return $result; 262 | } 263 | 264 | /** 265 | * @param string $field 266 | * @param string $rule 267 | * @param array $args 268 | * 269 | * @return string 270 | */ 271 | public function getFilledMessage(string $field, string $rule, array $args = []) : string 272 | { 273 | $message = $this->getMessage($field, $rule); 274 | if ($message === null) { 275 | return $this->getLanguage()->render('validation', $rule, $args); 276 | } 277 | return $this->getLanguage()->formatMessage($message, $args); 278 | } 279 | 280 | /** 281 | * Get a list of current rules. 282 | * 283 | * @return array|string>>> 284 | */ 285 | #[Pure] 286 | public function getRules() : array 287 | { 288 | return $this->rules; 289 | } 290 | 291 | /** 292 | * Set rules for a given field. 293 | * 294 | * @param string $field 295 | * @param array|string $rules 296 | * 297 | * @return static 298 | */ 299 | public function setRule(string $field, array | string $rules) : static 300 | { 301 | if (\is_array($rules)) { 302 | foreach ($rules as &$rule) { 303 | $rule = $this->parseRule($rule); 304 | } 305 | unset($rule); 306 | $this->rules[$field] = $rules; // @phpstan-ignore-line 307 | return $this; 308 | } 309 | $this->rules[$field] = $this->extractRules($rules); 310 | return $this; 311 | } 312 | 313 | /** 314 | * Set field rules. 315 | * 316 | * @param array|string> $rules An associative array 317 | * with field as keys and values as rules 318 | * 319 | * @return static 320 | */ 321 | public function setRules(array $rules) : static 322 | { 323 | $this->rules = []; 324 | foreach ($rules as $field => $rule) { 325 | $this->setRule($field, $rule); 326 | } 327 | return $this; 328 | } 329 | 330 | /** 331 | * Get latest error for a given field. 332 | * 333 | * @param string $field 334 | * 335 | * @return string|null 336 | */ 337 | public function getError(string $field) : ?string 338 | { 339 | $error = $this->errors[$field] ?? null; 340 | if ($error === null) { 341 | return null; 342 | } 343 | $error['args']['args'] = $error['args'] ? \implode(', ', $error['args']) : ''; // @phpstan-ignore-line 344 | $error['args']['field'] = $this->getLabel($field) ?? $field; 345 | return $this->getFilledMessage($field, $error['rule'], $error['args']); // @phpstan-ignore-line 346 | } 347 | 348 | /** 349 | * Get latest errors. 350 | * 351 | * @return array 352 | */ 353 | public function getErrors() : array 354 | { 355 | $messages = []; 356 | foreach (\array_keys($this->errors) as $field) { 357 | $messages[$field] = $this->getError($field); 358 | } 359 | return $messages; 360 | } 361 | 362 | /** 363 | * @param string $field 364 | * @param string $rule 365 | * @param array $args 366 | * 367 | * @return static 368 | */ 369 | public function setError(string $field, string $rule, array $args = []) : static 370 | { 371 | // @phpstan-ignore-next-line 372 | $this->errors[$field] = [ 373 | 'rule' => $rule, 374 | 'args' => $args, 375 | ]; 376 | return $this; 377 | } 378 | 379 | /** 380 | * @param string $field 381 | * 382 | * @return bool 383 | */ 384 | public function hasError(string $field) : bool 385 | { 386 | return isset($this->errors[$field]); 387 | } 388 | 389 | /** 390 | * Set a custom error message for a field rule. 391 | * 392 | * @param string $field The field name 393 | * @param string $rule The field rule name 394 | * @param string $message The custom error message for the field rule 395 | * 396 | * @return static 397 | */ 398 | public function setMessage(string $field, string $rule, string $message) : static 399 | { 400 | $this->messages[$field][$rule] = $message; 401 | return $this; 402 | } 403 | 404 | /** 405 | * Get the custom error message from a field rule. 406 | * 407 | * @param string $field The field name 408 | * @param string $rule The rule name 409 | * 410 | * @return string|null The message string or null if the message is not set 411 | */ 412 | public function getMessage(string $field, string $rule) : ?string 413 | { 414 | return $this->messages[$field][$rule] ?? null; 415 | } 416 | 417 | /** 418 | * Set many custom error messages. 419 | * 420 | * @param array> $messages A multi-dimensional 421 | * array with field names as keys and values as arrays where the keys are 422 | * rule names and values are the custom error message strings 423 | * 424 | * @return static 425 | */ 426 | public function setMessages(array $messages) : static 427 | { 428 | $this->messages = []; 429 | foreach ($messages as $field => $rules) { 430 | foreach ($rules as $rule => $message) { 431 | $this->setMessage($field, $rule, $message); 432 | } 433 | } 434 | return $this; 435 | } 436 | 437 | /** 438 | * Get all custom error messages set. 439 | * 440 | * @return array> 441 | */ 442 | public function getMessages() : array 443 | { 444 | return $this->messages; 445 | } 446 | 447 | /** 448 | * @param string $rule 449 | * @param string $field 450 | * @param array $args 451 | * @param array $data 452 | * 453 | * @return bool 454 | */ 455 | protected function validateRule(string $rule, string $field, array $args, array $data) : bool 456 | { 457 | foreach ($this->validators as $validator) { 458 | if (\is_callable([$validator, $rule])) { 459 | return $validator::$rule($field, $data, ...$args); 460 | } 461 | } 462 | throw new InvalidArgumentException( 463 | "Validation rule '{$rule}' not found on field '{$field}'" 464 | ); 465 | } 466 | 467 | /** 468 | * @param string $field 469 | * @param array> $rules 470 | * @param array $data 471 | * 472 | * @since 2.2 Added "blank", "null" and "empty" rules 473 | * 474 | * @return bool 475 | */ 476 | protected function validateField(string $field, array $rules, array $data) : bool 477 | { 478 | $removeKeys = []; 479 | foreach ($rules as $key => $rule) { 480 | $fieldExists = \array_key_exists($field, $data); 481 | // Field is optional. If the field is undefined, validation passes. 482 | if ($rule['rule'] === 'optional') { 483 | $removeKeys[] = $key; 484 | if (!$fieldExists) { 485 | return true; 486 | } 487 | } 488 | // Field must be defined and can have a blank string. 489 | if ($rule['rule'] === 'blank') { 490 | $removeKeys[] = $key; 491 | if ($fieldExists && $data[$field] === '') { 492 | return true; 493 | } 494 | } 495 | // Field must be defined and can have a null value. 496 | if ($rule['rule'] === 'null') { 497 | $removeKeys[] = $key; 498 | if ($fieldExists && $data[$field] === null) { 499 | return true; 500 | } 501 | } 502 | // Field must be defined and can have an empty value 503 | if ($rule['rule'] === 'empty') { 504 | $removeKeys[] = $key; 505 | if ($fieldExists && empty($data[$field])) { 506 | return true; 507 | } 508 | } 509 | } 510 | foreach ($removeKeys as $removeKey) { 511 | unset($rules[$removeKey]); 512 | } 513 | $status = true; 514 | foreach ($rules as $rule) { 515 | $rule['args'] = $this->replaceArgs($rule['args'], $data); 516 | $status = $this->validateRule($rule['rule'], $field, $rule['args'], $data); 517 | if ($status !== true) { 518 | $rule = $this->setEqualsField($rule); 519 | $this->setError($field, $rule['rule'], $rule['args']); 520 | break; 521 | } 522 | } 523 | return $status; 524 | } 525 | 526 | /** 527 | * Replace argument placeholders with data values. 528 | * 529 | * @param array $args 530 | * @param array $data 531 | * 532 | * @return array 533 | */ 534 | protected function replaceArgs(array $args, array $data) : array 535 | { 536 | $result = []; 537 | foreach ($args as $arg) { 538 | if (\preg_match('#^{(\w+)}$#', $arg)) { 539 | $key = \substr($arg, 1, -1); 540 | if (isset($data[$key])) { 541 | $result[] = $data[$key]; 542 | continue; 543 | } 544 | } 545 | $result[] = $arg; 546 | } 547 | return $result; 548 | } 549 | 550 | /** 551 | * @param array $rule 552 | * 553 | * @return array 554 | */ 555 | #[Pure] 556 | protected function setEqualsField(array $rule) : array 557 | { 558 | if ($rule['rule'] === 'equals' || $rule['rule'] === 'notEquals') { 559 | $rule['args'][0] = $this->getLabel($rule['args'][0]) ?? $rule['args'][0]; 560 | } 561 | return $rule; 562 | } 563 | 564 | /** 565 | * @param array> $fieldRules 566 | * @param array $data 567 | * 568 | * @return bool 569 | */ 570 | protected function run(array $fieldRules, array $data) : bool 571 | { 572 | $this->errors = []; 573 | $result = true; 574 | foreach ($fieldRules as $field => $rules) { 575 | $status = $this->validateField($field, $rules, $data); 576 | if (!$status) { 577 | $result = false; 578 | } 579 | } 580 | return $result; 581 | } 582 | 583 | /** 584 | * Validate data with all rules. 585 | * 586 | * @param array $data 587 | * 588 | * @return bool 589 | */ 590 | public function validate(array $data) : bool 591 | { 592 | if (isset($this->debugCollector)) { 593 | $start = \microtime(true); 594 | $validated = $this->run($this->getRules(), $data); 595 | $end = \microtime(true); 596 | $this->debugCollector->addData([ 597 | 'start' => $start, 598 | 'end' => $end, 599 | 'validated' => $validated, 600 | 'errors' => $this->getErrors(), 601 | 'type' => 'all', 602 | ]); 603 | return $validated; 604 | } 605 | return $this->run($this->getRules(), $data); 606 | } 607 | 608 | /** 609 | * Validate only fields set on data. 610 | * 611 | * @param array $data 612 | * 613 | * @return bool 614 | */ 615 | public function validateOnly(array $data) : bool 616 | { 617 | if (isset($this->debugCollector)) { 618 | $start = \microtime(true); 619 | $validated = $this->validateOnlySet($data); 620 | $end = \microtime(true); 621 | $this->debugCollector->addData([ 622 | 'start' => $start, 623 | 'end' => $end, 624 | 'validated' => $validated, 625 | 'errors' => $this->getErrors(), 626 | 'type' => 'only', 627 | ]); 628 | return $validated; 629 | } 630 | return $this->validateOnlySet($data); 631 | } 632 | 633 | /** 634 | * @param array $data 635 | * 636 | * @return bool 637 | */ 638 | protected function validateOnlySet(array $data) : bool 639 | { 640 | $fieldRules = \array_intersect_key( 641 | $this->getRules(), 642 | ArraySimple::convert($data) 643 | ); 644 | return $this->run($fieldRules, $data); 645 | } 646 | 647 | /** 648 | * Tells if a rule is available in the current validators. 649 | * 650 | * @param string $rule 651 | * 652 | * @return bool 653 | */ 654 | public function isRuleAvailable(string $rule) : bool 655 | { 656 | if (\in_array($rule, [ 657 | 'blank', 658 | 'empty', 659 | 'null', 660 | 'optional', 661 | ])) { 662 | return true; 663 | } 664 | foreach ($this->validators as $validator) { 665 | if (\is_callable([$validator, $rule])) { 666 | return true; 667 | } 668 | } 669 | return false; 670 | } 671 | 672 | public function setDebugCollector(ValidationCollector $debugCollector) : static 673 | { 674 | $this->debugCollector = $debugCollector; 675 | $this->debugCollector->setValidation($this); 676 | return $this; 677 | } 678 | 679 | public function getDebugCollector() : ?ValidationCollector 680 | { 681 | return $this->debugCollector ?? null; 682 | } 683 | } 684 | -------------------------------------------------------------------------------- /src/Validator.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Validation; 11 | 12 | use Framework\Helpers\ArraySimple; 13 | use InvalidArgumentException; 14 | use JetBrains\PhpStorm\Language; 15 | 16 | /** 17 | * Class Validator. 18 | * 19 | * @package validation 20 | */ 21 | class Validator extends BaseValidator 22 | { 23 | /** 24 | * Validates alphabetic characters. 25 | * 26 | * @param string $field 27 | * @param array $data 28 | * 29 | * @return bool 30 | */ 31 | public static function alpha(string $field, array $data) : bool 32 | { 33 | $data = static::getData($field, $data); 34 | return $data !== null && \ctype_alpha($data); 35 | } 36 | 37 | /** 38 | * Validates a number. 39 | * 40 | * @param string $field 41 | * @param array $data 42 | * 43 | * @return bool 44 | */ 45 | public static function number(string $field, array $data) : bool 46 | { 47 | $data = static::getData($field, $data); 48 | return \is_numeric($data); 49 | } 50 | 51 | /** 52 | * Validates a number or alphabetic characters. 53 | * 54 | * @param string $field 55 | * @param array $data 56 | * 57 | * @return bool 58 | */ 59 | public static function alphaNumber(string $field, array $data) : bool 60 | { 61 | $data = static::getData($field, $data); 62 | return $data !== null && \ctype_alnum($data); 63 | } 64 | 65 | /** 66 | * Validates a UUID. 67 | * 68 | * @param string $field 69 | * @param array $data 70 | * 71 | * @return bool 72 | */ 73 | public static function uuid(string $field, array $data) : bool 74 | { 75 | $data = static::getData($field, $data); 76 | if ($data === null) { 77 | return false; 78 | } 79 | if ($data === '00000000-0000-0000-0000-000000000000') { 80 | return false; 81 | } 82 | return \preg_match( 83 | '/^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$/', 84 | $data 85 | ) === 1; 86 | } 87 | 88 | /** 89 | * Validates a timezone. 90 | * 91 | * @param string $field 92 | * @param array $data 93 | * 94 | * @return bool 95 | */ 96 | public static function timezone(string $field, array $data) : bool 97 | { 98 | return static::in($field, $data, ...\DateTimeZone::listIdentifiers()); 99 | } 100 | 101 | /** 102 | * Validates a base64 string. 103 | * 104 | * @param string $field 105 | * @param array $data 106 | * 107 | * @return bool 108 | */ 109 | public static function base64(string $field, array $data) : bool 110 | { 111 | $data = static::getData($field, $data); 112 | if ($data === null) { 113 | return false; 114 | } 115 | $decoded = \base64_decode($data); 116 | return $decoded && \base64_encode($decoded) === $data; 117 | } 118 | 119 | /** 120 | * Validates a md5 hash. 121 | * 122 | * @param string $field 123 | * @param array $data 124 | * 125 | * @return bool 126 | */ 127 | public static function md5(string $field, array $data) : bool 128 | { 129 | $data = static::getData($field, $data); 130 | if ($data === null) { 131 | return false; 132 | } 133 | return (bool) \preg_match('/^[a-f0-9]{32}$/', $data); 134 | } 135 | 136 | /** 137 | * Validates a hexadecimal string. 138 | * 139 | * @param string $field 140 | * @param array $data 141 | * 142 | * @return bool 143 | */ 144 | public static function hex(string $field, array $data) : bool 145 | { 146 | $data = static::getData($field, $data); 147 | return $data !== null && \ctype_xdigit($data); 148 | } 149 | 150 | /** 151 | * Validates a hexadecimal color. 152 | * 153 | * @param string $field 154 | * @param array $data 155 | * 156 | * @return bool 157 | */ 158 | public static function hexColor(string $field, array $data) : bool 159 | { 160 | return static::regex($field, $data, '/^#([0-9A-Fa-f]{3}){1,2}$/'); 161 | } 162 | 163 | /** 164 | * Validates a JSON string. 165 | * 166 | * @param string $field 167 | * @param array $data 168 | * 169 | * @return bool 170 | */ 171 | public static function json(string $field, array $data) : bool 172 | { 173 | $data = static::getData($field, $data); 174 | if ($data === null) { 175 | return false; 176 | } 177 | return \json_validate($data); 178 | } 179 | 180 | /** 181 | * Validates a Regex pattern. 182 | * 183 | * @param string $field 184 | * @param array $data 185 | * @param string $pattern 186 | * 187 | * @return bool 188 | */ 189 | public static function regex( 190 | string $field, 191 | array $data, 192 | #[Language('RegExp')] 193 | string $pattern 194 | ) : bool { 195 | $data = static::getData($field, $data); 196 | return $data !== null && \preg_match($pattern, $data) === 1; 197 | } 198 | 199 | /** 200 | * Validates a Regex no matching pattern. 201 | * 202 | * @param string $field 203 | * @param array $data 204 | * @param string $pattern 205 | * 206 | * @return bool 207 | */ 208 | public static function notRegex( 209 | string $field, 210 | array $data, 211 | #[Language('RegExp')] 212 | string $pattern 213 | ) : bool { 214 | return !static::regex($field, $data, $pattern); 215 | } 216 | 217 | /** 218 | * Validate field has value equals other field. 219 | * 220 | * @param string $field 221 | * @param array $data 222 | * @param string $equalsField 223 | * 224 | * @return bool 225 | */ 226 | public static function equals(string $field, array $data, string $equalsField) : bool 227 | { 228 | $field = ArraySimple::value($field, $data); 229 | if (!\is_scalar($field)) { 230 | return false; 231 | } 232 | $equalsField = ArraySimple::value($equalsField, $data); 233 | if (!\is_scalar($equalsField)) { 234 | return false; 235 | } 236 | return (string) $field === (string) $equalsField; 237 | } 238 | 239 | /** 240 | * Validate field has not value equals other field. 241 | * 242 | * @param string $field 243 | * @param array $data 244 | * @param string $diffField 245 | * 246 | * @return bool 247 | */ 248 | public static function notEquals(string $field, array $data, string $diffField) : bool 249 | { 250 | return !static::equals($field, $data, $diffField); 251 | } 252 | 253 | /** 254 | * Validate field between min and max values. 255 | * 256 | * @param string $field 257 | * @param array $data 258 | * @param int|string $min 259 | * @param int|string $max 260 | * 261 | * @return bool 262 | */ 263 | public static function between( 264 | string $field, 265 | array $data, 266 | int | string $min, 267 | int | string $max 268 | ) : bool { 269 | $data = static::getData($field, $data); 270 | return $data !== null && $data >= $min && $data <= $max; 271 | } 272 | 273 | /** 274 | * Validate field not between min and max values. 275 | * 276 | * @param string $field 277 | * @param array $data 278 | * @param int|string $min 279 | * @param int|string $max 280 | * 281 | * @return bool 282 | */ 283 | public static function notBetween( 284 | string $field, 285 | array $data, 286 | int | string $min, 287 | int | string $max 288 | ) : bool { 289 | return !static::between($field, $data, $min, $max); 290 | } 291 | 292 | /** 293 | * Validate field is in list. 294 | * 295 | * @param string $field 296 | * @param array $data 297 | * @param string $in 298 | * @param string ...$others 299 | * 300 | * @return bool 301 | */ 302 | public static function in(string $field, array $data, string $in, string ...$others) : bool 303 | { 304 | $data = static::getData($field, $data); 305 | return $data !== null && \in_array($data, [$in, ...$others], true); 306 | } 307 | 308 | /** 309 | * Validate field is not in list. 310 | * 311 | * @param string $field 312 | * @param array $data 313 | * @param string $notIn 314 | * @param string ...$others 315 | * 316 | * @return bool 317 | */ 318 | public static function notIn(string $field, array $data, string $notIn, string ...$others) : bool 319 | { 320 | return !static::in($field, $data, $notIn, ...$others); 321 | } 322 | 323 | /** 324 | * Validates an IP. 325 | * 326 | * @param string $field 327 | * @param array $data 328 | * @param int|string $version 4, 6 or 0 to both 329 | * 330 | * @return bool 331 | */ 332 | public static function ip(string $field, array $data, int | string $version = 0) : bool 333 | { 334 | $data = static::getData($field, $data); 335 | if ($data === null) { 336 | return false; 337 | } 338 | $version = (int) $version; 339 | if ($version !== 0) { 340 | $version = match ($version) { 341 | 4 => \FILTER_FLAG_IPV4, 342 | 6 => \FILTER_FLAG_IPV6, 343 | default => throw new InvalidArgumentException( 344 | "Invalid IP Version: {$version}" 345 | ), 346 | }; 347 | } 348 | return \filter_var($data, \FILTER_VALIDATE_IP, $version) !== false; 349 | } 350 | 351 | /** 352 | * Validates an URL. 353 | * 354 | * @param string $field 355 | * @param array $data 356 | * 357 | * @return bool 358 | */ 359 | public static function url(string $field, array $data) : bool 360 | { 361 | $data = static::getData($field, $data); 362 | if ($data === null) { 363 | return false; 364 | } 365 | if (\preg_match('/^(?:([^:]*)\:)?\/\/(.+)$/', $data, $matches)) { 366 | if (!\in_array($matches[1], ['http', 'https'], true)) { 367 | return false; 368 | } 369 | $data = $matches[2]; 370 | } 371 | $data = 'http://' . $data; 372 | return \filter_var($data, \FILTER_VALIDATE_URL) !== false; 373 | } 374 | 375 | /** 376 | * Validates a datetime format. 377 | * 378 | * @param string $field 379 | * @param array $data 380 | * @param string $format 381 | * 382 | * @return bool 383 | */ 384 | public static function datetime( 385 | string $field, 386 | array $data, 387 | string $format = 'Y-m-d H:i:s' 388 | ) : bool { 389 | $data = static::getData($field, $data); 390 | if ($data === null) { 391 | return false; 392 | } 393 | $datetime = \DateTime::createFromFormat($format, $data); 394 | if ($datetime === false) { 395 | return false; 396 | } 397 | if ($datetime->format($format) !== $data) { 398 | return false; 399 | } 400 | return \DateTime::getLastErrors() === false; 401 | } 402 | 403 | /** 404 | * Validates a email. 405 | * 406 | * @param string $field 407 | * @param array $data 408 | * 409 | * @return bool 410 | */ 411 | public static function email(string $field, array $data) : bool 412 | { 413 | $data = static::getData($field, $data); 414 | if ($data === null) { 415 | return false; 416 | } 417 | if (\preg_match('#\A([^@]+)@(.+)\z#', $data, $matches)) { 418 | $data = $matches[1] . '@' . \idn_to_ascii($matches[2]); 419 | } 420 | return (bool) \filter_var($data, \FILTER_VALIDATE_EMAIL); 421 | } 422 | 423 | /** 424 | * Validates is greater than. 425 | * 426 | * @param string $field 427 | * @param array $data 428 | * @param int|string $greaterThan 429 | * 430 | * @return bool 431 | */ 432 | public static function greater( 433 | string $field, 434 | array $data, 435 | int | string $greaterThan 436 | ) : bool { 437 | $data = static::getData($field, $data); 438 | return $data !== null && $data > $greaterThan; 439 | } 440 | 441 | /** 442 | * Validates is greater than or equal to. 443 | * 444 | * @param string $field 445 | * @param array $data 446 | * @param int|string $greaterThanOrEqualTo 447 | * 448 | * @return bool 449 | */ 450 | public static function greaterOrEqual( 451 | string $field, 452 | array $data, 453 | int | string $greaterThanOrEqualTo 454 | ) : bool { 455 | $data = static::getData($field, $data); 456 | return $data !== null && $data >= $greaterThanOrEqualTo; 457 | } 458 | 459 | /** 460 | * Validates is less than. 461 | * 462 | * @param string $field 463 | * @param array $data 464 | * @param int|string $lessThan 465 | * 466 | * @return bool 467 | */ 468 | public static function less( 469 | string $field, 470 | array $data, 471 | int | string $lessThan 472 | ) : bool { 473 | $data = static::getData($field, $data); 474 | return $data !== null && $data < $lessThan; 475 | } 476 | 477 | /** 478 | * Validates is less than or equal to. 479 | * 480 | * @param string $field 481 | * @param array $data 482 | * @param int|string $lessThanOrEqualTo 483 | * 484 | * @return bool 485 | */ 486 | public static function lessOrEqual( 487 | string $field, 488 | array $data, 489 | int | string $lessThanOrEqualTo 490 | ) : bool { 491 | $data = static::getData($field, $data); 492 | return $data !== null && $data <= $lessThanOrEqualTo; 493 | } 494 | 495 | /** 496 | * Validates a latin text. 497 | * 498 | * @param string $field 499 | * @param array $data 500 | * 501 | * @return bool 502 | */ 503 | public static function latin(string $field, array $data) : bool 504 | { 505 | $data = static::getData($field, $data); 506 | return $data !== null && \preg_match('/^[\p{Latin}]+$/u', $data); 507 | } 508 | 509 | /** 510 | * Validates max length. 511 | * 512 | * @param string $field 513 | * @param array $data 514 | * @param int|string $maxLength 515 | * 516 | * @return bool 517 | */ 518 | public static function maxLength(string $field, array $data, int | string $maxLength) : bool 519 | { 520 | $data = static::getData($field, $data); 521 | return $data !== null && \mb_strlen($data) <= (int) $maxLength; 522 | } 523 | 524 | /** 525 | * Validates min length. 526 | * 527 | * @param string $field 528 | * @param array $data 529 | * @param int|string $minLength 530 | * 531 | * @return bool 532 | */ 533 | public static function minLength(string $field, array $data, int | string $minLength) : bool 534 | { 535 | $data = static::getData($field, $data); 536 | return $data !== null && \mb_strlen($data) >= (int) $minLength; 537 | } 538 | 539 | /** 540 | * Validates exact length. 541 | * 542 | * @param string $field 543 | * @param array $data 544 | * @param int|string $length 545 | * 546 | * @return bool 547 | */ 548 | public static function length(string $field, array $data, int | string $length) : bool 549 | { 550 | $data = static::getData($field, $data); 551 | return $data !== null && \mb_strlen($data) === (int) $length; 552 | } 553 | 554 | /** 555 | * Validates required value. 556 | * 557 | * @param string $field 558 | * @param array $data 559 | * 560 | * @return bool 561 | */ 562 | public static function required(string $field, array $data) : bool 563 | { 564 | $data = static::getData($field, $data); 565 | return $data !== null && \trim($data) !== ''; 566 | } 567 | 568 | /** 569 | * Validates field is set. 570 | * 571 | * @param string $field 572 | * @param array $data 573 | * 574 | * @return bool 575 | */ 576 | public static function isset(string $field, array $data) : bool 577 | { 578 | return static::getData($field, $data) !== null; 579 | } 580 | 581 | /** 582 | * Validates array. 583 | * 584 | * @param string $field 585 | * @param array $data 586 | * 587 | * @since 2.2 588 | * 589 | * @return bool 590 | */ 591 | public static function array(string $field, array $data) : bool 592 | { 593 | return \is_array(ArraySimple::value($field, $data)); 594 | } 595 | 596 | /** 597 | * Validates boolean. 598 | * 599 | * @param string $field 600 | * @param array $data 601 | * 602 | * @since 2.2 603 | * 604 | * @return bool 605 | */ 606 | public static function bool(string $field, array $data) : bool 607 | { 608 | return \is_bool(ArraySimple::value($field, $data)); 609 | } 610 | 611 | /** 612 | * Validates float. 613 | * 614 | * @param string $field 615 | * @param array $data 616 | * 617 | * @since 2.2 618 | * 619 | * @return bool 620 | */ 621 | public static function float(string $field, array $data) : bool 622 | { 623 | return \is_float(ArraySimple::value($field, $data)); 624 | } 625 | 626 | /** 627 | * Validates integer. 628 | * 629 | * @param string $field 630 | * @param array $data 631 | * 632 | * @since 2.2 633 | * 634 | * @return bool 635 | */ 636 | public static function int(string $field, array $data) : bool 637 | { 638 | return \is_int(ArraySimple::value($field, $data)); 639 | } 640 | 641 | /** 642 | * Validates object. 643 | * 644 | * @param string $field 645 | * @param array $data 646 | * 647 | * @since 2.2 648 | * 649 | * @return bool 650 | */ 651 | public static function object(string $field, array $data) : bool 652 | { 653 | return \is_object(ArraySimple::value($field, $data)); 654 | } 655 | 656 | /** 657 | * Validates string. 658 | * 659 | * @param string $field 660 | * @param array $data 661 | * 662 | * @since 2.2 663 | * 664 | * @return bool 665 | */ 666 | public static function string(string $field, array $data) : bool 667 | { 668 | return \is_string(ArraySimple::value($field, $data)); 669 | } 670 | 671 | /** 672 | * Validates slug. 673 | * 674 | * @param string $field 675 | * @param array $data 676 | * 677 | * @since 2.6 678 | * 679 | * @return bool 680 | */ 681 | public static function slug(string $field, array $data) : bool 682 | { 683 | $data = static::getData($field, $data); 684 | return $data !== null && \preg_match('/^([a-z0-9_-]+)$/', $data) === 1; 685 | } 686 | 687 | /** 688 | * Validates special characters. 689 | * 690 | * @see https://owasp.org/www-community/password-special-characters 691 | * 692 | * @param string $field 693 | * @param array $data 694 | * @param int|string $quantity 695 | * @param string $characters 696 | * 697 | * @return bool 698 | */ 699 | public static function specialChar( 700 | string $field, 701 | array $data, 702 | int | string $quantity = 1, 703 | string $characters = '!"#$%&\'()*+,-./:;=<>?@[\]^_`{|}~' 704 | ) : bool { 705 | $quantity = (int) $quantity; 706 | if ($quantity < 1) { 707 | throw new InvalidArgumentException('Special characters quantity must be greater than 0'); 708 | } 709 | $data = static::getData($field, $data); 710 | if ($data === null) { 711 | return false; 712 | } 713 | $data = (array) \preg_split('//u', $data, -1, \PREG_SPLIT_NO_EMPTY); 714 | $characters = (array) \preg_split('//u', $characters, -1, \PREG_SPLIT_NO_EMPTY); 715 | $found = 0; 716 | foreach ($characters as $char) { 717 | foreach ($data as $item) { 718 | if ($char === $item) { 719 | $found++; 720 | if ($found === $quantity) { 721 | return true; 722 | } 723 | break; 724 | } 725 | } 726 | } 727 | return false; 728 | } 729 | } 730 | --------------------------------------------------------------------------------