Просмотр исходного кода

Merge pull request #17 from alcaeus/type-conversion

Ensure methods return correct values
Andreas 10 лет назад
Родитель
Сommit
eb4e0e2480

+ 1 - 1
lib/Alcaeus/MongoDbAdapter/AbstractCursor.php

@@ -110,7 +110,7 @@ abstract class AbstractCursor
     {
         $document = $this->ensureIterator()->current();
         if ($document !== null) {
-            $document = TypeConverter::convertObjectToLegacyArray($document);
+            $document = TypeConverter::toLegacy($document);
         }
 
         return $document;

+ 13 - 3
lib/Alcaeus/MongoDbAdapter/Helper/WriteConcern.php

@@ -51,9 +51,9 @@ trait WriteConcern
     /**
      * @param string|int $wstring
      * @param int $wtimeout
-     * @return bool
+     * @return \MongoDB\Driver\WriteConcern
      */
-    protected function setWriteConcernFromParameters($wstring, $wtimeout = 0)
+    protected function createWriteConcernFromParameters($wstring, $wtimeout)
     {
         if (! is_string($wstring) && ! is_int($wstring)) {
             trigger_error("w for WriteConcern must be a string or integer", E_WARNING);
@@ -61,7 +61,17 @@ trait WriteConcern
         }
 
         // Ensure wtimeout is not < 0
-        $this->writeConcern = new \MongoDB\Driver\WriteConcern($wstring, max($wtimeout, 0));
+        return new \MongoDB\Driver\WriteConcern($wstring, max($wtimeout, 0));
+    }
+
+    /**
+     * @param string|int $wstring
+     * @param int $wtimeout
+     * @return bool
+     */
+    protected function setWriteConcernFromParameters($wstring, $wtimeout = 0)
+    {
+        $this->writeConcern = $this->createWriteConcernFromParameters($wstring, $wtimeout);
 
         return true;
     }

+ 85 - 30
lib/Alcaeus/MongoDbAdapter/TypeConverter.php

@@ -20,53 +20,78 @@ namespace Alcaeus\MongoDbAdapter;
  */
 class TypeConverter
 {
-    public static function convertLegacyArrayToObject($array)
-    {
-        // TODO: provide actual class once mongodb/mongo-php-library#78 has been merged
-        $result = [];
-
-        foreach ($array as $key => $value) {
-            $result[$key] = (is_array($value)) ? static::convertLegacyArrayToObject($value) : static::convertToBSONType($value);
-        }
-
-        return self::ensureCorrectType($result);
-    }
-
-    public static function convertObjectToLegacyArray($object)
-    {
-        $result = [];
-
-        foreach ($object as $key => $value) {
-            // TODO: use actual class instead of \stdClass once mongodb/mongo-php-library#78 has been merged
-            $result[$key] = ($value instanceof \stdClass || is_array($value)) ? static::convertObjectToLegacyArray($value) : static::convertToLegacyType($value);
-        }
-
-        return $result;
-    }
-
-    public static function convertToLegacyType($value)
+    /**
+     * Converts a legacy type to the new BSON type
+     *
+     * This method handles type conversion from ext-mongo to ext-mongodb:
+     *  - For all types (MongoId, MongoDate, etc.) it returns the correct BSON
+     *    object instance
+     *  - For arrays and objects it iterates over properties and converts each
+     *    item individually
+     *  - For other types it returns the value unconverted
+     *
+     * @param mixed $value
+     * @return mixed
+     */
+    public static function fromLegacy($value)
     {
         switch (true) {
-            case $value instanceof \MongoDB\BSON\ObjectID:
-                return new \MongoId($value);
+            case $value instanceof TypeInterface:
+                return $value->toBSONType();
+            case is_array($value):
+            case is_object($value);
+                $result = [];
+
+                foreach ($value as $key => $item) {
+                    $result[$key] = self::fromLegacy($item);
+                }
 
+                return self::ensureCorrectType($result);
             default:
                 return $value;
         }
     }
 
-    public static function convertToBSONType($value)
+    /**
+     * Converts a BSON type to the legacy types
+     *
+     * This method handles type conversion from ext-mongodb to ext-mongo:
+     *  - For all instances of \MongoDB\BSON\Type it returns an object of the
+     *    corresponding legacy type (MongoId, MongoDate, etc.)
+     *  - For arrays and objects it iterates over properties and converts each
+     *    item individually
+     *  - For other types it returns the value unconverted
+     *
+     * @param mixed $value
+     * @return mixed
+     */
+    public static function toLegacy($value)
     {
         switch (true) {
-            case $value instanceof TypeInterface:
-                return $value->toBSONType();
+            case $value instanceof \MongoDB\BSON\Type:
+                return self::convertBSONObjectToLegacy($value);
+            case is_array($value):
+            case is_object($value):
+                $result = [];
 
+                foreach ($value as $key => $item) {
+                    $result[$key] = self::toLegacy($item);
+                }
+
+                return $result;
             default:
                 return $value;
         }
     }
 
     /**
+     * Helper method to find out if an array has numerical indexes
+     *
+     * For performance reason, this method checks the first array index only.
+     * More thorough inspection of the array might be needed.
+     * Note: Returns true for empty arrays to preserve compatibility with empty
+     * lists.
+     *
      * @param array $array
      * @return bool
      */
@@ -76,6 +101,36 @@ class TypeConverter
     }
 
     /**
+     * Converter method to convert a BSON object to its legacy type
+     *
+     * @param \MongoDB\BSON\Type $value
+     * @return mixed
+     */
+    private static function convertBSONObjectToLegacy(\MongoDB\BSON\Type $value)
+    {
+        switch (true) {
+            case $value instanceof \MongoDB\BSON\ObjectID:
+                return new \MongoId($value);
+            case $value instanceof \MongoDB\BSON\Binary:
+                return new \MongoBinData($value);
+            case $value instanceof \MongoDB\BSON\Javascript:
+                return new \MongoCode($value);
+            case $value instanceof \MongoDB\BSON\MaxKey:
+                return new \MongoMaxKey();
+            case $value instanceof \MongoDB\BSON\MinKey:
+                return new \MongoMinKey();
+            case $value instanceof \MongoDB\BSON\Regex:
+                return new \MongoRegex($value);
+            case $value instanceof \MongoDB\BSON\Timestamp:
+                return new \MongoTimestamp($value);
+            case $value instanceof \MongoDB\BSON\UTCDatetime:
+                return new \MongoDate($value);
+            default:
+                return $value;
+        }
+    }
+
+    /**
      * Converts all arrays with non-numeric keys to stdClass
      *
      * @param array $array

+ 12 - 0
lib/Mongo/Mongo.php

@@ -198,6 +198,18 @@ class Mongo extends MongoClient
     }
 
     /**
+     * Choose a new secondary for slaveOkay reads
+     *
+     * @link www.php.net/manual/en/mongo.switchslave.php
+     * @return string The address of the secondary this connection is using for reads. This may be the same as the previous address as addresses are randomly chosen. It may return only one address if only one secondary (or only the primary) is available.
+     * @throws MongoException (error code 15) if it is called on a non-replica-set connection. It will also throw MongoExceptions if it cannot find anyone (primary or secondary) to read from (error code 16).
+     */
+    public function switchSlave()
+    {
+        $this->notImplemented();
+    }
+
+    /**
      * Creates a database error on the database.
      *
      * @link http://www.php.net/manual/en/mongo.forceerror.php

+ 0 - 12
lib/Mongo/MongoClient.php

@@ -291,18 +291,6 @@ class MongoClient
     }
 
     /**
-     * Choose a new secondary for slaveOkay reads
-     *
-     * @link www.php.net/manual/en/mongo.switchslave.php
-     * @return string The address of the secondary this connection is using for reads. This may be the same as the previous address as addresses are randomly chosen. It may return only one address if only one secondary (or only the primary) is available.
-     * @throws MongoException (error code 15) if it is called on a non-replica-set connection. It will also throw MongoExceptions if it cannot find anyone (primary or secondary) to read from (error code 16).
-     */
-    public function switchSlave()
-    {
-        $this->notImplemented();
-    }
-
-    /**
      * String representation of this connection
      *
      * @link http://www.php.net/manual/en/mongoclient.tostring.php

+ 17 - 1
lib/Mongo/MongoCode.php

@@ -13,7 +13,7 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-class MongoCode
+class MongoCode implements \Alcaeus\MongoDbAdapter\TypeInterface
 {
     /**
      * @var string
@@ -32,6 +32,11 @@ class MongoCode
      */
     public function __construct($code, array $scope = [])
     {
+        if ($code instanceof \MongoDB\BSON\Javascript) {
+            // @todo Use properties from object once they are accessible
+            $code = '';
+        }
+
         $this->code = $code;
         $this->scope = $scope;
     }
@@ -44,4 +49,15 @@ class MongoCode
     {
         return $this->code;
     }
+
+    /**
+     * Converts this MongoCode to the new BSON JavaScript type
+     *
+     * @return \MongoDB\BSON\Javascript
+     * @internal This method is not part of the ext-mongo API
+     */
+    public function toBSONType()
+    {
+        return new \MongoDB\BSON\Javascript($this->code, $this->scope);
+    }
 }

