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

nette / utils / 20431395313

22 Dec 2025 12:06PM UTC coverage: 93.164% (+71.8%) from 21.324%
20431395313

push

github

dg
Html::addText() accepts int|null for back compatibility [Closes #332][Closes #333]

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

140 existing lines in 15 files now uncovered.

2058 of 2209 relevant lines covered (93.16%)

0.93 hits per line

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

98.21
/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
use function array_key_exists, class_exists, explode, gettype, interface_exists, is_callable, is_float, is_int, is_iterable, is_numeric, is_object, is_string, preg_match, str_ends_with, str_replace, str_starts_with, strlen, strtolower, substr, trait_exists, var_export;
14

15

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

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

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

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

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

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

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

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

94

95
        /**
96
         * Verifies that the value is of expected types separated by pipe.
97
         * @throws AssertionException
98
         */
99
        public static function assert(mixed $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, return: true);
1✔
107
                        } elseif (is_object($value)) {
1✔
108
                                $type .= ' ' . $value::class;
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
         * @throws AssertionException
120
         */
121
        public static function assertField(
1✔
122
                array $array,
123
                $key,
124
                ?string $expected = null,
125
                string $label = "item '%' in array",
126
        ): void
127
        {
128
                if (!array_key_exists($key, $array)) {
1✔
129
                        throw new AssertionException('Missing ' . str_replace('%', $key, $label) . '.');
1✔
130

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

136

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

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

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

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

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

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

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

191
                        return true;
1✔
192
                }
193

194
                return false;
1✔
195
        }
196

197

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

210
                return true;
1✔
211
        }
212

213

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

223

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

233

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

243

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

252

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

261

262
        /**
263
         * Checks if the value is 0, '', false or null.
264
         * @return ($value is 0|''|false|null ? true : false)
265
         */
266
        public static function isNone(mixed $value): bool
1✔
267
        {
268
                return $value == null; // intentionally ==
1✔
269
        }
270

271

272
        /** @internal */
273
        public static function isMixed(): bool
274
        {
275
                return true;
1✔
276
        }
277

278

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

289

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

300
                $limit = $range[0] ?? $range[1];
1✔
301
                if (is_string($limit)) {
1✔
302
                        $value = (string) $value;
1✔
303
                } elseif ($limit instanceof \DateTimeInterface) {
1✔
304
                        if (!$value instanceof \DateTimeInterface) {
1✔
UNCOV
305
                                return false;
×
306
                        }
307
                } elseif (is_numeric($value)) {
1✔
308
                        $value *= 1;
1✔
309
                } else {
310
                        return false;
1✔
311
                }
312

313
                return (!isset($range[0]) || ($value >= $range[0])) && (!isset($range[1]) || ($value <= $range[1]));
1✔
314
        }
315

316

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

334

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

357

358
        /**
359
         * Checks if the value is a valid URI address, that is, actually a string beginning with a syntactically valid schema.
360
         */
361
        public static function isUri(string $value): bool
1✔
362
        {
363
                return (bool) preg_match('#^[a-z\d+\.-]+:\S+$#Di', $value);
1✔
364
        }
365

366

367
        /**
368
         * Checks whether the input is a class, interface or trait.
369
         * @deprecated
370
         */
371
        public static function isType(string $type): bool
1✔
372
        {
373
                return class_exists($type) || interface_exists($type) || trait_exists($type);
1✔
374
        }
375

376

377
        /**
378
         * Checks whether the input is a valid PHP identifier.
379
         */
380
        public static function isPhpIdentifier(string $value): bool
1✔
381
        {
382
                return preg_match('#^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$#D', $value) === 1;
1✔
383
        }
384

385

386
        /**
387
         * Determines if type is PHP built-in type. Otherwise, it is the class name.
388
         */
389
        public static function isBuiltinType(string $type): bool
1✔
390
        {
391
                return isset(self::BuiltinTypes[strtolower($type)]);
1✔
392
        }
393

394

395
        /**
396
         * Determines if type is special class name self/parent/static.
397
         */
398
        public static function isClassKeyword(string $name): bool
1✔
399
        {
400
                return (bool) preg_match('#^(self|parent|static)$#Di', $name);
1✔
401
        }
402

403

404
        /**
405
         * Checks whether the given type declaration is syntactically valid.
406
         */
407
        public static function isTypeDeclaration(string $type): bool
1✔
408
        {
409
                return (bool) preg_match(<<<'XX'
1✔
410
                        ~((?n)
1✔
411
                                \?? (?<type> \\? (?<name> [a-zA-Z_\x7f-\xff][\w\x7f-\xff]*) (\\ (?&name))* ) |
412
                                (?<intersection> (?&type) (& (?&type))+ ) |
413
                                (?<upart> (?&type) | \( (?&intersection) \) )  (\| (?&upart))+
414
                        )$~xAD
415
                        XX, $type);
1✔
416
        }
417
}
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

© 2025 Coveralls, Inc