Bladeren bron

Implement MongoGridFS

Olivier Lechevalier 10 jaren geleden
bovenliggende
commit
86f2ee39d4

+ 211 - 13
lib/Mongo/MongoGridFS.php

@@ -13,7 +13,10 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-class MongoGridFS extends MongoCollection {
+class MongoGridFS extends MongoCollection
+{
+    const DEFAULT_CHUNK_SIZE = 262144; // 256 kb
+
     const ASCENDING = 1;
     const DESCENDING = -1;
 
@@ -35,7 +38,12 @@ class MongoGridFS extends MongoCollection {
      */
     protected $chunksName;
 
+    /**
+     * @var MongoDB
+     */
+    protected $database;
 
+    protected $ensureIndexes = false;
 
     /**
      * Files as stored across two collections, the first containing file meta
@@ -48,14 +56,34 @@ class MongoGridFS extends MongoCollection {
      * @param mixed $chunks  [optional]
      * @return MongoGridFS
      */
-    public function __construct($db, $prefix = "fs", $chunks = "fs") {}
+    public function __construct(MongoDB $db, $prefix = "fs", $chunks = null)
+    {
+        if ($chunks) {
+            trigger_error(E_DEPRECATED, "The 'chunks' argument is deprecated and ignored");
+        }
+        if (empty($prefix)) {
+            throw new \InvalidArgumentException('prefix can not be empty');
+        }
+
+        $this->database = $db;
+        $this->filesName = $prefix . '.files';
+        $this->chunksName = $prefix . '.chunks';
+
+        $this->chunks = $db->selectCollection($this->chunksName);
+
+        parent::__construct($db, $this->filesName);
+    }
 
     /**
      * Drops the files and chunks collections
      * @link http://php.net/manual/en/mongogridfs.drop.php
      * @return array The database response
      */
-    public function drop() {}
+    public function drop()
+    {
+        $this->chunks->drop();
+        parent::drop();
+    }
 
     /**
      * @link http://php.net/manual/en/mongogridfs.find.php
@@ -63,7 +91,13 @@ class MongoGridFS extends MongoCollection {
      * @param array $fields Fields to return
      * @return MongoGridFSCursor A MongoGridFSCursor
      */
-    public function find(array $query = array(), array $fields = array()) {}
+    public function find(array $query = array(), array $fields = array())
+    {
+        $cursor = new MongoGridFSCursor($this, $this->db->getConnection(), (string)$this, $query, $fields);
+        $cursor->setReadPreference($this->getReadPreference());
+
+        return $cursor;
+    }
 
     /**
      * Stores a file in the database
@@ -73,7 +107,28 @@ class MongoGridFS extends MongoCollection {
      * @param array $options Options for the store. "safe": Check that this store succeeded
      * @return mixed Returns the _id of the saved object
      */
-    public function storeFile($filename, $extra = array(), $options = array()) {}
+    public function storeFile($filename, array $extra = array(), array $options = array())
+    {
+        if (is_string($filename)) {
+            $md5 = md5_file($filename);
+            $shortName = basename($filename);
+            $filename = fopen($filename, 'r');
+        }
+        if (! is_resource($filename)) {
+            throw new \InvalidArgumentException();
+        }
+        $length = fstat($filename)['size'];
+        $extra['chunkSize'] = isset($extra['chunkSize']) ? $extra['chunkSize']: self::DEFAULT_CHUNK_SIZE;
+        $extra['_id'] = isset($extra['_id']) ?: new MongoId();
+        $extra['length'] = $length;
+        $extra['md5'] = isset($md5) ? $md5 : $this->calculateMD5($filename);
+        $extra['filename'] = isset($extra['filename']) ? $extra['filename'] : $shortName;
+
+        $fileDocument = $this->insertFile($extra);
+        $this->insertChunksFromFile($filename, $fileDocument);
+
+        return $fileDocument['_id'];
+    }
 
     /**
      * Chunkifies and stores bytes in the database
@@ -83,7 +138,19 @@ class MongoGridFS extends MongoCollection {
      * @param array $options Options for the store. "safe": Check that this store succeeded
      * @return mixed The _id of the object saved
      */
-    public function storeBytes($bytes, $extra = array(), $options = array()) {}
+    public function storeBytes($bytes, array $extra = array(), array $options = array())
+    {
+        $length = mb_strlen($bytes, '8bit');
+        $extra['chunkSize'] = isset($extra['chunkSize']) ? $extra['chunkSize'] : self::DEFAULT_CHUNK_SIZE;
+        $extra['_id'] = isset($extra['_id']) ?: new MongoId();
+        $extra['length'] = $length;
+        $extra['md5'] = md5($bytes);
+
+        $file = $this->insertFile($extra);
+        $this->insertChunksFromBytes($bytes, $file);
+
+        return $file['_id'];
+    }
 
     /**
      * Returns a single file matching the criteria
@@ -92,7 +159,14 @@ class MongoGridFS extends MongoCollection {
      * @param array $fields Fields of the results to return.
      * @return MongoGridFSFile|null
      */
-    public function findOne(array $query = array(), array $fields = array()) {}
+    public function findOne(array $query = [], array $fields = [], array $options = [])
+    {
+        $file = parent::findOne($query, $fields);
+        if (! $file) {
+            return;
+        }
+        return new MongoGridFSFile($this, $file);
+    }
 
     /**
      * Removes files from the collections
@@ -102,7 +176,16 @@ class MongoGridFS extends MongoCollection {
      * @throws MongoCursorException
      * @return boolean
      */
-    public function remove(array $criteria = array(), array $options = array()) {}
+    public function remove(array $criteria = [], array $options = [])
+    {
+        $matchingFiles = parent::find($criteria, ['_id' => 1]);
+        $ids = [];
+        foreach ($matchingFiles as $file) {
+            $ids[] = $file['_id'];
+        }
+        $this->chunks->remove(['files_id' => ['$in' => $ids]], ['justOne' => false]);
+        return parent::remove($criteria, ['justOne' => false] + $options);
+    }
 
     /**
      * Delete a file from the database
@@ -110,7 +193,17 @@ class MongoGridFS extends MongoCollection {
      * @param mixed $id _id of the file to remove
      * @return boolean Returns true if the remove was successfully sent to the database.
      */
-    public function delete($id) {}
+    public function delete($id)
+    {
+        if (is_string($id)) {
+            $id = new MongoId($id);
+        }
+        if (! $id instanceof MongoId) {
+            return false;
+        }
+        $this->chunks->remove(['files_id' => $id], ['justOne' => false]);
+        return parent::remove(['_id' => $id]);
+    }
 
     /**
      * Saves an uploaded file directly from a POST to the database
@@ -119,8 +212,14 @@ class MongoGridFS extends MongoCollection {
      * @param array $metadata An array of extra fields for the uploaded file.
      * @return mixed Returns the _id of the uploaded file.
      */
-    public function storeUpload($name, array $metadata = array()) {}
-
+    public function storeUpload($name, array $metadata = array())
+    {
+        if (! isset($_FILES[$name]) || $_FILES[$name]['error'] !== UPLOAD_ERR_OK) {
+            throw new \InvalidArgumentException();
+        }
+        $metadata += ['filename' => $_FILES[$name]['name']];
+        return $this->storeFile($_FILES[$name]['tmp_name'], $metadata);
+    }
 
     /**
      * Retrieve a file from the database
@@ -128,7 +227,16 @@ class MongoGridFS extends MongoCollection {
      * @param mixed $id _id of the file to find.
      * @return MongoGridFSFile|null Returns the file, if found, or NULL.
      */
-    public function __get($id) {}
+    public function __get($id)
+    {
+        if (is_string($id)) {
+            $id = new MongoId($id);
+        }
+        if (! $id instanceof MongoId) {
+            return false;
+        }
+        return $this->findOne(['_id' => $id]);
+    }
 
     /**
      * Stores a file in the database
@@ -137,6 +245,96 @@ class MongoGridFS extends MongoCollection {
      * @param array $extra Other metadata to add to the file saved
      * @return mixed Returns the _id of the saved object
      */
-    public function put($filename, array $extra = array()) {}
+    public function put($filename, array $extra = array())
+    {
+        return $this->storeFile($filename, $extra);
+    }
+
+    private function ensureIndexes()
+    {
+        if ($this->ensureIndexes) {
+            return;
+        }
+        $this->ensureFilesIndex();
+        $this->ensureChunksIndex();
+        $this->ensuredIndexes = true;
+    }
+
+    private function ensureChunksIndex()
+    {
+        foreach ($this->chunks->getIndexInfo() as $index) {
+            if (isset($index['unique']) && $index['unique'] && $index['key'] === ['files_id' => 1, 'n' => 1]) {
+                return;
+            }
+        }
+        $this->chunks->createIndex(['files_id' => 1, 'n' => 1], ['unique' => true]);
+    }
+
+    private function ensureFilesIndex()
+    {
+        foreach ($this->getIndexInfo() as $index) {
+            if ($index['key'] === ['filename' => 1, 'uploadDate' => 1]) {
+                return;
+            }
+        }
+        $this->createIndex(['filename' => 1, 'uploadDate' => 1]);
+    }
+
+    private function insertChunksFromFile($file, $fileInfo)
+    {
+        $length = $fileInfo['length'];
+        $chunkSize = $fileInfo['chunkSize'];
+        $fileId = $fileInfo['_id'];
+        $offset = 0;
+        $i = 0;
+
+        rewind($file);
+
+        while ($offset < $length) {
+            $data = stream_get_contents($file, $chunkSize);
+            $this->insertChunk($fileId, $data, $i++);
+            $offset += $chunkSize;
+        }
+    }
+
+    private function calculateMD5($file)
+    {
+        // XXX: this could be really a bad idea with big files...
+        rewind($file);
+        $data = stream_get_contents($file);
+
+        return md5($data);
+    }
+
+    private function insertChunksFromBytes($bytes, $fileInfo)
+    {
+        $length = $fileInfo['length'];
+        $chunkSize = $fileInfo['chunkSize'];
+        $fileId = $fileInfo['_id'];
+        $i = 0;
+
+        $chunks = str_split($bytes, $chunkSize);
+        foreach ($chunks as $chunk) {
+            $this->insertChunk($fileId, $chunk, $i++);
+        }
+    }
+
+    private function insertChunk($id, $data, $chunkNumber)
+    {
+        $chunk = [
+            'files_id' => $id,
+            'n' => $chunkNumber,
+            'data' => new MongoBinData($data),
+        ];
+        return $this->chunks->insert($chunk);
+    }
+
+    private function insertFile($metadata)
+    {
+        $this->ensureIndexes();
+        $metadata['uploadDate'] = new MongoDate();
+        $this->insert($metadata);
+        return $metadata;
+    }
 
 }

+ 25 - 0
tests/Alcaeus/MongoDbAdapter/MongoCollectionTest.php

@@ -339,6 +339,31 @@ class MongoCollectionTest extends TestCase
         $this->assertAttributeSame('bar', 'foo', $object);
     }
 
+    public function testRemoveOne()
+    {
+        $id = '54203e08d51d4a1f868b456e';
+        $collection = $this->getCollection();
+
+        $collection->insert(['_id' => new \MongoId($id), 'foo' => 'bar']);
+        $collection->remove(['_id' => new \MongoId($id)]);
+
+        $newCollection = $this->getCheckDatabase()->selectCollection('test');
+        $this->assertSame(0, $newCollection->count());
+    }
+
+    public function testRemoveMultiple()
+    {
+        $collection = $this->getCollection();
+
+
+        $collection->insert(['foo' => 'bar']);
+        $collection->insert(['foo' => 'bar']);
+        $collection->remove([], ['justOne' => false]);
+
+        $newCollection = $this->getCheckDatabase()->selectCollection('test');
+        $this->assertSame(0, $newCollection->count());
+    }
+
     public function testSaveUpdate()
     {
         $id = '54203e08d51d4a1f868b456e';

+ 353 - 0
tests/Alcaeus/MongoDbAdapter/MongoGridFSTest.php

@@ -0,0 +1,353 @@
+<?php
+
+namespace Alcaeus\MongoDbAdapter\Tests;
+
+class MongoGridFSTest extends TestCase
+{
+    public function testChunkProperty()
+    {
+        $collection = $this->getGridFS();
+        $this->assertInstanceOf('MongoCollection', $collection->chunks);
+        $this->assertSame('mongo-php-adapter.testfs.chunks', (string) $collection->chunks);
+    }
+
+    public function testCustomCollectionName()
+    {
+        $collection = $this->getGridFS('foofs');
+        $this->assertSame('mongo-php-adapter.foofs.files', (string) $collection);
+        $this->assertInstanceOf('MongoCollection', $collection->chunks);
+        $this->assertSame('mongo-php-adapter.foofs.chunks', (string) $collection->chunks);
+    }
+
+    public function testDrop()
+    {
+        $collection = $this->getGridFS();
+
+        $collection->insert(['foo' => 'bar']);
+        $collection->chunks->insert(['foo' => 'bar']);
+
+        $collection->drop();
+
+        $newCollection = $this->getCheckDatabase()->selectCollection('testfs.files');
+        $newChunksCollection = $this->getCheckDatabase()->selectCollection('testfs.chunks');
+        $this->assertSame(0, $newCollection->count());
+        $this->assertSame(0, $newChunksCollection->count());
+    }
+
+    public function testFindReturnsGridFSCursor()
+    {
+        $this->prepareData();
+        $collection = $this->getGridFS();
+
+        $this->assertInstanceOf('MongoGridFSCursor', $collection->find());
+    }
+
+    public function testStoringData()
+    {
+        $collection = $this->getGridFS();
+
+        $id = $collection->storeBytes(
+            'abcd',
+            [
+                'foo' => 'bar',
+                'chunkSize' => 2,
+            ]
+        );
+
+        $newCollection = $this->getCheckDatabase()->selectCollection('testfs.files');
+        $newChunksCollection = $this->getCheckDatabase()->selectCollection('testfs.chunks');
+        $this->assertSame(1, $newCollection->count());
+        $this->assertSame(2, $newChunksCollection->count());
+
+        $record = $newCollection->findOne();
+        $this->assertNotNull($record);
+        $this->assertAttributeInstanceOf('MongoDB\BSON\ObjectID', '_id', $record);
+        $this->assertSame((string) $id, (string) $record->_id);
+        $this->assertObjectHasAttribute('foo', $record);
+        $this->assertAttributeSame('bar', 'foo', $record);
+        $this->assertObjectHasAttribute('length', $record);
+        $this->assertAttributeSame(4, 'length', $record);
+        $this->assertObjectHasAttribute('chunkSize', $record);
+        $this->assertAttributeSame(2, 'chunkSize', $record);
+        $this->assertObjectHasAttribute('md5', $record);
+        $this->assertAttributeSame('e2fc714c4727ee9395f324cd2e7f331f', 'md5', $record);
+
+        $chunksCursor = $newChunksCollection->find([], ['sort' => ['n' => 1]]);
+        $chunks = iterator_to_array($chunksCursor);
+        $firstChunk = $chunks[0];
+        $this->assertNotNull($firstChunk);
+        $this->assertAttributeInstanceOf('MongoDB\BSON\ObjectID', 'files_id', $firstChunk);
+        $this->assertSame((string) $id, (string) $firstChunk->files_id);
+        $this->assertAttributeSame(0, 'n', $firstChunk);
+        $this->assertAttributeInstanceOf('MongoDB\BSON\Binary', 'data', $firstChunk);
+        $this->assertSame('ab', (string) $firstChunk->data->getData());
+
+        $secondChunck = $chunks[1];
+        $this->assertNotNull($secondChunck);
+        $this->assertAttributeInstanceOf('MongoDB\BSON\ObjectID', 'files_id', $secondChunck);
+        $this->assertSame((string) $id, (string) $secondChunck->files_id);
+        $this->assertAttributeSame(1, 'n', $secondChunck);
+        $this->assertAttributeInstanceOf('MongoDB\BSON\Binary', 'data', $secondChunck);
+        $this->assertSame('cd', (string) $secondChunck->data->getData());
+    }
+
+    public function testIndexesCreation()
+    {
+        $collection = $this->getGridFS();
+
+        $id = $collection->storeBytes(
+            'abcd',
+            [
+                'foo' => 'bar',
+                'chunkSize' => 2,
+            ]
+        );
+
+        $newCollection = $this->getCheckDatabase()->selectCollection('testfs.files');
+
+        $indexes = iterator_to_array($newCollection->listIndexes());
+        $this->assertCount(2, $indexes);
+        $index = $indexes[1];
+        $this->assertSame(['filename' => 1, 'uploadDate' => 1], $index->getKey());
+
+        $newChunksCollection = $this->getCheckDatabase()->selectCollection('testfs.chunks');
+        $indexes = iterator_to_array($newChunksCollection->listIndexes());
+        $this->assertCount(2, $indexes);
+        $index = $indexes[1];
+        $this->assertSame(['files_id' => 1, 'n' => 1], $index->getKey());
+        $this->assertTrue($index->isUnique());
+    }
+
+
+    public function testDelete()
+    {
+        $collection = $this->getGridFS();
+        $id = $this->prepareFile();
+
+        $collection->delete($id);
+
+        $newCollection = $this->getCheckDatabase()->selectCollection('testfs.files');
+        $newChunksCollection = $this->getCheckDatabase()->selectCollection('testfs.chunks');
+        $this->assertSame(0, $newCollection->count());
+        $this->assertSame(0, $newChunksCollection->count());
+    }
+
+    public function testRemove()
+    {
+        $collection = $this->getGridFS();
+        $this->prepareFile('data', ['foo' => 'bar']);
+        $this->prepareFile('data', ['foo' => 'bar']);
+
+        $collection->remove(['foo' => 'bar']);
+
+        $newCollection = $this->getCheckDatabase()->selectCollection('testfs.files');
+        $newChunksCollection = $this->getCheckDatabase()->selectCollection('testfs.chunks');
+        $this->assertSame(0, $newCollection->count());
+        $this->assertSame(0, $newChunksCollection->count());
+    }
+
+    public function testStoreFile()
+    {
+        $collection = $this->getGridFS();
+
+        $id = $collection->storeFile(__FILE__, ['chunkSize' => 100, 'foo' => 'bar']);
+
+
+        $newCollection = $this->getCheckDatabase()->selectCollection('testfs.files');
+        $newChunksCollection = $this->getCheckDatabase()->selectCollection('testfs.chunks');
+        $this->assertSame(1, $newCollection->count());
+
+        $md5 = md5_file(__FILE__);
+        $size = filesize(__FILE__);
+        $filename = basename(__FILE__);
+        $record = $newCollection->findOne();
+        $this->assertNotNull($record);
+        $this->assertAttributeInstanceOf('MongoDB\BSON\ObjectID', '_id', $record);
+        $this->assertSame((string) $id, (string) $record->_id);
+        $this->assertObjectHasAttribute('foo', $record);
+        $this->assertAttributeSame('bar', 'foo', $record);
+        $this->assertObjectHasAttribute('length', $record);
+        $this->assertAttributeSame($size, 'length', $record);
+        $this->assertObjectHasAttribute('chunkSize', $record);
+        $this->assertAttributeSame(100, 'chunkSize', $record);
+        $this->assertObjectHasAttribute('md5', $record);
+        $this->assertAttributeSame($md5, 'md5', $record);
+        $this->assertObjectHasAttribute('filename', $record);
+        $this->assertAttributeSame($filename, 'filename', $record);
+
+        $numberOfChunks = (int)ceil($size / 100);
+        $this->assertSame($numberOfChunks, $newChunksCollection->count());
+        $expectedContent = substr(file_get_contents(__FILE__), 0, 100);
+
+        $firstChunk = $newChunksCollection->findOne([], ['sort' => ['n' => 1]]);
+        $this->assertNotNull($firstChunk);
+        $this->assertAttributeInstanceOf('MongoDB\BSON\ObjectID', 'files_id', $firstChunk);
+        $this->assertSame((string) $id, (string) $firstChunk->files_id);
+        $this->assertAttributeSame(0, 'n', $firstChunk);
+        $this->assertAttributeInstanceOf('MongoDB\BSON\Binary', 'data', $firstChunk);
+        $this->assertSame($expectedContent, (string) $firstChunk->data->getData());
+    }
+
+    public function testStoreFileResource()
+    {
+        $collection = $this->getGridFS();
+
+        $id = $collection->storeFile(
+            fopen(__FILE__, 'r'),
+            ['chunkSize' => 100, 'foo' => 'bar', 'filename' => 'test.php']
+        );
+
+
+        $newCollection = $this->getCheckDatabase()->selectCollection('testfs.files');
+        $newChunksCollection = $this->getCheckDatabase()->selectCollection('testfs.chunks');
+        $this->assertSame(1, $newCollection->count());
+
+        $md5 = md5_file(__FILE__);
+        $size = filesize(__FILE__);
+        $filename = basename(__FILE__);
+        $record = $newCollection->findOne();
+        $this->assertNotNull($record);
+        $this->assertAttributeInstanceOf('MongoDB\BSON\ObjectID', '_id', $record);
+        $this->assertSame((string) $id, (string) $record->_id);
+        $this->assertObjectHasAttribute('foo', $record);
+        $this->assertAttributeSame('bar', 'foo', $record);
+        $this->assertObjectHasAttribute('length', $record);
+        $this->assertAttributeSame($size, 'length', $record);
+        $this->assertObjectHasAttribute('chunkSize', $record);
+        $this->assertAttributeSame(100, 'chunkSize', $record);
+        $this->assertObjectHasAttribute('md5', $record);
+        $this->assertAttributeSame($md5, 'md5', $record);
+        $this->assertObjectHasAttribute('filename', $record);
+        $this->assertAttributeSame('test.php', 'filename', $record);
+
+        $numberOfChunks = (int)ceil($size / 100);
+        $this->assertSame($numberOfChunks, $newChunksCollection->count());
+        $expectedContent = substr(file_get_contents(__FILE__), 0, 100);
+
+        $firstChunk = $newChunksCollection->findOne([], ['sort' => ['n' => 1]]);
+        $this->assertNotNull($firstChunk);
+        $this->assertAttributeInstanceOf('MongoDB\BSON\ObjectID', 'files_id', $firstChunk);
+        $this->assertSame((string) $id, (string) $firstChunk->files_id);
+        $this->assertAttributeSame(0, 'n', $firstChunk);
+        $this->assertAttributeInstanceOf('MongoDB\BSON\Binary', 'data', $firstChunk);
+        $this->assertSame($expectedContent, (string) $firstChunk->data->getData());
+    }
+
+    public function testStoreUpload()
+    {
+        $collection = $this->getGridFS();
+
+        $_FILES['foo'] = [
+            'name' => 'test.php',
+            'error' => UPLOAD_ERR_OK,
+            'tmp_name' => __FILE__,
+        ];
+
+        $id = $collection->storeUpload(
+            'foo',
+            ['chunkSize' => 100, 'foo' => 'bar']
+        );
+
+
+        $newCollection = $this->getCheckDatabase()->selectCollection('testfs.files');
+        $newChunksCollection = $this->getCheckDatabase()->selectCollection('testfs.chunks');
+        $this->assertSame(1, $newCollection->count());
+
+        $md5 = md5_file(__FILE__);
+        $size = filesize(__FILE__);
+        $filename = basename(__FILE__);
+        $record = $newCollection->findOne();
+        $this->assertNotNull($record);
+        $this->assertAttributeInstanceOf('MongoDB\BSON\ObjectID', '_id', $record);
+        $this->assertSame((string) $id, (string) $record->_id);
+        $this->assertObjectHasAttribute('foo', $record);
+        $this->assertAttributeSame('bar', 'foo', $record);
+        $this->assertObjectHasAttribute('length', $record);
+        $this->assertAttributeSame($size, 'length', $record);
+        $this->assertObjectHasAttribute('chunkSize', $record);
+        $this->assertAttributeSame(100, 'chunkSize', $record);
+        $this->assertObjectHasAttribute('md5', $record);
+        $this->assertAttributeSame($md5, 'md5', $record);
+        $this->assertObjectHasAttribute('filename', $record);
+        $this->assertAttributeSame('test.php', 'filename', $record);
+
+        $numberOfChunks = (int)ceil($size / 100);
+        $this->assertSame($numberOfChunks, $newChunksCollection->count());
+    }
+
+    public function testFindOneReturnsFile()
+    {
+        $collection = $this->getGridFS();
+        $this->prepareFile();
+
+        $result = $collection->findOne();
+
+        $this->assertInstanceOf('MongoGridFSFile', $result);
+    }
+
+    public function testMagicGetter()
+    {
+        $collection = $this->getGridFS();
+        $id = (string) $this->prepareFile();
+
+        $result = $collection->$id;
+
+        $this->assertInstanceOf('MongoGridFSFile', $result);
+    }
+
+    public function testPut()
+    {
+        $collection = $this->getGridFS();
+
+        $id = $collection->put(__FILE__, ['chunkSize' => 100, 'foo' => 'bar']);
+
+
+        $newCollection = $this->getCheckDatabase()->selectCollection('testfs.files');
+        $newChunksCollection = $this->getCheckDatabase()->selectCollection('testfs.chunks');
+        $this->assertSame(1, $newCollection->count());
+
+        $size = filesize(__FILE__);
+        $numberOfChunks = (int)ceil($size / 100);
+        $this->assertSame($numberOfChunks, $newChunksCollection->count());
+    }
+
+    /**
+     * @var \MongoID
+     */
+    protected function prepareFile($data = 'abcd', $extra = [])
+    {
+        $collection = $this->getGridFS();
+
+        // to make sure we have multiple chunks
+        $extra += ['chunkSize' => 2];
+
+        return $collection->storeBytes($data, $extra);
+    }
+
+    /**
+     * @param string $name
+     * @param \MongoDB|null $database
+     * @return \MongoGridFS
+     */
+    protected function getGridFS($name = 'testfs', \MongoDB $database = null)
+    {
+        if ($database === null) {
+            $database = $this->getDatabase();
+        }
+
+        return new \MongoGridFS($database, $name);
+    }
+
+    /**
+     * @return \MongoCollection
+     */
+    protected function prepareData()
+    {
+        $collection = $this->getGridFS();
+
+        $collection->insert(['foo' => 'bar']);
+        $collection->insert(['foo' => 'bar']);
+        $collection->insert(['foo' => 'foo']);
+        return $collection;
+    }
+}