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<string, mixed> */
+    /**
+     * @var array<string, mixed>
+     * @psalm-var PipelineExpression
+     */
     private array $pipeline;
 
     /** @var array<string, mixed> */
@@ -36,6 +40,7 @@ final class Aggregation implements IteratorAggregate
     /**
      * @param array<string, mixed> $pipeline
      * @param array<string, mixed> $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 5a0f785771..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<StageExpression>
  */
 class Builder
 {
@@ -83,9 +85,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 +104,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 +125,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 +140,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 +153,20 @@ 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);
+    }
+
+    /**
+     * 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);
+
+        return $this->addStage($stage);
     }
 
     /**
@@ -195,9 +204,20 @@ public function expr(): Expr
     public function facet(): Stage\Facet
     {
         $stage = new Stage\Facet($this);
-        $this->addStage($stage);
 
-        return $stage;
+        return $this->addStage($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);
+
+        return $this->addStage($stage);
     }
 
     /**
@@ -218,9 +238,8 @@ public function facet(): Stage\Facet
     public function geoNear($x, $y = null): Stage\GeoNear
     {
         $stage = new Stage\GeoNear($this, $x, $y);
-        $this->addStage($stage);
 
-        return $stage;
+        return $this->addStage($stage);
     }
 
     /**
@@ -252,6 +271,7 @@ public function getAggregation(array $options = []): Aggregation
      * given.
      *
      * @return array<array<string, mixed>>
+     * @psalm-return PipelineExpression
      */
     // phpcs:enable Squiz.Commenting.FunctionComment.ExtraParamComment
     public function getPipeline(/* bool $applyFilters = true */): array
