1414namespace League \Uri ;
1515
1616use BackedEnum ;
17- use DateTimeInterface ;
18- use League \Uri \Contracts \FragmentDirective ;
1917use League \Uri \Contracts \UriComponentInterface ;
20- use League \Uri \Contracts \UriInterface ;
2118use Stringable ;
2219use TypeError ;
2320use Uri \Rfc3986 \Uri as Rfc3986Uri ;
2421use Uri \WhatWg \Url as WhatWgUrl ;
22+ use ValueError ;
2523
2624use function array_is_list ;
2725use function array_map ;
@@ -70,7 +68,6 @@ enum StringCoercionMode
7068 * - Backed Enum: converted to their backing value and then stringify see int and string
7169 * - Array as list are flatten into a string list using the "," character as separator
7270 * - Associative array, Unit Enum, any object without stringification semantics is coerced to "[object Object]".
73- * - DateTimeInterface object are stringify following EcmaScript `Date.prototype.toString()` semantics
7471 */
7572 case Ecmascript;
7673
@@ -82,30 +79,33 @@ public function isCoercible(mixed $value): bool
8279 ? !is_resource ($ value )
8380 : match (true ) {
8481 $ value instanceof Rfc3986Uri,
85- $ value instanceof WhatWgUrl,
86- $ value instanceof BackedEnum,
87- $ value instanceof Stringable,
82+ $ value instanceof WhatWgUrl,
83+ $ value instanceof BackedEnum,
84+ $ value instanceof Stringable,
8885 is_scalar ($ value ),
89- null === $ value => true ,
86+ null === $ value => true ,
9087 default => false ,
9188 };
9289 }
9390
91+ /**
92+ * @throws TypeError if the type is not supported by the specific case
93+ * @throws ValueError if circular reference is detected
94+ */
9495 public function coerce (mixed $ value ): ?string
9596 {
96- $ value = match (true ) {
97- $ value instanceof UriComponentInterface,
98- $ value instanceof FragmentDirective => $ value ->value (),
99- $ value instanceof UriInterface,
100- $ value instanceof WhatWgUrl => $ value ->toAsciiString (),
101- $ value instanceof Rfc3986Uri => $ value ->toString (),
102- $ value instanceof BackedEnum => $ value ->value ,
103- $ value instanceof Stringable => (string ) $ value ,
104- default => $ value ,
105- };
106-
107- if (self ::Ecmascript === $ this ) {
108- return match (true ) {
97+ return match ($ this ) {
98+ self ::Ecmascript => match (true ) {
99+ $ value instanceof Rfc3986Uri => $ value ->toString (),
100+ $ value instanceof WhatWgUrl => $ value ->toAsciiString (),
101+ $ value instanceof BackedEnum => (string ) $ value ->value ,
102+ $ value instanceof Stringable => $ value ->__toString (),
103+ is_object ($ value ) => '[object Object] ' ,
104+ is_array ($ value ) => match (true ) {
105+ self ::hasCircularReference ($ value ) => throw new ValueError ('Recursive array structure detected; unable to coerce value. ' ),
106+ array_is_list ($ value ) => implode (', ' , array_map ($ this ->coerce (...), $ value )),
107+ default => '[object Object] ' ,
108+ },
109109 true === $ value => 'true ' ,
110110 false === $ value => 'false ' ,
111111 null === $ value => 'null ' ,
@@ -114,31 +114,29 @@ public function coerce(mixed $value): ?string
114114 is_infinite ($ value ) => 0 < $ value ? 'Infinity ' : '-Infinity ' ,
115115 default => (string ) json_encode ($ value , JSON_PRESERVE_ZERO_FRACTION ),
116116 },
117- is_object ($ value ) => '[object Object] ' ,
118- is_array ($ value ) => match (true ) {
119- self ::isRecursive ($ value ) => throw new TypeError ('Recursive array structure detected; unable to coerce value. ' ),
120- array_is_list ($ value ) => implode (', ' , array_map ($ this ->coerce (...), $ value )),
121- default => '[object Object] ' ,
122- },
123- !is_scalar ($ value ) => throw new TypeError ('Unable to coerce value of type " ' .get_debug_type ($ value ).'" ' ),
124- default => (string ) $ value ,
125- };
126- }
127-
128- return match (true ) {
129- false === $ value => '0 ' ,
130- true === $ value => '1 ' ,
131- null === $ value => null ,
132- !is_scalar ($ value ) => throw new TypeError ('Unable to coerce value of type " ' .get_debug_type ($ value ).'" ' ),
133- default => (string ) $ value ,
117+ is_scalar ($ value ) => (string ) $ value ,
118+ default => throw new TypeError ('Unable to coerce value of type " ' .get_debug_type ($ value ).'" with " ' .$ this ->name .'" coercion. ' ),
119+ },
120+ self ::Native => match (true ) {
121+ $ value instanceof UriComponentInterface => $ value ->value (),
122+ $ value instanceof WhatWgUrl => $ value ->toAsciiString (),
123+ $ value instanceof Rfc3986Uri => $ value ->toString (),
124+ $ value instanceof BackedEnum => (string ) $ value ->value ,
125+ $ value instanceof Stringable => $ value ->__toString (),
126+ false === $ value => '0 ' ,
127+ true === $ value => '1 ' ,
128+ null === $ value => null ,
129+ is_scalar ($ value ) => (string ) $ value ,
130+ default => throw new TypeError ('Unable to coerce value of type " ' .get_debug_type ($ value ).'" with " ' .$ this ->name .'" coercion. ' ),
131+ },
134132 };
135133 }
136134
137135 /**
138136 * Array recursion detection.
139137 * @see https://stackoverflow.com/questions/9042142/detecting-infinite-array-recursion-in-php
140138 */
141- private static function isRecursive (array &$ arr ): bool
139+ private static function hasCircularReference (array &$ arr ): bool
142140 {
143141 if (isset ($ arr [self ::RECURSION_MARKER ])) {
144142 return true ;
@@ -147,7 +145,7 @@ private static function isRecursive(array &$arr): bool
147145 try {
148146 $ arr [self ::RECURSION_MARKER ] = true ;
149147 foreach ($ arr as $ key => &$ value ) {
150- if (self ::RECURSION_MARKER !== $ key && is_array ($ value ) && self ::isRecursive ($ value )) {
148+ if (self ::RECURSION_MARKER !== $ key && is_array ($ value ) && self ::hasCircularReference ($ value )) {
151149 return true ;
152150 }
153151 }
0 commit comments