<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\Utils;
use Nette;
/**
* Validation utilities.
*/
class Validators
{
use Nette\StaticClass;
private const BuiltinTypes = [
'string' => 1, 'int' => 1, 'float' => 1, 'bool' => 1, 'array' => 1, 'object' => 1,
'callable' => 1, 'iterable' => 1, 'void' => 1, 'null' => 1, 'mixed' => 1, 'false' => 1,
'never' => 1, 'true' => 1,
];
/** @var array<string,?callable> */
protected static $validators = [
// PHP types
'array' => 'is_array',
'bool' => 'is_bool',
'boolean' => 'is_bool',
'float' => 'is_float',
'int' => 'is_int',
'integer' => 'is_int',
'null' => 'is_null',
'object' => 'is_object',
'resource' => 'is_resource',
'scalar' => 'is_scalar',
'string' => 'is_string',
// pseudo-types
'callable' => [self::class, 'isCallable'],
'iterable' => 'is_iterable',
'list' => [Arrays::class, 'isList'],
'mixed' => [self::class, 'isMixed'],
'none' => [self::class, 'isNone'],
'number' => [self::class, 'isNumber'],
'numeric' => [self::class, 'isNumeric'],
'numericint' => [self::class, 'isNumericInt'],
// string patterns
'alnum' => 'ctype_alnum',
'alpha' => 'ctype_alpha',
'digit' => 'ctype_digit',
'lower' => 'ctype_lower',
'pattern' => null,
'space' => 'ctype_space',
'unicode' => [self::class, 'isUnicode'],
'upper' => 'ctype_upper',
'xdigit' => 'ctype_xdigit',
// syntax validation
'email' => [self::class, 'isEmail'],
'identifier' => [self::class, 'isPhpIdentifier'],
'uri' => [self::class, 'isUri'],
'url' => [self::class, 'isUrl'],
// environment validation
'class' => 'class_exists',
'interface' => 'interface_exists',
'directory' => 'is_dir',
'file' => 'is_file',
'type' => [self::class, 'isType'],
];
/** @var array<string,callable> */
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',
];
/**
* Verifies that the value is of expected types separated by pipe.
* @throws AssertionException
*/
public static function assert(mixed $value, string $expected, string $label = 'variable'): void
{
if (!static::is($value, $expected)) {
$expected = str_replace(['|', ':'], [' or ', ' in range '], $expected);
$translate = ['boolean' => 'bool', 'integer' => 'int', 'double' => 'float', 'NULL' => 'null'];
$type = $translate[gettype($value)] ?? gettype($value);
if (is_int($value) || is_float($value) || (is_string($value) && strlen($value) < 40)) {
$type .= ' ' . var_export($value, return: true);
} elseif (is_object($value)) {
$type .= ' ' . $value::class;
}
throw new AssertionException("The $label expects to be $expected, $type given.");
}
}
/**
* Verifies that element $key in array is of expected types separated by pipe.
* @param mixed[] $array
* @throws AssertionException
*/
public static function assertField(
array $array,
$key,
?string $expected = null,
string $label = "item '%' in array",
): void
{
if (!array_key_exists($key, $array)) {
throw new AssertionException('Missing ' . str_replace('%', $key, $label) . '.');
} elseif ($expected) {
static::assert($array[$key], $expected, str_replace('%', $key, $label));
}
}
/**
* Verifies that the value is of expected types separated by pipe.
*/
public static function is(mixed $value, string $expected): bool
{
foreach (explode('|', $expected) as $item) {
if (str_ends_with($item, '[]')) {
if (is_iterable($value) && self::everyIs($value, substr($item, 0, -2))) {
return true;
}
continue;
} elseif (str_starts_with($item, '?')) {
$item = substr($item, 1);
if ($value === null) {
return true;
}
}
[$type] = $item = explode(':', $item, 2);
if (isset(static::$validators[$type])) {
try {
if (!static::$validators[$type]($value)) {
continue;
}
} catch (\TypeError $e) {
continue;
}
} elseif ($type === 'pattern') {
if (Strings::match($value, '|^' . ($item[1] ?? '') . '$|D')) {
return true;
}
continue;
} elseif (!$value instanceof $type) {
continue;
}
if (isset($item[1])) {
$length = $value;
if (isset(static::$counters[$type])) {
$length = static::$counters[$type]($value);
}
$range = explode('..', $item[1]);
if (!isset($range[1])) {
$range[1] = $range[0];
}
if (($range[0] !== '' && $length < $range[0]) || ($range[1] !== '' && $length > $range[1])) {
continue;
}
}
return true;
}
return false;
}
/**
* Finds whether all values are of expected types separated by pipe.
* @param mixed[] $values
*/
public static function everyIs(iterable $values, string $expected): bool
{
foreach ($values as $value) {
if (!static::is($value, $expected)) {
return false;
}
}
return true;
}
/**
* Checks if the value is an integer or a float.
* @return ($value is int|float ? true : false)
*/
public static function isNumber(mixed $value): bool
{
return is_int($value) || is_float($value);
}
/**
* Checks if the value is an integer or a integer written in a string.
* @return ($value is non-empty-string ? bool : ($value is int ? true : false))
*/
public static function isNumericInt(mixed $value): bool
{
return is_int($value) || (is_string($value) && preg_match('#^[+-]?[0-9]+$#D', $value));
}
/**
* Checks if the value is a number or a number written in a string.
* @return ($value is non-empty-string ? bool : ($value is int|float ? true : false))
*/
public static function isNumeric(mixed $value): bool
{
return is_float($value) || is_int($value) || (is_string($value) && preg_match('#^[+-]?([0-9]++\.?[0-9]*|\.[0-9]+)$#D', $value));
}
/**
* Checks if the value is a syntactically correct callback.
*/
public static function isCallable(mixed $value): bool
{
return $value && is_callable($value, syntax_only: true);
}
/**
* Checks if the value is a valid UTF-8 string.
*/
public static function isUnicode(mixed $value): bool
{
return is_string($value) && preg_match('##u', $value);
}
/**
* Checks if the value is 0, '', false or null.
* @return ($value is 0|''|false|null ? true : false)
*/
public static function isNone(mixed $value): bool
{
return $value == null; // intentionally ==
}
/** @internal */
public static function isMixed(): bool
{
return true;
}
/**
* Checks if a variable is a zero-based integer indexed array.
* @deprecated use Nette\Utils\Arrays::isList
* @return ($value is list ? true : false)
*/
public static function isList(mixed $value): bool
{
return Arrays::isList($value);
}
/**
* Checks if the value is in the given range [min, max], where the upper or lower limit can be omitted (null).
* Numbers, strings and DateTime objects can be compared.
*/
public static function isInRange(mixed $value, array $range): bool
{
if ($value === null || !(isset($range[0]) || isset($range[1]))) {
return false;
}
$limit = $range[0] ?? $range[1];
if (is_string($limit)) {
$value = (string) $value;
} elseif ($limit instanceof \DateTimeInterface) {
if (!$value instanceof \DateTimeInterface) {
return false;
}
} elseif (is_numeric($value)) {
$value *= 1;
} else {
return false;
}
return (!isset($range[0]) || ($value >= $range[0])) && (!isset($range[1]) || ($value <= $range[1]));
}
/**
* Checks if the value is a valid email address. It does not verify that the domain actually exists, only the syntax is verified.
*/
public static function isEmail(string $value): bool
{
$atom = "[-a-z0-9!#$%&'*+/=?^_`{|}~]"; // RFC 5322 unquoted characters in local-part
$alpha = "a-z\x80-\xFF"; // superset of IDN
return (bool) preg_match(<<<XX
(^(?n)
("([ !#-[\\]-~]*|\\\\[ -~])+"|$atom+(\\.$atom+)*) # quoted or unquoted
@
([0-9$alpha]([-0-9$alpha]{0,61}[0-9$alpha])?\\.)+ # domain - RFC 1034
[$alpha]([-0-9$alpha]{0,17}[$alpha])? # top domain
$)Dix
XX, $value);
}
/**
* Checks if the value is a valid URL address.
*/
public static function isUrl(string $value): bool
{
$alpha = "a-z\x80-\xFF";
return (bool) preg_match(<<<XX
(^(?n)
https?://(
(([-_0-9$alpha]+\\.)* # subdomain
[0-9$alpha]([-0-9$alpha]{0,61}[0-9$alpha])?\\.)? # domain
[$alpha]([-0-9$alpha]{0,17}[$alpha])? # top domain
|\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3} # IPv4
|\\[[0-9a-f:]{3,39}\\] # IPv6
)(:\\d{1,5})? # port
(/\\S*)? # path
(\\?\\S*)? # query
(\\#\\S*)? # fragment
$)Dix
XX, $value);
}
/**
* Checks if the value is a valid URI address, that is, actually a string beginning with a syntactically valid schema.
*/
public static function isUri(string $value): bool
{
return (bool) preg_match('#^[a-z\d+\.-]+:\S+$#Di', $value);
}
/**
* Checks whether the input is a class, interface or trait.
* @deprecated
*/
public static function isType(string $type): bool
{
return class_exists($type) || interface_exists($type) || trait_exists($type);
}
/**
* Checks whether the input is a valid PHP identifier.
*/
public static function isPhpIdentifier(string $value): bool
{
return preg_match('#^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$#D', $value) === 1;
}
/**
* Determines if type is PHP built-in type. Otherwise, it is the class name.
*/
public static function isBuiltinType(string $type): bool
{
return isset(self::BuiltinTypes[strtolower($type)]);
}
/**
* Determines if type is special class name self/parent/static.
*/
public static function isClassKeyword(string $name): bool
{
return (bool) preg_match('#^(self|parent|static)$#Di', $name);
}
/**
* Checks whether the given type declaration is syntactically valid.
*/
public static function isTypeDeclaration(string $type): bool
{
return (bool) preg_match(<<<'XX'
~((?n)
\?? (?<type> \\? (?<name> [a-zA-Z_\x7f-\xff][\w\x7f-\xff]*) (\\ (?&name))* ) |
(?<intersection> (?&type) (& (?&type))+ ) |
(?<upart> (?&type) | \( (?&intersection) \) ) (\| (?&upart))+
)$~xAD
XX, $type);
}
}
How To Payment
Making a payment on our website is quick and secure. Start by logging in or creating an account. Select your preferred payment method, input the required details, and review the information. Once you confirm everything is correct, click on the "Submit Payment" button. You’ll receive instant confirmation and can track your payment status through your account dashboard. It’s an easy and secure process.