Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.52% covered (success)
98.52%
133 / 135
92.86% covered (success)
92.86%
26 / 28
CRAP
0.00% covered (danger)
0.00%
0 / 1
FormValidator
98.52% covered (success)
98.52%
133 / 135
92.86% covered (success)
92.86%
26 / 28
95
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
6
 createFromConfig
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
6
 addValidators
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 addValidator
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
5
 hasValidators
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 getValidators
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 hasValidator
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 getValidator
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
3
 removeValidators
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 removeValidator
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 setRequired
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 isRequired
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 removeRequired
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 setValues
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getValues
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 filterValue
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 filter
85.71% covered (success)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
4.05
 validate
97.22% covered (success)
97.22%
35 / 36
0.00% covered (danger)
0.00%
0 / 1
21
 hasErrors
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 getErrors
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 getError
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
3
 addError
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 count
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 toArray
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __set
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __get
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __isset
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __unset
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
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 */
14namespace 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
27class 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     * @return FormValidator
254     */
255    public function setRequired(mixed $required): FormValidator
256    {
257        if (!is_array($required)) {
258            $required = [$required];
259        }
260
261        foreach ($required as $req) {
262            if (!in_array($req, $this->required)) {
263                $this->required[] = $req;
264            }
265        }
266
267        return $this;
268    }
269
270    /**
271     * Is required
272     *
273     * @param  string $field
274     * @return bool
275     */
276    public function isRequired(string $field): bool
277    {
278        return (in_array($field, $this->required));
279    }
280
281    /**
282     * Remove required
283     *
284     * @param  string $field
285     * @return FormValidator
286     */
287    public function removeRequired(string $field): FormValidator
288    {
289        if (in_array($field, $this->required)) {
290            unset($this->required[array_search($field, $this->required)]);
291        }
292
293        return $this;
294    }
295
296    /**
297     * Set values
298     *
299     * @param  array $values
300     * @return FormValidator
301     */
302    public function setValues(array $values): FormValidator
303    {
304        $this->values = $values;
305        return $this;
306    }
307
308    /**
309     * Get values
310     *
311     * @return array
312     */
313    public function getValues(): array
314    {
315        return $this->values;
316    }
317
318    /**
319     * Filter value with the filters
320     *
321     * @param  mixed $field
322     * @throws Exception
323     * @return mixed
324     */
325    public function filterValue(mixed $field): mixed
326    {
327        if (!isset($this->values[$field])) {
328            throw new Exception("Error: A value for '" . $field . "' has not been set.");
329        }
330
331        $value = $this->values[$field];
332
333        foreach ($this->filters as $filter) {
334            $value = $filter->filter($value, $field);
335        }
336
337        $this->values[$field] = $value;
338
339        return $value;
340    }
341
342    /**
343     * Filter values with the filters
344     *
345     * @param  mixed $values
346     * @return mixed
347     */
348    public function filter(mixed $values = null): mixed
349    {
350        if ($values !== null) {
351            $this->values = $values;
352        }
353
354        if (is_array($this->values)) {
355            foreach ($this->values as $name => $value) {
356                $this->values[$name] = $this->filterValue($name);
357            }
358        } else {
359            $this->values = $this->filterValue($this->values);
360        }
361
362        return $this->values;
363    }
364
365    /**
366     * Validate values
367     *
368     * @param  mixed $fields
369     * @return bool
370     */
371    public function validate(mixed $fields = null): bool
372    {
373        $this->filter();
374
375        if ($fields !== null) {
376            $fields     = (!is_array($fields)) ? [$fields] : $fields;
377            $formFields = array_filter(
378                $this->values,
379                function ($key) use ($fields) {
380                    return in_array($key, $fields);
381                },
382                ARRAY_FILTER_USE_KEY
383            );
384
385            foreach ($formFields as $field) {
386                if (in_array($field, $this->required) && !isset($formFields[$field])) {
387                    $this->addError($field, 'This field is required.');
388                }
389            }
390        } else {
391            $formFields = $this->values;
392            // Check for required fields
393            foreach ($this->required as $required) {
394                if (!isset($formFields[$required])) {
395                    $this->addError($required, 'This field is required.');
396                }
397            }
398        }
399
400        // Check for required fields and execute any field validators
401        foreach ($formFields as $field => $value) {
402            if ($this->hasValidators($field)) {
403                foreach ($this->validators[$field] as $validator) {
404                    if ($validator instanceof \Pop\Validator\ValidatorInterface) {
405                        if (!$validator->evaluate($value)) {
406                            $this->addError($field, $validator->getMessage());
407                        }
408                    } else if (is_callable($validator)) {
409                        $result = call_user_func_array($validator, [$value, $formFields]);
410                        if ($result instanceof \Pop\Validator\ValidatorInterface) {
411                            if (!$result->evaluate($value)) {
412                                $this->addError($field, $result->getMessage());
413                            }
414                        } else if (is_array($result)) {
415                            foreach ($result as $val) {
416                                if ($val instanceof \Pop\Validator\ValidatorInterface) {
417                                    if (!$val->evaluate($value)) {
418                                        $this->addError($field, $val->getMessage());
419                                    }
420                                }
421                            }
422                        } else if ($result !== null) {
423                            $this->addError($field, $result);
424                        }
425                    }
426                }
427            }
428        }
429
430        return !$this->hasErrors();
431    }
432
433    /**
434     * Has errors
435     *
436     * @param  ?string $field
437     * @return bool
438     */
439    public function hasErrors(?string $field = null): bool
440    {
441        if ($field !== null) {
442            return (isset($this->errors[$field]) && (count($this->errors[$field]) > 0));
443        } else {
444            return (count($this->errors) > 0);
445        }
446    }
447
448    /**
449     * Get errors
450     *
451     * @param  ?string $field
452     * @return array
453     */
454    public function getErrors(?string $field = null): array
455    {
456        if (($field !== null) && isset($this->errors[$field])) {
457            return $this->errors[$field];
458        } else {
459            return $this->errors;
460        }
461    }
462
463    /**
464     * Get error
465     *
466     * @param  string $field
467     * @param  int    $index
468     * @return mixed
469     */
470    public function getError(string $field, int $index): mixed
471    {
472        return (isset($this->errors[$field]) && isset($this->errors[$field][$index])) ?
473            $this->errors[$field][$index] : null;
474    }
475
476    /**
477     * Add error
478     *
479     * @param  string $field
480     * @param  string $error
481     * @return FormValidator
482     */
483    protected function addError(string $field, string $error): FormValidator
484    {
485        if (!isset($this->errors[$field])) {
486            $this->errors[$field] = [];
487        }
488
489        if (!in_array($error, $this->errors[$field])) {
490            $this->errors[$field][] = $error;
491        }
492
493        return $this;
494    }
495
496    /**
497     * Count of values
498     *
499     * @return int
500     */
501    public function count(): int
502    {
503        return count($this->values);
504    }
505
506    /**
507     * Get values
508     *
509     * @return array
510     */
511    public function toArray(): array
512    {
513        return $this->values;
514    }
515
516    /**
517     * Set method to set the property to the value of values[$name]
518     *
519     * @param  string $name
520     * @param  mixed $value
521     * @return void
522     */
523    public function __set(string $name, mixed $value): void
524    {
525        $this->values[$name] = $value;
526    }
527
528    /**
529     * Get method to return the value of values[$name]
530     *
531     * @param  string $name
532     * @return mixed
533     */
534    public function __get(string $name): mixed
535    {
536        return $this->values[$name] ?? null;
537    }
538
539    /**
540     * Return the isset value of values[$name]
541     *
542     * @param  string $name
543     * @return bool
544     */
545    public function __isset(string $name): bool
546    {
547        return isset($this->values[$name]);
548    }
549
550    /**
551     * Unset values[$name]
552     *
553     * @param  string $name
554     * @return void
555     */
556    public function __unset(string $name): void
557    {
558        if (isset($this->values[$name])) {
559            unset($this->values[$name]);
560        }
561    }
562
563}