diff --git a/psalm-baseline.xml b/psalm-baseline.xml
index b83ae43a889..a284dd38e2d 100644
--- a/psalm-baseline.xml
+++ b/psalm-baseline.xml
@@ -212,14 +212,14 @@
-
-
-
-
+
+
+
+
@@ -402,6 +402,7 @@
+
diff --git a/src/Mapping/ClassMetadata.php b/src/Mapping/ClassMetadata.php
index 70d3ea7042f..7c9020805a5 100644
--- a/src/Mapping/ClassMetadata.php
+++ b/src/Mapping/ClassMetadata.php
@@ -7,6 +7,7 @@
use BackedEnum;
use BadMethodCallException;
use Doctrine\DBAL\Platforms\AbstractPlatform;
+use Doctrine\DBAL\Types\Types;
use Doctrine\Deprecations\Deprecation;
use Doctrine\Instantiator\Instantiator;
use Doctrine\Instantiator\InstantiatorInterface;
@@ -23,6 +24,7 @@
use ReflectionProperty;
use Stringable;
+use function array_column;
use function array_diff;
use function array_intersect;
use function array_key_exists;
@@ -34,6 +36,7 @@
use function assert;
use function class_exists;
use function count;
+use function defined;
use function enum_exists;
use function explode;
use function in_array;
@@ -1119,9 +1122,7 @@ private function validateAndCompleteTypedFieldMapping(array $mapping): array
{
$field = $this->reflClass->getProperty($mapping['fieldName']);
- $mapping = $this->typedFieldMapper->validateAndComplete($mapping, $field);
-
- return $mapping;
+ return $this->typedFieldMapper->validateAndComplete($mapping, $field);
}
/**
@@ -1232,6 +1233,14 @@ protected function validateAndCompleteFieldMapping(array $mapping): FieldMapping
if (! empty($mapping->id)) {
$this->containsEnumIdentifier = true;
}
+
+ if (
+ defined('Doctrine\DBAL\Types\Types::ENUM')
+ && $mapping->type === Types::ENUM
+ && ! isset($mapping->options['values'])
+ ) {
+ $mapping->options['values'] = array_column($mapping->enumType::cases(), 'value');
+ }
}
return $mapping;
diff --git a/src/Mapping/DefaultTypedFieldMapper.php b/src/Mapping/DefaultTypedFieldMapper.php
index 49144b8b7c8..40b37b8c426 100644
--- a/src/Mapping/DefaultTypedFieldMapper.php
+++ b/src/Mapping/DefaultTypedFieldMapper.php
@@ -16,6 +16,7 @@
use function array_merge;
use function assert;
+use function defined;
use function enum_exists;
use function is_a;
@@ -49,30 +50,40 @@ public function validateAndComplete(array $mapping, ReflectionProperty $field):
{
$type = $field->getType();
+ if (! $type instanceof ReflectionNamedType) {
+ return $mapping;
+ }
+
if (
- ! isset($mapping['type'])
- && ($type instanceof ReflectionNamedType)
+ ! $type->isBuiltin()
+ && enum_exists($type->getName())
+ && (! isset($mapping['type']) || (
+ defined('Doctrine\DBAL\Types\Types::ENUM')
+ && $mapping['type'] === Types::ENUM
+ ))
) {
- if (! $type->isBuiltin() && enum_exists($type->getName())) {
- $reflection = new ReflectionEnum($type->getName());
- if (! $reflection->isBacked()) {
- throw MappingException::backedEnumTypeRequired(
- $field->class,
- $mapping['fieldName'],
- $type->getName(),
- );
- }
+ $reflection = new ReflectionEnum($type->getName());
+ if (! $reflection->isBacked()) {
+ throw MappingException::backedEnumTypeRequired(
+ $field->class,
+ $mapping['fieldName'],
+ $type->getName(),
+ );
+ }
- assert(is_a($type->getName(), BackedEnum::class, true));
- $mapping['enumType'] = $type->getName();
- $type = $reflection->getBackingType();
+ assert(is_a($type->getName(), BackedEnum::class, true));
+ $mapping['enumType'] = $type->getName();
+ $type = $reflection->getBackingType();
- assert($type instanceof ReflectionNamedType);
- }
+ assert($type instanceof ReflectionNamedType);
+ }
- if (isset($this->typedFieldMappings[$type->getName()])) {
- $mapping['type'] = $this->typedFieldMappings[$type->getName()];
- }
+ if (isset($mapping['type'])) {
+ return $mapping;
+ }
+
+ if (isset($this->typedFieldMappings[$type->getName()])) {
+ $mapping['type'] = $this->typedFieldMappings[$type->getName()];
}
return $mapping;
diff --git a/tests/Tests/Models/Enums/CardNativeEnum.php b/tests/Tests/Models/Enums/CardNativeEnum.php
new file mode 100644
index 00000000000..83d4d59a778
--- /dev/null
+++ b/tests/Tests/Models/Enums/CardNativeEnum.php
@@ -0,0 +1,25 @@
+ ['H', 'D', 'C', 'S', 'Z']])]
+ public $suit;
+}
diff --git a/tests/Tests/Models/Enums/TypedCardNativeEnum.php b/tests/Tests/Models/Enums/TypedCardNativeEnum.php
new file mode 100644
index 00000000000..59e4eb00e55
--- /dev/null
+++ b/tests/Tests/Models/Enums/TypedCardNativeEnum.php
@@ -0,0 +1,23 @@
+_em->flush();
$this->_em->clear();
- $fetchedCard = $this->_em->find(Card::class, $card->id);
+ $fetchedCard = $this->_em->find($cardClass, $card->id);
$this->assertInstanceOf(Suit::class, $fetchedCard->suit);
$this->assertEquals(Suit::Clubs, $fetchedCard->suit);
@@ -417,6 +421,10 @@ public function testFindByEnum(): void
#[DataProvider('provideCardClasses')]
public function testEnumWithNonMatchingDatabaseValueThrowsException(string $cardClass): void
{
+ if ($cardClass === TypedCardNativeEnum::class) {
+ self::markTestSkipped('MySQL won\'t allow us to insert invalid values in this case.');
+ }
+
$this->setUpEntitySchema([$cardClass]);
$card = new $cardClass();
@@ -429,7 +437,7 @@ public function testEnumWithNonMatchingDatabaseValueThrowsException(string $card
$metadata = $this->_em->getClassMetadata($cardClass);
$this->_em->getConnection()->update(
$metadata->table['name'],
- [$metadata->fieldMappings['suit']->columnName => 'invalid'],
+ [$metadata->fieldMappings['suit']->columnName => 'Z'],
[$metadata->fieldMappings['id']->columnName => $card->id],
);
@@ -437,7 +445,7 @@ public function testEnumWithNonMatchingDatabaseValueThrowsException(string $card
$this->expectExceptionMessage(sprintf(
<<<'EXCEPTION'
Context: Trying to hydrate enum property "%s::$suit"
-Problem: Case "invalid" is not listed in enum "Doctrine\Tests\Models\Enums\Suit"
+Problem: Case "Z" is not listed in enum "Doctrine\Tests\Models\Enums\Suit"
Solution: Either add the case to the enum type or migrate the database column to use another case of the enum
EXCEPTION
,
@@ -447,13 +455,16 @@ public function testEnumWithNonMatchingDatabaseValueThrowsException(string $card
$this->_em->find($cardClass, $card->id);
}
- /** @return array */
- public static function provideCardClasses(): array
+ /** @return iterable */
+ public static function provideCardClasses(): iterable
{
- return [
- Card::class => [Card::class],
- TypedCard::class => [TypedCard::class],
- ];
+ yield Card::class => [Card::class];
+ yield TypedCard::class => [TypedCard::class];
+
+ if (class_exists(EnumType::class)) {
+ yield CardNativeEnum::class => [CardNativeEnum::class];
+ yield TypedCardNativeEnum::class => [TypedCardNativeEnum::class];
+ }
}
public function testItAllowsReadingAttributes(): void