+ 165 - 54
lib/Mongo/MongoCollection.php

@@ -170,14 +170,13 @@ class MongoCollection
         ];
 
         // Convert cursor option
-        if (! isset($options['cursor']) || $options['cursor'] === true || $options['cursor'] === []) {
-            // Cursor option needs to be an object convert bools and empty arrays since those won't be handled by TypeConverter
-            $options['cursor'] = new \stdClass;
+        if (! isset($options['cursor'])) {
+            $options['cursor'] = true;
         }
 
         $command += $options;
 
-        $cursor = new MongoCommandCursor($this->db->getConnection(), (string)$this, $command);
+        $cursor = new MongoCommandCursor($this->db->getConnection(), (string) $this, $command);
         $cursor->setReadPreference($this->getReadPreference());
 
         return $cursor;
@@ -224,7 +223,7 @@ class MongoCollection
      */
     public function drop()
     {
-        return $this->collection->drop();
+        return TypeConverter::toLegacy($this->collection->drop());
     }
 
     /**
@@ -257,7 +256,21 @@ class MongoCollection
      */
     public function insert($a, array $options = [])
     {
-        return $this->collection->insertOne(TypeConverter::convertLegacyArrayToObject($a), $options);
+        $result = $this->collection->insertOne(
+            TypeConverter::fromLegacy($a),
+            $this->convertWriteConcernOptions($options)
+        );
+
+        if (! $result->isAcknowledged()) {
+            return true;
+        }
+
+        return [
+            'ok' => 1.0,
+            'n' => 0,
+            'err' => null,
+            'errmsg' => null,
+        ];
     }
 
     /**
@@ -267,11 +280,27 @@ class MongoCollection
      * @param array $a An array of arrays.
      * @param array $options Options for the inserts.
      * @throws MongoCursorException
-     * @return mixed f "safe" is set, returns an associative array with the status of the inserts ("ok") and any error that may have occured ("err"). Otherwise, returns TRUE if the batch insert was successfully sent, FALSE otherwise.
+     * @return mixed If "safe" is set, returns an associative array with the status of the inserts ("ok") and any error that may have occured ("err"). Otherwise, returns TRUE if the batch insert was successfully sent, FALSE otherwise.
      */
     public function batchInsert(array $a, array $options = [])
     {
-        return $this->collection->insertMany($a, $options);
+        $result = $this->collection->insertMany(
+            TypeConverter::fromLegacy($a),
+            $this->convertWriteConcernOptions($options)
+        );
+
+        if (! $result->isAcknowledged()) {
+            return true;
+        }
+
+        return [
+            'connectionId' => 0,
+            'n' => 0,
+            'syncMillis' => 0,
+            'writtenTo' => null,
+            'err' => null,
+            'errmsg' => null,
+        ];
     }
 
     /**
@@ -286,10 +315,29 @@ class MongoCollection
      */
     public function update(array $criteria , array $newobj, array $options = [])
     {
-        $multiple = ($options['multiple']) ? $options['multiple'] : false;
+        $multiple = isset($options['multiple']) ? $options['multiple'] : false;
         $method = $multiple ? 'updateMany' : 'updateOne';
+        unset($options['multiple']);
 
-        return $this->collection->$method($criteria, $newobj, $options);
+        /** @var \MongoDB\UpdateResult $result */
+        $result = $this->collection->$method(
+            TypeConverter::fromLegacy($criteria),
+            TypeConverter::fromLegacy($newobj),
+            $this->convertWriteConcernOptions($options)
+        );
+
+        if (! $result->isAcknowledged()) {
+            return true;
+        }
+
+        return [
+            'ok' => 1.0,
+            'nModified' => $result->getModifiedCount(),
+            'n' => $result->getMatchedCount(),
+            'err' => null,
+            'errmsg' => null,
+            'updatedExisting' => $result->getUpsertedCount() == 0,
+        ];
     }
 
     /**
@@ -305,10 +353,25 @@ class MongoCollection
      */
     public function remove(array $criteria = [], array $options = [])
     {
-        $multiple = isset($options['justOne']) ? !$options['justOne'] : false;
+        $multiple = isset($options['justOne']) ? !$options['justOne'] : true;
         $method = $multiple ? 'deleteMany' : 'deleteOne';
 
-        return $this->collection->$method($criteria, $options);
+        /** @var \MongoDB\DeleteResult $result */
+        $result = $this->collection->$method(
+            TypeConverter::fromLegacy($criteria),
+            $this->convertWriteConcernOptions($options)
+        );
+
+        if (! $result->isAcknowledged()) {
+            return true;
+        }
+
+        return [
+            'ok' => 1.0,
+            'n' => $result->getDeletedCount(),
+            'err' => null,
+            'errmsg' => null
+        ];
     }
 
     /**
@@ -321,7 +384,7 @@ class MongoCollection
      */
     public function find(array $query = [], array $fields = [])
     {
-        $cursor = new MongoCursor($this->db->getConnection(), (string)$this, $query, $fields);
+        $cursor = new MongoCursor($this->db->getConnection(), (string) $this, $query, $fields);
         $cursor->setReadPreference($this->getReadPreference());
 
         return $cursor;
@@ -337,11 +400,12 @@ class MongoCollection
      */
     public function distinct($key, array $query = [])
     {
-        return array_map([TypeConverter::class, 'convertToLegacyType'], $this->collection->distinct($key, $query));
+        return array_map([TypeConverter::class, 'toLegacy'], $this->collection->distinct($key, $query));
     }
 
     /**
      * Update a document and return it
+     *
      * @link http://www.php.net/manual/ru/mongocollection.findandmodify.php
      * @param array $query The query criteria to search for.
      * @param array $update The update criteria.
@@ -351,26 +415,26 @@ class MongoCollection
      */
     public function findAndModify(array $query, array $update = null, array $fields = null, array $options = [])
     {
-        $query = TypeConverter::convertLegacyArrayToObject($query);
+        $query = TypeConverter::fromLegacy($query);
 
         if (isset($options['remove'])) {
             unset($options['remove']);
             $document = $this->collection->findOneAndDelete($query, $options);
         } else {
-            $update = is_array($update) ? TypeConverter::convertLegacyArrayToObject($update) : [];
+            $update = is_array($update) ? TypeConverter::fromLegacy($update) : [];
 
             if (isset($options['new'])) {
                 $options['returnDocument'] = \MongoDB\Operation\FindOneAndUpdate::RETURN_DOCUMENT_AFTER;
                 unset($options['new']);
             }
 
-            $options['projection'] = is_array($fields) ? TypeConverter::convertLegacyArrayToObject($fields) : [];
+            $options['projection'] = is_array($fields) ? TypeConverter::fromLegacy($fields) : [];
 
             $document = $this->collection->findOneAndUpdate($query, $update, $options);
         }
 
         if ($document) {
-            $document = TypeConverter::convertObjectToLegacyArray($document);
+            $document = TypeConverter::toLegacy($document);
         }
 
         return $document;
@@ -378,16 +442,20 @@ class MongoCollection
 
     /**
      * Querys this collection, returning a single element
+     *
      * @link http://www.php.net/manual/en/mongocollection.findone.php
      * @param array $query The fields for which to search.
      * @param array $fields Fields of the results to return.
+     * @param array $options
      * @return array|null
      */
-    public function findOne(array $query = [], array $fields = [])
+    public function findOne(array $query = [], array $fields = [], array $options = [])
     {
-        $document = $this->collection->findOne(TypeConverter::convertLegacyArrayToObject($query), ['projection' => $fields]);
+        $options = ['projection' => $fields] + $options;
+
+        $document = $this->collection->findOne(TypeConverter::fromLegacy($query), $options);
         if ($document !== null) {
-            $document = TypeConverter::convertObjectToLegacyArray($document);
+            $document = TypeConverter::toLegacy($document);
         }
 
         return $document;
@@ -395,6 +463,7 @@ class MongoCollection
 
     /**
      * Creates an index on the given field(s), or does nothing if the index already exists
+     *
      * @link http://www.php.net/manual/en/mongocollection.createindex.php
      * @param array $keys Field or fields to use as index.
      * @param array $options [optional] This parameter is an associative array of the form array("optionname" => <boolean>, ...).
@@ -416,12 +485,13 @@ class MongoCollection
     }
 
     /**
-     * @deprecated Use MongoCollection::createIndex() instead.
      * Creates an index on the given field(s), or does nothing if the index already exists
+     *
      * @link http://www.php.net/manual/en/mongocollection.ensureindex.php
      * @param array $keys Field or fields to use as index.
      * @param array $options [optional] This parameter is an associative array of the form array("optionname" => <boolean>, ...).
      * @return boolean always true
+     * @deprecated Use MongoCollection::createIndex() instead.
      */
     public function ensureIndex(array $keys, array $options = [])
     {
@@ -432,6 +502,7 @@ class MongoCollection
 
     /**
      * Deletes an index from this collection
+     *
      * @link http://www.php.net/manual/en/mongocollection.deleteindex.php
      * @param string|array $keys Field or fields from which to delete the index.
      * @return array Returns the database response.
@@ -441,26 +512,28 @@ class MongoCollection
         if (is_string($keys)) {
             $indexName = $keys;
         } elseif (is_array($keys)) {
-            $indexName = self::toIndexString($keys);
+            $indexName = \MongoDB\generate_index_name($keys);
         } else {
             throw new \InvalidArgumentException();
         }
 
-        return TypeConverter::convertObjectToLegacyArray($this->collection->dropIndex($indexName));
+        return TypeConverter::toLegacy($this->collection->dropIndex($indexName));
     }
 
     /**
      * Delete all indexes for this collection
+     *
      * @link http://www.php.net/manual/en/mongocollection.deleteindexes.php
      * @return array Returns the database response.
      */
     public function deleteIndexes()
     {
-        return TypeConverter::convertObjectToLegacyArray($this->collection->dropIndexes());
+        return TypeConverter::toLegacy($this->collection->dropIndexes());
     }
 
     /**
      * Returns an array of index names for this collection
+     *
      * @link http://www.php.net/manual/en/mongocollection.getindexinfo.php
      * @return array Returns a list of index names.
      */
@@ -480,13 +553,15 @@ class MongoCollection
 
     /**
      * Counts the number of documents in this collection
+     *
      * @link http://www.php.net/manual/en/mongocollection.count.php
      * @param array|stdClass $query
+     * @param array $options
      * @return int Returns the number of documents matching the query.
      */
-    public function count($query = [])
+    public function count($query = [], array $options = [])
     {
-        return $this->collection->count($query);
+        return $this->collection->count(TypeConverter::fromLegacy($query), $options);
     }
 
     /**
@@ -504,30 +579,48 @@ class MongoCollection
     public function save($a, array $options = [])
     {
         if (is_object($a)) {
-            $a = (array)$a;
+            $a = (array) $a;
         }
+
         if ( ! array_key_exists('_id', $a)) {
             $id = new \MongoId();
         } else {
             $id = $a['_id'];
             unset($a['_id']);
         }
-        $filter = ['_id' => $id];
-        $filter = TypeConverter::convertLegacyArrayToObject($filter);
-        $a = TypeConverter::convertLegacyArrayToObject($a);
-        return $this->collection->updateOne($filter, ['$set' => $a], ['upsert' => true]);
+        $options['upsert'] = true;
+
+        return $this->update(['_id' => $id], ['$set' => $a], $options);
     }
 
     /**
      * Creates a database reference
      *
      * @link http://www.php.net/manual/en/mongocollection.createdbref.php
-     * @param array $a Object to which to create a reference.
+     * @param array|object $document_or_id Object to which to create a reference.
      * @return array Returns a database reference array.
      */
-    public function createDBRef(array $a)
+    public function createDBRef($document_or_id)
     {
-        return \MongoDBRef::create($this->name, $a['_id']);
+        if ($document_or_id instanceof \MongoId) {
+            $id = $document_or_id;
+        } elseif (is_object($document_or_id)) {
+            if (! isset($document_or_id->_id)) {
+                return null;
+            }
+
+            $id = $document_or_id->_id;
+        } elseif (is_array($document_or_id)) {
+            if (! isset($document_or_id['_id'])) {
+                return null;
+            }
+
+            $id = $document_or_id['_id'];
+        } else {
+            $id = $document_or_id;
+        }
+
+        return MongoDBRef::create($this->name, $id);
     }
 
     /**
@@ -539,21 +632,7 @@ class MongoCollection
      */
     public function getDBRef(array $ref)
     {
-        return \MongoDBRef::get($this->db, $ref);
-    }
-
-    /**
-     * @param mixed $keys
-     * @static
-     * @return string
-     */
-    protected static function toIndexString($keys)
-    {
-        $result = '';
-        foreach ($keys as $name => $direction) {
-            $result .= sprintf('%s_%d', $name, $direction);
-        }
-        return $result;
+        return $this->db->getDBRef($ref);
     }
 
     /**
@@ -562,7 +641,7 @@ class MongoCollection
      * @link http://www.php.net/manual/en/mongocollection.group.php
      * @param mixed $keys Fields to group by. If an array or non-code object is passed, it will be the key used to group results.
      * @param array $initial Initial value of the aggregation counter object.
-     * @param MongoCode $reduce A function that aggregates (reduces) the objects iterated.
+     * @param MongoCode|string $reduce A function that aggregates (reduces) the objects iterated.
      * @param array $condition An condition that must be true for a row to be considered.
      * @return array
      */
@@ -571,9 +650,7 @@ class MongoCollection
         if (is_string($reduce)) {
             $reduce = new MongoCode($reduce);
         }
-        if ( ! $reduce instanceof MongoCode) {
-            throw new \InvalidArgumentExcption('reduce parameter should be a string or MongoCode instance.');
-        }
+
         $command = [
             'group' => [
                 'ns' => $this->name,
@@ -634,5 +711,39 @@ class MongoCollection
             $this->collection = $this->collection->withOptions($options);
         }
     }
+
+    /**
+     * Converts legacy write concern options to a WriteConcern object
+     *
+     * @param array $options
+     * @return array
+     */
+    private function convertWriteConcernOptions(array $options)
+    {
+        if (isset($options['safe'])) {
+            $options['w'] = ($options['safe']) ? 1 : 0;
+        }
+
+        if (isset($options['wtimeout']) && !isset($options['wTimeoutMS'])) {
+            $options['wTimeoutMS'] = $options['wtimeout'];
+        }
+
+        if (isset($options['w']) || !isset($options['wTimeoutMS'])) {
+            $collectionWriteConcern = $this->getWriteConcern();
+            $writeConcern = $this->createWriteConcernFromParameters(
+                isset($options['w']) ? $options['w'] : $collectionWriteConcern['w'],
+                isset($options['wTimeoutMS']) ? $options['wTimeoutMS'] : $collectionWriteConcern['wtimeout']
+            );
+
+            $options['writeConcern'] = $writeConcern;
+        }
+
+        unset($options['safe']);
+        unset($options['w']);
+        unset($options['wTimeout']);
+        unset($options['wTimeoutMS']);
+
+        return $options;
+    }
 }
 

+ 8 - 1
lib/Mongo/MongoCommandCursor.php

@@ -53,7 +53,14 @@ class MongoCommandCursor extends AbstractCursor implements MongoCursorInterface
     protected function ensureCursor()
     {
         if ($this->cursor === null) {
-            $this->cursor = $this->db->command(TypeConverter::convertLegacyArrayToObject($this->command), $this->getOptions());
+            $convertedCommand = TypeConverter::fromLegacy($this->command);
+            if (isset($convertedCommand->cursor)) {
+                if ($convertedCommand->cursor === true || $convertedCommand->cursor === []) {
+                    $convertedCommand->cursor = new \stdClass();
+                }
+            }
+
+            $this->cursor = $this->db->command($convertedCommand, $this->getOptions());
         }
 
         return $this->cursor;

+ 33 - 5
lib/Mongo/MongoDB.php

@@ -14,6 +14,7 @@
  */
 
 use Alcaeus\MongoDbAdapter\Helper;
+use Alcaeus\MongoDbAdapter\TypeConverter;
 use MongoDB\Model\CollectionInfo;
 
 /**
@@ -116,9 +117,36 @@ class MongoDB
     }
 
     /**
+     * Returns information about collections in this database
+     *
+     * @link http://www.php.net/manual/en/mongodb.getcollectioninfo.php
+     * @param array $options An array of options for listing the collections.
+     * @return array
+     */
+    public function getCollectionInfo(array $options = [])
+    {
+        // The includeSystemCollections option is no longer supported
+        if (isset($options['includeSystemCollections'])) {
+            unset($options['includeSystemCollections']);
+        }
+
+        $collections = $this->db->listCollections($options);
+
+        $getCollectionInfo = function (CollectionInfo $collectionInfo) {
+            return [
+                'name' => $collectionInfo->getName(),
+                'options' => $collectionInfo->getOptions(),
+            ];
+        };
+
+        return array_map($getCollectionInfo, iterator_to_array($collections));
+    }
+
+    /**
      * Get all collections from this database
      *
      * @link http://www.php.net/manual/en/mongodb.getcollectionnames.php
+     * @param array $options An array of options for listing the collections.
      * @return array Returns the names of the all the collections in the database as an array
      */
     public function getCollectionNames(array $options = [])
@@ -134,7 +162,7 @@ class MongoDB
             return $collectionInfo->getName();
         };
 
-        return array_map($getCollectionName, (array)$collections);
+        return array_map($getCollectionName, iterator_to_array($collections));
     }
 
     /**
@@ -193,7 +221,7 @@ class MongoDB
      */
     public function drop()
     {
-        return $this->db->drop();
+        return TypeConverter::toLegacy($this->db->drop());
     }
 
     /**
@@ -206,7 +234,7 @@ class MongoDB
      */
     public function repair($preserve_cloned_files = FALSE, $backup_original_files = FALSE)
     {
-        return [];
+        $this->notImplemented();
     }
 
     /**
@@ -251,7 +279,7 @@ class MongoDB
             $coll = $coll->getName();
         }
 
-        return $this->db->dropCollection((string) $coll);
+        return TypeConverter::toLegacy($this->db->dropCollection((string) $coll));
     }
 
     /**
@@ -259,7 +287,7 @@ class MongoDB
      *
      * @link http://www.php.net/manual/en/mongodb.listcollections.php
      * @param array $options
-     * @return array Returns a list of MongoCollections.
+     * @return MongoCollection[] Returns a list of MongoCollections.
      */
     public function listCollections(array $options = [])
     {

+ 30 - 0
tests/Alcaeus/MongoDbAdapter/MongoCodeTest.php

@@ -0,0 +1,30 @@
+<?php
+
+namespace Alcaeus\MongoDbAdapter\Tests;
+
+/**
+ * @author alcaeus <alcaeus@alcaeus.org>
+ */
+class MongoCodeTest extends TestCase
+{
+    public function testCreate()
+    {
+        $code = new \MongoCode('code', ['scope' => 'bleh']);
+        $this->assertAttributeSame('code', 'code', $code);
+        $this->assertAttributeSame(['scope' => 'bleh'], 'scope', $code);
+
+        $this->assertSame('code', (string) $code);
+
+        $bsonCode = $code->toBSONType();
+        $this->assertInstanceOf('MongoDB\BSON\Javascript', $bsonCode);
+    }
+
+    public function testCreateWithBsonObject()
+    {
+        $bsonCode = new \MongoDB\BSON\Javascript('code', ['scope' => 'bleh']);
+        $code = new \MongoCode($bsonCode);
+
+        $this->assertAttributeSame('', 'code', $code);
+        $this->assertAttributeSame([], 'scope', $code);
+    }
+}

+ 133 - 1
tests/Alcaeus/MongoDbAdapter/MongoCollectionTest.php

@@ -19,7 +19,13 @@ class MongoCollectionTest extends TestCase
         $id = '54203e08d51d4a1f868b456e';
         $collection = $this->getCollection();
 
-        $collection->insert(['_id' => new \MongoId($id), 'foo' => 'bar']);
+        $expected = [
+            'ok' => 1.0,
+            'n' => 0,
+            'err' => null,
+            'errmsg' => null,
+        ];
+        $this->assertSame($expected, $collection->insert(['_id' => new \MongoId($id), 'foo' => 'bar']));
 
         $newCollection = $this->getCheckDatabase()->selectCollection('test');
         $this->assertSame(1, $newCollection->count());
@@ -32,6 +38,121 @@ class MongoCollectionTest extends TestCase
         $this->assertAttributeSame('bar', 'foo', $object);
     }
 
+    public function testUnacknowledgedWrite()
+    {
+        $this->assertTrue($this->getCollection()->insert(['foo' => 'bar'], ['w' => 0]));
+    }
+
+    public function testInsertMany()
+    {
+        $expected = [
+            'connectionId' => 0,
+            'n' => 0,
+            'syncMillis' => 0,
+            'writtenTo' => null,
+            'err' => null,
+            'errmsg' => null
+        ];
+
+        $documents = [
+            ['foo' => 'bar'],
+            ['bar' => 'foo']
+        ];
+        $this->assertSame($expected, $this->getCollection()->batchInsert($documents));
+    }
+
+    public function testUpdateOne()
+    {
+        $this->getCollection()->insert(['foo' => 'bar']);
+        $this->getCollection()->insert(['foo' => 'bar']);
+        $expected = [
+            'ok' => 1.0,
+            'nModified' => 1,
+            'n' => 1,
+            'err' => null,
+            'errmsg' => null,
+            'updatedExisting' => true,
+        ];
+
+        $result = $this->getCollection()->update(['foo' => 'bar'], ['$set' => ['foo' => 'foo']]);
+        $this->assertSame($expected, $result);
+
+        $this->assertSame(1, $this->getCheckDatabase()->selectCollection('test')->count(['foo' => 'foo']));
+    }
+
+    public function testUpdateMany()
+    {
+        $this->getCollection()->insert(['change' => true, 'foo' => 'bar']);
+        $this->getCollection()->insert(['change' => true, 'foo' => 'bar']);
+        $this->getCollection()->insert(['change' => true, 'foo' => 'foo']);
+        $expected = [
+            'ok' => 1.0,
+            'nModified' => 2,
+            'n' => 3,
+            'err' => null,
+            'errmsg' => null,
+            'updatedExisting' => true,
+        ];
+
+        $result = $this->getCollection()->update(['change' => true], ['$set' => ['foo' => 'foo']], ['multiple' => true]);
+        $this->assertSame($expected, $result);
+
+        $this->assertSame(3, $this->getCheckDatabase()->selectCollection('test')->count(['foo' => 'foo']));
+    }
+
+    public function testUnacknowledgedUpdate()
+    {
+        $this->getCollection()->insert(['foo' => 'bar']);
+        $this->getCollection()->insert(['foo' => 'bar']);
+
+        $this->assertTrue($this->getCollection()->update(['foo' => 'bar'], ['$set' => ['foo' => 'foo']], ['w' => 0]));
+    }
+
+    public function testRemoveMultiple()
+    {
+        $this->getCollection()->insert(['change' => true, 'foo' => 'bar']);
+        $this->getCollection()->insert(['change' => true, 'foo' => 'bar']);
+        $this->getCollection()->insert(['change' => true, 'foo' => 'foo']);
+        $expected = [
+            'ok' => 1.0,
+            'n' => 2,
+            'err' => null,
+            'errmsg' => null,
+        ];
+
+        $result = $this->getCollection()->remove(['foo' => 'bar']);
+        $this->assertSame($expected, $result);
+
+        $this->assertSame(1, $this->getCheckDatabase()->selectCollection('test')->count());
+    }
+
+    public function testRemoveSingle()
+    {
+        $this->getCollection()->insert(['change' => true, 'foo' => 'bar']);
+        $this->getCollection()->insert(['change' => true, 'foo' => 'bar']);
+        $this->getCollection()->insert(['change' => true, 'foo' => 'foo']);
+        $expected = [
+            'ok' => 1.0,
+            'n' => 1,
+            'err' => null,
+            'errmsg' => null,
+        ];
+
+        $result = $this->getCollection()->remove(['foo' => 'bar'], ['justOne' => true]);
+        $this->assertSame($expected, $result);
+
+        $this->assertSame(2, $this->getCheckDatabase()->selectCollection('test')->count());
+    }
+
+    public function testRemoveUnacknowledged()
+    {
+        $this->getCollection()->insert(['change' => true, 'foo' => 'bar']);
+        $this->getCollection()->insert(['change' => true, 'foo' => 'bar']);
+        $this->getCollection()->insert(['change' => true, 'foo' => 'foo']);
+
+        $this->assertTrue($this->getCollection()->remove(['foo' => 'bar'], ['w' => 0]));
+    }
+
     public function testFindReturnsCursor()
     {
         $this->prepareData();
@@ -479,6 +600,17 @@ class MongoCollectionTest extends TestCase
         );
     }
 
+    public function testDrop()
+    {
+        $this->getCollection()->insert(['foo' => 'bar']);
+        $expected = [
+            'ns' => (string) $this->getCollection(),
+            'nIndexesWas' => 1,
+            'ok' => 1.0
+        ];
+        $this->assertSame($expected, $this->getCollection()->drop());
+    }
+
     /**
      * @return \MongoCollection
      */

+ 1 - 1
tests/Alcaeus/MongoDbAdapter/MongoCommandCursorTest.php

@@ -27,7 +27,7 @@ class MongoCommandCursorTest extends TestCase
                         '$match' => ['foo' => 'bar']
                     ]
                 ],
