diff --git a/psalm-baseline.xml b/psalm-baseline.xml
index 00c11f728..f51206d0d 100644
--- a/psalm-baseline.xml
+++ b/psalm-baseline.xml
@@ -417,8 +417,9 @@
-
-
+
+
+
@@ -448,6 +449,7 @@
+
@@ -458,6 +460,10 @@
+
+
+
+
@@ -466,9 +472,14 @@
+
+
+
+
+
diff --git a/src/Collection.php b/src/Collection.php
index 28133ff55..7769b9a5b 100644
--- a/src/Collection.php
+++ b/src/Collection.php
@@ -262,6 +262,7 @@ public function aggregate(array $pipeline, array $options = [])
*/
public function bulkWrite(array $operations, array $options = [])
{
+ $options = $this->inheritBuilderEncoder($options);
$options = $this->inheritWriteOptions($options);
$options = $this->inheritCodec($options);
@@ -286,6 +287,7 @@ public function bulkWrite(array $operations, array $options = [])
*/
public function count(array|object $filter = [], array $options = [])
{
+ $filter = $this->builderEncoder->encodeIfSupported($filter);
$options = $this->inheritReadOptions($options);
$operation = new Count($this->databaseName, $this->collectionName, $filter, $options);
@@ -307,6 +309,7 @@ public function count(array|object $filter = [], array $options = [])
*/
public function countDocuments(array|object $filter = [], array $options = [])
{
+ $filter = $this->builderEncoder->encodeIfSupported($filter);
$options = $this->inheritReadOptions($options);
$operation = new CountDocuments($this->databaseName, $this->collectionName, $filter, $options);
@@ -444,6 +447,7 @@ public function createSearchIndexes(array $indexes, array $options = []): array
*/
public function deleteMany(array|object $filter, array $options = [])
{
+ $filter = $this->builderEncoder->encodeIfSupported($filter);
$options = $this->inheritWriteOptions($options);
$operation = new DeleteMany($this->databaseName, $this->collectionName, $filter, $options);
@@ -465,6 +469,7 @@ public function deleteMany(array|object $filter, array $options = [])
*/
public function deleteOne(array|object $filter, array $options = [])
{
+ $filter = $this->builderEncoder->encodeIfSupported($filter);
$options = $this->inheritWriteOptions($options);
$operation = new DeleteOne($this->databaseName, $this->collectionName, $filter, $options);
@@ -487,6 +492,7 @@ public function deleteOne(array|object $filter, array $options = [])
*/
public function distinct(string $fieldName, array|object $filter = [], array $options = [])
{
+ $filter = $this->builderEncoder->encodeIfSupported($filter);
$options = $this->inheritReadOptions($options);
$options = $this->inheritTypeMap($options);
@@ -645,6 +651,7 @@ public function explain(Explainable $explainable, array $options = [])
*/
public function find(array|object $filter = [], array $options = [])
{
+ $filter = $this->builderEncoder->encodeIfSupported($filter);
$options = $this->inheritReadOptions($options);
$options = $this->inheritCodecOrTypeMap($options);
@@ -667,6 +674,7 @@ public function find(array|object $filter = [], array $options = [])
*/
public function findOne(array|object $filter = [], array $options = [])
{
+ $filter = $this->builderEncoder->encodeIfSupported($filter);
$options = $this->inheritReadOptions($options);
$options = $this->inheritCodecOrTypeMap($options);
@@ -692,6 +700,7 @@ public function findOne(array|object $filter = [], array $options = [])
*/
public function findOneAndDelete(array|object $filter, array $options = [])
{
+ $filter = $this->builderEncoder->encodeIfSupported($filter);
$options = $this->inheritWriteOptions($options);
$options = $this->inheritCodecOrTypeMap($options);
@@ -722,6 +731,7 @@ public function findOneAndDelete(array|object $filter, array $options = [])
*/
public function findOneAndReplace(array|object $filter, array|object $replacement, array $options = [])
{
+ $filter = $this->builderEncoder->encodeIfSupported($filter);
$options = $this->inheritWriteOptions($options);
$options = $this->inheritCodecOrTypeMap($options);
@@ -752,6 +762,7 @@ public function findOneAndReplace(array|object $filter, array|object $replacemen
*/
public function findOneAndUpdate(array|object $filter, array|object $update, array $options = [])
{
+ $filter = $this->builderEncoder->encodeIfSupported($filter);
$options = $this->inheritWriteOptions($options);
$options = $this->inheritCodecOrTypeMap($options);
@@ -1000,6 +1011,7 @@ public function rename(string $toCollectionName, ?string $toDatabaseName = null,
*/
public function replaceOne(array|object $filter, array|object $replacement, array $options = [])
{
+ $filter = $this->builderEncoder->encodeIfSupported($filter);
$options = $this->inheritWriteOptions($options);
$options = $this->inheritCodec($options);
@@ -1023,6 +1035,8 @@ public function replaceOne(array|object $filter, array|object $replacement, arra
*/
public function updateMany(array|object $filter, array|object $update, array $options = [])
{
+ $filter = $this->builderEncoder->encodeIfSupported($filter);
+ $update = $this->builderEncoder->encodeIfSupported($update);
$options = $this->inheritWriteOptions($options);
$operation = new UpdateMany($this->databaseName, $this->collectionName, $filter, $update, $options);
@@ -1045,6 +1059,8 @@ public function updateMany(array|object $filter, array|object $update, array $op
*/
public function updateOne(array|object $filter, array|object $update, array $options = [])
{
+ $filter = $this->builderEncoder->encodeIfSupported($filter);
+ $update = $this->builderEncoder->encodeIfSupported($update);
$options = $this->inheritWriteOptions($options);
$operation = new UpdateOne($this->databaseName, $this->collectionName, $filter, $update, $options);
@@ -1112,6 +1128,11 @@ public function withOptions(array $options = [])
return new Collection($this->manager, $this->databaseName, $this->collectionName, $options);
}
+ private function inheritBuilderEncoder(array $options): array
+ {
+ return ['builderEncoder' => $this->builderEncoder] + $options;
+ }
+
private function inheritCodec(array $options): array
{
// If the options contain a type map, don't inherit anything
diff --git a/src/Operation/BulkWrite.php b/src/Operation/BulkWrite.php
index 700ce3943..6da8a68b3 100644
--- a/src/Operation/BulkWrite.php
+++ b/src/Operation/BulkWrite.php
@@ -17,8 +17,10 @@
namespace MongoDB\Operation;
+use MongoDB\Builder\BuilderEncoder;
use MongoDB\BulkWriteResult;
use MongoDB\Codec\DocumentCodec;
+use MongoDB\Codec\Encoder;
use MongoDB\Driver\BulkWrite as Bulk;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Driver\Server;
@@ -94,6 +96,9 @@ class BulkWrite implements Executable
*
* Supported options for the bulk write operation:
*
+ * * builderEncoder (MongoDB\Builder\Encoder): Encoder for query and
+ * aggregation builders. If not given, the default encoder will be used.
+ *
* * bypassDocumentValidation (boolean): If true, allows the write to
* circumvent document level validation. The default is false.
*
@@ -137,6 +142,10 @@ public function __construct(private string $databaseName, private string $collec
$options += ['ordered' => true];
+ if (isset($options['builderEncoder']) && ! $options['builderEncoder'] instanceof Encoder) {
+ throw InvalidArgumentException::invalidType('"builderEncoder" option', $options['builderEncoder'], Encoder::class);
+ }
+
if (isset($options['bypassDocumentValidation']) && ! is_bool($options['bypassDocumentValidation'])) {
throw InvalidArgumentException::invalidType('"bypassDocumentValidation" option', $options['bypassDocumentValidation'], 'boolean');
}
@@ -169,7 +178,7 @@ public function __construct(private string $databaseName, private string $collec
unset($options['writeConcern']);
}
- $this->operations = $this->validateOperations($operations, $options['codec'] ?? null);
+ $this->operations = $this->validateOperations($operations, $options['codec'] ?? null, $options['builderEncoder'] ?? new BuilderEncoder());
$this->options = $options;
}
@@ -264,7 +273,7 @@ private function createExecuteOptions(): array
* @param array[] $operations
* @return array[]
*/
- private function validateOperations(array $operations, ?DocumentCodec $codec): array
+ private function validateOperations(array $operations, ?DocumentCodec $codec, Encoder $builderEncoder): array
{
foreach ($operations as $i => $operation) {
if (! is_array($operation)) {
@@ -298,6 +307,8 @@ private function validateOperations(array $operations, ?DocumentCodec $codec): a
case self::DELETE_MANY:
case self::DELETE_ONE:
+ $operations[$i][$type][0] = $builderEncoder->encodeIfSupported($args[0]);
+
if (! isset($args[1])) {
$args[1] = [];
}
@@ -317,6 +328,8 @@ private function validateOperations(array $operations, ?DocumentCodec $codec): a
break;
case self::REPLACE_ONE:
+ $operations[$i][$type][0] = $builderEncoder->encodeIfSupported($args[0]);
+
if (! isset($args[1]) && ! array_key_exists(1, $args)) {
throw new InvalidArgumentException(sprintf('Missing second argument for $operations[%d]["%s"]', $i, $type));
}
@@ -367,10 +380,14 @@ private function validateOperations(array $operations, ?DocumentCodec $codec): a
case self::UPDATE_MANY:
case self::UPDATE_ONE:
+ $operations[$i][$type][0] = $builderEncoder->encodeIfSupported($args[0]);
+
if (! isset($args[1]) && ! array_key_exists(1, $args)) {
throw new InvalidArgumentException(sprintf('Missing second argument for $operations[%d]["%s"]', $i, $type));
}
+ $operations[$i][$type][1] = $args[1] = $builderEncoder->encodeIfSupported($args[1]);
+
if ((! is_document($args[1]) || ! is_first_key_operator($args[1])) && ! is_pipeline($args[1])) {
throw new InvalidArgumentException(sprintf('Expected update operator(s) or non-empty pipeline for $operations[%d]["%s"][1]', $i, $type));
}
diff --git a/tests/Collection/BuilderCollectionFunctionalTest.php b/tests/Collection/BuilderCollectionFunctionalTest.php
new file mode 100644
index 000000000..15a89cda5
--- /dev/null
+++ b/tests/Collection/BuilderCollectionFunctionalTest.php
@@ -0,0 +1,250 @@
+collection->insertMany([['x' => 1], ['x' => 2], ['x' => 2]]);
+ }
+
+ public function testAggregate(): void
+ {
+ $this->markTestSkipped('Not supported yet');
+ }
+
+ public function testBulkWriteDeleteMany(): void
+ {
+ $result = $this->collection->bulkWrite([
+ [
+ 'deleteMany' => [
+ Query::query(x: Query::gt(1)),
+ ],
+ ],
+ ]);
+ $this->assertEquals(2, $result->getDeletedCount());
+ }
+
+ public function testBulkWriteDeleteOne(): void
+ {
+ $result = $this->collection->bulkWrite([
+ [
+ 'deleteOne' => [
+ Query::query(x: Query::eq(1)),
+ ],
+ ],
+ ]);
+ $this->assertEquals(1, $result->getDeletedCount());
+ }
+
+ public function testBulkWriteReplaceOne(): void
+ {
+ $result = $this->collection->bulkWrite([
+ [
+ 'replaceOne' => [
+ Query::query(x: Query::eq(1)),
+ ['x' => 3],
+ ],
+ ],
+ ]);
+ $this->assertEquals(1, $result->getModifiedCount());
+
+ $result = $this->collection->findOne(Query::query(x: Query::eq(3)));
+ $this->assertEquals(3, $result->x);
+ }
+
+ public function testBulkWriteUpdateMany(): void
+ {
+ $result = $this->collection->bulkWrite([
+ [
+ 'updateMany' => [
+ Query::query(x: Query::gt(1)),
+ // @todo Use Builder when update operators are supported by PHPLIB-1507
+ ['$set' => ['x' => 3]],
+ ],
+ ],
+ ]);
+ $this->assertEquals(2, $result->getModifiedCount());
+
+ $result = $this->collection->find(Query::query(x: Query::eq(3)))->toArray();
+ $this->assertCount(2, $result);
+ $this->assertEquals(3, $result[0]->x);
+ }
+
+ public function testBulkWriteUpdateOne(): void
+ {
+ $result = $this->collection->bulkWrite([
+ [
+ 'updateOne' => [
+ Query::query(x: Query::eq(1)),
+ // @todo Use Builder when update operators are supported by PHPLIB-1507
+ ['$set' => ['x' => 3]],
+ ],
+ ],
+ ]);
+
+ $this->assertEquals(1, $result->getModifiedCount());
+
+ $result = $this->collection->findOne(Query::query(x: Query::eq(3)));
+ $this->assertEquals(3, $result->x);
+ }
+
+ public function testCountDocuments(): void
+ {
+ $result = $this->collection->countDocuments(Query::query(x: Query::gt(1)));
+ $this->assertEquals(2, $result);
+ }
+
+ public function testDeleteMany(): void
+ {
+ $result = $this->collection->deleteMany(Query::query(x: Query::gt(1)));
+ $this->assertEquals(2, $result->getDeletedCount());
+ }
+
+ public function testDeleteOne(): void
+ {
+ $result = $this->collection->deleteOne(Query::query(x: Query::gt(1)));
+ $this->assertEquals(1, $result->getDeletedCount());
+ }
+
+ public function testDistinct(): void
+ {
+ $result = $this->collection->distinct('x', Query::query(x: Query::gt(1)));
+ $this->assertEquals([2], $result);
+ }
+
+ public function testFind(): void
+ {
+ $results = $this->collection->find(Query::query(x: Query::gt(1)))->toArray();
+ $this->assertCount(2, $results);
+ $this->assertEquals(2, $results[0]->x);
+ }
+
+ public function testFindOne(): void
+ {
+ $result = $this->collection->findOne(Query::query(x: Query::eq(1)));
+ $this->assertEquals(1, $result->x);
+ }
+
+ public function testFindOneAndDelete(): void
+ {
+ $result = $this->collection->findOneAndDelete(Query::query(x: Query::eq(1)));
+ $this->assertEquals(1, $result->x);
+
+ $result = $this->collection->find()->toArray();
+ $this->assertCount(2, $result);
+ }
+
+ public function testFindOneAndReplace(): void
+ {
+ $this->collection->insertOne(['x' => 1]);
+
+ $result = $this->collection->findOneAndReplace(
+ Query::query(x: Query::lt(2)),
+ ['x' => 3],
+ );
+ $this->assertEquals(1, $result->x);
+
+ $result = $this->collection->findOne(Query::query(x: Query::eq(3)));
+ $this->assertEquals(3, $result->x);
+ }
+
+ public function testFindOneAndUpdate(): void
+ {
+ $result = $this->collection->findOneAndUpdate(
+ Query::query(x: Query::lt(2)),
+ // @todo Use Builder when update operators are supported by PHPLIB-1507
+ ['$set' => ['x' => 3]],
+ );
+ $this->assertEquals(1, $result->x);
+
+ $result = $this->collection->findOne(Query::query(x: Query::eq(3)));
+ $this->assertEquals(3, $result->x);
+ }
+
+ public function testReplaceOne(): void
+ {
+ $this->collection->insertOne(['x' => 1]);
+
+ $result = $this->collection->replaceOne(
+ Query::query(x: Query::lt(2)),
+ ['x' => 3],
+ );
+ $this->assertEquals(1, $result->getModifiedCount());
+
+ $result = $this->collection->findOne(Query::query(x: Query::eq(3)));
+ $this->assertEquals(3, $result->x);
+ }
+
+ public function testUpdateOne(): void
+ {
+ $this->collection->insertOne(['x' => 1]);
+
+ $result = $this->collection->updateOne(
+ Query::query(x: Query::lt(2)),
+ // @todo Use Builder when update operators are supported by PHPLIB-1507
+ ['$set' => ['x' => 3]],
+ );
+ $this->assertEquals(1, $result->getModifiedCount());
+
+ $result = $this->collection->findOne(Query::query(x: Query::eq(3)));
+ $this->assertEquals(3, $result->x);
+ }
+
+ public function testUpdateWithPipeline(): void
+ {
+ $this->skipIfServerVersion('<', '4.2.0', 'Pipeline-style updates are not supported');
+
+ $result = $this->collection->updateOne(
+ Query::query(x: Query::lt(2)),
+ new Pipeline(
+ Stage::set(x: 3),
+ ),
+ );
+
+ $this->assertEquals(1, $result->getModifiedCount());
+ }
+
+ public function testUpdateMany(): void
+ {
+ $result = $this->collection->updateMany(
+ Query::query(x: Query::gt(1)),
+ // @todo Use Builder when update operators are supported by PHPLIB-1507
+ ['$set' => ['x' => 3]],
+ );
+ $this->assertEquals(2, $result->getModifiedCount());
+
+ $result = $this->collection->find(Query::query(x: Query::eq(3)))->toArray();
+ $this->assertCount(2, $result);
+ $this->assertEquals(3, $result[0]->x);
+ }
+
+ public function testUpdateManyWithPipeline(): void
+ {
+ $this->skipIfServerVersion('<', '4.2.0', 'Pipeline-style updates are not supported');
+
+ $result = $this->collection->updateMany(
+ Query::query(x: Query::gt(1)),
+ new Pipeline(
+ Stage::set(x: 3),
+ ),
+ );
+ $this->assertEquals(2, $result->getModifiedCount());
+
+ $result = $this->collection->find(Query::query(x: Query::eq(3)))->toArray();
+ $this->assertCount(2, $result);
+ $this->assertEquals(3, $result[0]->x);
+ }
+
+ public function testWatch(): void
+ {
+ $this->markTestSkipped('Not supported yet');
+ }
+}