1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
namespace Suricate; |
6
|
|
|
|
7
|
|
|
use InvalidArgumentException; |
8
|
|
|
use BadMethodCallException; |
9
|
|
|
use Exception; |
10
|
|
|
|
11
|
|
|
/** |
12
|
|
|
* Validator |
13
|
|
|
* Inspired from Kieron Wilson PHP Validator |
14
|
|
|
* |
15
|
|
|
* @method Validator true(string $errorMessage) |
16
|
|
|
* @method Validator false(string $errorMessage) |
17
|
|
|
* @method Validator equalTo(mixed $toTest, string $errorMessage) |
18
|
|
|
* @method Validator identicalTo(mixed $toTest, string $errorMessage) |
19
|
|
|
* @method Validator lessThan(mixed $toTest, string $errorMessage) |
20
|
|
|
* @method Validator lessThanOrEqual(mixed $toTest, string $errorMessage) |
21
|
|
|
* @method Validator greaterThan(mixed $toTest, string $errorMessage) |
22
|
|
|
* @method Validator greaterThanOrEqual(mixed $toTest, string $errorMessage) |
23
|
|
|
* @method Validator blank(string $errorMessage) |
24
|
|
|
* @method Validator null(string $errorMessage) |
25
|
|
|
* @method Validator type(string $testType, string $errorMessage) |
26
|
|
|
* &method Validator email(string $errorMessage) |
27
|
|
|
* &method Validator url(string $errorMessage) |
28
|
|
|
* &method Validator ip(string $errorMessage) |
29
|
|
|
* &method Validator regexp(string $errorMessage) |
30
|
|
|
* @author Mathieu LESNIAK <[email protected]> |
31
|
|
|
* @copyright Mathieu LESNIAK |
32
|
|
|
* @package Suricate |
33
|
|
|
*/ |
34
|
|
|
class Validator |
35
|
|
|
{ |
36
|
|
|
private $errors = []; |
37
|
|
|
private $checks = []; |
38
|
|
|
private $datas; |
39
|
|
|
private $value; |
40
|
|
|
private $index; |
41
|
|
|
private $stop = false; |
42
|
|
|
|
43
|
15 |
|
public function __construct($input) |
44
|
|
|
{ |
45
|
15 |
|
$this->datas = $input; |
46
|
15 |
|
$this->value = $input; |
47
|
15 |
|
$this->createChecks(); |
48
|
15 |
|
} |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* Initialize checks |
52
|
|
|
* |
53
|
|
|
* @return void |
54
|
|
|
* @SuppressWarnings(PHPMD.CyclomaticComplexity) |
55
|
|
|
* @SuppressWarnings(PHPMD.ExcessiveMethodLength) |
56
|
|
|
*/ |
57
|
15 |
|
private function createChecks() |
58
|
|
|
{ |
59
|
|
|
$this->checks['equalTo'] = function ($value, $compare) { |
60
|
1 |
|
return $value == $compare; |
61
|
|
|
}; |
62
|
|
|
|
63
|
|
|
$this->checks['identicalTo'] = function ($value, $compare) { |
64
|
1 |
|
return $value === $compare; |
65
|
|
|
}; |
66
|
|
|
|
67
|
|
|
$this->checks['lessThan'] = function ($value, $compare) { |
68
|
1 |
|
return $value < $compare; |
69
|
|
|
}; |
70
|
|
|
|
71
|
|
|
$this->checks['lessThanOrEqual'] = function ($value, $compare) { |
72
|
1 |
|
return $value <= $compare; |
73
|
|
|
}; |
74
|
|
|
|
75
|
|
|
$this->checks['greaterThan'] = function ($value, $compare) { |
76
|
1 |
|
return $value > $compare; |
77
|
|
|
}; |
78
|
|
|
|
79
|
|
|
$this->checks['greaterThanOrEqual'] = function ($value, $compare) { |
80
|
1 |
|
return $value >= $compare; |
81
|
|
|
}; |
82
|
|
|
|
83
|
|
|
$this->checks['blank'] = function ($value) { |
84
|
1 |
|
return $value == ''; |
85
|
|
|
}; |
86
|
|
|
|
87
|
|
|
$this->checks['null'] = function ($value) { |
88
|
1 |
|
return is_null($value); |
89
|
|
|
}; |
90
|
|
|
|
91
|
|
|
$this->checks['true'] = function ($value) { |
92
|
1 |
|
return $value === true; |
93
|
|
|
}; |
94
|
|
|
|
95
|
|
|
$this->checks['false'] = function ($value) { |
96
|
1 |
|
return !($value === true); |
97
|
|
|
}; |
98
|
|
|
|
99
|
|
|
$this->checks['type'] = function ($value, $type) { |
100
|
1 |
|
switch ($type) { |
101
|
1 |
|
case 'array': |
102
|
1 |
|
return is_array($value); |
103
|
1 |
|
case 'bool': |
104
|
1 |
|
return is_bool($value); |
105
|
1 |
|
case 'callable': |
106
|
|
|
return is_callable($value); |
107
|
1 |
|
case 'float': |
108
|
1 |
|
return is_float($value); |
109
|
1 |
|
case 'int': |
110
|
1 |
|
return is_int($value); |
111
|
1 |
|
case 'numeric': |
112
|
1 |
|
return is_numeric($value); |
113
|
1 |
|
case 'object': |
114
|
1 |
|
return is_object($value); |
115
|
1 |
|
case 'resource': |
116
|
|
|
return is_resource($value); |
117
|
1 |
|
case 'scalar': |
118
|
|
|
return is_scalar($value); |
119
|
1 |
|
case 'string': |
120
|
1 |
|
return is_string($value); |
121
|
|
|
default: |
122
|
|
|
throw new InvalidArgumentException( |
123
|
|
|
'Unknown type to check ' . $type |
124
|
|
|
); |
125
|
|
|
} |
126
|
|
|
}; |
127
|
|
|
|
128
|
|
|
$this->checks['email'] = function ($value) { |
129
|
1 |
|
return filter_var($value, FILTER_VALIDATE_EMAIL) !== false; |
130
|
|
|
}; |
131
|
|
|
|
132
|
|
|
$this->checks['url'] = function ($value) { |
133
|
1 |
|
return filter_var($value, FILTER_VALIDATE_URL) !== false; |
134
|
|
|
}; |
135
|
|
|
|
136
|
|
|
$this->checks['ip'] = function ($value) { |
137
|
1 |
|
return filter_var($value, FILTER_VALIDATE_IP) !== false; |
138
|
|
|
}; |
139
|
|
|
|
140
|
|
|
$this->checks['regexp'] = function ($value, $regexp) { |
141
|
|
|
return filter_var($value, FILTER_VALIDATE_REGEXP, [ |
142
|
|
|
"options" => ['regexp' => $regexp] |
143
|
|
|
]) !== false; |
144
|
|
|
}; |
145
|
|
|
|
146
|
|
|
$this->checks['longerThan'] = function ($value, $length) { |
147
|
|
|
return strlen((string) $value) > $length; |
148
|
|
|
}; |
149
|
|
|
|
150
|
|
|
$this->checks['longerThanOrEqual'] = function ($value, $length) { |
151
|
|
|
return strlen((string) $value) >= $length; |
152
|
|
|
}; |
153
|
|
|
|
154
|
|
|
$this->checks['shorterThan'] = function ($value, $length) { |
155
|
|
|
return strlen((string) $value) < $length; |
156
|
|
|
}; |
157
|
|
|
|
158
|
|
|
$this->checks['shortThanOrEqual'] = function ($value, $length) { |
159
|
|
|
return strlen((string) $value) <= $length; |
160
|
|
|
}; |
161
|
|
|
|
162
|
|
|
$this->checks['contains'] = function ($value, $toFind) { |
163
|
|
|
return strpos((string) $value, $toFind) !== false; |
164
|
|
|
}; |
165
|
|
|
|
166
|
|
|
$this->checks['alnum'] = function ($value) { |
167
|
1 |
|
return ctype_alnum($value); |
168
|
|
|
}; |
169
|
|
|
|
170
|
|
|
$this->checks['alpha'] = function ($value) { |
171
|
|
|
return ctype_alpha($value); |
172
|
|
|
}; |
173
|
|
|
|
174
|
|
|
$this->checks['digit'] = function ($value) { |
175
|
|
|
return ctype_digit($value); |
176
|
|
|
}; |
177
|
|
|
|
178
|
|
|
$this->checks['lower'] = function ($value) { |
179
|
|
|
return ctype_lower($value); |
180
|
|
|
}; |
181
|
|
|
|
182
|
|
|
$this->checks['upper'] = function ($value) { |
183
|
|
|
return ctype_upper($value); |
184
|
|
|
}; |
185
|
|
|
|
186
|
|
|
$this->checks['space'] = function ($value) { |
187
|
|
|
return ctype_space($value); |
188
|
|
|
}; |
189
|
15 |
|
} |
190
|
|
|
|
191
|
|
|
public function validate($index = null) |
192
|
|
|
{ |
193
|
|
|
if ($index === null) { |
194
|
|
|
$this->value = $this->datas; |
195
|
|
|
$this->index = null; |
196
|
|
|
return $this; |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
if (is_object($this->datas)) { |
200
|
|
|
try { |
201
|
|
|
$this->value = $this->datas->$index; |
202
|
|
|
} catch (Exception $e) { |
|
|
|
|
203
|
|
|
throw new InvalidArgumentException( |
204
|
|
|
'class property "' . $index . '" does not exists' |
205
|
|
|
); |
206
|
|
|
} |
207
|
|
|
$this->index = $index; |
208
|
|
|
|
209
|
|
|
return $this; |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
if (array_key_exists($index, $this->datas)) { |
213
|
|
|
$this->value = $this->datas[$index]; |
214
|
|
|
$this->index = $index; |
215
|
|
|
|
216
|
|
|
return $this; |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
throw new InvalidArgumentException( |
220
|
|
|
'Index / Property "' . $index . '" does not exists' |
221
|
|
|
); |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
public function callValidate() |
225
|
|
|
{ |
226
|
|
|
$args = func_get_args(); |
227
|
|
|
if (count($args) < 1) { |
228
|
|
|
throw new InvalidArgumentException('bad number of arguments'); |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
$method = array_shift($args); |
232
|
|
|
if (is_callable($method)) { |
233
|
|
|
$this->index = null; |
234
|
|
|
$this->value = call_user_func_array($method, $args); |
235
|
|
|
|
236
|
|
|
return $this; |
237
|
|
|
} |
238
|
|
|
|
239
|
|
|
throw new InvalidArgumentException('Bad method'); |
240
|
|
|
} |
241
|
|
|
|
242
|
15 |
|
public function __call($method, $parameters) |
243
|
|
|
{ |
244
|
15 |
|
if (!$this->stop) { |
245
|
|
|
// Stop on error, ignore others tests if fails |
246
|
15 |
|
if (substr($method, 0, 4) == 'stop') { |
247
|
|
|
$stopOnError = true; |
248
|
|
|
$method = lcFirst(substr($method, 4)); |
249
|
|
|
} else { |
250
|
15 |
|
$stopOnError = false; |
251
|
|
|
} |
252
|
|
|
|
253
|
|
|
// Negation check |
254
|
15 |
|
if (substr(strtolower($method), 0, 3) == 'not') { |
255
|
|
|
$negation = true; |
256
|
|
|
$method = lcFirst(substr($method, 3)); |
257
|
|
|
} else { |
258
|
15 |
|
$negation = false; |
259
|
|
|
} |
260
|
|
|
|
261
|
15 |
|
if (!isset($this->checks[$method])) { |
262
|
|
|
throw new BadMethodCallException('Unknown check ' . $method); |
263
|
|
|
} else { |
264
|
15 |
|
$validator = $this->checks[$method]; |
265
|
|
|
} |
266
|
|
|
|
267
|
15 |
|
$errorMessage = array_pop($parameters); |
268
|
|
|
|
269
|
15 |
|
array_unshift($parameters, $this->value); |
270
|
|
|
|
271
|
15 |
|
$validation = (bool) (call_user_func_array( |
272
|
15 |
|
$validator, |
273
|
|
|
$parameters |
274
|
15 |
|
) ^ $negation); |
275
|
15 |
|
if (!$validation) { |
276
|
9 |
|
if ($stopOnError) { |
277
|
|
|
$this->stop = true; |
278
|
|
|
} |
279
|
9 |
|
if ($this->index === null) { |
280
|
9 |
|
$this->errors[] = $errorMessage; |
281
|
|
|
} else { |
282
|
|
|
$this->errors[$this->index][] = $errorMessage; |
283
|
|
|
} |
284
|
|
|
} |
285
|
|
|
} |
286
|
|
|
|
287
|
15 |
|
return $this; |
288
|
|
|
} |
289
|
|
|
|
290
|
2 |
|
public function getErrors($index = null) |
291
|
|
|
{ |
292
|
2 |
|
if ($index === null) { |
293
|
2 |
|
return $this->errors; |
294
|
|
|
} else { |
295
|
|
|
return isset($this->errors[$index]) ? $this->errors[$index] : []; |
296
|
|
|
} |
297
|
|
|
} |
298
|
|
|
|
299
|
15 |
|
public function pass() |
300
|
|
|
{ |
301
|
15 |
|
return count($this->errors) == 0; |
302
|
|
|
} |
303
|
|
|
|
304
|
8 |
|
public function fails() |
305
|
|
|
{ |
306
|
8 |
|
return !$this->pass(); |
307
|
|
|
} |
308
|
|
|
} |
309
|
|
|
|
This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.
Unreachable code is most often the result of
return
,die
orexit
statements that have been added for debug purposes.In the above example, the last
return false
will never be executed, because a return statement has already been met in every possible execution path.