1 | <?php |
||
2 | /** |
||
3 | * __ ___ ____ _ ___ _ __ |
||
4 | * / |/ /_ __/ / /_(_)___/ (_)___ ___ ___ ____ _____(_)___ ____ ____ _/ / |
||
5 | * / /|_/ / / / / / __/ / __ / / __ `__ \/ _ \/ __ \/ ___/ / __ \/ __ \ / __ `/ / |
||
6 | * / / / / /_/ / / /_/ / /_/ / / / / / / / __/ / / (__ ) / /_/ / / / // /_/ / / |
||
7 | * /_/ /_/\__,_/_/\__/_/\__,_/_/_/ /_/ /_/\___/_/ /_/____/_/\____/_/ /_(_)__,_/_/ |
||
8 | * |
||
9 | * Array Validation Library |
||
10 | * Copyright (c) Multidimension.al (http://multidimension.al) |
||
11 | * Github : https://github.com/multidimension-al/array-validation |
||
12 | * |
||
13 | * Licensed under The MIT License |
||
14 | * For full copyright and license information, please see the LICENSE file |
||
15 | * Redistributions of files must retain the above copyright notice. |
||
16 | * |
||
17 | * @copyright Copyright © 2017-2019 Multidimension.al (http://multidimension.al) |
||
18 | * @link https://github.com/multidimension-al/array-validation Github |
||
19 | * @license http://www.opensource.org/licenses/mit-license.php MIT License |
||
20 | */ |
||
21 | |||
22 | namespace Multidimensional\ArrayValidation; |
||
23 | |||
24 | use Exception; |
||
25 | |||
26 | class Validation |
||
27 | { |
||
28 | /** |
||
29 | * @param array $array |
||
30 | * @param array $rules |
||
31 | * @return true |
||
32 | * @throws Exception |
||
33 | */ |
||
34 | 60 | public static function validate($array, $rules) |
|
35 | { |
||
36 | 60 | if (is_array($array) && is_array($rules)) { |
|
0 ignored issues
–
show
introduced
by
![]() |
|||
37 | 58 | self::checkRequired($array, $rules); |
|
38 | 52 | foreach ($array as $key => $value) { |
|
39 | 50 | if (!isset($rules[$key]) && !array_key_exists($key, $rules)) { |
|
40 | 2 | throw new Exception(sprintf('Unexpected key "%s" found in array.', $key)); |
|
41 | } |
||
42 | |||
43 | 50 | if (is_array($value) && isset($rules[$key]['type']) && strtolower($rules[$key]['type']) == 'array' && isset($rules[$key]['fields'])) { |
|
44 | 6 | self::validate($value, $rules[$key]['fields']); |
|
45 | 50 | } elseif (in_array($key, array_keys($rules))) { |
|
46 | 51 | self::validateField($value, $rules[$key], $key); |
|
47 | } |
||
48 | } |
||
49 | 2 | } elseif (!is_array($array)) { |
|
50 | 2 | throw new Exception('Validation array not found.'); |
|
51 | 2 | } elseif (!is_array($rules)) { |
|
52 | 2 | throw new Exception('Validation rules array not found.'); |
|
53 | } |
||
54 | |||
55 | 44 | return true; |
|
56 | } |
||
57 | |||
58 | /** |
||
59 | * Main required check function. Must be fed with two variables, both of type array. |
||
60 | * Returns void if all checks pass without a Exception being thrown. Only |
||
61 | * performs checkRequired operation on values that are not set in the main $array. |
||
62 | * |
||
63 | * @param array $array Validation Array comprised of key / value pairs to be checked. |
||
64 | * @param array $rules Rules Array comprised of properly formatted keys with rules. |
||
65 | * @return void |
||
66 | * @throws Exception |
||
67 | */ |
||
68 | 58 | protected static function checkRequired($array, $rules) |
|
69 | { |
||
70 | 58 | if (is_array($rules)) { |
|
0 ignored issues
–
show
|
|||
71 | 58 | foreach ($rules as $key => $value) { |
|
72 | 56 | if (self::requiredNull($array, $key) || self::requiredEmpty($array, $key)) { |
|
73 | 34 | if (isset($value['required'])) { |
|
74 | 18 | if (is_array($value['required']) && !self::requiredOne($array, $value['required'])) { |
|
75 | // |
||
76 | 18 | } elseif (($value['required'] == 'true' || $value['required'] === true) && isset($array[$key]) && (!empty($array[$key]) || is_numeric($array[$key]))) { |
|
77 | // |
||
78 | } else { |
||
79 | 35 | throw new Exception(sprintf('Required value not found for key: %s.', $key)); |
|
80 | } |
||
81 | } |
||
82 | } |
||
83 | } |
||
84 | } |
||
85 | |||
86 | 52 | return; |
|
87 | } |
||
88 | |||
89 | /** |
||
90 | * Returns true when $key value is null, returns false when it is not. |
||
91 | * |
||
92 | * @param array $array |
||
93 | * @param string $key |
||
94 | * @return true|false |
||
95 | */ |
||
96 | 56 | protected static function requiredNull($array, $key) |
|
97 | { |
||
98 | 56 | if ((!isset($array[$key]) && !array_key_exists($key, $array)) |
|
99 | 52 | || ((isset($array[$key]) || array_key_exists($key, $array)) |
|
100 | 56 | && ($array[$key] === null || $array[$key] === 'null')) |
|
101 | ) { |
||
102 | 22 | return true; |
|
103 | } |
||
104 | |||
105 | 48 | return false; |
|
106 | } |
||
107 | |||
108 | /** |
||
109 | * Returns true when $key value is null, returns false when it is not. |
||
110 | * |
||
111 | * @param array $array |
||
112 | * @param string $key |
||
113 | * @return true|false |
||
114 | */ |
||
115 | 50 | protected static function requiredEmpty($array, $key) |
|
116 | { |
||
117 | 50 | if ((!isset($array[$key]) && !array_key_exists($key, $array)) |
|
118 | 48 | || ((isset($array[$key]) || array_key_exists($key, $array)) |
|
119 | 50 | && (empty($array[$key] && !is_numeric($array[$key])))) |
|
120 | ) { |
||
121 | 20 | return true; |
|
122 | } |
||
123 | |||
124 | 46 | return false; |
|
125 | } |
||
126 | |||
127 | /** |
||
128 | * Returns true if one of the conditions comes back as requiring the key being tested |
||
129 | * to be required. Returns false if all the conditions find that checks do not require |
||
130 | * the key value to be present. |
||
131 | * |
||
132 | * @param array $array |
||
133 | * @param array $rules |
||
134 | * @return true|false |
||
135 | */ |
||
136 | 8 | protected static function requiredOne($array, $rules) |
|
137 | { |
||
138 | 8 | if (is_array($rules)) { |
|
0 ignored issues
–
show
|
|||
139 | 8 | foreach ($rules as $key => $value) { |
|
140 | 8 | if ((is_array($value) && self::requiredAll($array, $value)) || self::requiredTest($array, $value, $key)) { |
|
141 | 8 | return true; |
|
142 | } |
||
143 | } |
||
144 | } |
||
145 | 4 | return false; |
|
146 | } |
||
147 | |||
148 | /** |
||
149 | * Checks an array of $rules and returns false if any one of the rules fails. |
||
150 | * If all rule tests pass, then the method returns true. |
||
151 | * |
||
152 | * @param array $array |
||
153 | * @param array $rules |
||
154 | * @return true|false |
||
155 | */ |
||
156 | 2 | protected static function requiredAll($array, $rules) |
|
157 | { |
||
158 | 2 | if (is_array($rules)) { |
|
0 ignored issues
–
show
|
|||
159 | 2 | foreach ($rules as $key => $value) { |
|
160 | 2 | if (!self::requiredTest($array, $value, $key)) { |
|
161 | 2 | return false; |
|
162 | } |
||
163 | } |
||
164 | } |
||
165 | |||
166 | 2 | return true; |
|
167 | } |
||
168 | |||
169 | /** |
||
170 | * Returns output of testing functions. Will return true if $key is required |
||
171 | * false if the $key is not required. |
||
172 | * |
||
173 | * @param $array |
||
174 | * @param $value |
||
175 | * @param $key |
||
176 | * @return true|false |
||
177 | */ |
||
178 | 8 | protected static function requiredTest($array, $value, $key) |
|
179 | { |
||
180 | 8 | if (is_null($value) || $value == 'null') { |
|
181 | 4 | return self::requiredNull($array, $key); |
|
182 | 4 | } elseif (empty($value) && !is_numeric($value)) { |
|
183 | 2 | return self::requiredEmpty($array, $key); |
|
184 | 2 | } elseif (isset($array[$key]) && $array[$key] == $value) { |
|
185 | 2 | return true; |
|
186 | } |
||
187 | |||
188 | 2 | return false; |
|
189 | } |
||
190 | |||
191 | /** |
||
192 | * @param string $value |
||
193 | * @param array $rules |
||
194 | * @param string $key |
||
195 | * @return true |
||
196 | * @throws Exception |
||
197 | */ |
||
198 | 50 | public static function validateField($value, $rules, $key) |
|
199 | { |
||
200 | 50 | if (is_array($value) && isset($rules['type']) && strtolower($rules['type']) == 'group' && isset($rules['fields'])) { |
|
0 ignored issues
–
show
|
|||
201 | 6 | foreach ($value as $k => $v) { |
|
202 | 6 | if (is_array($v) && !isset($rules['fields'][$k])) { |
|
203 | 6 | self::validate($v, $rules['fields']); |
|
204 | 2 | } elseif (isset($rules['fields'][$k])) { |
|
205 | 4 | self::validateField($v, $rules['fields'][$k], $k); |
|
206 | } |
||
207 | } |
||
208 | 50 | } elseif (is_array($value)) { |
|
0 ignored issues
–
show
|
|||
209 | 4 | throw new Exception(sprintf('Unexpected array found for key: %s.', $key)); |
|
210 | } |
||
211 | |||
212 | 48 | if (isset($value) && $value !== null && $value != 'null' && (!empty($value) || is_numeric($value))) { |
|
213 | 44 | if (isset($rules['type']) && $rules['type'] != 'group') { |
|
214 | 44 | if ($rules['type'] === 'integer') { |
|
215 | 8 | self::validateInteger($value, $key); |
|
216 | 42 | } elseif ($rules['type'] === 'decimal') { |
|
217 | 6 | self::validateDecimal($value, $key); |
|
218 | 40 | } elseif ($rules['type'] === 'string') { |
|
219 | 36 | self::validateString($value, $key); |
|
220 | 4 | } elseif ($rules['type'] === 'boolean') { |
|
221 | 2 | self::validateBoolean($value, $key); |
|
222 | } else { |
||
223 | 2 | throw new Exception(sprintf('Invalid type "%s" for key: %s.', $rules['type'], $key)); |
|
224 | } |
||
225 | } |
||
226 | |||
227 | 40 | if (isset($rules['values'])) { |
|
228 | 10 | self::validateValues($value, $rules['values'], $key); |
|
229 | } |
||
230 | |||
231 | 38 | if (isset($rules['pattern'])) { |
|
232 | 16 | if (strtoupper($rules['pattern']) == 'ISO 8601' || strtoupper($rules['pattern']) == 'ISO8601') { |
|
233 | 2 | self::validateISO8601($value, $key); |
|
234 | 16 | } else if (strtoupper($rules['pattern']) == 'URL') { |
|
235 | 2 | self::validateURL($value, $key); |
|
236 | 14 | } else if (strtoupper($rules['pattern']) == 'EMAIL') { |
|
237 | 2 | self::validateEmail($value, $key); |
|
238 | 12 | } else if (strtoupper($rules['pattern']) == 'IP') { |
|
239 | 4 | self::validateIP($value, $key); |
|
240 | 10 | } else if (strtoupper($rules['pattern']) == 'MAC') { |
|
241 | 2 | self::validateMAC($value, $key); |
|
242 | } else { |
||
243 | 8 | self::validatePattern($value, $rules['pattern'], $key); |
|
244 | } |
||
245 | } |
||
246 | } |
||
247 | |||
248 | 44 | return true; |
|
249 | } |
||
250 | |||
251 | /** |
||
252 | * @param int $value |
||
253 | * @param string|null $key |
||
254 | * @return true|false |
||
255 | * @throws Exception |
||
256 | */ |
||
257 | 8 | protected static function validateInteger($value, $key) |
|
258 | { |
||
259 | 8 | if (!is_int($value)) { |
|
0 ignored issues
–
show
|
|||
260 | 2 | throw new Exception(sprintf('Invalid integer "%s" for key: %s.', $value, $key)); |
|
261 | } |
||
262 | |||
263 | 8 | return true; |
|
264 | } |
||
265 | |||
266 | /** |
||
267 | * @param float $value |
||
268 | * @param string|null $key |
||
269 | * @return true|false |
||
270 | * @throws Exception |
||
271 | */ |
||
272 | 6 | protected static function validateDecimal($value, $key) |
|
273 | { |
||
274 | 6 | if (!is_float($value)) { |
|
0 ignored issues
–
show
|
|||
275 | 2 | throw new Exception(sprintf('Invalid decimal "%s" for key: %s.', $value, $key)); |
|
276 | } |
||
277 | |||
278 | 6 | return true; |
|
279 | } |
||
280 | |||
281 | /** |
||
282 | * @param string $value |
||
283 | * @param string|null $key |
||
284 | * @return true|false |
||
285 | * @throws Exception |
||
286 | */ |
||
287 | 36 | protected static function validateString($value, $key) |
|
288 | { |
||
289 | 36 | if (!is_string($value)) { |
|
0 ignored issues
–
show
|
|||
290 | 2 | throw new Exception(sprintf('Invalid string "%s" for key: %s.', $value, $key)); |
|
291 | } |
||
292 | |||
293 | 36 | return true; |
|
294 | } |
||
295 | |||
296 | /** |
||
297 | * @param bool $value |
||
298 | * @param string|null $key |
||
299 | * @return true |
||
300 | * @throws Exception |
||
301 | */ |
||
302 | 2 | protected static function validateBoolean($value, $key) |
|
303 | { |
||
304 | 2 | if (!is_bool($value)) { |
|
0 ignored issues
–
show
|
|||
305 | 2 | throw new Exception(sprintf('Invalid boolean "%s" for key: %s.', $value, $key)); |
|
306 | } |
||
307 | |||
308 | return true; |
||
309 | } |
||
310 | |||
311 | /** |
||
312 | * @param string $value |
||
313 | * @param array|string $array |
||
314 | * @param string|null $key |
||
315 | * @return bool |
||
316 | * @throws Exception |
||
317 | */ |
||
318 | 10 | protected static function validateValues($value, $array, $key) |
|
319 | { |
||
320 | 10 | $compareArray = []; |
|
321 | 10 | if (is_array($array)) { |
|
322 | 8 | foreach ($array as $compareKey) { |
|
323 | 8 | $compareScore = levenshtein($value, $compareKey); |
|
324 | 8 | $compareArray[$compareKey] = $compareScore; |
|
325 | 8 | if ($compareScore === 0) { |
|
326 | 7 | return true; |
|
327 | } |
||
328 | } |
||
329 | 2 | } elseif (strcasecmp($value, $array) === 0) { |
|
330 | 2 | return true; |
|
331 | } |
||
332 | |||
333 | 2 | $errorMessage = sprintf('Invalid value "%s" for key: %s.', $value, $key); |
|
334 | |||
335 | 1 | array_walk($compareArray, function(&$item) { |
|
336 | 2 | $item = abs($item); |
|
337 | 2 | }); |
|
338 | 2 | asort($compareArray); |
|
339 | 2 | $compareArray = array_keys($compareArray); |
|
340 | 2 | if (count($compareArray)) { |
|
341 | 2 | $errorMessage .= sprintf(' Did you mean "%s"?', array_shift($compareArray)); |
|
342 | } |
||
343 | |||
344 | 2 | throw new Exception($errorMessage); |
|
345 | } |
||
346 | |||
347 | /** |
||
348 | * @param string $value |
||
349 | * @param string $key |
||
350 | * @return bool |
||
351 | * @throws Exception |
||
352 | */ |
||
353 | 2 | protected static function validateISO8601($value, $key) |
|
354 | { |
||
355 | 2 | $pattern = '(?:[1-9]\d{3}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1\d|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[1-9]\d(?:0[48]|[2468][048]|[13579][26])|(?:[2468][048]|[13579][26])00)-02-29)T(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:Z|[+-][01]\d:[0-5]\d)'; |
|
356 | 2 | if (!preg_match('/^'.$pattern.'$/', $value)) { |
|
357 | 2 | throw new Exception(sprintf('Invalid value "%s" does not match ISO 8601 pattern for key: %s.', $value, $key)); |
|
358 | } |
||
359 | |||
360 | 2 | return true; |
|
361 | } |
||
362 | |||
363 | /** |
||
364 | * @param string $value |
||
365 | * @param string $key |
||
366 | * @return bool |
||
367 | * @throws Exception |
||
368 | */ |
||
369 | 2 | protected static function validateURL($value, $key) |
|
370 | { |
||
371 | 2 | if (!filter_var($value, FILTER_VALIDATE_URL)) { |
|
372 | 2 | throw new Exception(sprintf('Invalid value "%s" does not match URL pattern for key: %s.', $value, $key)); |
|
373 | } |
||
374 | |||
375 | 2 | return true; |
|
376 | } |
||
377 | |||
378 | /** |
||
379 | * @param string $value |
||
380 | * @param string $key |
||
381 | * @return bool |
||
382 | * @throws Exception |
||
383 | */ |
||
384 | 2 | protected static function validateEmail($value, $key) |
|
385 | { |
||
386 | 2 | if (!filter_var($value, FILTER_VALIDATE_EMAIL)) { |
|
387 | 2 | throw new Exception(sprintf('Invalid value "%s" does not match email pattern for key: %s.', $value, $key)); |
|
388 | } |
||
389 | |||
390 | 2 | return true; |
|
391 | } |
||
392 | |||
393 | /** |
||
394 | * @param string $value |
||
395 | * @param string $key |
||
396 | * @return bool |
||
397 | * @throws Exception |
||
398 | */ |
||
399 | 4 | protected static function validateIP($value, $key) |
|
400 | { |
||
401 | 4 | if (!filter_var($value, FILTER_VALIDATE_IP)) { |
|
402 | 2 | throw new Exception(sprintf('Invalid value "%s" does not match IP address pattern for key: %s.', $value, $key)); |
|
403 | } |
||
404 | |||
405 | 4 | return true; |
|
406 | } |
||
407 | |||
408 | /** |
||
409 | * @param string $value |
||
410 | * @param string $key |
||
411 | * @return bool |
||
412 | * @throws Exception |
||
413 | */ |
||
414 | 2 | protected static function validateMAC($value, $key) |
|
415 | { |
||
416 | 2 | if (!filter_var($value, FILTER_VALIDATE_MAC)) { |
|
417 | 2 | throw new Exception(sprintf('Invalid value "%s" does not match MAC address pattern for key: %s.', $value, $key)); |
|
418 | } |
||
419 | |||
420 | 2 | return true; |
|
421 | } |
||
422 | |||
423 | |||
424 | /** |
||
425 | * @param string $value |
||
426 | * @param string $pattern |
||
427 | * @param string $key |
||
428 | * @return bool |
||
429 | * @throws Exception |
||
430 | */ |
||
431 | 8 | protected static function validatePattern($value, $pattern, $key) |
|
432 | { |
||
433 | 8 | if (!preg_match('/^'.$pattern.'$/', $value)) { |
|
434 | 2 | throw new Exception(sprintf('Invalid value "%s" does not match pattern "%s" for key: %s.', $value, $pattern, $key)); |
|
435 | } |
||
436 | |||
437 | 8 | return true; |
|
438 | } |
||
439 | } |
||
440 |