diff --git a/src/Utils/Cast.php b/src/Utils/Cast.php new file mode 100644 index 000000000..ff21a6ec4 --- /dev/null +++ b/src/Utils/Cast.php @@ -0,0 +1,99 @@ + $value, + is_int($value) => $value !== 0, + is_float($value) => $value !== 0.0, + is_string($value) => $value !== '' && $value !== '0', + default => throw new TypeError('Cannot cast ' . get_debug_type($value) . ' to bool.'), + }; + } + + + public static function int(mixed $value): int + { + return match (true) { + is_bool($value) => (int) $value, + is_int($value) => $value, + is_float($value) => $value === (float) ($tmp = (int) $value) + ? $tmp + : throw new TypeError('Cannot cast ' . self::string($value) . ' to int.'), + is_string($value) => preg_match('~^-?\d+(\.0*)?$~D', $value) + ? (int) $value + : throw new TypeError("Cannot cast '$value' to int."), + default => throw new TypeError('Cannot cast ' . get_debug_type($value) . ' to int.'), + }; + } + + + public static function float(mixed $value): float + { + return match (true) { + is_bool($value) => $value ? 1.0 : 0.0, + is_int($value) => (float) $value, + is_float($value) => $value, + is_string($value) => preg_match('~^-?\d+(\.\d*)?$~D', $value) + ? (float) $value + : throw new TypeError("Cannot cast '$value' to float."), + default => throw new TypeError('Cannot cast ' . get_debug_type($value) . ' to float.'), + }; + } + + + public static function string(mixed $value): string + { + return match (true) { + is_bool($value) => $value ? '1' : '0', + is_int($value) => (string) $value, + is_float($value) => str_contains($tmp = (string) $value, '.') ? $tmp : $tmp . '.0', + is_string($value) => $value, + default => throw new TypeError('Cannot cast ' . get_debug_type($value) . ' to string.'), + }; + } + + + public static function boolOrNull(mixed $value): ?bool + { + return $value === null ? null : self::bool($value); + } + + + public static function intOrNull(mixed $value): ?int + { + return $value === null ? null : self::int($value); + } + + + public static function floatOrNull(mixed $value): ?float + { + return $value === null ? null : self::float($value); + } + + + public static function stringOrNull(mixed $value): ?string + { + return $value === null ? null : self::string($value); + } +} diff --git a/tests/Utils/Cast.phpt b/tests/Utils/Cast.phpt new file mode 100644 index 000000000..fa15ca8f6 --- /dev/null +++ b/tests/Utils/Cast.phpt @@ -0,0 +1,139 @@ + Cast::bool([]), + TypeError::class, + 'Cannot cast array to bool.', +); +Assert::exception( + fn() => Cast::bool(null), + TypeError::class, + 'Cannot cast null to bool.', +); + + +// int +Assert::same(0, Cast::int(false)); +Assert::same(1, Cast::int(true)); +Assert::same(0, Cast::int(0)); +Assert::same(1, Cast::int(1)); +Assert::exception( + fn() => Cast::int(PHP_INT_MAX + 1), + TypeError::class, + 'Cannot cast 9.2233720368548E+18 to int.', +); +Assert::same(0, Cast::int(0.0)); +Assert::same(1, Cast::int(1.0)); +Assert::exception( + fn() => Cast::int(0.1), + TypeError::class, + 'Cannot cast 0.1 to int.', +); +Assert::exception( + fn() => Cast::int(''), + TypeError::class, + "Cannot cast '' to int.", +); +Assert::same(0, Cast::int('0')); +Assert::same(1, Cast::int('1')); +Assert::same(-1, Cast::int('-1.')); +Assert::same(1, Cast::int('1.0000')); +Assert::exception( + fn() => Cast::int('0.1'), + TypeError::class, + "Cannot cast '0.1' to int.", +); +Assert::exception( + fn() => Cast::int([]), + TypeError::class, + 'Cannot cast array to int.', +); +Assert::exception( + fn() => Cast::int(null), + TypeError::class, + 'Cannot cast null to int.', +); + + +// float +Assert::same(0.0, Cast::float(false)); +Assert::same(1.0, Cast::float(true)); +Assert::same(0.0, Cast::float(0)); +Assert::same(1.0, Cast::float(1)); +Assert::same(0.0, Cast::float(0.0)); +Assert::same(1.0, Cast::float(1.0)); +Assert::same(0.1, Cast::float(0.1)); +Assert::exception( + fn() => Cast::float(''), + TypeError::class, + "Cannot cast '' to float.", +); +Assert::same(0.0, Cast::float('0')); +Assert::same(1.0, Cast::float('1')); +Assert::same(-1.0, Cast::float('-1.')); +Assert::same(1.0, Cast::float('1.0')); +Assert::same(0.1, Cast::float('0.1')); +Assert::exception( + fn() => Cast::float([]), + TypeError::class, + 'Cannot cast array to float.', +); +Assert::exception( + fn() => Cast::float(null), + TypeError::class, + 'Cannot cast null to float.', +); + + +// string +Assert::same('0', Cast::string(false)); // differs from PHP strict casting +Assert::same('1', Cast::string(true)); +Assert::same('0', Cast::string(0)); +Assert::same('1', Cast::string(1)); +Assert::same('0.0', Cast::string(0.0)); // differs from PHP strict casting +Assert::same('1.0', Cast::string(1.0)); // differs from PHP strict casting +Assert::same('-0.1', Cast::string(-0.1)); +Assert::same('9.2233720368548E+18', Cast::string(PHP_INT_MAX + 1)); +Assert::same('', Cast::string('')); +Assert::same('x', Cast::string('x')); +Assert::exception( + fn() => Cast::string([]), + TypeError::class, + 'Cannot cast array to string.', +); +Assert::exception( + fn() => Cast::string(null), + TypeError::class, + 'Cannot cast null to string.', +); + + +// OrNull +Assert::true(Cast::boolOrNull(true)); +Assert::null(Cast::boolOrNull(null)); +Assert::same(0, Cast::intOrNull(0)); +Assert::null(Cast::intOrNull(null)); +Assert::same(0.0, Cast::floatOrNull(0)); +Assert::null(Cast::floatOrNull(null)); +Assert::same('0', Cast::stringOrNull(0)); +Assert::null(Cast::stringOrNull(null));