Skip to content

Commit

Permalink
Support options like charset and collation on DiscriminatedColumn
Browse files Browse the repository at this point in the history
[closes #10462]
  • Loading branch information
JanTvrdik committed Mar 29, 2023
1 parent aec3556 commit dc3f3f7
Show file tree
Hide file tree
Showing 11 changed files with 168 additions and 31 deletions.
1 change: 1 addition & 0 deletions docs/en/reference/attributes-reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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_column>`.

.. _attrref_discriminatormap:

Expand Down
3 changes: 2 additions & 1 deletion lib/Doctrine/ORM/Mapping/Builder/ClassMetadataBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -220,10 +220,11 @@ public function setSingleTableInheritance()
* @param string $type
* @param int $length
* @psalm-param class-string<BackedEnum>|null $enumType
* @psalm-param array<string, mixed> $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(
[
Expand Down
1 change: 1 addition & 0 deletions lib/Doctrine/ORM/Mapping/ClassMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
* length?: int,
* columnDefinition?: string|null,
* enumType?: class-string<BackedEnum>|null,
* options?: array<string, mixed>,
* }
* @psalm-type EmbeddedClassMapping = array{
* class: class-string,
Expand Down
2 changes: 1 addition & 1 deletion lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -3131,7 +3131,7 @@ public function addEntityListener($eventName, $class, $method)
* @see getDiscriminatorColumn()
*
* @param mixed[]|null $columnDef
* @psalm-param DiscriminatorColumnMapping|array{name: string|null, fieldName?: string, type?: string, length?: int, columnDefinition?: string|null, enumType?: class-string<BackedEnum>|null}|null $columnDef
* @psalm-param DiscriminatorColumnMapping|array{name: string|null, fieldName?: string, type?: string, length?: int, columnDefinition?: string|null, enumType?: class-string<BackedEnum>|null, options?: array<string, mixed>}|null $columnDef
*
* @return void
*
Expand Down
15 changes: 13 additions & 2 deletions lib/Doctrine/ORM/Mapping/DiscriminatorColumn.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,29 @@ final class DiscriminatorColumn implements MappingAttribute
*/
public $enumType = null;

/** @param class-string<\BackedEnum>|null $enumType */
/**
* @var array<string, mixed>
* @readonly
*/
public $options = [];

/**
* @param class-string<\BackedEnum>|null $enumType
* @param array<string, mixed> $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;
}
}
22 changes: 13 additions & 9 deletions lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
}
Expand Down
22 changes: 13 additions & 9 deletions lib/Doctrine/ORM/Mapping/Driver/AttributeDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
}
Expand Down
22 changes: 13 additions & 9 deletions lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -204,15 +204,19 @@ public function loadMetadataForClass($className, PersistenceClassMetadata $metad
// Evaluate <discriminator-column...>
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]);
}
Expand Down
1 change: 1 addition & 0 deletions lib/Doctrine/ORM/Tools/SchemaTool.php
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,7 @@ private function addDiscriminatorColumnDefinition(ClassMetadata $class, Table $t
$options['columnDefinition'] = $discrColumn['columnDefinition'];
}

$options = $this->gatherColumnOptions($discrColumn) + $options;
$table->addColumn($discrColumn['name'], $discrColumn['type'], $options);
}

Expand Down
68 changes: 68 additions & 0 deletions tests/Doctrine/Tests/ORM/Functional/Ticket/GH10462Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\ORM\Functional\Ticket;

use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\DiscriminatorColumn;
use Doctrine\ORM\Mapping\DiscriminatorMap;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\InheritanceType;
use Doctrine\ORM\Mapping\Table;
use Doctrine\Tests\OrmFunctionalTestCase;

use function method_exists;

class GH10462Test extends OrmFunctionalTestCase
{
public function testCharsetAndCollationOptionsOnDiscriminatedColumn(): void
{
if (! $this->_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
{
/**
* @var int
* @Id
* @Column(type="integer")
* @GeneratedValue
*/
public $id;
}

/**
* @Entity
*/
class GH10462Employee extends GH10462Person
{
}
42 changes: 42 additions & 0 deletions tests/Doctrine/Tests/ORM/Mapping/AttributeReaderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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
{
}

0 comments on commit dc3f3f7

Please sign in to comment.