Skip to content

Commit a846fab

Browse files
committed
added Cast
1 parent 736c567 commit a846fab

File tree

8 files changed

+381
-5
lines changed

8 files changed

+381
-5
lines changed

readme.md

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ In package nette/utils you will find a set of useful classes for everyday use:
1414

1515
[Arrays](https://doc.nette.org/utils/arrays)<br>
1616
[Callback](https://doc.nette.org/utils/callback) - PHP callbacks<br>
17+
[Cast](https://doc.nette.org/utils/cast) - Safe lossless type casting<br>
1718
[Filesystem](https://doc.nette.org/utils/filesystem) - copying, renaming, …<br>
1819
[Finder](https://doc.nette.org/utils/finder) - finds files and directories<br>
1920
[Floats](https://doc.nette.org/utils/floats) - floating point numbers<br>

src/Utils/Arrays.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ public static function mergeTree(array $array1, array $array2): array
9898
*/
9999
public static function getKeyOffset(array $array, string|int $key): ?int
100100
{
101-
return Helpers::falseToNull(array_search(self::toKey($key), array_keys($array), strict: true));
101+
return Cast::falseToNull(array_search(self::toKey($key), array_keys($array), strict: true));
102102
}
103103

104104

src/Utils/Cast.php

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

src/Utils/Helpers.php

+1-3
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+
/** use Cast::falseToNull() */
4947
public static function falseToNull(mixed $value): mixed
5048
{
5149
return $value === false ? null : $value;

src/Utils/Strings.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -521,7 +521,7 @@ private static function pos(string $haystack, string $needle, int $nth = 1): ?in
521521
}
522522
}
523523

524-
return Helpers::falseToNull($pos);
524+
return Cast::falseToNull($pos);
525525
}
526526

527527

tests/Utils/Cast.falseToNull().phpt

+17
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

+135
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));

0 commit comments

Comments
 (0)