Skip to content

Commit f826d0f

Browse files
Added new Eloquent methods: whereDoesntHaveRelation, orWhereDoesntHaveRelation, whereMorphDoesntHaveRelation and orWhereMorphDoesntHaveRelation (#53996)
1 parent 6c9e29f commit f826d0f

File tree

2 files changed

+156
-0
lines changed

2 files changed

+156
-0
lines changed

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

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,46 @@ public function orWhereRelation($relation, $column, $operator = null, $value = n
424424
});
425425
}
426426

427+
/**
428+
* Add a basic count / exists condition to a relationship query.
429+
*
430+
* @param \Illuminate\Database\Eloquent\Relations\Relation<*, *, *>|string $relation
431+
* @param \Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column
432+
* @param mixed $operator
433+
* @param mixed $value
434+
* @return $this
435+
*/
436+
public function whereDoesntHaveRelation($relation, $column, $operator = null, $value = null)
437+
{
438+
return $this->whereDoesntHave($relation, function ($query) use ($column, $operator, $value) {
439+
if ($column instanceof Closure) {
440+
$column($query);
441+
} else {
442+
$query->where($column, $operator, $value);
443+
}
444+
});
445+
}
446+
447+
/**
448+
* Add an "or where" clause to a relationship query.
449+
*
450+
* @param \Illuminate\Database\Eloquent\Relations\Relation<*, *, *>|string $relation
451+
* @param \Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column
452+
* @param mixed $operator
453+
* @param mixed $value
454+
* @return $this
455+
*/
456+
public function orWhereDoesntHaveRelation($relation, $column, $operator = null, $value = null)
457+
{
458+
return $this->orWhereDoesntHave($relation, function ($query) use ($column, $operator, $value) {
459+
if ($column instanceof Closure) {
460+
$column($query);
461+
} else {
462+
$query->where($column, $operator, $value);
463+
}
464+
});
465+
}
466+
427467
/**
428468
* Add a polymorphic relationship condition to the query with a where clause.
429469
*
@@ -458,6 +498,40 @@ public function orWhereMorphRelation($relation, $types, $column, $operator = nul
458498
});
459499
}
460500

501+
/**
502+
* Add a polymorphic relationship condition to the query with a doesn't have clause.
503+
*
504+
* @param \Illuminate\Database\Eloquent\Relations\MorphTo<*, *>|string $relation
505+
* @param string|array $types
506+
* @param \Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column
507+
* @param mixed $operator
508+
* @param mixed $value
509+
* @return $this
510+
*/
511+
public function whereMorphDoesntHaveRelation($relation, $types, $column, $operator = null, $value = null)
512+
{
513+
return $this->whereDoesntHaveMorph($relation, $types, function ($query) use ($column, $operator, $value) {
514+
$query->where($column, $operator, $value);
515+
});
516+
}
517+
518+
/**
519+
* Add a polymorphic relationship condition to the query with an "or doesn't have" clause.
520+
*
521+
* @param \Illuminate\Database\Eloquent\Relations\MorphTo<*, *>|string $relation
522+
* @param string|array $types
523+
* @param \Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column
524+
* @param mixed $operator
525+
* @param mixed $value
526+
* @return $this
527+
*/
528+
public function orWhereMorphDoesntHaveRelation($relation, $types, $column, $operator = null, $value = null)
529+
{
530+
return $this->orWhereDoesntHaveMorph($relation, $types, function ($query) use ($column, $operator, $value) {
531+
$query->where($column, $operator, $value);
532+
});
533+
}
534+
461535
/**
462536
* Add a morph-to relationship condition to the query.
463537
*

tests/Integration/Database/EloquentWhereHasTest.php

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,44 @@ public function testOrWhereRelationCallback($callbackEloquent, $callbackQuery)
8686
$this->assertEquals($userOrWhereHas->first()->id, $query->first()->id);
8787
}
8888

89+
/**
90+
* Check that the 'whereDoesntHaveRelation' callback function works.
91+
*/
92+
#[DataProvider('dataProviderWhereRelationCallback')]
93+
public function testWhereDoesntRelationCallback($callbackEloquent, $callbackQuery)
94+
{
95+
$userWhereDoesntRelation = User::whereDoesntHaveRelation('posts', $callbackEloquent);
96+
$userWhereHas = User::whereDoesntHave('posts', $callbackEloquent);
97+
$query = DB::table('users')->whereNotExists($callbackQuery);
98+
99+
$this->assertEquals($userWhereDoesntRelation->getQuery()->toSql(), $query->toSql());
100+
$this->assertEquals($userWhereDoesntRelation->getQuery()->toSql(), $userWhereHas->toSql());
101+
$this->assertEquals($userWhereHas->getQuery()->toSql(), $query->toSql());
102+
103+
$this->assertEquals($userWhereDoesntRelation->first()->id, $query->first()->id);
104+
$this->assertEquals($userWhereDoesntRelation->first()->id, $userWhereHas->first()->id);
105+
$this->assertEquals($userWhereHas->first()->id, $query->first()->id);
106+
}
107+
108+
/**
109+
* Check that the 'orWhereDoesntRelation' callback function works.
110+
*/
111+
#[DataProvider('dataProviderWhereRelationCallback')]
112+
public function testOrWhereDoesntRelationCallback($callbackEloquent, $callbackQuery)
113+
{
114+
$userOrWhereDoesntRelation = User::orWhereDoesntHaveRelation('posts', $callbackEloquent);
115+
$userOrWhereHas = User::orWhereDoesntHave('posts', $callbackEloquent);
116+
$query = DB::table('users')->orWhereNotExists($callbackQuery);
117+
118+
$this->assertEquals($userOrWhereDoesntRelation->getQuery()->toSql(), $query->toSql());
119+
$this->assertEquals($userOrWhereDoesntRelation->getQuery()->toSql(), $userOrWhereHas->toSql());
120+
$this->assertEquals($userOrWhereHas->getQuery()->toSql(), $query->toSql());
121+
122+
$this->assertEquals($userOrWhereDoesntRelation->first()->id, $query->first()->id);
123+
$this->assertEquals($userOrWhereDoesntRelation->first()->id, $userOrWhereHas->first()->id);
124+
$this->assertEquals($userOrWhereHas->first()->id, $query->first()->id);
125+
}
126+
89127
public static function dataProviderWhereRelationCallback()
90128
{
91129
$callbackArray = function ($value) {
@@ -158,6 +196,50 @@ public function testOrWhereMorphRelation()
158196
$this->assertEquals([1, 2], $comments->pluck('id')->all());
159197
}
160198

199+
public function testWhereDoesntHaveRelation()
200+
{
201+
$users = User::whereDoesntHaveRelation('posts', 'public', true)->get();
202+
203+
$this->assertEquals([2], $users->pluck('id')->all());
204+
}
205+
206+
public function testOrWhereDoesntHaveRelation()
207+
{
208+
$users = User::whereDoesntHaveRelation('posts', 'public', true)->orWhereDoesntHaveRelation('posts', 'public', false)->get();
209+
210+
$this->assertEquals([1, 2], $users->pluck('id')->all());
211+
}
212+
213+
public function testNestedWhereDoesntHaveRelation()
214+
{
215+
$texts = User::whereDoesntHaveRelation('posts.texts', 'content', 'test')->get();
216+
217+
$this->assertEquals([2], $texts->pluck('id')->all());
218+
}
219+
220+
public function testNestedOrWhereDoesntHaveRelation()
221+
{
222+
$texts = User::whereDoesntHaveRelation('posts.texts', 'content', 'test')->orWhereDoesntHaveRelation('posts.texts', 'content', 'test2')->get();
223+
224+
$this->assertEquals([1, 2], $texts->pluck('id')->all());
225+
}
226+
227+
public function testWhereMorphDoesntHaveRelation()
228+
{
229+
$comments = Comment::whereMorphDoesntHaveRelation('commentable', '*', 'public', true)->get();
230+
231+
$this->assertEquals([2], $comments->pluck('id')->all());
232+
}
233+
234+
public function testOrWhereMorphDoesntHaveRelation()
235+
{
236+
$comments = Comment::whereMorphDoesntHaveRelation('commentable', '*', 'public', true)
237+
->orWhereMorphDoesntHaveRelation('commentable', '*', 'public', false)
238+
->get();
239+
240+
$this->assertEquals([1, 2], $comments->pluck('id')->all());
241+
}
242+
161243
public function testWithCount()
162244
{
163245
$users = User::whereHas('posts', function ($query) {

0 commit comments

Comments
 (0)