Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 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
41 changes: 40 additions & 1 deletion src/Configurator.php
Original file line number Diff line number Diff line change
Expand Up @@ -112,14 +112,20 @@ public function initFields(EntitySchema $entity, \ReflectionClass $class, string
}

$field = $this->initField($property->getName(), $column, $class, $columnPrefix);
$field->setEntityClass($property->getDeclaringClass()->getName());
$field->setEntityClass($this->findOwningEntity($class, $property->getDeclaringClass())->getName());
$entity->getFields()->set($property->getName(), $field);
}
}

public function initRelations(EntitySchema $entity, \ReflectionClass $class): void
{
foreach ($class->getProperties() as $property) {
// ignore properties declared by parent entties
// otherwise all the relation columns declared in parent would be duplicated across all child tables in JTI
if ($this->findOwningEntity($class, $property->getDeclaringClass())->getName() !== $class->getName()) {
continue;
}
Comment thread
roxblnfk marked this conversation as resolved.
Outdated

$metadata = $this->getPropertyMetadata($property, RelationAnnotation\RelationInterface::class);

foreach ($metadata as $meta) {
Expand Down Expand Up @@ -413,4 +419,37 @@ private function isOnInsertGeneratedField(Field $field): bool
default => $field->isPrimary()
};
}

/**
* Function to find an owning entity class in the inheritance hierarchy.
*
* Entity classes may extend a base class and this function is needed route the properties from declaring class to the entity class.
* The function stops only when the declaring class is truly found, it does not naively stop on first entity.
* This behaviour makes it also functional in cases of Joined Table Inheritance on theoretically any number of nesting levels.
*/
private function findOwningEntity(\ReflectionClass $currentClass, \ReflectionClass $declaringClass): \ReflectionClass
{
// latest found entityClass before declaringClass
$latestEntityClass = $currentClass;

do {
// we found declaringClass in the hierarchy
// in most cases the execution will stop here in first loop
if ($currentClass->getName() === $declaringClass->getName()) {
return $latestEntityClass;
}

$currentClass = $currentClass->getParentClass();

// not possible to happen for logical reasons, but defensively check anyway
if (!$currentClass instanceof \ReflectionClass) {
return $latestEntityClass;
}

// if a currentClass in hierarchy is an entity on its own, the property belongs to that entity
if (\count($currentClass->getAttributes(Entity::class)) > 0) {
$latestEntityClass = $currentClass;
}
} while (true); // the inheritance hierarchy cannot be infinite
}
}
6 changes: 5 additions & 1 deletion src/TableInheritance.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,11 @@ public function run(Registry $registry): Registry

// All child should be presented in a schema as separated entity
// Every child will be handled according its table inheritance type
\assert($child->getRole() !== null && $entity !== null && isset($parent));
// todo should $parent be not null?
// \assert(isset($parent));

\assert($child->getRole() !== null && $entity !== null);

if (!$registry->hasEntity($child->getRole())) {
$registry->register($child);

Expand Down
5 changes: 2 additions & 3 deletions tests/Annotated/Fixtures/Fixtures16/ExecutiveProxy.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,11 @@
/**
* This proxy class doesn't have an {@see Entity} annotation (attribute) declaration,
* and it shouldn't be presented in Schema.
* Note: this behavior might be improved. There will be added support for
* annotated base class columns without Entity annotation declaration.
* But all the classes that extend this class should contain all the fields from this class.
*/
class ExecutiveProxy extends Employee
{
/** @Column(type="string") */
/** @Column(type="string", name="proxy") */
#[Column(type: 'string', name: 'proxy')]
public ?string $proxyFieldWithAnnotation = null;

Expand Down
6 changes: 3 additions & 3 deletions tests/Annotated/Functional/Driver/Common/BaseTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -115,14 +115,14 @@ public function getDriver(): Driver

public static function singularReadersProvider(): \Traversable
{
yield ['Annotation reader' => new AnnotationReader()];
yield ['Attribute reader' => new AttributeReader()];
yield 'Annotation reader' => [new AnnotationReader()];
yield 'Attribute reader' => [new AttributeReader()];
}

public static function allReadersProvider(): \Traversable
{
yield from static::singularReadersProvider();
yield ['Selective reader' => new SelectiveReader([new AttributeReader(), new AnnotationReader()])];
yield 'Selective reader' => [new SelectiveReader([new AttributeReader(), new AnnotationReader()])];
}

protected function getDatabase(): Database
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ public function testTableInheritance(ReaderInterface $reader): void
$this->assertSame('secret', $loadedExecutive->hidden);
$this->assertSame(15000, $loadedExecutive->bonus);
$this->assertSame('executive', $loadedExecutive->getType());
$this->assertNull($loadedExecutive->proxyFieldWithAnnotation);
$this->assertSame('value', $loadedExecutive->proxyFieldWithAnnotation);
}

#[DataProvider('allReadersProvider')]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,12 @@ public function testTableInheritance(ReaderInterface $reader): void
$this->assertSame('foo_id', $schema['executive'][SchemaInterface::PARENT_KEY]);
$this->assertSame('executives', $schema['executive'][SchemaInterface::TABLE]);
$this->assertSame(
['bonus' => 'bonus', 'foo_id' => 'id', 'hidden' => 'hidden'],
[
'bonus' => 'bonus',
'proxyFieldWithAnnotation' => 'proxy',
'foo_id' => 'id',
'hidden' => 'hidden',
],
$schema['executive'][SchemaInterface::COLUMNS]
);

Expand Down
2 changes: 1 addition & 1 deletion tests/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ services:
ACCEPT_EULA: "Y"

mysql_latest:
image: mysql:latest
image: mysql:8.0
restart: always
command: --default-authentication-plugin=mysql_native_password
ports:
Expand Down