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\ReadConcern;
  23  use MongoDB\Driver\ReadPreference;
  24  use MongoDB\Driver\Server;
  25  use MongoDB\Driver\Session;
  26  use MongoDB\Exception\InvalidArgumentException;
  27  use MongoDB\Exception\UnexpectedValueException;
  28  use MongoDB\Exception\UnsupportedException;
  29  
  30  use function current;
  31  use function is_array;
  32  use function is_float;
  33  use function is_integer;
  34  use function is_object;
  35  use function is_string;
  36  
  37  /**
  38   * Operation for the count command.
  39   *
  40   * @api
  41   * @see \MongoDB\Collection::count()
  42   * @see https://mongodb.com/docs/manual/reference/command/count/
  43   */
  44  class Count implements Executable, Explainable
  45  {
  46      /** @var string */
  47      private $databaseName;
  48  
  49      /** @var string */
  50      private $collectionName;
  51  
  52      /** @var array|object */
  53      private $filter;
  54  
  55      /** @var array */
  56      private $options;
  57  
  58      /**
  59       * Constructs a count command.
  60       *
  61       * Supported options:
  62       *
  63       *  * collation (document): Collation specification.
  64       *
  65       *  * comment (mixed): BSON value to attach as a comment to this command.
  66       *
  67       *    This is not supported for servers versions < 4.4.
  68       *
  69       *  * hint (string|document): The index to use. Specify either the index
  70       *    name as a string or the index key pattern as a document. If specified,
  71       *    then the query system will only consider plans using the hinted index.
  72       *
  73       *  * limit (integer): The maximum number of documents to count.
  74       *
  75       *  * maxTimeMS (integer): The maximum amount of time to allow the query to
  76       *    run.
  77       *
  78       *  * readConcern (MongoDB\Driver\ReadConcern): Read concern.
  79       *
  80       *  * readPreference (MongoDB\Driver\ReadPreference): Read preference.
  81       *
  82       *  * session (MongoDB\Driver\Session): Client session.
  83       *
  84       *  * skip (integer): The number of documents to skip before returning the
  85       *    documents.
  86       *
  87       * @param string       $databaseName   Database name
  88       * @param string       $collectionName Collection name
  89       * @param array|object $filter         Query by which to filter documents
  90       * @param array        $options        Command options
  91       * @throws InvalidArgumentException for parameter/option parsing errors
  92       */
  93      public function __construct(string $databaseName, string $collectionName, $filter = [], array $options = [])
  94      {
  95          if (! is_array($filter) && ! is_object($filter)) {
  96              throw InvalidArgumentException::invalidType('$filter', $filter, 'array or object');
  97          }
  98  
  99          if (isset($options['collation']) && ! is_array($options['collation']) && ! is_object($options['collation'])) {
 100              throw InvalidArgumentException::invalidType('"collation" option', $options['collation'], 'array or object');
 101          }
 102  
 103          if (isset($options['hint']) && ! is_string($options['hint']) && ! is_array($options['hint']) && ! is_object($options['hint'])) {
 104              throw InvalidArgumentException::invalidType('"hint" option', $options['hint'], 'string or array or object');
 105          }
 106  
 107          if (isset($options['limit']) && ! is_integer($options['limit'])) {
 108              throw InvalidArgumentException::invalidType('"limit" option', $options['limit'], 'integer');
 109          }
 110  
 111          if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
 112              throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
 113          }
 114  
 115          if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
 116              throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], ReadConcern::class);
 117          }
 118  
 119          if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
 120              throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class);
 121          }
 122  
 123          if (isset($options['session']) && ! $options['session'] instanceof Session) {
 124              throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class);
 125          }
 126  
 127          if (isset($options['skip']) && ! is_integer($options['skip'])) {
 128              throw InvalidArgumentException::invalidType('"skip" option', $options['skip'], 'integer');
 129          }
 130  
 131          if (isset($options['readConcern']) && $options['readConcern']->isDefault()) {
 132              unset($options['readConcern']);
 133          }
 134  
 135          $this->databaseName = $databaseName;
 136          $this->collectionName = $collectionName;
 137          $this->filter = $filter;
 138          $this->options = $options;
 139      }
 140  
 141      /**
 142       * Execute the operation.
 143       *
 144       * @see Executable::execute()
 145       * @return integer
 146       * @throws UnexpectedValueException if the command response was malformed
 147       * @throws UnsupportedException if read concern is used and unsupported
 148       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 149       */
 150      public function execute(Server $server)
 151      {
 152          $inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction();
 153          if ($inTransaction && isset($this->options['readConcern'])) {
 154              throw UnsupportedException::readConcernNotSupportedInTransaction();
 155          }
 156  
 157          $cursor = $server->executeReadCommand($this->databaseName, new Command($this->createCommandDocument()), $this->createOptions());
 158          $result = current($cursor->toArray());
 159  
 160          // Older server versions may return a float
 161          if (! is_object($result) || ! isset($result->n) || ! (is_integer($result->n) || is_float($result->n))) {
 162              throw new UnexpectedValueException('count command did not return a numeric "n" value');
 163          }
 164  
 165          return (integer) $result->n;
 166      }
 167  
 168      /**
 169       * Returns the command document for this operation.
 170       *
 171       * @see Explainable::getCommandDocument()
 172       * @return array
 173       */
 174      public function getCommandDocument(Server $server)
 175      {
 176          return $this->createCommandDocument();
 177      }
 178  
 179      /**
 180       * Create the count command document.
 181       */
 182      private function createCommandDocument(): array
 183      {
 184          $cmd = ['count' => $this->collectionName];
 185  
 186          if (! empty($this->filter)) {
 187              $cmd['query'] = (object) $this->filter;
 188          }
 189  
 190          if (isset($this->options['collation'])) {
 191              $cmd['collation'] = (object) $this->options['collation'];
 192          }
 193  
 194          if (isset($this->options['hint'])) {
 195              $cmd['hint'] = is_array($this->options['hint']) ? (object) $this->options['hint'] : $this->options['hint'];
 196          }
 197  
 198          foreach (['comment', 'limit', 'maxTimeMS', 'skip'] as $option) {
 199              if (isset($this->options[$option])) {
 200                  $cmd[$option] = $this->options[$option];
 201              }
 202          }
 203  
 204          return $cmd;
 205      }
 206  
 207      /**
 208       * Create options for executing the command.
 209       *
 210       * @see https://php.net/manual/en/mongodb-driver-server.executereadcommand.php
 211       */
 212      private function createOptions(): array
 213      {
 214          $options = [];
 215  
 216          if (isset($this->options['readConcern'])) {
 217              $options['readConcern'] = $this->options['readConcern'];
 218          }
 219  
 220          if (isset($this->options['readPreference'])) {
 221              $options['readPreference'] = $this->options['readPreference'];
 222          }
 223  
 224          if (isset($this->options['session'])) {
 225              $options['session'] = $this->options['session'];
 226          }
 227  
 228          return $options;
 229      }
 230  }