Skip to content

Commit 04df42b

Browse files
committed
PHPORM-155 Integrate aggregation builder to the Query builder
1 parent 12e78e5 commit 04df42b

File tree

4 files changed

+91
-19
lines changed

4 files changed

+91
-19
lines changed

src/Eloquent/Builder.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@ class Builder extends EloquentBuilder
5151
'tomql',
5252
];
5353

54+
public function aggregate($function = null, $columns = ['*'])
55+
{
56+
$result = $this->toBase()->aggregate($function, $columns);
57+
58+
return $result ?? $this;
59+
}
60+
5461
/** @inheritdoc */
5562
public function update(array $values, array $options = [])
5663
{

src/Query/Builder.php

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
use MongoDB\Builder\Stage\ReplaceRootStage;
3333
use MongoDB\Builder\Stage\SkipStage;
3434
use MongoDB\Builder\Stage\SortStage;
35+
use MongoDB\Builder\Stage\UnwindStage;
36+
use MongoDB\Builder\Type\StageInterface;
3537
use MongoDB\Builder\Variable;
3638
use MongoDB\Driver\Cursor;
3739
use Override;
@@ -292,13 +294,9 @@ public function dump(mixed ...$args)
292294
}
293295

294296
/**
295-
* Return the MongoDB query to be run in the form of an element array like ['method' => [arguments]].
296-
*
297-
* Example: ['find' => [['name' => 'John Doe'], ['projection' => ['birthday' => 1]]]]
298-
*
299-
* @return array<string, mixed[]>
297+
* @return StageInterface[]
300298
*/
301-
public function toMql(): array
299+
protected function getPipeline(): array
302300
{
303301
$columns = $this->columns ?? [];
304302

@@ -373,33 +371,33 @@ public function toMql(): array
373371
// Build the aggregation pipeline.
374372
$pipeline = [];
375373
if ($wheres) {
376-
$pipeline[] = ['$match' => $wheres];
374+
$pipeline[] = new MatchStage(...$wheres);
377375
}
378376

379377
// apply unwinds for subdocument array aggregation
380378
foreach ($unwinds as $unwind) {
381-
$pipeline[] = ['$unwind' => '$' . $unwind];
379+
$pipeline[] = new UnwindStage($unwind);
382380
}
383381

384382
if ($group) {
385-
$pipeline[] = ['$group' => $group];
383+
$pipeline[] = new GroupStage(...$group);
386384
}
387385

388386
// Apply order and limit
389387
if ($this->orders) {
390-
$pipeline[] = ['$sort' => $this->orders];
388+
$pipeline[] = new SortStage($this->orders);
391389
}
392390

393391
if ($this->offset) {
394-
$pipeline[] = ['$skip' => $this->offset];
392+
$pipeline[] = new SkipStage($this->offset);
395393
}
396394

397395
if ($this->limit) {
398-
$pipeline[] = ['$limit' => $this->limit];
396+
$pipeline[] = new LimitStage($this->limit);
399397
}
400398

401399
if ($this->projections) {
402-
$pipeline[] = ['$project' => $this->projections];
400+
$pipeline[] = new ProjectStage(...$this->projections);
403401
}
404402

405403
$options = [
@@ -457,6 +455,22 @@ public function toMql(): array
457455
$pipeline[] = new ProjectStage(...$projection);
458456
}
459457

458+
return $pipeline;
459+
}
460+
461+
/**
462+
* Return the MongoDB query to be run in the form of an element array like ['method' => [arguments]].
463+
*
464+
* Example: ['find' => [['name' => 'John Doe'], ['projection' => ['birthday' => 1]]]]
465+
*
466+
* @return array<string, mixed[]>
467+
*/
468+
public function toMql(): array
469+
{
470+
$pipeline = $this->getPipeline();
471+
$encoder = new BuilderEncoder();
472+
$pipeline = $encoder->encode(new Pipeline(...$pipeline));
473+
460474
$options = ['typeMap' => ['root' => 'array', 'document' => 'array']];
461475

462476
if ($this->timeout) {
@@ -468,12 +482,8 @@ public function toMql(): array
468482
}
469483

470484
$options = array_merge($options, $this->options);
471-
472485
$options = $this->inheritConnectionOptions($options);
473486

474-
$encoder = new BuilderEncoder();
475-
$pipeline = $encoder->encode(new Pipeline(...$pipeline));
476-
477487
return ['aggregate' => [$pipeline, $options]];
478488
}
479489

@@ -554,9 +564,16 @@ public function generateCacheKey()
554564
return md5(serialize(array_values($key)));
555565
}
556566

557-
/** @inheritdoc */
558-
public function aggregate($function, $columns = [])
567+
/**
568+
* @return self|PipelineBuilder
569+
* @psalm-return $function === null ? PipelineBuilder : self
570+
*/
571+
public function aggregate($function = null, $columns = [])
559572
{
573+
if ($function === null) {
574+
return new PipelineBuilder($this->getPipeline());
575+
}
576+
560577
$this->aggregate = [
561578
'function' => $function,
562579
'columns' => $columns,

src/Query/PipelineBuilder.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MongoDB\Laravel\Query;
6+
7+
use MongoDB\Builder\Stage\FluentFactory;
8+
9+
class PipelineBuilder extends FluentFactory
10+
{
11+
public function __construct(array $pipeline = [])
12+
{
13+
$this->pipeline = $pipeline;
14+
}
15+
}

tests/Query/PipelineBuilderTest.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MongoDB\Laravel\Tests\Query;
6+
7+
use MongoDB\Laravel\Query\PipelineBuilder;
8+
use MongoDB\Laravel\Tests\Models\User;
9+
use MongoDB\Laravel\Tests\TestCase;
10+
11+
use function assert;
12+
13+
class PipelineBuilderTest extends TestCase
14+
{
15+
public function testCreateFromQueryBuilder(): void
16+
{
17+
$builder = User::where('foo', 'bar')->aggregate();
18+
assert($builder instanceof PipelineBuilder);
19+
$builder->addFields(baz: 'qux');
20+
21+
$this->assertSame(
22+
[
23+
[
24+
'$match' => ['foo' => 'bar'],
25+
],
26+
[
27+
'$addFields' => ['baz' => 'qux'],
28+
],
29+
],
30+
$builder->getPipeline(),
31+
);
32+
}
33+
}

0 commit comments

Comments
 (0)