Permalink
Browse files

Added GridFS storage driver and test case

  • Loading branch information...
1 parent 52766a4 commit 85019cdc2d12e4958ee0ff74e0d96b265b45d8af @christeredvartsen christeredvartsen committed Apr 16, 2012
Showing with 507 additions and 0 deletions.
  1. +245 −0 library/Imbo/Storage/GridFS.php
  2. +262 −0 tests/Imbo/Storage/GridFSTest.php
View
245 library/Imbo/Storage/GridFS.php
@@ -0,0 +1,245 @@
+<?php
+/**
+ * Imbo
+ *
+ * Copyright (c) 2011-2012, Christer Edvartsen <cogo@starzinger.net>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * @package Storage
+ * @author Christer Edvartsen <cogo@starzinger.net>
+ * @copyright Copyright (c) 2011-2012, Christer Edvartsen <cogo@starzinger.net>
+ * @license http://www.opensource.org/licenses/mit-license MIT License
+ * @link https://github.com/imbo/imbo
+ */
+
+namespace Imbo\Storage;
+
+use Imbo\Image\ImageInterface,
+ Imbo\Exception\StorageException,
+ Imbo\Exception,
+ Mongo,
+ MongoGridFS,
+ MongoCursorException,
+ DateTime;
+
+/**
+ * GridFS (MongoDB) database driver
+ *
+ * A GridFS storage driver for Imbo
+ *
+ * Valid parameters for this driver:
+ *
+ * - <pre>(string) databaseName</pre> Name of the database. Defaults to 'imbo_storage'
+ * - <pre>(string) server</pre> The server string to use when connecting to MongoDB. Defaults to
+ * 'mongodb://localhost:27017'
+ * - <pre>(array) options</pre> Options to use when creating the Mongo instance. Defaults to
+ * array('connect' => true, 'timeout' => 1000).
+ * - <pre>(string) slaveOk</pre> If reads should be sent to secondary members of a replica set for
+ * all possible queries. Defaults to false.
+ *
+ * @package Storage
+ * @author Christer Edvartsen <cogo@starzinger.net>
+ * @copyright Copyright (c) 2011-2012, Christer Edvartsen <cogo@starzinger.net>
+ * @license http://www.opensource.org/licenses/mit-license MIT License
+ * @link https://github.com/imbo/imbo
+ */
+class GridFS implements StorageInterface {
+ /**
+ * Mongo instance
+ *
+ * @var Mongo
+ */
+ private $mongo;
+
+ /**
+ * The grid instance
+ *
+ * @var MongoGridFS
+ */
+ private $grid;
+
+ /**
+ * Parameters for the driver
+ *
+ * @var array
+ */
+ private $params = array(
+ // Database name
+ 'databaseName' => 'imbo_storage',
+
+ // Server string and ctor options
+ 'server' => 'mongodb://localhost:27017',
+ 'options' => array('connect' => true, 'timeout' => 1000),
+
+ // Other options
+ 'slaveOk' => true,
+ );
+
+ /**
+ * Class constructor
+ *
+ * @param array $params Parameters for the driver
+ * @param Mongo $mongo Mongo instance
+ * @param MongoGridFS $grid MongoGridFS instance
+ */
+ public function __construct(array $params = null, Mongo $mongo = null, MongoGridFS $grid = null) {
+ if ($params !== null) {
+ $this->params = array_replace_recursive($this->params, $params);
+ }
+
+ if ($mongo !== null) {
+ $this->mongo = $mongo;
+ }
+
+ if ($grid !== null) {
+ $this->grid = $grid;
+ }
+ }
+
+ /**
+ * @see Imbo\Storage\StorageInterface::store()
+ */
+ public function store($publicKey, $imageIdentifier, ImageInterface $image) {
+ if ($this->imageExists($publicKey, $imageIdentifier, true) !== false) {
+ $e = new StorageException('Image already exists', 400);
+ $e->setImboErrorCode(Exception::IMAGE_ALREADY_EXISTS);
+
+ throw $e;
+ }
+
+ $extra = array(
+ 'publicKey' => $publicKey,
+ 'imageIdentifier' => $imageIdentifier,
+ 'created' => time(),
+ );
+
+ $options = array('safe' => true);
+
+ try {
+ $result = $this->getGrid()->storeBytes($image->getBlob(), $extra, $options);
+ } catch (MongoCursorException $e) {
+ throw new StorageException('Could not store file', 500, $e);
+ }
+
+ return true;
+ }
+
+ /**
+ * @see Imbo\Storage\StorageInterface::delete()
+ */
+ public function delete($publicKey, $imageIdentifier) {
+ if (($file = $this->imageExists($publicKey, $imageIdentifier)) === false) {
+ throw new StorageException('File not found', 404);
+ }
+
+ return $this->getGrid()->delete($file->file['_id']);
+ }
+
+ /**
+ * @see Imbo\Storage\StorageInterface::load()
+ */
+ public function load($publicKey, $imageIdentifier, ImageInterface $image) {
+ if (($file = $this->imageExists($publicKey, $imageIdentifier)) === false) {
+ throw new StorageException('File not found', 404);
+ }
+
+ $image->setBlob($file->getBytes());
+
+ return true;
+ }
+
+ /**
+ * @see Imbo\Storage\StorageInterface::getLastModified()
+ */
+ public function getLastModified($publicKey, $imageIdentifier, $formatted = false) {
+ if (($file = $this->imageExists($publicKey, $imageIdentifier)) === false) {
+ throw new StorageException('File not found', 404);
+ }
+
+ $timestamp = $file->file['created'];
+ $date = new DateTime('@' . $timestamp);
+
+ if ($formatted) {
+ return $date->format('D, d M Y H:i:s') . ' GMT';
+ }
+
+ return $date;
+ }
+
+ /**
+ * Get the grid instance
+ *
+ * @return MongoGridFS
+ */
+ protected function getGrid() {
+ if ($this->grid === null) {
+ try {
+ $database = $this->getMongo()->selectDB($this->params['databaseName']);
+ $this->grid = $database->getGridFS();
+ } catch (InvalidArgumentException $d) {
+ throw new DatabaseException('Could not fetch grid FS', 500);
+ }
+ }
+
+ return $this->grid;
+ }
+
+ /**
+ * Get the mongo instance
+ *
+ * @return Mongo
+ */
+ protected function getMongo() {
+ if ($this->mongo === null) {
+ try {
+ $this->mongo = new Mongo($this->params['server'], $this->params['options']);
+
+ if ($this->params['slaveOk'] === true) {
+ $this->mongo->setSlaveOkay(true);
+ }
+ } catch (MongoException $e) {
+ throw new StorageException('Could not connect to database', 500);
+ }
+ }
+
+ return $this->mongo;
+ }
+
+ /**
+ * Check if an image exists
+ *
+ * @param string $publicKey The public key of the user
+ * @param string $imageIdentifier The image identifier
+ * @return boolean|MongoGridFSFile Returns false if the file does not exist or an instance of
+ * MongoGridFSFile if the file exists
+ */
+ private function imageExists($publicKey, $imageIdentifier) {
+ $cursor = $this->getGrid()->find(array(
+ 'publicKey' => $publicKey,
+ 'imageIdentifier' => $imageIdentifier
+ ));
+
+ if ($cursor->count()) {
+ return $cursor->getNext();
+ }
+
+ return false;
+ }
+}
View
262 tests/Imbo/Storage/GridFSTest.php
@@ -0,0 +1,262 @@
+<?php
+/**
+ * Imbo
+ *
+ * Copyright (c) 2011-2012, Christer Edvartsen <cogo@starzinger.net>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * @package Unittests
+ * @author Christer Edvartsen <cogo@starzinger.net>
+ * @copyright Copyright (c) 2011-2012, Christer Edvartsen <cogo@starzinger.net>
+ * @license http://www.opensource.org/licenses/mit-license MIT License
+ * @link https://github.com/imbo/imbo
+ */
+
+namespace Imbo\Storage;
+
+use DateTime,
+ MongoGridFSFile;
+
+/**
+ * @package Unittests
+ * @author Christer Edvartsen <cogo@starzinger.net>
+ * @copyright Copyright (c) 2011-2012, Christer Edvartsen <cogo@starzinger.net>
+ * @license http://www.opensource.org/licenses/mit-license MIT License
+ * @link https://github.com/imbo/imbo
+ * @covers Imbo\Storage\Filesystem
+ */
+class GridFSTest extends \PHPUnit_Framework_TestCase {
+ /**
+ * @var Imbo\Storage\GridFS
+ */
+ private $driver;
+
+ /**
+ * @var MongoGridFS
+ */
+ private $grid;
+
+ /**
+ * Public key that can be used in tests
+ *
+ * @var string
+ */
+ private $publicKey = 'key';
+
+ /**
+ * Image identifier that can be used in tests
+ *
+ * @var string
+ */
+ private $imageIdentifier = '96d08a5943ebf1c5635a2995c9408cdd';
+
+ /**
+ * Setup method
+ */
+ public function setUp() {
+ $this->grid = $this->getMockBuilder('MongoGridFS')->disableOriginalConstructor()->getMock();
+ $this->driver = new GridFS(array(), null, $this->grid);
+ }
+
+ /**
+ * @expectedException Imbo\Exception\StorageException
+ * @expectedExceptionCode 400
+ * @covers Imbo\Storage\GridFS::store
+ * @covers Imbo\Storage\GridFS::imageExists
+ */
+ public function testStoreWhenImageExists() {
+ $file = $this->getMockBuilder('MongoGridFSFile')->disableOriginalConstructor()->getMock();
+
+ $cursor = $this->getMockBuilder('MongoGridFSCursor')->disableOriginalConstructor()->getMock();
+ $cursor->expects($this->once())->method('count')->will($this->returnValue(1));
+ $cursor->expects($this->once())->method('getNext')->will($this->returnValue($file));
+
+ $this->grid->expects($this->once())->method('find')->will($this->returnValue($cursor));
+
+ $this->driver->store($this->publicKey, $this->imageIdentifier, $this->getMock('Imbo\Image\ImageInterface'));
+ }
+
+ /**
+ * @covers Imbo\Storage\GridFS::store
+ * @covers Imbo\Storage\GridFS::imageExists
+ */
+ public function testStore() {
+ $data = 'some content';
+
+ $image = $this->getMock('Imbo\Image\ImageInterface');
+ $image->expects($this->once())->method('getBlob')->will($this->returnValue($data));
+
+ $cursor = $this->getMockBuilder('MongoGridFSCursor')->disableOriginalConstructor()->getMock();
+ $cursor->expects($this->once())->method('count')->will($this->returnValue(0));
+
+ $this->grid->expects($this->once())->method('find')->will($this->returnValue($cursor));
+ $this->grid->expects($this->once())->method('storeBytes')->with($data, $this->isType('array'), $this->isType('array'))->will($this->returnValue($cursor));
+
+ $this->assertTrue($this->driver->store($this->publicKey, $this->imageIdentifier, $image));
+ }
+
+ /**
+ * @expectedException Imbo\Exception\StorageException
+ * @expectedExceptionCode 500
+ * @covers Imbo\Storage\GridFS::store
+ * @covers Imbo\Storage\GridFS::imageExists
+ */
+ public function testStoreWhenMongoThrowsException() {
+ $data = 'some content';
+
+ $image = $this->getMock('Imbo\Image\ImageInterface');
+ $image->expects($this->once())->method('getBlob')->will($this->returnValue($data));
+
+ $cursor = $this->getMockBuilder('MongoGridFSCursor')->disableOriginalConstructor()->getMock();
+ $cursor->expects($this->once())->method('count')->will($this->returnValue(0));
+
+ $this->grid->expects($this->once())->method('find')->will($this->returnValue($cursor));
+ $this->grid->expects($this->once())->method('storeBytes')->will($this->throwException($this->getMock('MongoCursorException')));
+
+ $this->assertTrue($this->driver->store($this->publicKey, $this->imageIdentifier, $image));
+ }
+
+ /**
+ * @expectedException Imbo\Exception\StorageException
+ * @expectedExceptionCode 404
+ * @covers Imbo\Storage\GridFS::delete
+ * @covers Imbo\Storage\GridFS::imageExists
+ */
+ public function testDeleteFileThatDoesNotExist() {
+ $cursor = $this->getMockBuilder('MongoGridFSCursor')->disableOriginalConstructor()->getMock();
+ $cursor->expects($this->once())->method('count')->will($this->returnValue(0));
+
+ $this->grid->expects($this->once())->method('find')->will($this->returnValue($cursor));
+
+ $this->driver->delete($this->publicKey, $this->imageIdentifier);
+ }
+
+ /**
+ * @covers Imbo\Storage\GridFS::delete
+ * @covers Imbo\Storage\GridFS::imageExists
+ */
+ public function testDeleteFile() {
+ $file = $this->getMockBuilder('MongoGridFSFile')->disableOriginalConstructor()->getMock();
+
+ $cursor = $this->getMockBuilder('MongoGridFSCursor')->disableOriginalConstructor()->getMock();
+ $cursor->expects($this->once())->method('count')->will($this->returnValue(1));
+ $cursor->expects($this->once())->method('getNext')->will($this->returnValue($file));
+
+ $this->grid->expects($this->once())->method('find')->will($this->returnValue($cursor));
+ $this->grid->expects($this->once())->method('delete');
+
+ $this->driver->delete($this->publicKey, $this->imageIdentifier);
+ }
+
+ /**
+ * @expectedException Imbo\Exception\StorageException
+ * @expectedExceptionCode 404
+ * @covers Imbo\Storage\GridFS::load
+ * @covers Imbo\Storage\GridFS::imageExists
+ */
+ public function testLoadFileThatDoesNotExist() {
+ $cursor = $this->getMockBuilder('MongoGridFSCursor')->disableOriginalConstructor()->getMock();
+ $cursor->expects($this->once())->method('count')->will($this->returnValue(0));
+
+ $this->grid->expects($this->once())->method('find')->will($this->returnValue($cursor));
+
+ $this->driver->load($this->publicKey, $this->imageIdentifier, $this->getMock('Imbo\Image\ImageInterface'));
+ }
+
+ /**
+ * @covers Imbo\Storage\GridFS::load
+ * @covers Imbo\Storage\GridFS::imageExists
+ */
+ public function testLoadFile() {
+ $data = 'file contents';
+
+ $image = $this->getMock('Imbo\Image\ImageInterface');
+ $image->expects($this->once())->method('setBlob')->with($data);
+
+ $file = $this->getMockBuilder('MongoGridFSFile')->disableOriginalConstructor()->getMock();
+ $file->expects($this->once())->method('getBytes')->will($this->returnValue($data));
+
+ $cursor = $this->getMockBuilder('MongoGridFSCursor')->disableOriginalConstructor()->getMock();
+ $cursor->expects($this->once())->method('count')->will($this->returnValue(1));
+ $cursor->expects($this->once())->method('getNext')->will($this->returnValue($file));
+
+ $this->grid->expects($this->once())->method('find')->will($this->returnValue($cursor));
+
+ $this->assertTrue($this->driver->load($this->publicKey, $this->imageIdentifier, $image));
+ }
+
+ /**
+ * @expectedException Imbo\Exception\StorageException
+ * @expectedExceptionCode 404
+ * @covers Imbo\Storage\GridFS::getLastModified
+ * @covers Imbo\Storage\GridFS::imageExists
+ */
+ public function testGetLastModifiedWhenImageDoesNotExist() {
+ $cursor = $this->getMockBuilder('MongoGridFSCursor')->disableOriginalConstructor()->getMock();
+ $cursor->expects($this->once())->method('count')->will($this->returnValue(0));
+
+ $this->grid->expects($this->once())->method('find')->will($this->returnValue($cursor));
+
+ $this->driver->getLastModified($this->publicKey, $this->imageIdentifier);
+ }
+
+ /**
+ * @covers Imbo\Storage\GridFS::getLastModified
+ * @covers Imbo\Storage\GridFS::imageExists
+ */
+ public function testGetLastModified() {
+ $file = new TestFile();
+
+ $cursor = $this->getMockBuilder('MongoGridFSCursor')->disableOriginalConstructor()->getMock();
+ $cursor->expects($this->once())->method('count')->will($this->returnValue(1));
+ $cursor->expects($this->once())->method('getNext')->will($this->returnValue($file));
+
+ $this->grid->expects($this->once())->method('find')->will($this->returnValue($cursor));
+
+ $this->assertInstanceOf('DateTime', ($date = $this->driver->getLastModified($this->publicKey, $this->imageIdentifier)));
+ $this->assertSame(1334579830, $date->getTimestamp());
+
+ }
+
+ /**
+ * @covers Imbo\Storage\GridFS::getLastModified
+ * @covers Imbo\Storage\GridFS::imageExists
+ */
+ public function testGetLastModifiedAsFormattedString() {
+ $time = 1334579830;
+ $file = new TestFile();
+
+ $cursor = $this->getMockBuilder('MongoGridFSCursor')->disableOriginalConstructor()->getMock();
+ $cursor->expects($this->once())->method('count')->will($this->returnValue(1));
+ $cursor->expects($this->once())->method('getNext')->will($this->returnValue($file));
+
+ $this->grid->expects($this->once())->method('find')->will($this->returnValue($cursor));
+
+ $this->assertSame('Mon, 16 Apr 2012 12:37:10 GMT', $this->driver->getLastModified($this->publicKey, $this->imageIdentifier, true));
+ }
+}
+
+class TestFile extends MongoGridFSFile {
+ public $file = array(
+ 'created' => 1334579830,
+ );
+
+ public function __construct() {}
+}

0 comments on commit 85019cd

Please sign in to comment.