@@ -321,9 +341,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);
     }
 
     /**
@@ -335,9 +354,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);
     }
 
     /**
@@ -358,9 +376,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);
     }
 
     /**
@@ -371,9 +388,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);
     }
 
     /**
@@ -386,9 +402,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);
     }
 
     /**
@@ -400,9 +415,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);
     }
 
     /**
@@ -416,6 +430,19 @@ 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);
+
+        return $this->addStage($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.
@@ -425,9 +452,8 @@ public function matchExpr(): QueryExpr
     public function out(string $from): Stage\Out
     {
         $stage = new Stage\Out($this, $from, $this->dm);
-        $this->addStage($stage);
 
-        return $stage;
+        return $this->addStage($stage);
     }
 
     /**
@@ -440,9 +466,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);
     }
 
     /**
@@ -454,9 +479,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);
     }
 
     /**
@@ -473,9 +497,28 @@ 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);
+    }
+
+    /**
+     * 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);
+
+        return $this->addStage($stage);
     }
 
     /**
@@ -496,9 +539,23 @@ 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);
+    }
+
+    /**
+     * 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);
+
+        return $this->addStage($stage);
     }
 
     /**
@@ -510,9 +567,8 @@ public function sample(int $size): Stage\Sample
     public function skip(int $skip): Stage\Skip
     {
         $stage = new Stage\Skip($this, $skip);
-        $this->addStage($stage);
 
-        return $stage;
+        return $this->addStage($stage);
     }
 
     /**
@@ -533,9 +589,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);
     }
 
     /**
@@ -547,9 +602,34 @@ 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);
+    }
+
+    /**
+     * 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);
+
+        return $this->addStage($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);
+
+        return $this->addStage($stage);
     }
 
     /**
@@ -563,15 +643,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
     {
diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Expr.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Expr.php
index 32cf7d1f5f..060f703199 100644
--- a/lib/Doctrine/ODM/MongoDB/Aggregation/Expr.php
+++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Expr.php
@@ -19,10 +19,13 @@
 use function func_get_args;
 use function is_array;
 use function is_string;
+use function sprintf;
 use function substr;
 
 /**
  * Fluent interface for building aggregation pipelines.
+ *
+ * @psalm-type OperatorExpression = array<string, mixed>|object
  */
 class Expr implements GenericOperatorsInterface
 {
@@ -469,7 +472,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 +1691,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.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage.php
index 28c3e777b2..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<non-empty-string, mixed>|object
  */
 abstract class Stage
 {
@@ -136,6 +139,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.
@@ -148,6 +161,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.
@@ -166,6 +189,7 @@ public function geoNear($x, $y = null): Stage\GeoNear
      * Returns the assembled aggregation pipeline
      *
      * @return array<array<string, mixed>>
+     * @psalm-return PipelineExpression
      */
     public function getPipeline(): array
     {
@@ -242,6 +266,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.
@@ -292,6 +327,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.
      */
@@ -312,6 +365,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.
@@ -350,6 +416,28 @@ 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.
+     *
+     * @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/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 @@
+<?php
+
+declare(strict_types=1);
+
+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;
+
+abstract class AbstractReplace extends Operator
+{
+    /** @var string|mixed[]|Expr|null */
+    protected $expression;
+    protected DocumentManager $dm;
+    protected ClassMetadata $class;
+
+    /** @param string|mixed[]|Expr|null $expression */
+    final public function __construct(Builder $builder, DocumentManager $documentManager, ClassMetadata $class, $expression = null)
+    {
+        Operator::__construct($builder);
+
+        $this->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 7bf77b2b76..b658656bb9 100644
--- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/AddFields.php
+++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/AddFields.php
@@ -4,15 +4,19 @@
 
 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<string, OperatorExpression|mixed>}
  */
 final class AddFields extends Operator
 {
+    /** @return AddFieldsStageExpression */
     public function getExpression(): array
     {
-        return [
-            '$addFields' => $this->expr->getExpression(),
-        ];
+        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
new file mode 100644
index 0000000000..36c93778a1
--- /dev/null
+++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Densify.php
@@ -0,0 +1,88 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Doctrine\ODM\MongoDB\Aggregation\Stage;
+
+use Doctrine\ODM\MongoDB\Aggregation\Builder;
+use Doctrine\ODM\MongoDB\Aggregation\Stage;
+use MongoDB\BSON\UTCDateTime;
+
+use function array_values;
+
+/**
+ * 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<string>,
+ *         range: object{
+ *             bounds?: BoundsType,
+ *             step?: int|float,
+ *             unit?: UnitType
+ *         }
+ *     }
+ * }
+ */
+class Densify extends Stage
+{
+    private string $field;
+
+    /** @var array<string> */
+    private array $partitionByFields = [];
+
+    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
+    {
+        $this->partitionByFields = array_values($fields);
+
+        return $this;
+    }
+
+    /**
+     * @param array|string $bounds
+     * @param int|float    $step
+     * @psalm-param BoundsType  $bounds
+     * @psalm-param ''|UnitType $unit
+     */
+    public function range($bounds, $step, string $unit = ''): self
+    {
+        $this->range = (object) [
+            'bounds' => $bounds,
+            'step' => $step,
+        ];
+
+        if ($unit !== '') {
+            $this->range->unit = $unit;
+        }
+
+        return $this;
+    }
+
+    /** @psalm-return DensifyStageExpression */
+    public function getExpression(): array
+    {
+        $params = (object) [
+            'field' => $this->field,
+            'range' => $this->range,
+        ];
+
+        if ($this->partitionByFields) {
+            $params->partitionByFields = $this->partitionByFields;
+        }
+
+        return ['$densify' => $params];
+    }
+}
diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Facet.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Facet.php
index 4d2ef13cab..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<string, PipelineExpression>}
  */
 class Facet extends Stage
 {
@@ -47,7 +50,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.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill.php
new file mode 100644
index 0000000000..8a594be506
--- /dev/null
+++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill.php
@@ -0,0 +1,116 @@
+<?php
+
+declare(strict_types=1);
+
+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\Aggregation\Stage\Fill\Output;
+
+use function array_values;
+use function is_array;
+use function is_string;
+use function strtolower;
+
+/**
+ * Fluent interface for adding a $fill stage to an aggregation pipeline.
+ *
+ * @psalm-import-type SortDirectionKeywords from Sort
+ * @psalm-import-type OperatorExpression from Expr
+ * @psalm-type SortDirection = int|SortDirectionKeywords
+ * @psalm-type SortShape = array<string, SortDirection>
+ * @psalm-type FillStageExpression = array{
+ *     '$fill': array{
+ *         partitionBy?: string|OperatorExpression,
+ *         partitionByFields?: list<string>,
+ *         sortBy?: SortShape,
+ *         output: array,
+ *     }
+ * }
+ */
+class Fill extends Stage
+{
+    /** @var mixed|Expr|null */
+    private $partitionBy = null;
+
+    /** @var array<string> */
+    private array $partitionByFields = [];
+
+    /** @var array<string, int> */
+    private array $sortBy = [];
+
+    private Output $output;
+
+    public function __construct(Builder $builder)
+    {
+        parent::__construct($builder);
+
+        $this->output = new Output($this->builder, $this);
+    }
+
+    /** @param mixed|Expr $expression */
+    public function partitionBy($expression): self
+    {
+        $this->partitionBy = $expression;
+
+        return $this;
+    }
+
+    public function partitionByFields(string ...$fields): self
+    {
+        $this->partitionByFields = array_values($fields);
+
+        return $this;
+    }
+
+    /**
+     * @param array<string, int|string>|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
+    {
+        $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
+    {
+        return $this->output;
+    }
+
+    public function getExpression(): array
+    {
+        $params = (object) [
+            'output' => (object) $this->output->getExpression(),
+        ];
+
+        if ($this->partitionBy) {
+            $params->partitionBy = $this->partitionBy instanceof Expr
+                ? $this->partitionBy->getExpression()
+                : $this->partitionBy;
+        }
+
+        if ($this->partitionByFields) {
+            $params->partitionByFields = $this->partitionByFields;
+        }
+
+        if ($this->sortBy) {
+            $params->sortBy = (object) $this->sortBy;
+        }
+
+        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..3591672759
--- /dev/null
+++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill/Output.php
@@ -0,0 +1,111 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Doctrine\ODM\MongoDB\Aggregation\Stage\Fill;
+
+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;
+
+use function func_get_args;
+use function sprintf;
+
+/**
+ * Fluent builder for output param of $fill stage
+ *
+ * @psalm-import-type SortShape from Fill
+ */
+class Output extends Stage
+{
+    private Fill $fill;
+
+    private string $currentField = '';
+
+    /** @var array<string, array<string, mixed>> */
+    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, int|string>|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): void
+    {
+        if (! $this->currentField) {
+            throw new LogicException(sprintf('%s requires setting a current field using field().', $method));
+        }
+    }
+}
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<string, mixed>}
  */
 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<string, mixed>,
+ *     }
+ * }
  */
 class Lookup extends Stage
 {
@@ -32,10 +45,13 @@ class Lookup extends Stage
 
     private ?string $as = null;
 
-    /** @var array<string, string>|null */
+    /** @var array<string, mixed>|null */
     private ?array $let = null;
 
-    /** @var Builder|array<array<string, mixed>>|null */
+    /**
+     * @var Builder|array<array<string, mixed>>|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<string, string> $let
+     * @param array<string, mixed> $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<array<string, mixed>> $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
new file mode 100644
index 0000000000..90174c153e
--- /dev/null
+++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Merge.php
@@ -0,0 +1,162 @@
+<?php
+
+declare(strict_types=1);
+
+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;
+use InvalidArgumentException;
+
+use function array_values;
+use function count;
+use function is_array;
+
+/**
+ * @psalm-import-type PipelineExpression from Builder
+ * @psalm-type OutputCollection = string|array{db: string, coll: string}
+ * @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<string>,
+ *         let?: array<string, mixed|Expr>,
+ *         whenMatched?: WhenMatchedType,
+ *         whenNotMatched?: WhenNotMatchedType,
+ *     }
+ * }
+ */
+class Merge extends Stage
+{
+    private DocumentManager $dm;
+
+    /**
+     * @var string|array
+     * @psalm-var OutputCollection
+     */
+    private $into;
+
+    /** @var list<string> */
+    private array $on = [];
+
+    /** @var array<string, mixed|Expr> */
+    private array $let = [];
+
+    /**
+     * @var string|array|Builder|Stage
+     * @psalm-var WhenMatchedParamType
+     */
+    private $whenMatched;
+
+    private ?string $whenNotMatched = null;
+
+    public function __construct(Builder $builder, DocumentManager $documentManager)
+    {
+        parent::__construct($builder);
+
+        $this->dm = $documentManager;
+    }
+
+    /** @psalm-return MergeStageExpression */
+    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<string, mixed|Expr> $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 WhenMatchedParamType $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/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<string, OperatorExpression|mixed>}
  */
 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<string, OperatorExpression|mixed>}
  */
 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
new file mode 100644
index 0000000000..317cde3c28
--- /dev/null
+++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/ReplaceWith.php
@@ -0,0 +1,20 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Doctrine\ODM\MongoDB\Aggregation\Stage;
+
+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
+    {
+        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
new file mode 100644
index 0000000000..cca5aed345
--- /dev/null
+++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Set.php
@@ -0,0 +1,22 @@
+<?php
+
+declare(strict_types=1);
+
+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<string, OperatorExpression|mixed>}
+ */
+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<string, int|SortMeta|string>
+ * @psalm-type SortDirectionKeywords = 'asc'|'desc'
+ * @psalm-type SortMeta = array{'$meta': SortMetaKeywords}
+ * @psalm-type SortShape = array<string, int|SortMeta|SortDirectionKeywords>
+ * @psalm-type SortStageExpression = array{
+ *     '$sort': array<string, int|SortMeta>
+ * }
  */
 class Sort extends Stage
 {
@@ -27,7 +31,8 @@ class Sort extends Stage
     /**
      * @param array<string, int|string|array<string, string>>|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
new file mode 100644
index 0000000000..945dd9fe72
--- /dev/null
+++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnionWith.php
@@ -0,0 +1,83 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Doctrine\ODM\MongoDB\Aggregation\Stage;
+
+use Doctrine\ODM\MongoDB\Aggregation\Builder;
+use Doctrine\ODM\MongoDB\Aggregation\Stage;
+use Doctrine\ODM\MongoDB\DocumentManager;
+use Doctrine\Persistence\Mapping\MappingException;
+use InvalidArgumentException;
+
+/**
+ * Fluent interface for adding a $unionWith stage to an aggregation pipeline.
+ *
+ * @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
+{
+    private DocumentManager $dm;
+
+    private string $collection;
+
+    /**
+     * @var array|Builder|null
+     * @psalm-var ?PipelineParamType
+     */
+    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 PipelineParamType $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;
+    }
+
+    /** @psalm-return UnionWithStageExpression */
+    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/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnsetStage.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnsetStage.php
new file mode 100644
index 0000000000..774b45406a
--- /dev/null
+++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnsetStage.php
@@ -0,0 +1,41 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Doctrine\ODM\MongoDB\Aggregation\Stage;
+
+use Doctrine\ODM\MongoDB\Aggregation\Builder;
+use Doctrine\ODM\MongoDB\Aggregation\Stage;
+use Doctrine\ODM\MongoDB\Persisters\DocumentPersister;
+
+use function array_map;
+use function array_values;
+
+/**
+ * Fluent interface for adding an $unset stage to an aggregation pipeline.
+ *
+ * @psalm-type UnsetStageExpression = array{'$unset': list<string>}
+ */
+class UnsetStage extends Stage
+{
+    private DocumentPersister $documentPersister;
+
+    /** @var list<string> */
+    private array $fields;
+
+    public function __construct(Builder $builder, DocumentPersister $documentPersister, string ...$fields)
+    {
+        parent::__construct($builder);
+
+        $this->documentPersister = $documentPersister;
+        $this->fields            = array_values($fields);
+    }
+
+    /** @return UnsetStageExpression */
+    public function getExpression(): array
+    {
+        return [
+            '$unset' => array_map([$this->documentPersister, 'prepareFieldName'], $this->fields),
+        ];
+    }
+}
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/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/phpstan-baseline.neon b/phpstan-baseline.neon
index 409b6e9de0..d67a346142 100644
--- a/phpstan-baseline.neon
+++ b/phpstan-baseline.neon
@@ -1,244 +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\\<Doctrine\\\\Persistence\\\\Mapping\\\\ClassMetadata\\<object\\>\\>\\) 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\\<TKey of \\(int\\|string\\),T of object\\>\\:\\:\\$mapping \\(Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\\\FieldMapping\\|null\\) does not accept array\\<string, array\\<int\\|string, mixed\\>\\|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\\<int, Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ChildDocumentWithDiscriminator\\> of property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ParentDocumentWithDiscriminator\\:\\:\\$embeddedChildren is not covariant with PHPDoc type array\\<Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ChildDocument\\>\\|Doctrine\\\\Common\\\\Collections\\\\Collection\\<int, Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ChildDocument\\> 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\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ChildDocumentWithDiscriminator\\>\\|Doctrine\\\\Common\\\\Collections\\\\Collection\\<int, Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ChildDocumentWithDiscriminator\\> of property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ParentDocumentWithDiscriminator\\:\\:\\$referencedChildren is not covariant with PHPDoc type array\\<Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ChildDocument\\>\\|Doctrine\\\\Common\\\\Collections\\\\Collection\\<int, Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ChildDocument\\> 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\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ChildDocumentWithDiscriminator\\>\\|Doctrine\\\\Common\\\\Collections\\\\Collection\\<int, Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ChildDocumentWithDiscriminator\\> of property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ParentDocumentWithoutDiscriminator\\:\\:\\$embeddedChildren is not covariant with PHPDoc type array\\<Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ChildDocument\\>\\|Doctrine\\\\Common\\\\Collections\\\\Collection\\<int, Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ChildDocument\\> 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\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ChildDocumentWithDiscriminator\\>\\|Doctrine\\\\Common\\\\Collections\\\\Collection\\<int, Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ChildDocumentWithDiscriminator\\> of property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ParentDocumentWithoutDiscriminator\\:\\:\\$referencedChildren is not covariant with PHPDoc type array\\<Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ChildDocument\\>\\|Doctrine\\\\Common\\\\Collections\\\\Collection\\<int, Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ChildDocument\\> 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\\<int, Documents\\\\Project\\>\\|null, Doctrine\\\\Common\\\\Collections\\\\ArrayCollection\\<int, Documents\\\\SubProject\\> 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\\<object\\>\\:\\:addIndex\\(\\) expects array\\{background\\?\\: bool, bits\\?\\: int, default_language\\?\\: string, expireAfterSeconds\\?\\: int, language_override\\?\\: string, min\\?\\: float, max\\?\\: float, name\\?\\: string, \\.\\.\\.\\}, array\\<string, non\\-empty\\-array\\<string, array\\<int\\|string, mixed\\>\\|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/Out.php
-
-        # $this->mapping['targetDocument'] is class-string<T>
-        -
-            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\\>&TDocument of object\\=, string\\=, array\\<string, mixed\\>\\=, Closure\\|null\\=, array\\<string, mixed\\>\\=\\)\\: 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\\<object\\>\\:\\:setProxyInitializer\\(\\) expects \\(Closure\\(ProxyManager\\\\Proxy\\\\GhostObjectInterface\\<object\\>\\=, string\\=, array\\<string, mixed\\>\\=, Closure\\|null\\=, array\\<string, mixed\\>\\=\\)\\: 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, \\-1\\|1\\>\\|string, limit\\?\\: int, maxTimeMS\\?\\: int, multiple\\?\\: bool, new\\?\\: bool, newObj\\?\\: array\\<string, mixed\\>, query\\?\\: array\\<string, mixed\\>, \\.\\.\\.\\}, 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\\<TKey of \\(int\\|string\\),T of object\\>\\:\\:\\$hints \\(Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\\\Hints\\) does not accept array\\<int, mixed\\>\\.$#"
-            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\\<int, mixed\\> 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\\<int, non\\-empty\\-array\\<non\\-empty\\-string, mixed\\>\\|object\\> but returns array\\<array\\<string, mixed\\>\\>\\.$#"
+			count: 2
+			path: lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php
+
+		-
+			message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Builder\\:\\:getPipeline\\(\\) should return array\\<int, non\\-empty\\-array\\<non\\-empty\\-string, mixed\\>\\|object\\> but returns non\\-empty\\-array\\<array\\<string, mixed\\>\\>\\.$#"
+			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\\<Doctrine\\\\Persistence\\\\Mapping\\\\ClassMetadata\\<object\\>\\>\\) 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\\<Doctrine\\\\ODM\\\\MongoDB\\\\DocumentManager\\>\\:\\:__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\\<object\\>\\:\\:setProxyInitializer\\(\\) expects \\(Closure\\(ProxyManager\\\\Proxy\\\\GhostObjectInterface\\<object\\>\\=, string\\=, array\\<string, mixed\\>\\=, Closure\\|null\\=, array\\<string, mixed\\>\\=\\)\\: 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\\<int\\|string, non\\-empty\\-array\\<int, string\\>\\|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, array\\<string, string\\>\\|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\\<object\\>\\:\\:addIndex\\(\\) expects array\\{background\\?\\: bool, bits\\?\\: int, default_language\\?\\: string, expireAfterSeconds\\?\\: int, language_override\\?\\: string, min\\?\\: float, max\\?\\: float, name\\?\\: string, \\.\\.\\.\\}, array\\<string, non\\-empty\\-array\\<string, array\\<int\\|string, mixed\\>\\|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\\<int, mixed\\> 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\\<TKey of \\(int\\|string\\),T of object\\>\\:\\:\\$hints \\(Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\\\Hints\\) does not accept array\\<int, mixed\\>\\.$#"
+			count: 1
+			path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php
+
+		-
+			message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\<TKey of \\(int\\|string\\),T of object\\>\\:\\:\\$mapping \\(Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\\\FieldMapping\\|null\\) does not accept array\\<string, array\\<int\\|string, mixed\\>\\|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\\>&TDocument of object\\=, string\\=, array\\<string, mixed\\>\\=, Closure\\|null\\=, array\\<string, mixed\\>\\=\\)\\: 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\\<int, Documents\\\\Project\\>\\|null, Doctrine\\\\Common\\\\Collections\\\\ArrayCollection\\<int, Documents\\\\SubProject\\> 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\\<int, Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ChildDocumentWithDiscriminator\\> of property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ParentDocumentWithDiscriminator\\:\\:\\$embeddedChildren is not covariant with PHPDoc type array\\<Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ChildDocument\\>\\|Doctrine\\\\Common\\\\Collections\\\\Collection\\<int, Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ChildDocument\\> 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\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ChildDocumentWithDiscriminator\\>\\|Doctrine\\\\Common\\\\Collections\\\\Collection\\<int, Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ChildDocumentWithDiscriminator\\> of property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ParentDocumentWithDiscriminator\\:\\:\\$referencedChildren is not covariant with PHPDoc type array\\<Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ChildDocument\\>\\|Doctrine\\\\Common\\\\Collections\\\\Collection\\<int, Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ChildDocument\\> 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\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ChildDocumentWithDiscriminator\\>\\|Doctrine\\\\Common\\\\Collections\\\\Collection\\<int, Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ChildDocumentWithDiscriminator\\> of property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ParentDocumentWithoutDiscriminator\\:\\:\\$embeddedChildren is not covariant with PHPDoc type array\\<Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ChildDocument\\>\\|Doctrine\\\\Common\\\\Collections\\\\Collection\\<int, Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ChildDocument\\> 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\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ChildDocumentWithDiscriminator\\>\\|Doctrine\\\\Common\\\\Collections\\\\Collection\\<int, Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ChildDocumentWithDiscriminator\\> of property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ParentDocumentWithoutDiscriminator\\:\\:\\$referencedChildren is not covariant with PHPDoc type array\\<Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ChildDocument\\>\\|Doctrine\\\\Common\\\\Collections\\\\Collection\\<int, Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ChildDocument\\> 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\\<class@anonymous/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest\\.php\\:235\\>\\:\\: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\\<class@anonymous/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest\\.php\\:255\\>\\:\\: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, \\-1\\|1\\>\\|string, limit\\?\\: int, maxTimeMS\\?\\: int, multiple\\?\\: bool, new\\?\\: bool, newObj\\?\\: array\\<string, mixed\\>, query\\?\\: array\\<string, mixed\\>, \\.\\.\\.\\}, 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 49373a5ce8..e76c8e36dc 100644
--- a/psalm-baseline.xml
+++ b/psalm-baseline.xml
@@ -1,19 +1,14 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<files psalm-version="5.6.0@e784128902dfe01d489c4123d69918a9f3c1eac5">
+<files psalm-version="5.8.0@9cf4f60a333f779ad3bc704a555920e81d4fdcda">
   <file src="lib/Doctrine/ODM/MongoDB/Aggregation/Aggregation.php">
     <MissingTemplateParam>
       <code>IteratorAggregate</code>
     </MissingTemplateParam>
   </file>