-                'cursor' => new \stdClass()
+                'cursor' => true,
             ],
             'fields' => null,
             'started_iterating' => false,

+ 51 - 0
tests/Alcaeus/MongoDbAdapter/MongoDBTest.php

@@ -128,4 +128,55 @@ class MongoDBTest extends TestCase
         $this->getCollection()->insert(['foo' => 'bar']);
         $this->assertEquals(['ok' => 1, 'retval' => 1], $db->execute("return db.test.count();"));
     }
+
+    public function testGetCollectionNames()
+    {
+        $this->getCollection()->insert(['foo' => 'bar']);
+        $this->assertContains('test', $this->getDatabase()->getCollectionNames());
+    }
+
+    public function testGetCollectionInfo()
+    {
+        $this->getCollection()->insert(['foo' => 'bar']);
+
+        foreach ($this->getDatabase()->getCollectionInfo() as $collectionInfo) {
+            if ($collectionInfo['name'] === 'test') {
+                $this->assertSame(['name' => 'test', 'options' => []], $collectionInfo);
+                return;
+            }
+        }
+
+        $this->fail('The test collection was not found');
+    }
+
+    public function testListCollections()
+    {
+        $this->getCollection()->insert(['foo' => 'bar']);
+        foreach ($this->getDatabase()->listCollections() as $collection) {
+            $this->assertInstanceOf('MongoCollection', $collection);
+
+            if ($collection->getName() === 'test') {
+                return;
+            }
+        }
+
+        $this->fail('The test collection was not found');
+    }
+
+    public function testDrop()
+    {
+        $this->getCollection()->insert(['foo' => 'bar']);
+        $this->assertSame(['dropped' => 'mongo-php-adapter', 'ok' => 1.0], $this->getDatabase()->drop());
+    }
+
+    public function testDropCollection()
+    {
+        $this->getCollection()->insert(['foo' => 'bar']);
+        $expected = [
+            'ns' => (string) $this->getCollection(),
+            'nIndexesWas' => 1,
+            'ok' => 1.0
+        ];
+        $this->assertSame($expected, $this->getDatabase()->dropCollection('test'));
+    }
 }