Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 39 and 311] [Versions 39 and 400] [Versions 39 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\Operation;
  19  
  20  use MongoDB\Driver\BulkWrite as Bulk;
  21  use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
  22  use MongoDB\Driver\Server;
  23  use MongoDB\Driver\Session;
  24  use MongoDB\Driver\WriteConcern;
  25  use MongoDB\Exception\InvalidArgumentException;
  26  use MongoDB\Exception\UnsupportedException;
  27  use MongoDB\UpdateResult;
  28  use function is_array;
  29  use function is_bool;
  30  use function is_object;
  31  use function MongoDB\is_first_key_operator;
  32  use function MongoDB\is_pipeline;
  33  use function MongoDB\server_supports_feature;
  34  
  35  /**
  36   * Operation for the update command.
  37   *
  38   * This class is used internally by the ReplaceOne, UpdateMany, and UpdateOne
  39   * operation classes.
  40   *
  41   * @internal
  42   * @see http://docs.mongodb.org/manual/reference/command/update/
  43   */
  44  class Update implements Executable, Explainable
  45  {
  46      /** @var integer */
  47      private static $wireVersionForArrayFilters = 6;
  48  
  49      /** @var integer */
  50      private static $wireVersionForCollation = 5;
  51  
  52      /** @var integer */
  53      private static $wireVersionForDocumentLevelValidation = 4;
  54  
  55      /** @var string */
  56      private $databaseName;
  57  
  58      /** @var string */
  59      private $collectionName;
  60  
  61      /** @var array|object */
  62      private $filter;
  63  
  64      /** @var array|object */
  65      private $update;
  66  
  67      /** @var array */
  68      private $options;
  69  
  70      /**
  71       * Constructs a update command.
  72       *
  73       * Supported options:
  74       *
  75       *  * arrayFilters (document array): A set of filters specifying to which
  76       *    array elements an update should apply.
  77       *
  78       *    This is not supported for server versions < 3.6 and will result in an
  79       *    exception at execution time if used.
  80       *
  81       *  * bypassDocumentValidation (boolean): If true, allows the write to
  82       *    circumvent document level validation.
  83       *
  84       *    For servers < 3.2, this option is ignored as document level validation
  85       *    is not available.
  86       *
  87       *  * collation (document): Collation specification.
  88       *
  89       *    This is not supported for server versions < 3.4 and will result in an
  90       *    exception at execution time if used.
  91       *
  92       *  * multi (boolean): When true, updates all documents matching the query.
  93       *    This option cannot be true if the $update argument is a replacement
  94       *    document (i.e. contains no update operators). The default is false.
  95       *
  96       *  * session (MongoDB\Driver\Session): Client session.
  97       *
  98       *    Sessions are not supported for server versions < 3.6.
  99       *
 100       *  * upsert (boolean): When true, a new document is created if no document
 101       *    matches the query. The default is false.
 102       *
 103       *  * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
 104       *
 105       * @param string       $databaseName   Database name
 106       * @param string       $collectionName Collection name
 107       * @param array|object $filter         Query by which to delete documents
 108       * @param array|object $update         Update to apply to the matched
 109       *                                     document(s) or a replacement document
 110       * @param array        $options        Command options
 111       * @throws InvalidArgumentException for parameter/option parsing errors
 112       */
 113      public function __construct($databaseName, $collectionName, $filter, $update, array $options = [])
 114      {
 115          if (! is_array($filter) && ! is_object($filter)) {
 116              throw InvalidArgumentException::invalidType('$filter', $filter, 'array or object');
 117          }
 118  
 119          if (! is_array($update) && ! is_object($update)) {
 120              throw InvalidArgumentException::invalidType('$update', $filter, 'array or object');
 121          }
 122  
 123          $options += [
 124              'multi' => false,
 125              'upsert' => false,
 126          ];
 127  
 128          if (isset($options['arrayFilters']) && ! is_array($options['arrayFilters'])) {
 129              throw InvalidArgumentException::invalidType('"arrayFilters" option', $options['arrayFilters'], 'array');
 130          }
 131  
 132          if (isset($options['bypassDocumentValidation']) && ! is_bool($options['bypassDocumentValidation'])) {
 133              throw InvalidArgumentException::invalidType('"bypassDocumentValidation" option', $options['bypassDocumentValidation'], 'boolean');
 134          }
 135  
 136          if (isset($options['collation']) && ! is_array($options['collation']) && ! is_object($options['collation'])) {
 137              throw InvalidArgumentException::invalidType('"collation" option', $options['collation'], 'array or object');
 138          }
 139  
 140          if (! is_bool($options['multi'])) {
 141              throw InvalidArgumentException::invalidType('"multi" option', $options['multi'], 'boolean');
 142          }
 143  
 144          if ($options['multi'] && ! is_first_key_operator($update) && ! is_pipeline($update)) {
 145              throw new InvalidArgumentException('"multi" option cannot be true if $update is a replacement document');
 146          }
 147  
 148          if (isset($options['session']) && ! $options['session'] instanceof Session) {
 149              throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class);
 150          }
 151  
 152          if (! is_bool($options['upsert'])) {
 153              throw InvalidArgumentException::invalidType('"upsert" option', $options['upsert'], 'boolean');
 154          }
 155  
 156          if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
 157              throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class);
 158          }
 159  
 160          if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) {
 161              unset($options['writeConcern']);
 162          }
 163  
 164          $this->databaseName = (string) $databaseName;
 165          $this->collectionName = (string) $collectionName;
 166          $this->filter = $filter;
 167          $this->update = $update;
 168          $this->options = $options;
 169      }
 170  
 171      /**
 172       * Execute the operation.
 173       *
 174       * @see Executable::execute()
 175       * @param Server $server
 176       * @return UpdateResult
 177       * @throws UnsupportedException if array filters or collation is used and unsupported
 178       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 179       */
 180      public function execute(Server $server)
 181      {
 182          if (isset($this->options['arrayFilters']) && ! server_supports_feature($server, self::$wireVersionForArrayFilters)) {
 183              throw UnsupportedException::arrayFiltersNotSupported();
 184          }
 185  
 186          if (isset($this->options['collation']) && ! server_supports_feature($server, self::$wireVersionForCollation)) {
 187              throw UnsupportedException::collationNotSupported();
 188          }
 189  
 190          $inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction();
 191          if ($inTransaction && isset($this->options['writeConcern'])) {
 192              throw UnsupportedException::writeConcernNotSupportedInTransaction();
 193          }
 194  
 195          $bulkOptions = [];
 196  
 197          if (! empty($this->options['bypassDocumentValidation']) &&
 198              server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)
 199          ) {
 200              $bulkOptions['bypassDocumentValidation'] = $this->options['bypassDocumentValidation'];
 201          }
 202  
 203          $bulk = new Bulk($bulkOptions);
 204          $bulk->update($this->filter, $this->update, $this->createUpdateOptions());
 205  
 206          $writeResult = $server->executeBulkWrite($this->databaseName . '.' . $this->collectionName, $bulk, $this->createExecuteOptions());
 207  
 208          return new UpdateResult($writeResult);
 209      }
 210  
 211      public function getCommandDocument(Server $server)
 212      {
 213          $cmd = ['update' => $this->collectionName, 'updates' => [['q' => $this->filter, 'u' => $this->update] + $this->createUpdateOptions()]];
 214  
 215          if (isset($this->options['writeConcern'])) {
 216              $cmd['writeConcern'] = $this->options['writeConcern'];
 217          }
 218  
 219          if (! empty($this->options['bypassDocumentValidation']) &&
 220              server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)
 221          ) {
 222              $cmd['bypassDocumentValidation'] = $this->options['bypassDocumentValidation'];
 223          }
 224  
 225          return $cmd;
 226      }
 227  
 228      /**
 229       * Create options for executing the bulk write.
 230       *
 231       * @see http://php.net/manual/en/mongodb-driver-server.executebulkwrite.php
 232       * @return array
 233       */
 234      private function createExecuteOptions()
 235      {
 236          $options = [];
 237  
 238          if (isset($this->options['session'])) {
 239              $options['session'] = $this->options['session'];
 240          }
 241  
 242          if (isset($this->options['writeConcern'])) {
 243              $options['writeConcern'] = $this->options['writeConcern'];
 244          }
 245  
 246          return $options;
 247      }
 248  
 249      /**
 250       * Create options for the update command.
 251       *
 252       * Note that these options are different from the bulk write options, which
 253       * are created in createExecuteOptions().
 254       *
 255       * @return array
 256       */
 257      private function createUpdateOptions()
 258      {
 259          $updateOptions = [
 260              'multi' => $this->options['multi'],
 261              'upsert' => $this->options['upsert'],
 262          ];
 263  
 264          if (isset($this->options['arrayFilters'])) {
 265              $updateOptions['arrayFilters'] = $this->options['arrayFilters'];
 266          }
 267  
 268          if (isset($this->options['collation'])) {
 269              $updateOptions['collation'] = (object) $this->options['collation'];
 270          }
 271  
 272          return $updateOptions;
 273      }
 274  }