-  <file src="lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php">
-    <InvalidArgument>
-      <code>$fields</code>
-    </InvalidArgument>
-  </file>
   <file src="lib/Doctrine/ODM/MongoDB/Configuration.php">
     <TypeDoesNotContainType>
-      <code>$reflectionClass-&gt;implementsInterface(GridFSRepository::class)</code>
-      <code>$reflectionClass-&gt;implementsInterface(ObjectRepository::class)</code>
+      <code><![CDATA[$reflectionClass->implementsInterface(GridFSRepository::class)]]></code>
+      <code><![CDATA[$reflectionClass->implementsInterface(ObjectRepository::class)]]></code>
     </TypeDoesNotContainType>
   </file>
   <file src="lib/Doctrine/ODM/MongoDB/DocumentManager.php">
@@ -33,7 +28,7 @@
   </file>
   <file src="lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php">
     <ImplementedReturnTypeMismatch>
-      <code>array&lt;string|null&gt;</code>
+      <code><![CDATA[array<string|null>]]></code>
     </ImplementedReturnTypeMismatch>
     <InvalidArgument>
       <code>$mapping</code>
@@ -43,29 +38,29 @@
       <code>$mapping</code>
     </InvalidArgument>
     <InvalidArrayOffset>
-      <code>[$this-&gt;identifier =&gt; $this-&gt;getIdentifierValue($object)]</code>
+      <code><![CDATA[[$this->identifier => $this->getIdentifierValue($object)]]]></code>
     </InvalidArrayOffset>
     <InvalidPropertyAssignmentValue>
