Skip to content

Commit 1152372

Browse files
committed
added Cast
1 parent ae7e5dc commit 1152372

File tree

5 files changed

+377
-3
lines changed

5 files changed

+377
-3
lines changed

src/Utils/Cast.php

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
<?php
2+
3+
/**
4+
* This file is part of the Nette Framework (https://nette.org)
5+
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace Nette\Utils;
11+
12+
use Nette;
13+
use TypeError;
14+
15+
16+
/**
17+
* Converts variables in a similar way to implicit casting in PHP in strict types mode.
18+
*/
19+
final class Cast
20+
{
21+
use Nette\StaticClass;
22+
23+
/**
24+
* Converts a value to a specified type. Supported types: bool, int, float, string, array.
25+
* @throws TypeError if the value cannot be converted
26+
*/
27+
public static function to(mixed $value, string $type): mixed
28+
{
29+
return match ($type) {
30+
'bool' => self::toBool($value),
31+
'int' => self::toInt($value),
32+
'float' => self::toFloat($value),
33+
'string' => self::toString($value),
34+
'array' => self::toArray($value),
35+
default => throw new TypeError("Unsupported type '$type'."),
36+
};
37+
}
38+
39+
40+
/**
41+
* Converts a value to a specified type or returns null if the value is null.
42+
* Supported types: bool, int, float, string, array.
43+
* @throws TypeError if the value cannot be converted
44+
*/
45+
public static function toOrNull(mixed $value, string $type): mixed
46+
{
47+
return $value === null ? null : self::to($value, $type);
48+
}
49+
50+
51+
/**
52+
* Converts a value to a boolean.
53+
* @throws TypeError if the value cannot be converted
54+
*/
55+
public static function toBool(mixed $value): bool
56+
{
57+
return match (true) {
58+
is_bool($value) => $value,
59+
is_int($value) => $value !== 0,
60+
is_float($value) => $value !== 0.0,
61+
is_string($value) => $value !== '' && $value !== '0',
62+
$value === null => false,
63+
default => throw new TypeError('Cannot cast ' . get_debug_type($value) . ' to bool.'),
64+
};
65+
}
66+
67+
68+
/**
69+
* Converts a value to an integer.
70+
* @throws TypeError if the value cannot be converted
71+
*/
72+
public static function toInt(mixed $value): int
73+
{
74+
return match (true) {
75+
is_bool($value) => (int) $value,
76+
is_int($value) => $value,
77+
is_float($value) => $value === (float) ($tmp = (int) $value)
78+
? $tmp
79+
: throw new TypeError('Cannot cast ' . self::toString($value) . ' to int.'),
80+
is_string($value) => preg_match('~^-?\d+(\.0*)?$~D', $value)
81+
? (int) $value
82+
: throw new TypeError("Cannot cast '$value' to int."),
83+
$value === null => 0,
84+
default => throw new TypeError('Cannot cast ' . get_debug_type($value) . ' to int.'),
85+
};
86+
}
87+
88+
89+
/**
90+
* Converts a value to a float.
91+
* @throws TypeError if the value cannot be converted
92+
*/
93+
public static function toFloat(mixed $value): float
94+
{
95+
return match (true) {
96+
is_bool($value) => $value ? 1.0 : 0.0,
97+
is_int($value) => (float) $value,
98+
is_float($value) => $value,
99+
is_string($value) => preg_match('~^-?\d+(\.\d*)?$~D', $value)
100+
? (float) $value
101+
: throw new TypeError("Cannot cast '$value' to float."),
102+
$value === null => 0.0,
103+
default => throw new TypeError('Cannot cast ' . get_debug_type($value) . ' to float.'),
104+
};
105+
}
106+
107+
108+
/**
109+
* Converts a value to a string.
110+
* @throws TypeError if the value cannot be converted
111+
*/
112+
public static function toString(mixed $value): string
113+
{
114+
return match (true) {
115+
is_bool($value) => $value ? '1' : '0',
116+
is_int($value) => (string) $value,
117+
is_float($value) => str_contains($tmp = (string) $value, '.') ? $tmp : $tmp . '.0',
118+
is_string($value) => $value,
119+
$value === null => '',
120+
default => throw new TypeError('Cannot cast ' . get_debug_type($value) . ' to string.'),
121+
};
122+
}
123+
124+
125+
/**
126+
* Ensures the value is an array. Wraps the value in an array if it is not already one.
127+
*/
128+
public static function toArray(mixed $value): array
129+
{
130+
return match (true) {
131+
is_array($value) => $value,
132+
$value === null => [],
133+
default => [$value],
134+
};
135+
}
136+
137+
138+
/**
139+
* Converts a value to a boolean or returns null if the value is null.
140+
* @throws TypeError if the value cannot be converted
141+
*/
142+
public static function toBoolOrNull(mixed $value): ?bool
143+
{
144+
return $value === null ? null : self::toBool($value);
145+
}
146+
147+
148+
/**
149+
* Converts a value to an integer or returns null if the value is null.
150+
* @throws TypeError if the value cannot be converted
151+
*/
152+
public static function toIntOrNull(mixed $value): ?int
153+
{
154+
return $value === null ? null : self::toInt($value);
155+
}
156+
157+
158+
/**
159+
* Converts a value to a float or returns null if the value is null.
160+
* @throws TypeError if the value cannot be converted
161+
*/
162+
public static function toFloatOrNull(mixed $value): ?float
163+
{
164+
return $value === null ? null : self::toFloat($value);
165+
}
166+
167+
168+
/**
169+
* Converts a value to a string or returns null if the value is null.
170+
* @throws TypeError if the value cannot be converted
171+
*/
172+
public static function toStringOrNull(mixed $value): ?string
173+
{
174+
return $value === null ? null : self::toString($value);
175+
}
176+
177+
178+
/**
179+
* Converts a value to an array or returns null if the value is null.
180+
*/
181+
public static function toArrayOrNull(mixed $value): ?array
182+
{
183+
return $value === null ? null : self::toArray($value);
184+
}
185+
186+
187+
/**
188+
* Converts false to null, does not change other values.
189+
*/
190+
public static function falseToNull(mixed $value): mixed
191+
{
192+
return $value === false ? null : $value;
193+
}
194+
}

