From c018fc4633b96d47e581cfc3a41473e3229d6ec8 Mon Sep 17 00:00:00 2001 From: shimon Date: Sun, 6 Jul 2025 15:47:57 +0300 Subject: [PATCH 01/23] bulkWrite function --- BULK_WRITE_README.md | 248 ++++++++++++++++++++++++++++++++++++++++ src/Client.php | 121 ++++++++++++++++++++ tests/BulkWriteTest.php | 152 ++++++++++++++++++++++++ 3 files changed, 521 insertions(+) create mode 100644 BULK_WRITE_README.md create mode 100644 tests/BulkWriteTest.php diff --git a/BULK_WRITE_README.md b/BULK_WRITE_README.md new file mode 100644 index 0000000..d7576c4 --- /dev/null +++ b/BULK_WRITE_README.md @@ -0,0 +1,248 @@ +# MongoDB Bulk Write Support + +This document describes the new `bulkWrite` function added to the Utopia MongoDB client, which allows you to perform multiple write operations in a single request. + +## Overview + +The `bulkWrite` function supports the following operation types: +- `insertOne` - Insert a single document +- `updateOne` - Update a single document +- `updateMany` - Update multiple documents +- `replaceOne` - Replace a single document +- `deleteOne` - Delete a single document +- `deleteMany` - Delete multiple documents + +## Function Signature + +```php +public function bulkWrite(string $collection, array $operations, array $options = []): stdClass +``` + +### Parameters + +- `$collection` (string): The name of the collection to perform operations on +- `$operations` (array): Array of operations to perform +- `$options` (array): Optional parameters for the bulk write operation + +### Options + +- `ordered` (boolean): Whether operations should be executed in order. Default: `true` + +## Usage Examples + +### Basic Bulk Write + +```php +$operations = [ + [ + 'operationType' => 'insertOne', + 'document' => [ + 'name' => 'John Doe', + 'email' => 'john@example.com', + 'age' => 30 + ] + ], + [ + 'operationType' => 'updateOne', + 'filter' => ['email' => 'john@example.com'], + 'update' => ['$set' => ['age' => 31]], + 'upsert' => false + ], + [ + 'operationType' => 'deleteOne', + 'filter' => ['email' => 'old@example.com'] + ] +]; + +$result = $client->bulkWrite('users', $operations); +``` + +### Insert Operations + +```php +$operations = [ + [ + 'operationType' => 'insertOne', + 'document' => [ + 'name' => 'Product 1', + 'price' => 100, + 'category' => 'electronics' + ] + ], + [ + 'operationType' => 'insertOne', + 'document' => [ + 'name' => 'Product 2', + 'price' => 200, + 'category' => 'clothing' + ] + ] +]; +``` + +### Update Operations + +```php +$operations = [ + [ + 'operationType' => 'updateOne', + 'filter' => ['email' => 'john@example.com'], + 'update' => ['$set' => ['age' => 31]], + 'upsert' => false + ], + [ + 'operationType' => 'updateMany', + 'filter' => ['age' => ['$lt' => 30]], + 'update' => ['$set' => ['status' => 'young']], + 'upsert' => false + ] +]; +``` + +### Replace Operations + +```php +$operations = [ + [ + 'operationType' => 'replaceOne', + 'filter' => ['email' => 'jane@example.com'], + 'replacement' => [ + 'name' => 'Jane Smith Updated', + 'email' => 'jane.updated@example.com', + 'age' => 26, + 'status' => 'updated' + ], + 'upsert' => false + ] +]; +``` + +### Delete Operations + +```php +$operations = [ + [ + 'operationType' => 'deleteOne', + 'filter' => ['email' => 'john@example.com'] + ], + [ + 'operationType' => 'deleteMany', + 'filter' => ['status' => 'inactive'] + ] +]; +``` + +### Ordered vs Unordered Operations + +#### Ordered Operations (Default) +Operations are executed in the order they appear in the array. If an operation fails, subsequent operations are not executed. + +```php +$result = $client->bulkWrite('users', $operations, [ + 'ordered' => true +]); +``` + +#### Unordered Operations +Operations are executed in parallel. If one operation fails, others may still succeed. + +```php +$result = $client->bulkWrite('users', $operations, [ + 'ordered' => false +]); +``` + +## Error Handling + +The function throws `Utopia\Mongo\Exception` for various error conditions: + +- Missing `operationType` in operation +- Missing required fields for specific operations +- Unsupported operation types +- MongoDB server errors + +## Operation Types Reference + +### insertOne +```php +[ + 'operationType' => 'insertOne', + 'document' => [ + 'field1' => 'value1', + 'field2' => 'value2' + ] +] +``` + +### updateOne +```php +[ + 'operationType' => 'updateOne', + 'filter' => ['field' => 'value'], + 'update' => ['$set' => ['field' => 'new_value']], + 'upsert' => false +] +``` + +### updateMany +```php +[ + 'operationType' => 'updateMany', + 'filter' => ['field' => ['$lt' => 100]], + 'update' => ['$set' => ['status' => 'updated']], + 'upsert' => false +] +``` + +### replaceOne +```php +[ + 'operationType' => 'replaceOne', + 'filter' => ['email' => 'user@example.com'], + 'replacement' => [ + 'name' => 'New Name', + 'email' => 'new@example.com' + ], + 'upsert' => false +] +``` + +### deleteOne +```php +[ + 'operationType' => 'deleteOne', + 'filter' => ['email' => 'user@example.com'] +] +``` + +### deleteMany +```php +[ + 'operationType' => 'deleteMany', + 'filter' => ['status' => 'inactive'] +] +``` + +## Performance Benefits + +Using `bulkWrite` instead of individual operations provides several benefits: + +1. **Reduced Network Overhead**: Multiple operations are sent in a single request +2. **Atomic Operations**: All operations succeed or fail together (when ordered) +3. **Better Performance**: Especially useful for large datasets +4. **Transaction Support**: Can be used within transactions + +## Testing + +Run the test file to see examples in action: + +```bash +php tests/BulkWriteTest.php +``` + +## Notes + +- The function automatically generates `_id` fields for insert operations if not provided +- Null values are automatically filtered out from documents +- The function follows the same patterns as existing methods in the client +- All operations are validated before being sent to MongoDB \ No newline at end of file diff --git a/src/Client.php b/src/Client.php index 2f719eb..522a98d 100644 --- a/src/Client.php +++ b/src/Client.php @@ -36,6 +36,7 @@ class Client public const COMMAND_AGGREGATE = "aggregate"; public const COMMAND_DISTINCT = "distinct"; public const COMMAND_MAP_REDUCE = "mapReduce"; + public const COMMAND_BULK_WRITE = "bulkWrite"; /** * Authentication for connection @@ -532,6 +533,125 @@ public function update(string $collection, array $where = [], array $updates = [ return $this; } + /** + * Bulk write operations. + * https://docs.mongodb.com/manual/reference/command/bulkWrite/#mongodb-dbcommand-dbcmd.bulkWrite + * + * @param string $collection + * @param array $operations Array of operations to perform + * @param array $options + * + * @return stdClass + * @throws Exception + */ + public function bulkWrite(string $collection, array $operations, array $options = []): stdClass + { + $bulkOperations = []; + + foreach ($operations as $operation) { + if (!isset($operation['operationType'])) { + throw new Exception('Each operation must have an operationType'); + } + + $operationType = $operation['operationType']; + $bulkOperation = []; + + switch ($operationType) { + case 'insertOne': + if (!isset($operation['document'])) { + throw new Exception('insertOne operation requires a document'); + } + + $docObj = new stdClass(); + foreach ($operation['document'] as $key => $value) { + if (\is_null($value)) { + continue; + } + $docObj->{$key} = $value; + } + $docObj->_id ??= new BSON\ObjectId(); + + $bulkOperation = [ + 'insertOne' => [ + 'document' => $docObj + ] + ]; + break; + + case 'updateOne': + case 'updateMany': + if (!isset($operation['filter']) || !isset($operation['update'])) { + throw new Exception($operationType . ' operation requires filter and update'); + } + + $cleanUpdates = []; + foreach ($operation['update'] as $k => $v) { + if (\is_null($v)) { + continue; + } + $cleanUpdates[$k] = $v; + } + + $bulkOperation = [ + $operationType => [ + 'filter' => $this->toObject($operation['filter']), + 'update' => $this->toObject($cleanUpdates), + 'upsert' => $operation['upsert'] ?? false + ] + ]; + break; + + case 'replaceOne': + if (!isset($operation['filter']) || !isset($operation['replacement'])) { + throw new Exception('replaceOne operation requires filter and replacement'); + } + + $cleanReplacement = []; + foreach ($operation['replacement'] as $k => $v) { + if (\is_null($v)) { + continue; + } + $cleanReplacement[$k] = $v; + } + + $bulkOperation = [ + 'replaceOne' => [ + 'filter' => $this->toObject($operation['filter']), + 'replacement' => $this->toObject($cleanReplacement), + 'upsert' => $operation['upsert'] ?? false + ] + ]; + break; + + case 'deleteOne': + case 'deleteMany': + if (!isset($operation['filter'])) { + throw new Exception($operationType . ' operation requires a filter'); + } + + $bulkOperation = [ + $operationType => [ + 'filter' => $this->toObject($operation['filter']) + ] + ]; + break; + + default: + throw new Exception('Unsupported operation type: ' . $operationType); + } + + $bulkOperations[] = $bulkOperation; + } + + return $this->query( + array_merge([ + self::COMMAND_BULK_WRITE => $collection, + 'operations' => $bulkOperations, + 'ordered' => $options['ordered'] ?? true + ], $options) + ); + } + /** * Insert, or update, a document/s. * https://docs.mongodb.com/manual/reference/command/update/#syntax @@ -782,4 +902,5 @@ private function cleanFilters($filters): array return $cleanedFilters; } + } diff --git a/tests/BulkWriteTest.php b/tests/BulkWriteTest.php new file mode 100644 index 0000000..dbabbb7 --- /dev/null +++ b/tests/BulkWriteTest.php @@ -0,0 +1,152 @@ +client = new Client( + 'test_db', + 'localhost', + 27017, + 'test_user', + 'test_password', + true + ); + } + + public function testBulkWrite() + { + try { + // Connect to MongoDB + $this->client->connect(); + + $collection = 'users'; + + // Example bulk write operations + $operations = [ + // Insert operations + [ + 'operationType' => 'insertOne', + 'document' => [ + 'name' => 'John Doe', + 'email' => 'john@example.com', + 'age' => 30 + ] + ], + [ + 'operationType' => 'insertOne', + 'document' => [ + 'name' => 'Jane Smith', + 'email' => 'jane@example.com', + 'age' => 25 + ] + ], + + // Update operations + [ + 'operationType' => 'updateOne', + 'filter' => ['email' => 'john@example.com'], + 'update' => ['$set' => ['age' => 31]], + 'upsert' => false + ], + [ + 'operationType' => 'updateMany', + 'filter' => ['age' => ['$lt' => 30]], + 'update' => ['$set' => ['status' => 'young']], + 'upsert' => false + ], + + // Replace operation + [ + 'operationType' => 'replaceOne', + 'filter' => ['email' => 'jane@example.com'], + 'replacement' => [ + 'name' => 'Jane Smith Updated', + 'email' => 'jane.updated@example.com', + 'age' => 26, + 'status' => 'updated' + ], + 'upsert' => false + ], + + // Delete operations + [ + 'operationType' => 'deleteOne', + 'filter' => ['email' => 'nonexistent@example.com'] + ], + [ + 'operationType' => 'deleteMany', + 'filter' => ['status' => 'old'] + ] + ]; + + // Execute bulk write with ordered operations (default) + $result = $this->client->bulkWrite($collection, $operations, [ + 'ordered' => true + ]); + + echo "Bulk write completed successfully!\n"; + echo "Result: " . json_encode($result, JSON_PRETTY_PRINT) . "\n"; + + } catch (Exception $e) { + echo "Error: " . $e->getMessage() . "\n"; + } + } + + public function testUnorderedBulkWrite() + { + try { + $collection = 'products'; + + $operations = [ + [ + 'operationType' => 'insertOne', + 'document' => [ + 'name' => 'Product 1', + 'price' => 100, + 'category' => 'electronics' + ] + ], + [ + 'operationType' => 'insertOne', + 'document' => [ + 'name' => 'Product 2', + 'price' => 200, + 'category' => 'clothing' + ] + ], + [ + 'operationType' => 'updateMany', + 'filter' => ['category' => 'electronics'], + 'update' => ['$set' => ['discount' => 0.1]], + 'upsert' => false + ] + ]; + + // Execute bulk write with unordered operations + $result = $this->client->bulkWrite($collection, $operations, [ + 'ordered' => false + ]); + + echo "Unordered bulk write completed successfully!\n"; + echo "Result: " . json_encode($result, JSON_PRETTY_PRINT) . "\n"; + + } catch (Exception $e) { + echo "Error: " . $e->getMessage() . "\n"; + } + } +} + +// Example usage +if (php_sapi_name() === 'cli') { + $test = new BulkWriteTest(); + $test->testBulkWrite(); + $test->testUnorderedBulkWrite(); +} \ No newline at end of file From 92008d90df18731ea87d4ef1387cddc3918912b9 Mon Sep 17 00:00:00 2001 From: shimon Date: Mon, 7 Jul 2025 18:19:20 +0300 Subject: [PATCH 02/23] Adding bulkUpsert workaround instad of bulkRrites --- BULK_WRITE_README.md | 248 ---------------------------------------- tests/BulkWriteTest.php | 152 ------------------------ 2 files changed, 400 deletions(-) delete mode 100644 BULK_WRITE_README.md delete mode 100644 tests/BulkWriteTest.php diff --git a/BULK_WRITE_README.md b/BULK_WRITE_README.md deleted file mode 100644 index d7576c4..0000000 --- a/BULK_WRITE_README.md +++ /dev/null @@ -1,248 +0,0 @@ -# MongoDB Bulk Write Support - -This document describes the new `bulkWrite` function added to the Utopia MongoDB client, which allows you to perform multiple write operations in a single request. - -## Overview - -The `bulkWrite` function supports the following operation types: -- `insertOne` - Insert a single document -- `updateOne` - Update a single document -- `updateMany` - Update multiple documents -- `replaceOne` - Replace a single document -- `deleteOne` - Delete a single document -- `deleteMany` - Delete multiple documents - -## Function Signature - -```php -public function bulkWrite(string $collection, array $operations, array $options = []): stdClass -``` - -### Parameters - -- `$collection` (string): The name of the collection to perform operations on -- `$operations` (array): Array of operations to perform -- `$options` (array): Optional parameters for the bulk write operation - -### Options - -- `ordered` (boolean): Whether operations should be executed in order. Default: `true` - -## Usage Examples - -### Basic Bulk Write - -```php -$operations = [ - [ - 'operationType' => 'insertOne', - 'document' => [ - 'name' => 'John Doe', - 'email' => 'john@example.com', - 'age' => 30 - ] - ], - [ - 'operationType' => 'updateOne', - 'filter' => ['email' => 'john@example.com'], - 'update' => ['$set' => ['age' => 31]], - 'upsert' => false - ], - [ - 'operationType' => 'deleteOne', - 'filter' => ['email' => 'old@example.com'] - ] -]; - -$result = $client->bulkWrite('users', $operations); -``` - -### Insert Operations - -```php -$operations = [ - [ - 'operationType' => 'insertOne', - 'document' => [ - 'name' => 'Product 1', - 'price' => 100, - 'category' => 'electronics' - ] - ], - [ - 'operationType' => 'insertOne', - 'document' => [ - 'name' => 'Product 2', - 'price' => 200, - 'category' => 'clothing' - ] - ] -]; -``` - -### Update Operations - -```php -$operations = [ - [ - 'operationType' => 'updateOne', - 'filter' => ['email' => 'john@example.com'], - 'update' => ['$set' => ['age' => 31]], - 'upsert' => false - ], - [ - 'operationType' => 'updateMany', - 'filter' => ['age' => ['$lt' => 30]], - 'update' => ['$set' => ['status' => 'young']], - 'upsert' => false - ] -]; -``` - -### Replace Operations - -```php -$operations = [ - [ - 'operationType' => 'replaceOne', - 'filter' => ['email' => 'jane@example.com'], - 'replacement' => [ - 'name' => 'Jane Smith Updated', - 'email' => 'jane.updated@example.com', - 'age' => 26, - 'status' => 'updated' - ], - 'upsert' => false - ] -]; -``` - -### Delete Operations - -```php -$operations = [ - [ - 'operationType' => 'deleteOne', - 'filter' => ['email' => 'john@example.com'] - ], - [ - 'operationType' => 'deleteMany', - 'filter' => ['status' => 'inactive'] - ] -]; -``` - -### Ordered vs Unordered Operations - -#### Ordered Operations (Default) -Operations are executed in the order they appear in the array. If an operation fails, subsequent operations are not executed. - -```php -$result = $client->bulkWrite('users', $operations, [ - 'ordered' => true -]); -``` - -#### Unordered Operations -Operations are executed in parallel. If one operation fails, others may still succeed. - -```php -$result = $client->bulkWrite('users', $operations, [ - 'ordered' => false -]); -``` - -## Error Handling - -The function throws `Utopia\Mongo\Exception` for various error conditions: - -- Missing `operationType` in operation -- Missing required fields for specific operations -- Unsupported operation types -- MongoDB server errors - -## Operation Types Reference - -### insertOne -```php -[ - 'operationType' => 'insertOne', - 'document' => [ - 'field1' => 'value1', - 'field2' => 'value2' - ] -] -``` - -### updateOne -```php -[ - 'operationType' => 'updateOne', - 'filter' => ['field' => 'value'], - 'update' => ['$set' => ['field' => 'new_value']], - 'upsert' => false -] -``` - -### updateMany -```php -[ - 'operationType' => 'updateMany', - 'filter' => ['field' => ['$lt' => 100]], - 'update' => ['$set' => ['status' => 'updated']], - 'upsert' => false -] -``` - -### replaceOne -```php -[ - 'operationType' => 'replaceOne', - 'filter' => ['email' => 'user@example.com'], - 'replacement' => [ - 'name' => 'New Name', - 'email' => 'new@example.com' - ], - 'upsert' => false -] -``` - -### deleteOne -```php -[ - 'operationType' => 'deleteOne', - 'filter' => ['email' => 'user@example.com'] -] -``` - -### deleteMany -```php -[ - 'operationType' => 'deleteMany', - 'filter' => ['status' => 'inactive'] -] -``` - -## Performance Benefits - -Using `bulkWrite` instead of individual operations provides several benefits: - -1. **Reduced Network Overhead**: Multiple operations are sent in a single request -2. **Atomic Operations**: All operations succeed or fail together (when ordered) -3. **Better Performance**: Especially useful for large datasets -4. **Transaction Support**: Can be used within transactions - -## Testing - -Run the test file to see examples in action: - -```bash -php tests/BulkWriteTest.php -``` - -## Notes - -- The function automatically generates `_id` fields for insert operations if not provided -- Null values are automatically filtered out from documents -- The function follows the same patterns as existing methods in the client -- All operations are validated before being sent to MongoDB \ No newline at end of file diff --git a/tests/BulkWriteTest.php b/tests/BulkWriteTest.php deleted file mode 100644 index dbabbb7..0000000 --- a/tests/BulkWriteTest.php +++ /dev/null @@ -1,152 +0,0 @@ -client = new Client( - 'test_db', - 'localhost', - 27017, - 'test_user', - 'test_password', - true - ); - } - - public function testBulkWrite() - { - try { - // Connect to MongoDB - $this->client->connect(); - - $collection = 'users'; - - // Example bulk write operations - $operations = [ - // Insert operations - [ - 'operationType' => 'insertOne', - 'document' => [ - 'name' => 'John Doe', - 'email' => 'john@example.com', - 'age' => 30 - ] - ], - [ - 'operationType' => 'insertOne', - 'document' => [ - 'name' => 'Jane Smith', - 'email' => 'jane@example.com', - 'age' => 25 - ] - ], - - // Update operations - [ - 'operationType' => 'updateOne', - 'filter' => ['email' => 'john@example.com'], - 'update' => ['$set' => ['age' => 31]], - 'upsert' => false - ], - [ - 'operationType' => 'updateMany', - 'filter' => ['age' => ['$lt' => 30]], - 'update' => ['$set' => ['status' => 'young']], - 'upsert' => false - ], - - // Replace operation - [ - 'operationType' => 'replaceOne', - 'filter' => ['email' => 'jane@example.com'], - 'replacement' => [ - 'name' => 'Jane Smith Updated', - 'email' => 'jane.updated@example.com', - 'age' => 26, - 'status' => 'updated' - ], - 'upsert' => false - ], - - // Delete operations - [ - 'operationType' => 'deleteOne', - 'filter' => ['email' => 'nonexistent@example.com'] - ], - [ - 'operationType' => 'deleteMany', - 'filter' => ['status' => 'old'] - ] - ]; - - // Execute bulk write with ordered operations (default) - $result = $this->client->bulkWrite($collection, $operations, [ - 'ordered' => true - ]); - - echo "Bulk write completed successfully!\n"; - echo "Result: " . json_encode($result, JSON_PRETTY_PRINT) . "\n"; - - } catch (Exception $e) { - echo "Error: " . $e->getMessage() . "\n"; - } - } - - public function testUnorderedBulkWrite() - { - try { - $collection = 'products'; - - $operations = [ - [ - 'operationType' => 'insertOne', - 'document' => [ - 'name' => 'Product 1', - 'price' => 100, - 'category' => 'electronics' - ] - ], - [ - 'operationType' => 'insertOne', - 'document' => [ - 'name' => 'Product 2', - 'price' => 200, - 'category' => 'clothing' - ] - ], - [ - 'operationType' => 'updateMany', - 'filter' => ['category' => 'electronics'], - 'update' => ['$set' => ['discount' => 0.1]], - 'upsert' => false - ] - ]; - - // Execute bulk write with unordered operations - $result = $this->client->bulkWrite($collection, $operations, [ - 'ordered' => false - ]); - - echo "Unordered bulk write completed successfully!\n"; - echo "Result: " . json_encode($result, JSON_PRETTY_PRINT) . "\n"; - - } catch (Exception $e) { - echo "Error: " . $e->getMessage() . "\n"; - } - } -} - -// Example usage -if (php_sapi_name() === 'cli') { - $test = new BulkWriteTest(); - $test->testBulkWrite(); - $test->testUnorderedBulkWrite(); -} \ No newline at end of file From ba4d7dc17f93e828908eeb840832b5d2ac66c247 Mon Sep 17 00:00:00 2001 From: shimon Date: Mon, 7 Jul 2025 18:33:54 +0300 Subject: [PATCH 03/23] bulkUpsert --- src/Client.php | 129 ++++++++----------------------------------------- 1 file changed, 21 insertions(+), 108 deletions(-) diff --git a/src/Client.php b/src/Client.php index 522a98d..b8b2a9f 100644 --- a/src/Client.php +++ b/src/Client.php @@ -534,122 +534,35 @@ public function update(string $collection, array $where = [], array $updates = [ } /** - * Bulk write operations. - * https://docs.mongodb.com/manual/reference/command/bulkWrite/#mongodb-dbcommand-dbcmd.bulkWrite + * Perform multiple upserts in a single update command (wire protocol batch). + * Each operation should have 'filter' and 'update' keys. * * @param string $collection - * @param array $operations Array of operations to perform + * @param array $operations * @param array $options - * - * @return stdClass + * @return self * @throws Exception */ - public function bulkWrite(string $collection, array $operations, array $options = []): stdClass + public function bulkUpsert(string $collection, array $operations, array $options = []): self { - $bulkOperations = []; - - foreach ($operations as $operation) { - if (!isset($operation['operationType'])) { - throw new Exception('Each operation must have an operationType'); - } - - $operationType = $operation['operationType']; - $bulkOperation = []; - - switch ($operationType) { - case 'insertOne': - if (!isset($operation['document'])) { - throw new Exception('insertOne operation requires a document'); - } - - $docObj = new stdClass(); - foreach ($operation['document'] as $key => $value) { - if (\is_null($value)) { - continue; - } - $docObj->{$key} = $value; - } - $docObj->_id ??= new BSON\ObjectId(); - - $bulkOperation = [ - 'insertOne' => [ - 'document' => $docObj - ] - ]; - break; - - case 'updateOne': - case 'updateMany': - if (!isset($operation['filter']) || !isset($operation['update'])) { - throw new Exception($operationType . ' operation requires filter and update'); - } - - $cleanUpdates = []; - foreach ($operation['update'] as $k => $v) { - if (\is_null($v)) { - continue; - } - $cleanUpdates[$k] = $v; - } - - $bulkOperation = [ - $operationType => [ - 'filter' => $this->toObject($operation['filter']), - 'update' => $this->toObject($cleanUpdates), - 'upsert' => $operation['upsert'] ?? false - ] - ]; - break; - - case 'replaceOne': - if (!isset($operation['filter']) || !isset($operation['replacement'])) { - throw new Exception('replaceOne operation requires filter and replacement'); - } - - $cleanReplacement = []; - foreach ($operation['replacement'] as $k => $v) { - if (\is_null($v)) { - continue; - } - $cleanReplacement[$k] = $v; - } - - $bulkOperation = [ - 'replaceOne' => [ - 'filter' => $this->toObject($operation['filter']), - 'replacement' => $this->toObject($cleanReplacement), - 'upsert' => $operation['upsert'] ?? false - ] - ]; - break; - - case 'deleteOne': - case 'deleteMany': - if (!isset($operation['filter'])) { - throw new Exception($operationType . ' operation requires a filter'); - } - - $bulkOperation = [ - $operationType => [ - 'filter' => $this->toObject($operation['filter']) - ] - ]; - break; - - default: - throw new Exception('Unsupported operation type: ' . $operationType); - } - - $bulkOperations[] = $bulkOperation; + $updates = []; + foreach ($operations as $op) { + $updates[] = [ + 'q' => $op['filter'], + 'u' => $op['update'], + 'upsert' => true, + ]; } - - return $this->query( - array_merge([ - self::COMMAND_BULK_WRITE => $collection, - 'operations' => $bulkOperations, - 'ordered' => $options['ordered'] ?? true - ], $options) + $this->query( + array_merge( + [ + 'update' => $collection, + 'updates' => $updates, + ], + $options + ) ); + return $this; } /** From 60f2c0079a491b79c61fdf7a6e2a31fe7a66ee79 Mon Sep 17 00:00:00 2001 From: shimon Date: Mon, 7 Jul 2025 18:35:26 +0300 Subject: [PATCH 04/23] bulkUpsert --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index b8b2a9f..5e53c07 100644 --- a/src/Client.php +++ b/src/Client.php @@ -36,7 +36,7 @@ class Client public const COMMAND_AGGREGATE = "aggregate"; public const COMMAND_DISTINCT = "distinct"; public const COMMAND_MAP_REDUCE = "mapReduce"; - public const COMMAND_BULK_WRITE = "bulkWrite"; + /** * Authentication for connection From 414d4d099386ba742d1620fe2a75afd6105ad611 Mon Sep 17 00:00:00 2001 From: shimon Date: Tue, 8 Jul 2025 20:47:22 +0300 Subject: [PATCH 05/23] added BulkUpsert test --- composer.json | 2 +- composer.lock | 457 +++++++++++++++++++------------------------- src/Client.php | 11 +- tests/MongoTest.php | 38 ++++ 4 files changed, 240 insertions(+), 268 deletions(-) diff --git a/composer.json b/composer.json index f4ef0dd..caf2163 100755 --- a/composer.json +++ b/composer.json @@ -30,7 +30,7 @@ "require": { "php": ">=8.0", "ext-mongodb": "*", - "mongodb/mongodb": "1.10.0" + "mongodb/mongodb": "^1.21" }, "require-dev": { "fakerphp/faker": "^1.14", diff --git a/composer.lock b/composer.lock index ec5f142..52cee61 100644 --- a/composer.lock +++ b/composer.lock @@ -4,98 +4,42 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f8b971c703d9d559265c484dd9d30fdd", + "content-hash": "104758a98a297851d8985a60cbfa65bc", "packages": [ - { - "name": "jean85/pretty-package-versions", - "version": "2.0.5", - "source": { - "type": "git", - "url": "https://github.com/Jean85/pretty-package-versions.git", - "reference": "ae547e455a3d8babd07b96966b17d7fd21d9c6af" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/ae547e455a3d8babd07b96966b17d7fd21d9c6af", - "reference": "ae547e455a3d8babd07b96966b17d7fd21d9c6af", - "shasum": "" - }, - "require": { - "composer-runtime-api": "^2.0.0", - "php": "^7.1|^8.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^2.17", - "jean85/composer-provided-replaced-stub-package": "^1.0", - "phpstan/phpstan": "^0.12.66", - "phpunit/phpunit": "^7.5|^8.5|^9.4", - "vimeo/psalm": "^4.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Jean85\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Alessandro Lai", - "email": "alessandro.lai85@gmail.com" - } - ], - "description": "A library to get pretty versions strings of installed dependencies", - "keywords": [ - "composer", - "package", - "release", - "versions" - ], - "support": { - "issues": "https://github.com/Jean85/pretty-package-versions/issues", - "source": "https://github.com/Jean85/pretty-package-versions/tree/2.0.5" - }, - "time": "2021-10-08T21:21:46+00:00" - }, { "name": "mongodb/mongodb", - "version": "1.10.0", + "version": "1.21.1", "source": { "type": "git", "url": "https://github.com/mongodb/mongo-php-library.git", - "reference": "b0bbd657f84219212487d01a8ffe93a789e1e488" + "reference": "37bc8df3a67ddf8380704a5ba5dbd00e92ec1f6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mongodb/mongo-php-library/zipball/b0bbd657f84219212487d01a8ffe93a789e1e488", - "reference": "b0bbd657f84219212487d01a8ffe93a789e1e488", + "url": "https://api.github.com/repos/mongodb/mongo-php-library/zipball/37bc8df3a67ddf8380704a5ba5dbd00e92ec1f6a", + "reference": "37bc8df3a67ddf8380704a5ba5dbd00e92ec1f6a", "shasum": "" }, "require": { - "ext-hash": "*", - "ext-json": "*", - "ext-mongodb": "^1.11.0", - "jean85/pretty-package-versions": "^1.2 || ^2.0.1", - "php": "^7.1 || ^8.0", - "symfony/polyfill-php80": "^1.19" + "composer-runtime-api": "^2.0", + "ext-mongodb": "^1.21.0", + "php": "^8.1", + "psr/log": "^1.1.4|^2|^3" + }, + "replace": { + "mongodb/builder": "*" }, "require-dev": { - "doctrine/coding-standard": "^9.0", - "squizlabs/php_codesniffer": "^3.6", - "symfony/phpunit-bridge": "^5.2" + "doctrine/coding-standard": "^12.0", + "phpunit/phpunit": "^10.5.35", + "rector/rector": "^1.2", + "squizlabs/php_codesniffer": "^3.7", + "vimeo/psalm": "6.5.*" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.10.x-dev" + "dev-master": "1.x-dev" } }, "autoload": { @@ -118,6 +62,10 @@ { "name": "Jeremy Mikola", "email": "jmikola@gmail.com" + }, + { + "name": "Jérôme Tamarelle", + "email": "jerome.tamarelle@mongodb.com" } ], "description": "MongoDB driver library", @@ -130,47 +78,37 @@ ], "support": { "issues": "https://github.com/mongodb/mongo-php-library/issues", - "source": "https://github.com/mongodb/mongo-php-library/tree/1.10.0" + "source": "https://github.com/mongodb/mongo-php-library/tree/1.21.1" }, - "time": "2021-10-20T22:22:37+00:00" + "time": "2025-02-28T17:24:20+00:00" }, { - "name": "symfony/polyfill-php80", - "version": "v1.28.0", + "name": "psr/log", + "version": "3.0.2", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5" + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5", - "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=8.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.28-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "dev-master": "3.x-dev" } }, "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, - "classmap": [ - "Resources/stubs" - ] + "Psr\\Log\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -178,44 +116,21 @@ ], "authors": [ { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", - "homepage": "https://symfony.com", + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" + "log", + "psr", + "psr-3" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0" + "source": "https://github.com/php-fig/log/tree/3.0.2" }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-09-11T13:17:53+00:00" } ], "packages-dev": [ @@ -291,16 +206,16 @@ }, { "name": "fakerphp/faker", - "version": "v1.23.0", + "version": "v1.24.1", "source": { "type": "git", "url": "https://github.com/FakerPHP/Faker.git", - "reference": "e3daa170d00fde61ea7719ef47bb09bb8f1d9b01" + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/e3daa170d00fde61ea7719ef47bb09bb8f1d9b01", - "reference": "e3daa170d00fde61ea7719ef47bb09bb8f1d9b01", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", "shasum": "" }, "require": { @@ -326,11 +241,6 @@ "ext-mbstring": "Required for multibyte Unicode string functionality." }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "v1.21-dev" - } - }, "autoload": { "psr-4": { "Faker\\": "src/Faker/" @@ -353,9 +263,9 @@ ], "support": { "issues": "https://github.com/FakerPHP/Faker/issues", - "source": "https://github.com/FakerPHP/Faker/tree/v1.23.0" + "source": "https://github.com/FakerPHP/Faker/tree/v1.24.1" }, - "time": "2023-06-12T08:44:38+00:00" + "time": "2024-11-21T13:46:39+00:00" }, { "name": "laravel/pint", @@ -425,16 +335,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.11.1", + "version": "1.13.3", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" + "reference": "faed855a7b5f4d4637717c2b3863e277116beb36" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/faed855a7b5f4d4637717c2b3863e277116beb36", + "reference": "faed855a7b5f4d4637717c2b3863e277116beb36", "shasum": "" }, "require": { @@ -442,11 +352,12 @@ }, "conflict": { "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3,<3.2.2" + "doctrine/common": "<2.13.3 || >=3 <3.2.2" }, "require-dev": { "doctrine/collections": "^1.6.8", "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, "type": "library", @@ -472,7 +383,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.3" }, "funding": [ { @@ -480,29 +391,31 @@ "type": "tidelift" } ], - "time": "2023-03-08T13:26:56+00:00" + "time": "2025-07-05T12:25:42+00:00" }, { "name": "nikic/php-parser", - "version": "v4.17.1", + "version": "v5.5.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d" + "reference": "ae59794362fe85e051a58ad36b289443f57be7a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", - "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ae59794362fe85e051a58ad36b289443f57be7a9", + "reference": "ae59794362fe85e051a58ad36b289443f57be7a9", "shasum": "" }, "require": { + "ext-ctype": "*", + "ext-json": "*", "ext-tokenizer": "*", - "php": ">=7.0" + "php": ">=7.4" }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^9.0" }, "bin": [ "bin/php-parse" @@ -510,7 +423,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.9-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -534,26 +447,27 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.17.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.5.0" }, - "time": "2023-08-13T19:53:39+00:00" + "time": "2025-05-31T08:24:38+00:00" }, { "name": "phar-io/manifest", - "version": "2.0.3", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + "reference": "54750ef60c58e43759730615a392c31c80e23176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", "shasum": "" }, "require": { "ext-dom": "*", + "ext-libxml": "*", "ext-phar": "*", "ext-xmlwriter": "*", "phar-io/version": "^3.0.1", @@ -594,9 +508,15 @@ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", "support": { "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.3" + "source": "https://github.com/phar-io/manifest/tree/2.0.4" }, - "time": "2021-07-20T11:28:43+00:00" + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" }, { "name": "phar-io/version", @@ -710,35 +630,35 @@ }, { "name": "phpunit/php-code-coverage", - "version": "9.2.27", + "version": "9.2.32", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "b0a88255cb70d52653d80c890bd7f38740ea50d1" + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/b0a88255cb70d52653d80c890bd7f38740ea50d1", - "reference": "b0a88255cb70d52653d80c890bd7f38740ea50d1", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/85402a822d1ecf1db1096959413d35e1c37cf1a5", + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.15", + "nikic/php-parser": "^4.19.1 || ^5.1.0", "php": ">=7.3", - "phpunit/php-file-iterator": "^3.0.3", - "phpunit/php-text-template": "^2.0.2", - "sebastian/code-unit-reverse-lookup": "^2.0.2", - "sebastian/complexity": "^2.0", - "sebastian/environment": "^5.1.2", - "sebastian/lines-of-code": "^1.0.3", - "sebastian/version": "^3.0.1", - "theseer/tokenizer": "^1.2.0" + "phpunit/php-file-iterator": "^3.0.6", + "phpunit/php-text-template": "^2.0.4", + "sebastian/code-unit-reverse-lookup": "^2.0.3", + "sebastian/complexity": "^2.0.3", + "sebastian/environment": "^5.1.5", + "sebastian/lines-of-code": "^1.0.4", + "sebastian/version": "^3.0.2", + "theseer/tokenizer": "^1.2.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^9.6" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -747,7 +667,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.2-dev" + "dev-main": "9.2.x-dev" } }, "autoload": { @@ -776,7 +696,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.27" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.32" }, "funding": [ { @@ -784,7 +704,7 @@ "type": "github" } ], - "time": "2023-07-26T13:44:30+00:00" + "time": "2024-08-22T04:23:01+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1029,45 +949,45 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.11", + "version": "9.6.23", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "810500e92855eba8a7a5319ae913be2da6f957b0" + "reference": "43d2cb18d0675c38bd44982a5d1d88f6d53d8d95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/810500e92855eba8a7a5319ae913be2da6f957b0", - "reference": "810500e92855eba8a7a5319ae913be2da6f957b0", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/43d2cb18d0675c38bd44982a5d1d88f6d53d8d95", + "reference": "43d2cb18d0675c38bd44982a5d1d88f6d53d8d95", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.3.1 || ^2", + "doctrine/instantiator": "^1.5.0 || ^2", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.10.1", - "phar-io/manifest": "^2.0.3", - "phar-io/version": "^3.0.2", + "myclabs/deep-copy": "^1.13.1", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.13", - "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-code-coverage": "^9.2.32", + "phpunit/php-file-iterator": "^3.0.6", "phpunit/php-invoker": "^3.1.1", - "phpunit/php-text-template": "^2.0.3", - "phpunit/php-timer": "^5.0.2", - "sebastian/cli-parser": "^1.0.1", - "sebastian/code-unit": "^1.0.6", + "phpunit/php-text-template": "^2.0.4", + "phpunit/php-timer": "^5.0.3", + "sebastian/cli-parser": "^1.0.2", + "sebastian/code-unit": "^1.0.8", "sebastian/comparator": "^4.0.8", - "sebastian/diff": "^4.0.3", - "sebastian/environment": "^5.1.3", - "sebastian/exporter": "^4.0.5", - "sebastian/global-state": "^5.0.1", - "sebastian/object-enumerator": "^4.0.3", - "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^3.2", + "sebastian/diff": "^4.0.6", + "sebastian/environment": "^5.1.5", + "sebastian/exporter": "^4.0.6", + "sebastian/global-state": "^5.0.7", + "sebastian/object-enumerator": "^4.0.4", + "sebastian/resource-operations": "^3.0.4", + "sebastian/type": "^3.2.1", "sebastian/version": "^3.0.2" }, "suggest": { @@ -1112,7 +1032,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.11" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.23" }, "funding": [ { @@ -1123,12 +1043,20 @@ "url": "https://github.com/sebastianbergmann", "type": "github" }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, { "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", "type": "tidelift" } ], - "time": "2023-08-19T07:10:56+00:00" + "time": "2025-05-02T06:40:34+00:00" }, { "name": "psr/container", @@ -1185,16 +1113,16 @@ }, { "name": "sebastian/cli-parser", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", "shasum": "" }, "require": { @@ -1229,7 +1157,7 @@ "homepage": "https://github.com/sebastianbergmann/cli-parser", "support": { "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2" }, "funding": [ { @@ -1237,7 +1165,7 @@ "type": "github" } ], - "time": "2020-09-28T06:08:49+00:00" + "time": "2024-03-02T06:27:43+00:00" }, { "name": "sebastian/code-unit", @@ -1426,20 +1354,20 @@ }, { "name": "sebastian/complexity", - "version": "2.0.2", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", "shasum": "" }, "require": { - "nikic/php-parser": "^4.7", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=7.3" }, "require-dev": { @@ -1471,7 +1399,7 @@ "homepage": "https://github.com/sebastianbergmann/complexity", "support": { "issues": "https://github.com/sebastianbergmann/complexity/issues", - "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" }, "funding": [ { @@ -1479,20 +1407,20 @@ "type": "github" } ], - "time": "2020-10-26T15:52:27+00:00" + "time": "2023-12-22T06:19:30+00:00" }, { "name": "sebastian/diff", - "version": "4.0.5", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131" + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131", - "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", "shasum": "" }, "require": { @@ -1537,7 +1465,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5" + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" }, "funding": [ { @@ -1545,7 +1473,7 @@ "type": "github" } ], - "time": "2023-05-07T05:35:17+00:00" + "time": "2024-03-02T06:30:58+00:00" }, { "name": "sebastian/environment", @@ -1612,16 +1540,16 @@ }, { "name": "sebastian/exporter", - "version": "4.0.5", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", - "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72", + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72", "shasum": "" }, "require": { @@ -1677,7 +1605,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6" }, "funding": [ { @@ -1685,20 +1613,20 @@ "type": "github" } ], - "time": "2022-09-14T06:03:37+00:00" + "time": "2024-03-02T06:33:00+00:00" }, { "name": "sebastian/global-state", - "version": "5.0.6", + "version": "5.0.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bde739e7565280bda77be70044ac1047bc007e34" + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bde739e7565280bda77be70044ac1047bc007e34", - "reference": "bde739e7565280bda77be70044ac1047bc007e34", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", "shasum": "" }, "require": { @@ -1741,7 +1669,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.6" + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.7" }, "funding": [ { @@ -1749,24 +1677,24 @@ "type": "github" } ], - "time": "2023-08-02T09:26:13+00:00" + "time": "2024-03-02T06:35:11+00:00" }, { "name": "sebastian/lines-of-code", - "version": "1.0.3", + "version": "1.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", "shasum": "" }, "require": { - "nikic/php-parser": "^4.6", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=7.3" }, "require-dev": { @@ -1798,7 +1726,7 @@ "homepage": "https://github.com/sebastianbergmann/lines-of-code", "support": { "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" }, "funding": [ { @@ -1806,7 +1734,7 @@ "type": "github" } ], - "time": "2020-11-28T06:42:11+00:00" + "time": "2023-12-22T06:20:34+00:00" }, { "name": "sebastian/object-enumerator", @@ -1985,16 +1913,16 @@ }, { "name": "sebastian/resource-operations", - "version": "3.0.3", + "version": "3.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", "shasum": "" }, "require": { @@ -2006,7 +1934,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -2027,8 +1955,7 @@ "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", "support": { - "issues": "https://github.com/sebastianbergmann/resource-operations/issues", - "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" }, "funding": [ { @@ -2036,7 +1963,7 @@ "type": "github" } ], - "time": "2020-09-28T06:45:17+00:00" + "time": "2024-03-14T16:00:52+00:00" }, { "name": "sebastian/type", @@ -2191,16 +2118,16 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v3.3.0", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", "shasum": "" }, "require": { @@ -2208,12 +2135,12 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "3.4-dev" - }, "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" } }, "autoload": { @@ -2238,7 +2165,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.3.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" }, "funding": [ { @@ -2254,20 +2181,20 @@ "type": "tidelift" } ], - "time": "2023-05-23T14:45:45+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "theseer/tokenizer", - "version": "1.2.1", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", "shasum": "" }, "require": { @@ -2296,7 +2223,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" }, "funding": [ { @@ -2304,7 +2231,7 @@ "type": "github" } ], - "time": "2021-07-28T10:34:58+00:00" + "time": "2024-03-03T12:36:25+00:00" } ], "aliases": [], @@ -2317,5 +2244,5 @@ "ext-mongodb": "*" }, "platform-dev": [], - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.2.0" } diff --git a/src/Client.php b/src/Client.php index 5e53c07..433a527 100644 --- a/src/Client.php +++ b/src/Client.php @@ -547,16 +547,23 @@ public function bulkUpsert(string $collection, array $operations, array $options { $updates = []; foreach ($operations as $op) { + $cleanUpdate = []; + foreach ($op['update'] as $k => $v) { + if (!is_null($v)) { + $cleanUpdate[$k] = $v; + } + } + $updates[] = [ 'q' => $op['filter'], - 'u' => $op['update'], + 'u' => $cleanUpdate, 'upsert' => true, ]; } $this->query( array_merge( [ - 'update' => $collection, + self::COMMAND_UPDATE => $collection, 'updates' => $updates, ], $options diff --git a/tests/MongoTest.php b/tests/MongoTest.php index 4040408..69d98d3 100644 --- a/tests/MongoTest.php +++ b/tests/MongoTest.php @@ -224,4 +224,42 @@ public function testExceedTimeException() ['maxTimeMS'=> 1] )->cursor->firstBatch ?? []; } + + + public function testBulkUpsert() + { + $this->getDatabase()->insert( + 'movies_upsert', + [ + 'name' => 'Gone with the wind', + 'language' => 'English', + 'country' => 'UK', + 'counter' => 1 + ] + ); + + $this->getDatabase()->bulkUpsert('movies_upsert', [ + [ + 'filter' => ['name' => 'Gone with the wind'], + 'update' => [ + '$set' => ['country' => 'USA'], + '$inc' => ['counter' => 3] + ] + ], + [ + 'filter' => ['name' => 'The godfather'], + 'update' => [ + '$set' => ['name' => 'The godfather 2', 'country' => 'USA', 'language' => 'English'] + ] + ], + ]); + + $documents = $this->getDatabase()->find('movies_upsert')->cursor->firstBatch ?? []; + var_dump($documents); + self::assertCount(2, $documents); + self::assertEquals(4, $documents[0]->counter); + self::assertEquals('The godfather 2', $documents[1]->name); + self::assertEquals('USA', $documents[1]->country); + self::assertEquals('English', $documents[1]->language); + } } From 0f42269d72f81fa412b0cb3f527eec00f279d123 Mon Sep 17 00:00:00 2001 From: shimon Date: Thu, 10 Jul 2025 12:49:18 +0300 Subject: [PATCH 06/23] Remove trailing whitespace in Client.php and MongoTest.php for code cleanliness --- src/Client.php | 1 - tests/MongoTest.php | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Client.php b/src/Client.php index 433a527..0a9b3d1 100644 --- a/src/Client.php +++ b/src/Client.php @@ -822,5 +822,4 @@ private function cleanFilters($filters): array return $cleanedFilters; } - } diff --git a/tests/MongoTest.php b/tests/MongoTest.php index 69d98d3..6ab8325 100644 --- a/tests/MongoTest.php +++ b/tests/MongoTest.php @@ -236,7 +236,7 @@ public function testBulkUpsert() 'country' => 'UK', 'counter' => 1 ] - ); + ); $this->getDatabase()->bulkUpsert('movies_upsert', [ [ From deaa0015b3cceb2c9a7883f1f12a157d32fa91de Mon Sep 17 00:00:00 2001 From: shimon Date: Mon, 14 Jul 2025 11:01:22 +0300 Subject: [PATCH 07/23] cleanup --- tests/MongoTest.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/MongoTest.php b/tests/MongoTest.php index 6ab8325..3f0b779 100644 --- a/tests/MongoTest.php +++ b/tests/MongoTest.php @@ -236,7 +236,7 @@ public function testBulkUpsert() 'country' => 'UK', 'counter' => 1 ] - ); + ); $this->getDatabase()->bulkUpsert('movies_upsert', [ [ @@ -255,7 +255,6 @@ public function testBulkUpsert() ]); $documents = $this->getDatabase()->find('movies_upsert')->cursor->firstBatch ?? []; - var_dump($documents); self::assertCount(2, $documents); self::assertEquals(4, $documents[0]->counter); self::assertEquals('The godfather 2', $documents[1]->name); From 088d890e1646a143ee95eb17e983e7944b3bcecd Mon Sep 17 00:00:00 2001 From: shimon Date: Sun, 20 Jul 2025 13:59:11 +0300 Subject: [PATCH 08/23] merged bulkUpsert() with upsert --- composer.json | 2 +- composer.lock | 95 ++++++++++++++++++++++++++++++++++++++++----- src/Client.php | 57 ++++++--------------------- tests/MongoTest.php | 9 +++-- 4 files changed, 103 insertions(+), 60 deletions(-) diff --git a/composer.json b/composer.json index caf2163..1ee3dcb 100755 --- a/composer.json +++ b/composer.json @@ -30,7 +30,7 @@ "require": { "php": ">=8.0", "ext-mongodb": "*", - "mongodb/mongodb": "^1.21" + "mongodb/mongodb": "2.1.0" }, "require-dev": { "fakerphp/faker": "^1.14", diff --git a/composer.lock b/composer.lock index 52cee61..0fa64a4 100644 --- a/composer.lock +++ b/composer.lock @@ -4,27 +4,28 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "104758a98a297851d8985a60cbfa65bc", + "content-hash": "6def0d090ef1b5df4a7d2af3ea71fe1c", "packages": [ { "name": "mongodb/mongodb", - "version": "1.21.1", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/mongodb/mongo-php-library.git", - "reference": "37bc8df3a67ddf8380704a5ba5dbd00e92ec1f6a" + "reference": "3bbe7ba9578724c7e1f47fcd17c881c0995baaad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mongodb/mongo-php-library/zipball/37bc8df3a67ddf8380704a5ba5dbd00e92ec1f6a", - "reference": "37bc8df3a67ddf8380704a5ba5dbd00e92ec1f6a", + "url": "https://api.github.com/repos/mongodb/mongo-php-library/zipball/3bbe7ba9578724c7e1f47fcd17c881c0995baaad", + "reference": "3bbe7ba9578724c7e1f47fcd17c881c0995baaad", "shasum": "" }, "require": { "composer-runtime-api": "^2.0", - "ext-mongodb": "^1.21.0", + "ext-mongodb": "^2.1", "php": "^8.1", - "psr/log": "^1.1.4|^2|^3" + "psr/log": "^1.1.4|^2|^3", + "symfony/polyfill-php85": "^1.32" }, "replace": { "mongodb/builder": "*" @@ -78,9 +79,9 @@ ], "support": { "issues": "https://github.com/mongodb/mongo-php-library/issues", - "source": "https://github.com/mongodb/mongo-php-library/tree/1.21.1" + "source": "https://github.com/mongodb/mongo-php-library/tree/2.1.0" }, - "time": "2025-02-28T17:24:20+00:00" + "time": "2025-05-23T10:48:05+00:00" }, { "name": "psr/log", @@ -131,6 +132,82 @@ "source": "https://github.com/php-fig/log/tree/3.0.2" }, "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "symfony/polyfill-php85", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php85.git", + "reference": "6fedf31ce4e3648f4ff5ca58bfd53127d38f05fd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/6fedf31ce4e3648f4ff5ca58bfd53127d38f05fd", + "reference": "6fedf31ce4e3648f4ff5ca58bfd53127d38f05fd", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php85\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php85/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-05-02T08:40:52+00:00" } ], "packages-dev": [ diff --git a/src/Client.php b/src/Client.php index 0a9b3d1..b9adf45 100644 --- a/src/Client.php +++ b/src/Client.php @@ -534,18 +534,20 @@ public function update(string $collection, array $where = [], array $updates = [ } /** - * Perform multiple upserts in a single update command (wire protocol batch). - * Each operation should have 'filter' and 'update' keys. + * Insert, or update, document(s) with support for bulk operations. + * https://docs.mongodb.com/manual/reference/command/update/#syntax * * @param string $collection - * @param array $operations + * @param array $operations Array of operations, each with 'filter' and 'update' keys * @param array $options + * * @return self * @throws Exception */ - public function bulkUpsert(string $collection, array $operations, array $options = []): self + public function upsert(string $collection, array $operations, array $options = []): self { $updates = []; + foreach ($operations as $op) { $cleanUpdate = []; foreach ($op['update'] as $k => $v) { @@ -554,12 +556,16 @@ public function bulkUpsert(string $collection, array $operations, array $options } } - $updates[] = [ + $updateOperation = [ 'q' => $op['filter'], 'u' => $cleanUpdate, 'upsert' => true, + 'multi' => isset($op['multi']) ? $op['multi'] : false, ]; + + $updates[] = $updateOperation; } + $this->query( array_merge( [ @@ -572,48 +578,7 @@ public function bulkUpsert(string $collection, array $operations, array $options return $this; } - /** - * Insert, or update, a document/s. - * https://docs.mongodb.com/manual/reference/command/update/#syntax - * - * @param string $collection - * @param array $where - * @param array $updates - * @param array $options - * - * @return Client - * @throws Exception - */ - - public function upsert(string $collection, array $where = [], array $updates = [], array $options = []): self - { - $cleanUpdates = []; - - foreach ($updates as $k => $v) { - if (\is_null($v)) { - continue; - } - $cleanUpdates[$k] = $v; - } - - - $this->query( - array_merge( - [ - 'update' => $collection, - 'updates' => [ - [ - 'q' => ['_uid' => $where['_uid']], - 'u' => ['$set' => $cleanUpdates], - ] - ], - ], - $options - ) - ); - return $this; - } /** * Find a document/s. diff --git a/tests/MongoTest.php b/tests/MongoTest.php index 3f0b779..1fa38b0 100644 --- a/tests/MongoTest.php +++ b/tests/MongoTest.php @@ -19,7 +19,7 @@ public static function getDatabase(): Client if (!is_null(self::$db)) { return self::$db; } - + $client = new Client('testing', 'mongo', 27017, 'root', 'example', false); $client->connect(); @@ -110,6 +110,7 @@ public function testCreateDocument() public function testCreateDocuments(): array { + $docs = $this->getDatabase()->insertMany( 'movies', [ @@ -226,7 +227,7 @@ public function testExceedTimeException() } - public function testBulkUpsert() + public function testUpsert() { $this->getDatabase()->insert( 'movies_upsert', @@ -237,8 +238,8 @@ public function testBulkUpsert() 'counter' => 1 ] ); - - $this->getDatabase()->bulkUpsert('movies_upsert', [ + + $this->getDatabase()->upsert('movies_upsert', [ [ 'filter' => ['name' => 'Gone with the wind'], 'update' => [ From 9c67b64f90f9737c2d10279554c7a856ca586f10 Mon Sep 17 00:00:00 2001 From: shimon Date: Mon, 21 Jul 2025 10:00:47 +0300 Subject: [PATCH 09/23] detect replicaSet --- src/Client.php | 23 +++++++++++++++++++++++ tests/MongoTest.php | 1 + 2 files changed, 24 insertions(+) diff --git a/src/Client.php b/src/Client.php index b9adf45..667bc44 100644 --- a/src/Client.php +++ b/src/Client.php @@ -787,4 +787,27 @@ private function cleanFilters($filters): array return $cleanedFilters; } + + private ?bool $replicaSet = null; + + /** + * Check if MongoDB is running as a replica set. + * + * @return bool True if this is a replica set, false if standalone + * @throws Exception + */ + public function isReplicaSet(): bool + { + if ($this->replicaSet !== null) { + return $this->replicaSet; + } + + $result = $this->query([ + 'isMaster' => 1, + ], 'admin'); + + $this->replicaSet = property_exists($result, 'setName'); + return $this->replicaSet; + } + } diff --git a/tests/MongoTest.php b/tests/MongoTest.php index 1fa38b0..9093ac1 100644 --- a/tests/MongoTest.php +++ b/tests/MongoTest.php @@ -262,4 +262,5 @@ public function testUpsert() self::assertEquals('USA', $documents[1]->country); self::assertEquals('English', $documents[1]->language); } + } From 8fe9907bf75fd46533a7bc0b9689540a72ce7690 Mon Sep 17 00:00:00 2001 From: shimon Date: Tue, 22 Jul 2025 09:45:35 +0300 Subject: [PATCH 10/23] update phpStan --- composer.json | 2 +- composer.lock | 23 +++++++++++------------ src/Client.php | 4 ++-- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/composer.json b/composer.json index 1ee3dcb..e1afb4a 100755 --- a/composer.json +++ b/composer.json @@ -37,6 +37,6 @@ "phpunit/phpunit": "^9.4", "swoole/ide-helper": "4.8.0", "laravel/pint": "1.2.*", - "phpstan/phpstan": "1.8.*" + "phpstan/phpstan": "2.1.*" } } diff --git a/composer.lock b/composer.lock index 0fa64a4..9cedc28 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "6def0d090ef1b5df4a7d2af3ea71fe1c", + "content-hash": "eb347e1bd2b8e17ec80b21c30ec72876", "packages": [ { "name": "mongodb/mongodb", @@ -648,20 +648,20 @@ }, { "name": "phpstan/phpstan", - "version": "1.8.11", + "version": "2.1.19", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "46e223dd68a620da18855c23046ddb00940b4014" + "reference": "473a8c30e450d87099f76313edcbb90852f9afdf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/46e223dd68a620da18855c23046ddb00940b4014", - "reference": "46e223dd68a620da18855c23046ddb00940b4014", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/473a8c30e450d87099f76313edcbb90852f9afdf", + "reference": "473a8c30e450d87099f76313edcbb90852f9afdf", "shasum": "" }, "require": { - "php": "^7.2|^8.0" + "php": "^7.4|^8.0" }, "conflict": { "phpstan/phpstan-shim": "*" @@ -686,8 +686,11 @@ "static analysis" ], "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.8.11" + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" }, "funding": [ { @@ -697,13 +700,9 @@ { "url": "https://github.com/phpstan", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" } ], - "time": "2022-10-24T15:45:13+00:00" + "time": "2025-07-21T19:58:24+00:00" }, { "name": "phpunit/php-code-coverage", diff --git a/src/Client.php b/src/Client.php index 667bc44..945af8e 100644 --- a/src/Client.php +++ b/src/Client.php @@ -137,7 +137,7 @@ public function query(array $command, ?string $db = null): stdClass|array|int '$db' => $db ?? $this->database, ]); - $sections = BSON\fromPHP($params); + $sections = BSON\fromPHPWithOptions($params); $message = pack('V*', 21 + strlen($sections), $this->id, 0, 2013, 0) . "\0" . $sections; return $this->send($message); } @@ -200,7 +200,7 @@ private function receive(): stdClass|array|int /** * @var stdClass $result */ - $result = BSON\toPHP(substr($res, 21, $responseLength - 21)); + $result = BSON\toPHPWithOptions(substr($res, 21, $responseLength - 21)); if (property_exists($result, "writeErrors")) { // Throws Utopia\Mongo\Exception From 0353429227a93bd12edcb3270cb146c6560a895f Mon Sep 17 00:00:00 2001 From: shimon Date: Tue, 22 Jul 2025 10:02:34 +0300 Subject: [PATCH 11/23] fix for codeQL --- src/Auth.php | 2 +- src/Client.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Auth.php b/src/Auth.php index 2cc1435..c2c988f 100644 --- a/src/Auth.php +++ b/src/Auth.php @@ -10,7 +10,7 @@ class Auth private string $secret; private string $authzid; private string $gs2Header; - private string $cnonce; + private ?string $cnonce = null; private string $firstMessageBare; private string $saltedPassword; private string $authMessage; diff --git a/src/Client.php b/src/Client.php index 945af8e..ac4d303 100644 --- a/src/Client.php +++ b/src/Client.php @@ -137,7 +137,7 @@ public function query(array $command, ?string $db = null): stdClass|array|int '$db' => $db ?? $this->database, ]); - $sections = BSON\fromPHPWithOptions($params); + $sections = MongoDB\BSON\fromPHP($params); $message = pack('V*', 21 + strlen($sections), $this->id, 0, 2013, 0) . "\0" . $sections; return $this->send($message); } @@ -200,7 +200,7 @@ private function receive(): stdClass|array|int /** * @var stdClass $result */ - $result = BSON\toPHPWithOptions(substr($res, 21, $responseLength - 21)); + $result = MongoDB\BSON\fromPHP(substr($res, 21, $responseLength - 21)); if (property_exists($result, "writeErrors")) { // Throws Utopia\Mongo\Exception From 348cf87abb86fcb9b5ca7d4e51cf6b3c24531cb8 Mon Sep 17 00:00:00 2001 From: shimon Date: Tue, 22 Jul 2025 12:07:31 +0300 Subject: [PATCH 12/23] updating ext to 2.1.1 --- Dockerfile-php-8.3 | 2 +- composer.json | 2 +- composer.lock | 4 ++-- src/Client.php | 17 ++++++++++------- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/Dockerfile-php-8.3 b/Dockerfile-php-8.3 index cb031e1..d65b1c8 100644 --- a/Dockerfile-php-8.3 +++ b/Dockerfile-php-8.3 @@ -13,7 +13,7 @@ RUN composer update --ignore-platform-reqs --optimize-autoloader \ FROM appwrite/utopia-base:php-8.3-0.1.0 as compile -ENV PHP_MONGO_VERSION=1.11.1 +ENV PHP_MONGO_VERSION=2.1.1 RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone diff --git a/composer.json b/composer.json index e1afb4a..8ca49e8 100755 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ }, "require": { "php": ">=8.0", - "ext-mongodb": "*", + "ext-mongodb": "2.1.1", "mongodb/mongodb": "2.1.0" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 9cedc28..24ee8e7 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "eb347e1bd2b8e17ec80b21c30ec72876", + "content-hash": "5c611cfa67003afadc6c7afff9166100", "packages": [ { "name": "mongodb/mongodb", @@ -2317,7 +2317,7 @@ "prefer-lowest": false, "platform": { "php": ">=8.0", - "ext-mongodb": "*" + "ext-mongodb": "^2.1" }, "platform-dev": [], "plugin-api-version": "2.2.0" diff --git a/src/Client.php b/src/Client.php index ac4d303..7947007 100644 --- a/src/Client.php +++ b/src/Client.php @@ -2,7 +2,8 @@ namespace Utopia\Mongo; -use MongoDB\BSON; +use MongoDB\BSON\Document; +use MongoDB\BSON\ObjectId; use Swoole\Client as SwooleClient; use Swoole\Coroutine\Client as CoroutineClient; use stdClass; @@ -88,6 +89,8 @@ public function __construct( 'authcid' => $user, 'secret' => Auth::encodeCredentials($user, $password) ]); + + } /** @@ -136,8 +139,8 @@ public function query(array $command, ?string $db = null): stdClass|array|int $params = array_merge($command, [ '$db' => $db ?? $this->database, ]); - - $sections = MongoDB\BSON\fromPHP($params); + + $sections = Document::fromPHP($params); $message = pack('V*', 21 + strlen($sections), $this->id, 0, 2013, 0) . "\0" . $sections; return $this->send($message); } @@ -200,8 +203,8 @@ private function receive(): stdClass|array|int /** * @var stdClass $result */ - $result = MongoDB\BSON\fromPHP(substr($res, 21, $responseLength - 21)); - + $bsonString = substr($res, 21, $responseLength - 21); + $result = Document::fromBSON($bsonString)->toPHP(); if (property_exists($result, "writeErrors")) { // Throws Utopia\Mongo\Exception throw new Exception( @@ -435,7 +438,7 @@ public function insert(string $collection, array $document, array $options = []) $docObj->{$key} = $value; } - $docObj->_id ??= new BSON\ObjectId(); + $docObj->_id ??= new ObjectId(); $this->query(array_merge([ self::COMMAND_INSERT => $collection, @@ -460,7 +463,7 @@ public function insertMany(string $collection, array $documents, array $options $docObj->{$key} = $value; } - $docObj->_id ??= new BSON\ObjectId(); + $docObj->_id ??= new ObjectId(); $docObjs[] = $docObj; } From a2435484a087748f9e81d50948e6db9d5ddc3b2f Mon Sep 17 00:00:00 2001 From: shimon Date: Tue, 22 Jul 2025 12:10:23 +0300 Subject: [PATCH 13/23] composer format --- src/Client.php | 7 ++----- tests/MongoTest.php | 8 +++----- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/Client.php b/src/Client.php index 7947007..d29bed7 100644 --- a/src/Client.php +++ b/src/Client.php @@ -89,8 +89,6 @@ public function __construct( 'authcid' => $user, 'secret' => Auth::encodeCredentials($user, $password) ]); - - } /** @@ -139,7 +137,7 @@ public function query(array $command, ?string $db = null): stdClass|array|int $params = array_merge($command, [ '$db' => $db ?? $this->database, ]); - + $sections = Document::fromPHP($params); $message = pack('V*', 21 + strlen($sections), $this->id, 0, 2013, 0) . "\0" . $sections; return $this->send($message); @@ -550,7 +548,7 @@ public function update(string $collection, array $where = [], array $updates = [ public function upsert(string $collection, array $operations, array $options = []): self { $updates = []; - + foreach ($operations as $op) { $cleanUpdate = []; foreach ($op['update'] as $k => $v) { @@ -812,5 +810,4 @@ public function isReplicaSet(): bool $this->replicaSet = property_exists($result, 'setName'); return $this->replicaSet; } - } diff --git a/tests/MongoTest.php b/tests/MongoTest.php index 9093ac1..aba9be3 100644 --- a/tests/MongoTest.php +++ b/tests/MongoTest.php @@ -19,7 +19,7 @@ public static function getDatabase(): Client if (!is_null(self::$db)) { return self::$db; } - + $client = new Client('testing', 'mongo', 27017, 'root', 'example', false); $client->connect(); @@ -110,7 +110,6 @@ public function testCreateDocument() public function testCreateDocuments(): array { - $docs = $this->getDatabase()->insertMany( 'movies', [ @@ -237,8 +236,8 @@ public function testUpsert() 'country' => 'UK', 'counter' => 1 ] - ); - + ); + $this->getDatabase()->upsert('movies_upsert', [ [ 'filter' => ['name' => 'Gone with the wind'], @@ -262,5 +261,4 @@ public function testUpsert() self::assertEquals('USA', $documents[1]->country); self::assertEquals('English', $documents[1]->language); } - } From a50588f81c35d7afc610cbd152baef1672bd633c Mon Sep 17 00:00:00 2001 From: shimon Date: Tue, 22 Jul 2025 12:14:09 +0300 Subject: [PATCH 14/23] phpStan fix --- src/Client.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Client.php b/src/Client.php index d29bed7..441d375 100644 --- a/src/Client.php +++ b/src/Client.php @@ -203,6 +203,9 @@ private function receive(): stdClass|array|int */ $bsonString = substr($res, 21, $responseLength - 21); $result = Document::fromBSON($bsonString)->toPHP(); + if (is_array($result)) { + $result = (object) $result; + } if (property_exists($result, "writeErrors")) { // Throws Utopia\Mongo\Exception throw new Exception( From f9dd89d517863091286c24d09e10e1d8c6c1b285 Mon Sep 17 00:00:00 2001 From: shimon Date: Tue, 22 Jul 2025 12:16:38 +0300 Subject: [PATCH 15/23] Update variable type annotation in Client.php from stdClass to object --- src/Client.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Client.php b/src/Client.php index 441d375..a80a5e7 100644 --- a/src/Client.php +++ b/src/Client.php @@ -198,8 +198,8 @@ private function receive(): stdClass|array|int (!isset($responseLength)) || ($receivedLength < $responseLength) ); - /** - * @var stdClass $result + /** + * @var object $result */ $bsonString = substr($res, 21, $responseLength - 21); $result = Document::fromBSON($bsonString)->toPHP(); From 101a7b20e02cf8211315f937c9564aecad028ac7 Mon Sep 17 00:00:00 2001 From: shimon Date: Tue, 22 Jul 2025 12:19:10 +0300 Subject: [PATCH 16/23] Remove unused variable annotation in Client.php --- src/Client.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Client.php b/src/Client.php index a80a5e7..59c53ff 100644 --- a/src/Client.php +++ b/src/Client.php @@ -198,9 +198,6 @@ private function receive(): stdClass|array|int (!isset($responseLength)) || ($receivedLength < $responseLength) ); - /** - * @var object $result - */ $bsonString = substr($res, 21, $responseLength - 21); $result = Document::fromBSON($bsonString)->toPHP(); if (is_array($result)) { From 00b99565dc43b13837c0321a00ad6fac581d2670 Mon Sep 17 00:00:00 2001 From: shimon Date: Tue, 22 Jul 2025 15:10:09 +0300 Subject: [PATCH 17/23] Update PHP MongoDB extension version to 2.1.1 in Dockerfile-php-8.0 --- Dockerfile-php-8.0 | 2 +- Dockerfile-php-8.1 | 2 +- Dockerfile-php-8.2 | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile-php-8.0 b/Dockerfile-php-8.0 index 0d3c013..e19c783 100755 --- a/Dockerfile-php-8.0 +++ b/Dockerfile-php-8.0 @@ -13,7 +13,7 @@ RUN composer update --ignore-platform-reqs --optimize-autoloader \ FROM appwrite/utopia-base:php-8.0-0.1.0 as compile -ENV PHP_MONGO_VERSION=1.11.1 +ENV PHP_MONGO_VERSION=2.1.1 RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone diff --git a/Dockerfile-php-8.1 b/Dockerfile-php-8.1 index 97bfddc..e459721 100644 --- a/Dockerfile-php-8.1 +++ b/Dockerfile-php-8.1 @@ -13,7 +13,7 @@ RUN composer update --ignore-platform-reqs --optimize-autoloader \ FROM appwrite/utopia-base:php-8.1-0.1.0 as compile -ENV PHP_MONGO_VERSION=1.11.1 +ENV PHP_MONGO_VERSION=2.1.1 RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone diff --git a/Dockerfile-php-8.2 b/Dockerfile-php-8.2 index 214e372..c3a0f73 100644 --- a/Dockerfile-php-8.2 +++ b/Dockerfile-php-8.2 @@ -13,7 +13,7 @@ RUN composer update --ignore-platform-reqs --optimize-autoloader \ FROM appwrite/utopia-base:php-8.2-0.1.0 as compile -ENV PHP_MONGO_VERSION=1.11.1 +ENV PHP_MONGO_VERSION=2.1.1 RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone From 0516d0d325ea54c093f18435e0b11795c1293328 Mon Sep 17 00:00:00 2001 From: shimon Date: Tue, 22 Jul 2025 16:34:49 +0300 Subject: [PATCH 18/23] Update GitHub Actions workflow to use PHP version 8.3 only --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 12495e9..8deed9a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php-versions: ['8.0', '8.1', '8.2', '8.3'] # add PHP versions as required + php-versions: ['8.3'] # add PHP versions as required steps: - name: Checkout repository From 12a267d6c83ce8102bf05eac87c04390b646143f Mon Sep 17 00:00:00 2001 From: shimon Date: Wed, 23 Jul 2025 08:51:59 +0300 Subject: [PATCH 19/23] Remove unsupported php versions --- Dockerfile-php-8.0 | 57 ---------------------------------------------- Dockerfile-php-8.1 | 57 ---------------------------------------------- Dockerfile-php-8.2 | 57 ---------------------------------------------- 3 files changed, 171 deletions(-) delete mode 100755 Dockerfile-php-8.0 delete mode 100644 Dockerfile-php-8.1 delete mode 100644 Dockerfile-php-8.2 diff --git a/Dockerfile-php-8.0 b/Dockerfile-php-8.0 deleted file mode 100755 index e19c783..0000000 --- a/Dockerfile-php-8.0 +++ /dev/null @@ -1,57 +0,0 @@ -FROM composer:2.0 as composer - -ARG TESTING=false -ENV TESTING=$TESTING - -WORKDIR /usr/local/src/ - -COPY composer.lock /usr/local/src/ -COPY composer.json /usr/local/src/ - -RUN composer update --ignore-platform-reqs --optimize-autoloader \ - --no-plugins --no-scripts --prefer-dist - -FROM appwrite/utopia-base:php-8.0-0.1.0 as compile - -ENV PHP_MONGO_VERSION=2.1.1 - -RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone - -RUN \ - apk update \ - && apk add --no-cache postgresql-libs postgresql-dev make automake autoconf gcc g++ git \ - && docker-php-ext-install opcache pgsql pdo_mysql pdo_pgsql \ - && rm -rf /var/cache/apk/* - -## MongoDB Extension -FROM compile AS mongodb -RUN \ - git clone --depth 1 --branch $PHP_MONGO_VERSION https://github.com/mongodb/mongo-php-driver.git \ - && cd mongo-php-driver \ - && git submodule update --init \ - && phpize \ - && ./configure \ - && make && make install - -FROM compile as final - -LABEL maintainer="team@appwrite.io" - -WORKDIR /usr/src/code - -RUN echo extension=mongodb.so >> /usr/local/etc/php/conf.d/mongodb.ini - -RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" - -RUN echo "opcache.enable_cli=1" >> $PHP_INI_DIR/php.ini - -RUN echo "memory_limit=1024M" >> $PHP_INI_DIR/php.ini - -COPY --from=composer /usr/local/src/vendor /usr/src/code/vendor -COPY --from=mongodb /usr/local/lib/php/extensions/no-debug-non-zts-20200930/mongodb.so /usr/local/lib/php/extensions/no-debug-non-zts-20200930/ - -# Add Source Code -COPY . /usr/src/code - -CMD [ "tail", "-f", "/dev/null" ] - diff --git a/Dockerfile-php-8.1 b/Dockerfile-php-8.1 deleted file mode 100644 index e459721..0000000 --- a/Dockerfile-php-8.1 +++ /dev/null @@ -1,57 +0,0 @@ -FROM composer:2.0 as composer - -ARG TESTING=false -ENV TESTING=$TESTING - -WORKDIR /usr/local/src/ - -COPY composer.lock /usr/local/src/ -COPY composer.json /usr/local/src/ - -RUN composer update --ignore-platform-reqs --optimize-autoloader \ - --no-plugins --no-scripts --prefer-dist - -FROM appwrite/utopia-base:php-8.1-0.1.0 as compile - -ENV PHP_MONGO_VERSION=2.1.1 - -RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone - -RUN \ - apk update \ - && apk add --no-cache postgresql-libs postgresql-dev make automake autoconf gcc g++ git \ - && docker-php-ext-install opcache pgsql pdo_mysql pdo_pgsql \ - && rm -rf /var/cache/apk/* - -## MongoDB Extension -FROM compile AS mongodb -RUN \ - git clone --depth 1 --branch $PHP_MONGO_VERSION https://github.com/mongodb/mongo-php-driver.git \ - && cd mongo-php-driver \ - && git submodule update --init \ - && phpize \ - && ./configure \ - && make && make install - -FROM compile as final - -LABEL maintainer="team@appwrite.io" - -WORKDIR /usr/src/code - -RUN echo extension=mongodb.so >> /usr/local/etc/php/conf.d/mongodb.ini - -RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" - -RUN echo "opcache.enable_cli=1" >> $PHP_INI_DIR/php.ini - -RUN echo "memory_limit=1024M" >> $PHP_INI_DIR/php.ini - -COPY --from=composer /usr/local/src/vendor /usr/src/code/vendor -COPY --from=mongodb /usr/local/lib/php/extensions/no-debug-non-zts-20210902/mongodb.so /usr/local/lib/php/extensions/no-debug-non-zts-20210902/ - -# Add Source Code -COPY . /usr/src/code - -CMD [ "tail", "-f", "/dev/null" ] - diff --git a/Dockerfile-php-8.2 b/Dockerfile-php-8.2 deleted file mode 100644 index c3a0f73..0000000 --- a/Dockerfile-php-8.2 +++ /dev/null @@ -1,57 +0,0 @@ -FROM composer:2.0 as composer - -ARG TESTING=false -ENV TESTING=$TESTING - -WORKDIR /usr/local/src/ - -COPY composer.lock /usr/local/src/ -COPY composer.json /usr/local/src/ - -RUN composer update --ignore-platform-reqs --optimize-autoloader \ - --no-plugins --no-scripts --prefer-dist - -FROM appwrite/utopia-base:php-8.2-0.1.0 as compile - -ENV PHP_MONGO_VERSION=2.1.1 - -RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone - -RUN \ - apk update \ - && apk add --no-cache postgresql-libs postgresql-dev make automake autoconf gcc g++ git \ - && docker-php-ext-install opcache pgsql pdo_mysql pdo_pgsql \ - && rm -rf /var/cache/apk/* - -## MongoDB Extension -FROM compile AS mongodb -RUN \ - git clone --depth 1 --branch $PHP_MONGO_VERSION https://github.com/mongodb/mongo-php-driver.git \ - && cd mongo-php-driver \ - && git submodule update --init \ - && phpize \ - && ./configure \ - && make && make install - -FROM compile as final - -LABEL maintainer="team@appwrite.io" - -WORKDIR /usr/src/code - -RUN echo extension=mongodb.so >> /usr/local/etc/php/conf.d/mongodb.ini - -RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" - -RUN echo "opcache.enable_cli=1" >> $PHP_INI_DIR/php.ini - -RUN echo "memory_limit=1024M" >> $PHP_INI_DIR/php.ini - -COPY --from=composer /usr/local/src/vendor /usr/src/code/vendor -COPY --from=mongodb /usr/local/lib/php/extensions/no-debug-non-zts-20220829/mongodb.so /usr/local/lib/php/extensions/no-debug-non-zts-20220829/ - -# Add Source Code -COPY . /usr/src/code - -CMD [ "tail", "-f", "/dev/null" ] - From 757e61da1beacc75423426771bc93b649a4ab000 Mon Sep 17 00:00:00 2001 From: Shimon Newman Date: Wed, 23 Jul 2025 16:50:50 +0300 Subject: [PATCH 20/23] Update src/Client.php Co-authored-by: Jake Barnby --- src/Client.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 59c53ff..3bf79e2 100644 --- a/src/Client.php +++ b/src/Client.php @@ -38,7 +38,6 @@ class Client public const COMMAND_DISTINCT = "distinct"; public const COMMAND_MAP_REDUCE = "mapReduce"; - /** * Authentication for connection */ From 03d1ba460813fa703039c2d177cdf6b61c08a19d Mon Sep 17 00:00:00 2001 From: shimon Date: Wed, 23 Jul 2025 17:11:19 +0300 Subject: [PATCH 21/23] Update return type of getCnonce method to nullable string and enhance MongoDB response documentation in Client.php --- src/Auth.php | 2 +- src/Client.php | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/Auth.php b/src/Auth.php index c2c988f..ca0aa59 100644 --- a/src/Auth.php +++ b/src/Auth.php @@ -218,7 +218,7 @@ public function verify(string $data): bool /** * @return string */ - public function getCnonce(): string + public function getCnonce(): ?string { return $this->cnonce; } diff --git a/src/Client.php b/src/Client.php index 3bf79e2..82fc566 100644 --- a/src/Client.php +++ b/src/Client.php @@ -197,6 +197,19 @@ private function receive(): stdClass|array|int (!isset($responseLength)) || ($receivedLength < $responseLength) ); + /* + * The first 21 bytes of the MongoDB wire protocol response consist of: + * - 16 bytes: Standard message header, which includes: + * - messageLength (4 bytes): Total size of the message, including the header. + * - requestID (4 bytes): Identifier for this message. + * - responseTo (4 bytes): The requestID that this message is responding to. + * - opCode (4 bytes): The operation code for the message type (e.g., OP_MSG). + * - 4 bytes: flagBits, which provide additional information about the message. + * - 1 byte: payloadType, indicating the type of the following payload (usually 0 for a BSON document). + * + * These 21 bytes are protocol metadata and precede the actual BSON-encoded document in the response. + */ + $bsonString = substr($res, 21, $responseLength - 21); $result = Document::fromBSON($bsonString)->toPHP(); if (is_array($result)) { @@ -555,10 +568,10 @@ public function upsert(string $collection, array $operations, array $options = [ $cleanUpdate[$k] = $v; } } - + $updateOperation = [ 'q' => $op['filter'], - 'u' => $cleanUpdate, + 'u' => $this->toObject($cleanUpdate), 'upsert' => true, 'multi' => isset($op['multi']) ? $op['multi'] : false, ]; From 7c8b6e90224bef1c1e582c9035cfd621bdec0561 Mon Sep 17 00:00:00 2001 From: shimon Date: Wed, 23 Jul 2025 17:12:52 +0300 Subject: [PATCH 22/23] Refactor comment formatting and clean up whitespace in Client.php --- src/Client.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Client.php b/src/Client.php index 82fc566..8dc5227 100644 --- a/src/Client.php +++ b/src/Client.php @@ -197,7 +197,7 @@ private function receive(): stdClass|array|int (!isset($responseLength)) || ($receivedLength < $responseLength) ); - /* + /* * The first 21 bytes of the MongoDB wire protocol response consist of: * - 16 bytes: Standard message header, which includes: * - messageLength (4 bytes): Total size of the message, including the header. @@ -568,7 +568,7 @@ public function upsert(string $collection, array $operations, array $options = [ $cleanUpdate[$k] = $v; } } - + $updateOperation = [ 'q' => $op['filter'], 'u' => $this->toObject($cleanUpdate), From 91a780b7a47f1f6869d8c819e927e20983c5ccfe Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 24 Jul 2025 02:47:53 +1200 Subject: [PATCH 23/23] Update src/Client.php --- src/Client.php | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Client.php b/src/Client.php index 8dc5227..04696c3 100644 --- a/src/Client.php +++ b/src/Client.php @@ -198,17 +198,17 @@ private function receive(): stdClass|array|int ); /* - * The first 21 bytes of the MongoDB wire protocol response consist of: - * - 16 bytes: Standard message header, which includes: - * - messageLength (4 bytes): Total size of the message, including the header. - * - requestID (4 bytes): Identifier for this message. - * - responseTo (4 bytes): The requestID that this message is responding to. - * - opCode (4 bytes): The operation code for the message type (e.g., OP_MSG). - * - 4 bytes: flagBits, which provide additional information about the message. - * - 1 byte: payloadType, indicating the type of the following payload (usually 0 for a BSON document). - * - * These 21 bytes are protocol metadata and precede the actual BSON-encoded document in the response. - */ + * The first 21 bytes of the MongoDB wire protocol response consist of: + * - 16 bytes: Standard message header, which includes: + * - messageLength (4 bytes): Total size of the message, including the header. + * - requestID (4 bytes): Identifier for this message. + * - responseTo (4 bytes): The requestID that this message is responding to. + * - opCode (4 bytes): The operation code for the message type (e.g., OP_MSG). + * - 4 bytes: flagBits, which provide additional information about the message. + * - 1 byte: payloadType, indicating the type of the following payload (usually 0 for a BSON document). + * + * These 21 bytes are protocol metadata and precede the actual BSON-encoded document in the response. + */ $bsonString = substr($res, 21, $responseLength - 21); $result = Document::fromBSON($bsonString)->toPHP();