diff --git a/psalm-baseline.xml b/psalm-baseline.xml
index a6dd2d5bf..5d728d764 100644
--- a/psalm-baseline.xml
+++ b/psalm-baseline.xml
@@ -450,95 +450,16 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
@@ -546,10 +467,23 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
diff --git a/src/Collection.php b/src/Collection.php
index 04a61981c..1f1e55b79 100644
--- a/src/Collection.php
+++ b/src/Collection.php
@@ -77,6 +77,7 @@
use function is_array;
use function strlen;
+/** @psalm-import-type OperationType from BulkWrite */
class Collection
{
private const DEFAULT_TYPE_MAP = [
@@ -254,8 +255,8 @@ public function aggregate(array|Pipeline $pipeline, array $options = []): Cursor
* Executes multiple write operations.
*
* @see BulkWrite::__construct() for supported options
- * @param array[] $operations List of write operations
- * @param array $options Command options
+ * @psalm-param list $operations List of write operations
+ * @param array $options Command options
* @throws UnsupportedException if options are not supported by the selected server
* @throws InvalidArgumentException for parameter/option parsing errors
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
diff --git a/src/Operation/BulkWrite.php b/src/Operation/BulkWrite.php
index dc6dbb4c7..a4bfde99a 100644
--- a/src/Operation/BulkWrite.php
+++ b/src/Operation/BulkWrite.php
@@ -45,6 +45,9 @@
* Operation for executing multiple write operations.
*
* @see \MongoDB\Collection::bulkWrite()
+ *
+ * @psalm-type Document = object|array
+ * @psalm-type OperationType = array{deleteMany: array{0: Document, 1?: array}}|array{deleteOne: array{0: Document, 1?: array}}|array{insertOne: array{0: Document}}|array{replaceOne: array{0: Document, 1: Document, 2?: array}}|array{updateMany: array{0: Document, 1: Document, 2?: array}}|array{updateOne: array{0: Document, 1: Document, 2?: array}}
*/
final class BulkWrite
{
@@ -55,7 +58,7 @@ final class BulkWrite
public const UPDATE_MANY = 'updateMany';
public const UPDATE_ONE = 'updateOne';
- /** @var array[] */
+ /** @psalm-var list */
private array $operations;
private array $options;
@@ -132,10 +135,11 @@ final class BulkWrite
*
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
*
- * @param string $databaseName Database name
- * @param string $collectionName Collection name
- * @param array[] $operations List of write operations
- * @param array $options Command options
+ * @param string $databaseName Database name
+ * @param string $collectionName Collection name
+ * @param array $operations List of write operations
+ * @psalm-param list $operations
+ * @param array $options Command options
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function __construct(private string $databaseName, private string $collectionName, array $operations, array $options = [])
@@ -276,12 +280,12 @@ private function createExecuteOptions(): array
}
/**
- * @param array[] $operations
- * @return array[]
+ * @psalm-param list $operations
+ * @psalm-return list
*/
private function validateOperations(array $operations, ?DocumentCodec $codec, Encoder $builderEncoder): array
{
- foreach ($operations as $i => $operation) {
+ foreach ($operations as $i => &$operation) {
if (! is_array($operation)) {
throw InvalidArgumentException::invalidType(sprintf('$operations[%d]', $i), $operation, 'array');
}
@@ -306,14 +310,14 @@ private function validateOperations(array $operations, ?DocumentCodec $codec, En
// $args[0] was already validated above. Since DocumentCodec::encode will always return a Document
// instance, there is no need to re-validate the returned value here.
if ($codec) {
- $operations[$i][$type][0] = $codec->encode($args[0]);
+ $operation[$type][0] = $codec->encode($args[0]);
}
break;
case self::DELETE_MANY:
case self::DELETE_ONE:
- $operations[$i][$type][0] = $builderEncoder->encodeIfSupported($args[0]);
+ $operation[$type][0] = $builderEncoder->encodeIfSupported($args[0]);
if (! isset($args[1])) {
$args[1] = [];
@@ -329,19 +333,19 @@ private function validateOperations(array $operations, ?DocumentCodec $codec, En
throw InvalidArgumentException::expectedDocumentType(sprintf('$operations[%d]["%s"][1]["collation"]', $i, $type), $args[1]['collation']);
}
- $operations[$i][$type][1] = $args[1];
+ $operation[$type][1] = $args[1];
break;
case self::REPLACE_ONE:
- $operations[$i][$type][0] = $builderEncoder->encodeIfSupported($args[0]);
+ $operation[$type][0] = $builderEncoder->encodeIfSupported($args[0]);
if (! isset($args[1]) && ! array_key_exists(1, $args)) {
throw new InvalidArgumentException(sprintf('Missing second argument for $operations[%d]["%s"]', $i, $type));
}
if ($codec) {
- $operations[$i][$type][1] = $codec->encode($args[1]);
+ $operation[$type][1] = $codec->encode($args[1]);
}
if (! is_document($args[1])) {
@@ -384,19 +388,19 @@ private function validateOperations(array $operations, ?DocumentCodec $codec, En
throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]["upsert"]', $i, $type), $args[2]['upsert'], 'boolean');
}
- $operations[$i][$type][2] = $args[2];
+ $operation[$type][2] = $args[2];
break;
case self::UPDATE_MANY:
case self::UPDATE_ONE:
- $operations[$i][$type][0] = $builderEncoder->encodeIfSupported($args[0]);
+ $operation[$type][0] = $builderEncoder->encodeIfSupported($args[0]);
if (! isset($args[1]) && ! array_key_exists(1, $args)) {
throw new InvalidArgumentException(sprintf('Missing second argument for $operations[%d]["%s"]', $i, $type));
}
- $operations[$i][$type][1] = $args[1] = $builderEncoder->encodeIfSupported($args[1]);
+ $operation[$type][1] = $args[1] = $builderEncoder->encodeIfSupported($args[1]);
if ((! is_document($args[1]) || ! is_first_key_operator($args[1])) && ! is_pipeline($args[1])) {
throw new InvalidArgumentException(sprintf('Expected update operator(s) or non-empty pipeline for $operations[%d]["%s"][1]', $i, $type));
@@ -433,7 +437,7 @@ private function validateOperations(array $operations, ?DocumentCodec $codec, En
throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]["upsert"]', $i, $type), $args[2]['upsert'], 'boolean');
}
- $operations[$i][$type][2] = $args[2];
+ $operation[$type][2] = $args[2];
break;
diff --git a/tests/Operation/BulkWriteTest.php b/tests/Operation/BulkWriteTest.php
index 30d5c6374..58df1493f 100644
--- a/tests/Operation/BulkWriteTest.php
+++ b/tests/Operation/BulkWriteTest.php
@@ -41,6 +41,15 @@ public function testMultipleOperationsInOneElement(): void
]);
}
+ public function testEmptyOperation(): void
+ {
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('Expected one element in $operation[0], actually: 0');
+ new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), [
+ [],
+ ]);
+ }
+
public function testUnknownOperation(): void
{
$this->expectException(InvalidArgumentException::class);