Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions src/Mapping/ClassMetadataFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
use function explode;
use function in_array;
use function is_a;
use function is_int;
use function is_subclass_of;
use function method_exists;
use function str_contains;
Expand Down Expand Up @@ -523,6 +524,17 @@ private function addInheritedIndexes(ClassMetadata $subClass, ClassMetadata $par
foreach (['uniqueConstraints', 'indexes'] as $indexType) {
if (isset($parentClass->table[$indexType])) {
foreach ($parentClass->table[$indexType] as $indexName => $index) {
if (is_int($indexName)) {
// Unnamed indices only have a positional key, so identify them by
// their columns: the inheriting table overrides such an index by
// declaring one on the same columns
if (! self::hasIndexOnSameColumns($subClass->table[$indexType] ?? [], $index)) {
$subClass->table[$indexType][] = $index;
}

continue;
}

if (isset($subClass->table[$indexType][$indexName])) {
continue; // Let the inheriting table override indices
}
Expand All @@ -533,6 +545,24 @@ private function addInheritedIndexes(ClassMetadata $subClass, ClassMetadata $par
}
}

/**
* @param array<int|string, array<string, mixed>> $indexes
* @param array<string, mixed> $index
*/
private static function hasIndexOnSameColumns(array $indexes, array $index): bool
{
foreach ($indexes as $existingIndex) {
if (
($existingIndex['columns'] ?? null) === ($index['columns'] ?? null)
&& ($existingIndex['fields'] ?? null) === ($index['fields'] ?? null)
) {
return true;
}
}

return false;
}

/**
* Completes the ID generator mapping. If "auto" is specified we choose the generator
* most appropriate for the targeted database platform.
Expand Down
102 changes: 102 additions & 0 deletions tests/Tests/ORM/Mapping/BasicInheritanceMappingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,61 @@ public function testMappedSuperclassIndex(): void
self::assertArrayHasKey('IDX_MAPPED2_INDEX', $class->table['indexes']);
}

/**
* Ensure unnamed indexes are inherited from the mapped superclass
* even when the entity declares unnamed indexes of its own.
*/
#[Group('GH-12179')]
public function testMappedSuperclassUnnamedIndexes(): void
{
$class = $this->cmf->getMetadataFor(EntityUnnamedIndexSubClass::class);
assert($class instanceof ClassMetadata);

self::assertSame(
[
['columns' => ['name']],
['columns' => ['mapped1']],
],
$class->table['uniqueConstraints'],
);

// The indexes on mapped3 and mapped4 are declared by both classes: they must not be duplicated
self::assertSame(
[
['columns' => ['code']],
['columns' => ['mapped3']],
['fields' => ['mapped4']],
['columns' => ['mapped2']],
],
$class->table['indexes'],
);
}

/**
* Ensure a named index of the entity replaces an unnamed index of the
* mapped superclass on the same columns instead of duplicating it.
*/
#[Group('GH-12179')]
public function testMappedSuperclassUnnamedIndexOverriddenByNamedIndex(): void
{
$class = $this->cmf->getMetadataFor(EntityNamedIndexSubClass::class);
assert($class instanceof ClassMetadata);

self::assertSame(
[['columns' => ['mapped1']]],
$class->table['uniqueConstraints'],
);

self::assertSame(
[
'IDX_MAPPED2' => ['columns' => ['mapped2']],
0 => ['columns' => ['mapped3']],
1 => ['fields' => ['mapped4']],
],
$class->table['indexes'],
);
}

#[DataProvider('invalidHierarchyDeclarationClasses')]
public function testUndeclaredHierarchyRejection(string $rootEntity, string $childClass): void
{
Expand Down Expand Up @@ -318,6 +373,53 @@ class EntityIndexSubClass extends MappedSuperclassBaseIndex
private string $name;
}

#[Table]
#[UniqueConstraint(columns: ['mapped1'])]
#[Index(columns: ['mapped2'])]
#[Index(columns: ['mapped3'])]
#[Index(fields: ['mapped4'])]
#[MappedSuperclass]
class MappedSuperclassBaseUnnamedIndex
{
#[Column(type: 'string', length: 255)]
private string $mapped1;
#[Column(type: 'string', length: 255)]
private string $mapped2;
#[Column(type: 'string', length: 255)]
private string $mapped3;
#[Column(type: 'string', length: 255)]
private string $mapped4;
}

#[Table]
#[UniqueConstraint(columns: ['name'])]
#[Index(columns: ['code'])]
#[Index(columns: ['mapped3'])]
#[Index(fields: ['mapped4'])]
#[Entity]
class EntityUnnamedIndexSubClass extends MappedSuperclassBaseUnnamedIndex
{
#[Id]
#[Column(type: 'integer')]
private int $id;

#[Column(type: 'string', length: 255)]
private string $name;

#[Column(type: 'string', length: 255)]
private string $code;
}

#[Table]
#[Index(name: 'IDX_MAPPED2', columns: ['mapped2'])]
#[Entity]
class EntityNamedIndexSubClass extends MappedSuperclassBaseUnnamedIndex
{
#[Id]
#[Column(type: 'integer')]
private int $id;
}

#[Entity]
#[InheritanceType('SINGLE_TABLE')]
#[DiscriminatorColumn(name: 'type', type: 'string', length: 20)]
Expand Down
Loading