Commit 78e69811 authored by Jeremy Mikola's avatar Jeremy Mikola

PHPLIB-232: Support typeMap option on GridFS Bucket

parent 31a8e20d
...@@ -31,6 +31,12 @@ replacement: ...@@ -31,6 +31,12 @@ replacement:
resource: "bucket" resource: "bucket"
parent: "database" parent: "database"
--- ---
source:
file: apiargs-common-option.yaml
ref: typeMap
replacement:
parent: "database"
---
source: source:
file: apiargs-common-option.yaml file: apiargs-common-option.yaml
ref: writeConcern ref: writeConcern
......
...@@ -31,6 +31,10 @@ replacement: ...@@ -31,6 +31,10 @@ replacement:
resource: "bucket" resource: "bucket"
parent: "database" parent: "database"
--- ---
source:
file: apiargs-MongoDBClient-method-construct-driverOptions.yaml
ref: typeMap
---
source: source:
file: apiargs-common-option.yaml file: apiargs-common-option.yaml
ref: writeConcern ref: writeConcern
......
...@@ -42,8 +42,8 @@ Errors/Exceptions ...@@ -42,8 +42,8 @@ Errors/Exceptions
Behavior Behavior
-------- --------
The selected bucket inherits options such as read preference and write concern The selected bucket inherits options such as read preference and type
from the :phpclass:`Database <MongoDB\\Database>` object. Options may be mapping from the :phpclass:`Database <MongoDB\\Database>` object. Options may be
overridden via the ``$options`` parameter. overridden via the ``$options`` parameter.
Example Example
......
...@@ -19,7 +19,7 @@ Definition ...@@ -19,7 +19,7 @@ Definition
.. code-block:: php .. code-block:: php
function getFileDocumentForStream($stream): object function getFileDocumentForStream($stream): array|object
This method has the following parameters: This method has the following parameters:
...@@ -28,7 +28,8 @@ Definition ...@@ -28,7 +28,8 @@ Definition
Return Values Return Values
------------- -------------
The metadata document associated with the GridFS stream. The metadata document associated with the GridFS stream. The return type will
depend on the bucket's ``typeMap`` option.
.. todo: add examples .. todo: add examples
......
...@@ -28,8 +28,8 @@ Definition ...@@ -28,8 +28,8 @@ Definition
Return Values Return Values
------------- -------------
The ``_id`` field of the metadata document associated with the GridFS The ``_id`` field of the metadata document associated with the GridFS stream.
stream. The return type will depend on the bucket's ``typeMap`` option.
.. todo: add examples .. todo: add examples
......
...@@ -317,6 +317,7 @@ class Database ...@@ -317,6 +317,7 @@ class Database
$options += [ $options += [
'readConcern' => $this->readConcern, 'readConcern' => $this->readConcern,
'readPreference' => $this->readPreference, 'readPreference' => $this->readPreference,
'typeMap' => $this->typeMap,
'writeConcern' => $this->writeConcern, 'writeConcern' => $this->writeConcern,
]; ];
......
...@@ -23,6 +23,11 @@ class Bucket ...@@ -23,6 +23,11 @@ class Bucket
{ {
private static $defaultBucketName = 'fs'; private static $defaultBucketName = 'fs';
private static $defaultChunkSizeBytes = 261120; private static $defaultChunkSizeBytes = 261120;
private static $defaultTypeMap = [
'array' => 'MongoDB\Model\BSONArray',
'document' => 'MongoDB\Model\BSONDocument',
'root' => 'MongoDB\Model\BSONDocument',
];
private static $streamWrapperProtocol = 'gridfs'; private static $streamWrapperProtocol = 'gridfs';
private $collectionWrapper; private $collectionWrapper;
...@@ -32,6 +37,7 @@ class Bucket ...@@ -32,6 +37,7 @@ class Bucket
private $chunkSizeBytes; private $chunkSizeBytes;
private $readConcern; private $readConcern;
private $readPreference; private $readPreference;
private $typeMap;
private $writeConcern; private $writeConcern;
/** /**
...@@ -49,6 +55,8 @@ class Bucket ...@@ -49,6 +55,8 @@ class Bucket
* *
* * readPreference (MongoDB\Driver\ReadPreference): Read preference. * * readPreference (MongoDB\Driver\ReadPreference): Read preference.
* *
* * typeMap (array): Default type map for cursors and BSON documents.
*
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern. * * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
* *
* @param Manager $manager Manager instance from the driver * @param Manager $manager Manager instance from the driver
...@@ -79,6 +87,10 @@ class Bucket ...@@ -79,6 +87,10 @@ class Bucket
throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference'); throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
} }
if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
}
if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) { if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern'); throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
} }
...@@ -89,9 +101,10 @@ class Bucket ...@@ -89,9 +101,10 @@ class Bucket
$this->chunkSizeBytes = $options['chunkSizeBytes']; $this->chunkSizeBytes = $options['chunkSizeBytes'];
$this->readConcern = isset($options['readConcern']) ? $options['readConcern'] : $this->manager->getReadConcern(); $this->readConcern = isset($options['readConcern']) ? $options['readConcern'] : $this->manager->getReadConcern();
$this->readPreference = isset($options['readPreference']) ? $options['readPreference'] : $this->manager->getReadPreference(); $this->readPreference = isset($options['readPreference']) ? $options['readPreference'] : $this->manager->getReadPreference();
$this->typeMap = isset($options['typeMap']) ? $options['typeMap'] : self::$defaultTypeMap;
$this->writeConcern = isset($options['writeConcern']) ? $options['writeConcern'] : $this->manager->getWriteConcern(); $this->writeConcern = isset($options['writeConcern']) ? $options['writeConcern'] : $this->manager->getWriteConcern();
$collectionOptions = array_intersect_key($options, ['readConcern' => 1, 'readPreference' => 1, 'writeConcern' => 1]); $collectionOptions = array_intersect_key($options, ['readConcern' => 1, 'readPreference' => 1, 'typeMap' => 1, 'writeConcern' => 1]);
$this->collectionWrapper = new CollectionWrapper($manager, $databaseName, $options['bucketName'], $collectionOptions); $this->collectionWrapper = new CollectionWrapper($manager, $databaseName, $options['bucketName'], $collectionOptions);
$this->registerStreamWrapper(); $this->registerStreamWrapper();
...@@ -112,6 +125,7 @@ class Bucket ...@@ -112,6 +125,7 @@ class Bucket
'chunkSizeBytes' => $this->chunkSizeBytes, 'chunkSizeBytes' => $this->chunkSizeBytes,
'readConcern' => $this->readConcern, 'readConcern' => $this->readConcern,
'readPreference' => $this->readPreference, 'readPreference' => $this->readPreference,
'typeMap' => $this->typeMap,
'writeConcern' => $this->writeConcern, 'writeConcern' => $this->writeConcern,
]; ];
} }
...@@ -233,22 +247,15 @@ class Bucket ...@@ -233,22 +247,15 @@ class Bucket
* Gets the file document of the GridFS file associated with a stream. * Gets the file document of the GridFS file associated with a stream.
* *
* @param resource $stream GridFS stream * @param resource $stream GridFS stream
* @return stdClass * @return array|object
* @throws InvalidArgumentException * @throws InvalidArgumentException
*/ */
public function getFileDocumentForStream($stream) public function getFileDocumentForStream($stream)
{ {
if ( ! is_resource($stream) || get_resource_type($stream) != "stream") { $file = $this->getRawFileDocumentForStream($stream);
throw InvalidArgumentException::invalidType('$stream', $stream, 'resource');
}
$metadata = stream_get_meta_data($stream); // Filter the raw document through the specified type map
return \MongoDB\BSON\toPHP(\MongoDB\BSON\fromPHP($file), $this->typeMap);
if (!$metadata['wrapper_data'] instanceof StreamWrapper) {
throw InvalidArgumentException::invalidType('$stream wrapper data', $metadata['wrapper_data'], 'MongoDB\Driver\GridFS\StreamWrapper');
}
return $metadata['wrapper_data']->getFile();
} }
/** /**
...@@ -261,7 +268,13 @@ class Bucket ...@@ -261,7 +268,13 @@ class Bucket
*/ */
public function getFileIdForStream($stream) public function getFileIdForStream($stream)
{ {
$file = $this->getFileDocumentForStream($stream); $file = $this->getRawFileDocumentForStream($stream);
/* Filter the raw document through the specified type map, but override
* the root type so we can reliably access the ID.
*/
$typeMap = ['root' => 'stdClass'] + $this->typeMap;
$file = \MongoDB\BSON\toPHP(\MongoDB\BSON\fromPHP($file), $typeMap);
if ( ! isset($file->_id) && ! property_exists($file, '_id')) { if ( ! isset($file->_id) && ! property_exists($file, '_id')) {
throw new CorruptFileException('file._id does not exist'); throw new CorruptFileException('file._id does not exist');
...@@ -466,6 +479,31 @@ class Bucket ...@@ -466,6 +479,31 @@ class Bucket
return sprintf('%s.%s.files', $this->databaseName, $this->bucketName); return sprintf('%s.%s.files', $this->databaseName, $this->bucketName);
} }
/**
* Gets the file document of the GridFS file associated with a stream.
*
* This returns the raw document from the StreamWrapper, which does not
* respect the Bucket's type map.
*
* @param resource $stream GridFS stream
* @return stdClass
* @throws InvalidArgumentException
*/
private function getRawFileDocumentForStream($stream)
{
if ( ! is_resource($stream) || get_resource_type($stream) != "stream") {
throw InvalidArgumentException::invalidType('$stream', $stream, 'resource');
}
$metadata = stream_get_meta_data($stream);
if (!$metadata['wrapper_data'] instanceof StreamWrapper) {
throw InvalidArgumentException::invalidType('$stream wrapper data', $metadata['wrapper_data'], 'MongoDB\Driver\GridFS\StreamWrapper');
}
return $metadata['wrapper_data']->getFile();
}
/** /**
* Opens a readable stream for the GridFS file. * Opens a readable stream for the GridFS file.
* *
......
...@@ -68,8 +68,8 @@ class CollectionWrapper ...@@ -68,8 +68,8 @@ class CollectionWrapper
*/ */
public function dropCollections() public function dropCollections()
{ {
$this->filesCollection->drop(); $this->filesCollection->drop(['typeMap' => []]);
$this->chunksCollection->drop(); $this->chunksCollection->drop(['typeMap' => []]);
} }
/** /**
...@@ -284,6 +284,7 @@ class CollectionWrapper ...@@ -284,6 +284,7 @@ class CollectionWrapper
return null === $this->filesCollection->findOne([], [ return null === $this->filesCollection->findOne([], [
'readPreference' => new ReadPreference(ReadPreference::RP_PRIMARY), 'readPreference' => new ReadPreference(ReadPreference::RP_PRIMARY),
'projection' => ['_id' => 1], 'projection' => ['_id' => 1],
'typeMap' => [],
]); ]);
} }
} }
...@@ -57,6 +57,10 @@ class BucketFunctionalTest extends FunctionalTestCase ...@@ -57,6 +57,10 @@ class BucketFunctionalTest extends FunctionalTestCase
$options[][] = ['readPreference' => $value]; $options[][] = ['readPreference' => $value];
} }
foreach ($this->getInvalidArrayValues() as $value) {
$options[][] = ['typeMap' => $value];
}
foreach ($this->getInvalidWriteConcernValues() as $value) { foreach ($this->getInvalidWriteConcernValues() as $value) {
$options[][] = ['writeConcern' => $value]; $options[][] = ['writeConcern' => $value];
} }
...@@ -314,6 +318,16 @@ class BucketFunctionalTest extends FunctionalTestCase ...@@ -314,6 +318,16 @@ class BucketFunctionalTest extends FunctionalTestCase
$this->assertSameDocuments($expected, $cursor); $this->assertSameDocuments($expected, $cursor);
} }
public function testFindUsesTypeMap()
{
$this->bucket->uploadFromStream('a', $this->createStream('foo'));
$cursor = $this->bucket->find();
$fileDocument = current($cursor->toArray());
$this->assertInstanceOf('MongoDB\Model\BSONDocument', $fileDocument);
}
public function testGetBucketNameWithCustomValue() public function testGetBucketNameWithCustomValue()
{ {
$bucket = new Bucket($this->manager, $this->getDatabaseName(), ['bucketName' => 'custom_fs']); $bucket = new Bucket($this->manager, $this->getDatabaseName(), ['bucketName' => 'custom_fs']);
...@@ -331,6 +345,18 @@ class BucketFunctionalTest extends FunctionalTestCase ...@@ -331,6 +345,18 @@ class BucketFunctionalTest extends FunctionalTestCase
$this->assertEquals($this->getDatabaseName(), $this->bucket->getDatabaseName()); $this->assertEquals($this->getDatabaseName(), $this->bucket->getDatabaseName());
} }
public function testGetFileDocumentForStreamUsesTypeMap()
{
$metadata = ['foo' => 'bar'];
$stream = $this->bucket->openUploadStream('filename', ['_id' => 1, 'metadata' => $metadata]);
$fileDocument = $this->bucket->getFileDocumentForStream($stream);
$this->assertInstanceOf('MongoDB\Model\BSONDocument', $fileDocument);
$this->assertInstanceOf('MongoDB\Model\BSONDocument', $fileDocument['metadata']);
$this->assertSame(['foo' => 'bar'], $fileDocument['metadata']->getArrayCopy());
}
public function testGetFileDocumentForStreamWithReadableStream() public function testGetFileDocumentForStreamWithReadableStream()
{ {
$metadata = ['foo' => 'bar']; $metadata = ['foo' => 'bar'];
...@@ -366,6 +392,16 @@ class BucketFunctionalTest extends FunctionalTestCase ...@@ -366,6 +392,16 @@ class BucketFunctionalTest extends FunctionalTestCase
$this->bucket->getFileDocumentForStream($stream); $this->bucket->getFileDocumentForStream($stream);
} }
public function testGetFileIdForStreamUsesTypeMap()
{
$stream = $this->bucket->openUploadStream('filename', ['_id' => ['x' => 1]]);
$id = $this->bucket->getFileIdForStream($stream);
$this->assertInstanceOf('MongoDB\Model\BSONDocument', $id);
$this->assertSame(['x' => 1], $id->getArrayCopy());
}
public function testGetFileIdForStreamWithReadableStream() public function testGetFileIdForStreamWithReadableStream()
{ {
$id = $this->bucket->uploadFromStream('filename', $this->createStream('foobar')); $id = $this->bucket->uploadFromStream('filename', $this->createStream('foobar'));
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment