diff --git a/.travis.yml b/.travis.yml index b6e6361f7..59b908f5e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,11 +7,12 @@ php: - 5.6 env: - - MONGODB_VERSION=0.2.0 + - MONGODB_VERSION=alpha services: mongodb before_script: - - pecl -q install -f mongodb-${MONGODB_VERSION} + - mongo --eval 'tojson(db.runCommand({buildInfo:1}))' + - pecl install -f mongodb-${MONGODB_VERSION} - php --ri mongodb - composer install --dev --no-interaction --prefer-source 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" diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 4df13239c..4f324887a 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -11,11 +11,13 @@ convertWarningsToExceptions="true" stopOnFailure="false" syntaxCheck="false" - bootstrap="vendor/autoload.php" + bootstrap="tests/bootstrap.php" > + + diff --git a/src/Client.php b/src/Client.php index 4c926a2ca..f83f0b87f 100644 --- a/src/Client.php +++ b/src/Client.php @@ -2,30 +2,34 @@ namespace MongoDB; -use MongoDB\Collection; -use MongoDB\Database; +use MongoDB\Driver\Command; +use MongoDB\Driver\Cursor; use MongoDB\Driver\Manager; -use MongoDB\Driver\Result; +use MongoDB\Driver\ReadPreference; +use MongoDB\Driver\WriteConcern; +use ArrayIterator; +use stdClass; +use UnexpectedValueException; 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); } @@ -33,42 +37,90 @@ public function __construct($uri, $options, $driverOptions) /** * Drop a database. * + * @see http://docs.mongodb.org/manual/reference/command/dropDatabase/ * @param string $databaseName - * @return Result + * @return Cursor */ 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); } /** - * Select a database + * List databases. * - * It acts as a bridge to access specific database commands - * - * @param string $databaseName The database to select - * @param WriteConcern $writeConcern Default Write Concern to apply - * @param ReadPreference $readPreferences Default Read Preferences to apply + * @see http://docs.mongodb.org/manual/reference/command/listDatabases/ + * @return Traversable + * @throws UnexpectedValueException if the command result is malformed */ - public function selectDatabase($databaseName, WriteConcern $writeConcern = null, ReadPreference $readPreferences = null) + public function listDatabases() { - return new Database($this->manager, $databaseName, $writeConcern, $readPreferences); + $command = new Command(array('listDatabases' => 1)); + + $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'); + } + + $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 specific collection in a database + * Select a 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 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 to select + * @param WriteConcern $writeConcern Default write concern to apply + * @param ReadPreference $readPreference Default read preference to apply + * @return Database */ - public function selectCollection($databaseName, $collectionName, WriteConcern $writeConcern = null, ReadPreference $readPreferences = null) + public function selectDatabase($databaseName, WriteConcern $writeConcern = null, ReadPreference $readPreference = null) { - return new Collection($this->manager, "{$databaseName}.{$collectionName}", $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 collection. + * + * 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 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 $readPreference = null) + { + $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..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; @@ -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 = (string) $namespace; + $this->wc = $writeConcern; + $this->rp = $readPreference; - list($this->dbname, $this->collname) = explode(".", $ns, 2); + list($this->dbname, $this->collname) = explode(".", $namespace, 2); } /** @@ -87,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); @@ -235,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"]; } @@ -325,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,11 +336,15 @@ public function distinct($fieldName, array $filter = array(), array $options = a /** * Drop this collection. * - * @return Result + * @see http://docs.mongodb.org/manual/reference/command/drop/ + * @return Cursor */ 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); } /** @@ -348,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) @@ -361,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() { @@ -376,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()) { @@ -434,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"]; } @@ -471,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"]; } @@ -509,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"]; } @@ -948,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 cb772b3a6..1da5d90db 100644 --- a/src/Database.php +++ b/src/Database.php @@ -3,34 +3,42 @@ namespace MongoDB; use MongoDB\Collection; +use MongoDB\Driver\Command; +use MongoDB\Driver\Cursor; use MongoDB\Driver\Manager; -use MongoDB\Driver\Result; +use MongoDB\Driver\Query; +use MongoDB\Driver\ReadPreference; +use MongoDB\Driver\Server; +use MongoDB\Driver\WriteConcern; +use MongoDB\Model\CollectionInfoIterator; +use MongoDB\Model\CollectionInfoCommandIterator; +use MongoDB\Model\CollectionInfoLegacyIterator; +use InvalidArgumentException; 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 = (string) $databaseName; + $this->writeConcern = $writeConcern; + $this->readPreference = $readPreference; } /** @@ -40,32 +48,45 @@ public function __construct(Manager $manager, $databaseName, WriteConcern $wc = * @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()) { - // 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); } /** * Drop this database. * - * @return Result + * @see http://docs.mongodb.org/manual/reference/command/dropDatabase/ + * @return Cursor */ public function drop() { - // TODO + $command = new Command(array('dropDatabase' => 1)); + $readPreference = new ReadPreference(ReadPreference::RP_PRIMARY); + + return $this->manager->executeCommand($this->databaseName, $command, $readPreference); } /** * Drop a collection within this database. * + * @see http://docs.mongodb.org/manual/reference/command/drop/ * @param string $collectionName - * @return Result + * @return Cursor */ 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); } /** @@ -73,27 +94,90 @@ 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 + $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); } /** - * 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); } -} + /** + * Returns information for all collections in this database using the + * listCollections command. + * + * @param Server $server + * @param array $options + * @return CollectionInfoCommandIterator + */ + private function listCollectionsCommand(Server $server, array $options = array()) + { + $command = new Command(array('listCollections' => 1) + $options); + $cursor = $server->executeCommand($this->databaseName, $command); + return new CollectionInfoCommandIterator($cursor); + } + /** + * Returns information for all collections in this database by querying + * the "system.namespaces" collection (MongoDB <2.8). + * + * @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(Server $server, 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', (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']))); + } + + $filter['name'] = $this->databaseName . '.' . $filter['name']; + } + + $namespace = $this->databaseName . '.system.namespaces'; + $query = new Query($filter); + $cursor = $server->executeQuery($namespace, $query); + + return new CollectionInfoLegacyIterator($cursor); + } +} 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 @@ +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); + } + + 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'); + } +} diff --git a/tests/CollectionFunctionalTest.php b/tests/CollectionFunctionalTest.php new file mode 100644 index 000000000..4d3bc5d20 --- /dev/null +++ b/tests/CollectionFunctionalTest.php @@ -0,0 +1,59 @@ +collection = new Collection($this->manager, $this->getNamespace()); + $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(); + + 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\Cursor', $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/DatabaseFunctionalTest.php b/tests/DatabaseFunctionalTest.php new file mode 100644 index 000000000..b49ccf6dc --- /dev/null +++ b/tests/DatabaseFunctionalTest.php @@ -0,0 +1,136 @@ +database = new Database($this->manager, $this->getDatabaseName()); + $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)); + $this->assertEquals(1, $writeResult->getInsertedCount()); + + $commandResult = $this->database->drop(); + $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()); + + $commandResult = $this->database->dropCollection($this->getCollectionName()); + $this->assertCommandSucceeded($commandResult); + $this->assertCollectionCount($this->getNamespace(), 0); + } + + public function testListCollections() + { + $commandResult = $this->database->createCollection($this->getCollectionName()); + $this->assertCommandSucceeded($commandResult); + + $collections = $this->database->listCollections(); + $this->assertInstanceOf('MongoDB\Model\CollectionInfoIterator', $collections); + + foreach ($collections as $collection) { + $this->assertInstanceOf('MongoDB\Model\CollectionInfo', $collection); + } + } + + 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. + * + * 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() === $collectionName) { + $foundCollection = $collection; + break; + } + } + + $this->assertNotNull($foundCollection, sprintf('Found %s collection in the database', $collectionName)); + + if ($callback !== null) { + call_user_func($callback, $foundCollection); + } + } +} 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/FunctionalTestCase.php b/tests/FunctionalTestCase.php new file mode 100644 index 000000000..e64090f65 --- /dev/null +++ b/tests/FunctionalTestCase.php @@ -0,0 +1,35 @@ +manager = new Manager($this->getUri()); + } + + public function assertCollectionCount($namespace, $count) + { + list($databaseName, $collectionName) = explode('.', $namespace, 2); + + $cursor = $this->manager->executeCommand($databaseName, new Command(array('count' => $collectionName))); + + $document = current($cursor->toArray()); + $this->assertArrayHasKey('n', $document); + $this->assertEquals($count, $document['n']); + } + + public function assertCommandSucceeded(Cursor $cursor) + { + $document = current($cursor->toArray()); + $this->assertArrayHasKey('ok', $document); + $this->assertEquals(1, $document['ok']); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 000000000..365405d17 --- /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://127.0.0.1:27017'; + } +} 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__); 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), - ); -} - -