Upstream nette/utils state of the touched files before dc5e00bdb1

This commit is contained in:
upstream committed 2026-09-04 09:22:41 +02:00
commit 385c8ee43f
2 files changed
+838

No files matched your search

+417
View File
@@ -0,0 +1,417 @@
<?php declare(strict_types=1);
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Utils;
use Nette;
use function array_key_exists, class_exists, explode, gettype, interface_exists, is_callable, is_float, is_int, is_iterable, is_numeric, is_object, is_string, preg_match, str_ends_with, str_replace, str_starts_with, strlen, strtolower, substr, trait_exists, var_export;
/**
* 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(mixed): bool)> */
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(mixed): int> */
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 item $key in array exists and is of expected types separated by pipe.
* @param mixed[] $array
* @throws AssertionException
*/
public static function assertField(
array $array,
int|string $key,
?string $expected = null,
string $label = "item '%' in array",
): void
{
if (!array_key_exists($key, $array)) {
throw new AssertionException('Missing ' . str_replace('%', (string) $key, $label) . '.');
} elseif ($expected) {
static::assert($array[$key], $expected, str_replace('%', (string) $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) {
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 iterable<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|0.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.
* @param array{int|float|string|\DateTimeInterface|null, int|float|string|\DateTimeInterface|null} $range
*/
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";
$octet = '(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])'; // 0..255
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
|$octet(\\.$octet){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);
}
}
+421
View File
@@ -0,0 +1,421 @@
<?php declare(strict_types=1);
/**
* Test: Nette\Utils\Validators::is()
*/
use Nette\Utils\Validators;
use Tester\Assert;
require __DIR__ . '/../bootstrap.php';
test('checks integer type with range constraints', function () {
Assert::false(Validators::is(true, 'int'));
Assert::false(Validators::is('1', 'int'));
Assert::true(Validators::is(1, 'integer'));
Assert::true(Validators::is(1, 'int'));
Assert::false(Validators::is(1, 'int:)'));
Assert::false(Validators::is(1, 'int:0'));
Assert::true(Validators::is(1, 'int:1'));
Assert::true(Validators::is(0, 'int:0'));
Assert::true(Validators::is(1, 'int:..'));
Assert::false(Validators::is(1, 'int:0..0'));
Assert::true(Validators::is(1, 'int:0..1'));
Assert::true(Validators::is(1, 'int:0..'));
Assert::false(Validators::is(1, 'int:..0'));
Assert::true(Validators::is(1, 'int:..1'));
Assert::true(Validators::is(0, 'int:..0'));
Assert::true(Validators::is(-1, 'int:..0'));
});
test('verifies validity of a float value', function () {
Assert::false(Validators::is(true, 'float'));
Assert::false(Validators::is('1', 'float'));
Assert::false(Validators::is(1, 'float'));
Assert::true(Validators::is(1.0, 'float'));
});
test('validates number type for both integers and floats', function () {
Assert::false(Validators::is(true, 'number'));
Assert::false(Validators::is('1', 'number'));
Assert::true(Validators::is(1, 'number'));
Assert::true(Validators::is(1.0, 'number'));
});
test('checks numeric string format with sign and decimal rules', function () {
Assert::false(Validators::is(true, 'numeric'));
Assert::true(Validators::is('1', 'numeric'));
Assert::true(Validators::is('-1', 'numeric'));
Assert::true(Validators::is('-1.5', 'numeric'));
Assert::true(Validators::is('-.5', 'numeric'));
Assert::true(Validators::is('+1', 'numeric'));
Assert::true(Validators::is('+1.5', 'numeric'));
Assert::true(Validators::is('+.5', 'numeric'));
Assert::false(Validators::is('1e6', 'numeric'));
Assert::true(Validators::is(1, 'numeric'));
Assert::true(Validators::is(1.0, 'numeric'));
});
test('validates strict numeric integer string format', function () {
Assert::false(Validators::is(true, 'numericint'));
Assert::true(Validators::is('1', 'numericint'));
Assert::true(Validators::is('-1', 'numericint'));
Assert::false(Validators::is('-1.5', 'numericint'));
Assert::false(Validators::is('-.5', 'numericint'));
Assert::true(Validators::is('+1', 'numericint'));
Assert::false(Validators::is('+1.5', 'numericint'));
Assert::false(Validators::is('+.5', 'numericint'));
Assert::false(Validators::is('1e6', 'numericint'));
Assert::true(Validators::is(1, 'numericint'));
Assert::false(Validators::is(1.0, 'numericint'));
});
test('ensures boolean value matches expected flag', function () {
Assert::false(Validators::is(1, 'bool'));
Assert::true(Validators::is(true, 'bool'));
Assert::true(Validators::is(false, 'bool'));
Assert::true(Validators::is(true, 'boolean'));
Assert::true(Validators::is(true, 'bool:1'));
Assert::false(Validators::is(true, 'bool:0'));
Assert::false(Validators::is(false, 'bool:1'));
Assert::true(Validators::is(false, 'bool:0'));
Assert::true(Validators::is(false, 'bool:0..1'));
});
test('checks string type with fixed length constraint', function () {
Assert::false(Validators::is(1, 'string'));
Assert::true(Validators::is('', 'string'));
Assert::true(Validators::is('hello', 'string'));
Assert::true(Validators::is('hello', 'string:5'));
Assert::false(Validators::is('hello', 'string:4'));
Assert::true(Validators::is('hello', 'string:4..'));
Assert::false(Validators::is('hello', 'string:1..4'));
});
test('validates Unicode string with length requirements', function () {
Assert::false(Validators::is(1, 'unicode'));
Assert::true(Validators::is('', 'unicode'));
Assert::true(Validators::is('hello', 'unicode'));
Assert::false(Validators::is("hello\xFF", 'unicode'));
Assert::true(Validators::is('hello', 'unicode:5'));
Assert::false(Validators::is('hello', 'unicode:4'));
Assert::true(Validators::is('hello', 'unicode:4..'));
Assert::false(Validators::is('hello', 'unicode:1..4'));
});
test('checks array structure against length constraints', function () {
Assert::false(Validators::is(null, 'array'));
Assert::true(Validators::is([], 'array'));
Assert::true(Validators::is([], 'array:0'));
Assert::true(Validators::is([1], 'array:1'));
Assert::true(Validators::is([1], 'array:0..'));
Assert::true(Validators::is([], 'array:..1'));
});
test('verifies that value is a sequential list', function () {
Assert::false(Validators::is(null, 'list'));
Assert::true(Validators::is([], 'list'));
Assert::true(Validators::is([1], 'list'));
Assert::true(Validators::is(['a', 'b', 'c'], 'list'));
Assert::false(Validators::is([4 => 1, 2, 3], 'list'));
Assert::false(Validators::is([1 => 'a', 0 => 'b'], 'list'));
Assert::false(Validators::is(['key' => 'value'], 'list'));
$arr = [];
$arr[] = &$arr;
Assert::true(Validators::is($arr, 'list'));
Assert::false(Validators::is([1, 2, 3], 'list:4'));
});
test('confirms value is an object instance', function () {
Assert::false(Validators::is(null, 'object'));
Assert::true(Validators::is(new stdClass, 'object'));
});
test('checks that value is scalar', function () {
Assert::false(Validators::is(null, 'scalar'));
Assert::false(Validators::is([], 'scalar'));
Assert::true(Validators::is(1, 'scalar'));
});
test('validates callable type including array callables', function () {
Assert::false(Validators::is(null, 'callable'));
Assert::false(Validators::is([], 'callable'));
Assert::false(Validators::is(1, 'callable'));
Assert::false(Validators::is('', 'callable'));
Assert::true(Validators::is('hello', 'callable'));
Assert::false(Validators::is(['hello'], 'callable'));
Assert::true(Validators::is(['hello', 'world'], 'callable'));
});
test('ensures value is null', function () {
Assert::false(Validators::is(0, 'null'));
Assert::true(Validators::is(null, 'null'));
});
test('accepts any value for mixed type', function () {
Assert::true(Validators::is([], 'mixed'));
Assert::true(Validators::is(null, 'mixed'));
});
test('validates email format with domain restrictions', function () {
Assert::false(Validators::is('', 'email'));
Assert::false(Validators::is(false, 'email'));
Assert::false(Validators::is('hello', 'email'));
Assert::true(Validators::is('hello@world.cz', 'email'));
Assert::false(Validators::is('hello@localhost', 'email'));
Assert::false(Validators::is('hello@127.0.0.1', 'email'));
Assert::false(Validators::is('hello@localhost.a0', 'email'));
Assert::false(Validators::is('hello@localhost.0a', 'email'));
Assert::true(Validators::is('hello@l.org', 'email'));
Assert::true(Validators::is('hello@1.org', 'email'));
Assert::false(Validators::is('jean.françois@lyotard.fr', 'email'));
Assert::true(Validators::is('jerzy@kosiński.pl', 'email'));
Assert::false(Validators::is('péter@esterházy.hu', 'email'));
Assert::true(Validators::is('hello@1.c0m', 'email'));
Assert::true(Validators::is('hello@1.c', 'email'));
});
test('verifies URL format across various domain cases', function () {
Assert::false(Validators::is('', 'url'));
Assert::false(Validators::is(false, 'url'));
Assert::false(Validators::is('hello', 'url'));
Assert::false(Validators::is('nette.org', 'url'));
Assert::false(Validators::is('http://nette.org0', 'url'));
Assert::false(Validators::is('http://nette.0org', 'url'));
Assert::false(Validators::is('http://_nette.org', 'url'));
Assert::false(Validators::is('http://www._nette.org', 'url'));
Assert::false(Validators::is('http://www.ne_tte.org', 'url'));
Assert::true(Validators::is('http://1.org', 'url'));
Assert::true(Validators::is('http://l.org', 'url'));
Assert::true(Validators::is('http://localhost', 'url'));
Assert::true(Validators::is('http://127.0.0.1', 'url'));
Assert::true(Validators::is('http://255.255.255.255', 'url'));
Assert::false(Validators::is('http://999.999.999.999', 'url'));
Assert::false(Validators::is('http://256.1.1.1', 'url'));
Assert::false(Validators::is('http://1.2.3', 'url'));
Assert::true(Validators::is('http://[::1]', 'url'));
Assert::true(Validators::is('http://[2001:0db8:0000:0000:0000:0000:1428:57AB]', 'url'));
Assert::true(Validators::is('http://nette.org/path', 'url'));
Assert::true(Validators::is('http://nette.org:8080/path', 'url'));
Assert::true(Validators::is('https://www.nette.org/path', 'url'));
Assert::true(Validators::is('https://www.nette.org/path?query#fragment', 'url'));
Assert::true(Validators::is('https://www.nette.org?query', 'url'));
Assert::true(Validators::is('https://www.nette.org#fragment', 'url'));
Assert::true(Validators::is('https://www.nette.org?#', 'url'));
Assert::true(Validators::is('https://example.c0m', 'url'));
Assert::true(Validators::is('https://example.l', 'url'));
Assert::true(Validators::is('http://one_two.example.com', 'url'));
Assert::true(Validators::is('http://_.example.com', 'url'));
Assert::true(Validators::is('http://_e_.example.com', 'url'));
});
test('checks URI format with proper scheme requirements', function () {
Assert::false(Validators::is('', 'uri'));
Assert::false(Validators::is(false, 'uri'));
Assert::false(Validators::is('hello', 'uri'));
Assert::false(Validators::is('nette.org', 'uri'));
Assert::false(Validators::is('mailto: gandalf@example.org', 'uri'));
Assert::false(Validators::is('invalid-scheme :gandalf@example.org', 'uri'));
Assert::false(Validators::is('invalid-scheme~:gandalf@example.org', 'uri'));
Assert::true(Validators::is('mailto:gandalf@example.org', 'uri'));
Assert::true(Validators::is('valid-scheme+.0:lalala', 'uri'));
Assert::true(Validators::is('bitcoin:mipcBbFg9gMiCh81Kj8tqqdgoZub1ZJRfn', 'uri'));
});
test('accepts empty and null values for none type', function () {
Assert::true(Validators::is(0, 'none'));
Assert::true(Validators::is('', 'none'));
Assert::true(Validators::is(null, 'none'));
Assert::true(Validators::is(false, 'none'));
Assert::false(Validators::is('0', 'none'));
Assert::true(Validators::is([], 'none'));
});
test('validates string against a regular expression pattern', function () {
Assert::true(Validators::is('', 'pattern'));
Assert::true(Validators::is(' 123', 'pattern:\s+\d+'));
Assert::false(Validators::is(' 123x', 'pattern:\s+\d+'));
});
test('ensures alphanumeric string meets minimum length', function () {
Assert::false(Validators::is('', 'alnum'));
Assert::false(Validators::is('a-1', 'alnum'));
Assert::true(Validators::is('a1', 'alnum'));
Assert::true(Validators::is('a1', 'alnum:2'));
});
test('checks alphabetic string within a specified length range', function () {
Assert::false(Validators::is('', 'alpha'));
Assert::false(Validators::is('a1', 'alpha'));
Assert::true(Validators::is('aA', 'alpha'));
Assert::true(Validators::is('aA', 'alpha:1..3'));
});
test('validates digit-only string with length limitations', function () {
Assert::false(Validators::is('', 'digit'));
Assert::false(Validators::is('123x', 'digit'));
Assert::true(Validators::is('123', 'digit'));
Assert::false(Validators::is('123', 'digit:..2'));
});
test('ensures string is entirely lowercase with fixed length', function () {
Assert::false(Validators::is('', 'lower'));
Assert::false(Validators::is('Hello', 'lower'));
Assert::true(Validators::is('hello', 'lower'));
Assert::false(Validators::is('hello', 'lower:9'));
});
test('confirms string is entirely uppercase', function () {
Assert::false(Validators::is('', 'upper'));
Assert::false(Validators::is('Hello', 'upper'));
Assert::true(Validators::is('HELLO', 'upper'));
});
test('validates that string contains only whitespace', function () {
Assert::false(Validators::is('', 'space'));
Assert::false(Validators::is(' 1', 'space'));
Assert::true(Validators::is(" \t\r\n", 'space'));
});
test('checks that string consists solely of hexadecimal digits', function () {
Assert::false(Validators::is('', 'xdigit'));
Assert::false(Validators::is('123x', 'xdigit'));
Assert::true(Validators::is('123aA', 'xdigit'));
});
test('validates union type allowing int or float values', function () {
Assert::true(Validators::is(1.0, 'int|float'));
Assert::true(Validators::is(1, 'int|float'));
Assert::false(Validators::is('1', 'int|float'));
});
test('verifies existence of a type by its name', function () {
class rimmer
{
}
interface kryton
{
}
Assert::true(Validators::is('rimmer', 'type'));
Assert::true(Validators::is('kryton', 'type'));
Assert::false(Validators::is('1', 'type'));
});
test('checks for the existence of a class by name', function () {
Assert::true(Validators::is('rimmer', 'class'));
Assert::false(Validators::is('kryton', 'class'));
Assert::false(Validators::is('1', 'class'));
});
test('determines if an interface exists by its name', function () {
Assert::false(Validators::is('rimmer', 'interface'));
Assert::true(Validators::is('kryton', 'interface'));
Assert::false(Validators::is('1', 'interface'));
});
test('validates file existence based on file type', function () {
Assert::true(Validators::is(__FILE__, 'file'));
Assert::false(Validators::is(__FILE__ . 'xx', 'class'));
});
test('confirms directory existence', function () {
Assert::true(Validators::is(__DIR__, 'directory'));
Assert::false(Validators::is(__DIR__ . 'xx', 'directory'));
});
test('ensures identifier follows naming conventions', function () {
Assert::true(Validators::is('Item', 'identifier'));
Assert::false(Validators::is('0Item', 'identifier'));
});
test('validates optional string or email format', function () {
Assert::true(Validators::is('', 'string:0|email'));
Assert::true(Validators::is('foo@bar.com', 'string:0|email'));
Assert::false(Validators::is('foo', 'string:0|email'));
});
test('checks that value is iterable from arrays, iterators, or generators', function () {
$gen = function () {
yield;
};
Assert::true(Validators::is([1, 2, 3], 'iterable'));
Assert::true(Validators::is(new ArrayIterator([1, 2, 3]), 'iterable'));
Assert::true(Validators::is($gen(), 'iterable'));
Assert::false(Validators::is(1, 'iterable'));
Assert::false(Validators::is(3.14, 'iterable'));
Assert::false(Validators::is(new stdClass, 'iterable'));
});
test('validates multi-dimensional arrays against element type rules', function () {
class Abc
{
}
Assert::true(Validators::is([], 'int[]'));
Assert::true(Validators::is(new ArrayIterator([]), 'int[]'));
Assert::false(Validators::is(1, 'int[]'));
Assert::false(Validators::is(2.15, 'int[]'));
Assert::true(Validators::is(2.15, 'float|int[]'));
Assert::true(Validators::is(2.15, 'int[]|float'));
Assert::true(Validators::is([1, 2, 3], 'int[]'));
Assert::false(Validators::is([1, 2, 3], 'int[][]'));
Assert::true(Validators::is([[1], [2, 3]], 'int[][]'));
Assert::false(Validators::is([1, 2.15, 3], 'int[]'));
Assert::true(Validators::is([1, 2.15, 3], 'number[]'));
Assert::true(Validators::is([new Abc], 'Abc[]'));
Assert::false(Validators::is([new Abc, new stdClass], 'Abc[]'));
Assert::true(Validators::is(['ABCD', 'EFGH', 'IJKL'], 'string:4[]'));
Assert::false(Validators::is(['ABCD', 'EFGH', 'IJKLM'], 'string:4[]'));
Assert::true(Validators::is([['ABCD', 'EFGH'], ['IJKL']], 'string:4[][]'));
Assert::false(Validators::is([['ABCD', 'EFGH'], ['IJKLM']], 'string:4[][]'));
});
test('checks nullable string type acceptance', function () {
Assert::true(Validators::is(null, '?string'));
Assert::true(Validators::is('1', '?string'));
Assert::false(Validators::is(true, '?int'));
Assert::false(Validators::is(0, '?string'));
});