• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nette / utils / 5707056869

pending completion
5707056869

push

github

dg
support for PHP 8.3

3 of 3 new or added lines in 1 file covered. (100.0%)

1580 of 1753 relevant lines covered (90.13%)

0.9 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.03
/src/Utils/Validators.php
1
<?php
2

3
/**
4
 * This file is part of the Nette Framework (https://nette.org)
5
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6
 */
7

8
declare(strict_types=1);
9

10
namespace Nette\Utils;
11

12
use Nette;
13

14

15
/**
16
 * Validation utilities.
17
 */
18
class Validators
19
{
20
        use Nette\StaticClass;
21

22
        private const BuiltinTypes = [
23
                'string' => 1, 'int' => 1, 'float' => 1, 'bool' => 1, 'array' => 1, 'object' => 1,
24
                'callable' => 1, 'iterable' => 1, 'void' => 1, 'null' => 1, 'mixed' => 1, 'false' => 1,
25
                'never' => 1, 'true' => 1,
26
        ];
27

28
        /** @var array<string,?callable> */
29
        protected static $validators = [
30
                // PHP types
31
                'array' => 'is_array',
32
                'bool' => 'is_bool',
33
                'boolean' => 'is_bool',
34
                'float' => 'is_float',
35
                'int' => 'is_int',
36
                'integer' => 'is_int',
37
                'null' => 'is_null',
38
                'object' => 'is_object',
39
                'resource' => 'is_resource',
40
                'scalar' => 'is_scalar',
41
                'string' => 'is_string',
42

43
                // pseudo-types
44
                'callable' => [self::class, 'isCallable'],
45
                'iterable' => 'is_iterable',
46
                'list' => [Arrays::class, 'isList'],
47
                'mixed' => [self::class, 'isMixed'],
48
                'none' => [self::class, 'isNone'],
49
                'number' => [self::class, 'isNumber'],
50
                'numeric' => [self::class, 'isNumeric'],
51
                'numericint' => [self::class, 'isNumericInt'],
52

53
                // string patterns
54
                'alnum' => 'ctype_alnum',
55
                'alpha' => 'ctype_alpha',
56
                'digit' => 'ctype_digit',
57
                'lower' => 'ctype_lower',
58
                'pattern' => null,
59
                'space' => 'ctype_space',
60
                'unicode' => [self::class, 'isUnicode'],
61
                'upper' => 'ctype_upper',
62
                'xdigit' => 'ctype_xdigit',
63

64
                // syntax validation
65
                'email' => [self::class, 'isEmail'],
66
                'identifier' => [self::class, 'isPhpIdentifier'],
67
                'uri' => [self::class, 'isUri'],
68
                'url' => [self::class, 'isUrl'],
69

70
                // environment validation
71
                'class' => 'class_exists',
72
                'interface' => 'interface_exists',
73
                'directory' => 'is_dir',
74
                'file' => 'is_file',
75
                'type' => [self::class, 'isType'],
76
        ];
77

78
        /** @var array<string,callable> */
79
        protected static $counters = [
80
                'string' => 'strlen',
81
                'unicode' => [Strings::class, 'length'],
82
                'array' => 'count',
83
                'list' => 'count',
84
                'alnum' => 'strlen',
85
                'alpha' => 'strlen',
86
                'digit' => 'strlen',
87
                'lower' => 'strlen',
88
                'space' => 'strlen',
89
                'upper' => 'strlen',
90
                'xdigit' => 'strlen',
91
        ];
92

93

94
        /**
95
         * Verifies that the value is of expected types separated by pipe.
96
         * @param  mixed  $value
97
         * @throws AssertionException
98
         */
99
        public static function assert($value, string $expected, string $label = 'variable'): void
1✔
100
        {
101
                if (!static::is($value, $expected)) {
1✔
102
                        $expected = str_replace(['|', ':'], [' or ', ' in range '], $expected);
1✔
103
                        $translate = ['boolean' => 'bool', 'integer' => 'int', 'double' => 'float', 'NULL' => 'null'];
1✔
104
                        $type = $translate[gettype($value)] ?? gettype($value);
1✔
105
                        if (is_int($value) || is_float($value) || (is_string($value) && strlen($value) < 40)) {
1✔
106
                                $type .= ' ' . var_export($value, true);
1✔
107
                        } elseif (is_object($value)) {
1✔
108
                                $type .= ' ' . get_class($value);
1✔
109
                        }
110

111
                        throw new AssertionException("The $label expects to be $expected, $type given.");
1✔
112
                }
113
        }
114

115

116
        /**
117
         * Verifies that element $key in array is of expected types separated by pipe.
118
         * @param  mixed[]  $array
119
         * @param  int|string  $key
120
         * @throws AssertionException
121
         */
122
        public static function assertField(
1✔
123
                array $array,
124
                $key,
125
                ?string $expected = null,
126
                string $label = "item '%' in array"
127
        ): void
128
        {
129
                if (!array_key_exists($key, $array)) {
1✔
130
                        throw new AssertionException('Missing ' . str_replace('%', $key, $label) . '.');
1✔
131

132
                } elseif ($expected) {
1✔
133
                        static::assert($array[$key], $expected, str_replace('%', $key, $label));
1✔
134
                }
135
        }
1✔
136

137

138
        /**
139
         * Verifies that the value is of expected types separated by pipe.
140
         * @param  mixed  $value
141
         */
142
        public static function is($value, string $expected): bool
1✔
143
        {
144
                foreach (explode('|', $expected) as $item) {
1✔
145
                        if (substr($item, -2) === '[]') {
1✔
146
                                if (is_iterable($value) && self::everyIs($value, substr($item, 0, -2))) {
1✔
147
                                        return true;
1✔
148
                                }
149

150
                                continue;
1✔
151
                        } elseif (substr($item, 0, 1) === '?') {
1✔
152
                                $item = substr($item, 1);
1✔
153
                                if ($value === null) {
1✔
154
                                        return true;
1✔
155
                                }
156
                        }
157

158
                        [$type] = $item = explode(':', $item, 2);
1✔
159
                        if (isset(static::$validators[$type])) {
1✔
160
                                try {
161
                                        if (!static::$validators[$type]($value)) {
1✔
162
                                                continue;
1✔
163
                                        }
164
                                } catch (\TypeError $e) {
1✔
165
                                        continue;
1✔
166
                                }
167
                        } elseif ($type === 'pattern') {
1✔
168
                                if (Strings::match($value, '|^' . ($item[1] ?? '') . '$|D')) {
1✔
169
                                        return true;
1✔
170
                                }
171

172
                                continue;
1✔
173
                        } elseif (!$value instanceof $type) {
1✔
174
                                continue;
1✔
175
                        }
176

177
                        if (isset($item[1])) {
1✔
178
                                $length = $value;
1✔
179
                                if (isset(static::$counters[$type])) {
1✔
180
                                        $length = static::$counters[$type]($value);
1✔
181
                                }
182

183
                                $range = explode('..', $item[1]);
1✔
184
                                if (!isset($range[1])) {
1✔
185
                                        $range[1] = $range[0];
1✔
186
                                }
187

188
                                if (($range[0] !== '' && $length < $range[0]) || ($range[1] !== '' && $length > $range[1])) {
1✔
189
                                        continue;
1✔
190
                                }
191
                        }
192

193
                        return true;
1✔
194
                }
195

196
                return false;
1✔
197
        }
198

199

200
        /**
201
         * Finds whether all values are of expected types separated by pipe.
202
         * @param  mixed[]  $values
203
         */
204
        public static function everyIs(iterable $values, string $expected): bool
1✔
205
        {
206
                foreach ($values as $value) {
1✔
207
                        if (!static::is($value, $expected)) {
1✔
208
                                return false;
1✔
209
                        }
210
                }
211

212
                return true;
1✔
213
        }
214

215

216
        /**
217
         * Checks if the value is an integer or a float.
218
         * @param  mixed  $value
219
         */
220
        public static function isNumber($value): bool
221
        {
222
                return is_int($value) || is_float($value);
1✔
223
        }
224

225

226
        /**
227
         * Checks if the value is an integer or a integer written in a string.
228
         * @param  mixed  $value
229
         */
230
        public static function isNumericInt($value): bool
231
        {
232
                return is_int($value) || (is_string($value) && preg_match('#^[+-]?[0-9]+$#D', $value));
1✔
233
        }
234

235

236
        /**
237
         * Checks if the value is a number or a number written in a string.
238
         * @param  mixed  $value
239
         */
240
        public static function isNumeric($value): bool
241
        {
242
                return is_float($value) || is_int($value) || (is_string($value) && preg_match('#^[+-]?([0-9]++\.?[0-9]*|\.[0-9]+)$#D', $value));
1✔
243
        }
244

245

246
        /**
247
         * Checks if the value is a syntactically correct callback.
248
         * @param  mixed  $value
249
         */
250
        public static function isCallable($value): bool
251
        {
252
                return $value && is_callable($value, true);
1✔
253
        }
254

255

256
        /**
257
         * Checks if the value is a valid UTF-8 string.
258
         * @param  mixed  $value
259
         */
260
        public static function isUnicode($value): bool
261
        {
262
                return is_string($value) && preg_match('##u', $value);
1✔
263
        }
264

265

266
        /**
267
         * Checks if the value is 0, '', false or null.
268
         * @param  mixed  $value
269
         */
270
        public static function isNone($value): bool
271
        {
272
                return $value == null; // intentionally ==
1✔
273
        }
274

275

276
        /** @internal */
277
        public static function isMixed(): bool
278
        {
279
                return true;
1✔
280
        }
281

282

283
        /**
284
         * Checks if a variable is a zero-based integer indexed array.
285
         * @param  mixed  $value
286
         * @deprecated  use Nette\Utils\Arrays::isList
287
         * @return ($value is list ? true : false)
288
         */
289
        public static function isList($value): bool
290
        {
291
                return Arrays::isList($value);
×
292
        }
293

294

295
        /**
296
         * Checks if the value is in the given range [min, max], where the upper or lower limit can be omitted (null).
297
         * Numbers, strings and DateTime objects can be compared.
298
         * @param  mixed  $value
299
         */
300
        public static function isInRange($value, array $range): bool
1✔
301
        {
302
                if ($value === null || !(isset($range[0]) || isset($range[1]))) {
1✔
303
                        return false;
1✔
304
                }
305

306
                $limit = $range[0] ?? $range[1];
1✔
307
                if (is_string($limit)) {
1✔
308
                        $value = (string) $value;
1✔
309
                } elseif ($limit instanceof \DateTimeInterface) {
1✔
310
                        if (!$value instanceof \DateTimeInterface) {
1✔
311
                                return false;
1✔
312
                        }
313
                } elseif (is_numeric($value)) {
1✔
314
                        $value *= 1;
1✔
315
                } else {
316
                        return false;
1✔
317
                }
318

319
                return (!isset($range[0]) || ($value >= $range[0])) && (!isset($range[1]) || ($value <= $range[1]));
1✔
320
        }
321

322

323
        /**
324
         * Checks if the value is a valid email address. It does not verify that the domain actually exists, only the syntax is verified.
325
         */
326
        public static function isEmail(string $value): bool
1✔
327
        {
328
                $atom = "[-a-z0-9!#$%&'*+/=?^_`{|}~]"; // RFC 5322 unquoted characters in local-part
1✔
329
                $alpha = "a-z\x80-\xFF"; // superset of IDN
1✔
330
                return (bool) preg_match(<<<XX
1✔
331
                (^
1✔
332
                        ("([ !#-[\\]-~]*|\\\\[ -~])+"|$atom+(\\.$atom+)*)  # quoted or unquoted
1✔
333
                        @
334
                        ([0-9$alpha]([-0-9$alpha]{0,61}[0-9$alpha])?\\.)+  # domain - RFC 1034
1✔
335
                        [$alpha]([-0-9$alpha]{0,17}[$alpha])?              # top domain
1✔
336
                $)Dix
337
XX
338
                        , $value);
339
        }
340

341

342
        /**
343
         * Checks if the value is a valid URL address.
344
         */
345
        public static function isUrl(string $value): bool
1✔
346
        {
347
                $alpha = "a-z\x80-\xFF";
1✔
348
                return (bool) preg_match(<<<XX
1✔
349
                (^
1✔
350
                        https?://(
351
                                (([-_0-9$alpha]+\\.)*                       # subdomain
1✔
352
                                        [0-9$alpha]([-0-9$alpha]{0,61}[0-9$alpha])?\\.)?  # domain
1✔
353
                                        [$alpha]([-0-9$alpha]{0,17}[$alpha])?   # top domain
1✔
354
                                |\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}  # IPv4
355
                                |\\[[0-9a-f:]{3,39}\\]                      # IPv6
356
                        )(:\\d{1,5})?                                   # port
357
                        (/\\S*)?                                        # path
358
                        (\\?\\S*)?                                      # query
359
                        (\\#\\S*)?                                      # fragment
360
                $)Dix
361
XX
362
                        , $value);
363
        }
