@@ -151,7 +151,7 @@ private function doMap(object $source, object|string|null $target, \WeakMap $obj
151151
152152 $ targetPropertyName = $ mapping ->target ?? $ propertyName ;
153153 $ value = $ this ->getSourceValue ($ source , $ mappedTarget , $ value , $ objectMap , $ mapping );
154- $ this ->storeValue ($ targetPropertyName , $ mapToProperties , $ ctorArguments , $ value );
154+ $ this ->storeValue ($ targetPropertyName , $ mapToProperties , $ ctorArguments , $ value, $ targetRefl , $ sourcePropertyName );
155155 }
156156
157157 if (!$ mappings && $ targetRefl ->hasProperty ($ propertyName )) {
@@ -161,7 +161,7 @@ private function doMap(object $source, object|string|null $target, \WeakMap $obj
161161 }
162162
163163 $ value = $ this ->getSourceValue ($ source , $ mappedTarget , $ this ->getRawValue ($ source , $ propertyName ), $ objectMap );
164- $ this ->storeValue ($ propertyName , $ mapToProperties , $ ctorArguments , $ value );
164+ $ this ->storeValue ($ propertyName , $ mapToProperties , $ ctorArguments , $ value, $ targetRefl , $ propertyName );
165165 }
166166 }
167167
@@ -238,6 +238,7 @@ private function getSourceValue(object $source, object $target, mixed $value, \W
238238
239239 if (
240240 \is_object ($ value )
241+ && !($ value instanceof \UnitEnum)
241242 && ($ innerMetadata = $ this ->metadataFactory ->create ($ value ))
242243 && ($ mapTo = $ this ->getMapTarget ($ innerMetadata , $ value , $ source , $ target ))
243244 && (\is_string ($ mapTo ->target ) && class_exists ($ mapTo ->target ))
@@ -277,8 +278,13 @@ private function getSourceValue(object $source, object $target, mixed $value, \W
277278 * @param array<string, mixed> $mapToProperties
278279 * @param array<string, mixed> $ctorArguments
279280 */
280- private function storeValue (string $ propertyName , array &$ mapToProperties , array &$ ctorArguments , mixed $ value ): void
281+ private function storeValue (string $ propertyName , array &$ mapToProperties , array &$ ctorArguments , mixed $ value, \ ReflectionClass $ targetRefl , ? string $ sourcePropertyName = null ): void
281282 {
283+ if ($ this ->needsEnumConversion ($ value , $ targetRefl , $ propertyName )) {
284+ $ property = $ targetRefl ->getProperty ($ propertyName );
285+ $ value = $ this ->convertEnumValue ($ value , $ property );
286+ }
287+
282288 if (\array_key_exists ($ propertyName , $ ctorArguments )) {
283289 $ ctorArguments [$ propertyName ] = $ value ;
284290
@@ -397,4 +403,108 @@ public function withObjectMapper(ObjectMapperInterface $objectMapper): static
397403
398404 return $ clone ;
399405 }
406+
407+ private function needsEnumConversion (mixed $ value , \ReflectionClass $ targetRefl , string $ propertyName ): bool
408+ {
409+ if ($ value instanceof \UnitEnum && !$ value instanceof \BackedEnum) {
410+ $ property = $ targetRefl ->getProperty ($ propertyName );
411+ $ targetType = $ property ->getType ();
412+
413+ if ($ targetType instanceof \ReflectionNamedType && \in_array ($ targetType ->getName (), ['string ' , 'int ' ], true )) {
414+ 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 ));
415+ }
416+
417+ return false ;
418+ }
419+
420+ if (!($ value instanceof \BackedEnum || \is_int ($ value ) || \is_string ($ value ))) {
421+ return false ;
422+ }
423+
424+ $ property = $ targetRefl ->getProperty ($ propertyName );
425+ $ targetType = $ property ->getType ();
426+
427+ if (!$ targetType instanceof \ReflectionNamedType) {
428+ return false ;
429+ }
430+
431+ $ targetTypeName = $ targetType ->getName ();
432+
433+ if (enum_exists ($ targetTypeName )) {
434+ return true ;
435+ }
436+
437+ if ($ value instanceof \BackedEnum && \in_array ($ targetTypeName , ['string ' , 'int ' ], true )) {
438+ return true ;
439+ }
440+
441+ return false ;
442+ }
443+
444+ /**
445+ * @return int|string|\BackedEnum the target-expected value
446+ */
447+ private function convertEnumValue (mixed $ sourceValue , \ReflectionProperty $ targetProperty ): int |string |\BackedEnum
448+ {
449+ $ targetType = $ targetProperty ->getType ();
450+ $ targetEnumTypeName = $ targetType instanceof \ReflectionNamedType ? $ targetType ->getName () : (string ) $ targetType ;
451+
452+ if ($ sourceValue instanceof \BackedEnum && \in_array ($ targetEnumTypeName , ['string ' , 'int ' ], true )) {
453+ return $ this ->convertFromBackedEnum ($ sourceValue , $ targetEnumTypeName );
454+ }
455+
456+ if (is_a ($ targetEnumTypeName , \BackedEnum::class, true ) && !\is_object ($ sourceValue )) {
457+ return $ this ->convertToBackedEnum ($ sourceValue , $ targetEnumTypeName );
458+ }
459+
460+ if (is_a ($ targetEnumTypeName , \UnitEnum::class, true ) && !is_a ($ targetEnumTypeName , \BackedEnum::class, true ) && !\is_object ($ sourceValue )) {
461+ 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 ()));
462+ }
463+
464+ return $ sourceValue ;
465+ }
466+
467+ /**
468+ * @template T of \BackedEnum
469+ *
470+ * @param class-string<T> $targetEnumClass
471+ *
472+ * @return T
473+ */
474+ private function convertToBackedEnum (mixed $ value , string $ targetEnumClass ): \BackedEnum
475+ {
476+ if ($ value instanceof $ targetEnumClass ) {
477+ return $ value ;
478+ }
479+
480+ $ targetBackingType = (new \ReflectionEnum ($ targetEnumClass ))->getBackingType ();
481+
482+ /** @var \ReflectionNamedType $targetBackingType */
483+ $ expectedType = $ targetBackingType ->getName ();
484+ $ actualType = get_debug_type ($ value );
485+
486+ if ($ expectedType !== $ actualType ) {
487+ throw new MappingTransformException (\sprintf ('Cannot convert value of type "%s" to "%s"-backed enum "%s". ' , $ actualType , $ expectedType , $ targetEnumClass ));
488+ }
489+
490+ try {
491+ return $ targetEnumClass ::from ($ value );
492+ } catch (\ValueError $ e ) {
493+ throw new MappingTransformException (\sprintf ('Invalid value "%s" for enum "%s": "%s" ' , $ value , $ targetEnumClass , $ e ->getMessage ()), 0 , $ e );
494+ }
495+ }
496+
497+ /**
498+ * @param 'int'|'string' $targetType
499+ */
500+ private function convertFromBackedEnum (\BackedEnum $ enum , string $ targetType ): int |string
501+ {
502+ $ backingType = get_debug_type ($ enum ->value );
503+
504+ if ($ backingType !== $ targetType ) {
505+ throw new MappingTransformException (\sprintf ('Cannot convert "%s" backed enum "%s" to "%s". ' , $ backingType , $ enum ::class, $ targetType ));
506+ }
507+
508+ return $ enum ->value ;
509+ }
400510}
0 commit comments