From c035baedad73375443236071ed9d0ca22b43533d Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Tue, 17 Mar 2015 19:08:45 -0400 Subject: [PATCH 01/21] Create test bootstrap for autoloading --- phpunit.xml.dist | 2 +- tests/bootstrap.php | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 tests/bootstrap.php diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 4df13239c..780460f30 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -11,7 +11,7 @@ convertWarningsToExceptions="true" stopOnFailure="false" syntaxCheck="false" - bootstrap="vendor/autoload.php" + bootstrap="tests/bootstrap.php" > diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 000000000..464fc5839 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,13 @@ +addPsr4('MongoDB\\Tests\\', __DIR__); From 24d34dccc9700e62855c0a7467b10118c450dfcb Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Tue, 17 Mar 2015 19:43:13 -0400 Subject: [PATCH 02/21] Create base classes for unit and functional tests --- phpunit.xml.dist | 2 ++ tests/FunctionalTestCase.php | 15 +++++++++++ tests/TestCase.php | 50 ++++++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+) create mode 100644 tests/FunctionalTestCase.php create mode 100644 tests/TestCase.php diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 780460f30..2da976ba7 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -16,6 +16,8 @@ + + diff --git a/tests/FunctionalTestCase.php b/tests/FunctionalTestCase.php new file mode 100644 index 000000000..b29e1520d --- /dev/null +++ b/tests/FunctionalTestCase.php @@ -0,0 +1,15 @@ +manager = new Manager($this->getUri()); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 000000000..d42ae0fde --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,50 @@ +getShortName(), $this->getName(false)); + } + + /** + * Return the test database name. + * + * @return string + */ + public function getDatabaseName() + { + return getenv('MONGODB_DATABASE') ?: 'phplib_test'; + } + + /** + * Return the test namespace. + * + * @return string + */ + public function getNamespace() + { + return sprintf('%s.%s', $this->getDatabaseName(), $this->getCollectionName()); + } + + /** + * Return the connection URI. + * + * @return string + */ + public function getUri() + { + return getenv('MONGODB_URI') ?: 'mongodb://localhost:27017'; + } +} From b85b91fb3cfd1561560e58da5cd1e1e627d07d2b Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Mon, 23 Mar 2015 20:45:08 -0400 Subject: [PATCH 03/21] PHPLIB-73: Inherit default write concern and read preferences Inheriting from the Client's URI options is not implemented, as it depends on PHPC-196. This commit also revises the documentation for selectDatabase(), selectCollection(), and the core class constructors. --- src/Client.php | 69 +++++++++++++++++++++++++++------------------- src/Collection.php | 25 ++++++++--------- src/Database.php | 54 +++++++++++++++++++----------------- 3 files changed, 82 insertions(+), 66 deletions(-) diff --git a/src/Client.php b/src/Client.php index 4c926a2ca..28d9115c6 100644 --- a/src/Client.php +++ b/src/Client.php @@ -5,27 +5,29 @@ use MongoDB\Collection; use MongoDB\Database; use MongoDB\Driver\Manager; +use MongoDB\Driver\ReadPreference; use MongoDB\Driver\Result; +use MongoDB\Driver\WriteConcern; class Client { private $manager; - private $wc; - private $rp; - + private $readPreference; + private $writeConcern; /** - * Constructs new Client instance + * Constructs a new Client instance. * - * This is the suggested main entry point using phongo. - * It acts as a bridge to access individual databases and collection tools - * which are provided in this namespace. + * This is the preferred class for connecting to a MongoDB server or + * cluster of servers. It serves as a gateway for accessing individual + * databases and collections. * - * @param Manager $uri The MongoDB URI to connect to - * @param WriteConcern $options URI Options - * @param ReadPreference $driverOptions Driver specific options + * @see http://docs.mongodb.org/manual/reference/connection-string/ + * @param string $uri MongoDB connection string + * @param array $options Additional connection string options + * @param array $driverOptions Driver-specific options */ - public function __construct($uri, $options, $driverOptions) + public function __construct($uri, array $options = array(), array $driverOptions = array()) { $this->manager = new Manager($uri, $options, $driverOptions); } @@ -42,33 +44,44 @@ public function dropDatabase($databaseName) } /** - * Select a database + * Select a database. * - * It acts as a bridge to access specific database commands + * If a write concern or read preference is not specified, the write concern + * or read preference of the Client will be applied, respectively. * - * @param string $databaseName The database to select - * @param WriteConcern $writeConcern Default Write Concern to apply - * @param ReadPreference $readPreferences Default Read Preferences to apply + * @param string $databaseName Name of the database to select + * @param WriteConcern $writeConcern Default write concern to apply + * @param ReadPreference $readPreference Default read preference to apply + * @return Database */ - public function selectDatabase($databaseName, WriteConcern $writeConcern = null, ReadPreference $readPreferences = null) + public function selectDatabase($databaseName, WriteConcern $writeConcern = null, ReadPreference $readPreference = null) { - return new Database($this->manager, $databaseName, $writeConcern, $readPreferences); + // TODO: inherit from Manager options once PHPC-196 is implemented + $writeConcern = $writeConcern ?: $this->writeConcern; + $readPreference = $readPreference ?: $this->readPreference; + + return new Database($this->manager, $databaseName, $writeConcern, $readPreference); } /** - * Select a specific collection in a database + * Select a collection. * - * It acts as a bridge to access specific collection commands + * If a write concern or read preference is not specified, the write concern + * or read preference of the Client will be applied, respectively. * - * @param string $databaseName The database where the $collectionName exists - * @param string $collectionName The collection to select - * @param WriteConcern $writeConcern Default Write Concern to apply - * @param ReadPreference $readPreferences Default Read Preferences to apply + * @param string $databaseName Name of the database containing the collection + * @param string $collectionName Name of the collection to select + * @param WriteConcern $writeConcern Default write concern to apply + * @param ReadPreference $readPreference Default read preference to apply + * @return Collection */ - public function selectCollection($databaseName, $collectionName, WriteConcern $writeConcern = null, ReadPreference $readPreferences = null) + public function selectCollection($databaseName, $collectionName, WriteConcern $writeConcern = null, ReadPreference $readPreference = null) { - return new Collection($this->manager, "{$databaseName}.{$collectionName}", $writeConcern, $readPreferences); - } + $namespace = $databaseName . '.' . $collectionName; + // TODO: inherit from Manager options once PHPC-196 is implemented + $writeConcern = $writeConcern ?: $this->writeConcern; + $readPreference = $readPreference ?: $this->readPreference; + return new Collection($this->manager, $namespace, $writeConcern, $readPreference); + } } - diff --git a/src/Collection.php b/src/Collection.php index c4bcbc7fe..bb14a448a 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -43,25 +43,24 @@ class Collection /** - * Constructs new Collection instance + * Constructs new Collection instance. * - * This is the suggested CRUD interface when using phongo. - * It implements the MongoDB CRUD specification which is an interface all MongoDB - * supported drivers follow. + * This class provides methods for collection-specific operations, such as + * CRUD (i.e. create, read, update, and delete) and index management. * - * @param Manager $manager The phongo Manager instance - * @param string $ns Fully Qualified Namespace (dbname.collname) - * @param WriteConcern $wc The WriteConcern to apply to writes - * @param ReadPreference $rp The ReadPreferences to apply to reads + * @param Manager $manager Manager instance from the driver + * @param string $namespace Collection namespace (e.g. "db.collection") + * @param WriteConcern $writeConcern Default write concern to apply + * @param ReadPreference $readPreference Default read preference to apply */ - public function __construct(Manager $manager, $ns, WriteConcern $wc = null, ReadPreference $rp = null) + public function __construct(Manager $manager, $namespace, WriteConcern $writeConcern = null, ReadPreference $readPreference = null) { $this->manager = $manager; - $this->ns = $ns; - $this->wc = $wc; - $this->rp = $rp; + $this->ns = $namespace; + $this->wc = $writeConcern; + $this->rp = $readPreference; - list($this->dbname, $this->collname) = explode(".", $ns, 2); + list($this->dbname, $this->collname) = explode(".", $namespace, 2); } /** diff --git a/src/Database.php b/src/Database.php index cb772b3a6..3c1e24f11 100644 --- a/src/Database.php +++ b/src/Database.php @@ -4,33 +4,34 @@ use MongoDB\Collection; use MongoDB\Driver\Manager; +use MongoDB\Driver\ReadPreference; use MongoDB\Driver\Result; +use MongoDB\Driver\WriteConcern; class Database { + private $databaseName; private $manager; - private $ns; - private $wc; - private $rp; - - private $dbname; + private $readPreference; + private $writeConcern; /** - * Constructs new Database instance + * Constructs new Database instance. * - * It acts as a bridge for database specific operations. + * This class provides methods for database-specific operations and serves + * as a gateway for accessing collections. * - * @param Manager $manager The phongo Manager instance - * @param string $dbname Fully Qualified database name - * @param WriteConcern $wc The WriteConcern to apply to writes - * @param ReadPreference $rp The ReadPreferences to apply to reads + * @param Manager $manager Manager instance from the driver + * @param string $databaseName Database name + * @param WriteConcern $writeConcern Default write concern to apply + * @param ReadPreference $readPreference Default read preference to apply */ - public function __construct(Manager $manager, $databaseName, WriteConcern $wc = null, ReadPreference $rp = null) + public function __construct(Manager $manager, $databaseName, WriteConcern $writeConcern = null, ReadPreference $readPreference = null) { $this->manager = $manager; - $this->dbname = $dbname; - $this->wc = $wc; - $this->rp = $rp; + $this->databaseName = $databaseName; + $this->writeConcern = $writeConcern; + $this->readPreference = $readPreference; } /** @@ -81,19 +82,22 @@ public function listCollections(array $options = array()) } /** - * Select a specific collection in this database + * Select a collection within this database. * - * It acts as a bridge to access specific collection commands + * If a write concern or read preference is not specified, the write concern + * or read preference of the Database will be applied, respectively. * - * @param string $collectionName The collection to select - * @param WriteConcern $writeConcern Default Write Concern to apply - * @param ReadPreference $readPreferences Default Read Preferences to apply + * @param string $collectionName Name of the collection to select + * @param WriteConcern $writeConcern Default write concern to apply + * @param ReadPreference $readPreference Default read preference to apply + * @return Collection */ - public function selectCollection($collectionName, WriteConcern $writeConcern = null, ReadPreference $readPreferences = null) + public function selectCollection($collectionName, WriteConcern $writeConcern = null, ReadPreference $readPreference = null) { - return new Collection($this->manager, "{$this->dbname}.{$collectionName}", $writeConcern, $readPreferences); - } + $namespace = $this->databaseName . '.' . $collectionName; + $writeConcern = $writeConcern ?: $this->writeConcern; + $readPreference = $readPreference ?: $this->readPreference; + return new Collection($this->manager, $namespace, $writeConcern, $readPreference); + } } - - From 10ea91c216008efd66f64e5a5dd15ad917a71558 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Mon, 23 Mar 2015 20:51:59 -0400 Subject: [PATCH 04/21] PHPLIB-65: Database drop methods --- src/Client.php | 10 +++++++--- src/Database.php | 7 ++++++- tests/ClientFunctionalTest.php | 22 ++++++++++++++++++++++ tests/DatabaseFunctionalTest.php | 23 +++++++++++++++++++++++ tests/FunctionalTestCase.php | 20 ++++++++++++++++++++ 5 files changed, 78 insertions(+), 4 deletions(-) create mode 100644 tests/ClientFunctionalTest.php create mode 100644 tests/DatabaseFunctionalTest.php diff --git a/src/Client.php b/src/Client.php index 28d9115c6..030674be1 100644 --- a/src/Client.php +++ b/src/Client.php @@ -2,8 +2,7 @@ namespace MongoDB; -use MongoDB\Collection; -use MongoDB\Database; +use MongoDB\Driver\Command; use MongoDB\Driver\Manager; use MongoDB\Driver\ReadPreference; use MongoDB\Driver\Result; @@ -35,12 +34,17 @@ public function __construct($uri, array $options = array(), array $driverOptions /** * Drop a database. * + * @see http://docs.mongodb.org/manual/reference/command/dropDatabase/ * @param string $databaseName * @return Result */ public function dropDatabase($databaseName) { - // TODO + $databaseName = (string) $databaseName; + $command = new Command(array('dropDatabase' => 1)); + $readPreference = new ReadPreference(ReadPreference::RP_PRIMARY); + + return $this->manager->executeCommand($databaseName, $command, $readPreference); } /** diff --git a/src/Database.php b/src/Database.php index 3c1e24f11..9e28574c7 100644 --- a/src/Database.php +++ b/src/Database.php @@ -3,6 +3,7 @@ namespace MongoDB; use MongoDB\Collection; +use MongoDB\Driver\Command; use MongoDB\Driver\Manager; use MongoDB\Driver\ReadPreference; use MongoDB\Driver\Result; @@ -51,11 +52,15 @@ public function createCollection($collectionName, array $options = array()) /** * Drop this database. * + * @see http://docs.mongodb.org/manual/reference/command/dropDatabase/ * @return Result */ public function drop() { - // TODO + $command = new Command(array('dropDatabase' => 1)); + $readPreference = new ReadPreference(ReadPreference::RP_PRIMARY); + + return $this->manager->executeCommand($this->databaseName, $command, $readPreference); } /** diff --git a/tests/ClientFunctionalTest.php b/tests/ClientFunctionalTest.php new file mode 100644 index 000000000..1787fd3ca --- /dev/null +++ b/tests/ClientFunctionalTest.php @@ -0,0 +1,22 @@ +manager->executeInsert($this->getNamespace(), array('x' => 1)); + $this->assertEquals(1, $writeResult->getInsertedCount()); + + $client = new Client($this->getUri()); + $commandResult = $client->dropDatabase($this->getDatabaseName()); + $this->assertCommandSucceeded($commandResult); + $this->assertCollectionCount($this->getNamespace(), 0); + } +} diff --git a/tests/DatabaseFunctionalTest.php b/tests/DatabaseFunctionalTest.php new file mode 100644 index 000000000..e5bac6e2e --- /dev/null +++ b/tests/DatabaseFunctionalTest.php @@ -0,0 +1,23 @@ +manager->executeInsert($this->getNamespace(), array('x' => 1)); + $this->assertEquals(1, $writeResult->getInsertedCount()); + + $database = new Database($this->manager, $this->getDatabaseName()); + $commandResult = $database->drop(); + $this->assertCommandSucceeded($commandResult); + $this->assertCollectionCount($this->getNamespace(), 0); + } +} diff --git a/tests/FunctionalTestCase.php b/tests/FunctionalTestCase.php index b29e1520d..04dca8bfe 100644 --- a/tests/FunctionalTestCase.php +++ b/tests/FunctionalTestCase.php @@ -2,7 +2,9 @@ namespace MongoDB\Tests; +use MongoDB\Driver\Command; use MongoDB\Driver\Manager; +use MongoDB\Driver\Result; abstract class FunctionalTestCase extends TestCase { @@ -12,4 +14,22 @@ public function setUp() { $this->manager = new Manager($this->getUri()); } + + public function assertCollectionCount($namespace, $count) + { + list($databaseName, $collectionName) = explode('.', $namespace, 2); + + $result = $this->manager->executeCommand($databaseName, new Command(array('count' => $collectionName))); + + $document = $result->toArray(); + $this->assertArrayHasKey('n', $document); + $this->assertEquals($count, $document['n']); + } + + public function assertCommandSucceeded(Result $result) + { + $document = $result->toArray(); + $this->assertArrayHasKey('ok', $document); + $this->assertEquals(1, $document['ok']); + } } From d1b7a485edfd202512c6a125f0f94b5fa0ce45bb Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Mon, 23 Mar 2015 20:52:42 -0400 Subject: [PATCH 05/21] Cast expected string arguments --- src/Collection.php | 2 +- src/Database.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Collection.php b/src/Collection.php index bb14a448a..ce1ae02ad 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -56,7 +56,7 @@ class Collection public function __construct(Manager $manager, $namespace, WriteConcern $writeConcern = null, ReadPreference $readPreference = null) { $this->manager = $manager; - $this->ns = $namespace; + $this->ns = (string) $namespace; $this->wc = $writeConcern; $this->rp = $readPreference; diff --git a/src/Database.php b/src/Database.php index 9e28574c7..9493f8913 100644 --- a/src/Database.php +++ b/src/Database.php @@ -30,7 +30,7 @@ class Database public function __construct(Manager $manager, $databaseName, WriteConcern $writeConcern = null, ReadPreference $readPreference = null) { $this->manager = $manager; - $this->databaseName = $databaseName; + $this->databaseName = (string) $databaseName; $this->writeConcern = $writeConcern; $this->readPreference = $readPreference; } From 7ada7c2f11f91bc4e96574bc146fc84c6abe0583 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Wed, 25 Mar 2015 14:13:58 -0400 Subject: [PATCH 06/21] Dump server buildInfo before running tests on Travis CI --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index b6e6361f7..c586e7b42 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,7 @@ env: services: mongodb before_script: + - mongo --eval 'tojson(db.runCommand({buildInfo:1}))' - pecl -q install -f mongodb-${MONGODB_VERSION} - php --ri mongodb - composer install --dev --no-interaction --prefer-source From 46d0767e6239e3a2cca70783a2c6dbc5c8b030dd Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Wed, 25 Mar 2015 15:11:38 -0400 Subject: [PATCH 07/21] Use 127.0.0.1 instead of localhost for default URI --- phpunit.xml.dist | 2 +- tests/TestCase.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 2da976ba7..4f324887a 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -16,7 +16,7 @@ - + diff --git a/tests/TestCase.php b/tests/TestCase.php index d42ae0fde..365405d17 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -45,6 +45,6 @@ public function getNamespace() */ public function getUri() { - return getenv('MONGODB_URI') ?: 'mongodb://localhost:27017'; + return getenv('MONGODB_URI') ?: 'mongodb://127.0.0.1:27017'; } } From e9a5e8a74806ff8905a0ae392b185006429c9f98 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Wed, 25 Mar 2015 16:24:44 -0400 Subject: [PATCH 08/21] Restructure CollectionTest and fixture generation functions This also changes the existing collection functional test to inherit FunctionTestCase --- tests/CollectionFunctionalTest.php | 49 +++++++++++++++++++++++++ tests/CollectionTest.php | 49 +++---------------------- tests/FixtureGenerator.php | 59 ++++++++++++++++++++++++++++++ tests/utils.inc | 44 ---------------------- 4 files changed, 113 insertions(+), 88 deletions(-) create mode 100644 tests/CollectionFunctionalTest.php create mode 100644 tests/FixtureGenerator.php delete mode 100644 tests/utils.inc diff --git a/tests/CollectionFunctionalTest.php b/tests/CollectionFunctionalTest.php new file mode 100644 index 000000000..5b60ee760 --- /dev/null +++ b/tests/CollectionFunctionalTest.php @@ -0,0 +1,49 @@ +collection = new Collection($this->manager, $this->getNamespace()); + $this->collection->deleteMany(array()); + } + + function testInsertAndRetrieve() + { + $generator = new FixtureGenerator(); + + for ($i = 0; $i < 10; $i++) { + $user = $generator->createUser(); + $result = $this->collection->insertOne($user); + $this->assertInstanceOf('MongoDB\InsertOneResult', $result); + $this->assertInstanceOf('BSON\ObjectId', $result->getInsertedId()); + $this->assertEquals(24, strlen($result->getInsertedId())); + + $user["_id"] = $result->getInsertedId(); + $document = $this->collection->findOne(array("_id" => $result->getInsertedId())); + $this->assertEquals($document, $user, "The inserted and returned objects are the same"); + } + + $this->assertEquals(10, $i); + + $query = array("firstName" => "Ransom"); + $count = $this->collection->count($query); + $this->assertEquals(1, $count); + $cursor = $this->collection->find($query); + $this->assertInstanceOf('MongoDB\Driver\Result', $cursor); + + foreach($cursor as $n => $person) { + $this->assertInternalType("array", $person); + } + $this->assertEquals(0, $n); + } +} diff --git a/tests/CollectionTest.php b/tests/CollectionTest.php index e54ee92bb..5d9964407 100644 --- a/tests/CollectionTest.php +++ b/tests/CollectionTest.php @@ -1,50 +1,12 @@ faker = Faker\Factory::create(); - $this->faker->seed(1234); - - $this->manager = new Manager("mongodb://localhost"); - $this->collection = new Collection($this->manager, "test.case"); - $this->collection->deleteMany(array()); - } - - function testInsertAndRetrieve() { - $collection = $this->collection; - - for($i=0; $i<10;$i++) { - $user = createUser($this->faker); - $result = $collection->insertOne($user); - $this->assertInstanceOf('MongoDB\InsertOneResult', $result); - $this->assertInstanceOf('BSON\ObjectId', $result->getInsertedId()); - $this->assertEquals(24, strlen($result->getInsertedId())); - - $user["_id"] = $result->getInsertedId(); - $document = $collection->findOne(array("_id" => $result->getInsertedId())); - $this->assertEquals($document, $user, "The inserted and returned objects are the same"); - } - - $this->assertEquals(10, $i); - - - $query = array("firstName" => "Ransom"); - $count = $collection->count($query); - $this->assertEquals(1, $count); - $cursor = $collection->find($query); - $this->assertInstanceOf('MongoDB\Driver\Result', $cursor); - - foreach($cursor as $n => $person) { - $this->assertInternalType("array", $person); - } - $this->assertEquals(0, $n); - } +use ReflectionClass; +use ReflectionMethod; +class CollectionTest extends TestCase +{ public function testMethodOrder() { $class = new ReflectionClass('MongoDB\Collection'); @@ -68,4 +30,3 @@ function(ReflectionMethod $method) { return $method->getName(); }, } } } - diff --git a/tests/FixtureGenerator.php b/tests/FixtureGenerator.php new file mode 100644 index 000000000..986dffaa5 --- /dev/null +++ b/tests/FixtureGenerator.php @@ -0,0 +1,59 @@ +faker = Factory::create(); + $this->faker->seed(1234); + } + + public function createUser() + { + return array( + "username" => $this->faker->unique()->userName, + "password" => $this->faker->sha256, + "email" => $this->faker->unique()->safeEmail, + "firstName" => $this->faker->firstName, + "lastName" => $this->faker->lastName, + "phoneNumber" => $this->faker->phoneNumber, + "altPhoneNumber" => $this->faker->optional(0.1)->phoneNumber, + "company" => $this->faker->company, + "bio" => $this->faker->paragraph, + "createdAt" => $this->faker->dateTimeBetween("2008-01-01T00:00:00+0000", "2014-08-01T00:00:00+0000")->getTimestamp(), + "addresses" => array( + $this->createAddress(), + $this->createAddress(), + $this->createAddress(), + ), + ); + } + + public function createAddress() + { + return (object) array( + "streetAddress" => $this->faker->streetAddress, + "city" => $this->faker->city, + "state" => $this->faker->state, + "postalCode" => $this->faker->postcode, + "loc" => $this->createGeoJsonPoint(), + ); + } + + public function createGeoJsonPoint() + { + return (object) array( + "type" => "Point", + "coordinates" => (object) array( + $this->faker->longitude, + $this->faker->latitude + ), + ); + } +} diff --git a/tests/utils.inc b/tests/utils.inc deleted file mode 100644 index b07b872b4..000000000 --- a/tests/utils.inc +++ /dev/null @@ -1,44 +0,0 @@ - $faker->unique()->userName, - "password" => $faker->sha256, - "email" => $faker->unique()->safeEmail, - "firstName" => $faker->firstName, - "lastName" => $faker->lastName, - "phoneNumber" => $faker->phoneNumber, - "altPhoneNumber" => $faker->optional(0.1)->phoneNumber, - "company" => $faker->company, - "bio" => $faker->paragraph, - "createdAt" => $faker->dateTimeBetween("2008-01-01T00:00:00+0000", "2014-08-01T00:00:00+0000")->getTimestamp(), - "addresses" => (object)array( - createAddress($faker), - createAddress($faker), - createAddress($faker), - ), - ); -} - -function createAddress($faker) -{ - return (object)array( - "streetAddress" => $faker->streetAddress, - "city" => $faker->city, - "state" => $faker->state, - "postalCode" => $faker->postcode, - "loc" => createGeoJsonPoint($faker), - ); -} - -function createGeoJsonPoint($faker) -{ - return (object)array( - "type" => "Point", - "coordinates" => (object)array($faker->longitude, $faker->latitude), - ); -} - - From 6c1d6ac1ccd464b6b1f6db9f774d63d3e7753daf Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Thu, 26 Mar 2015 13:23:06 -0400 Subject: [PATCH 09/21] PHPLIB-72: Database enumeration method --- src/Client.php | 36 ++++++++++++++++++++++++++++++++++ tests/ClientFunctionalTest.php | 24 +++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/src/Client.php b/src/Client.php index 030674be1..6e0551572 100644 --- a/src/Client.php +++ b/src/Client.php @@ -7,6 +7,9 @@ use MongoDB\Driver\ReadPreference; use MongoDB\Driver\Result; use MongoDB\Driver\WriteConcern; +use ArrayIterator; +use stdClass; +use UnexpectedValueException; class Client { @@ -47,6 +50,39 @@ public function dropDatabase($databaseName) return $this->manager->executeCommand($databaseName, $command, $readPreference); } + /** + * List databases. + * + * @see http://docs.mongodb.org/manual/reference/command/listDatabases/ + * @return Traversable + * @throws UnexpectedValueException if the command result is malformed + */ + public function listDatabases() + { + $command = new Command(array('listDatabases' => 1)); + + $result = $this->manager->executeCommand('admin', $command); + $result = iterator_to_array($result); + $result = current($result); + + if ( ! isset($result['databases']) || ! is_array($result['databases'])) { + throw new UnexpectedValueException('listDatabases command did not return a "databases" array'); + } + + $databases = array_map( + function(stdClass $database) { return (array) $database; }, + $result['databases'] + ); + + /* Return a Traversable instead of an array in case listDatabases is + * eventually changed to return a command cursor, like the collection + * and index enumeration commands. This makes the "totalSize" command + * field inaccessible, but users can manually invoke the command if they + * need that value. + */ + return new ArrayIterator($databases); + } + /** * Select a database. * diff --git a/tests/ClientFunctionalTest.php b/tests/ClientFunctionalTest.php index 1787fd3ca..06b67f169 100644 --- a/tests/ClientFunctionalTest.php +++ b/tests/ClientFunctionalTest.php @@ -19,4 +19,28 @@ public function testDropDatabase() $this->assertCommandSucceeded($commandResult); $this->assertCollectionCount($this->getNamespace(), 0); } + + public function testListDatabases() + { + $writeResult = $this->manager->executeInsert($this->getNamespace(), array('x' => 1)); + $this->assertEquals(1, $writeResult->getInsertedCount()); + + $client = new Client($this->getUri()); + $databases = $client->listDatabases(); + + $this->assertInstanceOf('Traversable', $databases); + + $foundDatabase = null; + + foreach ($databases as $database) { + if ($database['name'] === $this->getDatabaseName()) { + $foundDatabase = $database; + break; + } + } + + $this->assertNotNull($foundDatabase, 'Found test database in list of databases'); + $this->assertFalse($foundDatabase['empty'], 'Test database is not empty'); + $this->assertGreaterThan(0, $foundDatabase['sizeOnDisk'], 'Test database takes up disk space'); + } } From 28da2e1edc40a3573a0d97b3b95506549d0a6f93 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Thu, 26 Mar 2015 15:46:30 -0400 Subject: [PATCH 10/21] PHPLIB-71: Collection drop methods --- src/Collection.php | 6 +++++- src/Database.php | 7 ++++++- tests/CollectionFunctionalTest.php | 10 ++++++++++ tests/DatabaseFunctionalTest.php | 11 +++++++++++ 4 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/Collection.php b/src/Collection.php index ce1ae02ad..2bb07e928 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -334,11 +334,15 @@ public function distinct($fieldName, array $filter = array(), array $options = a /** * Drop this collection. * + * @see http://docs.mongodb.org/manual/reference/command/drop/ * @return Result */ public function drop() { - // TODO + $command = new Command(array('drop' => $this->collname)); + $readPreference = new ReadPreference(ReadPreference::RP_PRIMARY); + + return $this->manager->executeCommand($this->dbname, $command, $readPreference); } /** diff --git a/src/Database.php b/src/Database.php index 9493f8913..59f5ae0d7 100644 --- a/src/Database.php +++ b/src/Database.php @@ -66,12 +66,17 @@ public function drop() /** * Drop a collection within this database. * + * @see http://docs.mongodb.org/manual/reference/command/drop/ * @param string $collectionName * @return Result */ public function dropCollection($collectionName) { - // TODO + $collectionName = (string) $collectionName; + $command = new Command(array('drop' => $collectionName)); + $readPreference = new ReadPreference(ReadPreference::RP_PRIMARY); + + return $this->manager->executeCommand($this->databaseName, $command, $readPreference); } /** diff --git a/tests/CollectionFunctionalTest.php b/tests/CollectionFunctionalTest.php index 5b60ee760..bb79acaa9 100644 --- a/tests/CollectionFunctionalTest.php +++ b/tests/CollectionFunctionalTest.php @@ -17,6 +17,16 @@ public function setUp() $this->collection->deleteMany(array()); } + public function testDrop() + { + $writeResult = $this->collection->insertOne(array('x' => 1)); + $this->assertEquals(1, $writeResult->getInsertedCount()); + + $commandResult = $this->collection->drop(); + $this->assertCommandSucceeded($commandResult); + $this->assertCollectionCount($this->getNamespace(), 0); + } + function testInsertAndRetrieve() { $generator = new FixtureGenerator(); diff --git a/tests/DatabaseFunctionalTest.php b/tests/DatabaseFunctionalTest.php index e5bac6e2e..2e0d591b0 100644 --- a/tests/DatabaseFunctionalTest.php +++ b/tests/DatabaseFunctionalTest.php @@ -20,4 +20,15 @@ public function testDrop() $this->assertCommandSucceeded($commandResult); $this->assertCollectionCount($this->getNamespace(), 0); } + + public function testDropCollection() + { + $writeResult = $this->manager->executeInsert($this->getNamespace(), array('x' => 1)); + $this->assertEquals(1, $writeResult->getInsertedCount()); + + $database = new Database($this->manager, $this->getDatabaseName()); + $commandResult = $database->dropCollection($this->getCollectionName()); + $this->assertCommandSucceeded($commandResult); + $this->assertCollectionCount($this->getNamespace(), 0); + } } From da4fdf190af2d6c224a270265f09a8200f5a9a3d Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Fri, 27 Mar 2015 17:26:50 -0400 Subject: [PATCH 11/21] Setup Database object in DatabaseFunctionalTest --- tests/DatabaseFunctionalTest.php | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/tests/DatabaseFunctionalTest.php b/tests/DatabaseFunctionalTest.php index 2e0d591b0..5f414c8d8 100644 --- a/tests/DatabaseFunctionalTest.php +++ b/tests/DatabaseFunctionalTest.php @@ -10,13 +10,22 @@ */ class DatabaseFunctionalTest extends FunctionalTestCase { + private $database; + + public function setUp() + { + parent::setUp(); + + $this->database = new Database($this->manager, $this->getDatabaseName()); + $this->database->drop(); + } + public function testDrop() { $writeResult = $this->manager->executeInsert($this->getNamespace(), array('x' => 1)); $this->assertEquals(1, $writeResult->getInsertedCount()); - $database = new Database($this->manager, $this->getDatabaseName()); - $commandResult = $database->drop(); + $commandResult = $this->database->drop(); $this->assertCommandSucceeded($commandResult); $this->assertCollectionCount($this->getNamespace(), 0); } @@ -26,8 +35,7 @@ public function testDropCollection() $writeResult = $this->manager->executeInsert($this->getNamespace(), array('x' => 1)); $this->assertEquals(1, $writeResult->getInsertedCount()); - $database = new Database($this->manager, $this->getDatabaseName()); - $commandResult = $database->dropCollection($this->getCollectionName()); + $commandResult = $this->database->dropCollection($this->getCollectionName()); $this->assertCommandSucceeded($commandResult); $this->assertCollectionCount($this->getNamespace(), 0); } From 92ee3c511e086836e192dfc93984d491bbbb94b5 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Fri, 27 Mar 2015 17:28:15 -0400 Subject: [PATCH 12/21] PHPLIB-45: Collection enumeration methods --- src/Database.php | 64 ++++++++++++++++++- src/Model/CollectionInfo.php | 70 +++++++++++++++++++++ src/Model/CollectionInfoCommandIterator.php | 18 ++++++ src/Model/CollectionInfoIterator.php | 15 +++++ src/Model/CollectionInfoLegacyIterator.php | 59 +++++++++++++++++ tests/DatabaseFunctionalTest.php | 20 ++++++ 6 files changed, 244 insertions(+), 2 deletions(-) create mode 100644 src/Model/CollectionInfo.php create mode 100644 src/Model/CollectionInfoCommandIterator.php create mode 100644 src/Model/CollectionInfoIterator.php create mode 100644 src/Model/CollectionInfoLegacyIterator.php diff --git a/src/Database.php b/src/Database.php index 59f5ae0d7..3fc50d792 100644 --- a/src/Database.php +++ b/src/Database.php @@ -5,9 +5,14 @@ use MongoDB\Collection; use MongoDB\Driver\Command; use MongoDB\Driver\Manager; +use MongoDB\Driver\Query; use MongoDB\Driver\ReadPreference; use MongoDB\Driver\Result; use MongoDB\Driver\WriteConcern; +use MongoDB\Model\CollectionInfoIterator; +use MongoDB\Model\CollectionInfoCommandIterator; +use MongoDB\Model\CollectionInfoLegacyIterator; +use InvalidArgumentException; class Database { @@ -84,11 +89,12 @@ public function dropCollection($collectionName) * * @see http://docs.mongodb.org/manual/reference/command/listCollections/ * @param array $options - * @return Result + * @return CollectionInfoIterator */ public function listCollections(array $options = array()) { - // TODO + // TODO: Determine if command or legacy method should be used + return $this->listCollectionsCommand($options); } /** @@ -110,4 +116,58 @@ public function selectCollection($collectionName, WriteConcern $writeConcern = n return new Collection($this->manager, $namespace, $writeConcern, $readPreference); } + + /** + * Returns information for all collections in this database using the + * listCollections command. + * + * @param array $options + * @return CollectionInfoCommandIterator + */ + private function listCollectionsCommand(array $options = array()) + { + $command = new Command(array('listCollections' => 1) + $options); + // TODO: Relax RP if connected to a secondary node in standalone mode + $readPreference = new ReadPreference(ReadPreference::RP_PRIMARY); + + $result = $this->manager->executeCommand($this->databaseName, $command, $readPreference); + + return new CollectionInfoCommandIterator($result); + } + + /** + * Returns information for all collections in this database by querying + * the "system.namespaces" collection (MongoDB <2.8). + * + * @param array $options + * @return CollectionInfoLegacyIterator + * @throws InvalidArgumentException if the filter option is neither an array + * nor object, or if filter.name is not a + * string. + */ + private function listCollectionsLegacy(array $options = array()) + { + $filter = array_key_exists('filter', $options) ? $options['filter'] : array(); + + if ( ! is_array($filter) && ! is_object($filter)) { + throw new InvalidArgumentException(sprintf('Expected filter to be array or object, %s given', gettype($filter))); + } + + if (array_key_exists('name', $filter)) { + if ( ! is_string($filter['name'])) { + throw new InvalidArgumentException(sprintf('Filter "name" must be a string for MongoDB <2.8, %s given', gettype($filter['name']))); + } + + $filter['name'] = $this->databaseName . '.' . $filter['name']; + } + + $namespace = $this->databaseName . '.system.namespaces'; + $query = new Query($filter); + // TODO: Relax RP if connected to a secondary node in standalone mode + $readPreference = new ReadPreference(ReadPreference::RP_PRIMARY); + + $result = $this->manager->executeQuery($namespace, $query, $readPreference); + + return new CollectionInfoLegacyIterator($result); + } } diff --git a/src/Model/CollectionInfo.php b/src/Model/CollectionInfo.php new file mode 100644 index 000000000..9617250ad --- /dev/null +++ b/src/Model/CollectionInfo.php @@ -0,0 +1,70 @@ +name = (string) $info['name']; + $this->options = isset($info['options']) ? (array) $info['options'] : array(); + } + + /** + * Return the collection name. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Return the collection options. + * + * @return array + */ + public function getOptions() + { + return $this->options; + } + + /** + * Return whether the collection is a capped collection. + * + * @return boolean + */ + public function isCapped() + { + return isset($this->options['capped']) ? (boolean) $this->options['capped'] : false; + } + + /** + * Return the maximum number of documents to keep in the capped collection. + * + * @return integer|null + */ + public function getCappedMax() + { + return isset($this->options['max']) ? (integer) $this->options['max'] : null; + } + + /** + * Return the maximum size (in bytes) of the capped collection. + * + * @return integer|null + */ + public function getCappedSize() + { + return isset($this->options['size']) ? (integer) $this->options['size'] : null; + } +} diff --git a/src/Model/CollectionInfoCommandIterator.php b/src/Model/CollectionInfoCommandIterator.php new file mode 100644 index 000000000..ce0c28737 --- /dev/null +++ b/src/Model/CollectionInfoCommandIterator.php @@ -0,0 +1,18 @@ +assertCommandSucceeded($commandResult); $this->assertCollectionCount($this->getNamespace(), 0); } + + public function testListCollections() + { + $writeResult = $this->manager->executeInsert($this->getNamespace(), array('x' => 1)); + $this->assertEquals(1, $writeResult->getInsertedCount()); + + $collections = $this->database->listCollections(); + $this->assertInstanceOf('MongoDB\Model\CollectionInfoIterator', $collections); + + $foundCollection = null; + + foreach ($collections as $collection) { + if ($collection->getName() === $this->getCollectionName()) { + $foundCollection = $collection; + break; + } + } + + $this->assertNotNull($foundCollection, 'Found test collection in list of collection'); + } } From 0ffbae83eb730cdbabf28f4cdb4fa114fdc775ac Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Mon, 6 Apr 2015 18:29:15 -0400 Subject: [PATCH 13/21] Handle new Cursor and toArray() API in extension PHPC-224 consolidated the Result and Cursor classes into one. Additionally, PHPC-203 changed toArray() to return iterator_to_array($this) instead of only the first result document. For commands, this means we need to extract the first element in toArray()'s return value. --- src/Client.php | 9 ++++--- src/Collection.php | 38 ++++++++++++++++-------------- src/Database.php | 16 ++++++------- tests/CollectionFunctionalTest.php | 2 +- tests/FunctionalTestCase.php | 10 ++++---- 5 files changed, 38 insertions(+), 37 deletions(-) diff --git a/src/Client.php b/src/Client.php index 6e0551572..f83f0b87f 100644 --- a/src/Client.php +++ b/src/Client.php @@ -3,9 +3,9 @@ namespace MongoDB; use MongoDB\Driver\Command; +use MongoDB\Driver\Cursor; use MongoDB\Driver\Manager; use MongoDB\Driver\ReadPreference; -use MongoDB\Driver\Result; use MongoDB\Driver\WriteConcern; use ArrayIterator; use stdClass; @@ -39,7 +39,7 @@ public function __construct($uri, array $options = array(), array $driverOptions * * @see http://docs.mongodb.org/manual/reference/command/dropDatabase/ * @param string $databaseName - * @return Result + * @return Cursor */ public function dropDatabase($databaseName) { @@ -61,9 +61,8 @@ public function listDatabases() { $command = new Command(array('listDatabases' => 1)); - $result = $this->manager->executeCommand('admin', $command); - $result = iterator_to_array($result); - $result = current($result); + $cursor = $this->manager->executeCommand('admin', $command); + $result = current($cursor->toArray()); if ( ! isset($result['databases']) || ! is_array($result['databases'])) { throw new UnexpectedValueException('listDatabases command did not return a "databases" array'); diff --git a/src/Collection.php b/src/Collection.php index 2bb07e928..82bc054fa 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -3,10 +3,10 @@ namespace MongoDB; use MongoDB\Driver\Command; +use MongoDB\Driver\Cursor; use MongoDB\Driver\Manager; use MongoDB\Driver\Query; use MongoDB\Driver\ReadPreference; -use MongoDB\Driver\Result; use MongoDB\Driver\BulkWrite; use MongoDB\Driver\WriteConcern; @@ -86,14 +86,16 @@ public function aggregate(array $pipeline, array $options = array()) "pipeline" => $pipeline, ) + $options; - $result = $this->_runCommand($this->dbname, $cmd); - $doc = $result->toArray(); + $cursor = $this->_runCommand($this->dbname, $cmd); + if (isset($cmd["cursor"]) && $cmd["cursor"]) { - return $result; - } else { - if ($doc["ok"]) { - return new \ArrayIterator($doc["result"]); - } + return $cursor; + } + + $doc = current($cursor->toArray()); + + if ($doc["ok"]) { + return new \ArrayIterator($doc["result"]); } throw $this->_generateCommandException($doc); @@ -234,7 +236,7 @@ public function count(array $filter = array(), array $options = array()) "query" => $filter, ) + $options; - $doc = $this->_runCommand($this->dbname, $cmd)->toArray(); + $doc = current($this->_runCommand($this->dbname, $cmd)->toArray()); if ($doc["ok"]) { return $doc["n"]; } @@ -324,7 +326,7 @@ public function distinct($fieldName, array $filter = array(), array $options = a "query" => $filter, ) + $options; - $doc = $this->_runCommand($this->dbname, $cmd)->toArray(); + $doc = current($this->_runCommand($this->dbname, $cmd)->toArray()); if ($doc["ok"]) { return $doc["values"]; } @@ -335,7 +337,7 @@ public function distinct($fieldName, array $filter = array(), array $options = a * Drop this collection. * * @see http://docs.mongodb.org/manual/reference/command/drop/ - * @return Result + * @return Cursor */ public function drop() { @@ -351,7 +353,7 @@ public function drop() * @see http://docs.mongodb.org/manual/reference/command/dropIndexes/ * @see http://docs.mongodb.org/manual/reference/method/db.collection.dropIndex/ * @param string $indexName - * @return Result + * @return Cursor * @throws InvalidArgumentException if "*" is specified */ public function dropIndex($indexName) @@ -364,7 +366,7 @@ public function dropIndex($indexName) * * @see http://docs.mongodb.org/manual/reference/command/dropIndexes/ * @see http://docs.mongodb.org/manual/reference/method/db.collection.dropIndexes/ - * @return Result + * @return Cursor */ public function dropIndexes() { @@ -379,7 +381,7 @@ public function dropIndexes() * * @param array $filter The find query to execute * @param array $options Additional options - * @return Result + * @return Cursor */ public function find(array $filter = array(), array $options = array()) { @@ -437,7 +439,7 @@ public function findOneAndDelete(array $filter, array $options = array()) "query" => $filter, ) + $options; - $doc = $this->_runCommand($this->dbname, $cmd)->toArray(); + $doc = current($this->_runCommand($this->dbname, $cmd)->toArray()); if ($doc["ok"]) { return $doc["value"]; } @@ -474,7 +476,7 @@ public function findOneAndReplace(array $filter, array $replacement, array $opti "query" => $filter, ) + $options; - $doc = $this->_runCommand($this->dbname, $cmd)->toArray(); + $doc = current($this->_runCommand($this->dbname, $cmd)->toArray()); if ($doc["ok"]) { return $doc["value"]; } @@ -512,7 +514,7 @@ public function findOneAndUpdate(array $filter, array $update, array $options = "query" => $filter, ) + $options; - $doc = $this->_runCommand($this->dbname, $cmd)->toArray(); + $doc = current($this->_runCommand($this->dbname, $cmd)->toArray()); if ($doc["ok"]) { return $doc["value"]; } @@ -951,7 +953,7 @@ public function insertOne(array $document) * * @see http://docs.mongodb.org/manual/reference/command/listIndexes/ * @see http://docs.mongodb.org/manual/reference/method/db.collection.getIndexes/ - * @return Result + * @return Cursor */ public function listIndexes() { diff --git a/src/Database.php b/src/Database.php index 3fc50d792..2c272783c 100644 --- a/src/Database.php +++ b/src/Database.php @@ -4,10 +4,10 @@ use MongoDB\Collection; use MongoDB\Driver\Command; +use MongoDB\Driver\Cursor; use MongoDB\Driver\Manager; use MongoDB\Driver\Query; use MongoDB\Driver\ReadPreference; -use MongoDB\Driver\Result; use MongoDB\Driver\WriteConcern; use MongoDB\Model\CollectionInfoIterator; use MongoDB\Model\CollectionInfoCommandIterator; @@ -47,7 +47,7 @@ public function __construct(Manager $manager, $databaseName, WriteConcern $write * @see http://docs.mongodb.org/manual/reference/method/db.createCollection/ * @param string $collectionName * @param array $options - * @return Result + * @return Cursor */ public function createCollection($collectionName, array $options = array()) { @@ -58,7 +58,7 @@ public function createCollection($collectionName, array $options = array()) * Drop this database. * * @see http://docs.mongodb.org/manual/reference/command/dropDatabase/ - * @return Result + * @return Cursor */ public function drop() { @@ -73,7 +73,7 @@ public function drop() * * @see http://docs.mongodb.org/manual/reference/command/drop/ * @param string $collectionName - * @return Result + * @return Cursor */ public function dropCollection($collectionName) { @@ -130,9 +130,9 @@ private function listCollectionsCommand(array $options = array()) // TODO: Relax RP if connected to a secondary node in standalone mode $readPreference = new ReadPreference(ReadPreference::RP_PRIMARY); - $result = $this->manager->executeCommand($this->databaseName, $command, $readPreference); + $cursor = $this->manager->executeCommand($this->databaseName, $command, $readPreference); - return new CollectionInfoCommandIterator($result); + return new CollectionInfoCommandIterator($cursor); } /** @@ -166,8 +166,8 @@ private function listCollectionsLegacy(array $options = array()) // TODO: Relax RP if connected to a secondary node in standalone mode $readPreference = new ReadPreference(ReadPreference::RP_PRIMARY); - $result = $this->manager->executeQuery($namespace, $query, $readPreference); + $cursor = $this->manager->executeQuery($namespace, $query, $readPreference); - return new CollectionInfoLegacyIterator($result); + return new CollectionInfoLegacyIterator($cursor); } } diff --git a/tests/CollectionFunctionalTest.php b/tests/CollectionFunctionalTest.php index bb79acaa9..4d3bc5d20 100644 --- a/tests/CollectionFunctionalTest.php +++ b/tests/CollectionFunctionalTest.php @@ -49,7 +49,7 @@ function testInsertAndRetrieve() $count = $this->collection->count($query); $this->assertEquals(1, $count); $cursor = $this->collection->find($query); - $this->assertInstanceOf('MongoDB\Driver\Result', $cursor); + $this->assertInstanceOf('MongoDB\Driver\Cursor', $cursor); foreach($cursor as $n => $person) { $this->assertInternalType("array", $person); diff --git a/tests/FunctionalTestCase.php b/tests/FunctionalTestCase.php index 04dca8bfe..e64090f65 100644 --- a/tests/FunctionalTestCase.php +++ b/tests/FunctionalTestCase.php @@ -4,7 +4,7 @@ use MongoDB\Driver\Command; use MongoDB\Driver\Manager; -use MongoDB\Driver\Result; +use MongoDB\Driver\Cursor; abstract class FunctionalTestCase extends TestCase { @@ -19,16 +19,16 @@ public function assertCollectionCount($namespace, $count) { list($databaseName, $collectionName) = explode('.', $namespace, 2); - $result = $this->manager->executeCommand($databaseName, new Command(array('count' => $collectionName))); + $cursor = $this->manager->executeCommand($databaseName, new Command(array('count' => $collectionName))); - $document = $result->toArray(); + $document = current($cursor->toArray()); $this->assertArrayHasKey('n', $document); $this->assertEquals($count, $document['n']); } - public function assertCommandSucceeded(Result $result) + public function assertCommandSucceeded(Cursor $cursor) { - $document = $result->toArray(); + $document = current($cursor->toArray()); $this->assertArrayHasKey('ok', $document); $this->assertEquals(1, $document['ok']); } From fde9d8edb334f05afb5a43e79589dd10aab9e165 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Mon, 6 Apr 2015 22:43:18 -0400 Subject: [PATCH 14/21] PHPLIB-64: Collection creation method --- src/Database.php | 6 +++- tests/DatabaseFunctionalTest.php | 60 ++++++++++++++++++++++++++++++-- 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/src/Database.php b/src/Database.php index 2c272783c..2e244e83f 100644 --- a/src/Database.php +++ b/src/Database.php @@ -51,7 +51,11 @@ public function __construct(Manager $manager, $databaseName, WriteConcern $write */ public function createCollection($collectionName, array $options = array()) { - // TODO + $collectionName = (string) $collectionName; + $command = new Command(array('create' => $collectionName) + $options); + $readPreference = new ReadPreference(ReadPreference::RP_PRIMARY); + + return $this->manager->executeCommand($this->databaseName, $command, $readPreference); } /** diff --git a/tests/DatabaseFunctionalTest.php b/tests/DatabaseFunctionalTest.php index 19bafcce7..607eff659 100644 --- a/tests/DatabaseFunctionalTest.php +++ b/tests/DatabaseFunctionalTest.php @@ -4,6 +4,8 @@ use MongoDB\Client; use MongoDB\Database; +use MongoDB\Model\CollectionInfo; +use InvalidArgumentException; /** * Functional tests for the Database class. @@ -20,6 +22,33 @@ public function setUp() $this->database->drop(); } + public function testCreateCollection() + { + $that = $this; + $basicCollectionName = $this->getCollectionName() . '.basic'; + + $commandResult = $this->database->createCollection($basicCollectionName); + $this->assertCommandSucceeded($commandResult); + $this->assertCollectionExists($basicCollectionName, function(CollectionInfo $info) use ($that) { + $that->assertFalse($info->isCapped()); + }); + + $cappedCollectionName = $this->getCollectionName() . '.capped'; + $cappedCollectionOptions = array( + 'capped' => true, + 'max' => 100, + 'size' => 1048576, + ); + + $commandResult = $this->database->createCollection($cappedCollectionName, $cappedCollectionOptions); + $this->assertCommandSucceeded($commandResult); + $this->assertCollectionExists($cappedCollectionName, function(CollectionInfo $info) use ($that) { + $that->assertTrue($info->isCapped()); + $that->assertEquals(100, $info->getCappedMax()); + $that->assertEquals(1048576, $info->getCappedSize()); + }); + } + public function testDrop() { $writeResult = $this->manager->executeInsert($this->getNamespace(), array('x' => 1)); @@ -48,15 +77,42 @@ public function testListCollections() $collections = $this->database->listCollections(); $this->assertInstanceOf('MongoDB\Model\CollectionInfoIterator', $collections); + foreach ($collections as $collection) { + $this->assertInstanceOf('MongoDB\Model\CollectionInfo', $collection); + } + } + + /** + * Asserts that a collection with the given name exists in the database. + * + * An optional $callback may be provided, which should take a CollectionInfo + * argument as its first and only parameter. If a CollectionInfo matching + * the given name is found, it will be passed to the callback, which may + * perform additional assertions. + * + * @param callable $callback + */ + private function assertCollectionExists($collectionName, $callback = null) + { + if ($callback !== null && ! is_callable($callback)) { + throw new InvalidArgumentException('$callback is not a callable'); + } + + $collections = $this->database->listCollections(); + $foundCollection = null; foreach ($collections as $collection) { - if ($collection->getName() === $this->getCollectionName()) { + if ($collection->getName() === $collectionName) { $foundCollection = $collection; break; } } - $this->assertNotNull($foundCollection, 'Found test collection in list of collection'); + $this->assertNotNull($foundCollection, sprintf('Found %s collection in the database', $collectionName)); + + if ($callback !== null) { + call_user_func($callback, $foundCollection); + } } } From 9e62cabd2ee75fa6f3d8d5308c0bc2a59057c0bd Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Mon, 6 Apr 2015 22:45:02 -0400 Subject: [PATCH 15/21] Allow any extension version for development --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index fb95e2cb1..2ca78742e 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,7 @@ { "name": "Derick Rethans", "email": "github@derickrethans.nl" } ], "require": { - "ext-mongodb": "^0.2" + "ext-mongodb": "*" }, "require-dev": { "fzaninotto/faker": "~1.0" From a20fa27affa10b23062bd71f3374998b4167ff89 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Fri, 10 Apr 2015 17:13:41 -0400 Subject: [PATCH 16/21] PHPLIB-45: Construct CollectionInfoLegacyIterator from Traversable --- src/Model/CollectionInfoLegacyIterator.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/Model/CollectionInfoLegacyIterator.php b/src/Model/CollectionInfoLegacyIterator.php index a1ea22b0c..4426def61 100644 --- a/src/Model/CollectionInfoLegacyIterator.php +++ b/src/Model/CollectionInfoLegacyIterator.php @@ -3,9 +3,29 @@ namespace MongoDB\Model; use FilterIterator; +use Iterator; +use IteratorIterator; +use Traversable; class CollectionInfoLegacyIterator extends FilterIterator implements CollectionInfoIterator { + /** + * Constructor. + * + * @param Traversable $iterator + */ + public function __construct(Traversable $iterator) + { + /* FilterIterator requires an Iterator, so wrap all other Traversables + * with an IteratorIterator as a convenience. + */ + if ( ! $iterator instanceof Iterator) { + $iterator = new IteratorIterator($iterator); + } + + parent::__construct($iterator); + } + /** * Return the current element as a CollectionInfo instance. * From a32ac4f71b5ec3639fb8132113f436b65873814d Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Fri, 10 Apr 2015 17:21:27 -0400 Subject: [PATCH 17/21] PHPLIB-45: List collections according to wire protocol version --- src/Database.php | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/Database.php b/src/Database.php index 2e244e83f..b5e3bdca9 100644 --- a/src/Database.php +++ b/src/Database.php @@ -8,6 +8,7 @@ use MongoDB\Driver\Manager; use MongoDB\Driver\Query; use MongoDB\Driver\ReadPreference; +use MongoDB\Driver\Server; use MongoDB\Driver\WriteConcern; use MongoDB\Model\CollectionInfoIterator; use MongoDB\Model\CollectionInfoCommandIterator; @@ -97,8 +98,15 @@ public function dropCollection($collectionName) */ public function listCollections(array $options = array()) { - // TODO: Determine if command or legacy method should be used - return $this->listCollectionsCommand($options); + $readPreference = new ReadPreference(ReadPreference::RP_PRIMARY); + $server = $this->manager->selectServer($readPreference); + + $serverInfo = $server->getInfo(); + $maxWireVersion = isset($serverInfo['maxWireVersion']) ? $serverInfo['maxWireVersion'] : 0; + + return ($maxWireVersion >= 3) + ? $this->listCollectionsCommand($server, $options) + : $this->listCollectionsLegacy($server, $options); } /** @@ -125,16 +133,14 @@ public function selectCollection($collectionName, WriteConcern $writeConcern = n * Returns information for all collections in this database using the * listCollections command. * - * @param array $options + * @param Server $server + * @param array $options * @return CollectionInfoCommandIterator */ - private function listCollectionsCommand(array $options = array()) + private function listCollectionsCommand(Server $server, array $options = array()) { $command = new Command(array('listCollections' => 1) + $options); - // TODO: Relax RP if connected to a secondary node in standalone mode - $readPreference = new ReadPreference(ReadPreference::RP_PRIMARY); - - $cursor = $this->manager->executeCommand($this->databaseName, $command, $readPreference); + $cursor = $server->executeCommand($this->databaseName, $command); return new CollectionInfoCommandIterator($cursor); } @@ -143,13 +149,14 @@ private function listCollectionsCommand(array $options = array()) * Returns information for all collections in this database by querying * the "system.namespaces" collection (MongoDB <2.8). * - * @param array $options + * @param Server $server + * @param array $options * @return CollectionInfoLegacyIterator * @throws InvalidArgumentException if the filter option is neither an array * nor object, or if filter.name is not a * string. */ - private function listCollectionsLegacy(array $options = array()) + private function listCollectionsLegacy(Server $server, array $options = array()) { $filter = array_key_exists('filter', $options) ? $options['filter'] : array(); @@ -167,10 +174,7 @@ private function listCollectionsLegacy(array $options = array()) $namespace = $this->databaseName . '.system.namespaces'; $query = new Query($filter); - // TODO: Relax RP if connected to a secondary node in standalone mode - $readPreference = new ReadPreference(ReadPreference::RP_PRIMARY); - - $cursor = $this->manager->executeQuery($namespace, $query, $readPreference); + $cursor = $server->executeQuery($namespace, $query); return new CollectionInfoLegacyIterator($cursor); } From ccf1eba5ab0cf5c8e66c6633e035cbacf46fdb0b Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Fri, 10 Apr 2015 17:22:19 -0400 Subject: [PATCH 18/21] PHPLIB-45: Support stdClass listCollections() filter option --- src/Database.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Database.php b/src/Database.php index b5e3bdca9..1da5d90db 100644 --- a/src/Database.php +++ b/src/Database.php @@ -164,7 +164,9 @@ private function listCollectionsLegacy(Server $server, array $options = array()) throw new InvalidArgumentException(sprintf('Expected filter to be array or object, %s given', gettype($filter))); } - if (array_key_exists('name', $filter)) { + if (array_key_exists('name', (array) $filter)) { + $filter = (array) $filter; + if ( ! is_string($filter['name'])) { throw new InvalidArgumentException(sprintf('Filter "name" must be a string for MongoDB <2.8, %s given', gettype($filter['name']))); } From e574c2dfee66a8d0c043a768f13e64d3b68b7f05 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Fri, 10 Apr 2015 17:23:21 -0400 Subject: [PATCH 19/21] PHPLIB-45: Test listCollections with filter option --- tests/DatabaseFunctionalTest.php | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/tests/DatabaseFunctionalTest.php b/tests/DatabaseFunctionalTest.php index 607eff659..b49ccf6dc 100644 --- a/tests/DatabaseFunctionalTest.php +++ b/tests/DatabaseFunctionalTest.php @@ -71,8 +71,8 @@ public function testDropCollection() public function testListCollections() { - $writeResult = $this->manager->executeInsert($this->getNamespace(), array('x' => 1)); - $this->assertEquals(1, $writeResult->getInsertedCount()); + $commandResult = $this->database->createCollection($this->getCollectionName()); + $this->assertCommandSucceeded($commandResult); $collections = $this->database->listCollections(); $this->assertInstanceOf('MongoDB\Model\CollectionInfoIterator', $collections); @@ -82,6 +82,24 @@ public function testListCollections() } } + public function testListCollectionsWithFilter() + { + $commandResult = $this->database->createCollection($this->getCollectionName()); + $this->assertCommandSucceeded($commandResult); + + $collectionName = $this->getCollectionName(); + $options = array('filter' => array('name' => $collectionName)); + + $collections = $this->database->listCollections($options); + $this->assertInstanceOf('MongoDB\Model\CollectionInfoIterator', $collections); + $this->assertCount(1, $collections); + + foreach ($collections as $collection) { + $this->assertInstanceOf('MongoDB\Model\CollectionInfo', $collection); + $this->assertEquals($collectionName, $collection->getName()); + } + } + /** * Asserts that a collection with the given name exists in the database. * From fd0b861597753d3054f13ac920e548867124de91 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Fri, 10 Apr 2015 17:54:19 -0400 Subject: [PATCH 20/21] Install latest PECL extension when testing --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c586e7b42..95566f8e4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ php: - 5.6 env: - - MONGODB_VERSION=0.2.0 + - MONGODB_VERSION=alpha services: mongodb From 192d950bc0656dce318840b42d882b24a87584cc Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Fri, 10 Apr 2015 18:28:15 -0400 Subject: [PATCH 21/21] Don't be quiet when compiling the extension --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 95566f8e4..59b908f5e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,6 @@ services: mongodb before_script: - mongo --eval 'tojson(db.runCommand({buildInfo:1}))' - - pecl -q install -f mongodb-${MONGODB_VERSION} + - pecl install -f mongodb-${MONGODB_VERSION} - php --ri mongodb - composer install --dev --no-interaction --prefer-source