Skip to content

Commit 7835e8a

Browse files
committed
Make it possible to run the aggregation pipeline
1 parent a2859a6 commit 7835e8a

File tree

3 files changed

+88
-12
lines changed

3 files changed

+88
-12
lines changed

src/Query/Builder.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ class Builder extends BaseBuilder
8888
/**
8989
* The database collection.
9090
*
91-
* @var \MongoDB\Collection
91+
* @var \MongoDB\Laravel\Collection
9292
*/
9393
protected $collection;
9494

@@ -569,7 +569,7 @@ public function generateCacheKey()
569569
public function aggregate($function = null, $columns = [])
570570
{
571571
if ($function === null) {
572-
return new PipelineBuilder($this->getPipeline());
572+
return new PipelineBuilder($this->getPipeline(), $this->collection, $this->options);
573573
}
574574

575575
$this->aggregate = [

src/Query/PipelineBuilder.php

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,37 @@
44

55
namespace MongoDB\Laravel\Query;
66

7+
use Illuminate\Support\Collection as LaravelCollection;
8+
use MongoDB\Builder\BuilderEncoder;
79
use MongoDB\Builder\Stage\FluentFactory;
10+
use MongoDB\Laravel\Collection;
811

9-
class PipelineBuilder extends FluentFactory
12+
use function array_replace;
13+
use function collect;
14+
15+
final class PipelineBuilder extends FluentFactory
1016
{
11-
public function __construct(array $pipeline = [])
12-
{
17+
public function __construct(
18+
array $pipeline,
19+
private Collection $collection,
20+
private array $options,
21+
) {
1322
$this->pipeline = $pipeline;
1423
}
24+
25+
/**
26+
* Execute the aggregation pipeline and return the results.
27+
*/
28+
public function get(): LaravelCollection
29+
{
30+
$encoder = new BuilderEncoder();
31+
$pipeline = $encoder->encode($this->getPipeline());
32+
33+
$options = array_replace(
34+
['typeMap' => ['root' => 'array', 'document' => 'array']],
35+
$this->options,
36+
);
37+
38+
return collect($this->collection->aggregate($pipeline, $options)->toArray());
39+
}
1540
}

tests/Query/PipelineBuilderTest.php

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,15 @@
44

55
namespace MongoDB\Laravel\Tests\Query;
66

7+
use DateTimeImmutable;
8+
use Illuminate\Support\Collection;
79
use MongoDB\BSON\Document;
10+
use MongoDB\BSON\ObjectId;
11+
use MongoDB\BSON\UTCDateTime;
812
use MongoDB\Builder\BuilderEncoder;
13+
use MongoDB\Builder\Expression;
14+
use MongoDB\Builder\Type\TimeUnit;
15+
use MongoDB\Builder\Variable;
916
use MongoDB\Laravel\Query\PipelineBuilder;
1017
use MongoDB\Laravel\Tests\Models\User;
1118
use MongoDB\Laravel\Tests\TestCase;
@@ -14,27 +21,71 @@
1421

1522
class PipelineBuilderTest extends TestCase
1623
{
24+
public function tearDown(): void
25+
{
26+
User::truncate();
27+
}
28+
1729
public function testCreateFromQueryBuilder(): void
1830
{
19-
$builder = User::where('foo', 'bar')->aggregate();
20-
assert($builder instanceof PipelineBuilder);
21-
$builder->addFields(baz: 'qux');
31+
User::insert([
32+
// @todo apply cast to mass insert?
33+
['name' => 'John Doe', 'birthday' => new UTCDateTime(new DateTimeImmutable('1989-01-01'))],
34+
['name' => 'Jane Doe', 'birthday' => new UTCDateTime(new DateTimeImmutable('1990-01-01'))],
35+
]);
2236

37+
// Create the aggregation pipeline from the query builder
38+
$pipeline = User::where('name', 'John Doe')->aggregate();
39+
assert($pipeline instanceof PipelineBuilder);
40+
$pipeline
41+
->addFields(
42+
age: Expression::dateDiff(
43+
startDate: Expression::dateFieldPath('birthday'),
44+
endDate: Variable::now(),
45+
unit: TimeUnit::Year,
46+
),
47+
)
48+
->unset('birthday');
49+
50+
// The encoder is used to convert the pipeline to a BSON document
2351
$codec = new BuilderEncoder();
24-
$pipeline = $codec->encode($builder->getPipeline());
25-
$json = Document::fromPHP(['pipeline' => $pipeline])->toCanonicalExtendedJSON();
52+
$json = Document::fromPHP([
53+
'pipeline' => $codec->encode($pipeline->getPipeline()),
54+
])->toCanonicalExtendedJSON();
2655

56+
// Compare with the expected pipeline
2757
$expected = Document::fromPHP([
2858
'pipeline' => [
2959
[
30-
'$match' => ['foo' => 'bar'],
60+
'$match' => ['name' => 'John Doe'],
3161
],
3262
[
33-
'$addFields' => ['baz' => 'qux'],
63+
'$addFields' => [
64+
'age' => [
65+
'$dateDiff' => [
66+
'startDate' => '$birthday',
67+
'endDate' => '$$NOW',
68+
'unit' => 'year',
69+
],
70+
],
71+
],
72+
],
73+
[
74+
'$unset' => ['birthday'],
3475
],
3576
],
3677
])->toCanonicalExtendedJSON();
3778

3879
$this->assertJsonStringEqualsJsonString($expected, $json);
80+
81+
// Execute the pipeline and verify the results
82+
$results = $pipeline->get();
83+
84+
$this->assertInstanceOf(Collection::class, $results);
85+
$this->assertCount(1, $results);
86+
$this->assertInstanceOf(ObjectId::class, $results->first()['_id']);
87+
$this->assertSame('John Doe', $results->first()['name']);
88+
$this->assertIsInt($results->first()['age']);
89+
$this->assertArrayNotHasKey('birthday', $results->first());
3990
}
4091
}

0 commit comments

Comments
 (0)