This commit is contained in:
Javier Casares 2026-08-24 11:42:10 +00:00
commit 80a2202d6e
18 changed files with 305 additions and 122 deletions

View file

@ -9,7 +9,7 @@ namespace Nette\Schema;
use Nette;
use Nette\Utils\Reflection;
use function count, explode, get_debug_type, implode, in_array, is_array, is_float, is_int, is_object, is_scalar, is_string, method_exists, preg_match, preg_quote, preg_replace, preg_replace_callback, settype, str_replace, strlen, trim, var_export;
use function array_map, count, explode, get_debug_type, implode, in_array, is_array, is_float, is_int, is_object, is_scalar, is_string, is_subclass_of, method_exists, preg_match, preg_quote, preg_replace, preg_replace_callback, settype, str_replace, strlen, trim, var_export;
/**
@ -54,6 +54,9 @@ final class Helpers
}
/**
* Returns the type of a property or parameter as a string, or null if not determinable.
*/
public static function getPropertyType(\ReflectionProperty|\ReflectionParameter $prop): ?string
{
if ($type = Nette\Utils\Type::fromReflection($prop)) {
@ -89,6 +92,9 @@ final class Helpers
}
/**
* Formats a value for use in error messages (e.g., 'hello', true, object stdClass).
*/
public static function formatValue(mixed $value): string
{
if ($value instanceof DynamicParameter) {
@ -105,6 +111,9 @@ final class Helpers
}
/**
* Adds a TypeMismatch error to the context if the value does not match the expected type.
*/
public static function validateType(mixed $value, string $expected, Context $context): void
{
if (!Nette\Utils\Validators::is($value, $expected)) {
@ -119,7 +128,10 @@ final class Helpers
}
/** @param array{?float, ?float} $range */
/**
* Adds a range error to the context if the value (or its length for strings/arrays) is outside the given range.
* @param array{?float, ?float} $range
*/
public static function validateRange(mixed $value, array $range, Context $context, string $types = ''): void
{
if (is_array($value) || is_string($value)) {
@ -146,7 +158,10 @@ final class Helpers
}
/** @param array{?float, ?float} $range */
/**
* Checks whether a value falls within the given [min, max] range (null means no bound).
* @param array{?float, ?float} $range
*/
public static function isInRange(mixed $value, array $range): bool
{
return ($range[0] === null || $value >= $range[0])
@ -154,6 +169,9 @@ final class Helpers
}
/**
* Adds a PatternMismatch error to the context if the value does not match the pattern.
*/
public static function validatePattern(string $value, string $pattern, Context $context): void
{
if (!preg_match("\x01^(?:$pattern)$\x01Du", $value)) {
@ -166,7 +184,10 @@ final class Helpers
}
/** @return \Closure(mixed): mixed */
/**
* Returns a closure that casts a value to the given type (built-in, backed enum, class with constructor, or plain class).
* @return \Closure(mixed, Context): mixed
*/
public static function getCastStrategy(string $type): \Closure
{
if (Nette\Utils\Validators::isBuiltinType($type)) {
@ -174,12 +195,37 @@ final class Helpers
settype($value, $type);
return $value;
};
} elseif (method_exists($type, '__construct')) {
return static fn($value) => is_array($value) || $value instanceof \stdClass
? new $type(...(array) $value)
: new $type($value);
} else {
return static fn($value) => Nette\Utils\Arrays::toObject((array) $value, new $type);
} elseif (is_subclass_of($type, \BackedEnum::class)) {
return static function ($value, Context $context) use ($type) {
try {
return $type::from($value);
} catch (\TypeError | \ValueError) {
$context->addError(
'The %label% %path% expects to be %expected%, %value% given.',
Message::TypeMismatch,
['value' => $value, 'expected' => implode('|', array_map(fn(\BackedEnum $case) => self::formatValue($case->value), $type::cases()))],
);
return null;
}
};
} elseif (is_subclass_of($type, \UnitEnum::class)) {
throw new Nette\InvalidStateException("Cannot cast value to pure enum $type.");
}
$factory = method_exists($type, '__construct')
? static fn($value) => is_array($value) || $value instanceof \stdClass
? new $type(...(array) $value)
: new $type($value)
: static fn($value) => Nette\Utils\Arrays::toObject((array) $value, new $type);
return static function ($value) use ($factory, $type) {
try {
return $factory($value);
} catch (\Error $e) {
throw new Nette\InvalidStateException("Unable to cast value to $type: " . $e->getMessage(), 0, $e);
}
};
}
}