diff --git a/generator/config/expressions.php b/generator/config/expressions.php index 3211195fa..d4542ea99 100644 --- a/generator/config/expressions.php +++ b/generator/config/expressions.php @@ -79,6 +79,10 @@ 'returnType' => Type\ExpressionInterface::class, 'acceptedTypes' => [Type\ExpressionInterface::class, ...$bsonTypes['any']], ], + 'filter' => [ + 'returnType' => Type\QueryFilterInterface::class, + 'acceptedTypes' => [Type\QueryFilterInterface::class, ...$bsonTypes['any']], + ], 'query' => [ 'returnType' => Type\QueryInterface::class, 'acceptedTypes' => [Type\QueryInterface::class, ...$bsonTypes['object']], @@ -112,6 +116,10 @@ 'implements' => [ResolvesToAny::class], 'acceptedTypes' => ['string'], ], + 'geometry' => [ + 'returnType' => Type\GeometryInterface::class, + 'acceptedTypes' => [Type\GeometryInterface::class, ...$bsonTypes['object']], + ], // @todo add enum values 'Granularity' => [ @@ -155,7 +163,4 @@ 'GeoPoint' => [ 'acceptedTypes' => [...$bsonTypes['object']], ], - 'Geometry' => [ - 'acceptedTypes' => [...$bsonTypes['object']], - ], ]; diff --git a/generator/config/query/all.yaml b/generator/config/query/all.yaml index 3f8b5fabc..8165ecb56 100644 --- a/generator/config/query/all.yaml +++ b/generator/config/query/all.yaml @@ -4,7 +4,7 @@ category: - 'Array Query Operators' link: 'https://www.mongodb.com/docs/manual/reference/operator/query/all/' type: - - query + - filter encode: single description: | Matches arrays that contain all elements specified in the query. diff --git a/generator/config/query/bitsAllClear.yaml b/generator/config/query/bitsAllClear.yaml index 898289095..dba30976c 100644 --- a/generator/config/query/bitsAllClear.yaml +++ b/generator/config/query/bitsAllClear.yaml @@ -4,7 +4,7 @@ category: - 'Bitwise Query Operators' link: 'https://www.mongodb.com/docs/manual/reference/operator/query/bitsAllClear/' type: - - query + - filter encode: single description: | Matches numeric or binary values in which a set of bit positions all have a value of 0. diff --git a/generator/config/query/bitsAllSet.yaml b/generator/config/query/bitsAllSet.yaml index 04211aa21..0defa5212 100644 --- a/generator/config/query/bitsAllSet.yaml +++ b/generator/config/query/bitsAllSet.yaml @@ -4,7 +4,7 @@ category: - 'Bitwise Query Operators' link: 'https://www.mongodb.com/docs/manual/reference/operator/query/bitsAllSet/' type: - - query + - filter encode: single description: | Matches numeric or binary values in which a set of bit positions all have a value of 1. diff --git a/generator/config/query/bitsAnyClear.yaml b/generator/config/query/bitsAnyClear.yaml index 186a93994..513c0eb2d 100644 --- a/generator/config/query/bitsAnyClear.yaml +++ b/generator/config/query/bitsAnyClear.yaml @@ -4,7 +4,7 @@ category: - 'Bitwise Query Operators' link: 'https://www.mongodb.com/docs/manual/reference/operator/query/bitsAnyClear/' type: - - query + - filter encode: single description: | Matches numeric or binary values in which any bit from a set of bit positions has a value of 0. diff --git a/generator/config/query/bitsAnySet.yaml b/generator/config/query/bitsAnySet.yaml index 93eac60b5..db0a10964 100644 --- a/generator/config/query/bitsAnySet.yaml +++ b/generator/config/query/bitsAnySet.yaml @@ -4,7 +4,7 @@ category: - 'Bitwise Query Operators' link: 'https://www.mongodb.com/docs/manual/reference/operator/query/bitsAnySet/' type: - - query + - filter encode: single description: | Matches numeric or binary values in which any bit from a set of bit positions has a value of 1. diff --git a/generator/config/query/box.yaml b/generator/config/query/box.yaml index 80e03b197..b7146a728 100644 --- a/generator/config/query/box.yaml +++ b/generator/config/query/box.yaml @@ -4,7 +4,7 @@ category: - 'Geospatial Geometry Specifiers' link: 'https://www.mongodb.com/docs/manual/reference/operator/query/box/' type: - - query + - geometry encode: single description: | Specifies a rectangular box using legacy coordinate pairs for $geoWithin queries. The 2d index supports $box. diff --git a/generator/config/query/center.yaml b/generator/config/query/center.yaml index 81a234a6a..b751f92e2 100644 --- a/generator/config/query/center.yaml +++ b/generator/config/query/center.yaml @@ -4,7 +4,7 @@ category: - 'Geospatial Geometry Specifiers' link: 'https://www.mongodb.com/docs/manual/reference/operator/query/center/' type: - - query + - geometry encode: single description: | Specifies a circle using legacy coordinate pairs to $geoWithin queries when using planar geometry. The 2d index supports $center. diff --git a/generator/config/query/centerSphere.yaml b/generator/config/query/centerSphere.yaml index 5d9686222..e4c11571a 100644 --- a/generator/config/query/centerSphere.yaml +++ b/generator/config/query/centerSphere.yaml @@ -4,7 +4,7 @@ category: - 'Geospatial Geometry Specifiers' link: 'https://www.mongodb.com/docs/manual/reference/operator/query/centerSphere/' type: - - query + - geometry encode: single description: | Specifies a circle using either legacy coordinate pairs or GeoJSON format for $geoWithin queries when using spherical geometry. The 2dsphere and 2d indexes support $centerSphere. diff --git a/generator/config/query/elemMatch.yaml b/generator/config/query/elemMatch.yaml index 5cfa186e8..8ea30bf73 100644 --- a/generator/config/query/elemMatch.yaml +++ b/generator/config/query/elemMatch.yaml @@ -4,7 +4,7 @@ category: - 'Projection Operators' link: 'https://www.mongodb.com/docs/manual/reference/operator/query/elemMatch/' type: - - query + - filter - projection encode: object description: | diff --git a/generator/config/query/eq.yaml b/generator/config/query/eq.yaml index 47f0a75ad..57cf32abf 100644 --- a/generator/config/query/eq.yaml +++ b/generator/config/query/eq.yaml @@ -4,7 +4,7 @@ category: - 'Comparison Query Operators' link: 'https://www.mongodb.com/docs/manual/reference/operator/query/eq/' type: - - query + - filter encode: single description: | Matches values that are equal to a specified value. diff --git a/generator/config/query/exists.yaml b/generator/config/query/exists.yaml index 65dba48c6..aed7c6748 100644 --- a/generator/config/query/exists.yaml +++ b/generator/config/query/exists.yaml @@ -4,7 +4,7 @@ category: - 'Element Query Operators' link: 'https://www.mongodb.com/docs/manual/reference/operator/query/exists/' type: - - query + - filter encode: single description: | Matches documents that have the specified field. diff --git a/generator/config/query/expr.yaml b/generator/config/query/expr.yaml index c80d3ece7..62407ebb2 100644 --- a/generator/config/query/expr.yaml +++ b/generator/config/query/expr.yaml @@ -4,7 +4,7 @@ category: - 'Evaluation Query Operators' link: 'https://www.mongodb.com/docs/manual/reference/operator/query/expr/' type: - - query + - filter encode: single description: | Allows use of aggregation expressions within the query language. diff --git a/generator/config/query/geoIntersects.yaml b/generator/config/query/geoIntersects.yaml index a75e06617..6d0b3b342 100644 --- a/generator/config/query/geoIntersects.yaml +++ b/generator/config/query/geoIntersects.yaml @@ -4,7 +4,7 @@ category: - 'Geospatial Query Selectors' link: 'https://www.mongodb.com/docs/manual/reference/operator/query/geoIntersects/' type: - - query + - filter encode: single description: | Selects geometries that intersect with a GeoJSON geometry. The 2dsphere index supports $geoIntersects. @@ -12,4 +12,4 @@ arguments: - name: geometry type: - - Geometry + - geometry diff --git a/generator/config/query/geoWithin.yaml b/generator/config/query/geoWithin.yaml index 2484b6187..48b8705ec 100644 --- a/generator/config/query/geoWithin.yaml +++ b/generator/config/query/geoWithin.yaml @@ -4,7 +4,7 @@ category: - 'Geospatial Query Selectors' link: 'https://www.mongodb.com/docs/manual/reference/operator/query/geoWithin/' type: - - query + - filter encode: single description: | Selects geometries within a bounding GeoJSON geometry. The 2dsphere and 2d indexes support $geoWithin. @@ -12,4 +12,4 @@ arguments: - name: geometry type: - - Geometry + - geometry diff --git a/generator/config/query/geometry.yaml b/generator/config/query/geometry.yaml index 84f6cf838..a55730743 100644 --- a/generator/config/query/geometry.yaml +++ b/generator/config/query/geometry.yaml @@ -4,7 +4,7 @@ category: - 'Geospatial Geometry Specifiers' link: 'https://www.mongodb.com/docs/manual/reference/operator/query/geometry/' type: - - query + - geometry encode: object description: | Specifies a geometry in GeoJSON format to geospatial query operators. diff --git a/generator/config/query/gt.yaml b/generator/config/query/gt.yaml index 7b9888848..e39959f35 100644 --- a/generator/config/query/gt.yaml +++ b/generator/config/query/gt.yaml @@ -4,7 +4,7 @@ category: - 'Comparison Query Operators' link: 'https://www.mongodb.com/docs/manual/reference/operator/query/gt/' type: - - query + - filter encode: single description: | Matches values that are greater than a specified value. diff --git a/generator/config/query/gte.yaml b/generator/config/query/gte.yaml index 82b3e1f22..08720365e 100644 --- a/generator/config/query/gte.yaml +++ b/generator/config/query/gte.yaml @@ -4,7 +4,7 @@ category: - 'Comparison Query Operators' link: 'https://www.mongodb.com/docs/manual/reference/operator/query/gte/' type: - - query + - filter encode: single description: | Matches values that are greater than or equal to a specified value. diff --git a/generator/config/query/in.yaml b/generator/config/query/in.yaml index 9af595424..d6a3b6c6a 100644 --- a/generator/config/query/in.yaml +++ b/generator/config/query/in.yaml @@ -4,7 +4,7 @@ category: - 'Comparison Query Operators' link: 'https://www.mongodb.com/docs/manual/reference/operator/query/in/' type: - - query + - filter encode: single description: | Matches any of the values specified in an array. diff --git a/generator/config/query/lt.yaml b/generator/config/query/lt.yaml index 8763b0a58..934973433 100644 --- a/generator/config/query/lt.yaml +++ b/generator/config/query/lt.yaml @@ -4,7 +4,7 @@ category: - 'Comparison Query Operators' link: 'https://www.mongodb.com/docs/manual/reference/operator/query/lt/' type: - - query + - filter encode: single description: | Matches values that are less than a specified value. diff --git a/generator/config/query/lte.yaml b/generator/config/query/lte.yaml index 3370c2efe..c583f988d 100644 --- a/generator/config/query/lte.yaml +++ b/generator/config/query/lte.yaml @@ -4,7 +4,7 @@ category: - 'Comparison Query Operators' link: 'https://www.mongodb.com/docs/manual/reference/operator/query/lte/' type: - - query + - filter encode: single description: | Matches values that are less than or equal to a specified value. diff --git a/generator/config/query/maxDistance.yaml b/generator/config/query/maxDistance.yaml index cbfaf8fcc..5794bb1a9 100644 --- a/generator/config/query/maxDistance.yaml +++ b/generator/config/query/maxDistance.yaml @@ -4,7 +4,7 @@ category: - 'Geospatial Geometry Specifiers' link: 'https://www.mongodb.com/docs/manual/reference/operator/query/maxDistance/' type: - - query + - filter encode: single description: | Specifies a maximum distance to limit the results of $near and $nearSphere queries. The 2dsphere and 2d indexes support $maxDistance. diff --git a/generator/config/query/minDistance.yaml b/generator/config/query/minDistance.yaml index f01ca8691..8d588fc63 100644 --- a/generator/config/query/minDistance.yaml +++ b/generator/config/query/minDistance.yaml @@ -4,7 +4,7 @@ category: - 'Geospatial Geometry Specifiers' link: 'https://www.mongodb.com/docs/manual/reference/operator/query/minDistance/' type: - - query + - filter encode: single description: | Specifies a minimum distance to limit the results of $near and $nearSphere queries. For use with 2dsphere index only. diff --git a/generator/config/query/mod.yaml b/generator/config/query/mod.yaml index e251b0a46..8c341f77f 100644 --- a/generator/config/query/mod.yaml +++ b/generator/config/query/mod.yaml @@ -4,7 +4,7 @@ category: - 'Evaluation Query Operators' link: 'https://www.mongodb.com/docs/manual/reference/operator/query/mod/' type: - - query + - filter encode: array description: | Performs a modulo operation on the value of a field and selects documents with a specified result. diff --git a/generator/config/query/ne.yaml b/generator/config/query/ne.yaml index 366d999ec..6fe52b639 100644 --- a/generator/config/query/ne.yaml +++ b/generator/config/query/ne.yaml @@ -4,7 +4,7 @@ category: - 'Comparison Query Operators' link: 'https://www.mongodb.com/docs/manual/reference/operator/query/ne/' type: - - query + - filter encode: single description: | Matches all values that are not equal to a specified value. diff --git a/generator/config/query/near.yaml b/generator/config/query/near.yaml index 5b44aafd9..aa9330d02 100644 --- a/generator/config/query/near.yaml +++ b/generator/config/query/near.yaml @@ -4,7 +4,7 @@ category: - 'Geospatial Query Selectors' link: 'https://www.mongodb.com/docs/manual/reference/operator/query/near/' type: - - query + - filter encode: object description: | Returns geospatial objects in proximity to a point. Requires a geospatial index. The 2dsphere and 2d indexes support $near. @@ -12,7 +12,7 @@ arguments: - name: geometry type: - - Geometry + - geometry - name: maxDistance type: diff --git a/generator/config/query/nearSphere.yaml b/generator/config/query/nearSphere.yaml index d3d50c7c5..3b69927ac 100644 --- a/generator/config/query/nearSphere.yaml +++ b/generator/config/query/nearSphere.yaml @@ -4,7 +4,7 @@ category: - 'Geospatial Query Selectors' link: 'https://www.mongodb.com/docs/manual/reference/operator/query/nearSphere/' type: - - query + - filter encode: object description: | Returns geospatial objects in proximity to a point on a sphere. Requires a geospatial index. The 2dsphere and 2d indexes support $nearSphere. @@ -12,7 +12,7 @@ arguments: - name: geometry type: - - Geometry + - geometry - name: maxDistance type: diff --git a/generator/config/query/nin.yaml b/generator/config/query/nin.yaml index ded97c7c8..ebcdd3491 100644 --- a/generator/config/query/nin.yaml +++ b/generator/config/query/nin.yaml @@ -4,7 +4,7 @@ category: - 'Comparison Query Operators' link: 'https://www.mongodb.com/docs/manual/reference/operator/query/nin/' type: - - query + - filter encode: single description: | Matches none of the values specified in an array. diff --git a/generator/config/query/not.yaml b/generator/config/query/not.yaml index c69acd36e..b1c7eb971 100644 --- a/generator/config/query/not.yaml +++ b/generator/config/query/not.yaml @@ -4,7 +4,7 @@ category: - 'Logical Query Operators' link: 'https://www.mongodb.com/docs/manual/reference/operator/query/not/' type: - - query + - filter encode: single description: | Inverts the effect of a query expression and returns documents that do not match the query expression. @@ -13,3 +13,4 @@ arguments: name: expression type: - query + - regex diff --git a/generator/config/query/polygon.yaml b/generator/config/query/polygon.yaml index c8fbe2faf..2dfef5b5c 100644 --- a/generator/config/query/polygon.yaml +++ b/generator/config/query/polygon.yaml @@ -4,7 +4,7 @@ category: - 'Geospatial Geometry Specifiers' link: 'https://www.mongodb.com/docs/manual/reference/operator/query/polygon/' type: - - query + - geometry encode: single description: | Specifies a polygon to using legacy coordinate pairs for $geoWithin queries. The 2d index supports $center. diff --git a/generator/config/query/positional.yaml b/generator/config/query/positional.yaml deleted file mode 100644 index 38b9c4f5a..000000000 --- a/generator/config/query/positional.yaml +++ /dev/null @@ -1,10 +0,0 @@ -# $schema: ../schema.json -name: $ -category: - - 'Projection Operators' -link: 'https://www.mongodb.com/docs/manual/reference/operator/projection/positional/' -type: - - query -encode: object -description: | - Projects the first element in an array that matches the query condition. diff --git a/generator/config/query/regex.yaml b/generator/config/query/regex.yaml index 1af41a06e..2c5fa5461 100644 --- a/generator/config/query/regex.yaml +++ b/generator/config/query/regex.yaml @@ -4,7 +4,7 @@ category: - 'Evaluation Query Operators' link: 'https://www.mongodb.com/docs/manual/reference/operator/query/regex/' type: - - query + - filter encode: single description: | Selects documents where values match a specified regular expression. diff --git a/generator/config/query/size.yaml b/generator/config/query/size.yaml index a7c1515d6..cb4620392 100644 --- a/generator/config/query/size.yaml +++ b/generator/config/query/size.yaml @@ -4,7 +4,7 @@ category: - 'Array Query Operators' link: 'https://www.mongodb.com/docs/manual/reference/operator/query/size/' type: - - query + - filter encode: single description: | Selects documents if the array field is a specified size. diff --git a/generator/config/query/slice.yaml b/generator/config/query/slice.yaml index 93fd3e250..dfaa3be2a 100644 --- a/generator/config/query/slice.yaml +++ b/generator/config/query/slice.yaml @@ -2,9 +2,8 @@ name: $slice category: - 'Projection Operators' -link: 'https://www.mongodb.com/docs/manual/reference/operator/query/slice/' +link: 'https://www.mongodb.com/docs/manual/reference/operator/projection/slice/' type: - - query - projection encode: array description: | diff --git a/generator/config/query/type.yaml b/generator/config/query/type.yaml index 757647a03..7e7048462 100644 --- a/generator/config/query/type.yaml +++ b/generator/config/query/type.yaml @@ -4,7 +4,7 @@ category: - 'Element Query Operators' link: 'https://www.mongodb.com/docs/manual/reference/operator/query/type/' type: - - query + - filter encode: single description: | Selects documents if a field is of the specified type. diff --git a/generator/config/schema.json b/generator/config/schema.json index 8267ce80f..cd59c8e06 100644 --- a/generator/config/schema.json +++ b/generator/config/schema.json @@ -35,7 +35,9 @@ "projection", "stage", "query", + "filter", "window", + "geometry", "resolvesToAny", "resolvesToNumber", "resolvesToDouble", @@ -110,6 +112,8 @@ "pipeline", "window", "expression", + "geometry", + "any", "resolvesToNumber", "numberFieldPath", "number", "resolvesToDouble", "doubleFieldPath", "double", "resolvesToString", "stringFieldPath", "string", diff --git a/generator/src/OperatorClassGenerator.php b/generator/src/OperatorClassGenerator.php index 44e54167e..6bed4ce88 100644 --- a/generator/src/OperatorClassGenerator.php +++ b/generator/src/OperatorClassGenerator.php @@ -4,6 +4,7 @@ namespace MongoDB\CodeGenerator; use MongoDB\Builder\Encode; +use MongoDB\Builder\Type\QueryObject; use MongoDB\CodeGenerator\Definition\GeneratorDefinition; use MongoDB\CodeGenerator\Definition\OperatorDefinition; use MongoDB\CodeGenerator\Definition\VariadicType; @@ -129,6 +130,18 @@ public function createClass(GeneratorDefinition $definition, OperatorDefinition PHP); } + + if ($type->query) { + $namespace->addUseFunction('is_array'); + $namespace->addUseFunction('is_object'); + $namespace->addUse(QueryObject::class); + $constuctor->addBody(<<name}) || is_object(\${$argument->name})) { + \${$argument->name} = QueryObject::create(\${$argument->name}); + } + + PHP); + } } // Set property from constructor argument diff --git a/generator/src/OperatorGenerator.php b/generator/src/OperatorGenerator.php index 35fbdc3a5..d9316e92e 100644 --- a/generator/src/OperatorGenerator.php +++ b/generator/src/OperatorGenerator.php @@ -69,11 +69,12 @@ final protected function getType(string $type): ExpressionDefinition * Expression types can contain class names, interface, native types or "list". * PHPDoc types are more precise than native types, so we use them systematically even if redundant. * - * @return object{native:string,doc:string,use:list,list:bool} + * @return object{native:string,doc:string,use:list,list:bool,query:bool} */ final protected function getAcceptedTypes(ArgumentDefinition $arg): stdClass { $nativeTypes = []; + foreach ($arg->type as $type) { $type = $this->getType($type); $nativeTypes = array_merge($nativeTypes, $type->acceptedTypes); @@ -111,6 +112,9 @@ final protected function getAcceptedTypes(ArgumentDefinition $arg): stdClass $listCheck = in_array('\\' . PackedArray::class, $nativeTypes, true) && ! in_array('\\' . Document::class, $nativeTypes, true); + // If the argument is a query, we need to convert it to a QueryObject + $isQuery = in_array('query', $arg->type, true); + // mixed can only be used as a standalone type if (in_array('mixed', $nativeTypes, true)) { $nativeTypes = ['mixed']; @@ -125,6 +129,7 @@ final protected function getAcceptedTypes(ArgumentDefinition $arg): stdClass 'doc' => Type::union(...array_unique($docTypes)), 'use' => array_unique($use), 'list' => $listCheck, + 'query' => $isQuery, ]; } diff --git a/src/Builder/BuilderEncoder.php b/src/Builder/BuilderEncoder.php index 299a1c270..d87089d1d 100644 --- a/src/Builder/BuilderEncoder.php +++ b/src/Builder/BuilderEncoder.php @@ -7,8 +7,11 @@ use MongoDB\Builder\Expression\Variable; use MongoDB\Builder\Stage\GroupStage; use MongoDB\Builder\Type\AccumulatorInterface; +use MongoDB\Builder\Type\CombinedQueryFilter; use MongoDB\Builder\Type\ExpressionInterface; +use MongoDB\Builder\Type\QueryFilterInterface; use MongoDB\Builder\Type\QueryInterface; +use MongoDB\Builder\Type\QueryObject; use MongoDB\Builder\Type\StageInterface; use MongoDB\Builder\Type\WindowInterface; use MongoDB\Codec\EncodeIfSupported; @@ -19,8 +22,10 @@ use function array_key_first; use function assert; use function count; +use function get_debug_type; use function get_object_vars; use function is_array; +use function is_object; use function sprintf; /** @template-implements Encoder */ @@ -37,6 +42,7 @@ public function canEncode($value): bool || $value instanceof StageInterface || $value instanceof ExpressionInterface || $value instanceof QueryInterface + || $value instanceof QueryFilterInterface || $value instanceof AccumulatorInterface || $value instanceof WindowInterface; } @@ -69,6 +75,14 @@ public function encode($value): stdClass|array|string return '$$' . $value->expression; } + if ($value instanceof QueryObject) { + return $this->encodeQueryObject($value); + } + + if ($value instanceof CombinedQueryFilter) { + return $this->encodeCombinedFilter($value); + } + // The generic but incomplete encoding code switch ($value::ENCODE) { case Encode::Single: @@ -142,7 +156,7 @@ private function encodeAsObject(ExpressionInterface|StageInterface|QueryInterfac /** * Get the unique property of the operator as value */ - private function encodeAsSingle(ExpressionInterface|StageInterface|QueryInterface $value): stdClass + private function encodeAsSingle(ExpressionInterface|StageInterface|QueryInterface|QueryFilterInterface $value): stdClass { foreach (get_object_vars($value) as $val) { $result = $this->recursiveEncode($val); @@ -158,6 +172,45 @@ private function encodeAsSingle(ExpressionInterface|StageInterface|QueryInterfac throw new LogicException(sprintf('Class "%s" does not have a single property.', $value::class)); } + private function encodeCombinedFilter(CombinedQueryFilter $filter): stdClass + { + $result = new stdClass(); + foreach ($filter->filters as $filter) { + $filter = $this->recursiveEncode($filter); + if (is_object($filter)) { + $filter = get_object_vars($filter); + } elseif (! is_array($filter)) { + throw new LogicException(sprintf('Query filters must an array or an object. Got "%s"', get_debug_type($filter))); + } + + foreach ($filter as $key => $value) { + $result->{$key} = $value; + } + } + + return $result; + } + + /** + * Query objects are encoded by merging query operator with field path to filter operators in the same object. + */ + private function encodeQueryObject(QueryObject $query): stdClass + { + $result = new stdClass(); + foreach ($query->queries as $key => $value) { + if ($value instanceof QueryInterface) { + // The sub-objects is merged into the main object, replacing duplicate keys + foreach (get_object_vars($this->recursiveEncode($value)) as $subKey => $subValue) { + $result->{$subKey} = $subValue; + } + } else { + $result->{$key} = $this->encodeIfSupported($value); + } + } + + return $result; + } + /** * Nested arrays and objects must be encoded recursively. */ @@ -180,7 +233,7 @@ private function recursiveEncode(mixed $value): mixed return $this->encodeIfSupported($value); } - private function wrap(ExpressionInterface|StageInterface|QueryInterface $value, mixed $result): stdClass + private function wrap(ExpressionInterface|StageInterface|QueryInterface|QueryFilterInterface $value, mixed $result): stdClass { $object = new stdClass(); $object->{$value::NAME} = $result; diff --git a/src/Builder/Query.php b/src/Builder/Query.php index f6a796220..ddba7461d 100644 --- a/src/Builder/Query.php +++ b/src/Builder/Query.php @@ -2,9 +2,13 @@ namespace MongoDB\Builder; -use InvalidArgumentException; use MongoDB\BSON\Regex; +use MongoDB\BSON\Serializable; use MongoDB\Builder\Query\RegexOperator; +use MongoDB\Builder\Type\QueryFilterInterface; +use MongoDB\Builder\Type\QueryInterface; +use MongoDB\Builder\Type\QueryObject; +use stdClass; use function is_string; @@ -17,14 +21,19 @@ enum Query /** * Selects documents where values match a specified regular expression. */ - public static function regex(Regex|string $regex, string $flags = ''): RegexOperator + public static function regex(Regex|string $regex, ?string $flags = null): RegexOperator { if (is_string($regex)) { - $regex = new Regex($regex, $flags); - } elseif ($flags !== '') { - throw new InvalidArgumentException('Flags can only be specified when the regex is a string'); + $regex = new Regex($regex, $flags ?? ''); + } elseif (is_string($flags)) { + $regex = new Regex($regex->getPattern(), $flags); } return self::generatedRegex($regex); } + + public static function query(QueryFilterInterface|QueryInterface|Serializable|array|bool|float|int|null|stdClass|string ...$query): QueryInterface + { + return QueryObject::create($query); + } } diff --git a/src/Builder/Query/AllOperator.php b/src/Builder/Query/AllOperator.php index ab67d231e..fd2d795f3 100644 --- a/src/Builder/Query/AllOperator.php +++ b/src/Builder/Query/AllOperator.php @@ -18,7 +18,7 @@ use MongoDB\BSON\UTCDateTime; use MongoDB\Builder\Encode; use MongoDB\Builder\Expression\ResolvesToInt; -use MongoDB\Builder\Type\QueryInterface; +use MongoDB\Builder\Type\QueryFilterInterface; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Model\BSONArray; use stdClass; @@ -30,7 +30,7 @@ * * @see https://www.mongodb.com/docs/manual/reference/operator/query/all/ */ -class AllOperator implements QueryInterface +class AllOperator implements QueryFilterInterface { public const NAME = '$all'; public const ENCODE = \MongoDB\Builder\Encode::Single; diff --git a/src/Builder/Query/BitsAllClearOperator.php b/src/Builder/Query/BitsAllClearOperator.php index 2c327840e..0f6a3423b 100644 --- a/src/Builder/Query/BitsAllClearOperator.php +++ b/src/Builder/Query/BitsAllClearOperator.php @@ -9,7 +9,7 @@ use MongoDB\BSON\Binary; use MongoDB\BSON\PackedArray; use MongoDB\Builder\Encode; -use MongoDB\Builder\Type\QueryInterface; +use MongoDB\Builder\Type\QueryFilterInterface; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Model\BSONArray; @@ -21,7 +21,7 @@ * * @see https://www.mongodb.com/docs/manual/reference/operator/query/bitsAllClear/ */ -class BitsAllClearOperator implements QueryInterface +class BitsAllClearOperator implements QueryFilterInterface { public const NAME = '$bitsAllClear'; public const ENCODE = \MongoDB\Builder\Encode::Single; diff --git a/src/Builder/Query/BitsAllSetOperator.php b/src/Builder/Query/BitsAllSetOperator.php index f04754994..9f5d7bebc 100644 --- a/src/Builder/Query/BitsAllSetOperator.php +++ b/src/Builder/Query/BitsAllSetOperator.php @@ -9,7 +9,7 @@ use MongoDB\BSON\Binary; use MongoDB\BSON\PackedArray; use MongoDB\Builder\Encode; -use MongoDB\Builder\Type\QueryInterface; +use MongoDB\Builder\Type\QueryFilterInterface; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Model\BSONArray; @@ -21,7 +21,7 @@ * * @see https://www.mongodb.com/docs/manual/reference/operator/query/bitsAllSet/ */ -class BitsAllSetOperator implements QueryInterface +class BitsAllSetOperator implements QueryFilterInterface { public const NAME = '$bitsAllSet'; public const ENCODE = \MongoDB\Builder\Encode::Single; diff --git a/src/Builder/Query/BitsAnyClearOperator.php b/src/Builder/Query/BitsAnyClearOperator.php index 86233d483..33338b0a8 100644 --- a/src/Builder/Query/BitsAnyClearOperator.php +++ b/src/Builder/Query/BitsAnyClearOperator.php @@ -9,7 +9,7 @@ use MongoDB\BSON\Binary; use MongoDB\BSON\PackedArray; use MongoDB\Builder\Encode; -use MongoDB\Builder\Type\QueryInterface; +use MongoDB\Builder\Type\QueryFilterInterface; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Model\BSONArray; @@ -21,7 +21,7 @@ * * @see https://www.mongodb.com/docs/manual/reference/operator/query/bitsAnyClear/ */ -class BitsAnyClearOperator implements QueryInterface +class BitsAnyClearOperator implements QueryFilterInterface { public const NAME = '$bitsAnyClear'; public const ENCODE = \MongoDB\Builder\Encode::Single; diff --git a/src/Builder/Query/BitsAnySetOperator.php b/src/Builder/Query/BitsAnySetOperator.php index a536e3017..109fb9faf 100644 --- a/src/Builder/Query/BitsAnySetOperator.php +++ b/src/Builder/Query/BitsAnySetOperator.php @@ -9,7 +9,7 @@ use MongoDB\BSON\Binary; use MongoDB\BSON\PackedArray; use MongoDB\Builder\Encode; -use MongoDB\Builder\Type\QueryInterface; +use MongoDB\Builder\Type\QueryFilterInterface; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Model\BSONArray; @@ -21,7 +21,7 @@ * * @see https://www.mongodb.com/docs/manual/reference/operator/query/bitsAnySet/ */ -class BitsAnySetOperator implements QueryInterface +class BitsAnySetOperator implements QueryFilterInterface { public const NAME = '$bitsAnySet'; public const ENCODE = \MongoDB\Builder\Encode::Single; diff --git a/src/Builder/Query/BoxOperator.php b/src/Builder/Query/BoxOperator.php index e11001577..baf66c4c9 100644 --- a/src/Builder/Query/BoxOperator.php +++ b/src/Builder/Query/BoxOperator.php @@ -8,7 +8,7 @@ use MongoDB\BSON\PackedArray; use MongoDB\Builder\Encode; -use MongoDB\Builder\Type\QueryInterface; +use MongoDB\Builder\Type\GeometryInterface; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Model\BSONArray; @@ -20,7 +20,7 @@ * * @see https://www.mongodb.com/docs/manual/reference/operator/query/box/ */ -class BoxOperator implements QueryInterface +class BoxOperator implements GeometryInterface { public const NAME = '$box'; public const ENCODE = \MongoDB\Builder\Encode::Single; diff --git a/src/Builder/Query/CenterOperator.php b/src/Builder/Query/CenterOperator.php index e1b9f09ab..c3ac30ca8 100644 --- a/src/Builder/Query/CenterOperator.php +++ b/src/Builder/Query/CenterOperator.php @@ -8,7 +8,7 @@ use MongoDB\BSON\PackedArray; use MongoDB\Builder\Encode; -use MongoDB\Builder\Type\QueryInterface; +use MongoDB\Builder\Type\GeometryInterface; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Model\BSONArray; @@ -20,7 +20,7 @@ * * @see https://www.mongodb.com/docs/manual/reference/operator/query/center/ */ -class CenterOperator implements QueryInterface +class CenterOperator implements GeometryInterface { public const NAME = '$center'; public const ENCODE = \MongoDB\Builder\Encode::Single; diff --git a/src/Builder/Query/CenterSphereOperator.php b/src/Builder/Query/CenterSphereOperator.php index 8e0e74a51..ea6c2f0d1 100644 --- a/src/Builder/Query/CenterSphereOperator.php +++ b/src/Builder/Query/CenterSphereOperator.php @@ -8,7 +8,7 @@ use MongoDB\BSON\PackedArray; use MongoDB\Builder\Encode; -use MongoDB\Builder\Type\QueryInterface; +use MongoDB\Builder\Type\GeometryInterface; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Model\BSONArray; @@ -20,7 +20,7 @@ * * @see https://www.mongodb.com/docs/manual/reference/operator/query/centerSphere/ */ -class CenterSphereOperator implements QueryInterface +class CenterSphereOperator implements GeometryInterface { public const NAME = '$centerSphere'; public const ENCODE = \MongoDB\Builder\Encode::Single; diff --git a/src/Builder/Query/ElemMatchOperator.php b/src/Builder/Query/ElemMatchOperator.php index a57bbedd0..856f11aaa 100644 --- a/src/Builder/Query/ElemMatchOperator.php +++ b/src/Builder/Query/ElemMatchOperator.php @@ -10,15 +10,20 @@ use MongoDB\BSON\Serializable; use MongoDB\Builder\Encode; use MongoDB\Builder\Type\ProjectionInterface; +use MongoDB\Builder\Type\QueryFilterInterface; use MongoDB\Builder\Type\QueryInterface; +use MongoDB\Builder\Type\QueryObject; use stdClass; +use function is_array; +use function is_object; + /** * Projects the first element in an array that matches the specified $elemMatch condition. * * @see https://www.mongodb.com/docs/manual/reference/operator/query/elemMatch/ */ -class ElemMatchOperator implements QueryInterface, ProjectionInterface +class ElemMatchOperator implements QueryFilterInterface, ProjectionInterface { public const NAME = '$elemMatch'; public const ENCODE = \MongoDB\Builder\Encode::Object; @@ -31,6 +36,10 @@ class ElemMatchOperator implements QueryInterface, ProjectionInterface */ public function __construct(Document|Serializable|QueryInterface|stdClass|array $queries) { + if (is_array($queries) || is_object($queries)) { + $queries = QueryObject::create($queries); + } + $this->queries = $queries; } } diff --git a/src/Builder/Query/EqOperator.php b/src/Builder/Query/EqOperator.php index 621f8d481..28dfc4329 100644 --- a/src/Builder/Query/EqOperator.php +++ b/src/Builder/Query/EqOperator.php @@ -18,7 +18,7 @@ use MongoDB\BSON\UTCDateTime; use MongoDB\Builder\Encode; use MongoDB\Builder\Expression\ResolvesToInt; -use MongoDB\Builder\Type\QueryInterface; +use MongoDB\Builder\Type\QueryFilterInterface; use MongoDB\Model\BSONArray; use stdClass; @@ -27,7 +27,7 @@ * * @see https://www.mongodb.com/docs/manual/reference/operator/query/eq/ */ -class EqOperator implements QueryInterface +class EqOperator implements QueryFilterInterface { public const NAME = '$eq'; public const ENCODE = \MongoDB\Builder\Encode::Single; diff --git a/src/Builder/Query/ExistsOperator.php b/src/Builder/Query/ExistsOperator.php index d75f5116e..0dad0a1b9 100644 --- a/src/Builder/Query/ExistsOperator.php +++ b/src/Builder/Query/ExistsOperator.php @@ -7,14 +7,14 @@ namespace MongoDB\Builder\Query; use MongoDB\Builder\Encode; -use MongoDB\Builder\Type\QueryInterface; +use MongoDB\Builder\Type\QueryFilterInterface; /** * Matches documents that have the specified field. * * @see https://www.mongodb.com/docs/manual/reference/operator/query/exists/ */ -class ExistsOperator implements QueryInterface +class ExistsOperator implements QueryFilterInterface { public const NAME = '$exists'; public const ENCODE = \MongoDB\Builder\Encode::Single; diff --git a/src/Builder/Query/ExprOperator.php b/src/Builder/Query/ExprOperator.php index 3e6a56d04..7879ea5e7 100644 --- a/src/Builder/Query/ExprOperator.php +++ b/src/Builder/Query/ExprOperator.php @@ -19,7 +19,7 @@ use MongoDB\Builder\Encode; use MongoDB\Builder\Expression\ResolvesToInt; use MongoDB\Builder\Type\ExpressionInterface; -use MongoDB\Builder\Type\QueryInterface; +use MongoDB\Builder\Type\QueryFilterInterface; use MongoDB\Model\BSONArray; use stdClass; @@ -28,7 +28,7 @@ * * @see https://www.mongodb.com/docs/manual/reference/operator/query/expr/ */ -class ExprOperator implements QueryInterface +class ExprOperator implements QueryFilterInterface { public const NAME = '$expr'; public const ENCODE = \MongoDB\Builder\Encode::Single; diff --git a/src/Builder/Query/FactoryTrait.php b/src/Builder/Query/FactoryTrait.php index a74f5f1ed..79c73747b 100644 --- a/src/Builder/Query/FactoryTrait.php +++ b/src/Builder/Query/FactoryTrait.php @@ -19,6 +19,7 @@ use MongoDB\Builder\Expression\ResolvesToInt; use MongoDB\Builder\Optional; use MongoDB\Builder\Type\ExpressionInterface; +use MongoDB\Builder\Type\GeometryInterface; use MongoDB\Builder\Type\QueryInterface; use MongoDB\Model\BSONArray; use stdClass; @@ -192,9 +193,11 @@ public static function expr( * Selects geometries that intersect with a GeoJSON geometry. The 2dsphere index supports $geoIntersects. * * @see https://www.mongodb.com/docs/manual/reference/operator/query/geoIntersects/ - * @param Document|Serializable|array|stdClass $geometry + * @param Document|GeometryInterface|Serializable|array|stdClass $geometry */ - public static function geoIntersects(Document|Serializable|stdClass|array $geometry): GeoIntersectsOperator + public static function geoIntersects( + Document|Serializable|GeometryInterface|stdClass|array $geometry, + ): GeoIntersectsOperator { return new GeoIntersectsOperator($geometry); } @@ -220,9 +223,11 @@ public static function geometry( * Selects geometries within a bounding GeoJSON geometry. The 2dsphere and 2d indexes support $geoWithin. * * @see https://www.mongodb.com/docs/manual/reference/operator/query/geoWithin/ - * @param Document|Serializable|array|stdClass $geometry + * @param Document|GeometryInterface|Serializable|array|stdClass $geometry */ - public static function geoWithin(Document|Serializable|stdClass|array $geometry): GeoWithinOperator + public static function geoWithin( + Document|Serializable|GeometryInterface|stdClass|array $geometry, + ): GeoWithinOperator { return new GeoWithinOperator($geometry); } @@ -372,12 +377,12 @@ public static function ne( * Returns geospatial objects in proximity to a point. Requires a geospatial index. The 2dsphere and 2d indexes support $near. * * @see https://www.mongodb.com/docs/manual/reference/operator/query/near/ - * @param Document|Serializable|array|stdClass $geometry + * @param Document|GeometryInterface|Serializable|array|stdClass $geometry * @param Optional|int $maxDistance Distance in meters. Limits the results to those documents that are at most the specified distance from the center point. * @param Optional|int $minDistance Distance in meters. Limits the results to those documents that are at least the specified distance from the center point. */ public static function near( - Document|Serializable|stdClass|array $geometry, + Document|Serializable|GeometryInterface|stdClass|array $geometry, Optional|int $maxDistance = Optional::Undefined, Optional|int $minDistance = Optional::Undefined, ): NearOperator @@ -389,12 +394,12 @@ public static function near( * Returns geospatial objects in proximity to a point on a sphere. Requires a geospatial index. The 2dsphere and 2d indexes support $nearSphere. * * @see https://www.mongodb.com/docs/manual/reference/operator/query/nearSphere/ - * @param Document|Serializable|array|stdClass $geometry + * @param Document|GeometryInterface|Serializable|array|stdClass $geometry * @param Optional|int $maxDistance Distance in meters. * @param Optional|int $minDistance Distance in meters. Limits the results to those documents that are at least the specified distance from the center point. */ public static function nearSphere( - Document|Serializable|stdClass|array $geometry, + Document|Serializable|GeometryInterface|stdClass|array $geometry, Optional|int $maxDistance = Optional::Undefined, Optional|int $minDistance = Optional::Undefined, ): NearSphereOperator @@ -428,9 +433,9 @@ public static function nor(Document|Serializable|QueryInterface|stdClass|array . * Inverts the effect of a query expression and returns documents that do not match the query expression. * * @see https://www.mongodb.com/docs/manual/reference/operator/query/not/ - * @param Document|QueryInterface|Serializable|array|stdClass $expression + * @param Document|QueryInterface|Regex|Serializable|array|stdClass $expression */ - public static function not(Document|Serializable|QueryInterface|stdClass|array $expression): NotOperator + public static function not(Document|Regex|Serializable|QueryInterface|stdClass|array $expression): NotOperator { return new NotOperator($expression); } @@ -492,7 +497,7 @@ public static function size(int $value): SizeOperator /** * Limits the number of elements projected from an array. Supports skip and limit slices. * - * @see https://www.mongodb.com/docs/manual/reference/operator/query/slice/ + * @see https://www.mongodb.com/docs/manual/reference/operator/projection/slice/ * @param int $limit * @param int $skip */ diff --git a/src/Builder/Query/GeoIntersectsOperator.php b/src/Builder/Query/GeoIntersectsOperator.php index 6867fd9a0..fdffc5e77 100644 --- a/src/Builder/Query/GeoIntersectsOperator.php +++ b/src/Builder/Query/GeoIntersectsOperator.php @@ -9,7 +9,8 @@ use MongoDB\BSON\Document; use MongoDB\BSON\Serializable; use MongoDB\Builder\Encode; -use MongoDB\Builder\Type\QueryInterface; +use MongoDB\Builder\Type\GeometryInterface; +use MongoDB\Builder\Type\QueryFilterInterface; use stdClass; /** @@ -17,18 +18,18 @@ * * @see https://www.mongodb.com/docs/manual/reference/operator/query/geoIntersects/ */ -class GeoIntersectsOperator implements QueryInterface +class GeoIntersectsOperator implements QueryFilterInterface { public const NAME = '$geoIntersects'; public const ENCODE = \MongoDB\Builder\Encode::Single; - /** @param Document|Serializable|array|stdClass $geometry */ - public Document|Serializable|stdClass|array $geometry; + /** @param Document|GeometryInterface|Serializable|array|stdClass $geometry */ + public Document|Serializable|GeometryInterface|stdClass|array $geometry; /** - * @param Document|Serializable|array|stdClass $geometry + * @param Document|GeometryInterface|Serializable|array|stdClass $geometry */ - public function __construct(Document|Serializable|stdClass|array $geometry) + public function __construct(Document|Serializable|GeometryInterface|stdClass|array $geometry) { $this->geometry = $geometry; } diff --git a/src/Builder/Query/GeoWithinOperator.php b/src/Builder/Query/GeoWithinOperator.php index 069548b8a..e1316579b 100644 --- a/src/Builder/Query/GeoWithinOperator.php +++ b/src/Builder/Query/GeoWithinOperator.php @@ -9,7 +9,8 @@ use MongoDB\BSON\Document; use MongoDB\BSON\Serializable; use MongoDB\Builder\Encode; -use MongoDB\Builder\Type\QueryInterface; +use MongoDB\Builder\Type\GeometryInterface; +use MongoDB\Builder\Type\QueryFilterInterface; use stdClass; /** @@ -17,18 +18,18 @@ * * @see https://www.mongodb.com/docs/manual/reference/operator/query/geoWithin/ */ -class GeoWithinOperator implements QueryInterface +class GeoWithinOperator implements QueryFilterInterface { public const NAME = '$geoWithin'; public const ENCODE = \MongoDB\Builder\Encode::Single; - /** @param Document|Serializable|array|stdClass $geometry */ - public Document|Serializable|stdClass|array $geometry; + /** @param Document|GeometryInterface|Serializable|array|stdClass $geometry */ + public Document|Serializable|GeometryInterface|stdClass|array $geometry; /** - * @param Document|Serializable|array|stdClass $geometry + * @param Document|GeometryInterface|Serializable|array|stdClass $geometry */ - public function __construct(Document|Serializable|stdClass|array $geometry) + public function __construct(Document|Serializable|GeometryInterface|stdClass|array $geometry) { $this->geometry = $geometry; } diff --git a/src/Builder/Query/GeometryOperator.php b/src/Builder/Query/GeometryOperator.php index 00656f74a..1282d690e 100644 --- a/src/Builder/Query/GeometryOperator.php +++ b/src/Builder/Query/GeometryOperator.php @@ -10,7 +10,7 @@ use MongoDB\BSON\PackedArray; use MongoDB\BSON\Serializable; use MongoDB\Builder\Encode; -use MongoDB\Builder\Type\QueryInterface; +use MongoDB\Builder\Type\GeometryInterface; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Model\BSONArray; use stdClass; @@ -23,7 +23,7 @@ * * @see https://www.mongodb.com/docs/manual/reference/operator/query/geometry/ */ -class GeometryOperator implements QueryInterface +class GeometryOperator implements GeometryInterface { public const NAME = '$geometry'; public const ENCODE = \MongoDB\Builder\Encode::Object; diff --git a/src/Builder/Query/GtOperator.php b/src/Builder/Query/GtOperator.php index a4909b360..2c1d72ce5 100644 --- a/src/Builder/Query/GtOperator.php +++ b/src/Builder/Query/GtOperator.php @@ -18,7 +18,7 @@ use MongoDB\BSON\UTCDateTime; use MongoDB\Builder\Encode; use MongoDB\Builder\Expression\ResolvesToInt; -use MongoDB\Builder\Type\QueryInterface; +use MongoDB\Builder\Type\QueryFilterInterface; use MongoDB\Model\BSONArray; use stdClass; @@ -27,7 +27,7 @@ * * @see https://www.mongodb.com/docs/manual/reference/operator/query/gt/ */ -class GtOperator implements QueryInterface +class GtOperator implements QueryFilterInterface { public const NAME = '$gt'; public const ENCODE = \MongoDB\Builder\Encode::Single; diff --git a/src/Builder/Query/GteOperator.php b/src/Builder/Query/GteOperator.php index 5e84656fd..be5578477 100644 --- a/src/Builder/Query/GteOperator.php +++ b/src/Builder/Query/GteOperator.php @@ -18,7 +18,7 @@ use MongoDB\BSON\UTCDateTime; use MongoDB\Builder\Encode; use MongoDB\Builder\Expression\ResolvesToInt; -use MongoDB\Builder\Type\QueryInterface; +use MongoDB\Builder\Type\QueryFilterInterface; use MongoDB\Model\BSONArray; use stdClass; @@ -27,7 +27,7 @@ * * @see https://www.mongodb.com/docs/manual/reference/operator/query/gte/ */ -class GteOperator implements QueryInterface +class GteOperator implements QueryFilterInterface { public const NAME = '$gte'; public const ENCODE = \MongoDB\Builder\Encode::Single; diff --git a/src/Builder/Query/InOperator.php b/src/Builder/Query/InOperator.php index 538c192d1..217d7083e 100644 --- a/src/Builder/Query/InOperator.php +++ b/src/Builder/Query/InOperator.php @@ -8,7 +8,7 @@ use MongoDB\BSON\PackedArray; use MongoDB\Builder\Encode; -use MongoDB\Builder\Type\QueryInterface; +use MongoDB\Builder\Type\QueryFilterInterface; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Model\BSONArray; @@ -20,7 +20,7 @@ * * @see https://www.mongodb.com/docs/manual/reference/operator/query/in/ */ -class InOperator implements QueryInterface +class InOperator implements QueryFilterInterface { public const NAME = '$in'; public const ENCODE = \MongoDB\Builder\Encode::Single; diff --git a/src/Builder/Query/LtOperator.php b/src/Builder/Query/LtOperator.php index 51401cc22..9ce709334 100644 --- a/src/Builder/Query/LtOperator.php +++ b/src/Builder/Query/LtOperator.php @@ -18,7 +18,7 @@ use MongoDB\BSON\UTCDateTime; use MongoDB\Builder\Encode; use MongoDB\Builder\Expression\ResolvesToInt; -use MongoDB\Builder\Type\QueryInterface; +use MongoDB\Builder\Type\QueryFilterInterface; use MongoDB\Model\BSONArray; use stdClass; @@ -27,7 +27,7 @@ * * @see https://www.mongodb.com/docs/manual/reference/operator/query/lt/ */ -class LtOperator implements QueryInterface +class LtOperator implements QueryFilterInterface { public const NAME = '$lt'; public const ENCODE = \MongoDB\Builder\Encode::Single; diff --git a/src/Builder/Query/LteOperator.php b/src/Builder/Query/LteOperator.php index fff584b18..df6d8b8bb 100644 --- a/src/Builder/Query/LteOperator.php +++ b/src/Builder/Query/LteOperator.php @@ -18,7 +18,7 @@ use MongoDB\BSON\UTCDateTime; use MongoDB\Builder\Encode; use MongoDB\Builder\Expression\ResolvesToInt; -use MongoDB\Builder\Type\QueryInterface; +use MongoDB\Builder\Type\QueryFilterInterface; use MongoDB\Model\BSONArray; use stdClass; @@ -27,7 +27,7 @@ * * @see https://www.mongodb.com/docs/manual/reference/operator/query/lte/ */ -class LteOperator implements QueryInterface +class LteOperator implements QueryFilterInterface { public const NAME = '$lte'; public const ENCODE = \MongoDB\Builder\Encode::Single; diff --git a/src/Builder/Query/MaxDistanceOperator.php b/src/Builder/Query/MaxDistanceOperator.php index 30b95033a..5650f3bc8 100644 --- a/src/Builder/Query/MaxDistanceOperator.php +++ b/src/Builder/Query/MaxDistanceOperator.php @@ -10,14 +10,14 @@ use MongoDB\BSON\Int64; use MongoDB\Builder\Encode; use MongoDB\Builder\Expression\ResolvesToInt; -use MongoDB\Builder\Type\QueryInterface; +use MongoDB\Builder\Type\QueryFilterInterface; /** * Specifies a maximum distance to limit the results of $near and $nearSphere queries. The 2dsphere and 2d indexes support $maxDistance. * * @see https://www.mongodb.com/docs/manual/reference/operator/query/maxDistance/ */ -class MaxDistanceOperator implements QueryInterface +class MaxDistanceOperator implements QueryFilterInterface { public const NAME = '$maxDistance'; public const ENCODE = \MongoDB\Builder\Encode::Single; diff --git a/src/Builder/Query/MinDistanceOperator.php b/src/Builder/Query/MinDistanceOperator.php index 714c20ff3..4d88d94eb 100644 --- a/src/Builder/Query/MinDistanceOperator.php +++ b/src/Builder/Query/MinDistanceOperator.php @@ -8,14 +8,14 @@ use MongoDB\BSON\Int64; use MongoDB\Builder\Encode; -use MongoDB\Builder\Type\QueryInterface; +use MongoDB\Builder\Type\QueryFilterInterface; /** * Specifies a minimum distance to limit the results of $near and $nearSphere queries. For use with 2dsphere index only. * * @see https://www.mongodb.com/docs/manual/reference/operator/query/minDistance/ */ -class MinDistanceOperator implements QueryInterface +class MinDistanceOperator implements QueryFilterInterface { public const NAME = '$minDistance'; public const ENCODE = \MongoDB\Builder\Encode::Single; diff --git a/src/Builder/Query/ModOperator.php b/src/Builder/Query/ModOperator.php index 7d127f3d7..e00ef95ff 100644 --- a/src/Builder/Query/ModOperator.php +++ b/src/Builder/Query/ModOperator.php @@ -7,14 +7,14 @@ namespace MongoDB\Builder\Query; use MongoDB\Builder\Encode; -use MongoDB\Builder\Type\QueryInterface; +use MongoDB\Builder\Type\QueryFilterInterface; /** * Performs a modulo operation on the value of a field and selects documents with a specified result. * * @see https://www.mongodb.com/docs/manual/reference/operator/query/mod/ */ -class ModOperator implements QueryInterface +class ModOperator implements QueryFilterInterface { public const NAME = '$mod'; public const ENCODE = \MongoDB\Builder\Encode::Array; diff --git a/src/Builder/Query/NeOperator.php b/src/Builder/Query/NeOperator.php index f492e66fd..63cd6433d 100644 --- a/src/Builder/Query/NeOperator.php +++ b/src/Builder/Query/NeOperator.php @@ -18,7 +18,7 @@ use MongoDB\BSON\UTCDateTime; use MongoDB\Builder\Encode; use MongoDB\Builder\Expression\ResolvesToInt; -use MongoDB\Builder\Type\QueryInterface; +use MongoDB\Builder\Type\QueryFilterInterface; use MongoDB\Model\BSONArray; use stdClass; @@ -27,7 +27,7 @@ * * @see https://www.mongodb.com/docs/manual/reference/operator/query/ne/ */ -class NeOperator implements QueryInterface +class NeOperator implements QueryFilterInterface { public const NAME = '$ne'; public const ENCODE = \MongoDB\Builder\Encode::Single; diff --git a/src/Builder/Query/NearOperator.php b/src/Builder/Query/NearOperator.php index d84a5adef..68f3f7213 100644 --- a/src/Builder/Query/NearOperator.php +++ b/src/Builder/Query/NearOperator.php @@ -10,7 +10,8 @@ use MongoDB\BSON\Serializable; use MongoDB\Builder\Encode; use MongoDB\Builder\Optional; -use MongoDB\Builder\Type\QueryInterface; +use MongoDB\Builder\Type\GeometryInterface; +use MongoDB\Builder\Type\QueryFilterInterface; use stdClass; /** @@ -18,13 +19,13 @@ * * @see https://www.mongodb.com/docs/manual/reference/operator/query/near/ */ -class NearOperator implements QueryInterface +class NearOperator implements QueryFilterInterface { public const NAME = '$near'; public const ENCODE = \MongoDB\Builder\Encode::Object; - /** @param Document|Serializable|array|stdClass $geometry */ - public Document|Serializable|stdClass|array $geometry; + /** @param Document|GeometryInterface|Serializable|array|stdClass $geometry */ + public Document|Serializable|GeometryInterface|stdClass|array $geometry; /** @param Optional|int $maxDistance Distance in meters. Limits the results to those documents that are at most the specified distance from the center point. */ public Optional|int $maxDistance; @@ -33,12 +34,12 @@ class NearOperator implements QueryInterface public Optional|int $minDistance; /** - * @param Document|Serializable|array|stdClass $geometry + * @param Document|GeometryInterface|Serializable|array|stdClass $geometry * @param Optional|int $maxDistance Distance in meters. Limits the results to those documents that are at most the specified distance from the center point. * @param Optional|int $minDistance Distance in meters. Limits the results to those documents that are at least the specified distance from the center point. */ public function __construct( - Document|Serializable|stdClass|array $geometry, + Document|Serializable|GeometryInterface|stdClass|array $geometry, Optional|int $maxDistance = Optional::Undefined, Optional|int $minDistance = Optional::Undefined, ) { diff --git a/src/Builder/Query/NearSphereOperator.php b/src/Builder/Query/NearSphereOperator.php index c9759ee8a..ae511a8e8 100644 --- a/src/Builder/Query/NearSphereOperator.php +++ b/src/Builder/Query/NearSphereOperator.php @@ -10,7 +10,8 @@ use MongoDB\BSON\Serializable; use MongoDB\Builder\Encode; use MongoDB\Builder\Optional; -use MongoDB\Builder\Type\QueryInterface; +use MongoDB\Builder\Type\GeometryInterface; +use MongoDB\Builder\Type\QueryFilterInterface; use stdClass; /** @@ -18,13 +19,13 @@ * * @see https://www.mongodb.com/docs/manual/reference/operator/query/nearSphere/ */ -class NearSphereOperator implements QueryInterface +class NearSphereOperator implements QueryFilterInterface { public const NAME = '$nearSphere'; public const ENCODE = \MongoDB\Builder\Encode::Object; - /** @param Document|Serializable|array|stdClass $geometry */ - public Document|Serializable|stdClass|array $geometry; + /** @param Document|GeometryInterface|Serializable|array|stdClass $geometry */ + public Document|Serializable|GeometryInterface|stdClass|array $geometry; /** @param Optional|int $maxDistance Distance in meters. */ public Optional|int $maxDistance; @@ -33,12 +34,12 @@ class NearSphereOperator implements QueryInterface public Optional|int $minDistance; /** - * @param Document|Serializable|array|stdClass $geometry + * @param Document|GeometryInterface|Serializable|array|stdClass $geometry * @param Optional|int $maxDistance Distance in meters. * @param Optional|int $minDistance Distance in meters. Limits the results to those documents that are at least the specified distance from the center point. */ public function __construct( - Document|Serializable|stdClass|array $geometry, + Document|Serializable|GeometryInterface|stdClass|array $geometry, Optional|int $maxDistance = Optional::Undefined, Optional|int $minDistance = Optional::Undefined, ) { diff --git a/src/Builder/Query/NinOperator.php b/src/Builder/Query/NinOperator.php index af246dd8f..e5f34530d 100644 --- a/src/Builder/Query/NinOperator.php +++ b/src/Builder/Query/NinOperator.php @@ -8,7 +8,7 @@ use MongoDB\BSON\PackedArray; use MongoDB\Builder\Encode; -use MongoDB\Builder\Type\QueryInterface; +use MongoDB\Builder\Type\QueryFilterInterface; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Model\BSONArray; @@ -20,7 +20,7 @@ * * @see https://www.mongodb.com/docs/manual/reference/operator/query/nin/ */ -class NinOperator implements QueryInterface +class NinOperator implements QueryFilterInterface { public const NAME = '$nin'; public const ENCODE = \MongoDB\Builder\Encode::Single; diff --git a/src/Builder/Query/NotOperator.php b/src/Builder/Query/NotOperator.php index 50c8f96d2..cdaf287d6 100644 --- a/src/Builder/Query/NotOperator.php +++ b/src/Builder/Query/NotOperator.php @@ -7,29 +7,39 @@ namespace MongoDB\Builder\Query; use MongoDB\BSON\Document; +use MongoDB\BSON\Regex; use MongoDB\BSON\Serializable; use MongoDB\Builder\Encode; +use MongoDB\Builder\Type\QueryFilterInterface; use MongoDB\Builder\Type\QueryInterface; +use MongoDB\Builder\Type\QueryObject; use stdClass; +use function is_array; +use function is_object; + /** * Inverts the effect of a query expression and returns documents that do not match the query expression. * * @see https://www.mongodb.com/docs/manual/reference/operator/query/not/ */ -class NotOperator implements QueryInterface +class NotOperator implements QueryFilterInterface { public const NAME = '$not'; public const ENCODE = \MongoDB\Builder\Encode::Single; - /** @param Document|QueryInterface|Serializable|array|stdClass $expression */ - public Document|Serializable|QueryInterface|stdClass|array $expression; + /** @param Document|QueryInterface|Regex|Serializable|array|stdClass $expression */ + public Document|Regex|Serializable|QueryInterface|stdClass|array $expression; /** - * @param Document|QueryInterface|Serializable|array|stdClass $expression + * @param Document|QueryInterface|Regex|Serializable|array|stdClass $expression */ - public function __construct(Document|Serializable|QueryInterface|stdClass|array $expression) + public function __construct(Document|Regex|Serializable|QueryInterface|stdClass|array $expression) { + if (is_array($expression) || is_object($expression)) { + $expression = QueryObject::create($expression); + } + $this->expression = $expression; } } diff --git a/src/Builder/Query/PolygonOperator.php b/src/Builder/Query/PolygonOperator.php index fc24b1d9a..a138e0d61 100644 --- a/src/Builder/Query/PolygonOperator.php +++ b/src/Builder/Query/PolygonOperator.php @@ -8,7 +8,7 @@ use MongoDB\BSON\PackedArray; use MongoDB\Builder\Encode; -use MongoDB\Builder\Type\QueryInterface; +use MongoDB\Builder\Type\GeometryInterface; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Model\BSONArray; @@ -20,7 +20,7 @@ * * @see https://www.mongodb.com/docs/manual/reference/operator/query/polygon/ */ -class PolygonOperator implements QueryInterface +class PolygonOperator implements GeometryInterface { public const NAME = '$polygon'; public const ENCODE = \MongoDB\Builder\Encode::Single; diff --git a/src/Builder/Query/RegexOperator.php b/src/Builder/Query/RegexOperator.php index 170d9716d..137273eff 100644 --- a/src/Builder/Query/RegexOperator.php +++ b/src/Builder/Query/RegexOperator.php @@ -8,14 +8,14 @@ use MongoDB\BSON\Regex; use MongoDB\Builder\Encode; -use MongoDB\Builder\Type\QueryInterface; +use MongoDB\Builder\Type\QueryFilterInterface; /** * Selects documents where values match a specified regular expression. * * @see https://www.mongodb.com/docs/manual/reference/operator/query/regex/ */ -class RegexOperator implements QueryInterface +class RegexOperator implements QueryFilterInterface { public const NAME = '$regex'; public const ENCODE = \MongoDB\Builder\Encode::Single; diff --git a/src/Builder/Query/SizeOperator.php b/src/Builder/Query/SizeOperator.php index 6a02dc6e2..9a74be1f7 100644 --- a/src/Builder/Query/SizeOperator.php +++ b/src/Builder/Query/SizeOperator.php @@ -7,14 +7,14 @@ namespace MongoDB\Builder\Query; use MongoDB\Builder\Encode; -use MongoDB\Builder\Type\QueryInterface; +use MongoDB\Builder\Type\QueryFilterInterface; /** * Selects documents if the array field is a specified size. * * @see https://www.mongodb.com/docs/manual/reference/operator/query/size/ */ -class SizeOperator implements QueryInterface +class SizeOperator implements QueryFilterInterface { public const NAME = '$size'; public const ENCODE = \MongoDB\Builder\Encode::Single; diff --git a/src/Builder/Query/SliceOperator.php b/src/Builder/Query/SliceOperator.php index db58eecc8..b3a16c52a 100644 --- a/src/Builder/Query/SliceOperator.php +++ b/src/Builder/Query/SliceOperator.php @@ -8,14 +8,13 @@ use MongoDB\Builder\Encode; use MongoDB\Builder\Type\ProjectionInterface; -use MongoDB\Builder\Type\QueryInterface; /** * Limits the number of elements projected from an array. Supports skip and limit slices. * - * @see https://www.mongodb.com/docs/manual/reference/operator/query/slice/ + * @see https://www.mongodb.com/docs/manual/reference/operator/projection/slice/ */ -class SliceOperator implements QueryInterface, ProjectionInterface +class SliceOperator implements ProjectionInterface { public const NAME = '$slice'; public const ENCODE = \MongoDB\Builder\Encode::Array; diff --git a/src/Builder/Query/TypeOperator.php b/src/Builder/Query/TypeOperator.php index 17f0549ed..8d49c2a38 100644 --- a/src/Builder/Query/TypeOperator.php +++ b/src/Builder/Query/TypeOperator.php @@ -8,7 +8,7 @@ use MongoDB\BSON\PackedArray; use MongoDB\Builder\Encode; -use MongoDB\Builder\Type\QueryInterface; +use MongoDB\Builder\Type\QueryFilterInterface; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Model\BSONArray; @@ -20,7 +20,7 @@ * * @see https://www.mongodb.com/docs/manual/reference/operator/query/type/ */ -class TypeOperator implements QueryInterface +class TypeOperator implements QueryFilterInterface { public const NAME = '$type'; public const ENCODE = \MongoDB\Builder\Encode::Single; diff --git a/src/Builder/Stage.php b/src/Builder/Stage.php index fc553bcb6..7ab036fe1 100644 --- a/src/Builder/Stage.php +++ b/src/Builder/Stage.php @@ -2,7 +2,28 @@ namespace MongoDB\Builder; +use MongoDB\BSON\Document; +use MongoDB\BSON\Serializable; +use MongoDB\Builder\Stage\MatchStage; +use MongoDB\Builder\Type\QueryFilterInterface; +use MongoDB\Builder\Type\QueryInterface; +use stdClass; + enum Stage { - use Stage\FactoryTrait; + use Stage\FactoryTrait { + match as private generatedMatch; + } + + /** + * Filters the document stream to allow only matching documents to pass unmodified into the next pipeline stage. $match uses standard MongoDB queries. For each input document, outputs either one document (a match) or zero documents (no match). + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/match/ + * @param Document|QueryFilterInterface|QueryInterface|Serializable|array|stdClass $queries + */ + public static function match(QueryFilterInterface|QueryInterface|Serializable|array|bool|float|int|null|stdClass|string ...$queries): MatchStage + { + // Override the generated method to allow variadic arguments + return self::generatedMatch($queries); + } } diff --git a/src/Builder/Stage/GeoNearStage.php b/src/Builder/Stage/GeoNearStage.php index 00825b528..be2f9347a 100644 --- a/src/Builder/Stage/GeoNearStage.php +++ b/src/Builder/Stage/GeoNearStage.php @@ -14,9 +14,13 @@ use MongoDB\Builder\Expression\ResolvesToInt; use MongoDB\Builder\Optional; use MongoDB\Builder\Type\QueryInterface; +use MongoDB\Builder\Type\QueryObject; use MongoDB\Builder\Type\StageInterface; use stdClass; +use function is_array; +use function is_object; + /** * Returns an ordered stream of documents based on the proximity to a geospatial point. Incorporates the functionality of $match, $sort, and $limit for geospatial data. The output documents include an additional distance field and can include a location identifier field. * @@ -103,6 +107,10 @@ public function __construct( $this->key = $key; $this->maxDistance = $maxDistance; $this->minDistance = $minDistance; + if (is_array($query) || is_object($query)) { + $query = QueryObject::create($query); + } + $this->query = $query; $this->spherical = $spherical; } diff --git a/src/Builder/Stage/GraphLookupStage.php b/src/Builder/Stage/GraphLookupStage.php index adc137a03..b3d3fb2c9 100644 --- a/src/Builder/Stage/GraphLookupStage.php +++ b/src/Builder/Stage/GraphLookupStage.php @@ -21,10 +21,14 @@ use MongoDB\Builder\Optional; use MongoDB\Builder\Type\ExpressionInterface; use MongoDB\Builder\Type\QueryInterface; +use MongoDB\Builder\Type\QueryObject; use MongoDB\Builder\Type\StageInterface; use MongoDB\Model\BSONArray; use stdClass; +use function is_array; +use function is_object; + /** * Performs a recursive search on a collection. To each output document, adds a new array field that contains the traversal results of the recursive search for that document. * @@ -90,6 +94,10 @@ public function __construct( $this->as = $as; $this->maxDepth = $maxDepth; $this->depthField = $depthField; + if (is_array($restrictSearchWithMatch) || is_object($restrictSearchWithMatch)) { + $restrictSearchWithMatch = QueryObject::create($restrictSearchWithMatch); + } + $this->restrictSearchWithMatch = $restrictSearchWithMatch; } } diff --git a/src/Builder/Stage/MatchStage.php b/src/Builder/Stage/MatchStage.php index 64b49736c..eb0ddbb26 100644 --- a/src/Builder/Stage/MatchStage.php +++ b/src/Builder/Stage/MatchStage.php @@ -10,9 +10,13 @@ use MongoDB\BSON\Serializable; use MongoDB\Builder\Encode; use MongoDB\Builder\Type\QueryInterface; +use MongoDB\Builder\Type\QueryObject; use MongoDB\Builder\Type\StageInterface; use stdClass; +use function is_array; +use function is_object; + /** * Filters the document stream to allow only matching documents to pass unmodified into the next pipeline stage. $match uses standard MongoDB queries. For each input document, outputs either one document (a match) or zero documents (no match). * @@ -31,6 +35,10 @@ class MatchStage implements StageInterface */ public function __construct(Document|Serializable|QueryInterface|stdClass|array $query) { + if (is_array($query) || is_object($query)) { + $query = QueryObject::create($query); + } + $this->query = $query; } } diff --git a/src/Builder/Type/CombinedQueryFilter.php b/src/Builder/Type/CombinedQueryFilter.php new file mode 100644 index 000000000..6f075a878 --- /dev/null +++ b/src/Builder/Type/CombinedQueryFilter.php @@ -0,0 +1,28 @@ + $filters */ + public function __construct(public array $filters) + { + foreach ($filters as $filter) { + if (! $filter instanceof QueryFilterInterface && ! $filter instanceof Serializable && ! is_array($filter) && ! $filter instanceof stdClass) { + throw new InvalidArgumentException(sprintf('Expected filters to be a list of %s, %s, array or stdClass, %s given.', QueryFilterInterface::class, Document::class, get_debug_type($filter))); + } + } + } +} diff --git a/src/Builder/Type/GeometryInterface.php b/src/Builder/Type/GeometryInterface.php new file mode 100644 index 000000000..5ba0495f5 --- /dev/null +++ b/src/Builder/Type/GeometryInterface.php @@ -0,0 +1,8 @@ + $query) { + if ($query instanceof QueryInterface) { + if (! is_numeric($fieldPath)) { + throw new InvalidArgumentException(sprintf('Query operator "%s" cannot be used with a field path. Got "%s".', $query::NAME, $fieldPath)); + } + + if (! $query instanceof self) { + if (isset($seenQueryOperators[$query::NAME])) { + throw new InvalidArgumentException(sprintf('Query operator "%s" cannot be used multiple times in the same query.', $query::NAME)); + } + + $seenQueryOperators[$query::NAME] = true; + } + + $this->queries[] = $query; + continue; + } + + // Convert list of filters into $and + if (self::isListOfFilters($query)) { + if (count($query) === 1) { + $query = $query[0]; + } else { + $query = new CombinedQueryFilter($query); + } + } + + // Numbers are valid field paths, nothing to validate. + $this->queries[$fieldPath] = $query; + } + } + + private static function isListOfFilters(mixed $values): bool + { + if (! is_array($values) || ! array_is_list($values)) { + return false; + } + + foreach ($values as $value) { + if ($value instanceof QueryFilterInterface) { + return true; + } + } + + return false; + } +} diff --git a/src/Builder/architecture.md b/src/Builder/architecture.md index d9564d6c9..98bfa7c6d 100644 --- a/src/Builder/architecture.md +++ b/src/Builder/architecture.md @@ -88,83 +88,43 @@ result types would be rejected. We rely on the server will reject the query if the expression is not of the expected type. -# Query +# Query & Filter -The `query` are not `expression`. They are used in a `$match`, `$geoNear` or `$graphLookup` stage. -The query operators must implement the `QueryInterface` so that we can type hint the query parameter of the stages. +The `query` are used in a `$match`, `$geoNear` or `$graphLookup` stages and `$elemMatch` operator. +The `filter` are used compose query. A query is a map of field name to filter and/or a list of other queries. -Encoding of queries is different from encoding of expressions. -The query is encoded as a map of `fieldName: { $operator: value }` mixed with the specific operators `$and`, `$nor`, `$or`. - -We use named arguments to map each field name to a filter or a list of filters. +Queries can be create with `$and`, `$or`, `$nor` operators or with the `Query` factory class, but also with a specific +helper function `Query::query()` that accepts variadic named arguments. +We customize the `Stage::match()` factory function to shortcut the `Query::query()` function. ```php -$pipeline = [ - matchStage( - // $or, $and, $nor can't have a field name - // An exception will be thrown if a field name is used - Query::or( - foo: Query::eq(...), - Query::and( - bar: Query::eq(...), - baz: Query::eq(...), - ) +Stage::match( + // $or, $and, $nor can't have a field name + // An exception will be thrown if a field name is used + Query::or( + Query::query(foo: Query::eq(...)), + Query::and( + Query::query(bar: Query::eq(...)), + Query::query(baz: Query::eq(...)), ) - // Equality query on a field - foo: '...', - // Negate a query with $not - bar: Query::not(Query::gt(...)) - // Use array unpacking for complex field path - ...['foo.$.baz' => Query::eq(...)], - // Multiple filters on the same field - baz: [Query::lt(...), Query::gt(...)] ) -]; + // Equality query on a field + foo: '...', + // Negate a query with $not + bar: Query::not(Query::gt(...)) + // Use array unpacking for complex field path + ...['foo.$.baz' => Query::eq(...)], + // Multiple filters on the same field + baz: [Query::lt(...), Query::gt(...)] +) ``` -Strengths: -- Syntax is close to the MongoDB syntax - -Weaknesses: -- New syntax for multiple filters on the same field, risk of confusion with implicit `$eq` array value. -- Need to use array unpacking for complex field path - -**Alternative to discuss**: Use the same syntax for query and expression operators. - +Without the custom factory function, the queries would be written with a single root query object. ```php -$pipeline = [ - matchStage( - // $or, $and, $nor can't have a field name - // An exception will be thrown if a field name is used - Query::or( - Query::eq('foo', ...), - Query::and( - Query::eq('bar', ...), - Query::eq('baz', ...), - ) - ) - // Equality query on a field - Query::eq('foo', '...'), - // Negate a query with $not - Query::not(Query::gt('bar', ...)) - // Use array unpacking for complex field path - Query::eq('foo.$.baz', ...), - // Multiple filters on the same field - Query::lt('baz', ...), - Query::gt('baz', ...) - ) -]; +Stage::match(Query::or(Query::query(...), Query::query(...))); +Stage::match(Query::query(...)); ``` -Strengths: -- Same syntax as Java driver -- Same syntax for query and expression operators. But we cannot use the same factory function because value argument - don't have the same type (a value for query, an expression for expression operators - -Weaknesses: -- New syntax for multiple filters on the same field, risk of confusion with implicit `$eq` array value. -- Need to use array unpacking for complex field path - # Projection @@ -296,5 +256,8 @@ The `$regex` query requires a `MongoDB\BSON\Regex` object as parameter. We creat this object from a string. ```php -function regex(MongoDB\BSON\Regex|string $pattern, string $flags = ''): RegexQuery +function regex(MongoDB\BSON\Regex|string $pattern, string $flags = ''): RegexOperator ``` + +## `$` positional projection + diff --git a/tests/Builder/BuilderEncoderTest.php b/tests/Builder/BuilderEncoderTest.php index 3cee631a7..dd31e089d 100644 --- a/tests/Builder/BuilderEncoderTest.php +++ b/tests/Builder/BuilderEncoderTest.php @@ -27,7 +27,7 @@ class BuilderEncoderTest extends TestCase public function testPipeline(): void { $pipeline = new Pipeline( - Stage::match(object(author: 'dave')), + Stage::match(author: 'dave'), Stage::limit(1), ); @@ -59,8 +59,8 @@ public function testPerformCount(): void $pipeline = new Pipeline( Stage::match( Query::or( - object(score: [Query::gt(70), Query::lt(90)]), - object(views: Query::gte(1000)), + Query::query(score: [Query::gt(70), Query::lt(90)]), + Query::query(views: Query::gte(1000)), ), ), Stage::group( @@ -73,8 +73,7 @@ public function testPerformCount(): void [ '$match' => [ '$or' => [ - // same as ['score' => ['$gt' => 70, '$lt' => 90]], - ['score' => [['$gt' => 70], ['$lt' => 90]]], + ['score' => ['$gt' => 70, '$lt' => 90]], ['views' => ['$gte' => 1000]], ], ],