-      <code>$this-&gt;associationMappings</code>
-      <code>$this-&gt;associationMappings</code>
-      <code>$this-&gt;fieldMappings</code>
+      <code><![CDATA[$this->associationMappings]]></code>
+      <code><![CDATA[$this->associationMappings]]></code>
+      <code><![CDATA[$this->fieldMappings]]></code>
     </InvalidPropertyAssignmentValue>
     <InvalidReturnStatement>
       <code>$mapping</code>
       <code>$mapping</code>
-      <code>array_filter(
-            $this-&gt;associationMappings,
-            static fn ($assoc) =&gt; ! empty($assoc['embedded'])
-        )</code>
+      <code><![CDATA[array_filter(
+            $this->associationMappings,
+            static fn ($assoc) => ! empty($assoc['embedded'])
+        )]]></code>
     </InvalidReturnStatement>
     <InvalidReturnType>
       <code>FieldMapping</code>
       <code>FieldMappingConfig</code>
-      <code>array&lt;string, FieldMapping&gt;</code>
+      <code><![CDATA[array<string, FieldMapping>]]></code>
     </InvalidReturnType>
     <LessSpecificImplementedReturnType>
       <code>array</code>
-      <code>array&lt;string|null&gt;</code>
+      <code><![CDATA[array<string|null>]]></code>
     </LessSpecificImplementedReturnType>
   </file>
   <file src="lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactory.php">
