@@ -119,8 +119,8 @@ private function addMethodBindings(
119119 $ methodName = $ reflectionMethod ->getName ();
120120 foreach ($ this ->getBindAttributes ($ reflectionMethod ) as $ reflectionAttribute ) {
121121 $ bindKey = $ this ->getBindKey ($ reflectionAttribute , $ reflectionMethod );
122- $ attributeCache [$ bindKey ] = fn (object $ object): null | iterable | string
123- => $ this ->nullableStringOrIterable ($ object , $ methodName );
122+ $ attributeCache [$ bindKey ] = fn (object $ object, bool $ preserveTypes = false ): mixed
123+ => $ this ->nullableStringOrIterable ($ object , $ methodName, $ preserveTypes );
124124 $ this ->cacheNestedClassName (
125125 $ objectKeys ,
126126 $ bindKey ,
@@ -144,8 +144,8 @@ private function addPropertyBindings(
144144 if (!empty ($ attributeList )) {
145145 foreach ($ attributeList as $ reflectionAttribute ) {
146146 $ bindKey = $ this ->getBindKey ($ reflectionAttribute );
147- $ attributeCache [$ bindKey ] = fn (object $ object): null | iterable | string
148- => $ this ->nullableStringOrIterable ($ object , $ propertyName );
147+ $ attributeCache [$ bindKey ] = fn (object $ object, bool $ preserveTypes = false ): mixed
148+ => $ this ->nullableStringOrIterable ($ object , $ propertyName, $ preserveTypes );
149149 $ this ->cacheNestedClassName (
150150 $ objectKeys ,
151151 $ bindKey ,
@@ -160,9 +160,9 @@ private function addPropertyBindings(
160160 return ;
161161 }
162162
163- $ attributeCache [$ propertyName ] = fn (object $ object): null | iterable | string
163+ $ attributeCache [$ propertyName ] = fn (object $ object, bool $ preserveTypes = false ): mixed
164164 => isset ($ object ->$ propertyName )
165- ? $ this ->nullableStringOrIterable ($ object , $ propertyName )
165+ ? $ this ->nullableStringOrIterable ($ object , $ propertyName, $ preserveTypes )
166166 : null ;
167167 $ this ->cacheNestedClassName (
168168 $ objectKeys ,
@@ -249,39 +249,47 @@ private function appendNestedBindableKeys(
249249 }
250250
251251 /**
252- * @param object|array<string, string> $object
253- * @return array<string, string>
252+ * @param object|array<string, mixed> $object
253+ * @return array<string, mixed>
254+ * @param bool $preserveTypes Retain original values alongside ordinary object binding values.
254255 */
255- public function convertToKvp (object |array $ object ):array {
256+ public function convertToKvp (object |array $ object, bool $ preserveTypes = false ):array {
256257 if (is_array ($ object )) {
257258 return $ object ;
258259 }
259260
260261 if ($ object instanceof stdClass) {
261- return $ this ->stringifyMap (get_object_vars ($ object ));
262+ $ values = get_object_vars ($ object );
263+ return $ preserveTypes
264+ ? array_map (fn (mixed $ value ):mixed => $ this ->preserveValue ($ value ), $ values )
265+ : $ this ->stringifyMap ($ values );
262266 }
263267
264268 if (!$ this ->isBindable ($ object )) {
265269 return [];
266270 }
267271
268- return $ this ->convertBindableObjectToKvp ($ object );
272+ return $ this ->convertBindableObjectToKvp ($ object, $ preserveTypes );
269273 }
270274
271- /** @return array<string, null|string|array<int|string, mixed> > */
272- private function convertBindableObjectToKvp (object $ object ):array {
275+ /** @return array<string, mixed> */
276+ private function convertBindableObjectToKvp (object $ object, bool $ preserveTypes ):array {
273277 $ kvp = [];
274278 foreach ($ this ->bindableClassMap [$ object ::class] as $ key => $ valueGetter ) {
275279 $ targetObject = $ this ->resolveTargetObject ($ object , $ key );
276280 $ value = $ targetObject
277- ? $ valueGetter ($ targetObject )
281+ ? $ valueGetter ($ targetObject, $ preserveTypes )
278282 : null ;
279- $ kvp [$ key ] = $ this ->normalizeValue ($ value );
283+ $ kvp [$ key ] = $ preserveTypes ? $ this -> preserveValue ( $ value ) : $ this ->normalizeValue ($ value );
280284 }
281285
282286 return $ kvp ;
283287 }
284288
289+ private function preserveValue (mixed $ value ):mixed {
290+ return is_iterable ($ value ) ? $ value : new BindableValue ($ value );
291+ }
292+
285293 private function resolveTargetObject (object $ object , string $ key ):?object {
286294 $ targetObject = $ object ;
287295 $ segments = explode (". " , $ key );
@@ -392,11 +400,11 @@ private function isAttribute(
392400 return strcasecmp ($ reflectionAttribute ->getName (), $ className ) === 0 ;
393401 }
394402
395- /** @return null|string|array<int|string, mixed> */
396403 private function nullableStringOrIterable (
397404 object $ object ,
398405 string $ keyOrMethod ,
399- ):null |iterable |string {
406+ bool $ preserveTypes = false ,
407+ ):mixed {
400408 if (method_exists ($ object , $ keyOrMethod )) {
401409 $ value = $ object ->$ keyOrMethod ();
402410 }
@@ -407,7 +415,7 @@ private function nullableStringOrIterable(
407415 return null ;
408416 }
409417
410- if (is_scalar ($ value )) {
418+ if ($ preserveTypes || is_scalar ($ value )) {
411419 return $ value ;
412420 }
413421 elseif (is_iterable ($ value )) {
0 commit comments