@@ -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 ))
@@ -267,8 +268,13 @@ private function getSourceValue(object $source, object $target, mixed $value, \W
267268 * @param array<string, mixed> $mapToProperties
268269 * @param array<string, mixed> $ctorArguments
269270 */
270- private function storeValue (string $ propertyName , array &$ mapToProperties , array &$ ctorArguments , mixed $ value ): void
271+ private function storeValue (string $ propertyName , array &$ mapToProperties , array &$ ctorArguments , mixed $ value, \ ReflectionClass $ targetRefl , ? string $ sourcePropertyName = null ): void
271272 {
273+ if ($ this ->needsEnumConversion ($ value , $ targetRefl , $ propertyName )) {
274+ $ property = $ targetRefl ->getProperty ($ propertyName );
275+ $ value = $ this ->convertEnumValue ($ value , $ property );
276+ }
277+
272278 if (\array_key_exists ($ propertyName , $ ctorArguments )) {
273279 $ ctorArguments [$ propertyName ] = $ value ;
274280
@@ -387,4 +393,108 @@ public function withObjectMapper(ObjectMapperInterface $objectMapper): static
387393
388394 return $ clone ;
389395 }
396+
397+ private function needsEnumConversion (mixed $ value , \ReflectionClass $ targetRefl , string $ propertyName ): bool
398+ {
399+ if ($ value instanceof \UnitEnum && !$ value instanceof \BackedEnum) {
400+ $ property = $ targetRefl ->getProperty ($ propertyName );
401+ $ targetType = $ property ->getType ();
402+
403+ if ($ targetType instanceof \ReflectionNamedType && \in_array ($ targetType ->getName (), ['string ' , 'int ' ], true )) {
404+ 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 ));
405+ }
406+
407+ return false ;
408+ }
409+
410+ if (!($ value instanceof \BackedEnum || \is_int ($ value ) || \is_string ($ value ))) {
411+ return false ;
412+ }
413+
414+ $ property = $ targetRefl ->getProperty ($ propertyName );
415+ $ targetType = $ property ->getType ();
416+
417+ if (!$ targetType instanceof \ReflectionNamedType) {
418+ return false ;
419+ }
420+
421+ $ targetTypeName = $ targetType ->getName ();
422+
423+ if (enum_exists ($ targetTypeName )) {
424+ return true ;
425+ }
426+
427+ if ($ value instanceof \BackedEnum && \in_array ($ targetTypeName , ['string ' , 'int ' ], true )) {
428+ return true ;
429+ }
430+
431+ return false ;
432+ }
433+
434+ /**
435+ * @return int|string|\BackedEnum the target-expected value
436+ */
437+ private function convertEnumValue (mixed $ sourceValue , \ReflectionProperty $ targetProperty ): int |string |\BackedEnum
438+ {
439+ $ targetType = $ targetProperty ->getType ();
440+ $ targetEnumTypeName = $ targetType instanceof \ReflectionNamedType ? $ targetType ->getName () : (string ) $ targetType ;
441+
442+ if ($ sourceValue instanceof \BackedEnum && \in_array ($ targetEnumTypeName , ['string ' , 'int ' ], true )) {
443+ return $ this ->convertFromBackedEnum ($ sourceValue , $ targetEnumTypeName );
444+ }
445+
446+ if (is_a ($ targetEnumTypeName , \BackedEnum::class, true ) && !\is_object ($ sourceValue )) {
447+ return $ this ->convertToBackedEnum ($ sourceValue , $ targetEnumTypeName );
448+ }
449+
450+ if (is_a ($ targetEnumTypeName , \UnitEnum::class, true ) && !is_a ($ targetEnumTypeName , \BackedEnum::class, true ) && !\is_object ($ sourceValue )) {
451+ 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 ()));
452+ }
453+
454+ return $ sourceValue ;
455+ }
456+
457+ /**
458+ * @template T of \BackedEnum
459+ *
460+ * @param class-string<T> $targetEnumClass
461+ *
462+ * @return T
463+ */
464+ private function convertToBackedEnum (mixed $ value , string $ targetEnumClass ): \BackedEnum
465+ {
466+ if ($ value instanceof $ targetEnumClass ) {
467+ return $ value ;
468+ }
469+
470+ $ targetBackingType = (new \ReflectionEnum ($ targetEnumClass ))->getBackingType ();
471+
472+ /** @var \ReflectionNamedType $targetBackingType */
473+ $ expectedType = $ targetBackingType ->getName ();
474+ $ actualType = get_debug_type ($ value );
475+
476+ if ($ expectedType !== $ actualType ) {
477+ throw new MappingTransformException (\sprintf ('Cannot convert value of type "%s" to "%s"-backed enum "%s". ' , $ actualType , $ expectedType , $ targetEnumClass ));
478+ }
479+
480+ try {
481+ return $ targetEnumClass ::from ($ value );
482+ } catch (\ValueError $ e ) {
483+ throw new MappingTransformException (\sprintf ('Invalid value "%s" for enum "%s": "%s" ' , $ value , $ targetEnumClass , $ e ->getMessage ()), 0 , $ e );
484+ }
485+ }
486+
487+ /**
488+ * @param 'int'|'string' $targetType
489+ */
490+ private function convertFromBackedEnum (\BackedEnum $ enum , string $ targetType ): int |string
491+ {
492+ $ backingType = get_debug_type ($ enum ->value );
493+
494+ if ($ backingType !== $ targetType ) {
495+ throw new MappingTransformException (\sprintf ('Cannot convert "%s" backed enum "%s" to "%s". ' , $ backingType , $ enum ::class, $ targetType ));
496+ }
497+
498+ return $ enum ->value ;
499+ }
390500}
0 commit comments