Subversion-Projekte lars-tiefland.laravel_shop

Revision

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