From 4c7789c6b6767b0508affa53701c7ff24ea92d10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Tvrd=C3=ADk?= Date: Wed, 29 Mar 2023 15:48:26 +0200 Subject: [PATCH] Support options like charset and collation on DiscriminatedColumn [closes #10462] --- docs/en/reference/attributes-reference.rst | 1 + .../Mapping/Builder/ClassMetadataBuilder.php | 3 +- .../ORM/Mapping/ClassMetadataInfo.php | 3 +- .../ORM/Mapping/DiscriminatorColumn.php | 15 ++++- .../ORM/Mapping/Driver/AnnotationDriver.php | 22 +++--- .../ORM/Mapping/Driver/AttributeDriver.php | 22 +++--- lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php | 22 +++--- lib/Doctrine/ORM/Tools/SchemaTool.php | 1 + .../ORM/Functional/Ticket/GH10462Test.php | 67 +++++++++++++++++++ .../Tests/ORM/Mapping/AttributeReaderTest.php | 42 ++++++++++++ 10 files changed, 167 insertions(+), 31 deletions(-) create mode 100644 tests/Doctrine/Tests/ORM/Functional/Ticket/GH10462Test.php diff --git a/docs/en/reference/attributes-reference.rst b/docs/en/reference/attributes-reference.rst index 1f2fa339b9d..3452f78acb5 100644 --- a/docs/en/reference/attributes-reference.rst +++ b/docs/en/reference/attributes-reference.rst @@ -368,6 +368,7 @@ Optional parameters: - **length**: By default this is 255. - **columnDefinition**: By default this is null the definition according to the type will be used. This option allows to override it. - **enumType**: By default this is `null`. Allows to map discriminatorColumn value to PHP enum +- **options**: See "options" attribute on :ref:`#[Column] `. .. _attrref_discriminatormap: diff --git a/lib/Doctrine/ORM/Mapping/Builder/ClassMetadataBuilder.php b/lib/Doctrine/ORM/Mapping/Builder/ClassMetadataBuilder.php index ef797b32636..ee42b30acba 100644 --- a/lib/Doctrine/ORM/Mapping/Builder/ClassMetadataBuilder.php +++ b/lib/Doctrine/ORM/Mapping/Builder/ClassMetadataBuilder.php @@ -220,10 +220,11 @@ public function setSingleTableInheritance() * @param string $type * @param int $length * @psalm-param class-string|null $enumType + * @psalm-param array $options * * @return $this */ - public function setDiscriminatorColumn($name, $type = 'string', $length = 255, ?string $columnDefinition = null, ?string $enumType = null) + public function setDiscriminatorColumn($name, $type = 'string', $length = 255, ?string $columnDefinition = null, ?string $enumType = null, array $options = []) { $this->cm->setDiscriminatorColumn( [ diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 13e5a18302b..1485e478dc9 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -144,6 +144,7 @@ * length?: int, * columnDefinition?: string|null, * enumType?: class-string|null, + * options?: array * } */ class ClassMetadataInfo implements ClassMetadata @@ -3236,7 +3237,7 @@ public function addEntityListener($eventName, $class, $method) * @see getDiscriminatorColumn() * * @param mixed[]|null $columnDef - * @psalm-param array{name: string|null, fieldName?: string, type?: string, length?: int, columnDefinition?: string|null, enumType?: class-string|null}|null $columnDef + * @psalm-param array{name: string|null, fieldName?: string, type?: string, length?: int, columnDefinition?: string|null, enumType?: class-string|null, options?: array}|null $columnDef * * @return void * diff --git a/lib/Doctrine/ORM/Mapping/DiscriminatorColumn.php b/lib/Doctrine/ORM/Mapping/DiscriminatorColumn.php index dac5eaeb6c4..9cec7ad75ef 100644 --- a/lib/Doctrine/ORM/Mapping/DiscriminatorColumn.php +++ b/lib/Doctrine/ORM/Mapping/DiscriminatorColumn.php @@ -45,18 +45,29 @@ final class DiscriminatorColumn implements MappingAttribute */ public $enumType = null; - /** @param class-string<\BackedEnum>|null $enumType */ + /** + * @var array + * @readonly + */ + public $options = []; + + /** + * @param class-string<\BackedEnum>|null $enumType + * @param array $options + */ public function __construct( ?string $name = null, ?string $type = null, ?int $length = null, ?string $columnDefinition = null, - ?string $enumType = null + ?string $enumType = null, + array $options = [] ) { $this->name = $name; $this->type = $type; $this->length = $length; $this->columnDefinition = $columnDefinition; $this->enumType = $enumType; + $this->options = $options; } } diff --git a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php index 550675e0763..9453db24ed7 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php @@ -313,15 +313,19 @@ public function loadMetadataForClass($className, PersistenceClassMetadata $metad $discrColumnAnnot = $classAnnotations[Mapping\DiscriminatorColumn::class]; assert($discrColumnAnnot instanceof Mapping\DiscriminatorColumn); - $metadata->setDiscriminatorColumn( - [ - 'name' => $discrColumnAnnot->name, - 'type' => $discrColumnAnnot->type ?: 'string', - 'length' => $discrColumnAnnot->length ?? 255, - 'columnDefinition' => $discrColumnAnnot->columnDefinition, - 'enumType' => $discrColumnAnnot->enumType, - ] - ); + $columnDef = [ + 'name' => $discrColumnAnnot->name, + 'type' => $discrColumnAnnot->type ?: 'string', + 'length' => $discrColumnAnnot->length ?? 255, + 'columnDefinition' => $discrColumnAnnot->columnDefinition, + 'enumType' => $discrColumnAnnot->enumType, + ]; + + if ($discrColumnAnnot->options) { + $columnDef['options'] = $discrColumnAnnot->options; + } + + $metadata->setDiscriminatorColumn($columnDef); } else { $metadata->setDiscriminatorColumn(['name' => 'dtype', 'type' => 'string', 'length' => 255]); } diff --git a/lib/Doctrine/ORM/Mapping/Driver/AttributeDriver.php b/lib/Doctrine/ORM/Mapping/Driver/AttributeDriver.php index 25c77ad3faf..4baf2cb05af 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/AttributeDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/AttributeDriver.php @@ -265,15 +265,19 @@ public function loadMetadataForClass($className, PersistenceClassMetadata $metad if (isset($classAttributes[Mapping\DiscriminatorColumn::class])) { $discrColumnAttribute = $classAttributes[Mapping\DiscriminatorColumn::class]; - $metadata->setDiscriminatorColumn( - [ - 'name' => isset($discrColumnAttribute->name) ? (string) $discrColumnAttribute->name : null, - 'type' => isset($discrColumnAttribute->type) ? (string) $discrColumnAttribute->type : 'string', - 'length' => isset($discrColumnAttribute->length) ? (int) $discrColumnAttribute->length : 255, - 'columnDefinition' => isset($discrColumnAttribute->columnDefinition) ? (string) $discrColumnAttribute->columnDefinition : null, - 'enumType' => isset($discrColumnAttribute->enumType) ? (string) $discrColumnAttribute->enumType : null, - ] - ); + $columnDef = [ + 'name' => isset($discrColumnAttribute->name) ? (string) $discrColumnAttribute->name : null, + 'type' => isset($discrColumnAttribute->type) ? (string) $discrColumnAttribute->type : 'string', + 'length' => isset($discrColumnAttribute->length) ? (int) $discrColumnAttribute->length : 255, + 'columnDefinition' => isset($discrColumnAttribute->columnDefinition) ? (string) $discrColumnAttribute->columnDefinition : null, + 'enumType' => isset($discrColumnAttribute->enumType) ? (string) $discrColumnAttribute->enumType : null, + ]; + + if ($discrColumnAttribute->options) { + $columnDef['options'] = (array) $discrColumnAttribute->options; + } + + $metadata->setDiscriminatorColumn($columnDef); } else { $metadata->setDiscriminatorColumn(['name' => 'dtype', 'type' => 'string', 'length' => 255]); } diff --git a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php index b5b0bc34fd0..332fd89f5c2 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php @@ -204,15 +204,19 @@ public function loadMetadataForClass($className, PersistenceClassMetadata $metad // Evaluate if (isset($xmlRoot->{'discriminator-column'})) { $discrColumn = $xmlRoot->{'discriminator-column'}; - $metadata->setDiscriminatorColumn( - [ - 'name' => isset($discrColumn['name']) ? (string) $discrColumn['name'] : null, - 'type' => isset($discrColumn['type']) ? (string) $discrColumn['type'] : 'string', - 'length' => isset($discrColumn['length']) ? (int) $discrColumn['length'] : 255, - 'columnDefinition' => isset($discrColumn['column-definition']) ? (string) $discrColumn['column-definition'] : null, - 'enumType' => isset($discrColumn['enum-type']) ? (string) $discrColumn['enum-type'] : null, - ] - ); + $columnDef = [ + 'name' => isset($discrColumn['name']) ? (string) $discrColumn['name'] : null, + 'type' => isset($discrColumn['type']) ? (string) $discrColumn['type'] : 'string', + 'length' => isset($discrColumn['length']) ? (int) $discrColumn['length'] : 255, + 'columnDefinition' => isset($discrColumn['column-definition']) ? (string) $discrColumn['column-definition'] : null, + 'enumType' => isset($discrColumn['enum-type']) ? (string) $discrColumn['enum-type'] : null, + ]; + + if (isset($discrColumn['options'])) { + $columnDef['options'] = $this->parseOptions($discrColumn['options']->children()); + } + + $metadata->setDiscriminatorColumn($columnDef); } else { $metadata->setDiscriminatorColumn(['name' => 'dtype', 'type' => 'string', 'length' => 255]); } diff --git a/lib/Doctrine/ORM/Tools/SchemaTool.php b/lib/Doctrine/ORM/Tools/SchemaTool.php index fa5f87335fd..efd07170f05 100644 --- a/lib/Doctrine/ORM/Tools/SchemaTool.php +++ b/lib/Doctrine/ORM/Tools/SchemaTool.php @@ -440,6 +440,7 @@ private function addDiscriminatorColumnDefinition(ClassMetadata $class, Table $t $options['columnDefinition'] = $discrColumn['columnDefinition']; } + $options = $this->gatherColumnOptions($discrColumn) + $options; $table->addColumn($discrColumn['name'], $discrColumn['type'], $options); } diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/GH10462Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/GH10462Test.php new file mode 100644 index 00000000000..4c3f7915611 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/GH10462Test.php @@ -0,0 +1,67 @@ +_em->getConnection()->getDatabasePlatform() instanceof MySQLPlatform) { + self::markTestSkipped('This test is useful for all databases, but designed only for mysql.'); + } + + if (method_exists(AbstractPlatform::class, 'getGuidExpression')) { + self::markTestSkipped('Test valid for doctrine/dbal:3.x only.'); + } + + $this->createSchemaForModels( + GH10462Person::class, + GH10462Employee::class, + ); + + $schemaManager = $this->createSchemaManager(); + $personOptions = $schemaManager->introspectTable('gh10462_person')->getColumn('discr')->toArray(); + self::assertSame('ascii', $personOptions['charset']); + self::assertSame('ascii_general_ci', $personOptions['collation']); + } +} + +/** + * @Entity + * @Table(name="gh10462_person") + * @InheritanceType("SINGLE_TABLE") + * @DiscriminatorColumn(name="discr", type="string", options={"charset"="ascii", "collation"="ascii_general_ci"}) + * @DiscriminatorMap({"person"=GH10462Person::class, "employee"=GH10462Employee::class}) + */ +class GH10462Person +{ + /** + * @Id + * @Column(type="integer") + * @GeneratedValue + */ + public int|null $id = null; +} + +/** + * @Entity + */ +class GH10462Employee extends GH10462Person +{ +} diff --git a/tests/Doctrine/Tests/ORM/Mapping/AttributeReaderTest.php b/tests/Doctrine/Tests/ORM/Mapping/AttributeReaderTest.php index f14fd2d0153..7efdae171bc 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/AttributeReaderTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/AttributeReaderTest.php @@ -5,13 +5,21 @@ namespace Doctrine\Tests\ORM\Mapping; use Doctrine\ORM\Mapping as ORM; +use Doctrine\ORM\Mapping\Column; +use Doctrine\ORM\Mapping\DiscriminatorColumn; +use Doctrine\ORM\Mapping\DiscriminatorMap; use Doctrine\ORM\Mapping\Driver\AttributeReader; +use Doctrine\ORM\Mapping\Entity; +use Doctrine\ORM\Mapping\GeneratedValue; +use Doctrine\ORM\Mapping\Id; +use Doctrine\ORM\Mapping\InheritanceType; use Doctrine\ORM\Mapping\InverseJoinColumn; use Doctrine\ORM\Mapping\JoinColumn; use Doctrine\ORM\Mapping\JoinTable; use Doctrine\ORM\Mapping\ManyToMany; use LogicException; use PHPUnit\Framework\TestCase; +use ReflectionClass; use ReflectionProperty; /** @@ -72,6 +80,19 @@ public function testJoinColumnOptions(): void 'collation' => 'utf8mb4_bin', ], $inverseJoinColumns[0]->options); } + + public function testDiscriminatedColumnOptions(): void + { + $reader = new AttributeReader(); + $class = new ReflectionClass(TestPerson::class); + + $attributes = $reader->getClassAttributes($class); + self::assertArrayHasKey(DiscriminatorColumn::class, $attributes); + self::assertSame([ + 'charset' => 'ascii', + 'collation' => 'ascii_general_ci', + ], $attributes[DiscriminatorColumn::class]->options); + } } #[ORM\Entity] @@ -101,3 +122,24 @@ class TestTag #[ORM\GeneratedValue] public $id; } + + +#[Entity] +#[InheritanceType('SINGLE_TABLE')] +#[DiscriminatorColumn(name: 'discr', options: [ + 'charset' => 'ascii', + 'collation' => 'ascii_general_ci', +])] +#[DiscriminatorMap(['person' => TestPerson::class, 'employee' => TestEmployee::class])] +class TestPerson +{ + #[Id] + #[Column(type: 'integer')] + #[GeneratedValue] + public int|null $id = null; +} + +#[Entity] +class TestEmployee extends TestPerson +{ +}