@@ -155,7 +155,7 @@ private function doMap(object $source, object|string|null $target, \WeakMap $obj
155155 }
156156
157157 $ value = $ this ->getSourceValue ($ source , $ mappedTarget , $ value , $ objectMap , $ mapping );
158- $ this ->storeValue ($ targetPropertyName , $ mapToProperties , $ ctorArguments , $ value );
158+ $ this ->storeValue ($ targetPropertyName , $ mapToProperties , $ ctorArguments , $ value, $ targetRefl , $ sourcePropertyName );
159159 }
160160
161161 if (!$ mappings && $ targetRefl ->hasProperty ($ propertyName )) {
@@ -165,7 +165,7 @@ private function doMap(object $source, object|string|null $target, \WeakMap $obj
165165 }
166166
167167 $ value = $ this ->getSourceValue ($ source , $ mappedTarget , $ this ->getRawValue ($ source , $ propertyName ), $ objectMap );
168- $ this ->storeValue ($ propertyName , $ mapToProperties , $ ctorArguments , $ value );
168+ $ this ->storeValue ($ propertyName , $ mapToProperties , $ ctorArguments , $ value, $ targetRefl , $ propertyName );
169169 }
170170 }
171171
@@ -230,6 +230,7 @@ private function getSourceValue(object $source, object $target, mixed $value, \W
230230
231231 if (
232232 \is_object ($ value )
233+ && !($ value instanceof \UnitEnum)
233234 && ($ innerMetadata = $ this ->metadataFactory ->create ($ value ))
234235 && ($ mapTo = $ this ->getMapTarget ($ innerMetadata , $ value , $ source , $ target ))
235236 && (\is_string ($ mapTo ->target ) && class_exists ($ mapTo ->target ))
@@ -265,8 +266,13 @@ private function getSourceValue(object $source, object $target, mixed $value, \W
265266 * @param array<string, mixed> $mapToProperties
266267 * @param array<string, mixed> $ctorArguments
267268 */
268- private function storeValue (string $ propertyName , array &$ mapToProperties , array &$ ctorArguments , mixed $ value ): void
269+ private function storeValue (string $ propertyName , array &$ mapToProperties , array &$ ctorArguments , mixed $ value, \ ReflectionClass $ targetRefl , ? string $ sourcePropertyName = null ): void
269270 {
271+ if ($ this ->needsEnumConversion ($ value , $ targetRefl , $ propertyName )) {
272+ $ property = $ targetRefl ->getProperty ($ propertyName );
273+ $ value = $ this ->convertEnumValue ($ value , $ property );
274+ }
275+
270276 if (\array_key_exists ($ propertyName , $ ctorArguments )) {
271277 $ ctorArguments [$ propertyName ] = $ value ;
272278
@@ -385,4 +391,108 @@ public function withObjectMapper(ObjectMapperInterface $objectMapper): static
385391
386392 return $ clone ;
387393 }
394+
395+ private function needsEnumConversion (mixed $ value , \ReflectionClass $ targetRefl , string $ propertyName ): bool
396+ {
397+ if ($ value instanceof \UnitEnum && !$ value instanceof \BackedEnum) {
398+ $ property = $ targetRefl ->getProperty ($ propertyName );
399+ $ targetType = $ property ->getType ();
400+
401+ if ($ targetType instanceof \ReflectionNamedType && \in_array ($ targetType ->getName (), ['string ' , 'int ' ], true )) {
402+ throw new MappingTransformException (\sprintf ('Cannot map pure enum "%s" to scalar type "%s" on property "%s". Only BackedEnum can be converted to scalar values. ' , $ value ::class, $ targetType ->getName (), $ propertyName ));
403+ }
404+
405+ return false ;
406+ }
407+
408+ if (!($ value instanceof \BackedEnum || \is_int ($ value ) || \is_string ($ value ))) {
409+ return false ;
410+ }
411+
412+ $ property = $ targetRefl ->getProperty ($ propertyName );
413+ $ targetType = $ property ->getType ();
414+
415+ if (!$ targetType instanceof \ReflectionNamedType) {
416+ return false ;
417+ }
418+
419+ $ targetTypeName = $ targetType ->getName ();
420+
421+ if (enum_exists ($ targetTypeName )) {
422+ return true ;
423+ }
424+
425+ if ($ value instanceof \BackedEnum && \in_array ($ targetTypeName , ['string ' , 'int ' ], true )) {
426+ return true ;
427+ }
428+
429+ return false ;
430+ }
431+
432+ /**
433+ * @return int|string|\BackedEnum the target-expected value
434+ */
435+ private function convertEnumValue (mixed $ sourceValue , \ReflectionProperty $ targetProperty ): int |string |\BackedEnum
436+ {
437+ $ targetType = $ targetProperty ->getType ();
438+ $ targetEnumTypeName = $ targetType instanceof \ReflectionNamedType ? $ targetType ->getName () : (string ) $ targetType ;
439+
440+ if ($ sourceValue instanceof \BackedEnum && \in_array ($ targetEnumTypeName , ['string ' , 'int ' ], true )) {
441+ return $ this ->convertFromBackedEnum ($ sourceValue , $ targetEnumTypeName );
442+ }
443+
444+ if (is_a ($ targetEnumTypeName , \BackedEnum::class, true ) && !\is_object ($ sourceValue )) {
445+ return $ this ->convertToBackedEnum ($ sourceValue , $ targetEnumTypeName );
446+ }
447+
448+ if (is_a ($ targetEnumTypeName , \UnitEnum::class, true ) && !is_a ($ targetEnumTypeName , \BackedEnum::class, true ) && !\is_object ($ sourceValue )) {
449+ throw new MappingTransformException (\sprintf ('Cannot map "%s" to "%s" on property "%s". Pure enums cannot be mapped from scalar values. ' , get_debug_type ($ sourceValue ), $ targetEnumTypeName , $ targetProperty ->getName ()));
450+ }
451+
452+ return $ sourceValue ;
453+ }
454+
455+ /**
456+ * @template T of \BackedEnum
457+ *
458+ * @param class-string<T> $targetEnumClass
459+ *
460+ * @return T
461+ */
462+ private function convertToBackedEnum (mixed $ value , string $ targetEnumClass ): \BackedEnum
463+ {
464+ if ($ value instanceof $ targetEnumClass ) {
465+ return $ value ;
466+ }
467+
468+ $ targetBackingType = (new \ReflectionEnum ($ targetEnumClass ))->getBackingType ();
469+
470+ /** @var \ReflectionNamedType $targetBackingType */
471+ $ expectedType = $ targetBackingType ->getName ();
472+ $ actualType = get_debug_type ($ value );
473+
474+ if ($ expectedType !== $ actualType ) {
475+ throw new MappingTransformException (\sprintf ('Cannot convert value of type "%s" to "%s"-backed enum "%s". ' , $ actualType , $ expectedType , $ targetEnumClass ));
476+ }
477+
478+ try {
479+ return $ targetEnumClass ::from ($ value );
480+ } catch (\ValueError $ e ) {
481+ throw new MappingTransformException (\sprintf ('Invalid value "%s" for enum "%s": "%s" ' , $ value , $ targetEnumClass , $ e ->getMessage ()), 0 , $ e );
482+ }
483+ }
484+
485+ /**
486+ * @param 'int'|'string' $targetType
487+ */
488+ private function convertFromBackedEnum (\BackedEnum $ enum , string $ targetType ): int |string
489+ {
490+ $ backingType = get_debug_type ($ enum ->value );
491+
492+ if ($ backingType !== $ targetType ) {
493+ throw new MappingTransformException (\sprintf ('Cannot convert "%s" backed enum "%s" to "%s". ' , $ backingType , $ enum ::class, $ targetType ));
494+ }
495+
496+ return $ enum ->value ;
497+ }
388498}
0 commit comments