Skip to content

[12.x] Fixed an issue when calling hasNested with a first relationship of type morphTo. #56512

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,23 @@ protected function hasNested($relations, $operator = '>=', $count = 1, $boolean
{
$relations = explode('.', $relations);

$initialRelations = [...$relations];

$doesntHave = $operator === '<' && $count === 1;

if ($doesntHave) {
$operator = '>=';
$count = 1;
}

$closure = function ($q) use (&$closure, &$relations, $operator, $count, $callback) {
$closure = function ($q) use (&$closure, &$relations, $operator, $count, $callback, $initialRelations) {
// If the same closure is called multiple times, reset the relation array to loop through them again...
if ($count === 1 && empty($relations)) {
$relations = [...$initialRelations];

array_shift($relations);
}

// In order to nest "has", we need to add count relation constraints on the
// callback Closure. We'll do this by simply passing the Closure its own
// reference to itself so it calls itself recursively on each segment.
Expand Down
88 changes: 88 additions & 0 deletions tests/Database/DatabaseEloquentBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1727,6 +1727,64 @@ public function testHasNested()
$this->assertEquals($builder->toSql(), $result);
}

public function testHasNestedWithMorphTo()
{
$model = new EloquentBuilderTestModelParentStub;
$connection = $this->mockConnectionForModel($model, '');

$morphToKey = $model->morph()->getMorphType();

$connection->shouldReceive('select')->once()->andReturn([
[$morphToKey => EloquentBuilderTestModelFarRelatedStub::class],
[$morphToKey => EloquentBuilderTestModelOtherFarRelatedStub::class],
]);

$builder = $model->orWhereHasMorph('morph', [EloquentBuilderTestModelFarRelatedStub::class], function ($q) {
$q->has('baz');
})->orWhereHasMorph('morph', [EloquentBuilderTestModelOtherFarRelatedStub::class], function ($q) {
$q->has('baz');
});

$results = $model->has('morph.baz')->toSql();

// we need to adjust the expected builder because some parathesis are added,
// which doesn't impact the behavior of the test.

$builderSql = $builder->toSql();
$builderSql = str_replace(')))) or ((', '))) or (', $builderSql);

$this->assertSame($builderSql, $results);
}

public function testHasNestedWithMorphToAndMultipleSubRelations()
{
$model = new EloquentBuilderTestModelParentStub;
$connection = $this->mockConnectionForModel($model, '');

$morphToKey = $model->morph()->getMorphType();

$connection->shouldReceive('select')->once()->andReturn([
[$morphToKey => EloquentBuilderTestModelFarRelatedStub::class],
[$morphToKey => EloquentBuilderTestModelOtherFarRelatedStub::class],
]);

$builder = $model->orWhereHasMorph('morph', [EloquentBuilderTestModelFarRelatedStub::class], function ($q) {
$q->has('baz.bam');
})->orWhereHasMorph('morph', [EloquentBuilderTestModelOtherFarRelatedStub::class], function ($q) {
$q->has('baz.bam');
});

$results = $model->has('morph.baz.bam')->toSql();

// we need to adjust the expected builder because some parathesis are added,
// which doesn't impact the behavior of the test.

$builderSql = $builder->toSql();
$builderSql = str_replace(')))) or ((', '))) or (', $builderSql);

$this->assertSame($builderSql, $results);
}

public function testOrHasNested()
{
$model = new EloquentBuilderTestModelParentStub;
Expand Down Expand Up @@ -2683,6 +2741,8 @@ protected function mockConnectionForModel($model, $database)
$resolver = m::mock(ConnectionResolverInterface::class, ['connection' => $connection]);
$class = get_class($model);
$class::setConnectionResolver($resolver);

return $connection;
}

protected function getBuilder()
Expand Down Expand Up @@ -2835,6 +2895,11 @@ public function baz()
{
return $this->hasMany(EloquentBuilderTestModelFarRelatedStub::class);
}

public function bam()
{
return $this->hasMany(EloquentBuilderTestModelOtherFarRelatedStub::class);
}
}

class EloquentBuilderTestModelFarRelatedStub extends Model
Expand All @@ -2848,6 +2913,29 @@ public function roles()
'self_id',
);
}

public function baz()
{
return $this->belongsTo(EloquentBuilderTestModelCloseRelatedStub::class);
}
}

class EloquentBuilderTestModelOtherFarRelatedStub extends Model
{
public function roles()
{
return $this->belongsToMany(
EloquentBuilderTestModelParentStub::class,
'user_role',
'related_id',
'self_id',
);
}

public function baz()
{
return $this->belongsTo(EloquentBuilderTestModelCloseRelatedStub::class);
}
}

class EloquentBuilderTestModelSelfRelatedStub extends Model
Expand Down