Skip to content

Commit 52a4eab

Browse files
authored
Merge branch '4.3' into merge-4.2-into-4.3-1712671033107
2 parents d51a3a1 + c9b9d2c commit 52a4eab

File tree

8 files changed

+295
-15
lines changed

8 files changed

+295
-15
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ All notable changes to this project will be documented in this file.
33

44
## [unreleased]
55

6+
* New aggregation pipeline builder by @GromNaN in [#2738](https://github.com/mongodb/laravel-mongodb/pull/2738)
7+
* Drop support for Composer 1.x by @GromNaN in [#2784](https://github.com/mongodb/laravel-mongodb/pull/2784)
68

7-
## [4.2.0] - 2024-12-14
9+
## [4.2.0] - 2024-03-14
810

911
* Add support for Laravel 11 by @GromNaN in [#2735](https://github.com/mongodb/laravel-mongodb/pull/2735)
1012
* Implement Model::createOrFirst() using findOneAndUpdate operation by @GromNaN in [#2742](https://github.com/mongodb/laravel-mongodb/pull/2742)

composer.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,25 @@
2424
"require": {
2525
"php": "^8.1",
2626
"ext-mongodb": "^1.15",
27+
"composer-runtime-api": "^2.0.0",
2728
"illuminate/support": "^10.0|^11",
2829
"illuminate/container": "^10.0|^11",
2930
"illuminate/database": "^10.30|^11",
3031
"illuminate/events": "^10.0|^11",
3132
"mongodb/mongodb": "^1.15"
3233
},
3334
"require-dev": {
35+
"mongodb/builder": "^0.2",
3436
"phpunit/phpunit": "^10.3",
3537
"orchestra/testbench": "^8.0|^9.0",
3638
"mockery/mockery": "^1.4.4",
3739
"doctrine/coding-standard": "12.0.x-dev",
3840
"spatie/laravel-query-builder": "^5.6",
3941
"phpstan/phpstan": "^1.10"
4042
},
43+
"suggest": {
44+
"mongodb/builder": "Provides a fluent aggregation builder for MongoDB pipelines"
45+
},
4146
"minimum-stability": "dev",
4247
"replace": {
4348
"jenssegers/mongodb": "self.version"

src/Connection.php

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
use MongoDB\Laravel\Concerns\ManagesTransactions;
1717
use Throwable;
1818

19-
use function class_exists;
2019
use function filter_var;
2120
use function implode;
2221
use function is_array;
@@ -324,14 +323,10 @@ private static function getVersion(): string
324323

325324
private static function lookupVersion(): string
326325
{
327-
if (class_exists(InstalledVersions::class)) {
328-
try {
329-
return self::$version = InstalledVersions::getPrettyVersion('mongodb/laravel-mongodb');
330-
} catch (Throwable) {
331-
return self::$version = 'error';
332-
}
326+
try {
327+
return self::$version = InstalledVersions::getPrettyVersion('mongodb/laravel-mongodb') ?? 'unknown';
328+
} catch (Throwable) {
329+
return self::$version = 'error';
333330
}
334-
335-
return self::$version = 'unknown';
336331
}
337332
}

src/Eloquent/Builder.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use MongoDB\Laravel\Collection;
1212
use MongoDB\Laravel\Helpers\QueriesRelationships;
1313
use MongoDB\Laravel\Internal\FindAndModifyCommandSubscriber;
14+
use MongoDB\Laravel\Query\AggregationBuilder;
1415
use MongoDB\Model\BSONDocument;
1516
use MongoDB\Operation\FindOneAndUpdate;
1617

@@ -56,6 +57,18 @@ class Builder extends EloquentBuilder
5657
'tomql',
5758
];
5859

60+
/**
61+
* @return ($function is null ? AggregationBuilder : self)
62+
*
63+
* @inheritdoc
64+
*/
65+
public function aggregate($function = null, $columns = ['*'])
66+
{
67+
$result = $this->toBase()->aggregate($function, $columns);
68+
69+
return $result ?: $this;
70+
}
71+
5972
/** @inheritdoc */
6073
public function update(array $values, array $options = [])
6174
{
@@ -215,7 +228,7 @@ public function createOrFirst(array $attributes = [], array $values = []): Model
215228
$document = $collection->findOneAndUpdate(
216229
$attributes,
217230
// Before MongoDB 5.0, $setOnInsert requires a non-empty document.
218-
// This is should not be an issue as $values includes the query filter.
231+
// This should not be an issue as $values includes the query filter.
219232
['$setOnInsert' => (object) $values],
220233
[
221234
'upsert' => true,

src/Eloquent/Model.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
use function uniqid;
5050
use function var_export;
5151

52+
/** @mixin Builder */
5253
abstract class Model extends BaseModel
5354
{
5455
use HybridRelations;

src/Query/AggregationBuilder.php

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MongoDB\Laravel\Query;
6+
7+
use Illuminate\Support\Collection as LaravelCollection;
8+
use Illuminate\Support\LazyCollection;
9+
use InvalidArgumentException;
10+
use Iterator;
11+
use MongoDB\Builder\BuilderEncoder;
12+
use MongoDB\Builder\Stage\FluentFactoryTrait;
13+
use MongoDB\Collection as MongoDBCollection;
14+
use MongoDB\Driver\CursorInterface;
15+
use MongoDB\Laravel\Collection as LaravelMongoDBCollection;
16+
17+
use function array_replace;
18+
use function collect;
19+
use function sprintf;
20+
use function str_starts_with;
21+
22+
class AggregationBuilder
23+
{
24+
use FluentFactoryTrait;
25+
26+
public function __construct(
27+
private MongoDBCollection|LaravelMongoDBCollection $collection,
28+
private readonly array $options = [],
29+
) {
30+
}
31+
32+
/**
33+
* Add a stage without using the builder. Necessary if the stage is built
34+
* outside the builder, or it is not yet supported by the library.
35+
*/
36+
public function addRawStage(string $operator, mixed $value): static
37+
{
38+
if (! str_starts_with($operator, '$')) {
39+
throw new InvalidArgumentException(sprintf('The stage name "%s" is invalid. It must start with a "$" sign.', $operator));
40+
}
41+
42+
$this->pipeline[] = [$operator => $value];
43+
44+
return $this;
45+
}
46+
47+
/**
48+
* Execute the aggregation pipeline and return the results.
49+
*/
50+
public function get(array $options = []): LaravelCollection|LazyCollection
51+
{
52+
$cursor = $this->execute($options);
53+
54+
return collect($cursor->toArray());
55+
}
56+
57+
/**
58+
* Execute the aggregation pipeline and return the results in a lazy collection.
59+
*/
60+
public function cursor($options = []): LazyCollection
61+
{
62+
$cursor = $this->execute($options);
63+
64+
return LazyCollection::make(function () use ($cursor) {
65+
foreach ($cursor as $item) {
66+
yield $item;
67+
}
68+
});
69+
}
70+
71+
/**
72+
* Execute the aggregation pipeline and return the first result.
73+
*/
74+
public function first(array $options = []): mixed
75+
{
76+
return (clone $this)
77+
->limit(1)
78+
->get($options)
79+
->first();
80+
}
81+
82+
/**
83+
* Execute the aggregation pipeline and return MongoDB cursor.
84+
*/
85+
private function execute(array $options): CursorInterface&Iterator
86+
{
87+
$encoder = new BuilderEncoder();
88+
$pipeline = $encoder->encode($this->getPipeline());
89+
90+
$options = array_replace(
91+
['typeMap' => ['root' => 'array', 'document' => 'array']],
92+
$this->options,
93+
$options,
94+
);
95+
96+
return $this->collection->aggregate($pipeline, $options);
97+
}
98+
}

src/Query/Builder.php

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use MongoDB\BSON\ObjectID;
2222
use MongoDB\BSON\Regex;
2323
use MongoDB\BSON\UTCDateTime;
24+
use MongoDB\Builder\Stage\FluentFactoryTrait;
2425
use MongoDB\Driver\Cursor;
2526
use Override;
2627
use RuntimeException;
@@ -65,6 +66,7 @@
6566
use function strlen;
6667
use function strtolower;
6768
use function substr;
69+
use function trait_exists;
6870
use function var_export;
6971

7072
class Builder extends BaseBuilder
@@ -74,7 +76,7 @@ class Builder extends BaseBuilder
7476
/**
7577
* The database collection.
7678
*
77-
* @var \MongoDB\Collection
79+
* @var \MongoDB\Laravel\Collection
7880
*/
7981
protected $collection;
8082

@@ -83,7 +85,7 @@ class Builder extends BaseBuilder
8385
*
8486
* @var array
8587
*/
86-
public $projections;
88+
public $projections = [];
8789

8890
/**
8991
* The maximum amount of seconds to allow the query to run.
@@ -538,9 +540,26 @@ public function generateCacheKey()
538540
return md5(serialize(array_values($key)));
539541
}
540542

541-
/** @inheritdoc */
542-
public function aggregate($function, $columns = [])
543+
/** @return ($function is null ? AggregationBuilder : mixed) */
544+
public function aggregate($function = null, $columns = ['*'])
543545
{
546+
if ($function === null) {
547+
if (! trait_exists(FluentFactoryTrait::class)) {
548+
// This error will be unreachable when the mongodb/builder package will be merged into mongodb/mongodb
549+
throw new BadMethodCallException('Aggregation builder requires package mongodb/builder 0.2+');
550+
}
551+
552+
if ($columns !== ['*']) {
553+
throw new InvalidArgumentException('Columns cannot be specified to create an aggregation builder. Add a $project stage instead.');
554+
}
555+
556+
if ($this->wheres) {
557+
throw new BadMethodCallException('Aggregation builder does not support previous query-builder instructions. Use a $match stage instead.');
558+
}
559+
560+
return new AggregationBuilder($this->collection, $this->options);
561+
}
562+
544563
$this->aggregate = [
545564
'function' => $function,
546565
'columns' => $columns,

0 commit comments

Comments
 (0)