Merge branch 'MDL-54592-master' of git://github.com/vmdef/moodle
authorAdrian Greeve <abgreeve@gmail.com>
Tue, 19 Mar 2019 03:02:28 +0000 (11:02 +0800)
committerAdrian Greeve <abgreeve@gmail.com>
Tue, 19 Mar 2019 03:02:28 +0000 (11:02 +0800)
84 files changed:
.eslintignore
.stylelintignore
cache/stores/mongodb/MongoDB/BulkWriteResult.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/ChangeStream.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Client.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Collection.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Database.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/DeleteResult.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Exception/BadMethodCallException.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Exception/Exception.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Exception/InvalidArgumentException.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Exception/ResumeTokenException.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Exception/RuntimeException.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Exception/UnexpectedValueException.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Exception/UnsupportedException.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/GridFS/Bucket.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/GridFS/CollectionWrapper.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/GridFS/Exception/CorruptFileException.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/GridFS/Exception/FileNotFoundException.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/GridFS/ReadableStream.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/GridFS/StreamWrapper.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/GridFS/WritableStream.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/InsertManyResult.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/InsertOneResult.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/MapReduceResult.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Model/BSONArray.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Model/BSONDocument.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Model/BSONIterator.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Model/CachingIterator.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Model/CollectionInfo.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Model/CollectionInfoCommandIterator.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Model/CollectionInfoIterator.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Model/DatabaseInfo.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Model/DatabaseInfoIterator.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Model/DatabaseInfoLegacyIterator.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Model/IndexInfo.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Model/IndexInfoIterator.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Model/IndexInfoIteratorIterator.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Model/IndexInput.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Model/TypeMapArrayIterator.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Operation/Aggregate.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Operation/BulkWrite.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Operation/Count.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Operation/CountDocuments.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Operation/CreateCollection.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Operation/CreateIndexes.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Operation/DatabaseCommand.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Operation/Delete.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Operation/DeleteMany.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Operation/DeleteOne.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Operation/Distinct.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Operation/DropCollection.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Operation/DropDatabase.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Operation/DropIndexes.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Operation/EstimatedDocumentCount.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Operation/Executable.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Operation/Explain.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Operation/Explainable.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Operation/Find.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Operation/FindAndModify.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Operation/FindOne.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Operation/FindOneAndDelete.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Operation/FindOneAndReplace.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Operation/FindOneAndUpdate.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Operation/InsertMany.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Operation/InsertOne.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Operation/ListCollections.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Operation/ListDatabases.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Operation/ListIndexes.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Operation/MapReduce.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Operation/ModifyCollection.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Operation/ReplaceOne.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Operation/Update.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Operation/UpdateMany.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Operation/UpdateOne.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/Operation/Watch.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/UpdateResult.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/functions.php [new file with mode: 0755]
cache/stores/mongodb/MongoDB/readme_moodle.txt [new file with mode: 0644]
cache/stores/mongodb/addinstanceform.php
cache/stores/mongodb/lib.php
cache/stores/mongodb/thirdpartylibs.xml [new file with mode: 0644]
cache/upgrade.txt
lib/classes/component.php

index 420eb21..9153e18 100644 (file)
@@ -6,6 +6,7 @@ vendor/
 admin/tool/policy/amd/src/jquery-eu-cookie-law-popup.js
 admin/tool/usertours/amd/src/tour.js
 auth/cas/CAS/
+cache/stores/mongodb/MongoDB/
 enrol/lti/ims-blti/
 filter/algebra/AlgParser.pm
 filter/tex/mimetex.*
index be8cec8..55b87b5 100644 (file)
@@ -9,6 +9,7 @@ vendor/
 admin/tool/policy/amd/src/jquery-eu-cookie-law-popup.js
 admin/tool/usertours/amd/src/tour.js
 auth/cas/CAS/
+cache/stores/mongodb/MongoDB/
 enrol/lti/ims-blti/
 filter/algebra/AlgParser.pm
 filter/tex/mimetex.*
