From 1f289a37ec3625ae45a2dc5307ba35212417e2b8 Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Tue, 14 Mar 2023 13:46:16 +0100 Subject: [PATCH 01/23] Add $densify stage to aggregation pipeline builder --- .../ODM/MongoDB/Aggregation/Builder.php | 13 ++++ .../ODM/MongoDB/Aggregation/Stage.php | 10 +++ .../ODM/MongoDB/Aggregation/Stage/Densify.php | 68 +++++++++++++++++++ .../Tests/Aggregation/Stage/DensifyTest.php | 44 ++++++++++++ 4 files changed, 135 insertions(+) create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Densify.php create mode 100644 tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/DensifyTest.php diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php index 5a0f785771..3b06a0e417 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php @@ -160,6 +160,19 @@ public function count(string $fieldName): Stage\Count return $stage; } + /** + * Creates new documents in a sequence of documents where certain values in a field are missing. + * + * @see https://www.mongodb.com/docs/rapid/reference/operator/aggregation/densify/ + */ + public function densify(string $fieldName): Stage\Densify + { + $stage = new Stage\Densify($this, $fieldName); + $this->addStage($stage); + + return $stage; + } + /** * Executes the aggregation pipeline * diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage.php index 28c3e777b2..80e260c546 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage.php @@ -136,6 +136,16 @@ public function count(string $fieldName): Stage\Count return $this->builder->count($fieldName); } + /** + * Creates new documents in a sequence of documents where certain values in a field are missing. + * + * @see https://www.mongodb.com/docs/rapid/reference/operator/aggregation/densify/ + */ + public function densify(string $fieldName): Stage\Densify + { + return $this->builder->densify($fieldName); + } + /** * Processes multiple aggregation pipelines within a single stage on the * same set of input documents. diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Densify.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Densify.php new file mode 100644 index 0000000000..a803f247fc --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Densify.php @@ -0,0 +1,68 @@ + */ + private array $partitionByFields = []; + + private ?object $range = null; + + public function __construct(Builder $builder, string $fieldName) + { + parent::__construct($builder); + + $this->field = $fieldName; + } + + public function partitionByFields(string ...$fields): self + { + $this->partitionByFields = $fields; + + return $this; + } + + /** + * @param array{0: int, 1: int}|string $bounds + * @param int|float $step + */ + public function range($bounds, $step, string $unit = ''): self + { + $this->range = (object) [ + 'bounds' => $bounds, + 'step' => $step, + ]; + + if ($unit !== '') { + $this->range->unit = $unit; + } + + return $this; + } + + public function getExpression(): array + { + $params = (object) ['field' => $this->field]; + + if ($this->partitionByFields) { + $params->partitionByFields = $this->partitionByFields; + } + + if ($this->range) { + $params->range = $this->range; + } + + return ['$densify' => $params]; + } +} diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/DensifyTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/DensifyTest.php new file mode 100644 index 0000000000..ae527c95a1 --- /dev/null +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/DensifyTest.php @@ -0,0 +1,44 @@ +getTestAggregationBuilder(), 'someField'); + $densifyStage + ->partitionByFields('field1', 'field2') + ->range('full', 1); + + self::assertEquals( + [ + '$densify' => (object) [ + 'field' => 'someField', + 'partitionByFields' => ['field1', 'field2'], + 'range' => (object) [ + 'bounds' => 'full', + 'step' => 1, + ], + ], + ], + $densifyStage->getExpression(), + ); + } + + public function testCountFromBuilder(): void + { + $builder = $this->getTestAggregationBuilder(); + $builder->densify('someField'); + + self::assertEquals([['$densify' => (object) ['field' => 'someField']]], $builder->getPipeline()); + } +} From b10aed70b8a5455acc6e94348e6ec95a27e4e9dc Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Tue, 14 Mar 2023 14:24:37 +0100 Subject: [PATCH 02/23] Add $fill stage to aggregation pipeline builder --- .../ODM/MongoDB/Aggregation/Builder.php | 13 +++ .../ODM/MongoDB/Aggregation/Stage.php | 10 ++ .../ODM/MongoDB/Aggregation/Stage/Fill.php | 105 +++++++++++++++++ .../MongoDB/Aggregation/Stage/Fill/Output.php | 109 ++++++++++++++++++ .../Tests/Aggregation/Stage/FillTest.php | 70 +++++++++++ 5 files changed, 307 insertions(+) create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill/Output.php create mode 100644 tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/FillTest.php diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php index 3b06a0e417..e0c2a136df 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php @@ -213,6 +213,19 @@ public function facet(): Stage\Facet return $stage; } + /** + * Populates null and missing field values within documents. + * + * @see https://www.mongodb.com/docs/rapid/reference/operator/aggregation/fill/ + */ + public function fill(): Stage\Fill + { + $stage = new Stage\Fill($this); + $this->addStage($stage); + + return $stage; + } + /** * Outputs documents in order of nearest to farthest from a specified point. * diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage.php index 80e260c546..ce8a847a54 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage.php @@ -158,6 +158,16 @@ public function facet(): Stage\Facet return $this->builder->facet(); } + /** + * Populates null and missing field values within documents. + * + * @see https://www.mongodb.com/docs/rapid/reference/operator/aggregation/fill/ + */ + public function fill(): Stage\Fill + { + return $this->builder->fill(); + } + /** * Outputs documents in order of nearest to farthest from a specified point. * You can only use this as the first stage of a pipeline. diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill.php new file mode 100644 index 0000000000..b26783fb75 --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill.php @@ -0,0 +1,105 @@ + + */ +class Fill extends Stage +{ + /** @var mixed|Expr|null */ + private $partitionBy = null; + + /** @var array */ + private array $partitionByFields = []; + + /** @var array */ + private array $sortBy = []; + + private ?Output $output = null; + + public function __construct(Builder $builder) + { + parent::__construct($builder); + } + + /** @param mixed|Expr $expression */ + public function partitionBy($expression): self + { + $this->partitionBy = $expression; + + return $this; + } + + public function partitionByFields(string ...$fields): self + { + $this->partitionByFields = $fields; + + return $this; + } + + /** + * @param array|string $fieldName Field name or array of field/order pairs + * @param int|string $order Field order (if one field is specified) + * @psalm-param SortShape|string $fieldName + */ + public function sortBy($fieldName, $order = null): self + { + $fields = is_array($fieldName) ? $fieldName : [$fieldName => $order ?? 1]; + + foreach ($fields as $fieldName => $order) { + if (is_string($order)) { + $order = strtolower($order) === 'asc' ? 1 : -1; + } + + $this->sortBy[$fieldName] = $order; + } + + return $this; + } + + public function output(): Output + { + if (! $this->output) { + $this->output = new Output($this->builder, $this); + } + + return $this->output; + } + + public function getExpression(): array + { + $params = (object) []; + + if ($this->partitionBy) { + $params->partitionBy = $this->partitionBy; + } + + if ($this->partitionByFields) { + $params->partitionByFields = $this->partitionByFields; + } + + if ($this->sortBy) { + $params->sortBy = (object) $this->sortBy; + } + + if ($this->output) { + $params->output = (object) $this->output->getExpression(); + } + + return ['$fill' => $params]; + } +} diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill/Output.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill/Output.php new file mode 100644 index 0000000000..8c0cfbb1ec --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill/Output.php @@ -0,0 +1,109 @@ +> */ + private $output = []; + + public function __construct(Builder $builder, Fill $fill) + { + parent::__construct($builder); + + $this->fill = $fill; + } + + /** @param mixed|Expr $expression */ + public function partitionBy($expression): Fill + { + return $this->fill->partitionBy($expression); + } + + public function partitionByFields(string ...$fields): Fill + { + return $this->fill->partitionByFields(...$fields); + } + + /** + * @param array|string $fieldName Field name or array of field/order pairs + * @param int|string $order Field order (if one field is specified) + * @psalm-param SortShape|string $fieldName + */ + public function sortBy($fieldName, $order = null): Fill + { + return $this->fill->sortBy(...func_get_args()); + } + + /** + * Set the current field for building the expression. + */ + public function field(string $fieldName): self + { + $this->currentField = $fieldName; + + return $this; + } + + public function linear(): self + { + $this->requiresCurrentField(__METHOD__); + $this->output[$this->currentField] = ['method' => 'linear']; + + return $this; + } + + public function locf(): self + { + $this->requiresCurrentField(__METHOD__); + $this->output[$this->currentField] = ['method' => 'locf']; + + return $this; + } + + /** @param mixed|Expr $expression */ + public function value($expression): self + { + $this->requiresCurrentField(__METHOD__); + $this->output[$this->currentField] = [ + 'value' => $expression instanceof Expr ? $expression->getExpression() : $expression, + ]; + + return $this; + } + + public function getExpression(): array + { + return $this->output; + } + + /** + * Ensure that a current field has been set. + * + * @throws LogicException if a current field has not been set. + */ + private function requiresCurrentField(?string $method = null): void + { + if (! $this->currentField) { + throw new LogicException(($method ?: 'This method') . ' requires you set a current field using field().'); + } + } +} diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/FillTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/FillTest.php new file mode 100644 index 0000000000..4f21357677 --- /dev/null +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/FillTest.php @@ -0,0 +1,70 @@ +getTestAggregationBuilder()); + $fillStage + ->partitionByFields('field1', 'field2') + ->sortBy('field1', 1) + ->output() + ->field('foo')->locf() + ->field('bar')->linear() + ->field('fixed')->value(0); + + self::assertEquals( + [ + '$fill' => (object) [ + 'partitionByFields' => ['field1', 'field2'], + 'sortBy' => (object) ['field1' => 1], + 'output' => (object) [ + 'foo' => ['method' => 'locf'], + 'bar' => ['method' => 'linear'], + 'fixed' => ['value' => 0], + ], + ], + ], + $fillStage->getExpression(), + ); + } + + public function testCountFromBuilder(): void + { + $builder = $this->getTestAggregationBuilder(); + $builder->fill() + ->partitionBy('$field1') + ->sortBy('field1', 1) + ->output() + ->field('foo')->locf() + ->field('bar')->linear() + ->field('fixed')->value(0); + + self::assertEquals( + [ + [ + '$fill' => (object) [ + 'partitionBy' => '$field1', + 'sortBy' => (object) ['field1' => 1], + 'output' => (object) [ + 'foo' => ['method' => 'locf'], + 'bar' => ['method' => 'linear'], + 'fixed' => ['value' => 0], + ], + ], + ], + ], + $builder->getPipeline(), + ); + } +} From 4da931eb09d0e22ebde5c1583e1c8bcbb5ba32d4 Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Tue, 14 Mar 2023 15:15:55 +0100 Subject: [PATCH 03/23] Add $merge stage to aggregation pipeline builder --- .../ODM/MongoDB/Aggregation/Builder.php | 14 ++ .../ODM/MongoDB/Aggregation/Stage.php | 11 ++ .../ODM/MongoDB/Aggregation/Stage/Merge.php | 148 ++++++++++++++++++ phpstan-baseline.neon | 1 + .../Tests/Aggregation/Stage/MergeTest.php | 115 ++++++++++++++ 5 files changed, 289 insertions(+) create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Merge.php create mode 100644 tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/MergeTest.php diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php index e0c2a136df..97ece89029 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php @@ -442,6 +442,20 @@ public function matchExpr(): QueryExpr return $expr; } + /** + * Writes the results of the aggregation pipeline to a specified collection. + * The $merge operator must be the last stage in the pipeline. + * + * @see https://www.mongodb.com/docs/rapid/reference/operator/aggregation/merge/ + */ + public function merge(): Stage\Merge + { + $stage = new Stage\Merge($this, $this->dm); + $this->addStage($stage); + + return $stage; + } + /** * Takes the documents returned by the aggregation pipeline and writes them * to a specified collection. This must be the last stage in the pipeline. diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage.php index ce8a847a54..d4c3788977 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage.php @@ -262,6 +262,17 @@ public function match(): Stage\MatchStage return $this->builder->match(); } + /** + * Writes the results of the aggregation pipeline to a specified collection. + * The $merge operator must be the last stage in the pipeline. + * + * @see https://www.mongodb.com/docs/rapid/reference/operator/aggregation/merge/ + */ + public function merge(): Stage\Merge + { + return $this->builder->merge(); + } + /** * Takes the documents returned by the aggregation pipeline and writes them * to a specified collection. This must be the last stage in the pipeline. diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Merge.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Merge.php new file mode 100644 index 0000000000..28e9cd0bfa --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Merge.php @@ -0,0 +1,148 @@ +> + */ +class Merge extends Stage +{ + private DocumentManager $dm; + + /** + * @var string|array + * @psalm-var OutputCollection + */ + private $into; + + /** @var list */ + private array $on = []; + + /** @var array */ + private array $let = []; + + /** + * @var string|array|Builder + * @psalm-var WhenMatchedParam + */ + private $whenMatched; + + private ?string $whenNotMatched = null; + + public function __construct(Builder $builder, DocumentManager $documentManager) + { + parent::__construct($builder); + + $this->dm = $documentManager; + } + + public function getExpression(): array + { + $params = (object) [ + 'into' => $this->into, + ]; + + if ($this->on) { + $params->on = count($this->on) === 1 ? $this->on[0] : $this->on; + } + + if ($this->let) { + $params->let = $this->let; + } + + if ($this->whenMatched) { + $params->whenMatched = $this->whenMatched instanceof Builder + ? $this->whenMatched->getPipeline(false) + : $this->whenMatched; + } + + if ($this->whenNotMatched) { + $params->whenNotMatched = $this->whenNotMatched; + } + + return ['$merge' => $params]; + } + + /** + * @param string|array $collection + * @psalm-param OutputCollection $collection + */ + public function into($collection): self + { + if (is_array($collection)) { + $this->into = $collection; + + return $this; + } + + try { + $class = $this->dm->getClassMetadata($collection); + $this->into = $class->getCollection(); + } catch (MappingException $e) { + $this->into = $collection; + } + + return $this; + } + + /** + * Optional. Specifies variables to use in the pipeline stages. + * + * Use the variable expressions to access the fields from + * the joined collection's documents that are input to the pipeline. + * + * @param array $let + */ + public function let(array $let): self + { + $this->let = $let; + + return $this; + } + + public function on(string ...$fields): self + { + $this->on = array_values($fields); + + return $this; + } + + /** + * @param string|array|Builder|Stage $whenMatched + * @psalm-param WhenMatchedParam $whenMatched + */ + public function whenMatched($whenMatched): self + { + if ($whenMatched instanceof Stage) { + $this->whenMatched = $whenMatched->builder; + } else { + $this->whenMatched = $whenMatched; + } + + if ($this->builder === $this->whenMatched) { + throw new InvalidArgumentException('Cannot use the same Builder instance for $merge whenMatched pipeline.'); + } + + return $this; + } + + public function whenNotMatched(string $whenNotMatched): self + { + $this->whenNotMatched = $whenNotMatched; + + return $this; + } +} diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 409b6e9de0..e15bb06deb 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -155,6 +155,7 @@ parameters: paths: - lib/Doctrine/ODM/MongoDB/Aggregation/Stage/GraphLookup.php - lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Lookup.php + - lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Merge.php - lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Out.php # $this->mapping['targetDocument'] is class-string diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/MergeTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/MergeTest.php new file mode 100644 index 0000000000..3982ef45eb --- /dev/null +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/MergeTest.php @@ -0,0 +1,115 @@ +dm->createAggregationBuilder(SimpleReferenceUser::class); + $builder + ->merge() + ->into(User::class) + ->on('_id') + ->whenMatched('keepExisting'); + + $expectedPipeline = [ + [ + '$merge' => (object) [ + 'into' => 'users', + 'on' => '_id', + 'whenMatched' => 'keepExisting', + ], + ], + ]; + + self::assertEquals($expectedPipeline, $builder->getPipeline()); + } + + public function testMergeStageWithCollectionName(): void + { + $builder = $this->dm->createAggregationBuilder(SimpleReferenceUser::class); + $builder + ->merge() + ->into('someRandomCollectionName') + ->let(['foo' => 'bar']) + ->whenNotMatched('discard'); + + $expectedPipeline = [ + [ + '$merge' => (object) [ + 'into' => 'someRandomCollectionName', + 'let' => ['foo' => 'bar'], + 'whenNotMatched' => 'discard', + ], + ], + ]; + + self::assertEquals($expectedPipeline, $builder->getPipeline()); + } + + public static function providePipeline(): Generator + { + // TODO: use $set and $unset once they have been added + yield 'Array' => [ + 'pipeline' => [ + ['$addFields' => ['foo' => 'bar']], + ['$project' => ['bar' => false]], + ], + ]; + + yield 'Builder' => [ + 'pipeline' => static function (Builder $builder): Builder { + $builder + ->addFields() + ->field('foo')->expression('bar') + ->project() + ->excludeFields(['bar']); + + return $builder; + }, + ]; + } + + /** + * @param array>|callable $pipeline + * + * @dataProvider providePipeline + */ + public function testMergeStageWithPipeline($pipeline): void + { + if (is_callable($pipeline)) { + $pipeline = $pipeline($this->dm->createAggregationBuilder(SimpleReferenceUser::class)); + } + + $builder = $this->dm->createAggregationBuilder(SimpleReferenceUser::class); + $builder + ->merge() + ->into('someRandomCollectionName') + ->whenMatched($pipeline); + + $expectedPipeline = [ + [ + '$merge' => (object) [ + 'into' => 'someRandomCollectionName', + 'whenMatched' => [ + ['$addFields' => ['foo' => 'bar']], + ['$project' => ['bar' => false]], + ], + ], + ], + ]; + + self::assertEquals($expectedPipeline, $builder->getPipeline()); + } +} From 86432610395b8f8c05abd18fd8d0506ae80dd048 Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Tue, 14 Mar 2023 15:22:37 +0100 Subject: [PATCH 04/23] Add $replaceWith stage to aggregation pipeline builder --- .../ODM/MongoDB/Aggregation/Builder.php | 21 +++++ .../ODM/MongoDB/Aggregation/Stage.php | 18 ++++ .../MongoDB/Aggregation/Stage/ReplaceWith.php | 17 ++++ .../Aggregation/Stage/ReplaceWithTest.php | 90 +++++++++++++++++++ 4 files changed, 146 insertions(+) create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/ReplaceWith.php create mode 100644 tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/ReplaceWithTest.php diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php index 97ece89029..a574cdcefc 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php @@ -518,6 +518,27 @@ public function replaceRoot($expression = null): Stage\ReplaceRoot return $stage; } + /** + * Replaces the input document with the specified document. The operation + * replaces all existing fields in the input document, including the _id + * field. With $replaceWith, you can promote an embedded document to the + * top-level. You can also specify a new document as the replacement. + * + * The $replaceWith stage is an alias for $replaceRoot. + * + * @see https://www.mongodb.com/docs/rapid/reference/operator/aggregation/replaceWith/ + * + * @param string|mixed[]|Expr|null $expression Optional. A replacement expression that + * resolves to a document. + */ + public function replaceWith($expression = null): Stage\ReplaceWith + { + $stage = new Stage\ReplaceWith($this, $this->dm, $this->class, $expression); + $this->addStage($stage); + + return $stage; + } + /** * Controls if resulting iterator should be wrapped with CachingIterator. */ diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage.php index d4c3788977..5671a88c17 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage.php @@ -323,6 +323,24 @@ public function replaceRoot($expression = null): Stage\ReplaceRoot return $this->builder->replaceRoot($expression); } + /** + * Replaces the input document with the specified document. The operation + * replaces all existing fields in the input document, including the _id + * field. With $replaceWith, you can promote an embedded document to the + * top-level. You can also specify a new document as the replacement. + * + * The $replaceWith stage is an alias for $replaceRoot. + * + * @see https://www.mongodb.com/docs/rapid/reference/operator/aggregation/replaceWith/ + * + * @param string|mixed[]|Expr|null $expression Optional. A replacement expression that + * resolves to a document. + */ + public function replaceWith($expression = null): Stage\ReplaceWith + { + return $this->builder->replaceWith($expression); + } + /** * Controls if resulting iterator should be wrapped with CachingIterator. */ diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/ReplaceWith.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/ReplaceWith.php new file mode 100644 index 0000000000..7abcadc3c6 --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/ReplaceWith.php @@ -0,0 +1,17 @@ + $expression['$replaceRoot']['newRoot'], + ]; + } +} diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/ReplaceWithTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/ReplaceWithTest.php new file mode 100644 index 0000000000..c1107ae532 --- /dev/null +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/ReplaceWithTest.php @@ -0,0 +1,90 @@ +dm->createAggregationBuilder(User::class); + + $dateTime = new DateTimeImmutable('2000-01-01T00:00Z'); + $mongoDate = new UTCDateTime((int) $dateTime->format('Uv')); + $stage = $builder + ->replaceWith() + ->field('isToday') + ->eq('$createdAt', $dateTime); + + self::assertEquals( + [ + '$replaceWith' => (object) [ + 'isToday' => ['$eq' => ['$createdAt', $mongoDate]], + ], + ], + $stage->getExpression(), + ); + } + + public function testTypeConversionWithDirectExpression(): void + { + $builder = $this->dm->createAggregationBuilder(User::class); + + $dateTime = new DateTimeImmutable('2000-01-01T00:00Z'); + $mongoDate = new UTCDateTime((int) $dateTime->format('Uv')); + $stage = $builder + ->replaceWith( + $builder->expr() + ->field('isToday') + ->eq('$createdAt', $dateTime), + ); + + self::assertEquals( + [ + '$replaceWith' => (object) [ + 'isToday' => ['$eq' => ['$createdAt', $mongoDate]], + ], + ], + $stage->getExpression(), + ); + } + + public function testFieldNameConversion(): void + { + $builder = $this->dm->createAggregationBuilder(CmsComment::class); + + $stage = $builder + ->replaceWith() + ->field('someField') + ->concat('$authorIp', 'foo'); + + self::assertEquals( + [ + '$replaceWith' => (object) [ + 'someField' => ['$concat' => ['$ip', 'foo']], + ], + ], + $stage->getExpression(), + ); + } + + public function testFieldNameConversionWithDirectExpression(): void + { + $builder = $this->dm->createAggregationBuilder(CmsComment::class); + + $stage = $builder + ->replaceWith('$authorIp'); + + self::assertEquals( + ['$replaceWith' => '$ip'], + $stage->getExpression(), + ); + } +} From 3c1ee8de57af5e6139421ca6358681f694e3d1be Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Tue, 14 Mar 2023 16:26:52 +0100 Subject: [PATCH 05/23] Add $set stage to aggregation pipeline builder --- .../ODM/MongoDB/Aggregation/Builder.php | 16 +++++++++ .../ODM/MongoDB/Aggregation/Stage.php | 13 +++++++ .../MongoDB/Aggregation/Stage/AddFields.php | 13 +++++-- .../ODM/MongoDB/Aggregation/Stage/Set.php | 17 +++++++++ .../Tests/Aggregation/Stage/MergeTest.php | 8 ++--- .../Tests/Aggregation/Stage/SetTest.php | 35 +++++++++++++++++++ 6 files changed, 96 insertions(+), 6 deletions(-) create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Set.php create mode 100644 tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SetTest.php diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php index a574cdcefc..80216702e4 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php @@ -562,6 +562,22 @@ public function sample(int $size): Stage\Sample return $stage; } + /** + * Adds new fields to documents. $set outputs documents that contain all + * existing fields from the input documents and newly added fields. + * + * The $set stage is an alias for $addFields. + * + * @see https://www.mongodb.com/docs/rapid/reference/operator/aggregation/set/ + */ + public function set(): Stage\Set + { + $stage = new Stage\Set($this); + $this->addStage($stage); + + return $stage; + } + /** * Skips over the specified number of documents that pass into the stage and * passes the remaining documents to the next stage in the pipeline. diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage.php index 5671a88c17..c94f934dee 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage.php @@ -361,6 +361,19 @@ public function sample(int $size): Stage\Sample return $this->builder->sample($size); } + /** + * Adds new fields to documents. $set outputs documents that contain all + * existing fields from the input documents and newly added fields. + * + * The $set stage is an alias for $addFields. + * + * @see https://www.mongodb.com/docs/rapid/reference/operator/aggregation/set/ + */ + public function set(): Stage\Set + { + return $this->builder->set(); + } + /** * Skips over the specified number of documents that pass into the stage and * passes the remaining documents to the next stage in the pipeline. diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/AddFields.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/AddFields.php index 7bf77b2b76..ce30e51190 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/AddFields.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/AddFields.php @@ -7,12 +7,21 @@ /** * Fluent interface for adding a $addFields stage to an aggregation pipeline. */ -final class AddFields extends Operator +class AddFields extends Operator { + private bool $isSet = false; + + protected function isSet(): void + { + $this->isSet = true; + } + public function getExpression(): array { + $name = $this->isSet ? '$set' : '$addFields'; + return [ - '$addFields' => $this->expr->getExpression(), + $name => $this->expr->getExpression(), ]; } } diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Set.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Set.php new file mode 100644 index 0000000000..7d290f5875 --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Set.php @@ -0,0 +1,17 @@ +isSet(); + } +} diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/MergeTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/MergeTest.php index 3982ef45eb..ff4aae6134 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/MergeTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/MergeTest.php @@ -60,10 +60,10 @@ public function testMergeStageWithCollectionName(): void public static function providePipeline(): Generator { - // TODO: use $set and $unset once they have been added + // TODO: use $unset once it has been added yield 'Array' => [ 'pipeline' => [ - ['$addFields' => ['foo' => 'bar']], + ['$set' => ['foo' => 'bar']], ['$project' => ['bar' => false]], ], ]; @@ -71,7 +71,7 @@ public static function providePipeline(): Generator yield 'Builder' => [ 'pipeline' => static function (Builder $builder): Builder { $builder - ->addFields() + ->set() ->field('foo')->expression('bar') ->project() ->excludeFields(['bar']); @@ -103,7 +103,7 @@ public function testMergeStageWithPipeline($pipeline): void '$merge' => (object) [ 'into' => 'someRandomCollectionName', 'whenMatched' => [ - ['$addFields' => ['foo' => 'bar']], + ['$set' => ['foo' => 'bar']], ['$project' => ['bar' => false]], ], ], diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SetTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SetTest.php new file mode 100644 index 0000000000..70e3f463ed --- /dev/null +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SetTest.php @@ -0,0 +1,35 @@ +getTestAggregationBuilder()); + $setStage + ->field('product') + ->multiply('$field', 5); + + self::assertSame(['$set' => ['product' => ['$multiply' => ['$field', 5]]]], $setStage->getExpression()); + } + + public function testProjectFromBuilder(): void + { + $builder = $this->getTestAggregationBuilder(); + $builder + ->set() + ->field('product') + ->multiply('$field', 5); + + self::assertSame([['$set' => ['product' => ['$multiply' => ['$field', 5]]]]], $builder->getPipeline()); + } +} From 6a3dd48b2a0c01609a1460f410698278a21f1d4a Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Tue, 14 Mar 2023 16:38:14 +0100 Subject: [PATCH 06/23] Add $unset stage to aggregation pipeline builder --- .../ODM/MongoDB/Aggregation/Builder.php | 13 +++++++ .../ODM/MongoDB/Aggregation/Stage.php | 10 +++++ .../MongoDB/Aggregation/Stage/UnsetStage.php | 38 +++++++++++++++++++ .../Tests/Aggregation/Stage/MergeTest.php | 8 ++-- .../Tests/Aggregation/Stage/UnsetTest.php | 31 +++++++++++++++ 5 files changed, 95 insertions(+), 5 deletions(-) create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnsetStage.php create mode 100644 tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/UnsetTest.php diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php index 80216702e4..eb63d6674c 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php @@ -629,6 +629,19 @@ public function sortByCount(string $expression): Stage\SortByCount return $stage; } + /** + * Removes/excludes fields from documents. + * + * @see https://www.mongodb.com/docs/rapid/reference/operator/aggregation/unset/ + */ + public function unset(string ...$fields): Stage\UnsetStage + { + $stage = new Stage\UnsetStage($this, $this->getDocumentPersister(), ...$fields); + $this->addStage($stage); + + return $stage; + } + /** * Deconstructs an array field from the input documents to output a document * for each element. Each output document is the input document with the diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage.php index c94f934dee..c2b979d583 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage.php @@ -412,6 +412,16 @@ public function sort($fieldName, $order = null): Stage\Sort return $this->builder->sort($fieldName, $order); } + /** + * Removes/excludes fields from documents. + * + * @see https://www.mongodb.com/docs/rapid/reference/operator/aggregation/unset/ + */ + public function unset(string ...$fields): Stage\UnsetStage + { + return $this->builder->unset(...$fields); + } + /** * Deconstructs an array field from the input documents to output a document * for each element. Each output document is the input document with the diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnsetStage.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnsetStage.php new file mode 100644 index 0000000000..3582481a4e --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnsetStage.php @@ -0,0 +1,38 @@ + */ + private array $fields; + + public function __construct(Builder $builder, DocumentPersister $documentPersister, string ...$fields) + { + parent::__construct($builder); + + $this->documentPersister = $documentPersister; + $this->fields = array_values($fields); + } + + public function getExpression(): array + { + return [ + '$unset' => array_map([$this->documentPersister, 'prepareFieldName'], $this->fields), + ]; + } +} diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/MergeTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/MergeTest.php index ff4aae6134..9335204422 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/MergeTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/MergeTest.php @@ -60,11 +60,10 @@ public function testMergeStageWithCollectionName(): void public static function providePipeline(): Generator { - // TODO: use $unset once it has been added yield 'Array' => [ 'pipeline' => [ ['$set' => ['foo' => 'bar']], - ['$project' => ['bar' => false]], + ['$unset' => ['bar']], ], ]; @@ -73,8 +72,7 @@ public static function providePipeline(): Generator $builder ->set() ->field('foo')->expression('bar') - ->project() - ->excludeFields(['bar']); + ->unset('bar'); return $builder; }, @@ -104,7 +102,7 @@ public function testMergeStageWithPipeline($pipeline): void 'into' => 'someRandomCollectionName', 'whenMatched' => [ ['$set' => ['foo' => 'bar']], - ['$project' => ['bar' => false]], + ['$unset' => ['bar']], ], ], ], diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/UnsetTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/UnsetTest.php new file mode 100644 index 0000000000..ebf8a7d0e8 --- /dev/null +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/UnsetTest.php @@ -0,0 +1,31 @@ +dm->getUnitOfWork()->getDocumentPersister(User::class); + $unsetStage = new UnsetStage($this->getTestAggregationBuilder(), $documentPersister, 'id', 'foo', 'bar'); + + self::assertSame(['$unset' => ['_id', 'foo', 'bar']], $unsetStage->getExpression()); + } + + public function testLimitFromBuilder(): void + { + $builder = $this->getTestAggregationBuilder(); + $builder->unset('id', 'foo', 'bar'); + + self::assertSame([['$unset' => ['_id', 'foo', 'bar']]], $builder->getPipeline()); + } +} From f1bd9a73d2f7003cd0fa85413723934e5e4d5429 Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Tue, 14 Mar 2023 16:50:57 +0100 Subject: [PATCH 07/23] Add $unionWith stage to aggregation pipeline builder --- .../ODM/MongoDB/Aggregation/Builder.php | 15 ++++ .../ODM/MongoDB/Aggregation/Stage.php | 12 +++ .../MongoDB/Aggregation/Stage/UnionWith.php | 75 +++++++++++++++++++ phpstan-baseline.neon | 1 + .../Tests/Aggregation/Stage/UnionWithTest.php | 50 +++++++++++++ 5 files changed, 153 insertions(+) create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnionWith.php create mode 100644 tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/UnionWithTest.php diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php index eb63d6674c..83c9aee51a 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php @@ -629,6 +629,21 @@ public function sortByCount(string $expression): Stage\SortByCount return $stage; } + /** + * Performs a union of two collections. $unionWith combines pipeline results + * from two collections into a single result set. The stage outputs the + * combined result set (including duplicates) to the next stage. + * + * @see https://www.mongodb.com/docs/rapid/reference/operator/aggregation/unionWith/ + */ + public function unionWith(string $collection): Stage\UnionWith + { + $stage = new Stage\UnionWith($this, $this->dm, $collection); + $this->addStage($stage); + + return $stage; + } + /** * Removes/excludes fields from documents. * diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage.php index c2b979d583..a1cad26540 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage.php @@ -412,6 +412,18 @@ public function sort($fieldName, $order = null): Stage\Sort return $this->builder->sort($fieldName, $order); } + /** + * Performs a union of two collections. $unionWith combines pipeline results + * from two collections into a single result set. The stage outputs the + * combined result set (including duplicates) to the next stage. + * + * @see https://www.mongodb.com/docs/rapid/reference/operator/aggregation/unionWith/ + */ + public function unionWith(string $collection): Stage\UnionWith + { + return $this->builder->unionWith($collection); + } + /** * Removes/excludes fields from documents. * diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnionWith.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnionWith.php new file mode 100644 index 0000000000..12d235ad6a --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnionWith.php @@ -0,0 +1,75 @@ +> + */ +class UnionWith extends Stage +{ + private DocumentManager $dm; + + private string $collection; + + /** + * @var array|Builder|null + * @psalm-var Pipeline|null + */ + private $pipeline = null; + + public function __construct(Builder $builder, DocumentManager $documentManager, string $collection) + { + parent::__construct($builder); + + $this->dm = $documentManager; + + try { + $class = $this->dm->getClassMetadata($collection); + $this->collection = $class->getCollection(); + } catch (MappingException $e) { + $this->collection = $collection; + } + } + + /** + * @param array|Builder|Stage $pipeline + * @psalm-param Pipeline $pipeline + */ + public function pipeline($pipeline): self + { + if ($pipeline instanceof Stage) { + $this->pipeline = $pipeline->builder; + } else { + $this->pipeline = $pipeline; + } + + if ($this->builder === $this->pipeline) { + throw new InvalidArgumentException('Cannot use the same Builder instance for $unionWith pipeline.'); + } + + return $this; + } + + public function getExpression(): array + { + $params = (object) ['coll' => $this->collection]; + + if ($this->pipeline) { + $params->pipeline = $this->pipeline instanceof Builder + ? $this->pipeline->getPipeline(false) + : $this->pipeline; + } + + return ['$unionWith' => $params]; + } +} diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index e15bb06deb..30b248edaa 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -157,6 +157,7 @@ parameters: - lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Lookup.php - lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Merge.php - lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Out.php + - lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnionWith.php # $this->mapping['targetDocument'] is class-string - diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/UnionWithTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/UnionWithTest.php new file mode 100644 index 0000000000..8908d5c95d --- /dev/null +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/UnionWithTest.php @@ -0,0 +1,50 @@ +dm->createAggregationBuilder(SimpleReferenceUser::class); + $builder + ->unionWith(User::class); + + $expectedPipeline = [ + ['$unionWith' => (object) ['coll' => 'users']], + ]; + + self::assertEquals($expectedPipeline, $builder->getPipeline()); + } + + public function testUnionWithStageWithCollectionName(): void + { + $unionBuilder = $this->dm->createAggregationBuilder(SimpleReferenceUser::class); + $unionBuilder->match() + ->field('foo')->equals('bar'); + + $builder = $this->dm->createAggregationBuilder(SimpleReferenceUser::class); + $builder + ->unionWith('someRandomCollectionName') + ->pipeline($unionBuilder); + + $expectedPipeline = [ + [ + '$unionWith' => (object) [ + 'coll' => 'someRandomCollectionName', + 'pipeline' => [ + ['$match' => ['foo' => 'bar']], + ], + ], + ], + ]; + + self::assertEquals($expectedPipeline, $builder->getPipeline()); + } +} From e35e5d8a230c231958906f7f8b272d6e7a5effde Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Wed, 15 Mar 2023 14:29:01 +0100 Subject: [PATCH 08/23] Use templates for Builder::addStage --- .../ODM/MongoDB/Aggregation/Builder.php | 93 +++++++------------ 1 file changed, 34 insertions(+), 59 deletions(-) diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php index 83c9aee51a..38b93aeecf 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php @@ -83,9 +83,8 @@ public function __construct(DocumentManager $dm, string $documentName) public function addFields(): Stage\AddFields { $stage = new Stage\AddFields($this); - $this->addStage($stage); - return $stage; + return $this->addStage($stage); } /** @@ -103,9 +102,8 @@ public function addFields(): Stage\AddFields public function bucket(): Stage\Bucket { $stage = new Stage\Bucket($this, $this->dm, $this->class); - $this->addStage($stage); - return $stage; + return $this->addStage($stage); } /** @@ -125,9 +123,8 @@ public function bucket(): Stage\Bucket public function bucketAuto(): Stage\BucketAuto { $stage = new Stage\BucketAuto($this, $this->dm, $this->class); - $this->addStage($stage); - return $stage; + return $this->addStage($stage); } /** @@ -141,9 +138,8 @@ public function bucketAuto(): Stage\BucketAuto public function collStats(): Stage\CollStats { $stage = new Stage\CollStats($this); - $this->addStage($stage); - return $stage; + return $this->addStage($stage); } /** @@ -155,9 +151,8 @@ public function collStats(): Stage\CollStats public function count(string $fieldName): Stage\Count { $stage = new Stage\Count($this, $fieldName); - $this->addStage($stage); - return $stage; + return $this->addStage($stage); } /** @@ -168,9 +163,8 @@ public function count(string $fieldName): Stage\Count public function densify(string $fieldName): Stage\Densify { $stage = new Stage\Densify($this, $fieldName); - $this->addStage($stage); - return $stage; + return $this->addStage($stage); } /** @@ -208,9 +202,8 @@ public function expr(): Expr public function facet(): Stage\Facet { $stage = new Stage\Facet($this); - $this->addStage($stage); - return $stage; + return $this->addStage($stage); } /** @@ -221,9 +214,8 @@ public function facet(): Stage\Facet public function fill(): Stage\Fill { $stage = new Stage\Fill($this); - $this->addStage($stage); - return $stage; + return $this->addStage($stage); } /** @@ -244,9 +236,8 @@ public function fill(): Stage\Fill public function geoNear($x, $y = null): Stage\GeoNear { $stage = new Stage\GeoNear($this, $x, $y); - $this->addStage($stage); - return $stage; + return $this->addStage($stage); } /** @@ -347,9 +338,8 @@ public function getStage(int $index): Stage public function graphLookup(string $from): Stage\GraphLookup { $stage = new Stage\GraphLookup($this, $from, $this->dm, $this->class); - $this->addStage($stage); - return $stage; + return $this->addStage($stage); } /** @@ -361,9 +351,8 @@ public function graphLookup(string $from): Stage\GraphLookup public function group(): Stage\Group { $stage = new Stage\Group($this); - $this->addStage($stage); - return $stage; + return $this->addStage($stage); } /** @@ -384,9 +373,8 @@ public function hydrate(?string $className): self public function indexStats(): Stage\IndexStats { $stage = new Stage\IndexStats($this); - $this->addStage($stage); - return $stage; + return $this->addStage($stage); } /** @@ -397,9 +385,8 @@ public function indexStats(): Stage\IndexStats public function limit(int $limit): Stage\Limit { $stage = new Stage\Limit($this, $limit); - $this->addStage($stage); - return $stage; + return $this->addStage($stage); } /** @@ -412,9 +399,8 @@ public function limit(int $limit): Stage\Limit public function lookup(string $from): Stage\Lookup { $stage = new Stage\Lookup($this, $from, $this->dm, $this->class); - $this->addStage($stage); - return $stage; + return $this->addStage($stage); } /** @@ -426,9 +412,8 @@ public function lookup(string $from): Stage\Lookup public function match(): Stage\MatchStage { $stage = new Stage\MatchStage($this); - $this->addStage($stage); - return $stage; + return $this->addStage($stage); } /** @@ -451,9 +436,8 @@ public function matchExpr(): QueryExpr public function merge(): Stage\Merge { $stage = new Stage\Merge($this, $this->dm); - $this->addStage($stage); - return $stage; + return $this->addStage($stage); } /** @@ -465,9 +449,8 @@ public function merge(): Stage\Merge public function out(string $from): Stage\Out { $stage = new Stage\Out($this, $from, $this->dm); - $this->addStage($stage); - return $stage; + return $this->addStage($stage); } /** @@ -480,9 +463,8 @@ public function out(string $from): Stage\Out public function project(): Stage\Project { $stage = new Stage\Project($this); - $this->addStage($stage); - return $stage; + return $this->addStage($stage); } /** @@ -494,9 +476,8 @@ public function project(): Stage\Project public function redact(): Stage\Redact { $stage = new Stage\Redact($this); - $this->addStage($stage); - return $stage; + return $this->addStage($stage); } /** @@ -513,9 +494,8 @@ public function redact(): Stage\Redact public function replaceRoot($expression = null): Stage\ReplaceRoot { $stage = new Stage\ReplaceRoot($this, $this->dm, $this->class, $expression); - $this->addStage($stage); - return $stage; + return $this->addStage($stage); } /** @@ -534,9 +514,8 @@ public function replaceRoot($expression = null): Stage\ReplaceRoot public function replaceWith($expression = null): Stage\ReplaceWith { $stage = new Stage\ReplaceWith($this, $this->dm, $this->class, $expression); - $this->addStage($stage); - return $stage; + return $this->addStage($stage); } /** @@ -557,9 +536,8 @@ public function rewindable(bool $rewindable = true): self public function sample(int $size): Stage\Sample { $stage = new Stage\Sample($this, $size); - $this->addStage($stage); - return $stage; + return $this->addStage($stage); } /** @@ -573,9 +551,8 @@ public function sample(int $size): Stage\Sample public function set(): Stage\Set { $stage = new Stage\Set($this); - $this->addStage($stage); - return $stage; + return $this->addStage($stage); } /** @@ -587,9 +564,8 @@ public function set(): Stage\Set public function skip(int $skip): Stage\Skip { $stage = new Stage\Skip($this, $skip); - $this->addStage($stage); - return $stage; + return $this->addStage($stage); } /** @@ -610,9 +586,8 @@ public function sort($fieldName, $order = null): Stage\Sort $fields = is_array($fieldName) ? $fieldName : [$fieldName => $order]; // fixme: move to sort stage $stage = new Stage\Sort($this, $this->getDocumentPersister()->prepareSort($fields)); - $this->addStage($stage); - return $stage; + return $this->addStage($stage); } /** @@ -624,9 +599,8 @@ public function sort($fieldName, $order = null): Stage\Sort public function sortByCount(string $expression): Stage\SortByCount { $stage = new Stage\SortByCount($this, $expression, $this->dm, $this->class); - $this->addStage($stage); - return $stage; + return $this->addStage($stage); } /** @@ -639,9 +613,8 @@ public function sortByCount(string $expression): Stage\SortByCount public function unionWith(string $collection): Stage\UnionWith { $stage = new Stage\UnionWith($this, $this->dm, $collection); - $this->addStage($stage); - return $stage; + return $this->addStage($stage); } /** @@ -652,9 +625,8 @@ public function unionWith(string $collection): Stage\UnionWith public function unset(string ...$fields): Stage\UnsetStage { $stage = new Stage\UnsetStage($this, $this->getDocumentPersister(), ...$fields); - $this->addStage($stage); - return $stage; + return $this->addStage($stage); } /** @@ -668,15 +640,18 @@ public function unwind(string $fieldName): Stage\Unwind { // Fixme: move field name translation to stage $stage = new Stage\Unwind($this, $this->getDocumentPersister()->prepareFieldName($fieldName)); - $this->addStage($stage); - return $stage; + return $this->addStage($stage); } /** * Allows adding an arbitrary stage to the pipeline * - * @return Stage The method returns the stage given as an argument + * @param T $stage + * + * @return T The method returns the stage given as an argument + * + * @template T of Stage */ public function addStage(Stage $stage): Stage { From 4fe0c0f8ef58c576b435371402734247a990a467 Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Wed, 22 Mar 2023 12:06:05 +0100 Subject: [PATCH 09/23] Add template covariance error to psalm baseline --- psalm-baseline.xml | 205 +++++++++++++++++++++++---------------------- 1 file changed, 105 insertions(+), 100 deletions(-) diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 49373a5ce8..5579d1ab4a 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,5 +1,5 @@ - + IteratorAggregate @@ -12,8 +12,8 @@ - $reflectionClass->implementsInterface(GridFSRepository::class) - $reflectionClass->implementsInterface(ObjectRepository::class) + implementsInterface(GridFSRepository::class)]]> + implementsInterface(ObjectRepository::class)]]> @@ -33,7 +33,7 @@ - array<string|null> + ]]> $mapping @@ -43,29 +43,29 @@ $mapping - [$this->identifier => $this->getIdentifierValue($object)] + identifier => $this->getIdentifierValue($object)]]]> - $this->associationMappings - $this->associationMappings - $this->fieldMappings + associationMappings]]> + associationMappings]]> + fieldMappings]]> $mapping $mapping - array_filter( - $this->associationMappings, - static fn ($assoc) => ! empty($assoc['embedded']) - ) + associationMappings, + static fn ($assoc) => ! empty($assoc['embedded']) + )]]> FieldMapping FieldMappingConfig - array<string, FieldMapping> + ]]> array - array<string|null> + ]]> @@ -84,64 +84,64 @@ $options - $xmlRoot->field - $xmlRoot->id - $xmlRoot->{'also-load-methods'} - $xmlRoot->{'default-discriminator-value'} - $xmlRoot->{'discriminator-field'} - $xmlRoot->{'discriminator-map'} - $xmlRoot->{'embed-many'} - $xmlRoot->{'embed-one'} - $xmlRoot->{'indexes'} - $xmlRoot->{'lifecycle-callbacks'} - $xmlRoot->{'read-preference'} - $xmlRoot->{'reference-many'} - $xmlRoot->{'reference-one'} - $xmlRoot->{'schema-validation'} - $xmlRoot->{'shard-key'} + field]]> + id]]> + {'also-load-methods'}]]> + {'default-discriminator-value'}]]> + {'discriminator-field'}]]> + {'discriminator-map'}]]> + {'embed-many'}]]> + {'embed-one'}]]> + {'indexes'}]]> + {'lifecycle-callbacks'}]]> + {'read-preference'}]]> + {'reference-many'}]]> + {'reference-one'}]]> + {'schema-validation'}]]> + {'shard-key'}]]> - (bool) $mapping['background'] - (bool) $mapping['sparse'] - (bool) $mapping['unique'] - (string) $mapping['index-name'] + + + + assert($attributes instanceof SimpleXMLElement) - isset($xmlRoot->field) - isset($xmlRoot->id) - isset($xmlRoot->{'default-discriminator-value'}) - isset($xmlRoot->{'discriminator-field'}) - isset($xmlRoot->{'discriminator-map'}) - isset($xmlRoot->{'embed-many'}) - isset($xmlRoot->{'embed-one'}) - isset($xmlRoot->{'indexes'}) - isset($xmlRoot->{'lifecycle-callbacks'}) - isset($xmlRoot->{'read-preference'}) - isset($xmlRoot->{'reference-many'}) - isset($xmlRoot->{'reference-one'}) - isset($xmlRoot->{'schema-validation'}) - isset($xmlRoot->{'shard-key'}) + field)]]> + id)]]> + {'default-discriminator-value'})]]> + {'discriminator-field'})]]> + {'discriminator-map'})]]> + {'embed-many'})]]> + {'embed-one'})]]> + {'indexes'})]]> + {'lifecycle-callbacks'})]]> + {'read-preference'})]]> + {'reference-many'})]]> + {'reference-one'})]]> + {'schema-validation'})]]> + {'shard-key'})]]> - $xmlRoot->getName() === 'document' - $xmlRoot->getName() === 'embedded-document' - $xmlRoot->getName() === 'gridfs-file' - $xmlRoot->getName() === 'mapped-superclass' - $xmlRoot->getName() === 'query-result-document' - $xmlRoot->getName() === 'view' - isset($xmlRoot->{'also-load-methods'}) + getName() === 'document']]> + getName() === 'embedded-document']]> + getName() === 'gridfs-file']]> + getName() === 'mapped-superclass']]> + getName() === 'query-result-document']]> + getName() === 'view']]> + {'also-load-methods'})]]> - new PersistentCollection($coll, $dm, $dm->getUnitOfWork()) - new PersistentCollection($coll, $dm, $dm->getUnitOfWork()) + getUnitOfWork())]]> + getUnitOfWork())]]> - is_object($this->coll) + coll)]]> @@ -151,7 +151,7 @@ - $this->resolvedNames + resolvedNames]]> @@ -164,10 +164,10 @@ $query - $this->query - $this->query - $this->query - $this->query + query]]> + query]]> + query]]> + query]]> @@ -253,18 +253,23 @@ $mapping - $this->parentAssociations + parentAssociations]]> $divided - array<class-string, array{0: ClassMetadata<object>, 1: array<string, object>}> + , 1: array}>]]> - $mapping['targetDocument'] + + + + $documentPersister + + $mapping + $defaultFieldMapping @@ -275,12 +280,12 @@ - $class->associationMappings['ref'] - $class->associationMappings['ref1'] - $class->associationMappings['ref2'] - $class->associationMappings['ref3'] - $class->associationMappings['ref4'] - ['storeAs' => ClassMetadata::REFERENCE_STORE_AS_DB_REF] + associationMappings['ref']]]> + associationMappings['ref1']]]> + associationMappings['ref2']]]> + associationMappings['ref3']]]> + associationMappings['ref4']]]> + ClassMetadata::REFERENCE_STORE_AS_DB_REF]]]> @@ -290,9 +295,9 @@ - [$user->categories[0]->children, $user->categories[1]->children] - [$user->categories[0]->children[0]->children, $user->categories[0]->children[1]->children] - [$user->categories[0]->children[0]->children, $user->categories[0]->children[1]->children] + categories[0]->children, $user->categories[1]->children]]]> + categories[0]->children[0]->children, $user->categories[0]->children[1]->children]]]> + categories[0]->children[0]->children, $user->categories[0]->children[1]->children]]]> @@ -310,14 +315,14 @@ - new ArrayCollection([ + - new ArrayCollection([ + ])]]> + + ])]]> @@ -327,12 +332,12 @@ - ['upsert' => true] + true]]]> - $doc->embeds + embeds]]> @@ -366,11 +371,11 @@ - new ArrayCollection([ + + ])]]> new ArrayCollection([$referenceMany1, $referenceMany2]) @@ -384,29 +389,29 @@ DocumentManager - $this->dm + dm]]> $config $mapping - [ - 'fieldName' => 'assoc', - 'reference' => true, - 'type' => 'many', - 'targetDocument' => 'stdClass', - 'repositoryMethod' => 'fetch', - $prop => $value, - ] - [ - 'fieldName' => 'enum', - 'enumType' => Card::class, - ] - [ - 'fieldName' => 'enum', - 'enumType' => SuitNonBacked::class, - ] + 'assoc', + 'reference' => true, + 'type' => 'many', + 'targetDocument' => 'stdClass', + 'repositoryMethod' => 'fetch', + $prop => $value, + ]]]> + 'enum', + 'enumType' => Card::class, + ]]]> + 'enum', + 'enumType' => SuitNonBacked::class, + ]]]> @@ -416,7 +421,7 @@ - ['type' => -1] + -1]]]> From c1117f244748df304eae2326b87b772fc9c7da02 Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Thu, 23 Mar 2023 08:51:11 +0100 Subject: [PATCH 10/23] Remove unnecessary abstraction for $addFields and $set --- .../ODM/MongoDB/Aggregation/Stage/AddFields.php | 15 ++------------- .../ODM/MongoDB/Aggregation/Stage/Set.php | 13 ++++++------- 2 files changed, 8 insertions(+), 20 deletions(-) diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/AddFields.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/AddFields.php index ce30e51190..63e15ed131 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/AddFields.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/AddFields.php @@ -7,21 +7,10 @@ /** * Fluent interface for adding a $addFields stage to an aggregation pipeline. */ -class AddFields extends Operator +final class AddFields extends Operator { - private bool $isSet = false; - - protected function isSet(): void - { - $this->isSet = true; - } - public function getExpression(): array { - $name = $this->isSet ? '$set' : '$addFields'; - - return [ - $name => $this->expr->getExpression(), - ]; + return ['$addFields' => $this->expr->getExpression()]; } } diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Set.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Set.php index 7d290f5875..b11f5c16d2 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Set.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Set.php @@ -4,14 +4,13 @@ namespace Doctrine\ODM\MongoDB\Aggregation\Stage; -use Doctrine\ODM\MongoDB\Aggregation\Builder; - -class Set extends AddFields +/** + * Fluent interface for adding a $set stage to an aggregation pipeline. + */ +final class Set extends Operator { - public function __construct(Builder $builder) + public function getExpression(): array { - parent::__construct($builder); - - $this->isSet(); + return ['$set' => $this->expr->getExpression()]; } } From 6f72a6a8f5873448eb960c6e521e4a323d4050a6 Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Thu, 23 Mar 2023 08:53:34 +0100 Subject: [PATCH 11/23] Use array_fields for lists in variadic arguments --- lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Densify.php | 4 +++- lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill.php | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Densify.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Densify.php index a803f247fc..8e731bb534 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Densify.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Densify.php @@ -7,6 +7,8 @@ use Doctrine\ODM\MongoDB\Aggregation\Builder; use Doctrine\ODM\MongoDB\Aggregation\Stage; +use function array_values; + /** * Fluent interface for adding a $densify stage to an aggregation pipeline. */ @@ -28,7 +30,7 @@ public function __construct(Builder $builder, string $fieldName) public function partitionByFields(string ...$fields): self { - $this->partitionByFields = $fields; + $this->partitionByFields = array_values($fields); return $this; } diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill.php index b26783fb75..4fc564f64e 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill.php @@ -9,6 +9,7 @@ use Doctrine\ODM\MongoDB\Aggregation\Stage; use Doctrine\ODM\MongoDB\Aggregation\Stage\Fill\Output; +use function array_values; use function is_array; use function is_string; use function strtolower; @@ -46,7 +47,7 @@ public function partitionBy($expression): self public function partitionByFields(string ...$fields): self { - $this->partitionByFields = $fields; + $this->partitionByFields = array_values($fields); return $this; } From cc4abd9927a243f61f673d3a3f8116853d5e5dde Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Thu, 23 Mar 2023 08:58:41 +0100 Subject: [PATCH 12/23] Improve wording when field name is required in Expr classes --- lib/Doctrine/ODM/MongoDB/Aggregation/Expr.php | 18 ++----- .../ODM/MongoDB/Aggregation/Stage/Facet.php | 2 +- .../MongoDB/Aggregation/Stage/Fill/Output.php | 5 +- lib/Doctrine/ODM/MongoDB/Query/Expr.php | 50 +++++++++---------- .../Tests/Aggregation/Stage/FacetTest.php | 2 +- 5 files changed, 35 insertions(+), 42 deletions(-) diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Expr.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Expr.php index 32cf7d1f5f..fd5133ed5a 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Expr.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Expr.php @@ -19,6 +19,7 @@ use function func_get_args; use function is_array; use function is_string; +use function sprintf; use function substr; /** @@ -469,7 +470,10 @@ public function expr(): self */ public function expression($value): self { - $this->requiresCurrentField(__METHOD__); + if (! $this->currentField) { + throw new LogicException(sprintf('%s requires setting a current field using field().', __METHOD__)); + } + $this->expr[$this->currentField] = $this->ensureArray($value); return $this; @@ -1685,18 +1689,6 @@ private function operator(string $operator, $expression): self return $this; } - /** - * Ensure that a current field has been set. - * - * @throws LogicException if a current field has not been set. - */ - private function requiresCurrentField(?string $method = null): void - { - if (! $this->currentField) { - throw new LogicException(($method ?: 'This method') . ' requires you set a current field using field().'); - } - } - /** @throws BadMethodCallException if there is no current switch operator. */ private function requiresSwitchStatement(?string $method = null): void { diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Facet.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Facet.php index 4d2ef13cab..95fbad8560 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Facet.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Facet.php @@ -47,7 +47,7 @@ public function pipeline($builder): self { /** @psalm-suppress RedundantPropertyInitializationCheck because the property might not be set yet */ if (! isset($this->field)) { - throw new LogicException(__METHOD__ . ' requires you set a current field using field().'); + throw new LogicException(__METHOD__ . ' requires setting a current field using field().'); } if ($builder instanceof Stage) { diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill/Output.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill/Output.php index 8c0cfbb1ec..f1a4bc38b8 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill/Output.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill/Output.php @@ -10,6 +10,7 @@ use LogicException; use function func_get_args; +use function sprintf; /** * Fluent builder for output param of $fill stage @@ -100,10 +101,10 @@ public function getExpression(): array * * @throws LogicException if a current field has not been set. */ - private function requiresCurrentField(?string $method = null): void + private function requiresCurrentField(string $method): void { if (! $this->currentField) { - throw new LogicException(($method ?: 'This method') . ' requires you set a current field using field().'); + throw new LogicException(sprintf('%s requires setting a current field using field().', $method)); } } } diff --git a/lib/Doctrine/ODM/MongoDB/Query/Expr.php b/lib/Doctrine/ODM/MongoDB/Query/Expr.php index eb238ed2ad..414094fa42 100644 --- a/lib/Doctrine/ODM/MongoDB/Query/Expr.php +++ b/lib/Doctrine/ODM/MongoDB/Query/Expr.php @@ -162,7 +162,7 @@ public function addOr($expression, ...$expressions): self */ public function addToSet($valueOrExpression): self { - $this->requiresCurrentField(); + $this->requiresCurrentField(__METHOD__); $this->newObj['$addToSet'][$this->currentField] = static::convertExpression($valueOrExpression, $this->class); return $this; @@ -188,7 +188,7 @@ public function all(array $values): self */ protected function bit(string $operator, int $value): self { - $this->requiresCurrentField(); + $this->requiresCurrentField(__METHOD__); $this->newObj['$bit'][$this->currentField][$operator] = $value; return $this; @@ -227,7 +227,7 @@ public function bitOr(int $value): self */ public function bitsAllClear($value): self { - $this->requiresCurrentField(); + $this->requiresCurrentField(__METHOD__); return $this->operator('$bitsAllClear', $value); } @@ -243,7 +243,7 @@ public function bitsAllClear($value): self */ public function bitsAllSet($value): self { - $this->requiresCurrentField(); + $this->requiresCurrentField(__METHOD__); return $this->operator('$bitsAllSet', $value); } @@ -259,7 +259,7 @@ public function bitsAllSet($value): self */ public function bitsAnyClear($value): self { - $this->requiresCurrentField(); + $this->requiresCurrentField(__METHOD__); return $this->operator('$bitsAnyClear', $value); } @@ -275,7 +275,7 @@ public function bitsAnyClear($value): self */ public function bitsAnySet($value): self { - $this->requiresCurrentField(); + $this->requiresCurrentField(__METHOD__); return $this->operator('$bitsAnySet', $value); } @@ -345,7 +345,7 @@ public function currentDate(string $type = 'date'): self throw new InvalidArgumentException('Type for currentDate operator must be date or timestamp.'); } - $this->requiresCurrentField(); + $this->requiresCurrentField(__METHOD__); $this->newObj['$currentDate'][$this->currentField]['$type'] = $type; return $this; @@ -646,7 +646,7 @@ public function in(array $values): self */ public function inc($value): self { - $this->requiresCurrentField(); + $this->requiresCurrentField(__METHOD__); $this->newObj['$inc'][$this->currentField] = $value; return $this; @@ -657,7 +657,7 @@ public function inc($value): self */ public function includesReferenceTo(object $document): self { - $this->requiresCurrentField(); + $this->requiresCurrentField(__METHOD__); $mapping = $this->getReferenceMapping(); $reference = $this->dm->createReference($document, $mapping); $storeAs = $mapping['storeAs'] ?? null; @@ -755,7 +755,7 @@ public function lte($value): self */ public function max($value): self { - $this->requiresCurrentField(); + $this->requiresCurrentField(__METHOD__); $this->newObj['$max'][$this->currentField] = $value; return $this; @@ -771,7 +771,7 @@ public function max($value): self */ public function min($value): self { - $this->requiresCurrentField(); + $this->requiresCurrentField(__METHOD__); $this->newObj['$min'][$this->currentField] = $value; return $this; @@ -803,7 +803,7 @@ public function mod($divisor, $remainder = 0): self */ public function mul($value): self { - $this->requiresCurrentField(); + $this->requiresCurrentField(__METHOD__); $this->newObj['$mul'][$this->currentField] = $value; return $this; @@ -929,7 +929,7 @@ public function operator(string $operator, $value): self */ public function popFirst(): self { - $this->requiresCurrentField(); + $this->requiresCurrentField(__METHOD__); $this->newObj['$pop'][$this->currentField] = -1; return $this; @@ -943,7 +943,7 @@ public function popFirst(): self */ public function popLast(): self { - $this->requiresCurrentField(); + $this->requiresCurrentField(__METHOD__); $this->newObj['$pop'][$this->currentField] = 1; return $this; @@ -973,7 +973,7 @@ public function position(int $position): self */ public function pull($valueOrExpression): self { - $this->requiresCurrentField(); + $this->requiresCurrentField(__METHOD__); $this->newObj['$pull'][$this->currentField] = static::convertExpression($valueOrExpression, $this->class); return $this; @@ -990,7 +990,7 @@ public function pull($valueOrExpression): self */ public function pullAll(array $values): self { - $this->requiresCurrentField(); + $this->requiresCurrentField(__METHOD__); $this->newObj['$pullAll'][$this->currentField] = $values; return $this; @@ -1024,7 +1024,7 @@ public function push($valueOrExpression): self ); } - $this->requiresCurrentField(); + $this->requiresCurrentField(__METHOD__); $this->newObj['$push'][$this->currentField] = $valueOrExpression; return $this; @@ -1051,7 +1051,7 @@ public function range($start, $end): self */ public function references(object $document): self { - $this->requiresCurrentField(); + $this->requiresCurrentField(__METHOD__); $mapping = $this->getReferenceMapping(); $reference = $this->dm->createReference($document, $mapping); $storeAs = $mapping['storeAs'] ?? null; @@ -1100,7 +1100,7 @@ public function references(object $document): self */ public function rename(string $name): self { - $this->requiresCurrentField(); + $this->requiresCurrentField(__METHOD__); $this->newObj['$rename'][$this->currentField] = $name; return $this; @@ -1120,7 +1120,7 @@ public function rename(string $name): self */ public function set($value, bool $atomic = true): self { - $this->requiresCurrentField(); + $this->requiresCurrentField(__METHOD__); assert($this->currentField !== null); if ($atomic) { @@ -1184,7 +1184,7 @@ public function setNewObj(array $newObj): self */ public function setOnInsert($value): self { - $this->requiresCurrentField(); + $this->requiresCurrentField(__METHOD__); $this->newObj['$setOnInsert'][$this->currentField] = $value; return $this; @@ -1289,7 +1289,7 @@ public function type($type): self */ public function unsetField(): self { - $this->requiresCurrentField(); + $this->requiresCurrentField(__METHOD__); $this->newObj['$unset'][$this->currentField] = 1; return $this; @@ -1319,7 +1319,7 @@ public function where($javascript): self */ private function getReferenceMapping(): array { - $this->requiresCurrentField(); + $this->requiresCurrentField(__METHOD__); assert($this->currentField !== null); try { @@ -1368,10 +1368,10 @@ private function normalizeSortOrder($order): int * * @throws LogicException If a current field has not been set. */ - private function requiresCurrentField(): void + private function requiresCurrentField(string $method): void { if (! $this->currentField) { - throw new LogicException('This method requires you set a current field using field().'); + throw new LogicException(sprintf('%s requires setting a current field using field().', $method)); } } diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/FacetTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/FacetTest.php index 86ddfb25dd..5d1920b893 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/FacetTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/FacetTest.php @@ -62,7 +62,7 @@ public function testFacetThrowsExceptionWithoutFieldName(): void $facetStage = new Facet($this->getTestAggregationBuilder()); $this->expectException(LogicException::class); - $this->expectExceptionMessage('requires you set a current field using field().'); + $this->expectExceptionMessage('requires setting a current field using field().'); $facetStage->pipeline($this->getTestAggregationBuilder()); } From 227186ba76b85a7d80b23efc693062f99268ab32 Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Thu, 23 Mar 2023 08:59:40 +0100 Subject: [PATCH 13/23] Add missing type to whenMatched option --- lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Merge.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Merge.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Merge.php index 28e9cd0bfa..8c85a17a24 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Merge.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Merge.php @@ -35,7 +35,7 @@ class Merge extends Stage private array $let = []; /** - * @var string|array|Builder + * @var string|array|Builder|Stage * @psalm-var WhenMatchedParam */ private $whenMatched; From acfe9745df87c85701b4fd41f5d635479fcf935b Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Thu, 23 Mar 2023 09:00:27 +0100 Subject: [PATCH 14/23] Update type for let option --- lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Merge.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Merge.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Merge.php index 8c85a17a24..9ec37dab50 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Merge.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Merge.php @@ -5,6 +5,7 @@ namespace Doctrine\ODM\MongoDB\Aggregation\Stage; use Doctrine\ODM\MongoDB\Aggregation\Builder; +use Doctrine\ODM\MongoDB\Aggregation\Expr; use Doctrine\ODM\MongoDB\Aggregation\Stage; use Doctrine\ODM\MongoDB\DocumentManager; use Doctrine\Persistence\Mapping\MappingException; @@ -31,7 +32,7 @@ class Merge extends Stage /** @var list */ private array $on = []; - /** @var array */ + /** @var array */ private array $let = []; /** @@ -104,7 +105,7 @@ public function into($collection): self * Use the variable expressions to access the fields from * the joined collection's documents that are input to the pipeline. * - * @param array $let + * @param array $let */ public function let(array $let): self { From 0568e9084da0a677925b8f89aa28464fcf567c03 Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Thu, 23 Mar 2023 09:01:55 +0100 Subject: [PATCH 15/23] Use ? syntax for nullable type --- lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnionWith.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnionWith.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnionWith.php index 12d235ad6a..491014ca54 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnionWith.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnionWith.php @@ -23,7 +23,7 @@ class UnionWith extends Stage /** * @var array|Builder|null - * @psalm-var Pipeline|null + * @psalm-var ?Pipeline */ private $pipeline = null; From 7cd17776bbfb613b958291944eee03fe5b29e99a Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Thu, 23 Mar 2023 09:06:16 +0100 Subject: [PATCH 16/23] Rename test methods to hide copy/paste --- .../Tests/Aggregation/Stage/AddFieldsTest.php | 4 +-- .../Aggregation/Stage/BucketAutoTest.php | 6 ++-- .../Tests/Aggregation/Stage/BucketTest.php | 6 ++-- .../Tests/Aggregation/Stage/CollStatsTest.php | 10 +++--- .../Tests/Aggregation/Stage/CountTest.php | 4 +-- .../Tests/Aggregation/Stage/DensifyTest.php | 4 +-- .../Tests/Aggregation/Stage/FacetTest.php | 8 ++--- .../Tests/Aggregation/Stage/FillTest.php | 4 +-- .../Tests/Aggregation/Stage/GeoNearTest.php | 4 +-- .../Aggregation/Stage/GraphLookupTest.php | 14 ++++---- .../Tests/Aggregation/Stage/GroupTest.php | 6 ++-- .../Aggregation/Stage/IndexStatsTest.php | 4 +-- .../Tests/Aggregation/Stage/LimitTest.php | 4 +-- .../Tests/Aggregation/Stage/LookupTest.php | 32 +++++++++---------- .../Aggregation/Stage/MatchStageTest.php | 4 +-- .../Tests/Aggregation/Stage/MergeTest.php | 6 ++-- .../Tests/Aggregation/Stage/OutTest.php | 6 ++-- .../Tests/Aggregation/Stage/ProjectTest.php | 4 +-- .../Tests/Aggregation/Stage/RedactTest.php | 4 +-- .../Tests/Aggregation/Stage/SampleTest.php | 4 +-- .../Tests/Aggregation/Stage/SetTest.php | 4 +-- .../Tests/Aggregation/Stage/SkipTest.php | 4 +-- .../Tests/Aggregation/Stage/SortTest.php | 4 +-- .../Tests/Aggregation/Stage/UnionWithTest.php | 4 +-- .../Tests/Aggregation/Stage/UnsetTest.php | 4 +-- .../Tests/Aggregation/Stage/UnwindTest.php | 6 ++-- 26 files changed, 82 insertions(+), 82 deletions(-) diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/AddFieldsTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/AddFieldsTest.php index c863b6ac1f..c6017577f6 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/AddFieldsTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/AddFieldsTest.php @@ -12,7 +12,7 @@ class AddFieldsTest extends BaseTest { use AggregationTestTrait; - public function testAddFieldsStage(): void + public function testStage(): void { $addFieldsStage = new AddFields($this->getTestAggregationBuilder()); $addFieldsStage @@ -22,7 +22,7 @@ public function testAddFieldsStage(): void self::assertSame(['$addFields' => ['product' => ['$multiply' => ['$field', 5]]]], $addFieldsStage->getExpression()); } - public function testProjectFromBuilder(): void + public function testFromBuilder(): void { $builder = $this->getTestAggregationBuilder(); $builder diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/BucketAutoTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/BucketAutoTest.php index bbc8a79cfa..b5bac4f761 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/BucketAutoTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/BucketAutoTest.php @@ -15,7 +15,7 @@ class BucketAutoTest extends BaseTest { use AggregationTestTrait; - public function testBucketAutoStage(): void + public function testStage(): void { $bucketStage = new BucketAuto($this->getTestAggregationBuilder(), $this->dm, new ClassMetadata(User::class)); $bucketStage @@ -36,7 +36,7 @@ public function testBucketAutoStage(): void ], $bucketStage->getExpression()); } - public function testBucketAutoFromBuilder(): void + public function testFromBuilder(): void { $builder = $this->getTestAggregationBuilder(); $builder->bucketAuto() @@ -59,7 +59,7 @@ public function testBucketAutoFromBuilder(): void ], $builder->getPipeline()); } - public function testBucketAutoSkipsUndefinedProperties(): void + public function testSkipsUndefinedProperties(): void { $bucketStage = new BucketAuto($this->getTestAggregationBuilder(), $this->dm, new ClassMetadata(User::class)); $bucketStage diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/BucketTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/BucketTest.php index 2837cdb653..f23323e542 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/BucketTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/BucketTest.php @@ -15,7 +15,7 @@ class BucketTest extends BaseTest { use AggregationTestTrait; - public function testBucketStage(): void + public function testStage(): void { $bucketStage = new Bucket($this->getTestAggregationBuilder(), $this->dm, new ClassMetadata(User::class)); $bucketStage @@ -36,7 +36,7 @@ public function testBucketStage(): void ], $bucketStage->getExpression()); } - public function testBucketFromBuilder(): void + public function testBuilder(): void { $builder = $this->getTestAggregationBuilder(); $builder->bucket() @@ -59,7 +59,7 @@ public function testBucketFromBuilder(): void ], $builder->getPipeline()); } - public function testBucketSkipsUndefinedProperties(): void + public function testSkipsUndefinedProperties(): void { $bucketStage = new Bucket($this->getTestAggregationBuilder(), $this->dm, new ClassMetadata(User::class)); $bucketStage diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/CollStatsTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/CollStatsTest.php index 20c8c89d25..22ab166094 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/CollStatsTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/CollStatsTest.php @@ -12,14 +12,14 @@ class CollStatsTest extends BaseTest { use AggregationTestTrait; - public function testCollStatsStage(): void + public function testStage(): void { $collStatsStage = new CollStats($this->getTestAggregationBuilder()); self::assertSame(['$collStats' => []], $collStatsStage->getExpression()); } - public function testCollStatsStageWithLatencyStats(): void + public function testStageWithLatencyStats(): void { $collStatsStage = new CollStats($this->getTestAggregationBuilder()); $collStatsStage->showLatencyStats(); @@ -27,7 +27,7 @@ public function testCollStatsStageWithLatencyStats(): void self::assertSame(['$collStats' => ['latencyStats' => ['histograms' => false]]], $collStatsStage->getExpression()); } - public function testCollStatsStageWithLatencyStatsHistograms(): void + public function testStageWithLatencyStatsHistograms(): void { $collStatsStage = new CollStats($this->getTestAggregationBuilder()); $collStatsStage->showLatencyStats(true); @@ -35,7 +35,7 @@ public function testCollStatsStageWithLatencyStatsHistograms(): void self::assertSame(['$collStats' => ['latencyStats' => ['histograms' => true]]], $collStatsStage->getExpression()); } - public function testCollStatsStageWithStorageStats(): void + public function testStageWithStorageStats(): void { $collStatsStage = new CollStats($this->getTestAggregationBuilder()); $collStatsStage->showStorageStats(); @@ -43,7 +43,7 @@ public function testCollStatsStageWithStorageStats(): void self::assertSame(['$collStats' => ['storageStats' => []]], $collStatsStage->getExpression()); } - public function testCollStatsFromBuilder(): void + public function testFromBuilder(): void { $builder = $this->getTestAggregationBuilder(); $builder->collStats() diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/CountTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/CountTest.php index a10d79807c..dcb14bf7f8 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/CountTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/CountTest.php @@ -12,14 +12,14 @@ class CountTest extends BaseTest { use AggregationTestTrait; - public function testCountStage(): void + public function testStage(): void { $countStage = new Count($this->getTestAggregationBuilder(), 'document_count'); self::assertSame(['$count' => 'document_count'], $countStage->getExpression()); } - public function testCountFromBuilder(): void + public function testFromBuilder(): void { $builder = $this->getTestAggregationBuilder(); $builder->count('document_count'); diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/DensifyTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/DensifyTest.php index ae527c95a1..64a9865825 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/DensifyTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/DensifyTest.php @@ -12,7 +12,7 @@ class DensifyTest extends BaseTest { use AggregationTestTrait; - public function testDensifyStage(): void + public function testStage(): void { $densifyStage = new Densify($this->getTestAggregationBuilder(), 'someField'); $densifyStage @@ -34,7 +34,7 @@ public function testDensifyStage(): void ); } - public function testCountFromBuilder(): void + public function testFromBuilder(): void { $builder = $this->getTestAggregationBuilder(); $builder->densify('someField'); diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/FacetTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/FacetTest.php index 5d1920b893..ba254fb2bd 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/FacetTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/FacetTest.php @@ -15,7 +15,7 @@ class FacetTest extends BaseTest { use AggregationTestTrait; - public function testFacetStage(): void + public function testStage(): void { $nestedBuilder = $this->getTestAggregationBuilder(); $nestedBuilder->sortByCount('$tags'); @@ -35,7 +35,7 @@ public function testFacetStage(): void ], $facetStage->getExpression()); } - public function testFacetFromBuilder(): void + public function testFromBuilder(): void { $nestedBuilder = $this->getTestAggregationBuilder(); $nestedBuilder->sortByCount('$tags'); @@ -57,7 +57,7 @@ public function testFacetFromBuilder(): void ], $builder->getPipeline()); } - public function testFacetThrowsExceptionWithoutFieldName(): void + public function testThrowsExceptionWithoutFieldName(): void { $facetStage = new Facet($this->getTestAggregationBuilder()); @@ -67,7 +67,7 @@ public function testFacetThrowsExceptionWithoutFieldName(): void } /** @psalm-suppress InvalidArgument on purpose to throw exception */ - public function testFacetThrowsExceptionOnInvalidPipeline(): void + public function testThrowsExceptionOnInvalidPipeline(): void { $facetStage = new Facet($this->getTestAggregationBuilder()); diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/FillTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/FillTest.php index 4f21357677..5b6da57f9b 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/FillTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/FillTest.php @@ -12,7 +12,7 @@ class FillTest extends BaseTest { use AggregationTestTrait; - public function testFillStage(): void + public function testStage(): void { $fillStage = new Fill($this->getTestAggregationBuilder()); $fillStage @@ -39,7 +39,7 @@ public function testFillStage(): void ); } - public function testCountFromBuilder(): void + public function testFromBuilder(): void { $builder = $this->getTestAggregationBuilder(); $builder->fill() diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/GeoNearTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/GeoNearTest.php index 0edb0abf11..a21964021f 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/GeoNearTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/GeoNearTest.php @@ -12,7 +12,7 @@ class GeoNearTest extends BaseTest { use AggregationTestTrait; - public function testGeoNearStage(): void + public function testStage(): void { $geoNearStage = new GeoNear($this->getTestAggregationBuilder(), 0, 0); $geoNearStage @@ -24,7 +24,7 @@ public function testGeoNearStage(): void self::assertSame(['$geoNear' => $stage], $geoNearStage->getExpression()); } - public function testGeoNearFromBuilder(): void + public function testFromBuilder(): void { $builder = $this->getTestAggregationBuilder(); $builder diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/GraphLookupTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/GraphLookupTest.php index b734114b90..cc20237df9 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/GraphLookupTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/GraphLookupTest.php @@ -24,7 +24,7 @@ class GraphLookupTest extends BaseTest { use AggregationTestTrait; - public function testGraphLookupStage(): void + public function testStage(): void { $graphLookupStage = new GraphLookup($this->getTestAggregationBuilder(), 'employees', $this->dm, new ClassMetadata(User::class)); $graphLookupStage @@ -48,7 +48,7 @@ public function testGraphLookupStage(): void ); } - public function testGraphLookupFromBuilder(): void + public function testFromBuilder(): void { $builder = $this->getTestAggregationBuilder(); $builder->graphLookup('employees') @@ -74,7 +74,7 @@ public function testGraphLookupFromBuilder(): void ); } - public function testGraphLookupWithMatch(): void + public function testWithMatch(): void { $builder = $this->getTestAggregationBuilder(); $builder->graphLookup('employees') @@ -152,7 +152,7 @@ public function provideEmployeeAggregations(): array * * @dataProvider provideEmployeeAggregations */ - public function testGraphLookupWithEmployees(Closure $addGraphLookupStage, array $expectedFields): void + public function testWithEmployees(Closure $addGraphLookupStage, array $expectedFields): void { $this->insertEmployeeTestData(); @@ -225,7 +225,7 @@ public function provideTravellerAggregations(): array * * @dataProvider provideTravellerAggregations */ - public function testGraphLookupWithTraveller(Closure $addGraphLookupStage, array $expectedFields): void + public function testWithTraveller(Closure $addGraphLookupStage, array $expectedFields): void { $this->insertTravellerTestData(); @@ -251,7 +251,7 @@ public function testGraphLookupWithTraveller(Closure $addGraphLookupStage, array self::assertCount(3, $result); } - public function testGraphLookupWithUnmappedFields(): void + public function testWithUnmappedFields(): void { $builder = $this->dm->createAggregationBuilder(User::class); @@ -278,7 +278,7 @@ public function testGraphLookupWithUnmappedFields(): void self::assertEquals($expectedPipeline, $builder->getPipeline()); } - public function testGraphLookupWithconnectFromFieldToDifferentTargetClass(): void + public function testWithconnectFromFieldToDifferentTargetClass(): void { $builder = $this->dm->createAggregationBuilder(User::class); diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/GroupTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/GroupTest.php index fe7ba4234c..27bf887d7d 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/GroupTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/GroupTest.php @@ -70,7 +70,7 @@ static function (Expr $expr) { ]; } - public function testGroupStage(): void + public function testStage(): void { $groupStage = new Group($this->getTestAggregationBuilder()); $groupStage @@ -82,7 +82,7 @@ public function testGroupStage(): void self::assertSame(['$group' => ['_id' => '$field', 'count' => ['$sum' => 1]]], $groupStage->getExpression()); } - public function testGroupFromBuilder(): void + public function testFromBuilder(): void { $builder = $this->getTestAggregationBuilder(); $builder @@ -95,7 +95,7 @@ public function testGroupFromBuilder(): void self::assertSame([['$group' => ['_id' => '$field', 'count' => ['$sum' => 1]]]], $builder->getPipeline()); } - public function testGroupWithOperatorInId(): void + public function testWithOperatorInId(): void { $groupStage = new Group($this->getTestAggregationBuilder()); $groupStage diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/IndexStatsTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/IndexStatsTest.php index a1e26ab932..8efa61df13 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/IndexStatsTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/IndexStatsTest.php @@ -13,14 +13,14 @@ class IndexStatsTest extends BaseTest { use AggregationTestTrait; - public function testIndexStatsStage(): void + public function testStage(): void { $indexStatsStage = new IndexStats($this->getTestAggregationBuilder()); self::assertEquals(['$indexStats' => new stdClass()], $indexStatsStage->getExpression()); } - public function testIndexStatsFromBuilder(): void + public function testFromBuilder(): void { $builder = $this->getTestAggregationBuilder(); $builder->indexStats(); diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/LimitTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/LimitTest.php index f1dc448e54..b9695527fa 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/LimitTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/LimitTest.php @@ -12,14 +12,14 @@ class LimitTest extends BaseTest { use AggregationTestTrait; - public function testLimitStage(): void + public function testStage(): void { $limitStage = new Limit($this->getTestAggregationBuilder(), 10); self::assertSame(['$limit' => 10], $limitStage->getExpression()); } - public function testLimitFromBuilder(): void + public function testFromBuilder(): void { $builder = $this->getTestAggregationBuilder(); $builder->limit(10); diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/LookupTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/LookupTest.php index 9e55fa8684..ccadae3e6c 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/LookupTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/LookupTest.php @@ -20,7 +20,7 @@ public function setUp(): void $this->insertTestData(); } - public function testLookupStage(): void + public function testStage(): void { $builder = $this->dm->createAggregationBuilder(SimpleReferenceUser::class); $builder @@ -47,7 +47,7 @@ public function testLookupStage(): void self::assertSame('alcaeus', $result[0]['user'][0]['username']); } - public function testLookupStageWithPipelineAsArray(): void + public function testStageWithPipelineAsArray(): void { $builder = $this->dm->createAggregationBuilder(SimpleReferenceUser::class); $builder @@ -91,7 +91,7 @@ public function testLookupStageWithPipelineAsArray(): void $this->assertEquals($expectedPipeline, $builder->getPipeline()); } - public function testLookupStageWithPipelineAsStage(): void + public function testStageWithPipelineAsStage(): void { $builder = $this->dm->createAggregationBuilder(SimpleReferenceUser::class); $lookupPipelineBuilder = $this->dm->createAggregationBuilder(User::class); @@ -133,7 +133,7 @@ public function testLookupStageWithPipelineAsStage(): void $this->assertEquals($expectedPipeline, $builder->getPipeline()); } - public function testLookupThrowsExceptionUsingSameBuilderForPipeline(): void + public function testThrowsExceptionUsingSameBuilderForPipeline(): void { $builder = $this->dm->createAggregationBuilder(SimpleReferenceUser::class); @@ -151,7 +151,7 @@ public function testLookupThrowsExceptionUsingSameBuilderForPipeline(): void ); } - public function testLookupStageWithPipelineAndLocalForeignFields(): void + public function testStageWithPipelineAndLocalForeignFields(): void { $builder = $this->dm->createAggregationBuilder(SimpleReferenceUser::class); $lookupPipelineBuilder = $this->dm->createAggregationBuilder(User::class); @@ -192,7 +192,7 @@ public function testLookupStageWithPipelineAndLocalForeignFields(): void $this->assertEquals($expectedPipeline, $builder->getPipeline()); } - public function testLookupStageWithFieldNameTranslation(): void + public function testStageWithFieldNameTranslation(): void { $builder = $this->dm->createAggregationBuilder(SimpleReferenceUser::class); $builder @@ -215,7 +215,7 @@ public function testLookupStageWithFieldNameTranslation(): void self::assertEquals($expectedPipeline, $builder->getPipeline()); } - public function testLookupStageWithClassName(): void + public function testStageWithClassName(): void { $builder = $this->dm->createAggregationBuilder(SimpleReferenceUser::class); $builder @@ -244,7 +244,7 @@ public function testLookupStageWithClassName(): void self::assertSame('alcaeus', $result[0]['user'][0]['username']); } - public function testLookupStageWithCollectionName(): void + public function testStageWithCollectionName(): void { $builder = $this->dm->createAggregationBuilder(SimpleReferenceUser::class); $builder @@ -272,7 +272,7 @@ public function testLookupStageWithCollectionName(): void self::assertEmpty($result[0]['user']); } - public function testLookupStageReferenceMany(): void + public function testStageReferenceMany(): void { $builder = $this->dm->createAggregationBuilder(SimpleReferenceUser::class); $builder @@ -303,7 +303,7 @@ public function testLookupStageReferenceMany(): void self::assertSame('malarzm', $result[1]['users'][0]['username']); } - public function testLookupStageReferenceManyStoreAsRef(): void + public function testStageReferenceManyStoreAsRef(): void { $builder = $this->dm->createAggregationBuilder(ReferenceUser::class); $builder @@ -334,7 +334,7 @@ public function testLookupStageReferenceManyStoreAsRef(): void self::assertSame('malarzm', $result[1]['users'][0]['username']); } - public function testLookupStageReferenceOneInverse(): void + public function testStageReferenceOneInverse(): void { $builder = $this->dm->createAggregationBuilder(User::class); $builder @@ -366,7 +366,7 @@ public function testLookupStageReferenceOneInverse(): void self::assertCount(1, $result[0]['simpleReferenceOneInverse']); } - public function testLookupStageReferenceManyInverse(): void + public function testStageReferenceManyInverse(): void { $builder = $this->dm->createAggregationBuilder(User::class); $builder @@ -398,7 +398,7 @@ public function testLookupStageReferenceManyInverse(): void self::assertCount(1, $result[0]['simpleReferenceManyInverse']); } - public function testLookupStageReferenceOneInverseStoreAsRef(): void + public function testStageReferenceOneInverseStoreAsRef(): void { $builder = $this->dm->createAggregationBuilder(User::class); $builder @@ -430,7 +430,7 @@ public function testLookupStageReferenceOneInverseStoreAsRef(): void self::assertCount(1, $result[0]['embeddedReferenceOneInverse']); } - public function testLookupStageReferenceManyInverseStoreAsRef(): void + public function testStageReferenceManyInverseStoreAsRef(): void { $builder = $this->dm->createAggregationBuilder(User::class); $builder @@ -489,7 +489,7 @@ private function insertTestData(): void $this->dm->flush(); } - public function testLookupStageAndDefaultAlias(): void + public function testStageAndDefaultAlias(): void { $builder = $this->dm->createAggregationBuilder(User::class); $builder @@ -512,7 +512,7 @@ public function testLookupStageAndDefaultAlias(): void self::assertCount(1, $result[0]['simpleReferenceOneInverse']); } - public function testLookupStageAndDefaultAliasOverride(): void + public function testStageAndDefaultAliasOverride(): void { $builder = $this->dm->createAggregationBuilder(User::class); $builder diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/MatchStageTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/MatchStageTest.php index 941d207943..d6d3b22e67 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/MatchStageTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/MatchStageTest.php @@ -18,7 +18,7 @@ class MatchStageTest extends BaseTest { use AggregationTestTrait; - public function testMatchStage(): void + public function testStage(): void { $matchStage = new MatchStage($this->getTestAggregationBuilder()); $matchStage @@ -28,7 +28,7 @@ public function testMatchStage(): void self::assertSame(['$match' => ['someField' => 'someValue']], $matchStage->getExpression()); } - public function testMatchFromBuilder(): void + public function testFromBuilder(): void { $builder = $this->getTestAggregationBuilder(); $builder diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/MergeTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/MergeTest.php index 9335204422..b9efe9848c 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/MergeTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/MergeTest.php @@ -14,7 +14,7 @@ class MergeTest extends BaseTest { - public function testMergeStageWithClassName(): void + public function testStageWithClassName(): void { $builder = $this->dm->createAggregationBuilder(SimpleReferenceUser::class); $builder @@ -36,7 +36,7 @@ public function testMergeStageWithClassName(): void self::assertEquals($expectedPipeline, $builder->getPipeline()); } - public function testMergeStageWithCollectionName(): void + public function testStageWithCollectionName(): void { $builder = $this->dm->createAggregationBuilder(SimpleReferenceUser::class); $builder @@ -84,7 +84,7 @@ public static function providePipeline(): Generator * * @dataProvider providePipeline */ - public function testMergeStageWithPipeline($pipeline): void + public function testStageWithPipeline($pipeline): void { if (is_callable($pipeline)) { $pipeline = $pipeline($this->dm->createAggregationBuilder(SimpleReferenceUser::class)); diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/OutTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/OutTest.php index f592ce5a81..a8781eee25 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/OutTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/OutTest.php @@ -12,7 +12,7 @@ class OutTest extends BaseTest { - public function testOutStageWithClassName(): void + public function testStageWithClassName(): void { $builder = $this->dm->createAggregationBuilder(SimpleReferenceUser::class); $builder @@ -25,7 +25,7 @@ public function testOutStageWithClassName(): void self::assertEquals($expectedPipeline, $builder->getPipeline()); } - public function testOutStageWithCollectionName(): void + public function testStageWithCollectionName(): void { $builder = $this->dm->createAggregationBuilder(SimpleReferenceUser::class); $builder @@ -38,7 +38,7 @@ public function testOutStageWithCollectionName(): void self::assertEquals($expectedPipeline, $builder->getPipeline()); } - public function testOutStageWithShardedClassName(): void + public function testStageWithShardedClassName(): void { $builder = $this->dm->createAggregationBuilder(SimpleReferenceUser::class); $this->expectException(MappingException::class); diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/ProjectTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/ProjectTest.php index 8a296e825b..244b45dc7f 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/ProjectTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/ProjectTest.php @@ -16,7 +16,7 @@ class ProjectTest extends BaseTest { use AggregationTestTrait; - public function testProjectStage(): void + public function testStage(): void { $projectStage = new Project($this->getTestAggregationBuilder()); $projectStage @@ -28,7 +28,7 @@ public function testProjectStage(): void self::assertSame(['$project' => ['_id' => false, '$field' => true, '$otherField' => true, 'product' => ['$multiply' => ['$field', 5]]]], $projectStage->getExpression()); } - public function testProjectFromBuilder(): void + public function testFromBuilder(): void { $builder = $this->getTestAggregationBuilder(); $builder diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/RedactTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/RedactTest.php index e2a2fffbeb..2de2f038d1 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/RedactTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/RedactTest.php @@ -12,7 +12,7 @@ class RedactTest extends BaseTest { use AggregationTestTrait; - public function testRedactStage(): void + public function testStage(): void { $builder = $this->getTestAggregationBuilder(); @@ -27,7 +27,7 @@ public function testRedactStage(): void self::assertSame(['$redact' => ['$cond' => ['if' => ['$lte' => ['$accessLevel', 3]], 'then' => '$$KEEP', 'else' => '$$REDACT']]], $redactStage->getExpression()); } - public function testRedactFromBuilder(): void + public function testFromBuilder(): void { $builder = $this->getTestAggregationBuilder(); $builder diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SampleTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SampleTest.php index 1ee8259e5b..91e2f15b30 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SampleTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SampleTest.php @@ -12,14 +12,14 @@ class SampleTest extends BaseTest { use AggregationTestTrait; - public function testSampleStage(): void + public function testStage(): void { $sampleStage = new Sample($this->getTestAggregationBuilder(), 10); self::assertSame(['$sample' => ['size' => 10]], $sampleStage->getExpression()); } - public function testSampleFromBuilder(): void + public function testFromBuilder(): void { $builder = $this->getTestAggregationBuilder(); $builder->sample(10); diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SetTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SetTest.php index 70e3f463ed..5d41949c26 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SetTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SetTest.php @@ -12,7 +12,7 @@ class SetTest extends BaseTest { use AggregationTestTrait; - public function testSetStage(): void + public function testStage(): void { $setStage = new Set($this->getTestAggregationBuilder()); $setStage @@ -22,7 +22,7 @@ public function testSetStage(): void self::assertSame(['$set' => ['product' => ['$multiply' => ['$field', 5]]]], $setStage->getExpression()); } - public function testProjectFromBuilder(): void + public function testFromBuilder(): void { $builder = $this->getTestAggregationBuilder(); $builder diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SkipTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SkipTest.php index 3e3a988a98..a88d902b3d 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SkipTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SkipTest.php @@ -12,14 +12,14 @@ class SkipTest extends BaseTest { use AggregationTestTrait; - public function testSkipStage(): void + public function testStage(): void { $skipStage = new Skip($this->getTestAggregationBuilder(), 10); self::assertSame(['$skip' => 10], $skipStage->getExpression()); } - public function testSkipFromBuilder(): void + public function testFromBuilder(): void { $builder = $this->getTestAggregationBuilder(); $builder->skip(10); diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SortTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SortTest.php index 489690a208..3ffe4ac47f 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SortTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SortTest.php @@ -19,7 +19,7 @@ class SortTest extends BaseTest * * @dataProvider provideSortOptions */ - public function testSortStage(array $expectedSort, $field, ?string $order = null): void + public function testStage(array $expectedSort, $field, ?string $order = null): void { $sortStage = new Sort($this->getTestAggregationBuilder(), $field, $order); @@ -32,7 +32,7 @@ public function testSortStage(array $expectedSort, $field, ?string $order = null * * @dataProvider provideSortOptions */ - public function testSortFromBuilder(array $expectedSort, $field, ?string $order = null): void + public function testFromBuilder(array $expectedSort, $field, ?string $order = null): void { $builder = $this->getTestAggregationBuilder(); $builder->sort($field, $order); diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/UnionWithTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/UnionWithTest.php index 8908d5c95d..ba52c5385c 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/UnionWithTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/UnionWithTest.php @@ -10,7 +10,7 @@ class UnionWithTest extends BaseTest { - public function testUnionWithStageWithClassName(): void + public function testStageWithClassName(): void { $builder = $this->dm->createAggregationBuilder(SimpleReferenceUser::class); $builder @@ -23,7 +23,7 @@ public function testUnionWithStageWithClassName(): void self::assertEquals($expectedPipeline, $builder->getPipeline()); } - public function testUnionWithStageWithCollectionName(): void + public function testStageWithCollectionName(): void { $unionBuilder = $this->dm->createAggregationBuilder(SimpleReferenceUser::class); $unionBuilder->match() diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/UnsetTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/UnsetTest.php index ebf8a7d0e8..08b5b268eb 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/UnsetTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/UnsetTest.php @@ -13,7 +13,7 @@ class UnsetTest extends BaseTest { use AggregationTestTrait; - public function testUnsetStage(): void + public function testStage(): void { $documentPersister = $this->dm->getUnitOfWork()->getDocumentPersister(User::class); $unsetStage = new UnsetStage($this->getTestAggregationBuilder(), $documentPersister, 'id', 'foo', 'bar'); @@ -21,7 +21,7 @@ public function testUnsetStage(): void self::assertSame(['$unset' => ['_id', 'foo', 'bar']], $unsetStage->getExpression()); } - public function testLimitFromBuilder(): void + public function testFromBuilder(): void { $builder = $this->getTestAggregationBuilder(); $builder->unset('id', 'foo', 'bar'); diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/UnwindTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/UnwindTest.php index 70601e1bd3..071989e9ee 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/UnwindTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/UnwindTest.php @@ -12,14 +12,14 @@ class UnwindTest extends BaseTest { use AggregationTestTrait; - public function testUnwindStage(): void + public function testStage(): void { $unwindStage = new Unwind($this->getTestAggregationBuilder(), 'fieldName'); self::assertSame(['$unwind' => 'fieldName'], $unwindStage->getExpression()); } - public function testUnwindStageWithNewFields(): void + public function testStageWithNewFields(): void { $unwindStage = new Unwind($this->getTestAggregationBuilder(), 'fieldName'); $unwindStage @@ -29,7 +29,7 @@ public function testUnwindStageWithNewFields(): void self::assertSame(['$unwind' => ['path' => 'fieldName', 'includeArrayIndex' => 'index', 'preserveNullAndEmptyArrays' => true]], $unwindStage->getExpression()); } - public function testUnwindFromBuilder(): void + public function testFromBuilder(): void { $builder = $this->getTestAggregationBuilder(); $builder->unwind('fieldName'); From f831285174e3aabbb225c1a1eabe00aba57fea91 Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Thu, 23 Mar 2023 09:10:42 +0100 Subject: [PATCH 17/23] Test all range options for $densify --- .../ODM/MongoDB/Aggregation/Stage/Densify.php | 5 ++- .../Tests/Aggregation/Stage/DensifyTest.php | 45 +++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Densify.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Densify.php index 8e731bb534..aeb2fdc5c3 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Densify.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Densify.php @@ -6,6 +6,7 @@ use Doctrine\ODM\MongoDB\Aggregation\Builder; use Doctrine\ODM\MongoDB\Aggregation\Stage; +use MongoDB\BSON\UTCDateTime; use function array_values; @@ -36,8 +37,8 @@ public function partitionByFields(string ...$fields): self } /** - * @param array{0: int, 1: int}|string $bounds - * @param int|float $step + * @param array{0: int|float|UTCDateTime, 1: int|float|UTCDateTime}|string $bounds + * @param int|float $step */ public function range($bounds, $step, string $unit = ''): self { diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/DensifyTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/DensifyTest.php index 64a9865825..84132c1998 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/DensifyTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/DensifyTest.php @@ -34,6 +34,51 @@ public function testStage(): void ); } + public function testStageWithPartialBounds(): void + { + $densifyStage = new Densify($this->getTestAggregationBuilder(), 'someField'); + $densifyStage + ->partitionByFields('field1', 'field2') + ->range([1.5, 2.5], 0.1); + + self::assertEquals( + [ + '$densify' => (object) [ + 'field' => 'someField', + 'partitionByFields' => ['field1', 'field2'], + 'range' => (object) [ + 'bounds' => [1.5, 2.5], + 'step' => 0.1, + ], + ], + ], + $densifyStage->getExpression(), + ); + } + + public function testStageWithRangeUnit(): void + { + $densifyStage = new Densify($this->getTestAggregationBuilder(), 'someField'); + $densifyStage + ->partitionByFields('field1', 'field2') + ->range('full', 1, 'minute'); + + self::assertEquals( + [ + '$densify' => (object) [ + 'field' => 'someField', + 'partitionByFields' => ['field1', 'field2'], + 'range' => (object) [ + 'bounds' => 'full', + 'step' => 1, + 'unit' => 'minute', + ], + ], + ], + $densifyStage->getExpression(), + ); + } + public function testFromBuilder(): void { $builder = $this->getTestAggregationBuilder(); From 742d27c2e4189de936ee297c263231d34f14d9e1 Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Thu, 23 Mar 2023 09:16:09 +0100 Subject: [PATCH 18/23] Test complex values and sort for $fill --- .../Tests/Aggregation/Stage/FillTest.php | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/FillTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/FillTest.php index 5b6da57f9b..7bab5a7ed2 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/FillTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/FillTest.php @@ -14,14 +14,18 @@ class FillTest extends BaseTest public function testStage(): void { - $fillStage = new Fill($this->getTestAggregationBuilder()); + $builder = $this->getTestAggregationBuilder(); + $fillStage = new Fill($builder); $fillStage ->partitionByFields('field1', 'field2') ->sortBy('field1', 1) ->output() ->field('foo')->locf() ->field('bar')->linear() - ->field('fixed')->value(0); + ->field('fixed')->value(0) + ->field('computed')->value( + $builder->expr()->multiply('$value', 5), + ); self::assertEquals( [ @@ -32,6 +36,33 @@ public function testStage(): void 'foo' => ['method' => 'locf'], 'bar' => ['method' => 'linear'], 'fixed' => ['value' => 0], + 'computed' => ['value' => ['$multiply' => ['$value', 5]]], + ], + ], + ], + $fillStage->getExpression(), + ); + } + + public function testStageWithComplexSort(): void + { + $fillStage = new Fill($this->getTestAggregationBuilder()); + $fillStage + ->partitionByFields('field1', 'field2') + ->sortBy(['field1' => 'asc', 'field2' => 'desc']) + ->output() + ->field('foo')->locf(); + + self::assertEquals( + [ + '$fill' => (object) [ + 'partitionByFields' => ['field1', 'field2'], + 'sortBy' => (object) [ + 'field1' => 1, + 'field2' => -1, + ], + 'output' => (object) [ + 'foo' => ['method' => 'locf'], ], ], ], From b8c6aff6acdc3b58c5edfb68b9a374372179fa1c Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Thu, 23 Mar 2023 09:20:34 +0100 Subject: [PATCH 19/23] Test reusing same builder for $merge --- .../Tests/Aggregation/Stage/MergeTest.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/MergeTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/MergeTest.php index b9efe9848c..df664b8cac 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/MergeTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/MergeTest.php @@ -9,6 +9,7 @@ use Documents\SimpleReferenceUser; use Documents\User; use Generator; +use InvalidArgumentException; use function is_callable; @@ -110,4 +111,19 @@ public function testStageWithPipeline($pipeline): void self::assertEquals($expectedPipeline, $builder->getPipeline()); } + + public function testStageWithReusedPipeline(): void + { + $builder = $this->dm->createAggregationBuilder(SimpleReferenceUser::class); + $setStage = $builder->set() + ->field('foo')->expression('bar'); + + self::expectException(InvalidArgumentException::class); + self::expectExceptionMessage('Cannot use the same Builder instance for $merge whenMatched pipeline.'); + + $builder + ->merge() + ->into('someRandomCollectionName') + ->whenMatched($setStage); + } } From e517015dc81505b42f60d8421b2dd790cdd0b2c6 Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Thu, 23 Mar 2023 09:22:00 +0100 Subject: [PATCH 20/23] Simplify creation of UTCDateTime instances in tests --- tests/Doctrine/ODM/MongoDB/Tests/Aggregation/BuilderTest.php | 4 ++-- .../ODM/MongoDB/Tests/Aggregation/Stage/ReplaceRootTest.php | 4 ++-- .../ODM/MongoDB/Tests/Aggregation/Stage/ReplaceWithTest.php | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/BuilderTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/BuilderTest.php index 1fd2d294e7..31ee28f5f5 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/BuilderTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/BuilderTest.php @@ -285,7 +285,7 @@ public function testPipelineConvertsTypes(): void '$group' => [ '_id' => [ '$cond' => [ - 'if' => ['$lt' => ['$createdAt', new UTCDateTime((int) $dateTime->format('Uv'))]], + 'if' => ['$lt' => ['$createdAt', new UTCDateTime($dateTime)]], 'then' => true, 'else' => false, ], @@ -297,7 +297,7 @@ public function testPipelineConvertsTypes(): void '$replaceRoot' => [ 'newRoot' => (object) [ 'isToday' => [ - '$eq' => ['$createdAt', new UTCDateTime((int) $dateTime->format('Uv'))], + '$eq' => ['$createdAt', new UTCDateTime($dateTime)], ], ], ], diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/ReplaceRootTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/ReplaceRootTest.php index 8ac21bf494..a71086cbf2 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/ReplaceRootTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/ReplaceRootTest.php @@ -17,7 +17,7 @@ public function testTypeConversion(): void $builder = $this->dm->createAggregationBuilder(User::class); $dateTime = new DateTimeImmutable('2000-01-01T00:00Z'); - $mongoDate = new UTCDateTime((int) $dateTime->format('Uv')); + $mongoDate = new UTCDateTime($dateTime); $stage = $builder ->replaceRoot() ->field('isToday') @@ -40,7 +40,7 @@ public function testTypeConversionWithDirectExpression(): void $builder = $this->dm->createAggregationBuilder(User::class); $dateTime = new DateTimeImmutable('2000-01-01T00:00Z'); - $mongoDate = new UTCDateTime((int) $dateTime->format('Uv')); + $mongoDate = new UTCDateTime($dateTime); $stage = $builder ->replaceRoot( $builder->expr() diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/ReplaceWithTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/ReplaceWithTest.php index c1107ae532..c915ecaee7 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/ReplaceWithTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/ReplaceWithTest.php @@ -17,7 +17,7 @@ public function testTypeConversion(): void $builder = $this->dm->createAggregationBuilder(User::class); $dateTime = new DateTimeImmutable('2000-01-01T00:00Z'); - $mongoDate = new UTCDateTime((int) $dateTime->format('Uv')); + $mongoDate = new UTCDateTime($dateTime); $stage = $builder ->replaceWith() ->field('isToday') @@ -38,7 +38,7 @@ public function testTypeConversionWithDirectExpression(): void $builder = $this->dm->createAggregationBuilder(User::class); $dateTime = new DateTimeImmutable('2000-01-01T00:00Z'); - $mongoDate = new UTCDateTime((int) $dateTime->format('Uv')); + $mongoDate = new UTCDateTime($dateTime); $stage = $builder ->replaceWith( $builder->expr() From 77d5827ef33a53ae47d544ce4cd0a797f7a7a884 Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Thu, 23 Mar 2023 12:28:17 +0100 Subject: [PATCH 21/23] Allow expressions as partition in $fill stage --- .../ODM/MongoDB/Aggregation/Stage/Fill.php | 4 +++- .../Tests/Aggregation/Stage/FillTest.php | 24 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill.php index 4fc564f64e..39a1fc4b45 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill.php @@ -86,7 +86,9 @@ public function getExpression(): array $params = (object) []; if ($this->partitionBy) { - $params->partitionBy = $this->partitionBy; + $params->partitionBy = $this->partitionBy instanceof Expr + ? $this->partitionBy->getExpression() + : $this->partitionBy; } if ($this->partitionByFields) { diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/FillTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/FillTest.php index 7bab5a7ed2..e83b187a62 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/FillTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/FillTest.php @@ -44,6 +44,30 @@ public function testStage(): void ); } + public function testStageWithExpressionAsPartition(): void + { + $builder = $this->getTestAggregationBuilder(); + $fillStage = new Fill($builder); + $fillStage + ->partitionBy($builder->expr()->year('$field')) + ->sortBy('field1', 1) + ->output() + ->field('foo')->locf(); + + self::assertEquals( + [ + '$fill' => (object) [ + 'partitionBy' => ['$year' => '$field'], + 'sortBy' => (object) ['field1' => 1], + 'output' => (object) [ + 'foo' => ['method' => 'locf'], + ], + ], + ], + $fillStage->getExpression(), + ); + } + public function testStageWithComplexSort(): void { $fillStage = new Fill($this->getTestAggregationBuilder()); From 67847c5f1727f4f68d1c0e14fe3294d07108b75e Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Thu, 23 Mar 2023 13:01:24 +0100 Subject: [PATCH 22/23] Define psalm types for most pipeline stages --- .../ODM/MongoDB/Aggregation/Aggregation.php | 7 +- .../ODM/MongoDB/Aggregation/Builder.php | 3 + lib/Doctrine/ODM/MongoDB/Aggregation/Expr.php | 2 + .../ODM/MongoDB/Aggregation/Stage.php | 4 + .../Aggregation/Stage/AbstractReplace.php | 64 + .../MongoDB/Aggregation/Stage/AddFields.php | 6 + .../MongoDB/Aggregation/Stage/CollStats.php | 8 + .../ODM/MongoDB/Aggregation/Stage/Count.php | 3 + .../ODM/MongoDB/Aggregation/Stage/Densify.php | 21 +- .../ODM/MongoDB/Aggregation/Stage/Facet.php | 3 + .../ODM/MongoDB/Aggregation/Stage/Fill.php | 14 +- .../ODM/MongoDB/Aggregation/Stage/Group.php | 3 + .../MongoDB/Aggregation/Stage/IndexStats.php | 3 + .../ODM/MongoDB/Aggregation/Stage/Limit.php | 3 + .../ODM/MongoDB/Aggregation/Stage/Lookup.php | 52 +- .../ODM/MongoDB/Aggregation/Stage/Merge.php | 19 +- .../ODM/MongoDB/Aggregation/Stage/Out.php | 30 +- .../ODM/MongoDB/Aggregation/Stage/Project.php | 4 + .../ODM/MongoDB/Aggregation/Stage/Redact.php | 5 + .../MongoDB/Aggregation/Stage/ReplaceRoot.php | 65 +- .../MongoDB/Aggregation/Stage/ReplaceWith.php | 15 +- .../ODM/MongoDB/Aggregation/Stage/Sample.php | 3 + .../ODM/MongoDB/Aggregation/Stage/Set.php | 6 + .../ODM/MongoDB/Aggregation/Stage/Skip.php | 3 + .../ODM/MongoDB/Aggregation/Stage/Sort.php | 12 +- .../MongoDB/Aggregation/Stage/SortByCount.php | 2 + .../MongoDB/Aggregation/Stage/UnionWith.php | 14 +- .../MongoDB/Aggregation/Stage/UnsetStage.php | 3 + .../ODM/MongoDB/Aggregation/Stage/Unwind.php | 19 +- phpstan-baseline.neon | 1120 +++++++++++++---- psalm-baseline.xml | 5 - .../MongoDB/Tests/Aggregation/BuilderTest.php | 2 +- .../Aggregation/Stage/ReplaceRootTest.php | 6 +- .../Aggregation/Stage/ReplaceWithTest.php | 6 +- 34 files changed, 1181 insertions(+), 354 deletions(-) create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/AbstractReplace.php diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Aggregation.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Aggregation.php index 655de73d29..767539e6ba 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Aggregation.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Aggregation.php @@ -17,6 +17,7 @@ use function array_merge; use function assert; +/** @psalm-import-type PipelineExpression from Builder */ final class Aggregation implements IteratorAggregate { private DocumentManager $dm; @@ -25,7 +26,10 @@ final class Aggregation implements IteratorAggregate private Collection $collection; - /** @var array */ + /** + * @var array + * @psalm-var PipelineExpression + */ private array $pipeline; /** @var array */ @@ -36,6 +40,7 @@ final class Aggregation implements IteratorAggregate /** * @param array $pipeline * @param array $options + * @psalm-param PipelineExpression $pipeline */ public function __construct(DocumentManager $dm, ?ClassMetadata $classMetadata, Collection $collection, array $pipeline, array $options = [], bool $rewindable = true) { diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php index 38b93aeecf..949694971c 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php @@ -29,6 +29,8 @@ * Fluent interface for building aggregation pipelines. * * @psalm-import-type SortShape from Sort + * @psalm-import-type StageExpression from Stage + * @psalm-type PipelineExpression = list */ class Builder { @@ -269,6 +271,7 @@ public function getAggregation(array $options = []): Aggregation * given. * * @return array> + * @psalm-return PipelineExpression */ // phpcs:enable Squiz.Commenting.FunctionComment.ExtraParamComment public function getPipeline(/* bool $applyFilters = true */): array diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Expr.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Expr.php index fd5133ed5a..060f703199 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Expr.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Expr.php @@ -24,6 +24,8 @@ /** * Fluent interface for building aggregation pipelines. + * + * @psalm-type OperatorExpression = array|object */ class Expr implements GenericOperatorsInterface { diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage.php index a1cad26540..67130534a0 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage.php @@ -13,6 +13,9 @@ * Fluent interface for building aggregation pipelines. * * @internal + * + * @psalm-import-type PipelineExpression from Builder + * @psalm-type StageExpression = non-empty-array|object */ abstract class Stage { @@ -186,6 +189,7 @@ public function geoNear($x, $y = null): Stage\GeoNear * Returns the assembled aggregation pipeline * * @return array> + * @psalm-return PipelineExpression */ public function getPipeline(): array { diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/AbstractReplace.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/AbstractReplace.php new file mode 100644 index 0000000000..3603100584 --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/AbstractReplace.php @@ -0,0 +1,64 @@ +dm = $documentManager; + $this->class = $class; + $this->expression = $expression; + } + + private function getDocumentPersister(): DocumentPersister + { + return $this->dm->getUnitOfWork()->getDocumentPersister($this->class->name); + } + + /** @return array|string */ + protected function getReplaceExpression() + { + return $this->expression !== null ? $this->convertExpression($this->expression) : $this->expr->getExpression(); + } + + /** + * @param mixed[]|string|mixed $expression + * + * @return mixed[]|string|mixed + */ + private function convertExpression($expression) + { + if (is_array($expression)) { + return array_map([$this, 'convertExpression'], $expression); + } + + if (is_string($expression) && substr($expression, 0, 1) === '$') { + return '$' . $this->getDocumentPersister()->prepareFieldName(substr($expression, 1)); + } + + return Type::convertPHPToDatabaseValue(Expr::convertExpression($expression)); + } +} diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/AddFields.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/AddFields.php index 63e15ed131..b658656bb9 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/AddFields.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/AddFields.php @@ -4,11 +4,17 @@ namespace Doctrine\ODM\MongoDB\Aggregation\Stage; +use Doctrine\ODM\MongoDB\Aggregation\Expr; + /** * Fluent interface for adding a $addFields stage to an aggregation pipeline. + * + * @psalm-import-type OperatorExpression from Expr + * @psalm-type AddFieldsStageExpression = array{'$addFields': array} */ final class AddFields extends Operator { + /** @return AddFieldsStageExpression */ public function getExpression(): array { return ['$addFields' => $this->expr->getExpression()]; diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/CollStats.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/CollStats.php index 9d787b6429..f184273994 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/CollStats.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/CollStats.php @@ -9,6 +9,13 @@ /** * Fluent interface for adding a $collStats stage to an aggregation pipeline. + * + * @psalm-type CollStatsStageExpression = array{ + * '$collStats': array{ + * latencyStats?: array{histograms?: bool}, + * storageStats?: array{}, + * } + * } */ class CollStats extends Stage { @@ -45,6 +52,7 @@ public function showStorageStats(): self return $this; } + /** @psalm-return CollStatsStageExpression */ public function getExpression(): array { $collStats = []; diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Count.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Count.php index 647833e373..a8d83d21bc 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Count.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Count.php @@ -9,6 +9,8 @@ /** * Fluent interface for adding a $count stage to an aggregation pipeline. + * + * @psalm-type CountStageExpression = array{'$count': string} */ class Count extends Stage { @@ -21,6 +23,7 @@ public function __construct(Builder $builder, string $fieldName) $this->fieldName = $fieldName; } + /** @psalm-return CountStageExpression */ public function getExpression(): array { return [ diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Densify.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Densify.php index aeb2fdc5c3..5126c924ab 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Densify.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Densify.php @@ -12,6 +12,20 @@ /** * Fluent interface for adding a $densify stage to an aggregation pipeline. + * + * @psalm-type BoundsType = 'full'|'partition'|array{0: int|float|UTCDateTime, 1: int|float|UTCDateTime} + * @psalm-type UnitType = 'year'|'month'|'week'|'day'|'hour'|'minute'|'second'|'millisecond' + * @psalm-type DensifyStageExpression = array{ + * '$densify': object{ + * field: string, + * partitionByFields?: list, + * range?: object{ + * bounds: BoundsType, + * step: int|float, + * unit?: UnitType + * } + * } + * } */ class Densify extends Stage { @@ -37,8 +51,10 @@ public function partitionByFields(string ...$fields): self } /** - * @param array{0: int|float|UTCDateTime, 1: int|float|UTCDateTime}|string $bounds - * @param int|float $step + * @param array|string $bounds + * @param int|float $step + * @psalm-param BoundsType $bounds + * @psalm-param ''|UnitType $unit */ public function range($bounds, $step, string $unit = ''): self { @@ -54,6 +70,7 @@ public function range($bounds, $step, string $unit = ''): self return $this; } + /** @psalm-return DensifyStageExpression */ public function getExpression(): array { $params = (object) ['field' => $this->field]; diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Facet.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Facet.php index 95fbad8560..348f3f19de 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Facet.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Facet.php @@ -13,6 +13,9 @@ /** * Fluent interface for adding a $facet stage to an aggregation pipeline. + * + * @psalm-import-type PipelineExpression from Builder + * @psalm-type FacetStageExpression = array{'$facet': array} */ class Facet extends Stage { diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill.php index 39a1fc4b45..7fd6fccaef 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill.php @@ -17,7 +17,18 @@ /** * Fluent interface for adding a $fill stage to an aggregation pipeline. * - * @psalm-type SortShape = array + * @psalm-import-type SortDirectionKeywords from Sort + * @psalm-import-type OperatorExpression from Expr + * @psalm-type SortDirection = int|SortDirectionKeywords + * @psalm-type SortShape = array + * @psalm-type FillStageExpression = array{ + * '$fill': array{ + * partitionBy?: string|OperatorExpression, + * partitionByFields?: list, + * sortBy?: SortShape, + * output?: array, + * } + * } */ class Fill extends Stage { @@ -56,6 +67,7 @@ public function partitionByFields(string ...$fields): self * @param array|string $fieldName Field name or array of field/order pairs * @param int|string $order Field order (if one field is specified) * @psalm-param SortShape|string $fieldName + * @psalm-param SortDirection|null $order */ public function sortBy($fieldName, $order = null): self { diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Group.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Group.php index 88bb883dbe..4e7be5f50e 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Group.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Group.php @@ -9,6 +9,8 @@ /** * Fluent interface for adding a $group stage to an aggregation pipeline. + * + * @psalm-type GroupStageExpression = array{'$group': array} */ class Group extends Operator { @@ -22,6 +24,7 @@ public function __construct(Builder $builder) $this->expr = $builder->expr(); } + /** @psalm-return GroupStageExpression */ public function getExpression(): array { return [ diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/IndexStats.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/IndexStats.php index 2235f3af38..f59e7732fb 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/IndexStats.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/IndexStats.php @@ -9,9 +9,12 @@ /** * Fluent interface for adding a $indexStats stage to an aggregation pipeline. + * + * @psalm-type IndexStatsStageExpression = array{'$indexStats': object} */ class IndexStats extends Stage { + /** @psalm-return IndexStatsStageExpression */ public function getExpression(): array { return [ diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Limit.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Limit.php index ec2aa15596..8d4cef764a 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Limit.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Limit.php @@ -9,6 +9,8 @@ /** * Fluent interface for adding a $limit stage to an aggregation pipeline. + * + * @psalm-type LimitStageExpression = array{'$limit': int} */ class Limit extends Stage { @@ -21,6 +23,7 @@ public function __construct(Builder $builder, int $limit) $this->limit = $limit; } + /** @psalm-return LimitStageExpression */ public function getExpression(): array { return [ diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Lookup.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Lookup.php index a241a216f4..e2ad45f63a 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Lookup.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Lookup.php @@ -15,6 +15,19 @@ /** * Fluent interface for building aggregation pipelines. + * + * @psalm-import-type PipelineExpression from Builder + * @psalm-type PipelineParamType = Builder|Stage|PipelineExpression + * @psalm-type LookupStageExpression = array{ + * $lookup: array{ + * from: string, + * 'as'?: string, + * localField?: string, + * foreignField?: string, + * pipeline?: PipelineExpression, + * let?: array, + * } + * } */ class Lookup extends Stage { @@ -32,10 +45,13 @@ class Lookup extends Stage private ?string $as = null; - /** @var array|null */ + /** @var array|null */ private ?array $let = null; - /** @var Builder|array>|null */ + /** + * @var Builder|array>|null + * @psalm-var Builder|PipelineExpression|null + */ private $pipeline = null; private bool $excludeLocalAndForeignField = false; @@ -92,33 +108,40 @@ public function from(string $from): self return $this; } + /** @psalm-return LookupStageExpression */ public function getExpression(): array { - $expression = [ - '$lookup' => [ - 'from' => $this->from, - 'as' => $this->as, - ], + $lookup = [ + 'from' => $this->from, ]; + if ($this->as !== null) { + $lookup['as'] = $this->as; + } + if (! $this->excludeLocalAndForeignField) { - $expression['$lookup']['localField'] = $this->localField; - $expression['$lookup']['foreignField'] = $this->foreignField; + if ($this->localField !== null) { + $lookup['localField'] = $this->localField; + } + + if ($this->foreignField !== null) { + $lookup['foreignField'] = $this->foreignField; + } } if (! empty($this->let)) { - $expression['$lookup']['let'] = $this->let; + $lookup['let'] = $this->let; } if ($this->pipeline !== null) { if ($this->pipeline instanceof Builder) { - $expression['$lookup']['pipeline'] = $this->pipeline->getPipeline(false); + $lookup['pipeline'] = $this->pipeline->getPipeline(false); } else { - $expression['$lookup']['pipeline'] = $this->pipeline; + $lookup['pipeline'] = $this->pipeline; } } - return $expression; + return ['$lookup' => $lookup]; } /** @@ -157,7 +180,7 @@ public function foreignField(string $foreignField): self * Use the variable expressions to access the fields from * the joined collection's documents that are input to the pipeline. * - * @param array $let + * @param array $let */ public function let(array $let): self { @@ -177,6 +200,7 @@ public function let(array $let): self * and then reference the variables in the pipeline stages. * * @param Builder|Stage|array> $pipeline + * @psalm-param PipelineParamType $pipeline */ public function pipeline($pipeline): self { diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Merge.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Merge.php index 9ec37dab50..90174c153e 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Merge.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Merge.php @@ -16,8 +16,20 @@ use function is_array; /** + * @psalm-import-type PipelineExpression from Builder * @psalm-type OutputCollection = string|array{db: string, coll: string} - * @psalm-type WhenMatchedParam = Builder|Stage|string|list> + * @psalm-type WhenMatchedType = 'replace'|'keepExisting'|'merge'|'fail'|PipelineExpression + * @psalm-type WhenMatchedParamType = Builder|Stage|WhenMatchedType + * @psalm-type WhenNotMatchedType = 'insert'|'discard'|'fail' + * @psalm-type MergeStageExpression = array{ + * '$merge': object{ + * into: OutputCollection, + * on?: string|list, + * let?: array, + * whenMatched?: WhenMatchedType, + * whenNotMatched?: WhenNotMatchedType, + * } + * } */ class Merge extends Stage { @@ -37,7 +49,7 @@ class Merge extends Stage /** * @var string|array|Builder|Stage - * @psalm-var WhenMatchedParam + * @psalm-var WhenMatchedParamType */ private $whenMatched; @@ -50,6 +62,7 @@ public function __construct(Builder $builder, DocumentManager $documentManager) $this->dm = $documentManager; } + /** @psalm-return MergeStageExpression */ public function getExpression(): array { $params = (object) [ @@ -123,7 +136,7 @@ public function on(string ...$fields): self /** * @param string|array|Builder|Stage $whenMatched - * @psalm-param WhenMatchedParam $whenMatched + * @psalm-param WhenMatchedParamType $whenMatched */ public function whenMatched($whenMatched): self { diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Out.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Out.php index 05d3dff5eb..0e820f1f6a 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Out.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Out.php @@ -11,11 +11,21 @@ use Doctrine\ODM\MongoDB\Mapping\MappingException; use Doctrine\Persistence\Mapping\MappingException as BaseMappingException; +use function is_array; + +/** + * @psalm-import-type OutputCollection from Merge + * @psalm-type OutStageExpression = array{'$out': OutputCollection} + */ class Out extends Stage { private DocumentManager $dm; - private string $collection; + /** + * @var array|string + * @psalm-var OutputCollection + */ + private $out; public function __construct(Builder $builder, string $collection, DocumentManager $documentManager) { @@ -28,16 +38,26 @@ public function __construct(Builder $builder, string $collection, DocumentManage public function getExpression(): array { return [ - '$out' => $this->collection, + '$out' => $this->out, ]; } - public function out(string $collection): Stage\Out + /** + * @param string|array $collection + * @psalm-param OutputCollection $collection + */ + public function out($collection): Stage\Out { + if (is_array($collection)) { + $this->out = $collection; + + return $this; + } + try { $class = $this->dm->getClassMetadata($collection); } catch (BaseMappingException $e) { - $this->collection = $collection; + $this->out = $collection; return $this; } @@ -53,6 +73,6 @@ private function fromDocument(ClassMetadata $classMetadata): void throw MappingException::cannotUseShardedCollectionInOutStage($classMetadata->name); } - $this->collection = $classMetadata->getCollection(); + $this->out = $classMetadata->getCollection(); } } diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Project.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Project.php index 08e997c53f..76525952a8 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Project.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Project.php @@ -10,9 +10,13 @@ /** * Fluent interface for adding a $project stage to an aggregation pipeline. + * + * @psalm-import-type OperatorExpression from Expr + * @psalm-type ProjectStageExpression = array{'$project': array} */ class Project extends Operator { + /** @psalm-return ProjectStageExpression */ public function getExpression(): array { return [ diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Redact.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Redact.php index b05dca5baa..2ac792bdba 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Redact.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Redact.php @@ -4,8 +4,13 @@ namespace Doctrine\ODM\MongoDB\Aggregation\Stage; +use Doctrine\ODM\MongoDB\Aggregation\Expr; + /** * Fluent interface for adding a $redact stage to an aggregation pipeline. + * + * @psalm-import-type OperatorExpression from Expr + * @psalm-type SetStageExpression = array{'$redact': array} */ class Redact extends Operator { diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/ReplaceRoot.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/ReplaceRoot.php index e27598a386..3d25a2a74a 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/ReplaceRoot.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/ReplaceRoot.php @@ -4,68 +4,25 @@ namespace Doctrine\ODM\MongoDB\Aggregation\Stage; -use Doctrine\ODM\MongoDB\Aggregation\Builder; use Doctrine\ODM\MongoDB\Aggregation\Expr; -use Doctrine\ODM\MongoDB\DocumentManager; -use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; -use Doctrine\ODM\MongoDB\Persisters\DocumentPersister; -use Doctrine\ODM\MongoDB\Types\Type; -use function array_map; -use function is_array; -use function is_string; -use function substr; - -class ReplaceRoot extends Operator +/** + * @psalm-import-type OperatorExpression from Expr + * @psalm-type ReplaceRootStageExpression = array{ + * '$replaceRoot': array{ + * newRoot: OperatorExpression|string, + * } + * } + */ +class ReplaceRoot extends AbstractReplace { - private DocumentManager $dm; - - private ClassMetadata $class; - - /** @var string|mixed[]|Expr|null */ - private $expression; - - /** @param string|mixed[]|Expr|null $expression */ - public function __construct(Builder $builder, DocumentManager $documentManager, ClassMetadata $class, $expression = null) - { - parent::__construct($builder); - - $this->dm = $documentManager; - $this->class = $class; - $this->expression = $expression; - } - + /** @return ReplaceRootStageExpression */ public function getExpression(): array { - $expression = $this->expression !== null ? $this->convertExpression($this->expression) : $this->expr->getExpression(); - return [ '$replaceRoot' => [ - 'newRoot' => is_array($expression) ? (object) $expression : $expression, + 'newRoot' => $this->getReplaceExpression(), ], ]; } - - /** - * @param mixed[]|string|mixed $expression - * - * @return mixed[]|string|mixed - */ - private function convertExpression($expression) - { - if (is_array($expression)) { - return array_map([$this, 'convertExpression'], $expression); - } - - if (is_string($expression) && substr($expression, 0, 1) === '$') { - return '$' . $this->getDocumentPersister()->prepareFieldName(substr($expression, 1)); - } - - return Type::convertPHPToDatabaseValue(Expr::convertExpression($expression)); - } - - private function getDocumentPersister(): DocumentPersister - { - return $this->dm->getUnitOfWork()->getDocumentPersister($this->class->name); - } } diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/ReplaceWith.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/ReplaceWith.php index 7abcadc3c6..317cde3c28 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/ReplaceWith.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/ReplaceWith.php @@ -4,14 +4,17 @@ namespace Doctrine\ODM\MongoDB\Aggregation\Stage; -class ReplaceWith extends ReplaceRoot +use Doctrine\ODM\MongoDB\Aggregation\Expr; + +/** + * @psalm-import-type OperatorExpression from Expr + * @psalm-type ReplaceWithStageExpression = array{'$replaceWith': OperatorExpression|string} + */ +class ReplaceWith extends AbstractReplace { + /** @return ReplaceWithStageExpression */ public function getExpression(): array { - $expression = parent::getExpression(); - - return [ - '$replaceWith' => $expression['$replaceRoot']['newRoot'], - ]; + return ['$replaceWith' => $this->getReplaceExpression()]; } } diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Sample.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Sample.php index a60bc0b514..785157d018 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Sample.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Sample.php @@ -9,6 +9,8 @@ /** * Fluent interface for adding a $sample stage to an aggregation pipeline. + * + * @psalm-type SampleStageExpression = array{'$sample': array{size: int}} */ class Sample extends Stage { @@ -21,6 +23,7 @@ public function __construct(Builder $builder, int $size) $this->size = $size; } + /** @psalm-return SampleStageExpression */ public function getExpression(): array { return [ diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Set.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Set.php index b11f5c16d2..cca5aed345 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Set.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Set.php @@ -4,11 +4,17 @@ namespace Doctrine\ODM\MongoDB\Aggregation\Stage; +use Doctrine\ODM\MongoDB\Aggregation\Expr; + /** * Fluent interface for adding a $set stage to an aggregation pipeline. + * + * @psalm-import-type OperatorExpression from Expr + * @psalm-type SetStageExpression = array{'$set': array} */ final class Set extends Operator { + /** @psalm-return SetStageExpression */ public function getExpression(): array { return ['$set' => $this->expr->getExpression()]; diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Skip.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Skip.php index 062072dd44..89b67e66d0 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Skip.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Skip.php @@ -9,6 +9,8 @@ /** * Fluent interface for adding a $skip stage to an aggregation pipeline. + * + * @psalm-type SkipStageExpression = array{'$skip': int} */ class Skip extends Stage { @@ -21,6 +23,7 @@ public function __construct(Builder $builder, int $skip) $this->skip = $skip; } + /** @return SkipStageExpression */ public function getExpression(): array { return [ diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Sort.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Sort.php index e10bf3e062..235d753a6a 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Sort.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Sort.php @@ -16,8 +16,12 @@ * Fluent interface for adding a $sort stage to an aggregation pipeline. * * @psalm-type SortMetaKeywords = 'textScore'|'indexKey' - * @psalm-type SortMeta = array{"$meta": SortMetaKeywords} - * @psalm-type SortShape = array + * @psalm-type SortDirectionKeywords = 'asc'|'desc' + * @psalm-type SortMeta = array{'$meta': SortMetaKeywords} + * @psalm-type SortShape = array + * @psalm-type SortStageExpression = array{ + * '$sort': array + * } */ class Sort extends Stage { @@ -27,7 +31,8 @@ class Sort extends Stage /** * @param array>|string $fieldName Field name or array of field/order pairs * @param int|string $order Field order (if one field is specified) - * @psalm-param SortShape|string $fieldName + * @psalm-param SortShape|string $fieldName + * @psalm-param int|SortMeta|SortDirectionKeywords|null $order */ public function __construct(Builder $builder, $fieldName, $order = null) { @@ -50,6 +55,7 @@ public function __construct(Builder $builder, $fieldName, $order = null) } } + /** @psalm-return SortStageExpression */ public function getExpression(): array { return [ diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/SortByCount.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/SortByCount.php index 737675d86d..494b7f845a 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/SortByCount.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/SortByCount.php @@ -11,6 +11,7 @@ use function substr; +/** @psalm-type SortByCountStageExpression = array{'$sortByCount': string} */ class SortByCount extends Stage { private string $fieldName; @@ -28,6 +29,7 @@ public function __construct(Builder $builder, string $fieldName, DocumentManager $this->fieldName = '$' . $documentPersister->prepareFieldName(substr($fieldName, 1)); } + /** @psalm-return SortByCountStageExpression */ public function getExpression(): array { return [ diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnionWith.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnionWith.php index 491014ca54..945dd9fe72 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnionWith.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnionWith.php @@ -13,7 +13,14 @@ /** * Fluent interface for adding a $unionWith stage to an aggregation pipeline. * - * @psalm-type Pipeline = Builder|Stage|list> + * @psalm-import-type PipelineExpression from Builder + * @psalm-type PipelineParamType = array|Builder|Stage|PipelineExpression + * @psalm-type UnionWithStageExpression = array{ + * '$unionWith': object{ + * coll: string, + * pipeline?: PipelineExpression, + * } + * } */ class UnionWith extends Stage { @@ -23,7 +30,7 @@ class UnionWith extends Stage /** * @var array|Builder|null - * @psalm-var ?Pipeline + * @psalm-var ?PipelineParamType */ private $pipeline = null; @@ -43,7 +50,7 @@ public function __construct(Builder $builder, DocumentManager $documentManager, /** * @param array|Builder|Stage $pipeline - * @psalm-param Pipeline $pipeline + * @psalm-param PipelineParamType $pipeline */ public function pipeline($pipeline): self { @@ -60,6 +67,7 @@ public function pipeline($pipeline): self return $this; } + /** @psalm-return UnionWithStageExpression */ public function getExpression(): array { $params = (object) ['coll' => $this->collection]; diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnsetStage.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnsetStage.php index 3582481a4e..774b45406a 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnsetStage.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnsetStage.php @@ -13,6 +13,8 @@ /** * Fluent interface for adding an $unset stage to an aggregation pipeline. + * + * @psalm-type UnsetStageExpression = array{'$unset': list} */ class UnsetStage extends Stage { @@ -29,6 +31,7 @@ public function __construct(Builder $builder, DocumentPersister $documentPersist $this->fields = array_values($fields); } + /** @return UnsetStageExpression */ public function getExpression(): array { return [ diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Unwind.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Unwind.php index dd2ff3dc29..8f1419ca6d 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Unwind.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Unwind.php @@ -9,6 +9,14 @@ /** * Fluent interface for adding a $unwind stage to an aggregation pipeline. + * + * @psalm-type UnwindStageExpression = array{ + * '$unwind': string|array{ + * path: string, + * includeArrayIndex?: string, + * preserveNullAndEmptyArrays?: bool, + * } + * } */ class Unwind extends Stage { @@ -25,6 +33,7 @@ public function __construct(Builder $builder, string $fieldName) $this->fieldName = $fieldName; } + /** @psalm-return UnwindStageExpression */ public function getExpression(): array { // Fallback behavior for MongoDB < 3.2 @@ -36,12 +45,12 @@ public function getExpression(): array $unwind = ['path' => $this->fieldName]; - foreach (['includeArrayIndex', 'preserveNullAndEmptyArrays'] as $option) { - if (! $this->$option) { - continue; - } + if ($this->includeArrayIndex) { + $unwind['includeArrayIndex'] = $this->includeArrayIndex; + } - $unwind[$option] = $this->$option; + if ($this->preserveNullAndEmptyArrays) { + $unwind['preserveNullAndEmptyArrays'] = $this->preserveNullAndEmptyArrays; } return ['$unwind' => $unwind]; diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 30b248edaa..d67a346142 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,246 +1,876 @@ parameters: - ignoreErrors: - # Adding a parameter would be BC-break - - - message: "#^PHPDoc tag @param references unknown parameter\\: \\$applyFilters$#" - count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php - - # Making classes final as suggested would be a BC-break - - - message: "#^Unsafe usage of new static\\(\\)\\.$#" - paths: - - lib/Doctrine/ODM/MongoDB/Aggregation/Expr.php - - lib/Doctrine/ODM/MongoDB/DocumentManager.php - - # This cannot be solved the way it is, see https://github.com/vimeo/psalm/issues/5788 - - - message: "#^Return type \\(Doctrine\\\\ODM\\\\MongoDB\\\\Mapping\\\\ClassMetadataFactory\\) of method Doctrine\\\\ODM\\\\MongoDB\\\\DocumentManager\\:\\:getMetadataFactory\\(\\) should be compatible with return type \\(Doctrine\\\\Persistence\\\\Mapping\\\\ClassMetadataFactory\\\\>\\) of method Doctrine\\\\Persistence\\\\ObjectManager\\:\\:getMetadataFactory\\(\\)$#" - count: 1 - path: lib/Doctrine/ODM/MongoDB/DocumentManager.php - - # The limit option in GeoNear has been removed in MongoDB 4.2 in favor of $limit stage - - - message: "#^Return type \\(Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\GeoNear\\) of method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\GeoNear\\:\\:limit\\(\\) should be compatible with return type \\(Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Limit\\) of method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\:\\:limit\\(\\)$#" - count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/GeoNear.php - - - - message: "#DOCTRINE_MONGODB_DATABASE not found\\.$#" - paths: - - tests/Doctrine/ODM/MongoDB/Tests/BaseTest.php - - tests/Doctrine/ODM/MongoDB/Tests/DocumentRepositoryTest.php - - tests/Doctrine/ODM/MongoDB/Tests/Id/IncrementGeneratorTest.php - - tests/Doctrine/ODM/MongoDB/Tests/QueryTest.php - - - - message: "#DOCTRINE_MONGODB_SERVER not found\\.$#" - paths: - - tests/Doctrine/ODM/MongoDB/Tests/BaseTest.php - - - - message: "#^Parameter \\#1 \\$builder of method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Facet\\:\\:pipeline\\(\\) expects Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Builder\\|Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage, stdClass given\\.$#" - count: 1 - path: tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/FacetTest.php - - - - message: "#^Expression \"\\$groups\\[0\\]\" on a separate line does not do anything\\.$#" - count: 1 - path: tests/Doctrine/ODM/MongoDB/Tests/Functional/FunctionalTest.php - - - - message: "#^Unreachable statement \\- code above always terminates\\.$#" - count: 1 - path: tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH580Test.php - - - - message: "#^Parameter \\#1 \\$primer of method Doctrine\\\\ODM\\\\MongoDB\\\\Query\\\\Builder\\:\\:prime\\(\\) expects bool\\|\\(callable\\(\\)\\: mixed\\), 1 given\\.$#" - count: 1 - path: tests/Doctrine/ODM/MongoDB/Tests/Query/BuilderTest.php - - # Support for doctrine/collections v1 - - - message: '#^Method Doctrine\\ODM\\MongoDB\\PersistentCollection\:\:add\(\) with return type void returns true but should not return anything\.$#' - count: 1 - path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php - - # import type in PHPStan does not work, see https://github.com/phpstan/phpstan/issues/5091 - - - message: "#^Access to offset '.+' on an unknown class Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\\\FieldMapping\\.$#" - count: 12 - path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php - - # import type in PHPStan does not work, see https://github.com/phpstan/phpstan/issues/5091 - - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\:\\:getMapping\\(\\) should return array\\{type\\: string, fieldName\\: string, name\\: string, isCascadeRemove\\: bool, isCascadePersist\\: bool, isCascadeRefresh\\: bool, isCascadeMerge\\: bool, isCascadeDetach\\: bool, \\.\\.\\.\\} but returns Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\\\FieldMapping\\|null\\.$#" - count: 1 - path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php - - # import type in PHPStan does not work, see https://github.com/phpstan/phpstan/issues/5091 - - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\:\\:\\$mapping has unknown class Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\\\FieldMapping as its type\\.$#" - count: 1 - path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php - - # import type in PHPStan does not work, see https://github.com/phpstan/phpstan/issues/5091 - - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\\\:\\:\\$mapping \\(Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\\\FieldMapping\\|null\\) does not accept array\\\\|bool\\|int\\|string\\|null\\>\\.$#" - count: 1 - path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php - - # That statement is never reached because DateTimeInterface is either DateTimeImmutable or DateTime - - - message: "#^Unreachable statement \\- code above always terminates\\.$#" - count: 1 - path: lib/Doctrine/ODM/MongoDB/Types/DateImmutableType.php - - # These classes are not final - - - message: "#^Unsafe call to private method Doctrine\\\\ODM\\\\MongoDB\\\\Query\\\\Expr\\:\\:convertExpression\\(\\) through static::\\.$#" - count: 3 - path: lib/Doctrine/ODM/MongoDB/Query/Expr.php - - - - message: "#^Unsafe call to private method Doctrine\\\\ODM\\\\MongoDB\\\\Types\\\\DateType\\:\\:craftDateTime\\(\\) through static::\\.$#" - count: 1 - path: lib/Doctrine/ODM/MongoDB/Types/DateType.php - - # False positive, the exception is thrown - - - message: "#^Dead catch \\- MongoDB\\\\Driver\\\\Exception\\\\BulkWriteException is never thrown in the try block\\.$#" - count: 1 - path: tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH580Test.php - - # Properties are not covariant - - - message: "#^PHPDoc type Doctrine\\\\Common\\\\Collections\\\\Collection\\ of property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ParentDocumentWithDiscriminator\\:\\:\\$embeddedChildren is not covariant with PHPDoc type array\\\\|Doctrine\\\\Common\\\\Collections\\\\Collection\\ of overridden property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ParentDocument\\:\\:\\$embeddedChildren\\.$#" - count: 1 - path: tests/Doctrine/ODM/MongoDB/Tests/Functional/DiscriminatorsDefaultValueTest.php - - - - message: "#^PHPDoc type array\\\\|Doctrine\\\\Common\\\\Collections\\\\Collection\\ of property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ParentDocumentWithDiscriminator\\:\\:\\$referencedChildren is not covariant with PHPDoc type array\\\\|Doctrine\\\\Common\\\\Collections\\\\Collection\\ of overridden property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ParentDocument\\:\\:\\$referencedChildren\\.$#" - count: 1 - path: tests/Doctrine/ODM/MongoDB/Tests/Functional/DiscriminatorsDefaultValueTest.php - - - - message: "#^PHPDoc type array\\\\|Doctrine\\\\Common\\\\Collections\\\\Collection\\ of property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ParentDocumentWithoutDiscriminator\\:\\:\\$embeddedChildren is not covariant with PHPDoc type array\\\\|Doctrine\\\\Common\\\\Collections\\\\Collection\\ of overridden property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ParentDocument\\:\\:\\$embeddedChildren\\.$#" - count: 1 - path: tests/Doctrine/ODM/MongoDB/Tests/Functional/DiscriminatorsDefaultValueTest.php - - - - message: "#^PHPDoc type array\\\\|Doctrine\\\\Common\\\\Collections\\\\Collection\\ of property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ParentDocumentWithoutDiscriminator\\:\\:\\$referencedChildren is not covariant with PHPDoc type array\\\\|Doctrine\\\\Common\\\\Collections\\\\Collection\\ of overridden property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ParentDocument\\:\\:\\$referencedChildren\\.$#" - count: 1 - path: tests/Doctrine/ODM/MongoDB/Tests/Functional/DiscriminatorsDefaultValueTest.php - - # Collection elements cannot be covariant, see https://github.com/doctrine/collections/pull/220 - - - message: "#^Parameter \\#2 \\$projects of class Documents\\\\Developer constructor expects Doctrine\\\\Common\\\\Collections\\\\Collection\\\\|null, Doctrine\\\\Common\\\\Collections\\\\ArrayCollection\\ given\\.$#" - count: 1 - path: tests/Doctrine/ODM/MongoDB/Tests/DocumentRepositoryTest.php - - # When iterating over SimpleXMLElement, we cannot know the key values - - - message: "#^Parameter \\#2 \\$mapping of method Doctrine\\\\ODM\\\\MongoDB\\\\Mapping\\\\Driver\\\\XmlDriver\\:\\:addFieldMapping\\(\\) expects array#" - count: 2 - path: lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php - - - - message: "#^Parameter \\#2 \\$options of method Doctrine\\\\ODM\\\\MongoDB\\\\Mapping\\\\ClassMetadata\\\\:\\:addIndex\\(\\) expects array\\{background\\?\\: bool, bits\\?\\: int, default_language\\?\\: string, expireAfterSeconds\\?\\: int, language_override\\?\\: string, min\\?\\: float, max\\?\\: float, name\\?\\: string, \\.\\.\\.\\}, array\\\\|bool\\|float\\|int\\|string\\|null\\>\\|bool\\|float\\|int\\|string\\|null\\> given\\.$#" - count: 1 - path: lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php - - # This is handled by a try-catch block - - - message: "#^Unable to resolve the template type T in call to method Doctrine\\\\ODM\\\\MongoDB\\\\DocumentManager\\:\\:getClassMetadata\\(\\)$#" - paths: - - lib/Doctrine/ODM/MongoDB/Aggregation/Stage/GraphLookup.php - - lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Lookup.php - - lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Merge.php - - lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Out.php - - lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnionWith.php - - # $this->mapping['targetDocument'] is class-string - - - message: "#^Unable to resolve the template type T in call to method Doctrine\\\\ODM\\\\MongoDB\\\\DocumentManager\\:\\:getClassMetadata\\(\\)$#" - count: 1 - path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php - - # complains about types for arguments we do not use/care - - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Proxy\\\\Factory\\\\StaticProxyFactory\\:\\:createInitializer\\(\\) should return Closure\\(ProxyManager\\\\Proxy\\\\GhostObjectInterface\\&TDocument of object\\=, string\\=, array\\\\=, Closure\\|null\\=, array\\\\=\\)\\: bool but returns Closure\\(ProxyManager\\\\Proxy\\\\GhostObjectInterface, string, array, mixed, array\\)\\: true\\.$#" - count: 1 - path: lib/Doctrine/ODM/MongoDB/Proxy/Factory/StaticProxyFactory.php - - - - message: "#^Parameter \\#1 \\$initializer of method ProxyManager\\\\Proxy\\\\GhostObjectInterface\\\\:\\:setProxyInitializer\\(\\) expects \\(Closure\\(ProxyManager\\\\Proxy\\\\GhostObjectInterface\\\\=, string\\=, array\\\\=, Closure\\|null\\=, array\\\\=\\)\\: bool\\)\\|null, Closure\\(ProxyManager\\\\Proxy\\\\GhostObjectInterface, string, array, mixed, array\\)\\: true given\\.$#" - count: 1 - path: lib/Doctrine/ODM/MongoDB/Hydrator/HydratorFactory.php - - # compatibility layer for doctrine/persistence ^2.4 || ^3.0 - - - message: "#.*#" - count: 1 - path: lib/Doctrine/ODM/MongoDB/Event/OnClearEventArgs - - - - message: "#Property .+ is never written, only read.#" - path: tests - - - - message: "#Property .+ is never read, only written.#" - path: tests - - - - message: "#Property .+ is unused.#" - path: tests - - - - message: "#^Parameter \\#4 \\$query of class Doctrine\\\\ODM\\\\MongoDB\\\\Query\\\\Query constructor expects array\\{distinct\\?\\: string, hint\\?\\: array\\\\|string, limit\\?\\: int, maxTimeMS\\?\\: int, multiple\\?\\: bool, new\\?\\: bool, newObj\\?\\: array\\, query\\?\\: array\\, \\.\\.\\.\\}, array\\{type\\: \\-1\\} given\\.$#" - count: 1 - path: tests/Doctrine/ODM/MongoDB/Tests/QueryTest.php - - - - message: "#^Parameter \\#2 \\$referenceMapping of method Doctrine\\\\ODM\\\\MongoDB\\\\DocumentManager\\:\\:createReference\\(\\) expects array\\{type\\: string, fieldName\\: string, name\\: string, isCascadeRemove\\: bool, isCascadePersist\\: bool, isCascadeRefresh\\: bool, isCascadeMerge\\: bool, isCascadeDetach\\: bool, \\.\\.\\.\\}, array\\{storeAs\\: 'dbRef'\\} given\\.$#" - count: 1 - path: tests/Doctrine/ODM/MongoDB/Tests/DocumentManagerTest.php - - # import type in PHPStan does not work, see https://github.com/phpstan/phpstan/issues/5091 - - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\:\\:\\$hints \\(Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\\\Hints\\) does not accept default value of type array\\.$#" - count: 1 - path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php - - # import type in PHPStan does not work, see https://github.com/phpstan/phpstan/issues/5091 - - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\:\\:\\$hints has unknown class Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\\\Hints as its type\\.$#" - count: 1 - path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php - - # import type in PHPStan does not work, see https://github.com/phpstan/phpstan/issues/5091 - - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\\\:\\:\\$hints \\(Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\\\Hints\\) does not accept array\\\\.$#" - count: 1 - path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php - - # import type in PHPStan does not work, see https://github.com/phpstan/phpstan/issues/5091 - - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\:\\:getHints\\(\\) should return array\\ but returns Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\\\Hints\\.$#" - count: 1 - path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php - - # breaking types expected by static analysis to check exceptions - - - message: "#.+mapField\\(\\) expects.+enumType\\: 'Documents81#" - count: 2 - path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php - - - - message: "#has parameter \\$[^\\s]+ with no value type specified in iterable type array\\.$#" - count: 6 - path: tests/* - - # cannot know that Command::getHelper('documentManager') returns a DocumentManagerHelper - - - message: "#^Call to an undefined method Symfony\\\\Component\\\\Console\\\\Helper\\\\HelperInterface\\:\\:getDocumentManager\\(\\)\\.$#" - count: 7 - path: lib/Doctrine/ODM/MongoDB/Tools/Console/Command/* + ignoreErrors: + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Builder\\:\\:getPipeline\\(\\) should return array\\\\|object\\> but returns array\\\\>\\.$#" + count: 2 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Builder\\:\\:getPipeline\\(\\) should return array\\\\|object\\> but returns non\\-empty\\-array\\\\>\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php + + - + message: "#^PHPDoc tag @param references unknown parameter\\: \\$applyFilters$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php + + - + message: "#^Unsafe usage of new static\\(\\)\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Expr.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\AbstractReplace\\:\\:getReplaceExpression\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/AbstractReplace.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Densify\\:\\:getExpression\\(\\) has invalid return type Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\DensifyStageExpression\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Densify.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Densify\\:\\:getExpression\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Densify.php + + - + message: "#^PHPDoc tag @return with type Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\DensifyStageExpression is incompatible with native type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Densify.php + + - + message: "#^Return type \\(Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\GeoNear\\) of method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\GeoNear\\:\\:limit\\(\\) should be compatible with return type \\(Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Limit\\) of method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\:\\:limit\\(\\)$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/GeoNear.php + + - + message: "#^Unable to resolve the template type T in call to method Doctrine\\\\ODM\\\\MongoDB\\\\DocumentManager\\:\\:getClassMetadata\\(\\)$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/GraphLookup.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Lookup\\:\\:getExpression\\(\\) has invalid return type Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\LookupStageExpression\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Lookup.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Lookup\\:\\:getExpression\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Lookup.php + + - + message: "#^PHPDoc tag @return with type Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\LookupStageExpression is incompatible with native type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Lookup.php + + - + message: "#^Unable to resolve the template type T in call to method Doctrine\\\\ODM\\\\MongoDB\\\\DocumentManager\\:\\:getClassMetadata\\(\\)$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Lookup.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Merge\\:\\:getExpression\\(\\) has invalid return type Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\MergeStageExpression\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Merge.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Merge\\:\\:getExpression\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Merge.php + + - + message: "#^PHPDoc tag @return with type Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\MergeStageExpression is incompatible with native type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Merge.php + + - + message: "#^Unable to resolve the template type T in call to method Doctrine\\\\ODM\\\\MongoDB\\\\DocumentManager\\:\\:getClassMetadata\\(\\)$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Merge.php + + - + message: "#^Unable to resolve the template type T in call to method Doctrine\\\\ODM\\\\MongoDB\\\\DocumentManager\\:\\:getClassMetadata\\(\\)$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Out.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\UnionWith\\:\\:getExpression\\(\\) has invalid return type Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\UnionWithStageExpression\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnionWith.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\UnionWith\\:\\:getExpression\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnionWith.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\UnionWith\\:\\:pipeline\\(\\) has parameter \\$pipeline with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnionWith.php + + - + message: "#^PHPDoc tag @return with type Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\UnionWithStageExpression is incompatible with native type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnionWith.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\UnionWith\\:\\:\\$pipeline type has no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnionWith.php + + - + message: "#^Unable to resolve the template type T in call to method Doctrine\\\\ODM\\\\MongoDB\\\\DocumentManager\\:\\:getClassMetadata\\(\\)$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnionWith.php + + - + message: "#^Return type \\(Doctrine\\\\ODM\\\\MongoDB\\\\Mapping\\\\ClassMetadataFactory\\) of method Doctrine\\\\ODM\\\\MongoDB\\\\DocumentManager\\:\\:getMetadataFactory\\(\\) should be compatible with return type \\(Doctrine\\\\Persistence\\\\Mapping\\\\ClassMetadataFactory\\\\>\\) of method Doctrine\\\\Persistence\\\\ObjectManager\\:\\:getMetadataFactory\\(\\)$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/DocumentManager.php + + - + message: "#^Unsafe usage of new static\\(\\)\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/DocumentManager.php + + - + message: "#^Method Doctrine\\\\Persistence\\\\Event\\\\OnClearEventArgs\\\\:\\:__construct\\(\\) invoked with 2 parameters, 1 required\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Event/OnClearEventArgs.php + + - + message: "#^Parameter \\#1 \\$initializer of method ProxyManager\\\\Proxy\\\\GhostObjectInterface\\\\:\\:setProxyInitializer\\(\\) expects \\(Closure\\(ProxyManager\\\\Proxy\\\\GhostObjectInterface\\\\=, string\\=, array\\\\=, Closure\\|null\\=, array\\\\=\\)\\: bool\\)\\|null, Closure\\(ProxyManager\\\\Proxy\\\\GhostObjectInterface, string, array, mixed, array\\)\\: true given\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Hydrator/HydratorFactory.php + + - + message: "#^Parameter \\#2 \\$mapping of method Doctrine\\\\ODM\\\\MongoDB\\\\Mapping\\\\Driver\\\\XmlDriver\\:\\:addFieldMapping\\(\\) expects array\\{type\\?\\: string, fieldName\\?\\: string, name\\?\\: string, strategy\\?\\: string, association\\?\\: int, id\\?\\: bool, isOwningSide\\?\\: bool, collectionClass\\?\\: class\\-string, \\.\\.\\.\\}, array\\\\|bool\\|string\\> given\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php + + - + message: "#^Parameter \\#2 \\$mapping of method Doctrine\\\\ODM\\\\MongoDB\\\\Mapping\\\\Driver\\\\XmlDriver\\:\\:addFieldMapping\\(\\) expects array\\{type\\?\\: string, fieldName\\?\\: string, name\\?\\: string, strategy\\?\\: string, association\\?\\: int, id\\?\\: bool, isOwningSide\\?\\: bool, collectionClass\\?\\: class\\-string, \\.\\.\\.\\}, non\\-empty\\-array\\\\|string\\|true\\> given\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php + + - + message: "#^Parameter \\#2 \\$options of method Doctrine\\\\ODM\\\\MongoDB\\\\Mapping\\\\ClassMetadata\\\\:\\:addIndex\\(\\) expects array\\{background\\?\\: bool, bits\\?\\: int, default_language\\?\\: string, expireAfterSeconds\\?\\: int, language_override\\?\\: string, min\\?\\: float, max\\?\\: float, name\\?\\: string, \\.\\.\\.\\}, array\\\\|bool\\|float\\|int\\|string\\|null\\>\\|bool\\|float\\|int\\|string\\|null\\> given\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php + + - + message: "#^Access to offset 'embedded' on an unknown class Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\\\FieldMapping\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php + + - + message: "#^Access to offset 'isOwningSide' on an unknown class Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\\\FieldMapping\\.$#" + count: 3 + path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php + + - + message: "#^Access to offset 'orphanRemoval' on an unknown class Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\\\FieldMapping\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php + + - + message: "#^Access to offset 'reference' on an unknown class Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\\\FieldMapping\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php + + - + message: "#^Access to offset 'strategy' on an unknown class Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\\\FieldMapping\\.$#" + count: 4 + path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php + + - + message: "#^Access to offset 'targetDocument' on an unknown class Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\\\FieldMapping\\.$#" + count: 2 + path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\:\\:add\\(\\) with return type void returns true but should not return anything\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\:\\:getHints\\(\\) should return array\\ but returns Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\\\Hints\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\:\\:getMapping\\(\\) should return array\\{type\\: string, fieldName\\: string, name\\: string, isCascadeRemove\\: bool, isCascadePersist\\: bool, isCascadeRefresh\\: bool, isCascadeMerge\\: bool, isCascadeDetach\\: bool, \\.\\.\\.\\} but returns Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\\\FieldMapping\\|null\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\:\\:\\$hints \\(Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\\\Hints\\) does not accept default value of type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\:\\:\\$hints has unknown class Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\\\Hints as its type\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\:\\:\\$mapping has unknown class Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\\\FieldMapping as its type\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\\\:\\:\\$hints \\(Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\\\Hints\\) does not accept array\\\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\\\:\\:\\$mapping \\(Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\\\FieldMapping\\|null\\) does not accept array\\\\|bool\\|int\\|string\\|null\\>\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php + + - + message: "#^Unable to resolve the template type T in call to method Doctrine\\\\ODM\\\\MongoDB\\\\DocumentManager\\:\\:getClassMetadata\\(\\)$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Proxy\\\\Factory\\\\StaticProxyFactory\\:\\:createInitializer\\(\\) should return Closure\\(ProxyManager\\\\Proxy\\\\GhostObjectInterface\\&TDocument of object\\=, string\\=, array\\\\=, Closure\\|null\\=, array\\\\=\\)\\: bool but returns Closure\\(ProxyManager\\\\Proxy\\\\GhostObjectInterface, string, array, mixed, array\\)\\: true\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Proxy/Factory/StaticProxyFactory.php + + - + message: "#^Unsafe call to private method Doctrine\\\\ODM\\\\MongoDB\\\\Query\\\\Expr\\:\\:convertExpression\\(\\) through static\\:\\:\\.$#" + count: 3 + path: lib/Doctrine/ODM/MongoDB/Query/Expr.php + + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\Console\\\\Helper\\\\HelperInterface\\:\\:getDocumentManager\\(\\)\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Tools/Console/Command/ClearCache/MetadataCommand.php + + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\Console\\\\Helper\\\\HelperInterface\\:\\:getDocumentManager\\(\\)\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Tools/Console/Command/GenerateHydratorsCommand.php + + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\Console\\\\Helper\\\\HelperInterface\\:\\:getDocumentManager\\(\\)\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Tools/Console/Command/GeneratePersistentCollectionsCommand.php + + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\Console\\\\Helper\\\\HelperInterface\\:\\:getDocumentManager\\(\\)\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Tools/Console/Command/GenerateProxiesCommand.php + + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\Console\\\\Helper\\\\HelperInterface\\:\\:getDocumentManager\\(\\)\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Tools/Console/Command/QueryCommand.php + + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\Console\\\\Helper\\\\HelperInterface\\:\\:getDocumentManager\\(\\)\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Tools/Console/Command/Schema/AbstractCommand.php + + - + message: "#^Call to an undefined method Symfony\\\\Component\\\\Console\\\\Helper\\\\HelperInterface\\:\\:getDocumentManager\\(\\)\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Tools/Console/Command/Schema/ValidateCommand.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Types/DateImmutableType.php + + - + message: "#^Unsafe call to private method Doctrine\\\\ODM\\\\MongoDB\\\\Types\\\\DateType\\:\\:craftDateTime\\(\\) through static\\:\\:\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Types/DateType.php + + - + message: "#^Parameter \\#1 \\$builder of method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Facet\\:\\:pipeline\\(\\) expects Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Builder\\|Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage, stdClass given\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/FacetTest.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Aggregation\\\\Stage\\\\MatchStageTest\\:\\:testProxiedExprMethods\\(\\) has parameter \\$args with no value type specified in iterable type array\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/MatchStageTest.php + + - + message: "#^Constant DOCTRINE_MONGODB_DATABASE not found\\.$#" + count: 5 + path: tests/Doctrine/ODM/MongoDB/Tests/BaseTest.php + + - + message: "#^Constant DOCTRINE_MONGODB_SERVER not found\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/BaseTest.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\BaseTest\\:\\:assertArraySubset\\(\\) has parameter \\$array with no value type specified in iterable type array\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/BaseTest.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\BaseTest\\:\\:assertArraySubset\\(\\) has parameter \\$subset with no value type specified in iterable type array\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/BaseTest.php + + - + message: "#^Used constant DOCTRINE_MONGODB_DATABASE not found\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/BaseTest.php + + - + message: "#^Used constant DOCTRINE_MONGODB_SERVER not found\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/BaseTest.php + + - + message: "#^Parameter \\#2 \\$referenceMapping of method Doctrine\\\\ODM\\\\MongoDB\\\\DocumentManager\\:\\:createReference\\(\\) expects array\\{type\\: string, fieldName\\: string, name\\: string, isCascadeRemove\\: bool, isCascadePersist\\: bool, isCascadeRefresh\\: bool, isCascadeMerge\\: bool, isCascadeDetach\\: bool, \\.\\.\\.\\}, array\\{storeAs\\: 'dbRef'\\} given\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/DocumentManagerTest.php + + - + message: "#^Constant DOCTRINE_MONGODB_DATABASE not found\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/DocumentRepositoryTest.php + + - + message: "#^Parameter \\#2 \\$projects of class Documents\\\\Developer constructor expects Doctrine\\\\Common\\\\Collections\\\\Collection\\\\|null, Doctrine\\\\Common\\\\Collections\\\\ArrayCollection\\ given\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/DocumentRepositoryTest.php + + - + message: "#^Used constant DOCTRINE_MONGODB_DATABASE not found\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/DocumentRepositoryTest.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\CustomDatabaseTest\\:\\:\\$id is unused\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Functional/DatabasesTest.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\DefaultDatabaseTest\\:\\:\\$id is unused\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Functional/DatabasesTest.php + + - + message: "#^PHPDoc type Doctrine\\\\Common\\\\Collections\\\\Collection\\ of property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ParentDocumentWithDiscriminator\\:\\:\\$embeddedChildren is not covariant with PHPDoc type array\\\\|Doctrine\\\\Common\\\\Collections\\\\Collection\\ of overridden property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ParentDocument\\:\\:\\$embeddedChildren\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Functional/DiscriminatorsDefaultValueTest.php + + - + message: "#^PHPDoc type array\\\\|Doctrine\\\\Common\\\\Collections\\\\Collection\\ of property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ParentDocumentWithDiscriminator\\:\\:\\$referencedChildren is not covariant with PHPDoc type array\\\\|Doctrine\\\\Common\\\\Collections\\\\Collection\\ of overridden property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ParentDocument\\:\\:\\$referencedChildren\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Functional/DiscriminatorsDefaultValueTest.php + + - + message: "#^PHPDoc type array\\\\|Doctrine\\\\Common\\\\Collections\\\\Collection\\ of property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ParentDocumentWithoutDiscriminator\\:\\:\\$embeddedChildren is not covariant with PHPDoc type array\\\\|Doctrine\\\\Common\\\\Collections\\\\Collection\\ of overridden property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ParentDocument\\:\\:\\$embeddedChildren\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Functional/DiscriminatorsDefaultValueTest.php + + - + message: "#^PHPDoc type array\\\\|Doctrine\\\\Common\\\\Collections\\\\Collection\\ of property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ParentDocumentWithoutDiscriminator\\:\\:\\$referencedChildren is not covariant with PHPDoc type array\\\\|Doctrine\\\\Common\\\\Collections\\\\Collection\\ of overridden property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ParentDocument\\:\\:\\$referencedChildren\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Functional/DiscriminatorsDefaultValueTest.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\DocumentPersisterTestDocumentWithReferenceToDocumentWithCustomId\\:\\:\\$documentWithCustomId is never read, only written\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Functional/DocumentPersisterTest.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\DocumentPersisterTestDocumentWithReferenceToDocumentWithCustomId\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Functional/DocumentPersisterTest.php + + - + message: "#^Expression \"\\$groups\\[0\\]\" on a separate line does not do anything\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Functional/FunctionalTest.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ChildObject\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Functional/LifecycleTest.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ParentObject\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Functional/LifecycleTest.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\Hierarchy\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Functional/NestedDocumentsTest.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\Ticket\\\\GH1058PersistDocument\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH1058Test.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\Ticket\\\\GH1058PersistDocument\\:\\:\\$value is never read, only written\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH1058Test.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\Ticket\\\\GH1058UpsertDocument\\:\\:\\$value is never read, only written\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH1058Test.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\Ticket\\\\GH1964Document\\:\\:\\$id is unused\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH1964Test.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\Ticket\\\\GH1990Document\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH1990Test.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\Ticket\\\\GH1990Document\\:\\:\\$parent is never read, only written\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH1990Test.php + + - + message: "#^Dead catch \\- MongoDB\\\\Driver\\\\Exception\\\\BulkWriteException is never thrown in the try block\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH580Test.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH580Test.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\Ticket\\\\GH921Post\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH921Test.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\Ticket\\\\GH921User\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH921Test.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\Ticket\\\\GH999Document\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH999Test.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\Ticket\\\\MODM116Parent\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/MODM116Test.php + + - + message: "#^Constant DOCTRINE_MONGODB_DATABASE not found\\.$#" + count: 2 + path: tests/Doctrine/ODM/MongoDB/Tests/Id/IncrementGeneratorTest.php + + - + message: "#^Used constant DOCTRINE_MONGODB_DATABASE not found\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Id/IncrementGeneratorTest.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Mapping\\\\AnnotationDriverTestSuper\\:\\:\\$private is unused\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/AbstractAnnotationDriverTest.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Mapping\\\\DocumentSubClass2\\:\\:\\$id is unused\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/BasicInheritanceMappingTest.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Mapping\\\\DocumentSubClass2\\:\\:\\$name is unused\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/BasicInheritanceMappingTest.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Mapping\\\\DocumentSubClass\\:\\:\\$id is unused\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/BasicInheritanceMappingTest.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Mapping\\\\DocumentSubClass\\:\\:\\$name is unused\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/BasicInheritanceMappingTest.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Mapping\\\\GridFSChildClass\\:\\:\\$id is unused\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/BasicInheritanceMappingTest.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Mapping\\\\GridFSParentClass\\:\\:\\$id is unused\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/BasicInheritanceMappingTest.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Mapping\\\\MappedSuperclassBase\\:\\:\\$mapped1 is unused\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/BasicInheritanceMappingTest.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Mapping\\\\MappedSuperclassBase\\:\\:\\$mapped2 is unused\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/BasicInheritanceMappingTest.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Mapping\\\\MappedSuperclassBase\\:\\:\\$mappedRelated1 is unused\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/BasicInheritanceMappingTest.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Mapping\\\\MappedSuperclassBase\\:\\:\\$transient is unused\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/BasicInheritanceMappingTest.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Mapping\\\\TransientBaseClass\\:\\:\\$transient1 is unused\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/BasicInheritanceMappingTest.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Mapping\\\\TransientBaseClass\\:\\:\\$transient2 is unused\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/BasicInheritanceMappingTest.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Mapping\\\\LoadEventTestDocument\\:\\:\\$about is unused\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataLoadEventTest.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Mapping\\\\LoadEventTestDocument\\:\\:\\$id is unused\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataLoadEventTest.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Mapping\\\\LoadEventTestDocument\\:\\:\\$name is unused\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataLoadEventTest.php + + - + message: "#^Parameter \\#1 \\$mapping of method Doctrine\\\\ODM\\\\MongoDB\\\\Mapping\\\\ClassMetadata\\\\:\\:mapField\\(\\) expects array\\{type\\?\\: string, fieldName\\?\\: string, name\\?\\: string, strategy\\?\\: string, association\\?\\: int, id\\?\\: bool, isOwningSide\\?\\: bool, collectionClass\\?\\: class\\-string, \\.\\.\\.\\}, array\\{fieldName\\: 'enum', enumType\\: 'Documents81\\\\\\\\Card'\\} given\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php + + - + message: "#^Parameter \\#1 \\$mapping of method Doctrine\\\\ODM\\\\MongoDB\\\\Mapping\\\\ClassMetadata\\\\:\\:mapField\\(\\) expects array\\{type\\?\\: string, fieldName\\?\\: string, name\\?\\: string, strategy\\?\\: string, association\\?\\: int, id\\?\\: bool, isOwningSide\\?\\: bool, collectionClass\\?\\: class\\-string, \\.\\.\\.\\}, array\\{fieldName\\: 'enum', enumType\\: 'Documents81\\\\\\\\SuitNonBacked'\\} given\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php + + - + message: "#^Property DoctrineGlobal_User\\:\\:\\$email is unused\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/Documents/GlobalNamespaceDocument.php + + - + message: "#^Property DoctrineGlobal_User\\:\\:\\$id is unused\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/Documents/GlobalNamespaceDocument.php + + - + message: "#^Property DoctrineGlobal_User\\:\\:\\$username is unused\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/Documents/GlobalNamespaceDocument.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Mapping\\\\ShardedCollectionPerClass1\\:\\:\\$id is unused\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/ShardKeyInheritanceMappingTest.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Mapping\\\\ShardedSingleCollInheritance1\\:\\:\\$id is unused\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/ShardKeyInheritanceMappingTest.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Mapping\\\\ShardedSubclass\\:\\:\\$id is unused\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/ShardKeyInheritanceMappingTest.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Mapping\\\\ShardedSuperclass\\:\\:\\$name is unused\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/ShardKeyInheritanceMappingTest.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Query\\\\BuilderTest\\:\\:testExclude\\(\\) has parameter \\$expected with no value type specified in iterable type array\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Query/BuilderTest.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Query\\\\BuilderTest\\:\\:testProxiedExprMethods\\(\\) has parameter \\$args with no value type specified in iterable type array\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Query/BuilderTest.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Query\\\\BuilderTest\\:\\:testSelect\\(\\) has parameter \\$expected with no value type specified in iterable type array\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Query/BuilderTest.php + + - + message: "#^Parameter \\#1 \\$primer of method Doctrine\\\\ODM\\\\MongoDB\\\\Query\\\\Builder\\:\\:prime\\(\\) expects bool\\|\\(callable\\(\\)\\: mixed\\), 1 given\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Query/BuilderTest.php + + - + message: "#^Constant DOCTRINE_MONGODB_DATABASE not found\\.$#" + count: 2 + path: tests/Doctrine/ODM/MongoDB/Tests/QueryTest.php + + - + message: "#^Parameter \\#4 \\$query of class Doctrine\\\\ODM\\\\MongoDB\\\\Query\\\\Query constructor expects array\\{distinct\\?\\: string, hint\\?\\: array\\\\|string, limit\\?\\: int, maxTimeMS\\?\\: int, multiple\\?\\: bool, new\\?\\: bool, newObj\\?\\: array\\, query\\?\\: array\\, \\.\\.\\.\\}, array\\{type\\: \\-1\\} given\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/QueryTest.php + + - + message: "#^Used constant DOCTRINE_MONGODB_DATABASE not found\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/QueryTest.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Tools\\\\GH297\\\\User\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Tools/GH297/User.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Tools\\\\ResolveTargetDocument\\:\\:\\$embedMany is unused\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Tools/ResolveTargetDocumentListenerTest.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Tools\\\\ResolveTargetDocument\\:\\:\\$embedOne is unused\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Tools/ResolveTargetDocumentListenerTest.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Tools\\\\ResolveTargetDocument\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Tools/ResolveTargetDocumentListenerTest.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Tools\\\\ResolveTargetDocument\\:\\:\\$refMany is unused\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Tools/ResolveTargetDocumentListenerTest.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Tools\\\\ResolveTargetDocument\\:\\:\\$refOne is unused\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Tools/ResolveTargetDocumentListenerTest.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Tools\\\\TargetDocument\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Tools/ResolveTargetDocumentListenerTest.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\ArrayTest\\:\\:\\$id is unused\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/UnitOfWorkTest.php + + - + message: "#^Property Documents\\\\Account\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: tests/Documents/Account.php + + - + message: "#^Property Documents\\\\Address\\:\\:\\$test is unused\\.$#" + count: 1 + path: tests/Documents/Address.php + + - + message: "#^Property Documents\\\\Album\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: tests/Documents/Album.php + + - + message: "#^Property Documents\\\\Article\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: tests/Documents/Article.php + + - + message: "#^Property Documents\\\\Bars\\\\Bar\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: tests/Documents/Bars/Bar.php + + - + message: "#^Property Documents\\\\Category\\:\\:\\$id is unused\\.$#" + count: 1 + path: tests/Documents/Category.php + + - + message: "#^Property Documents\\\\Developer\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: tests/Documents/Developer.php + + - + message: "#^Property Documents\\\\Developer\\:\\:\\$name is never read, only written\\.$#" + count: 1 + path: tests/Documents/Developer.php + + - + message: "#^Property Documents\\\\Ecommerce\\\\StockItem\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: tests/Documents/Ecommerce/StockItem.php + + - + message: "#^Property Documents\\\\Event\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: tests/Documents/Event.php + + - + message: "#^Property Documents\\\\File\\:\\:\\$chunkSize is never written, only read\\.$#" + count: 1 + path: tests/Documents/File.php + + - + message: "#^Property Documents\\\\File\\:\\:\\$filename is never written, only read\\.$#" + count: 1 + path: tests/Documents/File.php + + - + message: "#^Property Documents\\\\File\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: tests/Documents/File.php + + - + message: "#^Property Documents\\\\File\\:\\:\\$length is never written, only read\\.$#" + count: 1 + path: tests/Documents/File.php + + - + message: "#^Property Documents\\\\File\\:\\:\\$uploadDate is never written, only read\\.$#" + count: 1 + path: tests/Documents/File.php + + - + message: "#^Property Documents\\\\FileWithoutChunkSize\\:\\:\\$chunkSize is never written, only read\\.$#" + count: 1 + path: tests/Documents/FileWithoutChunkSize.php + + - + message: "#^Property Documents\\\\FileWithoutChunkSize\\:\\:\\$filename is never written, only read\\.$#" + count: 1 + path: tests/Documents/FileWithoutChunkSize.php + + - + message: "#^Property Documents\\\\FileWithoutChunkSize\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: tests/Documents/FileWithoutChunkSize.php + + - + message: "#^Property Documents\\\\FileWithoutChunkSize\\:\\:\\$length is never written, only read\\.$#" + count: 1 + path: tests/Documents/FileWithoutChunkSize.php + + - + message: "#^Property Documents\\\\FileWithoutChunkSize\\:\\:\\$uploadDate is never written, only read\\.$#" + count: 1 + path: tests/Documents/FileWithoutChunkSize.php + + - + message: "#^Property Documents\\\\FileWithoutMetadata\\:\\:\\$filename is never written, only read\\.$#" + count: 1 + path: tests/Documents/FileWithoutMetadata.php + + - + message: "#^Property Documents\\\\FileWithoutMetadata\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: tests/Documents/FileWithoutMetadata.php + + - + message: "#^Property Documents\\\\Functional\\\\FavoritesUser\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: tests/Documents/Functional/FavoritesUser.php + + - + message: "#^Property Documents\\\\Group\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: tests/Documents/Group.php + + - + message: "#^Property Documents\\\\Message\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: tests/Documents/Message.php + + - + message: "#^Property Documents\\\\ProfileNotify\\:\\:\\$profileId is never written, only read\\.$#" + count: 1 + path: tests/Documents/ProfileNotify.php + + - + message: "#^Property Documents\\\\Project\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: tests/Documents/Project.php + + - + message: "#^Property Documents\\\\SchemaValidated\\:\\:\\$email is unused\\.$#" + count: 1 + path: tests/Documents/SchemaValidated.php + + - + message: "#^Property Documents\\\\SchemaValidated\\:\\:\\$id is unused\\.$#" + count: 1 + path: tests/Documents/SchemaValidated.php + + - + message: "#^Property Documents\\\\SchemaValidated\\:\\:\\$name is unused\\.$#" + count: 1 + path: tests/Documents/SchemaValidated.php + + - + message: "#^Property Documents\\\\SchemaValidated\\:\\:\\$phone is unused\\.$#" + count: 1 + path: tests/Documents/SchemaValidated.php + + - + message: "#^Property Documents\\\\SchemaValidated\\:\\:\\$status is unused\\.$#" + count: 1 + path: tests/Documents/SchemaValidated.php + + - + message: "#^Property Documents\\\\Task\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: tests/Documents/Task.php + + - + message: "#^Property Documents\\\\Tournament\\\\Participant\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: tests/Documents/Tournament/Participant.php + + - + message: "#^Property Documents\\\\Tournament\\\\Tournament\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: tests/Documents/Tournament/Tournament.php + + - + message: "#^Property Documents\\\\UserName\\:\\:\\$id is never written, only read\\.$#" + count: 1 + path: tests/Documents/UserName.php + + - + message: "#^Property Documents\\\\UserName\\:\\:\\$username is never written, only read\\.$#" + count: 1 + path: tests/Documents/UserName.php + + - + message: "#^Property Documents\\\\UserName\\:\\:\\$viewReference is never written, only read\\.$#" + count: 1 + path: tests/Documents/UserName.php + + - + message: "#^Property Documents\\\\ViewReference\\:\\:\\$referenceOneViewMappedBy is never written, only read\\.$#" + count: 1 + path: tests/Documents/ViewReference.php diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 5579d1ab4a..e76c8e36dc 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -5,11 +5,6 @@ IteratorAggregate - - - $fields - - implementsInterface(GridFSRepository::class)]]> diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/BuilderTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/BuilderTest.php index 31ee28f5f5..690cc003b0 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/BuilderTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/BuilderTest.php @@ -295,7 +295,7 @@ public function testPipelineConvertsTypes(): void ], [ '$replaceRoot' => [ - 'newRoot' => (object) [ + 'newRoot' => [ 'isToday' => [ '$eq' => ['$createdAt', new UTCDateTime($dateTime)], ], diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/ReplaceRootTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/ReplaceRootTest.php index a71086cbf2..d380816918 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/ReplaceRootTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/ReplaceRootTest.php @@ -26,7 +26,7 @@ public function testTypeConversion(): void self::assertEquals( [ '$replaceRoot' => [ - 'newRoot' => (object) [ + 'newRoot' => [ 'isToday' => ['$eq' => ['$createdAt', $mongoDate]], ], ], @@ -51,7 +51,7 @@ public function testTypeConversionWithDirectExpression(): void self::assertEquals( [ '$replaceRoot' => [ - 'newRoot' => (object) [ + 'newRoot' => [ 'isToday' => ['$eq' => ['$createdAt', $mongoDate]], ], ], @@ -72,7 +72,7 @@ public function testFieldNameConversion(): void self::assertEquals( [ '$replaceRoot' => [ - 'newRoot' => (object) [ + 'newRoot' => [ 'someField' => ['$concat' => ['$ip', 'foo']], ], ], diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/ReplaceWithTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/ReplaceWithTest.php index c915ecaee7..1f95c106ef 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/ReplaceWithTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/ReplaceWithTest.php @@ -25,7 +25,7 @@ public function testTypeConversion(): void self::assertEquals( [ - '$replaceWith' => (object) [ + '$replaceWith' => [ 'isToday' => ['$eq' => ['$createdAt', $mongoDate]], ], ], @@ -48,7 +48,7 @@ public function testTypeConversionWithDirectExpression(): void self::assertEquals( [ - '$replaceWith' => (object) [ + '$replaceWith' => [ 'isToday' => ['$eq' => ['$createdAt', $mongoDate]], ], ], @@ -67,7 +67,7 @@ public function testFieldNameConversion(): void self::assertEquals( [ - '$replaceWith' => (object) [ + '$replaceWith' => [ 'someField' => ['$concat' => ['$ip', 'foo']], ], ], From 3596a4ca46a85d92894b2032afe8dd50fda06391 Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Fri, 24 Mar 2023 11:24:11 +0100 Subject: [PATCH 23/23] Improve handling of required options as typed arguments --- .../ODM/MongoDB/Aggregation/Stage/Densify.php | 18 ++++++++--------- .../ODM/MongoDB/Aggregation/Stage/Fill.php | 18 +++++++---------- .../MongoDB/Aggregation/Stage/Fill/Output.php | 3 ++- .../Tests/Aggregation/Stage/DensifyTest.php | 20 +++++++++++++++++-- 4 files changed, 36 insertions(+), 23 deletions(-) diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Densify.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Densify.php index 5126c924ab..36c93778a1 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Densify.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Densify.php @@ -19,9 +19,9 @@ * '$densify': object{ * field: string, * partitionByFields?: list, - * range?: object{ - * bounds: BoundsType, - * step: int|float, + * range: object{ + * bounds?: BoundsType, + * step?: int|float, * unit?: UnitType * } * } @@ -34,13 +34,14 @@ class Densify extends Stage /** @var array */ private array $partitionByFields = []; - private ?object $range = null; + private object $range; public function __construct(Builder $builder, string $fieldName) { parent::__construct($builder); $this->field = $fieldName; + $this->range = (object) []; } public function partitionByFields(string ...$fields): self @@ -73,16 +74,15 @@ public function range($bounds, $step, string $unit = ''): self /** @psalm-return DensifyStageExpression */ public function getExpression(): array { - $params = (object) ['field' => $this->field]; + $params = (object) [ + 'field' => $this->field, + 'range' => $this->range, + ]; if ($this->partitionByFields) { $params->partitionByFields = $this->partitionByFields; } - if ($this->range) { - $params->range = $this->range; - } - return ['$densify' => $params]; } } diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill.php index 7fd6fccaef..8a594be506 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill.php @@ -26,7 +26,7 @@ * partitionBy?: string|OperatorExpression, * partitionByFields?: list, * sortBy?: SortShape, - * output?: array, + * output: array, * } * } */ @@ -41,11 +41,13 @@ class Fill extends Stage /** @var array */ private array $sortBy = []; - private ?Output $output = null; + private Output $output; public function __construct(Builder $builder) { parent::__construct($builder); + + $this->output = new Output($this->builder, $this); } /** @param mixed|Expr $expression */ @@ -86,16 +88,14 @@ public function sortBy($fieldName, $order = null): self public function output(): Output { - if (! $this->output) { - $this->output = new Output($this->builder, $this); - } - return $this->output; } public function getExpression(): array { - $params = (object) []; + $params = (object) [ + 'output' => (object) $this->output->getExpression(), + ]; if ($this->partitionBy) { $params->partitionBy = $this->partitionBy instanceof Expr @@ -111,10 +111,6 @@ public function getExpression(): array $params->sortBy = (object) $this->sortBy; } - if ($this->output) { - $params->output = (object) $this->output->getExpression(); - } - return ['$fill' => $params]; } } diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill/Output.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill/Output.php index f1a4bc38b8..3591672759 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill/Output.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill/Output.php @@ -6,6 +6,7 @@ use Doctrine\ODM\MongoDB\Aggregation\Builder; use Doctrine\ODM\MongoDB\Aggregation\Expr; +use Doctrine\ODM\MongoDB\Aggregation\Stage; use Doctrine\ODM\MongoDB\Aggregation\Stage\Fill; use LogicException; @@ -17,7 +18,7 @@ * * @psalm-import-type SortShape from Fill */ -class Output extends Fill +class Output extends Stage { private Fill $fill; diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/DensifyTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/DensifyTest.php index 84132c1998..7e581859df 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/DensifyTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/DensifyTest.php @@ -82,8 +82,24 @@ public function testStageWithRangeUnit(): void public function testFromBuilder(): void { $builder = $this->getTestAggregationBuilder(); - $builder->densify('someField'); + $builder + ->densify('someField') + ->range('full', 1, 'minute'); - self::assertEquals([['$densify' => (object) ['field' => 'someField']]], $builder->getPipeline()); + self::assertEquals( + [ + [ + '$densify' => (object) [ + 'field' => 'someField', + 'range' => (object) [ + 'bounds' => 'full', + 'step' => 1, + 'unit' => 'minute', + ], + ], + ], + ], + $builder->getPipeline(), + ); } }