Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
99.26% covered (success)
99.26%
134 / 135
97.50% covered (success)
97.50%
39 / 40
CRAP
0.00% covered (danger)
0.00%
0 / 1
Validator
99.26% covered (success)
99.26%
134 / 135
97.50% covered (success)
97.50%
39 / 40
83
0.00% covered (danger)
0.00%
0 / 1
 getData
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 alpha
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 number
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 alphaNumber
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 uuid
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
 timezone
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 base64
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 md5
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 hex
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 hexColor
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 json
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 regex
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 notRegex
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 equals
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 notEquals
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 between
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
3
 notBetween
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 in
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 notIn
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 ip
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
3
 url
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 datetime
92.31% covered (success)
92.31%
12 / 13
0.00% covered (danger)
0.00%
0 / 1
6.02
 email
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 greater
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 greaterOrEqual
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 less
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 lessOrEqual
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 latin
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 maxLength
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 minLength
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 length
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 required
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 isset
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 array
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 bool
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 float
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 int
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 object
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 string
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 specialChar
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
7
1<?php declare(strict_types=1);
2/*
3 * This file is part of Aplus Framework Validation Library.
4 *
5 * (c) Natan Felles <natanfelles@gmail.com>
6 *
7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code.
9 */
10namespace Framework\Validation;
11
12use Framework\Helpers\ArraySimple;
13use InvalidArgumentException;
14use JetBrains\PhpStorm\Language;
15
16/**
17 * Class Validator.
18 *
19 * @package validation
20 */
21class Validator
22{
23    /**
24     * Get field value from data.
25     *
26     * @param string $field
27     * @param array<string,mixed> $data
28     *
29     * @return string|null
30     */
31    protected static function getData(string $field, array $data) : ?string
32    {
33        $data = ArraySimple::value($field, $data);
34        return \is_scalar($data) ? (string) $data : null;
35    }
36
37    /**
38     * Validates alphabetic characters.
39     *
40     * @param string $field
41     * @param array<string,mixed> $data
42     *
43     * @return bool
44     */
45    public static function alpha(string $field, array $data) : bool
46    {
47        $data = static::getData($field, $data);
48        return $data !== null && \ctype_alpha($data);
49    }
50
51    /**
52     * Validates a number.
53     *
54     * @param string $field
55     * @param array<string,mixed> $data
56     *
57     * @return bool
58     */
59    public static function number(string $field, array $data) : bool
60    {
61        $data = static::getData($field, $data);
62        return \is_numeric($data);
63    }
64
65    /**
66     * Validates a number or alphabetic characters.
67     *
68     * @param string $field
69     * @param array<string,mixed> $data
70     *
71     * @return bool
72     */
73    public static function alphaNumber(string $field, array $data) : bool
74    {
75        $data = static::getData($field, $data);
76        return $data !== null && \ctype_alnum($data);
77    }
78
79    /**
80     * Validates a UUID.
81     *
82     * @param string $field
83     * @param array<string,mixed> $data
84     *
85     * @return bool
86     */
87    public static function uuid(string $field, array $data) : bool
88    {
89        $data = static::getData($field, $data);
90        if ($data === null) {
91            return false;
92        }
93        if ($data === '00000000-0000-0000-0000-000000000000') {
94            return false;
95        }
96        return \preg_match(
97            '/^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$/',
98            $data
99        ) === 1;
100    }
101
102    /**
103     * Validates a timezone.
104     *
105     * @param string $field
106     * @param array<string,mixed> $data
107     *
108     * @return bool
109     */
110    public static function timezone(string $field, array $data) : bool
111    {
112        return static::in($field, $data, ...\DateTimeZone::listIdentifiers());
113    }
114
115    /**
116     * Validates a base64 string.
117     *
118     * @param string $field
119     * @param array<string,mixed> $data
120     *
121     * @return bool
122     */
123    public static function base64(string $field, array $data) : bool
124    {
125        $data = static::getData($field, $data);
126        if ($data === null) {
127            return false;
128        }
129        $decoded = \base64_decode($data);
130        return $decoded && \base64_encode($decoded) === $data;
131    }
132
133    /**
134     * Validates a md5 hash.
135     *
136     * @param string $field
137     * @param array<string,mixed> $data
138     *
139     * @return bool
140     */
141    public static function md5(string $field, array $data) : bool
142    {
143        $data = static::getData($field, $data);
144        if ($data === null) {
145            return false;
146        }
147        return (bool) \preg_match('/^[a-f0-9]{32}$/', $data);
148    }
149
150    /**
151     * Validates a hexadecimal string.
152     *
153     * @param string $field
154     * @param array<string,mixed> $data
155     *
156     * @return bool
157     */
158    public static function hex(string $field, array $data) : bool
159    {
160        $data = static::getData($field, $data);
161        return $data !== null && \ctype_xdigit($data);
162    }
163
164    /**
165     * Validates a hexadecimal color.
166     *
167     * @param string $field
168     * @param array<string,mixed> $data
169     *
170     * @return bool
171     */
172    public static function hexColor(string $field, array $data) : bool
173    {
174        return static::regex($field, $data, '/^#([0-9A-Fa-f]{3}){1,2}$/');
175    }
176
177    /**
178     * Validates a JSON string.
179     *
180     * @param string $field
181     * @param array<string,mixed> $data
182     *
183     * @return bool
184     */
185    public static function json(string $field, array $data) : bool
186    {
187        $data = static::getData($field, $data);
188        if ($data === null) {
189            return false;
190        }
191        \json_decode($data);
192        return \json_last_error() === \JSON_ERROR_NONE;
193    }
194
195    /**
196     * Validates a Regex pattern.
197     *
198     * @param string $field
199     * @param array<string,mixed> $data
200     * @param string $pattern
201     *
202     * @return bool
203     */
204    public static function regex(
205        string $field,
206        array $data,
207        #[Language('RegExp')] string $pattern
208    ) : bool {
209        $data = static::getData($field, $data);
210        return $data !== null && \preg_match($pattern, $data) === 1;
211    }
212
213    /**
214     * Validates a Regex no matching pattern.
215     *
216     * @param string $field
217     * @param array<string,mixed> $data
218     * @param string $pattern
219     *
220     * @return bool
221     */
222    public static function notRegex(
223        string $field,
224        array $data,
225        #[Language('RegExp')] string $pattern
226    ) : bool {
227        return ! static::regex($field, $data, $pattern);
228    }
229
230    /**
231     * Validate field has value equals other field.
232     *
233     * @param string $field
234     * @param array<string,mixed> $data
235     * @param string $equalsField
236     *
237     * @return bool
238     */
239    public static function equals(string $field, array $data, string $equalsField) : bool
240    {
241        $field = ArraySimple::value($field, $data);
242        if ( ! \is_scalar($field)) {
243            return false;
244        }
245        $equalsField = ArraySimple::value($equalsField, $data);
246        if ( ! \is_scalar($equalsField)) {
247            return false;
248        }
249        return (string) $field === (string) $equalsField;
250    }
251
252    /**
253     * Validate field has not value equals other field.
254     *
255     * @param string $field
256     * @param array<string,mixed> $data
257     * @param string $diffField
258     *
259     * @return bool
260     */
261    public static function notEquals(string $field, array $data, string $diffField) : bool
262    {
263        return ! static::equals($field, $data, $diffField);
264    }
265
266    /**
267     * Validate field between min and max values.
268     *
269     * @param string $field
270     * @param array<string,mixed> $data
271     * @param int|string $min
272     * @param int|string $max
273     *
274     * @return bool
275     */
276    public static function between(
277        string $field,
278        array $data,
279        int | string $min,
280        int | string $max
281    ) : bool {
282        $data = static::getData($field, $data);
283        return $data !== null && $data >= $min && $data <= $max;
284    }
285
286    /**
287     * Validate field not between min and max values.
288     *
289     * @param string $field
290     * @param array<string,mixed> $data
291     * @param int|string $min
292     * @param int|string $max
293     *
294     * @return bool
295     */
296    public static function notBetween(
297        string $field,
298        array $data,
299        int | string $min,
300        int | string $max
301    ) : bool {
302        return ! static::between($field, $data, $min, $max);
303    }
304
305    /**
306     * Validate field is in list.
307     *
308     * @param string $field
309     * @param array<string,mixed> $data
310     * @param string $in
311     * @param string ...$others
312     *
313     * @return bool
314     */
315    public static function in(string $field, array $data, string $in, string ...$others) : bool
316    {
317        $data = static::getData($field, $data);
318        return $data !== null && \in_array($data, [$in, ...$others], true);
319    }
320
321    /**
322     * Validate field is not in list.
323     *
324     * @param string $field
325     * @param array<string,mixed> $data
326     * @param string $notIn
327     * @param string ...$others
328     *
329     * @return bool
330     */
331    public static function notIn(string $field, array $data, string $notIn, string ...$others) : bool
332    {
333        return ! static::in($field, $data, $notIn, ...$others);
334    }
335
336    /**
337     * Validates an IP.
338     *
339     * @param string $field
340     * @param array<string,mixed> $data
341     * @param int|string $version 4, 6 or 0 to both
342     *
343     * @return bool
344     */
345    public static function ip(string $field, array $data, int | string $version = 0) : bool
346    {
347        $data = static::getData($field, $data);
348        if ($data === null) {
349            return false;
350        }
351        $version = (int) $version;
352        if ($version !== 0) {
353            $version = match ($version) {
354                4 => \FILTER_FLAG_IPV4,
355                6 => \FILTER_FLAG_IPV6,
356                default => throw new InvalidArgumentException(
357                    "Invalid IP Version: {$version}"
358                ),
359            };
360        }
361        return \filter_var($data, \FILTER_VALIDATE_IP, $version) !== false;
362    }
363
364    /**
365     * Validates an URL.
366     *
367     * @param string $field
368     * @param array<string,mixed> $data
369     *
370     * @return bool
371     */
372    public static function url(string $field, array $data) : bool
373    {
374        $data = static::getData($field, $data);
375        if ($data === null) {
376            return false;
377        }
378        if (\preg_match('/^(?:([^:]*)\:)?\/\/(.+)$/', $data, $matches)) {
379            if ( ! \in_array($matches[1], ['http', 'https'], true)) {
380                return false;
381            }
382            $data = $matches[2];
383        }
384        $data = 'http://' . $data;
385        return \filter_var($data, \FILTER_VALIDATE_URL) !== false;
386    }
387
388    /**
389     * Validates a datetime format.
390     *
391     * @param string $field
392     * @param array<string,mixed> $data
393     * @param string $format
394     *
395     * @return bool
396     */
397    public static function datetime(
398        string $field,
399        array $data,
400        string $format = 'Y-m-d H:i:s'
401    ) : bool {
402        $data = static::getData($field, $data);
403        if ($data === null) {
404            return false;
405        }
406        $datetime = \DateTime::createFromFormat($format, $data);
407        if ($datetime === false) {
408            return false;
409        }
410        if ($datetime->format($format) !== $data) {
411            return false;
412        }
413        $lastErrors = \DateTime::getLastErrors();
414        if ($lastErrors === false) {
415            return true;
416        }
417        return $lastErrors['warning_count'] === 0
418            && $lastErrors['error_count'] === 0;
419    }
420
421    /**
422     * Validates a email.
423     *
424     * @param string $field
425     * @param array<string,mixed> $data
426     *
427     * @return bool
428     */
429    public static function email(string $field, array $data) : bool
430    {
431        $data = static::getData($field, $data);
432        if ($data === null) {
433            return false;
434        }
435        if (\preg_match('#\A([^@]+)@(.+)\z#', $data, $matches)) {
436            $data = $matches[1] . '@' . \idn_to_ascii($matches[2]);
437        }
438        return (bool) \filter_var($data, \FILTER_VALIDATE_EMAIL);
439    }
440
441    /**
442     * Validates is greater than.
443     *
444     * @param string $field
445     * @param array<string,mixed> $data
446     * @param int|string $greaterThan
447     *
448     * @return bool
449     */
450    public static function greater(
451        string $field,
452        array $data,
453        int | string $greaterThan
454    ) : bool {
455        $data = static::getData($field, $data);
456        return $data !== null && $data > $greaterThan;
457    }
458
459    /**
460     * Validates is greater than or equal to.
461     *
462     * @param string $field
463     * @param array<string,mixed> $data
464     * @param int|string $greaterThanOrEqualTo
465     *
466     * @return bool
467     */
468    public static function greaterOrEqual(
469        string $field,
470        array $data,
471        int | string $greaterThanOrEqualTo
472    ) : bool {
473        $data = static::getData($field, $data);
474        return $data !== null && $data >= $greaterThanOrEqualTo;
475    }
476
477    /**
478     * Validates is less than.
479     *
480     * @param string $field
481     * @param array<string,mixed> $data
482     * @param int|string $lessThan
483     *
484     * @return bool
485     */
486    public static function less(
487        string $field,
488        array $data,
489        int | string $lessThan
490    ) : bool {
491        $data = static::getData($field, $data);
492        return $data !== null && $data < $lessThan;
493    }
494
495    /**
496     * Validates is less than or equal to.
497     *
498     * @param string $field
499     * @param array<string,mixed> $data
500     * @param int|string $lessThanOrEqualTo
501     *
502     * @return bool
503     */
504    public static function lessOrEqual(
505        string $field,
506        array $data,
507        int | string $lessThanOrEqualTo
508    ) : bool {
509        $data = static::getData($field, $data);
510        return $data !== null && $data <= $lessThanOrEqualTo;
511    }
512
513    /**
514     * Validates a latin text.
515     *
516     * @param string $field
517     * @param array<string,mixed> $data
518     *
519     * @return bool
520     */
521    public static function latin(string $field, array $data) : bool
522    {
523        $data = static::getData($field, $data);
524        return $data !== null && \preg_match('/^[\p{Latin}]+$/u', $data);
525    }
526
527    /**
528     * Validates max length.
529     *
530     * @param string $field
531     * @param array<string,mixed> $data
532     * @param int|string $maxLength
533     *
534     * @return bool
535     */
536    public static function maxLength(string $field, array $data, int | string $maxLength) : bool
537    {
538        $data = static::getData($field, $data);
539        return $data !== null && \mb_strlen($data) <= (int) $maxLength;
540    }
541
542    /**
543     * Validates min length.
544     *
545     * @param string $field
546     * @param array<string,mixed> $data
547     * @param int|string $minLength
548     *
549     * @return bool
550     */
551    public static function minLength(string $field, array $data, int | string $minLength) : bool
552    {
553        $data = static::getData($field, $data);
554        return $data !== null && \mb_strlen($data) >= (int) $minLength;
555    }
556
557    /**
558     * Validates exact length.
559     *
560     * @param string $field
561     * @param array<string,mixed> $data
562     * @param int|string $length
563     *
564     * @return bool
565     */
566    public static function length(string $field, array $data, int | string $length) : bool
567    {
568        $data = static::getData($field, $data);
569        return $data !== null && \mb_strlen($data) === (int) $length;
570    }
571
572    /**
573     * Validates required value.
574     *
575     * @param string $field
576     * @param array<string,mixed> $data
577     *
578     * @return bool
579     */
580    public static function required(string $field, array $data) : bool
581    {
582        $data = static::getData($field, $data);
583        return $data !== null && \trim($data) !== '';
584    }
585
586    /**
587     * Validates field is set.
588     *
589     * @param string $field
590     * @param array<string,mixed> $data
591     *
592     * @return bool
593     */
594    public static function isset(string $field, array $data) : bool
595    {
596        return static::getData($field, $data) !== null;
597    }
598
599    /**
600     * Validates array.
601     *
602     * @param string $field
603     * @param array<string,mixed> $data
604     *
605     * @since 2.2
606     *
607     * @return bool
608     */
609    public static function array(string $field, array $data) : bool
610    {
611        return \is_array(ArraySimple::value($field, $data));
612    }
613
614    /**
615     * Validates boolean.
616     *
617     * @param string $field
618     * @param array<string,mixed> $data
619     *
620     * @since 2.2
621     *
622     * @return bool
623     */
624    public static function bool(string $field, array $data) : bool
625    {
626        return \is_bool(ArraySimple::value($field, $data));
627    }
628
629    /**
630     * Validates float.
631     *
632     * @param string $field
633     * @param array<string,mixed> $data
634     *
635     * @since 2.2
636     *
637     * @return bool
638     */
639    public static function float(string $field, array $data) : bool
640    {
641        return \is_float(ArraySimple::value($field, $data));
642    }
643
644    /**
645     * Validates integer.
646     *
647     * @param string $field
648     * @param array<string,mixed> $data
649     *
650     * @since 2.2
651     *
652     * @return bool
653     */
654    public static function int(string $field, array $data) : bool
655    {
656        return \is_int(ArraySimple::value($field, $data));
657    }
658
659    /**
660     * Validates object.
661     *
662     * @param string $field
663     * @param array<string,mixed> $data
664     *
665     * @since 2.2
666     *
667     * @return bool
668     */
669    public static function object(string $field, array $data) : bool
670    {
671        return \is_object(ArraySimple::value($field, $data));
672    }
673
674    /**
675     * Validates string.
676     *
677     * @param string $field
678     * @param array<string,mixed> $data
679     *
680     * @since 2.2
681     *
682     * @return bool
683     */
684    public static function string(string $field, array $data) : bool
685    {
686        return \is_string(ArraySimple::value($field, $data));
687    }
688
689    /**
690     * Validates special characters.
691     *
692     * @see https://owasp.org/www-community/password-special-characters
693     *
694     * @param string $field
695     * @param array<string,mixed> $data
696     * @param int|string $quantity
697     * @param string $characters
698     *
699     * @return bool
700     */
701    public static function specialChar(
702        string $field,
703        array $data,
704        int | string $quantity = 1,
705        string $characters = '!"#$%&\'()*+,-./:;=<>?@[\]^_`{|}~'
706    ) : bool {
707        $quantity = (int) $quantity;
708        if ($quantity < 1) {
709            throw new InvalidArgumentException('Special characters quantity must be greater than 0');
710        }
711        $data = static::getData($field, $data);
712        if ($data === null) {
713            return false;
714        }
715        $data = (array) \preg_split('//u', $data, -1, \PREG_SPLIT_NO_EMPTY);
716        $characters = (array) \preg_split('//u', $characters, -1, \PREG_SPLIT_NO_EMPTY);
717        $found = 0;
718        foreach ($characters as $char) {
719            foreach ($data as $item) {
720                if ($char === $item) {
721                    $found++;
722                    if ($found === $quantity) {
723                        return true;
724                    }
725                    break;
726                }
727            }
728        }
729        return false;
730    }
731}