Skip to content

Commit e993244

Browse files
authored
Create Validator.php
1 parent daec105 commit e993244

File tree

1 file changed

+383
-0
lines changed

1 file changed

+383
-0
lines changed

src/patch/Nette/Utils/Validator.php

+383
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,383 @@
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('
323+
(^
324+
("([ !#-[\\]-~]*|\\\\[ -~])+"|$atom+(\\.$atom+)*)
325+
@
326+
([0-9$alpha]([-0-9$alpha]{0,61}[0-9$alpha])?\\.)+
327+
[$alpha]([-0-9$alpha]{0,17}[$alpha])?
328+
$)Dix
329+
'
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('
341+
(^
342+
https?://(
343+
(([-_0-9$alpha]+\\.)*
344+
[0-9$alpha]([-0-9$alpha]{0,61}[0-9$alpha])?\\.)?
345+
[$alpha]([-0-9$alpha]{0,17}[$alpha])?
346+
|\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}
347+
|\\[[0-9a-f:]{3,39}\\]
348+
)(:\\d{1,5})?
349+
(/\\S*)?
350+
(\\?\\S*)?
351+
(\\#\\S*)?
352+
$)Dix
353+
'
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+
}

0 commit comments

Comments
 (0)