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\Operation;
  19  
  20  use MongoDB\Driver\Command;
  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  
  27  use function current;
  28  use function is_array;
  29  use function is_bool;
  30  use function is_integer;
  31  use function is_object;
  32  use function is_string;
  33  use function sprintf;
  34  use function trigger_error;
  35  
  36  use const E_USER_DEPRECATED;
  37  
  38  /**
  39   * Operation for the create command.
  40   *
  41   * @api
  42   * @see \MongoDB\Database::createCollection()
  43   * @see https://mongodb.com/docs/manual/reference/command/create/
  44   */
  45  class CreateCollection implements Executable
  46  {
  47      public const USE_POWER_OF_2_SIZES = 1;
  48      public const NO_PADDING = 2;
  49  
  50      /** @var string */
  51      private $databaseName;
  52  
  53      /** @var string */
  54      private $collectionName;
  55  
  56      /** @var array */
  57      private $options = [];
  58  
  59      /**
  60       * Constructs a create command.
  61       *
  62       * Supported options:
  63       *
  64       *  * autoIndexId (boolean): Specify false to disable the automatic creation
  65       *    of an index on the _id field. For replica sets, this option cannot be
  66       *    false. The default is true.
  67       *
  68       *    This option has been deprecated since MongoDB 3.2. As of MongoDB 4.0,
  69       *    this option cannot be false when creating a replicated collection
  70       *    (i.e. a collection outside of the local database in any mongod mode).
  71       *
  72       *  * capped (boolean): Specify true to create a capped collection. If set,
  73       *    the size option must also be specified. The default is false.
  74       *
  75       *  * comment (mixed): BSON value to attach as a comment to this command.
  76       *
  77       *    This is not supported for servers versions < 4.4.
  78       *
  79       *  * changeStreamPreAndPostImages (document): Used to configure support for
  80       *    pre- and post-images in change streams.
  81       *
  82       *    This is not supported for server versions < 6.0.
  83       *
  84       *  * clusteredIndex (document): A clustered index specification.
  85       *
  86       *    This is not supported for server versions < 5.3.
  87       *
  88       *  * collation (document): Collation specification.
  89       *
  90       *  * encryptedFields (document): CSFLE specification.
  91       *
  92       *  * expireAfterSeconds: The TTL for documents in time series collections.
  93       *
  94       *    This is not supported for servers versions < 5.0.
  95       *
  96       *  * flags (integer): Options for the MMAPv1 storage engine only. Must be a
  97       *    bitwise combination CreateCollection::USE_POWER_OF_2_SIZES and
  98       *    CreateCollection::NO_PADDING. The default is
  99       *    CreateCollection::USE_POWER_OF_2_SIZES.
 100       *
 101       *  * indexOptionDefaults (document): Default configuration for indexes when
 102       *    creating the collection.
 103       *
 104       *  * max (integer): The maximum number of documents allowed in the capped
 105       *    collection. The size option takes precedence over this limit.
 106       *
 107       *  * maxTimeMS (integer): The maximum amount of time to allow the query to
 108       *    run.
 109       *
 110       *  * pipeline (array): An array that consists of the aggregation pipeline
 111       *    stage(s), which will be applied to the collection or view specified by
 112       *    viewOn.
 113       *
 114       *  * session (MongoDB\Driver\Session): Client session.
 115       *
 116       *  * size (integer): The maximum number of bytes for a capped collection.
 117       *
 118       *  * storageEngine (document): Storage engine options.
 119       *
 120       *  * timeseries (document): Options for time series collections.
 121       *
 122       *    This is not supported for servers versions < 5.0.
 123       *
 124       *  * typeMap (array): Type map for BSON deserialization. This will only be
 125       *    used for the returned command result document.
 126       *
 127       *  * validationAction (string): Validation action.
 128       *
 129       *  * validationLevel (string): Validation level.
 130       *
 131       *  * validator (document): Validation rules or expressions.
 132       *
 133       *  * viewOn (string): The name of the source collection or view from which
 134       *    to create the view.
 135       *
 136       *  * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
 137       *
 138       * @see https://source.wiredtiger.com/2.4.1/struct_w_t___s_e_s_s_i_o_n.html#a358ca4141d59c345f401c58501276bbb
 139       * @see https://mongodb.com/docs/manual/core/schema-validation/
 140       * @param string $databaseName   Database name
 141       * @param string $collectionName Collection name
 142       * @param array  $options        Command options
 143       * @throws InvalidArgumentException for parameter/option parsing errors
 144       */
 145      public function __construct(string $databaseName, string $collectionName, array $options = [])
 146      {
 147          if (isset($options['autoIndexId']) && ! is_bool($options['autoIndexId'])) {
 148              throw InvalidArgumentException::invalidType('"autoIndexId" option', $options['autoIndexId'], 'boolean');
 149          }
 150  
 151          if (isset($options['capped']) && ! is_bool($options['capped'])) {
 152              throw InvalidArgumentException::invalidType('"capped" option', $options['capped'], 'boolean');
 153          }
 154  
 155          if (isset($options['changeStreamPreAndPostImages']) && ! is_array($options['changeStreamPreAndPostImages']) && ! is_object($options['changeStreamPreAndPostImages'])) {
 156              throw InvalidArgumentException::invalidType('"changeStreamPreAndPostImages" option', $options['changeStreamPreAndPostImages'], 'array or object');
 157          }
 158  
 159          if (isset($options['clusteredIndex']) && ! is_array($options['clusteredIndex']) && ! is_object($options['clusteredIndex'])) {
 160              throw InvalidArgumentException::invalidType('"clusteredIndex" option', $options['clusteredIndex'], 'array or object');
 161          }
 162  
 163          if (isset($options['collation']) && ! is_array($options['collation']) && ! is_object($options['collation'])) {
 164              throw InvalidArgumentException::invalidType('"collation" option', $options['collation'], 'array or object');
 165          }
 166  
 167          if (isset($options['encryptedFields']) && ! is_array($options['encryptedFields']) && ! is_object($options['encryptedFields'])) {
 168              throw InvalidArgumentException::invalidType('"encryptedFields" option', $options['encryptedFields'], 'array or object');
 169          }
 170  
 171          if (isset($options['expireAfterSeconds']) && ! is_integer($options['expireAfterSeconds'])) {
 172              throw InvalidArgumentException::invalidType('"expireAfterSeconds" option', $options['expireAfterSeconds'], 'integer');
 173          }
 174  
 175          if (isset($options['flags']) && ! is_integer($options['flags'])) {
 176              throw InvalidArgumentException::invalidType('"flags" option', $options['flags'], 'integer');
 177          }
 178  
 179          if (isset($options['indexOptionDefaults']) && ! is_array($options['indexOptionDefaults']) && ! is_object($options['indexOptionDefaults'])) {
 180              throw InvalidArgumentException::invalidType('"indexOptionDefaults" option', $options['indexOptionDefaults'], 'array or object');
 181          }
 182  
 183          if (isset($options['max']) && ! is_integer($options['max'])) {
 184              throw InvalidArgumentException::invalidType('"max" option', $options['max'], 'integer');
 185          }
 186  
 187          if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
 188              throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
 189          }
 190  
 191          if (isset($options['pipeline']) && ! is_array($options['pipeline'])) {
 192              throw InvalidArgumentException::invalidType('"pipeline" option', $options['pipeline'], 'array');
 193          }
 194  
 195          if (isset($options['session']) && ! $options['session'] instanceof Session) {
 196              throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class);
 197          }
 198  
 199          if (isset($options['size']) && ! is_integer($options['size'])) {
 200              throw InvalidArgumentException::invalidType('"size" option', $options['size'], 'integer');
 201          }
 202  
 203          if (isset($options['storageEngine']) && ! is_array($options['storageEngine']) && ! is_object($options['storageEngine'])) {
 204              throw InvalidArgumentException::invalidType('"storageEngine" option', $options['storageEngine'], 'array or object');
 205          }
 206  
 207          if (isset($options['timeseries']) && ! is_array($options['timeseries']) && ! is_object($options['timeseries'])) {
 208              throw InvalidArgumentException::invalidType('"timeseries" option', $options['timeseries'], ['array', 'object']);
 209          }
 210  
 211          if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
 212              throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
 213          }
 214  
 215          if (isset($options['validationAction']) && ! is_string($options['validationAction'])) {
 216              throw InvalidArgumentException::invalidType('"validationAction" option', $options['validationAction'], 'string');
 217          }
 218  
 219          if (isset($options['validationLevel']) && ! is_string($options['validationLevel'])) {
 220              throw InvalidArgumentException::invalidType('"validationLevel" option', $options['validationLevel'], 'string');
 221          }
 222  
 223          if (isset($options['validator']) && ! is_array($options['validator']) && ! is_object($options['validator'])) {
 224              throw InvalidArgumentException::invalidType('"validator" option', $options['validator'], 'array or object');
 225          }
 226  
 227          if (isset($options['viewOn']) && ! is_string($options['viewOn'])) {
 228              throw InvalidArgumentException::invalidType('"viewOn" option', $options['viewOn'], 'string');
 229          }
 230  
 231          if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
 232              throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class);
 233          }
 234  
 235          if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) {
 236              unset($options['writeConcern']);
 237          }
 238  
 239          if (isset($options['autoIndexId'])) {
 240              trigger_error('The "autoIndexId" option is deprecated and will be removed in a future release', E_USER_DEPRECATED);
 241          }
 242  
 243          if (isset($options['pipeline'])) {
 244              $expectedIndex = 0;
 245  
 246              foreach ($options['pipeline'] as $i => $operation) {
 247                  if ($i !== $expectedIndex) {
 248                      throw new InvalidArgumentException(sprintf('The "pipeline" option is not a list (unexpected index: "%s")', $i));
 249                  }
 250  
 251                  if (! is_array($operation) && ! is_object($operation)) {
 252                      throw InvalidArgumentException::invalidType(sprintf('$options["pipeline"][%d]', $i), $operation, 'array or object');
 253                  }
 254  
 255                  $expectedIndex += 1;
 256              }
 257          }
 258  
 259          $this->databaseName = $databaseName;
 260          $this->collectionName = $collectionName;
 261          $this->options = $options;
 262      }
 263  
 264      /**
 265       * Execute the operation.
 266       *
 267       * @see Executable::execute()
 268       * @return array|object Command result document
 269       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 270       */
 271      public function execute(Server $server)
 272      {
 273          $cursor = $server->executeWriteCommand($this->databaseName, $this->createCommand(), $this->createOptions());
 274  
 275          if (isset($this->options['typeMap'])) {
 276              $cursor->setTypeMap($this->options['typeMap']);
 277          }
 278  
 279          return current($cursor->toArray());
 280      }
 281  
 282      /**
 283       * Create the create command.
 284       */
 285      private function createCommand(): Command
 286      {
 287          $cmd = ['create' => $this->collectionName];
 288  
 289          foreach (['autoIndexId', 'capped', 'comment', 'expireAfterSeconds', 'flags', 'max', 'maxTimeMS', 'pipeline', 'size', 'validationAction', 'validationLevel', 'viewOn'] as $option) {
 290              if (isset($this->options[$option])) {
 291                  $cmd[$option] = $this->options[$option];
 292              }
 293          }
 294  
 295          foreach (['changeStreamPreAndPostImages', 'clusteredIndex', 'collation', 'encryptedFields', 'indexOptionDefaults', 'storageEngine', 'timeseries', 'validator'] as $option) {
 296              if (isset($this->options[$option])) {
 297                  $cmd[$option] = (object) $this->options[$option];
 298              }
 299          }
 300  
 301          return new Command($cmd);
 302      }
 303  
 304      /**
 305       * Create options for executing the command.
 306       *
 307       * @see https://php.net/manual/en/mongodb-driver-server.executewritecommand.php
 308       */
 309      private function createOptions(): array
 310      {
 311          $options = [];
 312  
 313          if (isset($this->options['session'])) {
 314              $options['session'] = $this->options['session'];
 315          }
 316  
 317          if (isset($this->options['writeConcern'])) {
 318              $options['writeConcern'] = $this->options['writeConcern'];
 319          }
 320  
 321          return $options;
 322      }
 323  }