Skip to content

Commit b7fd140

Browse files
committed
Add precision to string type coercer
1 parent 1ed4504 commit b7fd140

File tree

1 file changed

+66
-5
lines changed

1 file changed

+66
-5
lines changed

src/Type/Coercer/StringTypeCoercer.php

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@
1212
*/
1313
class StringTypeCoercer implements TypeCoercerInterface
1414
{
15+
/**
16+
* @var int<1, 53>
17+
*/
18+
private const DEFAULT_FLOAT_PRECISION = 14;
19+
1520
/** @var string */
1621
public const NULL_TO_STRING = '';
1722
/** @var string */
@@ -23,6 +28,44 @@ class StringTypeCoercer implements TypeCoercerInterface
2328
/** @var string */
2429
public const INF_TO_STRING = 'inf';
2530

31+
/**
32+
* @var non-empty-string
33+
*/
34+
private string $floatTemplate;
35+
36+
/**
37+
* @param int<1, 53>|null $floatPrecision
38+
*/
39+
public function __construct(?int $floatPrecision = null)
40+
{
41+
$this->floatTemplate = $this->createFloatTemplate(
42+
precision: $floatPrecision ?? $this->getDefaultFloatPrecision(),
43+
);
44+
}
45+
46+
/**
47+
* @param int<1, 53> $precision
48+
* @return non-empty-string
49+
*/
50+
private function createFloatTemplate(int $precision): string
51+
{
52+
return \sprintf('%%01.%df', $precision);
53+
}
54+
55+
/**
56+
* @return int<1, 53>
57+
*/
58+
private function getDefaultFloatPrecision(): int
59+
{
60+
$result = \ini_get('precision');
61+
62+
if (\is_numeric($result) && IntTypeCoercer::isSafeFloat((float) $result)) {
63+
return (int) $result;
64+
}
65+
66+
return self::DEFAULT_FLOAT_PRECISION;
67+
}
68+
2669
/**
2770
* @throws InvalidValueException
2871
*/
@@ -45,23 +88,41 @@ public function coerce(mixed $value, Context $context): string
4588
$value === \INF => static::INF_TO_STRING,
4689
$value === -\INF => '-' . static::INF_TO_STRING,
4790
// Other floating point values
48-
default => \str_ends_with(
49-
haystack: $formatted = \rtrim(\sprintf('%f', $value), '0'),
50-
needle: '.',
51-
) ? $formatted . '0' : $formatted,
91+
default => $this->floatToString($value, $context),
5292
},
5393
// Int
5494
\is_int($value),
5595
// Stringable
5696
$value instanceof \Stringable => (string) $value,
57-
\is_resource($value) => \get_resource_type($value),
5897
// Enum
5998
$value instanceof \BackedEnum => (string) $value->value,
6099
$value instanceof \UnitEnum => $value->name,
100+
// Resource
101+
\is_resource($value) => \get_resource_type($value),
102+
\get_debug_type($value) === 'resource (closed)' => 'resource',
61103
default => throw InvalidValueException::createFromContext(
62104
value: $value,
63105
context: $context,
64106
),
65107
};
66108
}
109+
110+
private function floatToString(float $value, Context $context): string
111+
{
112+
$formatted = \sprintf($this->floatTemplate, $value);
113+
$formatted = \rtrim($formatted, '0');
114+
115+
if (\str_ends_with($formatted, '.')) {
116+
$formatted .= '0';
117+
}
118+
119+
if ((float) $formatted === $value) {
120+
return $formatted;
121+
}
122+
123+
throw InvalidValueException::createFromContext(
124+
value: $value,
125+
context: $context,
126+
);
127+
}
67128
}

0 commit comments

Comments
 (0)