src/Utils/Helpers.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,7 @@ public static function getLastError(): string
4343
}
4444

4545

46-
/**
47-
* Converts false to null, does not change other values.
48-
*/
46+
/** @deprecated use Cast::falseToNull() */
4947
public static function falseToNull(mixed $value): mixed
5048
{
5149
return $value === false ? null : $value;

tests/Utils/Cast.falseToNull().phpt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Nette\Utils\Cast;
6+
use Tester\Assert;
7+
8+
9+
require __DIR__ . '/../bootstrap.php';
10+
11+
12+
Assert::same(1, Cast::falseToNull(1));
13+
Assert::same(0, Cast::falseToNull(0));
14+
Assert::same(null, Cast::falseToNull(null));
15+
Assert::same(true, Cast::falseToNull(true));
16+
Assert::same(null, Cast::falseToNull(false));
17+
Assert::same([], Cast::falseToNull([]));

tests/Utils/Cast.methods.phpt

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Nette\Utils\Cast;
6+
use Tester\Assert;
7+
8+
require __DIR__ . '/../bootstrap.php';
9+
10+
11+
// bool
12+
Assert::false(Cast::toBool(null));
13+
Assert::true(Cast::toBool(true));
14+
Assert::true(Cast::toBool(1));
15+
Assert::true(Cast::toBool(2));
16+
Assert::true(Cast::toBool(0.1));
17+
Assert::true(Cast::toBool('1'));
18+
Assert::true(Cast::toBool('0.0'));
19+
Assert::false(Cast::toBool(false));
20+
Assert::false(Cast::toBool(0));
21+
Assert::false(Cast::toBool(0.0));
22+
Assert::false(Cast::toBool(''));
23+
Assert::false(Cast::toBool('0'));
24+
Assert::exception(
25+
fn() => Cast::toBool([]),
26+
TypeError::class,
27+
'Cannot cast array to bool.',
28+
);
29+
30+
31+
// int
32+
Assert::same(0, Cast::toInt(null));
33+
Assert::same(0, Cast::toInt(false));
34+
Assert::same(1, Cast::toInt(true));
35+
Assert::same(0, Cast::toInt(0));
36+
Assert::same(1, Cast::toInt(1));
37+
Assert::exception(
38+
fn() => Cast::toInt(PHP_INT_MAX + 1),
39+
TypeError::class,
40+
'Cannot cast 9.2233720368548E+18 to int.',
41+
);
42+
Assert::same(0, Cast::toInt(0.0));
43+
Assert::same(1, Cast::toInt(1.0));
44+
Assert::exception(
45+
fn() => Cast::toInt(0.1),
46+
TypeError::class,
47+
'Cannot cast 0.1 to int.',
48+
);
49+
Assert::exception(
50+
fn() => Cast::toInt(''),
51+
TypeError::class,
52+
"Cannot cast '' to int.",
53+
);
54+
Assert::same(0, Cast::toInt('0'));
55+
Assert::same(1, Cast::toInt('1'));
56+
Assert::same(-1, Cast::toInt('-1.'));
57+
Assert::same(1, Cast::toInt('1.0000'));
58+
Assert::exception(
59+
fn() => Cast::toInt('0.1'),
60+
TypeError::class,
61+
"Cannot cast '0.1' to int.",
62+
);
63+
Assert::exception(
64+
fn() => Cast::toInt([]),
65+
TypeError::class,
66+
'Cannot cast array to int.',
67+
);
68+
69+
70+
// float
71+
Assert::same(0.0, Cast::toFloat(null));
72+
Assert::same(0.0, Cast::toFloat(false));
73+
Assert::same(1.0, Cast::toFloat(true));
74+
Assert::same(0.0, Cast::toFloat(0));
75+
Assert::same(1.0, Cast::toFloat(1));
76+
Assert::same(0.0, Cast::toFloat(0.0));
77+
Assert::same(1.0, Cast::toFloat(1.0));
78+
Assert::same(0.1, Cast::toFloat(0.1));
79+
Assert::exception(
80+
fn() => Cast::toFloat(''),
81+
TypeError::class,
82+
"Cannot cast '' to float.",
83+
);
84+
Assert::same(0.0, Cast::toFloat('0'));
85+
Assert::same(1.0, Cast::toFloat('1'));
86+
Assert::same(-1.0, Cast::toFloat('-1.'));
87+
Assert::same(1.0, Cast::toFloat('1.0'));
88+
Assert::same(0.1, Cast::toFloat('0.1'));
89+
Assert::exception(
90+
fn() => Cast::toFloat([]),
91+
TypeError::class,
92+
'Cannot cast array to float.',
93+
);
94+
95+
96+
// string
97+
Assert::same('', Cast::toString(null));
98+
Assert::same('0', Cast::toString(false)); // differs from PHP strict casting
99+
Assert::same('1', Cast::toString(true));
100+
Assert::same('0', Cast::toString(0));
101+
Assert::same('1', Cast::toString(1));
102+
Assert::same('0.0', Cast::toString(0.0)); // differs from PHP strict casting
103+
Assert::same('1.0', Cast::toString(1.0)); // differs from PHP strict casting
104+
Assert::same('-0.1', Cast::toString(-0.1));
105+
Assert::same('9.2233720368548E+18', Cast::toString(PHP_INT_MAX + 1));
106+
Assert::same('', Cast::toString(''));
107+
Assert::same('x', Cast::toString('x'));
108+
Assert::exception(
109+
fn() => Cast::toString([]),
110+
TypeError::class,
111+
'Cannot cast array to string.',
112+
);
113+
114+
115+
// array
116+
Assert::same([], Cast::toArray(null));
117+
Assert::same([false], Cast::toArray(false));
118+
Assert::same([true], Cast::toArray(true));
119+
Assert::same([0], Cast::toArray(0));
120+
Assert::same([0.0], Cast::toArray(0.0));
121+
Assert::same([1], Cast::toArray([1]));
122+
Assert::equal([new stdClass], Cast::toArray(new stdClass)); // differs from PHP strict casting
123+
124+
125+
// OrNull
126+
Assert::true(Cast::toBoolOrNull(true));
127+
Assert::null(Cast::toBoolOrNull(null));
128+
Assert::same(0, Cast::toIntOrNull(0));
129+
Assert::null(Cast::toIntOrNull(null));
130+
Assert::same(0.0, Cast::toFloatOrNull(0));
131+
Assert::null(Cast::toFloatOrNull(null));
132+
Assert::same('0', Cast::toStringOrNull(0));
133+
Assert::null(Cast::toStringOrNull(null));
134+
Assert::same([], Cast::toArrayOrNull([]));
135+
Assert::null(Cast::toArrayOrNull(null));

tests/Utils/Cast.to.phpt

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Nette\Utils\Cast;
6+
use Tester\Assert;
7+
8+
require __DIR__ . '/../bootstrap.php';
9+
10+
11+
// to
12+
Assert::same(false, Cast::to(null, 'bool'));
13+
Assert::same(0, Cast::to(null, 'int'));
14+
Assert::same(0.0, Cast::to(null, 'float'));
15+
Assert::same('', Cast::to(null, 'string'));
16+
Assert::same([], Cast::to(null, 'array'));
17+
Assert::exception(
18+
fn() => Cast::to(null, 'unknown'),
19+
TypeError::class,
20+
"Unsupported type 'unknown'.",
21+
);
22+
23+
24+
// toOrNull
25+
Assert::null(Cast::toOrNull(null, 'bool'));
26+
Assert::null(Cast::toOrNull(null, 'int'));
27+
Assert::null(Cast::toOrNull(null, 'float'));
28+
Assert::null(Cast::toOrNull(null, 'string'));
29+
Assert::null(Cast::toOrNull(null, 'array'));
30+
Assert::null(Cast::toOrNull(null, 'unknown')); // implementation imperfection

0 commit comments

Comments
 (0)