Skip to content

Commit

Permalink
Added new Eloquent methods: whereDoesntHaveRelation, `orWhereDoesnt…
Browse files Browse the repository at this point in the history
…HaveRelation`, `whereMorphDoesntHaveRelation` and `orWhereMorphDoesntHaveRelation` (#53996)
  • Loading branch information
andrey-helldar authored Dec 26, 2024
1 parent 6c9e29f commit f826d0f
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 0 deletions.
74 changes: 74 additions & 0 deletions src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,46 @@ public function orWhereRelation($relation, $column, $operator = null, $value = n
});
}

/**
* Add a basic count / exists condition to a relationship query.
*
* @param \Illuminate\Database\Eloquent\Relations\Relation<*, *, *>|string $relation
* @param \Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column
* @param mixed $operator
* @param mixed $value
* @return $this
*/
public function whereDoesntHaveRelation($relation, $column, $operator = null, $value = null)
{
return $this->whereDoesntHave($relation, function ($query) use ($column, $operator, $value) {
if ($column instanceof Closure) {
$column($query);
} else {
$query->where($column, $operator, $value);
}
});
}

/**
* Add an "or where" clause to a relationship query.
*
* @param \Illuminate\Database\Eloquent\Relations\Relation<*, *, *>|string $relation
* @param \Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column
* @param mixed $operator
* @param mixed $value
* @return $this
*/
public function orWhereDoesntHaveRelation($relation, $column, $operator = null, $value = null)
{
return $this->orWhereDoesntHave($relation, function ($query) use ($column, $operator, $value) {
if ($column instanceof Closure) {
$column($query);
} else {
$query->where($column, $operator, $value);
}
});
}

/**
* Add a polymorphic relationship condition to the query with a where clause.
*
Expand Down Expand Up @@ -458,6 +498,40 @@ public function orWhereMorphRelation($relation, $types, $column, $operator = nul
});
}

/**
* Add a polymorphic relationship condition to the query with a doesn't have clause.
*
* @param \Illuminate\Database\Eloquent\Relations\MorphTo<*, *>|string $relation
* @param string|array $types
* @param \Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column
* @param mixed $operator
* @param mixed $value
* @return $this
*/
public function whereMorphDoesntHaveRelation($relation, $types, $column, $operator = null, $value = null)
{
return $this->whereDoesntHaveMorph($relation, $types, function ($query) use ($column, $operator, $value) {
$query->where($column, $operator, $value);
});
}

/**
* Add a polymorphic relationship condition to the query with an "or doesn't have" clause.
*
* @param \Illuminate\Database\Eloquent\Relations\MorphTo<*, *>|string $relation
* @param string|array $types
* @param \Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column
* @param mixed $operator
* @param mixed $value
* @return $this
*/
public function orWhereMorphDoesntHaveRelation($relation, $types, $column, $operator = null, $value = null)
{
return $this->orWhereDoesntHaveMorph($relation, $types, function ($query) use ($column, $operator, $value) {
$query->where($column, $operator, $value);
});
}

/**
* Add a morph-to relationship condition to the query.
*
Expand Down
82 changes: 82 additions & 0 deletions tests/Integration/Database/EloquentWhereHasTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,44 @@ public function testOrWhereRelationCallback($callbackEloquent, $callbackQuery)
$this->assertEquals($userOrWhereHas->first()->id, $query->first()->id);
}

/**
* Check that the 'whereDoesntHaveRelation' callback function works.
*/
#[DataProvider('dataProviderWhereRelationCallback')]
public function testWhereDoesntRelationCallback($callbackEloquent, $callbackQuery)
{
$userWhereDoesntRelation = User::whereDoesntHaveRelation('posts', $callbackEloquent);
$userWhereHas = User::whereDoesntHave('posts', $callbackEloquent);
$query = DB::table('users')->whereNotExists($callbackQuery);

$this->assertEquals($userWhereDoesntRelation->getQuery()->toSql(), $query->toSql());
$this->assertEquals($userWhereDoesntRelation->getQuery()->toSql(), $userWhereHas->toSql());
$this->assertEquals($userWhereHas->getQuery()->toSql(), $query->toSql());

$this->assertEquals($userWhereDoesntRelation->first()->id, $query->first()->id);
$this->assertEquals($userWhereDoesntRelation->first()->id, $userWhereHas->first()->id);
$this->assertEquals($userWhereHas->first()->id, $query->first()->id);
}

/**
* Check that the 'orWhereDoesntRelation' callback function works.
*/
#[DataProvider('dataProviderWhereRelationCallback')]
public function testOrWhereDoesntRelationCallback($callbackEloquent, $callbackQuery)
{
$userOrWhereDoesntRelation = User::orWhereDoesntHaveRelation('posts', $callbackEloquent);
$userOrWhereHas = User::orWhereDoesntHave('posts', $callbackEloquent);
$query = DB::table('users')->orWhereNotExists($callbackQuery);

$this->assertEquals($userOrWhereDoesntRelation->getQuery()->toSql(), $query->toSql());
$this->assertEquals($userOrWhereDoesntRelation->getQuery()->toSql(), $userOrWhereHas->toSql());
$this->assertEquals($userOrWhereHas->getQuery()->toSql(), $query->toSql());

$this->assertEquals($userOrWhereDoesntRelation->first()->id, $query->first()->id);
$this->assertEquals($userOrWhereDoesntRelation->first()->id, $userOrWhereHas->first()->id);
$this->assertEquals($userOrWhereHas->first()->id, $query->first()->id);
}

public static function dataProviderWhereRelationCallback()
{
$callbackArray = function ($value) {
Expand Down Expand Up @@ -158,6 +196,50 @@ public function testOrWhereMorphRelation()
$this->assertEquals([1, 2], $comments->pluck('id')->all());
}

public function testWhereDoesntHaveRelation()
{
$users = User::whereDoesntHaveRelation('posts', 'public', true)->get();

$this->assertEquals([2], $users->pluck('id')->all());
}

public function testOrWhereDoesntHaveRelation()
{
$users = User::whereDoesntHaveRelation('posts', 'public', true)->orWhereDoesntHaveRelation('posts', 'public', false)->get();

$this->assertEquals([1, 2], $users->pluck('id')->all());
}

public function testNestedWhereDoesntHaveRelation()
{
$texts = User::whereDoesntHaveRelation('posts.texts', 'content', 'test')->get();

$this->assertEquals([2], $texts->pluck('id')->all());
}

public function testNestedOrWhereDoesntHaveRelation()
{
$texts = User::whereDoesntHaveRelation('posts.texts', 'content', 'test')->orWhereDoesntHaveRelation('posts.texts', 'content', 'test2')->get();

$this->assertEquals([1, 2], $texts->pluck('id')->all());
}

public function testWhereMorphDoesntHaveRelation()
{
$comments = Comment::whereMorphDoesntHaveRelation('commentable', '*', 'public', true)->get();

$this->assertEquals([2], $comments->pluck('id')->all());
}

public function testOrWhereMorphDoesntHaveRelation()
{
$comments = Comment::whereMorphDoesntHaveRelation('commentable', '*', 'public', true)
->orWhereMorphDoesntHaveRelation('commentable', '*', 'public', false)
->get();

$this->assertEquals([1, 2], $comments->pluck('id')->all());
}

public function testWithCount()
{
$users = User::whereHas('posts', function ($query) {
Expand Down

0 comments on commit f826d0f

Please sign in to comment.