From 3091159e4758a37dbe92a034cf8a26df2fb84c2d Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Wed, 29 Mar 2023 14:15:35 +0200 Subject: [PATCH] Add $search stage to aggregation pipeline builder (#2516) * Add $search stage to aggregation pipeline builder * Make AbstractSearchOperator::getExpression final * Make appendScore method private for scored operators * Add documentation links to search operator classes * Separate wildcard and regex search operators --- .../ODM/MongoDB/Aggregation/Builder.php | 13 + .../ODM/MongoDB/Aggregation/Stage.php | 11 + .../ODM/MongoDB/Aggregation/Stage/Search.php | 147 ++ .../Stage/Search/AbstractSearchOperator.php | 48 + .../Aggregation/Stage/Search/Autocomplete.php | 95 ++ .../Aggregation/Stage/Search/Compound.php | 125 ++ .../Compound/CompoundedAutocomplete.php | 17 + .../Compound/CompoundedEmbeddedDocument.php | 32 + .../Search/Compound/CompoundedEquals.php | 17 + .../Search/Compound/CompoundedExists.php | 17 + .../Search/Compound/CompoundedGeoShape.php | 17 + .../Search/Compound/CompoundedGeoWithin.php | 17 + .../Compound/CompoundedMoreLikeThis.php | 17 + .../Stage/Search/Compound/CompoundedNear.php | 17 + .../Search/Compound/CompoundedPhrase.php | 17 + .../Search/Compound/CompoundedQueryString.php | 17 + .../Stage/Search/Compound/CompoundedRange.php | 17 + .../Stage/Search/Compound/CompoundedRegex.php | 17 + .../Stage/Search/Compound/CompoundedText.php | 17 + .../Search/Compound/CompoundedWildcard.php | 17 + .../CompoundSearchOperatorInterface.php | 16 + .../Search/CompoundedSearchOperatorTrait.php | 65 + .../Stage/Search/EmbeddedDocument.php | 63 + .../Aggregation/Stage/Search/Equals.php | 64 + .../Aggregation/Stage/Search/Exists.php | 30 + .../Aggregation/Stage/Search/GeoShape.php | 80 ++ .../Aggregation/Stage/Search/GeoWithin.php | 118 ++ .../Aggregation/Stage/Search/MoreLikeThis.php | 38 + .../MongoDB/Aggregation/Stage/Search/Near.php | 84 ++ .../Aggregation/Stage/Search/Phrase.php | 64 + .../Aggregation/Stage/Search/QueryString.php | 58 + .../Aggregation/Stage/Search/Range.php | 94 ++ .../Aggregation/Stage/Search/Regex.php | 62 + .../Stage/Search/ScoredSearchOperator.php | 12 + .../Search/ScoredSearchOperatorTrait.php | 51 + .../Stage/Search/SearchOperator.php | 22 + .../Search/SupportsAllSearchOperators.php | 9 + .../SupportsAllSearchOperatorsTrait.php | 110 ++ .../Search/SupportsAutocompleteOperator.php | 10 + .../Stage/Search/SupportsCompoundOperator.php | 10 + .../SupportsCompoundableOperatorsTrait.php | 148 ++ .../SupportsCompoundableSearchOperators.php | 9 + .../SupportsEmbeddableSearchOperators.php | 9 + .../SupportsEmbeddedDocumentOperator.php | 10 + .../Stage/Search/SupportsEqualsOperator.php | 14 + .../Stage/Search/SupportsExistsOperator.php | 10 + .../Stage/Search/SupportsGeoShapeOperator.php | 16 + .../Search/SupportsGeoWithinOperator.php | 10 + .../Search/SupportsMoreLikeThisOperator.php | 11 + .../Stage/Search/SupportsNearOperator.php | 17 + .../Stage/Search/SupportsPhraseOperator.php | 10 + .../Search/SupportsQueryStringOperator.php | 10 + .../Stage/Search/SupportsRangeOperator.php | 10 + .../Stage/Search/SupportsRegexOperator.php | 10 + .../Stage/Search/SupportsTextOperator.php | 10 + .../Stage/Search/SupportsWildcardOperator.php | 10 + .../MongoDB/Aggregation/Stage/Search/Text.php | 87 ++ .../Aggregation/Stage/Search/Wildcard.php | 62 + phpstan-baseline.neon | 340 +++++ .../Tests/Aggregation/Stage/SearchTest.php | 1274 +++++++++++++++++ 60 files changed, 3829 insertions(+) create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/AbstractSearchOperator.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Autocomplete.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedAutocomplete.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedEmbeddedDocument.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedEquals.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedExists.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedGeoShape.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedGeoWithin.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedMoreLikeThis.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedNear.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedPhrase.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedQueryString.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedRange.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedRegex.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedText.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedWildcard.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/CompoundSearchOperatorInterface.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/CompoundedSearchOperatorTrait.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/EmbeddedDocument.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Equals.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Exists.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/GeoShape.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/GeoWithin.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/MoreLikeThis.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Near.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Phrase.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/QueryString.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Range.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Regex.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/ScoredSearchOperator.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/ScoredSearchOperatorTrait.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/SearchOperator.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/SupportsAllSearchOperators.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/SupportsAllSearchOperatorsTrait.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/SupportsAutocompleteOperator.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/SupportsCompoundOperator.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/SupportsCompoundableOperatorsTrait.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/SupportsCompoundableSearchOperators.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/SupportsEmbeddableSearchOperators.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/SupportsEmbeddedDocumentOperator.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/SupportsEqualsOperator.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/SupportsExistsOperator.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/SupportsGeoShapeOperator.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/SupportsGeoWithinOperator.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/SupportsMoreLikeThisOperator.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/SupportsNearOperator.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/SupportsPhraseOperator.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/SupportsQueryStringOperator.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/SupportsRangeOperator.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/SupportsRegexOperator.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/SupportsTextOperator.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/SupportsWildcardOperator.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Text.php create mode 100644 lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Wildcard.php create mode 100644 tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SearchTest.php diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php index 714288178..8dc6fb9be 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php @@ -543,6 +543,19 @@ public function sample(int $size): Stage\Sample return $this->addStage($stage); } + /** + * The $search stage performs a full-text search on the specified field or + * fields which must be covered by an Atlas Search index. + * + * @see https://www.mongodb.com/docs/atlas/atlas-search/query-syntax/#mongodb-pipeline-pipe.-search + */ + public function search(): Stage\Search + { + $stage = new Stage\Search($this); + + 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. diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage.php index 1100bac86..6900ca1ff 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage.php @@ -365,6 +365,17 @@ public function sample(int $size): Stage\Sample return $this->builder->sample($size); } + /** + * The $search stage performs a full-text search on the specified field or + * fields which must be covered by an Atlas Search index. + * + * @see https://www.mongodb.com/docs/atlas/atlas-search/query-syntax/#mongodb-pipeline-pipe.-search + */ + public function search(): Stage\Search + { + return $this->builder->search(); + } + /** * Adds new fields to documents. $set outputs documents that contain all * existing fields from the input documents and newly added fields. diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search.php new file mode 100644 index 000000000..e64de1972 --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search.php @@ -0,0 +1,147 @@ +indexName) { + $params->index = $this->indexName; + } + + if ($this->count) { + $params->count = $this->count; + } + + if ($this->highlight) { + $params->highlight = $this->highlight; + } + + if ($this->returnStoredSource !== null) { + $params->returnStoredSource = $this->returnStoredSource; + } + + if ($this->operator !== null) { + $operatorName = $this->operator->getOperatorName(); + $params->$operatorName = $this->operator->getOperatorParams(); + } + + return ['$search' => $params]; + } + + public function index(string $name): static + { + $this->indexName = $name; + + return $this; + } + + /** @psalm-param CountType $type */ + public function countDocuments(string $type, ?int $threshold = null): static + { + $this->count = (object) ['type' => $type]; + + if ($threshold !== null) { + $this->count->threshold = $threshold; + } + + return $this; + } + + public function highlight(string $path, ?int $maxCharsToExamine = null, ?int $maxNumPassages = null): static + { + $this->highlight = (object) ['path' => $path]; + + if ($maxCharsToExamine !== null) { + $this->highlight->maxCharsToExamine = $maxCharsToExamine; + } + + if ($maxNumPassages !== null) { + $this->highlight->maxNumPassages = $maxNumPassages; + } + + return $this; + } + + public function returnStoredSource(bool $returnStoredSource = true): static + { + $this->returnStoredSource = $returnStoredSource; + + return $this; + } + + /** + * @param T $operator + * + * @return T + * + * @template T of SearchOperator + */ + protected function addOperator(SearchOperator $operator): SearchOperator + { + return $this->operator = $operator; + } + + protected function getSearchStage(): static + { + return $this; + } +} diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/AbstractSearchOperator.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/AbstractSearchOperator.php new file mode 100644 index 000000000..54e3945b4 --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/AbstractSearchOperator.php @@ -0,0 +1,48 @@ +builder); + } + + public function index(string $name): Search + { + return $this->search->index($name); + } + + public function countDocuments(string $type, ?int $threshold = null): Search + { + return $this->search->countDocuments($type, $threshold); + } + + public function highlight(string $path, ?int $maxCharsToExamine = null, ?int $maxNumPassages = null): Search + { + return $this->search->highlight($path, $maxCharsToExamine, $maxNumPassages); + } + + public function returnStoredSource(bool $returnStoredSource): Search + { + return $this->search->returnStoredSource($returnStoredSource); + } + + /** @return array */ + final public function getExpression(): array + { + return [$this->getOperatorName() => $this->getOperatorParams()]; + } + + protected function getSearchStage(): Search + { + return $this->search; + } +} diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Autocomplete.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Autocomplete.php new file mode 100644 index 000000000..bb0d8fcac --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Autocomplete.php @@ -0,0 +1,95 @@ + */ + private array $query; + private string $path; + private string $tokenOrder = ''; + private ?object $fuzzy = null; + + public function __construct(Search $search, string $path, string ...$query) + { + parent::__construct($search); + + $this->query(...$query); + $this->path($path); + } + + public function query(string ...$query): static + { + $this->query = array_values($query); + + return $this; + } + + public function path(string $path): static + { + $this->path = $path; + + return $this; + } + + public function tokenOrder(string $order): static + { + $this->tokenOrder = $order; + + return $this; + } + + public function fuzzy(?int $maxEdits = null, ?int $prefixLength = null, ?int $maxExpansions = null): static + { + $this->fuzzy = (object) []; + if ($maxEdits !== null) { + $this->fuzzy->maxEdits = $maxEdits; + } + + if ($prefixLength !== null) { + $this->fuzzy->prefixLength = $prefixLength; + } + + if ($maxExpansions !== null) { + $this->fuzzy->maxExpansions = $maxExpansions; + } + + return $this; + } + + public function getOperatorName(): string + { + return 'autocomplete'; + } + + public function getOperatorParams(): object + { + $params = (object) [ + 'query' => $this->query, + 'path' => $this->path, + ]; + + if ($this->tokenOrder) { + $params->tokenOrder = $this->tokenOrder; + } + + if ($this->fuzzy) { + $params->fuzzy = $this->fuzzy; + } + + return $this->appendScore($params); + } +} diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound.php new file mode 100644 index 000000000..8b8f60427 --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound.php @@ -0,0 +1,125 @@ +> */ + private array $operators = [ + 'must' => [], + 'mustNot' => [], + 'should' => [], + 'filter' => [], + ]; + + private string $currentClause = 'must'; + private ?int $minimumShouldMatch = null; + + /** + * @param T $operator + * + * @return T + * + * @template T of SearchOperator + */ + protected function addOperator(SearchOperator $operator): SearchOperator + { + $this->operators[$this->currentClause][] = $operator; + + return $operator; + } + + public function must(): static + { + $this->currentClause = 'must'; + + return $this; + } + + public function mustNot(): static + { + $this->currentClause = 'mustNot'; + + return $this; + } + + public function should(?int $minimumShouldMatch = null): static + { + $this->currentClause = 'should'; + + if ($minimumShouldMatch !== null) { + $this->minimumShouldMatch($minimumShouldMatch); + } + + return $this; + } + + public function filter(): static + { + $this->currentClause = 'filter'; + + return $this; + } + + public function minimumShouldMatch(int $minimumShouldMatch): static + { + $this->minimumShouldMatch = $minimumShouldMatch; + + return $this; + } + + public function getOperatorName(): string + { + return 'compound'; + } + + public function getOperatorParams(): object + { + $params = (object) []; + + foreach ($this->operators as $clause => $operators) { + if (! $operators) { + continue; + } + + $params->$clause = array_map( + static function (SearchOperator $operator): object { + return (object) [ + $operator->getOperatorName() => $operator->getOperatorParams(), + ]; + }, + $operators, + ); + } + + if ($this->minimumShouldMatch !== null) { + $params->minimumShouldMatch = $this->minimumShouldMatch; + } + + return $this->appendScore($params); + } + + protected function getAddOperatorClosure(): Closure + { + return Closure::fromCallable([$this, 'addOperator']); + } + + protected function getCompoundStage(): Compound + { + return $this; + } +} diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedAutocomplete.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedAutocomplete.php new file mode 100644 index 000000000..d15814fd4 --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedAutocomplete.php @@ -0,0 +1,17 @@ +compound->must(); + } + + public function mustNot(): Compound + { + return $this->compound->mustNot(); + } + + public function should(?int $minimumShouldMatch = null): Compound + { + return $this->compound->should($minimumShouldMatch); + } + + public function filter(): Compound + { + return $this->compound->filter(); + } + + /** + * @param T $operator + * + * @return T + * + * @template T of SearchOperator + */ + protected function addOperator(SearchOperator $operator): SearchOperator + { + return $this->getAddOperatorClosure()($operator); + } + + protected function getAddOperatorClosure(): Closure + { + return $this->addOperator; + } + + protected function getCompoundStage(): Compound + { + return $this->compound; + } +} diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/EmbeddedDocument.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/EmbeddedDocument.php new file mode 100644 index 000000000..fe8d5a11b --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/EmbeddedDocument.php @@ -0,0 +1,63 @@ +path($path); + } + + public function path(string $path): static + { + $this->path = $path; + + return $this; + } + + /** + * @param T $operator + * + * @return T + * + * @template T of SearchOperator + */ + protected function addOperator(SearchOperator $operator): SearchOperator + { + return $this->operator = $operator; + } + + public function getOperatorName(): string + { + return 'embeddedDocument'; + } + + public function getOperatorParams(): object + { + $params = (object) ['path' => $this->path]; + + if ($this->operator) { + $params->operator = (object) $this->operator->getExpression(); + } + + return $this->appendScore($params); + } +} diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Equals.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Equals.php new file mode 100644 index 000000000..960013dbe --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Equals.php @@ -0,0 +1,64 @@ +path($path) + ->value($value); + } + + public function path(string $path): static + { + $this->path = $path; + + return $this; + } + + /** @param string|int|float|ObjectId|UTCDateTime|null $value */ + public function value($value): static + { + $this->value = $value; + + return $this; + } + + public function getOperatorName(): string + { + return 'equals'; + } + + public function getOperatorParams(): object + { + $params = (object) [ + 'path' => $this->path, + 'value' => $this->value, + ]; + + return $this->appendScore($params); + } +} diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Exists.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Exists.php new file mode 100644 index 000000000..8ad546384 --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Exists.php @@ -0,0 +1,30 @@ + $this->path]; + } +} diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/GeoShape.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/GeoShape.php new file mode 100644 index 000000000..600ad2f93 --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/GeoShape.php @@ -0,0 +1,80 @@ + */ + private array $path = []; + private string $relation = ''; + + /** @var LineString|Point|Polygon|MultiPolygon|array|null */ + private $geometry = null; + + /** @param LineString|Point|Polygon|MultiPolygon|array|null $geometry */ + public function __construct(Search $search, $geometry = null, string $relation = '', string ...$path) + { + parent::__construct($search); + + $this + ->geometry($geometry) + ->relation($relation) + ->path(...$path); + } + + public function path(string ...$path): static + { + $this->path = $path; + + return $this; + } + + public function relation(string $relation): static + { + $this->relation = $relation; + + return $this; + } + + /** @param LineString|Point|Polygon|MultiPolygon|array|null $geometry */ + public function geometry($geometry): static + { + $this->geometry = $geometry; + + return $this; + } + + public function getOperatorName(): string + { + return 'geoShape'; + } + + public function getOperatorParams(): object + { + $params = (object) [ + 'path' => $this->path, + 'relation' => $this->relation, + 'geometry' => $this->geometry instanceof Geometry + ? $this->geometry->jsonSerialize() + : $this->geometry, + ]; + + return $this->appendScore($params); + } +} diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/GeoWithin.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/GeoWithin.php new file mode 100644 index 000000000..026b850cc --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/GeoWithin.php @@ -0,0 +1,118 @@ + */ + private array $path = []; + private string $relation = ''; + private ?object $box = null; + private ?object $circle = null; + + /** @var array|MultiPolygon|Polygon|null */ + private $geometry = null; + + public function __construct(Search $search, string ...$path) + { + parent::__construct($search); + + $this->path(...$path); + } + + public function path(string ...$path): static + { + $this->path = $path; + + return $this; + } + + /** + * @param array|Point $bottomLeft + * @param array|Point $topRight + */ + public function box($bottomLeft, $topRight): static + { + $this->box = (object) [ + 'bottomLeft' => $this->convertGeometry($bottomLeft), + 'topRight' => $this->convertGeometry($topRight), + ]; + + return $this; + } + + /** + * @param array|Point $center + * @param int|float $radius + */ + public function circle($center, $radius): static + { + $this->circle = (object) [ + 'center' => $this->convertGeometry($center), + 'radius' => $radius, + ]; + + return $this; + } + + /** @param Polygon|MultiPolygon|array $geometry */ + public function geometry($geometry): static + { + $this->geometry = $geometry; + + return $this; + } + + public function getOperatorName(): string + { + return 'geoWithin'; + } + + public function getOperatorParams(): object + { + $params = (object) ['path' => $this->path]; + + if ($this->box) { + $params->box = $this->box; + } + + if ($this->circle) { + $params->circle = $this->circle; + } + + if ($this->geometry) { + $params->geometry = $this->convertGeometry($this->geometry); + } + + return $this->appendScore($params); + } + + /** + * @param array|Geometry $geometry + * + * @return array + */ + private function convertGeometry($geometry): array + { + if (! $geometry instanceof Geometry) { + return $geometry; + } + + return $geometry->jsonSerialize(); + } +} diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/MoreLikeThis.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/MoreLikeThis.php new file mode 100644 index 000000000..198a7f341 --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/MoreLikeThis.php @@ -0,0 +1,38 @@ +|object> */ + private array $like = []; + + /** @param array|object $documents */ + public function __construct(Search $search, ...$documents) + { + parent::__construct($search); + + $this->like = array_values($documents); + } + + public function getOperatorName(): string + { + return 'moreLikeThis'; + } + + public function getOperatorParams(): object + { + return (object) ['like' => $this->like]; + } +} diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Near.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Near.php new file mode 100644 index 000000000..ad0e1d1d3 --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Near.php @@ -0,0 +1,84 @@ + */ + private array $path; + + /** + * @param int|float|UTCDateTime|array|Point|null $origin + * @param int|float|null $pivot + */ + public function __construct(Search $search, $origin = null, $pivot = null, string ...$path) + { + parent::__construct($search); + + $this + ->origin($origin) + ->pivot($pivot) + ->path(...$path); + } + + /** @param int|float|UTCDateTime|array|Point|null $origin */ + public function origin($origin): static + { + $this->origin = $origin; + + return $this; + } + + /** @param int|float|null $pivot */ + public function pivot($pivot): static + { + $this->pivot = $pivot; + + return $this; + } + + public function path(string ...$path): static + { + $this->path = $path; + + return $this; + } + + public function getOperatorName(): string + { + return 'near'; + } + + public function getOperatorParams(): object + { + $params = (object) [ + 'origin' => $this->origin instanceof Geometry + ? $this->origin->jsonSerialize() + : $this->origin, + 'pivot' => $this->pivot, + 'path' => $this->path, + ]; + + return $this->appendScore($params); + } +} diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Phrase.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Phrase.php new file mode 100644 index 000000000..e397003bc --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Phrase.php @@ -0,0 +1,64 @@ + */ + private array $query = []; + + /** @var list */ + private array $path = []; + private ?int $slop = null; + + public function query(string ...$query): static + { + $this->query = array_values($query); + + return $this; + } + + public function path(string ...$path): static + { + $this->path = $path; + + return $this; + } + + public function slop(int $slop): static + { + $this->slop = $slop; + + return $this; + } + + public function getOperatorName(): string + { + return 'phrase'; + } + + public function getOperatorParams(): object + { + $params = (object) [ + 'query' => $this->query, + 'path' => $this->path, + ]; + + if ($this->slop !== null) { + $params->slop = $this->slop; + } + + return $this->appendScore($params); + } +} diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/QueryString.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/QueryString.php new file mode 100644 index 000000000..6b00ce5ed --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/QueryString.php @@ -0,0 +1,58 @@ +query($query) + ->defaultPath($defaultPath); + } + + public function query(string $query): static + { + $this->query = $query; + + return $this; + } + + public function defaultPath(string $defaultPath): static + { + $this->defaultPath = $defaultPath; + + return $this; + } + + public function getOperatorName(): string + { + return 'queryString'; + } + + public function getOperatorParams(): object + { + $params = (object) [ + 'query' => $this->query, + 'defaultPath' => $this->defaultPath, + ]; + + return $this->appendScore($params); + } +} diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Range.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Range.php new file mode 100644 index 000000000..62bed26a8 --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Range.php @@ -0,0 +1,94 @@ + */ + private array $path; + + /** @param int|float|UTCDateTime|null $value */ + public function gt($value): static + { + $this->gt = $value; + $this->includeLowerBound = false; + + return $this; + } + + /** @param int|float|UTCDateTime|null $value */ + public function gte($value): static + { + $this->gt = $value; + $this->includeLowerBound = true; + + return $this; + } + + /** @param int|float|UTCDateTime|null $value */ + public function lt($value): static + { + $this->lt = $value; + $this->includeLowerBound = false; + + return $this; + } + + /** @param int|float|UTCDateTime|null $value */ + public function lte($value): static + { + $this->lt = $value; + $this->includeLowerBound = true; + + return $this; + } + + public function path(string ...$path): static + { + $this->path = $path; + + return $this; + } + + public function getOperatorName(): string + { + return 'range'; + } + + public function getOperatorParams(): object + { + $params = (object) ['path' => $this->path]; + + if ($this->gt !== null) { + $name = $this->includeLowerBound ? 'gte' : 'gt'; + $params->$name = $this->gt; + } + + if ($this->lt !== null) { + $name = $this->includeLowerBound ? 'lte' : 'lt'; + $params->$name = $this->lt; + } + + return $this->appendScore($params); + } +} diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Regex.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Regex.php new file mode 100644 index 000000000..90fd1f8d8 --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Regex.php @@ -0,0 +1,62 @@ + */ + private array $query = []; + + /** @var list */ + private array $path = []; + private ?bool $allowAnalyzedField = null; + + public function query(string ...$query): static + { + $this->query = $query; + + return $this; + } + + public function path(string ...$path): static + { + $this->path = $path; + + return $this; + } + + public function allowAnalyzedField(bool $allowAnalyzedField = true): static + { + $this->allowAnalyzedField = $allowAnalyzedField; + + return $this; + } + + public function getOperatorName(): string + { + return 'regex'; + } + + public function getOperatorParams(): object + { + $params = (object) [ + 'query' => $this->query, + 'path' => $this->path, + ]; + + if ($this->allowAnalyzedField !== null) { + $params->allowAnalyzedField = $this->allowAnalyzedField; + } + + return $this->appendScore($params); + } +} diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/ScoredSearchOperator.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/ScoredSearchOperator.php new file mode 100644 index 000000000..46db863e5 --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/ScoredSearchOperator.php @@ -0,0 +1,12 @@ +score) { + return $params; + } + + $params->score = $this->score; + + return $params; + } + + public function boostScore(?float $value = null, ?string $path = null, ?float $undefined = null): static + { + $boost = (object) []; + if ($value !== null) { + $boost->value = $value; + } + + if ($path !== null) { + $boost->path = $path; + } + + if ($undefined !== null) { + $boost->undefined = $undefined; + } + + $this->score = (object) ['boost' => $boost]; + + return $this; + } + + public function constantScore(float $value): static + { + $this->score = (object) [ + 'constant' => (object) ['value' => $value], + ]; + + return $this; + } +} diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/SearchOperator.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/SearchOperator.php new file mode 100644 index 000000000..872b7cebc --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/SearchOperator.php @@ -0,0 +1,22 @@ + + */ + public function getExpression(): array; + + /** @internal */ + public function getOperatorName(): string; + + /** @internal */ + public function getOperatorParams(): object; +} diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/SupportsAllSearchOperators.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/SupportsAllSearchOperators.php new file mode 100644 index 000000000..a664adb14 --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/SupportsAllSearchOperators.php @@ -0,0 +1,9 @@ +addOperator(new Autocomplete($this->getSearchStage(), $path, ...$query)); + } + + public function compound(): Compound + { + return $this->addOperator(new Compound($this->getSearchStage())); + } + + public function embeddedDocument(string $path = ''): EmbeddedDocument + { + return $this->addOperator(new EmbeddedDocument($this->getSearchStage(), $path)); + } + + /** @param string|int|float|ObjectId|UTCDateTime|null $value */ + public function equals(string $path = '', $value = null): Equals + { + return $this->addOperator(new Equals($this->getSearchStage(), $path, $value)); + } + + public function exists(string $path): Exists + { + return $this->addOperator(new Exists($this->getSearchStage(), $path)); + } + + /** @param LineString|Point|Polygon|MultiPolygon|array|null $geometry */ + public function geoShape($geometry = null, string $relation = '', string ...$path): GeoShape + { + return $this->addOperator(new GeoShape($this->getSearchStage(), $geometry, $relation, ...$path)); + } + + public function geoWithin(string ...$path): GeoWithin + { + return $this->addOperator(new GeoWithin($this->getSearchStage(), ...$path)); + } + + /** @param array|object $documents */ + public function moreLikeThis(...$documents): MoreLikeThis + { + return $this->addOperator(new MoreLikeThis($this->getSearchStage(), ...$documents)); + } + + /** + * @param int|float|UTCDateTime|array|Point|null $origin + * @param int|float|null $pivot + */ + public function near($origin = null, $pivot = null, string ...$path): Near + { + return $this->addOperator(new Near($this->getSearchStage(), $origin, $pivot, ...$path)); + } + + public function phrase(): Phrase + { + return $this->addOperator(new Phrase($this->getSearchStage())); + } + + public function queryString(string $query = '', string $defaultPath = ''): QueryString + { + return $this->addOperator(new QueryString($this->getSearchStage(), $query, $defaultPath)); + } + + public function range(): Range + { + return $this->addOperator(new Range($this->getSearchStage())); + } + + public function regex(): Regex + { + return $this->addOperator(new Regex($this->getSearchStage())); + } + + public function text(): Text + { + return $this->addOperator(new Text($this->getSearchStage())); + } + + public function wildcard(): Wildcard + { + return $this->addOperator(new Wildcard($this->getSearchStage())); + } +} diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/SupportsAutocompleteOperator.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/SupportsAutocompleteOperator.php new file mode 100644 index 000000000..360831475 --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/SupportsAutocompleteOperator.php @@ -0,0 +1,10 @@ +addOperator(new CompoundedAutocomplete($this->getCompoundStage(), $this->getAddOperatorClosure(), $this->getSearchStage(), $path, ...$query)); + } + + /** @return EmbeddedDocument&CompoundSearchOperatorInterface */ + public function embeddedDocument(string $path = ''): EmbeddedDocument + { + return $this->addOperator(new CompoundedEmbeddedDocument($this->getCompoundStage(), $this->getAddOperatorClosure(), $this->getSearchStage(), $path)); + } + + /** + * @param string|int|float|ObjectId|UTCDateTime|null $value + * + * @return Equals&CompoundSearchOperatorInterface + */ + public function equals(string $path = '', $value = null): Equals + { + return $this->addOperator(new CompoundedEquals($this->getCompoundStage(), $this->getAddOperatorClosure(), $this->getSearchStage(), $path, $value)); + } + + /** @return Exists&CompoundSearchOperatorInterface */ + public function exists(string $path): Exists + { + return $this->addOperator(new CompoundedExists($this->getCompoundStage(), $this->getAddOperatorClosure(), $this->getSearchStage(), $path)); + } + + /** + * @param LineString|Point|Polygon|MultiPolygon|array|null $geometry + * + * @return GeoShape&CompoundSearchOperatorInterface + */ + public function geoShape($geometry = null, string $relation = '', string ...$path): GeoShape + { + return $this->addOperator(new CompoundedGeoShape($this->getCompoundStage(), $this->getAddOperatorClosure(), $this->getSearchStage(), $geometry, $relation, ...$path)); + } + + /** @return GeoWithin&CompoundSearchOperatorInterface */ + public function geoWithin(string ...$path): GeoWithin + { + return $this->addOperator(new CompoundedGeoWithin($this->getCompoundStage(), $this->getAddOperatorClosure(), $this->getSearchStage(), ...$path)); + } + + /** + * @param array|object $documents + * + * @return MoreLikeThis&CompoundSearchOperatorInterface + */ + public function moreLikeThis(...$documents): MoreLikeThis + { + return $this->addOperator(new CompoundedMoreLikeThis($this->getCompoundStage(), $this->getAddOperatorClosure(), $this->getSearchStage(), ...$documents)); + } + + /** + * @param int|float|UTCDateTime|array|Point|null $origin + * @param int|float|null $pivot + * + * @return Near&CompoundSearchOperatorInterface + */ + public function near($origin = null, $pivot = null, string ...$path): Near + { + return $this->addOperator(new CompoundedNear($this->getCompoundStage(), $this->getAddOperatorClosure(), $this->getSearchStage(), $origin, $pivot, ...$path)); + } + + /** @return Phrase&CompoundSearchOperatorInterface */ + public function phrase(): Phrase + { + return $this->addOperator(new CompoundedPhrase($this->getCompoundStage(), $this->getAddOperatorClosure(), $this->getSearchStage())); + } + + /** @return QueryString&CompoundSearchOperatorInterface */ + public function queryString(string $query = '', string $defaultPath = ''): QueryString + { + return $this->addOperator(new CompoundedQueryString($this->getCompoundStage(), $this->getAddOperatorClosure(), $this->getSearchStage(), $query, $defaultPath)); + } + + /** @return Range&CompoundSearchOperatorInterface */ + public function range(): Range + { + return $this->addOperator(new CompoundedRange($this->getCompoundStage(), $this->getAddOperatorClosure(), $this->getSearchStage())); + } + + /** @return Regex&CompoundSearchOperatorInterface */ + public function regex(): Regex + { + return $this->addOperator(new CompoundedRegex($this->getCompoundStage(), $this->getAddOperatorClosure(), $this->getSearchStage())); + } + + /** @return Text&CompoundSearchOperatorInterface */ + public function text(): Text + { + return $this->addOperator(new CompoundedText($this->getCompoundStage(), $this->getAddOperatorClosure(), $this->getSearchStage())); + } + + /** @return Wildcard&CompoundSearchOperatorInterface */ + public function wildcard(): Wildcard + { + return $this->addOperator(new CompoundedWildcard($this->getCompoundStage(), $this->getAddOperatorClosure(), $this->getSearchStage())); + } +} diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/SupportsCompoundableSearchOperators.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/SupportsCompoundableSearchOperators.php new file mode 100644 index 000000000..f1c2f097a --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/SupportsCompoundableSearchOperators.php @@ -0,0 +1,9 @@ +|object $documents */ + public function moreLikeThis(...$documents): MoreLikeThis; +} diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/SupportsNearOperator.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/SupportsNearOperator.php new file mode 100644 index 000000000..31fc8b477 --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/SupportsNearOperator.php @@ -0,0 +1,17 @@ + */ + private array $query = []; + + /** @var list */ + private array $path = []; + private ?object $fuzzy = null; + private string $synonyms = ''; + + public function query(string ...$query): static + { + $this->query = array_values($query); + + return $this; + } + + public function path(string ...$path): static + { + $this->path = $path; + + return $this; + } + + public function fuzzy(?int $maxEdits = null, ?int $prefixLength = null, ?int $maxExpansions = null): static + { + $this->fuzzy = (object) []; + if ($maxEdits !== null) { + $this->fuzzy->maxEdits = $maxEdits; + } + + if ($prefixLength !== null) { + $this->fuzzy->prefixLength = $prefixLength; + } + + if ($maxExpansions !== null) { + $this->fuzzy->maxExpansions = $maxExpansions; + } + + return $this; + } + + public function synonyms(string $synonyms): static + { + $this->synonyms = $synonyms; + + return $this; + } + + public function getOperatorName(): string + { + return 'text'; + } + + public function getOperatorParams(): object + { + $params = (object) [ + 'query' => $this->query, + 'path' => $this->path, + ]; + + if ($this->fuzzy) { + $params->fuzzy = $this->fuzzy; + } + + if ($this->synonyms) { + $params->synonyms = $this->synonyms; + } + + return $this->appendScore($params); + } +} diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Wildcard.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Wildcard.php new file mode 100644 index 000000000..43257ff35 --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Wildcard.php @@ -0,0 +1,62 @@ + */ + private array $query = []; + + /** @var list */ + private array $path = []; + private ?bool $allowAnalyzedField = null; + + public function query(string ...$query): static + { + $this->query = $query; + + return $this; + } + + public function path(string ...$path): static + { + $this->path = $path; + + return $this; + } + + public function allowAnalyzedField(bool $allowAnalyzedField = true): static + { + $this->allowAnalyzedField = $allowAnalyzedField; + + return $this; + } + + public function getOperatorName(): string + { + return 'wildcard'; + } + + public function getOperatorParams(): object + { + $params = (object) [ + 'query' => $this->query, + 'path' => $this->path, + ]; + + if ($this->allowAnalyzedField !== null) { + $params->allowAnalyzedField = $this->allowAnalyzedField; + } + + return $this->appendScore($params); + } +} diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 756aed42f..1fa2160b1 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -120,6 +120,331 @@ parameters: count: 1 path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Out.php + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\:\\:geoShape\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\:\\:near\\(\\) has parameter \\$origin with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\:\\:geoShape\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\:\\:near\\(\\) has parameter \\$origin with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedAutocomplete\\:\\:__construct\\(\\) has parameter \\$args with no type specified\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedAutocomplete.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedAutocomplete\\:\\:geoShape\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedAutocomplete.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedAutocomplete\\:\\:near\\(\\) has parameter \\$origin with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedAutocomplete.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedEmbeddedDocument\\:\\:__construct\\(\\) has parameter \\$args with no type specified\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedEmbeddedDocument.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedEmbeddedDocument\\:\\:geoShape\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedEmbeddedDocument.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedEmbeddedDocument\\:\\:near\\(\\) has parameter \\$origin with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedEmbeddedDocument.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedEquals\\:\\:__construct\\(\\) has parameter \\$args with no type specified\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedEquals.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedEquals\\:\\:geoShape\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedEquals.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedEquals\\:\\:near\\(\\) has parameter \\$origin with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedEquals.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedExists\\:\\:__construct\\(\\) has parameter \\$args with no type specified\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedExists.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedExists\\:\\:geoShape\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedExists.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedExists\\:\\:near\\(\\) has parameter \\$origin with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedExists.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedGeoShape\\:\\:__construct\\(\\) has parameter \\$args with no type specified\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedGeoShape.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedGeoShape\\:\\:geoShape\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedGeoShape.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedGeoShape\\:\\:near\\(\\) has parameter \\$origin with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedGeoShape.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedGeoWithin\\:\\:__construct\\(\\) has parameter \\$args with no type specified\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedGeoWithin.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedGeoWithin\\:\\:geoShape\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedGeoWithin.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedGeoWithin\\:\\:near\\(\\) has parameter \\$origin with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedGeoWithin.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedMoreLikeThis\\:\\:__construct\\(\\) has parameter \\$args with no type specified\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedMoreLikeThis.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedMoreLikeThis\\:\\:geoShape\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedMoreLikeThis.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedMoreLikeThis\\:\\:near\\(\\) has parameter \\$origin with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedMoreLikeThis.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedNear\\:\\:__construct\\(\\) has parameter \\$args with no type specified\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedNear.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedNear\\:\\:geoShape\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedNear.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedNear\\:\\:near\\(\\) has parameter \\$origin with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedNear.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedPhrase\\:\\:__construct\\(\\) has parameter \\$args with no type specified\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedPhrase.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedPhrase\\:\\:geoShape\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedPhrase.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedPhrase\\:\\:near\\(\\) has parameter \\$origin with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedPhrase.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedQueryString\\:\\:__construct\\(\\) has parameter \\$args with no type specified\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedQueryString.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedQueryString\\:\\:geoShape\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedQueryString.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedQueryString\\:\\:near\\(\\) has parameter \\$origin with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedQueryString.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedRange\\:\\:__construct\\(\\) has parameter \\$args with no type specified\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedRange.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedRange\\:\\:geoShape\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedRange.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedRange\\:\\:near\\(\\) has parameter \\$origin with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedRange.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedRegex\\:\\:__construct\\(\\) has parameter \\$args with no type specified\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedRegex.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedRegex\\:\\:geoShape\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedRegex.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedRegex\\:\\:near\\(\\) has parameter \\$origin with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedRegex.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedText\\:\\:__construct\\(\\) has parameter \\$args with no type specified\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedText.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedText\\:\\:geoShape\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedText.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedText\\:\\:near\\(\\) has parameter \\$origin with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedText.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedWildcard\\:\\:__construct\\(\\) has parameter \\$args with no type specified\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedWildcard.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedWildcard\\:\\:geoShape\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedWildcard.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedWildcard\\:\\:near\\(\\) has parameter \\$origin with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedWildcard.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\EmbeddedDocument\\:\\:geoShape\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/EmbeddedDocument.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\EmbeddedDocument\\:\\:near\\(\\) has parameter \\$origin with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/EmbeddedDocument.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\GeoShape\\:\\:__construct\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/GeoShape.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\GeoShape\\:\\:geometry\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/GeoShape.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\GeoShape\\:\\:\\$geometry type has no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/GeoShape.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\GeoWithin\\:\\:box\\(\\) has parameter \\$bottomLeft with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/GeoWithin.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\GeoWithin\\:\\:box\\(\\) has parameter \\$topRight with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/GeoWithin.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\GeoWithin\\:\\:circle\\(\\) has parameter \\$center with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/GeoWithin.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\GeoWithin\\:\\:convertGeometry\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/GeoWithin.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\GeoWithin\\:\\:convertGeometry\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/GeoWithin.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\GeoWithin\\:\\:geometry\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/GeoWithin.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\GeoWithin\\:\\:\\$geometry type has no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/GeoWithin.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\GeoWithin\\:\\:\\$relation is never read, only written\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/GeoWithin.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Near\\:\\:__construct\\(\\) has parameter \\$origin with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Near.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Near\\:\\:origin\\(\\) has parameter \\$origin with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Near.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Near\\:\\:\\$origin type has no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Near.php + + - + message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Range\\:\\:\\$includeUpperBound is never read, only written\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Range.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\SupportsGeoShapeOperator\\:\\:geoShape\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/SupportsGeoShapeOperator.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\SupportsNearOperator\\:\\:near\\(\\) has parameter \\$origin with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/SupportsNearOperator.php + - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\UnionWith\\:\\:getExpression\\(\\) has invalid return type Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\UnionWithStageExpression\\.$#" count: 1 @@ -340,6 +665,21 @@ parameters: count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/ProjectTest.php + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Aggregation\\\\Stage\\\\SearchTest\\:\\:testSearchCompoundOperators\\(\\) has parameter \\$expectedOperator with no value type specified in iterable type array\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SearchTest.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Aggregation\\\\Stage\\\\SearchTest\\:\\:testSearchEmbeddedDocumentOperators\\(\\) has parameter \\$expectedOperator with no value type specified in iterable type array\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SearchTest.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Aggregation\\\\Stage\\\\SearchTest\\:\\:testSearchOperators\\(\\) has parameter \\$expectedOperator with no value type specified in iterable type array\\.$#" + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SearchTest.php + - message: "#^Constant DOCTRINE_MONGODB_DATABASE not found\\.$#" count: 5 diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SearchTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SearchTest.php new file mode 100644 index 000000000..6213b88df --- /dev/null +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SearchTest.php @@ -0,0 +1,1274 @@ + [ + 'expectedOperator' => [ + 'autocomplete' => (object) [ + 'query' => ['MongoDB', 'Aggregation', 'Pipeline'], + 'path' => 'content', + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->autocomplete('content', 'MongoDB', 'Aggregation', 'Pipeline') + ->path('content'); + }, + ]; + + yield 'Autocomplete with token order' => [ + 'expectedOperator' => [ + 'autocomplete' => (object) [ + 'query' => ['MongoDB', 'Aggregation', 'Pipeline'], + 'path' => 'content', + 'tokenOrder' => 'any', + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->autocomplete() + ->query('MongoDB', 'Aggregation', 'Pipeline') + ->path('content') + ->tokenOrder('any'); + }, + ]; + + yield 'Autocomplete with boost score' => [ + 'expectedOperator' => [ + 'autocomplete' => (object) [ + 'query' => ['MongoDB', 'Aggregation', 'Pipeline'], + 'path' => 'content', + 'score' => (object) [ + 'boost' => (object) ['value' => 1.5], + ], + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->autocomplete() + ->query('MongoDB', 'Aggregation', 'Pipeline') + ->path('content') + ->boostScore(1.5); + }, + ]; + + yield 'Autocomplete with constant score' => [ + 'expectedOperator' => [ + 'autocomplete' => (object) [ + 'query' => ['MongoDB', 'Aggregation', 'Pipeline'], + 'path' => 'content', + 'score' => (object) [ + 'constant' => (object) ['value' => 1.5], + ], + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->autocomplete() + ->query('MongoDB', 'Aggregation', 'Pipeline') + ->path('content') + ->constantScore(1.5); + }, + ]; + + yield 'Autocomplete with fuzzy search' => [ + 'expectedOperator' => [ + 'autocomplete' => (object) [ + 'query' => ['MongoDB', 'Aggregation', 'Pipeline'], + 'path' => 'content', + 'fuzzy' => (object) [ + 'maxEdits' => 1, + 'prefixLength' => 2, + 'maxExpansions' => 3, + ], + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->autocomplete() + ->query('MongoDB', 'Aggregation', 'Pipeline') + ->path('content') + ->fuzzy(1, 2, 3); + }, + ]; + } + + public static function provideCompoundBuilders(): Generator + { + yield 'Compound with single must clause' => [ + 'expectedOperator' => [ + 'compound' => (object) [ + 'must' => [ + (object) [ + 'text' => (object) [ + 'query' => ['MongoDB', 'Aggregation', 'Pipeline'], + 'path' => ['items.content'], + 'synonyms' => 'mySynonyms', + ], + ], + ], + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->compound() + ->must() + ->text() + ->path('items.content') + ->query('MongoDB', 'Aggregation', 'Pipeline') + ->synonyms('mySynonyms'); + }, + ]; + + yield 'Compound with multiple must clauses' => [ + 'expectedOperator' => [ + 'compound' => (object) [ + 'must' => [ + (object) [ + 'text' => (object) [ + 'query' => ['MongoDB', 'Aggregation', 'Pipeline'], + 'path' => ['items.content'], + 'synonyms' => 'mySynonyms', + ], + ], + (object) [ + 'near' => (object) [ + 'origin' => 5, + 'pivot' => 3, + 'path' => ['value1', 'value2'], + ], + ], + ], + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->compound() + ->must() + ->text() + ->path('items.content') + ->query('MongoDB', 'Aggregation', 'Pipeline') + ->synonyms('mySynonyms') + ->near(5, 3, 'value1', 'value2'); + }, + ]; + + yield 'Compound with must and mustNot clauses' => [ + 'expectedOperator' => [ + 'compound' => (object) [ + 'must' => [ + (object) [ + 'text' => (object) [ + 'query' => ['MongoDB', 'Aggregation', 'Pipeline'], + 'path' => ['items.content'], + 'synonyms' => 'mySynonyms', + ], + ], + ], + 'mustNot' => [ + (object) [ + 'exists' => (object) ['path' => 'hidden'], + ], + ], + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->compound() + ->must() + ->text() + ->path('items.content') + ->query('MongoDB', 'Aggregation', 'Pipeline') + ->synonyms('mySynonyms') + ->mustNot() + ->exists('hidden'); + }, + ]; + } + + public static function provideEmbeddedDocumentBuilders(): Generator + { + yield 'EmbeddedDocument with single text operator' => [ + 'expectedOperator' => [ + 'embeddedDocument' => (object) [ + 'path' => 'items', + 'operator' => (object) [ + 'text' => (object) [ + 'query' => ['MongoDB', 'Aggregation', 'Pipeline'], + 'path' => ['items.content'], + 'synonyms' => 'mySynonyms', + ], + ], + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->embeddedDocument('items') + ->text() + ->path('items.content') + ->query('MongoDB', 'Aggregation', 'Pipeline') + ->synonyms('mySynonyms'); + }, + ]; + } + + public static function provideEmbeddedDocumentCompoundBuilders(): Generator + { + yield 'EmbeddedDocument with compound operator' => [ + 'expectedOperator' => [ + 'embeddedDocument' => (object) [ + 'path' => 'items', + 'operator' => (object) [ + 'compound' => (object) [ + 'must' => [ + (object) [ + 'text' => (object) [ + 'query' => ['MongoDB', 'Aggregation', 'Pipeline'], + 'path' => ['items.content'], + 'synonyms' => 'mySynonyms', + ], + ], + (object) [ + 'near' => (object) [ + 'origin' => 5, + 'pivot' => 3, + 'path' => ['items.value1'], + ], + ], + ], + 'mustNot' => [ + (object) [ + 'exists' => (object) ['path' => 'items.hidden'], + ], + ], + ], + ], + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->embeddedDocument('items') + ->compound() + ->must() + ->text() + ->path('items.content') + ->query('MongoDB', 'Aggregation', 'Pipeline') + ->synonyms('mySynonyms') + ->near(5, 3, 'items.value1') + ->mustNot() + ->exists('items.hidden'); + }, + ]; + } + + public static function provideEqualsBuilders(): Generator + { + yield 'Equals required only' => [ + 'expectedOperator' => [ + 'equals' => (object) [ + 'path' => 'content', + 'value' => 'MongoDB Aggregation Pipeline', + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->equals('content', 'MongoDB Aggregation Pipeline'); + }, + ]; + + yield 'Equals with boost score' => [ + 'expectedOperator' => [ + 'equals' => (object) [ + 'path' => 'content', + 'value' => 'MongoDB Aggregation Pipeline', + 'score' => (object) [ + 'boost' => (object) ['value' => 1.5], + ], + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->equals() + ->path('content') + ->value('MongoDB Aggregation Pipeline') + ->boostScore(1.5); + }, + ]; + + yield 'Equals with constant score' => [ + 'expectedOperator' => [ + 'equals' => (object) [ + 'path' => 'content', + 'value' => 'MongoDB Aggregation Pipeline', + 'score' => (object) [ + 'constant' => (object) ['value' => 1.5], + ], + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->equals() + ->path('content') + ->value('MongoDB Aggregation Pipeline') + ->constantScore(1.5); + }, + ]; + } + + public static function provideExistsBuilders(): Generator + { + yield 'Exists required only' => [ + 'expectedOperator' => [ + 'exists' => (object) ['path' => 'content'], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->exists('content'); + }, + ]; + } + + public static function provideGeoShapeBuilders(): Generator + { + yield 'CompoundedGeoShape required only' => [ + 'expectedOperator' => [ + 'geoShape' => (object) [ + 'path' => ['location1', 'location2'], + 'relation' => 'contains', + 'geometry' => ['coordinates' => [12.345, 23.456], 'type' => 'Point'], + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->geoShape( + new Point([12.345, 23.456]), + 'contains', + 'location1', + 'location2', + ); + }, + ]; + + yield 'CompoundedGeoShape with boost score' => [ + 'expectedOperator' => [ + 'geoShape' => (object) [ + 'path' => ['location'], + 'relation' => 'contains', + 'geometry' => ['coordinates' => [12.345, 23.456], 'type' => 'Point'], + 'score' => (object) [ + 'boost' => (object) ['value' => 1.5], + ], + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->geoShape() + ->path('location') + ->relation('contains') + ->geometry(new Point([12.345, 23.456])) + ->boostScore(1.5); + }, + ]; + + yield 'CompoundedGeoShape with constant score' => [ + 'expectedOperator' => [ + 'geoShape' => (object) [ + 'path' => ['location'], + 'relation' => 'contains', + 'geometry' => ['coordinates' => [12.345, 23.456], 'type' => 'Point'], + 'score' => (object) [ + 'constant' => (object) ['value' => 1.5], + ], + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->geoShape() + ->path('location') + ->relation('contains') + ->geometry(new Point([12.345, 23.456])) + ->constantScore(1.5); + }, + ]; + } + + public static function provideGeoWithinBuilders(): Generator + { + yield 'GeoWithin box' => [ + 'expectedOperator' => [ + 'geoWithin' => (object) [ + 'path' => ['location1', 'location2'], + 'box' => (object) [ + 'bottomLeft' => ['coordinates' => [-12.345, -23.456], 'type' => 'Point'], + 'topRight' => ['coordinates' => [12.345, 23.456], 'type' => 'Point'], + ], + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->geoWithin('location1', 'location2') + ->box(new Point([-12.345, -23.456]), new Point([12.345, 23.456])); + }, + ]; + + yield 'GeoWithin circle' => [ + 'expectedOperator' => [ + 'geoWithin' => (object) [ + 'path' => ['location'], + 'circle' => (object) [ + 'center' => ['coordinates' => [12.345, 23.456], 'type' => 'Point'], + 'radius' => 3.14, + ], + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->geoWithin() + ->path('location') + ->circle(new Point([12.345, 23.456]), 3.14); + }, + ]; + + yield 'GeoWithin geometry' => [ + 'expectedOperator' => [ + 'geoWithin' => (object) [ + 'path' => ['location'], + 'geometry' => [ + 'coordinates' => [ + [[0, 0], [0, 4], [4, 4], [4, 0], [0, 0]], + [[1, 1], [1, 3], [3, 3], [3, 1], [1, 1]], + ], + 'type' => 'Polygon', + ], + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->geoWithin() + ->path('location') + ->geometry(new Polygon([ + [[0, 0], [0, 4], [4, 4], [4, 0], [0, 0]], + [[1, 1], [1, 3], [3, 3], [3, 1], [1, 1]], + ])); + }, + ]; + + yield 'GeoWithin with boost score' => [ + 'expectedOperator' => [ + 'geoWithin' => (object) [ + 'path' => ['location'], + 'circle' => (object) [ + 'center' => ['coordinates' => [12.345, 23.456], 'type' => 'Point'], + 'radius' => 3.14, + ], + 'score' => (object) [ + 'boost' => (object) ['value' => 1.5], + ], + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->geoWithin() + ->path('location') + ->circle(new Point([12.345, 23.456]), 3.14) + ->boostScore(1.5); + }, + ]; + + yield 'GeoWithin with constant score' => [ + 'expectedOperator' => [ + 'geoWithin' => (object) [ + 'path' => ['location'], + 'circle' => (object) [ + 'center' => ['coordinates' => [12.345, 23.456], 'type' => 'Point'], + 'radius' => 3.14, + ], + 'score' => (object) [ + 'constant' => (object) ['value' => 1.5], + ], + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->geoWithin() + ->path('location') + ->circle(new Point([12.345, 23.456]), 3.14) + ->constantScore(1.5); + }, + ]; + } + + public static function provideMoreLikeThisBuilders(): Generator + { + yield 'MoreLikeThis with single like' => [ + 'expectedOperator' => [ + 'moreLikeThis' => (object) ['like' => [['title' => 'The Godfather']]], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->moreLikeThis(['title' => 'The Godfather']); + }, + ]; + + yield 'MoreLikeThis with multiple documents' => [ + 'expectedOperator' => [ + 'moreLikeThis' => (object) [ + 'like' => [ + ['title' => 'The Godfather'], + ['title' => 'The Green Mile'], + ], + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->moreLikeThis(['title' => 'The Godfather'], ['title' => 'The Green Mile']); + }, + ]; + } + + public static function provideNearBuilders(): Generator + { + yield 'Near with number' => [ + 'expectedOperator' => [ + 'near' => (object) [ + 'origin' => 5, + 'pivot' => 3, + 'path' => ['value1', 'value2'], + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->near(5, 3, 'value1', 'value2'); + }, + ]; + + $date = new UTCDateTime(); + + yield 'Near with date' => [ + 'expectedOperator' => [ + 'near' => (object) [ + 'origin' => $date, + 'pivot' => 2, + 'path' => ['createdAt'], + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) use ($date) { + return $stage->near() + ->path('createdAt') + ->origin($date) + ->pivot(2); + }, + ]; + + yield 'Near with point' => [ + 'expectedOperator' => [ + 'near' => (object) [ + 'origin' => ['coordinates' => [12.345, 23.456], 'type' => 'Point'], + 'pivot' => 2, + 'path' => ['createdAt'], + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->near() + ->path('createdAt') + ->origin(new Point([12.345, 23.456])) + ->pivot(2); + }, + ]; + } + + public static function providePhraseBuilders(): Generator + { + yield 'Phrase required only' => [ + 'expectedOperator' => [ + 'phrase' => (object) [ + 'query' => ['MongoDB', 'Aggregation', 'Pipeline'], + 'path' => ['title', 'content'], + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->phrase() + ->query('MongoDB', 'Aggregation', 'Pipeline') + ->path('title', 'content'); + }, + ]; + + yield 'Phrase with slop' => [ + 'expectedOperator' => [ + 'phrase' => (object) [ + 'query' => ['MongoDB', 'Aggregation', 'Pipeline'], + 'path' => ['content'], + 'slop' => 3, + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->phrase() + ->query('MongoDB', 'Aggregation', 'Pipeline') + ->path('content') + ->slop(3); + }, + ]; + + yield 'Phrase with boost score' => [ + 'expectedOperator' => [ + 'phrase' => (object) [ + 'query' => ['MongoDB', 'Aggregation', 'Pipeline'], + 'path' => ['content'], + 'score' => (object) [ + 'boost' => (object) ['value' => 1.5], + ], + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->phrase() + ->query('MongoDB', 'Aggregation', 'Pipeline') + ->path('content') + ->boostScore(1.5); + }, + ]; + + yield 'Phrase with constant score' => [ + 'expectedOperator' => [ + 'phrase' => (object) [ + 'query' => ['MongoDB', 'Aggregation', 'Pipeline'], + 'path' => ['content'], + 'score' => (object) [ + 'constant' => (object) ['value' => 1.5], + ], + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->phrase() + ->query('MongoDB', 'Aggregation', 'Pipeline') + ->path('content') + ->constantScore(1.5); + }, + ]; + } + + public static function provideQueryStringBuilders(): Generator + { + yield 'QueryString required only' => [ + 'expectedOperator' => [ + 'queryString' => (object) [ + 'query' => 'MongoDB Aggregation Pipeline', + 'defaultPath' => 'content', + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->queryString('MongoDB Aggregation Pipeline', 'content'); + }, + ]; + + yield 'QueryString with boost score' => [ + 'expectedOperator' => [ + 'queryString' => (object) [ + 'query' => 'content:pipeline OR title:pipeline', + 'defaultPath' => 'content', + 'score' => (object) [ + 'boost' => (object) ['value' => 1.5], + ], + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->queryString() + ->query('content:pipeline OR title:pipeline') + ->defaultPath('content') + ->boostScore(1.5); + }, + ]; + + yield 'QueryString with constant score' => [ + 'expectedOperator' => [ + 'queryString' => (object) [ + 'query' => 'content:pipeline OR title:pipeline', + 'defaultPath' => 'content', + 'score' => (object) [ + 'constant' => (object) ['value' => 1.5], + ], + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->queryString() + ->query('content:pipeline OR title:pipeline') + ->defaultPath('content') + ->constantScore(1.5); + }, + ]; + } + + public static function provideRangeBuilders(): Generator + { + yield 'Range gt only' => [ + 'expectedOperator' => [ + 'range' => (object) [ + 'path' => ['field1', 'field2'], + 'gt' => 5, + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->range() + ->path('field1', 'field2') + ->gt(5); + }, + ]; + + yield 'Range gte only' => [ + 'expectedOperator' => [ + 'range' => (object) [ + 'path' => ['field1', 'field2'], + 'gte' => 5, + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->range() + ->path('field1', 'field2') + ->gte(5); + }, + ]; + + yield 'Range lt only' => [ + 'expectedOperator' => [ + 'range' => (object) [ + 'path' => ['field1', 'field2'], + 'lt' => 5, + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->range() + ->path('field1', 'field2') + ->lt(5); + }, + ]; + + yield 'Range lte only' => [ + 'expectedOperator' => [ + 'range' => (object) [ + 'path' => ['field1', 'field2'], + 'lte' => 5, + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->range() + ->path('field1', 'field2') + ->lte(5); + }, + ]; + + yield 'Range both bounds' => [ + 'expectedOperator' => [ + 'range' => (object) [ + 'path' => ['field1', 'field2'], + 'lte' => 10, + 'gte' => 5, + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->range() + ->path('field1', 'field2') + ->lte(10) + ->gte(5); + }, + ]; + + yield 'Range with boost score' => [ + 'expectedOperator' => [ + 'range' => (object) [ + 'path' => ['field1', 'field2'], + 'lte' => 10, + 'gte' => 5, + 'score' => (object) [ + 'boost' => (object) ['value' => 1.5], + ], + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->range() + ->path('field1', 'field2') + ->lte(10) + ->gte(5) + ->boostScore(1.5); + }, + ]; + + yield 'Range with constant score' => [ + 'expectedOperator' => [ + 'range' => (object) [ + 'path' => ['field1', 'field2'], + 'lte' => 10, + 'gte' => 5, + 'score' => (object) [ + 'constant' => (object) ['value' => 1.5], + ], + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->range() + ->path('field1', 'field2') + ->lte(10) + ->gte(5) + ->constantScore(1.5); + }, + ]; + } + + public static function provideRegexBuilders(): Generator + { + yield 'Regex required only' => [ + 'expectedOperator' => [ + 'regex' => (object) [ + 'query' => ['MongoDB', 'Aggregation', 'Pipeline'], + 'path' => ['title', 'content'], + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->regex() + ->query('MongoDB', 'Aggregation', 'Pipeline') + ->path('title', 'content'); + }, + ]; + + yield 'Regex with allowAnalyzedField true' => [ + 'expectedOperator' => [ + 'regex' => (object) [ + 'query' => ['MongoDB', 'Aggregation', 'Pipeline'], + 'path' => ['title', 'content'], + 'allowAnalyzedField' => true, + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->regex() + ->query('MongoDB', 'Aggregation', 'Pipeline') + ->path('title', 'content') + ->allowAnalyzedField(); + }, + ]; + + yield 'Regex with allowAnalyzedField false' => [ + 'expectedOperator' => [ + 'regex' => (object) [ + 'query' => ['MongoDB', 'Aggregation', 'Pipeline'], + 'path' => ['title', 'content'], + 'allowAnalyzedField' => false, + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->regex() + ->query('MongoDB', 'Aggregation', 'Pipeline') + ->path('title', 'content') + ->allowAnalyzedField(false); + }, + ]; + + yield 'Regex with boost score' => [ + 'expectedOperator' => [ + 'regex' => (object) [ + 'query' => ['MongoDB', 'Aggregation', 'Pipeline'], + 'path' => ['title', 'content'], + 'score' => (object) [ + 'boost' => (object) ['value' => 1.5], + ], + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->regex() + ->query('MongoDB', 'Aggregation', 'Pipeline') + ->path('title', 'content') + ->boostScore(1.5); + }, + ]; + + yield 'Regex with constant score' => [ + 'expectedOperator' => [ + 'regex' => (object) [ + 'query' => ['MongoDB', 'Aggregation', 'Pipeline'], + 'path' => ['title', 'content'], + 'score' => (object) [ + 'constant' => (object) ['value' => 1.5], + ], + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->regex() + ->query('MongoDB', 'Aggregation', 'Pipeline') + ->path('title', 'content') + ->constantScore(1.5); + }, + ]; + } + + public static function provideTextBuilders(): Generator + { + yield 'Text required only' => [ + 'expectedOperator' => [ + 'text' => (object) [ + 'query' => ['MongoDB', 'Aggregation', 'Pipeline'], + 'path' => ['title', 'content'], + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->text() + ->query('MongoDB', 'Aggregation', 'Pipeline') + ->path('title', 'content'); + }, + ]; + + yield 'Text with synonyms' => [ + 'expectedOperator' => [ + 'text' => (object) [ + 'query' => ['MongoDB', 'Aggregation', 'Pipeline'], + 'path' => ['content'], + 'synonyms' => 'mySynonyms', + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->text() + ->query('MongoDB', 'Aggregation', 'Pipeline') + ->path('content') + ->synonyms('mySynonyms'); + }, + ]; + + yield 'Text with boost score' => [ + 'expectedOperator' => [ + 'text' => (object) [ + 'query' => ['MongoDB', 'Aggregation', 'Pipeline'], + 'path' => ['content'], + 'score' => (object) [ + 'boost' => (object) ['value' => 1.5], + ], + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->text() + ->query('MongoDB', 'Aggregation', 'Pipeline') + ->path('content') + ->boostScore(1.5); + }, + ]; + + yield 'Text with constant score' => [ + 'expectedOperator' => [ + 'text' => (object) [ + 'query' => ['MongoDB', 'Aggregation', 'Pipeline'], + 'path' => ['content'], + 'score' => (object) [ + 'constant' => (object) ['value' => 1.5], + ], + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->text() + ->query('MongoDB', 'Aggregation', 'Pipeline') + ->path('content') + ->constantScore(1.5); + }, + ]; + + yield 'Text with fuzzy search' => [ + 'expectedOperator' => [ + 'text' => (object) [ + 'query' => ['MongoDB', 'Aggregation', 'Pipeline'], + 'path' => ['content'], + 'fuzzy' => (object) [ + 'maxEdits' => 1, + 'prefixLength' => 2, + 'maxExpansions' => 3, + ], + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->text() + ->query('MongoDB', 'Aggregation', 'Pipeline') + ->path('content') + ->fuzzy(1, 2, 3); + }, + ]; + } + + public static function provideWildcardBuilders(): Generator + { + yield 'Wildcard required only' => [ + 'expectedOperator' => [ + 'wildcard' => (object) [ + 'query' => ['MongoDB', 'Aggregation', 'Pipeline'], + 'path' => ['title', 'content'], + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->wildcard() + ->query('MongoDB', 'Aggregation', 'Pipeline') + ->path('title', 'content'); + }, + ]; + + yield 'Wildcard with allowAnalyzedField true' => [ + 'expectedOperator' => [ + 'wildcard' => (object) [ + 'query' => ['MongoDB', 'Aggregation', 'Pipeline'], + 'path' => ['title', 'content'], + 'allowAnalyzedField' => true, + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->wildcard() + ->query('MongoDB', 'Aggregation', 'Pipeline') + ->path('title', 'content') + ->allowAnalyzedField(); + }, + ]; + + yield 'Wildcard with allowAnalyzedField false' => [ + 'expectedOperator' => [ + 'wildcard' => (object) [ + 'query' => ['MongoDB', 'Aggregation', 'Pipeline'], + 'path' => ['title', 'content'], + 'allowAnalyzedField' => false, + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->wildcard() + ->query('MongoDB', 'Aggregation', 'Pipeline') + ->path('title', 'content') + ->allowAnalyzedField(false); + }, + ]; + + yield 'Wildcard with boost score' => [ + 'expectedOperator' => [ + 'wildcard' => (object) [ + 'query' => ['MongoDB', 'Aggregation', 'Pipeline'], + 'path' => ['title', 'content'], + 'score' => (object) [ + 'boost' => (object) ['value' => 1.5], + ], + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->wildcard() + ->query('MongoDB', 'Aggregation', 'Pipeline') + ->path('title', 'content') + ->boostScore(1.5); + }, + ]; + + yield 'Wildcard with constant score' => [ + 'expectedOperator' => [ + 'wildcard' => (object) [ + 'query' => ['MongoDB', 'Aggregation', 'Pipeline'], + 'path' => ['title', 'content'], + 'score' => (object) [ + 'constant' => (object) ['value' => 1.5], + ], + ], + ], + /** @param Search|CompoundSearchOperatorInterface $stage */ + 'createOperator' => static function ($stage) { + return $stage->wildcard() + ->query('MongoDB', 'Aggregation', 'Pipeline') + ->path('title', 'content') + ->constantScore(1.5); + }, + ]; + } + + /** + * @dataProvider provideAutocompleteBuilders + * @dataProvider provideCompoundBuilders + * @dataProvider provideEmbeddedDocumentBuilders + * @dataProvider provideEmbeddedDocumentCompoundBuilders + * @dataProvider provideEqualsBuilders + * @dataProvider provideExistsBuilders + * @dataProvider provideGeoShapeBuilders + * @dataProvider provideGeoWithinBuilders + * @dataProvider provideMoreLikeThisBuilders + * @dataProvider provideNearBuilders + * @dataProvider providePhraseBuilders + * @dataProvider provideQueryStringBuilders + * @dataProvider provideRangeBuilders + * @dataProvider provideRegexBuilders + * @dataProvider provideTextBuilders + * @dataProvider provideWildcardBuilders + */ + public function testSearchOperators(array $expectedOperator, Closure $createOperator): void + { + $baseExpected = [ + 'index' => 'my_search_index', + 'highlight' => (object) [ + 'path' => 'content', + 'maxCharsToExamine' => 2, + 'maxNumPassages' => 3, + ], + 'count' => (object) [ + 'type' => 'lowerBound', + 'threshold' => 1000, + ], + 'returnStoredSource' => true, + ]; + + $searchStage = new Search($this->getTestAggregationBuilder()); + $searchStage + ->index('my_search_index'); + + $result = $createOperator($searchStage); + + self::logicalOr( + new IsInstanceOf(AbstractSearchOperator::class), + new IsInstanceOf(Search::class), + ); + + $result + ->highlight('content', 2, 3) + ->countDocuments('lowerBound', 1000) + ->returnStoredSource(); + + self::assertEquals( + ['$search' => (object) array_merge($baseExpected, $expectedOperator)], + $searchStage->getExpression(), + ); + } + + /** + * @dataProvider provideAutocompleteBuilders + * @dataProvider provideEmbeddedDocumentBuilders + * @dataProvider provideEqualsBuilders + * @dataProvider provideExistsBuilders + * @dataProvider provideGeoShapeBuilders + * @dataProvider provideGeoWithinBuilders + * @dataProvider provideMoreLikeThisBuilders + * @dataProvider provideNearBuilders + * @dataProvider providePhraseBuilders + * @dataProvider provideQueryStringBuilders + * @dataProvider provideRangeBuilders + * @dataProvider provideRegexBuilders + * @dataProvider provideTextBuilders + * @dataProvider provideWildcardBuilders + */ + public function testSearchCompoundOperators(array $expectedOperator, Closure $createOperator): void + { + $searchStage = new Search($this->getTestAggregationBuilder()); + $compound = $searchStage + ->index('my_search_index') + ->compound(); + + $compound = $createOperator($compound->must()); + $compound = $createOperator($compound->mustNot()); + $compound = $createOperator($compound->should(2)); + $compound = $createOperator($compound->filter()); + + self::assertInstanceOf(CompoundSearchOperatorInterface::class, $compound); + + $keys = ['must', 'mustNot', 'should', 'filter']; + + $expected = (object) [ + 'index' => 'my_search_index', + 'compound' => (object) array_combine( + $keys, + array_map( + static fn (string $value): array => [(object) $expectedOperator], + $keys, + ), + ), + ]; + + $expected->compound->minimumShouldMatch = 2; + + self::assertEquals( + ['$search' => $expected], + $searchStage->getExpression(), + ); + } + + /** + * @dataProvider provideAutocompleteBuilders + * @dataProvider provideCompoundBuilders + * @dataProvider provideEqualsBuilders + * @dataProvider provideExistsBuilders + * @dataProvider provideGeoShapeBuilders + * @dataProvider provideGeoWithinBuilders + * @dataProvider provideMoreLikeThisBuilders + * @dataProvider provideNearBuilders + * @dataProvider providePhraseBuilders + * @dataProvider provideQueryStringBuilders + * @dataProvider provideRangeBuilders + * @dataProvider provideRegexBuilders + * @dataProvider provideTextBuilders + * @dataProvider provideWildcardBuilders + */ + public function testSearchEmbeddedDocumentOperators(array $expectedOperator, Closure $createOperator): void + { + $searchStage = new Search($this->getTestAggregationBuilder()); + $embedded = $searchStage + ->index('my_search_index') + ->embeddedDocument('foo'); + + $createOperator($embedded); + + $expected = (object) [ + 'index' => 'my_search_index', + 'embeddedDocument' => (object) [ + 'path' => 'foo', + 'operator' => (object) $expectedOperator, + ], + ]; + + self::assertEquals( + ['$search' => $expected], + $searchStage->getExpression(), + ); + } +}