Skip to content

Commit c95e577

Browse files
committed
[ObjectMapper] refacto
- No attributes in DTO to let them simple - Explicit target/source in tests - All properties with same name for simple tests - Lot less DTOs needed - Bad enum mapping now throws MappingTransformException
1 parent 1fdc9b2 commit c95e577

32 files changed

+206
-605
lines changed

src/Symfony/Component/ObjectMapper/ObjectMapper.php

Lines changed: 57 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -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, $targetRefl);
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, $targetRefl);
168+
$this->storeValue($propertyName, $mapToProperties, $ctorArguments, $value, $targetRefl, $propertyName);
169169
}
170170
}
171171

@@ -268,9 +268,12 @@ private function getSourceValue(object $source, object $target, mixed $value, \W
268268
* @param array<string, mixed> $mapToProperties
269269
* @param array<string, mixed> $ctorArguments
270270
*/
271-
private function storeValue(string $propertyName, array &$mapToProperties, array &$ctorArguments, mixed $value, \ReflectionClass $targetRefl): void
271+
private function storeValue(string $propertyName, array &$mapToProperties, array &$ctorArguments, mixed $value, \ReflectionClass $targetRefl, ?string $sourcePropertyName = null): void
272272
{
273-
$value = $this->convertValueForProperty($value, $targetRefl, $propertyName);
273+
if ($this->needsEnumConversion($value, $targetRefl, $propertyName)) {
274+
$property = $targetRefl->getProperty($propertyName);
275+
$value = $this->convertEnumValue($value, $property);
276+
}
274277

275278
if (\array_key_exists($propertyName, $ctorArguments)) {
276279
$ctorArguments[$propertyName] = $value;
@@ -391,104 +394,105 @@ public function withObjectMapper(ObjectMapperInterface $objectMapper): static
391394
return $clone;
392395
}
393396

394-
private function needsEnumConversion(mixed $value, string $targetTypeName): bool
397+
private function needsEnumConversion(mixed $value, \ReflectionClass $targetRefl, string $propertyName): bool
395398
{
396-
if ($value instanceof \UnitEnum) {
397-
return true;
398-
}
399+
if ($value instanceof \UnitEnum && !$value instanceof \BackedEnum) {
400+
$property = $targetRefl->getProperty($propertyName);
401+
$targetType = $property->getType();
399402

400-
if (enum_exists($targetTypeName)) {
401-
return true;
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+
}
403406

404-
return false;
405-
}
407+
return false;
408+
}
406409

407-
private function convertValueForProperty(mixed $value, \ReflectionClass $targetRefl, string $propertyName): mixed
408-
{
409-
if (!$targetRefl->hasProperty($propertyName)) {
410-
return $value;
410+
if (!($value instanceof \BackedEnum || \is_int($value) || \is_string($value))) {
411+
return false;
411412
}
412413

413414
$property = $targetRefl->getProperty($propertyName);
414415
$targetType = $property->getType();
415416

416417
if (!$targetType instanceof \ReflectionNamedType) {
417-
return $value;
418+
return false;
418419
}
419420

420421
$targetTypeName = $targetType->getName();
421422

422-
if (!$this->needsEnumConversion($value, $targetTypeName)) {
423-
return $value;
423+
if (enum_exists($targetTypeName)) {
424+
return true;
424425
}
425426

426427
if ($value instanceof \BackedEnum && \in_array($targetTypeName, ['string', 'int'], true)) {
427-
return $this->convertFromBackedEnum($value, $targetTypeName);
428+
return true;
428429
}
429430

430-
if ($value instanceof \UnitEnum && !is_a($targetTypeName, \UnitEnum::class, true)) {
431-
throw new MappingException(\sprintf('Cannot convert enum "%s" to non-enum type "%s".', $value::class, $targetTypeName));
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);
432444
}
433445

434-
if (is_a($targetTypeName, \BackedEnum::class, true) && !\is_object($value)) {
435-
return $this->convertToBackedEnum($value, $targetTypeName);
446+
if (is_a($targetEnumTypeName, \BackedEnum::class, true) && !\is_object($sourceValue)) {
447+
return $this->convertToBackedEnum($sourceValue, $targetEnumTypeName);
436448
}
437449

438-
return $value;
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;
439455
}
440456

441457
/**
442458
* @template T of \BackedEnum
443459
*
444-
* @param class-string<T> $enumClass
460+
* @param class-string<T> $targetEnumClass
445461
*
446462
* @return T
447463
*/
448-
private function convertToBackedEnum(mixed $value, string $enumClass): \BackedEnum
464+
private function convertToBackedEnum(mixed $value, string $targetEnumClass): \BackedEnum
449465
{
450-
if ($value instanceof $enumClass) {
466+
if ($value instanceof $targetEnumClass) {
451467
return $value;
452468
}
453469

454-
if (!\is_scalar($value)) {
455-
throw new MappingException(\sprintf('Cannot convert non-scalar value of type "%s" to enum "%s".', get_debug_type($value), $enumClass));
456-
}
457-
458-
$enumReflection = new \ReflectionEnum($enumClass);
459-
$backingType = $enumReflection->getBackingType();
470+
$targetBackingType = (new \ReflectionEnum($targetEnumClass))->getBackingType();
460471

461-
if (!$backingType instanceof \ReflectionNamedType) {
462-
throw new MappingException(\sprintf('Cannot determine backing type for enum "%s".', $enumClass));
463-
}
464-
465-
$expectedType = $backingType->getName();
472+
/** @var \ReflectionNamedType $targetBackingType */
473+
$expectedType = $targetBackingType->getName();
466474
$actualType = get_debug_type($value);
467475

468476
if ($expectedType !== $actualType) {
469-
throw new MappingException(\sprintf('Cannot convert value of type "%s" to "%s"-backed enum "%s".', $actualType, $expectedType, $enumClass));
477+
throw new MappingTransformException(\sprintf('Cannot convert value of type "%s" to "%s"-backed enum "%s".', $actualType, $expectedType, $targetEnumClass));
470478
}
471479

472480
try {
473-
return $enumClass::from($value);
481+
return $targetEnumClass::from($value);
474482
} catch (\ValueError $e) {
475-
throw new MappingException(\sprintf('Invalid value "%s" for enum "%s": "%s"', $value, $enumClass, $e->getMessage()), 0, $e);
483+
throw new MappingTransformException(\sprintf('Invalid value "%s" for enum "%s": "%s"', $value, $targetEnumClass, $e->getMessage()), 0, $e);
476484
}
477485
}
478486

487+
/**
488+
* @param 'int'|'string' $targetType
489+
*/
479490
private function convertFromBackedEnum(\BackedEnum $enum, string $targetType): int|string
480491
{
481-
$enumReflection = new \ReflectionEnum($enum);
482-
$backingType = $enumReflection->getBackingType();
483-
484-
if (!$backingType instanceof \ReflectionNamedType) {
485-
throw new MappingException(\sprintf('Cannot determine backing type for enum "%s".', $enum::class));
486-
}
487-
488-
$backingTypeName = $backingType->getName();
492+
$backingType = get_debug_type($enum->value);
489493

490-
if ($backingTypeName !== $targetType) {
491-
throw new MappingException(\sprintf('Cannot convert enum "%s" with backing type "%s" to incompatible type "%s".', $enum::class, $backingTypeName, $targetType));
494+
if ($backingType !== $targetType) {
495+
throw new MappingTransformException(\sprintf('Cannot convert "%s" backed enum "%s" to "%s".', $backingType, $enum::class, $targetType));
492496
}
493497

494498
return $enum->value;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
4+
namespace Symfony\Component\ObjectMapper\Tests\Fixtures\EnumMapping;
5+
6+
class DtoWithIntBackedEnum
7+
{
8+
public function __construct(
9+
public Priority $property,
10+
) {
11+
}
12+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
4+
namespace Symfony\Component\ObjectMapper\Tests\Fixtures\EnumMapping;
5+
class DtoWithIntProperty
6+
{
7+
public function __construct(
8+
public int $property,
9+
) {
10+
}
11+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
4+
namespace Symfony\Component\ObjectMapper\Tests\Fixtures\EnumMapping;
5+
6+
class DtoWithPureEnum
7+
{
8+
public function __construct(
9+
public Role $property,
10+
) {
11+
}
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
4+
namespace Symfony\Component\ObjectMapper\Tests\Fixtures\EnumMapping;
5+
6+
class DtoWithStringBackedEnum
7+
{
8+
public function __construct(
9+
public Status $property,
10+
) {
11+
}
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
4+
namespace Symfony\Component\ObjectMapper\Tests\Fixtures\EnumMapping;
5+
6+
class DtoWithStringProperty
7+
{
8+
public function __construct(
9+
public string $property,
10+
) {
11+
}
12+
}

src/Symfony/Component/ObjectMapper/Tests/Fixtures/EnumMapping/EnumToIntSource.php

Lines changed: 0 additions & 23 deletions
This file was deleted.

src/Symfony/Component/ObjectMapper/Tests/Fixtures/EnumMapping/EnumToIntTarget.php

Lines changed: 0 additions & 20 deletions
This file was deleted.

src/Symfony/Component/ObjectMapper/Tests/Fixtures/EnumMapping/EnumToStringSource.php

Lines changed: 0 additions & 23 deletions
This file was deleted.

src/Symfony/Component/ObjectMapper/Tests/Fixtures/EnumMapping/EnumToStringTarget.php

Lines changed: 0 additions & 20 deletions
This file was deleted.

0 commit comments

Comments
 (0)