Skip to content

Commit 09fb4b4

Browse files
committed
PHPLIB-232: Support typeMap option on GridFS Bucket
1 parent 60cd810 commit 09fb4b4

9 files changed

+108
-21
lines changed

docs/includes/apiargs-MongoDBDatabase-method-selectGridFSBucket-option.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ replacement:
3131
resource: "bucket"
3232
parent: "database"
3333
---
34+
source:
35+
file: apiargs-common-option.yaml
36+
ref: typeMap
37+
replacement:
38+
parent: "database"
39+
---
3440
source:
3541
file: apiargs-common-option.yaml
3642
ref: writeConcern

docs/includes/apiargs-MongoDBGridFSBucket-method-construct-option.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ replacement:
3131
resource: "bucket"
3232
parent: "database"
3333
---
34+
source:
35+
file: apiargs-MongoDBClient-method-construct-driverOptions.yaml
36+
ref: typeMap
37+
---
3438
source:
3539
file: apiargs-common-option.yaml
3640
ref: writeConcern

docs/reference/method/MongoDBDatabase-selectGridFSBucket.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ Errors/Exceptions
4242
Behavior
4343
--------
4444

45-
The selected bucket inherits options such as read preference and write concern
46-
from the :phpclass:`Database <MongoDB\\Database>` object. Options may be
45+
The selected bucket inherits options such as read preference and type
46+
mapping from the :phpclass:`Database <MongoDB\\Database>` object. Options may be
4747
overridden via the ``$options`` parameter.
4848

4949
Example

docs/reference/method/MongoDBGridFSBucket-getFileDocumentForStream.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ Definition
1919

2020
.. code-block:: php
2121

22-
function getFileDocumentForStream($stream): object
22+
function getFileDocumentForStream($stream): array|object
2323

2424
This method has the following parameters:
2525

@@ -28,7 +28,8 @@ Definition
2828
Return Values
2929
-------------
3030

31-
The metadata document associated with the GridFS stream.
31+
The metadata document associated with the GridFS stream. The return type will
32+
depend on the bucket's ``typeMap`` option.
3233

3334
.. todo: add examples
3435

docs/reference/method/MongoDBGridFSBucket-getFileIdForStream.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ Definition
2828
Return Values
2929
-------------
3030

31-
The ``_id`` field of the metadata document associated with the GridFS
32-
stream.
31+
The ``_id`` field of the metadata document associated with the GridFS stream.
32+
The return type will depend on the bucket's ``typeMap`` option.
3333

3434
.. todo: add examples
3535

src/Database.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,7 @@ public function selectGridFSBucket(array $options = [])
317317
$options += [
318318
'readConcern' => $this->readConcern,
319319
'readPreference' => $this->readPreference,
320+
'typeMap' => $this->typeMap,
320321
'writeConcern' => $this->writeConcern,
321322
];
322323