@@ -84,64 +79,64 @@
       <code>$options</code>
     </InvalidArgument>
     <NoInterfaceProperties>
-      <code>$xmlRoot-&gt;field</code>
-      <code>$xmlRoot-&gt;id</code>
-      <code>$xmlRoot-&gt;{'also-load-methods'}</code>
-      <code>$xmlRoot-&gt;{'default-discriminator-value'}</code>
-      <code>$xmlRoot-&gt;{'discriminator-field'}</code>
-      <code>$xmlRoot-&gt;{'discriminator-map'}</code>
-      <code>$xmlRoot-&gt;{'embed-many'}</code>
-      <code>$xmlRoot-&gt;{'embed-one'}</code>
-      <code>$xmlRoot-&gt;{'indexes'}</code>
-      <code>$xmlRoot-&gt;{'lifecycle-callbacks'}</code>
-      <code>$xmlRoot-&gt;{'read-preference'}</code>
-      <code>$xmlRoot-&gt;{'reference-many'}</code>
-      <code>$xmlRoot-&gt;{'reference-one'}</code>
-      <code>$xmlRoot-&gt;{'schema-validation'}</code>
-      <code>$xmlRoot-&gt;{'shard-key'}</code>
+      <code><![CDATA[$xmlRoot->field]]></code>
+      <code><![CDATA[$xmlRoot->id]]></code>
+      <code><![CDATA[$xmlRoot->{'also-load-methods'}]]></code>
+      <code><![CDATA[$xmlRoot->{'default-discriminator-value'}]]></code>
+      <code><![CDATA[$xmlRoot->{'discriminator-field'}]]></code>
+      <code><![CDATA[$xmlRoot->{'discriminator-map'}]]></code>
+      <code><![CDATA[$xmlRoot->{'embed-many'}]]></code>
+      <code><![CDATA[$xmlRoot->{'embed-one'}]]></code>
+      <code><![CDATA[$xmlRoot->{'indexes'}]]></code>
+      <code><![CDATA[$xmlRoot->{'lifecycle-callbacks'}]]></code>
+      <code><![CDATA[$xmlRoot->{'read-preference'}]]></code>
+      <code><![CDATA[$xmlRoot->{'reference-many'}]]></code>
+      <code><![CDATA[$xmlRoot->{'reference-one'}]]></code>
+      <code><![CDATA[$xmlRoot->{'schema-validation'}]]></code>
+      <code><![CDATA[$xmlRoot->{'shard-key'}]]></code>
     </NoInterfaceProperties>
     <RedundantCast>
-      <code>(bool) $mapping['background']</code>
-      <code>(bool) $mapping['sparse']</code>
-      <code>(bool) $mapping['unique']</code>
-      <code>(string) $mapping['index-name']</code>
+      <code><![CDATA[(bool) $mapping['background']]]></code>
+      <code><![CDATA[(bool) $mapping['sparse']]]></code>
+      <code><![CDATA[(bool) $mapping['unique']]]></code>
+      <code><![CDATA[(string) $mapping['index-name']]]></code>
     </RedundantCast>
     <RedundantCondition>
       <code>assert($attributes instanceof SimpleXMLElement)</code>
-      <code>isset($xmlRoot-&gt;field)</code>
-      <code>isset($xmlRoot-&gt;id)</code>
-      <code>isset($xmlRoot-&gt;{'default-discriminator-value'})</code>
-      <code>isset($xmlRoot-&gt;{'discriminator-field'})</code>
-      <code>isset($xmlRoot-&gt;{'discriminator-map'})</code>
-      <code>isset($xmlRoot-&gt;{'embed-many'})</code>
-      <code>isset($xmlRoot-&gt;{'embed-one'})</code>
-      <code>isset($xmlRoot-&gt;{'indexes'})</code>
-      <code>isset($xmlRoot-&gt;{'lifecycle-callbacks'})</code>
-      <code>isset($xmlRoot-&gt;{'read-preference'})</code>
-      <code>isset($xmlRoot-&gt;{'reference-many'})</code>
-      <code>isset($xmlRoot-&gt;{'reference-one'})</code>
-      <code>isset($xmlRoot-&gt;{'schema-validation'})</code>
-      <code>isset($xmlRoot-&gt;{'shard-key'})</code>
+      <code><![CDATA[isset($xmlRoot->field)]]></code>
+      <code><![CDATA[isset($xmlRoot->id)]]></code>
+      <code><![CDATA[isset($xmlRoot->{'default-discriminator-value'})]]></code>
+      <code><![CDATA[isset($xmlRoot->{'discriminator-field'})]]></code>
+      <code><![CDATA[isset($xmlRoot->{'discriminator-map'})]]></code>
+      <code><![CDATA[isset($xmlRoot->{'embed-many'})]]></code>
+      <code><![CDATA[isset($xmlRoot->{'embed-one'})]]></code>
+      <code><![CDATA[isset($xmlRoot->{'indexes'})]]></code>
+      <code><![CDATA[isset($xmlRoot->{'lifecycle-callbacks'})]]></code>
+      <code><![CDATA[isset($xmlRoot->{'read-preference'})]]></code>
+      <code><![CDATA[isset($xmlRoot->{'reference-many'})]]></code>
+      <code><![CDATA[isset($xmlRoot->{'reference-one'})]]></code>
+      <code><![CDATA[isset($xmlRoot->{'schema-validation'})]]></code>
+      <code><![CDATA[isset($xmlRoot->{'shard-key'})]]></code>
     </RedundantCondition>
     <TypeDoesNotContainType>
-      <code>$xmlRoot-&gt;getName() === 'document'</code>
-      <code>$xmlRoot-&gt;getName() === 'embedded-document'</code>
-      <code>$xmlRoot-&gt;getName() === 'gridfs-file'</code>
-      <code>$xmlRoot-&gt;getName() === 'mapped-superclass'</code>
-      <code>$xmlRoot-&gt;getName() === 'query-result-document'</code>
-      <code>$xmlRoot-&gt;getName() === 'view'</code>
-      <code>isset($xmlRoot-&gt;{'also-load-methods'})</code>
+      <code><![CDATA[$xmlRoot->getName() === 'document']]></code>
+      <code><![CDATA[$xmlRoot->getName() === 'embedded-document']]></code>
+      <code><![CDATA[$xmlRoot->getName() === 'gridfs-file']]></code>
+      <code><![CDATA[$xmlRoot->getName() === 'mapped-superclass']]></code>
+      <code><![CDATA[$xmlRoot->getName() === 'query-result-document']]></code>
+      <code><![CDATA[$xmlRoot->getName() === 'view']]></code>
+      <code><![CDATA[isset($xmlRoot->{'also-load-methods'})]]></code>
     </TypeDoesNotContainType>
   </file>
   <file src="lib/Doctrine/ODM/MongoDB/PersistentCollection/AbstractPersistentCollectionFactory.php">
     <InvalidArgument>
