-
-
Notifications
You must be signed in to change notification settings - Fork 149
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
377 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
<?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; | ||
use TypeError; | ||
|
||
|
||
/** | ||
* Converts variables in a similar way to implicit casting in PHP in strict types mode. | ||
*/ | ||
final class Cast | ||
{ | ||
use Nette\StaticClass; | ||
|
||
/** | ||
* Converts a value to a specified type. Supported types: bool, int, float, string, array. | ||
* @throws TypeError if the value cannot be converted | ||
*/ | ||
public static function to(mixed $value, string $type): mixed | ||
{ | ||
return match ($type) { | ||
'bool' => self::toBool($value), | ||
'int' => self::toInt($value), | ||
'float' => self::toFloat($value), | ||
'string' => self::toString($value), | ||
'array' => self::toArray($value), | ||
default => throw new TypeError("Unsupported type '$type'."), | ||
}; | ||
} | ||
|
||
|
||
/** | ||
* Converts a value to a specified type or returns null if the value is null. | ||
* Supported types: bool, int, float, string, array. | ||
* @throws TypeError if the value cannot be converted | ||
*/ | ||
public static function toOrNull(mixed $value, string $type): mixed | ||
{ | ||
return $value === null ? null : self::to($value, $type); | ||
} | ||
|
||
|
||
/** | ||
* Converts a value to a boolean. | ||
* @throws TypeError if the value cannot be converted | ||
*/ | ||
public static function toBool(mixed $value): bool | ||
{ | ||
return match (true) { | ||
is_bool($value) => $value, | ||
is_int($value) => $value !== 0, | ||
is_float($value) => $value !== 0.0, | ||
is_string($value) => $value !== '' && $value !== '0', | ||
$value === null => false, | ||
default => throw new TypeError('Cannot cast ' . get_debug_type($value) . ' to bool.'), | ||
}; | ||
} | ||
|
||
|
||
/** | ||
* Converts a value to an integer. | ||
* @throws TypeError if the value cannot be converted | ||
*/ | ||
public static function toInt(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::toString($value) . ' to int.'), | ||
is_string($value) => preg_match('~^-?\d+(\.0*)?$~D', $value) | ||
? (int) $value | ||
: throw new TypeError("Cannot cast '$value' to int."), | ||
$value === null => 0, | ||
default => throw new TypeError('Cannot cast ' . get_debug_type($value) . ' to int.'), | ||
}; | ||
} | ||
|
||
|
||
/** | ||
* Converts a value to a float. | ||
* @throws TypeError if the value cannot be converted | ||
*/ | ||
public static function toFloat(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."), | ||
$value === null => 0.0, | ||
default => throw new TypeError('Cannot cast ' . get_debug_type($value) . ' to float.'), | ||
}; | ||
} | ||
|
||
|
||
/** | ||
* Converts a value to a string. | ||
* @throws TypeError if the value cannot be converted | ||
*/ | ||
public static function toString(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, | ||
$value === null => '', | ||
default => throw new TypeError('Cannot cast ' . get_debug_type($value) . ' to string.'), | ||
}; | ||
} | ||
|
||
|
||
/** | ||
* Ensures the value is an array. Wraps the value in an array if it is not already one. | ||
*/ | ||
public static function toArray(mixed $value): array | ||
{ | ||
return match (true) { | ||
is_array($value) => $value, | ||
$value === null => [], | ||
default => [$value], | ||
}; | ||
} | ||
|
||
|
||
/** | ||
* Converts a value to a boolean or returns null if the value is null. | ||
* @throws TypeError if the value cannot be converted | ||
*/ | ||
public static function toBoolOrNull(mixed $value): ?bool | ||
{ | ||
return $value === null ? null : self::toBool($value); | ||
} | ||
|
||
|
||
/** | ||
* Converts a value to an integer or returns null if the value is null. | ||
* @throws TypeError if the value cannot be converted | ||
*/ | ||
public static function toIntOrNull(mixed $value): ?int | ||
{ | ||
return $value === null ? null : self::toInt($value); | ||
} | ||
|
||
|
||
/** | ||
* Converts a value to a float or returns null if the value is null. | ||
* @throws TypeError if the value cannot be converted | ||
*/ | ||
public static function toFloatOrNull(mixed $value): ?float | ||
{ | ||
return $value === null ? null : self::toFloat($value); | ||
} | ||
|
||
|
||
/** | ||
* Converts a value to a string or returns null if the value is null. | ||
* @throws TypeError if the value cannot be converted | ||
*/ | ||
public static function toStringOrNull(mixed $value): ?string | ||
{ | ||
return $value === null ? null : self::toString($value); | ||
} | ||
|
||
|
||
/** | ||
* Converts a value to an array or returns null if the value is null. | ||
*/ | ||
public static function toArrayOrNull(mixed $value): ?array | ||
{ | ||
return $value === null ? null : self::toArray($value); | ||
} | ||
|
||
|
||
/** | ||
* Converts false to null, does not change other values. | ||
*/ | ||
public static function falseToNull(mixed $value): mixed | ||
{ | ||
return $value === false ? null : $value; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
use Nette\Utils\Cast; | ||
use Tester\Assert; | ||
|
||
|
||
require __DIR__ . '/../bootstrap.php'; | ||
|
||
|
||
Assert::same(1, Cast::falseToNull(1)); | ||
Assert::same(0, Cast::falseToNull(0)); | ||
Assert::same(null, Cast::falseToNull(null)); | ||
Assert::same(true, Cast::falseToNull(true)); | ||
Assert::same(null, Cast::falseToNull(false)); | ||
Assert::same([], Cast::falseToNull([])); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
use Nette\Utils\Cast; | ||
use Tester\Assert; | ||
|
||
require __DIR__ . '/../bootstrap.php'; | ||
|
||
|
||
// bool | ||
Assert::false(Cast::toBool(null)); | ||
Assert::true(Cast::toBool(true)); | ||
Assert::true(Cast::toBool(1)); | ||
Assert::true(Cast::toBool(2)); | ||
Assert::true(Cast::toBool(0.1)); | ||
Assert::true(Cast::toBool('1')); | ||
Assert::true(Cast::toBool('0.0')); | ||
Assert::false(Cast::toBool(false)); | ||
Assert::false(Cast::toBool(0)); | ||
Assert::false(Cast::toBool(0.0)); | ||
Assert::false(Cast::toBool('')); | ||
Assert::false(Cast::toBool('0')); | ||
Assert::exception( | ||
fn() => Cast::toBool([]), | ||
TypeError::class, | ||
'Cannot cast array to bool.', | ||
); | ||
|
||
|
||
// int | ||
Assert::same(0, Cast::toInt(null)); | ||
Assert::same(0, Cast::toInt(false)); | ||
Assert::same(1, Cast::toInt(true)); | ||
Assert::same(0, Cast::toInt(0)); | ||
Assert::same(1, Cast::toInt(1)); | ||
Assert::exception( | ||
fn() => Cast::toInt(PHP_INT_MAX + 1), | ||
TypeError::class, | ||
'Cannot cast 9.2233720368548E+18 to int.', | ||
); | ||
Assert::same(0, Cast::toInt(0.0)); | ||
Assert::same(1, Cast::toInt(1.0)); | ||
Assert::exception( | ||
fn() => Cast::toInt(0.1), | ||
TypeError::class, | ||
'Cannot cast 0.1 to int.', | ||
); | ||
Assert::exception( | ||
fn() => Cast::toInt(''), | ||
TypeError::class, | ||
"Cannot cast '' to int.", | ||
); | ||
Assert::same(0, Cast::toInt('0')); | ||
Assert::same(1, Cast::toInt('1')); | ||
Assert::same(-1, Cast::toInt('-1.')); | ||
Assert::same(1, Cast::toInt('1.0000')); | ||
Assert::exception( | ||
fn() => Cast::toInt('0.1'), | ||
TypeError::class, | ||
"Cannot cast '0.1' to int.", | ||
); | ||
Assert::exception( | ||
fn() => Cast::toInt([]), | ||
TypeError::class, | ||
'Cannot cast array to int.', | ||
); | ||
|
||
|
||
// float | ||
Assert::same(0.0, Cast::toFloat(null)); | ||
Assert::same(0.0, Cast::toFloat(false)); | ||
Assert::same(1.0, Cast::toFloat(true)); | ||
Assert::same(0.0, Cast::toFloat(0)); | ||
Assert::same(1.0, Cast::toFloat(1)); | ||
Assert::same(0.0, Cast::toFloat(0.0)); | ||
Assert::same(1.0, Cast::toFloat(1.0)); | ||
Assert::same(0.1, Cast::toFloat(0.1)); | ||
Assert::exception( | ||
fn() => Cast::toFloat(''), | ||
TypeError::class, | ||
"Cannot cast '' to float.", | ||
); | ||
Assert::same(0.0, Cast::toFloat('0')); | ||
Assert::same(1.0, Cast::toFloat('1')); | ||
Assert::same(-1.0, Cast::toFloat('-1.')); | ||
Assert::same(1.0, Cast::toFloat('1.0')); | ||
Assert::same(0.1, Cast::toFloat('0.1')); | ||
Assert::exception( | ||
fn() => Cast::toFloat([]), | ||
TypeError::class, | ||
'Cannot cast array to float.', | ||
); | ||
|
||
|
||
// string | ||
Assert::same('', Cast::toString(null)); | ||
Assert::same('0', Cast::toString(false)); // differs from PHP strict casting | ||
Assert::same('1', Cast::toString(true)); | ||
Assert::same('0', Cast::toString(0)); | ||
Assert::same('1', Cast::toString(1)); | ||
Assert::same('0.0', Cast::toString(0.0)); // differs from PHP strict casting | ||
Assert::same('1.0', Cast::toString(1.0)); // differs from PHP strict casting | ||
Assert::same('-0.1', Cast::toString(-0.1)); | ||
Assert::same('9.2233720368548E+18', Cast::toString(PHP_INT_MAX + 1)); | ||
Assert::same('', Cast::toString('')); | ||
Assert::same('x', Cast::toString('x')); | ||
Assert::exception( | ||
fn() => Cast::toString([]), | ||
TypeError::class, | ||
'Cannot cast array to string.', | ||
); | ||
|
||
|
||
// array | ||
Assert::same([], Cast::toArray(null)); | ||
Assert::same([false], Cast::toArray(false)); | ||
Assert::same([true], Cast::toArray(true)); | ||
Assert::same([0], Cast::toArray(0)); | ||
Assert::same([0.0], Cast::toArray(0.0)); | ||
Assert::same([1], Cast::toArray([1])); | ||
Assert::equal([new stdClass], Cast::toArray(new stdClass)); // differs from PHP strict casting | ||
|
||
|
||
// OrNull | ||
Assert::true(Cast::toBoolOrNull(true)); | ||
Assert::null(Cast::toBoolOrNull(null)); | ||
Assert::same(0, Cast::toIntOrNull(0)); | ||
Assert::null(Cast::toIntOrNull(null)); | ||
Assert::same(0.0, Cast::toFloatOrNull(0)); | ||
Assert::null(Cast::toFloatOrNull(null)); | ||
Assert::same('0', Cast::toStringOrNull(0)); | ||
Assert::null(Cast::toStringOrNull(null)); | ||
Assert::same([], Cast::toArrayOrNull([])); | ||
Assert::null(Cast::toArrayOrNull(null)); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
use Nette\Utils\Cast; | ||
use Tester\Assert; | ||
|
||
require __DIR__ . '/../bootstrap.php'; | ||
|
||
|
||
// to | ||
Assert::same(false, Cast::to(null, 'bool')); | ||
Assert::same(0, Cast::to(null, 'int')); | ||
Assert::same(0.0, Cast::to(null, 'float')); | ||
Assert::same('', Cast::to(null, 'string')); | ||
Assert::same([], Cast::to(null, 'array')); | ||
Assert::exception( | ||
fn() => Cast::to(null, 'unknown'), | ||
TypeError::class, | ||
"Unsupported type 'unknown'.", | ||
); | ||
|
||
|
||
// toOrNull | ||
Assert::null(Cast::toOrNull(null, 'bool')); | ||
Assert::null(Cast::toOrNull(null, 'int')); | ||
Assert::null(Cast::toOrNull(null, 'float')); | ||
Assert::null(Cast::toOrNull(null, 'string')); | ||
Assert::null(Cast::toOrNull(null, 'array')); | ||
Assert::null(Cast::toOrNull(null, 'unknown')); // implementation imperfection |