Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 148 | Zur aktuellen Revision | Details | Vergleich mit vorheriger | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 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
 
150 lars 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
 
148 lars 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
100
	{
101
		if (!static::is($value, $expected)) {
102
			$expected = str_replace(['|', ':'], [' or ', ' in range '], $expected);
103
			$translate = ['boolean' => 'bool', 'integer' => 'int', 'double' => 'float', 'NULL' => 'null'];
104
			$type = $translate[gettype($value)] ?? gettype($value);
105
			if (is_int($value) || is_float($value) || (is_string($value) && strlen($value) < 40)) {
106
				$type .= ' ' . var_export($value, true);
107
			} elseif (is_object($value)) {
108
				$type .= ' ' . get_class($value);
109
			}
110
 
111
			throw new AssertionException("The $label expects to be $expected, $type given.");
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(
123
		array $array,
124
		$key,
125
		?string $expected = null,
126
		string $label = "item '%' in array"
150 lars 127
	): void
128
	{
148 lars 129
		if (!array_key_exists($key, $array)) {
130
			throw new AssertionException('Missing ' . str_replace('%', $key, $label) . '.');
131
 
132
		} elseif ($expected) {
133
			static::assert($array[$key], $expected, str_replace('%', $key, $label));
134
		}
135
	}
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
143
	{
144
		foreach (explode('|', $expected) as $item) {
145
			if (substr($item, -2) === '[]') {
146
				if (is_iterable($value) && self::everyIs($value, substr($item, 0, -2))) {
147
					return true;
148
				}
149
 
150
				continue;
151
			} elseif (substr($item, 0, 1) === '?') {
152
				$item = substr($item, 1);
153
				if ($value === null) {
154
					return true;
155
				}
156
			}
157
 
158
			[$type] = $item = explode(':', $item, 2);
159
			if (isset(static::$validators[$type])) {
160
				try {
161
					if (!static::$validators[$type]($value)) {
162
						continue;
163
					}
164
				} catch (\TypeError $e) {
165
					continue;
166
				}
167
			} elseif ($type === 'pattern') {
168
				if (Strings::match($value, '|^' . ($item[1] ?? '') . '$|D')) {
169
					return true;
170
				}
171
 
172
				continue;
173
			} elseif (!$value instanceof $type) {
174
				continue;
175
			}
176
 
177
			if (isset($item[1])) {
178
				$length = $value;
179
				if (isset(static::$counters[$type])) {
180
					$length = static::$counters[$type]($value);
181
				}
182
 
183
				$range = explode('..', $item[1]);
184
				if (!isset($range[1])) {
185
					$range[1] = $range[0];
186
				}
187
 
188
				if (($range[0] !== '' && $length < $range[0]) || ($range[1] !== '' && $length > $range[1])) {
189
					continue;
190
				}
191
			}
192
 
193
			return true;
194
		}
195
 
196
		return false;
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
205
	{
206
		foreach ($values as $value) {
207
			if (!static::is($value, $expected)) {
208
				return false;
209
			}
210
		}
211
 
212
		return true;
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);
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));
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));
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);
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);
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 ==
273
	}
274
 
275
 
276
	/** @internal */
277
	public static function isMixed(): bool
