From b098ab947caf573d5e434884c0825f8bcec21f57 Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Fri, 24 Nov 2023 08:46:39 +0100 Subject: [PATCH] Support minDistance and maxDistance options for $near and $nearSphere operators (#2583) * Support minDistance and maxDistance options for $near and $nearSphere operators * Fix static analysis errors --- .../MongoDB/Persisters/DocumentPersister.php | 5 +- lib/Doctrine/ODM/MongoDB/Query/Builder.php | 8 +- lib/Doctrine/ODM/MongoDB/Query/Expr.php | 47 ++++- lib/Doctrine/ODM/MongoDB/Types/Type.php | 3 +- psalm-baseline.xml | 5 - .../ODM/MongoDB/Tests/Query/BuilderTest.php | 4 +- .../ODM/MongoDB/Tests/Query/ExprTest.php | 164 ++++++++++++++++++ 7 files changed, 215 insertions(+), 21 deletions(-) diff --git a/lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php b/lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php index c99cbb2616..373207d91f 100644 --- a/lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php +++ b/lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php @@ -29,9 +29,10 @@ use Doctrine\ODM\MongoDB\Utility\CollectionHelper; use Doctrine\Persistence\Mapping\MappingException; use InvalidArgumentException; +use Iterator as SplIterator; use MongoDB\BSON\ObjectId; use MongoDB\Collection; -use MongoDB\Driver\Cursor; +use MongoDB\Driver\CursorInterface; use MongoDB\Driver\Exception\Exception as DriverException; use MongoDB\Driver\Exception\WriteException; use MongoDB\Driver\WriteConcern; @@ -581,7 +582,7 @@ private function getShardKeyQuery(object $document): array /** * Wraps the supplied base cursor in the corresponding ODM class. */ - private function wrapCursor(Cursor $baseCursor): Iterator + private function wrapCursor(SplIterator&CursorInterface $baseCursor): Iterator { return new CachingIterator(new HydratingIterator($baseCursor, $this->dm->getUnitOfWork(), $this->class)); } diff --git a/lib/Doctrine/ODM/MongoDB/Query/Builder.php b/lib/Doctrine/ODM/MongoDB/Query/Builder.php index 4cb06a4dee..a01629873d 100644 --- a/lib/Doctrine/ODM/MongoDB/Query/Builder.php +++ b/lib/Doctrine/ODM/MongoDB/Query/Builder.php @@ -991,9 +991,9 @@ public function mul($value): self * @param float|array|Point $x * @param float $y */ - public function near($x, $y = null): self + public function near($x, $y = null, ?float $minDistance = null, ?float $maxDistance = null): self { - $this->expr->near($x, $y); + $this->expr->near($x, $y, $minDistance, $maxDistance); return $this; } @@ -1011,9 +1011,9 @@ public function near($x, $y = null): self * @param float|array|Point $x * @param float $y */ - public function nearSphere($x, $y = null): self + public function nearSphere($x, $y = null, ?float $minDistance = null, ?float $maxDistance = null): self { - $this->expr->nearSphere($x, $y); + $this->expr->nearSphere($x, $y, $minDistance, $maxDistance); return $this; } diff --git a/lib/Doctrine/ODM/MongoDB/Query/Expr.php b/lib/Doctrine/ODM/MongoDB/Query/Expr.php index 414094fa42..cd0d81412f 100644 --- a/lib/Doctrine/ODM/MongoDB/Query/Expr.php +++ b/lib/Doctrine/ODM/MongoDB/Query/Expr.php @@ -15,6 +15,7 @@ use MongoDB\BSON\Binary; use MongoDB\BSON\Javascript; +use function array_filter; use function array_key_exists; use function array_map; use function array_merge; @@ -822,17 +823,34 @@ public function mul($value): self * @param float|array|Point $x * @param float $y */ - public function near($x, $y = null): self + public function near($x, $y = null, ?float $minDistance = null, ?float $maxDistance = null): self { if ($x instanceof Point) { $x = $x->jsonSerialize(); } if (is_array($x)) { - return $this->operator('$near', ['$geometry' => $x]); + return $this->operator( + '$near', + array_filter([ + '$geometry' => $x, + '$minDistance' => $minDistance, + '$maxDistance' => $maxDistance, + ]), + ); + } + + $this->operator('$near', [$x, $y]); + + if ($minDistance !== null) { + $this->operator('$minDistance', $minDistance); } - return $this->operator('$near', [$x, $y]); + if ($maxDistance !== null) { + $this->operator('$maxDistance', $maxDistance); + } + + return $this; } /** @@ -848,17 +866,34 @@ public function near($x, $y = null): self * @param float|array|Point $x * @param float $y */ - public function nearSphere($x, $y = null): self + public function nearSphere($x, $y = null, ?float $minDistance = null, ?float $maxDistance = null): self { if ($x instanceof Point) { $x = $x->jsonSerialize(); } if (is_array($x)) { - return $this->operator('$nearSphere', ['$geometry' => $x]); + return $this->operator( + '$nearSphere', + array_filter([ + '$geometry' => $x, + '$minDistance' => $minDistance, + '$maxDistance' => $maxDistance, + ]), + ); + } + + $this->operator('$nearSphere', [$x, $y]); + + if ($minDistance !== null) { + $this->operator('$minDistance', $minDistance); } - return $this->operator('$nearSphere', [$x, $y]); + if ($maxDistance !== null) { + $this->operator('$maxDistance', $maxDistance); + } + + return $this; } /** diff --git a/lib/Doctrine/ODM/MongoDB/Types/Type.php b/lib/Doctrine/ODM/MongoDB/Types/Type.php index 2c27c96b82..90a795bbe4 100644 --- a/lib/Doctrine/ODM/MongoDB/Types/Type.php +++ b/lib/Doctrine/ODM/MongoDB/Types/Type.php @@ -257,8 +257,7 @@ public static function getTypesMap(): array return self::$typesMap; } - /** @return string */ - public function __toString() + public function __toString(): string { $e = explode('\\', static::class); $className = end($e); diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 287209a3f8..c62587de74 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -279,11 +279,6 @@ $datetime instanceof DateTime - - - __toString - - $assoc diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Query/BuilderTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Query/BuilderTest.php index 89293ca49f..fd18a4bc92 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Query/BuilderTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Query/BuilderTest.php @@ -504,8 +504,8 @@ public static function provideProxiedExprMethods(): array 'type()' => ['type', [7]], 'all()' => ['all', [['value1', 'value2']]], 'mod()' => ['mod', [2, 0]], - 'near()' => ['near', [1, 2]], - 'nearSphere()' => ['nearSphere', [1, 2]], + 'near()' => ['near', [1, 2], null, 5, 10], + 'nearSphere()' => ['nearSphere', [1, 2], null, 5, 10], 'geoIntersects()' => ['geoIntersects', [self::createGeometry()]], 'geoWithin()' => ['geoWithin', [self::createGeometry()]], 'geoWithinBox()' => ['geoWithinBox', [1, 2, 3, 4]], diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Query/ExprTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Query/ExprTest.php index 64a75e59c9..7fad6c1af2 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Query/ExprTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Query/ExprTest.php @@ -422,6 +422,64 @@ public function testNearWithGeoJsonPoint($point, array $expected): void self::assertEquals(['$near' => $expected], $expr->getQuery()); } + public function testNearWithGeoJsonPointAndMinDistance(): void + { + $expr = $this->createExpr(); + + $coordinates = [1, 2]; + $point = new Point($coordinates); + + self::assertSame($expr, $expr->near($point, null, 5)); + self::assertEquals( + [ + '$near' => [ + '$geometry' => ['type' => 'Point', 'coordinates' => $coordinates], + '$minDistance' => 5, + ], + ], + $expr->getQuery(), + ); + } + + public function testNearWithGeoJsonPointAndMaxDistance(): void + { + $expr = $this->createExpr(); + + $coordinates = [1, 2]; + $point = new Point($coordinates); + + self::assertSame($expr, $expr->near($point, null, null, 10)); + self::assertEquals( + [ + '$near' => [ + '$geometry' => ['type' => 'Point', 'coordinates' => $coordinates], + '$maxDistance' => 10, + ], + ], + $expr->getQuery(), + ); + } + + public function testNearWithGeoJsonPointAndMinAndMaxDistance(): void + { + $expr = $this->createExpr(); + + $coordinates = [1, 2]; + $point = new Point($coordinates); + + self::assertSame($expr, $expr->near($point, null, 5, 10)); + self::assertEquals( + [ + '$near' => [ + '$geometry' => ['type' => 'Point', 'coordinates' => $coordinates], + '$minDistance' => 5, + '$maxDistance' => 10, + ], + ], + $expr->getQuery(), + ); + } + public function testNearWithLegacyCoordinates(): void { $expr = $this->createExpr(); @@ -430,6 +488,30 @@ public function testNearWithLegacyCoordinates(): void self::assertEquals(['$near' => [1, 2]], $expr->getQuery()); } + public function testNearWithLegacyCoordinatesAndMinDistance(): void + { + $expr = $this->createExpr(); + + self::assertSame($expr, $expr->near(1, 2, 5)); + self::assertEquals(['$near' => [1, 2], '$minDistance' => 5], $expr->getQuery()); + } + + public function testNearWithLegacyCoordinatesAndMaxDistance(): void + { + $expr = $this->createExpr(); + + self::assertSame($expr, $expr->near(1, 2, null, 10)); + self::assertEquals(['$near' => [1, 2], '$maxDistance' => 10], $expr->getQuery()); + } + + public function testNearWithLegacyCoordinatesAndMinAndMaxDistance(): void + { + $expr = $this->createExpr(); + + self::assertSame($expr, $expr->near(1, 2, 5, 10)); + self::assertEquals(['$near' => [1, 2], '$minDistance' => 5, '$maxDistance' => 10], $expr->getQuery()); + } + /** * @param Point|array $point * @param array $expected @@ -443,6 +525,64 @@ public function testNearSphereWithGeoJsonPoint($point, array $expected): void self::assertEquals(['$nearSphere' => $expected], $expr->getQuery()); } + public function testNearSphereWithGeoJsonPointAndMinDistance(): void + { + $expr = $this->createExpr(); + + $coordinates = [1, 2]; + $point = new Point($coordinates); + + self::assertSame($expr, $expr->nearSphere($point, null, 5)); + self::assertEquals( + [ + '$nearSphere' => [ + '$geometry' => ['type' => 'Point', 'coordinates' => $coordinates], + '$minDistance' => 5, + ], + ], + $expr->getQuery(), + ); + } + + public function testNearSphereWithGeoJsonPointAndMaxDistance(): void + { + $expr = $this->createExpr(); + + $coordinates = [1, 2]; + $point = new Point($coordinates); + + self::assertSame($expr, $expr->nearSphere($point, null, null, 10)); + self::assertEquals( + [ + '$nearSphere' => [ + '$geometry' => ['type' => 'Point', 'coordinates' => $coordinates], + '$maxDistance' => 10, + ], + ], + $expr->getQuery(), + ); + } + + public function testNearSphereWithGeoJsonPointAndMinAndMaxDistance(): void + { + $expr = $this->createExpr(); + + $coordinates = [1, 2]; + $point = new Point($coordinates); + + self::assertSame($expr, $expr->nearSphere($point, null, 5, 10)); + self::assertEquals( + [ + '$nearSphere' => [ + '$geometry' => ['type' => 'Point', 'coordinates' => $coordinates], + '$minDistance' => 5, + '$maxDistance' => 10, + ], + ], + $expr->getQuery(), + ); + } + public function testNearSphereWithLegacyCoordinates(): void { $expr = $this->createExpr(); @@ -451,6 +591,30 @@ public function testNearSphereWithLegacyCoordinates(): void self::assertEquals(['$nearSphere' => [1, 2]], $expr->getQuery()); } + public function testNearSphereWithLegacyCoordinatesAndMinDistance(): void + { + $expr = $this->createExpr(); + + self::assertSame($expr, $expr->nearSphere(1, 2, 5)); + self::assertEquals(['$nearSphere' => [1, 2], '$minDistance' => 5], $expr->getQuery()); + } + + public function testNearSphereWithLegacyCoordinatesAndMaxDistance(): void + { + $expr = $this->createExpr(); + + self::assertSame($expr, $expr->nearSphere(1, 2, null, 10)); + self::assertEquals(['$nearSphere' => [1, 2], '$maxDistance' => 10], $expr->getQuery()); + } + + public function testNearSphereWithLegacyCoordinatesAndMinAndMaxDistance(): void + { + $expr = $this->createExpr(); + + self::assertSame($expr, $expr->nearSphere(1, 2, 5, 10)); + self::assertEquals(['$nearSphere' => [1, 2], '$minDistance' => 5, '$maxDistance' => 10], $expr->getQuery()); + } + public function testPullWithValue(): void { $expr = $this->createExpr();