Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

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

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