├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── examples ├── ExampleValidator.php └── index.php ├── phpunit.xml ├── tests ├── BooleanValidatorTest.php ├── CompareValidatorTest.php ├── EmailValidatorTest.php ├── FileValidatorTest.php ├── NumberValidatorTest.php ├── PhoneValidatorTest.php ├── RangeValidatorTest.php ├── RequiredValidatorTest.php ├── StringValidatorTest.php ├── UniqueValidatorTest.php ├── UrlValidatorTest.php └── ValidatorTest.php └── valify ├── Validator.php └── validators ├── AbstractValidator.php ├── BooleanValidator.php ├── CompareValidator.php ├── EmailValidator.php ├── FileValidator.php ├── NumberValidator.php ├── PhoneValidator.php ├── RangeValidator.php ├── RequiredValidator.php ├── StringValidator.php ├── UniqueValidator.php └── UrlValidator.php /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /index.php 3 | /vendor -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 xphoenyx 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | __*NB! This project is archived thus not maintained anymore*__ 2 | 3 | # Valify 4 | A little framework for user input validation. It is still in development, so keep an eye on commits. 5 | Inspired by [Yii2 input validation implementation](https://github.com/yiisoft/yii2/blob/master/docs/guide/input-validation.md). 6 | 7 | ## Requirements 8 | You need PHP 5.4 to run this code. 9 | 10 | ## Installation 11 | After downloading source, add next code to file, where you data is going to be validated: 12 | 13 | ```php 14 | require 'valify/Validator.php'; 15 | $validator = new Validator(); 16 | ``` 17 | 18 | Framework uses namespaces, so add next line to the top of file, where validator is called: 19 | 20 | ```php 21 | use valify\Validator; 22 | ``` 23 | 24 | There is also a more straightforward way to install this framework through the compser. 25 | In your project root, issue next command in terminal: 26 | 27 | `php composer.phar require xphoenyx/valify 1.*` 28 | 29 | Now you are ready to validate your data. 30 | 31 | ### Hint for a MVC pattern users 32 | 33 | You can implement your own methods in base model class. 34 | Please investigate an example below: 35 | 36 | ```php 37 | 38 | use valify\Validator; 39 | 40 | class Model { 41 | /* ... */ 42 | 43 | protected $validator; 44 | // Your rules 45 | public $rules = []; 46 | // Here is stored $_POST, for example 47 | public $data = []; 48 | 49 | function __construct() { 50 | /* ... */ 51 | $this->validator = new Validator(); 52 | /* ... */ 53 | } 54 | 55 | /* 56 | * Your other methods 57 | */ 58 | 59 | public function validate() { 60 | return $this->validator 61 | ->setRules($this->rules) 62 | ->loadData($this->data) 63 | ->validate(); 64 | } 65 | 66 | public function getErrors() { 67 | return $this->validator->getErrors(); 68 | } 69 | } 70 | ``` 71 | 72 | ## Usage 73 | Usage is similar to [Yii2 input validation](https://github.com/yiisoft/yii2/blob/master/docs/guide/input-validation.md). 74 | 75 | ### Define rules 76 | 77 | ```php 78 | $rules = [ 79 | [['username', 'password'], 'string', 'max'=>10], 80 | ['email', 'email', 'message'=>'Please provide a valid email'], 81 | ['remember_me', 'boolean'] 82 | /* ... */ 83 | ]; 84 | ``` 85 | 86 | Each validator accepts `message` parameter, which should contain an error message as string. 87 | You can access attribute name and its value in `message` by using so-called 'patterns': 88 | 89 | ```php 90 | ['email', 'email', 'message'=>'{value} for attribute "{attribute}" is not a valid email'], 91 | ``` 92 | 93 | *NB! If the value is not representable as a string, value type will be shown instead of value itself* 94 | 95 | You can also implement your own validators by extending `valify\validator\AbstractValidator` class. 96 | In this case, if you are not using composer autoloader, you should also import (require) AbstractValidator. 97 | To use own validator in rules, just define validator namespace as a validator name: 98 | 99 | ```php 100 | $rules = [ 101 | /* ... */ 102 | ['email', '\\examples\\ExampleValidator', 'ownProperty'=>'abc' /* ... */] 103 | /* ... */ 104 | ]; 105 | ``` 106 | 107 | Make sure your validator is loaded before defining a namespace in rules. 108 | Refer to the `valify\validators\ExampleValidator` for detailed implementation info. 109 | 110 | ### Define data to be validated 111 | 112 | Input data is expected in next format: 113 | 114 | ```php 115 | $data = [ 116 | 'username'=>'username', 117 | 'password'=>'123qwe', 118 | 'email'=>'address@gmail.com', 119 | 'remember_me'=>'1', 120 | /* ... */ 121 | ]; 122 | ``` 123 | 124 | ### Set rules and data 125 | 126 | ```php 127 | $validator = new Validator(); 128 | $validator = $validator->setRules($rules)->loadData($data) 129 | ``` 130 | 131 | You can call `setrules()` and `loadData()` multiple times: 132 | 133 | ```php 134 | $validator = new Validator(); 135 | $validator = $validator 136 | ->setRules([...]) 137 | ->loadData([...]) 138 | ->setRules([...]) 139 | ->setRules([...]) 140 | ->loadData([...]) 141 | ->loadData([...]); 142 | ``` 143 | 144 | ### Execute validation 145 | 146 | ```php 147 | $isValid = $validator->validate(); 148 | ``` 149 | 150 | You have an ability to perform a single value validation, without calling `setRules()` and `loadData()`: 151 | 152 | ```php 153 | $password = $_POST['password']; 154 | $isValid = Validator::validateFor('string', $password, ['min'=>6, 'max'=>20]); 155 | ``` 156 | 157 | For multiple value validation, pass an array of desired values as a second argument: 158 | 159 | ```php 160 | $values = [ 161 | $_POST['username'], 162 | $_POST['first_name'], 163 | $_POST['password'], 164 | ]; 165 | $isValid = Validator::validateFor('string', $values, ['min'=>3, 'max'=>30]); 166 | ``` 167 | 168 | `validateFor()` will return an object with two properties: 169 | - `isValid` - contains boolean value; 170 | - `lastError` - contains last validation error message; 171 | - `errors` - contains whole error message stack for validating attribute; 172 | 173 | ### Fetch error messages 174 | 175 | ```php 176 | if($validator->hasErrors()) { 177 | $errorMsgs = $validator->getErrors(); 178 | } 179 | ``` 180 | 181 | You can also get an error message of a single attribute: 182 | 183 | ```php 184 | $errorMsgForUserAttr = $validator->getError('username'); 185 | ``` 186 | 187 | As each attribute can have a few error messages, `getError()` will give you 188 | the last message of the corresponding attribute error stack (array). 189 | 190 | ## List of built-in validators: 191 | * boolean 192 | * email 193 | * file 194 | * required 195 | * string 196 | * url 197 | * phone 198 | * in 199 | * number 200 | * compare 201 | * unique 202 | 203 | For detailed parameter description of each validator, see class methods in valify/validators. 204 | 205 | ## Testing 206 | In order to properly run unit tests, you need to specify path to the composer autoloader file. 207 | Then you just issue the `phpunit` command in terminal under `valify` (component root) directory. 208 | 209 | ## Examples 210 | Check index.php in `examples` directory to view framework in action. 211 | 212 | All bug and issue reports are welcome as well as improvement proposals. Enjoy. 213 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xphoenyx/valify", 3 | "type": "library", 4 | "description": "A little framework for user input validation", 5 | "keywords": ["validation", "validator", "validate"], 6 | "homepage": "https://github.com/xphoenyx/valify", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Pavel Borunov", 11 | "email": "0xphoenyx0@gmail.com", 12 | "role": "Developer" 13 | } 14 | ], 15 | "require": { 16 | "php": ">=5.4.0" 17 | }, 18 | "require-dev": { 19 | "phpunit/phpunit": "4.0.*" 20 | }, 21 | "autoload": { 22 | "psr-4": { 23 | "valify\\": "valify" 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /examples/ExampleValidator.php: -------------------------------------------------------------------------------- 1 | attribute. 70 | * 71 | * @param $value 72 | */ 73 | protected function validateValue($value) { 74 | // Set an error message with some params (you can call addError() as many times as you want): 75 | $this->addError('Example error; Called at: {time}', ['{time}'=>date('H:i')]); 76 | 77 | // Your validation code here 78 | } 79 | } -------------------------------------------------------------------------------- /examples/index.php: -------------------------------------------------------------------------------- 1 | 10], 19 | ['email', 'email', 'message'=>'Please provide a valid email'], 20 | ['remember_me', 'boolean'], 21 | ['file', 'file', 'minSize'=>10000, 'maxFiles'=>2, 'extensions'=>['jpg'], 'checkExtensionByMimeType'=>false] 22 | // ['email', 'your\\own\\namespace\\ValidatorClass'] 23 | ]; 24 | 25 | if(!empty($_POST)) { 26 | $data = $_POST; 27 | $data['file'] = $_FILES['file']; 28 | $isValid = $validator->setRules($rules)->loadData($data)->validate(); 29 | } 30 | 31 | function getValue($val) { 32 | return isset($_POST[$val]) ? $_POST[$val] : ''; 33 | } 34 | 35 | ?> 36 | 37 | 38 | 39 | 40 | 45 | 46 | 47 |
48 |
49 |
50 | 51 | 52 | Test form 53 | 54 | ";print_r($validator->getErrors());echo "" ?> 55 | 56 | hasErrors()): ?> 57 | 58 | 59 | 60 | 61 |
62 | 63 |
64 | 65 |

