Skip to content

Commit b37c1c4

Browse files
committed
Improve type of Collection Bulk Write operations
1 parent 6bfa411 commit b37c1c4

File tree

4 files changed

+55
-106
lines changed

4 files changed

+55
-106
lines changed

psalm-baseline.xml

Lines changed: 20 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -454,106 +454,40 @@
454454
</file>
455455
<file src="src/Operation/BulkWrite.php">
456456
<MixedArgument>
457-
<code><![CDATA[$args]]></code>
458-
<code><![CDATA[$args]]></code>
459-
<code><![CDATA[$args]]></code>
460-
<code><![CDATA[$args[0]]]></code>
461-
<code><![CDATA[$args[0]]]></code>
462-
<code><![CDATA[$args[0]]]></code>
463-
<code><![CDATA[$args[0]]]></code>
464-
<code><![CDATA[$args[1]]]></code>
465-
<code><![CDATA[$args[1]]]></code>
466-
<code><![CDATA[$args[1]]]></code>
467-
<code><![CDATA[$args[1]]]></code>
468-
<code><![CDATA[$args[1]]]></code>
469457
<code><![CDATA[$args[1]]]></code>
470458
<code><![CDATA[$args[1]]]></code>
471-
<code><![CDATA[$args[2]]]></code>
472459
</MixedArgument>
473-
<MixedArrayAccess>
474-
<code><![CDATA[$args[0]]]></code>
475-
<code><![CDATA[$args[0]]]></code>
476-
<code><![CDATA[$args[0]]]></code>
477-
<code><![CDATA[$args[0]]]></code>
478-
<code><![CDATA[$args[0]]]></code>
479-
<code><![CDATA[$args[0]]]></code>
480-
<code><![CDATA[$args[0]]]></code>
481-
<code><![CDATA[$args[0]]]></code>
482-
<code><![CDATA[$args[0]]]></code>
483-
<code><![CDATA[$args[1]]]></code>
484-
<code><![CDATA[$args[1]]]></code>
485-
<code><![CDATA[$args[1]]]></code>
486-
<code><![CDATA[$args[1]]]></code>
487-
<code><![CDATA[$args[1]]]></code>
488-
<code><![CDATA[$args[1]]]></code>
489-
<code><![CDATA[$args[1]]]></code>
490-
<code><![CDATA[$args[1]]]></code>
491-
<code><![CDATA[$args[1]]]></code>
492-
<code><![CDATA[$args[1]]]></code>
493-
<code><![CDATA[$args[1]]]></code>
494-
<code><![CDATA[$args[1]]]></code>
495-
<code><![CDATA[$args[1]]]></code>
496-
<code><![CDATA[$args[2]]]></code>
497-
<code><![CDATA[$args[2]]]></code>
498-
<code><![CDATA[$args[2]]]></code>
499-
<code><![CDATA[$args[2]]]></code>
500-
<code><![CDATA[$args[2]]]></code>
501-
<code><![CDATA[$args[2]]]></code>
502-
<code><![CDATA[$args[2]]]></code>
503-
<code><![CDATA[$args[2]['upsert']]]></code>
504-
<code><![CDATA[$args[2]['upsert']]]></code>
505-
<code><![CDATA[$args[2]['upsert']]]></code>
506-
<code><![CDATA[$args[2]['upsert']]]></code>
507-
</MixedArrayAccess>
508-
<MixedArrayAssignment>
509-
<code><![CDATA[$args[1]]]></code>
510-
<code><![CDATA[$args[1]]]></code>
511-
<code><![CDATA[$args[1]]]></code>
512-
<code><![CDATA[$args[1]]]></code>
513-
<code><![CDATA[$args[1]['limit']]]></code>
514-
<code><![CDATA[$args[2]]]></code>
515-
<code><![CDATA[$args[2]]]></code>
516-
<code><![CDATA[$args[2]]]></code>
517-
<code><![CDATA[$args[2]]]></code>
518-
<code><![CDATA[$args[2]]]></code>
519-
<code><![CDATA[$args[2]]]></code>
520-
<code><![CDATA[$args[2]['multi']]]></code>
521-
<code><![CDATA[$args[2]['multi']]]></code>
522-
<code><![CDATA[$operations[$i][$type][0]]]></code>
523-
<code><![CDATA[$operations[$i][$type][0]]]></code>
524-
<code><![CDATA[$operations[$i][$type][0]]]></code>
525-
<code><![CDATA[$operations[$i][$type][0]]]></code>
526-
<code><![CDATA[$operations[$i][$type][1]]]></code>
527-
<code><![CDATA[$operations[$i][$type][1]]]></code>
528-
<code><![CDATA[$operations[$i][$type][1]]]></code>
529-
<code><![CDATA[$operations[$i][$type][2]]]></code>
530-
<code><![CDATA[$operations[$i][$type][2]]]></code>
531-
</MixedArrayAssignment>
532460
<MixedAssignment>
533-
<code><![CDATA[$args]]></code>
534-
<code><![CDATA[$args]]></code>
535461
<code><![CDATA[$args[1]]]></code>
536-
<code><![CDATA[$args[2]]]></code>
537-
<code><![CDATA[$args[2]]]></code>
538462
<code><![CDATA[$insertedIds[$i]]]></code>
539-
<code><![CDATA[$operations[$i][$type][0]]]></code>
540-
<code><![CDATA[$operations[$i][$type][0]]]></code>
541-
<code><![CDATA[$operations[$i][$type][0]]]></code>
542-
<code><![CDATA[$operations[$i][$type][1]]]></code>
543-
<code><![CDATA[$operations[$i][$type][1]]]></code>
544-
<code><![CDATA[$operations[$i][$type][2]]]></code>
545-
<code><![CDATA[$operations[$i][$type][2]]]></code>
463+
<code><![CDATA[$operation[$type][0]]]></code>
464+
<code><![CDATA[$operation[$type][0]]]></code>
465+
<code><![CDATA[$operation[$type][0]]]></code>
466+
<code><![CDATA[$operation[$type][1]]]></code>
546467
<code><![CDATA[$options[$option]]]></code>
547468
<code><![CDATA[$options['session']]]></code>
548469
<code><![CDATA[$options['writeConcern']]]></code>
549470
</MixedAssignment>
550471
<MixedMethodCall>
551472
<code><![CDATA[isInTransaction]]></code>
552473
</MixedMethodCall>
553-
<MixedOperand>
554-
<code><![CDATA[$args[2]]]></code>
474+
<NullArgument>
475+
<code><![CDATA[$type]]></code>
476+
</NullArgument>
477+
<PossiblyInvalidArgument>
478+
<code><![CDATA[$args[0]]]></code>
479+
<code><![CDATA[$args[1]]]></code>
480+
<code><![CDATA[$args[1]]]></code>
481+
</PossiblyInvalidArgument>
482+
<PossiblyNullArgument>
483+
<code><![CDATA[$args[1]]]></code>
484+
<code><![CDATA[$args[1]]]></code>
485+
</PossiblyNullArgument>
486+
<PossiblyUndefinedArrayOffset>
487+
<code><![CDATA[$args[1]]]></code>
488+
<code><![CDATA[$args[1]]]></code>
555489
<code><![CDATA[$args[2]]]></code>
556-
</MixedOperand>
490+
</PossiblyUndefinedArrayOffset>
557491
</file>
558492
<file src="src/Operation/ClientBulkWriteCommand.php">
559493
<MixedMethodCall>

