Skip to content

Commit 702c119

Browse files
authored
[9.x] Added Eloquent withWhereHas method (#42597)
* [9.x] Added Eloquent withWhereHas method * Fixed code style * Added withWhereHas tests. Related #42597 * Fixed code style typo. Related #42597
1 parent afe9c30 commit 702c119

6 files changed

+183
-0
lines changed

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,23 @@ public function whereHas($relation, Closure $callback = null, $operator = '>=',
152152
return $this->has($relation, $operator, $count, 'and', $callback);
153153
}
154154

155+
/**
156+
* Add a relationship count / exists condition to the query with where clauses.
157+
*
158+
* Also load the relationship with same condition.
159+
*
160+
* @param string $relation
161+
* @param \Closure|null $callback
162+
* @param string $operator
163+
* @param int $count
164+
* @return \Illuminate\Database\Eloquent\Builder|static
165+
*/
166+
public function withWhereHas($relation, Closure $callback = null, $operator = '>=', $count = 1)
167+
{
168+
return $this->whereHas($relation, $callback, $operator, $count)
169+
->with($callback ? [$relation => fn ($query) => $callback($query)] : $relation);
170+
}
171+
155172
/**
156173
* Add a relationship count / exists condition to the query with where clauses and an "or".
157174
*

tests/Database/DatabaseEloquentHasManyThroughIntegrationTest.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,18 @@ public function testWhereHasOnARelationWithCustomIntermediateAndLocalKey()
121121
$this->assertCount(1, $country);
122122
}
123123

124+
public function testWithWhereHasOnARelationWithCustomIntermediateAndLocalKey()
125+
{
126+
$this->seedData();
127+
$country = HasManyThroughIntermediateTestCountry::withWhereHas('posts', function ($query) {
128+
$query->where('title', 'A title');
129+
})->get();
130+
131+
$this->assertCount(1, $country);
132+
$this->assertTrue($country->first()->relationLoaded('posts'));
133+
$this->assertEquals($country->first()->posts->pluck('title')->unique()->toArray(), ['A title']);
134+
}
135+
124136
public function testFindMethod()
125137
{
126138
HasManyThroughTestCountry::create(['id' => 1, 'name' => 'United States of America', 'shortname' => 'us'])

tests/Database/DatabaseEloquentHasOneOfManyTest.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,27 @@ public function testHasNested()
267267
$this->assertFalse($found);
268268
}
269269

270+
public function testWithHasNested()
271+
{
272+
$user = HasOneOfManyTestUser::create();
273+
$previousLogin = $user->logins()->create();
274+
$latestLogin = $user->logins()->create();
275+
276+
$found = HasOneOfManyTestUser::withWhereHas('latest_login', function ($query) use ($latestLogin) {
277+
$query->where('logins.id', $latestLogin->id);
278+
})->first();
279+
280+
$this->assertTrue((bool) $found);
281+
$this->assertTrue($found->relationLoaded('latest_login'));
282+
$this->assertEquals($found->latest_login->id, $latestLogin->id);
283+
284+
$found = HasOneOfManyTestUser::withWhereHas('latest_login', function ($query) use ($previousLogin) {
285+
$query->where('logins.id', $previousLogin->id);
286+
})->exists();
287+
288+
$this->assertFalse($found);
289+
}
290+
270291
public function testHasCount()
271292
{
272293
$user = HasOneOfManyTestUser::create();

tests/Database/DatabaseEloquentHasOneThroughIntegrationTest.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,18 @@ public function testWhereHasOnARelationWithCustomIntermediateAndLocalKey()
116116
$this->assertCount(1, $position);
117117
}
118118

119+
public function testWithWhereHasOnARelationWithCustomIntermediateAndLocalKey()
120+
{
121+
$this->seedData();
122+
$position = HasOneThroughIntermediateTestPosition::withWhereHas('contract', function ($query) {
123+
$query->where('title', 'A title');
124+
})->get();
125+
126+
$this->assertCount(1, $position);
127+
$this->assertTrue($position->first()->relationLoaded('contract'));
128+
$this->assertEquals($position->first()->contract->pluck('title')->unique()->toArray(), ['A title']);
129+
}
130+
119131
public function testFirstOrFailThrowsAnException()
120132
{
121133
$this->expectException(ModelNotFoundException::class);

tests/Database/DatabaseEloquentIntegrationTest.php

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -827,6 +827,21 @@ public function testWhereHasOnSelfReferencingBelongsToManyRelationship()
827827
$this->assertSame('[email protected]', $results->first()->email);
828828
}
829829

830+
public function testWithWhereHasOnSelfReferencingBelongsToManyRelationship()
831+
{
832+
$user = EloquentTestUser::create(['email' => '[email protected]']);
833+
$user->friends()->create(['email' => '[email protected]']);
834+
835+
$results = EloquentTestUser::withWhereHas('friends', function ($query) {
836+
$query->where('email', '[email protected]');
837+
})->get();
838+
839+
$this->assertCount(1, $results);
840+
$this->assertSame('[email protected]', $results->first()->email);
841+
$this->assertTrue($results->first()->relationLoaded('friends'));
842+
$this->assertSame($results->first()->friends->pluck('email')->unique()->toArray(), ['[email protected]']);
843+
}
844+
830845
public function testHasOnNestedSelfReferencingBelongsToManyRelationship()
831846
{
832847
$user = EloquentTestUser::create(['email' => '[email protected]']);
@@ -853,6 +868,23 @@ public function testWhereHasOnNestedSelfReferencingBelongsToManyRelationship()
853868
$this->assertSame('[email protected]', $results->first()->email);
854869
}
855870

871+
public function testWithWhereHasOnNestedSelfReferencingBelongsToManyRelationship()
872+
{
873+
$user = EloquentTestUser::create(['email' => '[email protected]']);
874+
$friend = $user->friends()->create(['email' => '[email protected]']);
875+
$friend->friends()->create(['email' => '[email protected]']);
876+
877+
$results = EloquentTestUser::withWhereHas('friends.friends', function ($query) {
878+
$query->where('email', '[email protected]');
879+
})->get();
880+
881+
$this->assertCount(1, $results);
882+
$this->assertSame('[email protected]', $results->first()->email);
883+
$this->assertTrue($results->first()->relationLoaded('friends'));
884+
$this->assertSame($results->first()->friends->pluck('email')->unique()->toArray(), ['[email protected]']);
885+
$this->assertSame($results->first()->friends->pluck('friends')->flatten()->pluck('email')->unique()->toArray(), ['[email protected]']);
886+
}
887+
856888
public function testHasOnSelfReferencingBelongsToManyRelationshipWithWherePivot()
857889
{
858890
$user = EloquentTestUser::create(['email' => '[email protected]']);
@@ -909,6 +941,21 @@ public function testWhereHasOnSelfReferencingBelongsToRelationship()
909941
$this->assertSame('Child Post', $results->first()->name);
910942
}
911943

944+
public function testWithWhereHasOnSelfReferencingBelongsToRelationship()
945+
{
946+
$parentPost = EloquentTestPost::create(['name' => 'Parent Post', 'user_id' => 1]);
947+
EloquentTestPost::create(['name' => 'Child Post', 'parent_id' => $parentPost->id, 'user_id' => 2]);
948+
949+
$results = EloquentTestPost::withWhereHas('parentPost', function ($query) {
950+
$query->where('name', 'Parent Post');
951+
})->get();
952+
953+
$this->assertCount(1, $results);
954+
$this->assertSame('Child Post', $results->first()->name);
955+
$this->assertTrue($results->first()->relationLoaded('parentPost'));
956+
$this->assertSame($results->first()->parentPost->name, 'Parent Post');
957+
}
958+
912959
public function testHasOnNestedSelfReferencingBelongsToRelationship()
913960
{
914961
$grandParentPost = EloquentTestPost::create(['name' => 'Grandparent Post', 'user_id' => 1]);
@@ -935,6 +982,24 @@ public function testWhereHasOnNestedSelfReferencingBelongsToRelationship()
935982
$this->assertSame('Child Post', $results->first()->name);
936983
}
937984

985+
public function testWithWhereHasOnNestedSelfReferencingBelongsToRelationship()
986+
{
987+
$grandParentPost = EloquentTestPost::create(['name' => 'Grandparent Post', 'user_id' => 1]);
988+
$parentPost = EloquentTestPost::create(['name' => 'Parent Post', 'parent_id' => $grandParentPost->id, 'user_id' => 2]);
989+
EloquentTestPost::create(['name' => 'Child Post', 'parent_id' => $parentPost->id, 'user_id' => 3]);
990+
991+
$results = EloquentTestPost::withWhereHas('parentPost.parentPost', function ($query) {
992+
$query->where('name', 'Grandparent Post');
993+
})->get();
994+
995+
$this->assertCount(1, $results);
996+
$this->assertSame('Child Post', $results->first()->name);
997+
$this->assertTrue($results->first()->relationLoaded('parentPost'));
998+
$this->assertSame($results->first()->parentPost->name, 'Parent Post');
999+
$this->assertTrue($results->first()->parentPost->relationLoaded('parentPost'));
1000+
$this->assertSame($results->first()->parentPost->parentPost->name, 'Grandparent Post');
1001+
}
1002+
9381003
public function testHasOnSelfReferencingHasManyRelationship()
9391004
{
9401005
$parentPost = EloquentTestPost::create(['name' => 'Parent Post', 'user_id' => 1]);
@@ -959,6 +1024,21 @@ public function testWhereHasOnSelfReferencingHasManyRelationship()
9591024
$this->assertSame('Parent Post', $results->first()->name);
9601025
}
9611026

1027+
public function testWithWhereHasOnSelfReferencingHasManyRelationship()
1028+
{
1029+
$parentPost = EloquentTestPost::create(['name' => 'Parent Post', 'user_id' => 1]);
1030+
EloquentTestPost::create(['name' => 'Child Post', 'parent_id' => $parentPost->id, 'user_id' => 2]);
1031+
1032+
$results = EloquentTestPost::withWhereHas('childPosts', function ($query) {
1033+
$query->where('name', 'Child Post');
1034+
})->get();
1035+
1036+
$this->assertCount(1, $results);
1037+
$this->assertSame('Parent Post', $results->first()->name);
1038+
$this->assertTrue($results->first()->relationLoaded('childPosts'));
1039+
$this->assertSame($results->first()->childPosts->pluck('name')->unique()->toArray(), ['Child Post']);
1040+
}
1041+
9621042
public function testHasOnNestedSelfReferencingHasManyRelationship()
9631043
{
9641044
$grandParentPost = EloquentTestPost::create(['name' => 'Grandparent Post', 'user_id' => 1]);
@@ -985,6 +1065,23 @@ public function testWhereHasOnNestedSelfReferencingHasManyRelationship()
9851065
$this->assertSame('Grandparent Post', $results->first()->name);
9861066
}
9871067

1068+
public function testWithWhereHasOnNestedSelfReferencingHasManyRelationship()
1069+
{
1070+
$grandParentPost = EloquentTestPost::create(['name' => 'Grandparent Post', 'user_id' => 1]);
1071+
$parentPost = EloquentTestPost::create(['name' => 'Parent Post', 'parent_id' => $grandParentPost->id, 'user_id' => 2]);
1072+
EloquentTestPost::create(['name' => 'Child Post', 'parent_id' => $parentPost->id, 'user_id' => 3]);
1073+
1074+
$results = EloquentTestPost::withWhereHas('childPosts.childPosts', function ($query) {
1075+
$query->where('name', 'Child Post');
1076+
})->get();
1077+
1078+
$this->assertCount(1, $results);
1079+
$this->assertSame('Grandparent Post', $results->first()->name);
1080+
$this->assertTrue($results->first()->relationLoaded('childPosts'));
1081+
$this->assertSame($results->first()->childPosts->pluck('name')->unique()->toArray(), ['Parent Post']);
1082+
$this->assertSame($results->first()->childPosts->pluck('childPosts')->flatten()->pluck('name')->unique()->toArray(), ['Child Post']);
1083+
}
1084+
9881085
public function testHasWithNonWhereBindings()
9891086
{
9901087
$user = EloquentTestUser::create(['id' => 1, 'email' => '[email protected]']);

tests/Database/DatabaseEloquentMorphOneOfManyTest.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,30 @@ public function testExists()
115115
$this->assertTrue($exists);
116116
}
117117

118+
public function testWithWhereHas()
119+
{
120+
$product = MorphOneOfManyTestProduct::create();
121+
$previousState = $product->states()->create([
122+
'state' => 'draft',
123+
]);
124+
$currentState = $product->states()->create([
125+
'state' => 'active',
126+
]);
127+
128+
$exists = MorphOneOfManyTestProduct::withWhereHas('current_state', function ($q) use ($previousState) {
129+
$q->whereKey($previousState->getKey());
130+
})->exists();
131+
$this->assertFalse($exists);
132+
133+
$exists = MorphOneOfManyTestProduct::withWhereHas('current_state', function ($q) use ($currentState) {
134+
$q->whereKey($currentState->getKey());
135+
})->get();
136+
137+
$this->assertCount(1, $exists);
138+
$this->assertTrue($exists->first()->relationLoaded('current_state'));
139+
$this->assertSame($exists->first()->current_state->state, $currentState->state);
140+
}
141+
118142
public function testWithExists()
119143
{
120144
$product = MorphOneOfManyTestProduct::create();

0 commit comments

Comments
 (0)