Merge branch 'MDL-54592-master' of git://github.com/vmdef/moodle

This commit is contained in:
Adrian Greeve
2019-03-19 11:02:28 +08:00
84 changed files with 13445 additions and 82 deletions
+1
View File
@@ -6,6 +6,7 @@ vendor/
admin/tool/policy/amd/src/jquery-eu-cookie-law-popup.js
admin/tool/usertours/amd/src/tour.js
auth/cas/CAS/
cache/stores/mongodb/MongoDB/
enrol/lti/ims-blti/
filter/algebra/AlgParser.pm
filter/tex/mimetex.*
+1
View File
@@ -9,6 +9,7 @@ vendor/
admin/tool/policy/amd/src/jquery-eu-cookie-law-popup.js
admin/tool/usertours/amd/src/tour.js
auth/cas/CAS/
cache/stores/mongodb/MongoDB/
enrol/lti/ims-blti/
filter/algebra/AlgParser.pm
filter/tex/mimetex.*
+189
View File
@@ -0,0 +1,189 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB;
use MongoDB\Driver\WriteResult;
use MongoDB\Exception\BadMethodCallException;
/**
* Result class for a bulk write operation.
*/
class BulkWriteResult
{
private $writeResult;
private $insertedIds;
private $isAcknowledged;
/**
* Constructor.
*
* @param WriteResult $writeResult
* @param mixed[] $insertedIds
*/
public function __construct(WriteResult $writeResult, array $insertedIds)
{
$this->writeResult = $writeResult;
$this->insertedIds = $insertedIds;
$this->isAcknowledged = $writeResult->isAcknowledged();
}
/**
* Return the number of documents that were deleted.
*
* This method should only be called if the write was acknowledged.
*
* @see BulkWriteResult::isAcknowledged()
* @return integer
* @throws BadMethodCallException is the write result is unacknowledged
*/
public function getDeletedCount()
{
if ($this->isAcknowledged) {
return $this->writeResult->getDeletedCount();
}
throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
}
/**
* Return the number of documents that were inserted.
*
* This method should only be called if the write was acknowledged.
*
* @see BulkWriteResult::isAcknowledged()
* @return integer
* @throws BadMethodCallException is the write result is unacknowledged
*/
public function getInsertedCount()
{
if ($this->isAcknowledged) {
return $this->writeResult->getInsertedCount();
}
throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
}
/**
* Return a map of the inserted documents' IDs.
*
* The index of each ID in the map corresponds to each document's position
* in the bulk operation. If a document had an ID prior to inserting (i.e.
* the driver did not generate an ID), the index will contain its "_id"
* field value. Any driver-generated ID will be a MongoDB\BSON\ObjectId
* instance.
*
* @return mixed[]
*/
public function getInsertedIds()
{
return $this->insertedIds;
}
/**
* Return the number of documents that were matched by the filter.
*
* This method should only be called if the write was acknowledged.
*
* @see BulkWriteResult::isAcknowledged()
* @return integer
* @throws BadMethodCallException is the write result is unacknowledged
*/
public function getMatchedCount()
{
if ($this->isAcknowledged) {
return $this->writeResult->getMatchedCount();
}
throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
}
/**
* Return the number of documents that were modified.
*
* This value is undefined (i.e. null) if the write executed as a legacy
* operation instead of command.
*
* This method should only be called if the write was acknowledged.
*
* @see BulkWriteResult::isAcknowledged()
* @return integer|null
* @throws BadMethodCallException is the write result is unacknowledged
*/
public function getModifiedCount()
{
if ($this->isAcknowledged) {
return $this->writeResult->getModifiedCount();
}
throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
}
/**
* Return the number of documents that were upserted.
*
* This method should only be called if the write was acknowledged.
*
* @see BulkWriteResult::isAcknowledged()
* @return integer
* @throws BadMethodCallException is the write result is unacknowledged
*/
public function getUpsertedCount()
{
if ($this->isAcknowledged) {
return $this->writeResult->getUpsertedCount();
}
throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
}
/**
* Return a map of the upserted documents' IDs.
*
* The index of each ID in the map corresponds to each document's position
* in bulk operation. If a document had an ID prior to upserting (i.e. the
* server did not need to generate an ID), this will contain its "_id". Any
* server-generated ID will be a MongoDB\BSON\ObjectId instance.
*
* This method should only be called if the write was acknowledged.
*
* @see BulkWriteResult::isAcknowledged()
* @return mixed[]
* @throws BadMethodCallException is the write result is unacknowledged
*/
public function getUpsertedIds()
{
if ($this->isAcknowledged) {
return $this->writeResult->getUpsertedIds();
}
throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
}
/**
* Return whether this update was acknowledged by the server.
*
* If the update was not acknowledged, other fields from the WriteResult
* (e.g. matchedCount) will be undefined.
*
* @return boolean
*/
public function isAcknowledged()
{
return $this->isAcknowledged;
}
}
+228
View File
@@ -0,0 +1,228 @@
<?php
/*
* Copyright 2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB;
use MongoDB\BSON\Serializable;
use MongoDB\Driver\Cursor;
use MongoDB\Driver\Exception\ConnectionException;
use MongoDB\Driver\Exception\RuntimeException;
use MongoDB\Driver\Exception\ServerException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\ResumeTokenException;
use IteratorIterator;
use Iterator;
/**
* Iterator for a change stream.
*
* @api
* @see \MongoDB\Collection::watch()
* @see http://docs.mongodb.org/manual/reference/command/changeStream/
*/
class ChangeStream implements Iterator
{
/**
* @deprecated 1.4
* @todo Remove this in 2.0 (see: PHPLIB-360)
*/
const CURSOR_NOT_FOUND = 43;
private static $errorCodeCappedPositionLost = 136;
private static $errorCodeInterrupted = 11601;
private static $errorCodeCursorKilled = 237;
private $resumeToken;
private $resumeCallable;
private $csIt;
private $key = 0;
private $hasAdvanced = false;
/**
* Constructor.
*
* @internal
* @param Cursor $cursor
* @param callable $resumeCallable
*/
public function __construct(Cursor $cursor, callable $resumeCallable)
{
$this->resumeCallable = $resumeCallable;
$this->csIt = new IteratorIterator($cursor);
}
/**
* @see http://php.net/iterator.current
* @return mixed
*/
public function current()
{
return $this->csIt->current();
}
/**
* @return \MongoDB\Driver\CursorId
*/
public function getCursorId()
{
return $this->csIt->getInnerIterator()->getId();
}
/**
* @see http://php.net/iterator.key
* @return mixed
*/
public function key()
{
if ($this->valid()) {
return $this->key;
}
return null;
}
/**
* @see http://php.net/iterator.next
* @return void
*/
public function next()
{
try {
$this->csIt->next();
if ($this->valid()) {
if ($this->hasAdvanced) {
$this->key++;
}
$this->hasAdvanced = true;
$this->resumeToken = $this->extractResumeToken($this->csIt->current());
}
/* If the cursorId is 0, the server has invalidated the cursor so we
* will never perform another getMore. This means that we cannot
* resume and we can therefore unset the resumeCallable, which will
* free any reference to Watch. This will also free the only
* reference to an implicit session, since any such reference
* belongs to Watch. */
if ((string) $this->getCursorId() === '0') {
$this->resumeCallable = null;
}
} catch (RuntimeException $e) {
if ($this->isResumableError($e)) {
$this->resume();
}
}
}
/**
* @see http://php.net/iterator.rewind
* @return void
*/
public function rewind()
{
try {
$this->csIt->rewind();
if ($this->valid()) {
$this->hasAdvanced = true;
$this->resumeToken = $this->extractResumeToken($this->csIt->current());
}
// As with next(), free the callable once we know it will never be used.
if ((string) $this->getCursorId() === '0') {
$this->resumeCallable = null;
}
} catch (RuntimeException $e) {
if ($this->isResumableError($e)) {
$this->resume();
}
}
}
/**
* @see http://php.net/iterator.valid
* @return boolean
*/
public function valid()
{
return $this->csIt->valid();
}
/**
* Extracts the resume token (i.e. "_id" field) from the change document.
*
* @param array|document $document Change document
* @return mixed
* @throws InvalidArgumentException
* @throws ResumeTokenException if the resume token is not found or invalid
*/
private function extractResumeToken($document)
{
if ( ! is_array($document) && ! is_object($document)) {
throw InvalidArgumentException::invalidType('$document', $document, 'array or object');
}
if ($document instanceof Serializable) {
return $this->extractResumeToken($document->bsonSerialize());
}
$resumeToken = is_array($document)
? (isset($document['_id']) ? $document['_id'] : null)
: (isset($document->_id) ? $document->_id : null);
if ( ! isset($resumeToken)) {
throw ResumeTokenException::notFound();
}
if ( ! is_array($resumeToken) && ! is_object($resumeToken)) {
throw ResumeTokenException::invalidType($resumeToken);
}
return $resumeToken;
}
/**
* Determines if an exception is a resumable error.
*
* @see https://github.com/mongodb/specifications/blob/master/source/change-streams/change-streams.rst#resumable-error
* @param RuntimeException $exception
* @return boolean
*/
private function isResumableError(RuntimeException $exception)
{
if ($exception instanceof ConnectionException) {
return true;
}
if ( ! $exception instanceof ServerException) {
return false;
}
if (in_array($exception->getCode(), [self::$errorCodeCappedPositionLost, self::$errorCodeCursorKilled, self::$errorCodeInterrupted])) {
return false;
}
return true;
}
/**
* Creates a new changeStream after a resumable server error.
*
* @return void
*/
private function resume()
{
$newChangeStream = call_user_func($this->resumeCallable, $this->resumeToken);
$this->csIt = $newChangeStream->csIt;
$this->csIt->rewind();
}
}
+307
View File
@@ -0,0 +1,307 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB;
use MongoDB\Driver\Manager;
use MongoDB\Driver\ReadConcern;
use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Driver\Exception\InvalidArgumentException as DriverInvalidArgumentException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnexpectedValueException;
use MongoDB\Exception\UnsupportedException;
use MongoDB\Model\DatabaseInfoIterator;
use MongoDB\Operation\DropDatabase;
use MongoDB\Operation\ListDatabases;
use MongoDB\Operation\Watch;
class Client
{
private static $defaultTypeMap = [
'array' => 'MongoDB\Model\BSONArray',
'document' => 'MongoDB\Model\BSONDocument',
'root' => 'MongoDB\Model\BSONDocument',
];
private static $wireVersionForReadConcern = 4;
private static $wireVersionForWritableCommandWriteConcern = 5;
private $manager;
private $readConcern;
private $readPreference;
private $uri;
private $typeMap;
private $writeConcern;
/**
* Constructs a new Client instance.
*
* This is the preferred class for connecting to a MongoDB server or
* cluster of servers. It serves as a gateway for accessing individual
* databases and collections.
*
* Supported driver-specific options:
*
* * typeMap (array): Default type map for cursors and BSON documents.
*
* Other options are documented in MongoDB\Driver\Manager::__construct().
*
* @see http://docs.mongodb.org/manual/reference/connection-string/
* @see http://php.net/manual/en/mongodb-driver-manager.construct.php
* @see http://php.net/manual/en/mongodb.persistence.php#mongodb.persistence.typemaps
* @param string $uri MongoDB connection string
* @param array $uriOptions Additional connection string options
* @param array $driverOptions Driver-specific options
* @throws InvalidArgumentException for parameter/option parsing errors
* @throws DriverInvalidArgumentException for parameter/option parsing errors in the driver
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function __construct($uri = 'mongodb://127.0.0.1/', array $uriOptions = [], array $driverOptions = [])
{
$driverOptions += ['typeMap' => self::$defaultTypeMap];
if (isset($driverOptions['typeMap']) && ! is_array($driverOptions['typeMap'])) {
throw InvalidArgumentException::invalidType('"typeMap" driver option', $driverOptions['typeMap'], 'array');
}
$this->uri = (string) $uri;
$this->typeMap = isset($driverOptions['typeMap']) ? $driverOptions['typeMap'] : null;
unset($driverOptions['typeMap']);
$this->manager = new Manager($uri, $uriOptions, $driverOptions);
$this->readConcern = $this->manager->getReadConcern();
$this->readPreference = $this->manager->getReadPreference();
$this->writeConcern = $this->manager->getWriteConcern();
}
/**
* Return internal properties for debugging purposes.
*
* @see http://php.net/manual/en/language.oop5.magic.php#language.oop5.magic.debuginfo
* @return array
*/
public function __debugInfo()
{
return [
'manager' => $this->manager,
'uri' => $this->uri,
'typeMap' => $this->typeMap,
'writeConcern' => $this->writeConcern,
];
}
/**
* Select a database.
*
* Note: databases whose names contain special characters (e.g. "-") may
* be selected with complex syntax (e.g. $client->{"that-database"}) or
* {@link selectDatabase()}.
*
* @see http://php.net/oop5.overloading#object.get
* @see http://php.net/types.string#language.types.string.parsing.complex
* @param string $databaseName Name of the database to select
* @return Database
*/
public function __get($databaseName)
{
return $this->selectDatabase($databaseName);
}
/**
* Return the connection string (i.e. URI).
*
* @return string
*/
public function __toString()
{
return $this->uri;
}
/**
* Drop a database.
*
* @see DropDatabase::__construct() for supported options
* @param string $databaseName Database name
* @param array $options Additional options
* @return array|object Command result document
* @throws UnsupportedException if options are unsupported on the selected server
* @throws InvalidArgumentException for parameter/option parsing errors
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function dropDatabase($databaseName, array $options = [])
{
if ( ! isset($options['typeMap'])) {
$options['typeMap'] = $this->typeMap;
}
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) {
$options['writeConcern'] = $this->writeConcern;
}
$operation = new DropDatabase($databaseName, $options);
return $operation->execute($server);
}
/**
* Return the Manager.
*
* @return Manager
*/
public function getManager()
{
return $this->manager;
}
/**
* Return the read concern for this client.
*
* @see http://php.net/manual/en/mongodb-driver-readconcern.isdefault.php
* @return ReadConcern
*/
public function getReadConcern()
{
return $this->readConcern;
}
/**
* Return the read preference for this client.
*
* @return ReadPreference
*/
public function getReadPreference()
{
return $this->readPreference;
}
/**
* Return the type map for this client.
*
* @return array
*/
public function getTypeMap()
{
return $this->typeMap;
}
/**
* Return the write concern for this client.
*
* @see http://php.net/manual/en/mongodb-driver-writeconcern.isdefault.php
* @return WriteConcern
*/
public function getWriteConcern()
{
return $this->writeConcern;
}
/**
* List databases.
*
* @see ListDatabases::__construct() for supported options
* @return DatabaseInfoIterator
* @throws UnexpectedValueException if the command response was malformed
* @throws InvalidArgumentException for parameter/option parsing errors
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function listDatabases(array $options = [])
{
$operation = new ListDatabases($options);
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
return $operation->execute($server);
}
/**
* Select a collection.
*
* @see Collection::__construct() for supported options
* @param string $databaseName Name of the database containing the collection
* @param string $collectionName Name of the collection to select
* @param array $options Collection constructor options
* @return Collection
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function selectCollection($databaseName, $collectionName, array $options = [])
{
$options += ['typeMap' => $this->typeMap];
return new Collection($this->manager, $databaseName, $collectionName, $options);
}
/**
* Select a database.
*
* @see Database::__construct() for supported options
* @param string $databaseName Name of the database to select
* @param array $options Database constructor options
* @return Database
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function selectDatabase($databaseName, array $options = [])
{
$options += ['typeMap' => $this->typeMap];
return new Database($this->manager, $databaseName, $options);
}
/**
* Start a new client session.
*
* @see http://php.net/manual/en/mongodb-driver-manager.startsession.php
* @param array $options Session options
* @return MongoDB\Driver\Session
*/
public function startSession(array $options = [])
{
return $this->manager->startSession($options);
}
/**
* Create a change stream for watching changes to the cluster.
*
* @see Watch::__construct() for supported options
* @param array $pipeline List of pipeline operations
* @param array $options Command options
* @return ChangeStream
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function watch(array $pipeline = [], array $options = [])
{
if ( ! isset($options['readPreference'])) {
$options['readPreference'] = $this->readPreference;
}
$server = $this->manager->selectServer($options['readPreference']);
if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
$options['readConcern'] = $this->readConcern;
}
if ( ! isset($options['typeMap'])) {
$options['typeMap'] = $this->typeMap;
}
$operation = new Watch($this->manager, null, null, $pipeline, $options);
return $operation->execute($server);
}
}
+1092
View File
File diff suppressed because it is too large Load Diff
+463
View File
@@ -0,0 +1,463 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB;
use MongoDB\Collection;
use MongoDB\Driver\Cursor;
use MongoDB\Driver\Manager;
use MongoDB\Driver\ReadConcern;
use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
use MongoDB\GridFS\Bucket;
use MongoDB\Model\CollectionInfoIterator;
use MongoDB\Operation\CreateCollection;
use MongoDB\Operation\DatabaseCommand;
use MongoDB\Operation\DropCollection;
use MongoDB\Operation\DropDatabase;
use MongoDB\Operation\ListCollections;
use MongoDB\Operation\ModifyCollection;
use MongoDB\Operation\Watch;
class Database
{
private static $defaultTypeMap = [
'array' => 'MongoDB\Model\BSONArray',
'document' => 'MongoDB\Model\BSONDocument',
'root' => 'MongoDB\Model\BSONDocument',
];
private static $wireVersionForReadConcern = 4;
private static $wireVersionForWritableCommandWriteConcern = 5;
private $databaseName;
private $manager;
private $readConcern;
private $readPreference;
private $typeMap;
private $writeConcern;
/**
* Constructs new Database instance.
*
* This class provides methods for database-specific operations and serves
* as a gateway for accessing collections.
*
* Supported options:
*
* * readConcern (MongoDB\Driver\ReadConcern): The default read concern to
* use for database operations and selected collections. Defaults to the
* Manager's read concern.
*
* * readPreference (MongoDB\Driver\ReadPreference): The default read
* preference to use for database operations and selected collections.
* Defaults to the Manager's read preference.
*
* * typeMap (array): Default type map for cursors and BSON documents.
*
* * writeConcern (MongoDB\Driver\WriteConcern): The default write concern
* to use for database operations and selected collections. Defaults to
* the Manager's write concern.
*
* @param Manager $manager Manager instance from the driver
* @param string $databaseName Database name
* @param array $options Database options
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function __construct(Manager $manager, $databaseName, array $options = [])
{
if (strlen($databaseName) < 1) {
throw new InvalidArgumentException('$databaseName is invalid: ' . $databaseName);
}
if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern');
}
if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
}
if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
}
if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
}
$this->manager = $manager;
$this->databaseName = (string) $databaseName;
$this->readConcern = isset($options['readConcern']) ? $options['readConcern'] : $this->manager->getReadConcern();
$this->readPreference = isset($options['readPreference']) ? $options['readPreference'] : $this->manager->getReadPreference();
$this->typeMap = isset($options['typeMap']) ? $options['typeMap'] : self::$defaultTypeMap;
$this->writeConcern = isset($options['writeConcern']) ? $options['writeConcern'] : $this->manager->getWriteConcern();
}
/**
* Return internal properties for debugging purposes.
*
* @see http://php.net/manual/en/language.oop5.magic.php#language.oop5.magic.debuginfo
* @return array
*/
public function __debugInfo()
{
return [
'databaseName' => $this->databaseName,
'manager' => $this->manager,
'readConcern' => $this->readConcern,
'readPreference' => $this->readPreference,
'typeMap' => $this->typeMap,
'writeConcern' => $this->writeConcern,
];
}
/**
* Select a collection within this database.
*
* Note: collections whose names contain special characters (e.g. ".") may
* be selected with complex syntax (e.g. $database->{"system.profile"}) or
* {@link selectCollection()}.
*
* @see http://php.net/oop5.overloading#object.get
* @see http://php.net/types.string#language.types.string.parsing.complex
* @param string $collectionName Name of the collection to select
* @return Collection
*/
public function __get($collectionName)
{
return $this->selectCollection($collectionName);
}
/**
* Return the database name.
*
* @return string
*/
public function __toString()
{
return $this->databaseName;
}
/**
* Execute a command on this database.
*
* @see DatabaseCommand::__construct() for supported options
* @param array|object $command Command document
* @param array $options Options for command execution
* @return Cursor
* @throws InvalidArgumentException for parameter/option parsing errors
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function command($command, array $options = [])
{
if ( ! isset($options['readPreference'])) {
$options['readPreference'] = $this->readPreference;
}
if ( ! isset($options['typeMap'])) {
$options['typeMap'] = $this->typeMap;
}
$operation = new DatabaseCommand($this->databaseName, $command, $options);
$server = $this->manager->selectServer($options['readPreference']);
return $operation->execute($server);
}
/**
* Create a new collection explicitly.
*
* @see CreateCollection::__construct() for supported options
* @param string $collectionName
* @param array $options
* @return array|object Command result document
* @throws UnsupportedException if options are not supported by the selected server
* @throws InvalidArgumentException for parameter/option parsing errors
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function createCollection($collectionName, array $options = [])
{
if ( ! isset($options['typeMap'])) {
$options['typeMap'] = $this->typeMap;
}
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) {
$options['writeConcern'] = $this->writeConcern;
}
$operation = new CreateCollection($this->databaseName, $collectionName, $options);
return $operation->execute($server);
}
/**
* Drop this database.
*
* @see DropDatabase::__construct() for supported options
* @param array $options Additional options
* @return array|object Command result document
* @throws UnsupportedException if options are unsupported on the selected server
* @throws InvalidArgumentException for parameter/option parsing errors
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function drop(array $options = [])
{
if ( ! isset($options['typeMap'])) {
$options['typeMap'] = $this->typeMap;
}
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) {
$options['writeConcern'] = $this->writeConcern;
}
$operation = new DropDatabase($this->databaseName, $options);
return $operation->execute($server);
}
/**
* Drop a collection within this database.
*
* @see DropCollection::__construct() for supported options
* @param string $collectionName Collection name
* @param array $options Additional options
* @return array|object Command result document
* @throws UnsupportedException if options are unsupported on the selected server
* @throws InvalidArgumentException for parameter/option parsing errors
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function dropCollection($collectionName, array $options = [])
{
if ( ! isset($options['typeMap'])) {
$options['typeMap'] = $this->typeMap;
}
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) {
$options['writeConcern'] = $this->writeConcern;
}
$operation = new DropCollection($this->databaseName, $collectionName, $options);
return $operation->execute($server);
}
/**
* Returns the database name.
*
* @return string
*/
public function getDatabaseName()
{
return $this->databaseName;
}
/**
* Return the Manager.
*
* @return Manager
*/
public function getManager()
{
return $this->manager;
}
/**
* Return the read concern for this database.
*
* @see http://php.net/manual/en/mongodb-driver-readconcern.isdefault.php
* @return ReadConcern
*/
public function getReadConcern()
{
return $this->readConcern;
}
/**
* Return the read preference for this database.
*
* @return ReadPreference
*/
public function getReadPreference()
{
return $this->readPreference;
}
/**
* Return the type map for this database.
*
* @return array
*/
public function getTypeMap()
{
return $this->typeMap;
}
/**
* Return the write concern for this database.
*
* @see http://php.net/manual/en/mongodb-driver-writeconcern.isdefault.php
* @return WriteConcern
*/
public function getWriteConcern()
{
return $this->writeConcern;
}
/**
* Returns information for all collections in this database.
*
* @see ListCollections::__construct() for supported options
* @param array $options
* @return CollectionInfoIterator
* @throws InvalidArgumentException for parameter/option parsing errors
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function listCollections(array $options = [])
{
$operation = new ListCollections($this->databaseName, $options);
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
return $operation->execute($server);
}
/**
* Modifies a collection or view.
*
* @see ModifyCollection::__construct() for supported options
* @param string $collectionName Collection or view to modify
* @param array $collectionOptions Collection or view options to assign
* @param array $options Command options
* @throws InvalidArgumentException for parameter/option parsing errors
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function modifyCollection($collectionName, array $collectionOptions, array $options = [])
{
if ( ! isset($options['typeMap'])) {
$options['typeMap'] = $this->typeMap;
}
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) {
$options['writeConcern'] = $this->writeConcern;
}
$operation = new ModifyCollection($this->databaseName, $collectionName, $collectionOptions, $options);
return $operation->execute($server);
}
/**
* Select a collection within this database.
*
* @see Collection::__construct() for supported options
* @param string $collectionName Name of the collection to select
* @param array $options Collection constructor options
* @return Collection
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function selectCollection($collectionName, array $options = [])
{
$options += [
'readConcern' => $this->readConcern,
'readPreference' => $this->readPreference,
'typeMap' => $this->typeMap,
'writeConcern' => $this->writeConcern,
];
return new Collection($this->manager, $this->databaseName, $collectionName, $options);
}
/**
* Select a GridFS bucket within this database.
*
* @see Bucket::__construct() for supported options
* @param array $options Bucket constructor options
* @return Bucket
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function selectGridFSBucket(array $options = [])
{
$options += [
'readConcern' => $this->readConcern,
'readPreference' => $this->readPreference,
'typeMap' => $this->typeMap,
'writeConcern' => $this->writeConcern,
];
return new Bucket($this->manager, $this->databaseName, $options);
}
/**
* Create a change stream for watching changes to the database.
*
* @see Watch::__construct() for supported options
* @param array $pipeline List of pipeline operations
* @param array $options Command options
* @return ChangeStream
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function watch(array $pipeline = [], array $options = [])
{
if ( ! isset($options['readPreference'])) {
$options['readPreference'] = $this->readPreference;
}
$server = $this->manager->selectServer($options['readPreference']);
if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
$options['readConcern'] = $this->readConcern;
}
if ( ! isset($options['typeMap'])) {
$options['typeMap'] = $this->typeMap;
}
$operation = new Watch($this->manager, $this->databaseName, null, $pipeline, $options);
return $operation->execute($server);
}
/**
* Get a clone of this database with different options.
*
* @see Database::__construct() for supported options
* @param array $options Database constructor options
* @return Database
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function withOptions(array $options = [])
{
$options += [
'readConcern' => $this->readConcern,
'readPreference' => $this->readPreference,
'typeMap' => $this->typeMap,
'writeConcern' => $this->writeConcern,
];
return new Database($this->manager, $this->databaseName, $options);
}
}
+72
View File
@@ -0,0 +1,72 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB;
use MongoDB\Driver\WriteResult;
use MongoDB\Exception\BadMethodCallException;
/**
* Result class for a delete operation.
*/
class DeleteResult
{
private $writeResult;
private $isAcknowledged;
/**
* Constructor.
*
* @param WriteResult $writeResult
*/
public function __construct(WriteResult $writeResult)
{
$this->writeResult = $writeResult;
$this->isAcknowledged = $writeResult->isAcknowledged();
}
/**
* Return the number of documents that were deleted.
*
* This method should only be called if the write was acknowledged.
*
* @see DeleteResult::isAcknowledged()
* @return integer
* @throws BadMethodCallException is the write result is unacknowledged
*/
public function getDeletedCount()
{
if ($this->isAcknowledged) {
return $this->writeResult->getDeletedCount();
}
throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
}
/**
* Return whether this delete was acknowledged by the server.
*
* If the delete was not acknowledged, other fields from the WriteResult
* (e.g. deletedCount) will be undefined.
*
* @return boolean
*/
public function isAcknowledged()
{
return $this->isAcknowledged;
}
}
+43
View File
@@ -0,0 +1,43 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Exception;
class BadMethodCallException extends \BadMethodCallException implements Exception
{
/**
* Thrown when a mutable method is invoked on an immutable object.
*
* @param string $class Class name
* @return self
*/
public static function classIsImmutable($class)
{
return new static(sprintf('%s is immutable', $class));
}
/**
* Thrown when accessing a result field on an unacknowledged write result.
*
* @param string $method Method name
* @return self
*/
public static function unacknowledgedWriteResultAccess($method)
{
return new static(sprintf('%s should not be called for an unacknowledged write result', $method));
}
}
+22
View File
@@ -0,0 +1,22 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Exception;
interface Exception extends \MongoDB\Driver\Exception\Exception
{
}
+34
View File
@@ -0,0 +1,34 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Exception;
class InvalidArgumentException extends \MongoDB\Driver\Exception\InvalidArgumentException implements Exception
{
/**
* Thrown when an argument or option has an invalid type.
*
* @param string $name Name of the argument or option
* @param mixed $value Actual value (used to derive the type)
* @param string $expectedType Expected type
* @return self
*/
public static function invalidType($name, $value, $expectedType)
{
return new static(sprintf('Expected %s to have type "%s" but found "%s"', $name, $expectedType, is_object($value) ? get_class($value) : gettype($value)));
}
}
+42
View File
@@ -0,0 +1,42 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Exception;
class ResumeTokenException extends \Exception
{
/**
* Thrown when a resume token has an invalid type.
*
* @param mixed $value Actual value (used to derive the type)
* @return self
*/
public static function invalidType($value)
{
return new static(sprintf('Expected resume token to have type "array or object" but found "%s"', gettype($value)));
}
/**
* Thrown when a resume token is not found in a change document.
*
* @return self
*/
public static function notFound()
{
return new static('Resume token not found in change document');
}
}
+22
View File
@@ -0,0 +1,22 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Exception;
class RuntimeException extends \MongoDB\Driver\Exception\RuntimeException implements Exception
{
}
+22
View File
@@ -0,0 +1,22 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Exception;
class UnexpectedValueException extends \MongoDB\Driver\Exception\UnexpectedValueException implements Exception
{
}
+71
View File
@@ -0,0 +1,71 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Exception;
class UnsupportedException extends RuntimeException
{
/**
* Thrown when array filters are not supported by a server.
*
* @return self
*/
public static function arrayFiltersNotSupported()
{
return new static('Array filters are not supported by the server executing this operation');
}
/**
* Thrown when collations are not supported by a server.
*
* @return self
*/
public static function collationNotSupported()
{
return new static('Collations are not supported by the server executing this operation');
}
/**
* Thrown when explain is not supported by a server.
*
* @return self
*/
public static function explainNotSupported()
{
return new static('Explain is not supported by the server executing this operation');
}
/**
* Thrown when a command's readConcern option is not supported by a server.
*
* @return self
*/
public static function readConcernNotSupported()
{
return new static('Read concern is not supported by the server executing this command');
}
/**
* Thrown when a command's writeConcern option is not supported by a server.
*
* @return self
*/
public static function writeConcernNotSupported()
{
return new static('Write concern is not supported by the server executing this command');
}
}
+677
View File
@@ -0,0 +1,677 @@
<?php
/*
* Copyright 2016-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\GridFS;
use MongoDB\Collection;
use MongoDB\Driver\Cursor;
use MongoDB\Driver\Manager;
use MongoDB\Driver\ReadConcern;
use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\GridFS\Exception\CorruptFileException;
use MongoDB\GridFS\Exception\FileNotFoundException;
use MongoDB\Operation\Find;
use stdClass;
/**
* Bucket provides a public API for interacting with the GridFS files and chunks
* collections.
*
* @api
*/
class Bucket
{
private static $defaultBucketName = 'fs';
private static $defaultChunkSizeBytes = 261120;
private static $defaultTypeMap = [
'array' => 'MongoDB\Model\BSONArray',
'document' => 'MongoDB\Model\BSONDocument',
'root' => 'MongoDB\Model\BSONDocument',
];
private static $streamWrapperProtocol = 'gridfs';
private $collectionWrapper;
private $databaseName;
private $manager;
private $bucketName;
private $disableMD5;
private $chunkSizeBytes;
private $readConcern;
private $readPreference;
private $typeMap;
private $writeConcern;
/**
* Constructs a GridFS bucket.
*
* Supported options:
*
* * bucketName (string): The bucket name, which will be used as a prefix
* for the files and chunks collections. Defaults to "fs".
*
* * chunkSizeBytes (integer): The chunk size in bytes. Defaults to
* 261120 (i.e. 255 KiB).
*
* * disableMD5 (boolean): When true, no MD5 sum will be generated for
* each stored file. Defaults to "false".
*
* * readConcern (MongoDB\Driver\ReadConcern): Read concern.
*
* * readPreference (MongoDB\Driver\ReadPreference): Read preference.
*
* * typeMap (array): Default type map for cursors and BSON documents.
*
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
*
* @param Manager $manager Manager instance from the driver
* @param string $databaseName Database name
* @param array $options Bucket options
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function __construct(Manager $manager, $databaseName, array $options = [])
{
$options += [
'bucketName' => self::$defaultBucketName,
'chunkSizeBytes' => self::$defaultChunkSizeBytes,
'disableMD5' => false,
];
if (isset($options['bucketName']) && ! is_string($options['bucketName'])) {
throw InvalidArgumentException::invalidType('"bucketName" option', $options['bucketName'], 'string');
}
if (isset($options['chunkSizeBytes']) && ! is_integer($options['chunkSizeBytes'])) {
throw InvalidArgumentException::invalidType('"chunkSizeBytes" option', $options['chunkSizeBytes'], 'integer');
}
if (isset($options['chunkSizeBytes']) && $options['chunkSizeBytes'] < 1) {
throw new InvalidArgumentException(sprintf('Expected "chunkSizeBytes" option to be >= 1, %d given', $options['chunkSizeBytes']));
}
if (isset($options['disableMD5']) && ! is_bool($options['disableMD5'])) {
throw InvalidArgumentException::invalidType('"disableMD5" option', $options['disableMD5'], 'boolean');
}
if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern');
}
if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
}
if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
}
if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
}
$this->manager = $manager;
$this->databaseName = (string) $databaseName;
$this->bucketName = $options['bucketName'];
$this->chunkSizeBytes = $options['chunkSizeBytes'];
$this->disableMD5 = $options['disableMD5'];
$this->readConcern = isset($options['readConcern']) ? $options['readConcern'] : $this->manager->getReadConcern();
$this->readPreference = isset($options['readPreference']) ? $options['readPreference'] : $this->manager->getReadPreference();
$this->typeMap = isset($options['typeMap']) ? $options['typeMap'] : self::$defaultTypeMap;
$this->writeConcern = isset($options['writeConcern']) ? $options['writeConcern'] : $this->manager->getWriteConcern();
$collectionOptions = array_intersect_key($options, ['readConcern' => 1, 'readPreference' => 1, 'typeMap' => 1, 'writeConcern' => 1]);
$this->collectionWrapper = new CollectionWrapper($manager, $databaseName, $options['bucketName'], $collectionOptions);
$this->registerStreamWrapper();
}
/**
* Return internal properties for debugging purposes.
*
* @see http://php.net/manual/en/language.oop5.magic.php#language.oop5.magic.debuginfo
* @return array
*/
public function __debugInfo()
{
return [
'bucketName' => $this->bucketName,
'databaseName' => $this->databaseName,
'manager' => $this->manager,
'chunkSizeBytes' => $this->chunkSizeBytes,
'readConcern' => $this->readConcern,
'readPreference' => $this->readPreference,
'typeMap' => $this->typeMap,
'writeConcern' => $this->writeConcern,
];
}
/**
* Delete a file from the GridFS bucket.
*
* If the files collection document is not found, this method will still
* attempt to delete orphaned chunks.
*
* @param mixed $id File ID
* @throws FileNotFoundException if no file could be selected
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function delete($id)
{
$file = $this->collectionWrapper->findFileById($id);
$this->collectionWrapper->deleteFileAndChunksById($id);
if ($file === null) {
throw FileNotFoundException::byId($id, $this->getFilesNamespace());
}
}
/**
* Writes the contents of a GridFS file to a writable stream.
*
* @param mixed $id File ID
* @param resource $destination Writable Stream
* @throws FileNotFoundException if no file could be selected
* @throws InvalidArgumentException if $destination is not a stream
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function downloadToStream($id, $destination)
{
if ( ! is_resource($destination) || get_resource_type($destination) != "stream") {
throw InvalidArgumentException::invalidType('$destination', $destination, 'resource');
}
stream_copy_to_stream($this->openDownloadStream($id), $destination);
}
/**
* Writes the contents of a GridFS file, which is selected by name and
* revision, to a writable stream.
*
* Supported options:
*
* * revision (integer): Which revision (i.e. documents with the same
* filename and different uploadDate) of the file to retrieve. Defaults
* to -1 (i.e. the most recent revision).
*
* Revision numbers are defined as follows:
*
* * 0 = the original stored file
* * 1 = the first revision
* * 2 = the second revision
* * etc…
* * -2 = the second most recent revision
* * -1 = the most recent revision
*
* @param string $filename Filename
* @param resource $destination Writable Stream
* @param array $options Download options
* @throws FileNotFoundException if no file could be selected
* @throws InvalidArgumentException if $destination is not a stream
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function downloadToStreamByName($filename, $destination, array $options = [])
{
if ( ! is_resource($destination) || get_resource_type($destination) != "stream") {
throw InvalidArgumentException::invalidType('$destination', $destination, 'resource');
}
stream_copy_to_stream($this->openDownloadStreamByName($filename, $options), $destination);
}
/**
* Drops the files and chunks collections associated with this GridFS
* bucket.
*
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function drop()
{
$this->collectionWrapper->dropCollections();
}
/**
* Finds documents from the GridFS bucket's files collection matching the
* query.
*
* @see Find::__construct() for supported options
* @param array|object $filter Query by which to filter documents
* @param array $options Additional options
* @return Cursor
* @throws UnsupportedException if options are not supported by the selected server
* @throws InvalidArgumentException for parameter/option parsing errors
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function find($filter = [], array $options = [])
{
return $this->collectionWrapper->findFiles($filter, $options);
}
/**
* Finds a single document from the GridFS bucket's files collection
* matching the query.
*
* @see FindOne::__construct() for supported options
* @param array|object $filter Query by which to filter documents
* @param array $options Additional options
* @return array|object|null
* @throws UnsupportedException if options are not supported by the selected server
* @throws InvalidArgumentException for parameter/option parsing errors
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function findOne($filter = [], array $options = [])
{
return $this->collectionWrapper->findOneFile($filter, $options);
}
/**
* Return the bucket name.
*
* @return string
*/
public function getBucketName()
{
return $this->bucketName;
}
/**
* Return the chunks collection.
*
* @return Collection
*/
public function getChunksCollection()
{
return $this->collectionWrapper->getChunksCollection();
}
/**
* Return the chunk size in bytes.
*
* @return integer
*/
public function getChunkSizeBytes()
{
return $this->chunkSizeBytes;
}
/**
* Return the database name.
*
* @return string
*/
public function getDatabaseName()
{
return $this->databaseName;
}
/**
* Gets the file document of the GridFS file associated with a stream.
*
* @param resource $stream GridFS stream
* @return array|object
* @throws InvalidArgumentException if $stream is not a GridFS stream
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function getFileDocumentForStream($stream)
{
$file = $this->getRawFileDocumentForStream($stream);
// Filter the raw document through the specified type map
return \MongoDB\apply_type_map_to_document($file, $this->typeMap);
}
/**
* Gets the file document's ID of the GridFS file associated with a stream.
*
* @param resource $stream GridFS stream
* @return mixed
* @throws CorruptFileException if the file "_id" field does not exist
* @throws InvalidArgumentException if $stream is not a GridFS stream
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function getFileIdForStream($stream)
{
$file = $this->getRawFileDocumentForStream($stream);
/* Filter the raw document through the specified type map, but override
* the root type so we can reliably access the ID.
*/
$typeMap = ['root' => 'stdClass'] + $this->typeMap;
$file = \MongoDB\apply_type_map_to_document($file, $typeMap);
if ( ! isset($file->_id) && ! property_exists($file, '_id')) {
throw new CorruptFileException('file._id does not exist');
}
return $file->_id;
}
/**
* Return the files collection.
*
* @return Collection
*/
public function getFilesCollection()
{
return $this->collectionWrapper->getFilesCollection();
}
/**
* Return the read concern for this GridFS bucket.
*
* @see http://php.net/manual/en/mongodb-driver-readconcern.isdefault.php
* @return ReadConcern
*/
public function getReadConcern()
{
return $this->readConcern;
}
/**
* Return the read preference for this GridFS bucket.
*
* @return ReadPreference
*/
public function getReadPreference()
{
return $this->readPreference;
}
/**
* Return the type map for this GridFS bucket.
*
* @return array
*/
public function getTypeMap()
{
return $this->typeMap;
}
/**
* Return the write concern for this GridFS bucket.
*
* @see http://php.net/manual/en/mongodb-driver-writeconcern.isdefault.php
* @return WriteConcern
*/
public function getWriteConcern()
{
return $this->writeConcern;
}
/**
* Opens a readable stream for reading a GridFS file.
*
* @param mixed $id File ID
* @return resource
* @throws FileNotFoundException if no file could be selected
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function openDownloadStream($id)
{
$file = $this->collectionWrapper->findFileById($id);
if ($file === null) {
throw FileNotFoundException::byId($id, $this->getFilesNamespace());
}
return $this->openDownloadStreamByFile($file);
}
/**
* Opens a readable stream stream to read a GridFS file, which is selected
* by name and revision.
*
* Supported options:
*
* * revision (integer): Which revision (i.e. documents with the same
* filename and different uploadDate) of the file to retrieve. Defaults
* to -1 (i.e. the most recent revision).
*
* Revision numbers are defined as follows:
*
* * 0 = the original stored file
* * 1 = the first revision
* * 2 = the second revision
* * etc…
* * -2 = the second most recent revision
* * -1 = the most recent revision
*
* @param string $filename Filename
* @param array $options Download options
* @return resource
* @throws FileNotFoundException if no file could be selected
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function openDownloadStreamByName($filename, array $options = [])
{
$options += ['revision' => -1];
$file = $this->collectionWrapper->findFileByFilenameAndRevision($filename, $options['revision']);
if ($file === null) {
throw FileNotFoundException::byFilenameAndRevision($filename, $options['revision'], $this->getFilesNamespace());
}
return $this->openDownloadStreamByFile($file);
}
/**
* Opens a writable stream for writing a GridFS file.
*
* Supported options:
*
* * _id (mixed): File document identifier. Defaults to a new ObjectId.
*
* * chunkSizeBytes (integer): The chunk size in bytes. Defaults to the
* bucket's chunk size.
*
* * disableMD5 (boolean): When true, no MD5 sum will be generated for
* the stored file. Defaults to "false".
*
* * metadata (document): User data for the "metadata" field of the files
* collection document.
*
* @param string $filename Filename
* @param array $options Upload options
* @return resource
*/
public function openUploadStream($filename, array $options = [])
{
$options += ['chunkSizeBytes' => $this->chunkSizeBytes];
$path = $this->createPathForUpload();
$context = stream_context_create([
self::$streamWrapperProtocol => [
'collectionWrapper' => $this->collectionWrapper,
'filename' => $filename,
'options' => $options,
],
]);
return fopen($path, 'w', false, $context);
}
/**
* Renames the GridFS file with the specified ID.
*
* @param mixed $id File ID
* @param string $newFilename New filename
* @throws FileNotFoundException if no file could be selected
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function rename($id, $newFilename)
{
$updateResult = $this->collectionWrapper->updateFilenameForId($id, $newFilename);
if ($updateResult->getModifiedCount() === 1) {
return;
}
/* If the update resulted in no modification, it's possible that the
* file did not exist, in which case we must raise an error. Checking
* the write result's matched count will be most efficient, but fall
* back to a findOne operation if necessary (i.e. legacy writes).
*/
$found = $updateResult->getMatchedCount() !== null
? $updateResult->getMatchedCount() === 1
: $this->collectionWrapper->findFileById($id) !== null;
if ( ! $found) {
throw FileNotFoundException::byId($id, $this->getFilesNamespace());
}
}
/**
* Writes the contents of a readable stream to a GridFS file.
*
* Supported options:
*
* * _id (mixed): File document identifier. Defaults to a new ObjectId.
*
* * chunkSizeBytes (integer): The chunk size in bytes. Defaults to the
* bucket's chunk size.
*
* * disableMD5 (boolean): When true, no MD5 sum will be generated for
* the stored file. Defaults to "false".
*
* * metadata (document): User data for the "metadata" field of the files
* collection document.
*
* @param string $filename Filename
* @param resource $source Readable stream
* @param array $options Stream options
* @return mixed ID of the newly created GridFS file
* @throws InvalidArgumentException if $source is not a GridFS stream
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function uploadFromStream($filename, $source, array $options = [])
{
if ( ! is_resource($source) || get_resource_type($source) != "stream") {
throw InvalidArgumentException::invalidType('$source', $source, 'resource');
}
$destination = $this->openUploadStream($filename, $options);
stream_copy_to_stream($source, $destination);
return $this->getFileIdForStream($destination);
}
/**
* Creates a path for an existing GridFS file.
*
* @param stdClass $file GridFS file document
* @return string
*/
private function createPathForFile(stdClass $file)
{
if ( ! is_object($file->_id) || method_exists($file->_id, '__toString')) {
$id = (string) $file->_id;
} else {
$id = \MongoDB\BSON\toJSON(\MongoDB\BSON\fromPHP(['_id' => $file->_id]));
}
return sprintf(
'%s://%s/%s.files/%s',
self::$streamWrapperProtocol,
urlencode($this->databaseName),
urlencode($this->bucketName),
urlencode($id)
);
}
/**
* Creates a path for a new GridFS file, which does not yet have an ID.
*
* @return string
*/
private function createPathForUpload()
{
return sprintf(
'%s://%s/%s.files',
self::$streamWrapperProtocol,
urlencode($this->databaseName),
urlencode($this->bucketName)
);
}
/**
* Returns the names of the files collection.
*
* @return string
*/
private function getFilesNamespace()
{
return sprintf('%s.%s.files', $this->databaseName, $this->bucketName);
}
/**
* Gets the file document of the GridFS file associated with a stream.
*
* This returns the raw document from the StreamWrapper, which does not
* respect the Bucket's type map.
*
* @param resource $stream GridFS stream
* @return stdClass
* @throws InvalidArgumentException
*/
private function getRawFileDocumentForStream($stream)
{
if ( ! is_resource($stream) || get_resource_type($stream) != "stream") {
throw InvalidArgumentException::invalidType('$stream', $stream, 'resource');
}
$metadata = stream_get_meta_data($stream);
if ( ! isset ($metadata['wrapper_data']) || ! $metadata['wrapper_data'] instanceof StreamWrapper) {
throw InvalidArgumentException::invalidType('$stream wrapper data', isset($metadata['wrapper_data']) ? $metadata['wrapper_data'] : null, 'MongoDB\Driver\GridFS\StreamWrapper');
}
return $metadata['wrapper_data']->getFile();
}
/**
* Opens a readable stream for the GridFS file.
*
* @param stdClass $file GridFS file document
* @return resource
*/
private function openDownloadStreamByFile(stdClass $file)
{
$path = $this->createPathForFile($file);
$context = stream_context_create([
self::$streamWrapperProtocol => [
'collectionWrapper' => $this->collectionWrapper,
'file' => $file,
],
]);
return fopen($path, 'r', false, $context);
}
/**
* Registers the GridFS stream wrapper if it is not already registered.
*/
private function registerStreamWrapper()
{
if (in_array(self::$streamWrapperProtocol, stream_get_wrappers())) {
return;
}
StreamWrapper::register(self::$streamWrapperProtocol);
}
}
+338
View File
@@ -0,0 +1,338 @@
<?php
/*
* Copyright 2016-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\GridFS;
use MongoDB\Collection;
use MongoDB\UpdateResult;
use MongoDB\Driver\Cursor;
use MongoDB\Driver\Manager;
use MongoDB\Driver\ReadPreference;
use stdClass;
/**
* CollectionWrapper abstracts the GridFS files and chunks collections.
*
* @internal
*/
class CollectionWrapper
{
private $bucketName;
private $chunksCollection;
private $databaseName;
private $checkedIndexes = false;
private $filesCollection;
/**
* Constructs a GridFS collection wrapper.
*
* @see Collection::__construct() for supported options
* @param Manager $manager Manager instance from the driver
* @param string $databaseName Database name
* @param string $bucketName Bucket name
* @param array $collectionOptions Collection options
* @throws InvalidArgumentException
*/
public function __construct(Manager $manager, $databaseName, $bucketName, array $collectionOptions = [])
{
$this->databaseName = (string) $databaseName;
$this->bucketName = (string) $bucketName;
$this->filesCollection = new Collection($manager, $databaseName, sprintf('%s.files', $bucketName), $collectionOptions);
$this->chunksCollection = new Collection($manager, $databaseName, sprintf('%s.chunks', $bucketName), $collectionOptions);
}
/**
* Deletes all GridFS chunks for a given file ID.
*
* @param mixed $id
*/
public function deleteChunksByFilesId($id)
{
$this->chunksCollection->deleteMany(['files_id' => $id]);
}
/**
* Deletes a GridFS file and related chunks by ID.
*
* @param mixed $id
*/
public function deleteFileAndChunksById($id)
{
$this->filesCollection->deleteOne(['_id' => $id]);
$this->chunksCollection->deleteMany(['files_id' => $id]);
}
/**
* Drops the GridFS files and chunks collections.
*/
public function dropCollections()
{
$this->filesCollection->drop(['typeMap' => []]);
$this->chunksCollection->drop(['typeMap' => []]);
}
/**
* Finds GridFS chunk documents for a given file ID and optional offset.
*
* @param mixed $id File ID
* @param integer $fromChunk Starting chunk (inclusive)
* @return Cursor
*/
public function findChunksByFileId($id, $fromChunk = 0)
{
return $this->chunksCollection->find(
[
'files_id' => $id,
'n' => ['$gte' => $fromChunk],
],
[
'sort' => ['n' => 1],
'typeMap' => ['root' => 'stdClass'],
]
);
}
/**
* Finds a GridFS file document for a given filename and revision.
*
* Revision numbers are defined as follows:
*
* * 0 = the original stored file
* * 1 = the first revision
* * 2 = the second revision
* * etc…
* * -2 = the second most recent revision
* * -1 = the most recent revision
*
* @see Bucket::downloadToStreamByName()
* @see Bucket::openDownloadStreamByName()
* @param string $filename
* @param integer $revision
* @return stdClass|null
*/
public function findFileByFilenameAndRevision($filename, $revision)
{
$filename = (string) $filename;
$revision = (integer) $revision;
if ($revision < 0) {
$skip = abs($revision) - 1;
$sortOrder = -1;
} else {
$skip = $revision;
$sortOrder = 1;
}
return $this->filesCollection->findOne(
['filename' => $filename],
[
'skip' => $skip,
'sort' => ['uploadDate' => $sortOrder],
'typeMap' => ['root' => 'stdClass'],
]
);
}
/**
* Finds a GridFS file document for a given ID.
*
* @param mixed $id
* @return stdClass|null
*/
public function findFileById($id)
{
return $this->filesCollection->findOne(
['_id' => $id],
['typeMap' => ['root' => 'stdClass']]
);
}
/**
* Finds documents from the GridFS bucket's files collection.
*
* @see Find::__construct() for supported options
* @param array|object $filter Query by which to filter documents
* @param array $options Additional options
* @return Cursor
*/
public function findFiles($filter, array $options = [])
{
return $this->filesCollection->find($filter, $options);
}
/**
* Finds a single document from the GridFS bucket's files collection.
*
* @param array|object $filter Query by which to filter documents
* @param array $options Additional options
* @return array|object|null
*/
public function findOneFile($filter, array $options = [])
{
return $this->filesCollection->findOne($filter, $options);
}
/**
* Return the bucket name.
*
* @return string
*/
public function getBucketName()
{
return $this->bucketName;
}
/**
* Return the chunks collection.
*
* @return Collection
*/
public function getChunksCollection()
{
return $this->chunksCollection;
}
/**
* Return the database name.
*
* @return string
*/
public function getDatabaseName()
{
return $this->databaseName;
}
/**
* Return the files collection.
*
* @return Collection
*/
public function getFilesCollection()
{
return $this->filesCollection;
}
/**
* Inserts a document into the chunks collection.
*
* @param array|object $chunk Chunk document
*/
public function insertChunk($chunk)
{
if ( ! $this->checkedIndexes) {
$this->ensureIndexes();
}
$this->chunksCollection->insertOne($chunk);
}
/**
* Inserts a document into the files collection.
*
* The file document should be inserted after all chunks have been inserted.
*
* @param array|object $file File document
*/
public function insertFile($file)
{
if ( ! $this->checkedIndexes) {
$this->ensureIndexes();
}
$this->filesCollection->insertOne($file);
}
/**
* Updates the filename field in the file document for a given ID.
*
* @param mixed $id
* @param string $filename
* @return UpdateResult
*/
public function updateFilenameForId($id, $filename)
{
return $this->filesCollection->updateOne(
['_id' => $id],
['$set' => ['filename' => (string) $filename]]
);
}
/**
* Create an index on the chunks collection if it does not already exist.
*/
private function ensureChunksIndex()
{
foreach ($this->chunksCollection->listIndexes() as $index) {
if ($index->isUnique() && $index->getKey() === ['files_id' => 1, 'n' => 1]) {
return;
}
}
$this->chunksCollection->createIndex(['files_id' => 1, 'n' => 1], ['unique' => true]);
}
/**
* Create an index on the files collection if it does not already exist.
*/
private function ensureFilesIndex()
{
foreach ($this->filesCollection->listIndexes() as $index) {
if ($index->getKey() === ['filename' => 1, 'uploadDate' => 1]) {
return;
}
}
$this->filesCollection->createIndex(['filename' => 1, 'uploadDate' => 1]);
}
/**
* Ensure indexes on the files and chunks collections exist.
*
* This method is called once before the first write operation on a GridFS
* bucket. Indexes are only be created if the files collection is empty.
*/
private function ensureIndexes()
{
if ($this->checkedIndexes) {
return;
}
$this->checkedIndexes = true;
if ( ! $this->isFilesCollectionEmpty()) {
return;
}
$this->ensureFilesIndex();
$this->ensureChunksIndex();
}
/**
* Returns whether the files collection is empty.
*
* @return boolean
*/
private function isFilesCollectionEmpty()
{
return null === $this->filesCollection->findOne([], [
'readPreference' => new ReadPreference(ReadPreference::RP_PRIMARY),
'projection' => ['_id' => 1],
'typeMap' => [],
]);
}
}
+58
View File
@@ -0,0 +1,58 @@
<?php
/*
* Copyright 2016-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\GridFS\Exception;
use MongoDB\Exception\RuntimeException;
class CorruptFileException extends RuntimeException
{
/**
* Thrown when a chunk is not found for an expected index.
*
* @param integer $expectedIndex Expected index number
* @return self
*/
public static function missingChunk($expectedIndex)
{
return new static(sprintf('Chunk not found for index "%d"', $expectedIndex));
}
/**
* Thrown when a chunk has an unexpected index number.
*
* @param integer $index Actual index number (i.e. "n" field)
* @param integer $expectedIndex Expected index number
* @return self
*/
public static function unexpectedIndex($index, $expectedIndex)
{
return new static(sprintf('Expected chunk to have index "%d" but found "%d"', $expectedIndex, $index));
}
/**
* Thrown when a chunk has an unexpected data size.
*
* @param integer $size Actual size (i.e. "data" field length)
* @param integer $expectedSize Expected size
* @return self
*/
public static function unexpectedSize($size, $expectedSize)
{
return new static(sprintf('Expected chunk to have size "%d" but found "%d"', $expectedSize, $size));
}
}
+50
View File
@@ -0,0 +1,50 @@
<?php
/*
* Copyright 2016-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\GridFS\Exception;
use MongoDB\Exception\RuntimeException;
class FileNotFoundException extends RuntimeException
{
/**
* Thrown when a file cannot be found by its filename and revision.
*
* @param string $filename Filename
* @param integer $revision Revision
* @param string $namespace Namespace for the files collection
* @return self
*/
public static function byFilenameAndRevision($filename, $revision, $namespace)
{
return new static(sprintf('File with name "%s" and revision "%d" not found in "%s"', $filename, $revision, $namespace));
}
/**
* Thrown when a file cannot be found by its ID.
*
* @param mixed $id File ID
* @param string $namespace Namespace for the files collection
* @return self
*/
public static function byId($id, $namespace)
{
$json = \MongoDB\BSON\toJSON(\MongoDB\BSON\fromPHP(['_id' => $id]));
return new static(sprintf('File "%s" not found in "%s"', $json, $namespace));
}
}
+296
View File
@@ -0,0 +1,296 @@
<?php
/*
* Copyright 2016-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\GridFS;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\GridFS\Exception\CorruptFileException;
use IteratorIterator;
use stdClass;
/**
* ReadableStream abstracts the process of reading a GridFS file.
*
* @internal
*/
class ReadableStream
{
private $buffer;
private $bufferOffset = 0;
private $chunkSize;
private $chunkOffset = 0;
private $chunksIterator;
private $collectionWrapper;
private $expectedLastChunkSize = 0;
private $file;
private $length;
private $numChunks = 0;
/**
* Constructs a readable GridFS stream.
*
* @param CollectionWrapper $collectionWrapper GridFS collection wrapper
* @param stdClass $file GridFS file document
* @throws CorruptFileException
*/
public function __construct(CollectionWrapper $collectionWrapper, stdClass $file)
{
if ( ! isset($file->chunkSize) || ! is_integer($file->chunkSize) || $file->chunkSize < 1) {
throw new CorruptFileException('file.chunkSize is not an integer >= 1');
}
if ( ! isset($file->length) || ! is_integer($file->length) || $file->length < 0) {
throw new CorruptFileException('file.length is not an integer > 0');
}
if ( ! isset($file->_id) && ! property_exists($file, '_id')) {
throw new CorruptFileException('file._id does not exist');
}
$this->file = $file;
$this->chunkSize = (integer) $file->chunkSize;
$this->length = (integer) $file->length;
$this->collectionWrapper = $collectionWrapper;
if ($this->length > 0) {
$this->numChunks = (integer) ceil($this->length / $this->chunkSize);
$this->expectedLastChunkSize = ($this->length - (($this->numChunks - 1) * $this->chunkSize));
}
}
/**
* Return internal properties for debugging purposes.
*
* @see http://php.net/manual/en/language.oop5.magic.php#language.oop5.magic.debuginfo
* @return array
*/
public function __debugInfo()
{
return [
'bucketName' => $this->collectionWrapper->getBucketName(),
'databaseName' => $this->collectionWrapper->getDatabaseName(),
'file' => $this->file,
];
}
public function close()
{
// Nothing to do
}
/**
* Return the stream's file document.
*
* @return stdClass
*/
public function getFile()
{
return $this->file;
}
/**
* Return the stream's size in bytes.
*
* @return integer
*/
public function getSize()
{
return $this->length;
}
/**
* Return whether the current read position is at the end of the stream.
*
* @return boolean
*/
public function isEOF()
{
if ($this->chunkOffset === $this->numChunks - 1) {
return $this->bufferOffset >= $this->expectedLastChunkSize;
}
return $this->chunkOffset >= $this->numChunks;
}
/**
* Read bytes from the stream.
*
* Note: this method may return a string smaller than the requested length
* if data is not available to be read.
*
* @param integer $length Number of bytes to read
* @return string
* @throws InvalidArgumentException if $length is negative
*/
public function readBytes($length)
{
if ($length < 0) {
throw new InvalidArgumentException(sprintf('$length must be >= 0; given: %d', $length));
}
if ($this->chunksIterator === null) {
$this->initChunksIterator();
}
if ($this->buffer === null && ! $this->initBufferFromCurrentChunk()) {
return '';
}
$data = '';
while (strlen($data) < $length) {
if ($this->bufferOffset >= strlen($this->buffer) && ! $this->initBufferFromNextChunk()) {
break;
}
$initialDataLength = strlen($data);
$data .= substr($this->buffer, $this->bufferOffset, $length - $initialDataLength);
$this->bufferOffset += strlen($data) - $initialDataLength;
}
return $data;
}
/**
* Seeks the chunk and buffer offsets for the next read operation.
*
* @param integer $offset
* @throws InvalidArgumentException if $offset is out of range
*/
public function seek($offset)
{
if ($offset < 0 || $offset > $this->file->length) {
throw new InvalidArgumentException(sprintf('$offset must be >= 0 and <= %d; given: %d', $this->file->length, $offset));
}
/* Compute the offsets for the chunk and buffer (i.e. chunk data) from
* which we will expect to read after seeking. If the chunk offset
* changed, we'll also need to reset the buffer.
*/
$lastChunkOffset = $this->chunkOffset;
$this->chunkOffset = (integer) floor($offset / $this->chunkSize);
$this->bufferOffset = $offset % $this->chunkSize;
if ($lastChunkOffset === $this->chunkOffset) {
return;
}
if ($this->chunksIterator === null) {
return;
}
// Clear the buffer since the current chunk will be changed
$this->buffer = null;
/* If we are seeking to a previous chunk, we need to reinitialize the
* chunk iterator.
*/
if ($lastChunkOffset > $this->chunkOffset) {
$this->chunksIterator = null;
return;
}
/* If we are seeking to a subsequent chunk, we do not need to
* reinitalize the chunk iterator. Instead, we can simply move forward
* to $this->chunkOffset.
*/
$numChunks = $this->chunkOffset - $lastChunkOffset;
for ($i = 0; $i < $numChunks; $i++) {
$this->chunksIterator->next();
}
}
/**
* Return the current position of the stream.
*
* This is the offset within the stream where the next byte would be read.
*
* @return integer
*/
public function tell()
{
return ($this->chunkOffset * $this->chunkSize) + $this->bufferOffset;
}
/**
* Initialize the buffer to the current chunk's data.
*
* @return boolean Whether there was a current chunk to read
* @throws CorruptFileException if an expected chunk could not be read successfully
*/
private function initBufferFromCurrentChunk()
{
if ($this->chunkOffset === 0 && $this->numChunks === 0) {
return false;
}
if ( ! $this->chunksIterator->valid()) {
throw CorruptFileException::missingChunk($this->chunkOffset);
}
$currentChunk = $this->chunksIterator->current();
if ($currentChunk->n !== $this->chunkOffset) {
throw CorruptFileException::unexpectedIndex($currentChunk->n, $this->chunkOffset);
}
$this->buffer = $currentChunk->data->getData();
$actualChunkSize = strlen($this->buffer);
$expectedChunkSize = ($this->chunkOffset === $this->numChunks - 1)
? $this->expectedLastChunkSize
: $this->chunkSize;
if ($actualChunkSize !== $expectedChunkSize) {
throw CorruptFileException::unexpectedSize($actualChunkSize, $expectedChunkSize);
}
return true;
}
/**
* Advance to the next chunk and initialize the buffer to its data.
*
* @return boolean Whether there was a next chunk to read
* @throws CorruptFileException if an expected chunk could not be read successfully
*/
private function initBufferFromNextChunk()
{
if ($this->chunkOffset === $this->numChunks - 1) {
return false;
}
$this->bufferOffset = 0;
$this->chunkOffset++;
$this->chunksIterator->next();
return $this->initBufferFromCurrentChunk();
}
/**
* Initializes the chunk iterator starting from the current offset.
*/
private function initChunksIterator()
{
$cursor = $this->collectionWrapper->findChunksByFileId($this->file->_id, $this->chunkOffset);
$this->chunksIterator = new IteratorIterator($cursor);
$this->chunksIterator->rewind();
}
}
+308
View File
@@ -0,0 +1,308 @@
<?php
/*
* Copyright 2016-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\GridFS;
use MongoDB\BSON\UTCDateTime;
use Exception;
use stdClass;
/**
* Stream wrapper for reading and writing a GridFS file.
*
* @internal
* @see Bucket::openUploadStream()
* @see Bucket::openDownloadStream()
*/
class StreamWrapper
{
/**
* @var resource|null Stream context (set by PHP)
*/
public $context;
private $mode;
private $protocol;
private $stream;
/**
* Return the stream's file document.
*
* @return stdClass
*/
public function getFile()
{
return $this->stream->getFile();
}
/**
* Register the GridFS stream wrapper.
*
* @param string $protocol Protocol to use for stream_wrapper_register()
*/
public static function register($protocol = 'gridfs')
{
if (in_array($protocol, stream_get_wrappers())) {
stream_wrapper_unregister($protocol);
}
stream_wrapper_register($protocol, get_called_class(), \STREAM_IS_URL);
}
/**
* Closes the stream.
*
* @see http://php.net/manual/en/streamwrapper.stream-close.php
*/
public function stream_close()
{
$this->stream->close();
}
/**
* Returns whether the file pointer is at the end of the stream.
*
* @see http://php.net/manual/en/streamwrapper.stream-eof.php
* @return boolean
*/
public function stream_eof()
{
if ( ! $this->stream instanceof ReadableStream) {
return false;
}
return $this->stream->isEOF();
}
/**
* Opens the stream.
*
* @see http://php.net/manual/en/streamwrapper.stream-open.php
* @param string $path Path to the file resource
* @param string $mode Mode used to open the file (only "r" and "w" are supported)
* @param integer $options Additional flags set by the streams API
* @param string $openedPath Not used
*/
public function stream_open($path, $mode, $options, &$openedPath)
{
$this->initProtocol($path);
$this->mode = $mode;
if ($mode === 'r') {
return $this->initReadableStream();
}
if ($mode === 'w') {
return $this->initWritableStream();
}
return false;
}
/**
* Read bytes from the stream.
*
* Note: this method may return a string smaller than the requested length
* if data is not available to be read.
*
* @see http://php.net/manual/en/streamwrapper.stream-read.php
* @param integer $length Number of bytes to read
* @return string
*/
public function stream_read($length)
{
if ( ! $this->stream instanceof ReadableStream) {
return '';
}
try {
return $this->stream->readBytes($length);
} catch (Exception $e) {
trigger_error(sprintf('%s: %s', get_class($e), $e->getMessage()), \E_USER_WARNING);
return false;
}
}
/**
* Return the current position of the stream.
*
* @see http://php.net/manual/en/streamwrapper.stream-seek.php
* @param integer $offset Stream offset to seek to
* @param integer $whence One of SEEK_SET, SEEK_CUR, or SEEK_END
* @return boolean True if the position was updated and false otherwise
*/
public function stream_seek($offset, $whence = \SEEK_SET)
{
$size = $this->stream->getSize();
if ($whence === \SEEK_CUR) {
$offset += $this->stream->tell();
}
if ($whence === \SEEK_END) {
$offset += $size;
}
// WritableStreams are always positioned at the end of the stream
if ($this->stream instanceof WritableStream) {
return $offset === $size;
}
if ($offset < 0 || $offset > $size) {
return false;
}
$this->stream->seek($offset);
return true;
}
/**
* Return information about the stream.
*
* @see http://php.net/manual/en/streamwrapper.stream-stat.php
* @return array
*/
public function stream_stat()
{
$stat = $this->getStatTemplate();
$stat[2] = $stat['mode'] = $this->stream instanceof ReadableStream
? 0100444 // S_IFREG & S_IRUSR & S_IRGRP & S_IROTH
: 0100222; // S_IFREG & S_IWUSR & S_IWGRP & S_IWOTH
$stat[7] = $stat['size'] = $this->stream->getSize();
$file = $this->stream->getFile();
if (isset($file->uploadDate) && $file->uploadDate instanceof UTCDateTime) {
$timestamp = $file->uploadDate->toDateTime()->getTimestamp();
$stat[9] = $stat['mtime'] = $timestamp;
$stat[10] = $stat['ctime'] = $timestamp;
}
if (isset($file->chunkSize) && is_integer($file->chunkSize)) {
$stat[11] = $stat['blksize'] = $file->chunkSize;
}
return $stat;
}
/**
* Return the current position of the stream.
*
* @see http://php.net/manual/en/streamwrapper.stream-tell.php
* @return integer The current position of the stream
*/
public function stream_tell()
{
return $this->stream->tell();
}
/**
* Write bytes to the stream.
*
* @see http://php.net/manual/en/streamwrapper.stream-write.php
* @param string $data Data to write
* @return integer The number of bytes written
*/
public function stream_write($data)
{
if ( ! $this->stream instanceof WritableStream) {
return 0;
}
try {
return $this->stream->writeBytes($data);
} catch (Exception $e) {
trigger_error(sprintf('%s: %s', get_class($e), $e->getMessage()), \E_USER_WARNING);
return false;
}
}
/**
* Returns a stat template with default values.
*
* @return array
*/
private function getStatTemplate()
{
return [
0 => 0, 'dev' => 0,
1 => 0, 'ino' => 0,
2 => 0, 'mode' => 0,
3 => 0, 'nlink' => 0,
4 => 0, 'uid' => 0,
5 => 0, 'gid' => 0,
6 => -1, 'rdev' => -1,
7 => 0, 'size' => 0,
8 => 0, 'atime' => 0,
9 => 0, 'mtime' => 0,
10 => 0, 'ctime' => 0,
11 => -1, 'blksize' => -1,
12 => -1, 'blocks' => -1,
];
}
/**
* Initialize the protocol from the given path.
*
* @see StreamWrapper::stream_open()
* @param string $path
*/
private function initProtocol($path)
{
$parts = explode('://', $path, 2);
$this->protocol = $parts[0] ?: 'gridfs';
}
/**
* Initialize the internal stream for reading.
*
* @see StreamWrapper::stream_open()
* @return boolean
*/
private function initReadableStream()
{
$context = stream_context_get_options($this->context);
$this->stream = new ReadableStream(
$context[$this->protocol]['collectionWrapper'],
$context[$this->protocol]['file']
);
return true;
}
/**
* Initialize the internal stream for writing.
*
* @see StreamWrapper::stream_open()
* @return boolean
*/
private function initWritableStream()
{
$context = stream_context_get_options($this->context);
$this->stream = new WritableStream(
$context[$this->protocol]['collectionWrapper'],
$context[$this->protocol]['filename'],
$context[$this->protocol]['options']
);
return true;
}
}
+283
View File
@@ -0,0 +1,283 @@
<?php
/*
* Copyright 2016-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\GridFS;
use MongoDB\BSON\Binary;
use MongoDB\BSON\ObjectId;
use MongoDB\BSON\UTCDateTime;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use stdClass;
/**
* WritableStream abstracts the process of writing a GridFS file.
*
* @internal
*/
class WritableStream
{
private static $defaultChunkSizeBytes = 261120;
private $buffer = '';
private $chunkOffset = 0;
private $chunkSize;
private $disableMD5;
private $collectionWrapper;
private $file;
private $hashCtx;
private $isClosed = false;
private $length = 0;
/**
* Constructs a writable GridFS stream.
*
* Supported options:
*
* * _id (mixed): File document identifier. Defaults to a new ObjectId.
*
* * aliases (array of strings): DEPRECATED An array of aliases.
* Applications wishing to store aliases should add an aliases field to
* the metadata document instead.
*
* * chunkSizeBytes (integer): The chunk size in bytes. Defaults to
* 261120 (i.e. 255 KiB).
*
* * disableMD5 (boolean): When true, no MD5 sum will be generated.
* Defaults to "false".
*
* * contentType (string): DEPRECATED content type to be stored with the
* file. This information should now be added to the metadata.
*
* * metadata (document): User data for the "metadata" field of the files
* collection document.
*
* @param CollectionWrapper $collectionWrapper GridFS collection wrapper
* @param string $filename Filename
* @param array $options Upload options
* @throws InvalidArgumentException
*/
public function __construct(CollectionWrapper $collectionWrapper, $filename, array $options = [])
{
$options += [
'_id' => new ObjectId,
'chunkSizeBytes' => self::$defaultChunkSizeBytes,
'disableMD5' => false,
];
if (isset($options['aliases']) && ! \MongoDB\is_string_array($options['aliases'])) {
throw InvalidArgumentException::invalidType('"aliases" option', $options['aliases'], 'array of strings');
}
if (isset($options['chunkSizeBytes']) && ! is_integer($options['chunkSizeBytes'])) {
throw InvalidArgumentException::invalidType('"chunkSizeBytes" option', $options['chunkSizeBytes'], 'integer');
}
if (isset($options['chunkSizeBytes']) && $options['chunkSizeBytes'] < 1) {
throw new InvalidArgumentException(sprintf('Expected "chunkSizeBytes" option to be >= 1, %d given', $options['chunkSizeBytes']));
}
if (isset($options['disableMD5']) && ! is_bool($options['disableMD5'])) {
throw InvalidArgumentException::invalidType('"disableMD5" option', $options['disableMD5'], 'boolean');
}
if (isset($options['contentType']) && ! is_string($options['contentType'])) {
throw InvalidArgumentException::invalidType('"contentType" option', $options['contentType'], 'string');
}
if (isset($options['metadata']) && ! is_array($options['metadata']) && ! is_object($options['metadata'])) {
throw InvalidArgumentException::invalidType('"metadata" option', $options['metadata'], 'array or object');
}
$this->chunkSize = $options['chunkSizeBytes'];
$this->collectionWrapper = $collectionWrapper;
$this->disableMD5 = $options['disableMD5'];
if ( ! $this->disableMD5) {
$this->hashCtx = hash_init('md5');
}
$this->file = [
'_id' => $options['_id'],
'chunkSize' => $this->chunkSize,
'filename' => (string) $filename,
] + array_intersect_key($options, ['aliases' => 1, 'contentType' => 1, 'metadata' => 1]);
}
/**
* Return internal properties for debugging purposes.
*
* @see http://php.net/manual/en/language.oop5.magic.php#language.oop5.magic.debuginfo
* @return array
*/
public function __debugInfo()
{
return [
'bucketName' => $this->collectionWrapper->getBucketName(),
'databaseName' => $this->collectionWrapper->getDatabaseName(),
'file' => $this->file,
];
}
/**
* Closes an active stream and flushes all buffered data to GridFS.
*/
public function close()
{
if ($this->isClosed) {
// TODO: Should this be an error condition? e.g. BadMethodCallException
return;
}
if (strlen($this->buffer) > 0) {
$this->insertChunkFromBuffer();
}
$this->fileCollectionInsert();
$this->isClosed = true;
}
/**
* Return the stream's file document.
*
* @return stdClass
*/
public function getFile()
{
return (object) $this->file;
}
/**
* Return the stream's size in bytes.
*
* Note: this value will increase as more data is written to the stream.
*
* @return integer
*/
public function getSize()
{
return $this->length + strlen($this->buffer);
}
/**
* Return the current position of the stream.
*
* This is the offset within the stream where the next byte would be
* written. Since seeking is not supported and writes are appended, this is
* always the end of the stream.
*
* @see WritableStream::getSize()
* @return integer
*/
public function tell()
{
return $this->getSize();
}
/**
* Inserts binary data into GridFS via chunks.
*
* Data will be buffered internally until chunkSizeBytes are accumulated, at
* which point a chunk document will be inserted and the buffer reset.
*
* @param string $data Binary data to write
* @return integer
*/
public function writeBytes($data)
{
if ($this->isClosed) {
// TODO: Should this be an error condition? e.g. BadMethodCallException
return;
}
$bytesRead = 0;
while ($bytesRead != strlen($data)) {
$initialBufferLength = strlen($this->buffer);
$this->buffer .= substr($data, $bytesRead, $this->chunkSize - $initialBufferLength);
$bytesRead += strlen($this->buffer) - $initialBufferLength;
if (strlen($this->buffer) == $this->chunkSize) {
$this->insertChunkFromBuffer();
}
}
return $bytesRead;
}
private function abort()
{
try {
$this->collectionWrapper->deleteChunksByFilesId($this->file['_id']);
} catch (DriverRuntimeException $e) {
// We are already handling an error if abort() is called, so suppress this
}
$this->isClosed = true;
}
private function fileCollectionInsert()
{
$this->file['length'] = $this->length;
$this->file['uploadDate'] = new UTCDateTime;
if ( ! $this->disableMD5) {
$this->file['md5'] = hash_final($this->hashCtx);
}
try {
$this->collectionWrapper->insertFile($this->file);
} catch (DriverRuntimeException $e) {
$this->abort();
throw $e;
}
return $this->file['_id'];
}
private function insertChunkFromBuffer()
{
if (strlen($this->buffer) == 0) {
return;
}
$data = $this->buffer;
$this->buffer = '';
$chunk = [
'files_id' => $this->file['_id'],
'n' => $this->chunkOffset,
'data' => new Binary($data, Binary::TYPE_GENERIC),
];
if ( ! $this->disableMD5) {
hash_update($this->hashCtx, $data);
}
try {
$this->collectionWrapper->insertChunk($chunk);
} catch (DriverRuntimeException $e) {
$this->abort();
throw $e;
}
$this->length += strlen($data);
$this->chunkOffset++;
}
}
+91
View File
@@ -0,0 +1,91 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB;
use MongoDB\Driver\WriteResult;
use MongoDB\Exception\BadMethodCallException;
/**
* Result class for a multi-document insert operation.
*/
class InsertManyResult
{
private $writeResult;
private $insertedIds;
private $isAcknowledged;
/**
* Constructor.
*
* @param WriteResult $writeResult
* @param mixed[] $insertedIds
*/
public function __construct(WriteResult $writeResult, array $insertedIds)
{
$this->writeResult = $writeResult;
$this->insertedIds = $insertedIds;
$this->isAcknowledged = $writeResult->isAcknowledged();
}
/**
* Return the number of documents that were inserted.
*
* This method should only be called if the write was acknowledged.
*
* @see InsertManyResult::isAcknowledged()
* @return integer
* @throws BadMethodCallException is the write result is unacknowledged
*/
public function getInsertedCount()
{
if ($this->isAcknowledged) {
return $this->writeResult->getInsertedCount();
}
throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
}
/**
* Return a map of the inserted documents' IDs.
*
* The index of each ID in the map corresponds to each document's position
* in the bulk operation. If a document had an ID prior to inserting (i.e.
* the driver did not generate an ID), the index will contain its "_id"
* field value. Any driver-generated ID will be a MongoDB\BSON\ObjectId
* instance.
*
* @return mixed[]
*/
public function getInsertedIds()
{
return $this->insertedIds;
}
/**
* Return whether this insert result was acknowledged by the server.
*
* If the insert was not acknowledged, other fields from the WriteResult
* (e.g. insertedCount) will be undefined.
*
* @return boolean
*/
public function isAcknowledged()
{
return $this->writeResult->isAcknowledged();
}
}
+93
View File
@@ -0,0 +1,93 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB;
use MongoDB\Driver\WriteResult;
use MongoDB\Exception\BadMethodCallException;
/**
* Result class for a single-document insert operation.
*/
class InsertOneResult
{
private $writeResult;
private $insertedId;
private $isAcknowledged;
/**
* Constructor.
*
* @param WriteResult $writeResult
* @param mixed $insertedId
*/
public function __construct(WriteResult $writeResult, $insertedId)
{
$this->writeResult = $writeResult;
$this->insertedId = $insertedId;
$this->isAcknowledged = $writeResult->isAcknowledged();
}
/**
* Return the number of documents that were inserted.
*
* This method should only be called if the write was acknowledged.
*
* @see InsertOneResult::isAcknowledged()
* @return integer
* @throws BadMethodCallException is the write result is unacknowledged
*/
public function getInsertedCount()
{
if ($this->isAcknowledged) {
return $this->writeResult->getInsertedCount();
}
throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
}
/**
* Return the inserted document's ID.
*
* If the document had an ID prior to inserting (i.e. the driver did not
* need to generate an ID), this will contain its "_id". Any
* driver-generated ID will be a MongoDB\BSON\ObjectId instance.
*
* @return mixed
*/
public function getInsertedId()
{
return $this->insertedId;
}
/**
* Return whether this insert was acknowledged by the server.
*
* If the insert was not acknowledged, other fields from the WriteResult
* (e.g. insertedCount) will be undefined.
*
* If the insert was not acknowledged, other fields from the WriteResult
* (e.g. insertedCount) will be undefined and their getter methods should
* not be invoked.
*
* @return boolean
*/
public function isAcknowledged()
{
return $this->writeResult->isAcknowledged();
}
}
+100
View File
@@ -0,0 +1,100 @@
<?php
/*
* Copyright 2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB;
use IteratorAggregate;
use stdClass;
use Traversable;
/**
* Result class for mapReduce command results.
*
* This class allows for iteration of mapReduce results irrespective of the
* output method (e.g. inline, collection) via the IteratorAggregate interface.
* It also provides access to command statistics.
*
* @api
* @see \MongoDB\Collection::mapReduce()
* @see https://docs.mongodb.com/manual/reference/command/mapReduce/
*/
class MapReduceResult implements IteratorAggregate
{
private $getIterator;
private $executionTimeMS;
private $counts;
private $timing;
/**
* Constructor.
*
* @internal
* @param callable $getIterator Callback that returns a Traversable for mapReduce results
* @param stdClass $result Result document from the mapReduce command
*/
public function __construct(callable $getIterator, stdClass $result)
{
$this->getIterator = $getIterator;
$this->executionTimeMS = (integer) $result->timeMillis;
$this->counts = (array) $result->counts;
$this->timing = isset($result->timing) ? (array) $result->timing : [];
}
/**
* Returns various count statistics from the mapReduce command.
*
* @return array
*/
public function getCounts()
{
return $this->counts;
}
/**
* Return the command execution time in milliseconds.
*
* @return integer
*/
public function getExecutionTimeMS()
{
return $this->executionTimeMS;
}
/**
* Return the mapReduce results as a Traversable.
*
* @see http://php.net/iteratoraggregate.getiterator
* @return Traversable
*/
public function getIterator()
{
return call_user_func($this->getIterator);
}
/**
* Returns various timing statistics from the mapReduce command.
*
* Note: timing statistics are only available if the mapReduce command's
* "verbose" option was true; otherwise, an empty array will be returned.
*
* @return array
*/
public function getTiming()
{
return $this->timing;
}
}
+99
View File
@@ -0,0 +1,99 @@
<?php
/*
* Copyright 2016-present MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Model;
use MongoDB\BSON\Serializable;
use MongoDB\BSON\Unserializable;
use ArrayObject;
use JsonSerializable;
/**
* Model class for a BSON array.
*
* The internal data will be filtered through array_values() during BSON
* serialization to ensure that it becomes a BSON array.
*
* @api
*/
class BSONArray extends ArrayObject implements JsonSerializable, Serializable, Unserializable
{
/**
* Clone this BSONArray.
*/
public function __clone()
{
foreach ($this as $key => $value) {
$this[$key] = \MongoDB\recursive_copy($value);
}
}
/**
* Factory method for var_export().
*
* @see http://php.net/oop5.magic#object.set-state
* @see http://php.net/var-export
* @param array $properties
* @return self
*/
public static function __set_state(array $properties)
{
$array = new static;
$array->exchangeArray($properties);
return $array;
}
/**
* Serialize the array to BSON.
*
* The array data will be numerically reindexed to ensure that it is stored
* as a BSON array.
*
* @see http://php.net/mongodb-bson-serializable.bsonserialize
* @return array
*/
public function bsonSerialize()
{
return array_values($this->getArrayCopy());
}
/**
* Unserialize the document to BSON.
*
* @see http://php.net/mongodb-bson-unserializable.bsonunserialize
* @param array $data Array data
*/
public function bsonUnserialize(array $data)
{
self::__construct($data);
}
/**
* Serialize the array to JSON.
*
* The array data will be numerically reindexed to ensure that it is stored
* as a JSON array.
*
* @see http://php.net/jsonserializable.jsonserialize
* @return array
*/
public function jsonSerialize()
{
return array_values($this->getArrayCopy());
}
}
+106
View File
@@ -0,0 +1,106 @@
<?php
/*
* Copyright 2016-present MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Model;
use MongoDB\BSON\Serializable;
use MongoDB\BSON\Unserializable;
use ArrayObject;
use JsonSerializable;
/**
* Model class for a BSON document.
*
* The internal data will be cast to an object during BSON serialization to
* ensure that it becomes a BSON document.
*
* @api
*/
class BSONDocument extends ArrayObject implements JsonSerializable, Serializable, Unserializable
{
/**
* Deep clone this BSONDocument.
*/
public function __clone()
{
foreach ($this as $key => $value) {
$this[$key] = \MongoDB\recursive_copy($value);
}
}
/**
* Constructor.
*
* This overrides the parent constructor to allow property access of entries
* by default.
*
* @see http://php.net/arrayobject.construct
*/
public function __construct($input = [], $flags = ArrayObject::ARRAY_AS_PROPS, $iterator_class = 'ArrayIterator')
{
parent::__construct($input, $flags, $iterator_class);
}
/**
* Factory method for var_export().
*
* @see http://php.net/oop5.magic#object.set-state
* @see http://php.net/var-export
* @param array $properties
* @return self
*/
public static function __set_state(array $properties)
{
$document = new static;
$document->exchangeArray($properties);
return $document;
}
/**
* Serialize the document to BSON.
*
* @see http://php.net/mongodb-bson-serializable.bsonserialize
* @return object
*/
public function bsonSerialize()
{
return (object) $this->getArrayCopy();
}
/**
* Unserialize the document to BSON.
*
* @see http://php.net/mongodb-bson-unserializable.bsonunserialize
* @param array $data Array data
*/
public function bsonUnserialize(array $data)
{
parent::__construct($data, ArrayObject::ARRAY_AS_PROPS);
}
/**
* Serialize the array to JSON.
*
* @see http://php.net/jsonserializable.jsonserialize
* @return object
*/
public function jsonSerialize()
{
return (object) $this->getArrayCopy();
}
}
+135
View File
@@ -0,0 +1,135 @@
<?php
/*
* Copyright 2018 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Model;
use MongoDB\Exception\UnexpectedValueException;
use MongoDB\Model\BSONDocument;
use Iterator;
/**
* Iterator for BSON documents.
*/
class BSONIterator implements Iterator
{
private static $bsonSize = 4;
private $buffer;
private $bufferLength;
private $current;
private $key = 0;
private $position = 0;
private $options;
/**
* Constructs a BSON Iterator.
*
* Supported options:
*
* * typeMap (array): Type map for BSON deserialization.
*
* @internal
* @see http://php.net/manual/en/function.mongodb.bson-tophp.php
* @param string $data Concatenated, valid, BSON-encoded documents
* @param array $options Iterator options
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function __construct($data, array $options = [])
{
if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
}
if ( ! isset($options['typeMap'])) {
$options['typeMap'] = [];
}
$this->buffer = $data;
$this->bufferLength = strlen($data);
$this->options = $options;
}
/**
* @see http://php.net/iterator.current
* @return mixed
*/
public function current()
{
return $this->current;
}
/**
* @see http://php.net/iterator.key
* @return mixed
*/
public function key()
{
return $this->key;
}
/**
* @see http://php.net/iterator.next
* @return void
*/
public function next()
{
$this->key++;
$this->current = null;
$this->advance();
}
/**
* @see http://php.net/iterator.rewind
* @return void
*/
public function rewind()
{
$this->key = 0;
$this->position = 0;
$this->current = null;
$this->advance();
}
/**
* @see http://php.net/iterator.valid
* @return boolean
*/
public function valid()
{
return $this->current !== null;
}
private function advance()
{
if ($this->position === $this->bufferLength) {
return;
}
if (($this->bufferLength - $this->position) < self::$bsonSize) {
throw new UnexpectedValueException(sprintf('Expected at least %d bytes; %d remaining', self::$bsonSize, $this->bufferLength - $this->position));
}
list(,$documentLength) = unpack('V', substr($this->buffer, $this->position, self::$bsonSize));
if (($this->bufferLength - $this->position) < $documentLength) {
throw new UnexpectedValueException(sprintf('Expected %d bytes; %d remaining', $documentLength, $this->bufferLength - $this->position));
}
$this->current = \MongoDB\BSON\toPHP(substr($this->buffer, $this->position, $documentLength), $this->options['typeMap']);
$this->position += $documentLength;
}
}
+165
View File
@@ -0,0 +1,165 @@
<?php
/*
* Copyright 2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Model;
use Countable;
use Generator;
use Iterator;
use Traversable;
/**
* Iterator for wrapping a Traversable and caching its results.
*
* By caching results, this iterators allows a Traversable to be counted and
* rewound multiple times, even if the wrapped object does not natively support
* those operations (e.g. MongoDB\Driver\Cursor).
*
* @internal
*/
class CachingIterator implements Countable, Iterator
{
private $items = [];
private $iterator;
private $iteratorAdvanced = false;
private $iteratorExhausted = false;
/**
* Constructor.
*
* Initialize the iterator and stores the first item in the cache. This
* effectively rewinds the Traversable and the wrapping Generator, which
* will execute up to its first yield statement. Additionally, this mimics
* behavior of the SPL iterators and allows users to omit an explicit call
* to rewind() before using the other methods.
*
* @param Traversable $traversable
*/
public function __construct(Traversable $traversable)
{
$this->iterator = $this->wrapTraversable($traversable);
$this->storeCurrentItem();
}
/**
* @see http://php.net/countable.count
* @return integer
*/
public function count()
{
$this->exhaustIterator();
return count($this->items);
}
/**
* @see http://php.net/iterator.current
* @return mixed
*/
public function current()
{
return current($this->items);
}
/**
* @see http://php.net/iterator.key
* @return mixed
*/
public function key()
{
return key($this->items);
}
/**
* @see http://php.net/iterator.next
* @return void
*/
public function next()
{
if ( ! $this->iteratorExhausted) {
$this->iterator->next();
$this->storeCurrentItem();
}
next($this->items);
}
/**
* @see http://php.net/iterator.rewind
* @return void
*/
public function rewind()
{
/* If the iterator has advanced, exhaust it now so that future iteration
* can rely on the cache.
*/
if ($this->iteratorAdvanced) {
$this->exhaustIterator();
}
reset($this->items);
}
/**
* @see http://php.net/iterator.valid
* @return boolean
*/
public function valid()
{
return $this->key() !== null;
}
/**
* Ensures that the inner iterator is fully consumed and cached.
*/
private function exhaustIterator()
{
while ( ! $this->iteratorExhausted) {
$this->next();
}
}
/**
* Stores the current item in the cache.
*/
private function storeCurrentItem()
{
$key = $this->iterator->key();
if ($key === null) {
return;
}
$this->items[$key] = $this->iterator->current();
}
/**
* Wraps the Traversable with a Generator.
*
* @param Traversable $traversable
* @return Generator
*/
private function wrapTraversable(Traversable $traversable)
{
foreach ($traversable as $key => $value) {
yield $key => $value;
$this->iteratorAdvanced = true;
}
$this->iteratorExhausted = true;
}
}
+154
View File
@@ -0,0 +1,154 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Model;
use MongoDB\Exception\BadMethodCallException;
use ArrayAccess;
/**
* Collection information model class.
*
* This class models the collection information returned by the listCollections
* command or, for legacy servers, queries on the "system.namespaces"
* collection. It provides methods to access options for the collection.
*
* @api
* @see \MongoDB\Database::listCollections()
* @see https://github.com/mongodb/specifications/blob/master/source/enumerate-collections.rst
*/
class CollectionInfo implements ArrayAccess
{
private $info;
/**
* Constructor.
*
* @param array $info Collection info
*/
public function __construct(array $info)
{
$this->info = $info;
}
/**
* Return the collection info as an array.
*
* @see http://php.net/oop5.magic#language.oop5.magic.debuginfo
* @return array
*/
public function __debugInfo()
{
return $this->info;
}
/**
* Return the maximum number of documents to keep in the capped collection.
*
* @return integer|null
*/
public function getCappedMax()
{
return isset($this->info['options']['max']) ? (integer) $this->info['options']['max'] : null;
}
/**
* Return the maximum size (in bytes) of the capped collection.
*
* @return integer|null
*/
public function getCappedSize()
{
return isset($this->info['options']['size']) ? (integer) $this->info['options']['size'] : null;
}
/**
* Return the collection name.
*
* @return string
*/
public function getName()
{
return (string) $this->info['name'];
}
/**
* Return the collection options.
*
* @return array
*/
public function getOptions()
{
return isset($this->info['options']) ? (array) $this->info['options'] : [];
}
/**
* Return whether the collection is a capped collection.
*
* @return boolean
*/
public function isCapped()
{
return ! empty($this->info['options']['capped']);
}
/**
* Check whether a field exists in the collection information.
*
* @see http://php.net/arrayaccess.offsetexists
* @param mixed $key
* @return boolean
*/
public function offsetExists($key)
{
return array_key_exists($key, $this->info);
}
/**
* Return the field's value from the collection information.
*
* @see http://php.net/arrayaccess.offsetget
* @param mixed $key
* @return mixed
*/
public function offsetGet($key)
{
return $this->info[$key];
}
/**
* Not supported.
*
* @see http://php.net/arrayaccess.offsetset
* @throws BadMethodCallException
*/
public function offsetSet($key, $value)
{
throw BadMethodCallException::classIsImmutable(__CLASS__);
}
/**
* Not supported.
*
* @see http://php.net/arrayaccess.offsetunset
* @throws BadMethodCallException
*/
public function offsetUnset($key)
{
throw BadMethodCallException::classIsImmutable(__CLASS__);
}
}
+46
View File
@@ -0,0 +1,46 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Model;
use IteratorIterator;
/**
* CollectionInfoIterator for listCollections command results.
*
* This iterator may be used to wrap a Cursor returned by the listCollections
* command.
*
* @internal
* @see \MongoDB\Database::listCollections()
* @see https://github.com/mongodb/specifications/blob/master/source/enumerate-collections.rst
* @see http://docs.mongodb.org/manual/reference/command/listCollections/
*/
class CollectionInfoCommandIterator extends IteratorIterator implements CollectionInfoIterator
{
/**
* Return the current element as a CollectionInfo instance.
*
* @see CollectionInfoIterator::current()
* @see http://php.net/iterator.current
* @return CollectionInfo
*/
public function current()
{
return new CollectionInfo(parent::current());
}
}
+38
View File
@@ -0,0 +1,38 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Model;
use Iterator;
/**
* CollectionInfoIterator interface.
*
* This iterator is used for enumerating collections in a database.
*
* @api
* @see \MongoDB\Database::listCollections()
*/
interface CollectionInfoIterator extends Iterator
{
/**
* Return the current element as a CollectionInfo instance.
*
* @return CollectionInfo
*/
public function current();
}
+132
View File
@@ -0,0 +1,132 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Model;
use MongoDB\Exception\BadMethodCallException;
use ArrayAccess;
/**
* Database information model class.
*
* This class models the database information returned by the listDatabases
* command. It provides methods to access common database properties.
*
* @api
* @see \MongoDB\Client::listDatabases()
* @see http://docs.mongodb.org/manual/reference/command/listDatabases/
*/
class DatabaseInfo implements ArrayAccess
{
private $info;
/**
* Constructor.
*
* @param array $info Database info
*/
public function __construct(array $info)
{
$this->info = $info;
}
/**
* Return the collection info as an array.
*
* @see http://php.net/oop5.magic#language.oop5.magic.debuginfo
* @return array
*/
public function __debugInfo()
{
return $this->info;
}
/**
* Return the database name.
*
* @return string
*/
public function getName()
{
return (string) $this->info['name'];
}
/**
* Return the databases size on disk (in bytes).
*
* @return integer
*/
public function getSizeOnDisk()
{
return (integer) $this->info['sizeOnDisk'];
}
/**
* Return whether the database is empty.
*
* @return boolean
*/
public function isEmpty()
{
return (boolean) $this->info['empty'];
}
/**
* Check whether a field exists in the database information.
*
* @see http://php.net/arrayaccess.offsetexists
* @param mixed $key
* @return boolean
*/
public function offsetExists($key)
{
return array_key_exists($key, $this->info);
}
/**
* Return the field's value from the database information.
*
* @see http://php.net/arrayaccess.offsetget
* @param mixed $key
* @return mixed
*/
public function offsetGet($key)
{
return $this->info[$key];
}
/**
* Not supported.
*
* @see http://php.net/arrayaccess.offsetset
* @throws BadMethodCallException
*/
public function offsetSet($key, $value)
{
throw BadMethodCallException::classIsImmutable(__CLASS__);
}
/**
* Not supported.
*
* @see http://php.net/arrayaccess.offsetunset
* @throws BadMethodCallException
*/
public function offsetUnset($key)
{
throw BadMethodCallException::classIsImmutable(__CLASS__);
}
}
+38
View File
@@ -0,0 +1,38 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Model;
use Iterator;
/**
* DatabaseInfoIterator interface.
*
* This iterator is used for enumerating databases on a server.
*
* @api
* @see \MongoDB\Client::listDatabases()
*/
interface DatabaseInfoIterator extends Iterator
{
/**
* Return the current element as a DatabaseInfo instance.
*
* @return DatabaseInfo
*/
public function current();
}
+97
View File
@@ -0,0 +1,97 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Model;
/**
* DatabaseInfoIterator for inline listDatabases command results.
*
* This iterator may be used to wrap the array returned within the listDatabases
* command's single-document result.
*
* @internal
* @see \MongoDB\Client::listDatabases()
* @see http://docs.mongodb.org/manual/reference/command/listDatabases/
*/
class DatabaseInfoLegacyIterator implements DatabaseInfoIterator
{
private $databases;
/**
* Constructor.
*
* @param array $databases
*/
public function __construct(array $databases)
{
$this->databases = $databases;
}
/**
* Return the current element as a DatabaseInfo instance.
*
* @see DatabaseInfoIterator::current()
* @see http://php.net/iterator.current
* @return DatabaseInfo
*/
public function current()
{
return new DatabaseInfo(current($this->databases));
}
/**
* Return the key of the current element.
*
* @see http://php.net/iterator.key
* @return integer
*/
public function key()
{
return key($this->databases);
}
/**
* Move forward to next element.
*
* @see http://php.net/iterator.next
*/
public function next()
{
next($this->databases);
}
/**
* Rewind the Iterator to the first element.
*
* @see http://php.net/iterator.rewind
*/
public function rewind()
{
reset($this->databases);
}
/**
* Checks if current position is valid.
*
* @see http://php.net/iterator.valid
* @return boolean
*/
public function valid()
{
return key($this->databases) !== null;
}
}
+226
View File
@@ -0,0 +1,226 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Model;
use MongoDB\Exception\BadMethodCallException;
use ArrayAccess;
/**
* Index information model class.
*
* This class models the index information returned by the listIndexes command
* or, for legacy servers, queries on the "system.indexes" collection. It
* provides methods to access common index options, and allows access to other
* options through the ArrayAccess interface (write methods are not supported).
* For information on keys and index options, see the referenced
* db.collection.createIndex() documentation.
*
* @api
* @see \MongoDB\Collection::listIndexes()
* @see https://github.com/mongodb/specifications/blob/master/source/enumerate-indexes.rst
* @see http://docs.mongodb.org/manual/reference/method/db.collection.createIndex/
*/
class IndexInfo implements ArrayAccess
{
private $info;
/**
* Constructor.
*
* @param array $info Index info
*/
public function __construct(array $info)
{
$this->info = $info;
}
/**
* Return the collection info as an array.
*
* @see http://php.net/oop5.magic#language.oop5.magic.debuginfo
* @return array
*/
public function __debugInfo()
{
return $this->info;
}
/**
* Return the index name to allow casting IndexInfo to string.
*
* @return string
*/
public function __toString()
{
return $this->getName();
}
/**
* Return the index key.
*
* @return array
*/
public function getKey()
{
return (array) $this->info['key'];
}
/**
* Return the index name.
*
* @return string
*/
public function getName()
{
return (string) $this->info['name'];
}
/**
* Return the index namespace (e.g. "db.collection").
*
* @return string
*/
public function getNamespace()
{
return (string) $this->info['ns'];
}
/**
* Return the index version.
*
* @return integer
*/
public function getVersion()
{
return (integer) $this->info['v'];
}
/**
* Return whether or not this index is of type 2dsphere.
*
* @return boolean
*/
public function is2dSphere()
{
return array_search('2dsphere', $this->getKey(), true) !== false;
}
/**
* Return whether or not this index is of type geoHaystack.
*
* @return boolean
*/
public function isGeoHaystack()
{
return array_search('geoHaystack', $this->getKey(), true) !== false;
}
/**
* Return whether this is a sparse index.
*
* @see http://docs.mongodb.org/manual/core/index-sparse/
* @return boolean
*/
public function isSparse()
{
return ! empty($this->info['sparse']);
}
/**
* Return whether or not this index is of type text.
*
* @return boolean
*/
public function isText()
{
return array_search('text', $this->getKey(), true) !== false;
}
/**
* Return whether this is a TTL index.
*
* @see http://docs.mongodb.org/manual/core/index-ttl/
* @return boolean
*/
public function isTtl()
{
return array_key_exists('expireAfterSeconds', $this->info);
}
/**
* Return whether this is a unique index.
*
* @see http://docs.mongodb.org/manual/core/index-unique/
* @return boolean
*/
public function isUnique()
{
return ! empty($this->info['unique']);
}
/**
* Check whether a field exists in the index information.
*
* @see http://php.net/arrayaccess.offsetexists
* @param mixed $key
* @return boolean
*/
public function offsetExists($key)
{
return array_key_exists($key, $this->info);
}
/**
* Return the field's value from the index information.
*
* This method satisfies the Enumerating Indexes specification's requirement
* that index fields be made accessible under their original names. It may
* also be used to access fields that do not have a helper method.
*
* @see http://php.net/arrayaccess.offsetget
* @see https://github.com/mongodb/specifications/blob/master/source/enumerate-indexes.rst#getting-full-index-information
* @param mixed $key
* @return mixed
*/
public function offsetGet($key)
{
return $this->info[$key];
}
/**
* Not supported.
*
* @see http://php.net/arrayaccess.offsetset
* @throws BadMethodCallException
*/
public function offsetSet($key, $value)
{
throw BadMethodCallException::classIsImmutable(__CLASS__);
}
/**
* Not supported.
*
* @see http://php.net/arrayaccess.offsetunset
* @throws BadMethodCallException
*/
public function offsetUnset($key)
{
throw BadMethodCallException::classIsImmutable(__CLASS__);
}
}
+38
View File
@@ -0,0 +1,38 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Model;
use Iterator;
/**
* IndexInfoIterator interface.
*
* This iterator is used for enumerating indexes in a collection.
*
* @api
* @see \MongoDB\Collection::listIndexes()
*/
interface IndexInfoIterator extends Iterator
{
/**
* Return the current element as a IndexInfo instance.
*
* @return IndexInfo
*/
public function current();
}
+48
View File
@@ -0,0 +1,48 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Model;
use IteratorIterator;
/**
* IndexInfoIterator for both listIndexes command and legacy query results.
*
* This common iterator may be used to wrap a Cursor returned by both the
* listIndexes command and, for legacy servers, queries on the "system.indexes"
* collection.
*
* @internal
* @see \MongoDB\Collection::listIndexes()
* @see https://github.com/mongodb/specifications/blob/master/source/enumerate-indexes.rst
* @see http://docs.mongodb.org/manual/reference/command/listIndexes/
* @see http://docs.mongodb.org/manual/reference/system-collections/
*/
class IndexInfoIteratorIterator extends IteratorIterator implements IndexInfoIterator
{
/**
* Return the current element as an IndexInfo instance.
*
* @see IndexInfoIterator::current()
* @see http://php.net/iterator.current
* @return IndexInfo
*/
public function current()
{
return new IndexInfo(parent::current());
}
}
+99
View File
@@ -0,0 +1,99 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Model;
use MongoDB\BSON\Serializable;
use MongoDB\Exception\InvalidArgumentException;
/**
* Index input model class.
*
* This class is used to validate user input for index creation.
*
* @internal
* @see \MongoDB\Collection::createIndexes()
* @see https://github.com/mongodb/specifications/blob/master/source/enumerate-indexes.rst
* @see http://docs.mongodb.org/manual/reference/method/db.collection.createIndex/
*/
class IndexInput implements Serializable
{
private $index;
/**
* Constructor.
*
* @param array $index Index specification
* @throws InvalidArgumentException
*/
public function __construct(array $index)
{
if ( ! isset($index['key'])) {
throw new InvalidArgumentException('Required "key" document is missing from index specification');
}
if ( ! is_array($index['key']) && ! is_object($index['key'])) {
throw InvalidArgumentException::invalidType('"key" option', $index['key'], 'array or object');
}
foreach ($index['key'] as $fieldName => $order) {
if ( ! is_int($order) && ! is_float($order) && ! is_string($order)) {
throw InvalidArgumentException::invalidType(sprintf('order value for "%s" field within "key" option', $fieldName), $order, 'numeric or string');
}
}
if ( ! isset($index['ns'])) {
throw new InvalidArgumentException('Required "ns" option is missing from index specification');
}
if ( ! is_string($index['ns'])) {
throw InvalidArgumentException::invalidType('"ns" option', $index['ns'], 'string');
}
if ( ! isset($index['name'])) {
$index['name'] = \MongoDB\generate_index_name($index['key']);
}
if ( ! is_string($index['name'])) {
throw InvalidArgumentException::invalidType('"name" option', $index['name'], 'string');
}
$this->index = $index;
}
/**
* Return the index name.
*
* @param string
*/
public function __toString()
{
return $this->index['name'];
}
/**
* Serialize the index information to BSON for index creation.
*
* @see \MongoDB\Collection::createIndexes()
* @see http://php.net/mongodb-bson-serializable.bsonserialize
* @return array
*/
public function bsonSerialize()
{
return $this->index;
}
}
+166
View File
@@ -0,0 +1,166 @@
<?php
/*
* Copyright 2016-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Model;
use ArrayIterator;
use MongoDB\Exception\BadMethodCallException;
/**
* Iterator for applying a type map to documents in inline command results.
*
* @internal
*/
class TypeMapArrayIterator extends ArrayIterator
{
private $typeMap;
/**
* Constructor.
*
* @param array $documents
* @param array $typeMap
*/
public function __construct(array $documents = [], array $typeMap)
{
parent::__construct($documents);
$this->typeMap = $typeMap;
}
/**
* Not supported.
*
* @see http://php.net/arrayiterator.append
* @throws BadMethodCallException
*/
public function append($value)
{
throw BadMethodCallException::classIsImmutable(__CLASS__);
}
/**
* Not supported.
*
* @see http://php.net/arrayiterator.asort
* @throws BadMethodCallException
*/
public function asort()
{
throw BadMethodCallException::classIsImmutable(__CLASS__);
}
/**
* Return the current element with the type map applied to it.
*
* @see http://php.net/arrayiterator.current
* @return array|object
*/
public function current()
{
return \MongoDB\apply_type_map_to_document(parent::current(), $this->typeMap);
}
/**
* Not supported.
*
* @see http://php.net/arrayiterator.ksort
* @throws BadMethodCallException
*/
public function ksort()
{
throw BadMethodCallException::classIsImmutable(__CLASS__);
}
/**
* Not supported.
*
* @see http://php.net/arrayiterator.natcasesort
* @throws BadMethodCallException
*/
public function natcasesort()
{
throw BadMethodCallException::classIsImmutable(__CLASS__);
}
/**
* Not supported.
*
* @see http://php.net/arrayiterator.natsort
* @throws BadMethodCallException
*/
public function natsort()
{
throw BadMethodCallException::classIsImmutable(__CLASS__);
}
/**
* Return the value from the provided offset with the type map applied.
*
* @see http://php.net/arrayiterator.offsetget
* @param mixed $offset
* @return array|object
*/
public function offsetGet($offset)
{
return \MongoDB\apply_type_map_to_document(parent::offsetGet($offset), $this->typeMap);
}
/**
* Not supported.
*
* @see http://php.net/arrayiterator.offsetset
* @throws BadMethodCallException
*/
public function offsetSet($index, $newval)
{
throw BadMethodCallException::classIsImmutable(__CLASS__);
}
/**
* Not supported.
*
* @see http://php.net/arrayiterator.offsetunset
* @throws BadMethodCallException
*/
public function offsetUnset($index)
{
throw BadMethodCallException::classIsImmutable(__CLASS__);
}
/**
* Not supported.
*
* @see http://php.net/arrayiterator.uasort
* @throws BadMethodCallException
*/
public function uasort($cmp_function)
{
throw BadMethodCallException::classIsImmutable(__CLASS__);
}
/**
* Not supported.
*
* @see http://php.net/arrayiterator.uksort
* @throws BadMethodCallException
*/
public function uksort($cmp_function)
{
throw BadMethodCallException::classIsImmutable(__CLASS__);
}
}
+363
View File
@@ -0,0 +1,363 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\ReadConcern;
use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnexpectedValueException;
use MongoDB\Exception\UnsupportedException;
use MongoDB\Model\TypeMapArrayIterator;
use ArrayIterator;
use stdClass;
use Traversable;
/**
* Operation for the aggregate command.
*
* @api
* @see \MongoDB\Collection::aggregate()
* @see http://docs.mongodb.org/manual/reference/command/aggregate/
*/
class Aggregate implements Executable
{
private static $wireVersionForCollation = 5;
private static $wireVersionForDocumentLevelValidation = 4;
private static $wireVersionForReadConcern = 4;
private static $wireVersionForWriteConcern = 5;
private $databaseName;
private $collectionName;
private $pipeline;
private $options;
/**
* Constructs an aggregate command.
*
* Supported options:
*
* * allowDiskUse (boolean): Enables writing to temporary files. When set
* to true, aggregation stages can write data to the _tmp sub-directory
* in the dbPath directory. The default is false.
*
* * batchSize (integer): The number of documents to return per batch.
*
* * bypassDocumentValidation (boolean): If true, allows the write to
* circumvent document level validation. This only applies when the $out
* stage is specified.
*
* For servers < 3.2, this option is ignored as document level validation
* is not available.
*
* * collation (document): Collation specification.
*
* This is not supported for server versions < 3.4 and will result in an
* exception at execution time if used.
*
* * comment (string): An arbitrary string to help trace the operation
* through the database profiler, currentOp, and logs.
*
* * explain (boolean): Specifies whether or not to return the information
* on the processing of the pipeline.
*
* * hint (string|document): The index to use. Specify either the index
* name as a string or the index key pattern as a document. If specified,
* then the query system will only consider plans using the hinted index.
*
* * maxTimeMS (integer): The maximum amount of time to allow the query to
* run.
*
* * readConcern (MongoDB\Driver\ReadConcern): Read concern. Note that a
* "majority" read concern is not compatible with the $out stage.
*
* This is not supported for server versions < 3.2 and will result in an
* exception at execution time if used.
*
* * readPreference (MongoDB\Driver\ReadPreference): Read preference.
*
* This option is ignored if the $out stage is specified.
*
* * session (MongoDB\Driver\Session): Client session.
*
* Sessions are not supported for server versions < 3.6.
*
* * typeMap (array): Type map for BSON deserialization. This will be
* applied to the returned Cursor (it is not sent to the server).
*
* * useCursor (boolean): Indicates whether the command will request that
* the server provide results using a cursor. The default is true.
*
* This option allows users to turn off cursors if necessary to aid in
* mongod/mongos upgrades.
*
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern. This only
* applies when the $out stage is specified.
*
* This is not supported for server versions < 3.4 and will result in an
* exception at execution time if used.
*
* Note: Collection-agnostic commands (e.g. $currentOp) may be executed by
* specifying null for the collection name.
*
* @param string $databaseName Database name
* @param string|null $collectionName Collection name
* @param array $pipeline List of pipeline operations
* @param array $options Command options
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function __construct($databaseName, $collectionName, array $pipeline, array $options = [])
{
$expectedIndex = 0;
foreach ($pipeline as $i => $operation) {
if ($i !== $expectedIndex) {
throw new InvalidArgumentException(sprintf('$pipeline is not a list (unexpected index: "%s")', $i));
}
if ( ! is_array($operation) && ! is_object($operation)) {
throw InvalidArgumentException::invalidType(sprintf('$pipeline[%d]', $i), $operation, 'array or object');
}
$expectedIndex += 1;
}
$options += [
'allowDiskUse' => false,
'useCursor' => true,
];
if ( ! is_bool($options['allowDiskUse'])) {
throw InvalidArgumentException::invalidType('"allowDiskUse" option', $options['allowDiskUse'], 'boolean');
}
if (isset($options['batchSize']) && ! is_integer($options['batchSize'])) {
throw InvalidArgumentException::invalidType('"batchSize" option', $options['batchSize'], 'integer');
}
if (isset($options['bypassDocumentValidation']) && ! is_bool($options['bypassDocumentValidation'])) {
throw InvalidArgumentException::invalidType('"bypassDocumentValidation" option', $options['bypassDocumentValidation'], 'boolean');
}
if (isset($options['collation']) && ! is_array($options['collation']) && ! is_object($options['collation'])) {
throw InvalidArgumentException::invalidType('"collation" option', $options['collation'], 'array or object');
}
if (isset($options['comment']) && ! is_string($options['comment'])) {
throw InvalidArgumentException::invalidType('"comment" option', $options['comment'], 'string');
}
if (isset($options['explain']) && ! is_bool($options['explain'])) {
throw InvalidArgumentException::invalidType('"explain" option', $options['explain'], 'boolean');
}
if (isset($options['hint']) && ! is_string($options['hint']) && ! is_array($options['hint']) && ! is_object($options['hint'])) {
throw InvalidArgumentException::invalidType('"hint" option', $options['hint'], 'string or array or object');
}
if (isset($options['maxAwaitTimeMS']) && ! is_integer($options['maxAwaitTimeMS'])) {
throw InvalidArgumentException::invalidType('"maxAwaitTimeMS" option', $options['maxAwaitTimeMS'], 'integer');
}
if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
}
if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern');
}
if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
}
if (isset($options['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
}
if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
}
if ( ! is_bool($options['useCursor'])) {
throw InvalidArgumentException::invalidType('"useCursor" option', $options['useCursor'], 'boolean');
}
if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
}
if (isset($options['batchSize']) && ! $options['useCursor']) {
throw new InvalidArgumentException('"batchSize" option should not be used if "useCursor" is false');
}
if (isset($options['readConcern']) && $options['readConcern']->isDefault()) {
unset($options['readConcern']);
}
if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) {
unset($options['writeConcern']);
}
if ( ! empty($options['explain'])) {
$options['useCursor'] = false;
}
$this->databaseName = (string) $databaseName;
$this->collectionName = isset($collectionName) ? (string) $collectionName : null;
$this->pipeline = $pipeline;
$this->options = $options;
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return Traversable
* @throws UnexpectedValueException if the command response was malformed
* @throws UnsupportedException if collation, read concern, or write concern is used and unsupported
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function execute(Server $server)
{
if (isset($this->options['collation']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) {
throw UnsupportedException::collationNotSupported();
}
if (isset($this->options['readConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
throw UnsupportedException::readConcernNotSupported();
}
if (isset($this->options['writeConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForWriteConcern)) {
throw UnsupportedException::writeConcernNotSupported();
}
$hasExplain = ! empty($this->options['explain']);
$hasOutStage = \MongoDB\is_last_pipeline_operator_out($this->pipeline);
$command = $this->createCommand($server);
$options = $this->createOptions($hasOutStage, $hasExplain);
$cursor = ($hasOutStage && ! $hasExplain)
? $server->executeReadWriteCommand($this->databaseName, $command, $options)
: $server->executeReadCommand($this->databaseName, $command, $options);
if ($this->options['useCursor'] || $hasExplain) {
if (isset($this->options['typeMap'])) {
$cursor->setTypeMap($this->options['typeMap']);
}
return $cursor;
}
$result = current($cursor->toArray());
if ( ! isset($result->result) || ! is_array($result->result)) {
throw new UnexpectedValueException('aggregate command did not return a "result" array');
}
if (isset($this->options['typeMap'])) {
return new TypeMapArrayIterator($result->result, $this->options['typeMap']);
}
return new ArrayIterator($result->result);
}
/**
* Create the aggregate command.
*
* @param Server $server
* @return Command
*/
private function createCommand(Server $server)
{
$cmd = [
'aggregate' => isset($this->collectionName) ? $this->collectionName : 1,
'pipeline' => $this->pipeline,
];
$cmdOptions = [];
$cmd['allowDiskUse'] = $this->options['allowDiskUse'];
if (isset($this->options['bypassDocumentValidation']) && \MongoDB\server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)) {
$cmd['bypassDocumentValidation'] = $this->options['bypassDocumentValidation'];
}
foreach (['comment', 'explain', 'maxTimeMS'] as $option) {
if (isset($this->options[$option])) {
$cmd[$option] = $this->options[$option];
}
}
if (isset($this->options['collation'])) {
$cmd['collation'] = (object) $this->options['collation'];
}
if (isset($this->options['hint'])) {
$cmd['hint'] = is_array($this->options['hint']) ? (object) $this->options['hint'] : $this->options['hint'];
}
if (isset($this->options['maxAwaitTimeMS'])) {
$cmdOptions['maxAwaitTimeMS'] = $this->options['maxAwaitTimeMS'];
}
if ($this->options['useCursor']) {
$cmd['cursor'] = isset($this->options["batchSize"])
? ['batchSize' => $this->options["batchSize"]]
: new stdClass;
}
return new Command($cmd, $cmdOptions);
}
/**
* Create options for executing the command.
*
* @see http://php.net/manual/en/mongodb-driver-server.executereadcommand.php
* @see http://php.net/manual/en/mongodb-driver-server.executereadwritecommand.php
* @param boolean $hasOutStage
* @return array
*/
private function createOptions($hasOutStage, $hasExplain)
{
$options = [];
if (isset($this->options['readConcern'])) {
$options['readConcern'] = $this->options['readConcern'];
}
if ( ! $hasOutStage && isset($this->options['readPreference'])) {
$options['readPreference'] = $this->options['readPreference'];
}
if (isset($this->options['session'])) {
$options['session'] = $this->options['session'];
}
if ($hasOutStage && ! $hasExplain && isset($this->options['writeConcern'])) {
$options['writeConcern'] = $this->options['writeConcern'];
}
return $options;
}
}
+380
View File
@@ -0,0 +1,380 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Operation;
use MongoDB\BulkWriteResult;
use MongoDB\Driver\BulkWrite as Bulk;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
/**
* Operation for executing multiple write operations.
*
* @api
* @see \MongoDB\Collection::bulkWrite()
*/
class BulkWrite implements Executable
{
const DELETE_MANY = 'deleteMany';
const DELETE_ONE = 'deleteOne';
const INSERT_ONE = 'insertOne';
const REPLACE_ONE = 'replaceOne';
const UPDATE_MANY = 'updateMany';
const UPDATE_ONE = 'updateOne';
private static $wireVersionForArrayFilters = 6;
private static $wireVersionForCollation = 5;
private static $wireVersionForDocumentLevelValidation = 4;
private $databaseName;
private $collectionName;
private $operations;
private $options;
private $isArrayFiltersUsed = false;
private $isCollationUsed = false;
/**
* Constructs a bulk write operation.
*
* Example array structure for all supported operation types:
*
* [
* [ 'deleteMany' => [ $filter, $options ] ],
* [ 'deleteOne' => [ $filter, $options ] ],
* [ 'insertOne' => [ $document ] ],
* [ 'replaceOne' => [ $filter, $replacement, $options ] ],
* [ 'updateMany' => [ $filter, $update, $options ] ],
* [ 'updateOne' => [ $filter, $update, $options ] ],
* ]
*
* Arguments correspond to the respective Operation classes; however, the
* writeConcern option is specified for the top-level bulk write operation
* instead of each individual operation.
*
* Supported options for deleteMany and deleteOne operations:
*
* * collation (document): Collation specification.
*
* This is not supported for server versions < 3.4 and will result in an
* exception at execution time if used.
*
* Supported options for replaceOne, updateMany, and updateOne operations:
*
* * collation (document): Collation specification.
*
* This is not supported for server versions < 3.4 and will result in an
* exception at execution time if used.
*
* * upsert (boolean): When true, a new document is created if no document
* matches the query. The default is false.
*
* Supported options for updateMany and updateOne operations:
*
* * arrayFilters (document array): A set of filters specifying to which
* array elements an update should apply.
*
* This is not supported for server versions < 3.6 and will result in an
* exception at execution time if used.
*
* Supported options for the bulk write operation:
*
* * bypassDocumentValidation (boolean): If true, allows the write to
* circumvent document level validation. The default is false.
*
* For servers < 3.2, this option is ignored as document level validation
* is not available.
*
* * ordered (boolean): If true, when an insert fails, return without
* performing the remaining writes. If false, when a write fails,
* continue with the remaining writes, if any. The default is true.
*
* * session (MongoDB\Driver\Session): Client session.
*
* Sessions are not supported for server versions < 3.6.
*
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
*
* @param string $databaseName Database name
* @param string $collectionName Collection name
* @param array[] $operations List of write operations
* @param array $options Command options
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function __construct($databaseName, $collectionName, array $operations, array $options = [])
{
if (empty($operations)) {
throw new InvalidArgumentException('$operations is empty');
}
$expectedIndex = 0;
foreach ($operations as $i => $operation) {
if ($i !== $expectedIndex) {
throw new InvalidArgumentException(sprintf('$operations is not a list (unexpected index: "%s")', $i));
}
if ( ! is_array($operation)) {
throw InvalidArgumentException::invalidType(sprintf('$operations[%d]', $i), $operation, 'array');
}
if (count($operation) !== 1) {
throw new InvalidArgumentException(sprintf('Expected one element in $operation[%d], actually: %d', $i, count($operation)));
}
$type = key($operation);
$args = current($operation);
if ( ! isset($args[0]) && ! array_key_exists(0, $args)) {
throw new InvalidArgumentException(sprintf('Missing first argument for $operations[%d]["%s"]', $i, $type));
}
if ( ! is_array($args[0]) && ! is_object($args[0])) {
throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][0]', $i, $type), $args[0], 'array or object');
}
switch ($type) {
case self::INSERT_ONE:
break;
case self::DELETE_MANY:
case self::DELETE_ONE:
if ( ! isset($args[1])) {
$args[1] = [];
}
if ( ! is_array($args[1])) {
throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][1]', $i, $type), $args[1], 'array');
}
$args[1]['limit'] = ($type === self::DELETE_ONE ? 1 : 0);
if (isset($args[1]['collation'])) {
$this->isCollationUsed = true;
if ( ! is_array($args[1]['collation']) && ! is_object($args[1]['collation'])) {
throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][1]["collation"]', $i, $type), $args[1]['collation'], 'array or object');
}
}
$operations[$i][$type][1] = $args[1];
break;
case self::REPLACE_ONE:
if ( ! isset($args[1]) && ! array_key_exists(1, $args)) {
throw new InvalidArgumentException(sprintf('Missing second argument for $operations[%d]["%s"]', $i, $type));
}
if ( ! is_array($args[1]) && ! is_object($args[1])) {
throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][1]', $i, $type), $args[1], 'array or object');
}
if (\MongoDB\is_first_key_operator($args[1])) {
throw new InvalidArgumentException(sprintf('First key in $operations[%d]["%s"][1] is an update operator', $i, $type));
}
if ( ! isset($args[2])) {
$args[2] = [];
}
if ( ! is_array($args[2])) {
throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]', $i, $type), $args[2], 'array');
}
$args[2]['multi'] = false;
$args[2] += ['upsert' => false];
if (isset($args[2]['collation'])) {
$this->isCollationUsed = true;
if ( ! is_array($args[2]['collation']) && ! is_object($args[2]['collation'])) {
throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]["collation"]', $i, $type), $args[2]['collation'], 'array or object');
}
}
if ( ! is_bool($args[2]['upsert'])) {
throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]["upsert"]', $i, $type), $args[2]['upsert'], 'boolean');
}
$operations[$i][$type][2] = $args[2];
break;
case self::UPDATE_MANY:
case self::UPDATE_ONE:
if ( ! isset($args[1]) && ! array_key_exists(1, $args)) {
throw new InvalidArgumentException(sprintf('Missing second argument for $operations[%d]["%s"]', $i, $type));
}
if ( ! is_array($args[1]) && ! is_object($args[1])) {
throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][1]', $i, $type), $args[1], 'array or object');
}
if ( ! \MongoDB\is_first_key_operator($args[1])) {
throw new InvalidArgumentException(sprintf('First key in $operations[%d]["%s"][1] is not an update operator', $i, $type));
}
if ( ! isset($args[2])) {
$args[2] = [];
}
if ( ! is_array($args[2])) {
throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]', $i, $type), $args[2], 'array');
}
$args[2]['multi'] = ($type === self::UPDATE_MANY);
$args[2] += ['upsert' => false];
if (isset($args[2]['arrayFilters'])) {
$this->isArrayFiltersUsed = true;
if ( ! is_array($args[2]['arrayFilters'])) {
throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]["arrayFilters"]', $i, $type), $args[2]['arrayFilters'], 'array');
}
}
if (isset($args[2]['collation'])) {
$this->isCollationUsed = true;
if ( ! is_array($args[2]['collation']) && ! is_object($args[2]['collation'])) {
throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]["collation"]', $i, $type), $args[2]['collation'], 'array or object');
}
}
if ( ! is_bool($args[2]['upsert'])) {
throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]["upsert"]', $i, $type), $args[2]['upsert'], 'boolean');
}
$operations[$i][$type][2] = $args[2];
break;
default:
throw new InvalidArgumentException(sprintf('Unknown operation type "%s" in $operations[%d]', $type, $i));
}
$expectedIndex += 1;
}
$options += ['ordered' => true];
if (isset($options['bypassDocumentValidation']) && ! is_bool($options['bypassDocumentValidation'])) {
throw InvalidArgumentException::invalidType('"bypassDocumentValidation" option', $options['bypassDocumentValidation'], 'boolean');
}
if ( ! is_bool($options['ordered'])) {
throw InvalidArgumentException::invalidType('"ordered" option', $options['ordered'], 'boolean');
}
if (isset($options['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
}
if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
}
if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) {
unset($options['writeConcern']);
}
$this->databaseName = (string) $databaseName;
$this->collectionName = (string) $collectionName;
$this->operations = $operations;
$this->options = $options;
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return BulkWriteResult
* @throws UnsupportedException if array filters or collation is used and unsupported
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function execute(Server $server)
{
if ($this->isArrayFiltersUsed && ! \MongoDB\server_supports_feature($server, self::$wireVersionForArrayFilters)) {
throw UnsupportedException::arrayFiltersNotSupported();
}
if ($this->isCollationUsed && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) {
throw UnsupportedException::collationNotSupported();
}
$options = ['ordered' => $this->options['ordered']];
if (isset($this->options['bypassDocumentValidation']) && \MongoDB\server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)) {
$options['bypassDocumentValidation'] = $this->options['bypassDocumentValidation'];
}
$bulk = new Bulk($options);
$insertedIds = [];
foreach ($this->operations as $i => $operation) {
$type = key($operation);
$args = current($operation);
switch ($type) {
case self::DELETE_MANY:
case self::DELETE_ONE:
$bulk->delete($args[0], $args[1]);
break;
case self::INSERT_ONE:
$insertedIds[$i] = $bulk->insert($args[0]);
break;
case self::REPLACE_ONE:
case self::UPDATE_MANY:
case self::UPDATE_ONE:
$bulk->update($args[0], $args[1], $args[2]);
}
}
$writeResult = $server->executeBulkWrite($this->databaseName . '.' . $this->collectionName, $bulk, $this->createOptions());
return new BulkWriteResult($writeResult, $insertedIds);
}
/**
* Create options for executing the bulk write.
*
* @see http://php.net/manual/en/mongodb-driver-server.executebulkwrite.php
* @return array
*/
private function createOptions()
{
$options = [];
if (isset($this->options['session'])) {
$options['session'] = $this->options['session'];
}
if (isset($this->options['writeConcern'])) {
$options['writeConcern'] = $this->options['writeConcern'];
}
return $options;
}
}
+224
View File
@@ -0,0 +1,224 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\ReadConcern;
use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnexpectedValueException;
use MongoDB\Exception\UnsupportedException;
/**
* Operation for the count command.
*
* @api
* @see \MongoDB\Collection::count()
* @see http://docs.mongodb.org/manual/reference/command/count/
*/
class Count implements Executable, Explainable
{
private static $wireVersionForCollation = 5;
private static $wireVersionForReadConcern = 4;
private $databaseName;
private $collectionName;
private $filter;
private $options;
/**
* Constructs a count command.
*
* Supported options:
*
* * collation (document): Collation specification.
*
* This is not supported for server versions < 3.4 and will result in an
* exception at execution time if used.
*
* * hint (string|document): The index to use. Specify either the index
* name as a string or the index key pattern as a document. If specified,
* then the query system will only consider plans using the hinted index.
*
* * limit (integer): The maximum number of documents to count.
*
* * maxTimeMS (integer): The maximum amount of time to allow the query to
* run.
*
* * readConcern (MongoDB\Driver\ReadConcern): Read concern.
*
* This is not supported for server versions < 3.2 and will result in an
* exception at execution time if used.
*
* * readPreference (MongoDB\Driver\ReadPreference): Read preference.
*
* * session (MongoDB\Driver\Session): Client session.
*
* Sessions are not supported for server versions < 3.6.
*
* * skip (integer): The number of documents to skip before returning the
* documents.
*
* @param string $databaseName Database name
* @param string $collectionName Collection name
* @param array|object $filter Query by which to filter documents
* @param array $options Command options
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function __construct($databaseName, $collectionName, $filter = [], array $options = [])
{
if ( ! is_array($filter) && ! is_object($filter)) {
throw InvalidArgumentException::invalidType('$filter', $filter, 'array or object');
}
if (isset($options['collation']) && ! is_array($options['collation']) && ! is_object($options['collation'])) {
throw InvalidArgumentException::invalidType('"collation" option', $options['collation'], 'array or object');
}
if (isset($options['hint']) && ! is_string($options['hint']) && ! is_array($options['hint']) && ! is_object($options['hint'])) {
throw InvalidArgumentException::invalidType('"hint" option', $options['hint'], 'string or array or object');
}
if (isset($options['limit']) && ! is_integer($options['limit'])) {
throw InvalidArgumentException::invalidType('"limit" option', $options['limit'], 'integer');
}
if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
}
if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern');
}
if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
}
if (isset($options['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
}
if (isset($options['skip']) && ! is_integer($options['skip'])) {
throw InvalidArgumentException::invalidType('"skip" option', $options['skip'], 'integer');
}
if (isset($options['readConcern']) && $options['readConcern']->isDefault()) {
unset($options['readConcern']);
}
$this->databaseName = (string) $databaseName;
$this->collectionName = (string) $collectionName;
$this->filter = $filter;
$this->options = $options;
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return integer
* @throws UnexpectedValueException if the command response was malformed
* @throws UnsupportedException if collation or read concern is used and unsupported
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function execute(Server $server)
{
if (isset($this->options['collation']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) {
throw UnsupportedException::collationNotSupported();
}
if (isset($this->options['readConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
throw UnsupportedException::readConcernNotSupported();
}
$cursor = $server->executeReadCommand($this->databaseName, new Command($this->createCommandDocument()), $this->createOptions());
$result = current($cursor->toArray());
// Older server versions may return a float
if ( ! isset($result->n) || ! (is_integer($result->n) || is_float($result->n))) {
throw new UnexpectedValueException('count command did not return a numeric "n" value');
}
return (integer) $result->n;
}
public function getCommandDocument(Server $server)
{
return $this->createCommandDocument();
}
/**
* Create the count command document.
*
* @return array
*/
private function createCommandDocument()
{
$cmd = ['count' => $this->collectionName];
if ( ! empty($this->filter)) {
$cmd['query'] = (object) $this->filter;
}
if (isset($this->options['collation'])) {
$cmd['collation'] = (object) $this->options['collation'];
}
if (isset($this->options['hint'])) {
$cmd['hint'] = is_array($this->options['hint']) ? (object) $this->options['hint'] : $this->options['hint'];
}
foreach (['limit', 'maxTimeMS', 'skip'] as $option) {
if (isset($this->options[$option])) {
$cmd[$option] = $this->options[$option];
}
}
return $cmd;
}
/**
* Create options for executing the command.
*
* @see http://php.net/manual/en/mongodb-driver-server.executereadcommand.php
* @return array
*/
private function createOptions()
{
$options = [];
if (isset($this->options['readConcern'])) {
$options['readConcern'] = $this->options['readConcern'];
}
if (isset($this->options['readPreference'])) {
$options['readPreference'] = $this->options['readPreference'];
}
if (isset($this->options['session'])) {
$options['session'] = $this->options['session'];
}
return $options;
}
}
+237
View File
@@ -0,0 +1,237 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\ReadConcern;
use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnexpectedValueException;
use MongoDB\Exception\UnsupportedException;
/**
* Operation for obtaining an exact count of documents in a collection
*
* @api
* @see \MongoDB\Collection::countDocuments()
* @see https://github.com/mongodb/specifications/blob/master/source/crud/crud.rst#countdocuments
*/
class CountDocuments implements Executable
{
private static $wireVersionForCollation = 5;
private static $wireVersionForReadConcern = 4;
private $databaseName;
private $collectionName;
private $filter;
private $options;
/**
* Constructs an aggregate command for counting documents
*
* Supported options:
*
* * collation (document): Collation specification.
*
* This is not supported for server versions < 3.4 and will result in an
* exception at execution time if used.
*
* * hint (string|document): The index to use. Specify either the index
* name as a string or the index key pattern as a document. If specified,
* then the query system will only consider plans using the hinted index.
*
* * limit (integer): The maximum number of documents to count.
*
* * maxTimeMS (integer): The maximum amount of time to allow the query to
* run.
*
* * readConcern (MongoDB\Driver\ReadConcern): Read concern.
*
* This is not supported for server versions < 3.2 and will result in an
* exception at execution time if used.
*
* * readPreference (MongoDB\Driver\ReadPreference): Read preference.
*
* * session (MongoDB\Driver\Session): Client session.
*
* Sessions are not supported for server versions < 3.6.
*
* * skip (integer): The number of documents to skip before returning the
* documents.
*
* @param string $databaseName Database name
* @param string $collectionName Collection name
* @param array|object $filter Query by which to filter documents
* @param array $options Command options
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function __construct($databaseName, $collectionName, $filter, array $options = [])
{
if ( ! is_array($filter) && ! is_object($filter)) {
throw InvalidArgumentException::invalidType('$filter', $filter, 'array or object');
}
if (isset($options['collation']) && ! is_array($options['collation']) && ! is_object($options['collation'])) {
throw InvalidArgumentException::invalidType('"collation" option', $options['collation'], 'array or object');
}
if (isset($options['hint']) && ! is_string($options['hint']) && ! is_array($options['hint']) && ! is_object($options['hint'])) {
throw InvalidArgumentException::invalidType('"hint" option', $options['hint'], 'string or array or object');
}
if (isset($options['limit']) && ! is_integer($options['limit'])) {
throw InvalidArgumentException::invalidType('"limit" option', $options['limit'], 'integer');
}
if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
}
if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern');
}
if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
}
if (isset($options['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
}
if (isset($options['skip']) && ! is_integer($options['skip'])) {
throw InvalidArgumentException::invalidType('"skip" option', $options['skip'], 'integer');
}
if (isset($options['readConcern']) && $options['readConcern']->isDefault()) {
unset($options['readConcern']);
}
$this->databaseName = (string) $databaseName;
$this->collectionName = (string) $collectionName;
$this->filter = $filter;
$this->options = $options;
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return integer
* @throws UnexpectedValueException if the command response was malformed
* @throws UnsupportedException if collation or read concern is used and unsupported
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function execute(Server $server)
{
if (isset($this->options['collation']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) {
throw UnsupportedException::collationNotSupported();
}
if (isset($this->options['readConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
throw UnsupportedException::readConcernNotSupported();
}
$cursor = $server->executeReadCommand($this->databaseName, new Command($this->createCommandDocument()), $this->createOptions());
$allResults = $cursor->toArray();
/* If there are no documents to count, the aggregation pipeline has no items to group, and
* hence the result is an empty array (PHPLIB-376) */
if (count($allResults) == 0) {
return 0;
}
$result = current($allResults);
if ( ! isset($result->n) || ! (is_integer($result->n) || is_float($result->n))) {
throw new UnexpectedValueException('count command did not return a numeric "n" value');
}
return (integer) $result->n;
}
/**
* Create the count command document.
*
* @return array
*/
private function createCommandDocument()
{
$pipeline = [
['$match' => (object) $this->filter]
];
if (isset($this->options['skip'])) {
$pipeline[] = ['$skip' => $this->options['skip']];
}
if (isset($this->options['limit'])) {
$pipeline[] = ['$limit' => $this->options['limit']];
}
$pipeline[] = ['$group' => ['_id' => null, 'n' => ['$sum' => 1]]];
$cmd = [
'aggregate' => $this->collectionName,
'pipeline' => $pipeline,
'cursor' => (object) [],
];
if (isset($this->options['collation'])) {
$cmd['collation'] = (object) $this->options['collation'];
}
if (isset($this->options['hint'])) {
$cmd['hint'] = is_array($this->options['hint']) ? (object) $this->options['hint'] : $this->options['hint'];
}
if (isset($this->options['maxTimeMS'])) {
$cmd['maxTimeMS'] = $this->options['maxTimeMS'];
}
return $cmd;
}
/**
* Create options for executing the command.
*
* @see http://php.net/manual/en/mongodb-driver-server.executereadcommand.php
* @return array
*/
private function createOptions()
{
$options = [];
if (isset($this->options['readConcern'])) {
$options['readConcern'] = $this->options['readConcern'];
}
if (isset($this->options['readPreference'])) {
$options['readPreference'] = $this->options['readPreference'];
}
if (isset($this->options['session'])) {
$options['session'] = $this->options['session'];
}
return $options;
}
}
+258
View File
@@ -0,0 +1,258 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
/**
* Operation for the create command.
*
* @api
* @see \MongoDB\Database::createCollection()
* @see http://docs.mongodb.org/manual/reference/command/create/
*/
class CreateCollection implements Executable
{
const USE_POWER_OF_2_SIZES = 1;
const NO_PADDING = 2;
private static $wireVersionForCollation = 5;
private static $wireVersionForWriteConcern = 5;
private $databaseName;
private $collectionName;
private $options = [];
/**
* Constructs a create command.
*
* Supported options:
*
* * autoIndexId (boolean): Specify false to disable the automatic creation
* of an index on the _id field. For replica sets, this option cannot be
* false. The default is true.
*
* This option has been deprecated since MongoDB 3.2. As of MongoDB 4.0,
* this option cannot be false when creating a replicated collection
* (i.e. a collection outside of the local database in any mongod mode).
*
* * capped (boolean): Specify true to create a capped collection. If set,
* the size option must also be specified. The default is false.
*
* * collation (document): Collation specification.
*
* This is not supported for server versions < 3.4 and will result in an
* exception at execution time if used.
*
* * flags (integer): Options for the MMAPv1 storage engine only. Must be a
* bitwise combination CreateCollection::USE_POWER_OF_2_SIZES and
* CreateCollection::NO_PADDING. The default is
* CreateCollection::USE_POWER_OF_2_SIZES.
*
* * indexOptionDefaults (document): Default configuration for indexes when
* creating the collection.
*
* * max (integer): The maximum number of documents allowed in the capped
* collection. The size option takes precedence over this limit.
*
* * maxTimeMS (integer): The maximum amount of time to allow the query to
* run.
*
* * session (MongoDB\Driver\Session): Client session.
*
* Sessions are not supported for server versions < 3.6.
*
* * size (integer): The maximum number of bytes for a capped collection.
*
* * storageEngine (document): Storage engine options.
*
* * typeMap (array): Type map for BSON deserialization. This will only be
* used for the returned command result document.
*
* * validationAction (string): Validation action.
*
* * validationLevel (string): Validation level.
*
* * validator (document): Validation rules or expressions.
*
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
*
* This is not supported for server versions < 3.4 and will result in an
* exception at execution time if used.
*
* @see http://source.wiredtiger.com/2.4.1/struct_w_t___s_e_s_s_i_o_n.html#a358ca4141d59c345f401c58501276bbb
* @see https://docs.mongodb.org/manual/core/document-validation/
* @param string $databaseName Database name
* @param string $collectionName Collection name
* @param array $options Command options
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function __construct($databaseName, $collectionName, array $options = [])
{
if (isset($options['autoIndexId']) && ! is_bool($options['autoIndexId'])) {
throw InvalidArgumentException::invalidType('"autoIndexId" option', $options['autoIndexId'], 'boolean');
}
if (isset($options['capped']) && ! is_bool($options['capped'])) {
throw InvalidArgumentException::invalidType('"capped" option', $options['capped'], 'boolean');
}
if (isset($options['collation']) && ! is_array($options['collation']) && ! is_object($options['collation'])) {
throw InvalidArgumentException::invalidType('"collation" option', $options['collation'], 'array or object');
}
if (isset($options['flags']) && ! is_integer($options['flags'])) {
throw InvalidArgumentException::invalidType('"flags" option', $options['flags'], 'integer');
}
if (isset($options['indexOptionDefaults']) && ! is_array($options['indexOptionDefaults']) && ! is_object($options['indexOptionDefaults'])) {
throw InvalidArgumentException::invalidType('"indexOptionDefaults" option', $options['indexOptionDefaults'], 'array or object');
}
if (isset($options['max']) && ! is_integer($options['max'])) {
throw InvalidArgumentException::invalidType('"max" option', $options['max'], 'integer');
}
if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
}
if (isset($options['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
}
if (isset($options['size']) && ! is_integer($options['size'])) {
throw InvalidArgumentException::invalidType('"size" option', $options['size'], 'integer');
}
if (isset($options['storageEngine']) && ! is_array($options['storageEngine']) && ! is_object($options['storageEngine'])) {
throw InvalidArgumentException::invalidType('"storageEngine" option', $options['storageEngine'], 'array or object');
}
if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
}
if (isset($options['validationAction']) && ! is_string($options['validationAction'])) {
throw InvalidArgumentException::invalidType('"validationAction" option', $options['validationAction'], 'string');
}
if (isset($options['validationLevel']) && ! is_string($options['validationLevel'])) {
throw InvalidArgumentException::invalidType('"validationLevel" option', $options['validationLevel'], 'string');
}
if (isset($options['validator']) && ! is_array($options['validator']) && ! is_object($options['validator'])) {
throw InvalidArgumentException::invalidType('"validator" option', $options['validator'], 'array or object');
}
if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
}
if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) {
unset($options['writeConcern']);
}
if (isset($options['autoIndexId'])) {
trigger_error('The "autoIndexId" option is deprecated and will be removed in a future release', E_USER_DEPRECATED);
}
$this->databaseName = (string) $databaseName;
$this->collectionName = (string) $collectionName;
$this->options = $options;
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return array|object Command result document
* @throws UnsupportedException if collation or write concern is used and unsupported
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function execute(Server $server)
{
if (isset($this->options['collation']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) {
throw UnsupportedException::collationNotSupported();
}
if (isset($this->options['writeConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForWriteConcern)) {
throw UnsupportedException::writeConcernNotSupported();
}
$cursor = $server->executeWriteCommand($this->databaseName, $this->createCommand(), $this->createOptions());
if (isset($this->options['typeMap'])) {
$cursor->setTypeMap($this->options['typeMap']);
}
return current($cursor->toArray());
}
/**
* Create the create command.
*
* @return Command
*/
private function createCommand()
{
$cmd = ['create' => $this->collectionName];
foreach (['autoIndexId', 'capped', 'flags', 'max', 'maxTimeMS', 'size', 'validationAction', 'validationLevel'] as $option) {
if (isset($this->options[$option])) {
$cmd[$option] = $this->options[$option];
}
}
foreach (['collation', 'indexOptionDefaults', 'storageEngine', 'validator'] as $option) {
if (isset($this->options[$option])) {
$cmd[$option] = (object) $this->options[$option];
}
}
return new Command($cmd);
}
/**
* Create options for executing the command.
*
* @see http://php.net/manual/en/mongodb-driver-server.executewritecommand.php
* @return array
*/
private function createOptions()
{
$options = [];
if (isset($this->options['session'])) {
$options['session'] = $this->options['session'];
}
if (isset($this->options['writeConcern'])) {
$options['writeConcern'] = $this->options['writeConcern'];
}
return $options;
}
}
+188
View File
@@ -0,0 +1,188 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Operation;
use MongoDB\Driver\BulkWrite as Bulk;
use MongoDB\Driver\Command;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
use MongoDB\Model\IndexInput;
/**
* Operation for the createIndexes command.
*
* @api
* @see \MongoDB\Collection::createIndex()
* @see \MongoDB\Collection::createIndexes()
* @see http://docs.mongodb.org/manual/reference/command/createIndexes/
*/
class CreateIndexes implements Executable
{
private static $wireVersionForCollation = 5;
private static $wireVersionForWriteConcern = 5;
private $databaseName;
private $collectionName;
private $indexes = [];
private $isCollationUsed = false;
private $options = [];
/**
* Constructs a createIndexes command.
*
* Supported options:
*
* * maxTimeMS (integer): The maximum amount of time to allow the query to
* run.
*
* * session (MongoDB\Driver\Session): Client session.
*
* Sessions are not supported for server versions < 3.6.
*
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
*
* This is not supported for server versions < 3.4 and will result in an
* exception at execution time if used.
*
* @param string $databaseName Database name
* @param string $collectionName Collection name
* @param array[] $indexes List of index specifications
* @param array $options Command options
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function __construct($databaseName, $collectionName, array $indexes, array $options = [])
{
if (empty($indexes)) {
throw new InvalidArgumentException('$indexes is empty');
}
$expectedIndex = 0;
foreach ($indexes as $i => $index) {
if ($i !== $expectedIndex) {
throw new InvalidArgumentException(sprintf('$indexes is not a list (unexpected index: "%s")', $i));
}
if ( ! is_array($index)) {
throw InvalidArgumentException::invalidType(sprintf('$index[%d]', $i), $index, 'array');
}
if ( ! isset($index['ns'])) {
$index['ns'] = $databaseName . '.' . $collectionName;
}
if (isset($index['collation'])) {
$this->isCollationUsed = true;
}
$this->indexes[] = new IndexInput($index);
$expectedIndex += 1;
}
if (isset($options['maxTimeMS']) && !is_integer($options['maxTimeMS'])) {
throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
}
if (isset($options['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
}
if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
}
if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) {
unset($options['writeConcern']);
}
$this->databaseName = (string) $databaseName;
$this->collectionName = (string) $collectionName;
$this->options = $options;
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return string[] The names of the created indexes
* @throws UnsupportedException if collation or write concern is used and unsupported
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function execute(Server $server)
{
if ($this->isCollationUsed && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) {
throw UnsupportedException::collationNotSupported();
}
if (isset($this->options['writeConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForWriteConcern)) {
throw UnsupportedException::writeConcernNotSupported();
}
$this->executeCommand($server);
return array_map(function(IndexInput $index) { return (string) $index; }, $this->indexes);
}
/**
* Create options for executing the command.
*
* @see http://php.net/manual/en/mongodb-driver-server.executewritecommand.php
* @return array
*/
private function createOptions()
{
$options = [];
if (isset($this->options['session'])) {
$options['session'] = $this->options['session'];
}
if (isset($this->options['writeConcern'])) {
$options['writeConcern'] = $this->options['writeConcern'];
}
return $options;
}
/**
* Create one or more indexes for the collection using the createIndexes
* command.
*
* @param Server $server
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
private function executeCommand(Server $server)
{
$cmd = [
'createIndexes' => $this->collectionName,
'indexes' => $this->indexes,
];
if (isset($this->options['maxTimeMS'])) {
$cmd['maxTimeMS'] = $this->options['maxTimeMS'];
}
$server->executeWriteCommand($this->databaseName, new Command($cmd), $this->createOptions());
}
}
+123
View File
@@ -0,0 +1,123 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\Cursor;
use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Exception\InvalidArgumentException;
/**
* Operation for executing a database command.
*
* @api
* @see \MongoDB\Database::command()
*/
class DatabaseCommand implements Executable
{
private $databaseName;
private $command;
private $options;
/**
* Constructs a command.
*
* Supported options:
*
* * readPreference (MongoDB\Driver\ReadPreference): The read preference to
* use when executing the command. This may be used when issuing the
* command to a replica set or mongos node to ensure that the driver sets
* the wire protocol accordingly or adds the read preference to the
* command document, respectively.
*
* * session (MongoDB\Driver\Session): Client session.
*
* Sessions are not supported for server versions < 3.6.
*
* * typeMap (array): Type map for BSON deserialization. This will be
* applied to the returned Cursor (it is not sent to the server).
*
* @param string $databaseName Database name
* @param array|object $command Command document
* @param array $options Options for command execution
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function __construct($databaseName, $command, array $options = [])
{
if ( ! is_array($command) && ! is_object($command)) {
throw InvalidArgumentException::invalidType('$command', $command, 'array or object');
}
if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
}
if (isset($options['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
}
if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
}
$this->databaseName = (string) $databaseName;
$this->command = ($command instanceof Command) ? $command : new Command($command);
$this->options = $options;
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return Cursor
*/
public function execute(Server $server)
{
$cursor = $server->executeCommand($this->databaseName, $this->command, $this->createOptions());
if (isset($this->options['typeMap'])) {
$cursor->setTypeMap($this->options['typeMap']);
}
return $cursor;
}
/**
* Create options for executing the command.
*
* @see http://php.net/manual/en/mongodb-driver-server.executecommand.php
* @return array
*/
private function createOptions()
{
$options = [];
if (isset($this->options['readPreference'])) {
$options['readPreference'] = $this->options['readPreference'];
}
if (isset($this->options['session'])) {
$options['session'] = $this->options['session'];
}
return $options;
}
}
+178
View File
@@ -0,0 +1,178 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Operation;
use MongoDB\DeleteResult;
use MongoDB\Driver\BulkWrite as Bulk;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
/**
* Operation for the delete command.
*
* This class is used internally by the DeleteMany and DeleteOne operation
* classes.
*
* @internal
* @see http://docs.mongodb.org/manual/reference/command/delete/
*/
class Delete implements Executable, Explainable
{
private static $wireVersionForCollation = 5;
private $databaseName;
private $collectionName;
private $filter;
private $limit;
private $options;
/**
* Constructs a delete command.
*
* Supported options:
*
* * collation (document): Collation specification.
*
* This is not supported for server versions < 3.4 and will result in an
* exception at execution time if used.
*
* * session (MongoDB\Driver\Session): Client session.
*
* Sessions are not supported for server versions < 3.6.
*
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
*
* @param string $databaseName Database name
* @param string $collectionName Collection name
* @param array|object $filter Query by which to delete documents
* @param integer $limit The number of matching documents to
* delete. Must be 0 or 1, for all or a
* single document, respectively.
* @param array $options Command options
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function __construct($databaseName, $collectionName, $filter, $limit, array $options = [])
{
if ( ! is_array($filter) && ! is_object($filter)) {
throw InvalidArgumentException::invalidType('$filter', $filter, 'array or object');
}
if ($limit !== 0 && $limit !== 1) {
throw new InvalidArgumentException('$limit must be 0 or 1');
}
if (isset($options['collation']) && ! is_array($options['collation']) && ! is_object($options['collation'])) {
throw InvalidArgumentException::invalidType('"collation" option', $options['collation'], 'array or object');
}
if (isset($options['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
}
if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
}
if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) {
unset($options['writeConcern']);
}
$this->databaseName = (string) $databaseName;
$this->collectionName = (string) $collectionName;
$this->filter = $filter;
$this->limit = $limit;
$this->options = $options;
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return DeleteResult
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function execute(Server $server)
{
if (isset($this->options['collation']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) {
throw UnsupportedException::collationNotSupported();
}
$bulk = new Bulk();
$bulk->delete($this->filter, $this->createDeleteOptions());
$writeResult = $server->executeBulkWrite($this->databaseName . '.' . $this->collectionName, $bulk, $this->createExecuteOptions());
return new DeleteResult($writeResult);
}
public function getCommandDocument(Server $server)
{
$cmd = ['delete' => $this->collectionName, 'deletes' => [['q' => $this->filter] + $this->createDeleteOptions()]];
if (isset($this->options['writeConcern'])) {
$cmd['writeConcern'] = $this->options['writeConcern'];
}
return $cmd;
}
/**
* Create options for the delete command.
*
* Note that these options are different from the bulk write options, which
* are created in createExecuteOptions().
*
* @return array
*/
private function createDeleteOptions()
{
$deleteOptions = ['limit' => $this->limit];
if (isset($this->options['collation'])) {
$deleteOptions['collation'] = (object) $this->options['collation'];
}
return $deleteOptions;
}
/**
* Create options for executing the bulk write.
*
* @see http://php.net/manual/en/mongodb-driver-server.executebulkwrite.php
* @return array
*/
private function createExecuteOptions()
{
$options = [];
if (isset($this->options['session'])) {
$options['session'] = $this->options['session'];
}
if (isset($this->options['writeConcern'])) {
$options['writeConcern'] = $this->options['writeConcern'];
}
return $options;
}
}
+82
View File
@@ -0,0 +1,82 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Operation;
use MongoDB\DeleteResult;
use MongoDB\Driver\Server;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
/**
* Operation for deleting multiple document with the delete command.
*
* @api
* @see \MongoDB\Collection::deleteOne()
* @see http://docs.mongodb.org/manual/reference/command/delete/
*/
class DeleteMany implements Executable, Explainable
{
private $delete;
/**
* Constructs a delete command.
*
* Supported options:
*
* * collation (document): Collation specification.
*
* This is not supported for server versions < 3.4 and will result in an
* exception at execution time if used.
*
* * session (MongoDB\Driver\Session): Client session.
*
* Sessions are not supported for server versions < 3.6.
*
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
*
* @param string $databaseName Database name
* @param string $collectionName Collection name
* @param array|object $filter Query by which to delete documents
* @param array $options Command options
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function __construct($databaseName, $collectionName, $filter, array $options = [])
{
$this->delete = new Delete($databaseName, $collectionName, $filter, 0, $options);
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return DeleteResult
* @throws UnsupportedException if collation is used and unsupported
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function execute(Server $server)
{
return $this->delete->execute($server);
}
public function getCommandDocument(Server $server)
{
return $this->delete->getCommandDocument($server);
}
}
+82
View File
@@ -0,0 +1,82 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Operation;
use MongoDB\DeleteResult;
use MongoDB\Driver\Server;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
/**
* Operation for deleting a single document with the delete command.
*
* @api
* @see \MongoDB\Collection::deleteOne()
* @see http://docs.mongodb.org/manual/reference/command/delete/
*/
class DeleteOne implements Executable, Explainable
{
private $delete;
/**
* Constructs a delete command.
*
* Supported options:
*
* * collation (document): Collation specification.
*
* This is not supported for server versions < 3.4 and will result in an
* exception at execution time if used.
*
* * session (MongoDB\Driver\Session): Client session.
*
* Sessions are not supported for server versions < 3.6.
*
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
*
* @param string $databaseName Database name
* @param string $collectionName Collection name
* @param array|object $filter Query by which to delete documents
* @param array $options Command options
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function __construct($databaseName, $collectionName, $filter, array $options = [])
{
$this->delete = new Delete($databaseName, $collectionName, $filter, 1, $options);
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return DeleteResult
* @throws UnsupportedException if collation is used and unsupported
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function execute(Server $server)
{
return $this->delete->execute($server);
}
public function getCommandDocument(Server $server)
{
return $this->delete->getCommandDocument($server);
}
}
+202
View File
@@ -0,0 +1,202 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\ReadConcern;
use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnexpectedValueException;
use MongoDB\Exception\UnsupportedException;
/**
* Operation for the distinct command.
*
* @api
* @see \MongoDB\Collection::distinct()
* @see http://docs.mongodb.org/manual/reference/command/distinct/
*/
class Distinct implements Executable, Explainable
{
private static $wireVersionForCollation = 5;
private static $wireVersionForReadConcern = 4;
private $databaseName;
private $collectionName;
private $fieldName;
private $filter;
private $options;
/**
* Constructs a distinct command.
*
* Supported options:
*
* * collation (document): Collation specification.
*
* This is not supported for server versions < 3.4 and will result in an
* exception at execution time if used.
*
* * maxTimeMS (integer): The maximum amount of time to allow the query to
* run.
*
* * readConcern (MongoDB\Driver\ReadConcern): Read concern.
*
* This is not supported for server versions < 3.2 and will result in an
* exception at execution time if used.
*
* * readPreference (MongoDB\Driver\ReadPreference): Read preference.
*
* * session (MongoDB\Driver\Session): Client session.
*
* Sessions are not supported for server versions < 3.6.
*
* @param string $databaseName Database name
* @param string $collectionName Collection name
* @param string $fieldName Field for which to return distinct values
* @param array|object $filter Query by which to filter documents
* @param array $options Command options
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function __construct($databaseName, $collectionName, $fieldName, $filter = [], array $options = [])
{
if ( ! is_array($filter) && ! is_object($filter)) {
throw InvalidArgumentException::invalidType('$filter', $filter, 'array or object');
}
if (isset($options['collation']) && ! is_array($options['collation']) && ! is_object($options['collation'])) {
throw InvalidArgumentException::invalidType('"collation" option', $options['collation'], 'array or object');
}
if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
}
if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern');
}
if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
}
if (isset($options['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
}
if (isset($options['readConcern']) && $options['readConcern']->isDefault()) {
unset($options['readConcern']);
}
$this->databaseName = (string) $databaseName;
$this->collectionName = (string) $collectionName;
$this->fieldName = (string) $fieldName;
$this->filter = $filter;
$this->options = $options;
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return mixed[]
* @throws UnexpectedValueException if the command response was malformed
* @throws UnsupportedException if collation or read concern is used and unsupported
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function execute(Server $server)
{
if (isset($this->options['collation']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) {
throw UnsupportedException::collationNotSupported();
}
if (isset($this->options['readConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
throw UnsupportedException::readConcernNotSupported();
}
$cursor = $server->executeReadCommand($this->databaseName, new Command($this->createCommandDocument()), $this->createOptions());
$result = current($cursor->toArray());
if ( ! isset($result->values) || ! is_array($result->values)) {
throw new UnexpectedValueException('distinct command did not return a "values" array');
}
return $result->values;
}
public function getCommandDocument(Server $server)
{
return $this->createCommandDocument();
}
/**
* Create the distinct command document.
*
* @return array
*/
private function createCommandDocument()
{
$cmd = [
'distinct' => $this->collectionName,
'key' => $this->fieldName,
];
if ( ! empty($this->filter)) {
$cmd['query'] = (object) $this->filter;
}
if (isset($this->options['collation'])) {
$cmd['collation'] = (object) $this->options['collation'];
}
if (isset($this->options['maxTimeMS'])) {
$cmd['maxTimeMS'] = $this->options['maxTimeMS'];
}
return $cmd;
}
/**
* Create options for executing the command.
*
* @see http://php.net/manual/en/mongodb-driver-server.executereadcommand.php
* @return array
*/
private function createOptions()
{
$options = [];
if (isset($this->options['readConcern'])) {
$options['readConcern'] = $this->options['readConcern'];
}
if (isset($this->options['readPreference'])) {
$options['readPreference'] = $this->options['readPreference'];
}
if (isset($this->options['session'])) {
$options['session'] = $this->options['session'];
}
return $options;
}
}
+148
View File
@@ -0,0 +1,148 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
/**
* Operation for the drop command.
*
* @api
* @see \MongoDB\Collection::drop()
* @see \MongoDB\Database::dropCollection()
* @see http://docs.mongodb.org/manual/reference/command/drop/
*/
class DropCollection implements Executable
{
private static $errorMessageNamespaceNotFound = 'ns not found';
private static $wireVersionForWriteConcern = 5;
private $databaseName;
private $collectionName;
private $options;
/**
* Constructs a drop command.
*
* Supported options:
*
* * session (MongoDB\Driver\Session): Client session.
*
* Sessions are not supported for server versions < 3.6.
*
* * typeMap (array): Type map for BSON deserialization. This will be used
* for the returned command result document.
*
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
*
* This is not supported for server versions < 3.4 and will result in an
* exception at execution time if used.
*
* @param string $databaseName Database name
* @param string $collectionName Collection name
* @param array $options Command options
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function __construct($databaseName, $collectionName, array $options = [])
{
if (isset($options['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
}
if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
}
if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
}
if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) {
unset($options['writeConcern']);
}
$this->databaseName = (string) $databaseName;
$this->collectionName = (string) $collectionName;
$this->options = $options;
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return array|object Command result document
* @throws UnsupportedException if writeConcern is used and unsupported
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function execute(Server $server)
{
if (isset($this->options['writeConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForWriteConcern)) {
throw UnsupportedException::writeConcernNotSupported();
}
$command = new Command(['drop' => $this->collectionName]);
try {
$cursor = $server->executeWriteCommand($this->databaseName, $command, $this->createOptions());
} catch (DriverRuntimeException $e) {
/* The server may return an error if the collection does not exist.
* Check for an error message (unfortunately, there isn't a code)
* and NOP instead of throwing.
*/
if ($e->getMessage() === self::$errorMessageNamespaceNotFound) {
return (object) ['ok' => 0, 'errmsg' => self::$errorMessageNamespaceNotFound];
}
throw $e;
}
if (isset($this->options['typeMap'])) {
$cursor->setTypeMap($this->options['typeMap']);
}
return current($cursor->toArray());
}
/**
* Create options for executing the command.
*
* @see http://php.net/manual/en/mongodb-driver-server.executewritecommand.php
* @return array
*/
private function createOptions()
{
$options = [];
if (isset($this->options['session'])) {
$options['session'] = $this->options['session'];
}
if (isset($this->options['writeConcern'])) {
$options['writeConcern'] = $this->options['writeConcern'];
}
return $options;
}
}
+131
View File
@@ -0,0 +1,131 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
/**
* Operation for the dropDatabase command.
*
* @api
* @see \MongoDB\Client::dropDatabase()
* @see \MongoDB\Database::drop()
* @see http://docs.mongodb.org/manual/reference/command/dropDatabase/
*/
class DropDatabase implements Executable
{
private static $wireVersionForWriteConcern = 5;
private $databaseName;
private $options;
/**
* Constructs a dropDatabase command.
*
* Supported options:
*
* * session (MongoDB\Driver\Session): Client session.
*
* Sessions are not supported for server versions < 3.6.
*
* * typeMap (array): Type map for BSON deserialization. This will be used
* for the returned command result document.
*
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
*
* This is not supported for server versions < 3.4 and will result in an
* exception at execution time if used.
*
* @param string $databaseName Database name
* @param array $options Command options
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function __construct($databaseName, array $options = [])
{
if (isset($options['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
}
if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
}
if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
}
if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) {
unset($options['writeConcern']);
}
$this->databaseName = (string) $databaseName;
$this->options = $options;
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return array|object Command result document
* @throws UnsupportedException if writeConcern is used and unsupported
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function execute(Server $server)
{
if (isset($this->options['writeConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForWriteConcern)) {
throw UnsupportedException::writeConcernNotSupported();
}
$command = new Command(['dropDatabase' => 1]);
$cursor = $server->executeWriteCommand($this->databaseName, $command, $this->createOptions());
if (isset($this->options['typeMap'])) {
$cursor->setTypeMap($this->options['typeMap']);
}
return current($cursor->toArray());
}
/**
* Create options for executing the command.
*
* @see http://php.net/manual/en/mongodb-driver-server.executewritecommand.php
* @return array
*/
private function createOptions()
{
$options = [];
if (isset($this->options['session'])) {
$options['session'] = $this->options['session'];
}
if (isset($this->options['writeConcern'])) {
$options['writeConcern'] = $this->options['writeConcern'];
}
return $options;
}
}
+167
View File
@@ -0,0 +1,167 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
/**
* Operation for the dropIndexes command.
*
* @api
* @see \MongoDB\Collection::dropIndexes()
* @see http://docs.mongodb.org/manual/reference/command/dropIndexes/
*/
class DropIndexes implements Executable
{
private static $wireVersionForWriteConcern = 5;
private $databaseName;
private $collectionName;
private $indexName;
private $options;
/**
* Constructs a dropIndexes command.
*
* Supported options:
*
* * maxTimeMS (integer): The maximum amount of time to allow the query to
* run.
*
* * session (MongoDB\Driver\Session): Client session.
*
* Sessions are not supported for server versions < 3.6.
*
* * typeMap (array): Type map for BSON deserialization. This will be used
* for the returned command result document.
*
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
*
* This is not supported for server versions < 3.4 and will result in an
* exception at execution time if used.
*
* @param string $databaseName Database name
* @param string $collectionName Collection name
* @param string $indexName Index name (use "*" to drop all indexes)
* @param array $options Command options
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function __construct($databaseName, $collectionName, $indexName, array $options = [])
{
$indexName = (string) $indexName;
if ($indexName === '') {
throw new InvalidArgumentException('$indexName cannot be empty');
}
if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
}
if (isset($options['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
}
if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
}
if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
}
if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) {
unset($options['writeConcern']);
}
$this->databaseName = (string) $databaseName;
$this->collectionName = (string) $collectionName;
$this->indexName = $indexName;
$this->options = $options;
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return array|object Command result document
* @throws UnsupportedException if writeConcern is used and unsupported
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function execute(Server $server)
{
if (isset($this->options['writeConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForWriteConcern)) {
throw UnsupportedException::writeConcernNotSupported();
}
$cursor = $server->executeWriteCommand($this->databaseName, $this->createCommand(), $this->createOptions());
if (isset($this->options['typeMap'])) {
$cursor->setTypeMap($this->options['typeMap']);
}
return current($cursor->toArray());
}
/**
* Create the dropIndexes command.
*
* @return Command
*/
private function createCommand()
{
$cmd = [
'dropIndexes' => $this->collectionName,
'index' => $this->indexName,
];
if (isset($this->options['maxTimeMS'])) {
$cmd['maxTimeMS'] = $this->options['maxTimeMS'];
}
return new Command($cmd);
}
/**
* Create options for executing the command.
*
* @see http://php.net/manual/en/mongodb-driver-server.executewritecommand.php
* @return array
*/
private function createOptions()
{
$options = [];
if (isset($this->options['session'])) {
$options['session'] = $this->options['session'];
}
if (isset($this->options['writeConcern'])) {
$options['writeConcern'] = $this->options['writeConcern'];
}
return $options;
}
}
+173
View File
@@ -0,0 +1,173 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\ReadConcern;
use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnexpectedValueException;
use MongoDB\Exception\UnsupportedException;
/**
* Operation for obtaining an estimated count of documents in a collection
*
* @api
* @see \MongoDB\Collection::estimatedDocumentCount()
* @see http://docs.mongodb.org/manual/reference/command/count/
*/
class EstimatedDocumentCount implements Executable, Explainable
{
private static $wireVersionForCollation = 5;
private static $wireVersionForReadConcern = 4;
private $databaseName;
private $collectionName;
private $options;
/**
* Constructs a count command.
*
* Supported options:
*
* * maxTimeMS (integer): The maximum amount of time to allow the query to
* run.
*
* * readConcern (MongoDB\Driver\ReadConcern): Read concern.
*
* This is not supported for server versions < 3.2 and will result in an
* exception at execution time if used.
*
* * readPreference (MongoDB\Driver\ReadPreference): Read preference.
*
* * session (MongoDB\Driver\Session): Client session.
*
* Sessions are not supported for server versions < 3.6.
*
* @param string $databaseName Database name
* @param string $collectionName Collection name
* @param 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;
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return integer
* @throws UnexpectedValueException if the command response was malformed
* @throws UnsupportedException if collation or read concern is used and unsupported
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function execute(Server $server)
{
if (isset($this->options['collation']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) {
throw UnsupportedException::collationNotSupported();
}
if (isset($this->options['readConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
throw UnsupportedException::readConcernNotSupported();
}
$cursor = $server->executeReadCommand($this->databaseName, new Command($this->createCommandDocument()), $this->createOptions());
$result = current($cursor->toArray());
// Older server versions may return a float
if ( ! isset($result->n) || ! (is_integer($result->n) || is_float($result->n))) {
throw new UnexpectedValueException('count command did not return a numeric "n" value');
}
return (integer) $result->n;
}
public function getCommandDocument(Server $server)
{
return $this->createCommandDocument();
}
/**
* Create the count command document.
*
* @return array
*/
private function createCommandDocument()
{
$cmd = ['count' => $this->collectionName];
if (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;
}
}
+39
View File
@@ -0,0 +1,39 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Operation;
use MongoDB\Driver\Server;
/**
* Executable interface for operation classes.
*
* This interface is reserved for internal use until PHPC-378 is implemented,
* since execute() should ultimately be changed to use ServerInterface.
*
* @internal
*/
interface Executable
{
/**
* Execute the operation.
*
* @param Server $server
* @return mixed
*/
public function execute(Server $server);
}
+143
View File
@@ -0,0 +1,143 @@
<?php
/*
* Copyright 2018 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Operation;
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;
/**
* Operation for the explain command.
*
* @api
* @see \MongoDB\Collection::explain()
* @see http://docs.mongodb.org/manual/reference/command/explain/
*/
class Explain implements Executable
{
const VERBOSITY_ALL_PLANS = 'allPlansExecution';
const VERBOSITY_EXEC_STATS = 'executionStats';
const VERBOSITY_QUERY = 'queryPlanner';
private static $wireVersionForDistinct = 4;
private static $wireVersionForFindAndModify = 4;
private $databaseName;
private $explainable;
private $options;
/**
* Constructs an explain command for explainable operations.
*
* Supported options:
*
* * readPreference (MongoDB\Driver\ReadPreference): Read preference.
*
* * session (MongoDB\Driver\Session): Client session.
*
* * typeMap (array): Type map for BSON deserialization. This will be used
* used for the returned command result document.
*
* * verbosity (string): The mode in which the explain command will be run.
*
* @param string $databaseName Database name
* @param Explainable $explainable Operation to explain
* @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');
}
if (isset($options['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
}
if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
}
if (isset($options['verbosity']) && ! is_string($options['verbosity'])) {
throw InvalidArgumentException::invalidType('"verbosity" option', $options['verbosity'], 'string');
}
$this->databaseName = $databaseName;
$this->explainable = $explainable;
$this->options = $options;
}
public function execute(Server $server)
{
if ($this->explainable instanceof Distinct && ! \MongoDB\server_supports_feature($server, self::$wireVersionForDistinct)) {
throw UnsupportedException::explainNotSupported();
}
if ($this->isFindAndModify($this->explainable) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForFindAndModify)) {
throw UnsupportedException::explainNotSupported();
}
$cmd = ['explain' => $this->explainable->getCommandDocument($server)];
if (isset($this->options['verbosity'])) {
$cmd['verbosity'] = $this->options['verbosity'];
}
$cursor = $server->executeCommand($this->databaseName, new Command($cmd), $this->createOptions());
if (isset($this->options['typeMap'])) {
$cursor->setTypeMap($this->options['typeMap']);
}
return current($cursor->toArray());
}
/**
* Create options for executing the command.
*
* @see http://php.net/manual/en/mongodb-driver-server.executecommand.php
* @return array
*/
private function createOptions()
{
$options = [];
if (isset($this->options['readPreference'])) {
$options['readPreference'] = $this->options['readPreference'];
}
if (isset($this->options['session'])) {
$options['session'] = $this->options['session'];
}
return $options;
}
private function isFindAndModify($explainable)
{
if ($explainable instanceof FindAndModify || $explainable instanceof FindOneAndDelete || $explainable instanceof FindOneAndReplace || $explainable instanceof FindOneAndUpdate) {
return true;
}
return false;
}
}
+31
View File
@@ -0,0 +1,31 @@
<?php
/*
* Copyright 2018 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Operation;
use MongoDB\Driver\Server;
/**
* Explainable interface for explainable operations (count, distinct, find,
* findAndModify, delete, and update).
*
* @internal
*/
interface Explainable extends Executable
{
function getCommandDocument(Server $server);
}
+416
View File
@@ -0,0 +1,416 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Operation;
use MongoDB\Driver\Cursor;
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;
/**
* Operation for the find command.
*
* @api
* @see \MongoDB\Collection::find()
* @see http://docs.mongodb.org/manual/tutorial/query-documents/
* @see http://docs.mongodb.org/manual/reference/operator/query-modifier/
*/
class Find implements Executable, Explainable
{
const NON_TAILABLE = 1;
const TAILABLE = 2;
const TAILABLE_AWAIT = 3;
private static $wireVersionForCollation = 5;
private static $wireVersionForReadConcern = 4;
private $databaseName;
private $collectionName;
private $filter;
private $options;
/**
* Constructs a find command.
*
* Supported options:
*
* * allowPartialResults (boolean): Get partial results from a mongos if
* some shards are inaccessible (instead of throwing an error).
*
* * batchSize (integer): The number of documents to return per batch.
*
* * collation (document): Collation specification.
*
* This is not supported for server versions < 3.4 and will result in an
* exception at execution time if used.
*
* * comment (string): Attaches a comment to the query. If "$comment" also
* exists in the modifiers document, this option will take precedence.
*
* * cursorType (enum): Indicates the type of cursor to use. Must be either
* NON_TAILABLE, TAILABLE, or TAILABLE_AWAIT. The default is
* NON_TAILABLE.
*
* * hint (string|document): The index to use. Specify either the index
* name as a string or the index key pattern as a document. If specified,
* then the query system will only consider plans using the hinted index.
*
* * limit (integer): The maximum number of documents to return.
*
* * max (document): The exclusive upper bound for a specific index.
*
* * maxAwaitTimeMS (integer): The maxium amount of time for the server to wait
* on new documents to satisfy a query, if cursorType is TAILABLE_AWAIT.
*
* * maxScan (integer): Maximum number of documents or index keys to scan
* when executing the query.
*
* This option has been deprecated since version 1.4.
*
* * maxTimeMS (integer): The maximum amount of time to allow the query to
* run. If "$maxTimeMS" also exists in the modifiers document, this
* option will take precedence.
*
* * min (document): The inclusive upper bound for a specific index.
*
* * modifiers (document): Meta operators that modify the output or
* behavior of a query. Use of these operators is deprecated in favor of
* named options.
*
* * noCursorTimeout (boolean): The server normally times out idle cursors
* after an inactivity period (10 minutes) to prevent excess memory use.
* Set this option to prevent that.
*
* * oplogReplay (boolean): Internal replication use only. The driver
* should not set this.
*
* * projection (document): Limits the fields to return for the matching
* document.
*
* * 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.
*
* * returnKey (boolean): If true, returns only the index keys in the
* resulting documents.
*
* * session (MongoDB\Driver\Session): Client session.
*
* Sessions are not supported for server versions < 3.6.
*
* * showRecordId (boolean): Determines whether to return the record
* identifier for each document. If true, adds a field $recordId to the
* returned documents.
*
* * skip (integer): The number of documents to skip before returning.
*
* * snapshot (boolean): Prevents the cursor from returning a document more
* than once because of an intervening write operation.
*
* This options has been deprecated since version 1.4.
*
* * sort (document): The order in which to return matching documents. If
* "$orderby" also exists in the modifiers document, this option will
* take precedence.
*
* * 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 string $collectionName Collection name
* @param array|object $filter Query by which to filter documents
* @param array $options Command options
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function __construct($databaseName, $collectionName, $filter, array $options = [])
{
if ( ! is_array($filter) && ! is_object($filter)) {
throw InvalidArgumentException::invalidType('$filter', $filter, 'array or object');
}
if (isset($options['allowPartialResults']) && ! is_bool($options['allowPartialResults'])) {
throw InvalidArgumentException::invalidType('"allowPartialResults" option', $options['allowPartialResults'], 'boolean');
}
if (isset($options['batchSize']) && ! is_integer($options['batchSize'])) {
throw InvalidArgumentException::invalidType('"batchSize" option', $options['batchSize'], 'integer');
}
if (isset($options['collation']) && ! is_array($options['collation']) && ! is_object($options['collation'])) {
throw InvalidArgumentException::invalidType('"collation" option', $options['collation'], 'array or object');
}
if (isset($options['comment']) && ! is_string($options['comment'])) {
throw InvalidArgumentException::invalidType('"comment" option', $options['comment'], 'comment');
}
if (isset($options['cursorType'])) {
if ( ! is_integer($options['cursorType'])) {
throw InvalidArgumentException::invalidType('"cursorType" option', $options['cursorType'], 'integer');
}
if ($options['cursorType'] !== self::NON_TAILABLE &&
$options['cursorType'] !== self::TAILABLE &&
$options['cursorType'] !== self::TAILABLE_AWAIT) {
throw new InvalidArgumentException('Invalid value for "cursorType" option: ' . $options['cursorType']);
}
}
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['max']) && ! is_array($options['max']) && ! is_object($options['max'])) {
throw InvalidArgumentException::invalidType('"max" option', $options['max'], 'array or object');
}
if (isset($options['maxAwaitTimeMS']) && ! is_integer($options['maxAwaitTimeMS'])) {
throw InvalidArgumentException::invalidType('"maxAwaitTimeMS" option', $options['maxAwaitTimeMS'], 'integer');
}
if (isset($options['maxScan']) && ! is_integer($options['maxScan'])) {
throw InvalidArgumentException::invalidType('"maxScan" option', $options['maxScan'], 'integer');
}
if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
}
if (isset($options['min']) && ! is_array($options['min']) && ! is_object($options['min'])) {
throw InvalidArgumentException::invalidType('"min" option', $options['min'], 'array or object');
}
if (isset($options['modifiers']) && ! is_array($options['modifiers']) && ! is_object($options['modifiers'])) {
throw InvalidArgumentException::invalidType('"modifiers" option', $options['modifiers'], 'array or object');
}
if (isset($options['noCursorTimeout']) && ! is_bool($options['noCursorTimeout'])) {
throw InvalidArgumentException::invalidType('"noCursorTimeout" option', $options['noCursorTimeout'], 'boolean');
}
if (isset($options['oplogReplay']) && ! is_bool($options['oplogReplay'])) {
throw InvalidArgumentException::invalidType('"oplogReplay" option', $options['oplogReplay'], 'boolean');
}
if (isset($options['projection']) && ! is_array($options['projection']) && ! is_object($options['projection'])) {
throw InvalidArgumentException::invalidType('"projection" option', $options['projection'], 'array or object');
}
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['returnKey']) && ! is_bool($options['returnKey'])) {
throw InvalidArgumentException::invalidType('"returnKey" option', $options['returnKey'], 'boolean');
}
if (isset($options['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
}
if (isset($options['showRecordId']) && ! is_bool($options['showRecordId'])) {
throw InvalidArgumentException::invalidType('"showRecordId" option', $options['showRecordId'], 'boolean');
}
if (isset($options['skip']) && ! is_integer($options['skip'])) {
throw InvalidArgumentException::invalidType('"skip" option', $options['skip'], 'integer');
}
if (isset($options['snapshot']) && ! is_bool($options['snapshot'])) {
throw InvalidArgumentException::invalidType('"snapshot" option', $options['snapshot'], 'boolean');
}
if (isset($options['sort']) && ! is_array($options['sort']) && ! is_object($options['sort'])) {
throw InvalidArgumentException::invalidType('"sort" option', $options['sort'], 'array or object');
}
if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
}
if (isset($options['readConcern']) && $options['readConcern']->isDefault()) {
unset($options['readConcern']);
}
if (isset($options['snapshot'])) {
trigger_error('The "snapshot" option is deprecated and will be removed in a future release', E_USER_DEPRECATED);
}
if (isset($options['maxScan'])) {
trigger_error('The "maxScan" option is deprecated and will be removed in a future release', E_USER_DEPRECATED);
}
$this->databaseName = (string) $databaseName;
$this->collectionName = (string) $collectionName;
$this->filter = $filter;
$this->options = $options;
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return Cursor
* @throws UnsupportedException if collation or read concern is used and unsupported
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function execute(Server $server)
{
if (isset($this->options['collation']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) {
throw UnsupportedException::collationNotSupported();
}
if (isset($this->options['readConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
throw UnsupportedException::readConcernNotSupported();
}
$cursor = $server->executeQuery($this->databaseName . '.' . $this->collectionName, new Query($this->filter, $this->createQueryOptions()), $this->createExecuteOptions());
if (isset($this->options['typeMap'])) {
$cursor->setTypeMap($this->options['typeMap']);
}
return $cursor;
}
public function getCommandDocument(Server $server)
{
return $this->createCommandDocument();
}
/**
* Construct a command document for Find
*/
private function createCommandDocument()
{
$cmd = ['find' => $this->collectionName, 'filter' => (object) $this->filter];
$options = $this->createQueryOptions();
if (empty($options)) {
return $cmd;
}
// maxAwaitTimeMS is a Query level option so should not be considered here
unset($options['maxAwaitTimeMS']);
$modifierFallback = [
['allowPartialResults', 'partial'],
['comment', '$comment'],
['hint', '$hint'],
['maxScan', '$maxScan'],
['max', '$max'],
['maxTimeMS', '$maxTimeMS'],
['min', '$min'],
['returnKey', '$returnKey'],
['showRecordId', '$showDiskLoc'],
['sort', '$orderby'],
['snapshot', '$snapshot'],
];
foreach ($modifierFallback as $modifier) {
if ( ! isset($options[$modifier[0]]) && isset($options['modifiers'][$modifier[1]])) {
$options[$modifier[0]] = $options['modifiers'][$modifier[1]];
}
}
unset($options['modifiers']);
return $cmd + $options;
}
/**
* Create options for executing the command.
*
* @see http://php.net/manual/en/mongodb-driver-server.executequery.php
* @return array
*/
private function createExecuteOptions()
{
$options = [];
if (isset($this->options['readPreference'])) {
$options['readPreference'] = $this->options['readPreference'];
}
if (isset($this->options['session'])) {
$options['session'] = $this->options['session'];
}
return $options;
}
/**
* Create options for the find query.
*
* Note that these are separate from the options for executing the command,
* which are created in createExecuteOptions().
*
* @return array
*/
private function createQueryOptions()
{
$options = [];
if (isset($this->options['cursorType'])) {
if ($this->options['cursorType'] === self::TAILABLE) {
$options['tailable'] = true;
}
if ($this->options['cursorType'] === self::TAILABLE_AWAIT) {
$options['tailable'] = true;
$options['awaitData'] = true;
}
}
foreach (['allowPartialResults', 'batchSize', 'comment', 'hint', 'limit', 'maxAwaitTimeMS', 'maxScan', 'maxTimeMS', 'noCursorTimeout', 'oplogReplay', 'projection', 'readConcern', 'returnKey', 'showRecordId', 'skip', 'snapshot', 'sort'] as $option) {
if (isset($this->options[$option])) {
$options[$option] = $this->options[$option];
}
}
foreach (['collation', 'max', 'min'] as $option) {
if (isset($this->options[$option])) {
$options[$option] = (object) $this->options[$option];
}
}
$modifiers = empty($this->options['modifiers']) ? [] : (array) $this->options['modifiers'];
if ( ! empty($modifiers)) {
$options['modifiers'] = $modifiers;
}
return $options;
}
}
+293
View File
@@ -0,0 +1,293 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnexpectedValueException;
use MongoDB\Exception\UnsupportedException;
/**
* Operation for the findAndModify command.
*
* This class is used internally by the FindOneAndDelete, FindOneAndReplace, and
* FindOneAndUpdate operation classes.
*
* @internal
* @see http://docs.mongodb.org/manual/reference/command/findAndModify/
*/
class FindAndModify implements Executable, Explainable
{
private static $wireVersionForArrayFilters = 6;
private static $wireVersionForCollation = 5;
private static $wireVersionForDocumentLevelValidation = 4;
private static $wireVersionForWriteConcern = 4;
private $databaseName;
private $collectionName;
private $options;
/**
* Constructs a findAndModify command.
*
* Supported options:
*
* * arrayFilters (document array): A set of filters specifying to which
* array elements an update should apply.
*
* This is not supported for server versions < 3.6 and will result in an
* exception at execution time if used.
*
* * collation (document): Collation specification.
*
* This is not supported for server versions < 3.4 and will result in an
* exception at execution time if used.
*
* * bypassDocumentValidation (boolean): If true, allows the write to
* circumvent document level validation.
*
* For servers < 3.2, this option is ignored as document level validation
* is not available.
*
* * fields (document): Limits the fields to return for the matching
* document.
*
* * maxTimeMS (integer): The maximum amount of time to allow the query to
* run.
*
* * new (boolean): When true, returns the modified document rather than
* the original. This option is ignored for remove operations. The
* The default is false.
*
* * query (document): Query by which to filter documents.
*
* * remove (boolean): When true, removes the matched document. This option
* cannot be true if the update option is set. The default is false.
*
* * session (MongoDB\Driver\Session): Client session.
*
* Sessions are not supported for server versions < 3.6.
*
* * sort (document): Determines which document the operation modifies if
* the query selects multiple documents.
*
* * typeMap (array): Type map for BSON deserialization.
*
* * update (document): Update or replacement to apply to the matched
* document. This option cannot be set if the remove option is true.
*
* * upsert (boolean): When true, a new document is created if no document
* matches the query. This option is ignored for remove operations. The
* default is false.
*
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
*
* 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 name
* @param array $options Command options
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function __construct($databaseName, $collectionName, array $options)
{
$options += [
'new' => false,
'remove' => false,
'upsert' => false,
];
if (isset($options['arrayFilters']) && ! is_array($options['arrayFilters'])) {
throw InvalidArgumentException::invalidType('"arrayFilters" option', $options['arrayFilters'], 'array');
}
if (isset($options['bypassDocumentValidation']) && ! is_bool($options['bypassDocumentValidation'])) {
throw InvalidArgumentException::invalidType('"bypassDocumentValidation" option', $options['bypassDocumentValidation'], 'boolean');
}
if (isset($options['collation']) && ! is_array($options['collation']) && ! is_object($options['collation'])) {
throw InvalidArgumentException::invalidType('"collation" option', $options['collation'], 'array or object');
}
if (isset($options['fields']) && ! is_array($options['fields']) && ! is_object($options['fields'])) {
throw InvalidArgumentException::invalidType('"fields" option', $options['fields'], 'array or object');
}
if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
}
if ( ! is_bool($options['new'])) {
throw InvalidArgumentException::invalidType('"new" option', $options['new'], 'boolean');
}
if (isset($options['query']) && ! is_array($options['query']) && ! is_object($options['query'])) {
throw InvalidArgumentException::invalidType('"query" option', $options['query'], 'array or object');
}
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');
}
if (isset($options['sort']) && ! is_array($options['sort']) && ! is_object($options['sort'])) {
throw InvalidArgumentException::invalidType('"sort" option', $options['sort'], 'array or object');
}
if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
}
if (isset($options['update']) && ! is_array($options['update']) && ! is_object($options['update'])) {
throw InvalidArgumentException::invalidType('"update" option', $options['update'], 'array or object');
}
if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
}
if ( ! is_bool($options['upsert'])) {
throw InvalidArgumentException::invalidType('"upsert" option', $options['upsert'], 'boolean');
}
if ( ! (isset($options['update']) xor $options['remove'])) {
throw new InvalidArgumentException('The "remove" option must be true or an "update" document must be specified, but not both');
}
if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) {
unset($options['writeConcern']);
}
$this->databaseName = (string) $databaseName;
$this->collectionName = (string) $collectionName;
$this->options = $options;
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return array|object|null
* @throws UnexpectedValueException if the command response was malformed
* @throws UnsupportedException if array filters, collation, or write concern is used and unsupported
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function execute(Server $server)
{
if (isset($this->options['arrayFilters']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForArrayFilters)) {
throw UnsupportedException::arrayFiltersNotSupported();
}
if (isset($this->options['collation']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) {
throw UnsupportedException::collationNotSupported();
}
if (isset($this->options['writeConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForWriteConcern)) {
throw UnsupportedException::writeConcernNotSupported();
}
$cursor = $server->executeWriteCommand($this->databaseName, 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']);
}
return $result->value;
}
public function getCommandDocument(Server $server)
{
return $this->createCommandDocument($server);
}
/**
* Create the findAndModify command document.
*
* @return array
*/
private function createCommandDocument(Server $server)
{
$cmd = ['findAndModify' => $this->collectionName];
if ($this->options['remove']) {
$cmd['remove'] = true;
} else {
$cmd['new'] = $this->options['new'];
$cmd['upsert'] = $this->options['upsert'];
}
foreach (['collation', 'fields', 'query', 'sort', 'update'] as $option) {
if (isset($this->options[$option])) {
$cmd[$option] = (object) $this->options[$option];
}
}
if (isset($this->options['arrayFilters'])) {
$cmd['arrayFilters'] = $this->options['arrayFilters'];
}
if (isset($this->options['maxTimeMS'])) {
$cmd['maxTimeMS'] = $this->options['maxTimeMS'];
}
if (isset($this->options['bypassDocumentValidation']) && \MongoDB\server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)) {
$cmd['bypassDocumentValidation'] = $this->options['bypassDocumentValidation'];
}
return $cmd;
}
/**
* Create options for executing the command.
*
* @see http://php.net/manual/en/mongodb-driver-server.executewritecommand.php
* @return array
*/
private function createOptions()
{
$options = [];
if (isset($this->options['session'])) {
$options['session'] = $this->options['session'];
}
if (isset($this->options['writeConcern'])) {
$options['writeConcern'] = $this->options['writeConcern'];
}
return $options;
}
}
+136
View File
@@ -0,0 +1,136 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Operation;
use MongoDB\Driver\Server;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
/**
* Operation for finding a single document with the find command.
*
* @api
* @see \MongoDB\Collection::findOne()
* @see http://docs.mongodb.org/manual/tutorial/query-documents/
* @see http://docs.mongodb.org/manual/reference/operator/query-modifier/
*/
class FindOne implements Executable, Explainable
{
private $find;
/**
* Constructs a find command for finding a single document.
*
* Supported options:
*
* * collation (document): Collation specification.
*
* This is not supported for server versions < 3.4 and will result in an
* exception at execution time if used.
*
* * comment (string): Attaches a comment to the query. If "$comment" also
* exists in the modifiers document, this option will take precedence.
*
* * hint (string|document): The index to use. Specify either the index
* name as a string or the index key pattern as a document. If specified,
* then the query system will only consider plans using the hinted index.
*
* * max (document): The exclusive upper bound for a specific index.
*
* * maxScan (integer): Maximum number of documents or index keys to scan
* when executing the query.
*
* This option has been deprecated since version 1.4.
*
* * maxTimeMS (integer): The maximum amount of time to allow the query to
* run. If "$maxTimeMS" also exists in the modifiers document, this
* option will take precedence.
*
* * min (document): The inclusive upper bound for a specific index.
*
* * modifiers (document): Meta-operators modifying the output or behavior
* of a query.
*
* * projection (document): Limits the fields to return for the matching
* document.
*
* * 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.
*
* * returnKey (boolean): If true, returns only the index keys in the
* resulting documents.
*
* * session (MongoDB\Driver\Session): Client session.
*
* Sessions are not supported for server versions < 3.6.
*
* * showRecordId (boolean): Determines whether to return the record
* identifier for each document. If true, adds a field $recordId to the
* returned documents.
*
* * skip (integer): The number of documents to skip before returning.
*
* * sort (document): The order in which to return matching documents. If
* "$orderby" also exists in the modifiers document, this option will
* take precedence.
*
* * typeMap (array): Type map for BSON deserialization.
*
* @param string $databaseName Database name
* @param string $collectionName Collection name
* @param array|object $filter Query by which to filter documents
* @param array $options Command options
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function __construct($databaseName, $collectionName, $filter, array $options = [])
{
$this->find = new Find(
$databaseName,
$collectionName,
$filter,
['limit' => 1] + $options
);
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return array|object|null
* @throws UnsupportedException if collation or read concern is used and unsupported
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function execute(Server $server)
{
$cursor = $this->find->execute($server);
$document = current($cursor->toArray());
return ($document === false) ? null : $document;
}
public function getCommandDocument(Server $server)
{
return $this->find->getCommandDocument($server);
}
}
+113
View File
@@ -0,0 +1,113 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Operation;
use MongoDB\Driver\Server;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
/**
* Operation for deleting a document with the findAndModify command.
*
* @api
* @see \MongoDB\Collection::findOneAndDelete()
* @see http://docs.mongodb.org/manual/reference/command/findAndModify/
*/
class FindOneAndDelete implements Executable, Explainable
{
private $findAndModify;
/**
* Constructs a findAndModify command for deleting a document.
*
* Supported options:
*
* * collation (document): Collation specification.
*
* This is not supported for server versions < 3.4 and will result in an
* exception at execution time if used.
*
* * maxTimeMS (integer): The maximum amount of time to allow the query to
* run.
*
* * projection (document): Limits the fields to return for the matching
* document.
*
* * session (MongoDB\Driver\Session): Client session.
*
* Sessions are not supported for server versions < 3.6.
*
* * sort (document): Determines which document the operation modifies if
* the query selects multiple documents.
*
* * typeMap (array): Type map for BSON deserialization.
*
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
*
* 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 name
* @param array|object $filter Query by which to filter documents
* @param array $options Command options
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function __construct($databaseName, $collectionName, $filter, array $options = [])
{
if ( ! is_array($filter) && ! is_object($filter)) {
throw InvalidArgumentException::invalidType('$filter', $filter, 'array or object');
}
if (isset($options['projection']) && ! is_array($options['projection']) && ! is_object($options['projection'])) {
throw InvalidArgumentException::invalidType('"projection" option', $options['projection'], 'array or object');
}
if (isset($options['projection'])) {
$options['fields'] = $options['projection'];
}
unset($options['projection']);
$this->findAndModify = new FindAndModify(
$databaseName,
$collectionName,
['query' => $filter, 'remove' => true] + $options
);
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return array|object|null
* @throws UnsupportedException if collation or write concern is used and unsupported
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function execute(Server $server)
{
return $this->findAndModify->execute($server);
}
public function getCommandDocument(Server $server)
{
return $this->findAndModify->getCommandDocument($server);
}
}
+156
View File
@@ -0,0 +1,156 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Operation;
use MongoDB\Driver\Server;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
/**
* Operation for replacing a document with the findAndModify command.
*
* @api
* @see \MongoDB\Collection::findOneAndReplace()
* @see http://docs.mongodb.org/manual/reference/command/findAndModify/
*/
class FindOneAndReplace implements Executable, Explainable
{
const RETURN_DOCUMENT_BEFORE = 1;
const RETURN_DOCUMENT_AFTER = 2;
private $findAndModify;
/**
* Constructs a findAndModify command for replacing a document.
*
* Supported options:
*
* * bypassDocumentValidation (boolean): If true, allows the write to
* circumvent document level validation.
*
* For servers < 3.2, this option is ignored as document level validation
* is not available.
*
* * collation (document): Collation specification.
*
* This is not supported for server versions < 3.4 and will result in an
* exception at execution time if used.
*
* * maxTimeMS (integer): The maximum amount of time to allow the query to
* run.
*
* * projection (document): Limits the fields to return for the matching
* document.
*
* * returnDocument (enum): Whether to return the document before or after
* the update is applied. Must be either
* FindOneAndReplace::RETURN_DOCUMENT_BEFORE or
* FindOneAndReplace::RETURN_DOCUMENT_AFTER. The default is
* FindOneAndReplace::RETURN_DOCUMENT_BEFORE.
*
* * session (MongoDB\Driver\Session): Client session.
*
* Sessions are not supported for server versions < 3.6.
*
* * sort (document): Determines which document the operation modifies if
* the query selects multiple documents.
*
* * typeMap (array): Type map for BSON deserialization.
*
* * upsert (boolean): When true, a new document is created if no document
* matches the query. The default is false.
*
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
*
* 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 name
* @param array|object $filter Query by which to filter documents
* @param array|object $replacement Replacement document
* @param array $options Command options
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function __construct($databaseName, $collectionName, $filter, $replacement, array $options = [])
{
if ( ! is_array($filter) && ! is_object($filter)) {
throw InvalidArgumentException::invalidType('$filter', $filter, 'array or object');
}
if ( ! is_array($replacement) && ! is_object($replacement)) {
throw InvalidArgumentException::invalidType('$replacement', $replacement, 'array or object');
}
if (\MongoDB\is_first_key_operator($replacement)) {
throw new InvalidArgumentException('First key in $replacement argument is an update operator');
}
$options += [
'returnDocument' => self::RETURN_DOCUMENT_BEFORE,
'upsert' => false,
];
if (isset($options['projection']) && ! is_array($options['projection']) && ! is_object($options['projection'])) {
throw InvalidArgumentException::invalidType('"projection" option', $options['projection'], 'array or object');
}
if ( ! is_integer($options['returnDocument'])) {
throw InvalidArgumentException::invalidType('"returnDocument" option', $options['returnDocument'], 'integer');
}
if ($options['returnDocument'] !== self::RETURN_DOCUMENT_AFTER &&
$options['returnDocument'] !== self::RETURN_DOCUMENT_BEFORE) {
throw new InvalidArgumentException('Invalid value for "returnDocument" option: ' . $options['returnDocument']);
}
if (isset($options['projection'])) {
$options['fields'] = $options['projection'];
}
$options['new'] = $options['returnDocument'] === self::RETURN_DOCUMENT_AFTER;
unset($options['projection'], $options['returnDocument']);
$this->findAndModify = new FindAndModify(
$databaseName,
$collectionName,
['query' => $filter, 'update' => $replacement] + $options
);
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return array|object|null
* @throws UnsupportedException if collation or write concern is used and unsupported
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function execute(Server $server)
{
return $this->findAndModify->execute($server);
}
public function getCommandDocument(Server $server)
{
return $this->findAndModify->getCommandDocument($server);
}
}
+159
View File
@@ -0,0 +1,159 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Operation;
use MongoDB\Driver\Server;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
/**
* Operation for updating a document with the findAndModify command.
*
* @api
* @see \MongoDB\Collection::findOneAndUpdate()
* @see http://docs.mongodb.org/manual/reference/command/findAndModify/
*/
class FindOneAndUpdate implements Executable, Explainable
{
const RETURN_DOCUMENT_BEFORE = 1;
const RETURN_DOCUMENT_AFTER = 2;
private $findAndModify;
/**
* Constructs a findAndModify command for updating a document.
*
* Supported options:
*
* * arrayFilters (document array): A set of filters specifying to which
* array elements an update should apply.
*
* * bypassDocumentValidation (boolean): If true, allows the write to
* circumvent document level validation.
*
* For servers < 3.2, this option is ignored as document level validation
* is not available.
*
* * collation (document): Collation specification.
*
* This is not supported for server versions < 3.4 and will result in an
* exception at execution time if used.
*
* * maxTimeMS (integer): The maximum amount of time to allow the query to
* run.
*
* * projection (document): Limits the fields to return for the matching
* document.
*
* * returnDocument (enum): Whether to return the document before or after
* the update is applied. Must be either
* FindOneAndUpdate::RETURN_DOCUMENT_BEFORE or
* FindOneAndUpdate::RETURN_DOCUMENT_AFTER. The default is
* FindOneAndUpdate::RETURN_DOCUMENT_BEFORE.
*
* * session (MongoDB\Driver\Session): Client session.
*
* Sessions are not supported for server versions < 3.6.
*
* * sort (document): Determines which document the operation modifies if
* the query selects multiple documents.
*
* * typeMap (array): Type map for BSON deserialization.
*
* * upsert (boolean): When true, a new document is created if no document
* matches the query. The default is false.
*
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
*
* 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 name
* @param array|object $filter Query by which to filter documents
* @param array|object $update Update to apply to the matched document
* @param array $options Command options
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function __construct($databaseName, $collectionName, $filter, $update, array $options = [])
{
if ( ! is_array($filter) && ! is_object($filter)) {
throw InvalidArgumentException::invalidType('$filter', $filter, 'array or object');
}
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');
}
$options += [
'returnDocument' => self::RETURN_DOCUMENT_BEFORE,
'upsert' => false,
];
if (isset($options['projection']) && ! is_array($options['projection']) && ! is_object($options['projection'])) {
throw InvalidArgumentException::invalidType('"projection" option', $options['projection'], 'array or object');
}
if ( ! is_integer($options['returnDocument'])) {
throw InvalidArgumentException::invalidType('"returnDocument" option', $options['returnDocument'], 'integer');
}
if ($options['returnDocument'] !== self::RETURN_DOCUMENT_AFTER &&
$options['returnDocument'] !== self::RETURN_DOCUMENT_BEFORE) {
throw new InvalidArgumentException('Invalid value for "returnDocument" option: ' . $options['returnDocument']);
}
if (isset($options['projection'])) {
$options['fields'] = $options['projection'];
}
$options['new'] = $options['returnDocument'] === self::RETURN_DOCUMENT_AFTER;
unset($options['projection'], $options['returnDocument']);
$this->findAndModify = new FindAndModify(
$databaseName,
$collectionName,
['query' => $filter, 'update' => $update] + $options
);
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return array|object|null
* @throws UnsupportedException if collation or write concern is used and unsupported
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function execute(Server $server)
{
return $this->findAndModify->execute($server);
}
public function getCommandDocument(Server $server)
{
return $this->findAndModify->getCommandDocument($server);
}
}
+167
View File
@@ -0,0 +1,167 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Operation;
use MongoDB\InsertManyResult;
use MongoDB\Driver\BulkWrite as Bulk;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
/**
* Operation for inserting multiple documents with the insert command.
*
* @api
* @see \MongoDB\Collection::insertMany()
* @see http://docs.mongodb.org/manual/reference/command/insert/
*/
class InsertMany implements Executable
{
private static $wireVersionForDocumentLevelValidation = 4;
private $databaseName;
private $collectionName;
private $documents;
private $options;
/**
* Constructs an insert command.
*
* Supported options:
*
* * bypassDocumentValidation (boolean): If true, allows the write to
* circumvent document level validation.
*
* For servers < 3.2, this option is ignored as document level validation
* is not available.
*
* * ordered (boolean): If true, when an insert fails, return without
* performing the remaining writes. If false, when a write fails,
* continue with the remaining writes, if any. The default is true.
*
* * session (MongoDB\Driver\Session): Client session.
*
* Sessions are not supported for server versions < 3.6.
*
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
*
* @param string $databaseName Database name
* @param string $collectionName Collection name
* @param array[]|object[] $documents List of documents to insert
* @param array $options Command options
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function __construct($databaseName, $collectionName, array $documents, array $options = [])
{
if (empty($documents)) {
throw new InvalidArgumentException('$documents is empty');
}
$expectedIndex = 0;
foreach ($documents as $i => $document) {
if ($i !== $expectedIndex) {
throw new InvalidArgumentException(sprintf('$documents is not a list (unexpected index: "%s")', $i));
}
if ( ! is_array($document) && ! is_object($document)) {
throw InvalidArgumentException::invalidType(sprintf('$documents[%d]', $i), $document, 'array or object');
}
$expectedIndex += 1;
}
$options += ['ordered' => true];
if (isset($options['bypassDocumentValidation']) && ! is_bool($options['bypassDocumentValidation'])) {
throw InvalidArgumentException::invalidType('"bypassDocumentValidation" option', $options['bypassDocumentValidation'], 'boolean');
}
if ( ! is_bool($options['ordered'])) {
throw InvalidArgumentException::invalidType('"ordered" option', $options['ordered'], 'boolean');
}
if (isset($options['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
}
if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
}
if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) {
unset($options['writeConcern']);
}
$this->databaseName = (string) $databaseName;
$this->collectionName = (string) $collectionName;
$this->documents = $documents;
$this->options = $options;
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return InsertManyResult
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function execute(Server $server)
{
$options = ['ordered' => $this->options['ordered']];
if (isset($this->options['bypassDocumentValidation']) && \MongoDB\server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)) {
$options['bypassDocumentValidation'] = $this->options['bypassDocumentValidation'];
}
$bulk = new Bulk($options);
$insertedIds = [];
foreach ($this->documents as $i => $document) {
$insertedIds[$i] = $bulk->insert($document);
}
$writeResult = $server->executeBulkWrite($this->databaseName . '.' . $this->collectionName, $bulk, $this->createOptions());
return new InsertManyResult($writeResult, $insertedIds);
}
/**
* Create options for executing the bulk write.
*
* @see http://php.net/manual/en/mongodb-driver-server.executebulkwrite.php
* @return array
*/
private function createOptions()
{
$options = [];
if (isset($this->options['session'])) {
$options['session'] = $this->options['session'];
}
if (isset($this->options['writeConcern'])) {
$options['writeConcern'] = $this->options['writeConcern'];
}
return $options;
}
}
+139
View File
@@ -0,0 +1,139 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Operation;
use MongoDB\InsertOneResult;
use MongoDB\Driver\BulkWrite as Bulk;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
/**
* Operation for inserting a single document with the insert command.
*
* @api
* @see \MongoDB\Collection::insertOne()
* @see http://docs.mongodb.org/manual/reference/command/insert/
*/
class InsertOne implements Executable
{
private static $wireVersionForDocumentLevelValidation = 4;
private $databaseName;
private $collectionName;
private $document;
private $options;
/**
* Constructs an insert command.
*
* Supported options:
*
* * bypassDocumentValidation (boolean): If true, allows the write to
* circumvent document level validation.
*
* For servers < 3.2, this option is ignored as document level validation
* is not available.
*
* * session (MongoDB\Driver\Session): Client session.
*
* Sessions are not supported for server versions < 3.6.
*
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
*
* @param string $databaseName Database name
* @param string $collectionName Collection name
* @param array|object $document Document to insert
* @param array $options Command options
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function __construct($databaseName, $collectionName, $document, array $options = [])
{
if ( ! is_array($document) && ! is_object($document)) {
throw InvalidArgumentException::invalidType('$document', $document, 'array or object');
}
if (isset($options['bypassDocumentValidation']) && ! is_bool($options['bypassDocumentValidation'])) {
throw InvalidArgumentException::invalidType('"bypassDocumentValidation" option', $options['bypassDocumentValidation'], 'boolean');
}
if (isset($options['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
}
if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
}
if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) {
unset($options['writeConcern']);
}
$this->databaseName = (string) $databaseName;
$this->collectionName = (string) $collectionName;
$this->document = $document;
$this->options = $options;
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return InsertOneResult
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function execute(Server $server)
{
$options = [];
if (isset($this->options['bypassDocumentValidation']) && \MongoDB\server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)) {
$options['bypassDocumentValidation'] = $this->options['bypassDocumentValidation'];
}
$bulk = new Bulk($options);
$insertedId = $bulk->insert($this->document);
$writeResult = $server->executeBulkWrite($this->databaseName . '.' . $this->collectionName, $bulk, $this->createOptions());
return new InsertOneResult($writeResult, $insertedId);
}
/**
* Create options for executing the bulk write.
*
* @see http://php.net/manual/en/mongodb-driver-server.executebulkwrite.php
* @return array
*/
private function createOptions()
{
$options = [];
if (isset($this->options['session'])) {
$options['session'] = $this->options['session'];
}
if (isset($this->options['writeConcern'])) {
$options['writeConcern'] = $this->options['writeConcern'];
}
return $options;
}
}
+137
View File
@@ -0,0 +1,137 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\Query;
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;
/**
* Operation for the listCollections command.
*
* @api
* @see \MongoDB\Database::listCollections()
* @see http://docs.mongodb.org/manual/reference/command/listCollections/
*/
class ListCollections implements Executable
{
private $databaseName;
private $options;
/**
* Constructs a listCollections command.
*
* Supported options:
*
* * filter (document): Query by which to filter collections.
*
* * maxTimeMS (integer): The maximum amount of time to allow the query to
* run.
*
* * session (MongoDB\Driver\Session): Client session.
*
* Sessions are not supported for server versions < 3.6.
*
* @param string $databaseName Database name
* @param array $options Command options
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function __construct($databaseName, array $options = [])
{
if (isset($options['filter']) && ! is_array($options['filter']) && ! is_object($options['filter'])) {
throw InvalidArgumentException::invalidType('"filter" option', $options['filter'], 'array or object');
}
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');
}
$this->databaseName = (string) $databaseName;
$this->options = $options;
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return CollectionInfoIterator
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function execute(Server $server)
{
return $this->executeCommand($server);
}
/**
* Create options for executing the command.
*
* Note: read preference is intentionally omitted, as the spec requires that
* the command be executed on the primary.
*
* @see http://php.net/manual/en/mongodb-driver-server.executecommand.php
* @return array
*/
private function createOptions()
{
$options = [];
if (isset($this->options['session'])) {
$options['session'] = $this->options['session'];
}
return $options;
}
/**
* Returns information for all collections in this database using the
* listCollections command.
*
* @param Server $server
* @return CollectionInfoCommandIterator
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
private function executeCommand(Server $server)
{
$cmd = ['listCollections' => 1];
if ( ! empty($this->options['filter'])) {
$cmd['filter'] = (object) $this->options['filter'];
}
if (isset($this->options['maxTimeMS'])) {
$cmd['maxTimeMS'] = $this->options['maxTimeMS'];
}
$cursor = $server->executeCommand($this->databaseName, new Command($cmd), $this->createOptions());
$cursor->setTypeMap(['root' => 'array', 'document' => 'array']);
return new CollectionInfoCommandIterator(new CachingIterator($cursor));
}
}
+133
View File
@@ -0,0 +1,133 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnexpectedValueException;
use MongoDB\Model\DatabaseInfoIterator;
use MongoDB\Model\DatabaseInfoLegacyIterator;
/**
* Operation for the ListDatabases command.
*
* @api
* @see \MongoDB\Client::listDatabases()
* @see http://docs.mongodb.org/manual/reference/command/ListDatabases/
*/
class ListDatabases implements Executable
{
private $options;
/**
* Constructs a listDatabases command.
*
* Supported options:
*
* * filter (document): Query by which to filter databases.
*
* For servers < 3.6, this option is ignored.
*
* * maxTimeMS (integer): The maximum amount of time to allow the query to
* run.
*
* * session (MongoDB\Driver\Session): Client session.
*
* Sessions are not supported for server versions < 3.6.
*
* @param array $options Command options
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function __construct(array $options = [])
{
if (isset($options['filter']) && ! is_array($options['filter']) && ! is_object($options['filter'])) {
throw InvalidArgumentException::invalidType('"filter" option', $options['filter'], 'array or object');
}
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');
}
$this->options = $options;
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return DatabaseInfoIterator
* @throws UnexpectedValueException if the command response was malformed
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function execute(Server $server)
{
$cmd = ['listDatabases' => 1];
if ( ! empty($this->options['filter'])) {
$cmd['filter'] = (object) $this->options['filter'];
}
if (isset($this->options['maxTimeMS'])) {
$cmd['maxTimeMS'] = $this->options['maxTimeMS'];
}
$cursor = $server->executeCommand('admin', new Command($cmd), $this->createOptions());
$cursor->setTypeMap(['root' => 'array', 'document' => 'array']);
$result = current($cursor->toArray());
if ( ! isset($result['databases']) || ! is_array($result['databases'])) {
throw new UnexpectedValueException('listDatabases command did not return a "databases" array');
}
/* Return an Iterator instead of an array in case listDatabases is
* eventually changed to return a command cursor, like the collection
* and index enumeration commands. This makes the "totalSize" command
* field inaccessible, but users can manually invoke the command if they
* need that value.
*/
return new DatabaseInfoLegacyIterator($result['databases']);
}
/**
* Create options for executing the command.
*
* Note: read preference is intentionally omitted, as the spec requires that
* the command be executed on the primary.
*
* @see http://php.net/manual/en/mongodb-driver-server.executecommand.php
* @return array
*/
private function createOptions()
{
$options = [];
if (isset($this->options['session'])) {
$options['session'] = $this->options['session'];
}
return $options;
}
}
+146
View File
@@ -0,0 +1,146 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\Query;
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;
/**
* Operation for the listIndexes command.
*
* @api
* @see \MongoDB\Collection::listIndexes()
* @see http://docs.mongodb.org/manual/reference/command/listIndexes/
*/
class ListIndexes implements Executable
{
private static $errorCodeDatabaseNotFound = 60;
private static $errorCodeNamespaceNotFound = 26;
private $databaseName;
private $collectionName;
private $options;
/**
* Constructs a listIndexes command.
*
* Supported options:
*
* * maxTimeMS (integer): The maximum amount of time to allow the query to
* run.
*
* * session (MongoDB\Driver\Session): Client session.
*
* Sessions are not supported for server versions < 3.6.
*
* @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['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
}
$this->databaseName = (string) $databaseName;
$this->collectionName = (string) $collectionName;
$this->options = $options;
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return IndexInfoIterator
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function execute(Server $server)
{
return $this->executeCommand($server);
}
/**
* Create options for executing the command.
*
* Note: read preference is intentionally omitted, as the spec requires that
* the command be executed on the primary.
*
* @see http://php.net/manual/en/mongodb-driver-server.executecommand.php
* @return array
*/
private function createOptions()
{
$options = [];
if (isset($this->options['session'])) {
$options['session'] = $this->options['session'];
}
return $options;
}
/**
* Returns information for all indexes for this collection using the
* listIndexes command.
*
* @param Server $server
* @return IndexInfoIteratorIterator
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
private function executeCommand(Server $server)
{
$cmd = ['listIndexes' => $this->collectionName];
if (isset($this->options['maxTimeMS'])) {
$cmd['maxTimeMS'] = $this->options['maxTimeMS'];
}
try {
$cursor = $server->executeCommand($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);
}
throw $e;
}
$cursor->setTypeMap(['root' => 'array', 'document' => 'array']);
return new IndexInfoIteratorIterator(new CachingIterator($cursor));
}
}
+369
View File
@@ -0,0 +1,369 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Operation;
use MongoDB\BSON\JavascriptInterface;
use MongoDB\Driver\Command;
use MongoDB\Driver\ReadConcern;
use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnexpectedValueException;
use MongoDB\Exception\UnsupportedException;
use MongoDB\Model\TypeMapArrayIterator;
use MongoDB\MapReduceResult;
use ArrayIterator;
use stdClass;
/**
* Operation for the mapReduce command.
*
* @api
* @see \MongoDB\Collection::mapReduce()
* @see https://docs.mongodb.com/manual/reference/command/mapReduce/
*/
class MapReduce implements Executable
{
private static $wireVersionForCollation = 5;
private static $wireVersionForDocumentLevelValidation = 4;
private static $wireVersionForReadConcern = 4;
private static $wireVersionForWriteConcern = 4;
private $databaseName;
private $collectionName;
private $map;
private $reduce;
private $out;
private $options;
/**
* Constructs a mapReduce command.
*
* Required arguments:
*
* * map (MongoDB\BSON\Javascript): A JavaScript function that associates
* or "maps" a value with a key and emits the key and value pair.
*
* * reduce (MongoDB\BSON\Javascript): A JavaScript function that "reduces"
* to a single object all the values associated with a particular key.
*
* * out (string|document): Specifies where to output the result of the
* map-reduce operation. You can either output to a collection or return
* the result inline. On a primary member of a replica set you can output
* either to a collection or inline, but on a secondary, only inline
* output is possible.
*
* Supported options:
*
* * bypassDocumentValidation (boolean): If true, allows the write to
* circumvent document level validation. This only applies when results
* are output to a collection.
*
* For servers < 3.2, this option is ignored as document level validation
* is not available.
*
* * collation (document): Collation specification.
*
* This is not supported for server versions < 3.4 and will result in an
* exception at execution time if used.
*
* * finalize (MongoDB\BSON\JavascriptInterface): Follows the reduce method
* and modifies the output.
*
* * jsMode (boolean): Specifies whether to convert intermediate data into
* BSON format between the execution of the map and reduce functions.
*
* * limit (integer): Specifies a maximum number of documents for the input
* into the map function.
*
* * maxTimeMS (integer): The maximum amount of time to allow the query to
* run.
*
* * query (document): Specifies the selection criteria using query
* operators for determining the documents input to the map function.
*
* * readConcern (MongoDB\Driver\ReadConcern): Read concern. This is not
* supported when results are returned inline.
*
* 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 results are output to a collection.
*
* * scope (document): Specifies global variables that are accessible in
* the map, reduce and finalize functions.
*
* * session (MongoDB\Driver\Session): Client session.
*
* Sessions are not supported for server versions < 3.6.
*
* * sort (document): Sorts the input documents. This option is useful for
* optimization. For example, specify the sort key to be the same as the
* emit key so that there are fewer reduce operations. The sort key must
* be in an existing index for this collection.
*
* * typeMap (array): Type map for BSON deserialization. This will be
* applied to the returned Cursor (it is not sent to the server).
*
* * verbose (boolean): Specifies whether to include the timing information
* in the result information.
*
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern. This only
* applies when results are output to a collection.
*
* This is not supported for server versions < 3.4 and will result in an
* exception at execution time if used.
*
* @param string $databaseName Database name
* @param string $collectionName Collection name
* @param JavascriptInterface $map Map function
* @param JavascriptInterface $reduce Reduce function
* @param string|array|object $out Output specification
* @param array $options Command options
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function __construct($databaseName, $collectionName, JavascriptInterface $map, JavascriptInterface $reduce, $out, array $options = [])
{
if ( ! is_string($out) && ! is_array($out) && ! is_object($out)) {
throw InvalidArgumentException::invalidType('$out', $out, 'string or array or object');
}
if (isset($options['bypassDocumentValidation']) && ! is_bool($options['bypassDocumentValidation'])) {
throw InvalidArgumentException::invalidType('"bypassDocumentValidation" option', $options['bypassDocumentValidation'], 'boolean');
}
if (isset($options['collation']) && ! is_array($options['collation']) && ! is_object($options['collation'])) {
throw InvalidArgumentException::invalidType('"collation" option', $options['collation'], 'array or object');
}
if (isset($options['finalize']) && ! $options['finalize'] instanceof JavascriptInterface) {
throw InvalidArgumentException::invalidType('"finalize" option', $options['finalize'], 'MongoDB\Driver\Javascript');
}
if (isset($options['jsMode']) && ! is_bool($options['jsMode'])) {
throw InvalidArgumentException::invalidType('"jsMode" option', $options['jsMode'], 'boolean');
}
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['query']) && ! is_array($options['query']) && ! is_object($options['query'])) {
throw InvalidArgumentException::invalidType('"query" option', $options['query'], 'array or object');
}
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['scope']) && ! is_array($options['scope']) && ! is_object($options['scope'])) {
throw InvalidArgumentException::invalidType('"scope" option', $options['scope'], 'array or object');
}
if (isset($options['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
}
if (isset($options['sort']) && ! is_array($options['sort']) && ! is_object($options['sort'])) {
throw InvalidArgumentException::invalidType('"sort" option', $options['sort'], 'array or object');
}
if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
}
if (isset($options['verbose']) && ! is_bool($options['verbose'])) {
throw InvalidArgumentException::invalidType('"verbose" option', $options['verbose'], 'boolean');
}
if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
}
if (isset($options['readConcern']) && $options['readConcern']->isDefault()) {
unset($options['readConcern']);
}
if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) {
unset($options['writeConcern']);
}
$this->databaseName = (string) $databaseName;
$this->collectionName = (string) $collectionName;
$this->map = $map;
$this->reduce = $reduce;
$this->out = $out;
$this->options = $options;
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return MapReduceResult
* @throws UnexpectedValueException if the command response was malformed
* @throws UnsupportedException if collation, read concern, or write concern is used and unsupported
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function execute(Server $server)
{
if (isset($this->options['collation']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) {
throw UnsupportedException::collationNotSupported();
}
if (isset($this->options['readConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
throw UnsupportedException::readConcernNotSupported();
}
if (isset($this->options['writeConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForWriteConcern)) {
throw UnsupportedException::writeConcernNotSupported();
}
$hasOutputCollection = ! \MongoDB\is_mapreduce_output_inline($this->out);
$command = $this->createCommand($server);
$options = $this->createOptions($hasOutputCollection);
$cursor = $hasOutputCollection
? $server->executeReadWriteCommand($this->databaseName, $command, $options)
: $server->executeReadCommand($this->databaseName, $command, $options);
$result = current($cursor->toArray());
$getIterator = $this->createGetIteratorCallable($result, $server);
return new MapReduceResult($getIterator, $result);
}
/**
* Create the mapReduce command.
*
* @param Server $server
* @return Command
*/
private function createCommand(Server $server)
{
$cmd = [
'mapReduce' => $this->collectionName,
'map' => $this->map,
'reduce' => $this->reduce,
'out' => $this->out,
];
foreach (['finalize', 'jsMode', 'limit', 'maxTimeMS', 'verbose'] as $option) {
if (isset($this->options[$option])) {
$cmd[$option] = $this->options[$option];
}
}
foreach (['collation', 'query', 'scope', 'sort'] as $option) {
if (isset($this->options[$option])) {
$cmd[$option] = (object) $this->options[$option];
}
}
if (isset($this->options['bypassDocumentValidation']) && \MongoDB\server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)) {
$cmd['bypassDocumentValidation'] = $this->options['bypassDocumentValidation'];
}
return new Command($cmd);
}
/**
* Creates a callable for MapReduceResult::getIterator().
*
* @param stdClass $result
* @param Server $server
* @return callable
* @throws UnexpectedValueException if the command response was malformed
*/
private function createGetIteratorCallable(stdClass $result, Server $server)
{
// Inline results can be wrapped with an ArrayIterator
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 new ArrayIterator($results);
};
}
if (isset($result->result) && (is_string($result->result) || is_object($result->result))) {
$options = isset($this->options['typeMap']) ? ['typeMap' => $this->options['typeMap']] : [];
$find = is_string($result->result)
? new Find($this->databaseName, $result->result, [], $options)
: new Find($result->result->db, $result->result->collection, [], $options);
return function() use ($find, $server) {
return $find->execute($server);
};
}
throw new UnexpectedValueException('mapReduce command did not return inline results or an output collection');
}
/**
* Create options for executing the command.
*
* @see http://php.net/manual/en/mongodb-driver-server.executereadcommand.php
* @see http://php.net/manual/en/mongodb-driver-server.executereadwritecommand.php
* @param boolean $hasOutputCollection
* @return array
*/
private function createOptions($hasOutputCollection)
{
$options = [];
if (isset($this->options['readConcern'])) {
$options['readConcern'] = $this->options['readConcern'];
}
if ( ! $hasOutputCollection && isset($this->options['readPreference'])) {
$options['readPreference'] = $this->options['readPreference'];
}
if (isset($this->options['session'])) {
$options['session'] = $this->options['session'];
}
if ($hasOutputCollection && isset($this->options['writeConcern'])) {
$options['writeConcern'] = $this->options['writeConcern'];
}
return $options;
}
}
+135
View File
@@ -0,0 +1,135 @@
<?php
/*
* Copyright 2018 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
/**
* Operation for the collMod command.
*
* @api
* @see \MongoDB\Database::modifyCollection()
* @see http://docs.mongodb.org/manual/reference/command/collMod/
*/
class ModifyCollection implements Executable
{
private $databaseName;
private $collectionName;
private $collectionOptions;
private $options;
/**
* Constructs a collMod command.
*
* Supported options:
*
* * session (MongoDB\Driver\Session): Client session.
*
* Sessions are not supported for server versions < 3.6.
*
* * typeMap (array): Type map for BSON deserialization. This will only be
* used for the returned command result document.
*
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
*
* 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
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function __construct($databaseName, $collectionName, array $collectionOptions, array $options = [])
{
if (empty($collectionOptions)) {
throw new InvalidArgumentException('$collectionOptions is empty');
}
if (isset($options['session']) && ! $options['session'] instanceof Session) {
throw InvalidArgumentException::invalidType('"session" option', $options['session'], 'MongoDB\Driver\Session');
}
if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
}
if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
}
if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) {
unset($options['writeConcern']);
}
$this->databaseName = (string) $databaseName;
$this->collectionName = (string) $collectionName;
$this->collectionOptions = $collectionOptions;
$this->options = $options;
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return array|object Command result document
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function execute(Server $server)
{
if (isset($this->options['writeConcern']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForWriteConcern)) {
throw UnsupportedException::writeConcernNotSupported();
}
$cursor = $server->executeWriteCommand($this->databaseName, new Command(['collMod' => $this->collectionName] + $this->collectionOptions), $this->createOptions());
if (isset($this->options['typeMap'])) {
$cursor->setTypeMap($this->options['typeMap']);
}
return current($cursor->toArray());
}
/**
* Create options for executing the command.
*
* @see http://php.net/manual/en/mongodb-driver-server.executereadwritecommand.php
* @return array
*/
private function createOptions()
{
$options = [];
if (isset($this->options['session'])) {
$options['session'] = $this->options['session'];
}
if (isset($this->options['writeConcern'])) {
$options['writeConcern'] = $this->options['writeConcern'];
}
return $options;
}
}
+101
View File
@@ -0,0 +1,101 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Operation;
use MongoDB\UpdateResult;
use MongoDB\Driver\Server;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
/**
* Operation for replacing a single document with the update command.
*
* @api
* @see \MongoDB\Collection::replaceOne()
* @see http://docs.mongodb.org/manual/reference/command/update/
*/
class ReplaceOne implements Executable
{
private $update;
/**
* Constructs an update command.
*
* Supported options:
*
* * bypassDocumentValidation (boolean): If true, allows the write to
* circumvent document level validation.
*
* For servers < 3.2, this option is ignored as document level validation
* is not available.
*
* * collation (document): Collation specification.
*
* This is not supported for server versions < 3.4 and will result in an
* exception at execution time if used.
*
* * session (MongoDB\Driver\Session): Client session.
*
* Sessions are not supported for server versions < 3.6.
*
* * upsert (boolean): When true, a new document is created if no document
* matches the query. The default is false.
*
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
*
* @param string $databaseName Database name
* @param string $collectionName Collection name
* @param array|object $filter Query by which to filter documents
* @param array|object $replacement Replacement document
* @param array $options Command options
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function __construct($databaseName, $collectionName, $filter, $replacement, array $options = [])
{
if ( ! is_array($replacement) && ! is_object($replacement)) {
throw InvalidArgumentException::invalidType('$replacement', $replacement, 'array or object');
}
if (\MongoDB\is_first_key_operator($replacement)) {
throw new InvalidArgumentException('First key in $replacement argument is an update operator');
}
$this->update = new Update(
$databaseName,
$collectionName,
$filter,
$replacement,
['multi' => false] + $options
);
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return UpdateResult
* @throws UnsupportedException if collation is used and unsupported
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function execute(Server $server)
{
return $this->update->execute($server);
}
}
+245
View File
@@ -0,0 +1,245 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Operation;
use MongoDB\UpdateResult;
use MongoDB\Driver\BulkWrite as Bulk;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
/**
* Operation for the update command.
*
* This class is used internally by the ReplaceOne, UpdateMany, and UpdateOne
* operation classes.
*
* @internal
* @see http://docs.mongodb.org/manual/reference/command/update/
*/
class Update implements Executable, Explainable
{
private static $wireVersionForArrayFilters = 6;
private static $wireVersionForCollation = 5;
private static $wireVersionForDocumentLevelValidation = 4;
private $databaseName;
private $collectionName;
private $filter;
private $update;
private $options;
/**
* Constructs a update command.
*
* Supported options:
*
* * arrayFilters (document array): A set of filters specifying to which
* array elements an update should apply.
*
* This is not supported for server versions < 3.6 and will result in an
* exception at execution time if used.
*
* * bypassDocumentValidation (boolean): If true, allows the write to
* circumvent document level validation.
*
* For servers < 3.2, this option is ignored as document level validation
* is not available.
*
* * collation (document): Collation specification.
*
* This is not supported for server versions < 3.4 and will result in an
* exception at execution time if used.
*
* * multi (boolean): When true, updates all documents matching the query.
* This option cannot be true if the $update argument is a replacement
* document (i.e. contains no update operators). The default is false.
*
* * session (MongoDB\Driver\Session): Client session.
*
* Sessions are not supported for server versions < 3.6.
*
* * upsert (boolean): When true, a new document is created if no document
* matches the query. The default is false.
*
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
*
* @param string $databaseName Database name
* @param string $collectionName Collection name
* @param array|object $filter Query by which to delete documents
* @param array|object $update Update to apply to the matched
* document(s) or a replacement document
* @param array $options Command options
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function __construct($databaseName, $collectionName, $filter, $update, array $options = [])
{
if ( ! is_array($filter) && ! is_object($filter)) {
throw InvalidArgumentException::invalidType('$filter', $filter, 'array or object');
}
if ( ! is_array($update) && ! is_object($update)) {
throw InvalidArgumentException::invalidType('$update', $filter, 'array or object');
}
$options += [
'multi' => false,
'upsert' => false,
];
if (isset($options['arrayFilters']) && ! is_array($options['arrayFilters'])) {
throw InvalidArgumentException::invalidType('"arrayFilters" option', $options['arrayFilters'], 'array');
}
if (isset($options['bypassDocumentValidation']) && ! is_bool($options['bypassDocumentValidation'])) {
throw InvalidArgumentException::invalidType('"bypassDocumentValidation" option', $options['bypassDocumentValidation'], 'boolean');
}
if (isset($options['collation']) && ! is_array($options['collation']) && ! is_object($options['collation'])) {
throw InvalidArgumentException::invalidType('"collation" option', $options['collation'], 'array or object');
}
if ( ! is_bool($options['multi'])) {
throw InvalidArgumentException::invalidType('"multi" option', $options['multi'], 'boolean');
}
if ($options['multi'] && ! \MongoDB\is_first_key_operator($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');
}
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');
}
if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) {
unset($options['writeConcern']);
}
$this->databaseName = (string) $databaseName;
$this->collectionName = (string) $collectionName;
$this->filter = $filter;
$this->update = $update;
$this->options = $options;
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return UpdateResult
* @throws UnsupportedException if array filters or collation is used and unsupported
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function execute(Server $server)
{
if (isset($this->options['arrayFilters']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForArrayFilters)) {
throw UnsupportedException::arrayFiltersNotSupported();
}
if (isset($this->options['collation']) && ! \MongoDB\server_supports_feature($server, self::$wireVersionForCollation)) {
throw UnsupportedException::collationNotSupported();
}
$bulkOptions = [];
if (isset($this->options['bypassDocumentValidation']) && \MongoDB\server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)) {
$bulkOptions['bypassDocumentValidation'] = $this->options['bypassDocumentValidation'];
}
$bulk = new Bulk($bulkOptions);
$bulk->update($this->filter, $this->update, $this->createUpdateOptions());
$writeResult = $server->executeBulkWrite($this->databaseName . '.' . $this->collectionName, $bulk, $this->createExecuteOptions());
return new UpdateResult($writeResult);
}
public function getCommandDocument(Server $server)
{
$cmd = ['update' => $this->collectionName, 'updates' => [['q' => $this->filter, 'u' => $this->update] + $this->createUpdateOptions()]];
if (isset($this->options['writeConcern'])) {
$cmd['writeConcern'] = $this->options['writeConcern'];
}
if (isset($this->options['bypassDocumentValidation']) && \MongoDB\server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)) {
$cmd['bypassDocumentValidation'] = $this->options['bypassDocumentValidation'];
}
return $cmd;
}
/**
* Create options for executing the bulk write.
*
* @see http://php.net/manual/en/mongodb-driver-server.executebulkwrite.php
* @return array
*/
private function createExecuteOptions()
{
$options = [];
if (isset($this->options['session'])) {
$options['session'] = $this->options['session'];
}
if (isset($this->options['writeConcern'])) {
$options['writeConcern'] = $this->options['writeConcern'];
}
return $options;
}
/**
* Create options for the update command.
*
* Note that these options are different from the bulk write options, which
* are created in createExecuteOptions().
*
* @return array
*/
private function createUpdateOptions()
{
$updateOptions = [
'multi' => $this->options['multi'],
'upsert' => $this->options['upsert'],
];
if (isset($this->options['arrayFilters'])) {
$updateOptions['arrayFilters'] = $this->options['arrayFilters'];
}
if (isset($this->options['collation'])) {
$updateOptions['collation'] = (object) $this->options['collation'];
}
return $updateOptions;
}
}
+112
View File
@@ -0,0 +1,112 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Operation;
use MongoDB\UpdateResult;
use MongoDB\Driver\Server;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
/**
* Operation for updating multiple documents with the update command.
*
* @api
* @see \MongoDB\Collection::updateMany()
* @see http://docs.mongodb.org/manual/reference/command/update/
*/
class UpdateMany implements Executable, Explainable
{
private $update;
/**
* Constructs an update command.
*
* Supported options:
*
* * arrayFilters (document array): A set of filters specifying to which
* array elements an update should apply.
*
* This is not supported for server versions < 3.6 and will result in an$
* exception at execution time if used.
*
* * bypassDocumentValidation (boolean): If true, allows the write to
* circumvent document level validation.
*
* For servers < 3.2, this option is ignored as document level validation
* is not available.
*
* * collation (document): Collation specification.
*
* This is not supported for server versions < 3.4 and will result in an
* exception at execution time if used.
*
* * session (MongoDB\Driver\Session): Client session.
*
* Sessions are not supported for server versions < 3.6.
*
* * upsert (boolean): When true, a new document is created if no document
* matches the query. The default is false.
*
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
*
* @param string $databaseName Database name
* @param string $collectionName Collection name
* @param array|object $filter Query by which to filter documents
* @param array|object $update Update to apply to the matched documents
* @param array $options Command options
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function __construct($databaseName, $collectionName, $filter, $update, array $options = [])
{
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');
}
$this->update = new Update(
$databaseName,
$collectionName,
$filter,
$update,
['multi' => true] + $options
);
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return UpdateResult
* @throws UnsupportedException if collation is used and unsupported
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function execute(Server $server)
{
return $this->update->execute($server);
}
public function getCommandDocument(Server $server)
{
return $this->update->getCommandDocument($server);
}
}
+112
View File
@@ -0,0 +1,112 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Operation;
use MongoDB\UpdateResult;
use MongoDB\Driver\Server;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
/**
* Operation for updating a single document with the update command.
*
* @api
* @see \MongoDB\Collection::updateOne()
* @see http://docs.mongodb.org/manual/reference/command/update/
*/
class UpdateOne implements Executable, Explainable
{
private $update;
/**
* Constructs an update command.
*
* Supported options:
*
* * arrayFilters (document array): A set of filters specifying to which
* array elements an update should apply.
*
* This is not supported for server versions < 3.6 and will result in an$
* exception at execution time if used.
*
* * bypassDocumentValidation (boolean): If true, allows the write to
* circumvent document level validation.
*
* For servers < 3.2, this option is ignored as document level validation
* is not available.
*
* * collation (document): Collation specification.
*
* This is not supported for server versions < 3.4 and will result in an
* exception at execution time if used.
*
* * session (MongoDB\Driver\Session): Client session.
*
* Sessions are not supported for server versions < 3.6.
*
* * upsert (boolean): When true, a new document is created if no document
* matches the query. The default is false.
*
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
*
* @param string $databaseName Database name
* @param string $collectionName Collection name
* @param array|object $filter Query by which to filter documents
* @param array|object $update Update to apply to the matched document
* @param array $options Command options
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function __construct($databaseName, $collectionName, $filter, $update, array $options = [])
{
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');
}
$this->update = new Update(
$databaseName,
$collectionName,
$filter,
$update,
['multi' => false] + $options
);
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return UpdateResult
* @throws UnsupportedException if collation is used and unsupported
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function execute(Server $server)
{
return $this->update->execute($server);
}
public function getCommandDocument(Server $server)
{
return $this->update->getCommandDocument($server);
}
}
+286
View File
@@ -0,0 +1,286 @@
<?php
/*
* Copyright 2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB\Operation;
use MongoDB\ChangeStream;
use MongoDB\BSON\TimestampInterface;
use MongoDB\Driver\Command;
use MongoDB\Driver\Cursor;
use MongoDB\Driver\Manager;
use MongoDB\Driver\ReadConcern;
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;
/**
* Operation for creating a change stream with the aggregate command.
*
* Note: the implementation of CommandSubscriber is an internal implementation
* detail and should not be considered part of the public API.
*
* @api
* @see \MongoDB\Collection::watch()
* @see https://docs.mongodb.com/manual/changeStreams/
*/
class Watch implements Executable, /* @internal */ CommandSubscriber
{
private static $wireVersionForOperationTime = 7;
const FULL_DOCUMENT_DEFAULT = 'default';
const FULL_DOCUMENT_UPDATE_LOOKUP = 'updateLookup';
private $aggregate;
private $aggregateOptions;
private $changeStreamOptions;
private $collectionName;
private $databaseName;
private $operationTime;
private $pipeline;
private $resumeCallable;
/**
* Constructs an aggregate command for creating a change stream.
*
* Supported options:
*
* * batchSize (integer): The number of documents to return per batch.
*
* * collation (document): Specifies a collation.
*
* * fullDocument (string): Determines whether the "fullDocument" field
* will be populated for update operations. By default, change streams
* only return the delta of fields during the update operation (via the
* "updateDescription" field). To additionally return the most current
* majority-committed version of the updated document, specify
* "updateLookup" for this option. Defaults to "default".
*
* Insert and replace operations always include the "fullDocument" field
* and delete operations omit the field as the document no longer exists.
*
* * maxAwaitTimeMS (integer): The maximum amount of time for the server to
* wait on new documents to satisfy a change stream query.
*
* * readConcern (MongoDB\Driver\ReadConcern): Read concern.
*
* * readPreference (MongoDB\Driver\ReadPreference): Read preference. This
* will be used to select a new server when resuming. Defaults to a
* "primary" read preference.
*
* * 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.
*
* * session (MongoDB\Driver\Session): Client session.
*
* Sessions are not supported for server versions < 3.6.
*
* * 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.
*
* This option is not supported for server versions < 4.0.
*
* * typeMap (array): Type map for BSON deserialization. This will be
* applied to the returned Cursor (it is not sent to the server).
*
* Note: A database-level change stream may be created by specifying null
* 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
* @throws InvalidArgumentException for parameter/option parsing errors
*/
public function __construct(Manager $manager, $databaseName, $collectionName, array $pipeline, array $options = [])
{
if (isset($collectionName) && ! isset($databaseName)) {
throw new InvalidArgumentException('$collectionName should also be null if $databaseName is null');
}
$options += [
'fullDocument' => self::FULL_DOCUMENT_DEFAULT,
'readPreference' => new ReadPreference(ReadPreference::RP_PRIMARY),
];
if (isset($options['fullDocument']) && ! is_string($options['fullDocument'])) {
throw InvalidArgumentException::invalidType('"fullDocument" option', $options['fullDocument'], 'string');
}
if (isset($options['resumeAfter']) && ! is_array($options['resumeAfter']) && ! is_object($options['resumeAfter'])) {
throw InvalidArgumentException::invalidType('"resumeAfter" option', $options['resumeAfter'], 'array or object');
}
if (isset($options['startAtOperationTime']) && ! $options['startAtOperationTime'] instanceof TimestampInterface) {
throw InvalidArgumentException::invalidType('"startAtOperationTime" option', $options['startAtOperationTime'], TimestampInterface::class);
}
/* In the absence of an explicit session, create one to ensure that the
* initial aggregation and any resume attempts can use the same session
* ("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'])) {
try {
$options['session'] = $manager->startSession(['causalConsistency' => false]);
} catch (RuntimeException $e) {
/* We can ignore the exception, as libmongoc likely cannot
* create its own session and there is no risk of a mismatch. */
}
}
$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]);
// Null database name implies a cluster-wide change stream
if ($databaseName === null) {
$databaseName = 'admin';
$this->changeStreamOptions['allChangesForCluster'] = true;
}
$this->databaseName = (string) $databaseName;
$this->collectionName = isset($collectionName) ? (string) $collectionName : null;
$this->pipeline = $pipeline;
$this->aggregate = $this->createAggregate();
$this->resumeCallable = $this->createResumeCallable($manager);
}
/** @internal */
final public function commandFailed(CommandFailedEvent $event)
{
}
/** @internal */
final public function commandStarted(CommandStartedEvent $event)
{
}
/** @internal */
final public function commandSucceeded(CommandSucceededEvent $event)
{
if ($event->getCommandName() !== 'aggregate') {
return;
}
$reply = $event->getReply();
if (isset($reply->operationTime) && $reply->operationTime instanceof TimestampInterface) {
$this->operationTime = $reply->operationTime;
}
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return ChangeStream
* @throws UnsupportedException if collation or read concern is used and unsupported
* @throws RuntimeException for other driver errors (e.g. connection errors)
*/
public function execute(Server $server)
{
return new ChangeStream($this->executeAggregate($server), $this->resumeCallable);
}
/**
* Create the aggregate command for creating a change stream.
*
* This method is also used to recreate the aggregate command when resuming.
*
* @return Aggregate
*/
private function createAggregate()
{
$pipeline = $this->pipeline;
array_unshift($pipeline, ['$changeStream' => (object) $this->changeStreamOptions]);
return new Aggregate($this->databaseName, $this->collectionName, $pipeline, $this->aggregateOptions);
}
private function createResumeCallable(Manager $manager)
{
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);
};
}
/**
* Execute the aggregate command and optionally capture its operation time.
*
* @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);
try {
return $this->aggregate->execute($server);
} finally {
\MongoDB\Driver\Monitoring\removeSubscriber($this);
}
}
}
+140
View File
@@ -0,0 +1,140 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB;
use MongoDB\Driver\WriteResult;
use MongoDB\Exception\BadMethodCallException;
/**
* Result class for an update operation.
*/
class UpdateResult
{
private $writeResult;
private $isAcknowledged;
/**
* Constructor.
*
* @param WriteResult $writeResult
*/
public function __construct(WriteResult $writeResult)
{
$this->writeResult = $writeResult;
$this->isAcknowledged = $writeResult->isAcknowledged();
}
/**
* Return the number of documents that were matched by the filter.
*
* This method should only be called if the write was acknowledged.
*
* @see UpdateResult::isAcknowledged()
* @return integer
* @throws BadMethodCallException is the write result is unacknowledged
*/
public function getMatchedCount()
{
if ($this->isAcknowledged) {
return $this->writeResult->getMatchedCount();
}
throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
}
/**
* Return the number of documents that were modified.
*
* This value is undefined (i.e. null) if the write executed as a legacy
* operation instead of command.
*
* This method should only be called if the write was acknowledged.
*
* @see UpdateResult::isAcknowledged()
* @return integer|null
* @throws BadMethodCallException is the write result is unacknowledged
*/
public function getModifiedCount()
{
if ($this->isAcknowledged) {
return $this->writeResult->getModifiedCount();
}
throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
}
/**
* Return the number of documents that were upserted.
*
* This method should only be called if the write was acknowledged.
*
* @see UpdateResult::isAcknowledged()
* @return integer
* @throws BadMethodCallException is the write result is unacknowledged
*/
public function getUpsertedCount()
{
if ($this->isAcknowledged) {
return $this->writeResult->getUpsertedCount();
}
throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
}
/**
* Return the ID of the document inserted by an upsert operation.
*
* If the document had an ID prior to upserting (i.e. the server did not
* need to generate an ID), this will contain its "_id". Any
* server-generated ID will be a MongoDB\BSON\ObjectId instance.
*
* This value is undefined (i.e. null) if an upsert did not take place.
*
* This method should only be called if the write was acknowledged.
*
* @see UpdateResult::isAcknowledged()
* @return mixed|null
* @throws BadMethodCallException is the write result is unacknowledged
*/
public function getUpsertedId()
{
if ($this->isAcknowledged) {
foreach ($this->writeResult->getUpsertedIds() as $id) {
return $id;
}
return null;
}
throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
}
/**
* Return whether this update was acknowledged by the server.
*
* If the update was not acknowledged, other fields from the WriteResult
* (e.g. matchedCount) will be undefined and their getter methods should not
* be invoked.
*
* @return boolean
*/
public function isAcknowledged()
{
return $this->isAcknowledged;
}
}
+225
View File
@@ -0,0 +1,225 @@
<?php
/*
* Copyright 2015-2017 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace MongoDB;
use MongoDB\BSON\Serializable;
use MongoDB\Driver\ReadConcern;
use MongoDB\Driver\Server;
use MongoDB\Driver\WriteConcern;
use MongoDB\Exception\InvalidArgumentException;
use stdClass;
use ReflectionClass;
/**
* Applies a type map to a document.
*
* This function is used by operations where it is not possible to apply a type
* map to the cursor directly because the root document is a command response
* (e.g. findAndModify).
*
* @internal
* @param array|object $document Document to which the type map will be applied
* @param array $typeMap Type map for BSON deserialization.
* @return array|object
* @throws InvalidArgumentException
*/
function apply_type_map_to_document($document, array $typeMap)
{
if ( ! is_array($document) && ! is_object($document)) {
throw InvalidArgumentException::invalidType('$document', $document, 'array or object');
}
return \MongoDB\BSON\toPHP(\MongoDB\BSON\fromPHP($document), $typeMap);
}
/**
* Generate an index name from a key specification.
*
* @internal
* @param array|object $document Document containing fields mapped to values,
* which denote order or an index type
* @return string
* @throws InvalidArgumentException
*/
function generate_index_name($document)
{
if ($document instanceof Serializable) {
$document = $document->bsonSerialize();
}
if (is_object($document)) {
$document = get_object_vars($document);
}
if ( ! is_array($document)) {
throw InvalidArgumentException::invalidType('$document', $document, 'array or object');
}
$name = '';
foreach ($document as $field => $type) {
$name .= ($name != '' ? '_' : '') . $field . '_' . $type;
}
return $name;
}
/**
* Return whether the first key in the document starts with a "$" character.
*
* This is used for differentiating update and replacement documents.
*
* @internal
* @param array|object $document Update or replacement document
* @return boolean
* @throws InvalidArgumentException
*/
function is_first_key_operator($document)
{
if ($document instanceof Serializable) {
$document = $document->bsonSerialize();
}
if (is_object($document)) {
$document = get_object_vars($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 whether the aggregation pipeline ends with an $out operator.
*
* This is used for determining whether the aggregation pipeline must be
* executed against a primary server.
*
* @internal
* @param array $pipeline List of pipeline operations
* @return boolean
*/
function is_last_pipeline_operator_out(array $pipeline)
{
$lastOp = end($pipeline);
if ($lastOp === false) {
return false;
}
$lastOp = (array) $lastOp;
return key($lastOp) === '$out';
}
/**
* Return whether the "out" option for a mapReduce operation is "inline".
*
* This is used to determine if a mapReduce command requires a primary.
*
* @internal
* @see https://docs.mongodb.com/manual/reference/command/mapReduce/#output-inline
* @param string|array|object $out Output specification
* @return boolean
* @throws InvalidArgumentException
*/
function is_mapreduce_output_inline($out)
{
if ( ! is_array($out) && ! is_object($out)) {
return false;
}
if ($out instanceof Serializable) {
$out = $out->bsonSerialize();
}
if (is_object($out)) {
$out = get_object_vars($out);
}
if ( ! is_array($out)) {
throw InvalidArgumentException::invalidType('$out', $out, 'array or object');
}
reset($out);
return key($out) === 'inline';
}
/**
* Return whether the server supports a particular feature.
*
* @internal
* @param Server $server Server to check
* @param integer $feature Feature constant (i.e. wire protocol version)
* @return boolean
*/
function server_supports_feature(Server $server, $feature)
{
$info = $server->getInfo();
$maxWireVersion = isset($info['maxWireVersion']) ? (integer) $info['maxWireVersion'] : 0;
$minWireVersion = isset($info['minWireVersion']) ? (integer) $info['minWireVersion'] : 0;
return ($minWireVersion <= $feature && $maxWireVersion >= $feature);
}
function is_string_array($input) {
if (!is_array($input)){
return false;
}
foreach($input as $item) {
if (!is_string($item)) {
return false;
}
}
return true;
}
/**
* Performs a deep copy of a value.
*
* This function will clone objects and recursively copy values within arrays.
*
* @internal
* @see https://bugs.php.net/bug.php?id=49664
* @param mixed $element Value to be copied
* @return mixed
*/
function recursive_copy($element) {
if (is_array($element)) {
foreach ($element as $key => $value) {
$element[$key] = recursive_copy($value);
}
return $element;
}
if ( ! is_object($element)) {
return $element;
}
if ( ! (new ReflectionClass($element))->isCloneable()) {
return $element;
}
return clone $element;
}
+14
View File
@@ -0,0 +1,14 @@
MongoDB PHP
-----------
Downloaded from https://github.com/mongodb/mongo-php-library
Last commit on download: aac8e54009196f6544e50baf9b63dcf0eab3bbdf
This version (1.4) requires PHP mongodb extension >= 1.5
Import procedure:
- Copy all the files and folders from the folder mongodb/src in this directory.
- Copy the license file from the project root.
+1 -1
View File
@@ -51,7 +51,7 @@ class cachestore_mongodb_addinstance_form extends cachestore_addinstance_form {
global $OUTPUT;
$form = $this->_form;
if (!class_exists('MongoClient')) {
if (!version_compare(phpversion('mongodb'), '1.5', 'ge')) {
$form->addElement('html', $OUTPUT->notification(get_string('pleaseupgrademongo', 'cachestore_mongodb')));
}
+55 -81
View File
@@ -26,13 +26,15 @@
defined('MOODLE_INTERNAL') || die();
require_once('MongoDB/functions.php');
/**
* The MongoDB Cache store.
*
* This cache store uses the MongoDB Native Driver.
* This cache store uses the MongoDB Native Driver and the MongoDB PHP Library.
* For installation instructions have a look at the following two links:
* - {@link http://www.php.net/manual/en/mongo.installation.php}
* - {@link http://www.mongodb.org/display/DOCS/PHP+Language+Center}
* - {@link http://php.net/manual/en/set.mongodb.php}
* - {@link https://docs.mongodb.com/ecosystem/drivers/php/}
*
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -65,19 +67,19 @@ class cachestore_mongodb extends cache_store implements cache_is_configurable {
/**
* The Connection object
* @var Mongo
* @var MongoDB/Client
*/
protected $connection = false;
/**
* The Database Object
* @var MongoDB
* @var MongoDB/Database
*/
protected $database;
/**
* The Collection object
* @var MongoCollection
* @var MongoDB/Collection
*/
protected $collection;
@@ -105,14 +107,6 @@ class cachestore_mongodb extends cache_store implements cache_is_configurable {
*/
protected $isready = false;
/**
* Set to true if the Mongo extension is < version 1.3.
* If this is the case we must use the legacy Mongo class instead of MongoClient.
* Mongo is backwards compatible, although obviously deprecated.
* @var bool
*/
protected $legacymongo = false;
/**
* Constructs a new instance of the Mongo store.
*
@@ -148,16 +142,14 @@ class cachestore_mongodb extends cache_store implements cache_is_configurable {
$this->extendedmode = $configuration['extendedmode'];
}
// Test if the MongoClient class exists, if not we need to switch to legacy classes.
$this->legacymongo = (!class_exists('MongoClient'));
// MongoClient from Mongo 1.3 onwards. Mongo for earlier versions.
$class = ($this->legacymongo) ? 'Mongo' : 'MongoClient';
try {
$this->connection = new $class($this->server, $this->options);
$this->connection = new MongoDB\Client($this->server, $this->options);
// Required because MongoDB\Client does not try to connect to the server
$rp = new MongoDB\Driver\ReadPreference(MongoDB\Driver\ReadPreference::RP_PRIMARY);
$this->connection->getManager()->selectServer($rp);
$this->isready = true;
} catch (MongoConnectionException $e) {
// We only want to catch MongoConnectionExceptions here.
} catch (MongoDB\Driver\Exception\RuntimeException $e) {
// We only want to catch RuntimeException here.
}
}
@@ -166,7 +158,7 @@ class cachestore_mongodb extends cache_store implements cache_is_configurable {
* @return bool
*/
public static function are_requirements_met() {
return class_exists('MongoClient') || class_exists('Mongo');
return version_compare(phpversion('mongodb'), '1.5', 'ge');
}
/**
@@ -203,17 +195,18 @@ class cachestore_mongodb extends cache_store implements cache_is_configurable {
if ($this->is_initialised()) {
throw new coding_exception('This mongodb instance has already been initialised.');
}
$this->database = $this->connection->selectDB($this->databasename);
$this->database = $this->connection->selectDatabase($this->databasename);
$this->definitionhash = 'm'.$definition->generate_definition_hash();
$this->collection = $this->database->selectCollection($this->definitionhash);
$options = array('name' => 'idx_key');
if ($this->legacymongo) {
$options['safe'] = $this->usesafe;
} else {
$options['w'] = $this->usesafe ? 1 : 0;
}
$this->collection->ensureIndex(array('key' => 1), $options);
$w = $this->usesafe ? 1 : 0;
$wc = new MongoDB\Driver\WriteConcern($w);
$options['writeConcern'] = $wc;
$this->collection->createIndex(array('key' => 1), $options);
}
/**
@@ -221,7 +214,7 @@ class cachestore_mongodb extends cache_store implements cache_is_configurable {
* @return bool
*/
public function is_initialised() {
return ($this->database instanceof MongoDB);
return ($this->database instanceof MongoDB\Database);
}
/**
@@ -319,24 +312,20 @@ class cachestore_mongodb extends cache_store implements cache_is_configurable {
}
$record['data'] = serialize($data);
$options = array('upsert' => true);
if ($this->legacymongo) {
$options['safe'] = $this->usesafe;
} else {
$options['w'] = $this->usesafe ? 1 : 0;
}
$w = $this->usesafe ? 1 : 0;
$wc = new MongoDB\Driver\WriteConcern($w);
$options['writeConcern'] = $wc;
$this->delete($key);
$result = $this->collection->insert($record, $options);
if ($result === true) {
// Safe mode is off.
return true;
} else if (is_array($result)) {
if (empty($result['ok']) || isset($result['err'])) {
return false;
}
return true;
try {
$this->collection->insertOne($record, $options);
} catch (MongoDB\Exception\Exception $e) {
return false;
}
// Who knows?
return false;
return true;
}
/**
@@ -373,27 +362,23 @@ class cachestore_mongodb extends cache_store implements cache_is_configurable {
$criteria = $key;
}
$options = array('justOne' => false);
if ($this->legacymongo) {
$options['safe'] = $this->usesafe;
} else {
$options['w'] = $this->usesafe ? 1 : 0;
}
$result = $this->collection->remove($criteria, $options);
if ($result === true) {
// Safe mode.
return true;
} else if (is_array($result)) {
if (empty($result['ok']) || isset($result['err'])) {
return false;
} else if (empty($result['n'])) {
// Nothing was removed.
return false;
}
return true;
$w = $this->usesafe ? 1 : 0;
$wc = new MongoDB\Driver\WriteConcern($w);
$options['writeConcern'] = $wc;
try {
$result = $this->collection->deleteOne($criteria, $options);
} catch (\MongoDB\Exception $e) {
return false;
}
// Who knows?
return false;
if (empty($result->getDeletedCount())) {
return false;
}
return true;
}
/**
@@ -498,20 +483,10 @@ class cachestore_mongodb extends cache_store implements cache_is_configurable {
public function instance_deleted() {
// We can't use purge here that acts upon a collection.
// Instead we must drop the named database.
if ($this->connection) {
$connection = $this->connection;
} else {
try {
// MongoClient from Mongo 1.3 onwards. Mongo for earlier versions.
$class = ($this->legacymongo) ? 'Mongo' : 'MongoClient';
$connection = new $class($this->server, $this->options);
} catch (MongoConnectionException $e) {
// We only want to catch MongoConnectionExceptions here.
// If the server cannot be connected to we cannot clean it.
return;
}
if (!$this->is_ready()) {
return;
}
$database = $connection->selectDB($this->databasename);
$database = $this->connection->selectDatabase($this->databasename);
$database->drop();
$connection = null;
$database = null;
@@ -564,7 +539,6 @@ class cachestore_mongodb extends cache_store implements cache_is_configurable {
return $store;
}
/**
* Generates an instance of the cache store that can be used for testing.
*
@@ -577,7 +551,7 @@ class cachestore_mongodb extends cache_store implements cache_is_configurable {
// If the configuration is not defined correctly, return only the configuration know about.
if (defined('TEST_CACHESTORE_MONGODB_TESTSERVER')) {
$configuration['servers'] = explode("\n", TEST_CACHESTORE_MONGODB_TESTSERVER);
$configuration['server'] = TEST_CACHESTORE_MONGODB_TESTSERVER;
}
return $configuration;
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0"?>
<libraries>
<library>
<location>MongoDB</location>
<name>MongoDB PHP Library</name>
<license>Apache</license>
<version>1.4</version>
<licenseversion>2.0</licenseversion>
</library>
</libraries>
+5
View File
@@ -1,6 +1,11 @@
This files describes API changes in /cache/stores/* - cache store plugins.
Information provided here is intended especially for developers.
=== 3.7 ===
* Upgraded MongoDB cache store to use the new lower level PHP-driver and MongoDB PHP Library.
* The mongodb extension has replaced the old mongo extension. The mongodb pecl extension >= 1.5 must be installed to use MongoDB
cache store.
=== 3.6 ===
* The `cache::now()` function now takes an optional boolean parameter to indicate that the cache should return a more
accurate time, generated by the PHP `microtime` function.
+1
View File
@@ -86,6 +86,7 @@ class core_component {
'Phpml' => 'lib/mlbackend/php/phpml/src/Phpml',
'PHPMailer\\PHPMailer' => 'lib/phpmailer/src',
'RedeyeVentures\\GeoPattern' => 'lib/geopattern-php/GeoPattern',
'MongoDB' => 'cache/stores/mongodb/MongoDB',
);
/**