Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401]

   1  <?php
   2  /*
   3   * Copyright 2015-2017 MongoDB, Inc.
   4   *
   5   * Licensed under the Apache License, Version 2.0 (the "License");
   6   * you may not use this file except in compliance with the License.
   7   * You may obtain a copy of the License at
   8   *
   9   *   http://www.apache.org/licenses/LICENSE-2.0
  10   *
  11   * Unless required by applicable law or agreed to in writing, software
  12   * distributed under the License is distributed on an "AS IS" BASIS,
  13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14   * See the License for the specific language governing permissions and
  15   * limitations under the License.
  16   */
  17  
  18  namespace MongoDB;
  19  
  20  use MongoDB\BSON\JavascriptInterface;
  21  use MongoDB\Driver\Cursor;
  22  use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
  23  use MongoDB\Driver\Manager;
  24  use MongoDB\Driver\ReadConcern;
  25  use MongoDB\Driver\ReadPreference;
  26  use MongoDB\Driver\WriteConcern;
  27  use MongoDB\Exception\InvalidArgumentException;
  28  use MongoDB\Exception\UnexpectedValueException;
  29  use MongoDB\Exception\UnsupportedException;
  30  use MongoDB\Model\BSONArray;
  31  use MongoDB\Model\BSONDocument;
  32  use MongoDB\Model\IndexInfo;
  33  use MongoDB\Model\IndexInfoIterator;
  34  use MongoDB\Operation\Aggregate;
  35  use MongoDB\Operation\BulkWrite;
  36  use MongoDB\Operation\Count;
  37  use MongoDB\Operation\CountDocuments;
  38  use MongoDB\Operation\CreateIndexes;
  39  use MongoDB\Operation\DeleteMany;
  40  use MongoDB\Operation\DeleteOne;
  41  use MongoDB\Operation\Distinct;
  42  use MongoDB\Operation\DropCollection;
  43  use MongoDB\Operation\DropIndexes;
  44  use MongoDB\Operation\EstimatedDocumentCount;
  45  use MongoDB\Operation\Explain;
  46  use MongoDB\Operation\Explainable;
  47  use MongoDB\Operation\Find;
  48  use MongoDB\Operation\FindOne;
  49  use MongoDB\Operation\FindOneAndDelete;
  50  use MongoDB\Operation\FindOneAndReplace;
  51  use MongoDB\Operation\FindOneAndUpdate;
  52  use MongoDB\Operation\InsertMany;
  53  use MongoDB\Operation\InsertOne;
  54  use MongoDB\Operation\ListIndexes;
  55  use MongoDB\Operation\MapReduce;
  56  use MongoDB\Operation\ReplaceOne;
  57  use MongoDB\Operation\UpdateMany;
  58  use MongoDB\Operation\UpdateOne;
  59  use MongoDB\Operation\Watch;
  60  use Traversable;
  61  use function array_diff_key;
  62  use function array_intersect_key;
  63  use function current;
  64  use function is_array;
  65  use function strlen;
  66  
  67  class Collection
  68  {
  69      /** @var array */
  70      private static $defaultTypeMap = [
  71          'array' => BSONArray::class,
  72          'document' => BSONDocument::class,
  73          'root' => BSONDocument::class,
  74      ];
  75  
  76      /** @var integer */
  77      private static $wireVersionForFindAndModifyWriteConcern = 4;
  78  
  79      /** @var integer */
  80      private static $wireVersionForReadConcern = 4;
  81  
  82      /** @var integer */
  83      private static $wireVersionForWritableCommandWriteConcern = 5;
  84  
  85      /** @var integer */
  86      private static $wireVersionForReadConcernWithWriteStage = 8;
  87  
  88      /** @var string */
  89      private $collectionName;
  90  
  91      /** @var string */
  92      private $databaseName;
  93  
  94      /** @var Manager */
  95      private $manager;
  96  
  97      /** @var ReadConcern */
  98      private $readConcern;
  99  
 100      /** @var ReadPreference */
 101      private $readPreference;
 102  
 103      /** @var array */
 104      private $typeMap;
 105  
 106      /** @var WriteConcern */
 107      private $writeConcern;
 108  
 109      /**
 110       * Constructs new Collection instance.
 111       *
 112       * This class provides methods for collection-specific operations, such as
 113       * CRUD (i.e. create, read, update, and delete) and index management.
 114       *
 115       * Supported options:
 116       *
 117       *  * readConcern (MongoDB\Driver\ReadConcern): The default read concern to
 118       *    use for collection operations. Defaults to the Manager's read concern.
 119       *
 120       *  * readPreference (MongoDB\Driver\ReadPreference): The default read
 121       *    preference to use for collection operations. Defaults to the Manager's
 122       *    read preference.
 123       *
 124       *  * typeMap (array): Default type map for cursors and BSON documents.
 125       *
 126       *  * writeConcern (MongoDB\Driver\WriteConcern): The default write concern
 127       *    to use for collection operations. Defaults to the Manager's write
 128       *    concern.
 129       *
 130       * @param Manager $manager        Manager instance from the driver
 131       * @param string  $databaseName   Database name
 132       * @param string  $collectionName Collection name
 133       * @param array   $options        Collection options
 134       * @throws InvalidArgumentException for parameter/option parsing errors
 135       */
 136      public function __construct(Manager $manager, $databaseName, $collectionName, array $options = [])
 137      {
 138          if (strlen($databaseName) < 1) {
 139              throw new InvalidArgumentException('$databaseName is invalid: ' . $databaseName);
 140          }
 141  
 142          if (strlen($collectionName) < 1) {
 143              throw new InvalidArgumentException('$collectionName is invalid: ' . $collectionName);
 144          }
 145  
 146          if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
 147              throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], ReadConcern::class);
 148          }
 149  
 150          if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
 151              throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class);
 152          }
 153  
 154          if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
 155              throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
 156          }
 157  
 158          if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
 159              throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class);
 160          }
 161  
 162          $this->manager = $manager;
 163          $this->databaseName = (string) $databaseName;
 164          $this->collectionName = (string) $collectionName;
 165          $this->readConcern = isset($options['readConcern']) ? $options['readConcern'] : $this->manager->getReadConcern();
 166          $this->readPreference = isset($options['readPreference']) ? $options['readPreference'] : $this->manager->getReadPreference();
 167          $this->typeMap = isset($options['typeMap']) ? $options['typeMap'] : self::$defaultTypeMap;
 168          $this->writeConcern = isset($options['writeConcern']) ? $options['writeConcern'] : $this->manager->getWriteConcern();
 169      }
 170  
 171      /**
 172       * Return internal properties for debugging purposes.
 173       *
 174       * @see http://php.net/manual/en/language.oop5.magic.php#language.oop5.magic.debuginfo
 175       * @return array
 176       */
 177      public function __debugInfo()
 178      {
 179          return [
 180              'collectionName' => $this->collectionName,
 181              'databaseName' => $this->databaseName,
 182              'manager' => $this->manager,
 183              'readConcern' => $this->readConcern,
 184              'readPreference' => $this->readPreference,
 185              'typeMap' => $this->typeMap,
 186              'writeConcern' => $this->writeConcern,
 187          ];
 188      }
 189  
 190      /**
 191       * Return the collection namespace (e.g. "db.collection").
 192       *
 193       * @see https://docs.mongodb.org/manual/faq/developers/#faq-dev-namespace
 194       * @return string
 195       */
 196      public function __toString()
 197      {
 198          return $this->databaseName . '.' . $this->collectionName;
 199      }
 200  
 201      /**
 202       * Executes an aggregation framework pipeline on the collection.
 203       *
 204       * Note: this method's return value depends on the MongoDB server version
 205       * and the "useCursor" option. If "useCursor" is true, a Cursor will be
 206       * returned; otherwise, an ArrayIterator is returned, which wraps the
 207       * "result" array from the command response document.
 208       *
 209       * @see Aggregate::__construct() for supported options
 210       * @param array $pipeline List of pipeline operations
 211       * @param array $options  Command options
 212       * @return Traversable
 213       * @throws UnexpectedValueException if the command response was malformed
 214       * @throws UnsupportedException if options are not supported by the selected server
 215       * @throws InvalidArgumentException for parameter/option parsing errors
 216       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 217       */
 218      public function aggregate(array $pipeline, array $options = [])
 219      {
 220          $hasWriteStage = is_last_pipeline_operator_write($pipeline);
 221  
 222          if (! isset($options['readPreference']) && ! is_in_transaction($options)) {
 223              $options['readPreference'] = $this->readPreference;
 224          }
 225  
 226          if ($hasWriteStage) {
 227              $options['readPreference'] = new ReadPreference(ReadPreference::RP_PRIMARY);
 228          }
 229  
 230          $server = select_server($this->manager, $options);
 231  
 232          /* MongoDB 4.2 and later supports a read concern when an $out stage is
 233           * being used, but earlier versions do not.
 234           *
 235           * A read concern is also not compatible with transactions.
 236           */
 237          if (! isset($options['readConcern']) &&
 238              server_supports_feature($server, self::$wireVersionForReadConcern) &&
 239              ! is_in_transaction($options) &&
 240              ( ! $hasWriteStage || server_supports_feature($server, self::$wireVersionForReadConcernWithWriteStage))
 241          ) {
 242              $options['readConcern'] = $this->readConcern;
 243          }
 244  
 245          if (! isset($options['typeMap'])) {
 246              $options['typeMap'] = $this->typeMap;
 247          }
 248  
 249          if ($hasWriteStage &&
 250              ! isset($options['writeConcern']) &&
 251              server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern) &&
 252              ! is_in_transaction($options)) {
 253              $options['writeConcern'] = $this->writeConcern;
 254          }
 255  
 256          $operation = new Aggregate($this->databaseName, $this->collectionName, $pipeline, $options);
 257  
 258          return $operation->execute($server);
 259      }
 260  
 261      /**
 262       * Executes multiple write operations.
 263       *
 264       * @see BulkWrite::__construct() for supported options
 265       * @param array[] $operations List of write operations
 266       * @param array   $options    Command options
 267       * @return BulkWriteResult
 268       * @throws UnsupportedException if options are not supported by the selected server
 269       * @throws InvalidArgumentException for parameter/option parsing errors
 270       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 271       */
 272      public function bulkWrite(array $operations, array $options = [])
 273      {
 274          if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
 275              $options['writeConcern'] = $this->writeConcern;
 276          }
 277  
 278          $operation = new BulkWrite($this->databaseName, $this->collectionName, $operations, $options);
 279          $server = select_server($this->manager, $options);
 280  
 281          return $operation->execute($server);
 282      }
 283  
 284      /**
 285       * Gets the number of documents matching the filter.
 286       *
 287       * @see Count::__construct() for supported options
 288       * @param array|object $filter  Query by which to filter documents
 289       * @param array        $options Command options
 290       * @return integer
 291       * @throws UnexpectedValueException if the command response was malformed
 292       * @throws UnsupportedException if options are not supported by the selected server
 293       * @throws InvalidArgumentException for parameter/option parsing errors
 294       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 295       *
 296       * @deprecated 1.4
 297       */
 298      public function count($filter = [], array $options = [])
 299      {
 300          if (! isset($options['readPreference']) && ! is_in_transaction($options)) {
 301              $options['readPreference'] = $this->readPreference;
 302          }
 303  
 304          $server = select_server($this->manager, $options);
 305  
 306          if (! isset($options['readConcern']) && server_supports_feature($server, self::$wireVersionForReadConcern) && ! is_in_transaction($options)) {
 307              $options['readConcern'] = $this->readConcern;
 308          }
 309  
 310          $operation = new Count($this->databaseName, $this->collectionName, $filter, $options);
 311  
 312          return $operation->execute($server);
 313      }
 314  
 315      /**
 316       * Gets the number of documents matching the filter.
 317       *
 318       * @see CountDocuments::__construct() for supported options
 319       * @param array|object $filter  Query by which to filter documents
 320       * @param array        $options Command options
 321       * @return integer
 322       * @throws UnexpectedValueException if the command response was malformed
 323       * @throws UnsupportedException if options are not supported by the selected server
 324       * @throws InvalidArgumentException for parameter/option parsing errors
 325       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 326       */
 327      public function countDocuments($filter = [], array $options = [])
 328      {
 329          if (! isset($options['readPreference']) && ! is_in_transaction($options)) {
 330              $options['readPreference'] = $this->readPreference;
 331          }
 332  
 333          $server = select_server($this->manager, $options);
 334  
 335          if (! isset($options['readConcern']) && server_supports_feature($server, self::$wireVersionForReadConcern) && ! is_in_transaction($options)) {
 336              $options['readConcern'] = $this->readConcern;
 337          }
 338  
 339          $operation = new CountDocuments($this->databaseName, $this->collectionName, $filter, $options);
 340  
 341          return $operation->execute($server);
 342      }
 343  
 344      /**
 345       * Create a single index for the collection.
 346       *
 347       * @see Collection::createIndexes()
 348       * @see CreateIndexes::__construct() for supported command options
 349       * @param array|object $key     Document containing fields mapped to values,
 350       *                              which denote order or an index type
 351       * @param array        $options Index and command options
 352       * @return string The name of the created index
 353       * @throws UnsupportedException if options are not supported by the selected server
 354       * @throws InvalidArgumentException for parameter/option parsing errors
 355       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 356       */
 357      public function createIndex($key, array $options = [])
 358      {
 359          $commandOptionKeys = ['maxTimeMS' => 1, 'session' => 1, 'writeConcern' => 1];
 360          $indexOptions = array_diff_key($options, $commandOptionKeys);
 361          $commandOptions = array_intersect_key($options, $commandOptionKeys);
 362  
 363          return current($this->createIndexes([['key' => $key] + $indexOptions], $commandOptions));
 364      }
 365  
 366      /**
 367       * Create one or more indexes for the collection.
 368       *
 369       * Each element in the $indexes array must have a "key" document, which
 370       * contains fields mapped to an order or type. Other options may follow.
 371       * For example:
 372       *
 373       *     $indexes = [
 374       *         // Create a unique index on the "username" field
 375       *         [ 'key' => [ 'username' => 1 ], 'unique' => true ],
 376       *         // Create a 2dsphere index on the "loc" field with a custom name
 377       *         [ 'key' => [ 'loc' => '2dsphere' ], 'name' => 'geo' ],
 378       *     ];
 379       *
 380       * If the "name" option is unspecified, a name will be generated from the
 381       * "key" document.
 382       *
 383       * @see http://docs.mongodb.org/manual/reference/command/createIndexes/
 384       * @see http://docs.mongodb.org/manual/reference/method/db.collection.createIndex/
 385       * @see CreateIndexes::__construct() for supported command options
 386       * @param array[] $indexes List of index specifications
 387       * @param array   $options Command options
 388       * @return string[] The names of the created indexes
 389       * @throws UnsupportedException if options are not supported by the selected server
 390       * @throws InvalidArgumentException for parameter/option parsing errors
 391       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 392       */
 393      public function createIndexes(array $indexes, array $options = [])
 394      {
 395          $server = select_server($this->manager, $options);
 396  
 397          if (! isset($options['writeConcern']) && server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern) && ! is_in_transaction($options)) {
 398              $options['writeConcern'] = $this->writeConcern;
 399          }
 400  
 401          $operation = new CreateIndexes($this->databaseName, $this->collectionName, $indexes, $options);
 402  
 403          return $operation->execute($server);
 404      }
 405  
 406      /**
 407       * Deletes all documents matching the filter.
 408       *
 409       * @see DeleteMany::__construct() for supported options
 410       * @see http://docs.mongodb.org/manual/reference/command/delete/
 411       * @param array|object $filter  Query by which to delete documents
 412       * @param array        $options Command options
 413       * @return DeleteResult
 414       * @throws UnsupportedException if options are not supported by the selected server
 415       * @throws InvalidArgumentException for parameter/option parsing errors
 416       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 417       */
 418      public function deleteMany($filter, array $options = [])
 419      {
 420          if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
 421              $options['writeConcern'] = $this->writeConcern;
 422          }
 423  
 424          $operation = new DeleteMany($this->databaseName, $this->collectionName, $filter, $options);
 425          $server = select_server($this->manager, $options);
 426  
 427          return $operation->execute($server);
 428      }
 429  
 430      /**
 431       * Deletes at most one document matching the filter.
 432       *
 433       * @see DeleteOne::__construct() for supported options
 434       * @see http://docs.mongodb.org/manual/reference/command/delete/
 435       * @param array|object $filter  Query by which to delete documents
 436       * @param array        $options Command options
 437       * @return DeleteResult
 438       * @throws UnsupportedException if options are not supported by the selected server
 439       * @throws InvalidArgumentException for parameter/option parsing errors
 440       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 441       */
 442      public function deleteOne($filter, array $options = [])
 443      {
 444          if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
 445              $options['writeConcern'] = $this->writeConcern;
 446          }
 447  
 448          $operation = new DeleteOne($this->databaseName, $this->collectionName, $filter, $options);
 449          $server = select_server($this->manager, $options);
 450  
 451          return $operation->execute($server);
 452      }
 453  
 454      /**
 455       * Finds the distinct values for a specified field across the collection.
 456       *
 457       * @see Distinct::__construct() for supported options
 458       * @param string       $fieldName Field for which to return distinct values
 459       * @param array|object $filter    Query by which to filter documents
 460       * @param array        $options   Command options
 461       * @return mixed[]
 462       * @throws UnexpectedValueException if the command response was malformed
 463       * @throws UnsupportedException if options are not supported by the selected server
 464       * @throws InvalidArgumentException for parameter/option parsing errors
 465       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 466       */
 467      public function distinct($fieldName, $filter = [], array $options = [])
 468      {
 469          if (! isset($options['readPreference']) && ! is_in_transaction($options)) {
 470              $options['readPreference'] = $this->readPreference;
 471          }
 472  
 473          if (! isset($options['typeMap'])) {
 474              $options['typeMap'] = $this->typeMap;
 475          }
 476  
 477          $server = select_server($this->manager, $options);
 478  
 479          if (! isset($options['readConcern']) && server_supports_feature($server, self::$wireVersionForReadConcern) && ! is_in_transaction($options)) {
 480              $options['readConcern'] = $this->readConcern;
 481          }
 482  
 483          $operation = new Distinct($this->databaseName, $this->collectionName, $fieldName, $filter, $options);
 484  
 485          return $operation->execute($server);
 486      }
 487  
 488      /**
 489       * Drop this collection.
 490       *
 491       * @see DropCollection::__construct() for supported options
 492       * @param array $options Additional options
 493       * @return array|object Command result document
 494       * @throws UnsupportedException if options are not supported by the selected server
 495       * @throws InvalidArgumentException for parameter/option parsing errors
 496       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 497       */
 498      public function drop(array $options = [])
 499      {
 500          if (! isset($options['typeMap'])) {
 501              $options['typeMap'] = $this->typeMap;
 502          }
 503  
 504          $server = select_server($this->manager, $options);
 505  
 506          if (! isset($options['writeConcern']) && server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern) && ! is_in_transaction($options)) {
 507              $options['writeConcern'] = $this->writeConcern;
 508          }
 509  
 510          $operation = new DropCollection($this->databaseName, $this->collectionName, $options);
 511  
 512          return $operation->execute($server);
 513      }
 514  
 515      /**
 516       * Drop a single index in the collection.
 517       *
 518       * @see DropIndexes::__construct() for supported options
 519       * @param string|IndexInfo $indexName Index name or model object
 520       * @param array            $options   Additional options
 521       * @return array|object Command result document
 522       * @throws UnsupportedException if options are not supported by the selected server
 523       * @throws InvalidArgumentException for parameter/option parsing errors
 524       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 525       */
 526      public function dropIndex($indexName, array $options = [])
 527      {
 528          $indexName = (string) $indexName;
 529  
 530          if ($indexName === '*') {
 531              throw new InvalidArgumentException('dropIndexes() must be used to drop multiple indexes');
 532          }
 533  
 534          if (! isset($options['typeMap'])) {
 535              $options['typeMap'] = $this->typeMap;
 536          }
 537  
 538          $server = select_server($this->manager, $options);
 539  
 540          if (! isset($options['writeConcern']) && server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern) && ! is_in_transaction($options)) {
 541              $options['writeConcern'] = $this->writeConcern;
 542          }
 543  
 544          $operation = new DropIndexes($this->databaseName, $this->collectionName, $indexName, $options);
 545  
 546          return $operation->execute($server);
 547      }
 548  
 549      /**
 550       * Drop all indexes in the collection.
 551       *
 552       * @see DropIndexes::__construct() for supported options
 553       * @param array $options Additional options
 554       * @return array|object Command result document
 555       * @throws UnsupportedException if options are not supported by the selected server
 556       * @throws InvalidArgumentException for parameter/option parsing errors
 557       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 558       */
 559      public function dropIndexes(array $options = [])
 560      {
 561          if (! isset($options['typeMap'])) {
 562              $options['typeMap'] = $this->typeMap;
 563          }
 564  
 565          $server = select_server($this->manager, $options);
 566  
 567          if (! isset($options['writeConcern']) && server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern) && ! is_in_transaction($options)) {
 568              $options['writeConcern'] = $this->writeConcern;
 569          }
 570  
 571          $operation = new DropIndexes($this->databaseName, $this->collectionName, '*', $options);
 572  
 573          return $operation->execute($server);
 574      }
 575  
 576      /**
 577       * Gets an estimated number of documents in the collection using the collection metadata.
 578       *
 579       * @see EstimatedDocumentCount::__construct() for supported options
 580       * @param array $options Command options
 581       * @return integer
 582       * @throws UnexpectedValueException if the command response was malformed
 583       * @throws UnsupportedException if options are not supported by the selected server
 584       * @throws InvalidArgumentException for parameter/option parsing errors
 585       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 586       */
 587      public function estimatedDocumentCount(array $options = [])
 588      {
 589          if (! isset($options['readPreference']) && ! is_in_transaction($options)) {
 590              $options['readPreference'] = $this->readPreference;
 591          }
 592  
 593          $server = select_server($this->manager, $options);
 594  
 595          if (! isset($options['readConcern']) && server_supports_feature($server, self::$wireVersionForReadConcern) && ! is_in_transaction($options)) {
 596              $options['readConcern'] = $this->readConcern;
 597          }
 598  
 599          $operation = new EstimatedDocumentCount($this->databaseName, $this->collectionName, $options);
 600  
 601          return $operation->execute($server);
 602      }
 603  
 604      /**
 605       * Explains explainable commands.
 606       *
 607       * @see Explain::__construct() for supported options
 608       * @see http://docs.mongodb.org/manual/reference/command/explain/
 609       * @param Explainable $explainable Command on which to run explain
 610       * @param array       $options     Additional options
 611       * @return array|object
 612       * @throws UnsupportedException if explainable or options are not supported by the selected server
 613       * @throws InvalidArgumentException for parameter/option parsing errors
 614       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 615       */
 616      public function explain(Explainable $explainable, array $options = [])
 617      {
 618          if (! isset($options['readPreference']) && ! is_in_transaction($options)) {
 619              $options['readPreference'] = $this->readPreference;
 620          }
 621  
 622          if (! isset($options['typeMap'])) {
 623              $options['typeMap'] = $this->typeMap;
 624          }
 625  
 626          $server = select_server($this->manager, $options);
 627  
 628          $operation = new Explain($this->databaseName, $explainable, $options);
 629  
 630          return $operation->execute($server);
 631      }
 632  
 633      /**
 634       * Finds documents matching the query.
 635       *
 636       * @see Find::__construct() for supported options
 637       * @see http://docs.mongodb.org/manual/core/read-operations-introduction/
 638       * @param array|object $filter  Query by which to filter documents
 639       * @param array        $options Additional options
 640       * @return Cursor
 641       * @throws UnsupportedException if options are not supported by the selected server
 642       * @throws InvalidArgumentException for parameter/option parsing errors
 643       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 644       */
 645      public function find($filter = [], array $options = [])
 646      {
 647          if (! isset($options['readPreference']) && ! is_in_transaction($options)) {
 648              $options['readPreference'] = $this->readPreference;
 649          }
 650  
 651          $server = select_server($this->manager, $options);
 652  
 653          if (! isset($options['readConcern']) && server_supports_feature($server, self::$wireVersionForReadConcern) && ! is_in_transaction($options)) {
 654              $options['readConcern'] = $this->readConcern;
 655          }
 656  
 657          if (! isset($options['typeMap'])) {
 658              $options['typeMap'] = $this->typeMap;
 659          }
 660  
 661          $operation = new Find($this->databaseName, $this->collectionName, $filter, $options);
 662  
 663          return $operation->execute($server);
 664      }
 665  
 666      /**
 667       * Finds a single document matching the query.
 668       *
 669       * @see FindOne::__construct() for supported options
 670       * @see http://docs.mongodb.org/manual/core/read-operations-introduction/
 671       * @param array|object $filter  Query by which to filter documents
 672       * @param array        $options Additional options
 673       * @return array|object|null
 674       * @throws UnsupportedException if options are not supported by the selected server
 675       * @throws InvalidArgumentException for parameter/option parsing errors
 676       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 677       */
 678      public function findOne($filter = [], array $options = [])
 679      {
 680          if (! isset($options['readPreference']) && ! is_in_transaction($options)) {
 681              $options['readPreference'] = $this->readPreference;
 682          }
 683  
 684          $server = select_server($this->manager, $options);
 685  
 686          if (! isset($options['readConcern']) && server_supports_feature($server, self::$wireVersionForReadConcern) && ! is_in_transaction($options)) {
 687              $options['readConcern'] = $this->readConcern;
 688          }
 689  
 690          if (! isset($options['typeMap'])) {
 691              $options['typeMap'] = $this->typeMap;
 692          }
 693  
 694          $operation = new FindOne($this->databaseName, $this->collectionName, $filter, $options);
 695  
 696          return $operation->execute($server);
 697      }
 698  
 699      /**
 700       * Finds a single document and deletes it, returning the original.
 701       *
 702       * The document to return may be null if no document matched the filter.
 703       *
 704       * @see FindOneAndDelete::__construct() for supported options
 705       * @see http://docs.mongodb.org/manual/reference/command/findAndModify/
 706       * @param array|object $filter  Query by which to filter documents
 707       * @param array        $options Command options
 708       * @return array|object|null
 709       * @throws UnexpectedValueException if the command response was malformed
 710       * @throws UnsupportedException if options are not supported by the selected server
 711       * @throws InvalidArgumentException for parameter/option parsing errors
 712       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 713       */
 714      public function findOneAndDelete($filter, array $options = [])
 715      {
 716          $server = select_server($this->manager, $options);
 717  
 718          if (! isset($options['writeConcern']) && server_supports_feature($server, self::$wireVersionForFindAndModifyWriteConcern) && ! is_in_transaction($options)) {
 719              $options['writeConcern'] = $this->writeConcern;
 720          }
 721  
 722          if (! isset($options['typeMap'])) {
 723              $options['typeMap'] = $this->typeMap;
 724          }
 725  
 726          $operation = new FindOneAndDelete($this->databaseName, $this->collectionName, $filter, $options);
 727  
 728          return $operation->execute($server);
 729      }
 730  
 731      /**
 732       * Finds a single document and replaces it, returning either the original or
 733       * the replaced document.
 734       *
 735       * The document to return may be null if no document matched the filter. By
 736       * default, the original document is returned. Specify
 737       * FindOneAndReplace::RETURN_DOCUMENT_AFTER for the "returnDocument" option
 738       * to return the updated document.
 739       *
 740       * @see FindOneAndReplace::__construct() for supported options
 741       * @see http://docs.mongodb.org/manual/reference/command/findAndModify/
 742       * @param array|object $filter      Query by which to filter documents
 743       * @param array|object $replacement Replacement document
 744       * @param array        $options     Command options
 745       * @return array|object|null
 746       * @throws UnexpectedValueException if the command response was malformed
 747       * @throws UnsupportedException if options are not supported by the selected server
 748       * @throws InvalidArgumentException for parameter/option parsing errors
 749       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 750       */
 751      public function findOneAndReplace($filter, $replacement, array $options = [])
 752      {
 753          $server = select_server($this->manager, $options);
 754  
 755          if (! isset($options['writeConcern']) && server_supports_feature($server, self::$wireVersionForFindAndModifyWriteConcern) && ! is_in_transaction($options)) {
 756              $options['writeConcern'] = $this->writeConcern;
 757          }
 758  
 759          if (! isset($options['typeMap'])) {
 760              $options['typeMap'] = $this->typeMap;
 761          }
 762  
 763          $operation = new FindOneAndReplace($this->databaseName, $this->collectionName, $filter, $replacement, $options);
 764  
 765          return $operation->execute($server);
 766      }
 767  
 768      /**
 769       * Finds a single document and updates it, returning either the original or
 770       * the updated document.
 771       *
 772       * The document to return may be null if no document matched the filter. By
 773       * default, the original document is returned. Specify
 774       * FindOneAndUpdate::RETURN_DOCUMENT_AFTER for the "returnDocument" option
 775       * to return the updated document.
 776       *
 777       * @see FindOneAndReplace::__construct() for supported options
 778       * @see http://docs.mongodb.org/manual/reference/command/findAndModify/
 779       * @param array|object $filter  Query by which to filter documents
 780       * @param array|object $update  Update to apply to the matched document
 781       * @param array        $options Command options
 782       * @return array|object|null
 783       * @throws UnexpectedValueException if the command response was malformed
 784       * @throws UnsupportedException if options are not supported by the selected server
 785       * @throws InvalidArgumentException for parameter/option parsing errors
 786       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 787       */
 788      public function findOneAndUpdate($filter, $update, array $options = [])
 789      {
 790          $server = select_server($this->manager, $options);
 791  
 792          if (! isset($options['writeConcern']) && server_supports_feature($server, self::$wireVersionForFindAndModifyWriteConcern) && ! is_in_transaction($options)) {
 793              $options['writeConcern'] = $this->writeConcern;
 794          }
 795  
 796          if (! isset($options['typeMap'])) {
 797              $options['typeMap'] = $this->typeMap;
 798          }
 799  
 800          $operation = new FindOneAndUpdate($this->databaseName, $this->collectionName, $filter, $update, $options);
 801  
 802          return $operation->execute($server);
 803      }
 804  
 805      /**
 806       * Return the collection name.
 807       *
 808       * @return string
 809       */
 810      public function getCollectionName()
 811      {
 812          return $this->collectionName;
 813      }
 814  
 815      /**
 816       * Return the database name.
 817       *
 818       * @return string
 819       */
 820      public function getDatabaseName()
 821      {
 822          return $this->databaseName;
 823      }
 824  
 825      /**
 826       * Return the Manager.
 827       *
 828       * @return Manager
 829       */
 830      public function getManager()
 831      {
 832          return $this->manager;
 833      }
 834  
 835      /**
 836       * Return the collection namespace.
 837       *
 838       * @see https://docs.mongodb.org/manual/reference/glossary/#term-namespace
 839       * @return string
 840       */
 841      public function getNamespace()
 842      {
 843          return $this->databaseName . '.' . $this->collectionName;
 844      }
 845  
 846      /**
 847       * Return the read concern for this collection.
 848       *
 849       * @see http://php.net/manual/en/mongodb-driver-readconcern.isdefault.php
 850       * @return ReadConcern
 851       */
 852      public function getReadConcern()
 853      {
 854          return $this->readConcern;
 855      }
 856  
 857      /**
 858       * Return the read preference for this collection.
 859       *
 860       * @return ReadPreference
 861       */
 862      public function getReadPreference()
 863      {
 864          return $this->readPreference;
 865      }
 866  
 867      /**
 868       * Return the type map for this collection.
 869       *
 870       * @return array
 871       */
 872      public function getTypeMap()
 873      {
 874          return $this->typeMap;
 875      }
 876  
 877      /**
 878       * Return the write concern for this collection.
 879       *
 880       * @see http://php.net/manual/en/mongodb-driver-writeconcern.isdefault.php
 881       * @return WriteConcern
 882       */
 883      public function getWriteConcern()
 884      {
 885          return $this->writeConcern;
 886      }
 887  
 888      /**
 889       * Inserts multiple documents.
 890       *
 891       * @see InsertMany::__construct() for supported options
 892       * @see http://docs.mongodb.org/manual/reference/command/insert/
 893       * @param array[]|object[] $documents The documents to insert
 894       * @param array            $options   Command options
 895       * @return InsertManyResult
 896       * @throws InvalidArgumentException for parameter/option parsing errors
 897       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 898       */
 899      public function insertMany(array $documents, array $options = [])
 900      {
 901          if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
 902              $options['writeConcern'] = $this->writeConcern;
 903          }
 904  
 905          $operation = new InsertMany($this->databaseName, $this->collectionName, $documents, $options);
 906          $server = select_server($this->manager, $options);
 907  
 908          return $operation->execute($server);
 909      }
 910  
 911      /**
 912       * Inserts one document.
 913       *
 914       * @see InsertOne::__construct() for supported options
 915       * @see http://docs.mongodb.org/manual/reference/command/insert/
 916       * @param array|object $document The document to insert
 917       * @param array        $options  Command options
 918       * @return InsertOneResult
 919       * @throws InvalidArgumentException for parameter/option parsing errors
 920       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 921       */
 922      public function insertOne($document, array $options = [])
 923      {
 924          if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
 925              $options['writeConcern'] = $this->writeConcern;
 926          }
 927  
 928          $operation = new InsertOne($this->databaseName, $this->collectionName, $document, $options);
 929          $server = select_server($this->manager, $options);
 930  
 931          return $operation->execute($server);
 932      }
 933  
 934      /**
 935       * Returns information for all indexes for the collection.
 936       *
 937       * @see ListIndexes::__construct() for supported options
 938       * @param array $options
 939       * @return IndexInfoIterator
 940       * @throws InvalidArgumentException for parameter/option parsing errors
 941       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 942       */
 943      public function listIndexes(array $options = [])
 944      {
 945          $operation = new ListIndexes($this->databaseName, $this->collectionName, $options);
 946          $server = select_server($this->manager, $options);
 947  
 948          return $operation->execute($server);
 949      }
 950  
 951      /**
 952       * Executes a map-reduce aggregation on the collection.
 953       *
 954       * @see MapReduce::__construct() for supported options
 955       * @see http://docs.mongodb.org/manual/reference/command/mapReduce/
 956       * @param JavascriptInterface $map     Map function
 957       * @param JavascriptInterface $reduce  Reduce function
 958       * @param string|array|object $out     Output specification
 959       * @param array               $options Command options
 960       * @return MapReduceResult
 961       * @throws UnsupportedException if options are not supported by the selected server
 962       * @throws InvalidArgumentException for parameter/option parsing errors
 963       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 964       * @throws UnexpectedValueException if the command response was malformed
 965       */
 966      public function mapReduce(JavascriptInterface $map, JavascriptInterface $reduce, $out, array $options = [])
 967      {
 968          $hasOutputCollection = ! is_mapreduce_output_inline($out);
 969  
 970          if (! isset($options['readPreference']) && ! is_in_transaction($options)) {
 971              $options['readPreference'] = $this->readPreference;
 972          }
 973  
 974          // Check if the out option is inline because we will want to coerce a primary read preference if not
 975          if ($hasOutputCollection) {
 976              $options['readPreference'] = new ReadPreference(ReadPreference::RP_PRIMARY);
 977          }
 978  
 979          $server = select_server($this->manager, $options);
 980  
 981          /* A "majority" read concern is not compatible with inline output, so
 982           * avoid providing the Collection's read concern if it would conflict.
 983           *
 984           * A read concern is also not compatible with transactions.
 985           */
 986          if (! isset($options['readConcern']) && ! ($hasOutputCollection && $this->readConcern->getLevel() === ReadConcern::MAJORITY) && server_supports_feature($server, self::$wireVersionForReadConcern) && ! is_in_transaction($options)) {
 987              $options['readConcern'] = $this->readConcern;
 988          }
 989  
 990          if (! isset($options['typeMap'])) {
 991              $options['typeMap'] = $this->typeMap;
 992          }
 993  
 994          if (! isset($options['writeConcern']) && server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern) && ! is_in_transaction($options)) {
 995              $options['writeConcern'] = $this->writeConcern;
 996          }
 997  
 998          $operation = new MapReduce($this->databaseName, $this->collectionName, $map, $reduce, $out, $options);
 999  