278
	{
279
		return true;
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
	 */
288
	public static function isList($value): bool
289
	{
290
		return Arrays::isList($value);
291
	}
292
 
293
 
294
	/**
295
	 * Checks if the value is in the given range [min, max], where the upper or lower limit can be omitted (null).
296
	 * Numbers, strings and DateTime objects can be compared.
297
	 * @param  mixed  $value
298
	 */
299
	public static function isInRange($value, array $range): bool
300
	{
301
		if ($value === null || !(isset($range[0]) || isset($range[1]))) {
302
			return false;
303
		}
304
 
305
		$limit = $range[0] ?? $range[1];
306
		if (is_string($limit)) {
307
			$value = (string) $value;
308
		} elseif ($limit instanceof \DateTimeInterface) {
309
			if (!$value instanceof \DateTimeInterface) {
310
				return false;
311
			}
312
		} elseif (is_numeric($value)) {
313
			$value *= 1;
314
		} else {
315
			return false;
316
		}
317
 
318
		return (!isset($range[0]) || ($value >= $range[0])) && (!isset($range[1]) || ($value <= $range[1]));
319
	}
320
 
321
 
322
	/**
323
	 * Checks if the value is a valid email address. It does not verify that the domain actually exists, only the syntax is verified.
324
	 */
325
	public static function isEmail(string $value): bool
326
	{
327
		$atom = "[-a-z0-9!#$%&'*+/=?^_`{|}~]"; // RFC 5322 unquoted characters in local-part
328
		$alpha = "a-z\x80-\xFF"; // superset of IDN
329
		return (bool) preg_match(<<<XX
330
		(^
331
			("([ !#-[\\]-~]*|\\\\[ -~])+"|$atom+(\\.$atom+)*)  # quoted or unquoted
332
			@
333
			([0-9$alpha]([-0-9$alpha]{0,61}[0-9$alpha])?\\.)+  # domain - RFC 1034
334
			[$alpha]([-0-9$alpha]{0,17}[$alpha])?              # top domain
335
		$)Dix
336
XX
150 lars 337
			, $value);
148 lars 338
	}
339
 
340
 
341
	/**
342
	 * Checks if the value is a valid URL address.
343
	 */
344
	public static function isUrl(string $value): bool
345
	{
346
		$alpha = "a-z\x80-\xFF";
347
		return (bool) preg_match(<<<XX
348
		(^
349
			https?://(
350
				(([-_0-9$alpha]+\\.)*                       # subdomain
351
					[0-9$alpha]([-0-9$alpha]{0,61}[0-9$alpha])?\\.)?  # domain
352
					[$alpha]([-0-9$alpha]{0,17}[$alpha])?   # top domain
353
				|\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}  # IPv4
354
				|\\[[0-9a-f:]{3,39}\\]                      # IPv6
355
			)(:\\d{1,5})?                                   # port
356
			(/\\S*)?                                        # path
357
			(\\?\\S*)?                                      # query
358
			(\\#\\S*)?                                      # fragment
359
		$)Dix
360
XX
150 lars 361
			, $value);
148 lars 362
	}
363
 
364
 
365
	/**
366
	 * Checks if the value is a valid URI address, that is, actually a string beginning with a syntactically valid schema.
367
	 */
368
	public static function isUri(string $value): bool
369
	{
370
		return (bool) preg_match('#^[a-z\d+\.-]+:\S+$#Di', $value);
371
	}
372
 
373
 
374
	/**
375
	 * Checks whether the input is a class, interface or trait.
376
	 */
377
	public static function isType(string $type): bool
378
	{
379
		return class_exists($type) || interface_exists($type) || trait_exists($type);
380
	}
381
 
382
 
383
	/**
384
	 * Checks whether the input is a valid PHP identifier.
385
	 */
386
	public static function isPhpIdentifier(string $value): bool
387
	{
388
		return preg_match('#^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$#D', $value) === 1;
389
	}
150 lars 390
 
391
 
392
	/**
393
	 * Determines if type is PHP built-in type. Otherwise, it is the class name.
394
	 */
395
	public static function isBuiltinType(string $type): bool
396
	{
397
		return isset(self::BuiltinTypes[strtolower($type)]);
398
	}
399
 
400
 
401
	/**
402
	 * Determines if type is special class name self/parent/static.
403
	 */
404
	public static function isClassKeyword(string $name): bool
405
	{
406
		return (bool) preg_match('#^(self|parent|static)$#Di', $name);
407
	}
408
 
409
 
410
	/**
411
	 * Checks whether the given type declaration is syntactically valid.
412
	 */
413
	public static function isTypeDeclaration(string $type): bool
414
	{
415
		return (bool) preg_match(<<<'XX'
416
		~(
417
			\?? (?<type> \\? (?<name> [a-zA-Z_\x7f-\xff][\w\x7f-\xff]*) (\\ (?&name))* ) |
418
			(?<intersection> (?&type) (& (?&type))+ ) |
419
			(?<upart> (?&type) | \( (?&intersection) \) )  (\| (?&upart))+
420
		)$~xAD
421
XX
422
			, $type);
423
	}
148 lars 424
}