diff --git a/cache/stores/mongodb/MongoDB/BulkWriteResult.php b/cache/stores/mongodb/MongoDB/BulkWriteResult.php
new file mode 100755 (executable)
index 0000000..cdf6654
--- /dev/null
@@ -0,0 +1,189 @@
+<?php
+/*
+ * Copyright 2015-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB;
+
+use MongoDB\Driver\WriteResult;
+use MongoDB\Exception\BadMethodCallException;
+
+/**
+ * Result class for a bulk write operation.
+ */
+class BulkWriteResult
+{
+    private $writeResult;
+    private $insertedIds;
+    private $isAcknowledged;
+
+    /**
+     * Constructor.
+     *
+     * @param WriteResult $writeResult
+     * @param mixed[]     $insertedIds
+     */
+    public function __construct(WriteResult $writeResult, array $insertedIds)
+    {
+        $this->writeResult = $writeResult;
+        $this->insertedIds = $insertedIds;
+        $this->isAcknowledged = $writeResult->isAcknowledged();
+    }
+
+    /**
+     * Return the number of documents that were deleted.
+     *
+     * This method should only be called if the write was acknowledged.
+     *
+     * @see BulkWriteResult::isAcknowledged()
+     * @return integer
+     * @throws BadMethodCallException is the write result is unacknowledged
+     */
+    public function getDeletedCount()
+    {
+        if ($this->isAcknowledged) {
+            return $this->writeResult->getDeletedCount();
+        }
+
+        throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
+    }
+
+    /**
+     * Return the number of documents that were inserted.
+     *
+     * This method should only be called if the write was acknowledged.
+     *
+     * @see BulkWriteResult::isAcknowledged()
+     * @return integer
+     * @throws BadMethodCallException is the write result is unacknowledged
+     */
+    public function getInsertedCount()
+    {
+        if ($this->isAcknowledged) {
+            return $this->writeResult->getInsertedCount();
+        }
+
+        throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
+    }
+
+    /**
+     * Return a map of the inserted documents' IDs.
+     *
+     * The index of each ID in the map corresponds to each document's position
+     * in the bulk operation. If a document had an ID prior to inserting (i.e.
+     * the driver did not generate an ID), the index will contain its "_id"
+     * field value. Any driver-generated ID will be a MongoDB\BSON\ObjectId
+     * instance.
+     *
+     * @return mixed[]
+     */
+    public function getInsertedIds()
+    {
+        return $this->insertedIds;
+    }
+
+    /**
+     * Return the number of documents that were matched by the filter.
+     *
+     * This method should only be called if the write was acknowledged.
+     *
+     * @see BulkWriteResult::isAcknowledged()
+     * @return integer
+     * @throws BadMethodCallException is the write result is unacknowledged
+     */
+    public function getMatchedCount()
+    {
+        if ($this->isAcknowledged) {
+            return $this->writeResult->getMatchedCount();
+        }
+
+        throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
+    }
+
+    /**
+     * Return the number of documents that were modified.
+     *
+     * This value is undefined (i.e. null) if the write executed as a legacy
+     * operation instead of command.
+     *
+     * This method should only be called if the write was acknowledged.
+     *
+     * @see BulkWriteResult::isAcknowledged()
+     * @return integer|null
+     * @throws BadMethodCallException is the write result is unacknowledged
+     */
+    public function getModifiedCount()
+    {
+        if ($this->isAcknowledged) {
+            return $this->writeResult->getModifiedCount();
+        }
+
+        throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
+    }
+
+    /**
+     * Return the number of documents that were upserted.
+     *
+     * This method should only be called if the write was acknowledged.
+     *
+     * @see BulkWriteResult::isAcknowledged()
+     * @return integer
+     * @throws BadMethodCallException is the write result is unacknowledged
+     */
+    public function getUpsertedCount()
+    {
+        if ($this->isAcknowledged) {
+            return $this->writeResult->getUpsertedCount();
+        }
+
+        throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
+    }
+
+    /**
+     * Return a map of the upserted documents' IDs.
+     *
+     * The index of each ID in the map corresponds to each document's position
+     * in bulk operation. If a document had an ID prior to upserting (i.e. the
+     * server did not need to generate an ID), this will contain its "_id". Any
+     * server-generated ID will be a MongoDB\BSON\ObjectId instance.
+     *
+     * This method should only be called if the write was acknowledged.
+     *
+     * @see BulkWriteResult::isAcknowledged()
+     * @return mixed[]
+     * @throws BadMethodCallException is the write result is unacknowledged
+     */
+    public function getUpsertedIds()
+    {
+        if ($this->isAcknowledged) {
+            return $this->writeResult->getUpsertedIds();
+        }
+
+        throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
+    }
+
+    /**
+     * Return whether this update was acknowledged by the server.
+     *
+     * If the update was not acknowledged, other fields from the WriteResult
+     * (e.g. matchedCount) will be undefined.
+     *
+     * @return boolean
+     */
+    public function isAcknowledged()
+    {
+        return $this->isAcknowledged;
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/ChangeStream.php b/cache/stores/mongodb/MongoDB/ChangeStream.php
new file mode 100755 (executable)
index 0000000..98a703f
--- /dev/null
@@ -0,0 +1,228 @@
+<?php
+/*
+ * Copyright 2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB;
+
+use MongoDB\BSON\Serializable;
+use MongoDB\Driver\Cursor;
+use MongoDB\Driver\Exception\ConnectionException;
+use MongoDB\Driver\Exception\RuntimeException;
+use MongoDB\Driver\Exception\ServerException;
+use MongoDB\Exception\InvalidArgumentException;
+use MongoDB\Exception\ResumeTokenException;
+use IteratorIterator;
+use Iterator;
+
+/**
+ * Iterator for a change stream.
+ *
+ * @api
+ * @see \MongoDB\Collection::watch()
+ * @see http://docs.mongodb.org/manual/reference/command/changeStream/
+ */
+class ChangeStream implements Iterator
+{
+    /**
+     * @deprecated 1.4
+     * @todo Remove this in 2.0 (see: PHPLIB-360)
+     */
+    const CURSOR_NOT_FOUND = 43;
+
+    private static $errorCodeCappedPositionLost = 136;
+    private static $errorCodeInterrupted = 11601;
+    private static $errorCodeCursorKilled = 237;
+
+    private $resumeToken;
+    private $resumeCallable;
+    private $csIt;
+    private $key = 0;
+    private $hasAdvanced = false;
+
+    /**
+     * Constructor.
+     *
+     * @internal
+     * @param Cursor $cursor
+     * @param callable $resumeCallable
+     */
+    public function __construct(Cursor $cursor, callable $resumeCallable)
+    {
+        $this->resumeCallable = $resumeCallable;
+        $this->csIt = new IteratorIterator($cursor);
+    }
+
+    /**
+     * @see http://php.net/iterator.current
+     * @return mixed
+     */
+    public function current()
+    {
+        return $this->csIt->current();
+    }
+
+    /**
+     * @return \MongoDB\Driver\CursorId
+     */
+    public function getCursorId()
+    {
+        return $this->csIt->getInnerIterator()->getId();
+    }
+
+    /**
+     * @see http://php.net/iterator.key
+     * @return mixed
+     */
+    public function key()
+    {
+        if ($this->valid()) {
+            return $this->key;
+        }
+        return null;
+    }
+
+    /**
+     * @see http://php.net/iterator.next
+     * @return void
+     */
+    public function next()
+    {
+        try {
+            $this->csIt->next();
+            if ($this->valid()) {
+                if ($this->hasAdvanced) {
+                    $this->key++;
+                }
+                $this->hasAdvanced = true;
+                $this->resumeToken = $this->extractResumeToken($this->csIt->current());
+            }
+            /* If the cursorId is 0, the server has invalidated the cursor so we
+             * will never perform another getMore. This means that we cannot
+             * resume and we can therefore unset the resumeCallable, which will
+             * free any reference to Watch. This will also free the only
+             * reference to an implicit session, since any such reference
+             * belongs to Watch. */
+            if ((string) $this->getCursorId() === '0') {
+                $this->resumeCallable = null;
+            }
+        } catch (RuntimeException $e) {
+            if ($this->isResumableError($e)) {
+                $this->resume();
+            }
+        }
+    }
+
+    /**
+     * @see http://php.net/iterator.rewind
+     * @return void
+     */
+    public function rewind()
+    {
+        try {
+            $this->csIt->rewind();
+            if ($this->valid()) {
+                $this->hasAdvanced = true;
+                $this->resumeToken = $this->extractResumeToken($this->csIt->current());
+            }
+            // As with next(), free the callable once we know it will never be used.
+            if ((string) $this->getCursorId() === '0') {
+                $this->resumeCallable = null;
+            }
+        } catch (RuntimeException $e) {
+            if ($this->isResumableError($e)) {
+                $this->resume();
+            }
+        }
+    }
+
+    /**
+     * @see http://php.net/iterator.valid
+     * @return boolean
+     */
+    public function valid()
+    {
+        return $this->csIt->valid();
+    }
+
+    /**
+     * Extracts the resume token (i.e. "_id" field) from the change document.
+     *
+     * @param array|document $document Change document
+     * @return mixed
+     * @throws InvalidArgumentException
+     * @throws ResumeTokenException if the resume token is not found or invalid
+     */
+    private function extractResumeToken($document)
+    {
+        if ( ! is_array($document) && ! is_object($document)) {
+            throw InvalidArgumentException::invalidType('$document', $document, 'array or object');
+        }
+
+        if ($document instanceof Serializable) {
+            return $this->extractResumeToken($document->bsonSerialize());
+        }
+
+        $resumeToken = is_array($document)
+            ? (isset($document['_id']) ? $document['_id'] : null)
+            : (isset($document->_id) ? $document->_id : null);
+
+        if ( ! isset($resumeToken)) {
+            throw ResumeTokenException::notFound();
+        }
+
+        if ( ! is_array($resumeToken) && ! is_object($resumeToken)) {
+            throw ResumeTokenException::invalidType($resumeToken);
+        }
+
+        return $resumeToken;
+    }
+
+    /**
+     * Determines if an exception is a resumable error.
+     *
+     * @see https://github.com/mongodb/specifications/blob/master/source/change-streams/change-streams.rst#resumable-error
+     * @param RuntimeException $exception
+     * @return boolean
+     */
+    private function isResumableError(RuntimeException $exception)
+    {
+        if ($exception instanceof ConnectionException) {
+            return true;
+        }
+
+        if ( ! $exception instanceof ServerException) {
+            return false;
+        }
+
+        if (in_array($exception->getCode(), [self::$errorCodeCappedPositionLost, self::$errorCodeCursorKilled, self::$errorCodeInterrupted])) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Creates a new changeStream after a resumable server error.
+     *
+     * @return void
+     */
+    private function resume()
+    {
+        $newChangeStream = call_user_func($this->resumeCallable, $this->resumeToken);
+        $this->csIt = $newChangeStream->csIt;
+        $this->csIt->rewind();
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/Client.php b/cache/stores/mongodb/MongoDB/Client.php
new file mode 100755 (executable)
index 0000000..be26ec5
--- /dev/null
@@ -0,0 +1,307 @@
+<?php
+/*
+ * Copyright 2015-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB;
+
+use MongoDB\Driver\Manager;
+use MongoDB\Driver\ReadConcern;
+use MongoDB\Driver\ReadPreference;
+use MongoDB\Driver\WriteConcern;
+use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
+use MongoDB\Driver\Exception\InvalidArgumentException as DriverInvalidArgumentException;
+use MongoDB\Exception\InvalidArgumentException;
+use MongoDB\Exception\UnexpectedValueException;
+use MongoDB\Exception\UnsupportedException;
+use MongoDB\Model\DatabaseInfoIterator;
+use MongoDB\Operation\DropDatabase;
+use MongoDB\Operation\ListDatabases;
+use MongoDB\Operation\Watch;
+
+class Client
+{
+    private static $defaultTypeMap = [
+        'array' => 'MongoDB\Model\BSONArray',
+        'document' => 'MongoDB\Model\BSONDocument',
+        'root' => 'MongoDB\Model\BSONDocument',
+    ];
+    private static $wireVersionForReadConcern = 4;
+    private static $wireVersionForWritableCommandWriteConcern = 5;
+
+    private $manager;
+    private $readConcern;
+    private $readPreference;
+    private $uri;
+    private $typeMap;
+    private $writeConcern;
+
+    /**
+     * Constructs a new Client instance.
+     *
+     * This is the preferred class for connecting to a MongoDB server or
+     * cluster of servers. It serves as a gateway for accessing individual
+     * databases and collections.
+     *
+     * Supported driver-specific options:
+     *
+     *  * typeMap (array): Default type map for cursors and BSON documents.
+     *
+     * Other options are documented in MongoDB\Driver\Manager::__construct().
+     *
+     * @see http://docs.mongodb.org/manual/reference/connection-string/
+     * @see http://php.net/manual/en/mongodb-driver-manager.construct.php
+     * @see http://php.net/manual/en/mongodb.persistence.php#mongodb.persistence.typemaps
+     * @param string $uri           MongoDB connection string
+     * @param array  $uriOptions    Additional connection string options
+     * @param array  $driverOptions Driver-specific options
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverInvalidArgumentException for parameter/option parsing errors in the driver
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function __construct($uri = 'mongodb://127.0.0.1/', array $uriOptions = [], array $driverOptions = [])
+    {
+        $driverOptions += ['typeMap' => self::$defaultTypeMap];
+
+        if (isset($driverOptions['typeMap']) && ! is_array($driverOptions['typeMap'])) {
+            throw InvalidArgumentException::invalidType('"typeMap" driver option', $driverOptions['typeMap'], 'array');
+        }
+
+        $this->uri = (string) $uri;
+        $this->typeMap = isset($driverOptions['typeMap']) ? $driverOptions['typeMap'] : null;
+
+        unset($driverOptions['typeMap']);
+
+        $this->manager = new Manager($uri, $uriOptions, $driverOptions);
+        $this->readConcern = $this->manager->getReadConcern();
+        $this->readPreference = $this->manager->getReadPreference();
+        $this->writeConcern = $this->manager->getWriteConcern();
+    }
+
+    /**
+     * Return internal properties for debugging purposes.
+     *
+     * @see http://php.net/manual/en/language.oop5.magic.php#language.oop5.magic.debuginfo
+     * @return array
+     */
+    public function __debugInfo()
+    {
+        return [
+            'manager' => $this->manager,
+            'uri' => $this->uri,
+            'typeMap' => $this->typeMap,
+            'writeConcern' => $this->writeConcern,
+        ];
+    }
+
+    /**
+     * Select a database.
+     *
+     * Note: databases whose names contain special characters (e.g. "-") may
+     * be selected with complex syntax (e.g. $client->{"that-database"}) or
+     * {@link selectDatabase()}.
+     *
+     * @see http://php.net/oop5.overloading#object.get
+     * @see http://php.net/types.string#language.types.string.parsing.complex
+     * @param string $databaseName Name of the database to select
+     * @return Database
+     */
+    public function __get($databaseName)
+    {
+        return $this->selectDatabase($databaseName);
+    }
+
+    /**
+     * Return the connection string (i.e. URI).
+     *
+     * @return string
+     */
+    public function __toString()
+    {
+        return $this->uri;
+    }
+
+    /**
+     * Drop a database.
+     *
+     * @see DropDatabase::__construct() for supported options
+     * @param string $databaseName Database name
+     * @param array  $options      Additional options
+     * @return array|object Command result document
+     * @throws UnsupportedException if options are unsupported on the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function dropDatabase($databaseName, array $options = [])
+    {
+        if ( ! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+        if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        $operation = new DropDatabase($databaseName, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Return the Manager.
+     *
+     * @return Manager
+     */
+    public function getManager()
+    {
+        return $this->manager;
+    }
+
+    /**
+     * Return the read concern for this client.
+     *
+     * @see http://php.net/manual/en/mongodb-driver-readconcern.isdefault.php
+     * @return ReadConcern
+     */
+    public function getReadConcern()
+    {
+        return $this->readConcern;
+    }
+
+    /**
+     * Return the read preference for this client.
+     *
+     * @return ReadPreference
+     */
+    public function getReadPreference()
+    {
+        return $this->readPreference;
+    }
+
+    /**
+     * Return the type map for this client.
+     *
+     * @return array
+     */
+    public function getTypeMap()
+    {
+        return $this->typeMap;
+    }
+
+    /**
+     * Return the write concern for this client.
+     *
+     * @see http://php.net/manual/en/mongodb-driver-writeconcern.isdefault.php
+     * @return WriteConcern
+     */
+    public function getWriteConcern()
+    {
+        return $this->writeConcern;
+    }
+
+    /**
+     * List databases.
+     *
+     * @see ListDatabases::__construct() for supported options
+     * @return DatabaseInfoIterator
+     * @throws UnexpectedValueException if the command response was malformed
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function listDatabases(array $options = [])
+    {
+        $operation = new ListDatabases($options);
+        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Select a collection.
+     *
+     * @see Collection::__construct() for supported options
+     * @param string $databaseName   Name of the database containing the collection
+     * @param string $collectionName Name of the collection to select
+     * @param array  $options        Collection constructor options
+     * @return Collection
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function selectCollection($databaseName, $collectionName, array $options = [])
+    {
+        $options += ['typeMap' => $this->typeMap];
+
+        return new Collection($this->manager, $databaseName, $collectionName, $options);
+    }
+
+    /**
+     * Select a database.
+     *
+     * @see Database::__construct() for supported options
+     * @param string $databaseName Name of the database to select
+     * @param array  $options      Database constructor options
+     * @return Database
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function selectDatabase($databaseName, array $options = [])
+    {
+        $options += ['typeMap' => $this->typeMap];
+
+        return new Database($this->manager, $databaseName, $options);
+    }
+
+    /**
+     * Start a new client session.
+     *
+     * @see http://php.net/manual/en/mongodb-driver-manager.startsession.php
+     * @param array  $options      Session options
+     * @return MongoDB\Driver\Session
+     */
+    public function startSession(array $options = [])
+    {
+        return $this->manager->startSession($options);
+    }
+
+    /**
+     * Create a change stream for watching changes to the cluster.
+     *
+     * @see Watch::__construct() for supported options
+     * @param array $pipeline List of pipeline operations
+     * @param array $options  Command options
+     * @return ChangeStream
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function watch(array $pipeline = [], array $options = [])
+    {
+        if ( ! isset($options['readPreference'])) {
+            $options['readPreference'] = $this->readPreference;
+        }
+
+        $server = $this->manager->selectServer($options['readPreference']);
+
+        if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
+            $options['readConcern'] = $this->readConcern;
+        }
+
+        if ( ! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        $operation = new Watch($this->manager, null, null, $pipeline, $options);
+
+        return $operation->execute($server);
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/Collection.php b/cache/stores/mongodb/MongoDB/Collection.php
new file mode 100755 (executable)
index 0000000..769e835
--- /dev/null
@@ -0,0 +1,1092 @@
+<?php
+/*
+ * Copyright 2015-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB;
+
+use MongoDB\BSON\JavascriptInterface;
+use MongoDB\BSON\Serializable;
+use MongoDB\ChangeStream;
+use MongoDB\Driver\Cursor;
+use MongoDB\Driver\Manager;
+use MongoDB\Driver\ReadConcern;
+use MongoDB\Driver\ReadPreference;
+use MongoDB\Driver\WriteConcern;
+use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
+use MongoDB\Exception\InvalidArgumentException;
+use MongoDB\Exception\UnexpectedValueException;
+use MongoDB\Exception\UnsupportedException;
+use MongoDB\Model\IndexInfo;
+use MongoDB\Model\IndexInfoIterator;
+use MongoDB\Operation\Aggregate;
+use MongoDB\Operation\BulkWrite;
+use MongoDB\Operation\CreateIndexes;
+use MongoDB\Operation\Count;
+use MongoDB\Operation\CountDocuments;
+use MongoDB\Operation\DeleteMany;
+use MongoDB\Operation\DeleteOne;
+use MongoDB\Operation\Distinct;
+use MongoDB\Operation\DropCollection;
+use MongoDB\Operation\DropIndexes;
+use MongoDB\Operation\EstimatedDocumentCount;
+use MongoDB\Operation\Explain;
+use MongoDB\Operation\Explainable;
+use MongoDB\Operation\Find;
+use MongoDB\Operation\FindOne;
+use MongoDB\Operation\FindOneAndDelete;
+use MongoDB\Operation\FindOneAndReplace;
+use MongoDB\Operation\FindOneAndUpdate;
+use MongoDB\Operation\InsertMany;
+use MongoDB\Operation\InsertOne;
+use MongoDB\Operation\ListIndexes;
+use MongoDB\Operation\MapReduce;
+use MongoDB\Operation\ReplaceOne;
+use MongoDB\Operation\UpdateMany;
+use MongoDB\Operation\UpdateOne;
+use MongoDB\Operation\Watch;
+use Traversable;
+
+class Collection
+{
+    private static $defaultTypeMap = [
+        'array' => 'MongoDB\Model\BSONArray',
+        'document' => 'MongoDB\Model\BSONDocument',
+        'root' => 'MongoDB\Model\BSONDocument',
+    ];
+    private static $wireVersionForFindAndModifyWriteConcern = 4;
+    private static $wireVersionForReadConcern = 4;
+    private static $wireVersionForWritableCommandWriteConcern = 5;
+
+    private $collectionName;
+    private $databaseName;
+    private $manager;
+    private $readConcern;
+    private $readPreference;
+    private $typeMap;
+    private $writeConcern;
+
+    /**
+     * Constructs new Collection instance.
+     *
+     * This class provides methods for collection-specific operations, such as
+     * CRUD (i.e. create, read, update, and delete) and index management.
+     *
+     * Supported options:
+     *
+     *  * readConcern (MongoDB\Driver\ReadConcern): The default read concern to
+     *    use for collection operations. Defaults to the Manager's read concern.
+     *
+     *  * readPreference (MongoDB\Driver\ReadPreference): The default read
+     *    preference to use for collection operations. Defaults to the Manager's
+     *    read preference.
+     *
+     *  * typeMap (array): Default type map for cursors and BSON documents.
+     *
+     *  * writeConcern (MongoDB\Driver\WriteConcern): The default write concern
+     *    to use for collection operations. Defaults to the Manager's write
+     *    concern.
+     *
+     * @param Manager $manager        Manager instance from the driver
+     * @param string  $databaseName   Database name
+     * @param string  $collectionName Collection name
+     * @param array   $options        Collection options
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function __construct(Manager $manager, $databaseName, $collectionName, array $options = [])
+    {
+        if (strlen($databaseName) < 1) {
+            throw new InvalidArgumentException('$databaseName is invalid: ' . $databaseName);
+        }
+
+        if (strlen($collectionName) < 1) {
+            throw new InvalidArgumentException('$collectionName is invalid: ' . $collectionName);
+        }
+
+        if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
+            throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern');
+        }
+
+        if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
+            throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
+        }
+
+        if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
+            throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
+        }
+
+        if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
+            throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
+        }
+
+        $this->manager = $manager;
+        $this->databaseName = (string) $databaseName;
+        $this->collectionName = (string) $collectionName;
+        $this->readConcern = isset($options['readConcern']) ? $options['readConcern'] : $this->manager->getReadConcern();
+        $this->readPreference = isset($options['readPreference']) ? $options['readPreference'] : $this->manager->getReadPreference();
+        $this->typeMap = isset($options['typeMap']) ? $options['typeMap'] : self::$defaultTypeMap;
+        $this->writeConcern = isset($options['writeConcern']) ? $options['writeConcern'] : $this->manager->getWriteConcern();
+    }
+
+    /**
+     * Return internal properties for debugging purposes.
+     *
+     * @see http://php.net/manual/en/language.oop5.magic.php#language.oop5.magic.debuginfo
+     * @return array
+     */
+    public function __debugInfo()
+    {
+        return [
+            'collectionName' => $this->collectionName,
+            'databaseName' => $this->databaseName,
+            'manager' => $this->manager,
+            'readConcern' => $this->readConcern,
+            'readPreference' => $this->readPreference,
+            'typeMap' => $this->typeMap,
+            'writeConcern' => $this->writeConcern,
+        ];
+    }
+
+    /**
+     * Return the collection namespace (e.g. "db.collection").
+     *
+     * @see https://docs.mongodb.org/manual/faq/developers/#faq-dev-namespace
+     * @return string
+     */
+    public function __toString()
+    {
+        return $this->databaseName . '.' . $this->collectionName;
+    }
+
+    /**
+     * 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 Aggregate::__construct() for supported options
+     * @param array $pipeline List of pipeline operations
+     * @param array $options  Command options
+     * @return Traversable
+     * @throws UnexpectedValueException if the command response was malformed
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function aggregate(array $pipeline, array $options = [])
+    {
+        $hasOutStage = \MongoDB\is_last_pipeline_operator_out($pipeline);
+
+        if ( ! isset($options['readPreference'])) {
+            $options['readPreference'] = $this->readPreference;
+        }
+
+        if ($hasOutStage) {
+            $options['readPreference'] = new ReadPreference(ReadPreference::RP_PRIMARY);
+        }
+
+        $server = $this->manager->selectServer($options['readPreference']);
+
+        /* A "majority" read concern is not compatible with the $out stage, so
+         * avoid providing the Collection's read concern if it would conflict.
+         */
+        if ( ! isset($options['readConcern']) &&
+             ! ($hasOutStage && $this->readConcern->getLevel() === ReadConcern::MAJORITY) &&
+            \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
+            $options['readConcern'] = $this->readConcern;
+        }
+
+        if ( ! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        if ($hasOutStage && ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        $operation = new Aggregate($this->databaseName, $this->collectionName, $pipeline, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Executes multiple write operations.
+     *
+     * @see BulkWrite::__construct() for supported options
+     * @param array[] $operations List of write operations
+     * @param array   $options    Command options
+     * @return BulkWriteResult
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function bulkWrite(array $operations, array $options = [])
+    {
+        if ( ! isset($options['writeConcern'])) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        $operation = new BulkWrite($this->databaseName, $this->collectionName, $operations, $options);
+        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Gets the number of documents matching the filter.
+     *
+     * @see Count::__construct() for supported options
+     * @param array|object $filter  Query by which to filter documents
+     * @param array        $options Command options
+     * @return integer
+     * @throws UnexpectedValueException if the command response was malformed
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     *
+     * @deprecated 1.4
+     */
+    public function count($filter = [], array $options = [])
+    {
+        if ( ! isset($options['readPreference'])) {
+            $options['readPreference'] = $this->readPreference;
+        }
+
+        $server = $this->manager->selectServer($options['readPreference']);
+
+        if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
+            $options['readConcern'] = $this->readConcern;
+        }
+
+        $operation = new Count($this->databaseName, $this->collectionName, $filter, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Gets the number of documents matching the filter.
+     *
+     * @see CountDocuments::__construct() for supported options
+     * @param array|object $filter  Query by which to filter documents
+     * @param array        $options Command options
+     * @return integer
+     * @throws UnexpectedValueException if the command response was malformed
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function countDocuments($filter = [], array $options = [])
+    {
+        if ( ! isset($options['readPreference'])) {
+            $options['readPreference'] = $this->readPreference;
+        }
+
+        $server = $this->manager->selectServer($options['readPreference']);
+
+        if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
+            $options['readConcern'] = $this->readConcern;
+        }
+
+        $operation = new CountDocuments($this->databaseName, $this->collectionName, $filter, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Create a single index for the collection.
+     *
+     * @see Collection::createIndexes()
+     * @see CreateIndexes::__construct() for supported command options
+     * @param array|object $key     Document containing fields mapped to values,
+     *                              which denote order or an index type
+     * @param array        $options Index and command options
+     * @return string The name of the created index
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function createIndex($key, array $options = [])
+    {
+        $commandOptionKeys = ['maxTimeMS' => 1, 'session' => 1, 'writeConcern' => 1];
+        $indexOptions = array_diff_key($options, $commandOptionKeys);
+        $commandOptions = array_intersect_key($options, $commandOptionKeys);
+
+        return current($this->createIndexes([['key' => $key] + $indexOptions], $commandOptions));
+    }
+
+    /**
+     * Create one or more indexes for the collection.
+     *
+     * Each element in the $indexes array must have a "key" document, which
+     * contains fields mapped to an order or type. Other options may follow.
+     * For example:
+     *
+     *     $indexes = [
+     *         // Create a unique index on the "username" field
+     *         [ 'key' => [ 'username' => 1 ], 'unique' => true ],
+     *         // Create a 2dsphere index on the "loc" field with a custom name
+     *         [ 'key' => [ 'loc' => '2dsphere' ], 'name' => 'geo' ],
+     *     ];
+     *
+     * If the "name" option is unspecified, a name will be generated from the
+     * "key" document.
+     *
+     * @see http://docs.mongodb.org/manual/reference/command/createIndexes/
+     * @see http://docs.mongodb.org/manual/reference/method/db.collection.createIndex/
+     * @see CreateIndexes::__construct() for supported command options
+     * @param array[] $indexes List of index specifications
+     * @param array   $options Command options
+     * @return string[] The names of the created indexes
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function createIndexes(array $indexes, array $options = [])
+    {
+        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+        if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        $operation = new CreateIndexes($this->databaseName, $this->collectionName, $indexes, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Deletes all documents matching the filter.
+     *
+     * @see DeleteMany::__construct() for supported options
+     * @see http://docs.mongodb.org/manual/reference/command/delete/
+     * @param array|object $filter  Query by which to delete documents
+     * @param array        $options Command options
+     * @return DeleteResult
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function deleteMany($filter, array $options = [])
+    {
+        if ( ! isset($options['writeConcern'])) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        $operation = new DeleteMany($this->databaseName, $this->collectionName, $filter, $options);
+        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Deletes at most one document matching the filter.
+     *
+     * @see DeleteOne::__construct() for supported options
+     * @see http://docs.mongodb.org/manual/reference/command/delete/
+     * @param array|object $filter  Query by which to delete documents
+     * @param array        $options Command options
+     * @return DeleteResult
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function deleteOne($filter, array $options = [])
+    {
+        if ( ! isset($options['writeConcern'])) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        $operation = new DeleteOne($this->databaseName, $this->collectionName, $filter, $options);
+        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Finds the distinct values for a specified field across the collection.
+     *
+     * @see Distinct::__construct() for supported options
+     * @param string $fieldName Field for which to return distinct values
+     * @param array|object $filter  Query by which to filter documents
+     * @param array        $options Command options
+     * @return mixed[]
+     * @throws UnexpectedValueException if the command response was malformed
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function distinct($fieldName, $filter = [], array $options = [])
+    {
+        if ( ! isset($options['readPreference'])) {
+            $options['readPreference'] = $this->readPreference;
+        }
+
+        $server = $this->manager->selectServer($options['readPreference']);
+
+        if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
+            $options['readConcern'] = $this->readConcern;
+        }
+
+        $operation = new Distinct($this->databaseName, $this->collectionName, $fieldName, $filter, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Drop this collection.
+     *
+     * @see DropCollection::__construct() for supported options
+     * @param array $options Additional options
+     * @return array|object Command result document
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function drop(array $options = [])
+    {
+        if ( ! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+        if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        $operation = new DropCollection($this->databaseName, $this->collectionName, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Drop a single index in the collection.
+     *
+     * @see DropIndexes::__construct() for supported options
+     * @param string|IndexInfo $indexName Index name or model object
+     * @param array  $options   Additional options
+     * @return array|object Command result document
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function dropIndex($indexName, array $options = [])
+    {
+        $indexName = (string) $indexName;
+
+        if ($indexName === '*') {
+            throw new InvalidArgumentException('dropIndexes() must be used to drop multiple indexes');
+        }
+
+        if ( ! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+        if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        $operation = new DropIndexes($this->databaseName, $this->collectionName, $indexName, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Drop all indexes in the collection.
+     *
+     * @see DropIndexes::__construct() for supported options
+     * @param array $options Additional options
+     * @return array|object Command result document
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function dropIndexes(array $options = [])
+    {
+        if ( ! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+        if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        $operation = new DropIndexes($this->databaseName, $this->collectionName, '*', $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Gets an estimated number of documents in the collection using the collection metadata.
+     *
+     * @see EstimatedDocumentCount::__construct() for supported options
+     * @param array $options Command options
+     * @return integer
+     * @throws UnexpectedValueException if the command response was malformed
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function EstimatedDocumentCount(array $options = [])
+    {
+        if ( ! isset($options['readPreference'])) {
+            $options['readPreference'] = $this->readPreference;
+        }
+
+        $server = $this->manager->selectServer($options['readPreference']);
+
+        if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
+            $options['readConcern'] = $this->readConcern;
+        }
+
+        $operation = new EstimatedDocumentCount($this->databaseName, $this->collectionName, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Explains explainable commands.
+     *
+     * @see Explain::__construct() for supported options
+     * @see http://docs.mongodb.org/manual/reference/command/explain/
+     * @param Explainable $explainable  Command on which to run explain
+     * @param array       $options      Additional options
+     * @return array|object
+     * @throws UnsupportedException if explainable or options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function explain(Explainable $explainable, array $options = [])
+    {
+        if ( ! isset($options['readPreference'])) {
+            $options['readPreference'] = $this->readPreference;
+        }
+
+        if ( ! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        $server = $this->manager->selectServer($options['readPreference']);
+
+        $operation = new Explain($this->databaseName, $explainable, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Finds documents matching the query.
+     *
+     * @see Find::__construct() for supported options
+     * @see http://docs.mongodb.org/manual/core/read-operations-introduction/
+     * @param array|object $filter  Query by which to filter documents
+     * @param array        $options Additional options
+     * @return Cursor
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function find($filter = [], array $options = [])
+    {
+        if ( ! isset($options['readPreference'])) {
+            $options['readPreference'] = $this->readPreference;
+        }
+
+        $server = $this->manager->selectServer($options['readPreference']);
+
+        if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
+            $options['readConcern'] = $this->readConcern;
+        }
+
+        if ( ! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        $operation = new Find($this->databaseName, $this->collectionName, $filter, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Finds a single document matching the query.
+     *
+     * @see FindOne::__construct() for supported options
+     * @see http://docs.mongodb.org/manual/core/read-operations-introduction/
+     * @param array|object $filter  Query by which to filter documents
+     * @param array        $options Additional options
+     * @return array|object|null
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function findOne($filter = [], array $options = [])
+    {
+        if ( ! isset($options['readPreference'])) {
+            $options['readPreference'] = $this->readPreference;
+        }
+
+        $server = $this->manager->selectServer($options['readPreference']);
+
+        if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
+            $options['readConcern'] = $this->readConcern;
+        }
+
+        if ( ! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        $operation = new FindOne($this->databaseName, $this->collectionName, $filter, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Finds a single document and deletes it, returning the original.
+     *
+     * The document to return may be null if no document matched the filter.
+     *
+     * @see FindOneAndDelete::__construct() for supported options
+     * @see http://docs.mongodb.org/manual/reference/command/findAndModify/
+     * @param array|object $filter  Query by which to filter documents
+     * @param array        $options Command options
+     * @return array|object|null
+     * @throws UnexpectedValueException if the command response was malformed
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function findOneAndDelete($filter, array $options = [])
+    {
+        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+        if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForFindAndModifyWriteConcern)) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        if ( ! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        $operation = new FindOneAndDelete($this->databaseName, $this->collectionName, $filter, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Finds a single document and replaces it, returning either the original or
+     * the replaced document.
+     *
+     * The document to return may be null if no document matched the filter. By
+     * default, the original document is returned. Specify
+     * FindOneAndReplace::RETURN_DOCUMENT_AFTER for the "returnDocument" option
+     * to return the updated document.
+     *
+     * @see FindOneAndReplace::__construct() for supported options
+     * @see http://docs.mongodb.org/manual/reference/command/findAndModify/
+     * @param array|object $filter      Query by which to filter documents
+     * @param array|object $replacement Replacement document
+     * @param array        $options     Command options
+     * @return array|object|null
+     * @throws UnexpectedValueException if the command response was malformed
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function findOneAndReplace($filter, $replacement, array $options = [])
+    {
+        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+        if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForFindAndModifyWriteConcern)) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        if ( ! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        $operation = new FindOneAndReplace($this->databaseName, $this->collectionName, $filter, $replacement, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Finds a single document and updates it, returning either the original or
+     * the updated document.
+     *
+     * The document to return may be null if no document matched the filter. By
+     * default, the original document is returned. Specify
+     * FindOneAndUpdate::RETURN_DOCUMENT_AFTER for the "returnDocument" option
+     * to return the updated document.
+     *
+     * @see FindOneAndReplace::__construct() for supported options
+     * @see http://docs.mongodb.org/manual/reference/command/findAndModify/
+     * @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 array|object|null
+     * @throws UnexpectedValueException if the command response was malformed
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function findOneAndUpdate($filter, $update, array $options = [])
+    {
+        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+        if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForFindAndModifyWriteConcern)) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        if ( ! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        $operation = new FindOneAndUpdate($this->databaseName, $this->collectionName, $filter, $update, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Return the collection name.
+     *
+     * @return string
+     */
+    public function getCollectionName()
+    {
+        return $this->collectionName;
+    }
+
+    /**
+     * Return the database name.
+     *
+     * @return string
+     */
+    public function getDatabaseName()
+    {
+        return $this->databaseName;
+    }
+
+    /**
+     * Return the Manager.
+     *
+     * @return Manager
+     */
+    public function getManager()
+    {
+        return $this->manager;
+    }
+
+    /**
+     * Return the collection namespace.
+     *
+     * @see https://docs.mongodb.org/manual/reference/glossary/#term-namespace
+     * @return string
+     */
+    public function getNamespace()
+    {
+        return $this->databaseName . '.' . $this->collectionName;
+    }
+
+    /**
+     * Return the read concern for this collection.
+     *
+     * @see http://php.net/manual/en/mongodb-driver-readconcern.isdefault.php
+     * @return ReadConcern
+     */
+    public function getReadConcern()
+    {
+        return $this->readConcern;
+    }
+
+    /**
+     * Return the read preference for this collection.
+     *
+     * @return ReadPreference
+     */
+    public function getReadPreference()
+    {
+        return $this->readPreference;
+    }
+
+    /**
+     * Return the type map for this collection.
+     *
+     * @return array
+     */
+    public function getTypeMap()
+    {
+        return $this->typeMap;
+    }
+
+    /**
+     * Return the write concern for this collection.
+     *
+     * @see http://php.net/manual/en/mongodb-driver-writeconcern.isdefault.php
+     * @return WriteConcern
+     */
+    public function getWriteConcern()
+    {
+        return $this->writeConcern;
+    }
+
+    /**
+     * Inserts multiple documents.
+     *
+     * @see InsertMany::__construct() for supported options
+     * @see http://docs.mongodb.org/manual/reference/command/insert/
+     * @param array[]|object[] $documents The documents to insert
+     * @param array            $options   Command options
+     * @return InsertManyResult
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function insertMany(array $documents, array $options = [])
+    {
+        if ( ! isset($options['writeConcern'])) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        $operation = new InsertMany($this->databaseName, $this->collectionName, $documents, $options);
+        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Inserts one document.
+     *
+     * @see InsertOne::__construct() for supported options
+     * @see http://docs.mongodb.org/manual/reference/command/insert/
+     * @param array|object $document The document to insert
+     * @param array        $options  Command options
+     * @return InsertOneResult
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function insertOne($document, array $options = [])
+    {
+        if ( ! isset($options['writeConcern'])) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        $operation = new InsertOne($this->databaseName, $this->collectionName, $document, $options);
+        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Returns information for all indexes for the collection.
+     *
+     * @see ListIndexes::__construct() for supported options
+     * @return IndexInfoIterator
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function listIndexes(array $options = [])
+    {
+        $operation = new ListIndexes($this->databaseName, $this->collectionName, $options);
+        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Executes a map-reduce aggregation on the collection.
+     *
+     * @see MapReduce::__construct() for supported options
+     * @see http://docs.mongodb.org/manual/reference/command/mapReduce/
+     * @param JavascriptInterface $map            Map function
+     * @param JavascriptInterface $reduce         Reduce function
+     * @param string|array|object $out            Output specification
+     * @param array               $options        Command options
+     * @return MapReduceResult
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     * @throws UnexpectedValueException if the command response was malformed
+     */
+    public function mapReduce(JavascriptInterface $map, JavascriptInterface $reduce, $out, array $options = [])
+    {
+        $hasOutputCollection = ! \MongoDB\is_mapreduce_output_inline($out);
+
+        if ( ! isset($options['readPreference'])) {
+            $options['readPreference'] = $this->readPreference;
+        }
+
+        // Check if the out option is inline because we will want to coerce a primary read preference if not
+        if ($hasOutputCollection) {
+            $options['readPreference'] = new ReadPreference(ReadPreference::RP_PRIMARY);
+        }
+
+        $server = $this->manager->selectServer($options['readPreference']);
+
+        /* A "majority" read concern is not compatible with inline output, so
+         * avoid providing the Collection's read concern if it would conflict.
+         */
+        if ( ! isset($options['readConcern']) && ! ($hasOutputCollection && $this->readConcern->getLevel() === ReadConcern::MAJORITY) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
+            $options['readConcern'] = $this->readConcern;
+        }
+
+        if ( ! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        $operation = new MapReduce($this->databaseName, $this->collectionName, $map, $reduce, $out, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Replaces at most one document matching the filter.
+     *
+     * @see ReplaceOne::__construct() for supported options
+     * @see http://docs.mongodb.org/manual/reference/command/update/
+     * @param array|object $filter      Query by which to filter documents
+     * @param array|object $replacement Replacement document
+     * @param array        $options     Command options
+     * @return UpdateResult
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function replaceOne($filter, $replacement, array $options = [])
+    {
+        if ( ! isset($options['writeConcern'])) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        $operation = new ReplaceOne($this->databaseName, $this->collectionName, $filter, $replacement, $options);
+        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Updates all documents matching the filter.
+     *
+     * @see UpdateMany::__construct() for supported options
+     * @see http://docs.mongodb.org/manual/reference/command/update/
+     * @param array|object $filter  Query by which to filter documents
+     * @param array|object $update  Update to apply to the matched documents
+     * @param array        $options Command options
+     * @return UpdateResult
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function updateMany($filter, $update, array $options = [])
+    {
+        if ( ! isset($options['writeConcern'])) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        $operation = new UpdateMany($this->databaseName, $this->collectionName, $filter, $update, $options);
+        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Updates at most one document matching the filter.
+     *
+     * @see UpdateOne::__construct() for supported options
+     * @see http://docs.mongodb.org/manual/reference/command/update/
+     * @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 UpdateResult
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function updateOne($filter, $update, array $options = [])
+    {
+        if ( ! isset($options['writeConcern'])) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        $operation = new UpdateOne($this->databaseName, $this->collectionName, $filter, $update, $options);
+        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Create a change stream for watching changes to the collection.
+     *
+     * @see Watch::__construct() for supported options
+     * @param array $pipeline List of pipeline operations
+     * @param array $options  Command options
+     * @return ChangeStream
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function watch(array $pipeline = [], array $options = [])
+    {
+        if ( ! isset($options['readPreference'])) {
+            $options['readPreference'] = $this->readPreference;
+        }
+
+        $server = $this->manager->selectServer($options['readPreference']);
+
+        /* Although change streams require a newer version of the server than
+         * read concerns, perform the usual wire version check before inheriting
+         * the collection's read concern. In the event that the server is too
+         * old, this makes it more likely that users will encounter an error
+         * related to change streams being unsupported instead of an
+         * UnsupportedException regarding use of the "readConcern" option from
+         * the Aggregate operation class. */
+        if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
+            $options['readConcern'] = $this->readConcern;
+        }
+
+        if ( ! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        $operation = new Watch($this->manager, $this->databaseName, $this->collectionName, $pipeline, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Get a clone of this collection with different options.
+     *
+     * @see Collection::__construct() for supported options
+     * @param array $options Collection constructor options
+     * @return Collection
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function withOptions(array $options = [])
+    {
+        $options += [
+            'readConcern' => $this->readConcern,
+            'readPreference' => $this->readPreference,
+            'typeMap' => $this->typeMap,
+            'writeConcern' => $this->writeConcern,
+        ];
+
+        return new Collection($this->manager, $this->databaseName, $this->collectionName, $options);
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/Database.php b/cache/stores/mongodb/MongoDB/Database.php
new file mode 100755 (executable)
index 0000000..3b8b0e7
--- /dev/null
@@ -0,0 +1,463 @@
+<?php
+/*
+ * Copyright 2015-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB;
+
+use MongoDB\Collection;
+use MongoDB\Driver\Cursor;
+use MongoDB\Driver\Manager;
+use MongoDB\Driver\ReadConcern;
+use MongoDB\Driver\ReadPreference;
+use MongoDB\Driver\WriteConcern;
+use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
+use MongoDB\Exception\InvalidArgumentException;
+use MongoDB\Exception\UnsupportedException;
+use MongoDB\GridFS\Bucket;
+use MongoDB\Model\CollectionInfoIterator;
+use MongoDB\Operation\CreateCollection;
+use MongoDB\Operation\DatabaseCommand;
+use MongoDB\Operation\DropCollection;
+use MongoDB\Operation\DropDatabase;
+use MongoDB\Operation\ListCollections;
+use MongoDB\Operation\ModifyCollection;
+use MongoDB\Operation\Watch;
+
+class Database
+{
+    private static $defaultTypeMap = [
+        'array' => 'MongoDB\Model\BSONArray',
+        'document' => 'MongoDB\Model\BSONDocument',
+        'root' => 'MongoDB\Model\BSONDocument',
+    ];
+    private static $wireVersionForReadConcern = 4;
+    private static $wireVersionForWritableCommandWriteConcern = 5;
+
+    private $databaseName;
+    private $manager;
+    private $readConcern;
+    private $readPreference;
+    private $typeMap;
+    private $writeConcern;
+
+    /**
+     * Constructs new Database instance.
+     *
+     * This class provides methods for database-specific operations and serves
+     * as a gateway for accessing collections.
+     *
+     * Supported options:
+     *
+     *  * readConcern (MongoDB\Driver\ReadConcern): The default read concern to
+     *    use for database operations and selected collections. Defaults to the
+     *    Manager's read concern.
+     *
+     *  * readPreference (MongoDB\Driver\ReadPreference): The default read
+     *    preference to use for database operations and selected collections.
+     *    Defaults to the Manager's read preference.
+     *
+     *  * typeMap (array): Default type map for cursors and BSON documents.
+     *
+     *  * writeConcern (MongoDB\Driver\WriteConcern): The default write concern
+     *    to use for database operations and selected collections. Defaults to
+     *    the Manager's write concern.
+     *
+     * @param Manager $manager      Manager instance from the driver
+     * @param string  $databaseName Database name
+     * @param array   $options      Database options
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function __construct(Manager $manager, $databaseName, array $options = [])
+    {
+        if (strlen($databaseName) < 1) {
+            throw new InvalidArgumentException('$databaseName is invalid: ' . $databaseName);
+        }
+
+        if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
+            throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern');
+        }
+
+        if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
+            throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
+        }
+
+        if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
+            throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
+        }
+
+        if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
+            throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
+        }
+
+        $this->manager = $manager;
+        $this->databaseName = (string) $databaseName;
+        $this->readConcern = isset($options['readConcern']) ? $options['readConcern'] : $this->manager->getReadConcern();
+        $this->readPreference = isset($options['readPreference']) ? $options['readPreference'] : $this->manager->getReadPreference();
+        $this->typeMap = isset($options['typeMap']) ? $options['typeMap'] : self::$defaultTypeMap;
+        $this->writeConcern = isset($options['writeConcern']) ? $options['writeConcern'] : $this->manager->getWriteConcern();
+    }
+
+    /**
+     * Return internal properties for debugging purposes.
+     *
+     * @see http://php.net/manual/en/language.oop5.magic.php#language.oop5.magic.debuginfo
+     * @return array
+     */
+    public function __debugInfo()
+    {
+        return [
+            'databaseName' => $this->databaseName,
+            'manager' => $this->manager,
+            'readConcern' => $this->readConcern,
+            'readPreference' => $this->readPreference,
+            'typeMap' => $this->typeMap,
+            'writeConcern' => $this->writeConcern,
+        ];
+    }
+
+    /**
+     * Select a collection within this database.
+     *
+     * Note: collections whose names contain special characters (e.g. ".") may
+     * be selected with complex syntax (e.g. $database->{"system.profile"}) or
+     * {@link selectCollection()}.
+     *
+     * @see http://php.net/oop5.overloading#object.get
+     * @see http://php.net/types.string#language.types.string.parsing.complex
+     * @param string $collectionName Name of the collection to select
+     * @return Collection
+     */
+    public function __get($collectionName)
+    {
+        return $this->selectCollection($collectionName);
+    }
+
+    /**
+     * Return the database name.
+     *
+     * @return string
+     */
+    public function __toString()
+    {
+        return $this->databaseName;
+    }
+
+    /**
+     * Execute a command on this database.
+     *
+     * @see DatabaseCommand::__construct() for supported options
+     * @param array|object $command Command document
+     * @param array        $options Options for command execution
+     * @return Cursor
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function command($command, array $options = [])
+    {
+        if ( ! isset($options['readPreference'])) {
+            $options['readPreference'] = $this->readPreference;
+        }
+
+        if ( ! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        $operation = new DatabaseCommand($this->databaseName, $command, $options);
+        $server = $this->manager->selectServer($options['readPreference']);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Create a new collection explicitly.
+     *
+     * @see CreateCollection::__construct() for supported options
+     * @param string $collectionName
+     * @param array  $options
+     * @return array|object Command result document
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function createCollection($collectionName, array $options = [])
+    {
+        if ( ! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+        if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        $operation = new CreateCollection($this->databaseName, $collectionName, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Drop this database.
+     *
+     * @see DropDatabase::__construct() for supported options
+     * @param array $options Additional options
+     * @return array|object Command result document
+     * @throws UnsupportedException if options are unsupported on the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function drop(array $options = [])
+    {
+        if ( ! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+        if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        $operation = new DropDatabase($this->databaseName, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Drop a collection within this database.
+     *
+     * @see DropCollection::__construct() for supported options
+     * @param string $collectionName Collection name
+     * @param array  $options        Additional options
+     * @return array|object Command result document
+     * @throws UnsupportedException if options are unsupported on the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function dropCollection($collectionName, array $options = [])
+    {
+        if ( ! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+        if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        $operation = new DropCollection($this->databaseName, $collectionName, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Returns the database name.
+     *
+     * @return string
+     */
+    public function getDatabaseName()
+    {
+        return $this->databaseName;
+    }
+
+    /**
+     * Return the Manager.
+     *
+     * @return Manager
+     */
+    public function getManager()
+    {
+        return $this->manager;
+    }
+
+    /**
+     * Return the read concern for this database.
+     *
+     * @see http://php.net/manual/en/mongodb-driver-readconcern.isdefault.php
+     * @return ReadConcern
+     */
+    public function getReadConcern()
+    {
+        return $this->readConcern;
+    }
+
+    /**
+     * Return the read preference for this database.
+     *
+     * @return ReadPreference
+     */
+    public function getReadPreference()
+    {
+        return $this->readPreference;
+    }
+
+    /**
+     * Return the type map for this database.
+     *
+     * @return array
+     */
+    public function getTypeMap()
+    {
+        return $this->typeMap;
+    }
+
+    /**
+     * Return the write concern for this database.
+     *
+     * @see http://php.net/manual/en/mongodb-driver-writeconcern.isdefault.php
+     * @return WriteConcern
+     */
+    public function getWriteConcern()
+    {
+        return $this->writeConcern;
+    }
+
+    /**
+     * Returns information for all collections in this database.
+     *
+     * @see ListCollections::__construct() for supported options
+     * @param array $options
+     * @return CollectionInfoIterator
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function listCollections(array $options = [])
+    {
+        $operation = new ListCollections($this->databaseName, $options);
+        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Modifies a collection or view.
+     *
+     * @see ModifyCollection::__construct() for supported options
+     * @param string $collectionName    Collection or view to modify
+     * @param array  $collectionOptions Collection or view options to assign
+     * @param array  $options           Command options
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function modifyCollection($collectionName, array $collectionOptions, array $options = [])
+    {
+        if ( ! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
+
+        if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        $operation = new ModifyCollection($this->databaseName, $collectionName, $collectionOptions, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Select a collection within this database.
+     *
+     * @see Collection::__construct() for supported options
+     * @param string $collectionName Name of the collection to select
+     * @param array  $options        Collection constructor options
+     * @return Collection
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function selectCollection($collectionName, array $options = [])
+    {
+        $options += [
+            'readConcern' => $this->readConcern,
+            'readPreference' => $this->readPreference,
+            'typeMap' => $this->typeMap,
+            'writeConcern' => $this->writeConcern,
+        ];
+
+        return new Collection($this->manager, $this->databaseName, $collectionName, $options);
+    }
+
+    /**
+     * Select a GridFS bucket within this database.
+     *
+     * @see Bucket::__construct() for supported options
+     * @param array $options Bucket constructor options
+     * @return Bucket
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function selectGridFSBucket(array $options = [])
+    {
+        $options += [
+            'readConcern' => $this->readConcern,
+            'readPreference' => $this->readPreference,
+            'typeMap' => $this->typeMap,
+            'writeConcern' => $this->writeConcern,
+        ];
+
+        return new Bucket($this->manager, $this->databaseName, $options);
+    }
+
+    /**
+     * Create a change stream for watching changes to the database.
+     *
+     * @see Watch::__construct() for supported options
+     * @param array $pipeline List of pipeline operations
+     * @param array $options  Command options
+     * @return ChangeStream
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function watch(array $pipeline = [], array $options = [])
+    {
+        if ( ! isset($options['readPreference'])) {
+            $options['readPreference'] = $this->readPreference;
+        }
+
+        $server = $this->manager->selectServer($options['readPreference']);
+
+        if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
+            $options['readConcern'] = $this->readConcern;
+        }
+
+        if ( ! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        $operation = new Watch($this->manager, $this->databaseName, null, $pipeline, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Get a clone of this database with different options.
+     *
+     * @see Database::__construct() for supported options
+     * @param array $options Database constructor options
+     * @return Database
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function withOptions(array $options = [])
+    {
+        $options += [
+            'readConcern' => $this->readConcern,
+            'readPreference' => $this->readPreference,
+            'typeMap' => $this->typeMap,
+            'writeConcern' => $this->writeConcern,
+        ];
+
+        return new Database($this->manager, $this->databaseName, $options);
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/DeleteResult.php b/cache/stores/mongodb/MongoDB/DeleteResult.php
new file mode 100755 (executable)
index 0000000..a60137b
--- /dev/null
@@ -0,0 +1,72 @@
+<?php
+/*
+ * Copyright 2015-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB;
+
+use MongoDB\Driver\WriteResult;
+use MongoDB\Exception\BadMethodCallException;
+
+/**
+ * Result class for a delete operation.
+ */
+class DeleteResult
+{
+    private $writeResult;
+    private $isAcknowledged;
+
+    /**
+     * Constructor.
+     *
+     * @param WriteResult $writeResult
+     */
+    public function __construct(WriteResult $writeResult)
+    {
+        $this->writeResult = $writeResult;
+        $this->isAcknowledged = $writeResult->isAcknowledged();
+    }
+
+    /**
+     * Return the number of documents that were deleted.
+     *
+     * This method should only be called if the write was acknowledged.
+     *
+     * @see DeleteResult::isAcknowledged()
+     * @return integer
+     * @throws BadMethodCallException is the write result is unacknowledged
+     */
+    public function getDeletedCount()
+    {
+        if ($this->isAcknowledged) {
+            return $this->writeResult->getDeletedCount();
+        }
+
+        throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
+    }
+
+    /**
+     * Return whether this delete was acknowledged by the server.
+     *
+     * If the delete was not acknowledged, other fields from the WriteResult
+     * (e.g. deletedCount) will be undefined.
+     *
+     * @return boolean
+     */
+    public function isAcknowledged()
+    {
+        return $this->isAcknowledged;
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/Exception/BadMethodCallException.php b/cache/stores/mongodb/MongoDB/Exception/BadMethodCallException.php
new file mode 100755 (executable)
index 0000000..f1fe47a
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+/*
+ * Copyright 2015-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Exception;
+
+class BadMethodCallException extends \BadMethodCallException implements Exception
+{
+    /**
+     * Thrown when a mutable method is invoked on an immutable object.
+     *
+     * @param string $class Class name
+     * @return self
+     */
+    public static function classIsImmutable($class)
+    {
+        return new static(sprintf('%s is immutable', $class));
+    }
+
+    /**
+     * Thrown when accessing a result field on an unacknowledged write result.
+     *
+     * @param string $method Method name
+     * @return self
+     */
+    public static function unacknowledgedWriteResultAccess($method)
+    {
+        return new static(sprintf('%s should not be called for an unacknowledged write result', $method));
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/Exception/Exception.php b/cache/stores/mongodb/MongoDB/Exception/Exception.php
new file mode 100755 (executable)
index 0000000..703a44c
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+/*
+ * Copyright 2015-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Exception;
+
+interface Exception extends \MongoDB\Driver\Exception\Exception
+{
+}
diff --git a/cache/stores/mongodb/MongoDB/Exception/InvalidArgumentException.php b/cache/stores/mongodb/MongoDB/Exception/InvalidArgumentException.php
new file mode 100755 (executable)
index 0000000..622215a
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+/*
+ * Copyright 2015-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Exception;
+
+class InvalidArgumentException extends \MongoDB\Driver\Exception\InvalidArgumentException implements Exception
+{
+    /**
+     * Thrown when an argument or option has an invalid type.
+     *
+     * @param string $name         Name of the argument or option
+     * @param mixed  $value        Actual value (used to derive the type)
+     * @param string $expectedType Expected type
+     * @return self
+     */
+    public static function invalidType($name, $value, $expectedType)
+    {
+        return new static(sprintf('Expected %s to have type "%s" but found "%s"', $name, $expectedType, is_object($value) ? get_class($value) : gettype($value)));
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/Exception/ResumeTokenException.php b/cache/stores/mongodb/MongoDB/Exception/ResumeTokenException.php
new file mode 100755 (executable)
index 0000000..4aeb655
--- /dev/null
@@ -0,0 +1,42 @@
+<?php
+/*
+ * Copyright 2015-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Exception;
+
+class ResumeTokenException extends \Exception
+{
+    /**
+     * Thrown when a resume token has an invalid type.
+     *
+     * @param mixed $value Actual value (used to derive the type)
+     * @return self
+     */
+    public static function invalidType($value)
+    {
+        return new static(sprintf('Expected resume token to have type "array or object" but found "%s"', gettype($value)));
+    }
+
+    /**
+     * Thrown when a resume token is not found in a change document.
+     *
+     * @return self
+     */
+    public static function notFound()
+    {
+        return new static('Resume token not found in change document');
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/Exception/RuntimeException.php b/cache/stores/mongodb/MongoDB/Exception/RuntimeException.php
new file mode 100755 (executable)
index 0000000..4d8c0b8
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+/*
+ * Copyright 2015-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Exception;
+
+class RuntimeException extends \MongoDB\Driver\Exception\RuntimeException implements Exception
+{
+}
diff --git a/cache/stores/mongodb/MongoDB/Exception/UnexpectedValueException.php b/cache/stores/mongodb/MongoDB/Exception/UnexpectedValueException.php
new file mode 100755 (executable)
index 0000000..a65eaa7
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+/*
+ * Copyright 2015-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Exception;
+
+class UnexpectedValueException extends \MongoDB\Driver\Exception\UnexpectedValueException implements Exception
+{
+}
diff --git a/cache/stores/mongodb/MongoDB/Exception/UnsupportedException.php b/cache/stores/mongodb/MongoDB/Exception/UnsupportedException.php
new file mode 100755 (executable)
index 0000000..91f99b2
--- /dev/null
@@ -0,0 +1,71 @@
+<?php
+/*
+ * Copyright 2015-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Exception;
+
+class UnsupportedException extends RuntimeException
+{
+    /**
+     * Thrown when array filters are not supported by a server.
+     *
+     * @return self
+     */
+    public static function arrayFiltersNotSupported()
+    {
+        return new static('Array filters are not supported by the server executing this operation');
+    }
+
+    /**
+     * Thrown when collations are not supported by a server.
+     *
+     * @return self
+     */
+    public static function collationNotSupported()
+    {
+        return new static('Collations are not supported by the server executing this operation');
+    }
+
+    /**
+     * Thrown when explain is not supported by a server.
+     *
+     * @return self
+     */
+    public static function explainNotSupported()
+    {
+        return new static('Explain is not supported by the server executing this operation');
+    }
+
+    /**
+     * Thrown when a command's readConcern option is not supported by a server.
+     *
+     * @return self
+     */
+    public static function readConcernNotSupported()
+    {
+        return new static('Read concern is not supported by the server executing this command');
+    }
+
+    /**
+     * Thrown when a command's writeConcern option is not supported by a server.
+     *
+     * @return self
+     */
+    public static function writeConcernNotSupported()
+    {
+        return new static('Write concern is not supported by the server executing this command');
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/GridFS/Bucket.php b/cache/stores/mongodb/MongoDB/GridFS/Bucket.php
new file mode 100755 (executable)
index 0000000..243c331
--- /dev/null
@@ -0,0 +1,677 @@
+<?php
+/*
+ * Copyright 2016-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\GridFS;
+
+use MongoDB\Collection;
+use MongoDB\Driver\Cursor;
+use MongoDB\Driver\Manager;
+use MongoDB\Driver\ReadConcern;
+use MongoDB\Driver\ReadPreference;
+use MongoDB\Driver\WriteConcern;
+use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
+use MongoDB\Exception\InvalidArgumentException;
+use MongoDB\GridFS\Exception\CorruptFileException;
+use MongoDB\GridFS\Exception\FileNotFoundException;
+use MongoDB\Operation\Find;
+use stdClass;
+
+/**
+ * Bucket provides a public API for interacting with the GridFS files and chunks
+ * collections.
+ *
+ * @api
+ */
+class Bucket
+{
+    private static $defaultBucketName = 'fs';
+    private static $defaultChunkSizeBytes = 261120;
+    private static $defaultTypeMap = [
+        'array' => 'MongoDB\Model\BSONArray',
+        'document' => 'MongoDB\Model\BSONDocument',
+        'root' => 'MongoDB\Model\BSONDocument',
+    ];
+    private static $streamWrapperProtocol = 'gridfs';
+
+    private $collectionWrapper;
+    private $databaseName;
+    private $manager;
+    private $bucketName;
+    private $disableMD5;
+    private $chunkSizeBytes;
+    private $readConcern;
+    private $readPreference;
+    private $typeMap;
+    private $writeConcern;
+
+    /**
+     * Constructs a GridFS bucket.
+     *
+     * Supported options:
+     *
+     *  * bucketName (string): The bucket name, which will be used as a prefix
+     *    for the files and chunks collections. Defaults to "fs".
+     *
+     *  * chunkSizeBytes (integer): The chunk size in bytes. Defaults to
+     *    261120 (i.e. 255 KiB).
+     *
+     *  * disableMD5 (boolean): When true, no MD5 sum will be generated for
+     *    each stored file. Defaults to "false".
+     *
+     *  * readConcern (MongoDB\Driver\ReadConcern): Read concern.
+     *
+     *  * readPreference (MongoDB\Driver\ReadPreference): Read preference.
+     *
+     *  * typeMap (array): Default type map for cursors and BSON documents.
+     *
+     *  * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
+     *
+     * @param Manager $manager      Manager instance from the driver
+     * @param string  $databaseName Database name
+     * @param array   $options      Bucket options
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function __construct(Manager $manager, $databaseName, array $options = [])
+    {
+        $options += [
+            'bucketName' => self::$defaultBucketName,
+            'chunkSizeBytes' => self::$defaultChunkSizeBytes,
+            'disableMD5' => false,
+        ];
+
+        if (isset($options['bucketName']) && ! is_string($options['bucketName'])) {
+            throw InvalidArgumentException::invalidType('"bucketName" option', $options['bucketName'], 'string');
+        }
+
+        if (isset($options['chunkSizeBytes']) && ! is_integer($options['chunkSizeBytes'])) {
+            throw InvalidArgumentException::invalidType('"chunkSizeBytes" option', $options['chunkSizeBytes'], 'integer');
+        }
+
+        if (isset($options['chunkSizeBytes']) && $options['chunkSizeBytes'] < 1) {
+            throw new InvalidArgumentException(sprintf('Expected "chunkSizeBytes" option to be >= 1, %d given', $options['chunkSizeBytes']));
+        }
+
+        if (isset($options['disableMD5']) && ! is_bool($options['disableMD5'])) {
+            throw InvalidArgumentException::invalidType('"disableMD5" option', $options['disableMD5'], 'boolean');
+        }
+
+        if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
+            throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern');
+        }
+
+        if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
+            throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
+        }
+
+        if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
+            throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
+        }
+
+        if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
+            throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
+        }
+
+        $this->manager = $manager;
+        $this->databaseName = (string) $databaseName;
+        $this->bucketName = $options['bucketName'];
+        $this->chunkSizeBytes = $options['chunkSizeBytes'];
+        $this->disableMD5 = $options['disableMD5'];
+        $this->readConcern = isset($options['readConcern']) ? $options['readConcern'] : $this->manager->getReadConcern();
+        $this->readPreference = isset($options['readPreference']) ? $options['readPreference'] : $this->manager->getReadPreference();
+        $this->typeMap = isset($options['typeMap']) ? $options['typeMap'] : self::$defaultTypeMap;
+        $this->writeConcern = isset($options['writeConcern']) ? $options['writeConcern'] : $this->manager->getWriteConcern();
+
+        $collectionOptions = array_intersect_key($options, ['readConcern' => 1, 'readPreference' => 1, 'typeMap' => 1, 'writeConcern' => 1]);
+
+        $this->collectionWrapper = new CollectionWrapper($manager, $databaseName, $options['bucketName'], $collectionOptions);
+        $this->registerStreamWrapper();
+    }
+
+    /**
+     * Return internal properties for debugging purposes.
+     *
+     * @see http://php.net/manual/en/language.oop5.magic.php#language.oop5.magic.debuginfo
+     * @return array
+     */
+    public function __debugInfo()
+    {
+        return [
+            'bucketName' => $this->bucketName,
+            'databaseName' => $this->databaseName,
+            'manager' => $this->manager,
+            'chunkSizeBytes' => $this->chunkSizeBytes,
+            'readConcern' => $this->readConcern,
+            'readPreference' => $this->readPreference,
+            'typeMap' => $this->typeMap,
+            'writeConcern' => $this->writeConcern,
+        ];
+    }
+
+    /**
+     * Delete a file from the GridFS bucket.
+     *
+     * If the files collection document is not found, this method will still
+     * attempt to delete orphaned chunks.
+     *
+     * @param mixed $id File ID
+     * @throws FileNotFoundException if no file could be selected
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function delete($id)
+    {
+        $file = $this->collectionWrapper->findFileById($id);
+        $this->collectionWrapper->deleteFileAndChunksById($id);
+
+        if ($file === null) {
+            throw FileNotFoundException::byId($id, $this->getFilesNamespace());
+        }
+    }
+
+    /**
+     * Writes the contents of a GridFS file to a writable stream.
+     *
+     * @param mixed    $id          File ID
+     * @param resource $destination Writable Stream
+     * @throws FileNotFoundException if no file could be selected
+     * @throws InvalidArgumentException if $destination is not a stream
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function downloadToStream($id, $destination)
+    {
+        if ( ! is_resource($destination) || get_resource_type($destination) != "stream") {
+            throw InvalidArgumentException::invalidType('$destination', $destination, 'resource');
+        }
+
+        stream_copy_to_stream($this->openDownloadStream($id), $destination);
+    }
+
+    /**
+     * Writes the contents of a GridFS file, which is selected by name and
+     * revision, to a writable stream.
+     *
+     * Supported options:
+     *
+     *  * revision (integer): Which revision (i.e. documents with the same
+     *    filename and different uploadDate) of the file to retrieve. Defaults
+     *    to -1 (i.e. the most recent revision).
+     *
+     * Revision numbers are defined as follows:
+     *
+     *  * 0 = the original stored file
+     *  * 1 = the first revision
+     *  * 2 = the second revision
+     *  * etc…
+     *  * -2 = the second most recent revision
+     *  * -1 = the most recent revision
+     *
+     * @param string   $filename    Filename
+     * @param resource $destination Writable Stream
+     * @param array    $options     Download options
+     * @throws FileNotFoundException if no file could be selected
+     * @throws InvalidArgumentException if $destination is not a stream
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function downloadToStreamByName($filename, $destination, array $options = [])
+    {
+        if ( ! is_resource($destination) || get_resource_type($destination) != "stream") {
+            throw InvalidArgumentException::invalidType('$destination', $destination, 'resource');
+        }
+
+        stream_copy_to_stream($this->openDownloadStreamByName($filename, $options), $destination);
+    }
+
+    /**
+     * Drops the files and chunks collections associated with this GridFS
+     * bucket.
+     *
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function drop()
+    {
+        $this->collectionWrapper->dropCollections();
+    }
+
+    /**
+     * Finds documents from the GridFS bucket's files collection matching the
+     * query.
+     *
+     * @see Find::__construct() for supported options
+     * @param array|object $filter  Query by which to filter documents
+     * @param array        $options Additional options
+     * @return Cursor
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function find($filter = [], array $options = [])
+    {
+        return $this->collectionWrapper->findFiles($filter, $options);
+    }
+
+    /**
+     * Finds a single document from the GridFS bucket's files collection
+     * matching the query.
+     *
+     * @see FindOne::__construct() for supported options
+     * @param array|object $filter  Query by which to filter documents
+     * @param array        $options Additional options
+     * @return array|object|null
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function findOne($filter = [], array $options = [])
+    {
+        return $this->collectionWrapper->findOneFile($filter, $options);
+    }
+
+    /**
+     * Return the bucket name.
+     *
+     * @return string
+     */
+    public function getBucketName()
+    {
+        return $this->bucketName;
+    }
+
+    /**
+     * Return the chunks collection.
+     *
+     * @return Collection
+     */
+    public function getChunksCollection()
+    {
+        return $this->collectionWrapper->getChunksCollection();
+    }
+
+    /**
+     * Return the chunk size in bytes.
+     *
+     * @return integer
+     */
+    public function getChunkSizeBytes()
+    {
+        return $this->chunkSizeBytes;
+    }
+
+    /**
+     * Return the database name.
+     *
+     * @return string
+     */
+    public function getDatabaseName()
+    {
+        return $this->databaseName;
+    }
+
+    /**
+     * Gets the file document of the GridFS file associated with a stream.
+     *
+     * @param resource $stream GridFS stream
+     * @return array|object
+     * @throws InvalidArgumentException if $stream is not a GridFS stream
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function getFileDocumentForStream($stream)
+    {
+        $file = $this->getRawFileDocumentForStream($stream);
+
+        // Filter the raw document through the specified type map
+        return \MongoDB\apply_type_map_to_document($file, $this->typeMap);
+    }
+
+    /**
+     * Gets the file document's ID of the GridFS file associated with a stream.
+     *
+     * @param resource $stream GridFS stream
+     * @return mixed
+     * @throws CorruptFileException if the file "_id" field does not exist
+     * @throws InvalidArgumentException if $stream is not a GridFS stream
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function getFileIdForStream($stream)
+    {
+        $file = $this->getRawFileDocumentForStream($stream);
+
+        /* Filter the raw document through the specified type map, but override
+         * the root type so we can reliably access the ID.
+         */
+        $typeMap = ['root' => 'stdClass'] + $this->typeMap;
+        $file = \MongoDB\apply_type_map_to_document($file, $typeMap);
+
+        if ( ! isset($file->_id) && ! property_exists($file, '_id')) {
+            throw new CorruptFileException('file._id does not exist');
+        }
+
+        return $file->_id;
+    }
+
+    /**
+     * Return the files collection.
+     *
+     * @return Collection
+     */
+    public function getFilesCollection()
+    {
+        return $this->collectionWrapper->getFilesCollection();
+    }
+
+    /**
+     * Return the read concern for this GridFS bucket.
+     *
+     * @see http://php.net/manual/en/mongodb-driver-readconcern.isdefault.php
+     * @return ReadConcern
+     */
+    public function getReadConcern()
+    {
+        return $this->readConcern;
+    }
+
+    /**
+     * Return the read preference for this GridFS bucket.
+     *
+     * @return ReadPreference
+     */
+    public function getReadPreference()
+    {
+        return $this->readPreference;
+    }
+
+    /**
+     * Return the type map for this GridFS bucket.
+     *
+     * @return array
+     */
+    public function getTypeMap()
+    {
+        return $this->typeMap;
+    }
+
+    /**
+     * Return the write concern for this GridFS bucket.
+     *
+     * @see http://php.net/manual/en/mongodb-driver-writeconcern.isdefault.php
+     * @return WriteConcern
+     */
+    public function getWriteConcern()
+    {
+        return $this->writeConcern;
+    }
+
+    /**
+     * Opens a readable stream for reading a GridFS file.
+     *
+     * @param mixed $id File ID
+     * @return resource
+     * @throws FileNotFoundException if no file could be selected
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function openDownloadStream($id)
+    {
+        $file = $this->collectionWrapper->findFileById($id);
+
+        if ($file === null) {
+            throw FileNotFoundException::byId($id, $this->getFilesNamespace());
+        }
+
+        return $this->openDownloadStreamByFile($file);
+    }
+
+    /**
+     * Opens a readable stream stream to read a GridFS file, which is selected
+     * by name and revision.
+     *
+     * Supported options:
+     *
+     *  * revision (integer): Which revision (i.e. documents with the same
+     *    filename and different uploadDate) of the file to retrieve. Defaults
+     *    to -1 (i.e. the most recent revision).
+     *
+     * Revision numbers are defined as follows:
+     *
+     *  * 0 = the original stored file
+     *  * 1 = the first revision
+     *  * 2 = the second revision
+     *  * etc…
+     *  * -2 = the second most recent revision
+     *  * -1 = the most recent revision
+     *
+     * @param string $filename Filename
+     * @param array  $options  Download options
+     * @return resource
+     * @throws FileNotFoundException if no file could be selected
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function openDownloadStreamByName($filename, array $options = [])
+    {
+        $options += ['revision' => -1];
+
+        $file = $this->collectionWrapper->findFileByFilenameAndRevision($filename, $options['revision']);
+
+        if ($file === null) {
+            throw FileNotFoundException::byFilenameAndRevision($filename, $options['revision'], $this->getFilesNamespace());
+        }
+
+        return $this->openDownloadStreamByFile($file);
+    }
+
+    /**
+     * Opens a writable stream for writing a GridFS file.
+     *
+     * Supported options:
+     *
+     *  * _id (mixed): File document identifier. Defaults to a new ObjectId.
+     *
+     *  * chunkSizeBytes (integer): The chunk size in bytes. Defaults to the
+     *    bucket's chunk size.
+     *
+     *  * disableMD5 (boolean): When true, no MD5 sum will be generated for
+     *    the stored file. Defaults to "false".
+     *
+     *  * metadata (document): User data for the "metadata" field of the files
+     *    collection document.
+     *
+     * @param string $filename Filename
+     * @param array  $options  Upload options
+     * @return resource
+     */
+    public function openUploadStream($filename, array $options = [])
+    {
+        $options += ['chunkSizeBytes' => $this->chunkSizeBytes];
+
+        $path = $this->createPathForUpload();
+        $context = stream_context_create([
+            self::$streamWrapperProtocol => [
+                'collectionWrapper' => $this->collectionWrapper,
+                'filename' => $filename,
+                'options' => $options,
+            ],
+        ]);
+
+        return fopen($path, 'w', false, $context);
+    }
+
+    /**
+     * Renames the GridFS file with the specified ID.
+     *
+     * @param mixed  $id          File ID
+     * @param string $newFilename New filename
+     * @throws FileNotFoundException if no file could be selected
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function rename($id, $newFilename)
+    {
+        $updateResult = $this->collectionWrapper->updateFilenameForId($id, $newFilename);
+
+        if ($updateResult->getModifiedCount() === 1) {
+            return;
+        }
+
+        /* If the update resulted in no modification, it's possible that the
+         * file did not exist, in which case we must raise an error. Checking
+         * the write result's matched count will be most efficient, but fall
+         * back to a findOne operation if necessary (i.e. legacy writes).
+         */
+        $found = $updateResult->getMatchedCount() !== null
+            ? $updateResult->getMatchedCount() === 1
+            : $this->collectionWrapper->findFileById($id) !== null;
+
+        if ( ! $found) {
+            throw FileNotFoundException::byId($id, $this->getFilesNamespace());
+        }
+    }
+
+    /**
+     * Writes the contents of a readable stream to a GridFS file.
+     *
+     * Supported options:
+     *
+     *  * _id (mixed): File document identifier. Defaults to a new ObjectId.
+     *
+     *  * chunkSizeBytes (integer): The chunk size in bytes. Defaults to the
+     *    bucket's chunk size.
+     *
+     *  * disableMD5 (boolean): When true, no MD5 sum will be generated for
+     *    the stored file. Defaults to "false".
+     *
+     *  * metadata (document): User data for the "metadata" field of the files
+     *    collection document.
+     *
+     * @param string   $filename Filename
+     * @param resource $source   Readable stream
+     * @param array    $options  Stream options
+     * @return mixed ID of the newly created GridFS file
+     * @throws InvalidArgumentException if $source is not a GridFS stream
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function uploadFromStream($filename, $source, array $options = [])
+    {
+        if ( ! is_resource($source) || get_resource_type($source) != "stream") {
+            throw InvalidArgumentException::invalidType('$source', $source, 'resource');
+        }
+
+        $destination = $this->openUploadStream($filename, $options);
+        stream_copy_to_stream($source, $destination);
+
+        return $this->getFileIdForStream($destination);
+    }
+
+    /**
+     * Creates a path for an existing GridFS file.
+     *
+     * @param stdClass $file GridFS file document
+     * @return string
+     */
+    private function createPathForFile(stdClass $file)
+    {
+        if ( ! is_object($file->_id) || method_exists($file->_id, '__toString')) {
+            $id = (string) $file->_id;
+        } else {
+            $id = \MongoDB\BSON\toJSON(\MongoDB\BSON\fromPHP(['_id' => $file->_id]));
+        }
+
+        return sprintf(
+            '%s://%s/%s.files/%s',
+            self::$streamWrapperProtocol,
+            urlencode($this->databaseName),
+            urlencode($this->bucketName),
+            urlencode($id)
+        );
+    }
+
+    /**
+     * Creates a path for a new GridFS file, which does not yet have an ID.
+     *
+     * @return string
+     */
+    private function createPathForUpload()
+    {
+        return sprintf(
+            '%s://%s/%s.files',
+            self::$streamWrapperProtocol,
+            urlencode($this->databaseName),
+            urlencode($this->bucketName)
+        );
+    }
+
+    /**
+     * Returns the names of the files collection.
+     *
+     * @return string
+     */
+    private function getFilesNamespace()
+    {
+        return sprintf('%s.%s.files', $this->databaseName, $this->bucketName);
+    }
+
+    /**
+     * Gets the file document of the GridFS file associated with a stream.
+     *
+     * This returns the raw document from the StreamWrapper, which does not
+     * respect the Bucket's type map.
+     *
+     * @param resource $stream GridFS stream
+     * @return stdClass
+     * @throws InvalidArgumentException
+     */
+    private function getRawFileDocumentForStream($stream)
+    {
+        if ( ! is_resource($stream) || get_resource_type($stream) != "stream") {
+            throw InvalidArgumentException::invalidType('$stream', $stream, 'resource');
+        }
+
+        $metadata = stream_get_meta_data($stream);
+
+        if ( ! isset ($metadata['wrapper_data']) || ! $metadata['wrapper_data'] instanceof StreamWrapper) {
+            throw InvalidArgumentException::invalidType('$stream wrapper data', isset($metadata['wrapper_data']) ? $metadata['wrapper_data'] : null, 'MongoDB\Driver\GridFS\StreamWrapper');
+        }
+
+        return $metadata['wrapper_data']->getFile();
+    }
+
+    /**
+     * Opens a readable stream for the GridFS file.
+     *
+     * @param stdClass $file GridFS file document
+     * @return resource
+     */
+    private function openDownloadStreamByFile(stdClass $file)
+    {
+        $path = $this->createPathForFile($file);
+        $context = stream_context_create([
+            self::$streamWrapperProtocol => [
+                'collectionWrapper' => $this->collectionWrapper,
+                'file' => $file,
+            ],
+        ]);
+
+        return fopen($path, 'r', false, $context);
+    }
+
+    /**
+     * Registers the GridFS stream wrapper if it is not already registered.
+     */
+    private function registerStreamWrapper()
+    {
+        if (in_array(self::$streamWrapperProtocol, stream_get_wrappers())) {
+            return;
+        }
+
+        StreamWrapper::register(self::$streamWrapperProtocol);
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/GridFS/CollectionWrapper.php b/cache/stores/mongodb/MongoDB/GridFS/CollectionWrapper.php
new file mode 100755 (executable)
index 0000000..754fd4b
--- /dev/null
@@ -0,0 +1,338 @@
+<?php
+/*
+ * Copyright 2016-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\GridFS;
+
+use MongoDB\Collection;
+use MongoDB\UpdateResult;
+use MongoDB\Driver\Cursor;
+use MongoDB\Driver\Manager;
+use MongoDB\Driver\ReadPreference;
+use stdClass;
+
+/**
+ * CollectionWrapper abstracts the GridFS files and chunks collections.
+ *
+ * @internal
+ */
+class CollectionWrapper
+{
+    private $bucketName;
+    private $chunksCollection;
+    private $databaseName;
+    private $checkedIndexes = false;
+    private $filesCollection;
+
+    /**
+     * Constructs a GridFS collection wrapper.
+     *
+     * @see Collection::__construct() for supported options
+     * @param Manager $manager           Manager instance from the driver
+     * @param string  $databaseName      Database name
+     * @param string  $bucketName        Bucket name
+     * @param array   $collectionOptions Collection options
+     * @throws InvalidArgumentException
+     */
+    public function __construct(Manager $manager, $databaseName, $bucketName, array $collectionOptions = [])
+    {
+        $this->databaseName = (string) $databaseName;
+        $this->bucketName = (string) $bucketName;
+
+        $this->filesCollection = new Collection($manager, $databaseName, sprintf('%s.files', $bucketName), $collectionOptions);
+        $this->chunksCollection = new Collection($manager, $databaseName, sprintf('%s.chunks', $bucketName), $collectionOptions);
+    }
+
+    /**
+     * Deletes all GridFS chunks for a given file ID.
+     *
+     * @param mixed $id
+     */
+    public function deleteChunksByFilesId($id)
+    {
+        $this->chunksCollection->deleteMany(['files_id' => $id]);
+    }
+
+    /**
+     * Deletes a GridFS file and related chunks by ID.
+     *
+     * @param mixed $id
+     */
+    public function deleteFileAndChunksById($id)
+    {
+        $this->filesCollection->deleteOne(['_id' => $id]);
+        $this->chunksCollection->deleteMany(['files_id' => $id]);
+    }
+
+    /**
+     * Drops the GridFS files and chunks collections.
+     */
+    public function dropCollections()
+    {
+        $this->filesCollection->drop(['typeMap' => []]);
+        $this->chunksCollection->drop(['typeMap' => []]);
+    }
+
+    /**
+     * Finds GridFS chunk documents for a given file ID and optional offset.
+     *
+     * @param mixed   $id        File ID
+     * @param integer $fromChunk Starting chunk (inclusive)
+     * @return Cursor
+     */
+    public function findChunksByFileId($id, $fromChunk = 0)
+    {
+        return $this->chunksCollection->find(
+            [
+                'files_id' => $id,
+                'n' => ['$gte' => $fromChunk],
+            ],
+            [
+                'sort' => ['n' => 1],
+                'typeMap' => ['root' => 'stdClass'],
+            ]
+        );
+    }
+
+    /**
+     * Finds a GridFS file document for a given filename and revision.
+     *
+     * Revision numbers are defined as follows:
+     *
+     *  * 0 = the original stored file
+     *  * 1 = the first revision
+     *  * 2 = the second revision
+     *  * etc…
+     *  * -2 = the second most recent revision
+     *  * -1 = the most recent revision
+     *
+     * @see Bucket::downloadToStreamByName()
+     * @see Bucket::openDownloadStreamByName()
+     * @param string $filename
+     * @param integer $revision
+     * @return stdClass|null
+     */
+    public function findFileByFilenameAndRevision($filename, $revision)
+    {
+        $filename = (string) $filename;
+        $revision = (integer) $revision;
+
+        if ($revision < 0) {
+            $skip = abs($revision) - 1;
+            $sortOrder = -1;
+        } else {
+            $skip = $revision;
+            $sortOrder = 1;
+        }
+
+        return $this->filesCollection->findOne(
+            ['filename' => $filename],
+            [
+                'skip' => $skip,
+                'sort' => ['uploadDate' => $sortOrder],
+                'typeMap' => ['root' => 'stdClass'],
+            ]
+        );
+    }
+
+    /**
+     * Finds a GridFS file document for a given ID.
+     *
+     * @param mixed $id
+     * @return stdClass|null
+     */
+    public function findFileById($id)
+    {
+        return $this->filesCollection->findOne(
+            ['_id' => $id],
+            ['typeMap' => ['root' => 'stdClass']]
+        );
+    }
+
+    /**
+     * Finds documents from the GridFS bucket's files collection.
+     *
+     * @see Find::__construct() for supported options
+     * @param array|object $filter  Query by which to filter documents
+     * @param array        $options Additional options
+     * @return Cursor
+     */
+    public function findFiles($filter, array $options = [])
+    {
+        return $this->filesCollection->find($filter, $options);
+    }
+
+    /**
+     * Finds a single document from the GridFS bucket's files collection.
+     *
+     * @param array|object $filter  Query by which to filter documents
+     * @param array        $options Additional options
+     * @return array|object|null
+     */
+    public function findOneFile($filter, array $options = [])
+    {
+        return $this->filesCollection->findOne($filter, $options);
+    }
+
+    /**
+     * Return the bucket name.
+     *
+     * @return string
+     */
+    public function getBucketName()
+    {
+        return $this->bucketName;
+    }
+
+    /**
+     * Return the chunks collection.
+     *
+     * @return Collection
+     */
+    public function getChunksCollection()
+    {
+        return $this->chunksCollection;
+    }
+
+    /**
+     * Return the database name.
+     *
+     * @return string
+     */
+    public function getDatabaseName()
+    {
+        return $this->databaseName;
+    }
+
+    /**
+     * Return the files collection.
+     *
+     * @return Collection
+     */
+    public function getFilesCollection()
+    {
+        return $this->filesCollection;
+    }
+
+    /**
+     * Inserts a document into the chunks collection.
+     *
+     * @param array|object $chunk Chunk document
+     */
+    public function insertChunk($chunk)
+    {
+        if ( ! $this->checkedIndexes) {
+            $this->ensureIndexes();
+        }
+
+        $this->chunksCollection->insertOne($chunk);
+    }
+
+    /**
+     * Inserts a document into the files collection.
+     *
+     * The file document should be inserted after all chunks have been inserted.
+     *
+     * @param array|object $file File document
+     */
+    public function insertFile($file)
+    {
+        if ( ! $this->checkedIndexes) {
+            $this->ensureIndexes();
+        }
+
+        $this->filesCollection->insertOne($file);
+    }
+
+    /**
+     * Updates the filename field in the file document for a given ID.
+     *
+     * @param mixed  $id
+     * @param string $filename 
+     * @return UpdateResult
+     */
+    public function updateFilenameForId($id, $filename)
+    {
+        return $this->filesCollection->updateOne(
+            ['_id' => $id],
+            ['$set' => ['filename' => (string) $filename]]
+        );
+    }
+
+    /**
+     * Create an index on the chunks collection if it does not already exist.
+     */
+    private function ensureChunksIndex()
+    {
+        foreach ($this->chunksCollection->listIndexes() as $index) {
+            if ($index->isUnique() && $index->getKey() === ['files_id' => 1, 'n' => 1]) {
+                return;
+            }
+        }
+
+        $this->chunksCollection->createIndex(['files_id' => 1, 'n' => 1], ['unique' => true]);
+    }
+
+    /**
+     * Create an index on the files collection if it does not already exist.
+     */
+    private function ensureFilesIndex()
+    {
+        foreach ($this->filesCollection->listIndexes() as $index) {
+            if ($index->getKey() === ['filename' => 1, 'uploadDate' => 1]) {
+                return;
+            }
+        }
+
+        $this->filesCollection->createIndex(['filename' => 1, 'uploadDate' => 1]);
+    }
+
+    /**
+     * Ensure indexes on the files and chunks collections exist.
+     *
+     * This method is called once before the first write operation on a GridFS
+     * bucket. Indexes are only be created if the files collection is empty.
+     */
+    private function ensureIndexes()
+    {
+        if ($this->checkedIndexes) {
+            return;
+        }
+
+        $this->checkedIndexes = true;
+
+        if ( ! $this->isFilesCollectionEmpty()) {
+            return;
+        }
+
+        $this->ensureFilesIndex();
+        $this->ensureChunksIndex();
+    }
+
+    /**
+     * Returns whether the files collection is empty.
+     *
+     * @return boolean
+     */
+    private function isFilesCollectionEmpty()
+    {
+        return null === $this->filesCollection->findOne([], [
+            'readPreference' => new ReadPreference(ReadPreference::RP_PRIMARY),
+            'projection' => ['_id' => 1],
+            'typeMap' => [],
+        ]);
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/GridFS/Exception/CorruptFileException.php b/cache/stores/mongodb/MongoDB/GridFS/Exception/CorruptFileException.php
new file mode 100755 (executable)
index 0000000..787c9b8
--- /dev/null
@@ -0,0 +1,58 @@
+<?php
+/*
+ * Copyright 2016-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\GridFS\Exception;
+
+use MongoDB\Exception\RuntimeException;
+
+class CorruptFileException extends RuntimeException
+{
+    /**
+     * Thrown when a chunk is not found for an expected index.
+     *
+     * @param integer $expectedIndex Expected index number
+     * @return self
+     */
+    public static function missingChunk($expectedIndex)
+    {
+        return new static(sprintf('Chunk not found for index "%d"', $expectedIndex));
+    }
+
+    /**
+     * Thrown when a chunk has an unexpected index number.
+     *
+     * @param integer $index         Actual index number (i.e. "n" field)
+     * @param integer $expectedIndex Expected index number
+     * @return self
+     */
+    public static function unexpectedIndex($index, $expectedIndex)
+    {
+        return new static(sprintf('Expected chunk to have index "%d" but found "%d"', $expectedIndex, $index));
+    }
+
+    /**
+     * Thrown when a chunk has an unexpected data size.
+     *
+     * @param integer $size         Actual size (i.e. "data" field length)
+     * @param integer $expectedSize Expected size
+     * @return self
+     */
+    public static function unexpectedSize($size, $expectedSize)
+    {
+        return new static(sprintf('Expected chunk to have size "%d" but found "%d"', $expectedSize, $size));
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/GridFS/Exception/FileNotFoundException.php b/cache/stores/mongodb/MongoDB/GridFS/Exception/FileNotFoundException.php
new file mode 100755 (executable)
index 0000000..2d3d037
--- /dev/null
@@ -0,0 +1,50 @@
+<?php
+/*
+ * Copyright 2016-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\GridFS\Exception;
+
+use MongoDB\Exception\RuntimeException;
+
+class FileNotFoundException extends RuntimeException
+{
+    /**
+     * Thrown when a file cannot be found by its filename and revision.
+     *
+     * @param string  $filename  Filename
+     * @param integer $revision  Revision
+     * @param string  $namespace Namespace for the files collection
+     * @return self
+     */
+    public static function byFilenameAndRevision($filename, $revision, $namespace)
+    {
+        return new static(sprintf('File with name "%s" and revision "%d" not found in "%s"', $filename, $revision, $namespace));
+    }
+
+    /**
+     * Thrown when a file cannot be found by its ID.
+     *
+     * @param mixed  $id        File ID
+     * @param string $namespace Namespace for the files collection
+     * @return self
+     */
+    public static function byId($id, $namespace)
+    {
+        $json = \MongoDB\BSON\toJSON(\MongoDB\BSON\fromPHP(['_id' => $id]));
+
+        return new static(sprintf('File "%s" not found in "%s"', $json, $namespace));
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/GridFS/ReadableStream.php b/cache/stores/mongodb/MongoDB/GridFS/ReadableStream.php
new file mode 100755 (executable)
index 0000000..d49b213
--- /dev/null
@@ -0,0 +1,296 @@
+<?php
+/*
+ * Copyright 2016-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\GridFS;
+
+use MongoDB\Exception\InvalidArgumentException;
+use MongoDB\GridFS\Exception\CorruptFileException;
+use IteratorIterator;
+use stdClass;
+
+/**
+ * ReadableStream abstracts the process of reading a GridFS file.
+ *
+ * @internal
+ */
+class ReadableStream
+{
+    private $buffer;
+    private $bufferOffset = 0;
+    private $chunkSize;
+    private $chunkOffset = 0;
+    private $chunksIterator;
+    private $collectionWrapper;
+    private $expectedLastChunkSize = 0;
+    private $file;
+    private $length;
+    private $numChunks = 0;
+
+    /**
+     * Constructs a readable GridFS stream.
+     *
+     * @param CollectionWrapper $collectionWrapper GridFS collection wrapper
+     * @param stdClass          $file              GridFS file document
+     * @throws CorruptFileException
+     */
+    public function __construct(CollectionWrapper $collectionWrapper, stdClass $file)
+    {
+        if ( ! isset($file->chunkSize) || ! is_integer($file->chunkSize) || $file->chunkSize < 1) {
+            throw new CorruptFileException('file.chunkSize is not an integer >= 1');
+        }
+
+        if ( ! isset($file->length) || ! is_integer($file->length) || $file->length < 0) {
+            throw new CorruptFileException('file.length is not an integer > 0');
+        }
+
+        if ( ! isset($file->_id) && ! property_exists($file, '_id')) {
+            throw new CorruptFileException('file._id does not exist');
+        }
+
+        $this->file = $file;
+        $this->chunkSize = (integer) $file->chunkSize;
+        $this->length = (integer) $file->length;
+
+        $this->collectionWrapper = $collectionWrapper;
+
+        if ($this->length > 0) {
+            $this->numChunks = (integer) ceil($this->length / $this->chunkSize);
+            $this->expectedLastChunkSize = ($this->length - (($this->numChunks - 1) * $this->chunkSize));
+        }
+    }
+
+    /**
+     * Return internal properties for debugging purposes.
+     *
+     * @see http://php.net/manual/en/language.oop5.magic.php#language.oop5.magic.debuginfo
+     * @return array
+     */
+    public function __debugInfo()
+    {
+        return [
+            'bucketName' => $this->collectionWrapper->getBucketName(),
+            'databaseName' => $this->collectionWrapper->getDatabaseName(),
+            'file' => $this->file,
+        ];
+    }
+
+    public function close()
+    {
+        // Nothing to do
+    }
+
+    /**
+     * Return the stream's file document.
+     *
+     * @return stdClass
+     */
+    public function getFile()
+    {
+        return $this->file;
+    }
+
+    /**
+     * Return the stream's size in bytes.
+     *
+     * @return integer
+     */
+    public function getSize()
+    {
+        return $this->length;
+    }
+
+    /**
+     * Return whether the current read position is at the end of the stream.
+     *
+     * @return boolean
+     */
+    public function isEOF()
+    {
+        if ($this->chunkOffset === $this->numChunks - 1) {
+            return $this->bufferOffset >= $this->expectedLastChunkSize;
+        }
+
+        return $this->chunkOffset >= $this->numChunks;
+    }
+
+    /**
+     * Read bytes from the stream.
+     *
+     * Note: this method may return a string smaller than the requested length
+     * if data is not available to be read.
+     *
+     * @param integer $length Number of bytes to read
+     * @return string
+     * @throws InvalidArgumentException if $length is negative
+     */
+    public function readBytes($length)
+    {
+        if ($length < 0) {
+            throw new InvalidArgumentException(sprintf('$length must be >= 0; given: %d', $length));
+        }
+
+        if ($this->chunksIterator === null) {
+            $this->initChunksIterator();
+        }
+
+        if ($this->buffer === null && ! $this->initBufferFromCurrentChunk()) {
+            return '';
+        }
+
+        $data = '';
+
+        while (strlen($data) < $length) {
+            if ($this->bufferOffset >= strlen($this->buffer) && ! $this->initBufferFromNextChunk()) {
+                break;
+            }
+
+            $initialDataLength = strlen($data);
+            $data .= substr($this->buffer, $this->bufferOffset, $length - $initialDataLength);
+            $this->bufferOffset += strlen($data) - $initialDataLength;
+        }
+
+        return $data;
+    }
+
+    /**
+     * Seeks the chunk and buffer offsets for the next read operation.
+     *
+     * @param integer $offset
+     * @throws InvalidArgumentException if $offset is out of range
+     */
+    public function seek($offset)
+    {
+        if ($offset < 0 || $offset > $this->file->length) {
+            throw new InvalidArgumentException(sprintf('$offset must be >= 0 and <= %d; given: %d', $this->file->length, $offset));
+        }
+
+        /* Compute the offsets for the chunk and buffer (i.e. chunk data) from
+         * which we will expect to read after seeking. If the chunk offset
+         * changed, we'll also need to reset the buffer.
+         */
+        $lastChunkOffset = $this->chunkOffset;
+        $this->chunkOffset = (integer) floor($offset / $this->chunkSize);
+        $this->bufferOffset = $offset % $this->chunkSize;
+
+        if ($lastChunkOffset === $this->chunkOffset) {
+            return;
+        }
+
+        if ($this->chunksIterator === null) {
+            return;
+        }
+
+        // Clear the buffer since the current chunk will be changed
+        $this->buffer = null;
+
+        /* If we are seeking to a previous chunk, we need to reinitialize the
+         * chunk iterator.
+         */
+        if ($lastChunkOffset > $this->chunkOffset) {
+            $this->chunksIterator = null;
+            return;
+        }
+
+        /* If we are seeking to a subsequent chunk, we do not need to
+         * reinitalize the chunk iterator. Instead, we can simply move forward
+         * to $this->chunkOffset.
+         */
+        $numChunks = $this->chunkOffset - $lastChunkOffset;
+        for ($i = 0; $i < $numChunks; $i++) {
+            $this->chunksIterator->next();
+        }
+    }
+
+    /**
+     * Return the current position of the stream.
+     *
+     * This is the offset within the stream where the next byte would be read.
+     *
+     * @return integer
+     */
+    public function tell()
+    {
+        return ($this->chunkOffset * $this->chunkSize) + $this->bufferOffset;
+    }
+
+    /**
+     * Initialize the buffer to the current chunk's data.
+     *
+     * @return boolean Whether there was a current chunk to read
+     * @throws CorruptFileException if an expected chunk could not be read successfully
+     */
+    private function initBufferFromCurrentChunk()
+    {
+        if ($this->chunkOffset === 0 && $this->numChunks === 0) {
+            return false;
+        }
+
+        if ( ! $this->chunksIterator->valid()) {
+            throw CorruptFileException::missingChunk($this->chunkOffset);
+        }
+
+        $currentChunk = $this->chunksIterator->current();
+
+        if ($currentChunk->n !== $this->chunkOffset) {
+            throw CorruptFileException::unexpectedIndex($currentChunk->n, $this->chunkOffset);
+        }
+
+        $this->buffer = $currentChunk->data->getData();
+
+        $actualChunkSize = strlen($this->buffer);
+
+        $expectedChunkSize = ($this->chunkOffset === $this->numChunks - 1)
+            ? $this->expectedLastChunkSize
+            : $this->chunkSize;
+
+        if ($actualChunkSize !== $expectedChunkSize) {
+            throw CorruptFileException::unexpectedSize($actualChunkSize, $expectedChunkSize);
+        }
+
+        return true;
+    }
+
+    /**
+     * Advance to the next chunk and initialize the buffer to its data.
+     *
+     * @return boolean Whether there was a next chunk to read
+     * @throws CorruptFileException if an expected chunk could not be read successfully
+     */
+    private function initBufferFromNextChunk()
+    {
+        if ($this->chunkOffset === $this->numChunks - 1) {
+            return false;
+        }
+
+        $this->bufferOffset = 0;
+        $this->chunkOffset++;
+        $this->chunksIterator->next();
+
+        return $this->initBufferFromCurrentChunk();
+    }
+
+    /**
+     * Initializes the chunk iterator starting from the current offset.
+     */
+    private function initChunksIterator()
+    {
+        $cursor = $this->collectionWrapper->findChunksByFileId($this->file->_id, $this->chunkOffset);
+
+        $this->chunksIterator = new IteratorIterator($cursor);
+        $this->chunksIterator->rewind();
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/GridFS/StreamWrapper.php b/cache/stores/mongodb/MongoDB/GridFS/StreamWrapper.php
new file mode 100755 (executable)
index 0000000..29a2be6
--- /dev/null
@@ -0,0 +1,308 @@
+<?php
+/*
+ * Copyright 2016-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\GridFS;
+
+use MongoDB\BSON\UTCDateTime;
+use Exception;
+use stdClass;
+
+/**
+ * Stream wrapper for reading and writing a GridFS file.
+ *
+ * @internal
+ * @see Bucket::openUploadStream()
+ * @see Bucket::openDownloadStream()
+ */
+class StreamWrapper
+{
+    /**
+     * @var resource|null Stream context (set by PHP)
+     */
+    public $context;
+
+    private $mode;
+    private $protocol;
+    private $stream;
+
+    /**
+     * Return the stream's file document.
+     *
+     * @return stdClass
+     */
+    public function getFile()
+    {
+        return $this->stream->getFile();
+    }
+
+    /**
+     * Register the GridFS stream wrapper.
+     *
+     * @param string $protocol Protocol to use for stream_wrapper_register()
+     */
+    public static function register($protocol = 'gridfs')
+    {
+        if (in_array($protocol, stream_get_wrappers())) {
+            stream_wrapper_unregister($protocol);
+        }
+
+        stream_wrapper_register($protocol, get_called_class(), \STREAM_IS_URL);
+    }
+
+    /**
+     * Closes the stream.
+     *
+     * @see http://php.net/manual/en/streamwrapper.stream-close.php
+     */
+    public function stream_close()
+    {
+        $this->stream->close();
+    }
+
+    /**
+     * Returns whether the file pointer is at the end of the stream.
+     *
+     * @see http://php.net/manual/en/streamwrapper.stream-eof.php
+     * @return boolean
+     */
+    public function stream_eof()
+    {
+        if ( ! $this->stream instanceof ReadableStream) {
+            return false;
+        }
+
+        return $this->stream->isEOF();
+    }
+
+    /**
+     * Opens the stream.
+     *
+     * @see http://php.net/manual/en/streamwrapper.stream-open.php
+     * @param string  $path       Path to the file resource
+     * @param string  $mode       Mode used to open the file (only "r" and "w" are supported)
+     * @param integer $options    Additional flags set by the streams API
+     * @param string  $openedPath Not used
+     */
+    public function stream_open($path, $mode, $options, &$openedPath)
+    {
+        $this->initProtocol($path);
+        $this->mode = $mode;
+
+        if ($mode === 'r') {
+            return $this->initReadableStream();
+        }
+
+        if ($mode === 'w') {
+            return $this->initWritableStream();
+        }
+
+        return false;
+    }
+
+    /**
+     * Read bytes from the stream.
+     *
+     * Note: this method may return a string smaller than the requested length
+     * if data is not available to be read.
+     *
+     * @see http://php.net/manual/en/streamwrapper.stream-read.php
+     * @param integer $length Number of bytes to read
+     * @return string
+     */
+    public function stream_read($length)
+    {
+        if ( ! $this->stream instanceof ReadableStream) {
+            return '';
+        }
+
+        try {
+            return $this->stream->readBytes($length);
+        } catch (Exception $e) {
+            trigger_error(sprintf('%s: %s', get_class($e), $e->getMessage()), \E_USER_WARNING);
+            return false;
+        }
+    }
+
+    /**
+     * Return the current position of the stream.
+     *
+     * @see http://php.net/manual/en/streamwrapper.stream-seek.php
+     * @param integer $offset Stream offset to seek to
+     * @param integer $whence One of SEEK_SET, SEEK_CUR, or SEEK_END
+     * @return boolean True if the position was updated and false otherwise
+     */
+    public function stream_seek($offset, $whence = \SEEK_SET)
+    {
+        $size = $this->stream->getSize();
+
+        if ($whence === \SEEK_CUR) {
+            $offset += $this->stream->tell();
+        }
+
+        if ($whence === \SEEK_END) {
+            $offset += $size;
+        }
+
+        // WritableStreams are always positioned at the end of the stream
+        if ($this->stream instanceof WritableStream) {
+            return $offset === $size;
+        }
+
+        if ($offset < 0 || $offset > $size) {
+            return false;
+        }
+
+        $this->stream->seek($offset);
+
+        return true;
+    }
+
+    /**
+     * Return information about the stream.
+     *
+     * @see http://php.net/manual/en/streamwrapper.stream-stat.php
+     * @return array
+     */
+    public function stream_stat()
+    {
+        $stat = $this->getStatTemplate();
+
+        $stat[2] = $stat['mode'] = $this->stream instanceof ReadableStream
+            ? 0100444  // S_IFREG & S_IRUSR & S_IRGRP & S_IROTH
+            : 0100222; // S_IFREG & S_IWUSR & S_IWGRP & S_IWOTH
+        $stat[7] = $stat['size'] = $this->stream->getSize();
+
+        $file = $this->stream->getFile();
+
+        if (isset($file->uploadDate) && $file->uploadDate instanceof UTCDateTime) {
+            $timestamp = $file->uploadDate->toDateTime()->getTimestamp();
+            $stat[9] = $stat['mtime'] = $timestamp;
+            $stat[10] = $stat['ctime'] = $timestamp;
+        }
+
+        if (isset($file->chunkSize) && is_integer($file->chunkSize)) {
+            $stat[11] = $stat['blksize'] = $file->chunkSize;
+        }
+
+        return $stat;
+    }
+
+    /**
+     * Return the current position of the stream.
+     *
+     * @see http://php.net/manual/en/streamwrapper.stream-tell.php
+     * @return integer The current position of the stream
+     */
+    public function stream_tell()
+    {
+        return $this->stream->tell();
+    }
+
+    /**
+     * Write bytes to the stream.
+     *
+     * @see http://php.net/manual/en/streamwrapper.stream-write.php
+     * @param string $data Data to write
+     * @return integer The number of bytes written
+     */
+    public function stream_write($data)
+    {
+        if ( ! $this->stream instanceof WritableStream) {
+            return 0;
+        }
+
+        try {
+            return $this->stream->writeBytes($data);
+        } catch (Exception $e) {
+            trigger_error(sprintf('%s: %s', get_class($e), $e->getMessage()), \E_USER_WARNING);
+            return false;
+        }
+    }
+
+    /**
+     * Returns a stat template with default values.
+     *
+     * @return array
+     */
+    private function getStatTemplate()
+    {
+        return [
+            0  => 0,  'dev'     => 0,
+            1  => 0,  'ino'     => 0,
+            2  => 0,  'mode'    => 0,
+            3  => 0,  'nlink'   => 0,
+            4  => 0,  'uid'     => 0,
+            5  => 0,  'gid'     => 0,
+            6  => -1, 'rdev'    => -1,
+            7  => 0,  'size'    => 0,
+            8  => 0,  'atime'   => 0,
+            9  => 0,  'mtime'   => 0,
+            10 => 0,  'ctime'   => 0,
+            11 => -1, 'blksize' => -1,
+            12 => -1, 'blocks'  => -1,
+        ];
+    }
+
+    /**
+     * Initialize the protocol from the given path.
+     *
+     * @see StreamWrapper::stream_open()
+     * @param string $path
+     */
+    private function initProtocol($path)
+    {
+        $parts = explode('://', $path, 2);
+        $this->protocol = $parts[0] ?: 'gridfs';
+    }
+
+    /**
+     * Initialize the internal stream for reading.
+     *
+     * @see StreamWrapper::stream_open()
+     * @return boolean
+     */
+    private function initReadableStream()
+    {
+        $context = stream_context_get_options($this->context);
+
+        $this->stream = new ReadableStream(
+            $context[$this->protocol]['collectionWrapper'],
+            $context[$this->protocol]['file']
+        );
+
+        return true;
+    }
+
+    /**
+     * Initialize the internal stream for writing.
+     *
+     * @see StreamWrapper::stream_open()
+     * @return boolean
+     */
+    private function initWritableStream()
+    {
+        $context = stream_context_get_options($this->context);
+
+        $this->stream = new WritableStream(
+            $context[$this->protocol]['collectionWrapper'],
+            $context[$this->protocol]['filename'],
+            $context[$this->protocol]['options']
+        );
+
+        return true;
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/GridFS/WritableStream.php b/cache/stores/mongodb/MongoDB/GridFS/WritableStream.php
new file mode 100755 (executable)
index 0000000..1fed3f9
--- /dev/null
@@ -0,0 +1,283 @@
+<?php
+/*
+ * Copyright 2016-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\GridFS;
+
+use MongoDB\BSON\Binary;
+use MongoDB\BSON\ObjectId;
+use MongoDB\BSON\UTCDateTime;
+use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
+use MongoDB\Exception\InvalidArgumentException;
+use stdClass;
+
+/**
+ * WritableStream abstracts the process of writing a GridFS file.
+ *
+ * @internal
+ */
+class WritableStream
+{
+    private static $defaultChunkSizeBytes = 261120;
+
+    private $buffer = '';
+    private $chunkOffset = 0;
+    private $chunkSize;
+    private $disableMD5;
+    private $collectionWrapper;
+    private $file;
+    private $hashCtx;
+    private $isClosed = false;
+    private $length = 0;
+
+    /**
+     * Constructs a writable GridFS stream.
+     *
+     * Supported options:
+     *
+     *  * _id (mixed): File document identifier. Defaults to a new ObjectId.
+     *
+     *  * aliases (array of strings): DEPRECATED An array of aliases.
+     *    Applications wishing to store aliases should add an aliases field to
+     *    the metadata document instead.
+     *
+     *  * chunkSizeBytes (integer): The chunk size in bytes. Defaults to
+     *    261120 (i.e. 255 KiB).
+     *
+     *  * disableMD5 (boolean): When true, no MD5 sum will be generated.
+     *    Defaults to "false".
+     *
+     *  * contentType (string): DEPRECATED content type to be stored with the
+     *    file. This information should now be added to the metadata.
+     *
+     *  * metadata (document): User data for the "metadata" field of the files
+     *    collection document.
+     *
+     * @param CollectionWrapper $collectionWrapper GridFS collection wrapper
+     * @param string            $filename          Filename
+     * @param array             $options           Upload options
+     * @throws InvalidArgumentException
+     */
+    public function __construct(CollectionWrapper $collectionWrapper, $filename, array $options = [])
+    {
+        $options += [
+            '_id' => new ObjectId,
+            'chunkSizeBytes' => self::$defaultChunkSizeBytes,
+            'disableMD5' => false,
+        ];
+
+        if (isset($options['aliases']) && ! \MongoDB\is_string_array($options['aliases'])) {
+            throw InvalidArgumentException::invalidType('"aliases" option', $options['aliases'], 'array of strings');
+        }
+
+        if (isset($options['chunkSizeBytes']) && ! is_integer($options['chunkSizeBytes'])) {
+            throw InvalidArgumentException::invalidType('"chunkSizeBytes" option', $options['chunkSizeBytes'], 'integer');
+        }
+
+        if (isset($options['chunkSizeBytes']) && $options['chunkSizeBytes'] < 1) {
+            throw new InvalidArgumentException(sprintf('Expected "chunkSizeBytes" option to be >= 1, %d given', $options['chunkSizeBytes']));
+        }
+
+        if (isset($options['disableMD5']) && ! is_bool($options['disableMD5'])) {
+            throw InvalidArgumentException::invalidType('"disableMD5" option', $options['disableMD5'], 'boolean');
+        }
+
+        if (isset($options['contentType']) && ! is_string($options['contentType'])) {
+            throw InvalidArgumentException::invalidType('"contentType" option', $options['contentType'], 'string');
+        }
+
+        if (isset($options['metadata']) && ! is_array($options['metadata']) && ! is_object($options['metadata'])) {
+            throw InvalidArgumentException::invalidType('"metadata" option', $options['metadata'], 'array or object');
+        }
+
+        $this->chunkSize = $options['chunkSizeBytes'];
+        $this->collectionWrapper = $collectionWrapper;
+        $this->disableMD5 = $options['disableMD5'];
+
+        if ( ! $this->disableMD5) {
+            $this->hashCtx = hash_init('md5');
+        }
+
+        $this->file = [
+            '_id' => $options['_id'],
+            'chunkSize' => $this->chunkSize,
+            'filename' => (string) $filename,
+        ] + array_intersect_key($options, ['aliases' => 1, 'contentType' => 1, 'metadata' => 1]);
+    }
+
+    /**
+     * Return internal properties for debugging purposes.
+     *
+     * @see http://php.net/manual/en/language.oop5.magic.php#language.oop5.magic.debuginfo
+     * @return array
+     */
+    public function __debugInfo()
+    {
+        return [
+            'bucketName' => $this->collectionWrapper->getBucketName(),
+            'databaseName' => $this->collectionWrapper->getDatabaseName(),
+            'file' => $this->file,
+        ];
+    }
+
+    /**
+     * Closes an active stream and flushes all buffered data to GridFS.
+     */
+    public function close()
+    {
+        if ($this->isClosed) {
+            // TODO: Should this be an error condition? e.g. BadMethodCallException
+            return;
+        }
+
+        if (strlen($this->buffer) > 0) {
+            $this->insertChunkFromBuffer();
+        }
+
+        $this->fileCollectionInsert();
+        $this->isClosed = true;
+    }
+
+    /**
+     * Return the stream's file document.
+     *
+     * @return stdClass
+     */
+    public function getFile()
+    {
+        return (object) $this->file;
+    }
+
+    /**
+     * Return the stream's size in bytes.
+     *
+     * Note: this value will increase as more data is written to the stream.
+     *
+     * @return integer
+     */
+    public function getSize()
+    {
+        return $this->length + strlen($this->buffer);
+    }
+
+    /**
+     * Return the current position of the stream.
+     *
+     * This is the offset within the stream where the next byte would be
+     * written. Since seeking is not supported and writes are appended, this is
+     * always the end of the stream.
+     *
+     * @see WritableStream::getSize()
+     * @return integer
+     */
+    public function tell()
+    {
+        return $this->getSize();
+    }
+
+    /**
+     * Inserts binary data into GridFS via chunks.
+     *
+     * Data will be buffered internally until chunkSizeBytes are accumulated, at
+     * which point a chunk document will be inserted and the buffer reset.
+     *
+     * @param string $data Binary data to write
+     * @return integer
+     */
+    public function writeBytes($data)
+    {
+        if ($this->isClosed) {
+            // TODO: Should this be an error condition? e.g. BadMethodCallException
+            return;
+        }
+
+        $bytesRead = 0;
+
+        while ($bytesRead != strlen($data)) {
+            $initialBufferLength = strlen($this->buffer);
+            $this->buffer .= substr($data, $bytesRead, $this->chunkSize - $initialBufferLength);
+            $bytesRead += strlen($this->buffer) - $initialBufferLength;
+
+            if (strlen($this->buffer) == $this->chunkSize) {
+                $this->insertChunkFromBuffer();
+            }
+        }
+
+        return $bytesRead;
+    }
+
+    private function abort()
+    {
+        try {
+            $this->collectionWrapper->deleteChunksByFilesId($this->file['_id']);
+        } catch (DriverRuntimeException $e) {
+            // We are already handling an error if abort() is called, so suppress this
+        }
+
+        $this->isClosed = true;
+    }
+
+    private function fileCollectionInsert()
+    {
+        $this->file['length'] = $this->length;
+        $this->file['uploadDate'] = new UTCDateTime;
+
+        if ( ! $this->disableMD5) {
+            $this->file['md5'] = hash_final($this->hashCtx);
+        }
+
+        try {
+            $this->collectionWrapper->insertFile($this->file);
+        } catch (DriverRuntimeException $e) {
+            $this->abort();
+
+            throw $e;
+        }
+
+        return $this->file['_id'];
+    }
+
+    private function insertChunkFromBuffer()
+    {
+        if (strlen($this->buffer) == 0) {
+            return;
+        }
+
+        $data = $this->buffer;
+        $this->buffer = '';
+
+        $chunk = [
+            'files_id' => $this->file['_id'],
+            'n' => $this->chunkOffset,
+            'data' => new Binary($data, Binary::TYPE_GENERIC),
+        ];
+
+        if ( ! $this->disableMD5) {
+            hash_update($this->hashCtx, $data);
+        }
+
+        try {
+            $this->collectionWrapper->insertChunk($chunk);
+        } catch (DriverRuntimeException $e) {
+            $this->abort();
+
+            throw $e;
+        }
+
+        $this->length += strlen($data);
+        $this->chunkOffset++;
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/InsertManyResult.php b/cache/stores/mongodb/MongoDB/InsertManyResult.php
new file mode 100755 (executable)
index 0000000..a9ecb69
--- /dev/null
@@ -0,0 +1,91 @@
+<?php
+/*
+ * Copyright 2015-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB;
+
+use MongoDB\Driver\WriteResult;
+use MongoDB\Exception\BadMethodCallException;
+
+/**
+ * Result class for a multi-document insert operation.
+ */
+class InsertManyResult
+{
+    private $writeResult;
+    private $insertedIds;
+    private $isAcknowledged;
+
+    /**
+     * Constructor.
+     *
+     * @param WriteResult $writeResult
+     * @param mixed[]     $insertedIds
+     */
+    public function __construct(WriteResult $writeResult, array $insertedIds)
+    {
+        $this->writeResult = $writeResult;
+        $this->insertedIds = $insertedIds;
+        $this->isAcknowledged = $writeResult->isAcknowledged();
+    }
+
+    /**
+     * Return the number of documents that were inserted.
+     *
+     * This method should only be called if the write was acknowledged.
+     *
+     * @see InsertManyResult::isAcknowledged()
+     * @return integer
+     * @throws BadMethodCallException is the write result is unacknowledged
+     */
+    public function getInsertedCount()
+    {
+        if ($this->isAcknowledged) {
+            return $this->writeResult->getInsertedCount();
+        }
+
+        throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
+    }
+
+    /**
+     * Return a map of the inserted documents' IDs.
+     *
+     * The index of each ID in the map corresponds to each document's position
+     * in the bulk operation. If a document had an ID prior to inserting (i.e.
+     * the driver did not generate an ID), the index will contain its "_id"
+     * field value. Any driver-generated ID will be a MongoDB\BSON\ObjectId
+     * instance.
+     *
+     * @return mixed[]
+     */
+    public function getInsertedIds()
+    {
+        return $this->insertedIds;
+    }
+
+    /**
+     * Return whether this insert result was acknowledged by the server.
+     *
+     * If the insert was not acknowledged, other fields from the WriteResult
+     * (e.g. insertedCount) will be undefined.
+     *
+     * @return boolean
+     */
+    public function isAcknowledged()
+    {
+        return $this->writeResult->isAcknowledged();
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/InsertOneResult.php b/cache/stores/mongodb/MongoDB/InsertOneResult.php
new file mode 100755 (executable)
index 0000000..5a580bd
--- /dev/null
@@ -0,0 +1,93 @@
+<?php
+/*
+ * Copyright 2015-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB;
+
+use MongoDB\Driver\WriteResult;
+use MongoDB\Exception\BadMethodCallException;
+
+/**
+ * Result class for a single-document insert operation.
+ */
+class InsertOneResult
+{
+    private $writeResult;
+    private $insertedId;
+    private $isAcknowledged;
+
+    /**
+     * Constructor.
+     *
+     * @param WriteResult $writeResult
+     * @param mixed       $insertedId
+     */
+    public function __construct(WriteResult $writeResult, $insertedId)
+    {
+        $this->writeResult = $writeResult;
+        $this->insertedId = $insertedId;
+        $this->isAcknowledged = $writeResult->isAcknowledged();
+    }
+
+    /**
+     * Return the number of documents that were inserted.
+     *
+     * This method should only be called if the write was acknowledged.
+     *
+     * @see InsertOneResult::isAcknowledged()
+     * @return integer
+     * @throws BadMethodCallException is the write result is unacknowledged
+     */
+    public function getInsertedCount()
+    {
+        if ($this->isAcknowledged) {
+            return $this->writeResult->getInsertedCount();
+        }
+
+        throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
+    }
+
+    /**
+     * Return the inserted document's ID.
+     *
+     * If the document had an ID prior to inserting (i.e. the driver did not
+     * need to generate an ID), this will contain its "_id". Any
+     * driver-generated ID will be a MongoDB\BSON\ObjectId instance.
+     *
+     * @return mixed
+     */
+    public function getInsertedId()
+    {
+        return $this->insertedId;
+    }
+
+    /**
+     * Return whether this insert was acknowledged by the server.
+     *
+     * If the insert was not acknowledged, other fields from the WriteResult
+     * (e.g. insertedCount) will be undefined.
+     *
+     * If the insert was not acknowledged, other fields from the WriteResult
+     * (e.g. insertedCount) will be undefined and their getter methods should
+     * not be invoked.
+     *
+     * @return boolean
+     */
+    public function isAcknowledged()
+    {
+        return $this->writeResult->isAcknowledged();
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/MapReduceResult.php b/cache/stores/mongodb/MongoDB/MapReduceResult.php
new file mode 100755 (executable)
index 0000000..37ba38f
--- /dev/null
@@ -0,0 +1,100 @@
+<?php
+/*
+ * Copyright 2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB;
+
+use IteratorAggregate;
+use stdClass;
+use Traversable;
+
+/**
+ * Result class for mapReduce command results.
+ *
+ * This class allows for iteration of mapReduce results irrespective of the
+ * output method (e.g. inline, collection) via the IteratorAggregate interface.
+ * It also provides access to command statistics.
+ *
+ * @api
+ * @see \MongoDB\Collection::mapReduce()
+ * @see https://docs.mongodb.com/manual/reference/command/mapReduce/
+ */
+class MapReduceResult implements IteratorAggregate
+{
+    private $getIterator;
+    private $executionTimeMS;
+    private $counts;
+    private $timing;
+
+    /**
+     * Constructor.
+     *
+     * @internal
+     * @param callable $getIterator Callback that returns a Traversable for mapReduce results
+     * @param stdClass $result      Result document from the mapReduce command
+     */
+    public function __construct(callable $getIterator, stdClass $result)
+    {
+        $this->getIterator = $getIterator;
+        $this->executionTimeMS = (integer) $result->timeMillis;
+        $this->counts = (array) $result->counts;
+        $this->timing = isset($result->timing) ? (array) $result->timing : [];
+    }
+
+    /**
+     * Returns various count statistics from the mapReduce command.
+     *
+     * @return array
+     */
+    public function getCounts()
+    {
+        return $this->counts;
+    }
+
+    /**
+     * Return the command execution time in milliseconds.
+     *
+     * @return integer
+     */
+    public function getExecutionTimeMS()
+    {
+        return $this->executionTimeMS;
+    }
+
+    /**
+     * Return the mapReduce results as a Traversable.
+     *
+     * @see http://php.net/iteratoraggregate.getiterator
+     * @return Traversable
+     */
+    public function getIterator()
+    {
+        return call_user_func($this->getIterator);
+    }
+
+    /**
+     * Returns various timing statistics from the mapReduce command.
+     *
+     * Note: timing statistics are only available if the mapReduce command's
+     * "verbose" option was true; otherwise, an empty array will be returned.
+     *
+     * @return array
+     */
+    public function getTiming()
+    {
+        return $this->timing;
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/Model/BSONArray.php b/cache/stores/mongodb/MongoDB/Model/BSONArray.php
new file mode 100755 (executable)
index 0000000..61dc9b4
--- /dev/null
@@ -0,0 +1,99 @@
+<?php
+/*
+ * Copyright 2016-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Model;
+
+use MongoDB\BSON\Serializable;
+use MongoDB\BSON\Unserializable;
+use ArrayObject;
+use JsonSerializable;
+
+/**
+ * Model class for a BSON array.
+ *
+ * The internal data will be filtered through array_values() during BSON
+ * serialization to ensure that it becomes a BSON array.
+ *
+ * @api
+ */
+class BSONArray extends ArrayObject implements JsonSerializable, Serializable, Unserializable
+{
+    /**
+     * Clone this BSONArray.
+     */
+    public function __clone()
+    {
+        foreach ($this as $key => $value) {
+            $this[$key] = \MongoDB\recursive_copy($value);
+        }
+    }
+
+    /**
+     * Factory method for var_export().
+     *
+     * @see http://php.net/oop5.magic#object.set-state
+     * @see http://php.net/var-export
+     * @param array $properties
+     * @return self
+     */
+    public static function __set_state(array $properties)
+    {
+        $array = new static;
+        $array->exchangeArray($properties);
+
+        return $array;
+    }
+
+    /**
+     * Serialize the array to BSON.
+     *
+     * The array data will be numerically reindexed to ensure that it is stored
+     * as a BSON array.
+     *
+     * @see http://php.net/mongodb-bson-serializable.bsonserialize
+     * @return array
+     */
+    public function bsonSerialize()
+    {
+        return array_values($this->getArrayCopy());
+    }
+
+    /**
+     * Unserialize the document to BSON.
+     *
+     * @see http://php.net/mongodb-bson-unserializable.bsonunserialize
+     * @param array $data Array data
+     */
+    public function bsonUnserialize(array $data)
+    {
+        self::__construct($data);
+    }
+
+    /**
+     * Serialize the array to JSON.
+     *
+     * The array data will be numerically reindexed to ensure that it is stored
+     * as a JSON array.
+     *
+     * @see http://php.net/jsonserializable.jsonserialize
+     * @return array
+     */
+    public function jsonSerialize()
+    {
+        return array_values($this->getArrayCopy());
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/Model/BSONDocument.php b/cache/stores/mongodb/MongoDB/Model/BSONDocument.php
new file mode 100755 (executable)
index 0000000..90ea887
--- /dev/null
@@ -0,0 +1,106 @@
+<?php
+/*
+ * Copyright 2016-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Model;
+
+use MongoDB\BSON\Serializable;
+use MongoDB\BSON\Unserializable;
+use ArrayObject;
+use JsonSerializable;
+
+/**
+ * Model class for a BSON document.
+ *
+ * The internal data will be cast to an object during BSON serialization to
+ * ensure that it becomes a BSON document.
+ *
+ * @api
+ */
+class BSONDocument extends ArrayObject implements JsonSerializable, Serializable, Unserializable
+{
+    /**
+     * Deep clone this BSONDocument.
+     */
+    public function __clone()
+    {
+        foreach ($this as $key => $value) {
+            $this[$key] = \MongoDB\recursive_copy($value);
+        }
+    }
+
+    /**
+     * Constructor.
+     *
+     * This overrides the parent constructor to allow property access of entries
+     * by default.
+     *
+     * @see http://php.net/arrayobject.construct
+     */
+    public function __construct($input = [], $flags = ArrayObject::ARRAY_AS_PROPS, $iterator_class = 'ArrayIterator')
+    {
+        parent::__construct($input, $flags, $iterator_class);
+    }
+
+    /**
+     * Factory method for var_export().
+     *
+     * @see http://php.net/oop5.magic#object.set-state
+     * @see http://php.net/var-export
+     * @param array $properties
+     * @return self
+     */
+    public static function __set_state(array $properties)
+    {
+        $document = new static;
+        $document->exchangeArray($properties);
+
+        return $document;
+    }
+
+    /**
+     * Serialize the document to BSON.
+     *
+     * @see http://php.net/mongodb-bson-serializable.bsonserialize
+     * @return object
+     */
+    public function bsonSerialize()
+    {
+        return (object) $this->getArrayCopy();
+    }
+
+    /**
+     * Unserialize the document to BSON.
+     *
+     * @see http://php.net/mongodb-bson-unserializable.bsonunserialize
+     * @param array $data Array data
+     */
+    public function bsonUnserialize(array $data)
+    {
+        parent::__construct($data, ArrayObject::ARRAY_AS_PROPS);
+    }
+
+    /**
+     * Serialize the array to JSON.
+     *
+     * @see http://php.net/jsonserializable.jsonserialize
+     * @return object
+     */
+    public function jsonSerialize()
+    {
+        return (object) $this->getArrayCopy();
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/Model/BSONIterator.php b/cache/stores/mongodb/MongoDB/Model/BSONIterator.php
new file mode 100755 (executable)
index 0000000..f082794
--- /dev/null
@@ -0,0 +1,135 @@
+<?php
+/*
+ * Copyright 2018 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Model;
+
+use MongoDB\Exception\UnexpectedValueException;
+use MongoDB\Model\BSONDocument;
+use Iterator;
+
+/**
+ * Iterator for BSON documents.
+ */
+class BSONIterator implements Iterator
+{
+    private static $bsonSize = 4;
+
+    private $buffer;
+    private $bufferLength;
+    private $current;
+    private $key = 0;
+    private $position = 0;
+    private $options;
+
+    /**
+     * Constructs a BSON Iterator.
+     *
+     * Supported options:
+     *
+     *  * typeMap (array): Type map for BSON deserialization.
+     *
+     * @internal
+     * @see http://php.net/manual/en/function.mongodb.bson-tophp.php
+     * @param string $data    Concatenated, valid, BSON-encoded documents
+     * @param array  $options Iterator options
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function __construct($data, array $options = [])
+    {
+        if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
+            throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
+        }
+
+        if ( ! isset($options['typeMap'])) {
+            $options['typeMap'] = [];
+        }
+
+        $this->buffer = $data;
+        $this->bufferLength = strlen($data);
+        $this->options = $options;
+    }
+
+    /**
+     * @see http://php.net/iterator.current
+     * @return mixed
+     */
+    public function current()
+    {
+        return $this->current;
+    }
+
+    /**
+     * @see http://php.net/iterator.key
+     * @return mixed
+     */
+    public function key()
+    {
+        return $this->key;
+    }
+
+    /**
+     * @see http://php.net/iterator.next
+     * @return void
+     */
+    public function next()
+    {
+        $this->key++;
+        $this->current = null;
+        $this->advance();
+    }
+
+    /**
+     * @see http://php.net/iterator.rewind
+     * @return void
+     */
+    public function rewind()
+    {
+        $this->key = 0;
+        $this->position = 0;
+        $this->current = null;
+        $this->advance();
+    }
+
+    /**
+     * @see http://php.net/iterator.valid
+     * @return boolean
+     */
+    public function valid()
+    {
+        return $this->current !== null;
+    }
+
+    private function advance()
+    {
+        if ($this->position === $this->bufferLength) {
+            return;
+        }
+
+        if (($this->bufferLength - $this->position) < self::$bsonSize) {
+            throw new UnexpectedValueException(sprintf('Expected at least %d bytes; %d remaining', self::$bsonSize, $this->bufferLength - $this->position));
+        }
+
+        list(,$documentLength) = unpack('V', substr($this->buffer, $this->position, self::$bsonSize));
+
+        if (($this->bufferLength - $this->position) < $documentLength) {
+            throw new UnexpectedValueException(sprintf('Expected %d bytes; %d remaining', $documentLength, $this->bufferLength - $this->position));
+        }
+
+        $this->current = \MongoDB\BSON\toPHP(substr($this->buffer, $this->position, $documentLength), $this->options['typeMap']);
+        $this->position += $documentLength;
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/Model/CachingIterator.php b/cache/stores/mongodb/MongoDB/Model/CachingIterator.php
new file mode 100755 (executable)
index 0000000..adad231
--- /dev/null
@@ -0,0 +1,165 @@
+<?php
+/*
+ * Copyright 2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Model;
+
+use Countable;
+use Generator;
+use Iterator;
+use Traversable;
+
+/**
+ * Iterator for wrapping a Traversable and caching its results.
+ *
+ * By caching results, this iterators allows a Traversable to be counted and
+ * rewound multiple times, even if the wrapped object does not natively support
+ * those operations (e.g. MongoDB\Driver\Cursor).
+ *
+ * @internal
+ */
+class CachingIterator implements Countable, Iterator
+{
+    private $items = [];
+    private $iterator;
+    private $iteratorAdvanced = false;
+    private $iteratorExhausted = false;
+
+    /**
+     * Constructor.
+     *
+     * Initialize the iterator and stores the first item in the cache. This
+     * effectively rewinds the Traversable and the wrapping Generator, which
+     * will execute up to its first yield statement. Additionally, this mimics
+     * behavior of the SPL iterators and allows users to omit an explicit call
+     * to rewind() before using the other methods.
+     *
+     * @param Traversable $traversable
+     */
+    public function __construct(Traversable $traversable)
+    {
+        $this->iterator = $this->wrapTraversable($traversable);
+        $this->storeCurrentItem();
+    }
+
+    /**
+     * @see http://php.net/countable.count
+     * @return integer
+     */
+    public function count()
+    {
+        $this->exhaustIterator();
+
+        return count($this->items);
+    }
+
+    /**
+     * @see http://php.net/iterator.current
+     * @return mixed
+     */
+    public function current()
+    {
+        return current($this->items);
+    }
+
+    /**
+     * @see http://php.net/iterator.key
+     * @return mixed
+     */
+    public function key()
+    {
+        return key($this->items);
+    }
+
+    /**
+     * @see http://php.net/iterator.next
+     * @return void
+     */
+    public function next()
+    {
+        if ( ! $this->iteratorExhausted) {
+            $this->iterator->next();
+            $this->storeCurrentItem();
+        }
+
+        next($this->items);
+    }
+
+    /**
+     * @see http://php.net/iterator.rewind
+     * @return void
+     */
+    public function rewind()
+    {
+        /* If the iterator has advanced, exhaust it now so that future iteration
+         * can rely on the cache.
+         */
+        if ($this->iteratorAdvanced) {
+            $this->exhaustIterator();
+        }
+
+        reset($this->items);
+    }
+
+    /**
+     * @see http://php.net/iterator.valid
+     * @return boolean
+     */
+    public function valid()
+    {
+        return $this->key() !== null;
+    }
+
+    /**
+     * Ensures that the inner iterator is fully consumed and cached.
+     */
+    private function exhaustIterator()
+    {
+        while ( ! $this->iteratorExhausted) {
+            $this->next();
+        }
+    }
+
+    /**
+     * Stores the current item in the cache.
+     */
+    private function storeCurrentItem()
+    {
+        $key = $this->iterator->key();
+
+        if ($key === null) {
+            return;
+        }
+
+        $this->items[$key] = $this->iterator->current();
+    }
+
+    /**
+     * Wraps the Traversable with a Generator.
+     *
+     * @param Traversable $traversable
+     * @return Generator
+     */
+    private function wrapTraversable(Traversable $traversable)
+    {
+        foreach ($traversable as $key => $value) {
+            yield $key => $value;
+            $this->iteratorAdvanced = true;
+        }
+
+        $this->iteratorExhausted = true;
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/Model/CollectionInfo.php b/cache/stores/mongodb/MongoDB/Model/CollectionInfo.php
new file mode 100755 (executable)
index 0000000..39b20c4
--- /dev/null
@@ -0,0 +1,154 @@
+<?php
+/*
+ * Copyright 2015-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Model;
+
+use MongoDB\Exception\BadMethodCallException;
+use ArrayAccess;
+
+/**
+ * Collection information model class.
+ *
+ * This class models the collection information returned by the listCollections
+ * command or, for legacy servers, queries on the "system.namespaces"
+ * collection. It provides methods to access options for the collection.
+ *
+ * @api
+ * @see \MongoDB\Database::listCollections()
+ * @see https://github.com/mongodb/specifications/blob/master/source/enumerate-collections.rst
+ */
+class CollectionInfo implements ArrayAccess
+{
+    private $info;
+
+    /**
+     * Constructor.
+     *
+     * @param array $info Collection info
+     */
+    public function __construct(array $info)
+    {
+        $this->info = $info;
+    }
+
+    /**
+     * Return the collection info as an array.
+     *
+     * @see http://php.net/oop5.magic#language.oop5.magic.debuginfo
+     * @return array
+     */
+    public function __debugInfo()
+    {
+        return $this->info;
+    }
+
+    /**
+     * Return the maximum number of documents to keep in the capped collection.
+     *
+     * @return integer|null
+     */
+    public function getCappedMax()
+    {
+        return isset($this->info['options']['max']) ? (integer) $this->info['options']['max'] : null;
+    }
+
+    /**
+     * Return the maximum size (in bytes) of the capped collection.
+     *
+     * @return integer|null
+     */
+    public function getCappedSize()
+    {
+        return isset($this->info['options']['size']) ? (integer) $this->info['options']['size'] : null;
+    }
+
+    /**
+     * Return the collection name.
+     *
+     * @return string
+     */
+    public function getName()
+    {
+        return (string) $this->info['name'];
+    }
+
+    /**
+     * Return the collection options.
+     *
+     * @return array
+     */
+    public function getOptions()
+    {
+        return isset($this->info['options']) ? (array) $this->info['options'] : [];
+    }
+
+    /**
+     * Return whether the collection is a capped collection.
+     *
+     * @return boolean
+     */
+    public function isCapped()
+    {
+        return ! empty($this->info['options']['capped']);
+    }
+
+    /**
+     * Check whether a field exists in the collection information.
+     *
+     * @see http://php.net/arrayaccess.offsetexists
+     * @param mixed $key
+     * @return boolean
+     */
+    public function offsetExists($key)
+    {
+        return array_key_exists($key, $this->info);
+    }
+
+    /**
+     * Return the field's value from the collection information.
+     *
+     * @see http://php.net/arrayaccess.offsetget
+     * @param mixed $key
+     * @return mixed
+     */
+    public function offsetGet($key)
+    {
+        return $this->info[$key];
+    }
+
+    /**
+     * Not supported.
+     *
+     * @see http://php.net/arrayaccess.offsetset
+     * @throws BadMethodCallException
+     */
+    public function offsetSet($key, $value)
+    {
+        throw BadMethodCallException::classIsImmutable(__CLASS__);
+    }
+
+    /**
+     * Not supported.
+     *
+     * @see http://php.net/arrayaccess.offsetunset
+     * @throws BadMethodCallException
+     */
+    public function offsetUnset($key)
+    {
+        throw BadMethodCallException::classIsImmutable(__CLASS__);
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/Model/CollectionInfoCommandIterator.php b/cache/stores/mongodb/MongoDB/Model/CollectionInfoCommandIterator.php
new file mode 100755 (executable)
index 0000000..c4b7b8d
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+/*
+ * Copyright 2015-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Model;
+
+use IteratorIterator;
+
+/**
+ * CollectionInfoIterator for listCollections command results.
+ *
+ * This iterator may be used to wrap a Cursor returned by the listCollections
+ * command.
+ *
+ * @internal
+ * @see \MongoDB\Database::listCollections()
+ * @see https://github.com/mongodb/specifications/blob/master/source/enumerate-collections.rst
+ * @see http://docs.mongodb.org/manual/reference/command/listCollections/
+ */
+class CollectionInfoCommandIterator extends IteratorIterator implements CollectionInfoIterator
+{
+    /**
+     * Return the current element as a CollectionInfo instance.
+     *
+     * @see CollectionInfoIterator::current()
+     * @see http://php.net/iterator.current
+     * @return CollectionInfo
+     */
+    public function current()
+    {
+        return new CollectionInfo(parent::current());
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/Model/CollectionInfoIterator.php b/cache/stores/mongodb/MongoDB/Model/CollectionInfoIterator.php
new file mode 100755 (executable)
index 0000000..999ee9a
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+/*
+ * Copyright 2015-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Model;
+
+use Iterator;
+
+/**
+ * CollectionInfoIterator interface.
+ *
+ * This iterator is used for enumerating collections in a database.
+ *
+ * @api
+ * @see \MongoDB\Database::listCollections()
+ */
+interface CollectionInfoIterator extends Iterator
+{
+    /**
+     * Return the current element as a CollectionInfo instance.
+     *
+     * @return CollectionInfo
+     */
+    public function current();
+}
diff --git a/cache/stores/mongodb/MongoDB/Model/DatabaseInfo.php b/cache/stores/mongodb/MongoDB/Model/DatabaseInfo.php
new file mode 100755 (executable)
index 0000000..e93c5ca
--- /dev/null
@@ -0,0 +1,132 @@
+<?php
+/*
+ * Copyright 2015-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Model;
+
+use MongoDB\Exception\BadMethodCallException;
+use ArrayAccess;
+/**
+ * Database information model class.
+ *
+ * This class models the database information returned by the listDatabases
+ * command. It provides methods to access common database properties.
+ *
+ * @api
+ * @see \MongoDB\Client::listDatabases()
+ * @see http://docs.mongodb.org/manual/reference/command/listDatabases/
+ */
+class DatabaseInfo implements ArrayAccess
+{
+    private $info;
+
+    /**
+     * Constructor.
+     *
+     * @param array $info Database info
+     */
+    public function __construct(array $info)
+    {
+        $this->info = $info;
+    }
+
+    /**
+     * Return the collection info as an array.
+     *
+     * @see http://php.net/oop5.magic#language.oop5.magic.debuginfo
+     * @return array
+     */
+    public function __debugInfo()
+    {
+        return $this->info;
+    }
+
+    /**
+     * Return the database name.
+     *
+     * @return string
+     */
+    public function getName()
+    {
+        return (string) $this->info['name'];
+    }
+
+    /**
+     * Return the databases size on disk (in bytes).
+     *
+     * @return integer
+     */
+    public function getSizeOnDisk()
+    {
+        return (integer) $this->info['sizeOnDisk'];
+    }
+
+    /**
+     * Return whether the database is empty.
+     *
+     * @return boolean
+     */
+    public function isEmpty()
+    {
+        return (boolean) $this->info['empty'];
+    }
+
+    /**
+     * Check whether a field exists in the database information.
+     *
+     * @see http://php.net/arrayaccess.offsetexists
+     * @param mixed $key
+     * @return boolean
+     */
+    public function offsetExists($key)
+    {
+        return array_key_exists($key, $this->info);
+    }
+
+    /**
+     * Return the field's value from the database information.
+     *
+     * @see http://php.net/arrayaccess.offsetget
+     * @param mixed $key
+     * @return mixed
+     */
+    public function offsetGet($key)
+    {
+        return $this->info[$key];
+    }
+
+    /**
+     * Not supported.
+     *
+     * @see http://php.net/arrayaccess.offsetset
+     * @throws BadMethodCallException
+     */
+    public function offsetSet($key, $value)
+    {
+        throw BadMethodCallException::classIsImmutable(__CLASS__);
+    }
+
+    /**
+     * Not supported.
+     *
+     * @see http://php.net/arrayaccess.offsetunset
+     * @throws BadMethodCallException
+     */
+    public function offsetUnset($key)
+    {
+        throw BadMethodCallException::classIsImmutable(__CLASS__);
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/Model/DatabaseInfoIterator.php b/cache/stores/mongodb/MongoDB/Model/DatabaseInfoIterator.php
new file mode 100755 (executable)
index 0000000..91576eb
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+/*
+ * Copyright 2015-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Model;
+
+use Iterator;
+
+/**
+ * DatabaseInfoIterator interface.
+ *
+ * This iterator is used for enumerating databases on a server.
+ *
+ * @api
+ * @see \MongoDB\Client::listDatabases()
+ */
+interface DatabaseInfoIterator extends Iterator
+{
+    /**
+     * Return the current element as a DatabaseInfo instance.
+     *
+     * @return DatabaseInfo
+     */
+    public function current();
+}
diff --git a/cache/stores/mongodb/MongoDB/Model/DatabaseInfoLegacyIterator.php b/cache/stores/mongodb/MongoDB/Model/DatabaseInfoLegacyIterator.php
new file mode 100755 (executable)
index 0000000..04be47e
--- /dev/null
@@ -0,0 +1,97 @@
+<?php
+/*
+ * Copyright 2015-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Model;
+
+/**
+ * DatabaseInfoIterator for inline listDatabases command results.
+ *
+ * This iterator may be used to wrap the array returned within the listDatabases
+ * command's single-document result.
+ *
+ * @internal
+ * @see \MongoDB\Client::listDatabases()
+ * @see http://docs.mongodb.org/manual/reference/command/listDatabases/
+ */
+class DatabaseInfoLegacyIterator implements DatabaseInfoIterator
+{
+    private $databases;
+
+    /**
+     * Constructor.
+     *
+     * @param array $databases
+     */
+    public function __construct(array $databases)
+    {
+        $this->databases = $databases;
+    }
+
+    /**
+     * Return the current element as a DatabaseInfo instance.
+     *
+     * @see DatabaseInfoIterator::current()
+     * @see http://php.net/iterator.current
+     * @return DatabaseInfo
+     */
+    public function current()
+    {
+        return new DatabaseInfo(current($this->databases));
+    }
+
+    /**
+     * Return the key of the current element.
+     *
+     * @see http://php.net/iterator.key
+     * @return integer
+     */
+    public function key()
+    {
+        return key($this->databases);
+    }
+
+    /**
+     * Move forward to next element.
+     *
+     * @see http://php.net/iterator.next
+     */
+    public function next()
+    {
+        next($this->databases);
+    }
+
+    /**
+     * Rewind the Iterator to the first element.
+     *
+     * @see http://php.net/iterator.rewind
+     */
+    public function rewind()
+    {
+        reset($this->databases);
+    }
+
+    /**
+     * Checks if current position is valid.
+     *
+     * @see http://php.net/iterator.valid
+     * @return boolean
+     */
+    public function valid()
+    {
+        return key($this->databases) !== null;
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/Model/IndexInfo.php b/cache/stores/mongodb/MongoDB/Model/IndexInfo.php
new file mode 100755 (executable)
index 0000000..7491f34
--- /dev/null
@@ -0,0 +1,226 @@
+<?php
+/*
+ * Copyright 2015-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Model;
+
+use MongoDB\Exception\BadMethodCallException;
+use ArrayAccess;
+
+/**
+ * Index information model class.
+ *
+ * This class models the index information returned by the listIndexes command
+ * or, for legacy servers, queries on the "system.indexes" collection. It
+ * provides methods to access common index options, and allows access to other
+ * options through the ArrayAccess interface (write methods are not supported).
+ * For information on keys and index options, see the referenced
+ * db.collection.createIndex() documentation.
+ *
+ * @api
+ * @see \MongoDB\Collection::listIndexes()
+ * @see https://github.com/mongodb/specifications/blob/master/source/enumerate-indexes.rst
+ * @see http://docs.mongodb.org/manual/reference/method/db.collection.createIndex/
+ */
+class IndexInfo implements ArrayAccess
+{
+    private $info;
+
+    /**
+     * Constructor.
+     *
+     * @param array $info Index info
+     */
+    public function __construct(array $info)
+    {
+        $this->info = $info;
+    }
+
+    /**
+     * Return the collection info as an array.
+     *
+     * @see http://php.net/oop5.magic#language.oop5.magic.debuginfo
+     * @return array
+     */
+    public function __debugInfo()
+    {
+        return $this->info;
+    }
+
+    /**
+     * Return the index name to allow casting IndexInfo to string.
+     *
+     * @return string
+     */
+    public function __toString()
+    {
+        return $this->getName();
+    }
+
+    /**
+     * Return the index key.
+     *
+     * @return array
+     */
+    public function getKey()
+    {
+        return (array) $this->info['key'];
+    }
+
+    /**
+     * Return the index name.
+     *
+     * @return string
+     */
+    public function getName()
+    {
+        return (string) $this->info['name'];
+    }
+
+    /**
+     * Return the index namespace (e.g. "db.collection").
+     *
+     * @return string
+     */
+    public function getNamespace()
+    {
+        return (string) $this->info['ns'];
+    }
+
+    /**
+     * Return the index version.
+     *
+     * @return integer
+     */
+    public function getVersion()
+    {
+        return (integer) $this->info['v'];
+    }
+
+    /**
+     * Return whether or not this index is of type 2dsphere.
+     *
+     * @return boolean
+     */
+    public function is2dSphere()
+    {
+        return array_search('2dsphere', $this->getKey(), true) !== false;
+    }
+
+    /**
+     * Return whether or not this index is of type geoHaystack.
+     *
+     * @return boolean
+     */
+    public function isGeoHaystack()
+    {
+        return array_search('geoHaystack', $this->getKey(), true) !== false;
+    }
+
+    /**
+     * Return whether this is a sparse index.
+     *
+     * @see http://docs.mongodb.org/manual/core/index-sparse/
+     * @return boolean
+     */
+    public function isSparse()
+    {
+        return ! empty($this->info['sparse']);
+    }
+
+    /**
+     * Return whether or not this index is of type text.
+     *
+     * @return boolean
+     */
+    public function isText()
+    {
+        return array_search('text', $this->getKey(), true) !== false;
+    }
+
+    /**
+     * Return whether this is a TTL index.
+     *
+     * @see http://docs.mongodb.org/manual/core/index-ttl/
+     * @return boolean
+     */
+    public function isTtl()
+    {
+        return array_key_exists('expireAfterSeconds', $this->info);
+    }
+
+    /**
+     * Return whether this is a unique index.
+     *
+     * @see http://docs.mongodb.org/manual/core/index-unique/
+     * @return boolean
+     */
+    public function isUnique()
+    {
+        return ! empty($this->info['unique']);
+    }
+
+    /**
+     * Check whether a field exists in the index information.
+     *
+     * @see http://php.net/arrayaccess.offsetexists
+     * @param mixed $key
+     * @return boolean
+     */
+    public function offsetExists($key)
+    {
+        return array_key_exists($key, $this->info);
+    }
+
+    /**
+     * Return the field's value from the index information.
+     *
+     * This method satisfies the Enumerating Indexes specification's requirement
+     * that index fields be made accessible under their original names. It may
+     * also be used to access fields that do not have a helper method.
+     *
+     * @see http://php.net/arrayaccess.offsetget
+     * @see https://github.com/mongodb/specifications/blob/master/source/enumerate-indexes.rst#getting-full-index-information
+     * @param mixed $key
+     * @return mixed
+     */
+    public function offsetGet($key)
+    {
+        return $this->info[$key];
+    }
+
+    /**
+     * Not supported.
+     *
+     * @see http://php.net/arrayaccess.offsetset
+     * @throws BadMethodCallException
+     */
+    public function offsetSet($key, $value)
+    {
+        throw BadMethodCallException::classIsImmutable(__CLASS__);
+    }
+
+    /**
+     * Not supported.
+     *
+     * @see http://php.net/arrayaccess.offsetunset
+     * @throws BadMethodCallException
+     */
+    public function offsetUnset($key)
+    {
+        throw BadMethodCallException::classIsImmutable(__CLASS__);
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/Model/IndexInfoIterator.php b/cache/stores/mongodb/MongoDB/Model/IndexInfoIterator.php
new file mode 100755 (executable)
index 0000000..5195172
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+/*
+ * Copyright 2015-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Model;
+
+use Iterator;
+
+/**
+ * IndexInfoIterator interface.
+ *
+ * This iterator is used for enumerating indexes in a collection.
+ *
+ * @api
+ * @see \MongoDB\Collection::listIndexes()
+ */
+interface IndexInfoIterator extends Iterator
+{
+    /**
+     * Return the current element as a IndexInfo instance.
+     *
+     * @return IndexInfo
+     */
+    public function current();
+}
diff --git a/cache/stores/mongodb/MongoDB/Model/IndexInfoIteratorIterator.php b/cache/stores/mongodb/MongoDB/Model/IndexInfoIteratorIterator.php
new file mode 100755 (executable)
index 0000000..7eb3b6b
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+/*
+ * Copyright 2015-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Model;
+
+use IteratorIterator;
+
+/**
+ * IndexInfoIterator for both listIndexes command and legacy query results.
+ *
+ * This common iterator may be used to wrap a Cursor returned by both the
+ * listIndexes command and, for legacy servers, queries on the "system.indexes"
+ * collection.
+ *
+ * @internal
+ * @see \MongoDB\Collection::listIndexes()
+ * @see https://github.com/mongodb/specifications/blob/master/source/enumerate-indexes.rst
+ * @see http://docs.mongodb.org/manual/reference/command/listIndexes/
+ * @see http://docs.mongodb.org/manual/reference/system-collections/
+ */
+class IndexInfoIteratorIterator extends IteratorIterator implements IndexInfoIterator
+{
+    /**
+     * Return the current element as an IndexInfo instance.
+     *
+     * @see IndexInfoIterator::current()
+     * @see http://php.net/iterator.current
+     * @return IndexInfo
+     */
+    public function current()
+    {
+        return new IndexInfo(parent::current());
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/Model/IndexInput.php b/cache/stores/mongodb/MongoDB/Model/IndexInput.php
new file mode 100755 (executable)
index 0000000..b306fa0
--- /dev/null
@@ -0,0 +1,99 @@
+<?php
+/*
+ * Copyright 2015-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Model;
+
+use MongoDB\BSON\Serializable;
+use MongoDB\Exception\InvalidArgumentException;
+
+/**
+ * Index input model class.
+ *
+ * This class is used to validate user input for index creation.
+ *
+ * @internal
+ * @see \MongoDB\Collection::createIndexes()
+ * @see https://github.com/mongodb/specifications/blob/master/source/enumerate-indexes.rst
+ * @see http://docs.mongodb.org/manual/reference/method/db.collection.createIndex/
+ */
+class IndexInput implements Serializable
+{
+    private $index;
+
+    /**
+     * Constructor.
+     *
+     * @param array $index Index specification
+     * @throws InvalidArgumentException
+     */
+    public function __construct(array $index)
+    {
+        if ( ! isset($index['key'])) {
+            throw new InvalidArgumentException('Required "key" document is missing from index specification');
+        }
+
+        if ( ! is_array($index['key']) && ! is_object($index['key'])) {
+            throw InvalidArgumentException::invalidType('"key" option', $index['key'], 'array or object');
+        }
+
+        foreach ($index['key'] as $fieldName => $order) {
+            if ( ! is_int($order) && ! is_float($order) && ! is_string($order)) {
+                throw InvalidArgumentException::invalidType(sprintf('order value for "%s" field within "key" option', $fieldName), $order, 'numeric or string');
+            }
+        }
+
+        if ( ! isset($index['ns'])) {
+            throw new InvalidArgumentException('Required "ns" option is missing from index specification');
+        }
+
+        if ( ! is_string($index['ns'])) {
+            throw InvalidArgumentException::invalidType('"ns" option', $index['ns'], 'string');
+        }
+
+        if ( ! isset($index['name'])) {
+            $index['name'] = \MongoDB\generate_index_name($index['key']);
+        }
+
+        if ( ! is_string($index['name'])) {
+            throw InvalidArgumentException::invalidType('"name" option', $index['name'], 'string');
+        }
+
+        $this->index = $index;
+    }
+
+    /**
+     * Return the index name.
+     *
+     * @param string
+     */
+    public function __toString()
+    {
+        return $this->index['name'];
+    }
+
+    /**
+     * Serialize the index information to BSON for index creation.
+     *
+     * @see \MongoDB\Collection::createIndexes()
+     * @see http://php.net/mongodb-bson-serializable.bsonserialize
+     * @return array
+     */
+    public function bsonSerialize()
+    {
+        return $this->index;
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/Model/TypeMapArrayIterator.php b/cache/stores/mongodb/MongoDB/Model/TypeMapArrayIterator.php
new file mode 100755 (executable)
index 0000000..b30a0ec
--- /dev/null
@@ -0,0 +1,166 @@
+<?php
+/*
+ * Copyright 2016-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Model;
+
+use ArrayIterator;
+use MongoDB\Exception\BadMethodCallException;
+
+/**
+ * Iterator for applying a type map to documents in inline command results.
+ *
+ * @internal
+ */
+class TypeMapArrayIterator extends ArrayIterator
+{
+    private $typeMap;
+
+    /**
+     * Constructor.
+     *
+     * @param array $documents
+     * @param array $typeMap
+     */
+    public function __construct(array $documents = [], array $typeMap)
+    {
+        parent::__construct($documents);
+
+        $this->typeMap = $typeMap;
+    }
+
+    /**
+     * Not supported.
+     *
+     * @see http://php.net/arrayiterator.append
+     * @throws BadMethodCallException
+     */
+    public function append($value)
+    {
+        throw BadMethodCallException::classIsImmutable(__CLASS__);
+    }
+
+    /**
+     * Not supported.
+     *
+     * @see http://php.net/arrayiterator.asort
+     * @throws BadMethodCallException
+     */
+    public function asort()
+    {
+        throw BadMethodCallException::classIsImmutable(__CLASS__);
+    }
+
+    /**
+     * Return the current element with the type map applied to it.
+     *
+     * @see http://php.net/arrayiterator.current
+     * @return array|object
+     */
+    public function current()
+    {
+        return \MongoDB\apply_type_map_to_document(parent::current(), $this->typeMap);
+    }
+
+    /**
+     * Not supported.
+     *
+     * @see http://php.net/arrayiterator.ksort
+     * @throws BadMethodCallException
+     */
+    public function ksort()
+    {
+        throw BadMethodCallException::classIsImmutable(__CLASS__);
+    }
+
+    /**
+     * Not supported.
+     *
+     * @see http://php.net/arrayiterator.natcasesort
+     * @throws BadMethodCallException
+     */
+    public function natcasesort()
+    {
+        throw BadMethodCallException::classIsImmutable(__CLASS__);
+    }
+
+    /**
+     * Not supported.
+     *
+     * @see http://php.net/arrayiterator.natsort
+     * @throws BadMethodCallException
+     */
+    public function natsort()
+    {
+        throw BadMethodCallException::classIsImmutable(__CLASS__);
+    }
+
+    /**
+     * Return the value from the provided offset with the type map applied.
+     *
+     * @see http://php.net/arrayiterator.offsetget
+     * @param mixed $offset
+     * @return array|object
+     */
+    public function offsetGet($offset)
+    {
+        return \MongoDB\apply_type_map_to_document(parent::offsetGet($offset), $this->typeMap);
+    }
+
+    /**
+     * Not supported.
+     *
+     * @see http://php.net/arrayiterator.offsetset
+     * @throws BadMethodCallException
+     */
+    public function offsetSet($index, $newval)
+    {
+        throw BadMethodCallException::classIsImmutable(__CLASS__);
+    }
+
+    /**
+     * Not supported.
+     *
+     * @see http://php.net/arrayiterator.offsetunset
+     * @throws BadMethodCallException
+     */
+    public function offsetUnset($index)
+    {
+        throw BadMethodCallException::classIsImmutable(__CLASS__);
+    }
+
+    /**
+     * Not supported.
+     *
+     * @see http://php.net/arrayiterator.uasort
+     * @throws BadMethodCallException
+     */
+    public function uasort($cmp_function)
+    {
+        throw BadMethodCallException::classIsImmutable(__CLASS__);
+    }
+
+    /**
+     * Not supported.
+     *
+     * @see http://php.net/arrayiterator.uksort
+     * @throws BadMethodCallException
+     */
+    public function uksort($cmp_function)
+    {
+        throw BadMethodCallException::classIsImmutable(__CLASS__);
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/Operation/Aggregate.php b/cache/stores/mongodb/MongoDB/Operation/Aggregate.php
new file mode 100755 (executable)
index 0000000..2ddba68
--- /dev/null
@@ -0,0 +1,363 @@
+<?php
+/*
+ * Copyright 2015-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Operation;
+
+use MongoDB\Driver\Command;
+use MongoDB\Driver\ReadConcern;
+use MongoDB\Driver\ReadPreference;
+use MongoDB\Driver\Server;
+use MongoDB\Driver\Session;
+use MongoDB\Driver\WriteConcern;
+use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
+use MongoDB\Exception\InvalidArgumentException;
+use MongoDB\Exception\UnexpectedValueException;
+use MongoDB\Exception\UnsupportedException;
+use MongoDB\Model\TypeMapArrayIterator;
+use ArrayIterator;
+use stdClass;
+use Traversable;
+
+/**
+ * Operation for the aggregate command.
+ *
+ * @api
+ * @see \MongoDB\Collection::aggregate()
+ * @see http://docs.mongodb.org/manual/reference/command/aggregate/
+ */
+class Aggregate implements Executable
+{
+    private static $wireVersionForCollation = 5;
+    private static $wireVersionForDocumentLevelValidation = 4;
+    private static $wireVersionForReadConcern = 4;
+    private static $wireVersionForWriteConcern = 5;
+
+    private $databaseName;
+    private $collectionName;
+    private $pipeline;
+    private $options;
+
+    /**
+     * Constructs an aggregate command.
+     *
+     * Supported options:
+     *
+     *  * allowDiskUse (boolean): Enables writing to temporary files. When set
+     *    to true, aggregation stages can write data to the _tmp sub-directory
+     *    in the dbPath directory. The default is false.
+     *
+     *  * batchSize (integer): The number of documents to return per batch.
+     *
+     *  * bypassDocumentValidation (boolean): If true, allows the write to
+     *    circumvent document level validation. This only applies when the $out
+     *    stage is specified.
+     *
+     *    For servers < 3.2, this option is ignored as document level validation
+     *    is not available.
+     *
+     *  * collation (document): Collation specification.
+     *
+     *    This is not supported for server versions < 3.4 and will result in an
+     *    exception at execution time if used.
+     *
+     *  * comment (string): An arbitrary string to help trace the operation
+     *    through the database profiler, currentOp, and logs.
+     *
+     *  * explain (boolean): Specifies whether or not to return the information
+     *    on the processing of the pipeline.
+     *
+     *  * hint (string|document): The index to use. Specify either the index
+     *    name as a string or the index key pattern as a document. If specified,
+     *    then the query system will only consider plans using the hinted index.
+     *
+     *  * maxTimeMS (integer): The maximum amount of time to allow the query to
+     *    run.
+     *
+     *  * readConcern (MongoDB\Driver\ReadConcern): Read concern. Note that a
+     *    "majority" read concern is not compatible with the $out stage.
+     *
+     *    This is not supported for server versions < 3.2 and will result in an
+     *    exception at execution time if used.
+     *
+     *  * readPreference (MongoDB\Driver\ReadPreference): Read preference.
+     *
+     *    This option is ignored if the $out stage is specified.
+     *
+     *  * session (MongoDB\Driver\Session): Client session.
+     *
+     *    Sessions are not supported for server versions < 3.6.
+     *
+     *  * typeMap (array): Type map for BSON deserialization. This will be
+     *    applied to the returned Cursor (it is not sent to the server).
+     *
+     *  * useCursor (boolean): Indicates whether the command will request that
+     *    the server provide results using a cursor. The default is true.
+     *
+     *    This option allows users to turn off cursors if necessary to aid in
+     *    mongod/mongos upgrades.
+     *
+     *  * writeConcern (MongoDB\Driver\WriteConcern): Write concern. This only
+     *    applies when the $out stage is specified.
+     *
+     *    This is not supported for server versions < 3.4 and will result in an
+     *    exception at execution time if used.
+     *
+     * Note: Collection-agnostic commands (e.g. $currentOp) may be executed by
+     * specifying null for the collection name.
+     *
+     * @param string      $databaseName   Database name
+     * @param string|null $collectionName Collection name
+     * @param array       $pipeline       List of pipeline operations
+     * @param array       $options        Command options
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function __construct($databaseName, $collectionName, array $pipeline, array $options = [])
+    {
+        $expectedIndex = 0;
+
+        foreach ($pipeline as $i => $operation) {
+            if ($i !== $expectedIndex) {
+                throw new InvalidArgumentException(sprintf('$pipeline is not a list (unexpected index: "%s")', $i));
+            }
+
+            if ( ! is_array($operation) && ! is_object($operation)) {
+                throw InvalidArgumentException::invalidType(sprintf('$pipeline[%d]', $i), $operation, 'array or object');
+            }
+
+            $expectedIndex += 1;
+        }
+
+        $options += [
+            'allowDiskUse' => false,
+            'useCursor' => true,
+        ];
+
+        if ( ! is_bool($options['allowDiskUse'])) {
+            throw InvalidArgumentException::invalidType('"allowDiskUse" option', $options['allowDiskUse'], 'boolean');
+        }
+
+        if (isset($options['batchSize']) && ! is_integer($options['batchSize'])) {
+            throw InvalidArgumentException::invalidType('"batchSize" option', $options['batchSize'], 'integer');
+        }
+
+        if (isset($options['bypassDocumentValidation']) && ! is_bool($options['bypassDocumentValidation'])) {
+            throw InvalidArgumentException::invalidType('"bypassDocumentValidation" option', $options['bypassDocumentValidation'], 'boolean');
+        }
+
+        if (isset($options['collation']) && ! is_array($options['collation']) && ! is_object($options['collation'])) {
+            throw InvalidArgumentException::invalidType('"collation" option', $options['collation'], 'array or object');
+        }
+
+        if (isset($options['comment']) && ! is_string($options['comment'])) {
+            throw InvalidArgumentException::invalidType('"comment" option', $options['comment'], 'string');
+        }
+
+        if (isset($options['explain']) && ! is_bool($options['explain'])) {
+            throw InvalidArgumentException::invalidType('"explain" option', $options['explain'], 'boolean');
+        }
+
+        if (isset($options['hint']) && ! is_string($options['hint']) && ! is_array($options['hint']) && ! is_object($options['hint'])) {
+            throw InvalidArgumentException::invalidType('"hint" option', $options['hint'], 'string or array or object');
+        }
+
+        if (isset($options['maxAwaitTimeMS']) && ! is_integer($options['maxAwaitTimeMS'])) {
+            throw InvalidArgumentException::invalidType('"maxAwaitTimeMS" option', $options['maxAwaitTimeMS'], 'integer');
+        }
+
+        if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
+            throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
+        }
+
+        if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
+            throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern');
+        }
+
+        if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
+            throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
+        }
+
+        if (isset($options['session']) && ! $options['session'] instanceof Session) {
+            throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
+        }
+
+        if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
+            throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
+        }
+
+        if ( ! is_bool($options['useCursor'])) {
+            throw InvalidArgumentException::invalidType('"useCursor" option', $options['useCursor'], 'boolean');
+        }
+
+        if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
+            throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
+        }
+
+        if (isset($options['batchSize']) && ! $options['useCursor']) {
+            throw new InvalidArgumentException('"batchSize" option should not be used if "useCursor" is false');
+        }
+
+        if (isset($options['readConcern']) && $options['readConcern']->isDefault()) {
+            unset($options['readConcern']);
+        }
+
+        if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) {
+            unset($options['writeConcern']);
+        }
+
+        if ( ! empty($options['explain'])) {
+            $options['useCursor'] = false;
+        }
+
+        $this->databaseName = (string) $databaseName;
+        $this->collectionName = isset($collectionName) ? (string) $collectionName : null;
+        $this->pipeline = $pipeline;
+        $this->options = $options;
+    }
+
+    /**
+     * Execute the operation.
+     *
+     * @see Executable::execute()
+     * @param Server $server
+     * @return Traversable
+     * @throws UnexpectedValueException if the command response was malformed
+     * @throws UnsupportedException if collation, read concern, or write concern is used and unsupported
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function execute(Server $server)
+    {
+        if (isset($this->options['collation']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) {
+            throw UnsupportedException::collationNotSupported();
+        }
+
+        if (isset($this->options['readConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
+            throw UnsupportedException::readConcernNotSupported();
+        }
+
+        if (isset($this->options['writeConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForWriteConcern)) {
+            throw UnsupportedException::writeConcernNotSupported();
+        }
+
+        $hasExplain = ! empty($this->options['explain']);
+        $hasOutStage = \MongoDB\is_last_pipeline_operator_out($this->pipeline);
+
+        $command = $this->createCommand($server);
+        $options = $this->createOptions($hasOutStage, $hasExplain);
+
+        $cursor = ($hasOutStage && ! $hasExplain)
+            ? $server->executeReadWriteCommand($this->databaseName, $command, $options)
+            : $server->executeReadCommand($this->databaseName, $command, $options);
+
+        if ($this->options['useCursor'] || $hasExplain) {
+            if (isset($this->options['typeMap'])) {
+                $cursor->setTypeMap($this->options['typeMap']);
+            }
+
+            return $cursor;
+        }
+
+        $result = current($cursor->toArray());
+
+        if ( ! isset($result->result) || ! is_array($result->result)) {
+            throw new UnexpectedValueException('aggregate command did not return a "result" array');
+        }
+
+        if (isset($this->options['typeMap'])) {
+            return new TypeMapArrayIterator($result->result, $this->options['typeMap']);
+        }
+
+        return new ArrayIterator($result->result);
+    }
+
+    /**
+     * Create the aggregate command.
+     *
+     * @param Server  $server
+     * @return Command
+     */
+    private function createCommand(Server $server)
+    {
+        $cmd = [
+            'aggregate' => isset($this->collectionName) ? $this->collectionName : 1,
+            'pipeline' => $this->pipeline,
+        ];
+        $cmdOptions = [];
+
+        $cmd['allowDiskUse'] = $this->options['allowDiskUse'];
+
+        if (isset($this->options['bypassDocumentValidation']) && \MongoDB\server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)) {
+            $cmd['bypassDocumentValidation'] = $this->options['bypassDocumentValidation'];
+        }
+
+        foreach (['comment', 'explain', 'maxTimeMS'] as $option) {
+            if (isset($this->options[$option])) {
+                $cmd[$option] = $this->options[$option];
+            }
+        }
+
+        if (isset($this->options['collation'])) {
+            $cmd['collation'] = (object) $this->options['collation'];
+        }
+
+        if (isset($this->options['hint'])) {
+            $cmd['hint'] = is_array($this->options['hint']) ? (object) $this->options['hint'] : $this->options['hint'];
+        }
+
+        if (isset($this->options['maxAwaitTimeMS'])) {
+            $cmdOptions['maxAwaitTimeMS'] = $this->options['maxAwaitTimeMS'];
+        }
+
+        if ($this->options['useCursor']) {
+            $cmd['cursor'] = isset($this->options["batchSize"])
+                ? ['batchSize' => $this->options["batchSize"]]
+                : new stdClass;
+        }
+
+        return new Command($cmd, $cmdOptions);
+    }
+
+    /**
+     * Create options for executing the command.
+     *
+     * @see http://php.net/manual/en/mongodb-driver-server.executereadcommand.php
+     * @see http://php.net/manual/en/mongodb-driver-server.executereadwritecommand.php
+     * @param boolean $hasOutStage
+     * @return array
+     */
+    private function createOptions($hasOutStage, $hasExplain)
+    {
+        $options = [];
+
+        if (isset($this->options['readConcern'])) {
+            $options['readConcern'] = $this->options['readConcern'];
+        }
+
+        if ( ! $hasOutStage && isset($this->options['readPreference'])) {
+            $options['readPreference'] = $this->options['readPreference'];
+        }
+
+        if (isset($this->options['session'])) {
+            $options['session'] = $this->options['session'];
+        }
+
+        if ($hasOutStage && ! $hasExplain && isset($this->options['writeConcern'])) {
+            $options['writeConcern'] = $this->options['writeConcern'];
+        }
+
+        return $options;
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/Operation/BulkWrite.php b/cache/stores/mongodb/MongoDB/Operation/BulkWrite.php
new file mode 100755 (executable)
index 0000000..82ef082
--- /dev/null
@@ -0,0 +1,380 @@
+<?php
+/*
+ * Copyright 2015-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Operation;
+
+use MongoDB\BulkWriteResult;
+use MongoDB\Driver\BulkWrite as Bulk;
+use MongoDB\Driver\Server;
+use MongoDB\Driver\Session;
+use MongoDB\Driver\WriteConcern;
+use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
+use MongoDB\Exception\InvalidArgumentException;
+use MongoDB\Exception\UnsupportedException;
+
+/**
+ * Operation for executing multiple write operations.
+ *
+ * @api
+ * @see \MongoDB\Collection::bulkWrite()
+ */
+class BulkWrite implements Executable
+{
+    const DELETE_MANY = 'deleteMany';
+    const DELETE_ONE  = 'deleteOne';
+    const INSERT_ONE  = 'insertOne';
+    const REPLACE_ONE = 'replaceOne';
+    const UPDATE_MANY = 'updateMany';
+    const UPDATE_ONE  = 'updateOne';
+
+    private static $wireVersionForArrayFilters = 6;
+    private static $wireVersionForCollation = 5;
+    private static $wireVersionForDocumentLevelValidation = 4;
+
+    private $databaseName;
+    private $collectionName;
+    private $operations;
+    private $options;
+    private $isArrayFiltersUsed = false;
+    private $isCollationUsed = false;
+
+    /**
+     * Constructs a bulk write operation.
+     *
+     * Example array structure for all supported operation types:
+     *
+     *  [
+     *    [ 'deleteMany' => [ $filter, $options ] ],
+     *    [ 'deleteOne'  => [ $filter, $options ] ],
+     *    [ 'insertOne'  => [ $document ] ],
+     *    [ 'replaceOne' => [ $filter, $replacement, $options ] ],
+     *    [ 'updateMany' => [ $filter, $update, $options ] ],
+     *    [ 'updateOne'  => [ $filter, $update, $options ] ],
+     *  ]
+     *
+     * Arguments correspond to the respective Operation classes; however, the
+     * writeConcern option is specified for the top-level bulk write operation
+     * instead of each individual operation.
+     *
+     * Supported options for deleteMany and deleteOne operations:
+     *
+     *  * collation (document): Collation specification.
+     *
+     *    This is not supported for server versions < 3.4 and will result in an
+     *    exception at execution time if used.
+     *
+     * Supported options for replaceOne, updateMany, and updateOne operations:
+     *
+     *  * collation (document): Collation specification.
+     *
+     *    This is not supported for server versions < 3.4 and will result in an
+     *    exception at execution time if used.
+     *
+     *  * upsert (boolean): When true, a new document is created if no document
+     *    matches the query. The default is false.
+     *
+     * Supported options for updateMany and updateOne operations:
+     *
+     *  * arrayFilters (document array): A set of filters specifying to which
+     *    array elements an update should apply.
+     *
+     *    This is not supported for server versions < 3.6 and will result in an
+     *    exception at execution time if used.
+     *
+     * Supported options for the bulk write operation:
+     *
+     *  * bypassDocumentValidation (boolean): If true, allows the write to
+     *    circumvent document level validation. The default is false.
+     *
+     *    For servers < 3.2, this option is ignored as document level validation
+     *    is not available.
+     *
+     *  * ordered (boolean): If true, when an insert fails, return without
+     *    performing the remaining writes. If false, when a write fails,
+     *    continue with the remaining writes, if any. The default is true.
+     *
+     *  * session (MongoDB\Driver\Session): Client session.
+     *
+     *    Sessions are not supported for server versions < 3.6.
+     *
+     *  * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
+     *
+     * @param string  $databaseName   Database name
+     * @param string  $collectionName Collection name
+     * @param array[] $operations     List of write operations
+     * @param array   $options        Command options
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function __construct($databaseName, $collectionName, array $operations, array $options = [])
+    {
+        if (empty($operations)) {
+            throw new InvalidArgumentException('$operations is empty');
+        }
+
+        $expectedIndex = 0;
+
+        foreach ($operations as $i => $operation) {
+            if ($i !== $expectedIndex) {
+                throw new InvalidArgumentException(sprintf('$operations is not a list (unexpected index: "%s")', $i));
+            }
+
+            if ( ! is_array($operation)) {
+                throw InvalidArgumentException::invalidType(sprintf('$operations[%d]', $i), $operation, 'array');
+            }
+
+            if (count($operation) !== 1) {
+                throw new InvalidArgumentException(sprintf('Expected one element in $operation[%d], actually: %d', $i, count($operation)));
+            }
+
+            $type = key($operation);
+            $args = current($operation);
+
+            if ( ! isset($args[0]) && ! array_key_exists(0, $args)) {
+                throw new InvalidArgumentException(sprintf('Missing first argument for $operations[%d]["%s"]', $i, $type));
+            }
+
+            if ( ! is_array($args[0]) && ! is_object($args[0])) {
+                throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][0]', $i, $type), $args[0], 'array or object');
+            }
+
+            switch ($type) {
+                case self::INSERT_ONE:
+                    break;
+
+                case self::DELETE_MANY:
+                case self::DELETE_ONE:
+                    if ( ! isset($args[1])) {
+                        $args[1] = [];
+                    }
+
+                    if ( ! is_array($args[1])) {
+                        throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][1]', $i, $type), $args[1], 'array');
+                    }
+
+                    $args[1]['limit'] = ($type === self::DELETE_ONE ? 1 : 0);
+
+                    if (isset($args[1]['collation'])) {
+                        $this->isCollationUsed = true;
+
+                        if ( ! is_array($args[1]['collation']) && ! is_object($args[1]['collation'])) {
+                            throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][1]["collation"]', $i, $type), $args[1]['collation'], 'array or object');
+                        }
+                    }
+
+                    $operations[$i][$type][1] = $args[1];
+
+                    break;
+
+                case self::REPLACE_ONE:
+                    if ( ! isset($args[1]) && ! array_key_exists(1, $args)) {
+                        throw new InvalidArgumentException(sprintf('Missing second argument for $operations[%d]["%s"]', $i, $type));
+                    }
+
+                    if ( ! is_array($args[1]) && ! is_object($args[1])) {
+                        throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][1]', $i, $type), $args[1], 'array or object');
+                    }
+
+                    if (\MongoDB\is_first_key_operator($args[1])) {
+                        throw new InvalidArgumentException(sprintf('First key in $operations[%d]["%s"][1] is an update operator', $i, $type));
+                    }
+
+                    if ( ! isset($args[2])) {
+                        $args[2] = [];
+                    }
+
+                    if ( ! is_array($args[2])) {
+                        throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]', $i, $type), $args[2], 'array');
+                    }
+
+                    $args[2]['multi'] = false;
+                    $args[2] += ['upsert' => false];
+
+                    if (isset($args[2]['collation'])) {
+                        $this->isCollationUsed = true;
+
+                        if ( ! is_array($args[2]['collation']) && ! is_object($args[2]['collation'])) {
+                            throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]["collation"]', $i, $type), $args[2]['collation'], 'array or object');
+                        }
+                    }
+
+                    if ( ! is_bool($args[2]['upsert'])) {
+                        throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]["upsert"]', $i, $type), $args[2]['upsert'], 'boolean');
+                    }
+
+                    $operations[$i][$type][2] = $args[2];
+
+                    break;
+
+                case self::UPDATE_MANY:
+                case self::UPDATE_ONE:
+                    if ( ! isset($args[1]) && ! array_key_exists(1, $args)) {
+                        throw new InvalidArgumentException(sprintf('Missing second argument for $operations[%d]["%s"]', $i, $type));
+                    }
+
+                    if ( ! is_array($args[1]) && ! is_object($args[1])) {
+                        throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][1]', $i, $type), $args[1], 'array or object');
+                    }
+
+                    if ( ! \MongoDB\is_first_key_operator($args[1])) {
+                        throw new InvalidArgumentException(sprintf('First key in $operations[%d]["%s"][1] is not an update operator', $i, $type));
+                    }
+
+                    if ( ! isset($args[2])) {
+                        $args[2] = [];
+                    }
+
+                    if ( ! is_array($args[2])) {
+                        throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]', $i, $type), $args[2], 'array');
+                    }
+
+                    $args[2]['multi'] = ($type === self::UPDATE_MANY);
+                    $args[2] += ['upsert' => false];
+
+                    if (isset($args[2]['arrayFilters'])) {
+                        $this->isArrayFiltersUsed = true;
+
+                        if ( ! is_array($args[2]['arrayFilters'])) {
+                            throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]["arrayFilters"]', $i, $type), $args[2]['arrayFilters'], 'array');
+                        }
+                    }
+
+                    if (isset($args[2]['collation'])) {
+                        $this->isCollationUsed = true;
+
+                        if ( ! is_array($args[2]['collation']) && ! is_object($args[2]['collation'])) {
+                            throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]["collation"]', $i, $type), $args[2]['collation'], 'array or object');
+                        }
+                    }
+
+                    if ( ! is_bool($args[2]['upsert'])) {
+                        throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]["upsert"]', $i, $type), $args[2]['upsert'], 'boolean');
+                    }
+
+                    $operations[$i][$type][2] = $args[2];
+
+                    break;
+
+                default:
+                    throw new InvalidArgumentException(sprintf('Unknown operation type "%s" in $operations[%d]', $type, $i));
+            }
+
+            $expectedIndex += 1;
+        }
+
+        $options += ['ordered' => true];
+
+        if (isset($options['bypassDocumentValidation']) && ! is_bool($options['bypassDocumentValidation'])) {
+            throw InvalidArgumentException::invalidType('"bypassDocumentValidation" option', $options['bypassDocumentValidation'], 'boolean');
+        }
+
+        if ( ! is_bool($options['ordered'])) {
+            throw InvalidArgumentException::invalidType('"ordered" option', $options['ordered'], 'boolean');
+        }
+
+        if (isset($options['session']) && ! $options['session'] instanceof Session) {
+            throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
+        }
+
+        if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
+            throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
+        }
+
+        if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) {
+            unset($options['writeConcern']);
+        }
+
+        $this->databaseName = (string) $databaseName;
+        $this->collectionName = (string) $collectionName;
+        $this->operations = $operations;
+        $this->options = $options;
+    }
+
+    /**
+     * Execute the operation.
+     *
+     * @see Executable::execute()
+     * @param Server $server
+     * @return BulkWriteResult
+     * @throws UnsupportedException if array filters or collation is used and unsupported
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function execute(Server $server)
+    {
+        if ($this->isArrayFiltersUsed && ! \MongoDB\server_supports_feature($server, self::$wireVersionForArrayFilters)) {
+            throw UnsupportedException::arrayFiltersNotSupported();
+        }
+
+        if ($this->isCollationUsed && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) {
+            throw UnsupportedException::collationNotSupported();
+        }
+
+        $options = ['ordered' => $this->options['ordered']];
+
+        if (isset($this->options['bypassDocumentValidation']) && \MongoDB\server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)) {
+            $options['bypassDocumentValidation'] = $this->options['bypassDocumentValidation'];
+        }
+
+        $bulk = new Bulk($options);
+        $insertedIds = [];
+
+        foreach ($this->operations as $i => $operation) {
+            $type = key($operation);
+            $args = current($operation);
+
+            switch ($type) {
+                case self::DELETE_MANY:
+                case self::DELETE_ONE:
+                    $bulk->delete($args[0], $args[1]);
+                    break;
+
+                case self::INSERT_ONE:
+                    $insertedIds[$i] = $bulk->insert($args[0]);
+                    break;
+
+                case self::REPLACE_ONE:
+                case self::UPDATE_MANY:
+                case self::UPDATE_ONE:
+                    $bulk->update($args[0], $args[1], $args[2]);
+            }
+        }
+
+        $writeResult = $server->executeBulkWrite($this->databaseName . '.' . $this->collectionName, $bulk, $this->createOptions());
+
+        return new BulkWriteResult($writeResult, $insertedIds);
+    }
+
+    /**
+     * Create options for executing the bulk write.
+     *
+     * @see http://php.net/manual/en/mongodb-driver-server.executebulkwrite.php
+     * @return array
+     */
+    private function createOptions()
+    {
+        $options = [];
+
+        if (isset($this->options['session'])) {
+            $options['session'] = $this->options['session'];
+        }
+
+        if (isset($this->options['writeConcern'])) {
+            $options['writeConcern'] = $this->options['writeConcern'];
+        }
+
+        return $options;
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/Operation/Count.php b/cache/stores/mongodb/MongoDB/Operation/Count.php
new file mode 100755 (executable)
index 0000000..771e276
--- /dev/null
@@ -0,0 +1,224 @@
+<?php
+/*
+ * Copyright 2015-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Operation;
+
+use MongoDB\Driver\Command;
+use MongoDB\Driver\ReadConcern;
+use MongoDB\Driver\ReadPreference;
+use MongoDB\Driver\Server;
+use MongoDB\Driver\Session;
+use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
+use MongoDB\Exception\InvalidArgumentException;
+use MongoDB\Exception\UnexpectedValueException;
+use MongoDB\Exception\UnsupportedException;
+
+/**
+ * Operation for the count command.
+ *
+ * @api
+ * @see \MongoDB\Collection::count()
+ * @see http://docs.mongodb.org/manual/reference/command/count/
+ */
+class Count implements Executable, Explainable
+{
+    private static $wireVersionForCollation = 5;
+    private static $wireVersionForReadConcern = 4;
+
+    private $databaseName;
+    private $collectionName;
+    private $filter;
+    private $options;
+
+    /**
+     * Constructs a count command.
+     *
+     * Supported options:
+     *
+     *  * collation (document): Collation specification.
+     *
+     *    This is not supported for server versions < 3.4 and will result in an
+     *    exception at execution time if used.
+     *
+     *  * hint (string|document): The index to use. Specify either the index
+     *    name as a string or the index key pattern as a document. If specified,
+     *    then the query system will only consider plans using the hinted index.
+     *
+     *  * limit (integer): The maximum number of documents to count.
+     *
+     *  * maxTimeMS (integer): The maximum amount of time to allow the query to
+     *    run.
+     *
+     *  * readConcern (MongoDB\Driver\ReadConcern): Read concern.
+     *
+     *    This is not supported for server versions < 3.2 and will result in an
+     *    exception at execution time if used.
+     *
+     *  * readPreference (MongoDB\Driver\ReadPreference): Read preference.
+     *
+     *  * session (MongoDB\Driver\Session): Client session.
+     *
+     *    Sessions are not supported for server versions < 3.6.
+     *
+     *  * skip (integer): The number of documents to skip before returning the
+     *    documents.
+     *
+     * @param string       $databaseName   Database name
+     * @param string       $collectionName Collection name
+     * @param array|object $filter         Query by which to filter documents
+     * @param array        $options        Command options
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function __construct($databaseName, $collectionName, $filter = [], array $options = [])
+    {
+        if ( ! is_array($filter) && ! is_object($filter)) {
+            throw InvalidArgumentException::invalidType('$filter', $filter, 'array or object');
+        }
+
+        if (isset($options['collation']) && ! is_array($options['collation']) && ! is_object($options['collation'])) {
+            throw InvalidArgumentException::invalidType('"collation" option', $options['collation'], 'array or object');
+        }
+
+        if (isset($options['hint']) && ! is_string($options['hint']) && ! is_array($options['hint']) && ! is_object($options['hint'])) {
+            throw InvalidArgumentException::invalidType('"hint" option', $options['hint'], 'string or array or object');
+        }
+
+        if (isset($options['limit']) && ! is_integer($options['limit'])) {
+            throw InvalidArgumentException::invalidType('"limit" option', $options['limit'], 'integer');
+        }
+
+        if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
+            throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
+        }
+
+        if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
+            throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern');
+        }
+
+        if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
+            throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
+        }
+
+        if (isset($options['session']) && ! $options['session'] instanceof Session) {
+            throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
+        }
+
+        if (isset($options['skip']) && ! is_integer($options['skip'])) {
+            throw InvalidArgumentException::invalidType('"skip" option', $options['skip'], 'integer');
+        }
+
+        if (isset($options['readConcern']) && $options['readConcern']->isDefault()) {
+            unset($options['readConcern']);
+        }
+
+        $this->databaseName = (string) $databaseName;
+        $this->collectionName = (string) $collectionName;
+        $this->filter = $filter;
+        $this->options = $options;
+    }
+
+    /**
+     * Execute the operation.
+     *
+     * @see Executable::execute()
+     * @param Server $server
+     * @return integer
+     * @throws UnexpectedValueException if the command response was malformed
+     * @throws UnsupportedException if collation or read concern is used and unsupported
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function execute(Server $server)
+    {
+        if (isset($this->options['collation']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) {
+            throw UnsupportedException::collationNotSupported();
+        }
+
+        if (isset($this->options['readConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
+            throw UnsupportedException::readConcernNotSupported();
+        }
+
+        $cursor = $server->executeReadCommand($this->databaseName, new Command($this->createCommandDocument()), $this->createOptions());
+        $result = current($cursor->toArray());
+
+        // 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 a numeric "n" value');
+        }
+
+        return (integer) $result->n;
+    }
+
+    public function getCommandDocument(Server $server)
+    {
+        return $this->createCommandDocument();
+    }
+
+    /**
+     * Create the count command document.
+     *
+     * @return array
+     */
+    private function createCommandDocument()
+    {
+        $cmd = ['count' => $this->collectionName];
+
+        if ( ! empty($this->filter)) {
+            $cmd['query'] = (object) $this->filter;
+        }
+
+        if (isset($this->options['collation'])) {
+            $cmd['collation'] = (object) $this->options['collation'];
+        }
+
+        if (isset($this->options['hint'])) {
+            $cmd['hint'] = is_array($this->options['hint']) ? (object) $this->options['hint'] : $this->options['hint'];
+        }
+
+        foreach (['limit', 'maxTimeMS', 'skip'] as $option) {
+            if (isset($this->options[$option])) {
+                $cmd[$option] = $this->options[$option];
+            }
+        }
+
+        return $cmd;
+    }
+
+    /**
+     * Create options for executing the command.
+     *
+     * @see http://php.net/manual/en/mongodb-driver-server.executereadcommand.php
+     * @return array
+     */
+    private function createOptions()
+    {
+        $options = [];
+
+        if (isset($this->options['readConcern'])) {
+            $options['readConcern'] = $this->options['readConcern'];
+        }
+
+        if (isset($this->options['readPreference'])) {
+            $options['readPreference'] = $this->options['readPreference'];
+        }
+
+        if (isset($this->options['session'])) {
+            $options['session'] = $this->options['session'];
+        }
+
+        return $options;
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/Operation/CountDocuments.php b/cache/stores/mongodb/MongoDB/Operation/CountDocuments.php
new file mode 100755 (executable)
index 0000000..d79bd7e
--- /dev/null
@@ -0,0 +1,237 @@
+<?php
+/*
+ * Copyright 2015-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Operation;
+
+use MongoDB\Driver\Command;
+use MongoDB\Driver\ReadConcern;
+use MongoDB\Driver\ReadPreference;
+use MongoDB\Driver\Server;
+use MongoDB\Driver\Session;
+use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
+use MongoDB\Exception\InvalidArgumentException;
+use MongoDB\Exception\UnexpectedValueException;
+use MongoDB\Exception\UnsupportedException;
+
+/**
+ * Operation for obtaining an exact count of documents in a collection
+ *
+ * @api
+ * @see \MongoDB\Collection::countDocuments()
+ * @see https://github.com/mongodb/specifications/blob/master/source/crud/crud.rst#countdocuments
+ */
+class CountDocuments implements Executable
+{
+    private static $wireVersionForCollation = 5;
+    private static $wireVersionForReadConcern = 4;
+
+    private $databaseName;
+    private $collectionName;
+    private $filter;
+    private $options;
+
+    /**
+     * Constructs an aggregate command for counting documents
+     *
+     * Supported options:
+     *
+     *  * collation (document): Collation specification.
+     *
+     *    This is not supported for server versions < 3.4 and will result in an
+     *    exception at execution time if used.
+     *
+     *  * hint (string|document): The index to use. Specify either the index
+     *    name as a string or the index key pattern as a document. If specified,
+     *    then the query system will only consider plans using the hinted index.
+     *
+     *  * limit (integer): The maximum number of documents to count.
+     *
+     *  * maxTimeMS (integer): The maximum amount of time to allow the query to
+     *    run.
+     *
+     *  * readConcern (MongoDB\Driver\ReadConcern): Read concern.
+     *
+     *    This is not supported for server versions < 3.2 and will result in an
+     *    exception at execution time if used.
+     *
+     *  * readPreference (MongoDB\Driver\ReadPreference): Read preference.
+     *
+     *  * session (MongoDB\Driver\Session): Client session.
+     *
+     *    Sessions are not supported for server versions < 3.6.
+     *
+     *  * skip (integer): The number of documents to skip before returning the
+     *    documents.
+     *
+     * @param string       $databaseName   Database name
+     * @param string       $collectionName Collection name
+     * @param array|object $filter         Query by which to filter documents
+     * @param array        $options        Command options
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function __construct($databaseName, $collectionName, $filter, array $options = [])
+    {
+        if ( ! is_array($filter) && ! is_object($filter)) {
+            throw InvalidArgumentException::invalidType('$filter', $filter, 'array or object');
+        }
+
+        if (isset($options['collation']) && ! is_array($options['collation']) && ! is_object($options['collation'])) {
+            throw InvalidArgumentException::invalidType('"collation" option', $options['collation'], 'array or object');
+        }
+
+        if (isset($options['hint']) && ! is_string($options['hint']) && ! is_array($options['hint']) && ! is_object($options['hint'])) {
+            throw InvalidArgumentException::invalidType('"hint" option', $options['hint'], 'string or array or object');
+        }
+
+        if (isset($options['limit']) && ! is_integer($options['limit'])) {
+            throw InvalidArgumentException::invalidType('"limit" option', $options['limit'], 'integer');
+        }
+
+        if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
+            throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
+        }
+
+        if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
+            throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern');
+        }
+
+        if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
+            throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
+        }
+
+        if (isset($options['session']) && ! $options['session'] instanceof Session) {
+            throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
+        }
+
+        if (isset($options['skip']) && ! is_integer($options['skip'])) {
+            throw InvalidArgumentException::invalidType('"skip" option', $options['skip'], 'integer');
+        }
+
+        if (isset($options['readConcern']) && $options['readConcern']->isDefault()) {
+            unset($options['readConcern']);
+        }
+
+        $this->databaseName = (string) $databaseName;
+        $this->collectionName = (string) $collectionName;
+        $this->filter = $filter;
+        $this->options = $options;
+    }
+
+    /**
+     * Execute the operation.
+     *
+     * @see Executable::execute()
+     * @param Server $server
+     * @return integer
+     * @throws UnexpectedValueException if the command response was malformed
+     * @throws UnsupportedException if collation or read concern is used and unsupported
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function execute(Server $server)
+    {
+        if (isset($this->options['collation']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) {
+            throw UnsupportedException::collationNotSupported();
+        }
+
+        if (isset($this->options['readConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
+            throw UnsupportedException::readConcernNotSupported();
+        }
+
+        $cursor = $server->executeReadCommand($this->databaseName, new Command($this->createCommandDocument()), $this->createOptions());
+        $allResults = $cursor->toArray();
+
+        /* If there are no documents to count, the aggregation pipeline has no items to group, and
+         * hence the result is an empty array (PHPLIB-376) */
+        if (count($allResults) == 0) {
+            return 0;
+        }
+
+        $result = current($allResults);
+        if ( ! isset($result->n) || ! (is_integer($result->n) || is_float($result->n))) {
+            throw new UnexpectedValueException('count command did not return a numeric "n" value');
+        }
+
+        return (integer) $result->n;
+    }
+
+    /**
+     * Create the count command document.
+     *
+     * @return array
+     */
+    private function createCommandDocument()
+    {
+        $pipeline = [
+            ['$match' => (object) $this->filter]
+        ];
+
+        if (isset($this->options['skip'])) {
+            $pipeline[] = ['$skip' => $this->options['skip']];
+        }
+
+        if (isset($this->options['limit'])) {
+            $pipeline[] = ['$limit' => $this->options['limit']];
+        }
+
+        $pipeline[] = ['$group' => ['_id' => null, 'n' => ['$sum' => 1]]];
+
+        $cmd = [
+            'aggregate' => $this->collectionName,
+            'pipeline' => $pipeline,
+            'cursor' => (object) [],
+        ];
+
+        if (isset($this->options['collation'])) {
+            $cmd['collation'] = (object) $this->options['collation'];
+        }
+
+        if (isset($this->options['hint'])) {
+            $cmd['hint'] = is_array($this->options['hint']) ? (object) $this->options['hint'] : $this->options['hint'];
+        }
+
+        if (isset($this->options['maxTimeMS'])) {
+            $cmd['maxTimeMS'] = $this->options['maxTimeMS'];
+        }
+
+        return $cmd;
+    }
+
+    /**
+     * Create options for executing the command.
+     *
+     * @see http://php.net/manual/en/mongodb-driver-server.executereadcommand.php
+     * @return array
+     */
+    private function createOptions()
+    {
+        $options = [];
+
+        if (isset($this->options['readConcern'])) {
+            $options['readConcern'] = $this->options['readConcern'];
+        }
+
+        if (isset($this->options['readPreference'])) {
+            $options['readPreference'] = $this->options['readPreference'];
+        }
+
+        if (isset($this->options['session'])) {
+            $options['session'] = $this->options['session'];
+        }
+
+        return $options;
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/Operation/CreateCollection.php b/cache/stores/mongodb/MongoDB/Operation/CreateCollection.php
new file mode 100755 (executable)
index 0000000..b5b9086
--- /dev/null
@@ -0,0 +1,258 @@
+<?php
+/*
+ * Copyright 2015-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Operation;
+
+use MongoDB\Driver\Command;
+use MongoDB\Driver\Server;
+use MongoDB\Driver\Session;
+use MongoDB\Driver\WriteConcern;
+use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
+use MongoDB\Exception\InvalidArgumentException;
+use MongoDB\Exception\UnsupportedException;
+
+/**
+ * Operation for the create command.
+ *
+ * @api
+ * @see \MongoDB\Database::createCollection()
+ * @see http://docs.mongodb.org/manual/reference/command/create/
+ */
+class CreateCollection implements Executable
+{
+    const USE_POWER_OF_2_SIZES = 1;
+    const NO_PADDING = 2;
+
+    private static $wireVersionForCollation = 5;
+    private static $wireVersionForWriteConcern = 5;
+
+    private $databaseName;
+    private $collectionName;
+    private $options = [];
+
+    /**
+     * Constructs a create command.
+     *
+     * Supported options:
+     *
+     *  * autoIndexId (boolean): Specify false to disable the automatic creation
+     *    of an index on the _id field. For replica sets, this option cannot be
+     *    false. The default is true.
+     *
+     *    This option has been deprecated since MongoDB 3.2. As of MongoDB 4.0,
+     *    this option cannot be false when creating a replicated collection
+     *    (i.e. a collection outside of the local database in any mongod mode).
+     *
+     *  * capped (boolean): Specify true to create a capped collection. If set,
+     *    the size option must also be specified. The default is false.
+     *
+     *  * collation (document): Collation specification.
+     *
+     *    This is not supported for server versions < 3.4 and will result in an
+     *    exception at execution time if used.
+     *
+     *  * flags (integer): Options for the MMAPv1 storage engine only. Must be a
+     *    bitwise combination CreateCollection::USE_POWER_OF_2_SIZES and
+     *    CreateCollection::NO_PADDING. The default is
+     *    CreateCollection::USE_POWER_OF_2_SIZES.
+     *
+     *  * indexOptionDefaults (document): Default configuration for indexes when
+     *    creating the collection.
+     *
+     *  * max (integer): The maximum number of documents allowed in the capped
+     *    collection. The size option takes precedence over this limit.
+     *
+     *  * maxTimeMS (integer): The maximum amount of time to allow the query to
+     *    run.
+     *
+     *  * session (MongoDB\Driver\Session): Client session.
+     *
+     *    Sessions are not supported for server versions < 3.6.
+     *
+     *  * size (integer): The maximum number of bytes for a capped collection.
+     *
+     *  * storageEngine (document): Storage engine options.
+     *
+     *  * typeMap (array): Type map for BSON deserialization. This will only be
+     *    used for the returned command result document.
+     *
+     *  * validationAction (string): Validation action.
+     *
+     *  * validationLevel (string): Validation level.
+     *
+     *  * validator (document): Validation rules or expressions.
+     *
+     *  * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
+     *
+     *    This is not supported for server versions < 3.4 and will result in an
+     *    exception at execution time if used.
+     *
+     * @see http://source.wiredtiger.com/2.4.1/struct_w_t___s_e_s_s_i_o_n.html#a358ca4141d59c345f401c58501276bbb
+     * @see https://docs.mongodb.org/manual/core/document-validation/
+     * @param string $databaseName   Database name
+     * @param string $collectionName Collection name
+     * @param array  $options        Command options
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function __construct($databaseName, $collectionName, array $options = [])
+    {
+        if (isset($options['autoIndexId']) && ! is_bool($options['autoIndexId'])) {
+            throw InvalidArgumentException::invalidType('"autoIndexId" option', $options['autoIndexId'], 'boolean');
+        }
+
+        if (isset($options['capped']) && ! is_bool($options['capped'])) {
+            throw InvalidArgumentException::invalidType('"capped" option', $options['capped'], 'boolean');
+        }
+
+        if (isset($options['collation']) && ! is_array($options['collation']) && ! is_object($options['collation'])) {
+            throw InvalidArgumentException::invalidType('"collation" option', $options['collation'], 'array or object');
+        }
+
+        if (isset($options['flags']) && ! is_integer($options['flags'])) {
+            throw InvalidArgumentException::invalidType('"flags" option', $options['flags'], 'integer');
+        }
+
+        if (isset($options['indexOptionDefaults']) && ! is_array($options['indexOptionDefaults']) && ! is_object($options['indexOptionDefaults'])) {
+            throw InvalidArgumentException::invalidType('"indexOptionDefaults" option', $options['indexOptionDefaults'], 'array or object');
+        }
+
+        if (isset($options['max']) && ! is_integer($options['max'])) {
+            throw InvalidArgumentException::invalidType('"max" option', $options['max'], 'integer');
+        }
+
+        if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
+            throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
+        }
+
+        if (isset($options['session']) && ! $options['session'] instanceof Session) {
+            throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
+        }
+
+        if (isset($options['size']) && ! is_integer($options['size'])) {
+            throw InvalidArgumentException::invalidType('"size" option', $options['size'], 'integer');
+        }
+
+        if (isset($options['storageEngine']) && ! is_array($options['storageEngine']) && ! is_object($options['storageEngine'])) {
+            throw InvalidArgumentException::invalidType('"storageEngine" option', $options['storageEngine'], 'array or object');
+        }
+
+        if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
+            throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
+        }
+
+        if (isset($options['validationAction']) && ! is_string($options['validationAction'])) {
+            throw InvalidArgumentException::invalidType('"validationAction" option', $options['validationAction'], 'string');
+        }
+
+        if (isset($options['validationLevel']) && ! is_string($options['validationLevel'])) {
+            throw InvalidArgumentException::invalidType('"validationLevel" option', $options['validationLevel'], 'string');
+        }
+
+        if (isset($options['validator']) && ! is_array($options['validator']) && ! is_object($options['validator'])) {
+            throw InvalidArgumentException::invalidType('"validator" option', $options['validator'], 'array or object');
+        }
+
+        if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
+            throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
+        }
+
+        if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) {
+            unset($options['writeConcern']);
+        }
+
+        if (isset($options['autoIndexId'])) {
+            trigger_error('The "autoIndexId" option is deprecated and will be removed in a future release', E_USER_DEPRECATED);
+        }
+
+        $this->databaseName = (string) $databaseName;
+        $this->collectionName = (string) $collectionName;
+        $this->options = $options;
+    }
+
+    /**
+     * Execute the operation.
+     *
+     * @see Executable::execute()
+     * @param Server $server
+     * @return array|object Command result document
+     * @throws UnsupportedException if collation or write concern is used and unsupported
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function execute(Server $server)
+    {
+        if (isset($this->options['collation']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) {
+            throw UnsupportedException::collationNotSupported();
+        }
+
+        if (isset($this->options['writeConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForWriteConcern)) {
+            throw UnsupportedException::writeConcernNotSupported();
+        }
+
+        $cursor = $server->executeWriteCommand($this->databaseName, $this->createCommand(), $this->createOptions());
+
+        if (isset($this->options['typeMap'])) {
+            $cursor->setTypeMap($this->options['typeMap']);
+        }
+
+        return current($cursor->toArray());
+    }
+
+    /**
+     * Create the create command.
+     *
+     * @return Command
+     */
+    private function createCommand()
+    {
+        $cmd = ['create' => $this->collectionName];
+
+        foreach (['autoIndexId', 'capped', 'flags', 'max', 'maxTimeMS', 'size', 'validationAction', 'validationLevel'] as $option) {
+            if (isset($this->options[$option])) {
+                $cmd[$option] = $this->options[$option];
+            }
+        }
+
+        foreach (['collation', 'indexOptionDefaults', 'storageEngine', 'validator'] as $option) {
+            if (isset($this->options[$option])) {
+                $cmd[$option] = (object) $this->options[$option];
+            }
+        }
+
+        return new Command($cmd);
+    }
+
+    /**
+     * Create options for executing the command.
+     *
+     * @see http://php.net/manual/en/mongodb-driver-server.executewritecommand.php
+     * @return array
+     */
+    private function createOptions()
+    {
+        $options = [];
+
+        if (isset($this->options['session'])) {
+            $options['session'] = $this->options['session'];
+        }
+
+        if (isset($this->options['writeConcern'])) {
+            $options['writeConcern'] = $this->options['writeConcern'];
+        }
+
+        return $options;
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/Operation/CreateIndexes.php b/cache/stores/mongodb/MongoDB/Operation/CreateIndexes.php
new file mode 100755 (executable)
index 0000000..07050f5
--- /dev/null
@@ -0,0 +1,188 @@
+<?php
+/*
+ * Copyright 2015-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Operation;
+
+use MongoDB\Driver\BulkWrite as Bulk;
+use MongoDB\Driver\Command;
+use MongoDB\Driver\Server;
+use MongoDB\Driver\Session;
+use MongoDB\Driver\WriteConcern;
+use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
+use MongoDB\Exception\InvalidArgumentException;
+use MongoDB\Exception\UnsupportedException;
+use MongoDB\Model\IndexInput;
+
+/**
+ * Operation for the createIndexes command.
+ *
+ * @api
+ * @see \MongoDB\Collection::createIndex()
+ * @see \MongoDB\Collection::createIndexes()
+ * @see http://docs.mongodb.org/manual/reference/command/createIndexes/
+ */
+class CreateIndexes implements Executable
+{
+    private static $wireVersionForCollation = 5;
+    private static $wireVersionForWriteConcern = 5;
+
+    private $databaseName;
+    private $collectionName;
+    private $indexes = [];
+    private $isCollationUsed = false;
+    private $options = [];
+
+    /**
+     * Constructs a createIndexes command.
+     *
+     * Supported options:
+     *
+     *  * maxTimeMS (integer): The maximum amount of time to allow the query to
+     *    run.
+     *
+     *  * session (MongoDB\Driver\Session): Client session.
+     *
+     *    Sessions are not supported for server versions < 3.6.
+     *
+     *  * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
+     *
+     *    This is not supported for server versions < 3.4 and will result in an
+     *    exception at execution time if used.
+     *
+     * @param string  $databaseName   Database name
+     * @param string  $collectionName Collection name
+     * @param array[] $indexes        List of index specifications
+     * @param array   $options        Command options
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function __construct($databaseName, $collectionName, array $indexes, array $options = [])
+    {
+        if (empty($indexes)) {
+            throw new InvalidArgumentException('$indexes is empty');
+        }
+
+        $expectedIndex = 0;
+
+        foreach ($indexes as $i => $index) {
+            if ($i !== $expectedIndex) {
+                throw new InvalidArgumentException(sprintf('$indexes is not a list (unexpected index: "%s")', $i));
+            }
+
+            if ( ! is_array($index)) {
+                throw InvalidArgumentException::invalidType(sprintf('$index[%d]', $i), $index, 'array');
+            }
+
+            if ( ! isset($index['ns'])) {
+                $index['ns'] = $databaseName . '.' . $collectionName;
+            }
+
+            if (isset($index['collation'])) {
+                $this->isCollationUsed = true;
+            }
+
+            $this->indexes[] = new IndexInput($index);
+
+            $expectedIndex += 1;
+        }
+
+        if (isset($options['maxTimeMS']) && !is_integer($options['maxTimeMS'])) {
+            throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
+        }
+
+        if (isset($options['session']) && ! $options['session'] instanceof Session) {
+            throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
+        }
+
+        if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
+            throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
+        }
+
+        if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) {
+            unset($options['writeConcern']);
+        }
+
+        $this->databaseName = (string) $databaseName;
+        $this->collectionName = (string) $collectionName;
+        $this->options = $options;
+    }
+
+    /**
+     * Execute the operation.
+     *
+     * @see Executable::execute()
+     * @param Server $server
+     * @return string[] The names of the created indexes
+     * @throws UnsupportedException if collation or write concern is used and unsupported
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function execute(Server $server)
+    {
+        if ($this->isCollationUsed && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) {
+            throw UnsupportedException::collationNotSupported();
+        }
+
+        if (isset($this->options['writeConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForWriteConcern)) {
+            throw UnsupportedException::writeConcernNotSupported();
+        }
+
+        $this->executeCommand($server);
+
+        return array_map(function(IndexInput $index) { return (string) $index; }, $this->indexes);
+    }
+
+    /**
+     * Create options for executing the command.
+     *
+     * @see http://php.net/manual/en/mongodb-driver-server.executewritecommand.php
+     * @return array
+     */
+    private function createOptions()
+    {
+        $options = [];
+
+        if (isset($this->options['session'])) {
+            $options['session'] = $this->options['session'];
+        }
+
+        if (isset($this->options['writeConcern'])) {
+            $options['writeConcern'] = $this->options['writeConcern'];
+        }
+
+        return $options;
+    }
+
+    /**
+     * Create one or more indexes for the collection using the createIndexes
+     * command.
+     *
+     * @param Server $server
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    private function executeCommand(Server $server)
+    {
+        $cmd = [
+            'createIndexes' => $this->collectionName,
+            'indexes' => $this->indexes,
+        ];
+
+        if (isset($this->options['maxTimeMS'])) {
+            $cmd['maxTimeMS'] = $this->options['maxTimeMS'];
+        }
+
+        $server->executeWriteCommand($this->databaseName, new Command($cmd), $this->createOptions());
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/Operation/DatabaseCommand.php b/cache/stores/mongodb/MongoDB/Operation/DatabaseCommand.php
new file mode 100755 (executable)
index 0000000..aa0a827
--- /dev/null
@@ -0,0 +1,123 @@
+<?php
+/*
+ * Copyright 2015-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Operation;
+
+use MongoDB\Driver\Command;
+use MongoDB\Driver\Cursor;
+use MongoDB\Driver\ReadPreference;
+use MongoDB\Driver\Server;
+use MongoDB\Driver\Session;
+use MongoDB\Exception\InvalidArgumentException;
+
+/**
+ * Operation for executing a database command.
+ *
+ * @api
+ * @see \MongoDB\Database::command()
+ */
+class DatabaseCommand implements Executable
+{
+    private $databaseName;
+    private $command;
+    private $options;
+
+    /**
+     * Constructs a command.
+     *
+     * Supported options:
+     *
+     *  * readPreference (MongoDB\Driver\ReadPreference): The read preference to
+     *    use when executing the command. This may be used when issuing the
+     *    command to a replica set or mongos node to ensure that the driver sets
+     *    the wire protocol accordingly or adds the read preference to the
+     *    command document, respectively.
+     *
+     *  * session (MongoDB\Driver\Session): Client session.
+     *
+     *    Sessions are not supported for server versions < 3.6.
+     *
+     *  * typeMap (array): Type map for BSON deserialization. This will be
+     *    applied to the returned Cursor (it is not sent to the server).
+     *
+     * @param string       $databaseName   Database name
+     * @param array|object $command        Command document
+     * @param array        $options        Options for command execution
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function __construct($databaseName, $command, array $options = [])
+    {
+        if ( ! is_array($command) && ! is_object($command)) {
+            throw InvalidArgumentException::invalidType('$command', $command, 'array or object');
+        }
+
+        if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
+            throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
+        }
+
+        if (isset($options['session']) && ! $options['session'] instanceof Session) {
+            throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
+        }
+
+        if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
+            throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
+        }
+
+        $this->databaseName = (string) $databaseName;
+        $this->command = ($command instanceof Command) ? $command : new Command($command);
+        $this->options = $options;
+    }
+
+    /**
+     * Execute the operation.
+     *
+     * @see Executable::execute()
+     * @param Server $server
+     * @return Cursor
+     */
+    public function execute(Server $server)
+    {
+        $cursor = $server->executeCommand($this->databaseName, $this->command, $this->createOptions());
+
+        if (isset($this->options['typeMap'])) {
+            $cursor->setTypeMap($this->options['typeMap']);
+        }
+
+        return $cursor;
+    }
+
+    /**
+     * Create options for executing the command.
+     *
+     * @see http://php.net/manual/en/mongodb-driver-server.executecommand.php
+     * @return array
+     */
+    private function createOptions()
+    {
+        $options = [];
+
+        if (isset($this->options['readPreference'])) {
+            $options['readPreference'] = $this->options['readPreference'];
+        }
+
+        if (isset($this->options['session'])) {
+            $options['session'] = $this->options['session'];
+        }
+
+        return $options;
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/Operation/Delete.php b/cache/stores/mongodb/MongoDB/Operation/Delete.php
new file mode 100755 (executable)
index 0000000..496c3dd
--- /dev/null
@@ -0,0 +1,178 @@
+<?php
+/*
+ * Copyright 2015-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Operation;
+
+use MongoDB\DeleteResult;
+use MongoDB\Driver\BulkWrite as Bulk;
+use MongoDB\Driver\Server;
+use MongoDB\Driver\Session;
+use MongoDB\Driver\WriteConcern;
+use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
+use MongoDB\Exception\InvalidArgumentException;
+use MongoDB\Exception\UnsupportedException;
+
+/**
+ * Operation for the delete command.
+ *
+ * This class is used internally by the DeleteMany and DeleteOne operation
+ * classes.
+ *
+ * @internal
+ * @see http://docs.mongodb.org/manual/reference/command/delete/
+ */
+class Delete implements Executable, Explainable
+{
+    private static $wireVersionForCollation = 5;
+
+    private $databaseName;
+    private $collectionName;
+    private $filter;
+    private $limit;
+    private $options;
+
+    /**
+     * Constructs a delete command.
+     *
+     * Supported options:
+     *
+     *  * collation (document): Collation specification.
+     *
+     *    This is not supported for server versions < 3.4 and will result in an
+     *    exception at execution time if used.
+     *
+     *  * session (MongoDB\Driver\Session): Client session.
+     *
+     *    Sessions are not supported for server versions < 3.6.
+     *
+     *  * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
+     *
+     * @param string       $databaseName   Database name
+     * @param string       $collectionName Collection name
+     * @param array|object $filter         Query by which to delete documents
+     * @param integer      $limit          The number of matching documents to
+     *                                     delete. Must be 0 or 1, for all or a
+     *                                     single document, respectively.
+     * @param array        $options        Command options
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function __construct($databaseName, $collectionName, $filter, $limit, array $options = [])
+    {
+        if ( ! is_array($filter) && ! is_object($filter)) {
+            throw InvalidArgumentException::invalidType('$filter', $filter, 'array or object');
+        }
+
+        if ($limit !== 0 && $limit !== 1) {
+            throw new InvalidArgumentException('$limit must be 0 or 1');
+        }
+
+        if (isset($options['collation']) && ! is_array($options['collation']) && ! is_object($options['collation'])) {
+            throw InvalidArgumentException::invalidType('"collation" option', $options['collation'], 'array or object');
+        }
+
+        if (isset($options['session']) && ! $options['session'] instanceof Session) {
+            throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
+        }
+
+        if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
+            throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
+        }
+
+        if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) {
+            unset($options['writeConcern']);
+        }
+
+        $this->databaseName = (string) $databaseName;
+        $this->collectionName = (string) $collectionName;
+        $this->filter = $filter;
+        $this->limit = $limit;
+        $this->options = $options;
+    }
+
+    /**
+     * Execute the operation.
+     *
+     * @see Executable::execute()
+     * @param Server $server
+     * @return DeleteResult
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function execute(Server $server)
+    {
+        if (isset($this->options['collation']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) {
+            throw UnsupportedException::collationNotSupported();
+        }
+
+        $bulk = new Bulk();
+        $bulk->delete($this->filter, $this->createDeleteOptions());
+
+        $writeResult = $server->executeBulkWrite($this->databaseName . '.' . $this->collectionName, $bulk, $this->createExecuteOptions());
+
+        return new DeleteResult($writeResult);
+    }
+
+    public function getCommandDocument(Server $server)
+    {
+        $cmd = ['delete' => $this->collectionName, 'deletes' => [['q' => $this->filter] + $this->createDeleteOptions()]];
+
+        if (isset($this->options['writeConcern'])) {
+            $cmd['writeConcern'] = $this->options['writeConcern'];
+        }
+
+        return $cmd;
+    }
+
+    /**
+     * Create options for the delete command.
+     *
+     * Note that these options are different from the bulk write options, which
+     * are created in createExecuteOptions().
+     *
+     * @return array
+     */
+    private function createDeleteOptions()
+    {
+        $deleteOptions = ['limit' => $this->limit];
+
+        if (isset($this->options['collation'])) {
+            $deleteOptions['collation'] = (object) $this->options['collation'];
+        }
+
+        return $deleteOptions;
+    }
+
+    /**
+     * Create options for executing the bulk write.
+     *
+     * @see http://php.net/manual/en/mongodb-driver-server.executebulkwrite.php
+     * @return array
+     */
+    private function createExecuteOptions()
+    {
+        $options = [];
+
+        if (isset($this->options['session'])) {
+            $options['session'] = $this->options['session'];
+        }
+
+        if (isset($this->options['writeConcern'])) {
+            $options['writeConcern'] = $this->options['writeConcern'];
+        }
+
+        return $options;
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/Operation/DeleteMany.php b/cache/stores/mongodb/MongoDB/Operation/DeleteMany.php
new file mode 100755 (executable)
index 0000000..5b32d03
--- /dev/null
@@ -0,0 +1,82 @@
+<?php
+/*
+ * Copyright 2015-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Operation;
+
+use MongoDB\DeleteResult;
+use MongoDB\Driver\Server;
+use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
+use MongoDB\Exception\InvalidArgumentException;
+use MongoDB\Exception\UnsupportedException;
+
+/**
+ * Operation for deleting multiple document with the delete command.
+ *
+ * @api
+ * @see \MongoDB\Collection::deleteOne()
+ * @see http://docs.mongodb.org/manual/reference/command/delete/
+ */
+class DeleteMany implements Executable, Explainable
+{
+    private $delete;
+
+    /**
+     * Constructs a delete command.
+     *
+     * Supported options:
+     *
+     *  * collation (document): Collation specification.
+     *
+     *    This is not supported for server versions < 3.4 and will result in an
+     *    exception at execution time if used.
+     *
+     *  * session (MongoDB\Driver\Session): Client session.
+     *
+     *    Sessions are not supported for server versions < 3.6.
+     *
+     *  * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
+     *
+     * @param string       $databaseName   Database name
+     * @param string       $collectionName Collection name
+     * @param array|object $filter         Query by which to delete documents
+     * @param array        $options        Command options
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function __construct($databaseName, $collectionName, $filter, array $options = [])
+    {
+        $this->delete = new Delete($databaseName, $collectionName, $filter, 0, $options);
+    }
+
+    /**
+     * Execute the operation.
+     *
+     * @see Executable::execute()
+     * @param Server $server
+     * @return DeleteResult
+     * @throws UnsupportedException if collation is used and unsupported
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function execute(Server $server)
+    {
+        return $this->delete->execute($server);
+    }
+
+    public function getCommandDocument(Server $server)
+    {
+        return $this->delete->getCommandDocument($server);
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/Operation/DeleteOne.php b/cache/stores/mongodb/MongoDB/Operation/DeleteOne.php
new file mode 100755 (executable)
index 0000000..e7d47de
--- /dev/null
@@ -0,0 +1,82 @@
+<?php
+/*
+ * Copyright 2015-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Operation;
+
+use MongoDB\DeleteResult;
+use MongoDB\Driver\Server;
+use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
+use MongoDB\Exception\InvalidArgumentException;
+use MongoDB\Exception\UnsupportedException;
+
+/**
+ * Operation for deleting a single document with the delete command.
+ *
+ * @api
+ * @see \MongoDB\Collection::deleteOne()
+ * @see http://docs.mongodb.org/manual/reference/command/delete/
+ */
+class DeleteOne implements Executable, Explainable
+{
+    private $delete;
+
+    /**
+     * Constructs a delete command.
+     *
+     * Supported options:
+     *
+     *  * collation (document): Collation specification.
+     *
+     *    This is not supported for server versions < 3.4 and will result in an
+     *    exception at execution time if used.
+     *
+     *  * session (MongoDB\Driver\Session): Client session.
+     *
+     *    Sessions are not supported for server versions < 3.6.
+     *
+     *  * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
+     *
+     * @param string       $databaseName   Database name
+     * @param string       $collectionName Collection name
+     * @param array|object $filter         Query by which to delete documents
+     * @param array        $options        Command options
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function __construct($databaseName, $collectionName, $filter, array $options = [])
+    {
+        $this->delete = new Delete($databaseName, $collectionName, $filter, 1, $options);
+    }
+
+    /**
+     * Execute the operation.
+     *
+     * @see Executable::execute()
+     * @param Server $server
+     * @return DeleteResult
+     * @throws UnsupportedException if collation is used and unsupported
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function execute(Server $server)
+    {
+        return $this->delete->execute($server);
+    }
+
+    public function getCommandDocument(Server $server)
+    {
+        return $this->delete->getCommandDocument($server);
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/Operation/Distinct.php b/cache/stores/mongodb/MongoDB/Operation/Distinct.php
new file mode 100755 (executable)
index 0000000..8e7f39e
--- /dev/null
@@ -0,0 +1,202 @@
+<?php
+/*
+ * Copyright 2015-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Operation;
+
+use MongoDB\Driver\Command;
+use MongoDB\Driver\ReadConcern;
+use MongoDB\Driver\ReadPreference;
+use MongoDB\Driver\Server;
+use MongoDB\Driver\Session;
+use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
+use MongoDB\Exception\InvalidArgumentException;
+use MongoDB\Exception\UnexpectedValueException;
+use MongoDB\Exception\UnsupportedException;
+
+/**
+ * Operation for the distinct command.
+ *
+ * @api
+ * @see \MongoDB\Collection::distinct()
+ * @see http://docs.mongodb.org/manual/reference/command/distinct/
+ */
+class Distinct implements Executable, Explainable
+{
+    private static $wireVersionForCollation = 5;
+    private static $wireVersionForReadConcern = 4;
+
+    private $databaseName;
+    private $collectionName;
+    private $fieldName;
+    private $filter;
+    private $options;
+
+    /**
+     * Constructs a distinct command.
+     *
+     * Supported options:
+     *
+     *  * collation (document): Collation specification.
+     *
+     *    This is not supported for server versions < 3.4 and will result in an
+     *    exception at execution time if used.
+     *
+     *  * maxTimeMS (integer): The maximum amount of time to allow the query to
+     *    run.
+     *
+     *  * readConcern (MongoDB\Driver\ReadConcern): Read concern.
+     *
+     *    This is not supported for server versions < 3.2 and will result in an
+     *    exception at execution time if used.
+     *
+     *  * readPreference (MongoDB\Driver\ReadPreference): Read preference.
+     *
+     *  * session (MongoDB\Driver\Session): Client session.
+     *
+     *    Sessions are not supported for server versions < 3.6.
+     *
+     * @param string       $databaseName   Database name
+     * @param string       $collectionName Collection name
+     * @param string       $fieldName      Field for which to return distinct values
+     * @param array|object $filter         Query by which to filter documents
+     * @param array        $options        Command options
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function __construct($databaseName, $collectionName, $fieldName, $filter = [], array $options = [])
+    {
+        if ( ! is_array($filter) && ! is_object($filter)) {
+            throw InvalidArgumentException::invalidType('$filter', $filter, 'array or object');
+        }
+
+        if (isset($options['collation']) && ! is_array($options['collation']) && ! is_object($options['collation'])) {
+            throw InvalidArgumentException::invalidType('"collation" option', $options['collation'], 'array or object');
+        }
+
+        if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
+            throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
+        }
+
+        if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
+            throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern');
+        }
+
+        if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
+            throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
+        }
+
+        if (isset($options['session']) && ! $options['session'] instanceof Session) {
+            throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
+        }
+
+        if (isset($options['readConcern']) && $options['readConcern']->isDefault()) {
+            unset($options['readConcern']);
+        }
+
+        $this->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[]
+     * @throws UnexpectedValueException if the command response was malformed
+     * @throws UnsupportedException if collation or read concern is used and unsupported
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function execute(Server $server)
+    {
+        if (isset($this->options['collation']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) {
+            throw UnsupportedException::collationNotSupported();
+        }
+
+        if (isset($this->options['readConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
+            throw UnsupportedException::readConcernNotSupported();
+        }
+
+        $cursor = $server->executeReadCommand($this->databaseName, new Command($this->createCommandDocument()), $this->createOptions());
+        $result = current($cursor->toArray());
+
+        if ( ! isset($result->values) || ! is_array($result->values)) {
+            throw new UnexpectedValueException('distinct command did not return a "values" array');
+        }
+
+        return $result->values;
+    }
+
+    public function getCommandDocument(Server $server)
+    {
+        return $this->createCommandDocument();
+    }
+
+    /**
+     * Create the distinct command document.
+     *
+     * @return array
+     */
+    private function createCommandDocument()
+    {
+        $cmd = [
+            'distinct' => $this->collectionName,
+            'key' => $this->fieldName,
+        ];
+
+        if ( ! empty($this->filter)) {
+            $cmd['query'] = (object) $this->filter;
+        }
+
+        if (isset($this->options['collation'])) {
+            $cmd['collation'] = (object) $this->options['collation'];
+        }
+
+        if (isset($this->options['maxTimeMS'])) {
+            $cmd['maxTimeMS'] = $this->options['maxTimeMS'];
+        }
+
+        return $cmd;
+    }
+
+    /**
+     * Create options for executing the command.
+     *
+     * @see http://php.net/manual/en/mongodb-driver-server.executereadcommand.php
+     * @return array
+     */
+    private function createOptions()
+    {
+        $options = [];
+
+        if (isset($this->options['readConcern'])) {
+            $options['readConcern'] = $this->options['readConcern'];
+        }
+
+        if (isset($this->options['readPreference'])) {
+            $options['readPreference'] = $this->options['readPreference'];
+        }
+
+        if (isset($this->options['session'])) {
+            $options['session'] = $this->options['session'];
+        }
+
+        return $options;
+    }
+}
diff --git a/cache/stores/mongodb/MongoDB/Operation/DropCollection.php b/cache/stores/mongodb/MongoDB/Operation/DropCollection.php
new file mode 100755 (executable)
index 0000000..1ee66fa
--- /dev/null
@@ -0,0 +1,148 @@
+<?php
+/*
+ * Copyright 2015-2017 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Operation;
+
+use MongoDB\Driver\Command;
+use MongoDB\Driver\Server;
+use MongoDB\Driver\Session;
+use MongoDB\Driver\WriteConcern;
+use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
+use MongoDB\Exception\InvalidArgumentException;
+use MongoDB\Exception\UnsupportedException;
+
+/**
+ * Operation for the drop command.
+ *
+ * @api
+ * @see \MongoDB\Collection::drop()
+ * @see \MongoDB\Database::dropCollection()
+ * @see http://docs.mongodb.org/manual/reference/command/drop/
+ */
+class DropCollection implements Executable
+{
+    private static $errorMessageNamespaceNotFound = 'ns not found';
+    private static $wireVersionForWriteConcern = 5;
+
+    private $databaseName;
+    private $collectionName;
+    private $options;
+
+    /**
+     * Constructs a drop command.
+     *
+     * Supported options:
+     *
+     *  * session (MongoDB\Driver\Session): Client session.
+     *
+     *    Sessions are not supported for server versions < 3.6.
+     *
+     *  * typeMap (array): Type map for BSON deserialization. This will be used
+     *    for the returned command result document.
+     *
+     *  * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
+     *
+     *    This is not supported for server versions < 3.4 and will result in an
+     *    exception at execution time if used.
+     *
+     * @param string $databaseName   Database name
+     * @param string $collectionName Collection name
+     * @param array  $options        Command options
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function __construct($databaseName, $collectionName, array $options = [])
+    {
+        if (isset($options['session']) && ! $options['session'] instanceof Session) {
+            throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
+        }
+
+        if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
+            throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
+        }
+
+        if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
+            throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
+        }
+
+        if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) {
+            unset($options['writeConcern']);
+        }
+
+        $this->databaseName = (string) $databaseName;
+        $this->collectionName = (string) $collectionName;
+        $this->options = $options;
+    }
+
+    /**
+     * Execute the operation.
+     *
+     * @see Executable::execute()
+     * @param Server $server
+     * @return array|object Command result document
+     * @throws UnsupportedException if writeConcern is used and unsupported
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function execute(Server $server)
+    {
+        if (isset($this->options['writeConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForWriteConcern)) {
+            throw UnsupportedException::writeConcernNotSupported();
+        }
+
+        $command = new Command(['drop' => $this->collectionName]);
+
+        try {
+            $cursor = $server->executeWriteCommand($this->databaseName, $command, $this->createOptions());
+        } catch (DriverRuntimeException $e) {
+            /* The server may return an error if the collection does not exist.
+             * Check for an error message (unfortunately, there isn't a code)
+             * and NOP instead of throwing.
+             */
+            if ($e->getMessage() === self::$errorMessageNamespaceNotFound) {
+                return (object) ['ok' => 0, 'errmsg' => self::$errorMessageNamespaceNotFound];
+            }
+
+            throw $e;
+        }
+
+        if (isset($this->options['typeMap'])) {
+            $cursor->setTypeMap($this->options['typeMap']);
+        }
+
+        return current($cursor->toArray());
+    }
+
+    /**
+     * Create options for executing the command.
+     *
+     * @see http://php.net/manual/en/mongodb-driver-server.executewritecommand.php
+     * @return array
+     */
+    private function createOptions()
+    {
+        $options = [];
+
+        if (isset($this->options['session'])) {
+            $options['session'] = $this->options['session'];
+        }
+
+        if (isset($this->options['writeConcern'])) {
+            $options['writeConcern'] = $this->options['writeConcern'];
+        }
+
+        return $options;
+    }