Skip to content

Commit 51d54ab

Browse files
committed
PHPLIB-1122: Support Document and PackedArray objects in public APIs
Excluding tests, most changes were concentrated in functions.php. A document_to_array() function was introduced to make it easier to access document values as arrays. This is mainly used by other utility functions (e.g. checking for dollar prefixed keys) but also ended up being used in the Find operation for handling the "modifiers" option. Adds tests for using Document and PackedArray objects in operations. Beyond handling of document/array types, this also filled gaps in test coverage for: * Validation for IndexInput key/name options * Passing a MongoDB\Driver\Command directly to DatabaseCommand * is_last_pipeline_operator_write()
1 parent a86a1f6 commit 51d54ab

22 files changed

+980
-173
lines changed

src/Operation/Find.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
use function is_integer;
3333
use function is_object;
3434
use function is_string;
35+
use function MongoDB\document_to_array;
3536
use function trigger_error;
3637

3738
use const E_USER_DEPRECATED;
@@ -427,7 +428,7 @@ private function createQueryOptions(): array
427428
}
428429
}
429430

430-
$modifiers = empty($this->options['modifiers']) ? [] : (array) $this->options['modifiers'];
431+
$modifiers = empty($this->options['modifiers']) ? [] : document_to_array($this->options['modifiers']);
431432

432433
if (! empty($modifiers)) {
433434
$options['modifiers'] = $modifiers;

src/functions.php

Lines changed: 71 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
namespace MongoDB;
1919

2020
use Exception;
21+
use MongoDB\BSON\Document;
22+
use MongoDB\BSON\PackedArray;
2123
use MongoDB\BSON\Serializable;
2224
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
2325
use MongoDB\Driver\Manager;
@@ -35,7 +37,6 @@
3537
use function assert;
3638
use function end;
3739
use function get_object_vars;
38-
use function in_array;
3940
use function is_array;
4041
use function is_object;
4142
use function is_string;
@@ -94,15 +95,34 @@ function apply_type_map_to_document($document, array $typeMap)
9495
}
9596

9697
/**
97-
* Generate an index name from a key specification.
98+
* Converts a document parameter to an array.
99+
*
100+
* This is used to facilitate unified access to document fields. It also handles
101+
* Document, PackedArray, and Serializable objects.
102+
*
103+
* PackedArray is intentionally allowed because this function is not used for
104+
* type checking. Prohibiting PackedArray would only require callers to check
105+
* for a PackedArray in order to avoid an exception. Furthermore, this function
106+
* does not distinguish between Serializable::bsonSerialize() return values that
107+
* might encode as a BSON document or array.
98108
*
99109
* @internal
100-
* @param array|object $document Document containing fields mapped to values,
101-
* which denote order or an index type
102-
* @throws InvalidArgumentException
110+
* @param array|object $document
111+
* @throws InvalidArgumentException if $document is not an array or object
103112
*/
104-
function generate_index_name($document): string
113+
function document_to_array($document): array
105114
{
115+
if ($document instanceof Document || $document instanceof PackedArray) {
116+
/* Nested documents and arrays are intentionally left as BSON. We avoid
117+
* iterator_to_array() since Document and PackedArray iteration returns
118+
* all values as MongoDB\BSON\Value instances. */
119+
return $document->toPHP([
120+
'array' => 'bson',
121+
'document' => 'bson',
122+
'root' => 'array',
123+
]);
124+
}
125+
106126
if ($document instanceof Serializable) {
107127
$document = $document->bsonSerialize();
108128
}
@@ -115,6 +135,21 @@ function generate_index_name($document): string
115135
throw InvalidArgumentException::invalidType('$document', $document, 'array or object');
116136
}
117137

