Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 311 and 401]

   1  <?php
   2  /*
   3   * Copyright 2015-2017 MongoDB, Inc.
   4   *
   5   * Licensed under the Apache License, Version 2.0 (the "License");
   6   * you may not use this file except in compliance with the License.
   7   * You may obtain a copy of the License at
   8   *
   9   *   http://www.apache.org/licenses/LICENSE-2.0
  10   *
  11   * Unless required by applicable law or agreed to in writing, software
  12   * distributed under the License is distributed on an "AS IS" BASIS,
  13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14   * See the License for the specific language governing permissions and
  15   * limitations under the License.
  16   */
  17  
  18  namespace MongoDB;
  19  
  20  use Exception;
  21  use MongoDB\BSON\Serializable;
  22  use MongoDB\Driver\Manager;
  23  use MongoDB\Driver\ReadPreference;
  24  use MongoDB\Driver\Server;
  25  use MongoDB\Driver\Session;
  26  use MongoDB\Exception\InvalidArgumentException;
  27  use MongoDB\Exception\RuntimeException;
  28  use MongoDB\Operation\WithTransaction;
  29  use ReflectionClass;
  30  use ReflectionException;
  31  use function end;
  32  use function get_object_vars;
  33  use function in_array;
  34  use function is_array;
  35  use function is_object;
  36  use function is_string;
  37  use function key;
  38  use function MongoDB\BSON\fromPHP;
  39  use function MongoDB\BSON\toPHP;
  40  use function reset;
  41  use function substr;
  42  
  43  /**
  44   * Applies a type map to a document.
  45   *
  46   * This function is used by operations where it is not possible to apply a type
  47   * map to the cursor directly because the root document is a command response
  48   * (e.g. findAndModify).
  49   *
  50   * @internal
  51   * @param array|object $document Document to which the type map will be applied
  52   * @param array        $typeMap  Type map for BSON deserialization.
  53   * @return array|object
  54   * @throws InvalidArgumentException
  55   */
  56  function apply_type_map_to_document($document, array $typeMap)
  57  {
  58      if (! is_array($document) && ! is_object($document)) {
  59          throw InvalidArgumentException::invalidType('$document', $document, 'array or object');
  60      }
  61  
  62      return toPHP(fromPHP($document), $typeMap);
  63  }
  64  
  65  /**
  66   * Generate an index name from a key specification.
  67   *
  68   * @internal
  69   * @param array|object $document Document containing fields mapped to values,
  70   *                               which denote order or an index type
  71   * @return string
  72   * @throws InvalidArgumentException
  73   */
  74  function generate_index_name($document)
  75  {
  76      if ($document instanceof Serializable) {
  77          $document = $document->bsonSerialize();
  78      }
  79  
  80      if (is_object($document)) {
  81          $document = get_object_vars($document);
  82      }
  83  
  84      if (! is_array($document)) {
  85          throw InvalidArgumentException::invalidType('$document', $document, 'array or object');
  86      }
  87  
  88      $name = '';
  89  
  90      foreach ($document as $field => $type) {
  91          $name .= ($name != '' ? '_' : '') . $field . '_' . $type;
  92      }
  93  
  94      return $name;
  95  }
  96  
  97  /**
  98   * Return whether the first key in the document starts with a "$" character.
  99   *
 100   * This is used for differentiating update and replacement documents.
 101   *
 102   * @internal
 103   * @param array|object $document Update or replacement document
 104   * @return boolean
 105   * @throws InvalidArgumentException
 106   */
 107  function is_first_key_operator($document)
 108  {
 109      if ($document instanceof Serializable) {
 110          $document = $document->bsonSerialize();
 111      }
 112  
 113      if (is_object($document)) {
 114          $document = get_object_vars($document);
 115      }
 116  
 117      if (! is_array($document)) {
 118          throw InvalidArgumentException::invalidType('$document', $document, 'array or object');
 119      }
 120  
 121      reset($document);
 122      $firstKey = (string) key($document);
 123  
 124      return isset($firstKey[0]) && $firstKey[0] === '$';
 125  }
 126  
 127  /**
 128   * Returns whether an update specification is a valid aggregation pipeline.
 129   *
 130   * @internal
 131   * @param mixed $pipeline
 132   * @return boolean
 133   */
 134  function is_pipeline($pipeline)
 135  {
 136      if (! is_array($pipeline)) {
 137          return false;
 138      }
 139  
 140      if ($pipeline === []) {
 141          return false;
 142      }
 143  
 144      $expectedKey = 0;
 145  
 146      foreach ($pipeline as $key => $stage) {
 147          if (! is_array($stage) && ! is_object($stage)) {
 148              return false;
 149          }
 150  
 151          if ($expectedKey !== $key) {
 152              return false;
 153          }
 154  
 155          $expectedKey++;
 156          $stage = (array) $stage;
 157          reset($stage);
 158          $key = key($stage);
 159  
 160          if (! isset($key[0]) || $key[0] !== '$') {
 161              return false;
 162          }
 163      }
 164  
 165      return true;
 166  }
 167  
 168  /**
 169   * Returns whether we are currently in a transaction.
 170   *
 171   * @internal
 172   * @param array $options Command options
 173   * @return boolean
 174   */
 175  function is_in_transaction(array $options)
 176  {
 177      if (isset($options['session']) && $options['session'] instanceof Session && $options['session']->isInTransaction()) {
 178          return true;
 179      }
 180  
 181      return false;
 182  }
 183  
 184  /**
 185   * Return whether the aggregation pipeline ends with an $out or $merge operator.
 186   *
 187   * This is used for determining whether the aggregation pipeline must be
 188   * executed against a primary server.
 189   *
 190   * @internal
 191   * @param array $pipeline List of pipeline operations
 192   * @return boolean
 193   */
 194  function is_last_pipeline_operator_write(array $pipeline)
 195  {
 196      $lastOp = end($pipeline);
 197  
 198      if ($lastOp === false) {
 199          return false;
 200      }
 201  
 202      $lastOp = (array) $lastOp;
 203  
 204      return in_array(key($lastOp), ['$out', '$merge'], true);
 205  }
 206  
 207  /**
 208   * Return whether the "out" option for a mapReduce operation is "inline".
 209   *
 210   * This is used to determine if a mapReduce command requires a primary.
 211   *
 212   * @internal
 213   * @see https://docs.mongodb.com/manual/reference/command/mapReduce/#output-inline
 214   * @param string|array|object $out Output specification
 215   * @return boolean
 216   * @throws InvalidArgumentException
 217   */
 218  function is_mapreduce_output_inline($out)
 219  {
 220      if (! is_array($out) && ! is_object($out)) {
 221          return false;
 222      }
 223  
 224      if ($out instanceof Serializable) {
 225          $out = $out->bsonSerialize();
 226      }
 227  
 228      if (is_object($out)) {
 229          $out = get_object_vars($out);
 230      }
 231  
 232      if (! is_array($out)) {
 233          throw InvalidArgumentException::invalidType('$out', $out, 'array or object');
 234      }
 235  
 236      reset($out);
 237  
 238      return key($out) === 'inline';
 239  }
 240  
 241  /**
 242   * Return whether the server supports a particular feature.
 243   *
 244   * @internal
 245   * @param Server  $server  Server to check
 246   * @param integer $feature Feature constant (i.e. wire protocol version)
 247   * @return boolean
 248   */
 249  function server_supports_feature(Server $server, $feature)
 250  {
 251      $info = $server->getInfo();
 252      $maxWireVersion = isset($info['maxWireVersion']) ? (integer) $info['maxWireVersion'] : 0;
 253      $minWireVersion = isset($info['minWireVersion']) ? (integer) $info['minWireVersion'] : 0;
 254  
 255      return $minWireVersion <= $feature && $maxWireVersion >= $feature;
 256  }
 257  
 258  function is_string_array($input)
 259  {
 260      if (! is_array($input)) {
 261          return false;
 262      }
 263      foreach ($input as $item) {
 264          if (! is_string($item)) {
 265              return false;
 266          }
 267      }
 268  
 269      return true;
 270  }
 271  
 272  /**
 273   * Performs a deep copy of a value.
 274   *
 275   * This function will clone objects and recursively copy values within arrays.
 276   *
 277   * @internal
 278   * @see https://bugs.php.net/bug.php?id=49664
 279   * @param mixed $element Value to be copied
 280   * @return mixed
 281   * @throws ReflectionException
 282   */
 283  function recursive_copy($element)
 284  {
 285      if (is_array($element)) {
 286          foreach ($element as $key => $value) {
 287              $element[$key] = recursive_copy($value);
 288          }
 289  
 290          return $element;
 291      }
 292  
 293      if (! is_object($element)) {
 294          return $element;
 295      }
 296  
 297      if (! (new ReflectionClass($element))->isCloneable()) {
 298          return $element;
 299      }
 300  
 301      return clone $element;
 302  }
 303  
 304  /**
 305   * Creates a type map to apply to a field type
 306   *
 307   * This is used in the Aggregate, Distinct, and FindAndModify operations to
 308   * apply the root-level type map to the document that will be returned. It also
 309   * replaces the root type with object for consistency within these operations
 310   *
 311   * An existing type map for the given field path will not be overwritten
 312   *
 313   * @internal
 314   * @param array  $typeMap   The existing typeMap
 315   * @param string $fieldPath The field path to apply the root type to
 316   * @return array
 317   */
 318  function create_field_path_type_map(array $typeMap, $fieldPath)
 319  {
 320      // If some field paths already exist, we prefix them with the field path we are assuming as the new root
 321      if (isset($typeMap['fieldPaths']) && is_array($typeMap['fieldPaths'])) {
 322          $fieldPaths = $typeMap['fieldPaths'];
 323  
 324          $typeMap['fieldPaths'] = [];
 325          foreach ($fieldPaths as $existingFieldPath => $type) {
 326              $typeMap['fieldPaths'][$fieldPath . '.' . $existingFieldPath] = $type;
 327          }
 328      }
 329  
 330      // If a root typemap was set, apply this to the field object
 331      if (isset($typeMap['root'])) {
 332          $typeMap['fieldPaths'][$fieldPath] = $typeMap['root'];
 333      }
 334  
 335      /* Special case if we want to convert an array, in which case we need to
 336       * ensure that the field containing the array is exposed as an array,
 337       * instead of the type given in the type map's array key. */
 338      if (substr($fieldPath, -2, 2) === '.$') {
 339          $typeMap['fieldPaths'][substr($fieldPath, 0, -2)] = 'array';
 340      }
 341  
 342      $typeMap['root'] = 'object';
 343  
 344      return $typeMap;
 345  }
 346  
 347  /**
 348   * Execute a callback within a transaction in the given session
 349   *
 350   * This helper takes care of retrying the commit operation or the entire
 351   * transaction if an error occurs.
 352   *
 353   * If the commit fails because of an UnknownTransactionCommitResult error, the
 354   * commit is retried without re-invoking the callback.
 355   * If the commit fails because of a TransientTransactionError, the entire
 356   * transaction will be retried. In this case, the callback will be invoked
 357   * again. It is important that the logic inside the callback is idempotent.
 358   *
 359   * In case of failures, the commit or transaction are retried until 120 seconds
 360   * from the initial call have elapsed. After that, no retries will happen and
 361   * the helper will throw the last exception received from the driver.
 362   *
 363   * @see Client::startSession
 364   * @see Session::startTransaction for supported transaction options
 365   *
 366   * @param Session  $session            A session object as retrieved by Client::startSession
 367   * @param callable $callback           A callback that will be invoked within the transaction
 368   * @param array    $transactionOptions Additional options that are passed to Session::startTransaction
 369   * @return void
 370   * @throws RuntimeException for driver errors while committing the transaction
 371   * @throws Exception for any other errors, including those thrown in the callback
 372   */
 373  function with_transaction(Session $session, callable $callback, array $transactionOptions = [])
 374  {
 375      $operation = new WithTransaction($callback, $transactionOptions);
 376      $operation->execute($session);
 377  }
 378  
 379  /**
 380   * Returns the session option if it is set and valid.
 381   *
 382   * @internal
 383   * @param array $options
 384   * @return Session|null
 385   */
 386  function extract_session_from_options(array $options)
 387  {
 388      if (! isset($options['session']) || ! $options['session'] instanceof Session) {
 389          return null;
 390      }
 391  
 392      return $options['session'];
 393  }
 394  
 395  /**
 396   * Returns the readPreference option if it is set and valid.
 397   *
 398   * @internal
 399   * @param array $options
 400   * @return ReadPreference|null
 401   */
 402  function extract_read_preference_from_options(array $options)
 403  {
 404      if (! isset($options['readPreference']) || ! $options['readPreference'] instanceof ReadPreference) {
 405          return null;
 406      }
 407  
 408      return $options['readPreference'];
 409  }
 410  
 411  /**
 412   * Performs server selection, respecting the readPreference and session options
 413   * (if given)
 414   *
 415   * @internal
 416   * @return Server
 417   */
 418  function select_server(Manager $manager, array $options)
 419  {
 420      $session = extract_session_from_options($options);
 421      if ($session instanceof Session && $session->getServer() !== null) {
 422          return $session->getServer();
 423      }
 424  
 425      $readPreference = extract_read_preference_from_options($options);
 426      if (! $readPreference instanceof ReadPreference) {
 427          // TODO: PHPLIB-476: Read transaction read preference once PHPC-1439 is implemented
 428          $readPreference = new ReadPreference(ReadPreference::RP_PRIMARY);
 429      }
 430  
 431      return $manager->selectServer($readPreference);
 432  }