src/Collection.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
use function is_bool;
8080
use function strlen;
8181

82+
/** @phpstan-import-type OperationType from BulkWrite */
8283
class Collection implements Stringable
8384
{
8485
private const DEFAULT_TYPE_MAP = [
@@ -262,12 +263,13 @@ public function aggregate(array|Pipeline $pipeline, array $options = []): Cursor
262263
/**
263264
* Executes multiple write operations.
264265
*
265-
* @see BulkWrite::__construct() for supported options
266-
* @param array[] $operations List of write operations
267-
* @param array $options Command options
266+
* @param list<array{deleteMany: list<array|object>}|array{deleteOne: list<array|object>}|array{insertOne: list<array|object>}|array{replaceOne: list<array|object>}|array{updateMany: list<array|object>}|array{updateOne: list<array|object>}> $operations List of write operations
267+
* @psalm-param list<OperationType> $operations List of write operations
268+
* @param array $options Command options
268269
* @throws UnsupportedException if options are not supported by the selected server
269270
* @throws InvalidArgumentException for parameter/option parsing errors
270271
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
272+
* @see BulkWrite::__construct() for supported options
271273
*/
272274
public function bulkWrite(array $operations, array $options = []): BulkWriteResult
273275
{

src/Operation/BulkWrite.php

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@
4545
* Operation for executing multiple write operations.
4646
*
4747
* @see \MongoDB\Collection::bulkWrite()
48+
*
49+
* @psalm-type Document = object|array
50+
* @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}}
4851
*/
4952
final class BulkWrite
5053
{
@@ -55,7 +58,7 @@ final class BulkWrite
5558
public const UPDATE_MANY = 'updateMany';
5659
public const UPDATE_ONE = 'updateOne';
5760

58-
/** @var array[] */
61+
/** @psalm-var list<OperationType> */
5962
private array $operations;
6063

6164
private array $options;
@@ -132,10 +135,11 @@ final class BulkWrite
132135
*
133136
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
134137
*
135-
* @param string $databaseName Database name
136-
* @param string $collectionName Collection name
137-
* @param array[] $operations List of write operations
138-
* @param array $options Command options
138+
* @param string $databaseName Database name
139+
* @param string $collectionName Collection name
140+
* @param array $operations List of write operations
141+
* @psalm-param list<OperationType> $operations
142+
* @param array $options Command options
139143
* @throws InvalidArgumentException for parameter/option parsing errors
140144
*/
141145
public function __construct(private string $databaseName, private string $collectionName, array $operations, array $options = [])
@@ -276,12 +280,12 @@ private function createExecuteOptions(): array
276280
}
277281

278282
/**
279-
* @param array[] $operations
280-
* @return array[]
283+
* @psalm-param list<OperationType> $operations
284+
* @psalm-return list<OperationType>
281285
*/
282286
private function validateOperations(array $operations, ?DocumentCodec $codec, Encoder $builderEncoder): array
283287
{
284-
foreach ($operations as $i => $operation) {
288+
foreach ($operations as $i => &$operation) {
285289
if (! is_array($operation)) {
286290
throw InvalidArgumentException::invalidType(sprintf('$operations[%d]', $i), $operation, 'array');
287291
}
@@ -306,14 +310,14 @@ private function validateOperations(array $operations, ?DocumentCodec $codec, En
306310
// $args[0] was already validated above. Since DocumentCodec::encode will always return a Document
307311
// instance, there is no need to re-validate the returned value here.
308312
if ($codec) {
309-
$operations[$i][$type][0] = $codec->encode($args[0]);
313+
$operation[$type][0] = $codec->encode($args[0]);
310314
}
311315

312316
break;
313317

314318
case self::DELETE_MANY:
315319
case self::DELETE_ONE:
316-
$operations[$i][$type][0] = $builderEncoder->encodeIfSupported($args[0]);
320+
$operation[$type][0] = $builderEncoder->encodeIfSupported($args[0]);
317321

318322
if (! isset($args[1])) {
319323
$args[1] = [];
@@ -329,19 +333,19 @@ private function validateOperations(array $operations, ?DocumentCodec $codec, En
329333
throw InvalidArgumentException::expectedDocumentType(sprintf('$operations[%d]["%s"][1]["collation"]', $i, $type), $args[1]['collation']);
330334
}
331335

332-
$operations[$i][$type][1] = $args[1];
336+
$operation[$type][1] = $args[1];
333337

334338
break;
335339

336340
case self::REPLACE_ONE:
337-
$operations[$i][$type][0] = $builderEncoder->encodeIfSupported($args[0]);
341+
$operation[$type][0] = $builderEncoder->encodeIfSupported($args[0]);
338342

339343
if (! isset($args[1]) && ! array_key_exists(1, $args)) {
340344
throw new InvalidArgumentException(sprintf('Missing second argument for $operations[%d]["%s"]', $i, $type));
341345
}
342346

343347
if ($codec) {
344-
$operations[$i][$type][1] = $codec->encode($args[1]);
348+
$operation[$type][1] = $codec->encode($args[1]);
345349
}
346350

347351
if (! is_document($args[1])) {
@@ -384,19 +388,19 @@ private function validateOperations(array $operations, ?DocumentCodec $codec, En
384388
throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]["upsert"]', $i, $type), $args[2]['upsert'], 'boolean');
385389
}
386390

387-
$operations[$i][$type][2] = $args[2];
391+
$operation[$type][2] = $args[2];
388392

389393
break;
390394

391395
case self::UPDATE_MANY:
392396
case self::UPDATE_ONE:
393-
$operations[$i][$type][0] = $builderEncoder->encodeIfSupported($args[0]);
397+
$operation[$type][0] = $builderEncoder->encodeIfSupported($args[0]);
394398

395399
if (! isset($args[1]) && ! array_key_exists(1, $args)) {
396400
throw new InvalidArgumentException(sprintf('Missing second argument for $operations[%d]["%s"]', $i, $type));
397401
}
398402

399-
$operations[$i][$type][1] = $args[1] = $builderEncoder->encodeIfSupported($args[1]);
403+
$operation[$type][1] = $args[1] = $builderEncoder->encodeIfSupported($args[1]);
400404

401405
if ((! is_document($args[1]) || ! is_first_key_operator($args[1])) && ! is_pipeline($args[1])) {
402406
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
433437
throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]["upsert"]', $i, $type), $args[2]['upsert'], 'boolean');
434438
}
435439

436-
$operations[$i][$type][2] = $args[2];
440+
$operation[$type][2] = $args[2];
437441

438442
break;
439443

tests/Operation/BulkWriteTest.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,15 @@ public function testMultipleOperationsInOneElement(): void
4141
]);
4242
}
4343

44+
public function testEmptyOperation(): void
45+
{
46+
$this->expectException(InvalidArgumentException::class);
47+
$this->expectExceptionMessage('Expected one element in $operation[0], actually: 0');
48+
new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), [
49+
[],
50+
]);
51+
}
52+
4453
public function testUnknownOperation(): void
4554
{
4655
$this->expectException(InvalidArgumentException::class);

0 commit comments

Comments
 (0)