Commit 8117675e authored by Jeremy Mikola's avatar Jeremy Mikola

Merge pull request #290

parents 79e12213 f0090c88
...@@ -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
......
source:
file: apiargs-MongoDBCollection-method-find-option.yaml
ref: projection
---
source:
file: apiargs-MongoDBCollection-method-find-option.yaml
ref: sort
---
source:
file: apiargs-MongoDBCollection-method-find-option.yaml
ref: skip
---
source:
file: apiargs-MongoDBCollection-common-option.yaml
ref: collation
---
source:
file: apiargs-MongoDBCollection-method-find-option.yaml
ref: comment
---
source:
file: apiargs-common-option.yaml
ref: maxTimeMS
---
source:
file: apiargs-MongoDBCollection-method-find-option.yaml
ref: readConcern
---
source:
file: apiargs-MongoDBGridFSBucket-method-find-option.yaml
ref: readPreference
---
source:
file: apiargs-MongoDBGridFSBucket-method-find-option.yaml
ref: typeMap
post: |
This will be used for the returned result document.
---
source:
file: apiargs-MongoDBCollection-method-find-option.yaml
ref: modifiers
...
...@@ -40,6 +40,7 @@ Methods ...@@ -40,6 +40,7 @@ Methods
/reference/method/MongoDBGridFSBucket-downloadToStreamByName /reference/method/MongoDBGridFSBucket-downloadToStreamByName
/reference/method/MongoDBGridFSBucket-drop /reference/method/MongoDBGridFSBucket-drop
/reference/method/MongoDBGridFSBucket-find /reference/method/MongoDBGridFSBucket-find
/reference/method/MongoDBGridFSBucket-findOne
/reference/method/MongoDBGridFSBucket-getBucketName /reference/method/MongoDBGridFSBucket-getBucketName
/reference/method/MongoDBGridFSBucket-getDatabaseName /reference/method/MongoDBGridFSBucket-getDatabaseName
/reference/method/MongoDBGridFSBucket-getFileDocumentForStream /reference/method/MongoDBGridFSBucket-getFileDocumentForStream
......
...@@ -32,9 +32,9 @@ Definition ...@@ -32,9 +32,9 @@ Definition
Return Values Return Values
------------- -------------
An array or object for the :term:`first document <natural order>` document that An array or object for the :term:`first document <natural order>` that matched
matched the query, or ``null`` if no document matched the query. The return type the query, or ``null`` if no document matched the query. The return type will
will depend on the ``typeMap`` option. depend on the ``typeMap`` option.
Errors/Exceptions Errors/Exceptions
----------------- -----------------
......
...@@ -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
......
...@@ -40,3 +40,4 @@ See Also ...@@ -40,3 +40,4 @@ See Also
-------- --------
- :phpmethod:`MongoDB\\Collection::find()` - :phpmethod:`MongoDB\\Collection::find()`
- :phpmethod:`MongoDB\\GridFS\\Bucket::findOne()`
==================================
MongoDB\\GridFS\\Bucket::findOne()
==================================
.. default-domain:: mongodb
.. contents:: On this page
:local:
:backlinks: none
:depth: 1
:class: singlecol
Definition
----------
.. phpmethod:: MongoDB\\GridFS\\Bucket::findOne()
Finds a single document from the GridFS bucket's files collection matching
the query.
.. code-block:: php
function findOne($filter = [], array $options = []): array|object|null
This method has the following parameters:
.. include:: /includes/apiargs/MongoDBCollection-method-findOne-param.rst
The ``$options`` parameter supports the following options:
.. include:: /includes/apiargs/MongoDBGridFSBucket-method-findOne-option.rst
Return Values
-------------
An array or object for the :term:`first document <natural order>` that matched
the query, or ``null`` if no document matched the query. The return type will
depend on the ``typeMap`` option.
.. todo: add examples
See Also
--------
- :phpmethod:`MongoDB\\Collection::findOne()`
- :phpmethod:`MongoDB\\GridFS\\Bucket::find()`
...@@ -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,
]; ];
} }
...@@ -204,11 +218,25 @@ class Bucket ...@@ -204,11 +218,25 @@ class Bucket
* @param array $options Additional options * @param array $options Additional options
* @return Cursor * @return Cursor
*/ */
public function find($filter, array $options = []) public function find($filter = [], array $options = [])
{ {
return $this->collectionWrapper->findFiles($filter, $options); return $this->collectionWrapper->findFiles($filter, $options);
} }
/**
* Finds a single document from the GridFS bucket's files collection
* matching the query.
*
* @see FindOne::__construct() for supported options
* @param array|object $filter Query by which to filter documents
* @param array $options Additional options
* @return array|object|null
*/
public function findOne($filter = [], array $options = [])
{
return $this->collectionWrapper->findOneFile($filter, $options);
}
/** /**
* Return the bucket name. * Return the bucket name.
* *
...@@ -233,22 +261,15 @@ class Bucket ...@@ -233,22 +261,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);
if (!$metadata['wrapper_data'] instanceof StreamWrapper) { // Filter the raw document through the specified type map
throw InvalidArgumentException::invalidType('$stream wrapper data', $metadata['wrapper_data'], 'MongoDB\Driver\GridFS\StreamWrapper'); return \MongoDB\BSON\toPHP(\MongoDB\BSON\fromPHP($file), $this->typeMap);
}
return $metadata['wrapper_data']->getFile();
} }
/** /**
...@@ -261,7 +282,13 @@ class Bucket ...@@ -261,7 +282,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 +493,31 @@ class Bucket ...@@ -466,6 +493,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' => []]);
} }
/** /**
...@@ -140,6 +140,18 @@ class CollectionWrapper ...@@ -140,6 +140,18 @@ class CollectionWrapper
return $this->filesCollection->find($filter, $options); return $this->filesCollection->find($filter, $options);
} }
/**
* Finds a single document from the GridFS bucket's files collection.
*
* @param array|object $filter Query by which to filter documents
* @param array $options Additional options
* @return array|object|null
*/
public function findOneFile($filter, array $options = [])
{
return $this->filesCollection->findOne($filter, $options);
}
/** /**
* Return the bucket name. * Return the bucket name.
* *
...@@ -284,6 +296,7 @@ class CollectionWrapper ...@@ -284,6 +296,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,38 @@ class BucketFunctionalTest extends FunctionalTestCase ...@@ -314,6 +318,38 @@ 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 testFindOne()
{
$this->bucket->uploadFromStream('a', $this->createStream('foo'));
$this->bucket->uploadFromStream('b', $this->createStream('foobar'));
$this->bucket->uploadFromStream('c', $this->createStream('foobarbaz'));
$fileDocument = $this->bucket->findOne(
['length' => ['$lte' => 6]],
[
'projection' => [
'filename' => 1,
'length' => 1,
'_id' => 0,
],
'sort' => ['length' => -1],
]
);
$this->assertInstanceOf('MongoDB\Model\BSONDocument', $fileDocument);
$this->assertSameDocument(['filename' => 'b', 'length' => 6], $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 +367,18 @@ class BucketFunctionalTest extends FunctionalTestCase ...@@ -331,6 +367,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 +414,16 @@ class BucketFunctionalTest extends FunctionalTestCase ...@@ -366,6 +414,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