diff --git a/composer.json b/composer.json index 2fad9e0b5..71300a921 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,8 @@ "ext-mongodb": ">=0.6.0" }, "autoload": { - "psr-4": { "MongoDB\\": "src/" } + "psr-4": { "MongoDB\\": "src/" }, + "files": [ "src/functions.php" ] }, "extra": { "branch-alias": { diff --git a/src/Client.php b/src/Client.php index 8386addf3..4031ed11e 100644 --- a/src/Client.php +++ b/src/Client.php @@ -7,9 +7,9 @@ use MongoDB\Driver\Manager; use MongoDB\Driver\ReadPreference; use MongoDB\Driver\WriteConcern; -use MongoDB\Exception\UnexpectedValueException; use MongoDB\Model\DatabaseInfoIterator; -use MongoDB\Model\DatabaseInfoLegacyIterator; +use MongoDB\Operation\DropDatabase; +use MongoDB\Operation\ListDatabases; class Client { @@ -37,45 +37,29 @@ 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 Cursor + * @return object Command result document */ public function dropDatabase($databaseName) { - $databaseName = (string) $databaseName; - $command = new Command(array('dropDatabase' => 1)); - $readPreference = new ReadPreference(ReadPreference::RP_PRIMARY); + $operation = new DropDatabase($databaseName); + $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); - return $this->manager->executeCommand($databaseName, $command, $readPreference); + return $operation->execute($server); } /** * List databases. * - * @see http://docs.mongodb.org/manual/reference/command/listDatabases/ + * @see ListDatabases::__construct() for supported options * @return DatabaseInfoIterator - * @throws UnexpectedValueException if the command result is malformed */ - public function listDatabases() + public function listDatabases(array $options = array()) { - $command = new Command(array('listDatabases' => 1)); + $operation = new ListDatabases($options); + $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); - $cursor = $this->manager->executeCommand('admin', $command); - $cursor->setTypeMap(array('document' => 'array')); - $result = current($cursor->toArray()); - - if ( ! isset($result['databases']) || ! is_array($result['databases'])) { - throw new UnexpectedValueException('listDatabases command did not return a "databases" array'); - } - - /* Return an Iterator 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 DatabaseInfoLegacyIterator($result['databases']); + return $operation->execute($server); } /** diff --git a/src/Collection.php b/src/Collection.php index b55d1c5b1..8d0d21871 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -13,8 +13,18 @@ use MongoDB\Exception\InvalidArgumentException; use MongoDB\Exception\UnexpectedTypeException; use MongoDB\Model\IndexInfoIterator; -use MongoDB\Model\IndexInfoIteratorIterator; use MongoDB\Model\IndexInput; +use MongoDB\Operation\Aggregate; +use MongoDB\Operation\CreateIndexes; +use MongoDB\Operation\Count; +use MongoDB\Operation\Distinct; +use MongoDB\Operation\DropCollection; +use MongoDB\Operation\DropIndexes; +use MongoDB\Operation\FindOneAndDelete; +use MongoDB\Operation\FindOneAndReplace; +use MongoDB\Operation\FindOneAndUpdate; +use MongoDB\Operation\ListIndexes; +use Traversable; class Collection { @@ -33,9 +43,6 @@ class Collection //self::QUERY_FLAG_TAILABLE_CURSOR | self::QUERY_FLAG_AWAIT_DATA; const CURSOR_TYPE_TAILABLE_AWAIT = 0x22; - const FIND_ONE_AND_RETURN_BEFORE = 0x01; - const FIND_ONE_AND_RETURN_AFTER = 0x02; - protected $manager; protected $ns; protected $wc; @@ -78,79 +85,25 @@ public function __toString() } /** - * Runs an aggregation framework pipeline + * Executes an aggregation framework pipeline on the collection. * * Note: this method's return value depends on the MongoDB server version * and the "useCursor" option. If "useCursor" is true, a Cursor will be * returned; otherwise, an ArrayIterator is returned, which wraps the * "result" array from the command response document. * - * @see http://docs.mongodb.org/manual/reference/command/aggregate/ - * - * @param array $pipeline The pipeline to execute - * @param array $options Additional options - * @return Iterator + * @see Aggregate::__construct() for supported options + * @param array $pipeline List of pipeline operations + * @param array $options Command options + * @return Traversable */ public function aggregate(array $pipeline, array $options = array()) { $readPreference = new ReadPreference(ReadPreference::RP_PRIMARY); $server = $this->manager->selectServer($readPreference); + $operation = new Aggregate($this->dbname, $this->collname, $pipeline, $options); - if (FeatureDetection::isSupported($server, FeatureDetection::API_AGGREGATE_CURSOR)) { - $options = array_merge( - array( - /** - * Enables writing to temporary files. When set to true, aggregation stages - * can write data to the _tmp subdirectory in the dbPath directory. The - * default is false. - * - * @see http://docs.mongodb.org/manual/reference/command/aggregate/ - */ - 'allowDiskUse' => false, - /** - * The number of documents to return per batch. - * - * @see http://docs.mongodb.org/manual/reference/command/aggregate/ - */ - 'batchSize' => 0, - /** - * The maximum amount of time to allow the query to run. - * - * @see http://docs.mongodb.org/manual/reference/command/aggregate/ - */ - 'maxTimeMS' => 0, - /** - * Indicates if the results should be provided as a cursor. - * - * @see http://docs.mongodb.org/manual/reference/command/aggregate/ - */ - 'useCursor' => true, - ), - $options - ); - } - - $options = $this->_massageAggregateOptions($options); - $command = new Command(array( - 'aggregate' => $this->collname, - 'pipeline' => $pipeline, - ) + $options); - $cursor = $server->executeCommand($this->dbname, $command); - - if ( ! empty($options["cursor"])) { - return $cursor; - } - - $doc = current($cursor->toArray()); - - if ($doc["ok"]) { - return new \ArrayIterator(array_map( - function (\stdClass $document) { return (array) $document; }, - $doc["result"] - )); - } - - throw $this->_generateCommandException($doc); + return $operation->execute($server); } /** @@ -286,35 +239,24 @@ public function bulkWrite(array $ops, array $options = array()) } /** - * Counts all documents matching $filter - * If no $filter provided, returns the numbers of documents in the collection + * Gets the number of documents matching the filter. * - * @see http://docs.mongodb.org/manual/reference/command/count/ - * @see Collection::getCountOptions() for supported $options - * - * @param array $filter The find query to execute - * @param array $options Additional options + * @see Count::__construct() for supported options + * @param array $filter Query by which to filter documents + * @param array $options Command options * @return integer */ public function count(array $filter = array(), array $options = array()) { - $cmd = array( - "count" => $this->collname, - "query" => (object) $filter, - ) + $options; - - $doc = current($this->_runCommand($this->dbname, $cmd)->toArray()); - if ($doc["ok"]) { - return (integer) $doc["n"]; - } - throw $this->_generateCommandException($doc); + $operation = new Count($this->dbname, $this->collname, $filter, $options); + $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); + + return $operation->execute($server); } /** * Create a single index for the collection. * - * @see http://docs.mongodb.org/manual/reference/command/createIndexes/ - * @see http://docs.mongodb.org/manual/reference/method/db.collection.createIndex/ * @see Collection::createIndexes() * @param array|object $key Document containing fields mapped to values, * which denote order or an index type @@ -345,34 +287,16 @@ public function createIndex($key, array $options = array()) * * @see http://docs.mongodb.org/manual/reference/command/createIndexes/ * @see http://docs.mongodb.org/manual/reference/method/db.collection.createIndex/ - * @param array $indexes List of index specifications + * @param array[] $indexes List of index specifications * @return string[] The names of the created indexes * @throws InvalidArgumentException if an index specification is invalid */ public function createIndexes(array $indexes) { - if (empty($indexes)) { - return array(); - } - - foreach ($indexes as $i => $index) { - if ( ! is_array($index)) { - throw new UnexpectedTypeException($index, 'array'); - } - - if ( ! isset($index['ns'])) { - $index['ns'] = $this->ns; - } - - $indexes[$i] = new IndexInput($index); - } - - $readPreference = new ReadPreference(ReadPreference::RP_PRIMARY); - $server = $this->manager->selectServer($readPreference); + $operation = new CreateIndexes($this->dbname, $this->collname, $indexes); + $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); - return (FeatureDetection::isSupported($server, FeatureDetection::API_CREATEINDEXES_CMD)) - ? $this->createIndexesCommand($server, $indexes) - : $this->createIndexesLegacy($server, $indexes); + return $operation->execute($server); } /** @@ -408,86 +332,67 @@ public function deleteOne(array $filter) } /** - * Finds the distinct values for a specified field across the collection + * Finds the distinct values for a specified field across the collection. * - * @see http://docs.mongodb.org/manual/reference/command/distinct/ - * @see Collection::getDistinctOptions() for supported $options - * - * @param string $fieldName The fieldname to use - * @param array $filter The find query to execute - * @param array $options Additional options - * @return integer + * @see Distinct::__construct() for supported options + * @param string $fieldName Field for which to return distinct values + * @param array $filter Query by which to filter documents + * @param array $options Command options + * @return mixed[] */ public function distinct($fieldName, array $filter = array(), array $options = array()) { - $options = array_merge($this->getDistinctOptions(), $options); - $cmd = array( - "distinct" => $this->collname, - "key" => $fieldName, - "query" => (object) $filter, - ) + $options; - - $doc = current($this->_runCommand($this->dbname, $cmd)->toArray()); - if ($doc["ok"]) { - return $doc["values"]; - } - throw $this->_generateCommandException($doc); + $operation = new Distinct($this->dbname, $this->collname, $fieldName, $filter, $options); + $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); + + return $operation->execute($server); } /** * Drop this collection. * - * @see http://docs.mongodb.org/manual/reference/command/drop/ - * @return Cursor + * @return object Command result document */ public function drop() { - $command = new Command(array('drop' => $this->collname)); - $readPreference = new ReadPreference(ReadPreference::RP_PRIMARY); + $operation = new DropCollection($this->dbname, $this->collname); + $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); - return $this->manager->executeCommand($this->dbname, $command, $readPreference); + return $operation->execute($server); } /** * Drop a single index in the collection. * - * @see http://docs.mongodb.org/manual/reference/command/dropIndexes/ - * @see http://docs.mongodb.org/manual/reference/method/db.collection.dropIndex/ - * @param string $indexName - * @return Cursor + * @param string $indexName Index name + * @return object Command result document * @throws InvalidArgumentException if $indexName is an empty string or "*" */ public function dropIndex($indexName) { $indexName = (string) $indexName; - if ($indexName === '') { - throw new InvalidArgumentException('Index name cannot be empty'); - } - if ($indexName === '*') { throw new InvalidArgumentException('dropIndexes() must be used to drop multiple indexes'); } - $command = new Command(array('dropIndexes' => $this->collname, 'index' => $indexName)); - $readPreference = new ReadPreference(ReadPreference::RP_PRIMARY); + $operation = new DropIndexes($this->dbname, $this->collname, $indexName); + $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); - return $this->manager->executeCommand($this->dbname, $command, $readPreference); + return $operation->execute($server); } /** * Drop all indexes in the collection. * - * @see http://docs.mongodb.org/manual/reference/command/dropIndexes/ - * @see http://docs.mongodb.org/manual/reference/method/db.collection.dropIndexes/ - * @return Cursor + * @return object Command result document */ public function dropIndexes() { - $command = new Command(array('dropIndexes' => $this->collname, 'index' => '*')); - $readPreference = new ReadPreference(ReadPreference::RP_PRIMARY); + $operation = new DropIndexes($this->dbname, $this->collname, '*'); + $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); - return $this->manager->executeCommand($this->dbname, $command, $readPreference); + return $operation->execute($server); } /** @@ -540,103 +445,63 @@ public function findOne(array $filter = array(), array $options = array()) /** * Finds a single document and deletes it, returning the original. * - * @see http://docs.mongodb.org/manual/reference/command/findAndModify/ - * @see Collection::getFindOneAndDelete() for supported $options + * The document to return may be null. * - * @param array $filter The $filter criteria to search for - * @param array $options Additional options - * @return array The original document + * @see FindOneAndDelete::__construct() for supported options + * @param array|object $filter Query by which to filter documents + * @param array $options Command options + * @return object|null */ - public function findOneAndDelete(array $filter, array $options = array()) + public function findOneAndDelete($filter, array $options = array()) { - $options = array_merge($this->getFindOneAndDeleteOptions(), $options); - $options = $this->_massageFindAndModifyOptions($options); - $cmd = array( - "findandmodify" => $this->collname, - "query" => $filter, - ) + $options; - - $doc = current($this->_runCommand($this->dbname, $cmd)->toArray()); - if ($doc["ok"]) { - return is_object($doc["value"]) ? (array) $doc["value"] : $doc["value"]; - } + $operation = new FindOneAndDelete($this->dbname, $this->collname, $filter, $options); + $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); - throw $this->_generateCommandException($doc); + return $operation->execute($server); } /** - * Finds a single document and replaces it, returning either the original or the replaced document - * By default, returns the original document. - * To return the new document set: - * $options = array("returnDocument" => Collection::FIND_ONE_AND_RETURN_AFTER); + * Finds a single document and replaces it, returning either the original or + * the replaced document. * - * @see http://docs.mongodb.org/manual/reference/command/findAndModify/ - * @see Collection::getFindOneAndReplace() for supported $options + * The document to return may be null. By default, the original document is + * returned. Specify FindOneAndReplace::RETURN_DOCUMENT_AFTER for the + * "returnDocument" option to return the updated document. * - * @param array $filter The $filter criteria to search for - * @param array $replacement The document to replace with - * @param array $options Additional options - * @return array + * @see FindOneAndReplace::__construct() for supported options + * @param array|object $filter Query by which to filter documents + * @param array|object $replacement Replacement document + * @param array $options Command options + * @return object|null */ - public function findOneAndReplace(array $filter, array $replacement, array $options = array()) + public function findOneAndReplace($filter, $replacement, array $options = array()) { - $firstKey = key($replacement); - if (isset($firstKey[0]) && $firstKey[0] == '$') { - throw new InvalidArgumentException("First key in \$replacement must NOT be a \$operator"); - } - - $options = array_merge($this->getFindOneAndReplaceOptions(), $options); - $options = $this->_massageFindAndModifyOptions($options, $replacement); - - $cmd = array( - "findandmodify" => $this->collname, - "query" => $filter, - ) + $options; + $operation = new FindOneAndReplace($this->dbname, $this->collname, $filter, $replacement, $options); + $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); - $doc = current($this->_runCommand($this->dbname, $cmd)->toArray()); - if ($doc["ok"]) { - return $this->_massageFindAndModifyResult($doc, $options); - } - - throw $this->_generateCommandException($doc); + return $operation->execute($server); } /** - * Finds a single document and updates it, returning either the original or the updated document - * By default, returns the original document. - * To return the new document set: - * $options = array("returnDocument" => Collection::FIND_ONE_AND_RETURN_AFTER); + * Finds a single document and updates it, returning either the original or + * the updated document. * + * The document to return may be null. By default, the original document is + * returned. Specify FindOneAndUpdate::RETURN_DOCUMENT_AFTER for the + * "returnDocument" option to return the updated document. * - * @see http://docs.mongodb.org/manual/reference/command/findAndModify/ - * @see Collection::getFindOneAndUpdate() for supported $options - * - * @param array $filter The $filter criteria to search for - * @param array $update An array of update operators to apply to the document - * @param array $options Additional options - * @return array + * @see FindOneAndReplace::__construct() for supported options + * @param array|object $filter Query by which to filter documents + * @param array|object $update Update to apply to the matched document + * @param array $options Command options + * @return object|null */ - public function findOneAndUpdate(array $filter, array $update, array $options = array()) + public function findOneAndUpdate($filter, $update, array $options = array()) { - $firstKey = key($update); - if (!isset($firstKey[0]) || $firstKey[0] != '$') { - throw new InvalidArgumentException("First key in \$update must be a \$operator"); - } - - $options = array_merge($this->getFindOneAndUpdateOptions(), $options); - $options = $this->_massageFindAndModifyOptions($options, $update); - - $cmd = array( - "findandmodify" => $this->collname, - "query" => $filter, - ) + $options; - - $doc = current($this->_runCommand($this->dbname, $cmd)->toArray()); - if ($doc["ok"]) { - return $this->_massageFindAndModifyResult($doc, $options); - } + $operation = new FindOneAndUpdate($this->dbname, $this->collname, $filter, $update, $options); + $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); - throw $this->_generateCommandException($doc); + return $operation->execute($server); } /** @@ -661,44 +526,6 @@ public function getCollectionName() return $this->collname; } - /** - * Retrieves all count options with their default values. - * - * @return array of Collection::count() options - */ - public function getCountOptions() - { - return array( - /** - * The index to use. - * - * @see http://docs.mongodb.org/manual/reference/command/count/ - */ - "hint" => "", // string or document - - /** - * The maximum number of documents to count. - * - * @see http://docs.mongodb.org/manual/reference/command/count/ - */ - "limit" => 0, - - /** - * The maximum amount of time to allow the query to run. - * - * @see http://docs.mongodb.org/manual/reference/command/count/ - */ - "maxTimeMS" => 0, - - /** - * The number of documents to skip before returning the documents. - * - * @see http://docs.mongodb.org/manual/reference/command/count/ - */ - "skip" => 0, - ); - } - /** * Return the database name. * @@ -709,150 +536,6 @@ public function getDatabaseName() return $this->dbname; } - /** - * Retrieves all distinct options with their default values. - * - * @return array of Collection::distinct() options - */ - public function getDistinctOptions() - { - return array( - /** - * The maximum amount of time to allow the query to run. The default is infinite. - * - * @see http://docs.mongodb.org/manual/reference/command/distinct/ - */ - "maxTimeMS" => 0, - ); - } - - /** - * Retrieves all findOneDelete options with their default values. - * - * @return array of Collection::findOneAndDelete() options - */ - public function getFindOneAndDeleteOptions() - { - return array( - - /** - * The maximum amount of time to allow the query to run. - * - * @see http://docs.mongodb.org/manual/reference/command/findAndModify/ - */ - "maxTimeMS" => 0, - - /** - * Limits the fields to return for all matching documents. - * - * @see http://docs.mongodb.org/manual/tutorial/project-fields-from-query-results - */ - "projection" => array(), - - /** - * Determines which document the operation modifies if the query selects multiple documents. - * - * @see http://docs.mongodb.org/manual/reference/command/findAndModify/ - */ - "sort" => array(), - ); - } - - /** - * Retrieves all findOneAndReplace options with their default values. - * - * @return array of Collection::findOneAndReplace() options - */ - public function getFindOneAndReplaceOptions() - { - return array( - - /** - * The maximum amount of time to allow the query to run. - * - * @see http://docs.mongodb.org/manual/reference/command/findAndModify/ - */ - "maxTimeMS" => 0, - - /** - * Limits the fields to return for all matching documents. - * - * @see http://docs.mongodb.org/manual/tutorial/project-fields-from-query-results - */ - "projection" => array(), - - /** - * When ReturnDocument.After, returns the replaced or inserted document rather than the original. - * Defaults to ReturnDocument.Before. - * - * @see http://docs.mongodb.org/manual/reference/command/findAndModify/ - */ - "returnDocument" => self::FIND_ONE_AND_RETURN_BEFORE, - - /** - * Determines which document the operation modifies if the query selects multiple documents. - * - * @see http://docs.mongodb.org/manual/reference/command/findAndModify/ - */ - "sort" => array(), - - /** - * When true, findAndModify creates a new document if no document matches the query. The - * default is false. - * - * @see http://docs.mongodb.org/manual/reference/command/findAndModify/ - */ - "upsert" => false, - ); - } - - /** - * Retrieves all findOneAndUpdate options with their default values. - * - * @return array of Collection::findOneAndUpdate() options - */ - public function getFindOneAndUpdateOptions() - { - return array( - - /** - * The maximum amount of time to allow the query to run. - * - * @see http://docs.mongodb.org/manual/reference/command/findAndModify/ - */ - "maxTimeMS" => 0, - - /** - * Limits the fields to return for all matching documents. - * - * @see http://docs.mongodb.org/manual/tutorial/project-fields-from-query-results - */ - "projection" => array(), - - /** - * When ReturnDocument.After, returns the updated or inserted document rather than the original. - * Defaults to ReturnDocument.Before. - * - * @see http://docs.mongodb.org/manual/reference/command/findAndModify/ - */ - "returnDocument" => self::FIND_ONE_AND_RETURN_BEFORE, - - /** - * Determines which document the operation modifies if the query selects multiple documents. - * - * @see http://docs.mongodb.org/manual/reference/command/findAndModify/ - */ - "sort" => array(), - - /** - * When true, creates a new document if no document matches the query. The default is false. - * - * @see http://docs.mongodb.org/manual/reference/command/findAndModify/ - */ - "upsert" => false, - ); - } - /** * Retrieves all find options with their default values. * @@ -1035,18 +718,15 @@ public function insertOne($document) /** * Returns information for all indexes for the collection. * - * @see http://docs.mongodb.org/manual/reference/command/listIndexes/ - * @see http://docs.mongodb.org/manual/reference/method/db.collection.getIndexes/ + * @see ListIndexes::__construct() for supported options * @return IndexInfoIterator */ - public function listIndexes() + public function listIndexes(array $options = array()) { - $readPreference = new ReadPreference(ReadPreference::RP_PRIMARY); - $server = $this->manager->selectServer($readPreference); + $operation = new ListIndexes($this->dbname, $this->collname, $options); + $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); - return (FeatureDetection::isSupported($server, FeatureDetection::API_LISTINDEXES_CMD)) - ? $this->listIndexesCommand($server) - : $this->listIndexesLegacy($server); + return $operation->execute($server); } /** @@ -1155,85 +835,6 @@ final protected function _delete($filter, $limit = 1) return $this->manager->executeBulkWrite($this->ns, $bulk, $this->wc); } - /** - * Internal helper for throwing an exception with error message - * @internal - */ - final protected function _generateCommandException($doc) - { - if ($doc["errmsg"]) { - return new RuntimeException($doc["errmsg"]); - } - var_dump($doc); - return new RuntimeException("FIXME: Unknown error"); - } - - /** - * Internal helper for massaging aggregate options - * @internal - */ - protected function _massageAggregateOptions($options) - { - if ( ! empty($options["useCursor"])) { - $options["cursor"] = isset($options["batchSize"]) - ? array("batchSize" => (integer) $options["batchSize"]) - : new stdClass; - } - unset($options["useCursor"], $options["batchSize"]); - - return $options; - } - - /** - * Internal helper for massaging findandmodify options - * @internal - */ - final protected function _massageFindAndModifyOptions($options, $update = array()) - { - $ret = array( - "sort" => $options["sort"], - "new" => isset($options["returnDocument"]) ? $options["returnDocument"] == self::FIND_ONE_AND_RETURN_AFTER : false, - "fields" => $options["projection"], - "upsert" => isset($options["upsert"]) ? $options["upsert"] : false, - ); - if ($update) { - $ret["update"] = $update; - } else { - $ret["remove"] = true; - } - return $ret; - } - - /** - * Internal helper for massaging the findAndModify result. - * - * @internal - * @param array $result - * @param array $options - * @return array|null - */ - final protected function _massageFindAndModifyResult(array $result, array $options) - { - if ($result['value'] === null) { - return null; - } - - /* Prior to 3.0, findAndModify returns an empty document instead of null - * when an upsert is performed and the pre-modified document was - * requested. - */ - if ($options['upsert'] && ! $options['new'] && - isset($result['lastErrorObject']->updatedExisting) && - ! $result['lastErrorObject']->updatedExisting) { - - return null; - } - - return is_object($result["value"]) - ? (array) $result['value'] - : $result['value']; - } - /** * Constructs the Query Wire Protocol field 'flags' based on $options * provided to other helpers @@ -1254,17 +855,6 @@ final protected function _opQueryFlags($options) return $flags; } - /** - * Internal helper for running a command - * @internal - */ - final protected function _runCommand($dbname, array $cmd, ReadPreference $rp = null) - { - //var_dump(\BSON\toJSON(\BSON\fromArray($cmd))); - $command = new Command($cmd); - return $this->manager->executeCommand($dbname, $command, $rp); - } - /** * Internal helper for replacing/updating one/many documents * @internal @@ -1277,77 +867,4 @@ protected function _update($filter, $update, $options) $bulk->update($filter, $update, $options); return $this->manager->executeBulkWrite($this->ns, $bulk, $this->wc); } - - /** - * Create one or more indexes for the collection using the createIndexes - * command. - * - * @param Server $server - * @param IndexInput[] $indexes - * @return string[] The names of the created indexes - */ - private function createIndexesCommand(Server $server, array $indexes) - { - $command = new Command(array( - 'createIndexes' => $this->collname, - 'indexes' => $indexes, - )); - $server->executeCommand($this->dbname, $command); - - return array_map(function(IndexInput $index) { return (string) $index; }, $indexes); - } - - /** - * Create one or more indexes for the collection by inserting into the - * "system.indexes" collection (MongoDB <2.6). - * - * @param Server $server - * @param IndexInput[] $indexes - * @return string[] The names of the created indexes - */ - private function createIndexesLegacy(Server $server, array $indexes) - { - $bulk = new BulkWrite(true); - - foreach ($indexes as $index) { - $bulk->insert($index); - } - - $server->executeBulkWrite($this->dbname . '.system.indexes', $bulk); - - return array_map(function(IndexInput $index) { return (string) $index; }, $indexes); - } - - /** - * Returns information for all indexes for this collection using the - * listIndexes command. - * - * @see http://docs.mongodb.org/manual/reference/command/listIndexes/ - * @param Server $server - * @return IndexInfoIteratorIterator - */ - private function listIndexesCommand(Server $server) - { - $command = new Command(array('listIndexes' => $this->collname)); - $cursor = $server->executeCommand($this->dbname, $command); - $cursor->setTypeMap(array('document' => 'array')); - - return new IndexInfoIteratorIterator($cursor); - } - - /** - * Returns information for all indexes for this collection by querying the - * "system.indexes" collection (MongoDB <2.8). - * - * @param Server $server - * @return IndexInfoIteratorIterator - */ - private function listIndexesLegacy(Server $server) - { - $query = new Query(array('ns' => $this->ns)); - $cursor = $server->executeQuery($this->dbname . '.system.indexes', $query); - $cursor->setTypeMap(array('document' => 'array')); - - return new IndexInfoIteratorIterator($cursor); - } } diff --git a/src/Database.php b/src/Database.php index 8ca8d11c4..7724d9777 100644 --- a/src/Database.php +++ b/src/Database.php @@ -12,8 +12,10 @@ use MongoDB\Driver\WriteConcern; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Model\CollectionInfoIterator; -use MongoDB\Model\CollectionInfoCommandIterator; -use MongoDB\Model\CollectionInfoLegacyIterator; +use MongoDB\Operation\CreateCollection; +use MongoDB\Operation\DropCollection; +use MongoDB\Operation\DropDatabase; +use MongoDB\Operation\ListCollections; class Database { @@ -54,49 +56,44 @@ public function __toString() /** * Create a new collection explicitly. * - * @see http://docs.mongodb.org/manual/reference/command/create/ - * @see http://docs.mongodb.org/manual/reference/method/db.createCollection/ + * @see CreateCollection::__construct() for supported options * @param string $collectionName * @param array $options - * @return Cursor + * @return object Command result document */ public function createCollection($collectionName, array $options = array()) { - $collectionName = (string) $collectionName; - $command = new Command(array('create' => $collectionName) + $options); - $readPreference = new ReadPreference(ReadPreference::RP_PRIMARY); + $operation = new CreateCollection($this->databaseName, $collectionName, $options); + $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); - return $this->manager->executeCommand($this->databaseName, $command, $readPreference); + return $operation->execute($server); } /** * Drop this database. * - * @see http://docs.mongodb.org/manual/reference/command/dropDatabase/ - * @return Cursor + * @return object Command result document */ public function drop() { - $command = new Command(array('dropDatabase' => 1)); - $readPreference = new ReadPreference(ReadPreference::RP_PRIMARY); + $operation = new DropDatabase($this->databaseName); + $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); - return $this->manager->executeCommand($this->databaseName, $command, $readPreference); + return $operation->execute($server); } /** * Drop a collection within this database. * - * @see http://docs.mongodb.org/manual/reference/command/drop/ * @param string $collectionName - * @return Cursor + * @return object Command result document */ public function dropCollection($collectionName) { - $collectionName = (string) $collectionName; - $command = new Command(array('drop' => $collectionName)); - $readPreference = new ReadPreference(ReadPreference::RP_PRIMARY); + $operation = new DropCollection($this->databaseName, $collectionName); + $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); - return $this->manager->executeCommand($this->databaseName, $command, $readPreference); + return $operation->execute($server); } /** @@ -112,18 +109,16 @@ public function getDatabaseName() /** * Returns information for all collections in this database. * - * @see http://docs.mongodb.org/manual/reference/command/listCollections/ + * @see ListCollections::__construct() for supported options * @param array $options * @return CollectionInfoIterator */ public function listCollections(array $options = array()) { - $readPreference = new ReadPreference(ReadPreference::RP_PRIMARY); - $server = $this->manager->selectServer($readPreference); + $operation = new ListCollections($this->databaseName, $options); + $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); - return (FeatureDetection::isSupported($server, FeatureDetection::API_LISTCOLLECTIONS_CMD)) - ? $this->listCollectionsCommand($server, $options) - : $this->listCollectionsLegacy($server, $options); + return $operation->execute($server); } /** @@ -145,58 +140,4 @@ 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 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); - $cursor->setTypeMap(array('document' => 'array')); - - 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); - $cursor->setTypeMap(array('document' => 'array')); - - return new CollectionInfoLegacyIterator($cursor); - } } diff --git a/src/Exception/InvalidArgumentTypeException.php b/src/Exception/InvalidArgumentTypeException.php new file mode 100644 index 000000000..9bc580c10 --- /dev/null +++ b/src/Exception/InvalidArgumentTypeException.php @@ -0,0 +1,11 @@ +getInfo(); - $maxWireVersion = isset($info['maxWireVersion']) ? (integer) $info['maxWireVersion'] : 0; - $minWireVersion = isset($info['minWireVersion']) ? (integer) $info['minWireVersion'] : 0; - - return ($minWireVersion <= $feature && $maxWireVersion >= $feature); - } -} diff --git a/src/Model/IndexInput.php b/src/Model/IndexInput.php index dc6338f7e..db4288992 100644 --- a/src/Model/IndexInput.php +++ b/src/Model/IndexInput.php @@ -50,7 +50,7 @@ public function __construct(array $index) } if ( ! isset($index['name'])) { - $index['name'] = $this->generateName($index['key']); + $index['name'] = \MongoDB\generate_index_name($index['key']); } if ( ! is_string($index['name'])) { @@ -80,22 +80,4 @@ public function bsonSerialize() { return $this->index; } - - /** - * Generates an index name from its key specification. - * - * @param array|object $key Document containing fields mapped to values, - * which denote order or an index type - * @return string - */ - private function generateName($key) - { - $name = ''; - - foreach ($key as $field => $type) { - $name .= ($name != '' ? '_' : '') . $field . '_' . $type; - } - - return $name; - } } diff --git a/src/Operation/Aggregate.php b/src/Operation/Aggregate.php new file mode 100644 index 000000000..37621d34c --- /dev/null +++ b/src/Operation/Aggregate.php @@ -0,0 +1,174 @@ += 2.6, this option allows users to turn off cursors if + * necessary to aid in mongod/mongos upgrades. + * + * @param string $databaseName Database name + * @param string $collectionName Collection name + * @param array $pipeline List of pipeline operations + * @param array $options Command options + * @throws InvalidArgumentException + */ + public function __construct($databaseName, $collectionName, array $pipeline, array $options = array()) + { + $options += array( + 'allowDiskUse' => false, + 'useCursor' => true, + ); + + if ( ! is_bool($options['allowDiskUse'])) { + throw new InvalidArgumentTypeException('"allowDiskUse" option', $options['allowDiskUse'], 'boolean'); + } + + if (isset($options['batchSize']) && ! is_integer($options['batchSize'])) { + throw new InvalidArgumentTypeException('"batchSize" option', $options['batchSize'], 'integer'); + } + + if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) { + throw new InvalidArgumentTypeException('"maxTimeMS" option', $options['maxTimeMS'], 'integer'); + } + + if ( ! is_bool($options['useCursor'])) { + throw new InvalidArgumentTypeException('"useCursor" option', $options['useCursor'], 'boolean'); + } + + if (isset($options['batchSize']) && ! $options['useCursor']) { + throw new InvalidArgumentException('"batchSize" option should not be used if "useCursor" is false'); + } + + $expectedIndex = 0; + + foreach ($pipeline as $i => $op) { + if ($i !== $expectedIndex) { + throw new InvalidArgumentException(sprintf('$pipeline is not a list (unexpected index: "%s")', $i)); + } + + if ( ! is_array($op) && ! is_object($op)) { + throw new InvalidArgumentTypeException(sprintf('$pipeline[%d]', $i), $op, 'array or object'); + } + + $expectedIndex += 1; + } + + $this->databaseName = (string) $databaseName; + $this->collectionName = (string) $collectionName; + $this->pipeline = $pipeline; + $this->options = $options; + } + + /** + * Execute the operation. + * + * @see Executable::execute() + * @param Server $server + * @return Traversable + */ + public function execute(Server $server) + { + $isCursorSupported = \MongoDB\server_supports_feature($server, self::$wireVersionForCursor); + + $command = $this->createCommand($server, $isCursorSupported); + $cursor = $server->executeCommand($this->databaseName, $command); + + if ($isCursorSupported && $this->options['useCursor']) { + return $cursor; + } + + $result = current($cursor->toArray()); + + if (empty($result['ok'])) { + throw new RuntimeException(isset($result['errmsg']) ? $result['errmsg'] : 'Unknown error'); + } + + if ( ! isset($result['result']) || ! is_array($result['result'])) { + throw new UnexpectedValueException('aggregate command did not return a "result" array'); + } + + return new ArrayIterator(array_map( + function (stdClass $document) { return (array) $document; }, + $result['result'] + )); + } + + /** + * Create the aggregate command. + * + * @param Server $server + * @param boolean $isCursorSupported + * @return Command + */ + private function createCommand(Server $server, $isCursorSupported) + { + $cmd = array( + 'aggregate' => $this->collectionName, + 'pipeline' => $this->pipeline, + ); + + // Servers < 2.6 do not support any command options + if ( ! $isCursorSupported) { + return new Command($cmd); + } + + $cmd['allowDiskUse'] = $this->options['allowDiskUse']; + + if (isset($this->options['maxTimeMS'])) { + $cmd['maxTimeMS'] = $this->options['maxTimeMS']; + } + + if ($this->options['useCursor']) { + $cmd['cursor'] = isset($this->options["batchSize"]) + ? array('batchSize' => $this->options["batchSize"]) + : new stdClass; + } + + return new Command($cmd); + } +} diff --git a/src/Operation/Count.php b/src/Operation/Count.php new file mode 100644 index 000000000..137fd47d9 --- /dev/null +++ b/src/Operation/Count.php @@ -0,0 +1,125 @@ +databaseName = (string) $databaseName; + $this->collectionName = (string) $collectionName; + $this->filter = $filter; + $this->options = $options; + } + + /** + * Execute the operation. + * + * @see Executable::execute() + * @param Server $server + * @return integer + */ + public function execute(Server $server) + { + $cursor = $server->executeCommand($this->databaseName, $this->createCommand()); + $result = current($cursor->toArray()); + + if (empty($result['ok'])) { + throw new RuntimeException(isset($result['errmsg']) ? $result['errmsg'] : 'Unknown error'); + } + + // Older server versions may return a float + if ( ! isset($result['n']) || ! (is_integer($result['n']) || is_float($result['n']))) { + throw new UnexpectedValueException('count command did not return an "n" value'); + } + + return (integer) $result['n']; + } + + /** + * Create the count command. + * + * @return Command + */ + private function createCommand() + { + $cmd = array( + 'count' => $this->collectionName, + ); + + if ( ! empty($this->filter)) { + $cmd['query'] = (object) $this->filter; + } + + foreach (array('hint', 'limit', 'maxTimeMS', 'skip') as $option) { + if (isset($this->options[$option])) { + $cmd[$option] = $this->options[$option]; + } + } + + return new Command($cmd); + } +} diff --git a/src/Operation/CreateCollection.php b/src/Operation/CreateCollection.php new file mode 100644 index 000000000..8b71d388c --- /dev/null +++ b/src/Operation/CreateCollection.php @@ -0,0 +1,139 @@ +databaseName = (string) $databaseName; + $this->collectionName = (string) $collectionName; + $this->options = $options; + } + + /** + * Execute the operation. + * + * For servers < 2.6, this will actually perform an insert operation on the + * database's "system.indexes" collection. + * + * @see Executable::execute() + * @param Server $server + * @return object Command result document + */ + public function execute(Server $server) + { + $cursor = $server->executeCommand($this->databaseName, $this->createCommand()); + $result = current($cursor->toArray()); + + if (empty($result['ok'])) { + throw new RuntimeException(isset($result['errmsg']) ? $result['errmsg'] : 'Unknown error'); + } + + return $result; + } + + /** + * Create the create command. + * + * @return Command + */ + private function createCommand() + { + $cmd = array('create' => $this->collectionName); + + foreach (array('autoIndexId', 'capped', 'flags', 'max', 'maxTimeMS', 'size') as $option) { + if (isset($this->options[$option])) { + $cmd[$option] = $this->options[$option]; + } + } + + if ( ! empty($this->options['storageEngine'])) { + $cmd['storageEngine'] = (object) $this->options['storageEngine']; + } + + return new Command($cmd); + } +} diff --git a/src/Operation/CreateIndexes.php b/src/Operation/CreateIndexes.php new file mode 100644 index 000000000..858d31e8b --- /dev/null +++ b/src/Operation/CreateIndexes.php @@ -0,0 +1,118 @@ +indexes[] = new IndexInput($index); + } + + $this->databaseName = (string) $databaseName; + $this->collectionName = (string) $collectionName; + } + + /** + * Execute the operation. + * + * For servers < 2.6, this will actually perform an insert operation on the + * database's "system.indexes" collection. + * + * @see Executable::execute() + * @param Server $server + * @return string[] The names of the created indexes + */ + public function execute(Server $server) + { + if (\MongoDB\server_supports_feature($server, self::$wireVersionForCommand)) { + $this->executeCommand($server); + } else { + $this->executeLegacy($server); + } + + return array_map(function(IndexInput $index) { return (string) $index; }, $this->indexes); + } + + /** + * Create one or more indexes for the collection using the createIndexes + * command. + * + * @param Server $server + */ + private function executeCommand(Server $server) + { + $command = new Command(array( + 'createIndexes' => $this->collectionName, + 'indexes' => $this->indexes, + )); + + $cursor = $server->executeCommand($this->databaseName, $command); + $result = current($cursor->toArray()); + + if (empty($result['ok'])) { + throw new RuntimeException(isset($result['errmsg']) ? $result['errmsg'] : 'Unknown error'); + } + } + + /** + * Create one or more indexes for the collection by inserting into the + * "system.indexes" collection (MongoDB <2.6). + * + * @param Server $server + * @param IndexInput[] $indexes + */ + private function executeLegacy(Server $server) + { + $bulk = new BulkWrite(true); + + foreach ($this->indexes as $index) { + $bulk->insert($index); + } + + $server->executeBulkWrite($this->databaseName . '.system.indexes', $bulk); + } +} diff --git a/src/Operation/Distinct.php b/src/Operation/Distinct.php new file mode 100644 index 000000000..576627054 --- /dev/null +++ b/src/Operation/Distinct.php @@ -0,0 +1,100 @@ +databaseName = (string) $databaseName; + $this->collectionName = (string) $collectionName; + $this->fieldName = (string) $fieldName; + $this->filter = $filter; + $this->options = $options; + } + + /** + * Execute the operation. + * + * @see Executable::execute() + * @param Server $server + * @return mixed[] + */ + public function execute(Server $server) + { + $cursor = $server->executeCommand($this->databaseName, $this->createCommand()); + $result = current($cursor->toArray()); + + if (empty($result['ok'])) { + throw new RuntimeException(isset($result['errmsg']) ? $result['errmsg'] : 'Unknown error'); + } + + if ( ! isset($result['values']) || ! is_array($result['values'])) { + throw new UnexpectedValueException('distinct command did not return a "values" array'); + } + + return $result['values']; + } + + /** + * Create the distinct command. + * + * @return Command + */ + private function createCommand() + { + $cmd = array( + 'distinct' => $this->collectionName, + 'key' => $this->fieldName, + ); + + if ( ! empty($this->filter)) { + $cmd['query'] = (object) $this->filter; + } + + if (isset($this->options['maxTimeMS'])) { + $cmd[$option] = $this->options['maxTimeMS']; + } + + return new Command($cmd); + } +} diff --git a/src/Operation/DropCollection.php b/src/Operation/DropCollection.php new file mode 100644 index 000000000..437ccfcb1 --- /dev/null +++ b/src/Operation/DropCollection.php @@ -0,0 +1,52 @@ +databaseName = (string) $databaseName; + $this->collectionName = (string) $collectionName; + } + + /** + * Execute the operation. + * + * @see Executable::execute() + * @param Server $server + * @return object Command result document + */ + public function execute(Server $server) + { + $cursor = $server->executeCommand($this->databaseName, new Command(array('drop' => $this->collectionName))); + $result = current($cursor->toArray()); + + if (empty($result['ok'])) { + throw new RuntimeException(isset($result['errmsg']) ? $result['errmsg'] : 'Unknown error'); + } + + return $result; + } +} diff --git a/src/Operation/DropDatabase.php b/src/Operation/DropDatabase.php new file mode 100644 index 000000000..4c4313d3e --- /dev/null +++ b/src/Operation/DropDatabase.php @@ -0,0 +1,50 @@ +databaseName = (string) $databaseName; + } + + /** + * Execute the operation. + * + * @see Executable::execute() + * @param Server $server + * @return object Command result document + */ + public function execute(Server $server) + { + $cursor = $server->executeCommand($this->databaseName, new Command(array('dropDatabase' => 1))); + $result = current($cursor->toArray()); + + if (empty($result['ok'])) { + throw new RuntimeException(isset($result['errmsg']) ? $result['errmsg'] : 'Unknown error'); + } + + return $result; + } +} diff --git a/src/Operation/DropIndexes.php b/src/Operation/DropIndexes.php new file mode 100644 index 000000000..78ab4de6e --- /dev/null +++ b/src/Operation/DropIndexes.php @@ -0,0 +1,67 @@ +databaseName = (string) $databaseName; + $this->collectionName = (string) $collectionName; + $this->indexName = $indexName; + } + + /** + * Execute the operation. + * + * @see Executable::execute() + * @param Server $server + * @return object Command result document + */ + public function execute(Server $server) + { + $cmd = array( + 'dropIndexes' => $this->collectionName, + 'index' => $this->indexName, + ); + + $cursor = $server->executeCommand($this->databaseName, new Command($cmd)); + $result = current($cursor->toArray()); + + if (empty($result['ok'])) { + throw new RuntimeException(isset($result['errmsg']) ? $result['errmsg'] : 'Unknown error'); + } + + return $result; + } +} diff --git a/src/Operation/Executable.php b/src/Operation/Executable.php new file mode 100644 index 000000000..4ef501d4e --- /dev/null +++ b/src/Operation/Executable.php @@ -0,0 +1,21 @@ + false, + 'remove' => false, + 'upsert' => false, + ); + + if (isset($options['fields']) && ! is_array($options['fields']) && ! is_object($options['fields'])) { + throw new InvalidArgumentTypeException('"fields" option', $options['fields'], 'array or object'); + } + + if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) { + throw new InvalidArgumentTypeException('"maxTimeMS" option', $options['maxTimeMS'], 'integer'); + } + + if ( ! is_bool($options['new'])) { + throw new InvalidArgumentTypeException('"new" option', $options['new'], 'boolean'); + } + + if (isset($options['query']) && ! is_array($options['query']) && ! is_object($options['query'])) { + throw new InvalidArgumentTypeException('"query" option', $options['query'], 'array or object'); + } + + if ( ! is_bool($options['remove'])) { + throw new InvalidArgumentTypeException('"remove" option', $options['remove'], 'boolean'); + } + + if (isset($options['sort']) && ! is_array($options['sort']) && ! is_object($options['sort'])) { + throw new InvalidArgumentTypeException('"sort" option', $options['sort'], 'array or object'); + } + + if (isset($options['update']) && ! is_array($options['update']) && ! is_object($options['update'])) { + throw new InvalidArgumentTypeException('"update" option', $options['update'], 'array or object'); + } + + if ( ! is_bool($options['upsert'])) { + throw new InvalidArgumentTypeException('"upsert" option', $options['upsert'], 'boolean'); + } + + if ( ! (isset($options['update']) xor $options['remove'])) { + throw new InvalidArgumentException('The "remove" option must be true or an "update" document must be specified, but not both'); + } + + $this->databaseName = (string) $databaseName; + $this->collectionName = (string) $collectionName; + $this->options = $options; + } + + /** + * Execute the operation. + * + * @see Executable::execute() + * @param Server $server + * @return object|null + */ + public function execute(Server $server) + { + $cursor = $server->executeCommand($this->databaseName, $this->createCommand()); + $result = current($cursor->toArray()); + + if (empty($result['ok'])) { + throw new RuntimeException(isset($result['errmsg']) ? $result['errmsg'] : 'Unknown error'); + } + + if ( ! isset($result['value'])) { + return null; + } + + /* Prior to 3.0, findAndModify returns an empty document instead of null + * when an upsert is performed and the pre-modified document was + * requested. + */ + if ($this->options['upsert'] && ! $this->options['new'] && + isset($result['lastErrorObject']->updatedExisting) && + ! $result['lastErrorObject']->updatedExisting) { + + return null; + } + + if ( ! is_object($result['value'])) { + throw new UnexpectedValueException('findAndModify command did not return a "value" document'); + } + + return $result['value']; + } + + /** + * Create the findAndModify command. + * + * @return Command + */ + private function createCommand() + { + $cmd = array( + 'findAndModify' => $this->collectionName, + ); + + if ($this->options['remove']) { + $cmd['remove'] = true; + } else { + $cmd['new'] = $this->options['new']; + $cmd['upsert'] = $this->options['upsert']; + } + + foreach (array('fields', 'query', 'sort', 'update') as $option) { + if (isset($this->options[$option])) { + $cmd[$option] = (object) $this->options[$option]; + } + } + + if (isset($this->options['maxTimeMS'])) { + $cmd['maxTimeMS'] = $this->options['maxTimeMS']; + } + + return new Command($cmd); + } +} diff --git a/src/Operation/FindOneAndDelete.php b/src/Operation/FindOneAndDelete.php new file mode 100644 index 000000000..4ff1cf88a --- /dev/null +++ b/src/Operation/FindOneAndDelete.php @@ -0,0 +1,83 @@ +findAndModify = new FindAndModify( + $databaseName, + $collectionName, + array( + 'fields' => isset($options['projection']) ? $options['projection'] : null, + 'maxTimeMS' => isset($options['maxTimeMS']) ? $options['maxTimeMS'] : null, + 'query' => $filter, + 'remove' => true, + 'sort' => isset($options['sort']) ? $options['sort'] : null, + ) + ); + } + + /** + * Execute the operation. + * + * @see Executable::execute() + * @param Server $server + * @return object|null + */ + public function execute(Server $server) + { + return $this->findAndModify->execute($server); + } +} diff --git a/src/Operation/FindOneAndReplace.php b/src/Operation/FindOneAndReplace.php new file mode 100644 index 000000000..dbf834360 --- /dev/null +++ b/src/Operation/FindOneAndReplace.php @@ -0,0 +1,122 @@ + self::RETURN_DOCUMENT_BEFORE, + 'upsert' => false, + ); + + if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) { + throw new InvalidArgumentTypeException('"maxTimeMS" option', $options['maxTimeMS'], 'integer'); + } + + if (isset($options['projection']) && ! is_array($options['projection']) && ! is_object($options['projection'])) { + throw new InvalidArgumentTypeException('"projection" option', $options['projection'], 'array or object'); + } + + if ( ! is_integer($options['returnDocument'])) { + throw new InvalidArgumentTypeException('"returnDocument" option', $options['returnDocument'], 'integer'); + } + + if ($options['returnDocument'] !== self::RETURN_DOCUMENT_AFTER && + $options['returnDocument'] !== self::RETURN_DOCUMENT_BEFORE) { + throw new InvalidArgumentException('Invalid value for "returnDocument" option: ' . $options['returnDocument']); + } + + if (isset($options['sort']) && ! is_array($options['sort']) && ! is_object($options['sort'])) { + throw new InvalidArgumentTypeException('"sort" option', $options['sort'], 'array or object'); + } + + if ( ! is_bool($options['upsert'])) { + throw new InvalidArgumentTypeException('"upsert" option', $options['upsert'], 'boolean'); + } + + $this->findAndModify = new FindAndModify( + $databaseName, + $collectionName, + array( + 'fields' => isset($options['projection']) ? $options['projection'] : null, + 'maxTimeMS' => isset($options['maxTimeMS']) ? $options['maxTimeMS'] : null, + 'new' => $options['returnDocument'] === self::RETURN_DOCUMENT_AFTER, + 'query' => $filter, + 'sort' => isset($options['sort']) ? $options['sort'] : null, + 'update' => $replacement, + 'upsert' => $options['upsert'], + ) + ); + } + + /** + * Execute the operation. + * + * @see Executable::execute() + * @param Server $server + * @return object|null + */ + public function execute(Server $server) + { + return $this->findAndModify->execute($server); + } +} diff --git a/src/Operation/FindOneAndUpdate.php b/src/Operation/FindOneAndUpdate.php new file mode 100644 index 000000000..7bcc05d7b --- /dev/null +++ b/src/Operation/FindOneAndUpdate.php @@ -0,0 +1,122 @@ + self::RETURN_DOCUMENT_BEFORE, + 'upsert' => false, + ); + + if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) { + throw new InvalidArgumentTypeException('"maxTimeMS" option', $options['maxTimeMS'], 'integer'); + } + + if (isset($options['projection']) && ! is_array($options['projection']) && ! is_object($options['projection'])) { + throw new InvalidArgumentTypeException('"projection" option', $options['projection'], 'array or object'); + } + + if ( ! is_integer($options['returnDocument'])) { + throw new InvalidArgumentTypeException('"returnDocument" option', $options['returnDocument'], 'integer'); + } + + if ($options['returnDocument'] !== self::RETURN_DOCUMENT_AFTER && + $options['returnDocument'] !== self::RETURN_DOCUMENT_BEFORE) { + throw new InvalidArgumentException('Invalid value for "returnDocument" option: ' . $options['returnDocument']); + } + + if (isset($options['sort']) && ! is_array($options['sort']) && ! is_object($options['sort'])) { + throw new InvalidArgumentTypeException('"sort" option', $options['sort'], 'array or object'); + } + + if ( ! is_bool($options['upsert'])) { + throw new InvalidArgumentTypeException('"upsert" option', $options['upsert'], 'boolean'); + } + + $this->findAndModify = new FindAndModify( + $databaseName, + $collectionName, + array( + 'fields' => isset($options['projection']) ? $options['projection'] : null, + 'maxTimeMS' => isset($options['maxTimeMS']) ? $options['maxTimeMS'] : null, + 'new' => $options['returnDocument'] === self::RETURN_DOCUMENT_AFTER, + 'query' => $filter, + 'sort' => isset($options['sort']) ? $options['sort'] : null, + 'update' => $update, + 'upsert' => $options['upsert'], + ) + ); + } + + /** + * Execute the operation. + * + * @see Executable::execute() + * @param Server $server + * @return object|null + */ + public function execute(Server $server) + { + return $this->findAndModify->execute($server); + } +} diff --git a/src/Operation/ListCollections.php b/src/Operation/ListCollections.php new file mode 100644 index 000000000..40a2b3cb6 --- /dev/null +++ b/src/Operation/ListCollections.php @@ -0,0 +1,122 @@ +databaseName = (string) $databaseName; + $this->options = $options; + } + + /** + * Execute the operation. + * + * @see Executable::execute() + * @param Server $server + * @return CollectionInfoIterator + */ + public function execute(Server $server) + { + return \MongoDB\server_supports_feature($server, self::$wireVersionForCommand) + ? $this->executeCommand($server) + : $this->executeLegacy($server); + } + + /** + * Returns information for all collections in this database using the + * listCollections command. + * + * @param Server $server + * @return CollectionInfoCommandIterator + */ + private function executeCommand(Server $server) + { + $cmd = array('listCollections' => 1); + + if ( ! empty($this->options['filter'])) { + $cmd['filter'] = (object) $this->options['filter']; + } + + if (isset($this->options['maxTimeMS'])) { + $cmd['maxTimeMS'] = $this->options['maxTimeMS']; + } + + $cursor = $server->executeCommand($this->databaseName, new Command($cmd)); + $cursor->setTypeMap(array('document' => 'array')); + + return new CollectionInfoCommandIterator($cursor); + } + + /** + * Returns information for all collections in this database by querying the + * "system.namespaces" collection (MongoDB <3.0). + * + * @param Server $server + * @return CollectionInfoLegacyIterator + * @throws InvalidArgumentException if filter.name is not a string. + */ + private function executeLegacy(Server $server) + { + $filter = empty($this->options['filter']) ? array() : (array) $this->options['filter']; + + if (array_key_exists('name', $filter)) { + if ( ! is_string($filter['name'])) { + throw new InvalidArgumentTypeException('filter name for MongoDB <3.0', $filter['name'], 'string'); + } + + $filter['name'] = $this->databaseName . '.' . $filter['name']; + } + + $options = isset($this->options['maxTimeMS']) + ? array('modifiers' => array('$maxTimeMS' => $this->options['maxTimeMS'])) + : array(); + + $cursor = $server->executeQuery($this->databaseName . '.system.namespaces', new Query($filter, $options)); + $cursor->setTypeMap(array('document' => 'array')); + + return new CollectionInfoLegacyIterator($cursor); + } +} diff --git a/src/Operation/ListDatabases.php b/src/Operation/ListDatabases.php new file mode 100644 index 000000000..1f9f200ed --- /dev/null +++ b/src/Operation/ListDatabases.php @@ -0,0 +1,77 @@ +options = $options; + } + + /** + * Execute the operation. + * + * @see Executable::execute() + * @param Server $server + * @return DatabaseInfoIterator + */ + public function execute(Server $server) + { + $cmd = array('listDatabases' => 1); + + if (isset($this->options['maxTimeMS'])) { + $cmd['maxTimeMS'] = $this->options['maxTimeMS']; + } + + $cursor = $server->executeCommand('admin', new Command($cmd)); + $cursor->setTypeMap(array('document' => 'array')); + $result = current($cursor->toArray()); + + if (empty($result['ok'])) { + throw new RuntimeException(isset($result['errmsg']) ? $result['errmsg'] : 'Unknown error'); + } + + if ( ! isset($result['databases']) || ! is_array($result['databases'])) { + throw new UnexpectedValueException('listDatabases command did not return a "databases" array'); + } + + /* Return an Iterator 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 DatabaseInfoLegacyIterator($result['databases']); + } +} diff --git a/src/Operation/ListIndexes.php b/src/Operation/ListIndexes.php new file mode 100644 index 000000000..1e223a010 --- /dev/null +++ b/src/Operation/ListIndexes.php @@ -0,0 +1,104 @@ +databaseName = (string) $databaseName; + $this->collectionName = (string) $collectionName; + $this->options = $options; + } + + /** + * Execute the operation. + * + * @see Executable::execute() + * @param Server $server + * @return IndexInfoIterator + */ + public function execute(Server $server) + { + return \MongoDB\server_supports_feature($server, self::$wireVersionForCommand) + ? $this->executeCommand($server) + : $this->executeLegacy($server); + } + + /** + * Returns information for all indexes for this collection using the + * listIndexes command. + * + * @param Server $server + * @return IndexInfoIteratorIterator + */ + private function executeCommand(Server $server) + { + $cmd = array('listIndexes' => $this->collectionName); + + if (isset($this->options['maxTimeMS'])) { + $cmd['maxTimeMS'] = $this->options['maxTimeMS']; + } + + $cursor = $server->executeCommand($this->databaseName, new Command($cmd)); + $cursor->setTypeMap(array('document' => 'array')); + + return new IndexInfoIteratorIterator($cursor); + } + + /** + * Returns information for all indexes for this collection by querying the + * "system.indexes" collection (MongoDB <3.0). + * + * @param Server $server + * @return IndexInfoIteratorIterator + */ + private function executeLegacy(Server $server) + { + $filter = array('ns' => $this->databaseName . '.' . $this->collectionName); + + $options = isset($this->options['maxTimeMS']) + ? array('modifiers' => array('$maxTimeMS' => $this->options['maxTimeMS'])) + : array(); + + $cursor = $server->executeQuery($this->databaseName . '.system.indexes', new Query($filter, $options)); + $cursor->setTypeMap(array('document' => 'array')); + + return new IndexInfoIteratorIterator($cursor); + } +} diff --git a/src/functions.php b/src/functions.php new file mode 100644 index 000000000..87c246ce9 --- /dev/null +++ b/src/functions.php @@ -0,0 +1,76 @@ + $type) { + $name .= ($name != '' ? '_' : '') . $field . '_' . $type; + } + + return $name; +} + +/** + * Return whether the server supports a particular feature. + * + * @internal + * @param Server $server Server to check + * @param integer $feature Feature constant (i.e. wire protocol version) + * @return boolean + */ +function server_supports_feature(Server $server, $feature) +{ + $info = $server->getInfo(); + $maxWireVersion = isset($info['maxWireVersion']) ? (integer) $info['maxWireVersion'] : 0; + $minWireVersion = isset($info['minWireVersion']) ? (integer) $info['minWireVersion'] : 0; + + return ($minWireVersion <= $feature && $maxWireVersion >= $feature); +} diff --git a/tests/Collection/CrudSpec/AggregateFunctionalTest.php b/tests/Collection/CrudSpec/AggregateFunctionalTest.php index bdacc989b..183cda284 100644 --- a/tests/Collection/CrudSpec/AggregateFunctionalTest.php +++ b/tests/Collection/CrudSpec/AggregateFunctionalTest.php @@ -3,7 +3,6 @@ namespace MongoDB\Tests\Collection\CrudSpec; use MongoDB\Collection; -use MongoDB\FeatureDetection; use MongoDB\Driver\ReadPreference; /** @@ -13,6 +12,8 @@ */ class AggregateFunctionalTest extends FunctionalTestCase { + private static $wireVersionForOutOperator = 2; + public function setUp() { parent::setUp(); @@ -36,14 +37,14 @@ public function testAggregateWithMultipleStages() ); // Use iterator_to_array() here since aggregate() may return an ArrayIterator - $this->assertSame($expected, iterator_to_array($cursor)); + $this->assertEquals($expected, iterator_to_array($cursor)); } public function testAggregateWithOut() { $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); - if ( ! FeatureDetection::isSupported($server, FeatureDetection::API_AGGREGATE_CURSOR)) { + if ( ! \MongoDB\server_supports_feature($server, self::$wireVersionForOutOperator)) { $this->markTestSkipped('$out aggregation pipeline operator is not supported'); } @@ -63,7 +64,7 @@ public function testAggregateWithOut() array('_id' => 3, 'x' => 33), ); - $this->assertSame($expected, $outputCollection->find()->toArray()); + $this->assertEquals($expected, $outputCollection->find()->toArray()); // Manually clean up our output collection $this->dropCollectionIfItExists($outputCollection); diff --git a/tests/Collection/CrudSpec/FindOneAndDeleteFunctionalTest.php b/tests/Collection/CrudSpec/FindOneAndDeleteFunctionalTest.php index 3d5aa9b0a..ecd321220 100644 --- a/tests/Collection/CrudSpec/FindOneAndDeleteFunctionalTest.php +++ b/tests/Collection/CrudSpec/FindOneAndDeleteFunctionalTest.php @@ -25,7 +25,7 @@ public function testFindOneAndDeleteWhenManyDocumentsMatch() ); $document = $this->collection->findOneAndDelete($filter, $options); - $this->assertSame(array('x' => 22), $document); + $this->assertEquals((object) array('x' => 22), $document); $expected = array( array('_id' => 1, 'x' => 11), @@ -44,7 +44,7 @@ public function testFindOneAndDeleteWhenOneDocumentMatches() ); $document = $this->collection->findOneAndDelete($filter, $options); - $this->assertSame(array('x' => 22), $document); + $this->assertEquals((object) array('x' => 22), $document); $expected = array( array('_id' => 1, 'x' => 11), diff --git a/tests/Collection/CrudSpec/FindOneAndReplaceFunctionalTest.php b/tests/Collection/CrudSpec/FindOneAndReplaceFunctionalTest.php index 26c5cf12c..9f4480d2b 100644 --- a/tests/Collection/CrudSpec/FindOneAndReplaceFunctionalTest.php +++ b/tests/Collection/CrudSpec/FindOneAndReplaceFunctionalTest.php @@ -3,6 +3,7 @@ namespace MongoDB\Tests\Collection\CrudSpec; use MongoDB\Collection; +use MongoDB\Operation\FindOneAndReplace; /** * CRUD spec functional tests for findOneAndReplace(). @@ -28,7 +29,7 @@ public function testFindOneAndReplaceWhenManyDocumentsMatchReturningDocumentBefo ); $document = $this->collection->findOneAndReplace($filter, $replacement, $options); - $this->assertSame(array('x' => 22), $document); + $this->assertEquals((object) array('x' => 22), $document); $expected = array( array('_id' => 1, 'x' => 11), @@ -46,11 +47,11 @@ public function testFindOneAndReplaceWhenManyDocumentsMatchReturningDocumentAfte $options = array( 'projection' => array('x' => 1, '_id' => 0), 'sort' => array('x' => 1), - 'returnDocument' => Collection::FIND_ONE_AND_RETURN_AFTER, + 'returnDocument' => FindOneAndReplace::RETURN_DOCUMENT_AFTER, ); $document = $this->collection->findOneAndReplace($filter, $replacement, $options); - $this->assertSame(array('x' => 32), $document); + $this->assertEquals((object) array('x' => 32), $document); $expected = array( array('_id' => 1, 'x' => 11), @@ -71,7 +72,7 @@ public function testFindOneAndReplaceWhenOneDocumentMatchesReturningDocumentBefo ); $document = $this->collection->findOneAndReplace($filter, $replacement, $options); - $this->assertSame(array('x' => 22), $document); + $this->assertEquals((object) array('x' => 22), $document); $expected = array( array('_id' => 1, 'x' => 11), @@ -89,11 +90,11 @@ public function testFindOneAndReplaceWhenOneDocumentMatchesReturningDocumentAfte $options = array( 'projection' => array('x' => 1, '_id' => 0), 'sort' => array('x' => 1), - 'returnDocument' => Collection::FIND_ONE_AND_RETURN_AFTER, + 'returnDocument' => FindOneAndReplace::RETURN_DOCUMENT_AFTER, ); $document = $this->collection->findOneAndReplace($filter, $replacement, $options); - $this->assertSame(array('x' => 32), $document); + $this->assertEquals((object) array('x' => 32), $document); $expected = array( array('_id' => 1, 'x' => 11), @@ -156,7 +157,7 @@ public function testFindOneAndReplaceWhenNoDocumentsMatchReturningDocumentAfterM $options = array( 'projection' => array('x' => 1, '_id' => 0), 'sort' => array('x' => 1), - 'returnDocument' => Collection::FIND_ONE_AND_RETURN_AFTER, + 'returnDocument' => FindOneAndReplace::RETURN_DOCUMENT_AFTER, ); $document = $this->collection->findOneAndReplace($filter, $replacement, $options); @@ -179,12 +180,12 @@ public function testFindOneAndReplaceWithUpsertWhenNoDocumentsMatchReturningDocu $options = array( 'projection' => array('x' => 1, '_id' => 0), 'sort' => array('x' => 1), - 'returnDocument' => Collection::FIND_ONE_AND_RETURN_AFTER, + 'returnDocument' => FindOneAndReplace::RETURN_DOCUMENT_AFTER, 'upsert' => true, ); $document = $this->collection->findOneAndReplace($filter, $replacement, $options); - $this->assertSame(array('x' => 44), $document); + $this->assertEquals((object) array('x' => 44), $document); $expected = array( array('_id' => 1, 'x' => 11), diff --git a/tests/Collection/CrudSpec/FindOneAndUpdateFunctionalTest.php b/tests/Collection/CrudSpec/FindOneAndUpdateFunctionalTest.php index 10a017da0..c07b9a98c 100644 --- a/tests/Collection/CrudSpec/FindOneAndUpdateFunctionalTest.php +++ b/tests/Collection/CrudSpec/FindOneAndUpdateFunctionalTest.php @@ -3,6 +3,7 @@ namespace MongoDB\Tests\Collection\CrudSpec; use MongoDB\Collection; +use MongoDB\Operation\FindOneAndUpdate; /** * CRUD spec functional tests for findOneAndUpdate(). @@ -28,7 +29,7 @@ public function testFindOneAndUpdateWhenManyDocumentsMatchReturningDocumentBefor ); $document = $this->collection->findOneAndUpdate($filter, $update, $options); - $this->assertSame(array('x' => 22), $document); + $this->assertEquals((object) array('x' => 22), $document); $expected = array( array('_id' => 1, 'x' => 11), @@ -46,11 +47,11 @@ public function testFindOneAndUpdateWhenManyDocumentsMatchReturningDocumentAfter $options = array( 'projection' => array('x' => 1, '_id' => 0), 'sort' => array('x' => 1), - 'returnDocument' => Collection::FIND_ONE_AND_RETURN_AFTER, + 'returnDocument' => FindOneAndUpdate::RETURN_DOCUMENT_AFTER, ); $document = $this->collection->findOneAndUpdate($filter, $update, $options); - $this->assertSame(array('x' => 23), $document); + $this->assertEquals((object) array('x' => 23), $document); $expected = array( array('_id' => 1, 'x' => 11), @@ -71,7 +72,7 @@ public function testFindOneAndUpdateWhenOneDocumentMatchesReturningDocumentBefor ); $document = $this->collection->findOneAndUpdate($filter, $update, $options); - $this->assertSame(array('x' => 22), $document); + $this->assertEquals((object) array('x' => 22), $document); $expected = array( array('_id' => 1, 'x' => 11), @@ -89,11 +90,11 @@ public function testFindOneAndUpdateWhenOneDocumentMatchesReturningDocumentAfter $options = array( 'projection' => array('x' => 1, '_id' => 0), 'sort' => array('x' => 1), - 'returnDocument' => Collection::FIND_ONE_AND_RETURN_AFTER, + 'returnDocument' => FindOneAndUpdate::RETURN_DOCUMENT_AFTER, ); $document = $this->collection->findOneAndUpdate($filter, $update, $options); - $this->assertSame(array('x' => 23), $document); + $this->assertEquals((object) array('x' => 23), $document); $expected = array( array('_id' => 1, 'x' => 11), @@ -155,7 +156,7 @@ public function testFindOneAndUpdateWhenNoDocumentsMatchReturningDocumentAfterMo $options = array( 'projection' => array('x' => 1, '_id' => 0), 'sort' => array('x' => 1), - 'returnDocument' => Collection::FIND_ONE_AND_RETURN_AFTER, + 'returnDocument' => FindOneAndUpdate::RETURN_DOCUMENT_AFTER, ); $document = $this->collection->findOneAndUpdate($filter, $update, $options); @@ -177,12 +178,12 @@ public function testFindOneAndUpdateWithUpsertWhenNoDocumentsMatchReturningDocum $options = array( 'projection' => array('x' => 1, '_id' => 0), 'sort' => array('x' => 1), - 'returnDocument' => Collection::FIND_ONE_AND_RETURN_AFTER, + 'returnDocument' => FindOneAndUpdate::RETURN_DOCUMENT_AFTER, 'upsert' => true, ); $document = $this->collection->findOneAndUpdate($filter, $update, $options); - $this->assertSame(array('x' => 1), $document); + $this->assertEquals((object) array('x' => 1), $document); $expected = array( array('_id' => 1, 'x' => 11), diff --git a/tests/Collection/IndexManagementFunctionalTest.php b/tests/Collection/IndexManagementFunctionalTest.php index d91f62918..a9fba6409 100644 --- a/tests/Collection/IndexManagementFunctionalTest.php +++ b/tests/Collection/IndexManagementFunctionalTest.php @@ -85,7 +85,10 @@ public function testCreateIndexes() }); } - public function testCreateIndexesWithEmptyInputIsNop() + /** + * @expectedException MongoDB\Exception\InvalidArgumentException + */ + public function testCreateIndexesRequiresAtLeastOneIndex() { $this->assertSame(array(), $this->collection->createIndexes(array())); } diff --git a/tests/FunctionalTestCase.php b/tests/FunctionalTestCase.php index cdf522e5c..94c98e2f9 100644 --- a/tests/FunctionalTestCase.php +++ b/tests/FunctionalTestCase.php @@ -21,15 +21,19 @@ protected function assertCollectionCount($namespace, $count) list($databaseName, $collectionName) = explode('.', $namespace, 2); $cursor = $this->manager->executeCommand($databaseName, new Command(array('count' => $collectionName))); - + $cursor->setTypeMap(array('document' => 'array')); $document = current($cursor->toArray()); + $this->assertArrayHasKey('n', $document); $this->assertEquals($count, $document['n']); } - protected function assertCommandSucceeded(Cursor $cursor) + protected function assertCommandSucceeded($document) { - $document = current($cursor->toArray()); + if (is_object($document)) { + $document = get_object_vars($document); + } + $this->assertArrayHasKey('ok', $document); $this->assertEquals(1, $document['ok']); } @@ -42,6 +46,7 @@ protected function getServerVersion(ReadPreference $readPreference = null) $readPreference ?: new ReadPreference(ReadPreference::RP_PRIMARY) ); + $cursor->setTypeMap(array('document' => 'array')); $document = current($cursor->toArray()); return $document['version']; diff --git a/tests/PedantryTest.php b/tests/PedantryTest.php index 5ee136c62..f0f16361e 100644 --- a/tests/PedantryTest.php +++ b/tests/PedantryTest.php @@ -62,6 +62,10 @@ public function provideProjectClassNames() $files = new RegexIterator(new RecursiveIteratorIterator(new RecursiveDirectoryIterator($srcDir)), '/\.php$/i'); foreach ($files as $file) { + if ($file->getFilename() === 'functions.php') { + continue; + } + $classNames[][] = 'MongoDB\\' . str_replace(DIRECTORY_SEPARATOR, '\\', substr($file->getRealPath(), strlen($srcDir) + 1, -4)); }