Skip to content

Commit 1d4699e

Browse files
committed
Review
1 parent 299d5ef commit 1d4699e

File tree

3 files changed

+33
-7
lines changed

3 files changed

+33
-7
lines changed

src/Eloquent/Builder.php

+32-2
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,19 @@
2222
use MongoDB\Laravel\Relations\EmbedsOneOrMany;
2323
use MongoDB\Laravel\Relations\HasMany;
2424
use MongoDB\Model\BSONDocument;
25+
use RuntimeException;
26+
use TypeError;
2527

2628
use function array_key_exists;
2729
use function array_merge;
30+
use function assert;
2831
use function collect;
2932
use function count;
3033
use function explode;
34+
use function get_debug_type;
3135
use function is_array;
3236
use function is_object;
37+
use function is_string;
3338
use function iterator_to_array;
3439
use function property_exists;
3540
use function sprintf;
@@ -43,7 +48,11 @@ class Builder extends EloquentBuilder
4348
private const DUPLICATE_KEY_ERROR = 11000;
4449
use QueriesRelationships;
4550

46-
/** @var array{relation: Relation, function: string, constraints: array, column: string, alias: string}[] */
51+
/**
52+
* List of aggregations on the related models after the main query.
53+
*
54+
* @var array{relation: Relation, function: string, constraints: array, column: string, alias: string}[]
55+
*/
4756
private array $withAggregate = [];
4857

4958
/**
@@ -306,19 +315,37 @@ public function createOrFirst(array $attributes = [], array $values = [])
306315
}
307316
}
308317

318+
/**
319+
* Add subsequent queries to include an aggregate value for a relationship.
320+
* For embedded relations, a projection is used to calculate the aggregate.
321+
*
322+
* @see \Illuminate\Database\Eloquent\Concerns\QueriesRelationships::withAggregate()
323+
*
324+
* @param mixed $relations Name of the relationship or an array of relationships to closure for constraint
325+
* @param string $column Name of the field to aggregate
326+
* @param string $function Required aggregation function name (count, min, max, avg)
327+
*
328+
* @return $this
329+
*/
309330
public function withAggregate($relations, $column, $function = null)
310331
{
311332
if (empty($relations)) {
312333
return $this;
313334
}
314335

336+
assert(is_string($function), new TypeError('Argument 3 ($function) passed to withAggregate must be of the type string, ' . get_debug_type($function) . ' given'));
337+
315338
$relations = is_array($relations) ? $relations : [$relations];
316339

317340
foreach ($this->parseWithRelations($relations) as $name => $constraints) {
318341
$segments = explode(' ', $name);
319342

343+
$alias = match (true) {
344+
count($segments) === 1 => Str::snake($segments[0]) . '_' . $function,
345+
count($segments) === 3 && Str::lower($segments[1]) => $segments[2],
346+
default => throw new InvalidArgumentException(sprintf('Invalid relation name format. Expected "relation as alias" or "relation", got "%s"', $name)),
347+
};
320348
$name = $segments[0];
321-
$alias = (count($segments) === 3 && Str::lower($segments[1]) === 'as' ? $segments[2] : Str::snake($name) . '_' . $function);
322349

323350
$relation = $this->getRelationWithoutConstraints($name);
324351

@@ -347,6 +374,7 @@ public function withAggregate($relations, $column, $function = null)
347374
throw new InvalidArgumentException(sprintf('Invalid aggregate function "%s"', $function));
348375
}
349376
} else {
377+
// The aggregation will be performed after the main query, during eager loading.
350378
$this->withAggregate[$alias] = [
351379
'relation' => $relation,
352380
'function' => $function,
@@ -384,6 +412,8 @@ public function eagerLoadRelations(array $models)
384412

385413
$model->setAttribute($withAggregate['alias'], $value);
386414
}
415+
} else {
416+
throw new RuntimeException(sprintf('Unsupported relation type for aggregation', $withAggregate['relation']::class));
387417
}
388418
}
389419
}

tests/Eloquent/EloquentWithAggregateTest.php

+1
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ public function testWithAggregateMultipleResults()
162162
['id' => 4, 'twos_count' => 0],
163163
], $results->get());
164164

165+
// Only 2 queries should be executed: the main query and the aggregate grouped by foreign id
165166
self::assertSame(2, count($connection->getQueryLog()));
166167
$connection->flushQueryLog();
167168

tests/HybridRelationsTest.php

-5
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,6 @@ public function testHybridWhereHas()
157157

158158
public function testHybridWith()
159159
{
160-
DB::connection('mongodb')->enableQueryLog();
161160
$user = new SqlUser();
162161
$otherUser = new SqlUser();
163162
$this->assertInstanceOf(SqlUser::class, $user);
@@ -207,10 +206,6 @@ public function testHybridWith()
207206
->each(function ($user) {
208207
$this->assertEquals($user->id, $user->books->count());
209208
});
210-
//SqlUser::withCount('books')->get()
211-
// ->each(function ($user) {
212-
// $this->assertEquals($user->id, $user->books_count);
213-
// });
214209

215210
SqlUser::whereHas('sqlBooks', function ($query) {
216211
return $query->where('title', 'LIKE', 'Harry%');

0 commit comments

Comments
 (0)