diff --git a/extensions/mongo/Command.php b/extensions/mongo/Collection.php similarity index 63% rename from extensions/mongo/Command.php rename to extensions/mongo/Collection.php index 7a21177..fc15b89 100644 --- a/extensions/mongo/Command.php +++ b/extensions/mongo/Collection.php @@ -7,61 +7,48 @@ namespace yii\mongo; -use \yii\base\Component; +use yii\base\Object; use Yii; /** - * Class Command + * Collection represents the Mongo collection information. * * @author Paul Klimov * @since 2.0 */ -class Command extends Component +class Collection extends Object { /** - * @var Connection the Mongo connection that this command is associated with + * @var \MongoCollection Mongo collection instance. */ - public $db; + public $mongoCollection; /** - * Drops the current database + * Drops this collection. */ - public function dropDb() + public function drop() { - $this->db->db->drop(); + $this->mongoCollection->drop(); } /** - * Drops the specified collection. - * @param string $name collection name. - */ - public function dropCollection($name) - { - $collection = $this->db->getCollection($name); - $collection->drop(); - } - - /** - * @param $collection * @param array $query * @param array $fields * @return \MongoCursor */ - public function find($collection, $query = [], $fields = []) + public function find($query = [], $fields = []) { - $collection = $this->db->getCollection($collection); - return $collection->find($query, $fields); + return $this->mongoCollection->find($query, $fields); } /** - * @param $collection * @param array $query * @param array $fields * @return array */ - public function findAll($collection, $query = [], $fields = []) + public function findAll($query = [], $fields = []) { - $cursor = $this->find($collection, $query, $fields); + $cursor = $this->find($query, $fields); $result = []; foreach ($cursor as $data) { $result[] = $data; @@ -71,20 +58,18 @@ class Command extends Component /** * Inserts new data into collection. - * @param string $collection name of the collection. * @param array|object $data data to be inserted. * @param array $options list of options in format: optionName => optionValue. * @return \MongoId new record id instance. * @throws Exception on failure. */ - public function insert($collection, $data, $options = []) + public function insert($data, $options = []) { - $token = 'Inserting data into ' . $collection; + $token = 'Inserting data into ' . $this->mongoCollection->getName(); Yii::info($token, __METHOD__); try { Yii::beginProfile($token, __METHOD__); - $collection = $this->db->getCollection($collection); - $this->tryResultError($collection->insert($data, $options)); + $this->tryResultError($this->mongoCollection->insert($data, $options)); Yii::endProfile($token, __METHOD__); return is_array($data) ? $data['_id'] : $data->_id; } catch (\Exception $e) { @@ -95,20 +80,18 @@ class Command extends Component /** * Update the existing database data, otherwise insert this data - * @param string $collection name of the collection. * @param array|object $data data to be updated/inserted. * @param array $options list of options in format: optionName => optionValue. * @return \MongoId updated/new record id instance. * @throws Exception on failure. */ - public function save($collection, $data, $options = []) + public function save($data, $options = []) { - $token = 'Saving data into ' . $collection; + $token = 'Saving data into ' . $this->mongoCollection->getName(); Yii::info($token, __METHOD__); try { Yii::beginProfile($token, __METHOD__); - $collection = $this->db->getCollection($collection); - $this->tryResultError($collection->save($data, $options)); + $this->tryResultError($this->mongoCollection->save($data, $options)); Yii::endProfile($token, __METHOD__); return is_array($data) ? $data['_id'] : $data->_id; } catch (\Exception $e) { @@ -119,20 +102,18 @@ class Command extends Component /** * Removes data from the collection. - * @param string $collection name of the collection. * @param array $criteria description of records to remove. * @param array $options list of options in format: optionName => optionValue. * @return boolean whether operation was successful. * @throws Exception on failure. */ - public function remove($collection, $criteria = [], $options = []) + public function remove($criteria = [], $options = []) { - $token = 'Removing data from ' . $collection; + $token = 'Removing data from ' . $this->mongoCollection->getName(); Yii::info($token, __METHOD__); try { Yii::beginProfile($token, __METHOD__); - $collection = $this->db->getCollection($collection); - $this->tryResultError($collection->remove($criteria, $options)); + $this->tryResultError($this->mongoCollection->remove($criteria, $options)); Yii::endProfile($token, __METHOD__); return true; } catch (\Exception $e) { diff --git a/extensions/mongo/Connection.php b/extensions/mongo/Connection.php index 8158b15..ce99ee4 100644 --- a/extensions/mongo/Connection.php +++ b/extensions/mongo/Connection.php @@ -24,14 +24,16 @@ use Yii; class Connection extends Component { /** - * @var \MongoCollection[] list of Mongo collection available in database. - */ - private $_collections = []; - - /** - * @var \MongoClient mongo client instance. + * @var string host:port + * + * Correct syntax is: + * mongodb://[username:password@]host1[:port1][,host2[:port2:],...][/dbname] + * For example: + * mongodb://localhost:27017 + * mongodb://developer:somepassword@localhost:27017 + * mongodb://developer:somepassword@localhost:27017/mydatabase */ - public $client; + public $dsn; /** * @var array connection options. * for example: @@ -45,34 +47,83 @@ class Connection extends Component */ public $options = []; /** - * @var string host:port - * - * Correct syntax is: - * mongodb://[username:password@]host1[:port1][,host2[:port2:],...] - * For example: mongodb://localhost:27017 + * @var string name of the Mongo database to use by default. */ - public $dsn; + public $defaultDatabaseName; + /** + * @var \MongoClient mongo client instance. + */ + public $mongoClient; + /** + * @var Database[] list of Mongo databases + */ + private $_databases = []; + + /** + * Returns the Mongo collection with the given name. + * @param string|null $name collection name, if null default one will be used. + * @param boolean $refresh whether to reload the table schema even if it is found in the cache. + * @return Database database instance. + */ + public function getDatabase($name = null, $refresh = false) + { + if ($name === null) { + $name = $this->fetchDefaultDatabaseName(); + } + if ($refresh || !array_key_exists($name, $this->_databases)) { + $this->_databases[$name] = $this->selectDatabase($name); + } + return $this->_databases[$name]; + } + /** - * @var string name of the Mongo database to use + * Returns [[defaultDatabaseName]] value, if it is not set, + * attempts to determine it from [[dsn]] value. + * @return string default database name + * @throws \yii\base\InvalidConfigException if unable to determine default database name. */ - public $dbName; + protected function fetchDefaultDatabaseName() + { + if ($this->defaultDatabaseName === null) { + if (preg_match('/^mongodb:\\/\\/.+\\/(.+)$/s', $this->dsn, $matches)) { + $this->defaultDatabaseName = $matches[1]; + } else { + throw new InvalidConfigException("Unable to determine default database name from dsn."); + } + } + return $this->defaultDatabaseName; + } + /** - * @var \MongoDb Mongo database instance. + * Selects the database with given name. + * @param string $name database name. + * @return Database database instance. */ - public $db; + protected function selectDatabase($name) + { + $this->open(); + return Yii::createObject([ + 'class' => 'yii\mongo\Database', + 'mongoDb' => $this->mongoClient->selectDB($name) + ]); + } /** * Returns the Mongo collection with the given name. - * @param string $name collection name + * @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. - * @return \MongoCollection mongo collection instance. + * @return Collection Mongo collection instance. */ public function getCollection($name, $refresh = false) { - if ($refresh || !array_key_exists($name, $this->_collections)) { - $this->_collections[$name] = $this->client->selectCollection($this->dbName, $name); + if (is_array($name)) { + list ($dbName, $collectionName) = $name; + return $this->getDatabase($dbName)->getCollection($collectionName, $refresh); + } else { + return $this->getDatabase()->getCollection($name, $refresh); } - return $this->_collections[$name]; } /** @@ -81,7 +132,7 @@ class Connection extends Component */ public function getIsActive() { - return is_object($this->client) && $this->client->connected; + return is_object($this->mongoClient) && $this->mongoClient->connected; } /** @@ -91,7 +142,7 @@ class Connection extends Component */ public function open() { - if ($this->client === null) { + if ($this->mongoClient === null) { if (empty($this->dsn)) { throw new InvalidConfigException($this->className() . '::dsn cannot be empty.'); } @@ -101,9 +152,10 @@ class Connection extends Component Yii::beginProfile($token, __METHOD__); $options = $this->options; $options['connect'] = true; - $options['db'] = $this->dbName; - $this->client = new \MongoClient($this->dsn, $options); - $this->db = $this->client->selectDB($this->dbName); + if ($this->defaultDatabaseName !== null) { + $options['db'] = $this->defaultDatabaseName; + } + $this->mongoClient = new \MongoClient($this->dsn, $options); Yii::endProfile($token, __METHOD__); } catch (\Exception $e) { Yii::endProfile($token, __METHOD__); @@ -118,32 +170,10 @@ class Connection extends Component */ public function close() { - if ($this->client !== null) { + if ($this->mongoClient !== null) { Yii::trace('Closing Mongo connection: ' . $this->dsn, __METHOD__); - $this->client = null; - $this->db = null; + $this->mongoClient = null; + $this->_databases = []; } } - - /** - * Returns the query builder for the current DB connection. - * @return QueryBuilder the query builder for the current DB connection. - */ - public function getQueryBuilder() - { - return new QueryBuilder($this); - } - - /** - * Creates a command for execution. - * @return Command the Mongo command - */ - public function createCommand() - { - $this->open(); - $command = new Command([ - 'db' => $this, - ]); - return $command; - } } \ No newline at end of file diff --git a/extensions/mongo/Database.php b/extensions/mongo/Database.php new file mode 100644 index 0000000..4954a20 --- /dev/null +++ b/extensions/mongo/Database.php @@ -0,0 +1,64 @@ + + * @since 2.0 + */ +class Database extends Object +{ + /** + * @var \MongoDB Mongo database instance. + */ + public $mongoDb; + /** + * @var Collection[] list of collections. + */ + private $_collections = []; + + /** + * Returns the Mongo collection with the given name. + * @param string $name collection name + * @param boolean $refresh whether to reload the table schema even if it is found in the cache. + * @return Collection mongo collection instance. + */ + public function getCollection($name, $refresh = false) + { + if ($refresh || !array_key_exists($name, $this->_collections)) { + $this->_collections[$name] = $this->selectCollection($name); + } + return $this->_collections[$name]; + } + + /** + * Selects collection with given name. + * @param string $name collection name. + * @return Collection collection instance. + */ + protected function selectCollection($name) + { + return Yii::createObject([ + 'class' => 'yii\mongo\Collection', + 'mongoCollection' => $this->mongoDb->selectCollection($name) + ]); + } + + /** + * Drops this database. + */ + public function drop() + { + $this->mongoDb->drop(); + } +} \ No newline at end of file diff --git a/tests/unit/extensions/mongo/CommandTest.php b/tests/unit/extensions/mongo/CollectionTest.php similarity index 64% rename from tests/unit/extensions/mongo/CommandTest.php rename to tests/unit/extensions/mongo/CollectionTest.php index c9621d1..5dd7d59 100644 --- a/tests/unit/extensions/mongo/CommandTest.php +++ b/tests/unit/extensions/mongo/CollectionTest.php @@ -5,7 +5,7 @@ namespace yiiunit\extensions\mongo; /** * @group mongo */ -class CommandTest extends MongoTestCase +class CollectionTest extends MongoTestCase { protected function tearDown() { @@ -17,12 +17,12 @@ class CommandTest extends MongoTestCase public function testInsert() { - $command = $this->getConnection()->createCommand(); + $collection = $this->getConnection()->getCollection('customer'); $data = [ 'name' => 'customer 1', 'address' => 'customer 1 address', ]; - $id = $command->insert('customer', $data); + $id = $collection->insert($data); $this->assertTrue($id instanceof \MongoId); $this->assertNotEmpty($id->__toString()); } @@ -32,26 +32,26 @@ class CommandTest extends MongoTestCase */ public function testFindAll() { - $command = $this->getConnection()->createCommand(); + $collection = $this->getConnection()->getCollection('customer'); $data = [ 'name' => 'customer 1', 'address' => 'customer 1 address', ]; - $id = $command->insert('customer', $data); + $id = $collection->insert($data); - $rows = $command->findAll('customer'); + $rows = $collection->findAll(); $this->assertEquals(1, count($rows)); $this->assertEquals($id, $rows[0]['_id']); } public function testSave() { - $command = $this->getConnection()->createCommand(); + $collection = $this->getConnection()->getCollection('customer'); $data = [ 'name' => 'customer 1', 'address' => 'customer 1 address', ]; - $id = $command->save('customer', $data); + $id = $collection->save($data); $this->assertTrue($id instanceof \MongoId); $this->assertNotEmpty($id->__toString()); } @@ -61,18 +61,18 @@ class CommandTest extends MongoTestCase */ public function testUpdate() { - $command = $this->getConnection()->createCommand(); + $collection = $this->getConnection()->getCollection('customer'); $data = [ 'name' => 'customer 1', 'address' => 'customer 1 address', ]; - $newId = $command->save('customer', $data); + $newId = $collection->save($data); - $updatedId = $command->save('customer', $data); + $updatedId = $collection->save($data); $this->assertEquals($newId, $updatedId, 'Unable to update data!'); $data['_id'] = $newId->__toString(); - $updatedId = $command->save('customer', $data); + $updatedId = $collection->save($data); $this->assertEquals($newId, $updatedId, 'Unable to updated data by string id!'); } @@ -81,16 +81,16 @@ class CommandTest extends MongoTestCase */ public function testRemove() { - $command = $this->getConnection()->createCommand(); + $collection = $this->getConnection()->getCollection('customer'); $data = [ 'name' => 'customer 1', 'address' => 'customer 1 address', ]; - $id = $command->insert('customer', $data); + $id = $collection->insert($data); - $command->remove('customer', ['_id' => $id]); + $collection->remove(['_id' => $id]); - $rows = $command->findAll('customer'); + $rows = $collection->findAll(); $this->assertEquals(0, count($rows)); } } \ No newline at end of file diff --git a/tests/unit/extensions/mongo/ConnectionTest.php b/tests/unit/extensions/mongo/ConnectionTest.php index 175a665..3b8a1a2 100644 --- a/tests/unit/extensions/mongo/ConnectionTest.php +++ b/tests/unit/extensions/mongo/ConnectionTest.php @@ -2,7 +2,9 @@ namespace yiiunit\extensions\mongo; +use yii\mongo\Collection; use yii\mongo\Connection; +use yii\mongo\Database; /** * @group mongo @@ -17,7 +19,7 @@ class ConnectionTest extends MongoTestCase $connection->open(); $this->assertEquals($params['dsn'], $connection->dsn); - $this->assertEquals($params['dbName'], $connection->dbName); + $this->assertEquals($params['defaultDatabaseName'], $connection->defaultDatabaseName); $this->assertEquals($params['options'], $connection->options); } @@ -26,17 +28,15 @@ class ConnectionTest extends MongoTestCase $connection = $this->getConnection(false, false); $this->assertFalse($connection->isActive); - $this->assertEquals(null, $connection->client); + $this->assertEquals(null, $connection->mongoClient); $connection->open(); $this->assertTrue($connection->isActive); - $this->assertTrue(is_object($connection->client)); - $this->assertTrue(is_object($connection->db)); + $this->assertTrue(is_object($connection->mongoClient)); $connection->close(); $this->assertFalse($connection->isActive); - $this->assertEquals(null, $connection->client); - $this->assertEquals(null, $connection->db); + $this->assertEquals(null, $connection->mongoClient); $connection = new Connection; $connection->dsn = 'unknown::memory:'; @@ -44,10 +44,52 @@ class ConnectionTest extends MongoTestCase $connection->open(); } + public function testGetDatabase() + { + $connection = $this->getConnection(); + + $database = $connection->getDatabase($connection->defaultDatabaseName); + $this->assertTrue($database instanceof Database); + $this->assertTrue($database->mongoDb instanceof \MongoDB); + + $database2 = $connection->getDatabase($connection->defaultDatabaseName); + $this->assertTrue($database === $database2); + + $databaseRefreshed = $connection->getDatabase($connection->defaultDatabaseName, true); + $this->assertFalse($database === $databaseRefreshed); + } + + /** + * @depends testGetDatabase + */ + public function testGetDefaultDatabase() + { + $connection = new Connection(); + $connection->dsn = $this->mongoConfig['dsn']; + $connection->defaultDatabaseName = $this->mongoConfig['defaultDatabaseName']; + $database = $connection->getDatabase(); + $this->assertTrue($database instanceof Database, 'Unable to get default database!'); + + $connection = new Connection(); + $connection->dsn = $this->mongoConfig['dsn'] . '/' . $this->mongoConfig['defaultDatabaseName']; + $database = $connection->getDatabase(); + $this->assertTrue($database instanceof Database, 'Unable to determine default database from dsn!'); + } + + /** + * @depends testGetDefaultDatabase + */ public function testGetCollection() { - $connection = $this->getConnection(false); + $connection = $this->getConnection(); + $collection = $connection->getCollection('customer'); - $this->assertTrue($collection instanceof \MongoCollection); + $this->assertTrue($collection instanceof Collection); + + $collection2 = $connection->getCollection('customer'); + $this->assertTrue($collection === $collection2); + + $collection2 = $connection->getCollection('customer', 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 new file mode 100644 index 0000000..ec0bf2d --- /dev/null +++ b/tests/unit/extensions/mongo/DatabaseTest.php @@ -0,0 +1,34 @@ +dropCollection('customer'); + parent::tearDown(); + } + + // Tests : + + public function testGetCollection() + { + $database = $connection = $this->getConnection()->getDatabase(); + + $collection = $database->getCollection('customer'); + $this->assertTrue($collection instanceof Collection); + $this->assertTrue($collection->mongoCollection instanceof \MongoCollection); + + $collection2 = $database->getCollection('customer'); + $this->assertTrue($collection === $collection2); + + $collectionRefreshed = $database->getCollection('customer', true); + $this->assertFalse($collection === $collectionRefreshed); + } +} \ No newline at end of file diff --git a/tests/unit/extensions/mongo/MongoTestCase.php b/tests/unit/extensions/mongo/MongoTestCase.php index a4f20ac..61f35c8 100644 --- a/tests/unit/extensions/mongo/MongoTestCase.php +++ b/tests/unit/extensions/mongo/MongoTestCase.php @@ -14,7 +14,7 @@ class MongoTestCase extends TestCase */ protected $mongoConfig = [ 'dsn' => 'mongodb://localhost:27017', - 'dbName' => 'yii2test', + 'defaultDatabaseName' => 'yii2test', ]; /** * @var Connection Mongo connection instance. @@ -76,8 +76,8 @@ class MongoTestCase extends TestCase } $db = new Connection; $db->dsn = $this->mongoConfig['dsn']; - if (isset($this->mongoConfig['dbName'])) { - $db->dbName = $this->mongoConfig['dbName']; + if (isset($this->mongoConfig['defaultDatabaseName'])) { + $db->defaultDatabaseName = $this->mongoConfig['defaultDatabaseName']; } if (isset($this->mongoConfig['options'])) { $db->options = $this->mongoConfig['options'];