364

365

366
        /**
367
         * Checks if the value is a valid URI address, that is, actually a string beginning with a syntactically valid schema.
368
         */
369
        public static function isUri(string $value): bool
1✔
370
        {
371
                return (bool) preg_match('#^[a-z\d+\.-]+:\S+$#Di', $value);
1✔
372
        }
373

374

375
        /**
376
         * Checks whether the input is a class, interface or trait.
377
         */
378
        public static function isType(string $type): bool
1✔
379
        {
380
                return class_exists($type) || interface_exists($type) || trait_exists($type);
1✔
381
        }
382

383

384
        /**
385
         * Checks whether the input is a valid PHP identifier.
386
         */
387
        public static function isPhpIdentifier(string $value): bool
1✔
388
        {
389
                return preg_match('#^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$#D', $value) === 1;
1✔
390
        }
391

392

393
        /**
394
         * Determines if type is PHP built-in type. Otherwise, it is the class name.
395
         */
396
        public static function isBuiltinType(string $type): bool
1✔
397
        {
398
                return isset(self::BuiltinTypes[strtolower($type)]);
1✔
399
        }
400

401

402
        /**
403
         * Determines if type is special class name self/parent/static.
404
         */
405
        public static function isClassKeyword(string $name): bool
1✔
406
        {
407
                return (bool) preg_match('#^(self|parent|static)$#Di', $name);
1✔
408
        }
409

410

411
        /**
412
         * Checks whether the given type declaration is syntactically valid.
413
         */
414
        public static function isTypeDeclaration(string $type): bool
1✔
415
        {
416
                return (bool) preg_match(<<<'XX'
1✔
417
                ~(
1✔
418
                        \?? (?<type> \\? (?<name> [a-zA-Z_\x7f-\xff][\w\x7f-\xff]*) (\\ (?&name))* ) |
419
                        (?<intersection> (?&type) (& (?&type))+ ) |
420
                        (?<upart> (?&type) | \( (?&intersection) \) )  (\| (?&upart))+
421
                )$~xAD
422
XX
423
                        , $type);
424
        }
425
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc