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);