Skip to content
Closed
50 changes: 45 additions & 5 deletions src/Illuminate/Database/Eloquent/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -256,13 +256,29 @@ public function loadMissing($relations)
*/
public function loadMissingRelationshipChain(array $tuples)
{
[$relation, $class] = array_shift($tuples);
[$relationData, $class] = array_shift($tuples);

$this->filter(function ($model) use ($relation, $class) {
$relation = $relationData;
$columns = null;

if (is_string($relation) && str_contains($relation, ':')) {
[$relation, $columnString] = explode(':', $relationData, 2);
$columns = explode(',', $columnString);
}

$filtered = $this->filter(function ($model) use ($relation, $class) {
return ! is_null($model) &&
! $model->relationLoaded($relation) &&
$model::class === $class;
})->load($relation);
});

if ($columns) {
$filtered->load([$relation => function ($query) use ($columns) {
$query->select($columns);
}]);
} else {
$filtered->load($relation);
}

if (empty($tuples)) {
return;
Expand Down Expand Up @@ -753,11 +769,35 @@ protected function duplicateComparator($strict)
/**
* Enable relationship autoloading for all models in this collection.
*
* @param array|string|null $relations
* @return $this
*/
public function withRelationshipAutoloading()
public function withRelationshipAutoloading($relations = null)
{
$callback = fn ($tuples) => $this->loadMissingRelationshipChain($tuples);
$relationMap = [];

if (! is_null($relations)) {
if (is_string($relations)) {
$relations = [$relations];
}

foreach ($relations as $relation) {
$baseName = explode(':', $relation)[0];
$relationMap[$baseName] = $relation;
}
}

$callback = function ($tuples) use ($relationMap) {
if (! empty($relationMap)) {
$relationName = explode(':', $tuples[0][0])[0];

if (isset($relationMap[$relationName])) {
$tuples[0][0] = $relationMap[$relationName];
}
}

$this->loadMissingRelationshipChain($tuples);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

todo: this part will be modified, can be improved overall

};

foreach ($this as $model) {
if (! $model->hasRelationAutoloadCallback()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1129,11 +1129,12 @@ public function setRelations(array $relations)
/**
* Enable relationship autoloading for this model.
*
* @param array|string|null $relations
* @return $this
*/
public function withRelationshipAutoloading()
public function withRelationshipAutoloading($relations = null)
{
$this->newCollection([$this])->withRelationshipAutoloading();
$this->newCollection([$this])->withRelationshipAutoloading($relations);

return $this;
}
Expand Down
98 changes: 98 additions & 0 deletions tests/Integration/Database/EloquentModelRelationAutoloadTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,104 @@ public function testRelationAutoloadForSingleModel()
DB::disableQueryLog();
}

public function testWithRelationshipAutoloadingSelectiveColumnsLoadsOnlySpecifiedColumns()
{
$post = Post::create();
$post->comments()->create([
'parent_id' => 123,
]);

DB::enableQueryLog();

$post->withRelationshipAutoloading([
'comments:id,commentable_id',
]);

$this->assertNotNull($post->comments[0]->id);
$this->assertFalse(array_key_exists('parent_id', $post->comments[0]->getAttributes()));
$this->assertTrue($post->relationLoaded('comments'));

DB::disableQueryLog();
}

public function testWithRelationshipAutoloadingSelectiveColumns()
{
$post = Post::create();
$comment1 = $post->comments()->create([
'parent_id' => null,
]);
$comment2 = $post->comments()->create([
'parent_id' => $comment1->id,
]);
$comment2->likes()->create();
$comment2->likes()->create();

$likes = [];

DB::enableQueryLog();

$post->withRelationshipAutoloading([
'comments:id,commentable_id',
]);

foreach ($post->comments as $comment) {
$likes = array_merge($likes, $comment->likes->all());
}

$this->assertCount(2, DB::getQueryLog());
$this->assertNotNull($post->comments[0]->id);
$this->assertFalse(array_key_exists('parent_id', $post->comments[0]->getAttributes()));
$this->assertTrue($post->relationLoaded('comments'));
$this->assertCount(2, $likes);
$this->assertTrue($post->comments[0]->relationLoaded('likes'));

DB::disableQueryLog();
}

public function testWithRelationshipAutoloadingSelectiveColumnsAcrossPolymorphicTypes()
{
$post = Post::create();
$video = Video::create();

$postComment1 = $post->comments()->create([
'parent_id' => null,
]);
$postComment2 = $post->comments()->create([
'parent_id' => $postComment1->id,
]);
$videoComment1 = $video->comments()->create([
'parent_id' => null,
]);
$videoComment2 = $video->comments()->create([
'parent_id' => $videoComment1->id,
]);

DB::enableQueryLog();

$post->withRelationshipAutoloading([
'comments:id,commentable_id,commentable_type',
]);
$video->withRelationshipAutoloading([
'comments:id,commentable_id,commentable_type,parent_id',
]);

$this->assertNotNull($post->comments[0]->id);
$this->assertNotNull($video->comments[0]->id);

$this->assertTrue($post->relationLoaded('comments'));
$this->assertTrue($video->relationLoaded('comments'));
$this->assertFalse(array_key_exists('parent_id', $post->comments[0]->getAttributes()));
$this->assertFalse(array_key_exists('parent_id', $post->comments[1]->getAttributes()));
$this->assertTrue(array_key_exists('parent_id', $video->comments[0]->getAttributes()));
$this->assertTrue(array_key_exists('parent_id', $video->comments[1]->getAttributes()));
$this->assertSame(Post::class, $post->comments[0]->commentable_type);
$this->assertSame(Video::class, $video->comments[0]->commentable_type);
$this->assertEquals($post->id, $post->comments[0]->commentable_id);
$this->assertEquals($video->id, $video->comments[0]->commentable_id);

DB::disableQueryLog();
}

public function testRelationAutoloadWithSerialization()
{
Model::automaticallyEagerLoadRelationships();
Expand Down