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
17 changes: 17 additions & 0 deletions packages/database/module.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,28 @@
use Marko\Core\Container\ContainerInterface;
use Marko\Core\Path\ProjectPaths;
use Marko\Database\Connection\TransactionInterface;
use Marko\Database\Entity\EntityDiscovery;
use Marko\Database\Entity\EntityMetadataFactory;
use Marko\Database\Seed\SeederDiscovery;
use Marko\Database\Seed\SeederDiscoveryInterface;
use Marko\Database\Seed\SeederRunner;

return [
'singletons' => [
EntityMetadataFactory::class,
],
'boot' => function (
EntityDiscovery $discovery,
EntityMetadataFactory $metadataFactory,
ProjectPaths $paths,
): void {
$entityClasses = array_merge(
$discovery->discoverInVendor($paths->vendor),
$discovery->discoverInModules($paths->modules),
$discovery->discoverInApp($paths->app),
);
$metadataFactory->linkExtendersFrom($entityClasses);
},
'bindings' => [
SeederDiscoveryInterface::class => SeederDiscovery::class,
SeederRunner::class => function (ContainerInterface $container): SeederRunner {
Expand Down
27 changes: 27 additions & 0 deletions packages/database/src/Entity/EntityMetadataFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,33 @@ public function linkExtenders(
return $linked;
}

/**
* Scan a list of entity classes and link any extenders to their parent metadata.
*
* @param array<class-string> $entityClasses
*/
public function linkExtendersFrom(array $entityClasses): void
{
$extenders = [];
foreach ($entityClasses as $entityClass) {
$reflection = new ReflectionClass($entityClass);
$tableAttrs = $reflection->getAttributes(Table::class);
if (count($tableAttrs) === 0) {
continue;
}
$tableAttr = $tableAttrs[0]->newInstance();
if ($tableAttr->extends !== null) {
$extenders[$tableAttr->extends][] = $entityClass;
}
}
foreach ($extenders as $parentClass => $extenderClasses) {
if (!class_exists($parentClass, true)) {
throw EntityException::extenderParentClassNotFound($extenderClasses[0], $parentClass);
}
$this->linkExtenders($parentClass, $extenderClasses);
}
}

/**
* Clear the metadata cache.
*/
Expand Down
24 changes: 24 additions & 0 deletions packages/database/tests/Entity/EntityMetadataFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,30 @@ class UntypedPropertyEntity extends Entity
->toThrow(EntityException::class, "Chained extension is not supported. $extender's parent $parent is itself an extender. Extend the root entity directly.");
});

it('linkExtendersFrom scans entity classes and links extenders to their parents', function (): void {
$this->factory->linkExtendersFrom([
ExtenderParentEntity::class,
BasicExtenderEntity::class,
]);

$parentMetadata = $this->factory->parse(ExtenderParentEntity::class);

expect($parentMetadata->extenders)->toBe([BasicExtenderEntity::class]);
});

it('linkExtendersFrom ignores entities without extends', function (): void {
$this->factory->linkExtendersFrom([ExtenderParentEntity::class]);

$parentMetadata = $this->factory->parse(ExtenderParentEntity::class);

expect($parentMetadata->extenders)->toBe([]);
});

it('linkExtendersFrom throws when an extender references a parent class that cannot be autoloaded', function (): void {
expect(fn () => $this->factory->linkExtendersFrom([ExtenderWithMissingParentEntity::class]))
->toThrow(EntityException::class, 'does not exist');
});

it('clears cached metadata', function (): void {
$entity = new #[Table('test')] class () extends Entity
{
Expand Down