Skip to content

Commit fef0498

Browse files
[12.x] Improve circular relation check in Automatic Relation Loading (#55542)
* [12.x] Improve circular relation check in Automatic Relation Loading * [12.x] Improve circular relation check in Automatic Relation Loading * Remove unnecesary argument * Add more tests * Fix cs * update tests * Update HasRelationships.php --------- Co-authored-by: Taylor Otwell <taylor@laravel.com>
1 parent 39fa9f4 commit fef0498

File tree

3 files changed

+73
-9
lines changed

3 files changed

+73
-9
lines changed

src/Illuminate/Database/Eloquent/Collection.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -761,7 +761,7 @@ public function withRelationshipAutoloading()
761761

762762
foreach ($this as $model) {
763763
if (! $model->hasRelationAutoloadCallback()) {
764-
$model->autoloadRelationsUsing($callback);
764+
$model->autoloadRelationsUsing($callback, $this);
765765
}
766766
}
767767

src/Illuminate/Database/Eloquent/Concerns/HasRelationships.php

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@ trait HasRelationships
4646
*/
4747
protected $relationAutoloadCallback = null;
4848

49+
/**
50+
* The relationship autoloader callback context.
51+
*
52+
* @var mixed
53+
*/
54+
protected $relationAutoloadContext = null;
55+
4956
/**
5057
* The many to many relationship methods.
5158
*
@@ -118,10 +125,16 @@ public function hasRelationAutoloadCallback()
118125
*/
119126
public function autoloadRelationsUsing(Closure $callback, $context = null)
120127
{
128+
// Prevent circular relation autoloading...
129+
if ($context && $this->relationAutoloadContext === $context) {
130+
return $this;
131+
}
132+
121133
$this->relationAutoloadCallback = $callback;
134+
$this->relationAutoloadContext = $context;
122135

123136
foreach ($this->relations as $key => $value) {
124-
$this->propagateRelationAutoloadCallbackToRelation($key, $value, $context);
137+
$this->propagateRelationAutoloadCallbackToRelation($key, $value);
125138
}
126139

127140
return $this;
@@ -163,10 +176,9 @@ protected function invokeRelationAutoloadCallbackFor($key, $tuples)
163176
*
164177
* @param string $key
165178
* @param mixed $models
166-
* @param mixed $context
167179
* @return void
168180
*/
169-
protected function propagateRelationAutoloadCallbackToRelation($key, $models, $context = null)
181+
protected function propagateRelationAutoloadCallbackToRelation($key, $models)
170182
{
171183
if (! $this->hasRelationAutoloadCallback() || ! $models) {
172184
return;
@@ -183,10 +195,7 @@ protected function propagateRelationAutoloadCallbackToRelation($key, $models, $c
183195
$callback = fn (array $tuples) => $this->invokeRelationAutoloadCallbackFor($key, $tuples);
184196

185197
foreach ($models as $model) {
186-
// Check if relation autoload contexts are different to avoid circular relation autoload...
187-
if ((is_null($context) || $context !== $model) && is_object($model) && method_exists($model, 'autoloadRelationsUsing')) {
188-
$model->autoloadRelationsUsing($callback, $context);
189-
}
198+
$model->autoloadRelationsUsing($callback, $this->relationAutoloadContext);
190199
}
191200
}
192201

@@ -1086,7 +1095,7 @@ public function setRelation($relation, $value)
10861095
{
10871096
$this->relations[$relation] = $value;
10881097

1089-
$this->propagateRelationAutoloadCallbackToRelation($relation, $value, $this);
1098+
$this->propagateRelationAutoloadCallbackToRelation($relation, $value);
10901099

10911100
return $this;
10921101
}

tests/Integration/Database/EloquentModelRelationAutoloadTest.php

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ public function testRelationAutoloadForCollection()
6161
$this->assertCount(2, DB::getQueryLog());
6262
$this->assertCount(3, $likes);
6363
$this->assertTrue($posts[0]->comments[0]->relationLoaded('likes'));
64+
65+
DB::disableQueryLog();
6466
}
6567

6668
public function testRelationAutoloadForSingleModel()
@@ -84,6 +86,8 @@ public function testRelationAutoloadForSingleModel()
8486
$this->assertCount(2, DB::getQueryLog());
8587
$this->assertCount(2, $likes);
8688
$this->assertTrue($post->comments[0]->relationLoaded('likes'));
89+
90+
DB::disableQueryLog();
8791
}
8892

8993
public function testRelationAutoloadWithSerialization()
@@ -109,6 +113,50 @@ public function testRelationAutoloadWithSerialization()
109113
$this->assertCount(2, DB::getQueryLog());
110114

111115
Model::automaticallyEagerLoadRelationships(false);
116+
117+
DB::disableQueryLog();
118+
}
119+
120+
public function testRelationAutoloadWithCircularRelations()
121+
{
122+
$post = Post::create();
123+
$comment1 = $post->comments()->create(['parent_id' => null]);
124+
$comment2 = $post->comments()->create(['parent_id' => $comment1->id]);
125+
$post->likes()->create();
126+
127+
DB::enableQueryLog();
128+
129+
$post->withRelationshipAutoloading();
130+
$comment = $post->comments->first();
131+
$comment->setRelation('post', $post);
132+
133+
$this->assertCount(1, $post->likes);
134+
135+
$this->assertCount(2, DB::getQueryLog());
136+
137+
DB::disableQueryLog();
138+
}
139+
140+
public function testRelationAutoloadWithChaperoneRelations()
141+
{
142+
Model::automaticallyEagerLoadRelationships();
143+
144+
$post = Post::create();
145+
$comment1 = $post->comments()->create(['parent_id' => null]);
146+
$comment2 = $post->comments()->create(['parent_id' => $comment1->id]);
147+
$post->likes()->create();
148+
149+
DB::enableQueryLog();
150+
151+
$post->load('commentsWithChaperone');
152+
153+
$this->assertCount(1, $post->likes);
154+
155+
$this->assertCount(2, DB::getQueryLog());
156+
157+
Model::automaticallyEagerLoadRelationships(false);
158+
159+
DB::disableQueryLog();
112160
}
113161

114162
public function testRelationAutoloadVariousNestedMorphRelations()
@@ -163,6 +211,8 @@ public function testRelationAutoloadVariousNestedMorphRelations()
163211
$this->assertCount(2, $videos);
164212
$this->assertTrue($videoLike->relationLoaded('likeable'));
165213
$this->assertTrue($videoLike->likeable->relationLoaded('commentable'));
214+
215+
DB::disableQueryLog();
166216
}
167217
}
168218

@@ -197,6 +247,11 @@ public function comments()
197247
return $this->morphMany(Comment::class, 'commentable');
198248
}
199249

250+
public function commentsWithChaperone()
251+
{
252+
return $this->morphMany(Comment::class, 'commentable')->chaperone();
253+
}
254+
200255
public function likes()
201256
{
202257
return $this->morphMany(Like::class, 'likeable');

0 commit comments

Comments
 (0)