getError('username')?>

66 |
67 |
68 | 69 | 70 |
71 | 72 |
73 | 74 |

getError('password')?>

75 |
76 |
77 | 78 | 79 |
80 | 81 |
82 | 83 |

getError('email')?>

84 |
85 |
86 | 87 | 88 |
89 | 90 |
91 | 92 |
93 |
94 | 95 | 96 |
97 | 98 |
99 | 100 |
101 |
102 | 103 |
104 | 105 |
106 | 107 |
108 |
109 |

getError('file')?>

110 | 111 | 112 |
113 |
114 | 115 |
116 |
117 | 118 |
119 |
120 |
121 | 122 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | tests 14 | 15 | 16 | -------------------------------------------------------------------------------- /tests/BooleanValidatorTest.php: -------------------------------------------------------------------------------- 1 | '1', 'bool2'=>'0'])->isValid; 12 | 13 | $this->assertTrue($isValid); 14 | } 15 | 16 | public function testIntIsValid() 17 | { 18 | $isValid = Validator::validateFor('boolean', ['bool1'=>1, 'bool2'=>0])->isValid; 19 | 20 | $this->assertTrue($isValid); 21 | } 22 | 23 | public function testStrictStringIsValid() 24 | { 25 | $isValid = Validator::validateFor('boolean', ['bool1'=>'1', 'bool2'=>'0'], ['strict'=>true])->isValid; 26 | 27 | $this->assertTrue($isValid); 28 | } 29 | 30 | public function testStrictIntIsValid() 31 | { 32 | $isValid = Validator::validateFor('boolean', ['bool1'=>1, 'bool2'=>0], ['strict'=>true])->isValid; 33 | 34 | $this->assertFalse($isValid); 35 | } 36 | 37 | public function testStrictAnythingElseIsValid() 38 | { 39 | $data = [ 40 | 'notBool1'=>'bar', 41 | 'notBool2'=>2, 42 | 'notBool3'=>123.456, 43 | 'notBool4'=>true, 44 | 'notBool5'=>null 45 | ]; 46 | 47 | $isValid = Validator::validateFor('boolean', $data, ['strict'=>true])->isValid; 48 | 49 | $this->assertFalse($isValid); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/CompareValidatorTest.php: -------------------------------------------------------------------------------- 1 | '='])->isValid; 11 | 12 | $this->assertFalse($isValid); 13 | } 14 | 15 | public function testAreRepresentableDataTypesValid() 16 | { 17 | $data = [ 18 | null, 19 | 1234, 20 | 1234.5678 21 | ]; 22 | 23 | $isValid = true; 24 | $message = null; 25 | foreach ($data as $value) { 26 | $valid = Validator::validateFor('compare', $value, ['compareValue' => $value]); 27 | if(!$valid->isValid) { 28 | $isValid = false; 29 | $message = $valid->error; 30 | break; 31 | } 32 | } 33 | 34 | $this->assertTrue($isValid, $message); 35 | } 36 | 37 | public function testSoftComparison() { 38 | $isValid = Validator::validateFor('compare', 1234, ['compareValue' => '1234'])->isValid; 39 | 40 | $this->assertTrue($isValid); 41 | } 42 | 43 | public function testStrictComparison() { 44 | $isValid = Validator::validateFor('compare', 12345, ['compareValue' => '12345', 'operator' => '==='])->isValid; 45 | 46 | $this->assertFalse($isValid); 47 | } 48 | 49 | public function testSoftInvertedComparison() { 50 | $isValid = Validator::validateFor('compare', 12345, ['compareValue' => '12345', 'operator' => '!='])->isValid; 51 | 52 | $this->assertFalse($isValid); 53 | } 54 | 55 | public function testStrictInvertedComparison() { 56 | $isValid = Validator::validateFor('compare', 12345, ['compareValue' => '12345', 'operator' => '!=='])->isValid; 57 | 58 | $this->assertTrue($isValid); 59 | } 60 | 61 | public function testBiggerThanComparison() { 62 | $isValid = Validator::validateFor('compare', 12345.2, ['compareValue' => 12345.1, 'operator' => '>'])->isValid; 63 | 64 | $this->assertTrue($isValid); 65 | } 66 | 67 | public function testMoreOrEqualComparison() { 68 | $data = [ 69 | 12344 => 12344, 70 | 12345 => 12344 71 | ]; 72 | 73 | $isValid = true; 74 | foreach ($data as $val => $compVal) { 75 | $valid = Validator::validateFor('compare', $val, ['compareValue' => $compVal, 'operator' => '>='])->isValid; 76 | if(!$valid) 77 | $isValid = false; 78 | } 79 | 80 | $this->assertTrue($isValid); 81 | } 82 | 83 | public function testLessThanComparison() { 84 | $isValid = Validator::validateFor('compare', 12345.1, ['compareValue' => 12345.2, 'operator' => '<'])->isValid; 85 | 86 | $this->assertTrue($isValid); 87 | } 88 | 89 | public function testLessOrEqualComparison() { 90 | $data = [ 91 | 12345 => 12345, 92 | 12344 => 12345 93 | ]; 94 | 95 | $isValid = true; 96 | foreach ($data as $val => $compVal) { 97 | $valid = Validator::validateFor('compare', $val, ['compareValue' => $compVal, 'operator' => '<='])->isValid; 98 | if(!$valid) 99 | $isValid = false; 100 | } 101 | 102 | $this->assertTrue($isValid); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /tests/EmailValidatorTest.php: -------------------------------------------------------------------------------- 1 | isValid; 19 | 20 | $this->assertFalse($isValid); 21 | } 22 | 23 | public function testIsRegExpWorkingProperly() { 24 | $data = [ 25 | 'gmail.com', 26 | 'address@gmail', 27 | ]; 28 | 29 | $isValid = Validator::validateFor('email', $data)->isValid; 30 | 31 | $this->assertFalse($isValid); 32 | } 33 | 34 | public function testIsDnsFake() { 35 | $isValid = Validator::validateFor('email', 'address@fakedns.eu', ['checkDNS'=>true])->isValid; 36 | 37 | $this->assertFalse($isValid); 38 | } 39 | 40 | public function testIsEmailValid() { 41 | $isValid = Validator::validateFor('email', 'address@gmail.com', ['checkDNS'=>true])->isValid; 42 | 43 | $this->assertTrue($isValid); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/FileValidatorTest.php: -------------------------------------------------------------------------------- 1 | validator = new Validator(); 14 | 15 | 16 | $this->path = __DIR__ . '/testfile.txt'; 17 | 18 | $testFile = fopen($this->path, "w"); 19 | $txt = ''; 20 | for($i = 0; $i < 10000; $i++) 21 | $txt .= 'Test text '; 22 | fwrite($testFile, $txt); 23 | fclose($testFile); 24 | 25 | $this->mimeType = finfo_file(finfo_open(FILEINFO_MIME_TYPE), $this->path); 26 | $this->size = filesize($this->path); 27 | } 28 | 29 | public function testIsFakeFileDataValid() 30 | { 31 | 32 | $data = [ 33 | 'testFile' => [ 34 | 'name' => 'testfile.txt', 35 | 'type' => $this->mimeType, 36 | // 'size' => $this->size, // Lets omit this line to make file data not valid 37 | 'tmp_name' => $this->path, 38 | 'error' => 0, 39 | ] 40 | ]; 41 | 42 | // $validator = $this->validator->setRules([[array_keys($data), 'file']]); 43 | // 44 | // $isValid = $validator->loadData($data)->validate(); 45 | // $this->assertEquals(false, $isValid); 46 | 47 | 48 | $isValid = Validator::validateFor('file', $data)->isValid; 49 | 50 | $this->assertFalse($isValid); 51 | } 52 | 53 | public function testIsEmptyFileDataValid() { 54 | $data = [ 55 | 'testFile' => [ 56 | 'name' => '', 57 | 'type' => '', 58 | 'size' => '', 59 | 'tmp_name' => '', 60 | 'error' => 4, // Means that empty file uploaded 61 | ] 62 | ]; 63 | 64 | # When using static method validateFor(), 65 | # the value is being checked for emptiness. 66 | # We allow empty value for this case 67 | # by setting 'allowEmpty to true just 68 | # to check that if all array keys exist 69 | # in file array, the file counts as valid 70 | $isValid = Validator::validateFor('file', $data, ['allowEmpty' => true])->isValid; 71 | 72 | $this->assertTrue($isValid); 73 | } 74 | 75 | public function testIsTooBigAmountOfFilesValid() { 76 | $data = [ 77 | 'testFile' => [ 78 | 'name' => ['testfile.txt', 'testfile.txt'], 79 | 'type' => [$this->mimeType, $this->mimeType], 80 | 'size' => [$this->size, $this->size], 81 | 'tmp_name' => [$this->path, $this->path], 82 | 'error' => [0, 0], 83 | ] 84 | ]; 85 | 86 | # By default, only a single file is allowed to be uploaded. 87 | # So multiple file upload should not be valid. 88 | $isValid = Validator::validateFor('file', $data)->isValid; 89 | 90 | $this->assertFalse($isValid); 91 | } 92 | 93 | public function testIsTooBigSizeValid() { 94 | $data = [ 95 | 'testFile' => [ 96 | 'name' => 'testfile.txt', 97 | 'type' => $this->mimeType, 98 | 'size' => $this->size, 99 | 'tmp_name' => $this->path, 100 | 'error' => 0, 101 | ] 102 | ]; 103 | 104 | // $validator = $this->validator->setRules([[array_keys($data), 'file', 'maxSize' => 900]]); 105 | // 106 | // $isValid = $validator->loadData($data)->validate(); 107 | // $this->assertEquals(false, $isValid); 108 | 109 | 110 | $isValid = Validator::validateFor('file', $data, ['maxSize' => 900])->isValid; 111 | 112 | $this->assertFalse($isValid); 113 | } 114 | 115 | public function testIsTooSmallSizeValid() { 116 | $data = [ 117 | 'testFile' => [ 118 | 'name' => 'testfile.txt', 119 | 'type' => $this->mimeType, 120 | 'size' => $this->size, 121 | 'tmp_name' => $this->path, 122 | 'error' => 0, 123 | ] 124 | ]; 125 | 126 | // $validator = $this->validator->setRules([[array_keys($data), 'file', 'minSize' => 9000000]]); 127 | // 128 | // $isValid = $validator->loadData($data)->validate(); 129 | // $this->assertEquals(false, $isValid); 130 | 131 | $isValid = Validator::validateFor('file', $data, ['minSize' => 9000000])->isValid; 132 | 133 | $this->assertFalse($isValid); 134 | } 135 | 136 | public function testIsWrongExtensionValid() { 137 | $data = [ 138 | 'testFile' => [ 139 | 'name' => 'testfile.txt', 140 | 'type' => $this->mimeType, 141 | 'size' => $this->size, 142 | 'tmp_name' => $this->path, 143 | 'error' => 0, 144 | ] 145 | ]; 146 | 147 | // $validator = $this->validator->setRules([[array_keys($data), 'file', 'extensions' => ['jpg'], 'checkExtensionByMimeType' => false]]); 148 | // 149 | // $isValid = $validator->loadData($data)->validate(); 150 | // $this->assertEquals(false, $isValid); 151 | 152 | 153 | $isValid = Validator::validateFor('file', $data, ['extensions' => ['jpg'], 'checkExtensionByMimeType' => false])->isValid; 154 | 155 | $this->assertFalse($isValid); 156 | } 157 | 158 | public function testIsWrongMimeTypeValid() { 159 | $data = [ 160 | 'testFile' => [ 161 | 'name' => 'testfile.jpg', 162 | 'type' => $this->mimeType, 163 | 'size' => $this->size, 164 | 'tmp_name' => $this->path, 165 | 'error' => 0, 166 | ] 167 | ]; 168 | 169 | $isValid = Validator::validateFor('file', $data, ['extensions' => ['jpg']])->isValid; 170 | 171 | $this->assertFalse($isValid); 172 | } 173 | 174 | public function testIsProperFileValidationWorking() { 175 | $data = [ 176 | 'testFile' => [ 177 | 'name' => 'testfile.txt', 178 | 'type' => $this->mimeType, 179 | 'size' => $this->size, 180 | 'tmp_name' => $this->path, 181 | 'error' => 0, 182 | ] 183 | ]; 184 | 185 | $isValid = Validator::validateFor('file', $data, ['extensions' => ['txt']])->isValid; 186 | 187 | $this->assertTrue($isValid); 188 | } 189 | 190 | public function tearDown() 191 | { 192 | unlink($this->path); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /tests/NumberValidatorTest.php: -------------------------------------------------------------------------------- 1 | isValid; 10 | 11 | $this->assertFalse($isValid); 12 | } 13 | 14 | public function testIsTextIsValid() { 15 | $isValid = Validator::validateFor('number', 'asfsa656')->isValid; 16 | 17 | $this->assertFalse($isValid); 18 | } 19 | 20 | public function testIsStringValid() { 21 | $isValid = Validator::validateFor('number', '656.35')->isValid; 22 | 23 | $this->assertTrue($isValid); 24 | } 25 | 26 | public function testIsValidWithIntegerOnlyEnabled() { 27 | $isValid = Validator::validateFor('number', 656.35, ['integerOnly'=>true])->isValid; 28 | 29 | $this->assertFalse($isValid); 30 | } 31 | 32 | public function testIsBiggerThanAllowedValid() { 33 | $isValid = Validator::validateFor('number', 656.35, ['max'=>656])->isValid; 34 | 35 | $this->assertFalse($isValid); 36 | } 37 | 38 | public function testIsLessThanAllowedValid() { 39 | $isValid = Validator::validateFor('number', 656.35, ['min'=>657])->isValid; 40 | 41 | $this->assertFalse($isValid); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/PhoneValidatorTest.php: -------------------------------------------------------------------------------- 1 | isValid; 11 | 12 | $this->assertFalse($isValid); 13 | } 14 | 15 | public function testIsTooShortValid() 16 | { 17 | $isValid = Validator::validateFor('phone', '555')->isValid; 18 | 19 | $this->assertFalse($isValid); 20 | } 21 | 22 | public function testIsValidWithCountryCodeCheck() 23 | { 24 | $isValid = Validator::validateFor('phone', '55555555')->isValid; 25 | 26 | $this->assertFalse($isValid); 27 | } 28 | 29 | public function testIsValidWithoutCountryCodeCheck() 30 | { 31 | $isValid = Validator::validateFor('phone', '55555555', ['checkCountryCode'=>false])->isValid; 32 | 33 | $this->asserttrue($isValid); 34 | } 35 | 36 | public function testIsValidWitCountryCode() 37 | { 38 | $isValid = Validator::validateFor('phone', '+999 55555555')->isValid; 39 | 40 | $this->assertTrue($isValid); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/RangeValidatorTest.php: -------------------------------------------------------------------------------- 1 | range(1,5)])->isValid; 18 | $this->assertFalse($isValid); 19 | } 20 | 21 | public function testIsOutOfRangeValidWithInvertedComparison() { 22 | $isValid = Validator::validateFor('in', 6, ['range'=>range(1,5), 'not'=>true])->isValid; 23 | $this->assertTrue($isValid); 24 | } 25 | 26 | public function testIsSoftComparisonWorking() { 27 | $isValid = Validator::validateFor('in', "5", ['range'=>range(1,5)])->isValid; 28 | $this->assertTrue($isValid); 29 | } 30 | 31 | public function testIsStrictComparisonWorking() { 32 | $isValid = Validator::validateFor('in', "5", ['range'=>range(1,5), 'strict'=>true])->isValid; 33 | $this->assertFalse($isValid); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/RequiredValidatorTest.php: -------------------------------------------------------------------------------- 1 | isValid; 11 | 12 | $this->assertFalse($isValid); 13 | } 14 | 15 | public function testIsEmptyValidUsingStrictComparison() 16 | { 17 | $isValid = Validator::validateFor('required', '', ['strict'=>true])->isValid; 18 | 19 | $this->assertTrue($isValid); 20 | } 21 | 22 | public function testAreSpacesValid() 23 | { 24 | $isValid = Validator::validateFor('required', ' ')->isValid; 25 | 26 | $this->assertFalse($isValid); 27 | } 28 | 29 | public function testIsEqualToRequiredValue() 30 | { 31 | $isValid = Validator::validateFor('required', '1', ['requiredValue'=>1])->isValid; 32 | 33 | $this->assertTrue($isValid); 34 | } 35 | 36 | public function testIsEqualToRequiredValueUsingStrictComparison() 37 | { 38 | $isValid = Validator::validateFor('required', '1', ['requiredValue'=>1, 'strict'=>true])->isValid; 39 | 40 | $this->assertFalse($isValid); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/StringValidatorTest.php: -------------------------------------------------------------------------------- 1 | validator = new Validator(); 10 | } 11 | 12 | public function testEverythingButTheStringIsNotValid() 13 | { 14 | $data = [ 15 | 123456789, 16 | 123.456, 17 | true, 18 | null 19 | ]; 20 | 21 | $isValid = Validator::validateFor('string', $data)->isValid; 22 | 23 | $this->assertFalse($isValid); 24 | } 25 | 26 | public function testIsStringWithLengthOutOfProperLengthValid() 27 | { 28 | $isValid = Validator::validateFor('string', '123qwe456rty', ['length'=>6])->isValid; 29 | 30 | $this->assertFalse($isValid); 31 | } 32 | 33 | public function testIsTooLongStringValid() 34 | { 35 | $isValid = Validator::validateFor('string', '123qwe456rty', ['max'=>6])->isValid; 36 | 37 | $this->assertFalse($isValid); 38 | } 39 | 40 | public function testIsTooShortStringValid() 41 | { 42 | $isValid = Validator::validateFor('string', '123qwe456rty', ['min'=>50])->isValid; 43 | 44 | $this->assertFalse($isValid); 45 | } 46 | 47 | public function testIsStringValidWithRestrictions() 48 | { 49 | $isValid = Validator::validateFor('string', '123qwe456rty', ['min'=>6, 'max'=>20])->isValid; 50 | 51 | $this->assertTrue($isValid); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tests/UniqueValidatorTest.php: -------------------------------------------------------------------------------- 1 | 'table', 'column'=>'column']); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/UrlValidatorTest.php: -------------------------------------------------------------------------------- 1 | isValid; 12 | 13 | $this->assertFalse($isValid); 14 | } 15 | 16 | public function testIsValidWithoutProtocol() 17 | { 18 | $isValid = Validator::validateFor('url', 'google.com')->isValid; 19 | 20 | $this->assertFalse($isValid); 21 | } 22 | 23 | public function testIsValidWithoutTopLevelDomain() 24 | { 25 | $isValid = Validator::validateFor('url', 'http://google')->isValid; 26 | 27 | $this->assertFalse($isValid); 28 | } 29 | 30 | public function testIsValidWithProtocol() 31 | { 32 | $isValid = Validator::validateFor('url', 'http://google.com')->isValid; 33 | 34 | $this->assertTrue($isValid); 35 | } 36 | 37 | public function testIsProtocolValidationDisablingWorking() 38 | { 39 | $isValid = Validator::validateFor('url', 'http://google.com', ['validSchemes'=>['https']])->isValid; 40 | 41 | $this->assertFalse($isValid); 42 | } 43 | 44 | public function testIsDisabledIDNCheckWorking() 45 | { 46 | $isValid = Validator::validateFor('url', 'http://täst.de')->isValid; 47 | 48 | $this->assertFalse($isValid); 49 | } 50 | 51 | public function testIsEnabledIDNCheckWorking() 52 | { 53 | $isValid = Validator::validateFor('url', 'http://täst.de', ['enableIDN'=>true])->isValid; 54 | 55 | $this->assertTrue($isValid); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tests/ValidatorTest.php: -------------------------------------------------------------------------------- 1 | 5, 'max'=>100])->isValid; 10 | 11 | $this->assertTrue($isValid); 12 | } 13 | 14 | public function testSingleValidationOnMultipleValues() { 15 | $emails = [ 16 | 'test@gmail.com', 17 | 'test2@yahoo.com' 18 | ]; 19 | 20 | $isValid = Validator::validateFor('email', $emails, ['checkDNS'=>true]); 21 | 22 | $this->assertTrue($isValid->isValid, $isValid->lastError); 23 | } 24 | 25 | /** 26 | * @expectedException \UnexpectedValueException 27 | * @expectedExceptionMessage Validator name must be a string, array given 28 | */ 29 | public function testIsValidatorNameAsNonStringValid() { 30 | $isValid = Validator::validateFor([], ''); 31 | 32 | $this->assertFalse($isValid->isValid, $isValid->lastError); 33 | } 34 | 35 | /** 36 | * As no cycle run on empty array of values, 37 | * no any validation loaded, 38 | * so no any errors produced. 39 | */ 40 | public function testIsEmptyArrayValidOnSingleValidation() { 41 | $isValid = Validator::validateFor('phone', []); 42 | 43 | $this->assertTrue($isValid->isValid, $isValid->lastError); 44 | } 45 | 46 | public function testIsEmptyValueValidOnSingleValidation() { 47 | $isValid = Validator::validateFor('phone', ''); 48 | 49 | $this->assertFalse($isValid->isValid, $isValid->lastError); 50 | } 51 | 52 | /** 53 | * @expectedException \UnexpectedValueException 54 | * @expectedExceptionMessage Every rule must be provided as an array and must include validator name and value attribute 55 | */ 56 | public function testIsNonArrayRuleValid() { 57 | $v = new Validator(); 58 | $rules = [ 59 | new \stdClass(), 60 | ]; 61 | 62 | $v->setRules($rules); 63 | } 64 | 65 | /** 66 | * @expectedException \UnexpectedValueException 67 | * @expectedExceptionMessage Every rule must be provided as an array and must include validator name and value attribute 68 | */ 69 | public function testIsRuleWithoutValidatorNameValid() { 70 | $v = new Validator(); 71 | $rules = [[ 72 | 'first_name', 73 | ]]; 74 | 75 | $v->setRules($rules); 76 | } 77 | 78 | /** 79 | * @expectedException \UnexpectedValueException 80 | * @expectedExceptionMessage Validator name must be a string, object given 81 | */ 82 | public function testIsNonStringValidatorNameValid() { 83 | $v = new Validator(); 84 | $rules = [[ 85 | 'first_name', 86 | new \stdClass(), 87 | ]]; 88 | 89 | $v->setRules($rules); 90 | } 91 | 92 | public function testLoadRulesAndDataMultipleTimes() { 93 | $v = new Validator(); 94 | 95 | $v->loadData(['email' => 'test@gmail.com']) 96 | ->loadData(['first_name' => 'John']); 97 | 98 | $v->loadData(['last_name' => 'Doe']) 99 | ->setRules([['email', 'email']]) 100 | ->setRules([[['first_name', 'last_name'], 'string', 'max'=>10]]); 101 | 102 | $isValid = $v->validate(); 103 | 104 | $this->assertTrue($isValid); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /valify/Validator.php: -------------------------------------------------------------------------------- 1 | '\valify\validators\BooleanValidator', 12 | 'compare' => '\valify\validators\CompareValidator', 13 | 'date' => '\valify\validators\DateValidator', 14 | 'default' => '\valify\validators\DefaultValueValidator', 15 | 'email' => '\valify\validators\EmailValidator', 16 | 'file' => '\valify\validators\FileValidator', 17 | 'image' => '\valify\validators\ImageValidator', 18 | 'in' => '\valify\validators\RangeValidator', 19 | 'match' => '\valify\validators\RegularExpressionValidator', 20 | 'number' => '\valify\validators\NumberValidator', 21 | 'phone' => '\valify\validators\PhoneValidator', 22 | 'required' => '\valify\validators\RequiredValidator', 23 | 'string' => '\valify\validators\StringValidator', 24 | 'unique' => '\valify\validators\UniqueValidator', 25 | 'url' => '\valify\validators\UrlValidator', 26 | ]; 27 | 28 | /** 29 | * You can perform a single validation by using this method. 30 | * Result of validate() method (boolean) will be returned. 31 | * 32 | * @param $name string - Name of validator 33 | * @param $value mixed - Value to validate. If array, 34 | * all keys are taken as attributes and values as values. 35 | * @param $params array - Params for a validator 36 | * @return object 37 | * @throws \Exception 38 | */ 39 | public static function validateFor($name, $value, array $params = []) { 40 | if( !is_string($name) ) 41 | throw new \UnexpectedValueException("Validator name must be a string, " . gettype($name) . " given"); 42 | 43 | $rules = []; 44 | # By default, empty value should be not valid 45 | $params = array_merge(['allowEmpty'=>false], $params); 46 | 47 | if( is_array($value) ) { 48 | $rules[] = array_merge([array_keys($value), $name], $params); 49 | } else { 50 | $rules[] = array_merge([$name, $name], $params); 51 | $value = [$name => $value]; 52 | } 53 | 54 | $v = new Validator(); 55 | $result = new \stdClass(); 56 | $result->isValid = $v->setRules($rules)->loadData($value)->validate(); 57 | $result->lastError = $v->getLastError(); 58 | $result->errors = $v->getErrors(); 59 | unset($v); 60 | 61 | return $result; 62 | } 63 | 64 | /** 65 | * You can call this method multiple times. New rules 66 | * will be merged with already loaded ones. 67 | * 68 | * @param array $rules 69 | * @return $this 70 | */ 71 | public function setRules(array $rules = []) { 72 | foreach ($rules as $rule) { 73 | if( !is_array($rule) || count($rule) < 2 ) 74 | throw new \UnexpectedValueException("Every rule must be provided as an array and must include validator name and value attribute"); 75 | 76 | if( !is_string($rule[1]) ) 77 | throw new \UnexpectedValueException("Validator name must be a string, " . gettype($rule[1]) . " given"); 78 | } 79 | 80 | $this->_rules = array_merge($this->_rules, $rules); 81 | return $this; 82 | } 83 | 84 | /** 85 | * You can call this method multiple times. New data 86 | * will be merged with already loaded one. 87 | * 88 | * @param array $data 89 | * @return $this 90 | */ 91 | public function loadData(array $data = []) { 92 | $this->_data = array_merge($this->_data, $data); 93 | return $this; 94 | } 95 | 96 | /** 97 | * @return bool 98 | */ 99 | public function validate() { 100 | # Sort rules by validator name 101 | usort($this->_rules, function ($a, $b) { 102 | if ($a[1] == $b[1]) return 0; 103 | return ($a[1] < $b[1]) ? -1 : 1; 104 | }); 105 | 106 | foreach ($this->_rules as $rule) { 107 | $attribute = array_shift($rule); 108 | $validatorName = array_shift($rule); 109 | 110 | if($validatorName) { 111 | if( is_string($attribute) ) { 112 | $value = isset($this->_data[$attribute]) ? $this->_data[$attribute] : null; 113 | $this->callValidator($validatorName, [$attribute => $value], $rule); 114 | } elseif( is_array($attribute) ) { 115 | $safeData = array_intersect_key( $this->_data, array_flip($attribute) ); 116 | $this->callValidator($validatorName, $safeData, $rule); 117 | } 118 | } 119 | } 120 | 121 | return !$this->hasErrors(); 122 | } 123 | 124 | /** 125 | * After using validate(), we can 126 | * simply check, if there are any errors 127 | * 128 | * @return bool 129 | */ 130 | public function hasErrors() { 131 | return !empty($this->_errors); 132 | } 133 | 134 | /** 135 | * Get all error messages, or the only ones 136 | * for a particular attribute, if it is defined 137 | * 138 | * @return array 139 | */ 140 | public function getErrors($attribute = null) { 141 | return isset($this->_errors[$attribute]) ? $this->_errors[$attribute] : $this->_errors; 142 | } 143 | 144 | /** 145 | * Get a single error message for a particular attribute 146 | * 147 | * @param $attribute 148 | * @return array|null 149 | */ 150 | public function getError($attribute) { 151 | return isset($this->_errors[$attribute]) ? $this->_errors[$attribute][0] : null; 152 | } 153 | 154 | /** 155 | * @return null 156 | */ 157 | public function getLastError() { 158 | $errors = array_values($this->_errors); 159 | if( isset($errors[0][0]) ) 160 | return $errors[0][0]; 161 | return null; 162 | } 163 | 164 | private function callValidator($validator, $data, $rule = []) { 165 | if( isset($this->_builtInValidators[$validator]) ) { 166 | $namespace = $this->_builtInValidators[$validator]; 167 | $validator = $this->loadValidator($namespace); 168 | } elseif( strpos($validator, '\\') !== false ) { # Validator name is a namespace 169 | $validator = $this->loadValidator($validator); 170 | 171 | if( !is_subclass_of($validator, '\valify\validators\AbstractValidator', false) ) 172 | throw new \DomainException("Validator " . get_class($validator) . " must extend \\valify\\validators\\AbstractValidator class"); 173 | } 174 | 175 | if( is_object($validator) ) { 176 | /** @var $validator validators\AbstractValidator */ 177 | $validator = $this->setValidatorProperties($validator, $rule); 178 | 179 | foreach ($data as $attr => $val) { 180 | $validator->setAttributeAndValue($attr, $val); 181 | $validator->init(); 182 | 183 | if( $validator->gotErrors() ) 184 | $this->setErrorStack($validator->fetchErrors()); 185 | } 186 | } else { 187 | throw new \UnexpectedValueException("Validator " . get_class($validator) . " not found"); 188 | } 189 | } 190 | 191 | private function loadValidator($name) { 192 | $currentValidatorName = is_object($this->_currentValidator) 193 | ? trim(get_class($this->_currentValidator), '\\') 194 | : null; 195 | $name = trim($name, '\\'); 196 | 197 | if(!$this->_currentValidator || $currentValidatorName !== $name) { 198 | $validator = new $name(); 199 | $this->_currentValidator = $validator; 200 | } else { 201 | $validator = $this->_currentValidator; 202 | } 203 | 204 | return $validator; 205 | } 206 | 207 | private function setValidatorProperties($obj, $params) { 208 | foreach ($params as $prop => $value) 209 | $obj->$prop = $value; 210 | 211 | $obj->_data = $this->_data; 212 | 213 | return $obj; 214 | } 215 | 216 | private function setErrorStack($errors) { 217 | foreach ($errors as $attr => $msgs) 218 | $this->_errors[$attr] = $msgs; 219 | } 220 | 221 | public function cleanErrors() { 222 | $this->_errors = []; 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /valify/validators/AbstractValidator.php: -------------------------------------------------------------------------------- 1 | attribute = $attribute; 31 | $this->value = $value; 32 | } 33 | 34 | /** 35 | * Called by Validator class to execute validation. 36 | * Yes, some errors may be set in the init() method 37 | */ 38 | public function init() { 39 | if( !$this->gotErrors() && ( !$this->allowEmpty || ( $this->allowEmpty && !$this->isEmpty($this->value) ) ) ) { 40 | $this->validateValue($this->value); 41 | } 42 | } 43 | 44 | /** 45 | * Error message constructor. Attribute and value will always be 46 | * accessible in message string as {attribute} and {value} templates. 47 | * You can use own params to replace in $msg. 48 | * Usage: 49 | * $msg = "Length should be longer than {min} chars and not exceed {max} chars" 50 | * $params = ['{min}'=>3, '{max}'=>15] 51 | * 52 | * @param string $msg 53 | * @param array $params 54 | */ 55 | protected function addError($msg, $params = []) { 56 | $params = array_merge([ 57 | '{attribute}' => $this->attribute, 58 | '{value}' => $this->value, 59 | ], $params); 60 | 61 | foreach ($params as $template => $value) 62 | $params[$template] = $this->isPrintable($value) ? $value : gettype($value); 63 | 64 | $msg = str_replace(array_keys($params), array_values($params), $msg); 65 | 66 | $this->_errors[$this->attribute][] = $msg; 67 | } 68 | 69 | /** 70 | * Fetching errors for a particular validator 71 | * @return array 72 | */ 73 | public function fetchErrors() { 74 | return $this->_errors; 75 | } 76 | 77 | /** 78 | * Does current validator got any errors 79 | * @return bool 80 | */ 81 | public function gotErrors() { 82 | return !empty($this->_errors); 83 | } 84 | 85 | public function isPrintable($value) { 86 | return ( is_string($value) || is_numeric($value) ); 87 | } 88 | 89 | public function isEmpty($value) { 90 | $empty = true; 91 | 92 | if ( is_string($value) || is_numeric($value) ) { 93 | $empty = empty($value); 94 | } elseif( is_object($value) ) { 95 | $empty = $this->isEmpty( get_object_vars($value) ); 96 | } elseif( is_array($value) ) { 97 | foreach ($value as $key => $val) { 98 | if( !$this->isEmpty($val) ) { 99 | $empty = false; 100 | break; 101 | } 102 | } 103 | } 104 | 105 | return $empty; 106 | } 107 | 108 | abstract protected function validateValue($value); 109 | } -------------------------------------------------------------------------------- /valify/validators/BooleanValidator.php: -------------------------------------------------------------------------------- 1 | strict && ($value == $this->trueValue || $value == $this->falseValue) 26 | || $this->strict && ($value === $this->trueValue || $value === $this->falseValue); 27 | if (!$valid) 28 | $this->addError($this->message); 29 | } 30 | } -------------------------------------------------------------------------------- /valify/validators/CompareValidator.php: -------------------------------------------------------------------------------- 1 | `: check if value being validated is greater than the value being compared with. 39 | * - `>=`: check if value being validated is greater than or equal to the value being compared with. 40 | * - `<`: check if value being validated is less than the value being compared with. 41 | * - `<=`: check if value being validated is less than or equal to the value being compared with. 42 | */ 43 | public $operator = '=='; 44 | 45 | public function init() 46 | { 47 | if ($this->message === null) { 48 | switch ($this->operator) { 49 | case '==': 50 | $this->message = '{compareAttribute} must be repeated exactly.'; 51 | break; 52 | case '===': 53 | $this->message = '{compareAttribute} must be repeated exactly.'; 54 | break; 55 | case '!=': 56 | $this->message = '{compareAttribute} must not be equal to "{compareValue}".'; 57 | break; 58 | case '!==': 59 | $this->message = '{compareAttribute} must not be equal to "{compareValue}".'; 60 | break; 61 | case '>': 62 | $this->message = '{compareAttribute} must be greater than "{compareValue}".'; 63 | break; 64 | case '>=': 65 | $this->message = '{compareAttribute} must be greater than or equal to "{compareValue}".'; 66 | break; 67 | case '<': 68 | $this->message = '{compareAttribute} must be less than "{compareValue}".'; 69 | break; 70 | case '<=': 71 | $this->message = '{compareAttribute} must be less than or equal to "{compareValue}".'; 72 | break; 73 | default: 74 | $this->addError('Unknown operator: {operator}', ['{operator}' => $this->operator]); 75 | } 76 | } 77 | 78 | parent::init(); 79 | } 80 | 81 | protected function validateValue($value) { 82 | if ( !$this->isComparable($value) ) { 83 | $this->addError('{attribute} is invalid.'); 84 | } else { 85 | if ($this->compareValue !== null) { 86 | $compareAttribute = $compareValue = $this->compareValue; 87 | } else { 88 | $compareAttribute = $this->compareAttribute === null ? $this->attribute . '_repeat' : $this->compareAttribute; 89 | $compareValue = isset( $this->_data[$compareAttribute] ) ? $this->_data[$compareAttribute] : null; 90 | } 91 | 92 | if ( !$this->compareValues($this->operator, $value, $compareValue) ) { 93 | $this->addError($this->message, [ 94 | '{compareAttribute}' => $compareAttribute, 95 | '{compareValue}' => $compareValue, 96 | ]); 97 | } 98 | } 99 | } 100 | 101 | private function compareValues($operator, $value, $compareValue) 102 | { 103 | switch ($operator) { 104 | case '==': 105 | return $value == $compareValue; 106 | case '===': 107 | return $value === $compareValue; 108 | case '!=': 109 | return $value != $compareValue; 110 | case '!==': 111 | return $value !== $compareValue; 112 | case '>': 113 | return $value > $compareValue; 114 | case '>=': 115 | return $value >= $compareValue; 116 | case '<': 117 | return $value < $compareValue; 118 | case '<=': 119 | return $value <= $compareValue; 120 | default: 121 | return false; 122 | } 123 | } 124 | 125 | private function isComparable($value) { 126 | return ( is_null($value) || is_bool($value) || is_numeric($value) || is_string($value) || is_array($value) || is_object($value) ); 127 | } 128 | } -------------------------------------------------------------------------------- /valify/validators/EmailValidator.php: -------------------------------------------------------------------------------- 1 | ";print_r($value);echo ""; 22 | if (!is_string($value) || strlen($value) >= 320) { 23 | $this->addError($this->message); 24 | } elseif ( !preg_match('/^(.*?)$/', $value, $matches) ) { 25 | $this->addError($this->message); 26 | } else { 27 | $domain = $matches[3]; 28 | $valid = preg_match($this->pattern, $value); 29 | if (!$valid) 30 | $this->addError($this->message); 31 | if ($valid && $this->checkDNS) { 32 | $valid = checkdnsrr($domain, 'MX') || checkdnsrr($domain, 'A'); 33 | if(!$valid) 34 | $this->addError($this->DNSIsNotValid, ['{domain}'=>$domain]); 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /valify/validators/FileValidator.php: -------------------------------------------------------------------------------- 1 | extensions) ) { 72 | $this->extensions = preg_split('/[\s,]+/', strtolower($this->extensions), -1, PREG_SPLIT_NO_EMPTY); 73 | } else { 74 | $this->extensions = array_map('strtolower', $this->extensions); 75 | } 76 | if ( !is_array($this->mimeTypes) ) { 77 | $this->mimeTypes = preg_split('/[\s,]+/', strtolower($this->mimeTypes), -1, PREG_SPLIT_NO_EMPTY); 78 | } else { 79 | $this->mimeTypes = array_map('strtolower', $this->mimeTypes); 80 | } 81 | 82 | $uploadMaxFileSize = $this->sizeToBytes(ini_get('upload_max_filesize')); 83 | 84 | if ( isset($_POST['MAX_FILE_SIZE']) && $_POST['MAX_FILE_SIZE'] > 0 && $_POST['MAX_FILE_SIZE'] < $uploadMaxFileSize ) 85 | $uploadMaxFileSize = (int)$_POST['MAX_FILE_SIZE']; 86 | 87 | if( $this->maxSize == null || $this->maxSize > $uploadMaxFileSize ) 88 | $this->maxSize = $uploadMaxFileSize; 89 | 90 | parent::init(); 91 | } 92 | 93 | protected function validateValue($value) { 94 | $amountOfFiles = false; 95 | 96 | if ( !is_array($value) || !$this->isFile($value) ) { 97 | $this->addError($this->message); 98 | } else { 99 | $value = $this->normailzeData($value); 100 | $amountOfFiles = count($value); 101 | } 102 | 103 | 104 | if(!$amountOfFiles || $amountOfFiles > $this->maxFiles) { 105 | $this->addError($this->tooMany, ['{number}'=>$amountOfFiles, '{limit}'=>$this->maxFiles]); 106 | } else { 107 | foreach ($value as $file) { 108 | if($file['error'] == 4 && $this->allowEmpty) 109 | continue; 110 | elseif($file['error'] !== UPLOAD_ERR_OK) 111 | $this->addError( $this->errorCodeToMessage($file['error']) ); 112 | elseif($file['size'] == 0) 113 | $this->addError( $this->emptyFile, ['{name}'=>$file['name']] ); 114 | elseif($this->maxSize && $file['size'] > $this->maxSize) 115 | $this->addError( $this->tooBig, ['{name}'=>$file['name'], '{maxSize}'=>$this->maxSize] ); 116 | elseif($this->minSize && $file['size'] < $this->minSize) 117 | $this->addError( $this->tooSmall, ['{name}'=>$file['name'], '{minSize}'=>$this->minSize] ); 118 | elseif( !empty($this->extensions) ) { 119 | $extensions = $this->extensions; 120 | $extension = pathinfo($file['name'], PATHINFO_EXTENSION); 121 | 122 | if($this->checkExtensionByMimeType) { 123 | $mimeTypeExtensions = $this->getExtensionsByFileMimeType($file['tmp_name']); 124 | if( !in_array($extension, $mimeTypeExtensions) ) 125 | $extension = null; 126 | } 127 | 128 | if( !in_array($extension, $extensions) ) 129 | $this->addError( $this->wrongExtension, ['{extensions}'=>implode(', ', $this->extensions)] ); 130 | } elseif( !empty($this->mimeTypes) && !in_array($file['type'], $this->mimeTypes) ) 131 | $this->addError( $this->wrongMimeType, ['{mimeTypes}'=>implode(', ', $this->mimeTypes)] ); 132 | } 133 | } 134 | } 135 | 136 | private function isFile($value) { 137 | return isset($value['name'], $value['type'], $value['tmp_name'], $value['error'], $value['size']); 138 | } 139 | 140 | private function sizeToBytes($str) { 141 | switch (substr($str, -1)) { 142 | case 'M': 143 | case 'm': 144 | return (int)$str * 1048576; 145 | case 'K': 146 | case 'k': 147 | return (int)$str * 1024; 148 | case 'G': 149 | case 'g': 150 | return (int)$str * 1073741824; 151 | default: 152 | return (int)$str; 153 | } 154 | } 155 | 156 | private function errorCodeToMessage($code) { 157 | switch ($code) { 158 | case UPLOAD_ERR_INI_SIZE: 159 | $message = "The uploaded file exceeds the upload_max_filesize directive in php.ini"; 160 | break; 161 | case UPLOAD_ERR_FORM_SIZE: 162 | $message = "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form"; 163 | break; 164 | case UPLOAD_ERR_PARTIAL: 165 | $message = "The uploaded file was only partially uploaded"; 166 | break; 167 | case UPLOAD_ERR_NO_FILE: 168 | $message = "No file was uploaded"; 169 | break; 170 | case UPLOAD_ERR_NO_TMP_DIR: 171 | $message = "Missing a temporary folder"; 172 | break; 173 | case UPLOAD_ERR_CANT_WRITE: 174 | $message = "Failed to write file to disk"; 175 | break; 176 | case UPLOAD_ERR_EXTENSION: 177 | $message = "File upload stopped by extension"; 178 | break; 179 | 180 | default: 181 | $message = "Unknown upload error"; 182 | break; 183 | } 184 | return $message; 185 | } 186 | 187 | public function getExtensionsByFileMimeType($file) { 188 | $mimeType = $this->getMimeTypeForFile($file); 189 | 190 | $out = []; 191 | $file = fopen('/etc/mime.types', 'r'); 192 | while(($line = fgets($file)) !== false) { 193 | $line = trim(preg_replace('/#.*/', '', $line)); 194 | if(!$line) 195 | continue; 196 | $parts = preg_split('/\s+/', $line); 197 | if(count($parts) == 1) 198 | continue; 199 | $type = array_shift($parts); 200 | if($type == $mimeType) { 201 | $out = is_array($parts) ? $parts : [$parts]; 202 | break; 203 | } 204 | } 205 | fclose($file); 206 | 207 | return $out; 208 | } 209 | 210 | private function getMimeTypeForFile($file) { 211 | $finfo = finfo_open(FILEINFO_MIME_TYPE); // return mime type ala mimetype extension 212 | $fileMimeType = finfo_file($finfo, $file); 213 | finfo_close($finfo); 214 | 215 | return $fileMimeType; 216 | } 217 | 218 | private function normailzeData($value) { 219 | $normalizedValue = []; 220 | 221 | foreach ($value as $key => $val) { 222 | if(is_array($val)) { 223 | foreach ($val as $i => $name) 224 | $normalizedValue[$i][$key] = $name; 225 | } else { 226 | $normalizedValue[0][$key] = $val; 227 | } 228 | } 229 | 230 | return $normalizedValue; 231 | } 232 | } -------------------------------------------------------------------------------- /valify/validators/NumberValidator.php: -------------------------------------------------------------------------------- 1 | addError($this->message); 49 | } elseif($this->integerOnly && !preg_match($this->integerPattern, "$value") ) { 50 | $this->addError($this->message); 51 | } elseif ($this->min !== null && $value < $this->min) { 52 | $this->addError($this->tooSmall, ['min' => $this->min]); 53 | } elseif ($this->max !== null && $value > $this->max) { 54 | $this->addError($this->tooBig, ['max' => $this->max]); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /valify/validators/PhoneValidator.php: -------------------------------------------------------------------------------- 1 | = 20) { 23 | $this->addError($this->message); 24 | } else { 25 | $pattern = $this->checkCountryCode ? $this->pattern : $this->phonePattern; 26 | $valid = preg_match($pattern, trim($value)); 27 | if (!$valid) 28 | $this->addError($this->message); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /valify/validators/RangeValidator.php: -------------------------------------------------------------------------------- 1 | range) ) 28 | throw new \InvalidArgumentException($this->rangeErrorMessage); 29 | 30 | parent::init(); 31 | } 32 | 33 | protected function validateValue($value) { 34 | $value = (is_array($value) ? $value : [$value]); 35 | 36 | $in = true; 37 | foreach ($value as $v) { 38 | if ( !in_array($v, $this->range, $this->strict) ) { 39 | $in = false; 40 | break; 41 | } 42 | } 43 | 44 | if($this->not === $in) 45 | $this->addError($this->message); 46 | } 47 | } -------------------------------------------------------------------------------- /valify/validators/RequiredValidator.php: -------------------------------------------------------------------------------- 1 | requiredValue === null) { 37 | $value = is_string($value) ? trim($value) : $value; 38 | if ($this->strict && $value === null || !$this->strict && empty($value)) { 39 | $this->addError($this->message); 40 | } 41 | } elseif (!$this->strict && $value != $this->requiredValue || $this->strict && $value !== $this->requiredValue) { 42 | $this->addError($this->message, ['{requiredValue}' => $this->requiredValue]); 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /valify/validators/StringValidator.php: -------------------------------------------------------------------------------- 1 | addError($this->message); 46 | 47 | $length = mb_strlen($value, $this->encoding); 48 | 49 | if ($this->min !== null && $length < $this->min) 50 | $this->addError($this->tooShort, ['{min}' => $this->min]); 51 | 52 | if ($this->max !== null && $length > $this->max) 53 | $this->addError($this->tooLong, ['{max}' => $this->max]); 54 | 55 | if ($this->length !== null && $length !== $this->length) 56 | $this->addError($this->notEqual, ['{length}' => $this->length]); 57 | } 58 | } -------------------------------------------------------------------------------- /valify/validators/UniqueValidator.php: -------------------------------------------------------------------------------- 1 | edsn) 37 | $this->parseEDSN(); 38 | 39 | if(!$this->table || !$this->column) 40 | throw new \UnexpectedValueException('Table and column must be specified'); 41 | 42 | if( !is_string($this->table) || !is_string($this->column) ) 43 | throw new \UnexpectedValueException('Table and column must be set as string'); 44 | 45 | mysqli_report(MYSQLI_REPORT_STRICT); 46 | $mysqli = new \mysqli($this->host, $this->user, $this->pass, $this->dbname); 47 | 48 | if($mysqli->connect_errno) 49 | throw new \mysqli_sql_exception(printf("Connection error: %s\n", $mysqli->connect_error)); 50 | 51 | $this->conn = $mysqli; 52 | 53 | parent::init(); 54 | } 55 | 56 | protected function validateValue($value) { 57 | if( !$this->isPrintable($value) ) 58 | $this->addError('Value is not a string'); 59 | else { 60 | $value = empty($value) ? 'NULL' : "'" . $this->conn->real_escape_string($value) . "'"; 61 | 62 | $sql = "SELECT count(*) as count from " . $this->table . " where " . $this->column . " = " . $value; 63 | 64 | $res = $this->conn->query($sql); 65 | $res = $res->fetch_object(); 66 | 67 | if( (int)$res->count > 0 ) { 68 | $this->addError($this->message); 69 | } 70 | } 71 | } 72 | 73 | private function parseEDSN() { 74 | $edsn = explode(';', trim($this->edsn, ';')); 75 | 76 | foreach ($edsn as $param) { 77 | $name = substr($param, 0, strpos($param, '=')); 78 | $value = substr($param, strpos($param, '=') + 1); 79 | 80 | switch($name) { 81 | case 'db': 82 | $this->dbname = $value; 83 | break; 84 | case 'h': 85 | $this->host = $value; 86 | break; 87 | case 'u': 88 | $this->user = $value; 89 | break; 90 | case 'p': 91 | $this->pass = $value; 92 | break; 93 | case 't': 94 | $this->table = $value; 95 | break; 96 | case 'c': 97 | $this->column = $value; 98 | break; 99 | } 100 | } 101 | } 102 | } -------------------------------------------------------------------------------- /valify/validators/UrlValidator.php: -------------------------------------------------------------------------------- 1 | enableIDN && !function_exists('idn_to_ascii')) 37 | $this->addError($this->intlErrorMessage); 38 | 39 | parent::init(); 40 | } 41 | 42 | public function validateValue($value) { 43 | // make sure the length is limited to avoid DOS attacks 44 | if (is_string($value) && strlen($value) < 2000) { 45 | if ($this->defaultScheme !== null && strpos($value, '://') === false) { 46 | $value = $this->defaultScheme . '://' . $value; 47 | } 48 | if (strpos($this->pattern, '{schemes}') !== false) { 49 | $pattern = str_replace('{schemes}', '(' . implode('|', $this->validSchemes) . ')', $this->pattern); 50 | } else { 51 | $pattern = $this->pattern; 52 | } 53 | 54 | if ($this->enableIDN) { 55 | $value = preg_replace_callback('/:\/\/([^\/]+)/', function ($matches) { 56 | return '://' . idn_to_ascii($matches[1]); 57 | }, $value); 58 | } 59 | if (!preg_match($pattern, $value)) { 60 | $this->addError($this->message); 61 | } 62 | } else { 63 | $this->addError($this->message); 64 | } 65 | } 66 | } --------------------------------------------------------------------------------