-      <code>new PersistentCollection($coll, $dm, $dm-&gt;getUnitOfWork())</code>
-      <code>new PersistentCollection($coll, $dm, $dm-&gt;getUnitOfWork())</code>
+      <code><![CDATA[new PersistentCollection($coll, $dm, $dm->getUnitOfWork())]]></code>
+      <code><![CDATA[new PersistentCollection($coll, $dm, $dm->getUnitOfWork())]]></code>
     </InvalidArgument>
   </file>
   <file src="lib/Doctrine/ODM/MongoDB/PersistentCollection/PersistentCollectionTrait.php">
     <RedundantCondition>
-      <code>is_object($this-&gt;coll)</code>
+      <code><![CDATA[is_object($this->coll)]]></code>
     </RedundantCondition>
   </file>
   <file src="lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php">
@@ -151,7 +146,7 @@
   </file>
   <file src="lib/Doctrine/ODM/MongoDB/Proxy/Resolver/CachingClassNameResolver.php">
     <InvalidPropertyAssignmentValue>
-      <code>$this-&gt;resolvedNames</code>
+      <code><![CDATA[$this->resolvedNames]]></code>
     </InvalidPropertyAssignmentValue>
   </file>
   <file src="lib/Doctrine/ODM/MongoDB/Proxy/Resolver/ProxyManagerClassNameResolver.php">
@@ -164,10 +159,10 @@
       <code>$query</code>
     </InvalidArgument>
     <InvalidPropertyAssignmentValue>
-      <code>$this-&gt;query</code>
-      <code>$this-&gt;query</code>
-      <code>$this-&gt;query</code>
-      <code>$this-&gt;query</code>
+      <code><![CDATA[$this->query]]></code>
+      <code><![CDATA[$this->query]]></code>
+      <code><![CDATA[$this->query]]></code>
+      <code><![CDATA[$this->query]]></code>
     </InvalidPropertyAssignmentValue>
   </file>
   <file src="lib/Doctrine/ODM/MongoDB/Query/Query.php">
@@ -253,18 +248,23 @@
       <code>$mapping</code>
     </InvalidArgument>
     <InvalidPropertyAssignmentValue>
-      <code>$this-&gt;parentAssociations</code>
+      <code><![CDATA[$this->parentAssociations]]></code>
     </InvalidPropertyAssignmentValue>
     <InvalidReturnStatement>
       <code>$divided</code>
     </InvalidReturnStatement>
     <InvalidReturnType>
-      <code>array&lt;class-string, array{0: ClassMetadata&lt;object&gt;, 1: array&lt;string, object&gt;}&gt;</code>
+      <code><![CDATA[array<class-string, array{0: ClassMetadata<object>, 1: array<string, object>}>]]></code>
     </InvalidReturnType>
     <NullableReturnStatement>
-      <code>$mapping['targetDocument']</code>
+      <code><![CDATA[$mapping['targetDocument']]]></code>
     </NullableReturnStatement>
   </file>
+  <file src="tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/UnsetTest.php">
+    <InvalidArgument>
+      <code>$documentPersister</code>
+    </InvalidArgument>
+  </file>
   <file src="tests/Doctrine/ODM/MongoDB/Tests/ClassMetadataTestUtil.php">
     <InvalidReturnStatement>
       <code>$mapping + $defaultFieldMapping</code>
@@ -275,12 +275,12 @@
   </file>
   <file src="tests/Doctrine/ODM/MongoDB/Tests/DocumentManagerTest.php">
     <InvalidArgument>
-      <code>$class-&gt;associationMappings['ref']</code>
-      <code>$class-&gt;associationMappings['ref1']</code>
-      <code>$class-&gt;associationMappings['ref2']</code>
-      <code>$class-&gt;associationMappings['ref3']</code>
-      <code>$class-&gt;associationMappings['ref4']</code>
-      <code>['storeAs' =&gt; ClassMetadata::REFERENCE_STORE_AS_DB_REF]</code>
+      <code><![CDATA[$class->associationMappings['ref']]]></code>
+      <code><![CDATA[$class->associationMappings['ref1']]]></code>
+      <code><![CDATA[$class->associationMappings['ref2']]]></code>
+      <code><![CDATA[$class->associationMappings['ref3']]]></code>
+      <code><![CDATA[$class->associationMappings['ref4']]]></code>
+      <code><![CDATA[['storeAs' => ClassMetadata::REFERENCE_STORE_AS_DB_REF]]]></code>
     </InvalidArgument>
   </file>
   <file src="tests/Doctrine/ODM/MongoDB/Tests/DocumentRepositoryTest.php">
@@ -290,9 +290,9 @@
   </file>
   <file src="tests/Doctrine/ODM/MongoDB/Tests/Functional/CollectionPersisterTest.php">
     <InvalidArgument>
-      <code>[$user-&gt;categories[0]-&gt;children, $user-&gt;categories[1]-&gt;children]</code>
-      <code>[$user-&gt;categories[0]-&gt;children[0]-&gt;children, $user-&gt;categories[0]-&gt;children[1]-&gt;children]</code>
-      <code>[$user-&gt;categories[0]-&gt;children[0]-&gt;children, $user-&gt;categories[0]-&gt;children[1]-&gt;children]</code>
+      <code><![CDATA[[$user->categories[0]->children, $user->categories[1]->children]]]></code>
+      <code><![CDATA[[$user->categories[0]->children[0]->children, $user->categories[0]->children[1]->children]]]></code>
+      <code><![CDATA[[$user->categories[0]->children[0]->children, $user->categories[0]->children[1]->children]]]></code>
     </InvalidArgument>
   </file>
   <file src="tests/Doctrine/ODM/MongoDB/Tests/Functional/CustomCollectionsTest.php">
@@ -310,14 +310,14 @@
   </file>
   <file src="tests/Doctrine/ODM/MongoDB/Tests/Functional/ReferenceEmbeddedDocumentsTest.php">
     <InvalidArgument>
