PluginProbe
Packeta / 2.1
Packeta v2.1
2.3.2 2.3.1 trunk 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.3.0 1.3.1 1.3.2 1.4 1.4.1 1.4.2 1.4.3 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 All 56 releases
packeta / deps / nette / utils / src / Utils / Validators.php

Validators.php in Packeta 2.1, at deps/nette/utils/src/Utils/Validators.php

312 lines 10.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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 declare (strict_types=1);
8 namespace Packetery\Nette\Utils;
9
10 use Packetery\Nette;
11 /**
12 * Validation utilities.
13 */
14 class Validators
15 {
16 use \Packetery\Nette\StaticClass;
17 /** @var array<string,?callable> */
18 protected static $validators = [
19 // PHP types
20 'array' => 'is_array',
21 'bool' => 'is_bool',
22 'boolean' => 'is_bool',
23 'float' => 'is_float',
24 'int' => 'is_int',
25 'integer' => 'is_int',
26 'null' => 'is_null',
27 'object' => 'is_object',
28 'resource' => 'is_resource',
29 'scalar' => 'is_scalar',
30 'string' => 'is_string',
31 // pseudo-types
32 'callable' => [self::class, 'isCallable'],
33 'iterable' => 'is_iterable',
34 'list' => [Arrays::class, 'isList'],
35 'mixed' => [self::class, 'isMixed'],
36 'none' => [self::class, 'isNone'],
37 'number' => [self::class, 'isNumber'],
38 'numeric' => [self::class, 'isNumeric'],
39 'numericint' => [self::class, 'isNumericInt'],
40 // string patterns
41 'alnum' => 'ctype_alnum',
42 'alpha' => 'ctype_alpha',
43 'digit' => 'ctype_digit',
44 'lower' => 'ctype_lower',
45 'pattern' => null,
46 'space' => 'ctype_space',
47 'unicode' => [self::class, 'isUnicode'],
48 'upper' => 'ctype_upper',
49 'xdigit' => 'ctype_xdigit',
50 // syntax validation
51 'email' => [self::class, 'isEmail'],
52 'identifier' => [self::class, 'isPhpIdentifier'],
53 'uri' => [self::class, 'isUri'],
54 'url' => [self::class, 'isUrl'],
55 // environment validation
56 'class' => 'class_exists',
57 'interface' => 'interface_exists',
58 'directory' => 'is_dir',
59 'file' => 'is_file',
60 'type' => [self::class, 'isType'],
61 ];
62 /** @var array<string,callable> */
63 protected static $counters = ['string' => 'strlen', 'unicode' => [Strings::class, 'length'], 'array' => 'count', 'list' => 'count', 'alnum' => 'strlen', 'alpha' => 'strlen', 'digit' => 'strlen', 'lower' => 'strlen', 'space' => 'strlen', 'upper' => 'strlen', 'xdigit' => 'strlen'];
64 /**
65 * Verifies that the value is of expected types separated by pipe.
66 * @param mixed $value
67 * @throws AssertionException
68 */
69 public static function assert($value, string $expected, string $label = 'variable') : void
70 {
71 if (!static::is($value, $expected)) {
72 $expected = \str_replace(['|', ':'], [' or ', ' in range '], $expected);
73 $translate = ['boolean' => 'bool', 'integer' => 'int', 'double' => 'float', 'NULL' => 'null'];
74 $type = $translate[\gettype($value)] ?? \gettype($value);
75 if (\is_int($value) || \is_float($value) || \is_string($value) && \strlen($value) < 40) {
76 $type .= ' ' . \var_export($value, \true);
77 } elseif (\is_object($value)) {
78 $type .= ' ' . \get_class($value);
79 }
80 throw new AssertionException("The {$label} expects to be {$expected}, {$type} given.");
81 }
82 }
83 /**
84 * Verifies that element $key in array is of expected types separated by pipe.
85 * @param mixed[] $array
86 * @param int|string $key
87 * @throws AssertionException
88 */
89 public static function assertField(array $array, $key, ?string $expected = null, string $label = "item '%' in array") : void
90 {
91 if (!\array_key_exists($key, $array)) {
92 throw new AssertionException('Missing ' . \str_replace('%', $key, $label) . '.');
93 } elseif ($expected) {
94 static::assert($array[$key], $expected, \str_replace('%', $key, $label));
95 }
96 }
97 /**
98 * Verifies that the value is of expected types separated by pipe.
99 * @param mixed $value
100 */
101 public static function is($value, string $expected) : bool
102 {
103 foreach (\explode('|', $expected) as $item) {
104 if (\substr($item, -2) === '[]') {
105 if (\is_iterable($value) && self::everyIs($value, \substr($item, 0, -2))) {
106 return \true;
107 }
108 continue;
109 } elseif (\substr($item, 0, 1) === '?') {
110 $item = \substr($item, 1);
111 if ($value === null) {
112 return \true;
113 }
114 }
115 [$type] = $item = \explode(':', $item, 2);
116 if (isset(static::$validators[$type])) {
117 try {
118 if (!static::$validators[$type]($value)) {
119 continue;
120 }
121 } catch (\TypeError $e) {
122 continue;
123 }
124 } elseif ($type === 'pattern') {
125 if (Strings::match($value, '|^' . ($item[1] ?? '') . '$|D')) {
126 return \true;
127 }
128 continue;
129 } elseif (!$value instanceof $type) {
130 continue;
131 }
132 if (isset($item[1])) {
133 $length = $value;
134 if (isset(static::$counters[$type])) {
135 $length = static::$counters[$type]($value);
136 }
137 $range = \explode('..', $item[1]);
138 if (!isset($range[1])) {
139 $range[1] = $range[0];
140 }
141 if ($range[0] !== '' && $length < $range[0] || $range[1] !== '' && $length > $range[1]) {
142 continue;
143 }
144 }
145 return \true;
146 }
147 return \false;
148 }
149 /**
150 * Finds whether all values are of expected types separated by pipe.
151 * @param mixed[] $values
152 */
153 public static function everyIs(iterable $values, string $expected) : bool
154 {
155 foreach ($values as $value) {
156 if (!static::is($value, $expected)) {
157 return \false;
158 }
159 }
160 return \true;
161 }
162 /**
163 * Checks if the value is an integer or a float.
164 * @param mixed $value
165 */
166 public static function isNumber($value) : bool
167 {
168 return \is_int($value) || \is_float($value);
169 }
170 /**
171 * Checks if the value is an integer or a integer written in a string.
172 * @param mixed $value
173 */
174 public static function isNumericInt($value) : bool
175 {
176 return \is_int($value) || \is_string($value) && \preg_match('#^[+-]?[0-9]+$#D', $value);
177 }
178 /**
179 * Checks if the value is a number or a number written in a string.
180 * @param mixed $value
181 */
182 public static function isNumeric($value) : bool
183 {
184 return \is_float($value) || \is_int($value) || \is_string($value) && \preg_match('#^[+-]?([0-9]++\\.?[0-9]*|\\.[0-9]+)$#D', $value);
185 }
186 /**
187 * Checks if the value is a syntactically correct callback.
188 * @param mixed $value
189 */
190 public static function isCallable($value) : bool
191 {
192 return $value && \is_callable($value, \true);
193 }
194 /**
195 * Checks if the value is a valid UTF-8 string.
196 * @param mixed $value
197 */
198 public static function isUnicode($value) : bool
199 {
200 return \is_string($value) && \preg_match('##u', $value);
201 }
202 /**
203 * Checks if the value is 0, '', false or null.
204 * @param mixed $value
205 */
206 public static function isNone($value) : bool
207 {
208 return $value == null;
209 // intentionally ==
210 }
211 /** @internal */
212 public static function isMixed() : bool
213 {
214 return \true;
215 }
216 /**
217 * Checks if a variable is a zero-based integer indexed array.
218 * @param mixed $value
219 * @deprecated use \Packetery\Nette\Utils\Arrays::isList
220 */
221 public static function isList($value) : bool
222 {
223 return Arrays::isList($value);
224 }
225 /**
226 * Checks if the value is in the given range [min, max], where the upper or lower limit can be omitted (null).
227 * Numbers, strings and DateTime objects can be compared.
228 * @param mixed $value
229 */
230 public static function isInRange($value, array $range) : bool
231 {
232 if ($value === null || !(isset($range[0]) || isset($range[1]))) {
233 return \false;
234 }
235 $limit = $range[0] ?? $range[1];
236 if (\is_string($limit)) {
237 $value = (string) $value;
238 } elseif ($limit instanceof \DateTimeInterface) {
239 if (!$value instanceof \DateTimeInterface) {
240 return \false;
241 }
242 } elseif (\is_numeric($value)) {
243 $value *= 1;
244 } else {
245 return \false;
246 }
247 return (!isset($range[0]) || $value >= $range[0]) && (!isset($range[1]) || $value <= $range[1]);
248 }
249 /**
250 * Checks if the value is a valid email address. It does not verify that the domain actually exists, only the syntax is verified.
251 */
252 public static function isEmail(string $value) : bool
253 {
254 $atom = "[-a-z0-9!#\$%&'*+/=?^_`{|}~]";
255 // RFC 5322 unquoted characters in local-part
256 $alpha = "a-z\x80-\xff";
257 // superset of IDN
258 return (bool) \preg_match(<<<XX
259 \t\t(^
260 \t\t\t("([ !#-[\\]-~]*|\\\\[ -~])+"|{$atom}+(\\.{$atom}+)*) # quoted or unquoted
261 \t\t\t@
262 \t\t\t([0-9{$alpha}]([-0-9{$alpha}]{0,61}[0-9{$alpha}])?\\.)+ # domain - RFC 1034
263 \t\t\t[{$alpha}]([-0-9{$alpha}]{0,17}[{$alpha}])? # top domain
264 \t\t\$)Dix
265 XX
266 , $value);
267 }
268 /**
269 * Checks if the value is a valid URL address.
270 */
271 public static function isUrl(string $value) : bool
272 {
273 $alpha = "a-z\x80-\xff";
274 return (bool) \preg_match(<<<XX
275 \t\t(^
276 \t\t\thttps?://(
277 \t\t\t\t(([-_0-9{$alpha}]+\\.)* # subdomain
278 \t\t\t\t\t[0-9{$alpha}]([-0-9{$alpha}]{0,61}[0-9{$alpha}])?\\.)? # domain
279 \t\t\t\t\t[{$alpha}]([-0-9{$alpha}]{0,17}[{$alpha}])? # top domain
280 \t\t\t\t|\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3} # IPv4
281 \t\t\t\t|\\[[0-9a-f:]{3,39}\\] # IPv6
282 \t\t\t)(:\\d{1,5})? # port
283 \t\t\t(/\\S*)? # path
284 \t\t\t(\\?\\S*)? # query
285 \t\t\t(\\#\\S*)? # fragment
286 \t\t\$)Dix
287 XX
288 , $value);
289 }
290 /**
291 * Checks if the value is a valid URI address, that is, actually a string beginning with a syntactically valid schema.
292 */
293 public static function isUri(string $value) : bool
294 {
295 return (bool) \preg_match('#^[a-z\\d+\\.-]+:\\S+$#Di', $value);
296 }
297 /**
298 * Checks whether the input is a class, interface or trait.
299 */
300 public static function isType(string $type) : bool
301 {
302 return \class_exists($type) || \interface_exists($type) || \trait_exists($type);
303 }
304 /**
305 * Checks whether the input is a valid PHP identifier.
306 */
307 public static function isPhpIdentifier(string $value) : bool
308 {
309 return \preg_match('#^[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*$#D', $value) === 1;
310 }
311 }
312