138+
return $document;
139+
}
140+
141+
/**
142+
* Generate an index name from a key specification.
143+
*
144+
* @internal
145+
* @param array|object $document Document containing fields mapped to values,
146+
* which denote order or an index type
147+
* @throws InvalidArgumentException if $document is not an array or object
148+
*/
149+
function generate_index_name($document): string
150+
{
151+
$document = document_to_array($document);
152+
118153
$name = '';
119154

120155
foreach ($document as $field => $type) {
@@ -174,25 +209,17 @@ function get_encrypted_fields_from_server(string $databaseName, string $collecti
174209
/**
175210
* Return whether the first key in the document starts with a "$" character.
176211
*
177-
* This is used for differentiating update and replacement documents.
212+
* This is used for differentiating update and replacement documents. Since true
213+
* and false return values may be expected in different contexts, this function
214+
* intentionally throws if $document has an unexpected type.
178215
*
179216
* @internal
180217
* @param array|object $document Update or replacement document
181-
* @throws InvalidArgumentException
218+
* @throws InvalidArgumentException if $document is not an array or object
182219
*/
183220
function is_first_key_operator($document): bool
184221
{
185-
if ($document instanceof Serializable) {
186-
$document = $document->bsonSerialize();
187-
}
188-
189-
if (is_object($document)) {
190-
$document = get_object_vars($document);
191-
}
192-
193-
if (! is_array($document)) {
194-
throw InvalidArgumentException::invalidType('$document', $document, 'array or object');
195-
}
222+
$document = document_to_array($document);
196223

197224
reset($document);
198225
$firstKey = (string) key($document);
@@ -208,6 +235,21 @@ function is_first_key_operator($document): bool
208235
*/
209236
function is_pipeline($pipeline): bool
210237
{
238+
if ($pipeline instanceof PackedArray) {
239+
/* Nested documents and arrays are intentionally left as BSON. We avoid
240+
* iterator_to_array() since Document iteration returns all values as
241+
* MongoDB\BSON\Value instances. */
242+
$pipeline = $pipeline->toPHP([
243+
'array' => 'bson',
244+
'document' => 'bson',
245+
'root' => 'array',
246+
]);
247+
}
248+
249+
if ($pipeline instanceof Serializable) {
250+
$pipeline = $pipeline->bsonSerialize();
251+
}
252+
211253
if (! is_array($pipeline)) {
212254
return false;
213255
}
@@ -228,7 +270,7 @@ function is_pipeline($pipeline): bool
228270
}
229271

230272
$expectedKey++;
231-
$stage = (array) $stage;
273+
$stage = document_to_array($stage);
232274
reset($stage);
233275
$key = key($stage);
234276

@@ -272,9 +314,15 @@ function is_last_pipeline_operator_write(array $pipeline): bool
272314
return false;
273315
}
274316

275-
$lastOp = (array) $lastOp;
317+
if (! is_array($lastOp) && ! is_object($lastOp)) {
318+
return false;
319+
}
320+
321+
$lastOp = document_to_array($lastOp);
322+
323+
reset($lastOp);
276324

277-
return in_array(key($lastOp), ['$out', '$merge'], true);
325+
return key($lastOp) === '$merge' || key($lastOp) === '$out';
278326
}
279327

280328
/**
@@ -285,25 +333,14 @@ function is_last_pipeline_operator_write(array $pipeline): bool
285333
* @internal
286334
* @see https://mongodb.com/docs/manual/reference/command/mapReduce/#output-inline
287335
* @param string|array|object $out Output specification
288-
* @throws InvalidArgumentException
289336
*/
290337
function is_mapreduce_output_inline($out): bool
291338
{
292339
if (! is_array($out) && ! is_object($out)) {
293340
return false;
294341
}
295342

296-
if ($out instanceof Serializable) {
297-
$out = $out->bsonSerialize();
298-
}
299-
300-
if (is_object($out)) {
301-
$out = get_object_vars($out);
302-
}
303-
304-
if (! is_array($out)) {
305-
throw InvalidArgumentException::invalidType('$out', $out, 'array or object');
306-
}
343+
$out = document_to_array($out);
307344

308345
reset($out);
309346

0 commit comments

Comments
 (0)