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 Jean85\PrettyVersions;
  22  use MongoDB\Driver\ClientEncryption;
  23  use MongoDB\Driver\Exception\InvalidArgumentException as DriverInvalidArgumentException;
  24  use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
  25  use MongoDB\Driver\Manager;
  26  use MongoDB\Driver\ReadConcern;
  27  use MongoDB\Driver\ReadPreference;
  28  use MongoDB\Driver\Session;
  29  use MongoDB\Driver\WriteConcern;
  30  use MongoDB\Exception\InvalidArgumentException;
  31  use MongoDB\Exception\UnexpectedValueException;
  32  use MongoDB\Exception\UnsupportedException;
  33  use MongoDB\Model\BSONArray;
  34  use MongoDB\Model\BSONDocument;
  35  use MongoDB\Model\DatabaseInfoIterator;
  36  use MongoDB\Operation\DropDatabase;
  37  use MongoDB\Operation\ListDatabaseNames;
  38  use MongoDB\Operation\ListDatabases;
  39  use MongoDB\Operation\Watch;
  40  use Throwable;
  41  
  42  use function is_array;
  43  use function is_string;
  44  
  45  class Client
  46  {
  47      public const DEFAULT_URI = 'mongodb://127.0.0.1/';
  48  
  49      /** @var array */
  50      private static $defaultTypeMap = [
  51          'array' => BSONArray::class,
  52          'document' => BSONDocument::class,
  53          'root' => BSONDocument::class,
  54      ];
  55  
  56      /** @var string */
  57      private static $handshakeSeparator = ' / ';
  58  
  59      /** @var string|null */
  60      private static $version;
  61  
  62      /** @var Manager */
  63      private $manager;
  64  
  65      /** @var ReadConcern */
  66      private $readConcern;
  67  
  68      /** @var ReadPreference */
  69      private $readPreference;
  70  
  71      /** @var string */
  72      private $uri;
  73  
  74      /** @var array */
  75      private $typeMap;
  76  
  77      /** @var WriteConcern */
  78      private $writeConcern;
  79  
  80      /**
  81       * Constructs a new Client instance.
  82       *
  83       * This is the preferred class for connecting to a MongoDB server or
  84       * cluster of servers. It serves as a gateway for accessing individual
  85       * databases and collections.
  86       *
  87       * Supported driver-specific options:
  88       *
  89       *  * typeMap (array): Default type map for cursors and BSON documents.
  90       *
  91       * Other options are documented in MongoDB\Driver\Manager::__construct().
  92       *
  93       * @see https://mongodb.com/docs/manual/reference/connection-string/
  94       * @see https://php.net/manual/en/mongodb-driver-manager.construct.php
  95       * @see https://php.net/manual/en/mongodb.persistence.php#mongodb.persistence.typemaps
  96       * @param string|null $uri           MongoDB connection string. If none is provided, this defaults to self::DEFAULT_URI.
  97       * @param array       $uriOptions    Additional connection string options
  98       * @param array       $driverOptions Driver-specific options
  99       * @throws InvalidArgumentException for parameter/option parsing errors
 100       * @throws DriverInvalidArgumentException for parameter/option parsing errors in the driver
 101       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 102       */
 103      public function __construct(?string $uri = null, array $uriOptions = [], array $driverOptions = [])
 104      {
 105          $driverOptions += ['typeMap' => self::$defaultTypeMap];
 106  
 107          if (! is_array($driverOptions['typeMap'])) {
 108              throw InvalidArgumentException::invalidType('"typeMap" driver option', $driverOptions['typeMap'], 'array');
 109          }
 110  
 111          if (isset($driverOptions['autoEncryption']['keyVaultClient'])) {
 112              if ($driverOptions['autoEncryption']['keyVaultClient'] instanceof self) {
 113                  $driverOptions['autoEncryption']['keyVaultClient'] = $driverOptions['autoEncryption']['keyVaultClient']->manager;
 114              } elseif (! $driverOptions['autoEncryption']['keyVaultClient'] instanceof Manager) {
 115                  throw InvalidArgumentException::invalidType('"keyVaultClient" autoEncryption option', $driverOptions['autoEncryption']['keyVaultClient'], [self::class, Manager::class]);
 116              }
 117          }
 118  
 119          $driverOptions['driver'] = $this->mergeDriverInfo($driverOptions['driver'] ?? []);
 120  
 121          $this->uri = $uri ?? self::DEFAULT_URI;
 122          $this->typeMap = $driverOptions['typeMap'];
 123  
 124          unset($driverOptions['typeMap']);
 125  
 126          $this->manager = new Manager($uri, $uriOptions, $driverOptions);
 127          $this->readConcern = $this->manager->getReadConcern();
 128          $this->readPreference = $this->manager->getReadPreference();
 129          $this->writeConcern = $this->manager->getWriteConcern();
 130      }
 131  
 132      /**
 133       * Return internal properties for debugging purposes.
 134       *
 135       * @see https://php.net/manual/en/language.oop5.magic.php#language.oop5.magic.debuginfo
 136       * @return array
 137       */
 138      public function __debugInfo()
 139      {
 140          return [
 141              'manager' => $this->manager,
 142              'uri' => $this->uri,
 143              'typeMap' => $this->typeMap,
 144              'writeConcern' => $this->writeConcern,
 145          ];
 146      }
 147  
 148      /**
 149       * Select a database.
 150       *
 151       * Note: databases whose names contain special characters (e.g. "-") may
 152       * be selected with complex syntax (e.g. $client->{"that-database"}) or
 153       * {@link selectDatabase()}.
 154       *
 155       * @see https://php.net/oop5.overloading#object.get
 156       * @see https://php.net/types.string#language.types.string.parsing.complex
 157       * @param string $databaseName Name of the database to select
 158       * @return Database
 159       */
 160      public function __get(string $databaseName)
 161      {
 162          return $this->selectDatabase($databaseName);
 163      }
 164  
 165      /**
 166       * Return the connection string (i.e. URI).
 167       *
 168       * @return string
 169       */
 170      public function __toString()
 171      {
 172          return $this->uri;
 173      }
 174  
 175      /**
 176       * Returns a ClientEncryption instance for explicit encryption and decryption
 177       *
 178       * @param array $options Encryption options
 179       *
 180       * @return ClientEncryption
 181       */
 182      public function createClientEncryption(array $options)
 183      {
 184          if (isset($options['keyVaultClient'])) {
 185              if ($options['keyVaultClient'] instanceof self) {
 186                  $options['keyVaultClient'] = $options['keyVaultClient']->manager;
 187              } elseif (! $options['keyVaultClient'] instanceof Manager) {
 188                  throw InvalidArgumentException::invalidType('"keyVaultClient" option', $options['keyVaultClient'], [self::class, Manager::class]);
 189              }
 190          }
 191  
 192          return $this->manager->createClientEncryption($options);
 193      }
 194  
 195      /**
 196       * Drop a database.
 197       *
 198       * @see DropDatabase::__construct() for supported options
 199       * @param string $databaseName Database name
 200       * @param array  $options      Additional options
 201       * @return array|object Command result document
 202       * @throws UnsupportedException if options are unsupported on the selected server
 203       * @throws InvalidArgumentException for parameter/option parsing errors
 204       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 205       */
 206      public function dropDatabase(string $databaseName, array $options = [])
 207      {
 208          if (! isset($options['typeMap'])) {
 209              $options['typeMap'] = $this->typeMap;
 210          }
 211  
 212          $server = select_server($this->manager, $options);
 213  
 214          if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
 215              $options['writeConcern'] = $this->writeConcern;
 216          }
 217  
 218          $operation = new DropDatabase($databaseName, $options);
 219  
 220          return $operation->execute($server);
 221      }
 222  
 223      /**
 224       * Return the Manager.
 225       *
 226       * @return Manager
 227       */
 228      public function getManager()
 229      {
 230          return $this->manager;
 231      }
 232  
 233      /**
 234       * Return the read concern for this client.
 235       *
 236       * @see https://php.net/manual/en/mongodb-driver-readconcern.isdefault.php
 237       * @return ReadConcern
 238       */
 239      public function getReadConcern()
 240      {
 241          return $this->readConcern;
 242      }
 243  
 244      /**
 245       * Return the read preference for this client.
 246       *
 247       * @return ReadPreference
 248       */
 249      public function getReadPreference()
 250      {
 251          return $this->readPreference;
 252      }
 253  
 254      /**
 255       * Return the type map for this client.
 256       *
 257       * @return array
 258       */
 259      public function getTypeMap()
 260      {
 261          return $this->typeMap;
 262      }
 263  
 264      /**
 265       * Return the write concern for this client.
 266       *
 267       * @see https://php.net/manual/en/mongodb-driver-writeconcern.isdefault.php
 268       * @return WriteConcern
 269       */
 270      public function getWriteConcern()
 271      {
 272          return $this->writeConcern;
 273      }
 274  
 275      /**
 276       * List database names.
 277       *
 278       * @see ListDatabaseNames::__construct() for supported options
 279       * @throws UnexpectedValueException if the command response was malformed
 280       * @throws InvalidArgumentException for parameter/option parsing errors
 281       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 282       */
 283      public function listDatabaseNames(array $options = []): Iterator
 284      {
 285          $operation = new ListDatabaseNames($options);
 286          $server = select_server($this->manager, $options);
 287  
 288          return $operation->execute($server);
 289      }
 290  
 291      /**
 292       * List databases.
 293       *
 294       * @see ListDatabases::__construct() for supported options
 295       * @return DatabaseInfoIterator
 296       * @throws UnexpectedValueException if the command response was malformed
 297       * @throws InvalidArgumentException for parameter/option parsing errors
 298       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 299       */
 300      public function listDatabases(array $options = [])
 301      {
 302          $operation = new ListDatabases($options);
 303          $server = select_server($this->manager, $options);
 304  
 305          return $operation->execute($server);
 306      }
 307  
 308      /**
 309       * Select a collection.
 310       *
 311       * @see Collection::__construct() for supported options
 312       * @param string $databaseName   Name of the database containing the collection
 313       * @param string $collectionName Name of the collection to select
 314       * @param array  $options        Collection constructor options
 315       * @return Collection
 316       * @throws InvalidArgumentException for parameter/option parsing errors
 317       */
 318      public function selectCollection(string $databaseName, string $collectionName, array $options = [])
 319      {
 320          $options += ['typeMap' => $this->typeMap];
 321  
 322          return new Collection($this->manager, $databaseName, $collectionName, $options);
 323      }
 324  
 325      /**
 326       * Select a database.
 327       *
 328       * @see Database::__construct() for supported options
 329       * @param string $databaseName Name of the database to select
 330       * @param array  $options      Database constructor options
 331       * @return Database
 332       * @throws InvalidArgumentException for parameter/option parsing errors
 333       */
 334      public function selectDatabase(string $databaseName, array $options = [])
 335      {
 336          $options += ['typeMap' => $this->typeMap];
 337  
 338          return new Database($this->manager, $databaseName, $options);
 339      }
 340  
 341      /**
 342       * Start a new client session.
 343       *
 344       * @see https://php.net/manual/en/mongodb-driver-manager.startsession.php
 345       * @param array $options Session options
 346       * @return Session
 347       */
 348      public function startSession(array $options = [])
 349      {
 350          return $this->manager->startSession($options);
 351      }
 352  
 353      /**
 354       * Create a change stream for watching changes to the cluster.
 355       *
 356       * @see Watch::__construct() for supported options
 357       * @param array $pipeline List of pipeline operations
 358       * @param array $options  Command options
 359       * @return ChangeStream
 360       * @throws InvalidArgumentException for parameter/option parsing errors
 361       */
 362      public function watch(array $pipeline = [], array $options = [])
 363      {
 364          if (! isset($options['readPreference']) && ! is_in_transaction($options)) {
 365              $options['readPreference'] = $this->readPreference;
 366          }
 367  
 368          $server = select_server($this->manager, $options);
 369  
 370          if (! isset($options['readConcern']) && ! is_in_transaction($options)) {
 371              $options['readConcern'] = $this->readConcern;
 372          }
 373  
 374          if (! isset($options['typeMap'])) {
 375              $options['typeMap'] = $this->typeMap;
 376          }
 377  
 378          $operation = new Watch($this->manager, null, null, $pipeline, $options);
 379  
 380          return $operation->execute($server);
 381      }
 382  
 383      private static function getVersion(): string
 384      {
 385          if (self::$version === null) {
 386              try {
 387                  self::$version = PrettyVersions::getVersion('mongodb/mongodb')->getPrettyVersion();
 388              } catch (Throwable $t) {
 389                  return 'unknown';
 390              }
 391          }
 392  
 393          return self::$version;
 394      }
 395  
 396      private function mergeDriverInfo(array $driver): array
 397      {
 398          $mergedDriver = [
 399              'name' => 'PHPLIB',
 400              'version' => self::getVersion(),
 401          ];
 402  
 403          if (isset($driver['name'])) {
 404              if (! is_string($driver['name'])) {
 405                  throw InvalidArgumentException::invalidType('"name" handshake option', $driver['name'], 'string');
 406              }
 407  
 408              $mergedDriver['name'] .= self::$handshakeSeparator . $driver['name'];
 409          }
 410  
 411          if (isset($driver['version'])) {
 412              if (! is_string($driver['version'])) {
 413                  throw InvalidArgumentException::invalidType('"version" handshake option', $driver['version'], 'string');
 414              }
 415  
 416              $mergedDriver['version'] .= self::$handshakeSeparator . $driver['version'];
 417          }
 418  
 419          if (isset($driver['platform'])) {
 420              $mergedDriver['platform'] = $driver['platform'];
 421          }
 422  
 423          return $mergedDriver;
 424      }
 425  }