-      <code>new ArrayCollection([
+      <code><![CDATA[new ArrayCollection([
             new Issue('Issue #1', 'Issue #1 on Sub Project #1'),
             new Issue('Issue #2', 'Issue #2 on Sub Project #1'),
-        ])</code>
-      <code>new ArrayCollection([
+        ])]]></code>
+      <code><![CDATA[new ArrayCollection([
             new Issue('Issue #1', 'Issue #1 on Sub Project #2'),
             new Issue('Issue #2', 'Issue #2 on Sub Project #2'),
-        ])</code>
+        ])]]></code>
     </InvalidArgument>
   </file>
   <file src="tests/Doctrine/ODM/MongoDB/Tests/Functional/RepositoriesTest.php">
@@ -327,12 +327,12 @@
   </file>
   <file src="tests/Doctrine/ODM/MongoDB/Tests/Functional/ShardKeyTest.php">
     <InvalidArgument>
-      <code>['upsert' =&gt; true]</code>
+      <code><![CDATA[['upsert' => true]]]></code>
     </InvalidArgument>
   </file>
   <file src="tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH1011Test.php">
     <InvalidArgument>
-      <code>$doc-&gt;embeds</code>
+      <code><![CDATA[$doc->embeds]]></code>
     </InvalidArgument>
   </file>
   <file src="tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH245Test.php">
@@ -366,11 +366,11 @@
   </file>
   <file src="tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH597Test.php">
     <InvalidPropertyAssignmentValue>
-      <code>new ArrayCollection([
+      <code><![CDATA[new ArrayCollection([
             new GH597Comment('Comment 1'),
             new GH597Comment('Comment 2'),
             new GH597Comment('Comment 3'),
-        ])</code>
+        ])]]></code>
       <code>new ArrayCollection([$referenceMany1, $referenceMany2])</code>
     </InvalidPropertyAssignmentValue>
   </file>
@@ -384,29 +384,29 @@
       <code>DocumentManager</code>
     </InvalidNullableReturnType>
     <NullableReturnStatement>
-      <code>$this-&gt;dm</code>
+      <code><![CDATA[$this->dm]]></code>
     </NullableReturnStatement>
   </file>
   <file src="tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php">
     <InvalidArgument>
       <code>$config</code>
       <code>$mapping</code>
-      <code>[
-            'fieldName' =&gt; 'assoc',
-            'reference' =&gt; true,
-            'type' =&gt; 'many',
-            'targetDocument' =&gt; 'stdClass',
-            'repositoryMethod' =&gt; 'fetch',
-            $prop =&gt; $value,
-        ]</code>
-      <code>[
-            'fieldName' =&gt; 'enum',
-            'enumType' =&gt; Card::class,
-        ]</code>
-      <code>[
-            'fieldName' =&gt; 'enum',
-            'enumType' =&gt; SuitNonBacked::class,
-        ]</code>
+      <code><![CDATA[[
+            'fieldName' => 'assoc',
+            'reference' => true,
+            'type' => 'many',
+            'targetDocument' => 'stdClass',
+            'repositoryMethod' => 'fetch',
+            $prop => $value,
+        ]]]></code>
+      <code><![CDATA[[
+            'fieldName' => 'enum',
+            'enumType' => Card::class,
+        ]]]></code>
+      <code><![CDATA[[
+            'fieldName' => 'enum',
+            'enumType' => SuitNonBacked::class,
+        ]]]></code>
     </InvalidArgument>
   </file>
   <file src="tests/Doctrine/ODM/MongoDB/Tests/Query/BuilderTest.php">
@@ -416,7 +416,7 @@
   </file>
   <file src="tests/Doctrine/ODM/MongoDB/Tests/QueryTest.php">
     <InvalidArgument>
-      <code>['type' =&gt; -1]</code>
+      <code><![CDATA[['type' => -1]]]></code>
     </InvalidArgument>
   </file>
   <file src="tests/Doctrine/ODM/MongoDB/Tests/SchemaManagerTest.php">
diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/BuilderTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/BuilderTest.php
index 1fd2d294e7..690cc003b0 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,
                         ],
@@ -295,9 +295,9 @@ public function testPipelineConvertsTypes(): void
             ],
             [
                 '$replaceRoot' => [
-                    'newRoot' => (object) [
+                    'newRoot' => [
                         'isToday' => [
-                            '$eq' => ['$createdAt', new UTCDateTime((int) $dateTime->format('Uv'))],
+                            '$eq' => ['$createdAt', new UTCDateTime($dateTime)],
                         ],
                     ],
                 ],
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
new file mode 100644
index 0000000000..7e581859df
--- /dev/null
+++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/DensifyTest.php
@@ -0,0 +1,105 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Doctrine\ODM\MongoDB\Tests\Aggregation\Stage;
+
+use Doctrine\ODM\MongoDB\Aggregation\Stage\Densify;
+use Doctrine\ODM\MongoDB\Tests\Aggregation\AggregationTestTrait;
+use Doctrine\ODM\MongoDB\Tests\BaseTest;
+
+class DensifyTest extends BaseTest
+{
+    use AggregationTestTrait;
+
+    public function testStage(): void
+    {
+        $densifyStage = new Densify($this->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 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();
+        $builder
+            ->densify('someField')
+            ->range('full', 1, 'minute');
+
+        self::assertEquals(
+            [
+                [
+                    '$densify' => (object) [
+                        'field' => 'someField',
+                        'range' => (object) [
+                            'bounds' => 'full',
+                            'step' => 1,
+                            'unit' => 'minute',
+                        ],
+                    ],
+                ],
+            ],
+            $builder->getPipeline(),
+        );
+    }
+}
diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/FacetTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/FacetTest.php
index 86ddfb25dd..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,17 +57,17 @@ public function testFacetFromBuilder(): void
         ], $builder->getPipeline());
     }
 
