diff --git a/extensions/mongo/Collection.php b/extensions/mongo/Collection.php index 5eca45a..1add592 100644 --- a/extensions/mongo/Collection.php +++ b/extensions/mongo/Collection.php @@ -59,6 +59,7 @@ use Yii; * * @property string $name name of this collection. This property is read-only. * @property string $fullName full name of this collection, including database name. This property is read-only. + * @property array $lastError last error information. This property is read-only. * * @author Paul Klimov * @since 2.0 @@ -87,6 +88,14 @@ class Collection extends Object } /** + * @return array last error information. + */ + public function getLastError() + { + return $this->mongoCollection->db->lastError(); + } + + /** * Drops this collection. * @throws Exception on failure. * @return boolean whether the operation successful. @@ -553,6 +562,15 @@ class Collection extends Object } /** + * Throws an exception if there was an error on the last operation. + * @throws Exception if an error occurred. + */ + protected function tryLastError() + { + $this->tryResultError($this->getLastError()); + } + + /** * Converts user friendly condition keyword into actual Mongo condition keyword. * @param string $key raw condition key. * @return string actual key. diff --git a/extensions/mongo/Connection.php b/extensions/mongo/Connection.php index 5396e27..8c19cb4 100644 --- a/extensions/mongo/Connection.php +++ b/extensions/mongo/Connection.php @@ -162,10 +162,10 @@ class Connection extends Component /** * Returns the Mongo collection with the given name. - * @param string|array $name collection name. If string considered as the name of the collection + * @param string|array $name collection name. If string considered as the name of the collection * inside the default database. If array - first element considered as the name of the database, * second - as name of collection inside that database - * @param boolean $refresh whether to reload the table schema even if it is found in the cache. + * @param boolean $refresh whether to reload the collection instance even if it is found in the cache. * @return Collection Mongo collection instance. */ public function getCollection($name, $refresh = false) @@ -179,6 +179,28 @@ class Connection extends Component } /** + * Returns the Mongo GridFS collection. + * @param string|array $prefix collection prefix. If string considered as the prefix of the GridFS + * collection inside the default database. If array - first element considered as the name of the database, + * second - as prefix of the GridFS collection inside that database, if no second element present + * default "fs" prefix will be used. + * @param boolean $refresh whether to reload the collection instance even if it is found in the cache. + * @return file\Collection Mongo GridFS collection instance. + */ + public function getFileCollection($prefix = 'fs', $refresh = false) + { + if (is_array($prefix)) { + list ($dbName, $collectionPrefix) = $prefix; + if (!isset($collectionPrefix)) { + $collectionPrefix = 'fs'; + } + return $this->getDatabase($dbName)->getFileCollection($collectionPrefix, $refresh); + } else { + return $this->getDatabase()->getFileCollection($prefix, $refresh); + } + } + + /** * Returns a value indicating whether the Mongo connection is established. * @return boolean whether the Mongo connection is established */ diff --git a/extensions/mongo/Database.php b/extensions/mongo/Database.php index 6495eb8..01d43de 100644 --- a/extensions/mongo/Database.php +++ b/extensions/mongo/Database.php @@ -26,6 +26,10 @@ class Database extends Object * @var Collection[] list of collections. */ private $_collections = []; + /** + * @var file\Collection[] list of GridFS collections. + */ + private $_fileCollections = []; /** * Returns the Mongo collection with the given name. @@ -42,6 +46,20 @@ class Database extends Object } /** + * Returns Mongo GridFS collection with given prefix. + * @param string $prefix collection prefix. + * @param boolean $refresh whether to reload the table schema even if it is found in the cache. + * @return file\Collection mongo GridFS collection. + */ + public function getFileCollection($prefix = 'fs', $refresh = false) + { + if ($refresh || !array_key_exists($prefix, $this->_fileCollections)) { + $this->_fileCollections[$prefix] = $this->selectFileCollection($prefix); + } + return $this->_fileCollections[$prefix]; + } + + /** * Selects collection with given name. * @param string $name collection name. * @return Collection collection instance. @@ -55,6 +73,19 @@ class Database extends Object } /** + * Selects GridFS collection with given prefix. + * @param string $prefix file collection prefix. + * @return file\Collection file collection instance. + */ + protected function selectFileCollection($prefix) + { + return Yii::createObject([ + 'class' => 'yii\mongo\file\Collection', + 'mongoCollection' => $this->mongoDb->getGridFS($prefix) + ]); + } + + /** * Creates new collection. * Note: Mongo creates new collections automatically on the first demand, * this method makes sense only for the migration script or for the case diff --git a/extensions/mongo/file/Collection.php b/extensions/mongo/file/Collection.php new file mode 100644 index 0000000..f3e8d33 --- /dev/null +++ b/extensions/mongo/file/Collection.php @@ -0,0 +1,109 @@ + + * @since 2.0 + */ +class Collection extends \yii\mongo\Collection +{ + /** + * @var \MongoGridFS Mongo GridFS collection instance. + */ + public $mongoCollection; + + /** + * Removes data from the collection. + * @param array $condition description of records to remove. + * @param array $options list of options in format: optionName => optionValue. + * @return integer|boolean number of updated documents or whether operation was successful. + * @throws Exception on failure. + */ + public function remove($condition = [], $options = []) + { + $result = parent::remove($condition, $options); + $this->tryLastError(); // MongoGridFS::remove will return even if the remove failed + return $result; + } + + /** + * @param string $filename name of the file to store. + * @param array $metadata other metadata fields to include in the file document. + * @return mixed the "_id" of the saved file document. This will be a generated [[\MongoId]] + * unless an "_id" was explicitly specified in the metadata. + */ + public function put($filename, $metadata = []) + { + return $this->mongoCollection->put($filename, $metadata); + } + + /** + * @param string $bytes string of bytes to store. + * @param array $metadata other metadata fields to include in the file document. + * @param array $options list of options in format: optionName => optionValue + * @return mixed the "_id" of the saved file document. This will be a generated [[\MongoId]] + * unless an "_id" was explicitly specified in the metadata. + */ + public function storeBytes($bytes, $metadata = [], $options = []) + { + $options = array_merge(['w' => 1], $options); + return $this->mongoCollection->storeBytes($bytes, $metadata, $options); + } + + /** + * @param string $filename name of the file to store. + * @param array $metadata other metadata fields to include in the file document. + * @param array $options list of options in format: optionName => optionValue + * @return mixed the "_id" of the saved file document. This will be a generated [[\MongoId]] + * unless an "_id" was explicitly specified in the metadata. + */ + public function storeFile($filename, $metadata = [], $options = []) + { + $options = array_merge(['w' => 1], $options); + return $this->mongoCollection->storeFile($filename, $metadata, $options); + } + + /** + * @param string $name name of the uploaded file to store. This should correspond to + * the file field's name attribute in the HTML form. + * @param array $metadata other metadata fields to include in the file document. + * @return mixed the "_id" of the saved file document. This will be a generated [[\MongoId]] + * unless an "_id" was explicitly specified in the metadata. + */ + public function storeUploads($name, $metadata = []) + { + return $this->mongoCollection->storeUpload($name, $metadata); + } + + /** + * @param mixed $id _id of the file to find. + * @return \MongoGridFSFile|null found file, or null if file does not exist + */ + public function get($id) + { + return $this->mongoCollection->get($id); + } + + /** + * @param mixed $id _id of the file to find. + * @return boolean whether the operation was successful. + */ + public function delete($id) + { + $result = $this->mongoCollection->delete($id); + $this->tryResultError($result); + return true; + } +} \ No newline at end of file diff --git a/tests/unit/extensions/mongo/ConnectionTest.php b/tests/unit/extensions/mongo/ConnectionTest.php index b3252b9..04d5351 100644 --- a/tests/unit/extensions/mongo/ConnectionTest.php +++ b/tests/unit/extensions/mongo/ConnectionTest.php @@ -3,6 +3,7 @@ namespace yiiunit\extensions\mongo; use yii\mongo\Collection; +use yii\mongo\file\Collection as FileCollection; use yii\mongo\Connection; use yii\mongo\Database; @@ -98,4 +99,21 @@ class ConnectionTest extends MongoTestCase $collection2 = $connection->getCollection('customer', true); $this->assertFalse($collection === $collection2); } + + /** + * @depends testGetDefaultDatabase + */ + public function testGetFileCollection() + { + $connection = $this->getConnection(); + + $collection = $connection->getFileCollection('testfs'); + $this->assertTrue($collection instanceof FileCollection); + + $collection2 = $connection->getFileCollection('testfs'); + $this->assertTrue($collection === $collection2); + + $collection2 = $connection->getFileCollection('testfs', true); + $this->assertFalse($collection === $collection2); + } } \ No newline at end of file diff --git a/tests/unit/extensions/mongo/DatabaseTest.php b/tests/unit/extensions/mongo/DatabaseTest.php index dbe05c0..e844d9d 100644 --- a/tests/unit/extensions/mongo/DatabaseTest.php +++ b/tests/unit/extensions/mongo/DatabaseTest.php @@ -3,6 +3,7 @@ namespace yiiunit\extensions\mongo; use yii\mongo\Collection; +use yii\mongo\file\Collection as FileCollection; /** * @group mongo @@ -12,6 +13,7 @@ class DatabaseTest extends MongoTestCase protected function tearDown() { $this->dropCollection('customer'); + $this->dropFileCollection('testfs'); parent::tearDown(); } @@ -32,6 +34,21 @@ class DatabaseTest extends MongoTestCase $this->assertFalse($collection === $collectionRefreshed); } + public function testGetFileCollection() + { + $database = $connection = $this->getConnection()->getDatabase(); + + $collection = $database->getFileCollection('testfs'); + $this->assertTrue($collection instanceof FileCollection); + $this->assertTrue($collection->mongoCollection instanceof \MongoGridFS); + + $collection2 = $database->getFileCollection('testfs'); + $this->assertTrue($collection === $collection2); + + $collectionRefreshed = $database->getFileCollection('testfs', true); + $this->assertFalse($collection === $collectionRefreshed); + } + public function testCommand() { $database = $connection = $this->getConnection()->getDatabase(); diff --git a/tests/unit/extensions/mongo/MongoTestCase.php b/tests/unit/extensions/mongo/MongoTestCase.php index e344109..a14e27b 100644 --- a/tests/unit/extensions/mongo/MongoTestCase.php +++ b/tests/unit/extensions/mongo/MongoTestCase.php @@ -105,6 +105,21 @@ class MongoTestCase extends TestCase } /** + * Drops the specified file collection. + * @param string $name file collection name. + */ + protected function dropFileCollection($name) + { + if ($this->mongo) { + try { + $this->mongo->getFileCollection($name)->drop(); + } catch (Exception $e) { + // shut down exception + } + } + } + + /** * Finds all records in collection. * @param \yii\mongo\Collection $collection * @param array $condition diff --git a/tests/unit/extensions/mongo/file/CollectionTest.php b/tests/unit/extensions/mongo/file/CollectionTest.php new file mode 100644 index 0000000..bed7ab9 --- /dev/null +++ b/tests/unit/extensions/mongo/file/CollectionTest.php @@ -0,0 +1,87 @@ +dropFileCollection('fs'); + parent::tearDown(); + } + + // Tests : + + public function testFind() + { + $collection = $this->getConnection()->getFileCollection(); + $cursor = $collection->find(); + $this->assertTrue($cursor instanceof \MongoGridFSCursor); + } + + public function testStoreFile() + { + $collection = $this->getConnection()->getFileCollection(); + + $filename = __FILE__; + $id = $collection->storeFile($filename); + $this->assertTrue($id instanceof \MongoId); + + $files = $this->findAll($collection); + $this->assertEquals(1, count($files)); + + /** @var $file \MongoGridFSFile */ + $file = $files[0]; + $this->assertEquals($filename, $file->getFilename()); + $this->assertEquals(file_get_contents($filename), $file->getBytes()); + } + + public function testStoreBytes() + { + $collection = $this->getConnection()->getFileCollection(); + + $bytes = 'Test file content'; + $id = $collection->storeBytes($bytes); + $this->assertTrue($id instanceof \MongoId); + + $files = $this->findAll($collection); + $this->assertEquals(1, count($files)); + + /** @var $file \MongoGridFSFile */ + $file = $files[0]; + $this->assertEquals($bytes, $file->getBytes()); + } + + /** + * @depends testStoreBytes + */ + public function testGet() + { + $collection = $this->getConnection()->getFileCollection(); + + $bytes = 'Test file content'; + $id = $collection->storeBytes($bytes); + + $file = $collection->get($id); + $this->assertTrue($file instanceof \MongoGridFSFile); + $this->assertEquals($bytes, $file->getBytes()); + } + + /** + * @depends testGet + */ + public function testDelete() + { + $collection = $this->getConnection()->getFileCollection(); + + $bytes = 'Test file content'; + $id = $collection->storeBytes($bytes); + + $this->assertTrue($collection->delete($id)); + + $file = $collection->get($id); + $this->assertNull($file); + } +} \ No newline at end of file