Skip to content

Commit 8c999d1

Browse files
authored
PHPLIB-881: Consolidate validation of aggregation pipeline options (#1078)
- Use MongoDB\is_pipeline to detect or validate pipelines - Use standard wording for "Aggregation pipeline"
1 parent b3607d9 commit 8c999d1

12 files changed

+23
-39
lines changed

psalm-baseline.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,7 @@
418418
</file>
419419
<file src="src/Operation/CreateCollection.php">
420420
<MixedArgument occurrences="2">
421-
<code>$i</code>
421+
<code>$options['pipeline']</code>
422422
<code>$this-&gt;options['typeMap']</code>
423423
</MixedArgument>
424424
<MixedAssignment occurrences="4">

src/Client.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ public function startSession(array $options = [])
352352
* Create a change stream for watching changes to the cluster.
353353
*
354354
* @see Watch::__construct() for supported options
355-
* @param array $pipeline List of pipeline operations
355+
* @param array $pipeline Aggregation pipeline
356356
* @param array $options Command options
357357
* @return ChangeStream
358358
* @throws InvalidArgumentException for parameter/option parsing errors

src/Collection.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ public function __toString()
199199
* "result" array from the command response document.
200200
*
201201
* @see Aggregate::__construct() for supported options
202-
* @param array $pipeline List of pipeline operations
202+
* @param array $pipeline Aggregation pipeline
203203
* @param array $options Command options
204204
* @return Traversable
205205
* @throws UnexpectedValueException if the command response was malformed
@@ -1105,7 +1105,7 @@ public function updateOne($filter, $update, array $options = [])
11051105
* Create a change stream for watching changes to the collection.
11061106
*
11071107
* @see Watch::__construct() for supported options
1108-
* @param array $pipeline List of pipeline operations
1108+
* @param array $pipeline Aggregation pipeline
11091109
* @param array $options Command options
11101110
* @return ChangeStream
11111111
* @throws InvalidArgumentException for parameter/option parsing errors

src/Database.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ public function __toString()
187187
* and $listLocalSessions. Requires MongoDB >= 3.6
188188
*
189189
* @see Aggregate::__construct() for supported options
190-
* @param array $pipeline List of pipeline operations
190+
* @param array $pipeline Aggregation pipeline
191191
* @param array $options Command options
192192
* @return Traversable
193193
* @throws UnexpectedValueException if the command response was malformed
@@ -598,7 +598,7 @@ public function selectGridFSBucket(array $options = [])
598598
* Create a change stream for watching changes to the database.
599599
*
600600
* @see Watch::__construct() for supported options
601-
* @param array $pipeline List of pipeline operations
601+
* @param array $pipeline Aggregation pipeline
602602
* @param array $options Command options
603603
* @return ChangeStream
604604
* @throws InvalidArgumentException for parameter/option parsing errors

src/Operation/Aggregate.php

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
use MongoDB\Exception\UnsupportedException;
3232
use stdClass;
3333

34-
use function array_is_list;
3534
use function current;
3635
use function is_array;
3736
use function is_bool;
@@ -40,7 +39,7 @@
4039
use function is_string;
4140
use function MongoDB\create_field_path_type_map;
4241
use function MongoDB\is_last_pipeline_operator_write;
43-
use function sprintf;
42+
use function MongoDB\is_pipeline;
4443

4544
/**
4645
* Operation for the aggregate command.
@@ -132,20 +131,14 @@ class Aggregate implements Executable, Explainable
132131
*
133132
* @param string $databaseName Database name
134133
* @param string|null $collectionName Collection name
135-
* @param array $pipeline List of pipeline operations
134+
* @param array $pipeline Aggregation pipeline
136135
* @param array $options Command options
137136
* @throws InvalidArgumentException for parameter/option parsing errors
138137
*/
139138
public function __construct(string $databaseName, ?string $collectionName, array $pipeline, array $options = [])
140139
{
141-
if (! array_is_list($pipeline)) {
142-
throw new InvalidArgumentException('$pipeline is not a list');
143-
}
144-
145-
foreach ($pipeline as $i => $operation) {
146-
if (! is_array($operation) && ! is_object($operation)) {
147-
throw InvalidArgumentException::invalidType(sprintf('$pipeline[%d]', $i), $operation, 'array or object');
148-
}
140+
if (! is_pipeline($pipeline, true /* allowEmpty */)) {
141+
throw new InvalidArgumentException('$pipeline is not a valid aggregation pipeline');
149142
}
150143

151144
$options += ['useCursor' => true];

src/Operation/CreateCollection.php

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,13 @@
2424
use MongoDB\Driver\WriteConcern;
2525
use MongoDB\Exception\InvalidArgumentException;
2626

27-
use function array_is_list;
28-
use function assert;
2927
use function current;
3028
use function is_array;
3129
use function is_bool;
3230
use function is_integer;
3331
use function is_object;
3432
use function is_string;
35-
use function sprintf;
33+
use function MongoDB\is_pipeline;
3634
use function trigger_error;
3735

3836
use const E_USER_DEPRECATED;
@@ -242,18 +240,8 @@ public function __construct(string $databaseName, string $collectionName, array
242240
trigger_error('The "autoIndexId" option is deprecated and will be removed in a future release', E_USER_DEPRECATED);
243241
}
244242

245-
if (isset($options['pipeline'])) {
246-
$pipeline = $options['pipeline'];
247-
assert(is_array($pipeline));
248-
if (! array_is_list($pipeline)) {
249-
throw new InvalidArgumentException('The "pipeline" option is not a list');
250-
}
251-
252-
foreach ($options['pipeline'] as $i => $operation) {
253-
if (! is_array($operation) && ! is_object($operation)) {
254-
throw InvalidArgumentException::invalidType(sprintf('$options["pipeline"][%d]', $i), $operation, 'array or object');
255-
}
256-
}
243+
if (isset($options['pipeline']) && ! is_pipeline($options['pipeline'], true /* allowEmpty */)) {
244+
throw new InvalidArgumentException('"pipeline" option is not a valid aggregation pipeline');
257245
}
258246

259247
$this->databaseName = $databaseName;

src/Operation/Watch.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ class Watch implements Executable, /* @internal */ CommandSubscriber
196196
* @param Manager $manager Manager instance from the driver
197197
* @param string|null $databaseName Database name
198198
* @param string|null $collectionName Collection name
199-
* @param array $pipeline List of pipeline operations
199+
* @param array $pipeline Aggregation pipeline
200200
* @param array $options Command options
201201
* @throws InvalidArgumentException for parameter/option parsing errors
202202
*/

src/functions.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ function is_in_transaction(array $options): bool
300300
* executed against a primary server.
301301
*
302302
* @internal
303-
* @param array $pipeline List of pipeline operations
303+
* @param array $pipeline Aggregation pipeline
304304
*/
305305
function is_last_pipeline_operator_write(array $pipeline): bool
306306
{

tests/FunctionsTest.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,9 +243,9 @@ public function testIsLastPipelineOperatorWrite(callable $cast): void
243243
}
244244

245245
/** @dataProvider providePipelines */
246-
public function testIsPipeline($expected, $pipeline): void
246+
public function testIsPipeline($expected, $pipeline, $allowEmpty = false): void
247247
{
248-
$this->assertSame($expected, is_pipeline($pipeline));
248+
$this->assertSame($expected, is_pipeline($pipeline, $allowEmpty));
249249
}
250250

251251
public function providePipelines(): array
@@ -286,6 +286,9 @@ public function providePipelines(): array
286286
'invalid pipeline element type: Serializable' => [false, new BSONArray([new BSONArray([])])],
287287
'invalid pipeline element type: PackedArray' => [false, PackedArray::fromPHP([[]])],
288288
// Empty array has no pipeline stages
289+
'valid empty: array' => [true, [], true],
290+
'valid empty: Serializable' => [true, new BSONArray([]), true],
291+
'valid empty: PackedArray' => [true, PackedArray::fromPHP([]), true],
289292
'invalid empty: array' => [false, []],
290293
'invalid empty: Serializable' => [false, new BSONArray([])],
291294
'invalid empty: PackedArray' => [false, PackedArray::fromPHP([])],

tests/Operation/AggregateTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class AggregateTest extends TestCase
1010
public function testConstructorPipelineArgumentMustBeAList(): void
1111
{
1212
$this->expectException(InvalidArgumentException::class);
13-
$this->expectExceptionMessage('$pipeline is not a list');
13+
$this->expectExceptionMessage('$pipeline is not a valid aggregation pipeline');
1414
new Aggregate($this->getDatabaseName(), $this->getCollectionName(), [1 => ['$match' => ['x' => 1]]]);
1515
}
1616

tests/Operation/CreateCollectionTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class CreateCollectionTest extends TestCase
1010
public function testConstructorPipelineOptionMustBeAList(): void
1111
{
1212
$this->expectException(InvalidArgumentException::class);
13-
$this->expectExceptionMessage('The "pipeline" option is not a list');
13+
$this->expectExceptionMessage('"pipeline" option is not a valid aggregation pipeline');
1414
new CreateCollection($this->getDatabaseName(), $this->getCollectionName(), ['pipeline' => [1 => ['$match' => ['x' => 1]]]]);
1515
}
1616

tests/Operation/WatchTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public function testConstructorCollectionNameShouldBeNullIfDatabaseNameIsNull():
2323
public function testConstructorPipelineArgumentMustBeAList(): void
2424
{
2525
$this->expectException(InvalidArgumentException::class);
26-
$this->expectExceptionMessage('$pipeline is not a list');
26+
$this->expectExceptionMessage('$pipeline is not a valid aggregation pipeline');
2727

2828
/* Note: Watch uses array_unshift() to prepend the $changeStream stage
2929
* to the pipeline. Since array_unshift() reindexes numeric keys, we'll

0 commit comments

Comments
 (0)