Skip to content

Commit bb5dfb5

Browse files
nicDamoursNicolas D'Amourstaylorotwell
authored
[12.x] Fixed an issue when calling hasNested with a first relationship of type morphTo. (#56512)
* [BUGFIX] #56490 Fixed an issue with hasNested fn when the first relation is a morphTo relationship. The `$relation` array was not reset, so we were calling the `has` with a null relation. * [BUGFIX] #56490 Fixed formatting issue from style-ci. * [BUGFIX] #56490 Fixed formatting issue from style-ci. * Update QueriesRelationships.php --------- Co-authored-by: Nicolas D'Amours <[email protected]> Co-authored-by: Taylor Otwell <[email protected]>
1 parent d74b1c1 commit bb5dfb5

File tree

2 files changed

+98
-1
lines changed

2 files changed

+98
-1
lines changed

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,14 +89,23 @@ protected function hasNested($relations, $operator = '>=', $count = 1, $boolean
8989
{
9090
$relations = explode('.', $relations);
9191

92+
$initialRelations = [...$relations];
93+
9294
$doesntHave = $operator === '<' && $count === 1;
9395

9496
if ($doesntHave) {
9597
$operator = '>=';
9698
$count = 1;
9799
}
98100

99-
$closure = function ($q) use (&$closure, &$relations, $operator, $count, $callback) {
101+
$closure = function ($q) use (&$closure, &$relations, $operator, $count, $callback, $initialRelations) {
102+
// If the same closure is called multiple times, reset the relation array to loop through them again...
103+
if ($count === 1 && empty($relations)) {
104+
$relations = [...$initialRelations];
105+
106+
array_shift($relations);
107+
}
108+
100109
// In order to nest "has", we need to add count relation constraints on the
101110
// callback Closure. We'll do this by simply passing the Closure its own
102111
// reference to itself so it calls itself recursively on each segment.

tests/Database/DatabaseEloquentBuilderTest.php

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1727,6 +1727,64 @@ public function testHasNested()
17271727
$this->assertEquals($builder->toSql(), $result);
17281728
}
17291729

1730+
public function testHasNestedWithMorphTo()
1731+
{
1732+
$model = new EloquentBuilderTestModelParentStub;
1733+
$connection = $this->mockConnectionForModel($model, '');
1734+
1735+
$morphToKey = $model->morph()->getMorphType();
1736+
1737+
$connection->shouldReceive('select')->once()->andReturn([
1738+
[$morphToKey => EloquentBuilderTestModelFarRelatedStub::class],
1739+
[$morphToKey => EloquentBuilderTestModelOtherFarRelatedStub::class],
1740+
]);
1741+
1742+
$builder = $model->orWhereHasMorph('morph', [EloquentBuilderTestModelFarRelatedStub::class], function ($q) {
1743+
$q->has('baz');
1744+
})->orWhereHasMorph('morph', [EloquentBuilderTestModelOtherFarRelatedStub::class], function ($q) {
1745+
$q->has('baz');
1746+
});
1747+
1748+
$results = $model->has('morph.baz')->toSql();
1749+
1750+
// we need to adjust the expected builder because some parathesis are added,
1751+
// which doesn't impact the behavior of the test.
1752+
1753+
$builderSql = $builder->toSql();
1754+
$builderSql = str_replace(')))) or ((', '))) or (', $builderSql);
1755+
1756+
$this->assertSame($builderSql, $results);
1757+
}
1758+
1759+
public function testHasNestedWithMorphToAndMultipleSubRelations()
1760+
{
1761+
$model = new EloquentBuilderTestModelParentStub;
1762+
$connection = $this->mockConnectionForModel($model, '');
1763+
1764+
$morphToKey = $model->morph()->getMorphType();
1765+
1766+
$connection->shouldReceive('select')->once()->andReturn([
1767+
[$morphToKey => EloquentBuilderTestModelFarRelatedStub::class],
1768+
[$morphToKey => EloquentBuilderTestModelOtherFarRelatedStub::class],
1769+
]);
1770+
1771+
$builder = $model->orWhereHasMorph('morph', [EloquentBuilderTestModelFarRelatedStub::class], function ($q) {
1772+
$q->has('baz.bam');
1773+
})->orWhereHasMorph('morph', [EloquentBuilderTestModelOtherFarRelatedStub::class], function ($q) {
1774+
$q->has('baz.bam');
1775+
});
1776+
1777+
$results = $model->has('morph.baz.bam')->toSql();
1778+
1779+
// we need to adjust the expected builder because some parathesis are added,
1780+
// which doesn't impact the behavior of the test.
1781+
1782+
$builderSql = $builder->toSql();
1783+
$builderSql = str_replace(')))) or ((', '))) or (', $builderSql);
1784+
1785+
$this->assertSame($builderSql, $results);
1786+
}
1787+
17301788
public function testOrHasNested()
17311789
{
17321790
$model = new EloquentBuilderTestModelParentStub;
@@ -2747,6 +2805,8 @@ protected function mockConnectionForModel($model, $database)
27472805
$resolver = m::mock(ConnectionResolverInterface::class, ['connection' => $connection]);
27482806
$class = get_class($model);
27492807
$class::setConnectionResolver($resolver);
2808+
2809+
return $connection;
27502810
}
27512811

27522812
protected function getBuilder()
@@ -2899,6 +2959,11 @@ public function baz()
28992959
{
29002960
return $this->hasMany(EloquentBuilderTestModelFarRelatedStub::class);
29012961
}
2962+
2963+
public function bam()
2964+
{
2965+
return $this->hasMany(EloquentBuilderTestModelOtherFarRelatedStub::class);
2966+
}
29022967
}
29032968

29042969
class EloquentBuilderTestModelFarRelatedStub extends Model
@@ -2912,6 +2977,29 @@ public function roles()
29122977
'self_id',
29132978
);
29142979
}
2980+
2981+
public function baz()
2982+
{
2983+
return $this->belongsTo(EloquentBuilderTestModelCloseRelatedStub::class);
2984+
}
2985+
}
2986+
2987+
class EloquentBuilderTestModelOtherFarRelatedStub extends Model
2988+
{
2989+
public function roles()
2990+
{
2991+
return $this->belongsToMany(
2992+
EloquentBuilderTestModelParentStub::class,
2993+
'user_role',
2994+
'related_id',
2995+
'self_id',
2996+
);
2997+
}
2998+
2999+
public function baz()
3000+
{
3001+
return $this->belongsTo(EloquentBuilderTestModelCloseRelatedStub::class);
3002+
}
29153003
}
29163004

29173005
class EloquentBuilderTestModelSelfRelatedStub extends Model

0 commit comments

Comments
 (0)