1212 */
1313class 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