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->implementsInterface(GridFSRepository::class)</code> - <code>$reflectionClass->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<string|null></code> + <code><![CDATA[array<string|null>]]></code> </ImplementedReturnTypeMismatch> <InvalidArgument> <code>$mapping</code> @@ -43,29 +38,29 @@ <code>$mapping</code> </InvalidArgument> <InvalidArrayOffset> - <code>[$this->identifier => $this->getIdentifierValue($object)]</code> + <code><![CDATA[[$this->identifier => $this->getIdentifierValue($object)]]]></code> </InvalidArrayOffset> <InvalidPropertyAssignmentValue> - <code>$this->associationMappings</code> - <code>$this->associationMappings</code> - <code>$this->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->associationMappings, - static fn ($assoc) => ! 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<string, FieldMapping></code> + <code><![CDATA[array<string, FieldMapping>]]></code> </InvalidReturnType> <LessSpecificImplementedReturnType> <code>array</code> - <code>array<string|null></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->field</code> - <code>$xmlRoot->id</code> - <code>$xmlRoot->{'also-load-methods'}</code> - <code>$xmlRoot->{'default-discriminator-value'}</code> - <code>$xmlRoot->{'discriminator-field'}</code> - <code>$xmlRoot->{'discriminator-map'}</code> - <code>$xmlRoot->{'embed-many'}</code> - <code>$xmlRoot->{'embed-one'}</code> - <code>$xmlRoot->{'indexes'}</code> - <code>$xmlRoot->{'lifecycle-callbacks'}</code> - <code>$xmlRoot->{'read-preference'}</code> - <code>$xmlRoot->{'reference-many'}</code> - <code>$xmlRoot->{'reference-one'}</code> - <code>$xmlRoot->{'schema-validation'}</code> - <code>$xmlRoot->{'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->field)</code> - <code>isset($xmlRoot->id)</code> - <code>isset($xmlRoot->{'default-discriminator-value'})</code> - <code>isset($xmlRoot->{'discriminator-field'})</code> - <code>isset($xmlRoot->{'discriminator-map'})</code> - <code>isset($xmlRoot->{'embed-many'})</code> - <code>isset($xmlRoot->{'embed-one'})</code> - <code>isset($xmlRoot->{'indexes'})</code> - <code>isset($xmlRoot->{'lifecycle-callbacks'})</code> - <code>isset($xmlRoot->{'read-preference'})</code> - <code>isset($xmlRoot->{'reference-many'})</code> - <code>isset($xmlRoot->{'reference-one'})</code> - <code>isset($xmlRoot->{'schema-validation'})</code> - <code>isset($xmlRoot->{'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->getName() === 'document'</code> - <code>$xmlRoot->getName() === 'embedded-document'</code> - <code>$xmlRoot->getName() === 'gridfs-file'</code> - <code>$xmlRoot->getName() === 'mapped-superclass'</code> - <code>$xmlRoot->getName() === 'query-result-document'</code> - <code>$xmlRoot->getName() === 'view'</code> - <code>isset($xmlRoot->{'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->getUnitOfWork())</code> - <code>new PersistentCollection($coll, $dm, $dm->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->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->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->query</code> - <code>$this->query</code> - <code>$this->query</code> - <code>$this->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->parentAssociations</code> + <code><![CDATA[$this->parentAssociations]]></code> </InvalidPropertyAssignmentValue> <InvalidReturnStatement> <code>$divided</code> </InvalidReturnStatement> <InvalidReturnType> - <code>array<class-string, array{0: ClassMetadata<object>, 1: array<string, object>}></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->associationMappings['ref']</code> - <code>$class->associationMappings['ref1']</code> - <code>$class->associationMappings['ref2']</code> - <code>$class->associationMappings['ref3']</code> - <code>$class->associationMappings['ref4']</code> - <code>['storeAs' => 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->categories[0]->children, $user->categories[1]->children]</code> - <code>[$user->categories[0]->children[0]->children, $user->categories[0]->children[1]->children]</code> - <code>[$user->categories[0]->children[0]->children, $user->categories[0]->children[1]->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' => true]</code> + <code><![CDATA[['upsert' => true]]]></code> </InvalidArgument> </file> <file src="tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH1011Test.php"> <InvalidArgument> - <code>$doc->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->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' => 'assoc', - 'reference' => true, - 'type' => 'many', - 'targetDocument' => 'stdClass', - 'repositoryMethod' => 'fetch', - $prop => $value, - ]</code> - <code>[ - 'fieldName' => 'enum', - 'enumType' => Card::class, - ]</code> - <code>[ - 'fieldName' => 'enum', - 'enumType' => 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' => -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');