diff --git a/cache/stores/mongodb/MongoDB/LICENSE b/cache/stores/mongodb/LICENSE similarity index 100% rename from cache/stores/mongodb/MongoDB/LICENSE rename to cache/stores/mongodb/LICENSE diff --git a/cache/stores/mongodb/MongoDB/BulkWriteResult.php b/cache/stores/mongodb/MongoDB/BulkWriteResult.php index cdf6654ea89..68af2cd751e 100644 --- a/cache/stores/mongodb/MongoDB/BulkWriteResult.php +++ b/cache/stores/mongodb/MongoDB/BulkWriteResult.php @@ -25,13 +25,16 @@ use MongoDB\Exception\BadMethodCallException; */ class BulkWriteResult { + /** @var WriteResult */ private $writeResult; + + /** @var mixed[] */ private $insertedIds; + + /** @var boolean */ private $isAcknowledged; /** - * Constructor. - * * @param WriteResult $writeResult * @param mixed[] $insertedIds */ diff --git a/cache/stores/mongodb/MongoDB/ChangeStream.php b/cache/stores/mongodb/MongoDB/ChangeStream.php index 98a703fadd7..3c360fa8633 100644 --- a/cache/stores/mongodb/MongoDB/ChangeStream.php +++ b/cache/stores/mongodb/MongoDB/ChangeStream.php @@ -17,15 +17,15 @@ namespace MongoDB; -use MongoDB\BSON\Serializable; -use MongoDB\Driver\Cursor; +use Iterator; +use MongoDB\Driver\CursorId; 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; +use MongoDB\Model\ChangeStreamIterator; +use function call_user_func; +use function in_array; /** * Iterator for a change stream. @@ -42,27 +42,39 @@ class ChangeStream implements Iterator */ const CURSOR_NOT_FOUND = 43; - private static $errorCodeCappedPositionLost = 136; - private static $errorCodeInterrupted = 11601; - private static $errorCodeCursorKilled = 237; + /** @var array */ + private static $nonResumableErrorCodes = [ + 136, // CappedPositionLost + 237, // CursorKilled + 11601, // Interrupted + ]; - private $resumeToken; + /** @var callable */ private $resumeCallable; - private $csIt; + + /** @var ChangeStreamIterator */ + private $iterator; + + /** @var integer */ private $key = 0; + + /** + * Whether the change stream has advanced to its first result. This is used + * to determine whether $key should be incremented after an iteration event. + * + * @var boolean + */ private $hasAdvanced = false; /** - * Constructor. - * * @internal - * @param Cursor $cursor - * @param callable $resumeCallable + * @param ChangeStreamIterator $iterator + * @param callable $resumeCallable */ - public function __construct(Cursor $cursor, callable $resumeCallable) + public function __construct(ChangeStreamIterator $iterator, callable $resumeCallable) { + $this->iterator = $iterator; $this->resumeCallable = $resumeCallable; - $this->csIt = new IteratorIterator($cursor); } /** @@ -71,15 +83,29 @@ class ChangeStream implements Iterator */ public function current() { - return $this->csIt->current(); + return $this->iterator->current(); } /** - * @return \MongoDB\Driver\CursorId + * @return CursorId */ public function getCursorId() { - return $this->csIt->getInnerIterator()->getId(); + return $this->iterator->getInnerIterator()->getId(); + } + + /** + * Returns the resume token for the iterator's current position. + * + * Null may be returned if no change documents have been iterated and the + * server did not include a postBatchResumeToken in its aggregate or getMore + * command response. + * + * @return array|object|null + */ + public function getResumeToken() + { + return $this->iterator->getResumeToken(); } /** @@ -91,60 +117,40 @@ class ChangeStream implements Iterator if ($this->valid()) { return $this->key; } + return null; } /** * @see http://php.net/iterator.next * @return void + * @throws ResumeTokenException */ 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; - } + $this->iterator->next(); + $this->onIteration($this->hasAdvanced); } catch (RuntimeException $e) { - if ($this->isResumableError($e)) { - $this->resume(); - } + $this->resumeOrThrow($e); } } /** * @see http://php.net/iterator.rewind * @return void + * @throws ResumeTokenException */ 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; - } + $this->iterator->rewind(); + /* Unlike next() and resume(), the decision to increment the key + * does not depend on whether the change stream has advanced. This + * ensures that multiple calls to rewind() do not alter state. */ + $this->onIteration(false); } catch (RuntimeException $e) { - if ($this->isResumableError($e)) { - $this->resume(); - } + $this->resumeOrThrow($e); } } @@ -154,40 +160,7 @@ class ChangeStream implements Iterator */ 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; + return $this->iterator->valid(); } /** @@ -203,11 +176,15 @@ class ChangeStream implements Iterator return true; } - if ( ! $exception instanceof ServerException) { + if (! $exception instanceof ServerException) { return false; } - if (in_array($exception->getCode(), [self::$errorCodeCappedPositionLost, self::$errorCodeCursorKilled, self::$errorCodeInterrupted])) { + if ($exception->hasErrorLabel('NonResumableChangeStreamError')) { + return false; + } + + if (in_array($exception->getCode(), self::$nonResumableErrorCodes)) { return false; } @@ -215,14 +192,63 @@ class ChangeStream implements Iterator } /** - * Creates a new changeStream after a resumable server error. + * Perform housekeeping after an iteration event. + * + * @param boolean $incrementKey Increment $key if there is a current result + * @throws ResumeTokenException + */ + private function onIteration($incrementKey) + { + /* If the cursorId is 0, the server has invalidated the cursor and we + * will never perform another getMore nor need to resume since any + * remaining results (up to and including the invalidate event) will + * have been received in the last response. Therefore, we can unset the + * resumeCallable. This will free any reference to Watch as well as the + * only reference to any implicit session created therein. */ + if ((string) $this->getCursorId() === '0') { + $this->resumeCallable = null; + } + + /* Return early if there is not a current result. Avoid any attempt to + * increment the iterator's key. */ + if (! $this->valid()) { + return; + } + + if ($incrementKey) { + $this->key++; + } + + $this->hasAdvanced = true; + } + + /** + * Recreates the ChangeStreamIterator 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(); + $this->iterator = call_user_func($this->resumeCallable, $this->getResumeToken(), $this->hasAdvanced); + $this->iterator->rewind(); + + $this->onIteration($this->hasAdvanced); + } + + /** + * Either resumes after a resumable error or re-throws the exception. + * + * @param RuntimeException $exception + * @throws RuntimeException + */ + private function resumeOrThrow(RuntimeException $exception) + { + if ($this->isResumableError($exception)) { + $this->resume(); + + return; + } + + throw $exception; } } diff --git a/cache/stores/mongodb/MongoDB/Client.php b/cache/stores/mongodb/MongoDB/Client.php index be26ec5d04d..20310802d07 100644 --- a/cache/stores/mongodb/MongoDB/Client.php +++ b/cache/stores/mongodb/MongoDB/Client.php @@ -17,35 +17,55 @@ namespace MongoDB; +use MongoDB\Driver\Exception\InvalidArgumentException as DriverInvalidArgumentException; +use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; use MongoDB\Driver\Manager; use MongoDB\Driver\ReadConcern; use MongoDB\Driver\ReadPreference; +use MongoDB\Driver\Session; 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\BSONArray; +use MongoDB\Model\BSONDocument; use MongoDB\Model\DatabaseInfoIterator; use MongoDB\Operation\DropDatabase; use MongoDB\Operation\ListDatabases; use MongoDB\Operation\Watch; +use function is_array; class Client { + /** @var array */ private static $defaultTypeMap = [ - 'array' => 'MongoDB\Model\BSONArray', - 'document' => 'MongoDB\Model\BSONDocument', - 'root' => 'MongoDB\Model\BSONDocument', + 'array' => BSONArray::class, + 'document' => BSONDocument::class, + 'root' => BSONDocument::class, ]; + + /** @var integer */ private static $wireVersionForReadConcern = 4; + + /** @var integer */ private static $wireVersionForWritableCommandWriteConcern = 5; + /** @var Manager */ private $manager; + + /** @var ReadConcern */ private $readConcern; + + /** @var ReadPreference */ private $readPreference; + + /** @var string */ private $uri; + + /** @var array */ private $typeMap; + + /** @var WriteConcern */ private $writeConcern; /** @@ -146,13 +166,13 @@ class Client */ public function dropDatabase($databaseName, array $options = []) { - if ( ! isset($options['typeMap'])) { + if (! isset($options['typeMap'])) { $options['typeMap'] = $this->typeMap; } - $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); + $server = select_server($this->manager, $options); - if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) { + if (! isset($options['writeConcern']) && server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern) && ! is_in_transaction($options)) { $options['writeConcern'] = $this->writeConcern; } @@ -217,6 +237,7 @@ class Client * List databases. * * @see ListDatabases::__construct() for supported options + * @param array $options * @return DatabaseInfoIterator * @throws UnexpectedValueException if the command response was malformed * @throws InvalidArgumentException for parameter/option parsing errors @@ -225,7 +246,7 @@ class Client public function listDatabases(array $options = []) { $operation = new ListDatabases($options); - $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); + $server = select_server($this->manager, $options); return $operation->execute($server); } @@ -267,8 +288,8 @@ class Client * 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 + * @param array $options Session options + * @return Session */ public function startSession(array $options = []) { @@ -286,17 +307,17 @@ class Client */ public function watch(array $pipeline = [], array $options = []) { - if ( ! isset($options['readPreference'])) { + if (! isset($options['readPreference']) && ! is_in_transaction($options)) { $options['readPreference'] = $this->readPreference; } - $server = $this->manager->selectServer($options['readPreference']); + $server = select_server($this->manager, $options); - if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) { + if (! isset($options['readConcern']) && server_supports_feature($server, self::$wireVersionForReadConcern) && ! is_in_transaction($options)) { $options['readConcern'] = $this->readConcern; } - if ( ! isset($options['typeMap'])) { + if (! isset($options['typeMap'])) { $options['typeMap'] = $this->typeMap; } diff --git a/cache/stores/mongodb/MongoDB/Collection.php b/cache/stores/mongodb/MongoDB/Collection.php index 769e8353cea..82386445b98 100644 --- a/cache/stores/mongodb/MongoDB/Collection.php +++ b/cache/stores/mongodb/MongoDB/Collection.php @@ -18,24 +18,24 @@ namespace MongoDB; use MongoDB\BSON\JavascriptInterface; -use MongoDB\BSON\Serializable; -use MongoDB\ChangeStream; use MongoDB\Driver\Cursor; +use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; 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\BSONArray; +use MongoDB\Model\BSONDocument; 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\CreateIndexes; use MongoDB\Operation\DeleteMany; use MongoDB\Operation\DeleteOne; use MongoDB\Operation\Distinct; @@ -58,24 +58,52 @@ use MongoDB\Operation\UpdateMany; use MongoDB\Operation\UpdateOne; use MongoDB\Operation\Watch; use Traversable; +use function array_diff_key; +use function array_intersect_key; +use function current; +use function is_array; +use function strlen; class Collection { + /** @var array */ private static $defaultTypeMap = [ - 'array' => 'MongoDB\Model\BSONArray', - 'document' => 'MongoDB\Model\BSONDocument', - 'root' => 'MongoDB\Model\BSONDocument', + 'array' => BSONArray::class, + 'document' => BSONDocument::class, + 'root' => BSONDocument::class, ]; + + /** @var integer */ private static $wireVersionForFindAndModifyWriteConcern = 4; + + /** @var integer */ private static $wireVersionForReadConcern = 4; + + /** @var integer */ private static $wireVersionForWritableCommandWriteConcern = 5; + /** @var integer */ + private static $wireVersionForReadConcernWithWriteStage = 8; + + /** @var string */ private $collectionName; + + /** @var string */ private $databaseName; + + /** @var Manager */ private $manager; + + /** @var ReadConcern */ private $readConcern; + + /** @var ReadPreference */ private $readPreference; + + /** @var array */ private $typeMap; + + /** @var WriteConcern */ private $writeConcern; /** @@ -116,11 +144,11 @@ class Collection } if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) { - throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern'); + throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], ReadConcern::class); } if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) { - throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference'); + throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class); } if (isset($options['typeMap']) && ! is_array($options['typeMap'])) { @@ -128,7 +156,7 @@ class Collection } if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) { - throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern'); + throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class); } $this->manager = $manager; @@ -189,32 +217,39 @@ class Collection */ public function aggregate(array $pipeline, array $options = []) { - $hasOutStage = \MongoDB\is_last_pipeline_operator_out($pipeline); + $hasWriteStage = is_last_pipeline_operator_write($pipeline); - if ( ! isset($options['readPreference'])) { + if (! isset($options['readPreference']) && ! is_in_transaction($options)) { $options['readPreference'] = $this->readPreference; } - if ($hasOutStage) { + if ($hasWriteStage) { $options['readPreference'] = new ReadPreference(ReadPreference::RP_PRIMARY); } - $server = $this->manager->selectServer($options['readPreference']); + $server = select_server($this->manager, $options); - /* A "majority" read concern is not compatible with the $out stage, so - * avoid providing the Collection's read concern if it would conflict. + /* MongoDB 4.2 and later supports a read concern when an $out stage is + * being used, but earlier versions do not. + * + * A read concern is also not compatible with transactions. */ - if ( ! isset($options['readConcern']) && - ! ($hasOutStage && $this->readConcern->getLevel() === ReadConcern::MAJORITY) && - \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) { + if (! isset($options['readConcern']) && + server_supports_feature($server, self::$wireVersionForReadConcern) && + ! is_in_transaction($options) && + ( ! $hasWriteStage || server_supports_feature($server, self::$wireVersionForReadConcernWithWriteStage)) + ) { $options['readConcern'] = $this->readConcern; } - if ( ! isset($options['typeMap'])) { + if (! isset($options['typeMap'])) { $options['typeMap'] = $this->typeMap; } - if ($hasOutStage && ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) { + if ($hasWriteStage && + ! isset($options['writeConcern']) && + server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern) && + ! is_in_transaction($options)) { $options['writeConcern'] = $this->writeConcern; } @@ -236,12 +271,12 @@ class Collection */ public function bulkWrite(array $operations, array $options = []) { - if ( ! isset($options['writeConcern'])) { + if (! isset($options['writeConcern']) && ! is_in_transaction($options)) { $options['writeConcern'] = $this->writeConcern; } $operation = new BulkWrite($this->databaseName, $this->collectionName, $operations, $options); - $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); + $server = select_server($this->manager, $options); return $operation->execute($server); } @@ -262,13 +297,13 @@ class Collection */ public function count($filter = [], array $options = []) { - if ( ! isset($options['readPreference'])) { + if (! isset($options['readPreference']) && ! is_in_transaction($options)) { $options['readPreference'] = $this->readPreference; } - $server = $this->manager->selectServer($options['readPreference']); + $server = select_server($this->manager, $options); - if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) { + if (! isset($options['readConcern']) && server_supports_feature($server, self::$wireVersionForReadConcern) && ! is_in_transaction($options)) { $options['readConcern'] = $this->readConcern; } @@ -291,13 +326,13 @@ class Collection */ public function countDocuments($filter = [], array $options = []) { - if ( ! isset($options['readPreference'])) { + if (! isset($options['readPreference']) && ! is_in_transaction($options)) { $options['readPreference'] = $this->readPreference; } - $server = $this->manager->selectServer($options['readPreference']); + $server = select_server($this->manager, $options); - if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) { + if (! isset($options['readConcern']) && server_supports_feature($server, self::$wireVersionForReadConcern) && ! is_in_transaction($options)) { $options['readConcern'] = $this->readConcern; } @@ -357,9 +392,9 @@ class Collection */ public function createIndexes(array $indexes, array $options = []) { - $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); + $server = select_server($this->manager, $options); - if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) { + if (! isset($options['writeConcern']) && server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern) && ! is_in_transaction($options)) { $options['writeConcern'] = $this->writeConcern; } @@ -382,12 +417,12 @@ class Collection */ public function deleteMany($filter, array $options = []) { - if ( ! isset($options['writeConcern'])) { + if (! isset($options['writeConcern']) && ! is_in_transaction($options)) { $options['writeConcern'] = $this->writeConcern; } $operation = new DeleteMany($this->databaseName, $this->collectionName, $filter, $options); - $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); + $server = select_server($this->manager, $options); return $operation->execute($server); } @@ -406,12 +441,12 @@ class Collection */ public function deleteOne($filter, array $options = []) { - if ( ! isset($options['writeConcern'])) { + if (! isset($options['writeConcern']) && ! is_in_transaction($options)) { $options['writeConcern'] = $this->writeConcern; } $operation = new DeleteOne($this->databaseName, $this->collectionName, $filter, $options); - $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); + $server = select_server($this->manager, $options); return $operation->execute($server); } @@ -420,9 +455,9 @@ class Collection * 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 + * @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 @@ -431,13 +466,17 @@ class Collection */ public function distinct($fieldName, $filter = [], array $options = []) { - if ( ! isset($options['readPreference'])) { + if (! isset($options['readPreference']) && ! is_in_transaction($options)) { $options['readPreference'] = $this->readPreference; } - $server = $this->manager->selectServer($options['readPreference']); + if (! isset($options['typeMap'])) { + $options['typeMap'] = $this->typeMap; + } - if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) { + $server = select_server($this->manager, $options); + + if (! isset($options['readConcern']) && server_supports_feature($server, self::$wireVersionForReadConcern) && ! is_in_transaction($options)) { $options['readConcern'] = $this->readConcern; } @@ -458,13 +497,13 @@ class Collection */ public function drop(array $options = []) { - if ( ! isset($options['typeMap'])) { + if (! isset($options['typeMap'])) { $options['typeMap'] = $this->typeMap; } - $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); + $server = select_server($this->manager, $options); - if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) { + if (! isset($options['writeConcern']) && server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern) && ! is_in_transaction($options)) { $options['writeConcern'] = $this->writeConcern; } @@ -478,7 +517,7 @@ class Collection * * @see DropIndexes::__construct() for supported options * @param string|IndexInfo $indexName Index name or model object - * @param array $options Additional 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 @@ -492,13 +531,13 @@ class Collection throw new InvalidArgumentException('dropIndexes() must be used to drop multiple indexes'); } - if ( ! isset($options['typeMap'])) { + if (! isset($options['typeMap'])) { $options['typeMap'] = $this->typeMap; } - $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); + $server = select_server($this->manager, $options); - if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) { + if (! isset($options['writeConcern']) && server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern) && ! is_in_transaction($options)) { $options['writeConcern'] = $this->writeConcern; } @@ -519,13 +558,13 @@ class Collection */ public function dropIndexes(array $options = []) { - if ( ! isset($options['typeMap'])) { + if (! isset($options['typeMap'])) { $options['typeMap'] = $this->typeMap; } - $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); + $server = select_server($this->manager, $options); - if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) { + if (! isset($options['writeConcern']) && server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern) && ! is_in_transaction($options)) { $options['writeConcern'] = $this->writeConcern; } @@ -545,15 +584,15 @@ class Collection * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function EstimatedDocumentCount(array $options = []) + public function estimatedDocumentCount(array $options = []) { - if ( ! isset($options['readPreference'])) { + if (! isset($options['readPreference']) && ! is_in_transaction($options)) { $options['readPreference'] = $this->readPreference; } - $server = $this->manager->selectServer($options['readPreference']); + $server = select_server($this->manager, $options); - if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) { + if (! isset($options['readConcern']) && server_supports_feature($server, self::$wireVersionForReadConcern) && ! is_in_transaction($options)) { $options['readConcern'] = $this->readConcern; } @@ -567,8 +606,8 @@ class Collection * * @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 + * @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 @@ -576,15 +615,15 @@ class Collection */ public function explain(Explainable $explainable, array $options = []) { - if ( ! isset($options['readPreference'])) { + if (! isset($options['readPreference']) && ! is_in_transaction($options)) { $options['readPreference'] = $this->readPreference; } - if ( ! isset($options['typeMap'])) { + if (! isset($options['typeMap'])) { $options['typeMap'] = $this->typeMap; } - $server = $this->manager->selectServer($options['readPreference']); + $server = select_server($this->manager, $options); $operation = new Explain($this->databaseName, $explainable, $options); @@ -605,17 +644,17 @@ class Collection */ public function find($filter = [], array $options = []) { - if ( ! isset($options['readPreference'])) { + if (! isset($options['readPreference']) && ! is_in_transaction($options)) { $options['readPreference'] = $this->readPreference; } - $server = $this->manager->selectServer($options['readPreference']); + $server = select_server($this->manager, $options); - if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) { + if (! isset($options['readConcern']) && server_supports_feature($server, self::$wireVersionForReadConcern) && ! is_in_transaction($options)) { $options['readConcern'] = $this->readConcern; } - if ( ! isset($options['typeMap'])) { + if (! isset($options['typeMap'])) { $options['typeMap'] = $this->typeMap; } @@ -638,17 +677,17 @@ class Collection */ public function findOne($filter = [], array $options = []) { - if ( ! isset($options['readPreference'])) { + if (! isset($options['readPreference']) && ! is_in_transaction($options)) { $options['readPreference'] = $this->readPreference; } - $server = $this->manager->selectServer($options['readPreference']); + $server = select_server($this->manager, $options); - if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) { + if (! isset($options['readConcern']) && server_supports_feature($server, self::$wireVersionForReadConcern) && ! is_in_transaction($options)) { $options['readConcern'] = $this->readConcern; } - if ( ! isset($options['typeMap'])) { + if (! isset($options['typeMap'])) { $options['typeMap'] = $this->typeMap; } @@ -674,13 +713,13 @@ class Collection */ public function findOneAndDelete($filter, array $options = []) { - $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); + $server = select_server($this->manager, $options); - if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForFindAndModifyWriteConcern)) { + if (! isset($options['writeConcern']) && server_supports_feature($server, self::$wireVersionForFindAndModifyWriteConcern) && ! is_in_transaction($options)) { $options['writeConcern'] = $this->writeConcern; } - if ( ! isset($options['typeMap'])) { + if (! isset($options['typeMap'])) { $options['typeMap'] = $this->typeMap; } @@ -711,13 +750,13 @@ class Collection */ public function findOneAndReplace($filter, $replacement, array $options = []) { - $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); + $server = select_server($this->manager, $options); - if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForFindAndModifyWriteConcern)) { + if (! isset($options['writeConcern']) && server_supports_feature($server, self::$wireVersionForFindAndModifyWriteConcern) && ! is_in_transaction($options)) { $options['writeConcern'] = $this->writeConcern; } - if ( ! isset($options['typeMap'])) { + if (! isset($options['typeMap'])) { $options['typeMap'] = $this->typeMap; } @@ -748,13 +787,13 @@ class Collection */ public function findOneAndUpdate($filter, $update, array $options = []) { - $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); + $server = select_server($this->manager, $options); - if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForFindAndModifyWriteConcern)) { + if (! isset($options['writeConcern']) && server_supports_feature($server, self::$wireVersionForFindAndModifyWriteConcern) && ! is_in_transaction($options)) { $options['writeConcern'] = $this->writeConcern; } - if ( ! isset($options['typeMap'])) { + if (! isset($options['typeMap'])) { $options['typeMap'] = $this->typeMap; } @@ -859,12 +898,12 @@ class Collection */ public function insertMany(array $documents, array $options = []) { - if ( ! isset($options['writeConcern'])) { + if (! isset($options['writeConcern']) && ! is_in_transaction($options)) { $options['writeConcern'] = $this->writeConcern; } $operation = new InsertMany($this->databaseName, $this->collectionName, $documents, $options); - $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); + $server = select_server($this->manager, $options); return $operation->execute($server); } @@ -882,12 +921,12 @@ class Collection */ public function insertOne($document, array $options = []) { - if ( ! isset($options['writeConcern'])) { + if (! isset($options['writeConcern']) && ! is_in_transaction($options)) { $options['writeConcern'] = $this->writeConcern; } $operation = new InsertOne($this->databaseName, $this->collectionName, $document, $options); - $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); + $server = select_server($this->manager, $options); return $operation->execute($server); } @@ -896,6 +935,7 @@ class Collection * Returns information for all indexes for the collection. * * @see ListIndexes::__construct() for supported options + * @param array $options * @return IndexInfoIterator * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) @@ -903,7 +943,7 @@ class Collection public function listIndexes(array $options = []) { $operation = new ListIndexes($this->databaseName, $this->collectionName, $options); - $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); + $server = select_server($this->manager, $options); return $operation->execute($server); } @@ -913,10 +953,10 @@ class 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 + * @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 @@ -925,9 +965,9 @@ class Collection */ public function mapReduce(JavascriptInterface $map, JavascriptInterface $reduce, $out, array $options = []) { - $hasOutputCollection = ! \MongoDB\is_mapreduce_output_inline($out); + $hasOutputCollection = ! is_mapreduce_output_inline($out); - if ( ! isset($options['readPreference'])) { + if (! isset($options['readPreference']) && ! is_in_transaction($options)) { $options['readPreference'] = $this->readPreference; } @@ -936,20 +976,22 @@ class Collection $options['readPreference'] = new ReadPreference(ReadPreference::RP_PRIMARY); } - $server = $this->manager->selectServer($options['readPreference']); + $server = select_server($this->manager, $options); /* A "majority" read concern is not compatible with inline output, so * avoid providing the Collection's read concern if it would conflict. + * + * A read concern is also not compatible with transactions. */ - if ( ! isset($options['readConcern']) && ! ($hasOutputCollection && $this->readConcern->getLevel() === ReadConcern::MAJORITY) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) { + if (! isset($options['readConcern']) && ! ($hasOutputCollection && $this->readConcern->getLevel() === ReadConcern::MAJORITY) && server_supports_feature($server, self::$wireVersionForReadConcern) && ! is_in_transaction($options)) { $options['readConcern'] = $this->readConcern; } - if ( ! isset($options['typeMap'])) { + if (! isset($options['typeMap'])) { $options['typeMap'] = $this->typeMap; } - if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) { + if (! isset($options['writeConcern']) && server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern) && ! is_in_transaction($options)) { $options['writeConcern'] = $this->writeConcern; } @@ -973,12 +1015,12 @@ class Collection */ public function replaceOne($filter, $replacement, array $options = []) { - if ( ! isset($options['writeConcern'])) { + if (! isset($options['writeConcern']) && ! is_in_transaction($options)) { $options['writeConcern'] = $this->writeConcern; } $operation = new ReplaceOne($this->databaseName, $this->collectionName, $filter, $replacement, $options); - $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); + $server = select_server($this->manager, $options); return $operation->execute($server); } @@ -998,12 +1040,12 @@ class Collection */ public function updateMany($filter, $update, array $options = []) { - if ( ! isset($options['writeConcern'])) { + if (! isset($options['writeConcern']) && ! is_in_transaction($options)) { $options['writeConcern'] = $this->writeConcern; } $operation = new UpdateMany($this->databaseName, $this->collectionName, $filter, $update, $options); - $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); + $server = select_server($this->manager, $options); return $operation->execute($server); } @@ -1023,12 +1065,12 @@ class Collection */ public function updateOne($filter, $update, array $options = []) { - if ( ! isset($options['writeConcern'])) { + if (! isset($options['writeConcern']) && ! is_in_transaction($options)) { $options['writeConcern'] = $this->writeConcern; } $operation = new UpdateOne($this->databaseName, $this->collectionName, $filter, $update, $options); - $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); + $server = select_server($this->manager, $options); return $operation->execute($server); } @@ -1044,11 +1086,11 @@ class Collection */ public function watch(array $pipeline = [], array $options = []) { - if ( ! isset($options['readPreference'])) { + if (! isset($options['readPreference']) && ! is_in_transaction($options)) { $options['readPreference'] = $this->readPreference; } - $server = $this->manager->selectServer($options['readPreference']); + $server = select_server($this->manager, $options); /* Although change streams require a newer version of the server than * read concerns, perform the usual wire version check before inheriting @@ -1057,11 +1099,11 @@ class Collection * 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)) { + if (! isset($options['readConcern']) && server_supports_feature($server, self::$wireVersionForReadConcern) && ! is_in_transaction($options)) { $options['readConcern'] = $this->readConcern; } - if ( ! isset($options['typeMap'])) { + if (! isset($options['typeMap'])) { $options['typeMap'] = $this->typeMap; } diff --git a/cache/stores/mongodb/MongoDB/Database.php b/cache/stores/mongodb/MongoDB/Database.php index 3b8b0e711b3..323a69bfb34 100644 --- a/cache/stores/mongodb/MongoDB/Database.php +++ b/cache/stores/mongodb/MongoDB/Database.php @@ -17,17 +17,20 @@ namespace MongoDB; -use MongoDB\Collection; use MongoDB\Driver\Cursor; +use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; 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\GridFS\Bucket; +use MongoDB\Model\BSONArray; +use MongoDB\Model\BSONDocument; use MongoDB\Model\CollectionInfoIterator; +use MongoDB\Operation\Aggregate; use MongoDB\Operation\CreateCollection; use MongoDB\Operation\DatabaseCommand; use MongoDB\Operation\DropCollection; @@ -35,22 +38,44 @@ use MongoDB\Operation\DropDatabase; use MongoDB\Operation\ListCollections; use MongoDB\Operation\ModifyCollection; use MongoDB\Operation\Watch; +use Traversable; +use function is_array; +use function strlen; class Database { + /** @var array */ private static $defaultTypeMap = [ - 'array' => 'MongoDB\Model\BSONArray', - 'document' => 'MongoDB\Model\BSONDocument', - 'root' => 'MongoDB\Model\BSONDocument', + 'array' => BSONArray::class, + 'document' => BSONDocument::class, + 'root' => BSONDocument::class, ]; + + /** @var integer */ private static $wireVersionForReadConcern = 4; + + /** @var integer */ private static $wireVersionForWritableCommandWriteConcern = 5; + /** @var integer */ + private static $wireVersionForReadConcernWithWriteStage = 8; + + /** @var string */ private $databaseName; + + /** @var Manager */ private $manager; + + /** @var ReadConcern */ private $readConcern; + + /** @var ReadPreference */ private $readPreference; + + /** @var array */ private $typeMap; + + /** @var WriteConcern */ private $writeConcern; /** @@ -87,11 +112,11 @@ class Database } if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) { - throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern'); + throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], ReadConcern::class); } if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) { - throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference'); + throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class); } if (isset($options['typeMap']) && ! is_array($options['typeMap'])) { @@ -99,7 +124,7 @@ class Database } if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) { - throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern'); + throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class); } $this->manager = $manager; @@ -155,6 +180,63 @@ class Database return $this->databaseName; } + /** + * Runs an aggregation framework pipeline on the database for pipeline + * stages that do not require an underlying collection, such as $currentOp + * and $listLocalSessions. Requires MongoDB >= 3.6 + * + * @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 = []) + { + $hasWriteStage = is_last_pipeline_operator_write($pipeline); + + if (! isset($options['readPreference']) && ! is_in_transaction($options)) { + $options['readPreference'] = $this->readPreference; + } + + if ($hasWriteStage) { + $options['readPreference'] = new ReadPreference(ReadPreference::RP_PRIMARY); + } + + $server = select_server($this->manager, $options); + + /* MongoDB 4.2 and later supports a read concern when an $out stage is + * being used, but earlier versions do not. + * + * A read concern is also not compatible with transactions. + */ + if (! isset($options['readConcern']) && + server_supports_feature($server, self::$wireVersionForReadConcern) && + ! is_in_transaction($options) && + ( ! $hasWriteStage || server_supports_feature($server, self::$wireVersionForReadConcernWithWriteStage)) + ) { + $options['readConcern'] = $this->readConcern; + } + + if (! isset($options['typeMap'])) { + $options['typeMap'] = $this->typeMap; + } + + if ($hasWriteStage && + ! isset($options['writeConcern']) && + server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern) && + ! is_in_transaction($options)) { + $options['writeConcern'] = $this->writeConcern; + } + + $operation = new Aggregate($this->databaseName, null, $pipeline, $options); + + return $operation->execute($server); + } + /** * Execute a command on this database. * @@ -167,16 +249,16 @@ class Database */ public function command($command, array $options = []) { - if ( ! isset($options['readPreference'])) { + if (! isset($options['readPreference']) && ! is_in_transaction($options)) { $options['readPreference'] = $this->readPreference; } - if ( ! isset($options['typeMap'])) { + if (! isset($options['typeMap'])) { $options['typeMap'] = $this->typeMap; } $operation = new DatabaseCommand($this->databaseName, $command, $options); - $server = $this->manager->selectServer($options['readPreference']); + $server = select_server($this->manager, $options); return $operation->execute($server); } @@ -194,13 +276,13 @@ class Database */ public function createCollection($collectionName, array $options = []) { - if ( ! isset($options['typeMap'])) { + if (! isset($options['typeMap'])) { $options['typeMap'] = $this->typeMap; } - $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); + $server = select_server($this->manager, $options); - if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) { + if (! isset($options['writeConcern']) && server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern) && ! is_in_transaction($options)) { $options['writeConcern'] = $this->writeConcern; } @@ -221,13 +303,13 @@ class Database */ public function drop(array $options = []) { - if ( ! isset($options['typeMap'])) { + if (! isset($options['typeMap'])) { $options['typeMap'] = $this->typeMap; } - $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); + $server = select_server($this->manager, $options); - if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) { + if (! isset($options['writeConcern']) && server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern) && ! is_in_transaction($options)) { $options['writeConcern'] = $this->writeConcern; } @@ -249,13 +331,13 @@ class Database */ public function dropCollection($collectionName, array $options = []) { - if ( ! isset($options['typeMap'])) { + if (! isset($options['typeMap'])) { $options['typeMap'] = $this->typeMap; } - $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); + $server = select_server($this->manager, $options); - if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) { + if (! isset($options['writeConcern']) && server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern) && ! is_in_transaction($options)) { $options['writeConcern'] = $this->writeConcern; } @@ -338,7 +420,7 @@ class Database public function listCollections(array $options = []) { $operation = new ListCollections($this->databaseName, $options); - $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); + $server = select_server($this->manager, $options); return $operation->execute($server); } @@ -350,18 +432,19 @@ class Database * @param string $collectionName Collection or view to modify * @param array $collectionOptions Collection or view options to assign * @param array $options Command options + * @return array|object * @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'])) { + if (! isset($options['typeMap'])) { $options['typeMap'] = $this->typeMap; } - $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); + $server = select_server($this->manager, $options); - if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) { + if (! isset($options['writeConcern']) && server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern) && ! is_in_transaction($options)) { $options['writeConcern'] = $this->writeConcern; } @@ -422,17 +505,17 @@ class Database */ public function watch(array $pipeline = [], array $options = []) { - if ( ! isset($options['readPreference'])) { + if (! isset($options['readPreference']) && ! is_in_transaction($options)) { $options['readPreference'] = $this->readPreference; } - $server = $this->manager->selectServer($options['readPreference']); + $server = select_server($this->manager, $options); - if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) { + if (! isset($options['readConcern']) && server_supports_feature($server, self::$wireVersionForReadConcern) && ! is_in_transaction($options)) { $options['readConcern'] = $this->readConcern; } - if ( ! isset($options['typeMap'])) { + if (! isset($options['typeMap'])) { $options['typeMap'] = $this->typeMap; } diff --git a/cache/stores/mongodb/MongoDB/DeleteResult.php b/cache/stores/mongodb/MongoDB/DeleteResult.php index a60137b32d5..c5b9ce64d42 100644 --- a/cache/stores/mongodb/MongoDB/DeleteResult.php +++ b/cache/stores/mongodb/MongoDB/DeleteResult.php @@ -25,14 +25,12 @@ use MongoDB\Exception\BadMethodCallException; */ class DeleteResult { + /** @var WriteResult */ private $writeResult; + + /** @var boolean */ private $isAcknowledged; - /** - * Constructor. - * - * @param WriteResult $writeResult - */ public function __construct(WriteResult $writeResult) { $this->writeResult = $writeResult; diff --git a/cache/stores/mongodb/MongoDB/Exception/BadMethodCallException.php b/cache/stores/mongodb/MongoDB/Exception/BadMethodCallException.php index f1fe47af652..00cd79cf224 100644 --- a/cache/stores/mongodb/MongoDB/Exception/BadMethodCallException.php +++ b/cache/stores/mongodb/MongoDB/Exception/BadMethodCallException.php @@ -17,7 +17,10 @@ namespace MongoDB\Exception; -class BadMethodCallException extends \BadMethodCallException implements Exception +use BadMethodCallException as BaseBadMethodCallException; +use function sprintf; + +class BadMethodCallException extends BaseBadMethodCallException implements Exception { /** * Thrown when a mutable method is invoked on an immutable object. diff --git a/cache/stores/mongodb/MongoDB/Exception/Exception.php b/cache/stores/mongodb/MongoDB/Exception/Exception.php index 703a44c7964..154a193915c 100644 --- a/cache/stores/mongodb/MongoDB/Exception/Exception.php +++ b/cache/stores/mongodb/MongoDB/Exception/Exception.php @@ -17,6 +17,8 @@ namespace MongoDB\Exception; -interface Exception extends \MongoDB\Driver\Exception\Exception +use MongoDB\Driver\Exception\Exception as DriverException; + +interface Exception extends DriverException { } diff --git a/cache/stores/mongodb/MongoDB/Exception/InvalidArgumentException.php b/cache/stores/mongodb/MongoDB/Exception/InvalidArgumentException.php index 622215a6b16..e3b44478b27 100644 --- a/cache/stores/mongodb/MongoDB/Exception/InvalidArgumentException.php +++ b/cache/stores/mongodb/MongoDB/Exception/InvalidArgumentException.php @@ -17,7 +17,13 @@ namespace MongoDB\Exception; -class InvalidArgumentException extends \MongoDB\Driver\Exception\InvalidArgumentException implements Exception +use MongoDB\Driver\Exception\InvalidArgumentException as DriverInvalidArgumentException; +use function get_class; +use function gettype; +use function is_object; +use function sprintf; + +class InvalidArgumentException extends DriverInvalidArgumentException implements Exception { /** * Thrown when an argument or option has an invalid type. diff --git a/cache/stores/mongodb/MongoDB/Exception/ResumeTokenException.php b/cache/stores/mongodb/MongoDB/Exception/ResumeTokenException.php index 4aeb6559268..20036470c7e 100644 --- a/cache/stores/mongodb/MongoDB/Exception/ResumeTokenException.php +++ b/cache/stores/mongodb/MongoDB/Exception/ResumeTokenException.php @@ -17,7 +17,10 @@ namespace MongoDB\Exception; -class ResumeTokenException extends \Exception +use function gettype; +use function sprintf; + +class ResumeTokenException extends RuntimeException { /** * Thrown when a resume token has an invalid type. diff --git a/cache/stores/mongodb/MongoDB/Exception/RuntimeException.php b/cache/stores/mongodb/MongoDB/Exception/RuntimeException.php index 4d8c0b8af3b..a64c0a4a41d 100644 --- a/cache/stores/mongodb/MongoDB/Exception/RuntimeException.php +++ b/cache/stores/mongodb/MongoDB/Exception/RuntimeException.php @@ -17,6 +17,8 @@ namespace MongoDB\Exception; -class RuntimeException extends \MongoDB\Driver\Exception\RuntimeException implements Exception +use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; + +class RuntimeException extends DriverRuntimeException implements Exception { } diff --git a/cache/stores/mongodb/MongoDB/Exception/UnexpectedValueException.php b/cache/stores/mongodb/MongoDB/Exception/UnexpectedValueException.php index a65eaa7ccf8..e91b36981f2 100644 --- a/cache/stores/mongodb/MongoDB/Exception/UnexpectedValueException.php +++ b/cache/stores/mongodb/MongoDB/Exception/UnexpectedValueException.php @@ -17,6 +17,8 @@ namespace MongoDB\Exception; -class UnexpectedValueException extends \MongoDB\Driver\Exception\UnexpectedValueException implements Exception +use MongoDB\Driver\Exception\UnexpectedValueException as DriverUnexpectedValueException; + +class UnexpectedValueException extends DriverUnexpectedValueException implements Exception { } diff --git a/cache/stores/mongodb/MongoDB/Exception/UnsupportedException.php b/cache/stores/mongodb/MongoDB/Exception/UnsupportedException.php index 91f99b21469..918de9925d0 100644 --- a/cache/stores/mongodb/MongoDB/Exception/UnsupportedException.php +++ b/cache/stores/mongodb/MongoDB/Exception/UnsupportedException.php @@ -59,6 +59,16 @@ class UnsupportedException extends RuntimeException return new static('Read concern is not supported by the server executing this command'); } + /** + * Thrown when a readConcern is used with a read operation in a transaction. + * + * @return self + */ + public static function readConcernNotSupportedInTransaction() + { + return new static('The "readConcern" option cannot be specified within a transaction. Instead, specify it when starting the transaction.'); + } + /** * Thrown when a command's writeConcern option is not supported by a server. * @@ -68,4 +78,14 @@ class UnsupportedException extends RuntimeException { return new static('Write concern is not supported by the server executing this command'); } + + /** + * Thrown when a writeConcern is used with a write operation in a transaction. + * + * @return self + */ + public static function writeConcernNotSupportedInTransaction() + { + return new static('The "writeConcern" option cannot be specified within a transaction. Instead, specify it when starting the transaction.'); + } } diff --git a/cache/stores/mongodb/MongoDB/GridFS/Bucket.php b/cache/stores/mongodb/MongoDB/GridFS/Bucket.php index 243c331ec74..dc1532ce8f3 100644 --- a/cache/stores/mongodb/MongoDB/GridFS/Bucket.php +++ b/cache/stores/mongodb/MongoDB/GridFS/Bucket.php @@ -19,16 +19,40 @@ namespace MongoDB\GridFS; use MongoDB\Collection; use MongoDB\Driver\Cursor; +use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; 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\Exception\CorruptFileException; use MongoDB\GridFS\Exception\FileNotFoundException; +use MongoDB\Model\BSONArray; +use MongoDB\Model\BSONDocument; use MongoDB\Operation\Find; use stdClass; +use function array_intersect_key; +use function fopen; +use function get_resource_type; +use function in_array; +use function is_array; +use function is_bool; +use function is_integer; +use function is_object; +use function is_resource; +use function is_string; +use function method_exists; +use function MongoDB\apply_type_map_to_document; +use function MongoDB\BSON\fromPHP; +use function MongoDB\BSON\toJSON; +use function property_exists; +use function sprintf; +use function stream_context_create; +use function stream_copy_to_stream; +use function stream_get_meta_data; +use function stream_get_wrappers; +use function urlencode; /** * Bucket provides a public API for interacting with the GridFS files and chunks @@ -38,24 +62,50 @@ use stdClass; */ class Bucket { + /** @var string */ private static $defaultBucketName = 'fs'; + + /** @var integer */ private static $defaultChunkSizeBytes = 261120; + + /** @var array */ private static $defaultTypeMap = [ - 'array' => 'MongoDB\Model\BSONArray', - 'document' => 'MongoDB\Model\BSONDocument', - 'root' => 'MongoDB\Model\BSONDocument', + 'array' => BSONArray::class, + 'document' => BSONDocument::class, + 'root' => BSONDocument::class, ]; + + /** @var string */ private static $streamWrapperProtocol = 'gridfs'; + /** @var CollectionWrapper */ private $collectionWrapper; + + /** @var string */ private $databaseName; + + /** @var Manager */ private $manager; + + /** @var string */ private $bucketName; + + /** @var boolean */ private $disableMD5; + + /** @var integer */ private $chunkSizeBytes; + + /** @var ReadConcern */ private $readConcern; + + /** @var ReadPreference */ private $readPreference; + + /** @var array */ private $typeMap; + + /** @var WriteConcern */ private $writeConcern; /** @@ -110,11 +160,11 @@ class Bucket } if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) { - throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern'); + throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], ReadConcern::class); } if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) { - throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference'); + throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class); } if (isset($options['typeMap']) && ! is_array($options['typeMap'])) { @@ -122,7 +172,7 @@ class Bucket } if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) { - throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern'); + throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class); } $this->manager = $manager; @@ -192,7 +242,7 @@ class Bucket */ public function downloadToStream($id, $destination) { - if ( ! is_resource($destination) || get_resource_type($destination) != "stream") { + if (! is_resource($destination) || get_resource_type($destination) != "stream") { throw InvalidArgumentException::invalidType('$destination', $destination, 'resource'); } @@ -227,7 +277,7 @@ class Bucket */ public function downloadToStreamByName($filename, $destination, array $options = []) { - if ( ! is_resource($destination) || get_resource_type($destination) != "stream") { + if (! is_resource($destination) || get_resource_type($destination) != "stream") { throw InvalidArgumentException::invalidType('$destination', $destination, 'resource'); } @@ -332,7 +382,7 @@ class Bucket $file = $this->getRawFileDocumentForStream($stream); // Filter the raw document through the specified type map - return \MongoDB\apply_type_map_to_document($file, $this->typeMap); + return apply_type_map_to_document($file, $this->typeMap); } /** @@ -352,9 +402,9 @@ class Bucket * the root type so we can reliably access the ID. */ $typeMap = ['root' => 'stdClass'] + $this->typeMap; - $file = \MongoDB\apply_type_map_to_document($file, $typeMap); + $file = apply_type_map_to_document($file, $typeMap); - if ( ! isset($file->_id) && ! property_exists($file, '_id')) { + if (! isset($file->_id) && ! property_exists($file, '_id')) { throw new CorruptFileException('file._id does not exist'); } @@ -531,7 +581,7 @@ class Bucket ? $updateResult->getMatchedCount() === 1 : $this->collectionWrapper->findFileById($id) !== null; - if ( ! $found) { + if (! $found) { throw FileNotFoundException::byId($id, $this->getFilesNamespace()); } } @@ -561,7 +611,7 @@ class Bucket */ public function uploadFromStream($filename, $source, array $options = []) { - if ( ! is_resource($source) || get_resource_type($source) != "stream") { + if (! is_resource($source) || get_resource_type($source) != "stream") { throw InvalidArgumentException::invalidType('$source', $source, 'resource'); } @@ -579,10 +629,10 @@ class Bucket */ private function createPathForFile(stdClass $file) { - if ( ! is_object($file->_id) || method_exists($file->_id, '__toString')) { + 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])); + $id = toJSON(fromPHP(['_id' => $file->_id])); } return sprintf( @@ -631,14 +681,14 @@ class Bucket */ private function getRawFileDocumentForStream($stream) { - if ( ! is_resource($stream) || get_resource_type($stream) != "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'); + if (! isset($metadata['wrapper_data']) || ! $metadata['wrapper_data'] instanceof StreamWrapper) { + throw InvalidArgumentException::invalidType('$stream wrapper data', isset($metadata['wrapper_data']) ? $metadata['wrapper_data'] : null, StreamWrapper::class); } return $metadata['wrapper_data']->getFile(); diff --git a/cache/stores/mongodb/MongoDB/GridFS/CollectionWrapper.php b/cache/stores/mongodb/MongoDB/GridFS/CollectionWrapper.php index 754fd4b3cb0..dd4c1533614 100644 --- a/cache/stores/mongodb/MongoDB/GridFS/CollectionWrapper.php +++ b/cache/stores/mongodb/MongoDB/GridFS/CollectionWrapper.php @@ -18,11 +18,14 @@ namespace MongoDB\GridFS; use MongoDB\Collection; -use MongoDB\UpdateResult; use MongoDB\Driver\Cursor; use MongoDB\Driver\Manager; use MongoDB\Driver\ReadPreference; +use MongoDB\Exception\InvalidArgumentException; +use MongoDB\UpdateResult; use stdClass; +use function abs; +use function sprintf; /** * CollectionWrapper abstracts the GridFS files and chunks collections. @@ -31,10 +34,19 @@ use stdClass; */ class CollectionWrapper { + /** @var string */ private $bucketName; + + /** @var Collection */ private $chunksCollection; + + /** @var string */ private $databaseName; + + /** @var boolean */ private $checkedIndexes = false; + + /** @var Collection */ private $filesCollection; /** @@ -121,7 +133,7 @@ class CollectionWrapper * * @see Bucket::downloadToStreamByName() * @see Bucket::openDownloadStreamByName() - * @param string $filename + * @param string $filename * @param integer $revision * @return stdClass|null */ @@ -234,7 +246,7 @@ class CollectionWrapper */ public function insertChunk($chunk) { - if ( ! $this->checkedIndexes) { + if (! $this->checkedIndexes) { $this->ensureIndexes(); } @@ -250,7 +262,7 @@ class CollectionWrapper */ public function insertFile($file) { - if ( ! $this->checkedIndexes) { + if (! $this->checkedIndexes) { $this->ensureIndexes(); } @@ -261,7 +273,7 @@ class CollectionWrapper * Updates the filename field in the file document for a given ID. * * @param mixed $id - * @param string $filename + * @param string $filename * @return UpdateResult */ public function updateFilenameForId($id, $filename) @@ -314,7 +326,7 @@ class CollectionWrapper $this->checkedIndexes = true; - if ( ! $this->isFilesCollectionEmpty()) { + if (! $this->isFilesCollectionEmpty()) { return; } diff --git a/cache/stores/mongodb/MongoDB/GridFS/Exception/CorruptFileException.php b/cache/stores/mongodb/MongoDB/GridFS/Exception/CorruptFileException.php index 787c9b886fb..c353725692e 100644 --- a/cache/stores/mongodb/MongoDB/GridFS/Exception/CorruptFileException.php +++ b/cache/stores/mongodb/MongoDB/GridFS/Exception/CorruptFileException.php @@ -18,6 +18,7 @@ namespace MongoDB\GridFS\Exception; use MongoDB\Exception\RuntimeException; +use function sprintf; class CorruptFileException extends RuntimeException { diff --git a/cache/stores/mongodb/MongoDB/GridFS/Exception/FileNotFoundException.php b/cache/stores/mongodb/MongoDB/GridFS/Exception/FileNotFoundException.php index 2d3d0376184..f0d222381c2 100644 --- a/cache/stores/mongodb/MongoDB/GridFS/Exception/FileNotFoundException.php +++ b/cache/stores/mongodb/MongoDB/GridFS/Exception/FileNotFoundException.php @@ -18,6 +18,9 @@ namespace MongoDB\GridFS\Exception; use MongoDB\Exception\RuntimeException; +use function MongoDB\BSON\fromPHP; +use function MongoDB\BSON\toJSON; +use function sprintf; class FileNotFoundException extends RuntimeException { @@ -43,7 +46,7 @@ class FileNotFoundException extends RuntimeException */ public static function byId($id, $namespace) { - $json = \MongoDB\BSON\toJSON(\MongoDB\BSON\fromPHP(['_id' => $id])); + $json = toJSON(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 index d49b21304e5..7978de97270 100644 --- a/cache/stores/mongodb/MongoDB/GridFS/ReadableStream.php +++ b/cache/stores/mongodb/MongoDB/GridFS/ReadableStream.php @@ -17,10 +17,17 @@ namespace MongoDB\GridFS; +use IteratorIterator; use MongoDB\Exception\InvalidArgumentException; use MongoDB\GridFS\Exception\CorruptFileException; -use IteratorIterator; use stdClass; +use function ceil; +use function floor; +use function is_integer; +use function property_exists; +use function sprintf; +use function strlen; +use function substr; /** * ReadableStream abstracts the process of reading a GridFS file. @@ -29,15 +36,34 @@ use stdClass; */ class ReadableStream { + /** @var string|null */ private $buffer; + + /** @var integer */ private $bufferOffset = 0; + + /** @var integer */ private $chunkSize; + + /** @var integer */ private $chunkOffset = 0; + + /** @var IteratorIterator|null */ private $chunksIterator; + + /** @var CollectionWrapper */ private $collectionWrapper; + + /** @var float|integer */ private $expectedLastChunkSize = 0; + + /** @var stdClass */ private $file; + + /** @var integer */ private $length; + + /** @var integer */ private $numChunks = 0; /** @@ -49,15 +75,15 @@ class ReadableStream */ public function __construct(CollectionWrapper $collectionWrapper, stdClass $file) { - if ( ! isset($file->chunkSize) || ! is_integer($file->chunkSize) || $file->chunkSize < 1) { + 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) { + 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')) { + if (! isset($file->_id) && ! property_exists($file, '_id')) { throw new CorruptFileException('file._id does not exist'); } @@ -202,6 +228,7 @@ class ReadableStream */ if ($lastChunkOffset > $this->chunkOffset) { $this->chunksIterator = null; + return; } @@ -239,7 +266,7 @@ class ReadableStream return false; } - if ( ! $this->chunksIterator->valid()) { + if (! $this->chunksIterator->valid()) { throw CorruptFileException::missingChunk($this->chunkOffset); } @@ -253,7 +280,7 @@ class ReadableStream $actualChunkSize = strlen($this->buffer); - $expectedChunkSize = ($this->chunkOffset === $this->numChunks - 1) + $expectedChunkSize = $this->chunkOffset === $this->numChunks - 1 ? $this->expectedLastChunkSize : $this->chunkSize; diff --git a/cache/stores/mongodb/MongoDB/GridFS/StreamWrapper.php b/cache/stores/mongodb/MongoDB/GridFS/StreamWrapper.php index 29a2be68a0f..a79d4d904ec 100644 --- a/cache/stores/mongodb/MongoDB/GridFS/StreamWrapper.php +++ b/cache/stores/mongodb/MongoDB/GridFS/StreamWrapper.php @@ -17,9 +17,24 @@ namespace MongoDB\GridFS; -use MongoDB\BSON\UTCDateTime; use Exception; +use MongoDB\BSON\UTCDateTime; use stdClass; +use function explode; +use function get_class; +use function in_array; +use function is_integer; +use function sprintf; +use function stream_context_get_options; +use function stream_get_wrappers; +use function stream_wrapper_register; +use function stream_wrapper_unregister; +use function trigger_error; +use const E_USER_WARNING; +use const SEEK_CUR; +use const SEEK_END; +use const SEEK_SET; +use const STREAM_IS_URL; /** * Stream wrapper for reading and writing a GridFS file. @@ -30,13 +45,16 @@ use stdClass; */ class StreamWrapper { - /** - * @var resource|null Stream context (set by PHP) - */ + /** @var resource|null Stream context (set by PHP) */ public $context; + /** @var string|null */ private $mode; + + /** @var string|null */ private $protocol; + + /** @var ReadableStream|WritableStream|null */ private $stream; /** @@ -60,7 +78,7 @@ class StreamWrapper stream_wrapper_unregister($protocol); } - stream_wrapper_register($protocol, get_called_class(), \STREAM_IS_URL); + stream_wrapper_register($protocol, static::class, STREAM_IS_URL); } /** @@ -81,7 +99,7 @@ class StreamWrapper */ public function stream_eof() { - if ( ! $this->stream instanceof ReadableStream) { + if (! $this->stream instanceof ReadableStream) { return false; } @@ -96,6 +114,7 @@ class StreamWrapper * @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 + * @return boolean */ public function stream_open($path, $mode, $options, &$openedPath) { @@ -125,14 +144,15 @@ class StreamWrapper */ public function stream_read($length) { - if ( ! $this->stream instanceof ReadableStream) { + 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); + trigger_error(sprintf('%s: %s', get_class($e), $e->getMessage()), E_USER_WARNING); + return false; } } @@ -145,15 +165,15 @@ class StreamWrapper * @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) + public function stream_seek($offset, $whence = SEEK_SET) { $size = $this->stream->getSize(); - if ($whence === \SEEK_CUR) { + if ($whence === SEEK_CUR) { $offset += $this->stream->tell(); } - if ($whence === \SEEK_END) { + if ($whence === SEEK_END) { $offset += $size; } @@ -221,14 +241,15 @@ class StreamWrapper */ public function stream_write($data) { - if ( ! $this->stream instanceof WritableStream) { + 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); + trigger_error(sprintf('%s: %s', get_class($e), $e->getMessage()), E_USER_WARNING); + return false; } } @@ -241,6 +262,7 @@ class StreamWrapper private function getStatTemplate() { return [ + // phpcs:disable Squiz.Arrays.ArrayDeclaration.IndexNoNewline 0 => 0, 'dev' => 0, 1 => 0, 'ino' => 0, 2 => 0, 'mode' => 0, @@ -254,6 +276,7 @@ class StreamWrapper 10 => 0, 'ctime' => 0, 11 => -1, 'blksize' => -1, 12 => -1, 'blocks' => -1, + // phpcs:enable ]; } diff --git a/cache/stores/mongodb/MongoDB/GridFS/WritableStream.php b/cache/stores/mongodb/MongoDB/GridFS/WritableStream.php index 1fed3f9c86c..d7f1e4c47de 100644 --- a/cache/stores/mongodb/MongoDB/GridFS/WritableStream.php +++ b/cache/stores/mongodb/MongoDB/GridFS/WritableStream.php @@ -23,6 +23,19 @@ use MongoDB\BSON\UTCDateTime; use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; use MongoDB\Exception\InvalidArgumentException; use stdClass; +use function array_intersect_key; +use function hash_final; +use function hash_init; +use function hash_update; +use function is_array; +use function is_bool; +use function is_integer; +use function is_object; +use function is_string; +use function MongoDB\is_string_array; +use function sprintf; +use function strlen; +use function substr; /** * WritableStream abstracts the process of writing a GridFS file. @@ -31,16 +44,34 @@ use stdClass; */ class WritableStream { + /** @var integer */ private static $defaultChunkSizeBytes = 261120; + /** @var string */ private $buffer = ''; + + /** @var integer */ private $chunkOffset = 0; + + /** @var integer */ private $chunkSize; + + /** @var boolean */ private $disableMD5; + + /** @var CollectionWrapper */ private $collectionWrapper; + + /** @var array */ private $file; + + /** @var resource */ private $hashCtx; + + /** @var boolean */ private $isClosed = false; + + /** @var integer */ private $length = 0; /** @@ -74,12 +105,12 @@ class WritableStream public function __construct(CollectionWrapper $collectionWrapper, $filename, array $options = []) { $options += [ - '_id' => new ObjectId, + '_id' => new ObjectId(), 'chunkSizeBytes' => self::$defaultChunkSizeBytes, 'disableMD5' => false, ]; - if (isset($options['aliases']) && ! \MongoDB\is_string_array($options['aliases'])) { + if (isset($options['aliases']) && ! is_string_array($options['aliases'])) { throw InvalidArgumentException::invalidType('"aliases" option', $options['aliases'], 'array of strings'); } @@ -107,7 +138,7 @@ class WritableStream $this->collectionWrapper = $collectionWrapper; $this->disableMD5 = $options['disableMD5']; - if ( ! $this->disableMD5) { + if (! $this->disableMD5) { $this->hashCtx = hash_init('md5'); } @@ -233,9 +264,9 @@ class WritableStream private function fileCollectionInsert() { $this->file['length'] = $this->length; - $this->file['uploadDate'] = new UTCDateTime; + $this->file['uploadDate'] = new UTCDateTime(); - if ( ! $this->disableMD5) { + if (! $this->disableMD5) { $this->file['md5'] = hash_final($this->hashCtx); } @@ -265,7 +296,7 @@ class WritableStream 'data' => new Binary($data, Binary::TYPE_GENERIC), ]; - if ( ! $this->disableMD5) { + if (! $this->disableMD5) { hash_update($this->hashCtx, $data); } diff --git a/cache/stores/mongodb/MongoDB/InsertManyResult.php b/cache/stores/mongodb/MongoDB/InsertManyResult.php index a9ecb693990..2d288af4cfa 100644 --- a/cache/stores/mongodb/MongoDB/InsertManyResult.php +++ b/cache/stores/mongodb/MongoDB/InsertManyResult.php @@ -25,13 +25,16 @@ use MongoDB\Exception\BadMethodCallException; */ class InsertManyResult { + /** @var WriteResult */ private $writeResult; + + /** @var mixed[] */ private $insertedIds; + + /** @var boolean */ private $isAcknowledged; /** - * Constructor. - * * @param WriteResult $writeResult * @param mixed[] $insertedIds */ diff --git a/cache/stores/mongodb/MongoDB/InsertOneResult.php b/cache/stores/mongodb/MongoDB/InsertOneResult.php index 5a580bd3a17..c4dee6e9320 100644 --- a/cache/stores/mongodb/MongoDB/InsertOneResult.php +++ b/cache/stores/mongodb/MongoDB/InsertOneResult.php @@ -25,13 +25,16 @@ use MongoDB\Exception\BadMethodCallException; */ class InsertOneResult { + /** @var WriteResult */ private $writeResult; + + /** @var mixed */ private $insertedId; + + /** @var boolean */ private $isAcknowledged; /** - * Constructor. - * * @param WriteResult $writeResult * @param mixed $insertedId */ diff --git a/cache/stores/mongodb/MongoDB/MapReduceResult.php b/cache/stores/mongodb/MongoDB/MapReduceResult.php index 37ba38fde09..cda55a57a7f 100644 --- a/cache/stores/mongodb/MongoDB/MapReduceResult.php +++ b/cache/stores/mongodb/MongoDB/MapReduceResult.php @@ -20,6 +20,7 @@ namespace MongoDB; use IteratorAggregate; use stdClass; use Traversable; +use function call_user_func; /** * Result class for mapReduce command results. @@ -34,14 +35,19 @@ use Traversable; */ class MapReduceResult implements IteratorAggregate { + /** @var callable */ private $getIterator; + + /** @var integer */ private $executionTimeMS; + + /** @var array */ private $counts; + + /** @var array */ private $timing; /** - * Constructor. - * * @internal * @param callable $getIterator Callback that returns a Traversable for mapReduce results * @param stdClass $result Result document from the mapReduce command @@ -71,7 +77,7 @@ class MapReduceResult implements IteratorAggregate */ public function getExecutionTimeMS() { - return $this->executionTimeMS; + return (integer) $this->executionTimeMS; } /** diff --git a/cache/stores/mongodb/MongoDB/Model/BSONArray.php b/cache/stores/mongodb/MongoDB/Model/BSONArray.php index 61dc9b4ccf1..8230a5e5a9d 100644 --- a/cache/stores/mongodb/MongoDB/Model/BSONArray.php +++ b/cache/stores/mongodb/MongoDB/Model/BSONArray.php @@ -17,10 +17,12 @@ namespace MongoDB\Model; -use MongoDB\BSON\Serializable; -use MongoDB\BSON\Unserializable; use ArrayObject; use JsonSerializable; +use MongoDB\BSON\Serializable; +use MongoDB\BSON\Unserializable; +use function array_values; +use function MongoDB\recursive_copy; /** * Model class for a BSON array. @@ -38,7 +40,7 @@ class BSONArray extends ArrayObject implements JsonSerializable, Serializable, U public function __clone() { foreach ($this as $key => $value) { - $this[$key] = \MongoDB\recursive_copy($value); + $this[$key] = recursive_copy($value); } } @@ -52,7 +54,7 @@ class BSONArray extends ArrayObject implements JsonSerializable, Serializable, U */ public static function __set_state(array $properties) { - $array = new static; + $array = new static(); $array->exchangeArray($properties); return $array; diff --git a/cache/stores/mongodb/MongoDB/Model/BSONDocument.php b/cache/stores/mongodb/MongoDB/Model/BSONDocument.php index 90ea88705d4..b00ac48942f 100644 --- a/cache/stores/mongodb/MongoDB/Model/BSONDocument.php +++ b/cache/stores/mongodb/MongoDB/Model/BSONDocument.php @@ -17,10 +17,11 @@ namespace MongoDB\Model; -use MongoDB\BSON\Serializable; -use MongoDB\BSON\Unserializable; use ArrayObject; use JsonSerializable; +use MongoDB\BSON\Serializable; +use MongoDB\BSON\Unserializable; +use function MongoDB\recursive_copy; /** * Model class for a BSON document. @@ -38,17 +39,18 @@ class BSONDocument extends ArrayObject implements JsonSerializable, Serializable public function __clone() { foreach ($this as $key => $value) { - $this[$key] = \MongoDB\recursive_copy($value); + $this[$key] = recursive_copy($value); } } /** - * Constructor. - * * This overrides the parent constructor to allow property access of entries * by default. * * @see http://php.net/arrayobject.construct + * @param array $input + * @param integer $flags + * @param string $iterator_class */ public function __construct($input = [], $flags = ArrayObject::ARRAY_AS_PROPS, $iterator_class = 'ArrayIterator') { @@ -65,7 +67,7 @@ class BSONDocument extends ArrayObject implements JsonSerializable, Serializable */ public static function __set_state(array $properties) { - $document = new static; + $document = new static(); $document->exchangeArray($properties); return $document; diff --git a/cache/stores/mongodb/MongoDB/Model/BSONIterator.php b/cache/stores/mongodb/MongoDB/Model/BSONIterator.php index f0827943dd2..093bfbe5566 100644 --- a/cache/stores/mongodb/MongoDB/Model/BSONIterator.php +++ b/cache/stores/mongodb/MongoDB/Model/BSONIterator.php @@ -17,22 +17,40 @@ namespace MongoDB\Model; -use MongoDB\Exception\UnexpectedValueException; -use MongoDB\Model\BSONDocument; use Iterator; +use MongoDB\Exception\InvalidArgumentException; +use MongoDB\Exception\UnexpectedValueException; +use function is_array; +use function MongoDB\BSON\toPHP; +use function sprintf; +use function strlen; +use function substr; +use function unpack; /** * Iterator for BSON documents. */ class BSONIterator implements Iterator { + /** @var integer */ private static $bsonSize = 4; + /** @var string */ private $buffer; + + /** @var integer */ private $bufferLength; + + /** @var mixed */ private $current; + + /** @var integer */ private $key = 0; + + /** @var integer */ private $position = 0; + + /** @var array */ private $options; /** @@ -54,7 +72,7 @@ class BSONIterator implements Iterator throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array'); } - if ( ! isset($options['typeMap'])) { + if (! isset($options['typeMap'])) { $options['typeMap'] = []; } @@ -129,7 +147,7 @@ class BSONIterator implements Iterator 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->current = 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 index adad231cad5..ce56efa0da7 100644 --- a/cache/stores/mongodb/MongoDB/Model/CachingIterator.php +++ b/cache/stores/mongodb/MongoDB/Model/CachingIterator.php @@ -21,6 +21,11 @@ use Countable; use Generator; use Iterator; use Traversable; +use function count; +use function current; +use function key; +use function next; +use function reset; /** * Iterator for wrapping a Traversable and caching its results. @@ -33,14 +38,19 @@ use Traversable; */ class CachingIterator implements Countable, Iterator { + /** @var array */ private $items = []; + + /** @var Generator */ private $iterator; + + /** @var boolean */ private $iteratorAdvanced = false; + + /** @var boolean */ 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 @@ -90,7 +100,7 @@ class CachingIterator implements Countable, Iterator */ public function next() { - if ( ! $this->iteratorExhausted) { + if (! $this->iteratorExhausted) { $this->iterator->next(); $this->storeCurrentItem(); } @@ -128,7 +138,7 @@ class CachingIterator implements Countable, Iterator */ private function exhaustIterator() { - while ( ! $this->iteratorExhausted) { + while (! $this->iteratorExhausted) { $this->next(); } } diff --git a/cache/stores/mongodb/MongoDB/Model/ChangeStreamIterator.php b/cache/stores/mongodb/MongoDB/Model/ChangeStreamIterator.php new file mode 100644 index 00000000000..cf60a369dc3 --- /dev/null +++ b/cache/stores/mongodb/MongoDB/Model/ChangeStreamIterator.php @@ -0,0 +1,292 @@ +batchSize = $firstBatchSize; + $this->isRewindNop = ($firstBatchSize === 0); + $this->postBatchResumeToken = $postBatchResumeToken; + $this->resumeToken = $initialResumeToken; + } + + /** @internal */ + final public function commandFailed(CommandFailedEvent $event) + { + } + + /** @internal */ + final public function commandStarted(CommandStartedEvent $event) + { + if ($event->getCommandName() !== 'getMore') { + return; + } + + $this->batchPosition = 0; + $this->batchSize = null; + $this->postBatchResumeToken = null; + } + + /** @internal */ + final public function commandSucceeded(CommandSucceededEvent $event) + { + if ($event->getCommandName() !== 'getMore') { + return; + } + + $reply = $event->getReply(); + + if (! isset($reply->cursor->nextBatch) || ! is_array($reply->cursor->nextBatch)) { + throw new UnexpectedValueException('getMore command did not return a "cursor.nextBatch" array'); + } + + $this->batchSize = count($reply->cursor->nextBatch); + + if (isset($reply->cursor->postBatchResumeToken) && is_object($reply->cursor->postBatchResumeToken)) { + $this->postBatchResumeToken = $reply->cursor->postBatchResumeToken; + } + } + + /** + * @see https://php.net/iteratoriterator.current + * @return mixed + */ + public function current() + { + return $this->isValid ? parent::current() : null; + } + + /** + * Returns the resume token for the iterator's current position. + * + * Null may be returned if no change documents have been iterated and the + * server did not include a postBatchResumeToken in its aggregate or getMore + * command response. + * + * @return array|object|null + */ + public function getResumeToken() + { + return $this->resumeToken; + } + + /** + * @see https://php.net/iteratoriterator.key + * @return mixed + */ + public function key() + { + return $this->isValid ? parent::key() : null; + } + + /** + * @see https://php.net/iteratoriterator.rewind + * @return void + */ + public function next() + { + /* Determine if advancing the iterator will execute a getMore command + * (i.e. we are already positioned at the end of the current batch). If + * so, rely on the APM callbacks to reset $batchPosition and update + * $batchSize. Otherwise, we can forgo APM and manually increment + * $batchPosition after calling next(). */ + $getMore = $this->isAtEndOfBatch(); + + if ($getMore) { + addSubscriber($this); + } + + try { + parent::next(); + $this->onIteration(! $getMore); + } finally { + if ($getMore) { + removeSubscriber($this); + } + } + } + + /** + * @see https://php.net/iteratoriterator.rewind + * @return void + */ + public function rewind() + { + if ($this->isRewindNop) { + return; + } + + parent::rewind(); + $this->onIteration(false); + } + + /** + * @see https://php.net/iteratoriterator.valid + * @return boolean + */ + public function valid() + { + return $this->isValid; + } + + /** + * Extracts the resume token (i.e. "_id" field) from a change document. + * + * @param array|object $document Change document + * @return array|object + * @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)) { + $this->isValid = false; + throw ResumeTokenException::notFound(); + } + + if (! is_array($resumeToken) && ! is_object($resumeToken)) { + $this->isValid = false; + throw ResumeTokenException::invalidType($resumeToken); + } + + return $resumeToken; + } + + /** + * Return whether the iterator is positioned at the end of the batch. + * + * @return boolean + */ + private function isAtEndOfBatch() + { + return $this->batchPosition + 1 >= $this->batchSize; + } + + /** + * Perform housekeeping after an iteration event. + * + * @see https://github.com/mongodb/specifications/blob/master/source/change-streams/change-streams.rst#updating-the-cached-resume-token + * @param boolean $incrementBatchPosition + */ + private function onIteration($incrementBatchPosition) + { + $this->isValid = parent::valid(); + + /* Disable rewind()'s NOP behavior once we advance to a valid position. + * This will allow the driver to throw a LogicException if rewind() is + * called after the cursor has advanced past its first element. */ + if ($this->isRewindNop && $this->isValid) { + $this->isRewindNop = false; + } + + if ($incrementBatchPosition && $this->isValid) { + $this->batchPosition++; + } + + /* If the iterator is positioned at the end of the batch, apply the + * postBatchResumeToken if it's available. This handles both the case + * where the current batch is empty (since onIteration() will be called + * after a successful getMore) and when the iterator has advanced to the + * last document in its current batch. Otherwise, extract a resume token + * from the current document if possible. */ + if ($this->isAtEndOfBatch() && $this->postBatchResumeToken !== null) { + $this->resumeToken = $this->postBatchResumeToken; + } elseif ($this->isValid) { + $this->resumeToken = $this->extractResumeToken($this->current()); + } + } +} diff --git a/cache/stores/mongodb/MongoDB/Model/CollectionInfo.php b/cache/stores/mongodb/MongoDB/Model/CollectionInfo.php index 39b20c4a3b1..f4d61df2b71 100644 --- a/cache/stores/mongodb/MongoDB/Model/CollectionInfo.php +++ b/cache/stores/mongodb/MongoDB/Model/CollectionInfo.php @@ -17,8 +17,9 @@ namespace MongoDB\Model; -use MongoDB\Exception\BadMethodCallException; use ArrayAccess; +use MongoDB\Exception\BadMethodCallException; +use function array_key_exists; /** * Collection information model class. @@ -33,11 +34,10 @@ use ArrayAccess; */ class CollectionInfo implements ArrayAccess { + /** @var array */ private $info; /** - * Constructor. - * * @param array $info Collection info */ public function __construct(array $info) @@ -63,6 +63,7 @@ class CollectionInfo implements ArrayAccess */ public function getCappedMax() { + /* The MongoDB server might return this number as an integer or float */ return isset($this->info['options']['max']) ? (integer) $this->info['options']['max'] : null; } @@ -73,6 +74,7 @@ class CollectionInfo implements ArrayAccess */ public function getCappedSize() { + /* The MongoDB server might return this number as an integer or float */ return isset($this->info['options']['size']) ? (integer) $this->info['options']['size'] : null; } @@ -134,21 +136,24 @@ class CollectionInfo implements ArrayAccess * Not supported. * * @see http://php.net/arrayaccess.offsetset + * @param mixed $key + * @param mixed $value * @throws BadMethodCallException */ public function offsetSet($key, $value) { - throw BadMethodCallException::classIsImmutable(__CLASS__); + throw BadMethodCallException::classIsImmutable(self::class); } /** * Not supported. * * @see http://php.net/arrayaccess.offsetunset + * @param mixed $key * @throws BadMethodCallException */ public function offsetUnset($key) { - throw BadMethodCallException::classIsImmutable(__CLASS__); + throw BadMethodCallException::classIsImmutable(self::class); } } diff --git a/cache/stores/mongodb/MongoDB/Model/DatabaseInfo.php b/cache/stores/mongodb/MongoDB/Model/DatabaseInfo.php index e93c5ca0a58..9b656113d6d 100644 --- a/cache/stores/mongodb/MongoDB/Model/DatabaseInfo.php +++ b/cache/stores/mongodb/MongoDB/Model/DatabaseInfo.php @@ -17,8 +17,10 @@ namespace MongoDB\Model; -use MongoDB\Exception\BadMethodCallException; use ArrayAccess; +use MongoDB\Exception\BadMethodCallException; +use function array_key_exists; + /** * Database information model class. * @@ -31,11 +33,10 @@ use ArrayAccess; */ class DatabaseInfo implements ArrayAccess { + /** @var array */ private $info; /** - * Constructor. - * * @param array $info Database info */ public function __construct(array $info) @@ -71,6 +72,7 @@ class DatabaseInfo implements ArrayAccess */ public function getSizeOnDisk() { + /* The MongoDB server might return this number as an integer or float */ return (integer) $this->info['sizeOnDisk']; } @@ -112,21 +114,24 @@ class DatabaseInfo implements ArrayAccess * Not supported. * * @see http://php.net/arrayaccess.offsetset + * @param mixed $key + * @param mixed $value * @throws BadMethodCallException */ public function offsetSet($key, $value) { - throw BadMethodCallException::classIsImmutable(__CLASS__); + throw BadMethodCallException::classIsImmutable(self::class); } /** * Not supported. * * @see http://php.net/arrayaccess.offsetunset + * @param mixed $key * @throws BadMethodCallException */ public function offsetUnset($key) { - throw BadMethodCallException::classIsImmutable(__CLASS__); + throw BadMethodCallException::classIsImmutable(self::class); } } diff --git a/cache/stores/mongodb/MongoDB/Model/DatabaseInfoLegacyIterator.php b/cache/stores/mongodb/MongoDB/Model/DatabaseInfoLegacyIterator.php index 04be47e3061..e50b54166ef 100644 --- a/cache/stores/mongodb/MongoDB/Model/DatabaseInfoLegacyIterator.php +++ b/cache/stores/mongodb/MongoDB/Model/DatabaseInfoLegacyIterator.php @@ -17,6 +17,11 @@ namespace MongoDB\Model; +use function current; +use function key; +use function next; +use function reset; + /** * DatabaseInfoIterator for inline listDatabases command results. * @@ -29,11 +34,10 @@ namespace MongoDB\Model; */ class DatabaseInfoLegacyIterator implements DatabaseInfoIterator { + /** @var array */ private $databases; /** - * Constructor. - * * @param array $databases */ public function __construct(array $databases) diff --git a/cache/stores/mongodb/MongoDB/Model/IndexInfo.php b/cache/stores/mongodb/MongoDB/Model/IndexInfo.php index 7491f34aa83..ee8a29357a1 100644 --- a/cache/stores/mongodb/MongoDB/Model/IndexInfo.php +++ b/cache/stores/mongodb/MongoDB/Model/IndexInfo.php @@ -17,8 +17,10 @@ namespace MongoDB\Model; -use MongoDB\Exception\BadMethodCallException; use ArrayAccess; +use MongoDB\Exception\BadMethodCallException; +use function array_key_exists; +use function array_search; /** * Index information model class. @@ -37,11 +39,10 @@ use ArrayAccess; */ class IndexInfo implements ArrayAccess { + /** @var array */ private $info; /** - * Constructor. - * * @param array $info Index info */ public function __construct(array $info) @@ -206,21 +207,24 @@ class IndexInfo implements ArrayAccess * Not supported. * * @see http://php.net/arrayaccess.offsetset + * @param mixed $key + * @param mixed $value * @throws BadMethodCallException */ public function offsetSet($key, $value) { - throw BadMethodCallException::classIsImmutable(__CLASS__); + throw BadMethodCallException::classIsImmutable(self::class); } /** * Not supported. * * @see http://php.net/arrayaccess.offsetunset + * @param mixed $key * @throws BadMethodCallException */ public function offsetUnset($key) { - throw BadMethodCallException::classIsImmutable(__CLASS__); + throw BadMethodCallException::classIsImmutable(self::class); } } diff --git a/cache/stores/mongodb/MongoDB/Model/IndexInput.php b/cache/stores/mongodb/MongoDB/Model/IndexInput.php index b306fa002ab..ca85ccab943 100644 --- a/cache/stores/mongodb/MongoDB/Model/IndexInput.php +++ b/cache/stores/mongodb/MongoDB/Model/IndexInput.php @@ -19,6 +19,13 @@ namespace MongoDB\Model; use MongoDB\BSON\Serializable; use MongoDB\Exception\InvalidArgumentException; +use function is_array; +use function is_float; +use function is_int; +use function is_object; +use function is_string; +use function MongoDB\generate_index_name; +use function sprintf; /** * Index input model class. @@ -32,43 +39,42 @@ use MongoDB\Exception\InvalidArgumentException; */ class IndexInput implements Serializable { + /** @var array */ private $index; /** - * Constructor. - * * @param array $index Index specification * @throws InvalidArgumentException */ public function __construct(array $index) { - if ( ! isset($index['key'])) { + if (! isset($index['key'])) { throw new InvalidArgumentException('Required "key" document is missing from index specification'); } - if ( ! is_array($index['key']) && ! is_object($index['key'])) { + 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)) { + 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'])) { + if (! isset($index['ns'])) { throw new InvalidArgumentException('Required "ns" option is missing from index specification'); } - if ( ! is_string($index['ns'])) { + 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 (! isset($index['name'])) { + $index['name'] = generate_index_name($index['key']); } - if ( ! is_string($index['name'])) { + if (! is_string($index['name'])) { throw InvalidArgumentException::invalidType('"name" option', $index['name'], 'string'); } @@ -78,7 +84,7 @@ class IndexInput implements Serializable /** * Return the index name. * - * @param string + * @return string */ public function __toString() { diff --git a/cache/stores/mongodb/MongoDB/Model/TypeMapArrayIterator.php b/cache/stores/mongodb/MongoDB/Model/TypeMapArrayIterator.php deleted file mode 100644 index b30a0ec02ef..00000000000 --- a/cache/stores/mongodb/MongoDB/Model/TypeMapArrayIterator.php +++ /dev/null @@ -1,166 +0,0 @@ -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 index 2ddba685852..fc2e9ee40f9 100644 --- a/cache/stores/mongodb/MongoDB/Operation/Aggregate.php +++ b/cache/stores/mongodb/MongoDB/Operation/Aggregate.php @@ -17,20 +17,29 @@ namespace MongoDB\Operation; +use ArrayIterator; use MongoDB\Driver\Command; +use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; 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; +use function current; +use function is_array; +use function is_bool; +use function is_integer; +use function is_object; +use function is_string; +use function MongoDB\create_field_path_type_map; +use function MongoDB\is_last_pipeline_operator_write; +use function MongoDB\server_supports_feature; +use function sprintf; /** * Operation for the aggregate command. @@ -41,14 +50,28 @@ use Traversable; */ class Aggregate implements Executable { + /** @var integer */ private static $wireVersionForCollation = 5; + + /** @var integer */ private static $wireVersionForDocumentLevelValidation = 4; + + /** @var integer */ private static $wireVersionForReadConcern = 4; + + /** @var integer */ private static $wireVersionForWriteConcern = 5; + /** @var string */ private $databaseName; + + /** @var string|null */ private $collectionName; + + /** @var array */ private $pipeline; + + /** @var array */ private $options; /** @@ -63,8 +86,8 @@ class Aggregate implements Executable * * 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. + * circumvent document level validation. This only applies when an $out + * or $merge stage is specified. * * For servers < 3.2, this option is ignored as document level validation * is not available. @@ -87,15 +110,14 @@ class Aggregate implements Executable * * 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. + * * 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. * - * This option is ignored if the $out stage is specified. + * This option is ignored if an $out or $merge stage is specified. * * * session (MongoDB\Driver\Session): Client session. * @@ -111,7 +133,7 @@ class Aggregate implements Executable * mongod/mongos upgrades. * * * writeConcern (MongoDB\Driver\WriteConcern): Write concern. This only - * applies when the $out stage is specified. + * applies when an $out or $merge stage is specified. * * This is not supported for server versions < 3.4 and will result in an * exception at execution time if used. @@ -134,7 +156,7 @@ class Aggregate implements Executable throw new InvalidArgumentException(sprintf('$pipeline is not a list (unexpected index: "%s")', $i)); } - if ( ! is_array($operation) && ! is_object($operation)) { + if (! is_array($operation) && ! is_object($operation)) { throw InvalidArgumentException::invalidType(sprintf('$pipeline[%d]', $i), $operation, 'array or object'); } @@ -146,7 +168,7 @@ class Aggregate implements Executable 'useCursor' => true, ]; - if ( ! is_bool($options['allowDiskUse'])) { + if (! is_bool($options['allowDiskUse'])) { throw InvalidArgumentException::invalidType('"allowDiskUse" option', $options['allowDiskUse'], 'boolean'); } @@ -183,27 +205,27 @@ class Aggregate implements Executable } if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) { - throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern'); + throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], ReadConcern::class); } if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) { - throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference'); + throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class); } if (isset($options['session']) && ! $options['session'] instanceof Session) { - throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session'); + throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); } if (isset($options['typeMap']) && ! is_array($options['typeMap'])) { throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array'); } - if ( ! is_bool($options['useCursor'])) { + 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'); + throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class); } if (isset($options['batchSize']) && ! $options['useCursor']) { @@ -218,7 +240,7 @@ class Aggregate implements Executable unset($options['writeConcern']); } - if ( ! empty($options['explain'])) { + if (! empty($options['explain'])) { $options['useCursor'] = false; } @@ -240,25 +262,35 @@ class Aggregate implements Executable */ public function execute(Server $server) { - if (isset($this->options['collation']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) { + if (isset($this->options['collation']) && ! server_supports_feature($server, self::$wireVersionForCollation)) { throw UnsupportedException::collationNotSupported(); } - if (isset($this->options['readConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) { + if (isset($this->options['readConcern']) && ! server_supports_feature($server, self::$wireVersionForReadConcern)) { throw UnsupportedException::readConcernNotSupported(); } - if (isset($this->options['writeConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForWriteConcern)) { + if (isset($this->options['writeConcern']) && ! server_supports_feature($server, self::$wireVersionForWriteConcern)) { throw UnsupportedException::writeConcernNotSupported(); } + $inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction(); + if ($inTransaction) { + if (isset($this->options['readConcern'])) { + throw UnsupportedException::readConcernNotSupportedInTransaction(); + } + if (isset($this->options['writeConcern'])) { + throw UnsupportedException::writeConcernNotSupportedInTransaction(); + } + } + $hasExplain = ! empty($this->options['explain']); - $hasOutStage = \MongoDB\is_last_pipeline_operator_out($this->pipeline); + $hasWriteStage = is_last_pipeline_operator_write($this->pipeline); - $command = $this->createCommand($server); - $options = $this->createOptions($hasOutStage, $hasExplain); + $command = $this->createCommand($server, $hasWriteStage); + $options = $this->createOptions($hasWriteStage, $hasExplain); - $cursor = ($hasOutStage && ! $hasExplain) + $cursor = $hasWriteStage && ! $hasExplain ? $server->executeReadWriteCommand($this->databaseName, $command, $options) : $server->executeReadCommand($this->databaseName, $command, $options); @@ -270,14 +302,14 @@ class Aggregate implements Executable 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'])) { + $cursor->setTypeMap(create_field_path_type_map($this->options['typeMap'], 'result.$')); } - if (isset($this->options['typeMap'])) { - return new TypeMapArrayIterator($result->result, $this->options['typeMap']); + $result = current($cursor->toArray()); + + if (! isset($result->result) || ! is_array($result->result)) { + throw new UnexpectedValueException('aggregate command did not return a "result" array'); } return new ArrayIterator($result->result); @@ -287,9 +319,10 @@ class Aggregate implements Executable * Create the aggregate command. * * @param Server $server + * @param boolean $hasWriteStage * @return Command */ - private function createCommand(Server $server) + private function createCommand(Server $server, $hasWriteStage) { $cmd = [ 'aggregate' => isset($this->collectionName) ? $this->collectionName : 1, @@ -299,7 +332,9 @@ class Aggregate implements Executable $cmd['allowDiskUse'] = $this->options['allowDiskUse']; - if (isset($this->options['bypassDocumentValidation']) && \MongoDB\server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)) { + if (! empty($this->options['bypassDocumentValidation']) && + server_supports_feature($server, self::$wireVersionForDocumentLevelValidation) + ) { $cmd['bypassDocumentValidation'] = $this->options['bypassDocumentValidation']; } @@ -322,9 +357,12 @@ class Aggregate implements Executable } if ($this->options['useCursor']) { - $cmd['cursor'] = isset($this->options["batchSize"]) + /* Ignore batchSize if pipeline includes an $out or $merge stage, as + * no documents will be returned and sending a batchSize of zero + * could prevent the pipeline from executing at all. */ + $cmd['cursor'] = isset($this->options["batchSize"]) && ! $hasWriteStage ? ['batchSize' => $this->options["batchSize"]] - : new stdClass; + : new stdClass(); } return new Command($cmd, $cmdOptions); @@ -335,10 +373,11 @@ class Aggregate implements Executable * * @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 + * @param boolean $hasWriteStage + * @param boolean $hasExplain * @return array */ - private function createOptions($hasOutStage, $hasExplain) + private function createOptions($hasWriteStage, $hasExplain) { $options = []; @@ -346,7 +385,7 @@ class Aggregate implements Executable $options['readConcern'] = $this->options['readConcern']; } - if ( ! $hasOutStage && isset($this->options['readPreference'])) { + if (! $hasWriteStage && isset($this->options['readPreference'])) { $options['readPreference'] = $this->options['readPreference']; } @@ -354,7 +393,7 @@ class Aggregate implements Executable $options['session'] = $this->options['session']; } - if ($hasOutStage && ! $hasExplain && isset($this->options['writeConcern'])) { + if ($hasWriteStage && ! $hasExplain && isset($this->options['writeConcern'])) { $options['writeConcern'] = $this->options['writeConcern']; } diff --git a/cache/stores/mongodb/MongoDB/Operation/BulkWrite.php b/cache/stores/mongodb/MongoDB/Operation/BulkWrite.php index 82ef082ac43..95bd4bb1c43 100644 --- a/cache/stores/mongodb/MongoDB/Operation/BulkWrite.php +++ b/cache/stores/mongodb/MongoDB/Operation/BulkWrite.php @@ -19,12 +19,23 @@ namespace MongoDB\Operation; use MongoDB\BulkWriteResult; use MongoDB\Driver\BulkWrite as Bulk; +use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; 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 function array_key_exists; +use function count; +use function current; +use function is_array; +use function is_bool; +use function is_object; +use function key; +use function MongoDB\is_first_key_operator; +use function MongoDB\is_pipeline; +use function MongoDB\server_supports_feature; +use function sprintf; /** * Operation for executing multiple write operations. @@ -41,15 +52,31 @@ class BulkWrite implements Executable const UPDATE_MANY = 'updateMany'; const UPDATE_ONE = 'updateOne'; + /** @var integer */ private static $wireVersionForArrayFilters = 6; + + /** @var integer */ private static $wireVersionForCollation = 5; + + /** @var integer */ private static $wireVersionForDocumentLevelValidation = 4; + /** @var string */ private $databaseName; + + /** @var string */ private $collectionName; + + /** @var array[] */ private $operations; + + /** @var array */ private $options; + + /** @var boolean */ private $isArrayFiltersUsed = false; + + /** @var boolean */ private $isCollationUsed = false; /** @@ -132,7 +159,7 @@ class BulkWrite implements Executable throw new InvalidArgumentException(sprintf('$operations is not a list (unexpected index: "%s")', $i)); } - if ( ! is_array($operation)) { + if (! is_array($operation)) { throw InvalidArgumentException::invalidType(sprintf('$operations[%d]', $i), $operation, 'array'); } @@ -143,11 +170,11 @@ class BulkWrite implements Executable $type = key($operation); $args = current($operation); - if ( ! isset($args[0]) && ! array_key_exists(0, $args)) { + 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])) { + if (! is_array($args[0]) && ! is_object($args[0])) { throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][0]', $i, $type), $args[0], 'array or object'); } @@ -157,11 +184,11 @@ class BulkWrite implements Executable case self::DELETE_MANY: case self::DELETE_ONE: - if ( ! isset($args[1])) { + if (! isset($args[1])) { $args[1] = []; } - if ( ! is_array($args[1])) { + if (! is_array($args[1])) { throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][1]', $i, $type), $args[1], 'array'); } @@ -170,7 +197,7 @@ class BulkWrite implements Executable if (isset($args[1]['collation'])) { $this->isCollationUsed = true; - if ( ! is_array($args[1]['collation']) && ! is_object($args[1]['collation'])) { + 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'); } } @@ -180,23 +207,23 @@ class BulkWrite implements Executable break; case self::REPLACE_ONE: - if ( ! isset($args[1]) && ! array_key_exists(1, $args)) { + 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])) { + 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])) { + if (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])) { + if (! isset($args[2])) { $args[2] = []; } - if ( ! is_array($args[2])) { + if (! is_array($args[2])) { throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]', $i, $type), $args[2], 'array'); } @@ -206,12 +233,12 @@ class BulkWrite implements Executable if (isset($args[2]['collation'])) { $this->isCollationUsed = true; - if ( ! is_array($args[2]['collation']) && ! is_object($args[2]['collation'])) { + 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'])) { + if (! is_bool($args[2]['upsert'])) { throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]["upsert"]', $i, $type), $args[2]['upsert'], 'boolean'); } @@ -221,23 +248,23 @@ class BulkWrite implements Executable case self::UPDATE_MANY: case self::UPDATE_ONE: - if ( ! isset($args[1]) && ! array_key_exists(1, $args)) { + 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])) { + 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 (! is_first_key_operator($args[1]) && ! is_pipeline($args[1])) { + throw new InvalidArgumentException(sprintf('First key in $operations[%d]["%s"][1] is neither an update operator nor a pipeline', $i, $type)); } - if ( ! isset($args[2])) { + if (! isset($args[2])) { $args[2] = []; } - if ( ! is_array($args[2])) { + if (! is_array($args[2])) { throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]', $i, $type), $args[2], 'array'); } @@ -247,7 +274,7 @@ class BulkWrite implements Executable if (isset($args[2]['arrayFilters'])) { $this->isArrayFiltersUsed = true; - if ( ! is_array($args[2]['arrayFilters'])) { + if (! is_array($args[2]['arrayFilters'])) { throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]["arrayFilters"]', $i, $type), $args[2]['arrayFilters'], 'array'); } } @@ -255,12 +282,12 @@ class BulkWrite implements Executable if (isset($args[2]['collation'])) { $this->isCollationUsed = true; - if ( ! is_array($args[2]['collation']) && ! is_object($args[2]['collation'])) { + 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'])) { + if (! is_bool($args[2]['upsert'])) { throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]["upsert"]', $i, $type), $args[2]['upsert'], 'boolean'); } @@ -281,16 +308,16 @@ class BulkWrite implements Executable throw InvalidArgumentException::invalidType('"bypassDocumentValidation" option', $options['bypassDocumentValidation'], 'boolean'); } - if ( ! is_bool($options['ordered'])) { + 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'); + throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); } if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) { - throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern'); + throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class); } if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) { @@ -314,17 +341,24 @@ class BulkWrite implements Executable */ public function execute(Server $server) { - if ($this->isArrayFiltersUsed && ! \MongoDB\server_supports_feature($server, self::$wireVersionForArrayFilters)) { + if ($this->isArrayFiltersUsed && ! server_supports_feature($server, self::$wireVersionForArrayFilters)) { throw UnsupportedException::arrayFiltersNotSupported(); } - if ($this->isCollationUsed && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) { + if ($this->isCollationUsed && ! server_supports_feature($server, self::$wireVersionForCollation)) { throw UnsupportedException::collationNotSupported(); } + $inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction(); + if ($inTransaction && isset($this->options['writeConcern'])) { + throw UnsupportedException::writeConcernNotSupportedInTransaction(); + } + $options = ['ordered' => $this->options['ordered']]; - if (isset($this->options['bypassDocumentValidation']) && \MongoDB\server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)) { + if (! empty($this->options['bypassDocumentValidation']) && + server_supports_feature($server, self::$wireVersionForDocumentLevelValidation) + ) { $options['bypassDocumentValidation'] = $this->options['bypassDocumentValidation']; } diff --git a/cache/stores/mongodb/MongoDB/Operation/Count.php b/cache/stores/mongodb/MongoDB/Operation/Count.php index 771e276248f..2e00ae555f3 100644 --- a/cache/stores/mongodb/MongoDB/Operation/Count.php +++ b/cache/stores/mongodb/MongoDB/Operation/Count.php @@ -18,14 +18,21 @@ namespace MongoDB\Operation; use MongoDB\Driver\Command; +use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; 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; +use function current; +use function is_array; +use function is_float; +use function is_integer; +use function is_object; +use function is_string; +use function MongoDB\server_supports_feature; /** * Operation for the count command. @@ -36,12 +43,22 @@ use MongoDB\Exception\UnsupportedException; */ class Count implements Executable, Explainable { + /** @var integer */ private static $wireVersionForCollation = 5; + + /** @var integer */ private static $wireVersionForReadConcern = 4; + /** @var string */ private $databaseName; + + /** @var string */ private $collectionName; + + /** @var array|object */ private $filter; + + /** @var array */ private $options; /** @@ -85,7 +102,7 @@ class Count implements Executable, Explainable */ public function __construct($databaseName, $collectionName, $filter = [], array $options = []) { - if ( ! is_array($filter) && ! is_object($filter)) { + if (! is_array($filter) && ! is_object($filter)) { throw InvalidArgumentException::invalidType('$filter', $filter, 'array or object'); } @@ -106,15 +123,15 @@ class Count implements Executable, Explainable } if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) { - throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern'); + throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], ReadConcern::class); } if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) { - throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference'); + throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class); } if (isset($options['session']) && ! $options['session'] instanceof Session) { - throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session'); + throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); } if (isset($options['skip']) && ! is_integer($options['skip'])) { @@ -143,19 +160,24 @@ class Count implements Executable, Explainable */ public function execute(Server $server) { - if (isset($this->options['collation']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) { + if (isset($this->options['collation']) && ! server_supports_feature($server, self::$wireVersionForCollation)) { throw UnsupportedException::collationNotSupported(); } - if (isset($this->options['readConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) { + if (isset($this->options['readConcern']) && ! server_supports_feature($server, self::$wireVersionForReadConcern)) { throw UnsupportedException::readConcernNotSupported(); } + $inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction(); + if ($inTransaction && isset($this->options['readConcern'])) { + throw UnsupportedException::readConcernNotSupportedInTransaction(); + } + $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))) { + if (! isset($result->n) || ! (is_integer($result->n) || is_float($result->n))) { throw new UnexpectedValueException('count command did not return a numeric "n" value'); } @@ -176,7 +198,7 @@ class Count implements Executable, Explainable { $cmd = ['count' => $this->collectionName]; - if ( ! empty($this->filter)) { + if (! empty($this->filter)) { $cmd['query'] = (object) $this->filter; } diff --git a/cache/stores/mongodb/MongoDB/Operation/CountDocuments.php b/cache/stores/mongodb/MongoDB/Operation/CountDocuments.php index d79bd7e1b17..77499e5b73e 100644 --- a/cache/stores/mongodb/MongoDB/Operation/CountDocuments.php +++ b/cache/stores/mongodb/MongoDB/Operation/CountDocuments.php @@ -17,15 +17,18 @@ 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\Driver\Server; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Exception\UnexpectedValueException; use MongoDB\Exception\UnsupportedException; +use function array_intersect_key; +use function count; +use function current; +use function is_array; +use function is_float; +use function is_integer; +use function is_object; /** * Operation for obtaining an exact count of documents in a collection @@ -36,13 +39,23 @@ use MongoDB\Exception\UnsupportedException; */ class CountDocuments implements Executable { - private static $wireVersionForCollation = 5; - private static $wireVersionForReadConcern = 4; - + /** @var string */ private $databaseName; + + /** @var string */ private $collectionName; + + /** @var array|object */ private $filter; - private $options; + + /** @var array */ + private $aggregateOptions; + + /** @var array */ + private $countOptions; + + /** @var Aggregate */ + private $aggregate; /** * Constructs an aggregate command for counting documents @@ -85,50 +98,26 @@ class CountDocuments implements Executable */ public function __construct($databaseName, $collectionName, $filter, array $options = []) { - if ( ! is_array($filter) && ! is_object($filter)) { + 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; + + $this->aggregateOptions = array_intersect_key($options, ['collation' => 1, 'hint' => 1, 'maxTimeMS' => 1, 'readConcern' => 1, 'readPreference' => 1, 'session' => 1]); + $this->countOptions = array_intersect_key($options, ['limit' => 1, 'skip' => 1]); + + $this->aggregate = $this->createAggregate(); } /** @@ -143,15 +132,7 @@ class CountDocuments implements Executable */ 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()); + $cursor = $this->aggregate->execute($server); $allResults = $cursor->toArray(); /* If there are no documents to count, the aggregation pipeline has no items to group, and @@ -161,7 +142,7 @@ class CountDocuments implements Executable } $result = current($allResults); - if ( ! isset($result->n) || ! (is_integer($result->n) || is_float($result->n))) { + if (! isset($result->n) || ! (is_integer($result->n) || is_float($result->n))) { throw new UnexpectedValueException('count command did not return a numeric "n" value'); } @@ -169,69 +150,24 @@ class CountDocuments implements Executable } /** - * Create the count command document. - * - * @return array + * @return Aggregate */ - private function createCommandDocument() + private function createAggregate() { $pipeline = [ - ['$match' => (object) $this->filter] + ['$match' => (object) $this->filter], ]; - if (isset($this->options['skip'])) { - $pipeline[] = ['$skip' => $this->options['skip']]; + if (isset($this->countOptions['skip'])) { + $pipeline[] = ['$skip' => $this->countOptions['skip']]; } - if (isset($this->options['limit'])) { - $pipeline[] = ['$limit' => $this->options['limit']]; + if (isset($this->countOptions['limit'])) { + $pipeline[] = ['$limit' => $this->countOptions['limit']]; } - $pipeline[] = ['$group' => ['_id' => null, 'n' => ['$sum' => 1]]]; + $pipeline[] = ['$group' => ['_id' => 1, '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; + return new Aggregate($this->databaseName, $this->collectionName, $pipeline, $this->aggregateOptions); } } diff --git a/cache/stores/mongodb/MongoDB/Operation/CreateCollection.php b/cache/stores/mongodb/MongoDB/Operation/CreateCollection.php index b5b90867ead..98fac51d603 100644 --- a/cache/stores/mongodb/MongoDB/Operation/CreateCollection.php +++ b/cache/stores/mongodb/MongoDB/Operation/CreateCollection.php @@ -18,12 +18,21 @@ namespace MongoDB\Operation; use MongoDB\Driver\Command; +use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; 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 function current; +use function is_array; +use function is_bool; +use function is_integer; +use function is_object; +use function is_string; +use function MongoDB\server_supports_feature; +use function trigger_error; +use const E_USER_DEPRECATED; /** * Operation for the create command. @@ -37,11 +46,19 @@ class CreateCollection implements Executable const USE_POWER_OF_2_SIZES = 1; const NO_PADDING = 2; + /** @var integer */ private static $wireVersionForCollation = 5; + + /** @var integer */ private static $wireVersionForWriteConcern = 5; + /** @var string */ private $databaseName; + + /** @var string */ private $collectionName; + + /** @var array */ private $options = []; /** @@ -139,7 +156,7 @@ class CreateCollection implements Executable } if (isset($options['session']) && ! $options['session'] instanceof Session) { - throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session'); + throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); } if (isset($options['size']) && ! is_integer($options['size'])) { @@ -167,7 +184,7 @@ class CreateCollection implements Executable } if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) { - throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern'); + throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class); } if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) { @@ -194,11 +211,11 @@ class CreateCollection implements Executable */ public function execute(Server $server) { - if (isset($this->options['collation']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) { + if (isset($this->options['collation']) && ! server_supports_feature($server, self::$wireVersionForCollation)) { throw UnsupportedException::collationNotSupported(); } - if (isset($this->options['writeConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForWriteConcern)) { + if (isset($this->options['writeConcern']) && ! server_supports_feature($server, self::$wireVersionForWriteConcern)) { throw UnsupportedException::writeConcernNotSupported(); } diff --git a/cache/stores/mongodb/MongoDB/Operation/CreateIndexes.php b/cache/stores/mongodb/MongoDB/Operation/CreateIndexes.php index 07050f54f8f..bf4dea3d6b1 100644 --- a/cache/stores/mongodb/MongoDB/Operation/CreateIndexes.php +++ b/cache/stores/mongodb/MongoDB/Operation/CreateIndexes.php @@ -17,15 +17,19 @@ namespace MongoDB\Operation; -use MongoDB\Driver\BulkWrite as Bulk; use MongoDB\Driver\Command; +use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; 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; +use function array_map; +use function is_array; +use function is_integer; +use function MongoDB\server_supports_feature; +use function sprintf; /** * Operation for the createIndexes command. @@ -37,13 +41,25 @@ use MongoDB\Model\IndexInput; */ class CreateIndexes implements Executable { + /** @var integer */ private static $wireVersionForCollation = 5; + + /** @var integer */ private static $wireVersionForWriteConcern = 5; + /** @var string */ private $databaseName; + + /** @var string */ private $collectionName; + + /** @var array */ private $indexes = []; + + /** @var boolean */ private $isCollationUsed = false; + + /** @var array */ private $options = []; /** @@ -82,11 +98,11 @@ class CreateIndexes implements Executable throw new InvalidArgumentException(sprintf('$indexes is not a list (unexpected index: "%s")', $i)); } - if ( ! is_array($index)) { + if (! is_array($index)) { throw InvalidArgumentException::invalidType(sprintf('$index[%d]', $i), $index, 'array'); } - if ( ! isset($index['ns'])) { + if (! isset($index['ns'])) { $index['ns'] = $databaseName . '.' . $collectionName; } @@ -99,16 +115,16 @@ class CreateIndexes implements Executable $expectedIndex += 1; } - if (isset($options['maxTimeMS']) && !is_integer($options['maxTimeMS'])) { + 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'); + throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); } if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) { - throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern'); + throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class); } if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) { @@ -131,17 +147,24 @@ class CreateIndexes implements Executable */ public function execute(Server $server) { - if ($this->isCollationUsed && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) { + if ($this->isCollationUsed && ! server_supports_feature($server, self::$wireVersionForCollation)) { throw UnsupportedException::collationNotSupported(); } - if (isset($this->options['writeConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForWriteConcern)) { + if (isset($this->options['writeConcern']) && ! server_supports_feature($server, self::$wireVersionForWriteConcern)) { throw UnsupportedException::writeConcernNotSupported(); } + $inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction(); + if ($inTransaction && isset($this->options['writeConcern'])) { + throw UnsupportedException::writeConcernNotSupportedInTransaction(); + } + $this->executeCommand($server); - return array_map(function(IndexInput $index) { return (string) $index; }, $this->indexes); + return array_map(function (IndexInput $index) { + return (string) $index; + }, $this->indexes); } /** diff --git a/cache/stores/mongodb/MongoDB/Operation/DatabaseCommand.php b/cache/stores/mongodb/MongoDB/Operation/DatabaseCommand.php index aa0a8270ccd..af0f7624898 100644 --- a/cache/stores/mongodb/MongoDB/Operation/DatabaseCommand.php +++ b/cache/stores/mongodb/MongoDB/Operation/DatabaseCommand.php @@ -23,6 +23,8 @@ use MongoDB\Driver\ReadPreference; use MongoDB\Driver\Server; use MongoDB\Driver\Session; use MongoDB\Exception\InvalidArgumentException; +use function is_array; +use function is_object; /** * Operation for executing a database command. @@ -32,8 +34,13 @@ use MongoDB\Exception\InvalidArgumentException; */ class DatabaseCommand implements Executable { + /** @var string */ private $databaseName; + + /** @var array|Command|object */ private $command; + + /** @var array */ private $options; /** @@ -54,23 +61,23 @@ class DatabaseCommand implements Executable * * 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 + * @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)) { + 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'); + throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class); } if (isset($options['session']) && ! $options['session'] instanceof Session) { - throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session'); + throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); } if (isset($options['typeMap']) && ! is_array($options['typeMap'])) { @@ -78,7 +85,7 @@ class DatabaseCommand implements Executable } $this->databaseName = (string) $databaseName; - $this->command = ($command instanceof Command) ? $command : new Command($command); + $this->command = $command instanceof Command ? $command : new Command($command); $this->options = $options; } diff --git a/cache/stores/mongodb/MongoDB/Operation/Delete.php b/cache/stores/mongodb/MongoDB/Operation/Delete.php index 496c3dda31b..181351b0e97 100644 --- a/cache/stores/mongodb/MongoDB/Operation/Delete.php +++ b/cache/stores/mongodb/MongoDB/Operation/Delete.php @@ -19,12 +19,15 @@ namespace MongoDB\Operation; use MongoDB\DeleteResult; use MongoDB\Driver\BulkWrite as Bulk; +use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; 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 function is_array; +use function is_object; +use function MongoDB\server_supports_feature; /** * Operation for the delete command. @@ -37,12 +40,22 @@ use MongoDB\Exception\UnsupportedException; */ class Delete implements Executable, Explainable { + /** @var integer */ private static $wireVersionForCollation = 5; + /** @var string */ private $databaseName; + + /** @var string */ private $collectionName; + + /** @var array|object */ private $filter; + + /** @var integer */ private $limit; + + /** @var array */ private $options; /** @@ -72,7 +85,7 @@ class Delete implements Executable, Explainable */ public function __construct($databaseName, $collectionName, $filter, $limit, array $options = []) { - if ( ! is_array($filter) && ! is_object($filter)) { + if (! is_array($filter) && ! is_object($filter)) { throw InvalidArgumentException::invalidType('$filter', $filter, 'array or object'); } @@ -85,11 +98,11 @@ class Delete implements Executable, Explainable } if (isset($options['session']) && ! $options['session'] instanceof Session) { - throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session'); + throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); } if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) { - throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern'); + throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class); } if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) { @@ -113,10 +126,15 @@ class Delete implements Executable, Explainable */ public function execute(Server $server) { - if (isset($this->options['collation']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) { + if (isset($this->options['collation']) && ! server_supports_feature($server, self::$wireVersionForCollation)) { throw UnsupportedException::collationNotSupported(); } + $inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction(); + if ($inTransaction && isset($this->options['writeConcern'])) { + throw UnsupportedException::writeConcernNotSupportedInTransaction(); + } + $bulk = new Bulk(); $bulk->delete($this->filter, $this->createDeleteOptions()); diff --git a/cache/stores/mongodb/MongoDB/Operation/DeleteMany.php b/cache/stores/mongodb/MongoDB/Operation/DeleteMany.php index 5b32d0315e6..96c8031e9cc 100644 --- a/cache/stores/mongodb/MongoDB/Operation/DeleteMany.php +++ b/cache/stores/mongodb/MongoDB/Operation/DeleteMany.php @@ -18,8 +18,8 @@ namespace MongoDB\Operation; use MongoDB\DeleteResult; -use MongoDB\Driver\Server; use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; +use MongoDB\Driver\Server; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Exception\UnsupportedException; @@ -32,6 +32,7 @@ use MongoDB\Exception\UnsupportedException; */ class DeleteMany implements Executable, Explainable { + /** @var Delete */ private $delete; /** diff --git a/cache/stores/mongodb/MongoDB/Operation/DeleteOne.php b/cache/stores/mongodb/MongoDB/Operation/DeleteOne.php index e7d47ded43a..3377a66d8a6 100644 --- a/cache/stores/mongodb/MongoDB/Operation/DeleteOne.php +++ b/cache/stores/mongodb/MongoDB/Operation/DeleteOne.php @@ -18,8 +18,8 @@ namespace MongoDB\Operation; use MongoDB\DeleteResult; -use MongoDB\Driver\Server; use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; +use MongoDB\Driver\Server; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Exception\UnsupportedException; @@ -32,6 +32,7 @@ use MongoDB\Exception\UnsupportedException; */ class DeleteOne implements Executable, Explainable { + /** @var Delete */ private $delete; /** diff --git a/cache/stores/mongodb/MongoDB/Operation/Distinct.php b/cache/stores/mongodb/MongoDB/Operation/Distinct.php index 8e7f39efd42..f8e169ca71c 100644 --- a/cache/stores/mongodb/MongoDB/Operation/Distinct.php +++ b/cache/stores/mongodb/MongoDB/Operation/Distinct.php @@ -18,14 +18,20 @@ namespace MongoDB\Operation; use MongoDB\Driver\Command; +use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; 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; +use function current; +use function is_array; +use function is_integer; +use function is_object; +use function MongoDB\create_field_path_type_map; +use function MongoDB\server_supports_feature; /** * Operation for the distinct command. @@ -36,13 +42,25 @@ use MongoDB\Exception\UnsupportedException; */ class Distinct implements Executable, Explainable { + /** @var integer */ private static $wireVersionForCollation = 5; + + /** @var integer */ private static $wireVersionForReadConcern = 4; + /** @var string */ private $databaseName; + + /** @var string */ private $collectionName; + + /** @var string */ private $fieldName; + + /** @var array|object */ private $filter; + + /** @var array */ private $options; /** @@ -69,6 +87,8 @@ class Distinct implements Executable, Explainable * * Sessions are not supported for server versions < 3.6. * + * * typeMap (array): Type map for BSON deserialization. + * * @param string $databaseName Database name * @param string $collectionName Collection name * @param string $fieldName Field for which to return distinct values @@ -78,7 +98,7 @@ class Distinct implements Executable, Explainable */ public function __construct($databaseName, $collectionName, $fieldName, $filter = [], array $options = []) { - if ( ! is_array($filter) && ! is_object($filter)) { + if (! is_array($filter) && ! is_object($filter)) { throw InvalidArgumentException::invalidType('$filter', $filter, 'array or object'); } @@ -91,15 +111,19 @@ class Distinct implements Executable, Explainable } if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) { - throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern'); + throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], ReadConcern::class); } if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) { - throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference'); + throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class); } if (isset($options['session']) && ! $options['session'] instanceof Session) { - throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session'); + throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); + } + + if (isset($options['typeMap']) && ! is_array($options['typeMap'])) { + throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array'); } if (isset($options['readConcern']) && $options['readConcern']->isDefault()) { @@ -125,18 +149,28 @@ class Distinct implements Executable, Explainable */ public function execute(Server $server) { - if (isset($this->options['collation']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) { + if (isset($this->options['collation']) && ! server_supports_feature($server, self::$wireVersionForCollation)) { throw UnsupportedException::collationNotSupported(); } - if (isset($this->options['readConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) { + if (isset($this->options['readConcern']) && ! server_supports_feature($server, self::$wireVersionForReadConcern)) { throw UnsupportedException::readConcernNotSupported(); } + $inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction(); + if ($inTransaction && isset($this->options['readConcern'])) { + throw UnsupportedException::readConcernNotSupportedInTransaction(); + } + $cursor = $server->executeReadCommand($this->databaseName, new Command($this->createCommandDocument()), $this->createOptions()); + + if (isset($this->options['typeMap'])) { + $cursor->setTypeMap(create_field_path_type_map($this->options['typeMap'], 'values.$')); + } + $result = current($cursor->toArray()); - if ( ! isset($result->values) || ! is_array($result->values)) { + if (! isset($result->values) || ! is_array($result->values)) { throw new UnexpectedValueException('distinct command did not return a "values" array'); } @@ -160,7 +194,7 @@ class Distinct implements Executable, Explainable 'key' => $this->fieldName, ]; - if ( ! empty($this->filter)) { + if (! empty($this->filter)) { $cmd['query'] = (object) $this->filter; } diff --git a/cache/stores/mongodb/MongoDB/Operation/DropCollection.php b/cache/stores/mongodb/MongoDB/Operation/DropCollection.php index 1ee66fa926c..fb328cba10f 100644 --- a/cache/stores/mongodb/MongoDB/Operation/DropCollection.php +++ b/cache/stores/mongodb/MongoDB/Operation/DropCollection.php @@ -18,12 +18,15 @@ namespace MongoDB\Operation; use MongoDB\Driver\Command; +use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; 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 function current; +use function is_array; +use function MongoDB\server_supports_feature; /** * Operation for the drop command. @@ -35,11 +38,19 @@ use MongoDB\Exception\UnsupportedException; */ class DropCollection implements Executable { + /** @var string */ private static $errorMessageNamespaceNotFound = 'ns not found'; + + /** @var integer */ private static $wireVersionForWriteConcern = 5; + /** @var string */ private $databaseName; + + /** @var string */ private $collectionName; + + /** @var array */ private $options; /** @@ -67,7 +78,7 @@ class DropCollection implements Executable 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'); + throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); } if (isset($options['typeMap']) && ! is_array($options['typeMap'])) { @@ -75,7 +86,7 @@ class DropCollection implements Executable } if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) { - throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern'); + throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class); } if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) { @@ -98,10 +109,15 @@ class DropCollection implements Executable */ public function execute(Server $server) { - if (isset($this->options['writeConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForWriteConcern)) { + if (isset($this->options['writeConcern']) && ! server_supports_feature($server, self::$wireVersionForWriteConcern)) { throw UnsupportedException::writeConcernNotSupported(); } + $inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction(); + if ($inTransaction && isset($this->options['writeConcern'])) { + throw UnsupportedException::writeConcernNotSupportedInTransaction(); + } + $command = new Command(['drop' => $this->collectionName]); try { diff --git a/cache/stores/mongodb/MongoDB/Operation/DropDatabase.php b/cache/stores/mongodb/MongoDB/Operation/DropDatabase.php index ac1e90319a3..5a4335a7d8d 100644 --- a/cache/stores/mongodb/MongoDB/Operation/DropDatabase.php +++ b/cache/stores/mongodb/MongoDB/Operation/DropDatabase.php @@ -18,12 +18,15 @@ namespace MongoDB\Operation; use MongoDB\Driver\Command; +use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; 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 function current; +use function is_array; +use function MongoDB\server_supports_feature; /** * Operation for the dropDatabase command. @@ -35,9 +38,13 @@ use MongoDB\Exception\UnsupportedException; */ class DropDatabase implements Executable { + /** @var integer */ private static $wireVersionForWriteConcern = 5; + /** @var string */ private $databaseName; + + /** @var array */ private $options; /** @@ -64,7 +71,7 @@ class DropDatabase implements Executable public function __construct($databaseName, array $options = []) { if (isset($options['session']) && ! $options['session'] instanceof Session) { - throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session'); + throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); } if (isset($options['typeMap']) && ! is_array($options['typeMap'])) { @@ -72,7 +79,7 @@ class DropDatabase implements Executable } if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) { - throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern'); + throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class); } if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) { @@ -94,7 +101,7 @@ class DropDatabase implements Executable */ public function execute(Server $server) { - if (isset($this->options['writeConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForWriteConcern)) { + if (isset($this->options['writeConcern']) && ! server_supports_feature($server, self::$wireVersionForWriteConcern)) { throw UnsupportedException::writeConcernNotSupported(); } diff --git a/cache/stores/mongodb/MongoDB/Operation/DropIndexes.php b/cache/stores/mongodb/MongoDB/Operation/DropIndexes.php index 9dae54e911c..322e3b10f9a 100644 --- a/cache/stores/mongodb/MongoDB/Operation/DropIndexes.php +++ b/cache/stores/mongodb/MongoDB/Operation/DropIndexes.php @@ -18,12 +18,16 @@ namespace MongoDB\Operation; use MongoDB\Driver\Command; +use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; 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 function current; +use function is_array; +use function is_integer; +use function MongoDB\server_supports_feature; /** * Operation for the dropIndexes command. @@ -34,11 +38,19 @@ use MongoDB\Exception\UnsupportedException; */ class DropIndexes implements Executable { + /** @var integer */ private static $wireVersionForWriteConcern = 5; + /** @var string */ private $databaseName; + + /** @var string */ private $collectionName; + + /** @var string */ private $indexName; + + /** @var array */ private $options; /** @@ -80,7 +92,7 @@ class DropIndexes implements Executable } if (isset($options['session']) && ! $options['session'] instanceof Session) { - throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session'); + throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); } if (isset($options['typeMap']) && ! is_array($options['typeMap'])) { @@ -88,7 +100,7 @@ class DropIndexes implements Executable } if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) { - throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern'); + throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class); } if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) { @@ -112,10 +124,15 @@ class DropIndexes implements Executable */ public function execute(Server $server) { - if (isset($this->options['writeConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForWriteConcern)) { + if (isset($this->options['writeConcern']) && ! server_supports_feature($server, self::$wireVersionForWriteConcern)) { throw UnsupportedException::writeConcernNotSupported(); } + $inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction(); + if ($inTransaction && isset($this->options['writeConcern'])) { + throw UnsupportedException::writeConcernNotSupportedInTransaction(); + } + $cursor = $server->executeWriteCommand($this->databaseName, $this->createCommand(), $this->createOptions()); if (isset($this->options['typeMap'])) { diff --git a/cache/stores/mongodb/MongoDB/Operation/EstimatedDocumentCount.php b/cache/stores/mongodb/MongoDB/Operation/EstimatedDocumentCount.php index 621608d1900..2e6dd5ca132 100644 --- a/cache/stores/mongodb/MongoDB/Operation/EstimatedDocumentCount.php +++ b/cache/stores/mongodb/MongoDB/Operation/EstimatedDocumentCount.php @@ -17,15 +17,12 @@ 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\Driver\Server; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Exception\UnexpectedValueException; use MongoDB\Exception\UnsupportedException; +use function array_intersect_key; /** * Operation for obtaining an estimated count of documents in a collection @@ -36,13 +33,18 @@ use MongoDB\Exception\UnsupportedException; */ class EstimatedDocumentCount implements Executable, Explainable { - private static $wireVersionForCollation = 5; - private static $wireVersionForReadConcern = 4; - + /** @var string */ private $databaseName; + + /** @var string */ private $collectionName; + + /** @var array */ private $options; + /** @var Count */ + private $count; + /** * Constructs a count command. * @@ -62,36 +64,18 @@ class EstimatedDocumentCount implements Executable, Explainable * * Sessions are not supported for server versions < 3.6. * - * @param string $databaseName Database name - * @param string $collectionName Collection name - * @param array $options Command options + * @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['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->options = $options; + $this->options = array_intersect_key($options, ['maxTimeMS' => 1, 'readConcern' => 1, 'readPreference' => 1, 'session' => 1]); + + $this->count = $this->createCount(); } /** @@ -106,68 +90,19 @@ class EstimatedDocumentCount implements Executable, Explainable */ 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; + return $this->count->execute($server); } public function getCommandDocument(Server $server) { - return $this->createCommandDocument(); + return $this->count->getCommandDocument($server); } /** - * Create the count command document. - * - * @return array + * @return Count */ - private function createCommandDocument() + private function createCount() { - $cmd = ['count' => $this->collectionName]; - - 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; + return new Count($this->databaseName, $this->collectionName, [], $this->options); } } diff --git a/cache/stores/mongodb/MongoDB/Operation/Explain.php b/cache/stores/mongodb/MongoDB/Operation/Explain.php index ceb0711e0b7..28a23390748 100644 --- a/cache/stores/mongodb/MongoDB/Operation/Explain.php +++ b/cache/stores/mongodb/MongoDB/Operation/Explain.php @@ -21,9 +21,12 @@ use MongoDB\Driver\Command; use MongoDB\Driver\ReadPreference; use MongoDB\Driver\Server; use MongoDB\Driver\Session; -use MongoDB\Exception\UnsupportedException; use MongoDB\Exception\InvalidArgumentException; -use MongoDB\Model\BSONDocument; +use MongoDB\Exception\UnsupportedException; +use function current; +use function is_array; +use function is_string; +use function MongoDB\server_supports_feature; /** * Operation for the explain command. @@ -38,11 +41,19 @@ class Explain implements Executable const VERBOSITY_EXEC_STATS = 'executionStats'; const VERBOSITY_QUERY = 'queryPlanner'; + /** @var integer */ private static $wireVersionForDistinct = 4; + + /** @var integer */ private static $wireVersionForFindAndModify = 4; + /** @var string */ private $databaseName; + + /** @var Explainable */ private $explainable; + + /** @var array */ private $options; /** @@ -59,19 +70,19 @@ class Explain implements Executable * * * verbosity (string): The mode in which the explain command will be run. * - * @param string $databaseName Database name + * @param string $databaseName Database name * @param Explainable $explainable Operation to explain - * @param array $options Command options + * @param array $options Command options * @throws InvalidArgumentException for parameter/option parsing errors */ public function __construct($databaseName, Explainable $explainable, array $options = []) { if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) { - throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference'); + throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class); } if (isset($options['session']) && ! $options['session'] instanceof Session) { - throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session'); + throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); } if (isset($options['typeMap']) && ! is_array($options['typeMap'])) { @@ -89,11 +100,11 @@ class Explain implements Executable public function execute(Server $server) { - if ($this->explainable instanceof Distinct && ! \MongoDB\server_supports_feature($server, self::$wireVersionForDistinct)) { + if ($this->explainable instanceof Distinct && ! server_supports_feature($server, self::$wireVersionForDistinct)) { throw UnsupportedException::explainNotSupported(); } - if ($this->isFindAndModify($this->explainable) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForFindAndModify)) { + if ($this->isFindAndModify($this->explainable) && ! server_supports_feature($server, self::$wireVersionForFindAndModify)) { throw UnsupportedException::explainNotSupported(); } @@ -138,6 +149,7 @@ class Explain implements Executable if ($explainable instanceof FindAndModify || $explainable instanceof FindOneAndDelete || $explainable instanceof FindOneAndReplace || $explainable instanceof FindOneAndUpdate) { return true; } + return false; } } diff --git a/cache/stores/mongodb/MongoDB/Operation/Explainable.php b/cache/stores/mongodb/MongoDB/Operation/Explainable.php index ee7cbd26344..ed4e99ab4e8 100644 --- a/cache/stores/mongodb/MongoDB/Operation/Explainable.php +++ b/cache/stores/mongodb/MongoDB/Operation/Explainable.php @@ -27,5 +27,5 @@ use MongoDB\Driver\Server; */ interface Explainable extends Executable { - function getCommandDocument(Server $server); + public function getCommandDocument(Server $server); } diff --git a/cache/stores/mongodb/MongoDB/Operation/Find.php b/cache/stores/mongodb/MongoDB/Operation/Find.php index d7629d429d6..f6e11aba86a 100644 --- a/cache/stores/mongodb/MongoDB/Operation/Find.php +++ b/cache/stores/mongodb/MongoDB/Operation/Find.php @@ -18,15 +18,23 @@ namespace MongoDB\Operation; use MongoDB\Driver\Cursor; +use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; use MongoDB\Driver\Query; 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\UnsupportedException; -use MongoDB\Model\BSONDocument; +use function is_array; +use function is_bool; +use function is_integer; +use function is_object; +use function is_string; +use function MongoDB\server_supports_feature; +use function trigger_error; +use const E_USER_DEPRECATED; + /** * Operation for the find command. * @@ -41,12 +49,22 @@ class Find implements Executable, Explainable const TAILABLE = 2; const TAILABLE_AWAIT = 3; + /** @var integer */ private static $wireVersionForCollation = 5; + + /** @var integer */ private static $wireVersionForReadConcern = 4; + /** @var string */ private $databaseName; + + /** @var string */ private $collectionName; + + /** @var array|object */ private $filter; + + /** @var array */ private $options; /** @@ -147,7 +165,7 @@ class Find implements Executable, Explainable */ public function __construct($databaseName, $collectionName, $filter, array $options = []) { - if ( ! is_array($filter) && ! is_object($filter)) { + if (! is_array($filter) && ! is_object($filter)) { throw InvalidArgumentException::invalidType('$filter', $filter, 'array or object'); } @@ -168,7 +186,7 @@ class Find implements Executable, Explainable } if (isset($options['cursorType'])) { - if ( ! is_integer($options['cursorType'])) { + if (! is_integer($options['cursorType'])) { throw InvalidArgumentException::invalidType('"cursorType" option', $options['cursorType'], 'integer'); } @@ -224,11 +242,11 @@ class Find implements Executable, Explainable } if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) { - throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern'); + throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], ReadConcern::class); } if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) { - throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference'); + throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class); } if (isset($options['returnKey']) && ! is_bool($options['returnKey'])) { @@ -236,7 +254,7 @@ class Find implements Executable, Explainable } if (isset($options['session']) && ! $options['session'] instanceof Session) { - throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session'); + throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); } if (isset($options['showRecordId']) && ! is_bool($options['showRecordId'])) { @@ -288,14 +306,19 @@ class Find implements Executable, Explainable */ public function execute(Server $server) { - if (isset($this->options['collation']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) { + if (isset($this->options['collation']) && ! server_supports_feature($server, self::$wireVersionForCollation)) { throw UnsupportedException::collationNotSupported(); } - if (isset($this->options['readConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) { + if (isset($this->options['readConcern']) && ! server_supports_feature($server, self::$wireVersionForReadConcern)) { throw UnsupportedException::readConcernNotSupported(); } + $inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction(); + if ($inTransaction && isset($this->options['readConcern'])) { + throw UnsupportedException::readConcernNotSupportedInTransaction(); + } + $cursor = $server->executeQuery($this->databaseName . '.' . $this->collectionName, new Query($this->filter, $this->createQueryOptions()), $this->createExecuteOptions()); if (isset($this->options['typeMap'])) { @@ -341,7 +364,7 @@ class Find implements Executable, Explainable ]; foreach ($modifierFallback as $modifier) { - if ( ! isset($options[$modifier[0]]) && isset($options['modifiers'][$modifier[1]])) { + if (! isset($options[$modifier[0]]) && isset($options['modifiers'][$modifier[1]])) { $options[$modifier[0]] = $options['modifiers'][$modifier[1]]; } } @@ -407,7 +430,7 @@ class Find implements Executable, Explainable $modifiers = empty($this->options['modifiers']) ? [] : (array) $this->options['modifiers']; - if ( ! empty($modifiers)) { + if (! empty($modifiers)) { $options['modifiers'] = $modifiers; } diff --git a/cache/stores/mongodb/MongoDB/Operation/FindAndModify.php b/cache/stores/mongodb/MongoDB/Operation/FindAndModify.php index b00bd1e58c7..acce4366b03 100644 --- a/cache/stores/mongodb/MongoDB/Operation/FindAndModify.php +++ b/cache/stores/mongodb/MongoDB/Operation/FindAndModify.php @@ -18,13 +18,21 @@ namespace MongoDB\Operation; use MongoDB\Driver\Command; +use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; 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 function current; +use function is_array; +use function is_bool; +use function is_integer; +use function is_object; +use function MongoDB\create_field_path_type_map; +use function MongoDB\is_pipeline; +use function MongoDB\server_supports_feature; /** * Operation for the findAndModify command. @@ -37,13 +45,25 @@ use MongoDB\Exception\UnsupportedException; */ class FindAndModify implements Executable, Explainable { + /** @var integer */ private static $wireVersionForArrayFilters = 6; + + /** @var integer */ private static $wireVersionForCollation = 5; + + /** @var integer */ private static $wireVersionForDocumentLevelValidation = 4; + + /** @var integer */ private static $wireVersionForWriteConcern = 4; + /** @var string */ private $databaseName; + + /** @var string */ private $collectionName; + + /** @var array */ private $options; /** @@ -137,7 +157,7 @@ class FindAndModify implements Executable, Explainable throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer'); } - if ( ! is_bool($options['new'])) { + if (! is_bool($options['new'])) { throw InvalidArgumentException::invalidType('"new" option', $options['new'], 'boolean'); } @@ -145,12 +165,12 @@ class FindAndModify implements Executable, Explainable throw InvalidArgumentException::invalidType('"query" option', $options['query'], 'array or object'); } - if ( ! is_bool($options['remove'])) { + if (! is_bool($options['remove'])) { throw InvalidArgumentException::invalidType('"remove" option', $options['remove'], 'boolean'); } if (isset($options['session']) && ! $options['session'] instanceof Session) { - throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session'); + throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); } if (isset($options['sort']) && ! is_array($options['sort']) && ! is_object($options['sort'])) { @@ -166,14 +186,14 @@ class FindAndModify implements Executable, Explainable } if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) { - throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern'); + throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class); } - if ( ! is_bool($options['upsert'])) { + if (! is_bool($options['upsert'])) { throw InvalidArgumentException::invalidType('"upsert" option', $options['upsert'], 'boolean'); } - if ( ! (isset($options['update']) xor $options['remove'])) { + if (! (isset($options['update']) xor $options['remove'])) { throw new InvalidArgumentException('The "remove" option must be true or an "update" document must be specified, but not both'); } @@ -198,34 +218,32 @@ class FindAndModify implements Executable, Explainable */ public function execute(Server $server) { - if (isset($this->options['arrayFilters']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForArrayFilters)) { + if (isset($this->options['arrayFilters']) && ! server_supports_feature($server, self::$wireVersionForArrayFilters)) { throw UnsupportedException::arrayFiltersNotSupported(); } - if (isset($this->options['collation']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) { + if (isset($this->options['collation']) && ! server_supports_feature($server, self::$wireVersionForCollation)) { throw UnsupportedException::collationNotSupported(); } - if (isset($this->options['writeConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForWriteConcern)) { + if (isset($this->options['writeConcern']) && ! server_supports_feature($server, self::$wireVersionForWriteConcern)) { throw UnsupportedException::writeConcernNotSupported(); } + $inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction(); + if ($inTransaction && isset($this->options['writeConcern'])) { + throw UnsupportedException::writeConcernNotSupportedInTransaction(); + } + $cursor = $server->executeWriteCommand($this->databaseName, new Command($this->createCommandDocument($server)), $this->createOptions()); - $result = current($cursor->toArray()); - - if ( ! isset($result->value)) { - return null; - } - - if ( ! is_object($result->value)) { - throw new UnexpectedValueException('findAndModify command did not return a "value" document'); - } if (isset($this->options['typeMap'])) { - return \MongoDB\apply_type_map_to_document($result->value, $this->options['typeMap']); + $cursor->setTypeMap(create_field_path_type_map($this->options['typeMap'], 'value')); } - return $result->value; + $result = current($cursor->toArray()); + + return isset($result->value) ? $result->value : null; } public function getCommandDocument(Server $server) @@ -236,6 +254,7 @@ class FindAndModify implements Executable, Explainable /** * Create the findAndModify command document. * + * @param Server $server * @return array */ private function createCommandDocument(Server $server) @@ -249,12 +268,18 @@ class FindAndModify implements Executable, Explainable $cmd['upsert'] = $this->options['upsert']; } - foreach (['collation', 'fields', 'query', 'sort', 'update'] as $option) { + foreach (['collation', 'fields', 'query', 'sort'] as $option) { if (isset($this->options[$option])) { $cmd[$option] = (object) $this->options[$option]; } } + if (isset($this->options['update'])) { + $cmd['update'] = is_pipeline($this->options['update']) + ? $this->options['update'] + : (object) $this->options['update']; + } + if (isset($this->options['arrayFilters'])) { $cmd['arrayFilters'] = $this->options['arrayFilters']; } @@ -263,7 +288,9 @@ class FindAndModify implements Executable, Explainable $cmd['maxTimeMS'] = $this->options['maxTimeMS']; } - if (isset($this->options['bypassDocumentValidation']) && \MongoDB\server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)) { + if (! empty($this->options['bypassDocumentValidation']) && + server_supports_feature($server, self::$wireVersionForDocumentLevelValidation) + ) { $cmd['bypassDocumentValidation'] = $this->options['bypassDocumentValidation']; } diff --git a/cache/stores/mongodb/MongoDB/Operation/FindOne.php b/cache/stores/mongodb/MongoDB/Operation/FindOne.php index a5c682057e3..48f601ce6fa 100644 --- a/cache/stores/mongodb/MongoDB/Operation/FindOne.php +++ b/cache/stores/mongodb/MongoDB/Operation/FindOne.php @@ -17,10 +17,11 @@ namespace MongoDB\Operation; -use MongoDB\Driver\Server; use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; +use MongoDB\Driver\Server; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Exception\UnsupportedException; +use function current; /** * Operation for finding a single document with the find command. @@ -32,6 +33,7 @@ use MongoDB\Exception\UnsupportedException; */ class FindOne implements Executable, Explainable { + /** @var Find */ private $find; /** @@ -126,7 +128,7 @@ class FindOne implements Executable, Explainable $cursor = $this->find->execute($server); $document = current($cursor->toArray()); - return ($document === false) ? null : $document; + return $document === false ? null : $document; } public function getCommandDocument(Server $server) diff --git a/cache/stores/mongodb/MongoDB/Operation/FindOneAndDelete.php b/cache/stores/mongodb/MongoDB/Operation/FindOneAndDelete.php index 1a650f4c0db..489ca3e95f0 100644 --- a/cache/stores/mongodb/MongoDB/Operation/FindOneAndDelete.php +++ b/cache/stores/mongodb/MongoDB/Operation/FindOneAndDelete.php @@ -17,10 +17,12 @@ namespace MongoDB\Operation; -use MongoDB\Driver\Server; use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; +use MongoDB\Driver\Server; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Exception\UnsupportedException; +use function is_array; +use function is_object; /** * Operation for deleting a document with the findAndModify command. @@ -31,6 +33,7 @@ use MongoDB\Exception\UnsupportedException; */ class FindOneAndDelete implements Executable, Explainable { + /** @var FindAndModify */ private $findAndModify; /** @@ -71,7 +74,7 @@ class FindOneAndDelete implements Executable, Explainable */ public function __construct($databaseName, $collectionName, $filter, array $options = []) { - if ( ! is_array($filter) && ! is_object($filter)) { + if (! is_array($filter) && ! is_object($filter)) { throw InvalidArgumentException::invalidType('$filter', $filter, 'array or object'); } diff --git a/cache/stores/mongodb/MongoDB/Operation/FindOneAndReplace.php b/cache/stores/mongodb/MongoDB/Operation/FindOneAndReplace.php index c1b4892711e..43721c37257 100644 --- a/cache/stores/mongodb/MongoDB/Operation/FindOneAndReplace.php +++ b/cache/stores/mongodb/MongoDB/Operation/FindOneAndReplace.php @@ -17,10 +17,14 @@ namespace MongoDB\Operation; -use MongoDB\Driver\Server; use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; +use MongoDB\Driver\Server; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Exception\UnsupportedException; +use function is_array; +use function is_integer; +use function is_object; +use function MongoDB\is_first_key_operator; /** * Operation for replacing a document with the findAndModify command. @@ -34,6 +38,7 @@ class FindOneAndReplace implements Executable, Explainable const RETURN_DOCUMENT_BEFORE = 1; const RETURN_DOCUMENT_AFTER = 2; + /** @var FindAndModify */ private $findAndModify; /** @@ -90,15 +95,15 @@ class FindOneAndReplace implements Executable, Explainable */ public function __construct($databaseName, $collectionName, $filter, $replacement, array $options = []) { - if ( ! is_array($filter) && ! is_object($filter)) { + if (! is_array($filter) && ! is_object($filter)) { throw InvalidArgumentException::invalidType('$filter', $filter, 'array or object'); } - if ( ! is_array($replacement) && ! is_object($replacement)) { + if (! is_array($replacement) && ! is_object($replacement)) { throw InvalidArgumentException::invalidType('$replacement', $replacement, 'array or object'); } - if (\MongoDB\is_first_key_operator($replacement)) { + if (is_first_key_operator($replacement)) { throw new InvalidArgumentException('First key in $replacement argument is an update operator'); } @@ -111,7 +116,7 @@ class FindOneAndReplace implements Executable, Explainable throw InvalidArgumentException::invalidType('"projection" option', $options['projection'], 'array or object'); } - if ( ! is_integer($options['returnDocument'])) { + if (! is_integer($options['returnDocument'])) { throw InvalidArgumentException::invalidType('"returnDocument" option', $options['returnDocument'], 'integer'); } diff --git a/cache/stores/mongodb/MongoDB/Operation/FindOneAndUpdate.php b/cache/stores/mongodb/MongoDB/Operation/FindOneAndUpdate.php index c9d15cc214d..ed38e0c2772 100644 --- a/cache/stores/mongodb/MongoDB/Operation/FindOneAndUpdate.php +++ b/cache/stores/mongodb/MongoDB/Operation/FindOneAndUpdate.php @@ -17,10 +17,15 @@ namespace MongoDB\Operation; -use MongoDB\Driver\Server; use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; +use MongoDB\Driver\Server; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Exception\UnsupportedException; +use function is_array; +use function is_integer; +use function is_object; +use function MongoDB\is_first_key_operator; +use function MongoDB\is_pipeline; /** * Operation for updating a document with the findAndModify command. @@ -34,6 +39,7 @@ class FindOneAndUpdate implements Executable, Explainable const RETURN_DOCUMENT_BEFORE = 1; const RETURN_DOCUMENT_AFTER = 2; + /** @var FindAndModify */ private $findAndModify; /** @@ -93,16 +99,16 @@ class FindOneAndUpdate implements Executable, Explainable */ public function __construct($databaseName, $collectionName, $filter, $update, array $options = []) { - if ( ! is_array($filter) && ! is_object($filter)) { + if (! is_array($filter) && ! is_object($filter)) { throw InvalidArgumentException::invalidType('$filter', $filter, 'array or object'); } - if ( ! is_array($update) && ! is_object($update)) { + if (! is_array($update) && ! is_object($update)) { throw InvalidArgumentException::invalidType('$update', $update, 'array or object'); } - if ( ! \MongoDB\is_first_key_operator($update)) { - throw new InvalidArgumentException('First key in $update argument is not an update operator'); + if (! is_first_key_operator($update) && ! is_pipeline($update)) { + throw new InvalidArgumentException('Expected an update document with operator as first key or a pipeline'); } $options += [ @@ -114,7 +120,7 @@ class FindOneAndUpdate implements Executable, Explainable throw InvalidArgumentException::invalidType('"projection" option', $options['projection'], 'array or object'); } - if ( ! is_integer($options['returnDocument'])) { + if (! is_integer($options['returnDocument'])) { throw InvalidArgumentException::invalidType('"returnDocument" option', $options['returnDocument'], 'integer'); } diff --git a/cache/stores/mongodb/MongoDB/Operation/InsertMany.php b/cache/stores/mongodb/MongoDB/Operation/InsertMany.php index afa9427c170..768907592b6 100644 --- a/cache/stores/mongodb/MongoDB/Operation/InsertMany.php +++ b/cache/stores/mongodb/MongoDB/Operation/InsertMany.php @@ -17,13 +17,19 @@ namespace MongoDB\Operation; -use MongoDB\InsertManyResult; use MongoDB\Driver\BulkWrite as Bulk; +use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; 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\InsertManyResult; +use function is_array; +use function is_bool; +use function is_object; +use function MongoDB\server_supports_feature; +use function sprintf; /** * Operation for inserting multiple documents with the insert command. @@ -34,11 +40,19 @@ use MongoDB\Exception\InvalidArgumentException; */ class InsertMany implements Executable { + /** @var integer */ private static $wireVersionForDocumentLevelValidation = 4; + /** @var string */ private $databaseName; + + /** @var string */ private $collectionName; + + /** @var object[]|array[] */ private $documents; + + /** @var array */ private $options; /** @@ -81,7 +95,7 @@ class InsertMany implements Executable throw new InvalidArgumentException(sprintf('$documents is not a list (unexpected index: "%s")', $i)); } - if ( ! is_array($document) && ! is_object($document)) { + if (! is_array($document) && ! is_object($document)) { throw InvalidArgumentException::invalidType(sprintf('$documents[%d]', $i), $document, 'array or object'); } @@ -94,16 +108,16 @@ class InsertMany implements Executable throw InvalidArgumentException::invalidType('"bypassDocumentValidation" option', $options['bypassDocumentValidation'], 'boolean'); } - if ( ! is_bool($options['ordered'])) { + 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'); + throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); } if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) { - throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern'); + throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class); } if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) { @@ -126,9 +140,16 @@ class InsertMany implements Executable */ public function execute(Server $server) { + $inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction(); + if ($inTransaction && isset($this->options['writeConcern'])) { + throw UnsupportedException::writeConcernNotSupportedInTransaction(); + } + $options = ['ordered' => $this->options['ordered']]; - if (isset($this->options['bypassDocumentValidation']) && \MongoDB\server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)) { + if (! empty($this->options['bypassDocumentValidation']) && + server_supports_feature($server, self::$wireVersionForDocumentLevelValidation) + ) { $options['bypassDocumentValidation'] = $this->options['bypassDocumentValidation']; } diff --git a/cache/stores/mongodb/MongoDB/Operation/InsertOne.php b/cache/stores/mongodb/MongoDB/Operation/InsertOne.php index 209d5c2fae9..84d6a4aa17c 100644 --- a/cache/stores/mongodb/MongoDB/Operation/InsertOne.php +++ b/cache/stores/mongodb/MongoDB/Operation/InsertOne.php @@ -17,13 +17,18 @@ namespace MongoDB\Operation; -use MongoDB\InsertOneResult; use MongoDB\Driver\BulkWrite as Bulk; +use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; 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\InsertOneResult; +use function is_array; +use function is_bool; +use function is_object; +use function MongoDB\server_supports_feature; /** * Operation for inserting a single document with the insert command. @@ -34,11 +39,19 @@ use MongoDB\Exception\InvalidArgumentException; */ class InsertOne implements Executable { + /** @var integer */ private static $wireVersionForDocumentLevelValidation = 4; + /** @var string */ private $databaseName; + + /** @var string */ private $collectionName; + + /** @var array|object */ private $document; + + /** @var array */ private $options; /** @@ -66,7 +79,7 @@ class InsertOne implements Executable */ public function __construct($databaseName, $collectionName, $document, array $options = []) { - if ( ! is_array($document) && ! is_object($document)) { + if (! is_array($document) && ! is_object($document)) { throw InvalidArgumentException::invalidType('$document', $document, 'array or object'); } @@ -75,11 +88,11 @@ class InsertOne implements Executable } if (isset($options['session']) && ! $options['session'] instanceof Session) { - throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session'); + throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); } if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) { - throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern'); + throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class); } if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) { @@ -104,7 +117,14 @@ class InsertOne implements Executable { $options = []; - if (isset($this->options['bypassDocumentValidation']) && \MongoDB\server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)) { + $inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction(); + if (isset($this->options['writeConcern']) && $inTransaction) { + throw UnsupportedException::writeConcernNotSupportedInTransaction(); + } + + if (! empty($this->options['bypassDocumentValidation']) && + server_supports_feature($server, self::$wireVersionForDocumentLevelValidation) + ) { $options['bypassDocumentValidation'] = $this->options['bypassDocumentValidation']; } diff --git a/cache/stores/mongodb/MongoDB/Operation/ListCollections.php b/cache/stores/mongodb/MongoDB/Operation/ListCollections.php index 41988bc34a4..1d1fdf5612b 100644 --- a/cache/stores/mongodb/MongoDB/Operation/ListCollections.php +++ b/cache/stores/mongodb/MongoDB/Operation/ListCollections.php @@ -18,15 +18,16 @@ namespace MongoDB\Operation; use MongoDB\Driver\Command; -use MongoDB\Driver\Query; +use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; use MongoDB\Driver\Server; use MongoDB\Driver\Session; -use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Model\CachingIterator; use MongoDB\Model\CollectionInfoCommandIterator; use MongoDB\Model\CollectionInfoIterator; -use MongoDB\Model\CollectionInfoLegacyIterator; +use function is_array; +use function is_integer; +use function is_object; /** * Operation for the listCollections command. @@ -37,7 +38,10 @@ use MongoDB\Model\CollectionInfoLegacyIterator; */ class ListCollections implements Executable { + /** @var string */ private $databaseName; + + /** @var array */ private $options; /** @@ -69,7 +73,7 @@ class ListCollections implements Executable } if (isset($options['session']) && ! $options['session'] instanceof Session) { - throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session'); + throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); } $this->databaseName = (string) $databaseName; @@ -121,7 +125,7 @@ class ListCollections implements Executable { $cmd = ['listCollections' => 1]; - if ( ! empty($this->options['filter'])) { + if (! empty($this->options['filter'])) { $cmd['filter'] = (object) $this->options['filter']; } @@ -129,7 +133,7 @@ class ListCollections implements Executable $cmd['maxTimeMS'] = $this->options['maxTimeMS']; } - $cursor = $server->executeCommand($this->databaseName, new Command($cmd), $this->createOptions()); + $cursor = $server->executeReadCommand($this->databaseName, new Command($cmd), $this->createOptions()); $cursor->setTypeMap(['root' => 'array', 'document' => 'array']); return new CollectionInfoCommandIterator(new CachingIterator($cursor)); diff --git a/cache/stores/mongodb/MongoDB/Operation/ListDatabases.php b/cache/stores/mongodb/MongoDB/Operation/ListDatabases.php index fca38fb133d..c405236917b 100644 --- a/cache/stores/mongodb/MongoDB/Operation/ListDatabases.php +++ b/cache/stores/mongodb/MongoDB/Operation/ListDatabases.php @@ -18,13 +18,17 @@ namespace MongoDB\Operation; use MongoDB\Driver\Command; +use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; 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\Model\DatabaseInfoIterator; use MongoDB\Model\DatabaseInfoLegacyIterator; +use function current; +use function is_array; +use function is_integer; +use function is_object; /** * Operation for the ListDatabases command. @@ -35,6 +39,7 @@ use MongoDB\Model\DatabaseInfoLegacyIterator; */ class ListDatabases implements Executable { + /** @var array */ private $options; /** @@ -67,7 +72,7 @@ class ListDatabases implements Executable } if (isset($options['session']) && ! $options['session'] instanceof Session) { - throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session'); + throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); } $this->options = $options; @@ -86,7 +91,7 @@ class ListDatabases implements Executable { $cmd = ['listDatabases' => 1]; - if ( ! empty($this->options['filter'])) { + if (! empty($this->options['filter'])) { $cmd['filter'] = (object) $this->options['filter']; } @@ -94,11 +99,11 @@ class ListDatabases implements Executable $cmd['maxTimeMS'] = $this->options['maxTimeMS']; } - $cursor = $server->executeCommand('admin', new Command($cmd), $this->createOptions()); + $cursor = $server->executeReadCommand('admin', new Command($cmd), $this->createOptions()); $cursor->setTypeMap(['root' => 'array', 'document' => 'array']); $result = current($cursor->toArray()); - if ( ! isset($result['databases']) || ! is_array($result['databases'])) { + if (! isset($result['databases']) || ! is_array($result['databases'])) { throw new UnexpectedValueException('listDatabases command did not return a "databases" array'); } diff --git a/cache/stores/mongodb/MongoDB/Operation/ListIndexes.php b/cache/stores/mongodb/MongoDB/Operation/ListIndexes.php index a0ba544edaa..96aa5b95d11 100644 --- a/cache/stores/mongodb/MongoDB/Operation/ListIndexes.php +++ b/cache/stores/mongodb/MongoDB/Operation/ListIndexes.php @@ -17,16 +17,16 @@ namespace MongoDB\Operation; +use EmptyIterator; use MongoDB\Driver\Command; -use MongoDB\Driver\Query; +use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; use MongoDB\Driver\Server; use MongoDB\Driver\Session; -use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Model\CachingIterator; use MongoDB\Model\IndexInfoIterator; use MongoDB\Model\IndexInfoIteratorIterator; -use EmptyIterator; +use function is_integer; /** * Operation for the listIndexes command. @@ -37,11 +37,19 @@ use EmptyIterator; */ class ListIndexes implements Executable { + /** @var integer */ private static $errorCodeDatabaseNotFound = 60; + + /** @var integer */ private static $errorCodeNamespaceNotFound = 26; + /** @var string */ private $databaseName; + + /** @var string */ private $collectionName; + + /** @var array */ private $options; /** @@ -68,7 +76,7 @@ class ListIndexes implements Executable } if (isset($options['session']) && ! $options['session'] instanceof Session) { - throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session'); + throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); } $this->databaseName = (string) $databaseName; @@ -126,14 +134,14 @@ class ListIndexes implements Executable } try { - $cursor = $server->executeCommand($this->databaseName, new Command($cmd), $this->createOptions()); + $cursor = $server->executeReadCommand($this->databaseName, new Command($cmd), $this->createOptions()); } catch (DriverRuntimeException $e) { /* The server may return an error if the collection does not exist. * Check for possible error codes (see: SERVER-20463) and return an * empty iterator instead of throwing. */ if ($e->getCode() === self::$errorCodeNamespaceNotFound || $e->getCode() === self::$errorCodeDatabaseNotFound) { - return new IndexInfoIteratorIterator(new EmptyIterator); + return new IndexInfoIteratorIterator(new EmptyIterator()); } throw $e; diff --git a/cache/stores/mongodb/MongoDB/Operation/MapReduce.php b/cache/stores/mongodb/MongoDB/Operation/MapReduce.php index 1e9b4b9c6ab..22c357dc6ea 100644 --- a/cache/stores/mongodb/MongoDB/Operation/MapReduce.php +++ b/cache/stores/mongodb/MongoDB/Operation/MapReduce.php @@ -17,21 +17,29 @@ namespace MongoDB\Operation; +use ArrayIterator; use MongoDB\BSON\JavascriptInterface; use MongoDB\Driver\Command; +use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; 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 MongoDB\MapReduceResult; -use ArrayIterator; use stdClass; +use function current; +use function is_array; +use function is_bool; +use function is_integer; +use function is_object; +use function is_string; +use function MongoDB\create_field_path_type_map; +use function MongoDB\is_mapreduce_output_inline; +use function MongoDB\server_supports_feature; /** * Operation for the mapReduce command. @@ -42,16 +50,34 @@ use stdClass; */ class MapReduce implements Executable { + /** @var integer */ private static $wireVersionForCollation = 5; + + /** @var integer */ private static $wireVersionForDocumentLevelValidation = 4; + + /** @var integer */ private static $wireVersionForReadConcern = 4; + + /** @var integer */ private static $wireVersionForWriteConcern = 4; + /** @var string */ private $databaseName; + + /** @var string */ private $collectionName; + + /** @var JavascriptInterface */ private $map; + + /** @var JavascriptInterface */ private $reduce; + + /** @var array|object|string */ private $out; + + /** @var array */ private $options; /** @@ -144,7 +170,7 @@ class MapReduce implements Executable */ public function __construct($databaseName, $collectionName, JavascriptInterface $map, JavascriptInterface $reduce, $out, array $options = []) { - if ( ! is_string($out) && ! is_array($out) && ! is_object($out)) { + if (! is_string($out) && ! is_array($out) && ! is_object($out)) { throw InvalidArgumentException::invalidType('$out', $out, 'string or array or object'); } @@ -157,7 +183,7 @@ class MapReduce implements Executable } if (isset($options['finalize']) && ! $options['finalize'] instanceof JavascriptInterface) { - throw InvalidArgumentException::invalidType('"finalize" option', $options['finalize'], 'MongoDB\Driver\Javascript'); + throw InvalidArgumentException::invalidType('"finalize" option', $options['finalize'], JavascriptInterface::class); } if (isset($options['jsMode']) && ! is_bool($options['jsMode'])) { @@ -177,11 +203,11 @@ class MapReduce implements Executable } if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) { - throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern'); + throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], ReadConcern::class); } if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) { - throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference'); + throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class); } if (isset($options['scope']) && ! is_array($options['scope']) && ! is_object($options['scope'])) { @@ -189,7 +215,7 @@ class MapReduce implements Executable } if (isset($options['session']) && ! $options['session'] instanceof Session) { - throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session'); + throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); } if (isset($options['sort']) && ! is_array($options['sort']) && ! is_object($options['sort'])) { @@ -205,7 +231,7 @@ class MapReduce implements Executable } if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) { - throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern'); + throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class); } if (isset($options['readConcern']) && $options['readConcern']->isDefault()) { @@ -236,26 +262,46 @@ class MapReduce implements Executable */ public function execute(Server $server) { - if (isset($this->options['collation']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) { + if (isset($this->options['collation']) && ! server_supports_feature($server, self::$wireVersionForCollation)) { throw UnsupportedException::collationNotSupported(); } - if (isset($this->options['readConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) { + if (isset($this->options['readConcern']) && ! server_supports_feature($server, self::$wireVersionForReadConcern)) { throw UnsupportedException::readConcernNotSupported(); } - if (isset($this->options['writeConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForWriteConcern)) { + if (isset($this->options['writeConcern']) && ! server_supports_feature($server, self::$wireVersionForWriteConcern)) { throw UnsupportedException::writeConcernNotSupported(); } - $hasOutputCollection = ! \MongoDB\is_mapreduce_output_inline($this->out); + $inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction(); + if ($inTransaction) { + if (isset($this->options['readConcern'])) { + throw UnsupportedException::readConcernNotSupportedInTransaction(); + } + if (isset($this->options['writeConcern'])) { + throw UnsupportedException::writeConcernNotSupportedInTransaction(); + } + } + + $hasOutputCollection = ! is_mapreduce_output_inline($this->out); $command = $this->createCommand($server); $options = $this->createOptions($hasOutputCollection); + /* If the mapReduce operation results in a write, use + * executeReadWriteCommand to ensure we're handling the writeConcern + * option. + * In other cases, we use executeCommand as this will prevent the + * mapReduce operation from being retried when retryReads is enabled. + * See https://github.com/mongodb/specifications/blob/master/source/retryable-reads/retryable-reads.rst#unsupported-read-operations. */ $cursor = $hasOutputCollection ? $server->executeReadWriteCommand($this->databaseName, $command, $options) - : $server->executeReadCommand($this->databaseName, $command, $options); + : $server->executeCommand($this->databaseName, $command, $options); + + if (isset($this->options['typeMap']) && ! $hasOutputCollection) { + $cursor->setTypeMap(create_field_path_type_map($this->options['typeMap'], 'results.$')); + } $result = current($cursor->toArray()); @@ -291,7 +337,9 @@ class MapReduce implements Executable } } - if (isset($this->options['bypassDocumentValidation']) && \MongoDB\server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)) { + if (! empty($this->options['bypassDocumentValidation']) && + server_supports_feature($server, self::$wireVersionForDocumentLevelValidation) + ) { $cmd['bypassDocumentValidation'] = $this->options['bypassDocumentValidation']; } @@ -312,11 +360,7 @@ class MapReduce implements Executable if (isset($result->results) && is_array($result->results)) { $results = $result->results; - return function() use ($results) { - if (isset($this->options['typeMap'])) { - return new TypeMapArrayIterator($results, $this->options['typeMap']); - } - + return function () use ($results) { return new ArrayIterator($results); }; } @@ -328,7 +372,7 @@ class MapReduce implements Executable ? new Find($this->databaseName, $result->result, [], $options) : new Find($result->result->db, $result->result->collection, [], $options); - return function() use ($find, $server) { + return function () use ($find, $server) { return $find->execute($server); }; } @@ -352,7 +396,7 @@ class MapReduce implements Executable $options['readConcern'] = $this->options['readConcern']; } - if ( ! $hasOutputCollection && isset($this->options['readPreference'])) { + if (! $hasOutputCollection && isset($this->options['readPreference'])) { $options['readPreference'] = $this->options['readPreference']; } diff --git a/cache/stores/mongodb/MongoDB/Operation/ModifyCollection.php b/cache/stores/mongodb/MongoDB/Operation/ModifyCollection.php index 5b84f8aaec8..0dc01115427 100644 --- a/cache/stores/mongodb/MongoDB/Operation/ModifyCollection.php +++ b/cache/stores/mongodb/MongoDB/Operation/ModifyCollection.php @@ -18,11 +18,15 @@ namespace MongoDB\Operation; use MongoDB\Driver\Command; +use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; 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 function current; +use function is_array; +use function MongoDB\server_supports_feature; /** * Operation for the collMod command. @@ -33,9 +37,16 @@ use MongoDB\Exception\InvalidArgumentException; */ class ModifyCollection implements Executable { + /** @var string */ private $databaseName; + + /** @var string */ private $collectionName; + + /** @var array */ private $collectionOptions; + + /** @var array */ private $options; /** @@ -55,10 +66,10 @@ class ModifyCollection implements Executable * This is not supported for server versions < 3.2 and will result in an * exception at execution time if used. * - * @param string $databaseName Database name - * @param string $collectionName Collection or view to modify - * @param string $collectionOptions Collection or view options to assign - * @param array $options Command options + * @param string $databaseName Database name + * @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 */ public function __construct($databaseName, $collectionName, array $collectionOptions, array $options = []) @@ -68,7 +79,7 @@ class ModifyCollection implements Executable } if (isset($options['session']) && ! $options['session'] instanceof Session) { - throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session'); + throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); } if (isset($options['typeMap']) && ! is_array($options['typeMap'])) { @@ -76,7 +87,7 @@ class ModifyCollection implements Executable } if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) { - throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern'); + throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class); } if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) { @@ -99,7 +110,7 @@ class ModifyCollection implements Executable */ public function execute(Server $server) { - if (isset($this->options['writeConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForWriteConcern)) { + if (isset($this->options['writeConcern']) && ! server_supports_feature($server, self::$wireVersionForWriteConcern)) { throw UnsupportedException::writeConcernNotSupported(); } diff --git a/cache/stores/mongodb/MongoDB/Operation/ReplaceOne.php b/cache/stores/mongodb/MongoDB/Operation/ReplaceOne.php index 86a196cc4cc..cbbda7ff289 100644 --- a/cache/stores/mongodb/MongoDB/Operation/ReplaceOne.php +++ b/cache/stores/mongodb/MongoDB/Operation/ReplaceOne.php @@ -17,11 +17,15 @@ namespace MongoDB\Operation; -use MongoDB\UpdateResult; -use MongoDB\Driver\Server; use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; +use MongoDB\Driver\Server; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Exception\UnsupportedException; +use MongoDB\UpdateResult; +use function is_array; +use function is_object; +use function MongoDB\is_first_key_operator; +use function MongoDB\is_pipeline; /** * Operation for replacing a single document with the update command. @@ -32,6 +36,7 @@ use MongoDB\Exception\UnsupportedException; */ class ReplaceOne implements Executable { + /** @var Update */ private $update; /** @@ -68,14 +73,18 @@ class ReplaceOne implements Executable */ public function __construct($databaseName, $collectionName, $filter, $replacement, array $options = []) { - if ( ! is_array($replacement) && ! is_object($replacement)) { + if (! is_array($replacement) && ! is_object($replacement)) { throw InvalidArgumentException::invalidType('$replacement', $replacement, 'array or object'); } - if (\MongoDB\is_first_key_operator($replacement)) { + if (is_first_key_operator($replacement)) { throw new InvalidArgumentException('First key in $replacement argument is an update operator'); } + if (is_pipeline($replacement)) { + throw new InvalidArgumentException('$replacement argument is a pipeline'); + } + $this->update = new Update( $databaseName, $collectionName, diff --git a/cache/stores/mongodb/MongoDB/Operation/Update.php b/cache/stores/mongodb/MongoDB/Operation/Update.php index 96adfa75889..e4ee30dbc0b 100644 --- a/cache/stores/mongodb/MongoDB/Operation/Update.php +++ b/cache/stores/mongodb/MongoDB/Operation/Update.php @@ -17,14 +17,20 @@ namespace MongoDB\Operation; -use MongoDB\UpdateResult; use MongoDB\Driver\BulkWrite as Bulk; +use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; 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\UpdateResult; +use function is_array; +use function is_bool; +use function is_object; +use function MongoDB\is_first_key_operator; +use function MongoDB\is_pipeline; +use function MongoDB\server_supports_feature; /** * Operation for the update command. @@ -37,14 +43,28 @@ use MongoDB\Exception\UnsupportedException; */ class Update implements Executable, Explainable { + /** @var integer */ private static $wireVersionForArrayFilters = 6; + + /** @var integer */ private static $wireVersionForCollation = 5; + + /** @var integer */ private static $wireVersionForDocumentLevelValidation = 4; + /** @var string */ private $databaseName; + + /** @var string */ private $collectionName; + + /** @var array|object */ private $filter; + + /** @var array|object */ private $update; + + /** @var array */ private $options; /** @@ -92,11 +112,11 @@ class Update implements Executable, Explainable */ public function __construct($databaseName, $collectionName, $filter, $update, array $options = []) { - if ( ! is_array($filter) && ! is_object($filter)) { + if (! is_array($filter) && ! is_object($filter)) { throw InvalidArgumentException::invalidType('$filter', $filter, 'array or object'); } - if ( ! is_array($update) && ! is_object($update)) { + if (! is_array($update) && ! is_object($update)) { throw InvalidArgumentException::invalidType('$update', $filter, 'array or object'); } @@ -117,24 +137,24 @@ class Update implements Executable, Explainable throw InvalidArgumentException::invalidType('"collation" option', $options['collation'], 'array or object'); } - if ( ! is_bool($options['multi'])) { + if (! is_bool($options['multi'])) { throw InvalidArgumentException::invalidType('"multi" option', $options['multi'], 'boolean'); } - if ($options['multi'] && ! \MongoDB\is_first_key_operator($update)) { + if ($options['multi'] && ! is_first_key_operator($update) && ! is_pipeline($update)) { throw new InvalidArgumentException('"multi" option cannot be true if $update is a replacement document'); } if (isset($options['session']) && ! $options['session'] instanceof Session) { - throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session'); + throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); } - if ( ! is_bool($options['upsert'])) { + if (! is_bool($options['upsert'])) { throw InvalidArgumentException::invalidType('"upsert" option', $options['upsert'], 'boolean'); } if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) { - throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern'); + throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class); } if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) { @@ -159,17 +179,24 @@ class Update implements Executable, Explainable */ public function execute(Server $server) { - if (isset($this->options['arrayFilters']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForArrayFilters)) { + if (isset($this->options['arrayFilters']) && ! server_supports_feature($server, self::$wireVersionForArrayFilters)) { throw UnsupportedException::arrayFiltersNotSupported(); } - if (isset($this->options['collation']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) { + if (isset($this->options['collation']) && ! server_supports_feature($server, self::$wireVersionForCollation)) { throw UnsupportedException::collationNotSupported(); } + $inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction(); + if ($inTransaction && isset($this->options['writeConcern'])) { + throw UnsupportedException::writeConcernNotSupportedInTransaction(); + } + $bulkOptions = []; - if (isset($this->options['bypassDocumentValidation']) && \MongoDB\server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)) { + if (! empty($this->options['bypassDocumentValidation']) && + server_supports_feature($server, self::$wireVersionForDocumentLevelValidation) + ) { $bulkOptions['bypassDocumentValidation'] = $this->options['bypassDocumentValidation']; } @@ -189,7 +216,9 @@ class Update implements Executable, Explainable $cmd['writeConcern'] = $this->options['writeConcern']; } - if (isset($this->options['bypassDocumentValidation']) && \MongoDB\server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)) { + if (! empty($this->options['bypassDocumentValidation']) && + server_supports_feature($server, self::$wireVersionForDocumentLevelValidation) + ) { $cmd['bypassDocumentValidation'] = $this->options['bypassDocumentValidation']; } diff --git a/cache/stores/mongodb/MongoDB/Operation/UpdateMany.php b/cache/stores/mongodb/MongoDB/Operation/UpdateMany.php index 1abd4012422..9138fe6811d 100644 --- a/cache/stores/mongodb/MongoDB/Operation/UpdateMany.php +++ b/cache/stores/mongodb/MongoDB/Operation/UpdateMany.php @@ -17,11 +17,15 @@ namespace MongoDB\Operation; -use MongoDB\UpdateResult; -use MongoDB\Driver\Server; use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; +use MongoDB\Driver\Server; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Exception\UnsupportedException; +use MongoDB\UpdateResult; +use function is_array; +use function is_object; +use function MongoDB\is_first_key_operator; +use function MongoDB\is_pipeline; /** * Operation for updating multiple documents with the update command. @@ -32,6 +36,7 @@ use MongoDB\Exception\UnsupportedException; */ class UpdateMany implements Executable, Explainable { + /** @var Update */ private $update; /** @@ -74,12 +79,12 @@ class UpdateMany implements Executable, Explainable */ public function __construct($databaseName, $collectionName, $filter, $update, array $options = []) { - if ( ! is_array($update) && ! is_object($update)) { + if (! is_array($update) && ! is_object($update)) { throw InvalidArgumentException::invalidType('$update', $update, 'array or object'); } - if ( ! \MongoDB\is_first_key_operator($update)) { - throw new InvalidArgumentException('First key in $update argument is not an update operator'); + if (! is_first_key_operator($update) && ! is_pipeline($update)) { + throw new InvalidArgumentException('Expected an update document with operator as first key or a pipeline'); } $this->update = new Update( diff --git a/cache/stores/mongodb/MongoDB/Operation/UpdateOne.php b/cache/stores/mongodb/MongoDB/Operation/UpdateOne.php index 9ea8867f133..0c8d73a69c9 100644 --- a/cache/stores/mongodb/MongoDB/Operation/UpdateOne.php +++ b/cache/stores/mongodb/MongoDB/Operation/UpdateOne.php @@ -17,11 +17,15 @@ namespace MongoDB\Operation; -use MongoDB\UpdateResult; -use MongoDB\Driver\Server; use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; +use MongoDB\Driver\Server; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Exception\UnsupportedException; +use MongoDB\UpdateResult; +use function is_array; +use function is_object; +use function MongoDB\is_first_key_operator; +use function MongoDB\is_pipeline; /** * Operation for updating a single document with the update command. @@ -32,6 +36,7 @@ use MongoDB\Exception\UnsupportedException; */ class UpdateOne implements Executable, Explainable { + /** @var Update */ private $update; /** @@ -74,12 +79,12 @@ class UpdateOne implements Executable, Explainable */ public function __construct($databaseName, $collectionName, $filter, $update, array $options = []) { - if ( ! is_array($update) && ! is_object($update)) { + if (! is_array($update) && ! is_object($update)) { throw InvalidArgumentException::invalidType('$update', $update, 'array or object'); } - if ( ! \MongoDB\is_first_key_operator($update)) { - throw new InvalidArgumentException('First key in $update argument is not an update operator'); + if (! is_first_key_operator($update) && ! is_pipeline($update)) { + throw new InvalidArgumentException('Expected an update document with operator as first key or a pipeline'); } $this->update = new Update( diff --git a/cache/stores/mongodb/MongoDB/Operation/Watch.php b/cache/stores/mongodb/MongoDB/Operation/Watch.php index a109bd93d26..ef301603b20 100644 --- a/cache/stores/mongodb/MongoDB/Operation/Watch.php +++ b/cache/stores/mongodb/MongoDB/Operation/Watch.php @@ -17,23 +17,31 @@ namespace MongoDB\Operation; -use MongoDB\ChangeStream; use MongoDB\BSON\TimestampInterface; -use MongoDB\Driver\Command; +use MongoDB\ChangeStream; use MongoDB\Driver\Cursor; +use MongoDB\Driver\Exception\RuntimeException; use MongoDB\Driver\Manager; -use MongoDB\Driver\ReadConcern; +use MongoDB\Driver\Monitoring\CommandFailedEvent; +use MongoDB\Driver\Monitoring\CommandStartedEvent; +use MongoDB\Driver\Monitoring\CommandSubscriber; +use MongoDB\Driver\Monitoring\CommandSucceededEvent; use MongoDB\Driver\ReadPreference; use MongoDB\Driver\Server; -use MongoDB\Driver\Session; -use MongoDB\Driver\Exception\RuntimeException; -use MongoDB\Driver\Monitoring\CommandFailedEvent; -use MongoDB\Driver\Monitoring\CommandSubscriber; -use MongoDB\Driver\Monitoring\CommandStartedEvent; -use MongoDB\Driver\Monitoring\CommandSucceededEvent; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Exception\UnexpectedValueException; use MongoDB\Exception\UnsupportedException; +use MongoDB\Model\ChangeStreamIterator; +use function array_intersect_key; +use function array_unshift; +use function count; +use function is_array; +use function is_object; +use function is_string; +use function MongoDB\Driver\Monitoring\addSubscriber; +use function MongoDB\Driver\Monitoring\removeSubscriber; +use function MongoDB\select_server; +use function MongoDB\server_supports_feature; /** * Operation for creating a change stream with the aggregate command. @@ -47,19 +55,44 @@ use MongoDB\Exception\UnsupportedException; */ class Watch implements Executable, /* @internal */ CommandSubscriber { - private static $wireVersionForOperationTime = 7; - const FULL_DOCUMENT_DEFAULT = 'default'; const FULL_DOCUMENT_UPDATE_LOOKUP = 'updateLookup'; + /** @var integer */ + private static $wireVersionForStartAtOperationTime = 7; + + /** @var Aggregate */ private $aggregate; + + /** @var array */ private $aggregateOptions; + + /** @var array */ private $changeStreamOptions; + + /** @var string|null */ private $collectionName; + + /** @var string */ private $databaseName; + + /** @var integer|null */ + private $firstBatchSize; + + /** @var boolean */ + private $hasResumed = false; + + /** @var Manager */ + private $manager; + + /** @var TimestampInterface */ private $operationTime; + + /** @var array */ private $pipeline; - private $resumeCallable; + + /** @var object|null */ + private $postBatchResumeToken; /** * Constructs an aggregate command for creating a change stream. @@ -92,21 +125,31 @@ class Watch implements Executable, /* @internal */ CommandSubscriber * * resumeAfter (document): Specifies the logical starting point for the * new change stream. * - * Using this option in conjunction with "startAtOperationTime" will - * result in a server error. The options are mutually exclusive. + * Using this option in conjunction with "startAfter" and/or + * "startAtOperationTime" will result in a server error. The options are + * mutually exclusive. * * * session (MongoDB\Driver\Session): Client session. * * Sessions are not supported for server versions < 3.6. * + * * startAfter (document): Specifies the logical starting point for the + * new change stream. Unlike "resumeAfter", this option can be used with + * a resume token from an "invalidate" event. + * + * Using this option in conjunction with "resumeAfter" and/or + * "startAtOperationTime" will result in a server error. The options are + * mutually exclusive. + * * * startAtOperationTime (MongoDB\BSON\TimestampInterface): If specified, * the change stream will only provide changes that occurred at or after * the specified timestamp. Any command run against the server will * return an operation time that can be used here. Alternatively, an * operation time may be obtained from MongoDB\Driver\Server::getInfo(). * - * Using this option in conjunction with "resumeAfter" will result in a - * server error. The options are mutually exclusive. + * Using this option in conjunction with "resumeAfter" and/or + * "startAfter" will result in a server error. The options are mutually + * exclusive. * * This option is not supported for server versions < 4.0. * @@ -117,11 +160,11 @@ class Watch implements Executable, /* @internal */ CommandSubscriber * for the collection name. A cluster-level change stream may be created by * specifying null for both the database and collection name. * - * @param Manager $manager Manager instance from the driver - * @param string|null $databaseName Database name - * @param string|null $collectionName Collection name - * @param array $pipeline List of pipeline operations - * @param array $options Command options + * @param Manager $manager Manager instance from the driver + * @param string|null $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(Manager $manager, $databaseName, $collectionName, array $pipeline, array $options = []) @@ -143,6 +186,10 @@ class Watch implements Executable, /* @internal */ CommandSubscriber throw InvalidArgumentException::invalidType('"resumeAfter" option', $options['resumeAfter'], 'array or object'); } + if (isset($options['startAfter']) && ! is_array($options['startAfter']) && ! is_object($options['startAfter'])) { + throw InvalidArgumentException::invalidType('"startAfter" option', $options['startAfter'], 'array or object'); + } + if (isset($options['startAtOperationTime']) && ! $options['startAtOperationTime'] instanceof TimestampInterface) { throw InvalidArgumentException::invalidType('"startAtOperationTime" option', $options['startAtOperationTime'], TimestampInterface::class); } @@ -152,7 +199,7 @@ class Watch implements Executable, /* @internal */ CommandSubscriber * ("implicit from the user's perspective" per PHPLIB-342). Since this * is filling in for an implicit session, we default "causalConsistency" * to false. */ - if ( ! isset($options['session'])) { + if (! isset($options['session'])) { try { $options['session'] = $manager->startSession(['causalConsistency' => false]); } catch (RuntimeException $e) { @@ -162,7 +209,7 @@ class Watch implements Executable, /* @internal */ CommandSubscriber } $this->aggregateOptions = array_intersect_key($options, ['batchSize' => 1, 'collation' => 1, 'maxAwaitTimeMS' => 1, 'readConcern' => 1, 'readPreference' => 1, 'session' => 1, 'typeMap' => 1]); - $this->changeStreamOptions = array_intersect_key($options, ['fullDocument' => 1, 'resumeAfter' => 1, 'startAtOperationTime' => 1]); + $this->changeStreamOptions = array_intersect_key($options, ['fullDocument' => 1, 'resumeAfter' => 1, 'startAfter' => 1, 'startAtOperationTime' => 1]); // Null database name implies a cluster-wide change stream if ($databaseName === null) { @@ -170,12 +217,12 @@ class Watch implements Executable, /* @internal */ CommandSubscriber $this->changeStreamOptions['allChangesForCluster'] = true; } + $this->manager = $manager; $this->databaseName = (string) $databaseName; $this->collectionName = isset($collectionName) ? (string) $collectionName : null; $this->pipeline = $pipeline; $this->aggregate = $this->createAggregate(); - $this->resumeCallable = $this->createResumeCallable($manager); } /** @internal */ @@ -186,6 +233,12 @@ class Watch implements Executable, /* @internal */ CommandSubscriber /** @internal */ final public function commandStarted(CommandStartedEvent $event) { + if ($event->getCommandName() !== 'aggregate') { + return; + } + + $this->firstBatchSize = null; + $this->postBatchResumeToken = null; } /** @internal */ @@ -197,7 +250,18 @@ class Watch implements Executable, /* @internal */ CommandSubscriber $reply = $event->getReply(); - if (isset($reply->operationTime) && $reply->operationTime instanceof TimestampInterface) { + if (! isset($reply->cursor->firstBatch) || ! is_array($reply->cursor->firstBatch)) { + throw new UnexpectedValueException('aggregate command did not return a "cursor.firstBatch" array'); + } + + $this->firstBatchSize = count($reply->cursor->firstBatch); + + if (isset($reply->cursor->postBatchResumeToken) && is_object($reply->cursor->postBatchResumeToken)) { + $this->postBatchResumeToken = $reply->cursor->postBatchResumeToken; + } + + if ($this->shouldCaptureOperationTime($event->getServer()) && + isset($reply->operationTime) && $reply->operationTime instanceof TimestampInterface) { $this->operationTime = $reply->operationTime; } } @@ -213,11 +277,16 @@ class Watch implements Executable, /* @internal */ CommandSubscriber */ public function execute(Server $server) { - return new ChangeStream($this->executeAggregate($server), $this->resumeCallable); + return new ChangeStream( + $this->createChangeStreamIterator($server), + function ($resumeToken, $hasAdvanced) { + return $this->resume($resumeToken, $hasAdvanced); + } + ); } /** - * Create the aggregate command for creating a change stream. + * Create the aggregate command for a change stream. * * This method is also used to recreate the aggregate command when resuming. * @@ -231,56 +300,139 @@ class Watch implements Executable, /* @internal */ CommandSubscriber return new Aggregate($this->databaseName, $this->collectionName, $pipeline, $this->aggregateOptions); } - private function createResumeCallable(Manager $manager) + /** + * Create a ChangeStreamIterator by executing the aggregate command. + * + * @param Server $server + * @return ChangeStreamIterator + */ + private function createChangeStreamIterator(Server $server) { - return function($resumeToken = null) use ($manager) { - /* If a resume token was provided, update the "resumeAfter" option - * and ensure that "startAtOperationTime" is no longer set. */ - if ($resumeToken !== null) { - $this->changeStreamOptions['resumeAfter'] = $resumeToken; - unset($this->changeStreamOptions['startAtOperationTime']); - } - - /* If we captured an operation time from the first aggregate command - * and there is no "resumeAfter" option, set "startAtOperationTime" - * so that we can resume from the original aggregate's time. */ - if ($this->operationTime !== null && ! isset($this->changeStreamOptions['resumeAfter'])) { - $this->changeStreamOptions['startAtOperationTime'] = $this->operationTime; - } - - $this->aggregate = $this->createAggregate(); - - /* Select a new server using the read preference, execute this - * operation on it, and return the new ChangeStream. */ - $server = $manager->selectServer($this->aggregateOptions['readPreference']); - - return $this->execute($server); - }; + return new ChangeStreamIterator( + $this->executeAggregate($server), + $this->firstBatchSize, + $this->getInitialResumeToken(), + $this->postBatchResumeToken + ); } /** - * Execute the aggregate command and optionally capture its operation time. + * Execute the aggregate command. + * + * The command will be executed using APM so that we can capture data from + * its response (e.g. firstBatch size, postBatchResumeToken). * * @param Server $server * @return Cursor */ private function executeAggregate(Server $server) { - /* If we've already captured an operation time or the server does not - * support returning an operation time (e.g. MongoDB 3.6), execute the - * aggregation directly and return its cursor. */ - if ($this->operationTime !== null || ! \MongoDB\server_supports_feature($server, self::$wireVersionForOperationTime)) { - return $this->aggregate->execute($server); - } - - /* Otherwise, execute the aggregation using command monitoring so that - * we can capture its operation time with commandSucceeded(). */ - \MongoDB\Driver\Monitoring\addSubscriber($this); + addSubscriber($this); try { return $this->aggregate->execute($server); } finally { - \MongoDB\Driver\Monitoring\removeSubscriber($this); + removeSubscriber($this); } } + + /** + * Return the initial resume token for creating the ChangeStreamIterator. + * + * @see https://github.com/mongodb/specifications/blob/master/source/change-streams/change-streams.rst#updating-the-cached-resume-token + * @return array|object|null + */ + private function getInitialResumeToken() + { + if ($this->firstBatchSize === 0 && isset($this->postBatchResumeToken)) { + return $this->postBatchResumeToken; + } + + if (isset($this->changeStreamOptions['startAfter'])) { + return $this->changeStreamOptions['startAfter']; + } + + if (isset($this->changeStreamOptions['resumeAfter'])) { + return $this->changeStreamOptions['resumeAfter']; + } + + return null; + } + + /** + * Resumes a change stream. + * + * @see https://github.com/mongodb/specifications/blob/master/source/change-streams/change-streams.rst#resume-process + * @param array|object|null $resumeToken + * @param bool $hasAdvanced + * @return ChangeStreamIterator + * @throws InvalidArgumentException + */ + private function resume($resumeToken = null, $hasAdvanced = false) + { + if (isset($resumeToken) && ! is_array($resumeToken) && ! is_object($resumeToken)) { + throw InvalidArgumentException::invalidType('$resumeToken', $resumeToken, 'array or object'); + } + + $this->hasResumed = true; + + /* Select a new server using the original read preference. While watch + * is not usable within transactions, we still check if there is a + * pinned session. This is to avoid an ambiguous error message about + * running a command on the wrong server. */ + $server = select_server($this->manager, $this->aggregateOptions); + + $resumeOption = isset($this->changeStreamOptions['startAfter']) && ! $hasAdvanced ? 'startAfter' : 'resumeAfter'; + + unset($this->changeStreamOptions['resumeAfter']); + unset($this->changeStreamOptions['startAfter']); + unset($this->changeStreamOptions['startAtOperationTime']); + + if ($resumeToken !== null) { + $this->changeStreamOptions[$resumeOption] = $resumeToken; + } + + if ($resumeToken === null && $this->operationTime !== null) { + $this->changeStreamOptions['startAtOperationTime'] = $this->operationTime; + } + + // Recreate the aggregate command and return a new ChangeStreamIterator + $this->aggregate = $this->createAggregate(); + + return $this->createChangeStreamIterator($server); + } + + /** + * Determine whether to capture operation time from an aggregate response. + * + * @see https://github.com/mongodb/specifications/blob/master/source/change-streams/change-streams.rst#startatoperationtime + * @param Server $server + * @return boolean + */ + private function shouldCaptureOperationTime(Server $server) + { + if ($this->hasResumed) { + return false; + } + + if (isset($this->changeStreamOptions['resumeAfter']) || + isset($this->changeStreamOptions['startAfter']) || + isset($this->changeStreamOptions['startAtOperationTime'])) { + return false; + } + + if ($this->firstBatchSize > 0) { + return false; + } + + if ($this->postBatchResumeToken !== null) { + return false; + } + + if (! server_supports_feature($server, self::$wireVersionForStartAtOperationTime)) { + return false; + } + + return true; + } } diff --git a/cache/stores/mongodb/MongoDB/Operation/WithTransaction.php b/cache/stores/mongodb/MongoDB/Operation/WithTransaction.php new file mode 100644 index 00000000000..249616e6c83 --- /dev/null +++ b/cache/stores/mongodb/MongoDB/Operation/WithTransaction.php @@ -0,0 +1,127 @@ +callback = $callback; + $this->transactionOptions = $transactionOptions; + } + + /** + * Execute the operation in the given session + * + * This helper takes care of retrying the commit operation or the entire + * transaction if an error occurs. + * + * If the commit fails because of an UnknownTransactionCommitResult error, the + * commit is retried without re-invoking the callback. + * If the commit fails because of a TransientTransactionError, the entire + * transaction will be retried. In this case, the callback will be invoked + * again. It is important that the logic inside the callback is idempotent. + * + * In case of failures, the commit or transaction are retried until 120 seconds + * from the initial call have elapsed. After that, no retries will happen and + * the helper will throw the last exception received from the driver. + * + * @see Client::startSession + * + * @param Session $session A session object as retrieved by Client::startSession + * @return void + * @throws RuntimeException for driver errors while committing the transaction + * @throws Exception for any other errors, including those thrown in the callback + */ + public function execute(Session $session) + { + $startTime = time(); + + while (true) { + $session->startTransaction($this->transactionOptions); + + try { + call_user_func($this->callback, $session); + } catch (Exception $e) { + if ($session->isInTransaction()) { + $session->abortTransaction(); + } + + if ($e instanceof RuntimeException && + $e->hasErrorLabel('TransientTransactionError') && + ! $this->isTransactionTimeLimitExceeded($startTime) + ) { + continue; + } + + throw $e; + } + + if (! $session->isInTransaction()) { + // Assume callback intentionally ended the transaction + return; + } + + while (true) { + try { + $session->commitTransaction(); + } catch (RuntimeException $e) { + if ($e->getCode() !== 50 /* MaxTimeMSExpired */ && + $e->hasErrorLabel('UnknownTransactionCommitResult') && + ! $this->isTransactionTimeLimitExceeded($startTime) + ) { + // Retry committing the transaction + continue; + } + + if ($e->hasErrorLabel('TransientTransactionError') && + ! $this->isTransactionTimeLimitExceeded($startTime) + ) { + // Restart the transaction, invoking the callback again + continue 2; + } + + throw $e; + } + + // Commit was successful + break; + } + + // Transaction was successful + break; + } + } + + /** + * Returns whether the time limit for retrying transactions in the convenient transaction API has passed + * + * @param int $startTime The time the transaction was started + * @return bool + */ + private function isTransactionTimeLimitExceeded($startTime) + { + return time() - $startTime >= 120; + } +} diff --git a/cache/stores/mongodb/MongoDB/UpdateResult.php b/cache/stores/mongodb/MongoDB/UpdateResult.php index a19173f94c5..62660d2cade 100644 --- a/cache/stores/mongodb/MongoDB/UpdateResult.php +++ b/cache/stores/mongodb/MongoDB/UpdateResult.php @@ -25,14 +25,12 @@ use MongoDB\Exception\BadMethodCallException; */ class UpdateResult { + /** @var WriteResult */ private $writeResult; + + /** @var boolean */ private $isAcknowledged; - /** - * Constructor. - * - * @param WriteResult $writeResult - */ public function __construct(WriteResult $writeResult) { $this->writeResult = $writeResult; diff --git a/cache/stores/mongodb/MongoDB/functions.php b/cache/stores/mongodb/MongoDB/functions.php index f37226866d7..e828e669a4b 100644 --- a/cache/stores/mongodb/MongoDB/functions.php +++ b/cache/stores/mongodb/MongoDB/functions.php @@ -17,13 +17,28 @@ namespace MongoDB; +use Exception; use MongoDB\BSON\Serializable; -use MongoDB\Driver\ReadConcern; +use MongoDB\Driver\Manager; +use MongoDB\Driver\ReadPreference; use MongoDB\Driver\Server; -use MongoDB\Driver\WriteConcern; +use MongoDB\Driver\Session; use MongoDB\Exception\InvalidArgumentException; -use stdClass; +use MongoDB\Exception\RuntimeException; +use MongoDB\Operation\WithTransaction; use ReflectionClass; +use ReflectionException; +use function end; +use function get_object_vars; +use function in_array; +use function is_array; +use function is_object; +use function is_string; +use function key; +use function MongoDB\BSON\fromPHP; +use function MongoDB\BSON\toPHP; +use function reset; +use function substr; /** * Applies a type map to a document. @@ -40,11 +55,11 @@ use ReflectionClass; */ function apply_type_map_to_document($document, array $typeMap) { - if ( ! is_array($document) && ! is_object($document)) { + if (! is_array($document) && ! is_object($document)) { throw InvalidArgumentException::invalidType('$document', $document, 'array or object'); } - return \MongoDB\BSON\toPHP(\MongoDB\BSON\fromPHP($document), $typeMap); + return toPHP(fromPHP($document), $typeMap); } /** @@ -66,7 +81,7 @@ function generate_index_name($document) $document = get_object_vars($document); } - if ( ! is_array($document)) { + if (! is_array($document)) { throw InvalidArgumentException::invalidType('$document', $document, 'array or object'); } @@ -99,18 +114,75 @@ function is_first_key_operator($document) $document = get_object_vars($document); } - if ( ! is_array($document)) { + if (! is_array($document)) { throw InvalidArgumentException::invalidType('$document', $document, 'array or object'); } reset($document); $firstKey = (string) key($document); - return (isset($firstKey[0]) && $firstKey[0] === '$'); + return isset($firstKey[0]) && $firstKey[0] === '$'; } /** - * Return whether the aggregation pipeline ends with an $out operator. + * Returns whether an update specification is a valid aggregation pipeline. + * + * @internal + * @param mixed $pipeline + * @return boolean + */ +function is_pipeline($pipeline) +{ + if (! is_array($pipeline)) { + return false; + } + + if ($pipeline === []) { + return false; + } + + $expectedKey = 0; + + foreach ($pipeline as $key => $stage) { + if (! is_array($stage) && ! is_object($stage)) { + return false; + } + + if ($expectedKey !== $key) { + return false; + } + + $expectedKey++; + $stage = (array) $stage; + reset($stage); + $key = key($stage); + + if (! isset($key[0]) || $key[0] !== '$') { + return false; + } + } + + return true; +} + +/** + * Returns whether we are currently in a transaction. + * + * @internal + * @param array $options Command options + * @return boolean + */ +function is_in_transaction(array $options) +{ + if (isset($options['session']) && $options['session'] instanceof Session && $options['session']->isInTransaction()) { + return true; + } + + return false; +} + +/** + * Return whether the aggregation pipeline ends with an $out or $merge operator. * * This is used for determining whether the aggregation pipeline must be * executed against a primary server. @@ -119,7 +191,7 @@ function is_first_key_operator($document) * @param array $pipeline List of pipeline operations * @return boolean */ -function is_last_pipeline_operator_out(array $pipeline) +function is_last_pipeline_operator_write(array $pipeline) { $lastOp = end($pipeline); @@ -129,7 +201,7 @@ function is_last_pipeline_operator_out(array $pipeline) $lastOp = (array) $lastOp; - return key($lastOp) === '$out'; + return in_array(key($lastOp), ['$out', '$merge'], true); } /** @@ -145,7 +217,7 @@ function is_last_pipeline_operator_out(array $pipeline) */ function is_mapreduce_output_inline($out) { - if ( ! is_array($out) && ! is_object($out)) { + if (! is_array($out) && ! is_object($out)) { return false; } @@ -157,7 +229,7 @@ function is_mapreduce_output_inline($out) $out = get_object_vars($out); } - if ( ! is_array($out)) { + if (! is_array($out)) { throw InvalidArgumentException::invalidType('$out', $out, 'array or object'); } @@ -180,18 +252,20 @@ function server_supports_feature(Server $server, $feature) $maxWireVersion = isset($info['maxWireVersion']) ? (integer) $info['maxWireVersion'] : 0; $minWireVersion = isset($info['minWireVersion']) ? (integer) $info['minWireVersion'] : 0; - return ($minWireVersion <= $feature && $maxWireVersion >= $feature); + return $minWireVersion <= $feature && $maxWireVersion >= $feature; } -function is_string_array($input) { - if (!is_array($input)){ +function is_string_array($input) +{ + if (! is_array($input)) { return false; } - foreach($input as $item) { - if (!is_string($item)) { + foreach ($input as $item) { + if (! is_string($item)) { return false; } } + return true; } @@ -204,22 +278,155 @@ function is_string_array($input) { * @see https://bugs.php.net/bug.php?id=49664 * @param mixed $element Value to be copied * @return mixed + * @throws ReflectionException */ -function recursive_copy($element) { +function recursive_copy($element) +{ if (is_array($element)) { foreach ($element as $key => $value) { $element[$key] = recursive_copy($value); } + return $element; } - if ( ! is_object($element)) { + if (! is_object($element)) { return $element; } - if ( ! (new ReflectionClass($element))->isCloneable()) { + if (! (new ReflectionClass($element))->isCloneable()) { return $element; } return clone $element; } + +/** + * Creates a type map to apply to a field type + * + * This is used in the Aggregate, Distinct, and FindAndModify operations to + * apply the root-level type map to the document that will be returned. It also + * replaces the root type with object for consistency within these operations + * + * An existing type map for the given field path will not be overwritten + * + * @internal + * @param array $typeMap The existing typeMap + * @param string $fieldPath The field path to apply the root type to + * @return array + */ +function create_field_path_type_map(array $typeMap, $fieldPath) +{ + // If some field paths already exist, we prefix them with the field path we are assuming as the new root + if (isset($typeMap['fieldPaths']) && is_array($typeMap['fieldPaths'])) { + $fieldPaths = $typeMap['fieldPaths']; + + $typeMap['fieldPaths'] = []; + foreach ($fieldPaths as $existingFieldPath => $type) { + $typeMap['fieldPaths'][$fieldPath . '.' . $existingFieldPath] = $type; + } + } + + // If a root typemap was set, apply this to the field object + if (isset($typeMap['root'])) { + $typeMap['fieldPaths'][$fieldPath] = $typeMap['root']; + } + + /* Special case if we want to convert an array, in which case we need to + * ensure that the field containing the array is exposed as an array, + * instead of the type given in the type map's array key. */ + if (substr($fieldPath, -2, 2) === '.$') { + $typeMap['fieldPaths'][substr($fieldPath, 0, -2)] = 'array'; + } + + $typeMap['root'] = 'object'; + + return $typeMap; +} + +/** + * Execute a callback within a transaction in the given session + * + * This helper takes care of retrying the commit operation or the entire + * transaction if an error occurs. + * + * If the commit fails because of an UnknownTransactionCommitResult error, the + * commit is retried without re-invoking the callback. + * If the commit fails because of a TransientTransactionError, the entire + * transaction will be retried. In this case, the callback will be invoked + * again. It is important that the logic inside the callback is idempotent. + * + * In case of failures, the commit or transaction are retried until 120 seconds + * from the initial call have elapsed. After that, no retries will happen and + * the helper will throw the last exception received from the driver. + * + * @see Client::startSession + * @see Session::startTransaction for supported transaction options + * + * @param Session $session A session object as retrieved by Client::startSession + * @param callable $callback A callback that will be invoked within the transaction + * @param array $transactionOptions Additional options that are passed to Session::startTransaction + * @return void + * @throws RuntimeException for driver errors while committing the transaction + * @throws Exception for any other errors, including those thrown in the callback + */ +function with_transaction(Session $session, callable $callback, array $transactionOptions = []) +{ + $operation = new WithTransaction($callback, $transactionOptions); + $operation->execute($session); +} + +/** + * Returns the session option if it is set and valid. + * + * @internal + * @param array $options + * @return Session|null + */ +function extract_session_from_options(array $options) +{ + if (! isset($options['session']) || ! $options['session'] instanceof Session) { + return null; + } + + return $options['session']; +} + +/** + * Returns the readPreference option if it is set and valid. + * + * @internal + * @param array $options + * @return ReadPreference|null + */ +function extract_read_preference_from_options(array $options) +{ + if (! isset($options['readPreference']) || ! $options['readPreference'] instanceof ReadPreference) { + return null; + } + + return $options['readPreference']; +} + +/** + * Performs server selection, respecting the readPreference and session options + * (if given) + * + * @internal + * @return Server + */ +function select_server(Manager $manager, array $options) +{ + $session = extract_session_from_options($options); + if ($session instanceof Session && $session->getServer() !== null) { + return $session->getServer(); + } + + $readPreference = extract_read_preference_from_options($options); + if (! $readPreference instanceof ReadPreference) { + // TODO: PHPLIB-476: Read transaction read preference once PHPC-1439 is implemented + $readPreference = new ReadPreference(ReadPreference::RP_PRIMARY); + } + + return $manager->selectServer($readPreference); +} diff --git a/cache/stores/mongodb/readme_moodle.txt b/cache/stores/mongodb/readme_moodle.txt index 16b905a3370..33283da33a2 100644 --- a/cache/stores/mongodb/readme_moodle.txt +++ b/cache/stores/mongodb/readme_moodle.txt @@ -1,6 +1,6 @@ MongoDB PHP ----------- -Download from https://github.com/mongodb/mongo-php-library +Download from https://github.com/mongodb/mongo-php-library/releases Import procedure: @@ -8,8 +8,4 @@ Import procedure: - Copy the license file from the project root. - Update thirdpartylibs.xml with the latest version. -2019/03/14 ----------- -Last commit on download: aac8e54009196f6544e50baf9b63dcf0eab3bbdf - -This version (1.4.2) requires PHP mongodb extension >= 1.5 +This version (1.5.1) requires PHP mongodb extension >= 1.6.0 diff --git a/cache/stores/mongodb/thirdpartylibs.xml b/cache/stores/mongodb/thirdpartylibs.xml index afc8de0e7c4..f92af41ed07 100644 --- a/cache/stores/mongodb/thirdpartylibs.xml +++ b/cache/stores/mongodb/thirdpartylibs.xml @@ -4,7 +4,7 @@ MongoDB MongoDB PHP Library Apache - 1.4.2 + 1.5.1 2.0 \ No newline at end of file