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\Cursor;
  21  use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
  22  use MongoDB\Driver\Server;
  23  use MongoDB\Exception\InvalidArgumentException;
  24  use MongoDB\Exception\UnexpectedValueException;
  25  use MongoDB\Exception\UnsupportedException;
  26  
  27  use function array_intersect_key;
  28  use function assert;
  29  use function count;
  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  
  36  /**
  37   * Operation for obtaining an exact count of documents in a collection
  38   *
  39   * @api
  40   * @see \MongoDB\Collection::countDocuments()
  41   * @see https://github.com/mongodb/specifications/blob/master/source/crud/crud.rst#countdocuments
  42   */
  43  class CountDocuments implements Executable
  44  {
  45      /** @var string */
  46      private $databaseName;
  47  
  48      /** @var string */
  49      private $collectionName;
  50  
  51      /** @var array|object */
  52      private $filter;
  53  
  54      /** @var array */
  55      private $aggregateOptions;
  56  
  57      /** @var array */
  58      private $countOptions;
  59  
  60      /** @var Aggregate */
  61      private $aggregate;
  62  
  63      /**
  64       * Constructs an aggregate command for counting documents
  65       *
  66       * Supported options:
  67       *
  68       *  * collation (document): Collation specification.
  69       *
  70       *  * comment (mixed): BSON value to attach as a comment to this command.
  71       *
  72       *    Only string values are supported for server versions < 4.4.
  73       *
  74       *  * hint (string|document): The index to use. Specify either the index
  75       *    name as a string or the index key pattern as a document. If specified,
  76       *    then the query system will only consider plans using the hinted index.
  77       *
  78       *  * limit (integer): The maximum number of documents to count.
  79       *
  80       *  * maxTimeMS (integer): The maximum amount of time to allow the query to
  81       *    run.
  82       *
  83       *  * readConcern (MongoDB\Driver\ReadConcern): Read concern.
  84       *
  85       *  * readPreference (MongoDB\Driver\ReadPreference): Read preference.
  86       *
  87       *  * session (MongoDB\Driver\Session): Client session.
  88       *
  89       *  * skip (integer): The number of documents to skip before returning the
  90       *    documents.
  91       *
  92       * @param string       $databaseName   Database name
  93       * @param string       $collectionName Collection name
  94       * @param array|object $filter         Query by which to filter documents
  95       * @param array        $options        Command options
  96       * @throws InvalidArgumentException for parameter/option parsing errors
  97       */
  98      public function __construct(string $databaseName, string $collectionName, $filter, array $options = [])
  99      {
 100          if (! is_array($filter) && ! is_object($filter)) {
 101              throw InvalidArgumentException::invalidType('$filter', $filter, 'array or object');
 102          }
 103  
 104          if (isset($options['limit']) && ! is_integer($options['limit'])) {
 105              throw InvalidArgumentException::invalidType('"limit" option', $options['limit'], 'integer');
 106          }
 107  
 108          if (isset($options['skip']) && ! is_integer($options['skip'])) {
 109              throw InvalidArgumentException::invalidType('"skip" option', $options['skip'], 'integer');
 110          }
 111  
 112          $this->databaseName = $databaseName;
 113          $this->collectionName = $collectionName;
 114          $this->filter = $filter;
 115  
 116          $this->aggregateOptions = array_intersect_key($options, ['collation' => 1, 'comment' => 1, 'hint' => 1, 'maxTimeMS' => 1, 'readConcern' => 1, 'readPreference' => 1, 'session' => 1]);
 117          $this->countOptions = array_intersect_key($options, ['limit' => 1, 'skip' => 1]);
 118  
 119          $this->aggregate = $this->createAggregate();
 120      }
 121  
 122      /**
 123       * Execute the operation.
 124       *
 125       * @see Executable::execute()
 126       * @return integer
 127       * @throws UnexpectedValueException if the command response was malformed
 128       * @throws UnsupportedException if collation or read concern is used and unsupported
 129       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 130       */
 131      public function execute(Server $server)
 132      {
 133          $cursor = $this->aggregate->execute($server);
 134          assert($cursor instanceof Cursor);
 135  
 136          $allResults = $cursor->toArray();
 137  
 138          /* If there are no documents to count, the aggregation pipeline has no items to group, and
 139           * hence the result is an empty array (PHPLIB-376) */
 140          if (count($allResults) == 0) {
 141              return 0;
 142          }
 143  
 144          $result = current($allResults);
 145          if (! is_object($result) || ! isset($result->n) || ! (is_integer($result->n) || is_float($result->n))) {
 146              throw new UnexpectedValueException('count command did not return a numeric "n" value');
 147          }
 148  
 149          return (integer) $result->n;
 150      }
 151  
 152      private function createAggregate(): Aggregate
 153      {
 154          $pipeline = [
 155              ['$match' => (object) $this->filter],
 156          ];
 157  
 158          if (isset($this->countOptions['skip'])) {
 159              $pipeline[] = ['$skip' => $this->countOptions['skip']];
 160          }
 161  
 162          if (isset($this->countOptions['limit'])) {
 163              $pipeline[] = ['$limit' => $this->countOptions['limit']];
 164          }
 165  
 166          $pipeline[] = ['$group' => ['_id' => 1, 'n' => ['$sum' => 1]]];
 167  
 168          return new Aggregate($this->databaseName, $this->collectionName, $pipeline, $this->aggregateOptions);
 169      }
 170  }