-    public function testFacetThrowsExceptionWithoutFieldName(): void
+    public function testThrowsExceptionWithoutFieldName(): 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());
     }
 
     /** @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
new file mode 100644
index 0000000000..e83b187a62
--- /dev/null
+++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/FillTest.php
@@ -0,0 +1,125 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Doctrine\ODM\MongoDB\Tests\Aggregation\Stage;
+
+use Doctrine\ODM\MongoDB\Aggregation\Stage\Fill;
+use Doctrine\ODM\MongoDB\Tests\Aggregation\AggregationTestTrait;
+use Doctrine\ODM\MongoDB\Tests\BaseTest;
+
+class FillTest extends BaseTest
+{
+    use AggregationTestTrait;
+
+    public function testStage(): void
+    {
+        $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('computed')->value(
+                    $builder->expr()->multiply('$value', 5),
+                );
+
+        self::assertEquals(
+            [
+                '$fill' => (object) [
+                    'partitionByFields' => ['field1', 'field2'],
+                    'sortBy' => (object) ['field1' => 1],
+                    'output' => (object) [
+                        'foo' => ['method' => 'locf'],
+                        'bar' => ['method' => 'linear'],
+                        'fixed' => ['value' => 0],
+                        'computed' => ['value' => ['$multiply' => ['$value', 5]]],
+                    ],
+                ],
+            ],
+            $fillStage->getExpression(),
+        );
+    }
+
+    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());
+        $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'],
+                    ],
+                ],
+            ],
+            $fillStage->getExpression(),
+        );
+    }
+
+    public function testFromBuilder(): 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(),
+        );
+    }
+}
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
new file mode 100644
index 0000000000..df664b8cac
--- /dev/null
+++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/MergeTest.php
@@ -0,0 +1,129 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Doctrine\ODM\MongoDB\Tests\Aggregation\Stage;
+
+use Doctrine\ODM\MongoDB\Aggregation\Builder;
+use Doctrine\ODM\MongoDB\Tests\BaseTest;
+use Documents\SimpleReferenceUser;
+use Documents\User;
+use Generator;
+use InvalidArgumentException;
+
+use function is_callable;
+
+class MergeTest extends BaseTest
+{
+    public function testStageWithClassName(): void
+    {
+        $builder = $this->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 testStageWithCollectionName(): 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
+    {
+        yield 'Array' => [
+            'pipeline' => [
+                ['$set' => ['foo' => 'bar']],
+                ['$unset' => ['bar']],
+            ],
+        ];
+
+        yield 'Builder' => [
+            'pipeline' => static function (Builder $builder): Builder {
+                $builder
+                    ->set()
+                        ->field('foo')->expression('bar')
+                    ->unset('bar');
+
+                return $builder;
+            },
+        ];
+    }
+
+    /**
+     * @param array<array<string, mixed>>|callable $pipeline
+     *
+     * @dataProvider providePipeline
+     */
+    public function testStageWithPipeline($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' => [
+                        ['$set' => ['foo' => 'bar']],
+                        ['$unset' => ['bar']],
+                    ],
+                ],
+            ],
+        ];
+
+        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);
+    }
+}
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/ReplaceRootTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/ReplaceRootTest.php
index 8ac21bf494..d380816918 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')
@@ -26,7 +26,7 @@ public function testTypeConversion(): void
         self::assertEquals(
             [
                 '$replaceRoot' => [
-                    'newRoot' => (object) [
+                    'newRoot' => [
                         'isToday' => ['$eq' => ['$createdAt', $mongoDate]],
                     ],
                 ],
@@ -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()
@@ -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
new file mode 100644
index 0000000000..1f95c106ef
--- /dev/null
+++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/ReplaceWithTest.php
@@ -0,0 +1,90 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Doctrine\ODM\MongoDB\Tests\Aggregation\Stage;
+
+use DateTimeImmutable;
+use Doctrine\ODM\MongoDB\Tests\BaseTest;
+use Documents\CmsComment;
+use Documents\User;
+use MongoDB\BSON\UTCDateTime;
+
+class ReplaceWithTest extends BaseTest
+{
+    public function testTypeConversion(): void
+    {
+        $builder = $this->dm->createAggregationBuilder(User::class);
+
+        $dateTime  = new DateTimeImmutable('2000-01-01T00:00Z');
+        $mongoDate = new UTCDateTime($dateTime);
+        $stage     = $builder
+            ->replaceWith()
+                ->field('isToday')
+                ->eq('$createdAt', $dateTime);
+
+        self::assertEquals(
+            [
+                '$replaceWith' => [
+                    '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($dateTime);
+        $stage     = $builder
+            ->replaceWith(
+                $builder->expr()
+                    ->field('isToday')
+                    ->eq('$createdAt', $dateTime),
+            );
+
+        self::assertEquals(
+            [
+                '$replaceWith' => [
+                    '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' => [
+                    '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(),
+        );
+    }
+}
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
new file mode 100644
index 0000000000..5d41949c26
--- /dev/null
+++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SetTest.php
@@ -0,0 +1,35 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Doctrine\ODM\MongoDB\Tests\Aggregation\Stage;
+
+use Doctrine\ODM\MongoDB\Aggregation\Stage\Set;
+use Doctrine\ODM\MongoDB\Tests\Aggregation\AggregationTestTrait;
+use Doctrine\ODM\MongoDB\Tests\BaseTest;
+
+class SetTest extends BaseTest
+{
+    use AggregationTestTrait;
+
+    public function testStage(): void
+    {
+        $setStage = new Set($this->getTestAggregationBuilder());
+        $setStage
+            ->field('product')
+            ->multiply('$field', 5);
+
+        self::assertSame(['$set' => ['product' => ['$multiply' => ['$field', 5]]]], $setStage->getExpression());
+    }
+
+    public function testFromBuilder(): void
+    {
+        $builder = $this->getTestAggregationBuilder();
+        $builder
+            ->set()
+                ->field('product')
+                ->multiply('$field', 5);
+
+        self::assertSame([['$set' => ['product' => ['$multiply' => ['$field', 5]]]]], $builder->getPipeline());
+    }
+}
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
new file mode 100644
index 0000000000..ba52c5385c
--- /dev/null
+++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/UnionWithTest.php
@@ -0,0 +1,50 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Doctrine\ODM\MongoDB\Tests\Aggregation\Stage;
+
+use Doctrine\ODM\MongoDB\Tests\BaseTest;
+use Documents\SimpleReferenceUser;
+use Documents\User;
+
+class UnionWithTest extends BaseTest
+{
+    public function testStageWithClassName(): void
+    {
+        $builder = $this->dm->createAggregationBuilder(SimpleReferenceUser::class);
+        $builder
+            ->unionWith(User::class);
+
+        $expectedPipeline = [
+            ['$unionWith' => (object) ['coll' => 'users']],
+        ];
+
+        self::assertEquals($expectedPipeline, $builder->getPipeline());
+    }
+
+    public function testStageWithCollectionName(): 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());
+    }
+}
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..08b5b268eb
--- /dev/null
+++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/UnsetTest.php
@@ -0,0 +1,31 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Doctrine\ODM\MongoDB\Tests\Aggregation\Stage;
+
+use Doctrine\ODM\MongoDB\Aggregation\Stage\UnsetStage;
+use Doctrine\ODM\MongoDB\Tests\Aggregation\AggregationTestTrait;
+use Doctrine\ODM\MongoDB\Tests\BaseTest;
+use Documents\User;
+
+class UnsetTest extends BaseTest
+{
+    use AggregationTestTrait;
+
+    public function testStage(): void
+    {
+        $documentPersister = $this->dm->getUnitOfWork()->getDocumentPersister(User::class);
+        $unsetStage        = new UnsetStage($this->getTestAggregationBuilder(), $documentPersister, 'id', 'foo', 'bar');
+
+        self::assertSame(['$unset' => ['_id', 'foo', 'bar']], $unsetStage->getExpression());
+    }
+
+    public function testFromBuilder(): void
+    {
+        $builder = $this->getTestAggregationBuilder();
+        $builder->unset('id', 'foo', 'bar');
+
+        self::assertSame([['$unset' => ['_id', 'foo', 'bar']]], $builder->getPipeline());
+    }
+}
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');