1000          return $operation->execute($server);
1001      }
1002  
1003      /**
1004       * Replaces at most one document matching the filter.
1005       *
1006       * @see ReplaceOne::__construct() for supported options
1007       * @see http://docs.mongodb.org/manual/reference/command/update/
1008       * @param array|object $filter      Query by which to filter documents
1009       * @param array|object $replacement Replacement document
1010       * @param array        $options     Command options
1011       * @return UpdateResult
1012       * @throws UnsupportedException if options are not supported by the selected server
1013       * @throws InvalidArgumentException for parameter/option parsing errors
1014       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
1015       */
1016      public function replaceOne($filter, $replacement, array $options = [])
1017      {
1018          if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
1019              $options['writeConcern'] = $this->writeConcern;
1020          }
1021  
1022          $operation = new ReplaceOne($this->databaseName, $this->collectionName, $filter, $replacement, $options);
1023          $server = select_server($this->manager, $options);
1024  
1025          return $operation->execute($server);
1026      }
1027  
1028      /**
1029       * Updates all documents matching the filter.
1030       *
1031       * @see UpdateMany::__construct() for supported options
1032       * @see http://docs.mongodb.org/manual/reference/command/update/
1033       * @param array|object $filter  Query by which to filter documents
1034       * @param array|object $update  Update to apply to the matched documents
1035       * @param array        $options Command options
1036       * @return UpdateResult
1037       * @throws UnsupportedException if options are not supported by the selected server
1038       * @throws InvalidArgumentException for parameter/option parsing errors
1039       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
1040       */
1041      public function updateMany($filter, $update, array $options = [])
1042      {
1043          if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
1044              $options['writeConcern'] = $this->writeConcern;
1045          }
1046  
1047          $operation = new UpdateMany($this->databaseName, $this->collectionName, $filter, $update, $options);
1048          $server = select_server($this->manager, $options);
1049  
1050          return $operation->execute($server);
1051      }
1052  
1053      /**
1054       * Updates at most one document matching the filter.
1055       *
1056       * @see UpdateOne::__construct() for supported options
1057       * @see http://docs.mongodb.org/manual/reference/command/update/
1058       * @param array|object $filter  Query by which to filter documents
1059       * @param array|object $update  Update to apply to the matched document
1060       * @param array        $options Command options
1061       * @return UpdateResult
1062       * @throws UnsupportedException if options are not supported by the selected server
1063       * @throws InvalidArgumentException for parameter/option parsing errors
1064       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
1065       */
1066      public function updateOne($filter, $update, array $options = [])
1067      {
1068          if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
1069              $options['writeConcern'] = $this->writeConcern;
1070          }
1071  
1072          $operation = new UpdateOne($this->databaseName, $this->collectionName, $filter, $update, $options);
1073          $server = select_server($this->manager, $options);
1074  
1075          return $operation->execute($server);
1076      }
1077  
1078      /**
1079       * Create a change stream for watching changes to the collection.
1080       *
1081       * @see Watch::__construct() for supported options
1082       * @param array $pipeline List of pipeline operations
1083       * @param array $options  Command options
1084       * @return ChangeStream
1085       * @throws InvalidArgumentException for parameter/option parsing errors
1086       */
1087      public function watch(array $pipeline = [], array $options = [])
1088      {
1089          if (! isset($options['readPreference']) && ! is_in_transaction($options)) {
1090              $options['readPreference'] = $this->readPreference;
1091          }
1092  
1093          $server = select_server($this->manager, $options);
1094  
1095          /* Although change streams require a newer version of the server than
1096           * read concerns, perform the usual wire version check before inheriting
1097           * the collection's read concern. In the event that the server is too
1098           * old, this makes it more likely that users will encounter an error
1099           * related to change streams being unsupported instead of an
1100           * UnsupportedException regarding use of the "readConcern" option from
1101           * the Aggregate operation class. */
1102          if (! isset($options['readConcern']) && server_supports_feature($server, self::$wireVersionForReadConcern) && ! is_in_transaction($options)) {
1103              $options['readConcern'] = $this->readConcern;
1104          }
1105  
1106          if (! isset($options['typeMap'])) {
1107              $options['typeMap'] = $this->typeMap;
1108          }
1109  
1110          $operation = new Watch($this->manager, $this->databaseName, $this->collectionName, $pipeline, $options);
1111  
1112          return $operation->execute($server);
1113      }
1114  
1115      /**
1116       * Get a clone of this collection with different options.
1117       *
1118       * @see Collection::__construct() for supported options
1119       * @param array $options Collection constructor options
1120       * @return Collection
1121       * @throws InvalidArgumentException for parameter/option parsing errors
1122       */
1123      public function withOptions(array $options = [])
1124      {
1125          $options += [
1126              'readConcern' => $this->readConcern,
1127              'readPreference' => $this->readPreference,
1128              'typeMap' => $this->typeMap,
1129              'writeConcern' => $this->writeConcern,
1130          ];
1131  
1132          return new Collection($this->manager, $this->databaseName, $this->collectionName, $options);
1133      }
1134  }