Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
94.41% |
135 / 143 |
|
89.29% |
25 / 28 |
CRAP | |
0.00% |
0 / 1 |
FormValidator | |
94.41% |
135 / 143 |
|
89.29% |
25 / 28 |
100.72 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
6 | |||
createFromConfig | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
6 | |||
addValidators | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
addValidator | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
5 | |||
hasValidators | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
4 | |||
getValidators | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
4 | |||
hasValidator | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
getValidator | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
3 | |||
removeValidators | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
4 | |||
removeValidator | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
setRequired | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
4 | |||
isRequired | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
removeRequired | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
setValues | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getValues | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
filterValue | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 | |||
filter | |
85.71% |
6 / 7 |
|
0.00% |
0 / 1 |
4.05 | |||
validate | |
97.22% |
35 / 36 |
|
0.00% |
0 / 1 |
21 | |||
hasErrors | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
getErrors | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
getError | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
3 | |||
addError | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
count | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
toArray | |
33.33% |
3 / 9 |
|
0.00% |
0 / 1 |
12.41 | |||
__set | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
__get | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
__isset | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
__unset | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * Pop PHP Framework (http://www.popphp.org/) |
4 | * |
5 | * @link https://github.com/popphp/popphp-framework |
6 | * @author Nick Sagona, III <dev@nolainteractive.com> |
7 | * @copyright Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com) |
8 | * @license http://www.popphp.org/license New BSD License |
9 | */ |
10 | |
11 | /** |
12 | * @namespace |
13 | */ |
14 | namespace Pop\Form; |
15 | |
16 | /** |
17 | * Form validator class |
18 | * |
19 | * @category Pop |
20 | * @package Pop\Form |
21 | * @author Nick Sagona, III <dev@nolainteractive.com> |
22 | * @copyright Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com) |
23 | * @license http://www.popphp.org/license New BSD License |
24 | * @version 4.0.0 |
25 | */ |
26 | |
27 | class FormValidator implements FormInterface, \ArrayAccess, \Countable, \IteratorAggregate |
28 | { |
29 | |
30 | /** |
31 | * Trait declaration |
32 | */ |
33 | use FormTrait; |
34 | |
35 | /** |
36 | * Form validators |
37 | * @var array |
38 | */ |
39 | protected array $validators = []; |
40 | |
41 | /** |
42 | * Required fields |
43 | * @var array |
44 | */ |
45 | protected array $required = []; |
46 | |
47 | /** |
48 | * Form values |
49 | * @var array |
50 | */ |
51 | protected array $values = []; |
52 | |
53 | /** |
54 | * Form validation errors |
55 | * @var array |
56 | */ |
57 | protected array $errors = []; |
58 | |
59 | /** |
60 | * Constructor |
61 | * |
62 | * Instantiate the form validator object |
63 | * |
64 | * @param ?array $validators |
65 | * @param mixed $required |
66 | * @param ?array $values |
67 | * @param mixed $filters |
68 | */ |
69 | public function __construct(array $validators = null, mixed $required = null, ?array $values = null, mixed $filters = null) |
70 | { |
71 | if (!empty($validators)) { |
72 | $this->addValidators($validators); |
73 | } |
74 | if ($required !== null) { |
75 | $this->setRequired($required); |
76 | } |
77 | if ($values !== null) { |
78 | $this->setValues($values); |
79 | } |
80 | if ($filters !== null) { |
81 | if (is_array($filters)) { |
82 | $this->addFilters($filters); |
83 | } else { |
84 | $this->addFilter($filters); |
85 | } |
86 | } |
87 | } |
88 | |
89 | /** |
90 | * Create form validator from config |
91 | * |
92 | * @param array|FormConfig $formConfig |
93 | * @param mixed $required |
94 | * @param ?array $values |
95 | * @param mixed $filters |
96 | * @return FormValidator |
97 | */ |
98 | public static function createFromConfig( |
99 | array|FormConfig $formConfig, mixed $required = null, ?array $values = null, mixed $filters = null |
100 | ): FormValidator |
101 | { |
102 | $validators = []; |
103 | $required = []; |
104 | |
105 | foreach ($formConfig as $key => $value) { |
106 | if (!empty($value['validator'])) { |
107 | $validators[$key] = $value['validator']; |
108 | } else if (!empty($value['validators'])) { |
109 | $validators[$key] = $value['validators']; |
110 | } |
111 | if (isset($value['required']) && ($value['required'] == true)) { |
112 | $required[] = $key; |
113 | } |
114 | } |
115 | |
116 | return new self($validators, $required, $values, $filters); |
117 | } |
118 | |
119 | /** |
120 | * Add validators |
121 | * |
122 | * @param array $validators |
123 | * @return FormValidator |
124 | */ |
125 | public function addValidators(array $validators): FormValidator |
126 | { |
127 | foreach ($validators as $field => $validator) { |
128 | $this->addValidator($field, $validator); |
129 | } |
130 | return $this; |
131 | } |
132 | |
133 | /** |
134 | * Add validator |
135 | * |
136 | * @param string $field |
137 | * @param mixed $validator |
138 | * @return FormValidator |
139 | */ |
140 | public function addValidator(string $field, mixed $validator): FormValidator |
141 | { |
142 | if (!isset($this->validators[$field])) { |
143 | $this->validators[$field] = []; |
144 | } |
145 | |
146 | if (!is_array($validator)) { |
147 | $validator = [$validator]; |
148 | } |
149 | |
150 | foreach ($validator as $valid) { |
151 | if (!in_array($valid, $this->validators[$field], true)) { |
152 | $this->validators[$field][] = $valid; |
153 | } |
154 | } |
155 | |
156 | return $this; |
157 | } |
158 | |
159 | /** |
160 | * Has validators |
161 | * |
162 | * @param ?string $field |
163 | * @return bool |
164 | */ |
165 | public function hasValidators(?string $field = null) |
166 | { |
167 | if ($field === null) { |
168 | return (count($this->validators) > 0); |
169 | } else if (($field !== null) && isset($this->validators[$field])) { |
170 | return (count($this->validators[$field]) > 0); |
171 | } else { |
172 | return false; |
173 | } |
174 | } |
175 | |
176 | /** |
177 | * Get validators |
178 | * |
179 | * @param string $field |
180 | * @return mixed |
181 | */ |
182 | public function getValidators(?string $field = null) |
183 | { |
184 | if ($field === null) { |
185 | return $this->validators; |
186 | } else if (($field !== null) && isset($this->validators[$field])) { |
187 | return $this->validators[$field]; |
188 | } else { |
189 | return null; |
190 | } |
191 | } |
192 | |
193 | /** |
194 | * Has validator |
195 | * |
196 | * @param string $field |
197 | * @param int $index |
198 | * @return bool |
199 | */ |
200 | public function hasValidator(string $field, int $index): bool |
201 | { |
202 | return (isset($this->validators[$field]) && isset($this->validators[$field][$index])); |
203 | } |
204 | |
205 | /** |
206 | * Get validator |
207 | * |
208 | * @param string $field |
209 | * @param int $index |
210 | * @return mixed |
211 | */ |
212 | public function getValidator(string $field, int $index) |
213 | { |
214 | return (isset($this->validators[$field]) && isset($this->validators[$field][$index])) ? |
215 | $this->validators[$field][$index] : null; |
216 | } |
217 | |
218 | /** |
219 | * Remove validators |
220 | * |
221 | * @param ?string $field |
222 | * @return FormValidator |
223 | */ |
224 | public function removeValidators(?string $field = null): FormValidator |
225 | { |
226 | if (($field !== null) && isset($this->validators[$field])) { |
227 | unset($this->validators[$field]); |
228 | } else if ($field === null) { |
229 | $this->validators = []; |
230 | } |
231 | return $this; |
232 | } |
233 | |
234 | /** |
235 | * Remove validator |
236 | * |
237 | * @param string $field |
238 | * @param int $index |
239 | * @return FormValidator |
240 | */ |
241 | public function removeValidator(string $field, int $index): FormValidator |
242 | { |
243 | if (isset($this->validators[$field]) && isset($this->validators[$field][$index])) { |
244 | unset($this->validators[$field][$index]); |
245 | } |
246 | return $this; |
247 | } |
248 | |
249 | /** |
250 | * Set required |
251 | * |
252 | * @param mixed $required |
253 | * @param ?string $requiredMessage |
254 | * @return FormValidator |
255 | */ |
256 | public function setRequired(mixed $required, ?string $requiredMessage = 'This field is required.'): FormValidator |
257 | { |
258 | if (!is_array($required)) { |
259 | $required = [$required]; |
260 | } |
261 | |
262 | foreach ($required as $req) { |
263 | if (!in_array($req, $this->required)) { |
264 | $this->required[$req] = $requiredMessage; |
265 | } |
266 | } |
267 | |
268 | return $this; |
269 | } |
270 | |
271 | /** |
272 | * Is required |
273 | * |
274 | * @param string $field |
275 | * @return bool |
276 | */ |
277 | public function isRequired(string $field): bool |
278 | { |
279 | return array_key_exists($field, $this->required); |
280 | } |
281 | |
282 | /** |
283 | * Remove required |
284 | * |
285 | * @param string $field |
286 | * @return FormValidator |
287 | */ |
288 | public function removeRequired(string $field): FormValidator |
289 | { |
290 | if (array_key_exists($field, $this->required)) { |
291 | unset($this->required[$field]); |
292 | } |
293 | |
294 | return $this; |
295 | } |
296 | |
297 | /** |
298 | * Set values |
299 | * |
300 | * @param array $values |
301 | * @return FormValidator |
302 | */ |
303 | public function setValues(array $values): FormValidator |
304 | { |
305 | $this->values = $values; |
306 | return $this; |
307 | } |
308 | |
309 | /** |
310 | * Get values |
311 | * |
312 | * @return array |
313 | */ |
314 | public function getValues(): array |
315 | { |
316 | return $this->values; |
317 | } |
318 | |
319 | /** |
320 | * Filter value with the filters |
321 | * |
322 | * @param mixed $field |
323 | * @throws Exception |
324 | * @return mixed |
325 | */ |
326 | public function filterValue(mixed $field): mixed |
327 | { |
328 | if (!isset($this->values[$field])) { |
329 | throw new Exception("Error: A value for '" . $field . "' has not been set."); |
330 | } |
331 | |
332 | $value = $this->values[$field]; |
333 | |
334 | foreach ($this->filters as $filter) { |
335 | $value = $filter->filter($value, $field); |
336 | } |
337 | |
338 | $this->values[$field] = $value; |
339 | |
340 | return $value; |
341 | } |
342 | |
343 | /** |
344 | * Filter values with the filters |
345 | * |
346 | * @param mixed $values |
347 | * @return mixed |
348 | */ |
349 | public function filter(mixed $values = null): mixed |
350 | { |
351 | if ($values !== null) { |
352 | $this->values = $values; |
353 | } |
354 | |
355 | if (is_array($this->values)) { |
356 | foreach ($this->values as $name => $value) { |
357 | $this->values[$name] = $this->filterValue($name); |
358 | } |
359 | } else { |
360 | $this->values = $this->filterValue($this->values); |
361 | } |
362 | |
363 | return $this->values; |
364 | } |
365 | |
366 | /** |
367 | * Validate values |
368 | * |
369 | * @param mixed $fields |
370 | * @return bool |
371 | */ |
372 | public function validate(mixed $fields = null): bool |
373 | { |
374 | $this->filter(); |
375 | |
376 | if ($fields !== null) { |
377 | $fields = (!is_array($fields)) ? [$fields] : $fields; |
378 | $formFields = array_filter( |
379 | $this->values, |
380 | function ($key) use ($fields) { |
381 | return in_array($key, $fields); |
382 | }, |
383 | ARRAY_FILTER_USE_KEY |
384 | ); |
385 | |
386 | foreach ($formFields as $field) { |
387 | if (array_key_exists($field, $this->required) && !isset($formFields[$field])) { |
388 | $this->addError($field, $this->required[$field]); |
389 | } |
390 | } |
391 | } else { |
392 | $formFields = $this->values; |
393 | // Check for required fields |
394 | foreach ($this->required as $field => $requiredMessage) { |
395 | if (!isset($formFields[$field])) { |
396 | $this->addError($field, $requiredMessage); |
397 | } |
398 | } |
399 | } |
400 | |
401 | // Check for required fields and execute any field validators |
402 | foreach ($formFields as $field => $value) { |
403 | if ($this->hasValidators($field)) { |
404 | foreach ($this->validators[$field] as $validator) { |
405 | if ($validator instanceof \Pop\Validator\ValidatorInterface) { |
406 | if (!$validator->evaluate($value)) { |
407 | $this->addError($field, $validator->getMessage()); |
408 | } |
409 | } else if (is_callable($validator)) { |
410 | $result = call_user_func_array($validator, [$value, $formFields]); |
411 | if ($result instanceof \Pop\Validator\ValidatorInterface) { |
412 | if (!$result->evaluate($value)) { |
413 | $this->addError($field, $result->getMessage()); |
414 | } |
415 | } else if (is_array($result)) { |
416 | foreach ($result as $val) { |
417 | if ($val instanceof \Pop\Validator\ValidatorInterface) { |
418 | if (!$val->evaluate($value)) { |
419 | $this->addError($field, $val->getMessage()); |
420 | } |
421 | } |
422 | } |
423 | } else if ($result !== null) { |
424 | $this->addError($field, $result); |
425 | } |
426 | } |
427 | } |
428 | } |
429 | } |
430 | |
431 | return !$this->hasErrors(); |
432 | } |
433 | |
434 | /** |
435 | * Has errors |
436 | * |
437 | * @param ?string $field |
438 | * @return bool |
439 | */ |
440 | public function hasErrors(?string $field = null): bool |
441 | { |
442 | if ($field !== null) { |
443 | return (isset($this->errors[$field]) && (count($this->errors[$field]) > 0)); |
444 | } else { |
445 | return (count($this->errors) > 0); |
446 | } |
447 | } |
448 | |
449 | /** |
450 | * Get errors |
451 | * |
452 | * @param ?string $field |
453 | * @return array |
454 | */ |
455 | public function getErrors(?string $field = null): array |
456 | { |
457 | if (($field !== null) && isset($this->errors[$field])) { |
458 | return $this->errors[$field]; |
459 | } else { |
460 | return $this->errors; |
461 | } |
462 | } |
463 | |
464 | /** |
465 | * Get error |
466 | * |
467 | * @param string $field |
468 | * @param int $index |
469 | * @return mixed |
470 | */ |
471 | public function getError(string $field, int $index): mixed |
472 | { |
473 | return (isset($this->errors[$field]) && isset($this->errors[$field][$index])) ? |
474 | $this->errors[$field][$index] : null; |
475 | } |
476 | |
477 | /** |
478 | * Add error |
479 | * |
480 | * @param string $field |
481 | * @param string $error |
482 | * @return FormValidator |
483 | */ |
484 | protected function addError(string $field, string $error): FormValidator |
485 | { |
486 | if (!isset($this->errors[$field])) { |
487 | $this->errors[$field] = []; |
488 | } |
489 | |
490 | if (!in_array($error, $this->errors[$field])) { |
491 | $this->errors[$field][] = $error; |
492 | } |
493 | |
494 | return $this; |
495 | } |
496 | |
497 | /** |
498 | * Count of values |
499 | * |
500 | * @return int |
501 | */ |
502 | public function count(): int |
503 | { |
504 | return count($this->values); |
505 | } |
506 | |
507 | /** |
508 | * Get values |
509 | * |
510 | * @param array $options |
511 | * @return array |
512 | */ |
513 | public function toArray(array $options = []): array |
514 | { |
515 | $fieldValues = $this->values; |
516 | |
517 | if (!empty($options)) { |
518 | if (isset($options['exclude'])) { |
519 | if (!is_array($options['exclude'])) { |
520 | $options['exclude'] = [$options['exclude']]; |
521 | } |
522 | $fieldValues = array_diff_key($fieldValues, array_flip($options['exclude'])); |
523 | } |
524 | if (isset($options['filter'])) { |
525 | $fieldValues = array_filter($fieldValues, $options['filter']); |
526 | } |
527 | } |
528 | |
529 | return $fieldValues; |
530 | } |
531 | |
532 | /** |
533 | * Set method to set the property to the value of values[$name] |
534 | * |
535 | * @param string $name |
536 | * @param mixed $value |
537 | * @return void |
538 | */ |
539 | public function __set(string $name, mixed $value): void |
540 | { |
541 | $this->values[$name] = $value; |
542 | } |
543 | |
544 | /** |
545 | * Get method to return the value of values[$name] |
546 | * |
547 | * @param string $name |
548 | * @return mixed |
549 | */ |
550 | public function __get(string $name): mixed |
551 | { |
552 | return $this->values[$name] ?? null; |
553 | } |
554 | |
555 | /** |
556 | * Return the isset value of values[$name] |
557 | * |
558 | * @param string $name |
559 | * @return bool |
560 | */ |
561 | public function __isset(string $name): bool |
562 | { |
563 | return isset($this->values[$name]); |
564 | } |
565 | |
566 | /** |
567 | * Unset values[$name] |
568 | * |
569 | * @param string $name |
570 | * @return void |
571 | */ |
572 | public function __unset(string $name): void |
573 | { |
574 | if (isset($this->values[$name])) { |
575 | unset($this->values[$name]); |
576 | } |
577 | } |
578 | |
579 | } |