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 Iterator;
  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\GridFS\Bucket;
  31  use MongoDB\Model\BSONArray;
  32  use MongoDB\Model\BSONDocument;
  33  use MongoDB\Model\CollectionInfoIterator;
  34  use MongoDB\Operation\Aggregate;
  35  use MongoDB\Operation\CreateCollection;
  36  use MongoDB\Operation\CreateIndexes;
  37  use MongoDB\Operation\DatabaseCommand;
  38  use MongoDB\Operation\DropCollection;
  39  use MongoDB\Operation\DropDatabase;
  40  use MongoDB\Operation\ListCollectionNames;
  41  use MongoDB\Operation\ListCollections;
  42  use MongoDB\Operation\ModifyCollection;
  43  use MongoDB\Operation\RenameCollection;
  44  use MongoDB\Operation\Watch;
  45  use Traversable;
  46  
  47  use function is_array;
  48  use function strlen;
  49  
  50  class Database
  51  {
  52      /** @var array */
  53      private static $defaultTypeMap = [
  54          'array' => BSONArray::class,
  55          'document' => BSONDocument::class,
  56          'root' => BSONDocument::class,
  57      ];
  58  
  59      /** @var integer */
  60      private static $wireVersionForReadConcernWithWriteStage = 8;
  61  
  62      /** @var string */
  63      private $databaseName;
  64  
  65      /** @var Manager */
  66      private $manager;
  67  
  68      /** @var ReadConcern */
  69      private $readConcern;
  70  
  71      /** @var ReadPreference */
  72      private $readPreference;
  73  
  74      /** @var array */
  75      private $typeMap;
  76  
  77      /** @var WriteConcern */
  78      private $writeConcern;
  79  
  80      /**
  81       * Constructs new Database instance.
  82       *
  83       * This class provides methods for database-specific operations and serves
  84       * as a gateway for accessing collections.
  85       *
  86       * Supported options:
  87       *
  88       *  * readConcern (MongoDB\Driver\ReadConcern): The default read concern to
  89       *    use for database operations and selected collections. Defaults to the
  90       *    Manager's read concern.
  91       *
  92       *  * readPreference (MongoDB\Driver\ReadPreference): The default read
  93       *    preference to use for database operations and selected collections.
  94       *    Defaults to the Manager's read preference.
  95       *
  96       *  * typeMap (array): Default type map for cursors and BSON documents.
  97       *
  98       *  * writeConcern (MongoDB\Driver\WriteConcern): The default write concern
  99       *    to use for database operations and selected collections. Defaults to
 100       *    the Manager's write concern.
 101       *
 102       * @param Manager $manager      Manager instance from the driver
 103       * @param string  $databaseName Database name
 104       * @param array   $options      Database options
 105       * @throws InvalidArgumentException for parameter/option parsing errors
 106       */
 107      public function __construct(Manager $manager, string $databaseName, array $options = [])
 108      {
 109          if (strlen($databaseName) < 1) {
 110              throw new InvalidArgumentException('$databaseName is invalid: ' . $databaseName);
 111          }
 112  
 113          if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
 114              throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], ReadConcern::class);
 115          }
 116  
 117          if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
 118              throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class);
 119          }
 120  
 121          if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
 122              throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
 123          }
 124  
 125          if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
 126              throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class);
 127          }
 128  
 129          $this->manager = $manager;
 130          $this->databaseName = $databaseName;
 131          $this->readConcern = $options['readConcern'] ?? $this->manager->getReadConcern();
 132          $this->readPreference = $options['readPreference'] ?? $this->manager->getReadPreference();
 133          $this->typeMap = $options['typeMap'] ?? self::$defaultTypeMap;
 134          $this->writeConcern = $options['writeConcern'] ?? $this->manager->getWriteConcern();
 135      }
 136  
 137      /**
 138       * Return internal properties for debugging purposes.
 139       *
 140       * @see https://php.net/manual/en/language.oop5.magic.php#language.oop5.magic.debuginfo
 141       * @return array
 142       */
 143      public function __debugInfo()
 144      {
 145          return [
 146              'databaseName' => $this->databaseName,
 147              'manager' => $this->manager,
 148              'readConcern' => $this->readConcern,
 149              'readPreference' => $this->readPreference,
 150              'typeMap' => $this->typeMap,
 151              'writeConcern' => $this->writeConcern,
 152          ];
 153      }
 154  
 155      /**
 156       * Select a collection within this database.
 157       *
 158       * Note: collections whose names contain special characters (e.g. ".") may
 159       * be selected with complex syntax (e.g. $database->{"system.profile"}) or
 160       * {@link selectCollection()}.
 161       *
 162       * @see https://php.net/oop5.overloading#object.get
 163       * @see https://php.net/types.string#language.types.string.parsing.complex
 164       * @param string $collectionName Name of the collection to select
 165       * @return Collection
 166       */
 167      public function __get(string $collectionName)
 168      {
 169          return $this->selectCollection($collectionName);
 170      }
 171  
 172      /**
 173       * Return the database name.
 174       *
 175       * @return string
 176       */
 177      public function __toString()
 178      {
 179          return $this->databaseName;
 180      }
 181  
 182      /**
 183       * Runs an aggregation framework pipeline on the database for pipeline
 184       * stages that do not require an underlying collection, such as $currentOp
 185       * and $listLocalSessions. Requires MongoDB >= 3.6
 186       *
 187       * @see Aggregate::__construct() for supported options
 188       * @param array $pipeline List of pipeline operations
 189       * @param array $options  Command options
 190       * @return Traversable
 191       * @throws UnexpectedValueException if the command response was malformed
 192       * @throws UnsupportedException if options are not supported by the selected server
 193       * @throws InvalidArgumentException for parameter/option parsing errors
 194       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 195       */
 196      public function aggregate(array $pipeline, array $options = [])
 197      {
 198          $hasWriteStage = is_last_pipeline_operator_write($pipeline);
 199  
 200          if (! isset($options['readPreference']) && ! is_in_transaction($options)) {
 201              $options['readPreference'] = $this->readPreference;
 202          }
 203  
 204          $server = $hasWriteStage
 205              ? select_server_for_aggregate_write_stage($this->manager, $options)
 206              : select_server($this->manager, $options);
 207  
 208          /* MongoDB 4.2 and later supports a read concern when an $out stage is
 209           * being used, but earlier versions do not.
 210           *
 211           * A read concern is also not compatible with transactions.
 212           */
 213          if (
 214              ! isset($options['readConcern']) &&
 215              ! is_in_transaction($options) &&
 216              ( ! $hasWriteStage || server_supports_feature($server, self::$wireVersionForReadConcernWithWriteStage))
 217          ) {
 218              $options['readConcern'] = $this->readConcern;
 219          }
 220  
 221          if (! isset($options['typeMap'])) {
 222              $options['typeMap'] = $this->typeMap;
 223          }
 224  
 225          if ($hasWriteStage && ! isset($options['writeConcern']) && ! is_in_transaction($options)) {
 226              $options['writeConcern'] = $this->writeConcern;
 227          }
 228  
 229          $operation = new Aggregate($this->databaseName, null, $pipeline, $options);
 230  
 231          return $operation->execute($server);
 232      }
 233  
 234      /**
 235       * Execute a command on this database.
 236       *
 237       * @see DatabaseCommand::__construct() for supported options
 238       * @param array|object $command Command document
 239       * @param array        $options Options for command execution
 240       * @return Cursor
 241       * @throws InvalidArgumentException for parameter/option parsing errors
 242       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 243       */
 244      public function command($command, array $options = [])
 245      {
 246          if (! isset($options['typeMap'])) {
 247              $options['typeMap'] = $this->typeMap;
 248          }
 249  
 250          $operation = new DatabaseCommand($this->databaseName, $command, $options);
 251          $server = select_server($this->manager, $options);
 252  
 253          return $operation->execute($server);
 254      }
 255  
 256      /**
 257       * Create a new collection explicitly.
 258       *
 259       * @see CreateCollection::__construct() for supported options
 260       * @return array|object Command result document
 261       * @throws UnsupportedException if options are not supported by the selected server
 262       * @throws InvalidArgumentException for parameter/option parsing errors
 263       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 264       */
 265      public function createCollection(string $collectionName, array $options = [])
 266      {
 267          if (! isset($options['typeMap'])) {
 268              $options['typeMap'] = $this->typeMap;
 269          }
 270  
 271          $server = select_server($this->manager, $options);
 272  
 273          if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
 274              $options['writeConcern'] = $this->writeConcern;
 275          }
 276  
 277          $encryptedFields = $options['encryptedFields']
 278              ?? get_encrypted_fields_from_driver($this->databaseName, $collectionName, $this->manager)
 279              ?? null;
 280  
 281          if ($encryptedFields !== null) {
 282              // encryptedFields is passed to the create command
 283              $options['encryptedFields'] = $encryptedFields;
 284  
 285              $encryptedFields = (array) $encryptedFields;
 286              $enxcolOptions = ['clusteredIndex' => ['key' => ['_id' => 1], 'unique' => true]];
 287              (new CreateCollection($this->databaseName, $encryptedFields['escCollection'] ?? 'enxcol_.' . $collectionName . '.esc', $enxcolOptions))->execute($server);
 288              (new CreateCollection($this->databaseName, $encryptedFields['eccCollection'] ?? 'enxcol_.' . $collectionName . '.ecc', $enxcolOptions))->execute($server);
 289              (new CreateCollection($this->databaseName, $encryptedFields['ecocCollection'] ?? 'enxcol_.' . $collectionName . '.ecoc', $enxcolOptions))->execute($server);
 290          }
 291  
 292          $operation = new CreateCollection($this->databaseName, $collectionName, $options);
 293  
 294          $result = $operation->execute($server);
 295  
 296          if ($encryptedFields !== null) {
 297              (new CreateIndexes($this->databaseName, $collectionName, [['key' => ['__safeContent__' => 1]]]))->execute($server);
 298          }
 299  
 300          return $result;
 301      }
 302  
 303      /**
 304       * Drop this database.
 305       *
 306       * @see DropDatabase::__construct() for supported options
 307       * @param array $options Additional options
 308       * @return array|object Command result document
 309       * @throws UnsupportedException if options are unsupported on the selected server
 310       * @throws InvalidArgumentException for parameter/option parsing errors
 311       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 312       */
 313      public function drop(array $options = [])
 314      {
 315          if (! isset($options['typeMap'])) {
 316              $options['typeMap'] = $this->typeMap;
 317          }
 318  
 319          $server = select_server($this->manager, $options);
 320  
 321          if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
 322              $options['writeConcern'] = $this->writeConcern;
 323          }
 324  
 325          $operation = new DropDatabase($this->databaseName, $options);
 326  
 327          return $operation->execute($server);
 328      }
 329  
 330      /**
 331       * Drop a collection within this database.
 332       *
 333       * @see DropCollection::__construct() for supported options
 334       * @param string $collectionName Collection name
 335       * @param array  $options        Additional options
 336       * @return array|object Command result document
 337       * @throws UnsupportedException if options are unsupported on the selected server
 338       * @throws InvalidArgumentException for parameter/option parsing errors
 339       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 340       */
 341      public function dropCollection(string $collectionName, array $options = [])
 342      {
 343          if (! isset($options['typeMap'])) {
 344              $options['typeMap'] = $this->typeMap;
 345          }
 346  
 347          $server = select_server($this->manager, $options);
 348  
 349          if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
 350              $options['writeConcern'] = $this->writeConcern;
 351          }
 352  
 353          $encryptedFields = $options['encryptedFields']
 354              ?? get_encrypted_fields_from_driver($this->databaseName, $collectionName, $this->manager)
 355              ?? get_encrypted_fields_from_server($this->databaseName, $collectionName, $this->manager, $server)
 356              ?? null;
 357  
 358          if ($encryptedFields !== null) {
 359              // encryptedFields is not passed to the drop command
 360              unset($options['encryptedFields']);
 361  
 362              $encryptedFields = (array) $encryptedFields;
 363              (new DropCollection($this->databaseName, $encryptedFields['escCollection'] ?? 'enxcol_.' . $collectionName . '.esc'))->execute($server);
 364              (new DropCollection($this->databaseName, $encryptedFields['eccCollection'] ?? 'enxcol_.' . $collectionName . '.ecc'))->execute($server);
 365              (new DropCollection($this->databaseName, $encryptedFields['ecocCollection'] ?? 'enxcol_.' . $collectionName . '.ecoc'))->execute($server);
 366          }
 367  
 368          $operation = new DropCollection($this->databaseName, $collectionName, $options);
 369  
 370          return $operation->execute($server);
 371      }
 372  
 373      /**
 374       * Returns the database name.
 375       *
 376       * @return string
 377       */
 378      public function getDatabaseName()
 379      {
 380          return $this->databaseName;
 381      }
 382  
 383      /**
 384       * Return the Manager.
 385       *
 386       * @return Manager
 387       */
 388      public function getManager()
 389      {
 390          return $this->manager;
 391      }
 392  
 393      /**
 394       * Return the read concern for this database.
 395       *
 396       * @see https://php.net/manual/en/mongodb-driver-readconcern.isdefault.php
 397       * @return ReadConcern
 398       */
 399      public function getReadConcern()
 400      {
 401          return $this->readConcern;
 402      }
 403  
 404      /**
 405       * Return the read preference for this database.
 406       *
 407       * @return ReadPreference
 408       */
 409      public function getReadPreference()
 410      {
 411          return $this->readPreference;
 412      }
 413  
 414      /**
 415       * Return the type map for this database.
 416       *
 417       * @return array
 418       */
 419      public function getTypeMap()
 420      {
 421          return $this->typeMap;
 422      }
 423  
 424      /**
 425       * Return the write concern for this database.
 426       *
 427       * @see https://php.net/manual/en/mongodb-driver-writeconcern.isdefault.php
 428       * @return WriteConcern
 429       */
 430      public function getWriteConcern()
 431      {
 432          return $this->writeConcern;
 433      }
 434  
 435      /**
 436       * Returns the names of all collections in this database
 437       *
 438       * @see ListCollectionNames::__construct() for supported options
 439       * @throws InvalidArgumentException for parameter/option parsing errors
 440       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 441       */
 442      public function listCollectionNames(array $options = []): Iterator
 443      {
 444          $operation = new ListCollectionNames($this->databaseName, $options);
 445          $server = select_server($this->manager, $options);
 446  
 447          return $operation->execute($server);
 448      }
 449  
 450      /**
 451       * Returns information for all collections in this database.
 452       *
 453       * @see ListCollections::__construct() for supported options
 454       * @return CollectionInfoIterator
 455       * @throws InvalidArgumentException for parameter/option parsing errors
 456       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 457       */
 458      public function listCollections(array $options = [])
 459      {
 460          $operation = new ListCollections($this->databaseName, $options);
 461          $server = select_server($this->manager, $options);
 462  
 463          return $operation->execute($server);
 464      }
 465  
 466      /**
 467       * Modifies a collection or view.
 468       *
 469       * @see ModifyCollection::__construct() for supported options
 470       * @param string $collectionName    Collection or view to modify
 471       * @param array  $collectionOptions Collection or view options to assign
 472       * @param array  $options           Command options
 473       * @return array|object
 474       * @throws InvalidArgumentException for parameter/option parsing errors
 475       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 476       */
 477      public function modifyCollection(string $collectionName, array $collectionOptions, array $options = [])
 478      {
 479          if (! isset($options['typeMap'])) {
 480              $options['typeMap'] = $this->typeMap;
 481          }
 482  
 483          $server = select_server($this->manager, $options);
 484  
 485          if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
 486              $options['writeConcern'] = $this->writeConcern;
 487          }
 488  
 489          $operation = new ModifyCollection($this->databaseName, $collectionName, $collectionOptions, $options);
 490  
 491          return $operation->execute($server);
 492      }
 493  
 494      /**
 495       * Rename a collection within this database.
 496       *
 497       * @see RenameCollection::__construct() for supported options
 498       * @param string      $fromCollectionName Collection name
 499       * @param string      $toCollectionName   New name of the collection
 500       * @param string|null $toDatabaseName     New database name of the collection. Defaults to the original database.
 501       * @param array       $options            Additional options
 502       * @return array|object Command result document
 503       * @throws UnsupportedException if options are unsupported on the selected server
 504       * @throws InvalidArgumentException for parameter/option parsing errors
 505       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 506       */
 507      public function renameCollection(string $fromCollectionName, string $toCollectionName, ?string $toDatabaseName = null, array $options = [])
 508      {
 509          if (! isset($toDatabaseName)) {
 510              $toDatabaseName = $this->databaseName;
 511          }
 512  
 513          if (! isset($options['typeMap'])) {
 514              $options['typeMap'] = $this->typeMap;
 515          }
 516  
 517          $server = select_server($this->manager, $options);
 518  
 519          if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
 520              $options['writeConcern'] = $this->writeConcern;
 521          }
 522  
 523          $operation = new RenameCollection($this->databaseName, $fromCollectionName, $toDatabaseName, $toCollectionName, $options);
 524  
 525          return $operation->execute($server);
 526      }
 527  
 528      /**
 529       * Select a collection within this database.
 530       *
 531       * @see Collection::__construct() for supported options
 532       * @param string $collectionName Name of the collection to select
 533       * @param array  $options        Collection constructor options
 534       * @return Collection
 535       * @throws InvalidArgumentException for parameter/option parsing errors
 536       */
 537      public function selectCollection(string $collectionName, array $options = [])
 538      {
 539          $options += [
 540              'readConcern' => $this->readConcern,
 541              'readPreference' => $this->readPreference,
 542              'typeMap' => $this->typeMap,
 543              'writeConcern' => $this->writeConcern,
 544          ];
 545  
 546          return new Collection($this->manager, $this->databaseName, $collectionName, $options);
 547      }
 548  
 549      /**
 550       * Select a GridFS bucket within this database.
 551       *
 552       * @see Bucket::__construct() for supported options
 553       * @param array $options Bucket constructor options
 554       * @return Bucket
 555       * @throws InvalidArgumentException for parameter/option parsing errors
 556       */
 557      public function selectGridFSBucket(array $options = [])
 558      {
 559          $options += [
 560              'readConcern' => $this->readConcern,
 561              'readPreference' => $this->readPreference,
 562              'typeMap' => $this->typeMap,
 563              'writeConcern' => $this->writeConcern,
 564          ];
 565  
 566          return new Bucket($this->manager, $this->databaseName, $options);
 567      }
 568  
 569      /**
 570       * Create a change stream for watching changes to the database.
 571       *
 572       * @see Watch::__construct() for supported options
 573       * @param array $pipeline List of pipeline operations
 574       * @param array $options  Command options
 575       * @return ChangeStream
 576       * @throws InvalidArgumentException for parameter/option parsing errors
 577       */
 578      public function watch(array $pipeline = [], array $options = [])
 579      {
 580          if (! isset($options['readPreference']) && ! is_in_transaction($options)) {
 581              $options['readPreference'] = $this->readPreference;
 582          }
 583  
 584          $server = select_server($this->manager, $options);
 585  
 586          if (! isset($options['readConcern']) && ! is_in_transaction($options)) {
 587              $options['readConcern'] = $this->readConcern;
 588          }
 589  
 590          if (! isset($options['typeMap'])) {
 591              $options['typeMap'] = $this->typeMap;
 592          }
 593  
 594          $operation = new Watch($this->manager, $this->databaseName, null, $pipeline, $options);
 595  
 596          return $operation->execute($server);
 597      }
 598  
 599      /**
 600       * Get a clone of this database with different options.
 601       *
 602       * @see Database::__construct() for supported options
 603       * @param array $options Database constructor options
 604       * @return Database
 605       * @throws InvalidArgumentException for parameter/option parsing errors
 606       */
 607      public function withOptions(array $options = [])
 608      {
 609          $options += [
 610              'readConcern' => $this->readConcern,
 611              'readPreference' => $this->readPreference,
 612              'typeMap' => $this->typeMap,
 613              'writeConcern' => $this->writeConcern,
 614          ];
 615  
 616          return new Database($this->manager, $this->databaseName, $options);
 617      }
 618  }