diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search.php index e1eea694c..dc3850373 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search.php @@ -34,6 +34,8 @@ * maxNumPassages?: int, * }, * returnStoredSource?: bool, + * searchBefore?: string, + * searchAfter?: string, * sort?: object, * autocomplete?: object, * compound?: object, @@ -61,6 +63,8 @@ class Search extends Stage implements SupportsAllSearchOperators private ?object $count = null; private ?object $highlight = null; private ?bool $returnStoredSource = null; + private ?string $searchBefore = null; + private ?string $searchAfter = null; private ?SearchOperator $operator = null; /** @var array */ @@ -92,6 +96,14 @@ public function getExpression(): array $params->returnStoredSource = $this->returnStoredSource; } + if ($this->searchBefore) { + $params->searchBefore = $this->searchBefore; + } + + if ($this->searchAfter) { + $params->searchAfter = $this->searchAfter; + } + if ($this->sort) { $params->sort = (object) $this->sort; } @@ -145,6 +157,20 @@ public function returnStoredSource(bool $returnStoredSource = true): static return $this; } + public function searchBefore(string $searchBefore): static + { + $this->searchBefore = $searchBefore; + + return $this; + } + + public function searchAfter(string $searchAfter): static + { + $this->searchAfter = $searchAfter; + + return $this; + } + /** * @param array|string $fieldName Field name or array of field/order pairs * @param int|string $order Field order (if one field is specified) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 668e0e829..5c0f8b2fa 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1506,6 +1506,18 @@ parameters: count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SearchTest.php + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Aggregation\\\\Stage\\\\SearchTest\\:\\:testSearchOperatorsWithSearchBefore\\(\\) has parameter \\$expectedOperator with no value type specified in iterable type array\\.$#" + identifier: missingType.iterableValue + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SearchTest.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Aggregation\\\\Stage\\\\SearchTest\\:\\:testSearchOperatorsWithSearchAfter\\(\\) has parameter \\$expectedOperator with no value type specified in iterable type array\\.$#" + identifier: missingType.iterableValue + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SearchTest.php + - message: '#^Method Doctrine\\ODM\\MongoDB\\Tests\\Aggregation\\Stage\\SetWindowFieldsTest\:\:testOperators\(\) has parameter \$args with no type specified\.$#' identifier: missingType.parameter diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SearchTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SearchTest.php index 19ee8f9a5..7e67db19c 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SearchTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SearchTest.php @@ -1313,4 +1313,86 @@ public function testSearchEmbeddedDocumentOperators(array $expectedOperator, Clo $searchStage->getExpression(), ); } + + #[DataProvider('provideAutocompleteBuilders')] + public function testSearchOperatorsWithSearchBefore(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, + 'searchBefore' => 'marker', + ]; + + $searchStage = new Search($this->getTestAggregationBuilder()); + $searchStage + ->index('my_search_index') + ->searchBefore('marker'); + + $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')] + public function testSearchOperatorsWithSearchAfter(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, + 'searchAfter' => 'marker', + ]; + + $searchStage = new Search($this->getTestAggregationBuilder()); + $searchStage + ->index('my_search_index') + ->searchAfter('marker'); + + $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(), + ); + } }