src/GridFS/Bucket.php

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ class Bucket
2323
{
2424
private static $defaultBucketName = 'fs';
2525
private static $defaultChunkSizeBytes = 261120;
26+
private static $defaultTypeMap = [
27+
'array' => 'MongoDB\Model\BSONArray',
28+
'document' => 'MongoDB\Model\BSONDocument',
29+
'root' => 'MongoDB\Model\BSONDocument',
30+
];
2631
private static $streamWrapperProtocol = 'gridfs';
2732

2833
private $collectionWrapper;
@@ -32,6 +37,7 @@ class Bucket
3237
private $chunkSizeBytes;
3338
private $readConcern;
3439
private $readPreference;
40+
private $typeMap;
3541
private $writeConcern;
3642

3743
/**
@@ -49,6 +55,8 @@ class Bucket
4955
*
5056
* * readPreference (MongoDB\Driver\ReadPreference): Read preference.
5157
*
58+
* * typeMap (array): Default type map for cursors and BSON documents.
59+
*
5260
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
5361
*
5462
* @param Manager $manager Manager instance from the driver
@@ -79,6 +87,10 @@ public function __construct(Manager $manager, $databaseName, array $options = []
7987
throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
8088
}
8189

90+
if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
91+
throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
92+
}
93+
8294
if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
8395
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
8496
}
@@ -89,9 +101,10 @@ public function __construct(Manager $manager, $databaseName, array $options = []
89101
$this->chunkSizeBytes = $options['chunkSizeBytes'];
90102
$this->readConcern = isset($options['readConcern']) ? $options['readConcern'] : $this->manager->getReadConcern();
91103
$this->readPreference = isset($options['readPreference']) ? $options['readPreference'] : $this->manager->getReadPreference();
104+
$this->typeMap = isset($options['typeMap']) ? $options['typeMap'] : self::$defaultTypeMap;
92105
$this->writeConcern = isset($options['writeConcern']) ? $options['writeConcern'] : $this->manager->getWriteConcern();
93106

94-
$collectionOptions = array_intersect_key($options, ['readConcern' => 1, 'readPreference' => 1, 'writeConcern' => 1]);
107+
$collectionOptions = array_intersect_key($options, ['readConcern' => 1, 'readPreference' => 1, 'typeMap' => 1, 'writeConcern' => 1]);
95108

96109
$this->collectionWrapper = new CollectionWrapper($manager, $databaseName, $options['bucketName'], $collectionOptions);
97110
$this->registerStreamWrapper();
@@ -112,6 +125,7 @@ public function __debugInfo()
112125
'chunkSizeBytes' => $this->chunkSizeBytes,
113126
'readConcern' => $this->readConcern,
114127
'readPreference' => $this->readPreference,
128+
'typeMap' => $this->typeMap,
115129
'writeConcern' => $this->writeConcern,
116130
];
117131
}
@@ -233,22 +247,15 @@ public function getDatabaseName()
233247
* Gets the file document of the GridFS file associated with a stream.
234248
*
235249
* @param resource $stream GridFS stream
236-
* @return stdClass
250+
* @return array|object
237251
* @throws InvalidArgumentException
238252
*/
239253
public function getFileDocumentForStream($stream)
240254
{
241-
if ( ! is_resource($stream) || get_resource_type($stream) != "stream") {
242-
throw InvalidArgumentException::invalidType('$stream', $stream, 'resource');
243-
}
255+
$file = $this->getRawFileDocumentForStream($stream);
244256

245-
$metadata = stream_get_meta_data($stream);
246-
247-
if (!$metadata['wrapper_data'] instanceof StreamWrapper) {
248-
throw InvalidArgumentException::invalidType('$stream wrapper data', $metadata['wrapper_data'], 'MongoDB\Driver\GridFS\StreamWrapper');
249-
}
250-
251-
return $metadata['wrapper_data']->getFile();
257+
// Filter the raw document through the specified type map
258+
return \MongoDB\BSON\toPHP(\MongoDB\BSON\fromPHP($file), $this->typeMap);
252259
}
253260

254261
/**
@@ -261,7 +268,13 @@ public function getFileDocumentForStream($stream)
261268
*/
262269
public function getFileIdForStream($stream)
263270
{
264-
$file = $this->getFileDocumentForStream($stream);
271+
$file = $this->getRawFileDocumentForStream($stream);
272+
273+
/* Filter the raw document through the specified type map, but override
274+
* the root type so we can reliably access the ID.
275+
*/
276+
$typeMap = ['root' => 'stdClass'] + $this->typeMap;
277+
$file = \MongoDB\BSON\toPHP(\MongoDB\BSON\fromPHP($file), $typeMap);
265278

266279
if ( ! isset($file->_id) && ! property_exists($file, '_id')) {
267280
throw new CorruptFileException('file._id does not exist');
@@ -466,6 +479,31 @@ private function getFilesNamespace()
466479
return sprintf('%s.%s.files', $this->databaseName, $this->bucketName);
467480
}
468481

482+
/**
483+
* Gets the file document of the GridFS file associated with a stream.
484+
*
485+
* This returns the raw document from the StreamWrapper, which does not
486+
* respect the Bucket's type map.
487+
*
488+
* @param resource $stream GridFS stream
489+
* @return stdClass
490+
* @throws InvalidArgumentException
491+
*/
492+
private function getRawFileDocumentForStream($stream)
493+
{
494+
if ( ! is_resource($stream) || get_resource_type($stream) != "stream") {
495+
throw InvalidArgumentException::invalidType('$stream', $stream, 'resource');
496+
}
497+
498+
$metadata = stream_get_meta_data($stream);
499+
500+
if (!$metadata['wrapper_data'] instanceof StreamWrapper) {
501+
throw InvalidArgumentException::invalidType('$stream wrapper data', $metadata['wrapper_data'], 'MongoDB\Driver\GridFS\StreamWrapper');
502+
}
503+
504+
return $metadata['wrapper_data']->getFile();
505+
}
506+
469507
/**
470508
* Opens a readable stream for the GridFS file.
471509
*

src/GridFS/CollectionWrapper.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ public function deleteFileAndChunksById($id)
6868
*/
6969
public function dropCollections()
7070
{
71-
$this->filesCollection->drop();
72-
$this->chunksCollection->drop();
71+
$this->filesCollection->drop(['typeMap' => []]);
72+
$this->chunksCollection->drop(['typeMap' => []]);
7373
}
7474

7575
/**
@@ -284,6 +284,7 @@ private function isFilesCollectionEmpty()
284284
return null === $this->filesCollection->findOne([], [
285285
'readPreference' => new ReadPreference(ReadPreference::RP_PRIMARY),
286286
'projection' => ['_id' => 1],
287+
'typeMap' => [],
287288
]);
288289
}
289290
}

tests/GridFS/BucketFunctionalTest.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ public function provideInvalidConstructorOptions()
5757
$options[][] = ['readPreference' => $value];
5858
}
5959

60+
foreach ($this->getInvalidArrayValues() as $value) {
61+
$options[][] = ['typeMap' => $value];
62+
}
63+
6064
foreach ($this->getInvalidWriteConcernValues() as $value) {
6165
$options[][] = ['writeConcern' => $value];
6266
}
@@ -314,6 +318,16 @@ public function testFind()
314318
$this->assertSameDocuments($expected, $cursor);
315319
}
316320

321+
public function testFindUsesTypeMap()
322+
{
323+
$this->bucket->uploadFromStream('a', $this->createStream('foo'));
324+
325+
$cursor = $this->bucket->find();
326+
$fileDocument = current($cursor->toArray());
327+
328+
$this->assertInstanceOf('MongoDB\Model\BSONDocument', $fileDocument);
329+
}
330+
317331
public function testGetBucketNameWithCustomValue()
318332
{
319333
$bucket = new Bucket($this->manager, $this->getDatabaseName(), ['bucketName' => 'custom_fs']);
@@ -331,6 +345,18 @@ public function testGetDatabaseName()
331345
$this->assertEquals($this->getDatabaseName(), $this->bucket->getDatabaseName());
332346
}
333347

348+
public function testGetFileDocumentForStreamUsesTypeMap()
349+
{
350+
$metadata = ['foo' => 'bar'];
351+
$stream = $this->bucket->openUploadStream('filename', ['_id' => 1, 'metadata' => $metadata]);
352+
353+
$fileDocument = $this->bucket->getFileDocumentForStream($stream);
354+
355+
$this->assertInstanceOf('MongoDB\Model\BSONDocument', $fileDocument);
356+
$this->assertInstanceOf('MongoDB\Model\BSONDocument', $fileDocument['metadata']);
357+
$this->assertSame(['foo' => 'bar'], $fileDocument['metadata']->getArrayCopy());
358+
}
359+
334360
public function testGetFileDocumentForStreamWithReadableStream()
335361
{
336362
$metadata = ['foo' => 'bar'];
@@ -366,6 +392,16 @@ public function testGetFileDocumentForStreamShouldRequireStreamResource($stream)
366392
$this->bucket->getFileDocumentForStream($stream);
367393
}
368394

395+
public function testGetFileIdForStreamUsesTypeMap()
396+
{
397+
$stream = $this->bucket->openUploadStream('filename', ['_id' => ['x' => 1]]);
398+
399+
$id = $this->bucket->getFileIdForStream($stream);
400+
401+
$this->assertInstanceOf('MongoDB\Model\BSONDocument', $id);
402+
$this->assertSame(['x' => 1], $id->getArrayCopy());
403+
}
404+
369405
public function testGetFileIdForStreamWithReadableStream()
370406
{
371407
$id = $this->bucket->uploadFromStream('filename', $this->createStream('foobar'));

0 commit comments

Comments
 (0)