diff --git a/src/Mapping/ClassMetadata.php b/src/Mapping/ClassMetadata.php index 0b60ad2bac6..7dea8d30102 100644 --- a/src/Mapping/ClassMetadata.php +++ b/src/Mapping/ClassMetadata.php @@ -2190,6 +2190,20 @@ public function setDiscriminatorColumn(DiscriminatorColumnMapping|array|null $co throw MappingException::invalidDiscriminatorColumnType($this->name, $columnDef['type']); } + if (isset($columnDef['enumType'])) { + if (! enum_exists($columnDef['enumType'])) { + throw MappingException::nonEnumTypeMapped($this->name, $columnDef['fieldName'], $columnDef['enumType']); + } + + if ( + defined('Doctrine\DBAL\Types\Types::ENUM') + && $columnDef['type'] === Types::ENUM + && ! isset($columnDef['options']['values']) + ) { + $columnDef['options']['values'] = array_column($columnDef['enumType']::cases(), 'value'); + } + } + $this->discriminatorColumn = DiscriminatorColumnMapping::fromMappingArray($columnDef); } } @@ -2208,6 +2222,8 @@ final public function getDiscriminatorColumn(): DiscriminatorColumnMapping * Used for JOINED and SINGLE_TABLE inheritance mapping strategies. * * @param array $map + * + * @throws MappingException */ public function setDiscriminatorMap(array $map): void { @@ -2227,6 +2243,16 @@ public function setDiscriminatorMap(array $map): void ); } + $values = $this->discriminatorColumn->options['values'] ?? null; + + if ($values !== null) { + $diff = array_diff(array_keys($map), $values); + + if ($diff !== []) { + throw MappingException::invalidEntriesInDiscriminatorMap(array_values($diff), $this->name, $this->discriminatorColumn->enumType); + } + } + foreach ($map as $value => $className) { $this->addDiscriminatorMapClass($value, $className); } diff --git a/src/Mapping/MappingException.php b/src/Mapping/MappingException.php index 211e75c01a8..8450a350651 100644 --- a/src/Mapping/MappingException.php +++ b/src/Mapping/MappingException.php @@ -329,6 +329,24 @@ public static function fileMappingDriversRequireConfiguredDirectoryPath(string|n ); } + /** + * Returns an exception that indicates that discriminator entries used in a discriminator map + * does not exist in the backed enum provided by enumType option. + * + * @param array $entries The discriminator entries that could not be found. + * @param string $owningClass The class that declares the discriminator map. + * @param string $enumType The enum that entries were checked against. + */ + public static function invalidEntriesInDiscriminatorMap(array $entries, string $owningClass, string $enumType): self + { + return new self(sprintf( + "The entries %s in the discriminator map of class '%s' do not correspond to enum cases of '%s'.", + implode(', ', array_map(static fn ($entry): string => sprintf("'%s'", $entry), $entries)), + $owningClass, + $enumType, + )); + } + /** * Returns an exception that indicates that a class used in a discriminator map does not exist. * An example would be an outdated (maybe renamed) classname. diff --git a/tests/Tests/ORM/Tools/SchemaToolTest.php b/tests/Tests/ORM/Tools/SchemaToolTest.php index 0a11c8fea68..9cce6bf4670 100644 --- a/tests/Tests/ORM/Tools/SchemaToolTest.php +++ b/tests/Tests/ORM/Tools/SchemaToolTest.php @@ -45,6 +45,7 @@ use Doctrine\Tests\Models\Enums\Suit; use Doctrine\Tests\Models\Forum\ForumAvatar; use Doctrine\Tests\Models\Forum\ForumUser; +use Doctrine\Tests\Models\GH10288\GH10288People; use Doctrine\Tests\Models\NullDefault\NullDefaultColumn; use Doctrine\Tests\OrmTestCase; use PHPUnit\Framework\Attributes\Group; @@ -53,6 +54,7 @@ use function class_exists; use function count; use function current; +use function defined; use function enum_exists; use function method_exists; @@ -271,6 +273,34 @@ public function testSetDiscriminatorColumnWithoutLength(): void self::assertEquals(255, $column->getLength()); } + public function testSetDiscriminatorColumnWithEnumType(): void + { + $em = $this->getTestEntityManager(); + $schemaTool = new SchemaTool($em); + $metadata = $em->getClassMetadata(FirstEntity::class); + + $metadata->setInheritanceType(ClassMetadata::INHERITANCE_TYPE_SINGLE_TABLE); + $metadata->setDiscriminatorColumn([ + 'name' => 'discriminator', + 'type' => defined('Doctrine\DBAL\Types\Types::ENUM') ? 'enum' : 'string', + 'enumType' => GH10288People::class, + ]); + + $schema = $schemaTool->getSchemaFromMetadata([$metadata]); + + self::assertTrue($schema->hasTable('first_entity')); + $table = $schema->getTable('first_entity'); + + self::assertTrue($table->hasColumn('discriminator')); + $column = $table->getColumn('discriminator'); + self::assertEquals(GH10288People::class, $column->getPlatformOption('enumType')); + self::assertEquals([0 => 'boss', 1 => 'employee'], $column->getValues()); + + $this->expectException(MappingException::class); + $this->expectExceptionMessage("The entries 'user' in the discriminator map of class '" . FirstEntity::class . "' do not correspond to enum cases of '" . GH10288People::class . "'."); + $metadata->setDiscriminatorMap(['user' => CmsUser::class, 'employee' => CmsEmployee::class]); + } + public function testDerivedCompositeKey(): void { $em = $this->getTestEntityManager();