Skip to content

Commit a50491a

Browse files
committed
Fix issue with circular loads on ORM
1 parent a7cc5dc commit a50491a

File tree

4 files changed

+104
-5
lines changed

4 files changed

+104
-5
lines changed

packages/database/src/Builder/ModelInspector.php

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ public function getSelectFields(): ImmutableArray
365365
return $selectFields;
366366
}
367367

368-
public function resolveRelations(string $relationString, string $parent = ''): array
368+
public function resolveRelations(string $relationString, string $parent = '', array $visitedPaths = []): array
369369
{
370370
if ($relationString === '') {
371371
return [];
@@ -384,6 +384,11 @@ public function resolveRelations(string $relationString, string $parent = ''): a
384384
unset($relationNames[0]);
385385

386386
$relationModel = inspect($currentRelation);
387+
$modelType = $relationModel->getName();
388+
389+
if (in_array($modelType, $visitedPaths, true)) {
390+
return [$currentRelationName => $currentRelation->setParent($parent)];
391+
}
387392

388393
$newRelationString = implode('.', $relationNames);
389394
$currentRelation->setParent($parent);
@@ -395,10 +400,13 @@ public function resolveRelations(string $relationString, string $parent = ''): a
395400

396401
$relations = [$currentRelationName => $currentRelation];
397402

398-
return [...$relations, ...$relationModel->resolveRelations($newRelationString, $newParent)];
403+
return [
404+
...$relations,
405+
...$relationModel->resolveRelations($newRelationString, $newParent, [...$visitedPaths, $this->getName()]),
406+
];
399407
}
400408

401-
public function resolveEagerRelations(string $parent = ''): array
409+
public function resolveEagerRelations(string $parent = '', array $visitedPaths = []): array
402410
{
403411
if (! $this->isObjectModel()) {
404412
return [];
@@ -418,14 +426,23 @@ public function resolveEagerRelations(string $parent = ''): array
418426
continue;
419427
}
420428

421-
$relations[$property->getName()] = $currentRelation->setParent($parent);
422429
$newParent = ltrim(sprintf(
423430
'%s.%s',
424431
$parent,
425432
$currentRelationName,
426433
), '.');
427434

428-
foreach (inspect($currentRelation)->resolveEagerRelations($newParent) as $name => $nestedEagerRelation) {
435+
$relationModel = inspect($currentRelation);
436+
$modelType = $relationModel->getName();
437+
438+
if (in_array($modelType, $visitedPaths, true)) {
439+
continue;
440+
}
441+
442+
$relations[$property->getName()] = $currentRelation->setParent($parent);
443+
$newVisitedPaths = [...$visitedPaths, $this->getName()];
444+
445+
foreach ($relationModel->resolveEagerRelations($newParent, $newVisitedPaths) as $name => $nestedEagerRelation) {
429446
$relations[$name] = $nestedEagerRelation;
430447
}
431448
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\Tempest\Fixtures\Models;
6+
7+
use Tempest\Database\Eager;
8+
use Tempest\Database\IsDatabaseModel;
9+
use Tempest\Database\PrimaryKey;
10+
use Tempest\Database\Table;
11+
12+
#[Table('profiles_with_eager')]
13+
final class ProfileWithEager
14+
{
15+
use IsDatabaseModel;
16+
17+
public function __construct(
18+
public PrimaryKey $id,
19+
public string $bio,
20+
#[Eager]
21+
public ?UserWithEager $user = null,
22+
) {}
23+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\Tempest\Fixtures\Models;
6+
7+
use Tempest\Database\Eager;
8+
use Tempest\Database\IsDatabaseModel;
9+
use Tempest\Database\PrimaryKey;
10+
use Tempest\Database\Table;
11+
12+
#[Table('users_with_eager')]
13+
final class UserWithEager
14+
{
15+
use IsDatabaseModel;
16+
17+
public function __construct(
18+
public PrimaryKey $id,
19+
public string $name,
20+
#[Eager]
21+
public ?ProfileWithEager $profile = null,
22+
) {}
23+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\Tempest\Integration\Database;
6+
7+
use Tests\Tempest\Fixtures\Models\UserWithEager;
8+
use Tests\Tempest\Integration\FrameworkIntegrationTestCase;
9+
10+
use function Tempest\Database\inspect;
11+
12+
/**
13+
* @internal
14+
*/
15+
final class CircularEagerLoadingTest extends FrameworkIntegrationTestCase
16+
{
17+
public function test_circular_eager_loading_does_not_cause_infinite_loop(): void
18+
{
19+
$userInspector = inspect(UserWithEager::class);
20+
$eagerRelations = $userInspector->resolveEagerRelations();
21+
22+
$this->assertArrayHasKey('profile', $eagerRelations);
23+
$this->assertArrayNotHasKey('profile.user', $eagerRelations);
24+
$this->assertCount(1, $eagerRelations);
25+
}
26+
27+
public function test_circular_with_relations_does_not_cause_infinite_loop(): void
28+
{
29+
$userInspector = inspect(UserWithEager::class);
30+
$relations = $userInspector->resolveRelations('profile.user.profile');
31+
32+
$this->assertArrayHasKey('profile', $relations);
33+
$this->assertArrayHasKey('user', $relations);
34+
$this->assertCount(2, $relations);
35+
}
36+
}

0 commit comments

Comments
 (0)