Collection.php 40.1 KB
Newer Older
1
<?php
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/*
 * Copyright 2015-2017 MongoDB, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
17

18 19
namespace MongoDB;

20
use MongoDB\BSON\JavascriptInterface;
21
use MongoDB\BSON\Serializable;
22
use MongoDB\ChangeStream;
23
use MongoDB\Driver\Cursor;
24
use MongoDB\Driver\Manager;
25
use MongoDB\Driver\ReadConcern;
26 27
use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\WriteConcern;
28
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
29
use MongoDB\Exception\InvalidArgumentException;
30 31
use MongoDB\Exception\UnexpectedValueException;
use MongoDB\Exception\UnsupportedException;
32
use MongoDB\Model\IndexInfoIterator;
33
use MongoDB\Operation\Aggregate;
34
use MongoDB\Operation\BulkWrite;
35
use MongoDB\Operation\CreateIndexes;
36
use MongoDB\Operation\Count;
37 38
use MongoDB\Operation\DeleteMany;
use MongoDB\Operation\DeleteOne;
39
use MongoDB\Operation\Distinct;
40
use MongoDB\Operation\DropCollection;
41
use MongoDB\Operation\DropIndexes;
42 43
use MongoDB\Operation\Find;
use MongoDB\Operation\FindOne;
44 45 46
use MongoDB\Operation\FindOneAndDelete;
use MongoDB\Operation\FindOneAndReplace;
use MongoDB\Operation\FindOneAndUpdate;
47 48
use MongoDB\Operation\InsertMany;
use MongoDB\Operation\InsertOne;
49
use MongoDB\Operation\ListIndexes;
50
use MongoDB\Operation\MapReduce;
51 52 53
use MongoDB\Operation\ReplaceOne;
use MongoDB\Operation\UpdateMany;
use MongoDB\Operation\UpdateOne;
54
use MongoDB\Operation\Watch;
55
use Traversable;
56

57 58
class Collection
{
59 60 61 62 63
    private static $defaultTypeMap = [
        'array' => 'MongoDB\Model\BSONArray',
        'document' => 'MongoDB\Model\BSONDocument',
        'root' => 'MongoDB\Model\BSONDocument',
    ];
64
    private static $wireVersionForFindAndModifyWriteConcern = 4;
65
    private static $wireVersionForReadConcern = 4;
66
    private static $wireVersionForWritableCommandWriteConcern = 5;
67

68 69 70
    private $collectionName;
    private $databaseName;
    private $manager;
71
    private $readConcern;
72
    private $readPreference;
73
    private $typeMap;
74
    private $writeConcern;
Hannes Magnusson's avatar
Hannes Magnusson committed
75

76
    /**
77
     * Constructs new Collection instance.
78
     *
79 80
     * This class provides methods for collection-specific operations, such as
     * CRUD (i.e. create, read, update, and delete) and index management.
81
     *
82 83
     * Supported options:
     *
84 85 86
     *  * readConcern (MongoDB\Driver\ReadConcern): The default read concern to
     *    use for collection operations. Defaults to the Manager's read concern.
     *
87 88 89 90
     *  * readPreference (MongoDB\Driver\ReadPreference): The default read
     *    preference to use for collection operations. Defaults to the Manager's
     *    read preference.
     *
91 92
     *  * typeMap (array): Default type map for cursors and BSON documents.
     *
93 94 95 96
     *  * writeConcern (MongoDB\Driver\WriteConcern): The default write concern
     *    to use for collection operations. Defaults to the Manager's write
     *    concern.
     *
97 98 99 100
     * @param Manager $manager        Manager instance from the driver
     * @param string  $databaseName   Database name
     * @param string  $collectionName Collection name
     * @param array   $options        Collection options
101
     * @throws InvalidArgumentException for parameter/option parsing errors
102
     */
103
    public function __construct(Manager $manager, $databaseName, $collectionName, array $options = [])
104
    {
105 106
        if (strlen($databaseName) < 1) {
            throw new InvalidArgumentException('$databaseName is invalid: ' . $databaseName);
107 108
        }

109 110 111
        if (strlen($collectionName) < 1) {
            throw new InvalidArgumentException('$collectionName is invalid: ' . $collectionName);
        }
112

113
        if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
114
            throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern');
115 116
        }

117
        if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
118
            throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
119 120
        }

121
        if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
122
            throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
123 124
        }

125
        if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
126
            throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
127 128
        }

129
        $this->manager = $manager;
130 131
        $this->databaseName = (string) $databaseName;
        $this->collectionName = (string) $collectionName;
132
        $this->readConcern = isset($options['readConcern']) ? $options['readConcern'] : $this->manager->getReadConcern();
133
        $this->readPreference = isset($options['readPreference']) ? $options['readPreference'] : $this->manager->getReadPreference();
134
        $this->typeMap = isset($options['typeMap']) ? $options['typeMap'] : self::$defaultTypeMap;
135
        $this->writeConcern = isset($options['writeConcern']) ? $options['writeConcern'] : $this->manager->getWriteConcern();
136
    }
137

138 139 140 141
    /**
     * Return internal properties for debugging purposes.
     *
     * @see http://php.net/manual/en/language.oop5.magic.php#language.oop5.magic.debuginfo
142
     * @return array
143 144 145 146 147 148 149
     */
    public function __debugInfo()
    {
        return [
            'collectionName' => $this->collectionName,
            'databaseName' => $this->databaseName,
            'manager' => $this->manager,
150
            'readConcern' => $this->readConcern,
151
            'readPreference' => $this->readPreference,
152
            'typeMap' => $this->typeMap,
153 154 155 156
            'writeConcern' => $this->writeConcern,
        ];
    }

157
    /**
158
     * Return the collection namespace (e.g. "db.collection").
159
     *
160
     * @see https://docs.mongodb.org/manual/faq/developers/#faq-dev-namespace
161
     * @return string
162 163 164
     */
    public function __toString()
    {
165
        return $this->databaseName . '.' . $this->collectionName;
166 167
    }

168
    /**
169
     * Executes an aggregation framework pipeline on the collection.
170 171 172 173 174
     *
     * Note: this method's return value depends on the MongoDB server version
     * and the "useCursor" option. If "useCursor" is true, a Cursor will be
     * returned; otherwise, an ArrayIterator is returned, which wraps the
     * "result" array from the command response document.
175
     *
176 177 178 179
     * @see Aggregate::__construct() for supported options
     * @param array $pipeline List of pipeline operations
     * @param array $options  Command options
     * @return Traversable
180 181 182 183
     * @throws UnexpectedValueException if the command response was malformed
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
184
     */
Jeremy Mikola's avatar
Jeremy Mikola committed
185
    public function aggregate(array $pipeline, array $options = [])
186
    {
187 188
        $hasOutStage = \MongoDB\is_last_pipeline_operator_out($pipeline);

189 190 191 192
        if ( ! isset($options['readPreference'])) {
            $options['readPreference'] = $this->readPreference;
        }

193
        if ($hasOutStage) {
194 195 196
            $options['readPreference'] = new ReadPreference(ReadPreference::RP_PRIMARY);
        }

197 198 199 200 201 202 203 204 205 206 207
        $server = $this->manager->selectServer($options['readPreference']);

        /* A "majority" read concern is not compatible with the $out stage, so
         * avoid providing the Collection's read concern if it would conflict.
         */
        if ( ! isset($options['readConcern']) &&
             ! ($hasOutStage && $this->readConcern->getLevel() === ReadConcern::MAJORITY) &&
            \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
            $options['readConcern'] = $this->readConcern;
        }

208
        if ( ! isset($options['typeMap'])) {
209 210 211
            $options['typeMap'] = $this->typeMap;
        }

212 213 214 215 216 217
        if ($hasOutStage && ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) {
            $options['writeConcern'] = $this->writeConcern;
        }

        $operation = new Aggregate($this->databaseName, $this->collectionName, $pipeline, $options);

218
        return $operation->execute($server);
219 220 221
    }

    /**
222
     * Executes multiple write operations.
223
     *
224 225 226 227
     * @see BulkWrite::__construct() for supported options
     * @param array[] $operations List of write operations
     * @param array   $options    Command options
     * @return BulkWriteResult
228 229 230
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
231
     */
Jeremy Mikola's avatar
Jeremy Mikola committed
232
    public function bulkWrite(array $operations, array $options = [])
233
    {
234
        if ( ! isset($options['writeConcern'])) {
235
            $options['writeConcern'] = $this->writeConcern;
236
        }
237

238
        $operation = new BulkWrite($this->databaseName, $this->collectionName, $operations, $options);
239
        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
240

241
        return $operation->execute($server);
242 243 244
    }

    /**
245
     * Gets the number of documents matching the filter.
246
     *
247
     * @see Count::__construct() for supported options
248 249
     * @param array|object $filter  Query by which to filter documents
     * @param array        $options Command options
250
     * @return integer
251 252 253 254
     * @throws UnexpectedValueException if the command response was malformed
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
255
     */
Jeremy Mikola's avatar
Jeremy Mikola committed
256
    public function count($filter = [], array $options = [])
257
    {
258 259 260 261 262
        if ( ! isset($options['readPreference'])) {
            $options['readPreference'] = $this->readPreference;
        }

        $server = $this->manager->selectServer($options['readPreference']);
263

264 265 266 267 268 269
        if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
            $options['readConcern'] = $this->readConcern;
        }

        $operation = new Count($this->databaseName, $this->collectionName, $filter, $options);

270
        return $operation->execute($server);
271 272
    }

273
    /**
274
     * Create a single index for the collection.
275
     *
276
     * @see Collection::createIndexes()
277
     * @see CreateIndexes::__construct() for supported command options
278 279
     * @param array|object $key     Document containing fields mapped to values,
     *                              which denote order or an index type
280
     * @param array        $options Index and command options
281
     * @return string The name of the created index
282 283 284
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
285
     */
Jeremy Mikola's avatar
Jeremy Mikola committed
286
    public function createIndex($key, array $options = [])
287
    {
288 289 290
        $commandOptionKeys = ['maxTimeMS' => 1, 'session' => 1, 'writeConcern' => 1];
        $indexOptions = array_diff_key($options, $commandOptionKeys);
        $commandOptions = array_intersect_key($options, $commandOptionKeys);
291 292

        return current($this->createIndexes([['key' => $key] + $indexOptions], $commandOptions));
293 294 295
    }

    /**
296
     * Create one or more indexes for the collection.
297
     *
298 299 300 301 302 303 304 305 306 307 308 309 310
     * Each element in the $indexes array must have a "key" document, which
     * contains fields mapped to an order or type. Other options may follow.
     * For example:
     *
     *     $indexes = [
     *         // Create a unique index on the "username" field
     *         [ 'key' => [ 'username' => 1 ], 'unique' => true ],
     *         // Create a 2dsphere index on the "loc" field with a custom name
     *         [ 'key' => [ 'loc' => '2dsphere' ], 'name' => 'geo' ],
     *     ];
     *
     * If the "name" option is unspecified, a name will be generated from the
     * "key" document.
311 312 313
     *
     * @see http://docs.mongodb.org/manual/reference/command/createIndexes/
     * @see http://docs.mongodb.org/manual/reference/method/db.collection.createIndex/
314
     * @see CreateIndexes::__construct() for supported command options
315
     * @param array[] $indexes List of index specifications
316
     * @param array   $options Command options
317
     * @return string[] The names of the created indexes
318 319 320
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
321
     */
322
    public function createIndexes(array $indexes, array $options = [])
323
    {
324
        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
325

326 327 328 329 330 331
        if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) {
            $options['writeConcern'] = $this->writeConcern;
        }

        $operation = new CreateIndexes($this->databaseName, $this->collectionName, $indexes, $options);

332
        return $operation->execute($server);
333 334
    }

335
    /**
336
     * Deletes all documents matching the filter.
337
     *
338
     * @see DeleteMany::__construct() for supported options
339
     * @see http://docs.mongodb.org/manual/reference/command/delete/
340 341
     * @param array|object $filter  Query by which to delete documents
     * @param array        $options Command options
342
     * @return DeleteResult
343 344 345
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
346
     */
Jeremy Mikola's avatar
Jeremy Mikola committed
347
    public function deleteMany($filter, array $options = [])
348
    {
349
        if ( ! isset($options['writeConcern'])) {
350
            $options['writeConcern'] = $this->writeConcern;
351 352
        }

353
        $operation = new DeleteMany($this->databaseName, $this->collectionName, $filter, $options);
354
        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
355

356
        return $operation->execute($server);
357 358 359
    }

    /**
360
     * Deletes at most one document matching the filter.
361
     *
362
     * @see DeleteOne::__construct() for supported options
363
     * @see http://docs.mongodb.org/manual/reference/command/delete/
364 365
     * @param array|object $filter  Query by which to delete documents
     * @param array        $options Command options
366
     * @return DeleteResult
367 368 369
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
370
     */
Jeremy Mikola's avatar
Jeremy Mikola committed
371
    public function deleteOne($filter, array $options = [])
372
    {
373
        if ( ! isset($options['writeConcern'])) {
374
            $options['writeConcern'] = $this->writeConcern;
375
        }
376

377
        $operation = new DeleteOne($this->databaseName, $this->collectionName, $filter, $options);
378 379 380
        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));

        return $operation->execute($server);
381 382 383
    }

    /**
384
     * Finds the distinct values for a specified field across the collection.
385
     *
386 387
     * @see Distinct::__construct() for supported options
     * @param string $fieldName Field for which to return distinct values
388 389
     * @param array|object $filter  Query by which to filter documents
     * @param array        $options Command options
390
     * @return mixed[]
391 392 393 394
     * @throws UnexpectedValueException if the command response was malformed
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
395
     */
Jeremy Mikola's avatar
Jeremy Mikola committed
396
    public function distinct($fieldName, $filter = [], array $options = [])
397
    {
398 399 400 401 402
        if ( ! isset($options['readPreference'])) {
            $options['readPreference'] = $this->readPreference;
        }

        $server = $this->manager->selectServer($options['readPreference']);
403

404 405 406 407 408 409
        if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
            $options['readConcern'] = $this->readConcern;
        }

        $operation = new Distinct($this->databaseName, $this->collectionName, $fieldName, $filter, $options);

410
        return $operation->execute($server);
411 412
    }

413 414 415
    /**
     * Drop this collection.
     *
416 417 418
     * @see DropCollection::__construct() for supported options
     * @param array $options Additional options
     * @return array|object Command result document
419 420 421
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
422
     */
423
    public function drop(array $options = [])
424
    {
425 426 427 428
        if ( ! isset($options['typeMap'])) {
            $options['typeMap'] = $this->typeMap;
        }

429
        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
430

431 432 433 434 435 436
        if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) {
            $options['writeConcern'] = $this->writeConcern;
        }

        $operation = new DropCollection($this->databaseName, $this->collectionName, $options);

437
        return $operation->execute($server);
438 439
    }

440 441 442
    /**
     * Drop a single index in the collection.
     *
443
     * @see DropIndexes::__construct() for supported options
444
     * @param string $indexName Index name
445 446
     * @param array  $options   Additional options
     * @return array|object Command result document
447 448 449
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
450
     */
451
    public function dropIndex($indexName, array $options = [])
452
    {
453 454 455 456 457 458
        $indexName = (string) $indexName;

        if ($indexName === '*') {
            throw new InvalidArgumentException('dropIndexes() must be used to drop multiple indexes');
        }

459 460 461 462
        if ( ! isset($options['typeMap'])) {
            $options['typeMap'] = $this->typeMap;
        }

463
        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
464

465 466 467 468 469 470
        if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) {
            $options['writeConcern'] = $this->writeConcern;
        }

        $operation = new DropIndexes($this->databaseName, $this->collectionName, $indexName, $options);

471
        return $operation->execute($server);
472 473 474 475 476
    }

    /**
     * Drop all indexes in the collection.
     *
477 478 479
     * @see DropIndexes::__construct() for supported options
     * @param array $options Additional options
     * @return array|object Command result document
480 481 482
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
483
     */
484
    public function dropIndexes(array $options = [])
485
    {
486 487 488 489
        if ( ! isset($options['typeMap'])) {
            $options['typeMap'] = $this->typeMap;
        }

490
        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
491

492 493 494 495 496 497
        if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) {
            $options['writeConcern'] = $this->writeConcern;
        }

        $operation = new DropIndexes($this->databaseName, $this->collectionName, '*', $options);

498
        return $operation->execute($server);
499 500
    }

501
    /**
502
     * Finds documents matching the query.
503
     *
504
     * @see Find::__construct() for supported options
505
     * @see http://docs.mongodb.org/manual/core/read-operations-introduction/
506 507
     * @param array|object $filter  Query by which to filter documents
     * @param array        $options Additional options
508
     * @return Cursor
509 510 511
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
512
     */
Jeremy Mikola's avatar
Jeremy Mikola committed
513
    public function find($filter = [], array $options = [])
514
    {
515 516 517 518
        if ( ! isset($options['readPreference'])) {
            $options['readPreference'] = $this->readPreference;
        }

519 520 521 522 523 524
        $server = $this->manager->selectServer($options['readPreference']);

        if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
            $options['readConcern'] = $this->readConcern;
        }

525 526 527 528
        if ( ! isset($options['typeMap'])) {
            $options['typeMap'] = $this->typeMap;
        }

529
        $operation = new Find($this->databaseName, $this->collectionName, $filter, $options);
530

531
        return $operation->execute($server);
532 533
    }

534
    /**
535
     * Finds a single document matching the query.
536
     *
537
     * @see FindOne::__construct() for supported options
538
     * @see http://docs.mongodb.org/manual/core/read-operations-introduction/
539 540
     * @param array|object $filter  Query by which to filter documents
     * @param array        $options Additional options
541
     * @return array|object|null
542 543 544
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
545
     */
Jeremy Mikola's avatar
Jeremy Mikola committed
546
    public function findOne($filter = [], array $options = [])
547
    {
548 549 550 551
        if ( ! isset($options['readPreference'])) {
            $options['readPreference'] = $this->readPreference;
        }

552 553 554 555 556 557
        $server = $this->manager->selectServer($options['readPreference']);

        if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
            $options['readConcern'] = $this->readConcern;
        }

558 559 560 561
        if ( ! isset($options['typeMap'])) {
            $options['typeMap'] = $this->typeMap;
        }

562
        $operation = new FindOne($this->databaseName, $this->collectionName, $filter, $options);
563

564
        return $operation->execute($server);
565 566
    }

567
    /**
568
     * Finds a single document and deletes it, returning the original.
569
     *
570
     * The document to return may be null if no document matched the filter.
571
     *
572
     * @see FindOneAndDelete::__construct() for supported options
573
     * @see http://docs.mongodb.org/manual/reference/command/findAndModify/
574 575
     * @param array|object $filter  Query by which to filter documents
     * @param array        $options Command options
576
     * @return array|object|null
577 578 579 580
     * @throws UnexpectedValueException if the command response was malformed
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
581
     */
Jeremy Mikola's avatar
Jeremy Mikola committed
582
    public function findOneAndDelete($filter, array $options = [])
583
    {
584
        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
585

586 587 588 589
        if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForFindAndModifyWriteConcern)) {
            $options['writeConcern'] = $this->writeConcern;
        }

590 591 592 593
        if ( ! isset($options['typeMap'])) {
            $options['typeMap'] = $this->typeMap;
        }

594 595
        $operation = new FindOneAndDelete($this->databaseName, $this->collectionName, $filter, $options);

596
        return $operation->execute($server);
597 598 599
    }

    /**
600 601
     * Finds a single document and replaces it, returning either the original or
     * the replaced document.
602
     *
603 604 605 606
     * The document to return may be null if no document matched the filter. By
     * default, the original document is returned. Specify
     * FindOneAndReplace::RETURN_DOCUMENT_AFTER for the "returnDocument" option
     * to return the updated document.
607
     *
608
     * @see FindOneAndReplace::__construct() for supported options
609
     * @see http://docs.mongodb.org/manual/reference/command/findAndModify/
610 611 612
     * @param array|object $filter      Query by which to filter documents
     * @param array|object $replacement Replacement document
     * @param array        $options     Command options
613
     * @return array|object|null
614 615 616 617
     * @throws UnexpectedValueException if the command response was malformed
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
618
     */
Jeremy Mikola's avatar
Jeremy Mikola committed
619
    public function findOneAndReplace($filter, $replacement, array $options = [])
620
    {
621
        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
622

623 624 625 626
        if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForFindAndModifyWriteConcern)) {
            $options['writeConcern'] = $this->writeConcern;
        }

627 628 629 630
        if ( ! isset($options['typeMap'])) {
            $options['typeMap'] = $this->typeMap;
        }

631 632
        $operation = new FindOneAndReplace($this->databaseName, $this->collectionName, $filter, $replacement, $options);

633
        return $operation->execute($server);
634
    }
635

636
    /**
637 638
     * Finds a single document and updates it, returning either the original or
     * the updated document.
639
     *
640 641 642 643
     * The document to return may be null if no document matched the filter. By
     * default, the original document is returned. Specify
     * FindOneAndUpdate::RETURN_DOCUMENT_AFTER for the "returnDocument" option
     * to return the updated document.
644
     *
645
     * @see FindOneAndReplace::__construct() for supported options
646
     * @see http://docs.mongodb.org/manual/reference/command/findAndModify/
647 648 649
     * @param array|object $filter  Query by which to filter documents
     * @param array|object $update  Update to apply to the matched document
     * @param array        $options Command options
650
     * @return array|object|null
651 652 653 654
     * @throws UnexpectedValueException if the command response was malformed
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
655
     */
Jeremy Mikola's avatar
Jeremy Mikola committed
656
    public function findOneAndUpdate($filter, $update, array $options = [])
657
    {
658
        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
659

660 661 662 663
        if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForFindAndModifyWriteConcern)) {
            $options['writeConcern'] = $this->writeConcern;
        }

664 665 666 667
        if ( ! isset($options['typeMap'])) {
            $options['typeMap'] = $this->typeMap;
        }

668 669
        $operation = new FindOneAndUpdate($this->databaseName, $this->collectionName, $filter, $update, $options);

670
        return $operation->execute($server);
671
    }
672

673
    /**
674
     * Return the collection name.
675
     *
676
     * @return string
677
     */
678
    public function getCollectionName()
679
    {
680
        return $this->collectionName;
681 682 683
    }

    /**
684
     * Return the database name.
685 686
     *
     * @return string
687
     */
688
    public function getDatabaseName()
689
    {
690
        return $this->databaseName;
691 692
    }

693 694 695 696 697 698 699 700 701 702
    /**
     * Return the Manager.
     *
     * @return Manager
     */
    public function getManager()
    {
        return $this->manager;
    }

703 704 705
    /**
     * Return the collection namespace.
     *
706
     * @see https://docs.mongodb.org/manual/reference/glossary/#term-namespace
707 708 709 710
     * @return string
     */
    public function getNamespace()
    {
711
        return $this->databaseName . '.' . $this->collectionName;
712 713
    }

714
    /**
715
     * Return the read concern for this collection.
716
     *
717
     * @see http://php.net/manual/en/mongodb-driver-readconcern.isdefault.php
718 719 720 721 722 723 724 725
     * @return ReadConcern
     */
    public function getReadConcern()
    {
        return $this->readConcern;
    }

    /**
726
     * Return the read preference for this collection.
727 728 729 730 731 732 733 734 735
     *
     * @return ReadPreference
     */
    public function getReadPreference()
    {
        return $this->readPreference;
    }

    /**
736
     * Return the type map for this collection.
737 738 739 740 741 742 743 744 745
     *
     * @return array
     */
    public function getTypeMap()
    {
        return $this->typeMap;
    }

    /**
746
     * Return the write concern for this collection.
747
     *
748
     * @see http://php.net/manual/en/mongodb-driver-writeconcern.isdefault.php
749 750 751 752 753 754 755
     * @return WriteConcern
     */
    public function getWriteConcern()
    {
        return $this->writeConcern;
    }

756
    /**
757
     * Inserts multiple documents.
758
     *
759
     * @see InsertMany::__construct() for supported options
760
     * @see http://docs.mongodb.org/manual/reference/command/insert/
761
     * @param array[]|object[] $documents The documents to insert
762
     * @param array            $options   Command options
763
     * @return InsertManyResult
764 765
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
766
     */
Jeremy Mikola's avatar
Jeremy Mikola committed
767
    public function insertMany(array $documents, array $options = [])
768
    {
769
        if ( ! isset($options['writeConcern'])) {
770
            $options['writeConcern'] = $this->writeConcern;
771 772
        }

773
        $operation = new InsertMany($this->databaseName, $this->collectionName, $documents, $options);
774
        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
775

776
        return $operation->execute($server);
777 778 779
    }

    /**
780
     * Inserts one document.
781
     *
782
     * @see InsertOne::__construct() for supported options
783
     * @see http://docs.mongodb.org/manual/reference/command/insert/
784
     * @param array|object $document The document to insert
785
     * @param array        $options  Command options
786
     * @return InsertOneResult
787 788
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
789
     */
Jeremy Mikola's avatar
Jeremy Mikola committed
790
    public function insertOne($document, array $options = [])
791
    {
792
        if ( ! isset($options['writeConcern'])) {
793
            $options['writeConcern'] = $this->writeConcern;
794 795
        }

796
        $operation = new InsertOne($this->databaseName, $this->collectionName, $document, $options);
797 798 799
        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));

        return $operation->execute($server);
800
    }
801

802
    /**
803
     * Returns information for all indexes for the collection.
804
     *
805
     * @see ListIndexes::__construct() for supported options
806
     * @return IndexInfoIterator
807 808
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
809
     */
Jeremy Mikola's avatar
Jeremy Mikola committed
810
    public function listIndexes(array $options = [])
811
    {
812
        $operation = new ListIndexes($this->databaseName, $this->collectionName, $options);
813
        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
814

815
        return $operation->execute($server);
816 817
    }

818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834
    /**
     * Executes a map-reduce aggregation on the collection.
     *
     * @see MapReduce::__construct() for supported options
     * @see http://docs.mongodb.org/manual/reference/command/mapReduce/
     * @param JavascriptInterface $map            Map function
     * @param JavascriptInterface $reduce         Reduce function
     * @param string|array|object $out            Output specification
     * @param array               $options        Command options
     * @return MapReduceResult
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
     * @throws UnexpectedValueException if the command response was malformed
     */
    public function mapReduce(JavascriptInterface $map, JavascriptInterface $reduce, $out, array $options = [])
    {
835
        $hasOutputCollection = ! \MongoDB\is_mapreduce_output_inline($out);
836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867

        if ( ! isset($options['readPreference'])) {
            $options['readPreference'] = $this->readPreference;
        }

        // Check if the out option is inline because we will want to coerce a primary read preference if not
        if ($hasOutputCollection) {
            $options['readPreference'] = new ReadPreference(ReadPreference::RP_PRIMARY);
        }

        $server = $this->manager->selectServer($options['readPreference']);

        /* A "majority" read concern is not compatible with inline output, so
         * avoid providing the Collection's read concern if it would conflict.
         */
        if ( ! isset($options['readConcern']) && ! ($hasOutputCollection && $this->readConcern->getLevel() === ReadConcern::MAJORITY) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
            $options['readConcern'] = $this->readConcern;
        }

        if ( ! isset($options['typeMap'])) {
            $options['typeMap'] = $this->typeMap;
        }

        if (! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) {
            $options['writeConcern'] = $this->writeConcern;
        }

        $operation = new MapReduce($this->databaseName, $this->collectionName, $map, $reduce, $out, $options);

        return $operation->execute($server);
    }

868
    /**
869
     * Replaces at most one document matching the filter.
870
     *
871
     * @see ReplaceOne::__construct() for supported options
872
     * @see http://docs.mongodb.org/manual/reference/command/update/
873 874 875
     * @param array|object $filter      Query by which to filter documents
     * @param array|object $replacement Replacement document
     * @param array        $options     Command options
876
     * @return UpdateResult
877 878 879
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
880
     */
Jeremy Mikola's avatar
Jeremy Mikola committed
881
    public function replaceOne($filter, $replacement, array $options = [])
882
    {
883
        if ( ! isset($options['writeConcern'])) {
884
            $options['writeConcern'] = $this->writeConcern;
885 886
        }

887
        $operation = new ReplaceOne($this->databaseName, $this->collectionName, $filter, $replacement, $options);
888 889 890
        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));

        return $operation->execute($server);
891 892 893
    }

    /**
894
     * Updates all documents matching the filter.
895
     *
896
     * @see UpdateMany::__construct() for supported options
897
     * @see http://docs.mongodb.org/manual/reference/command/update/
898 899 900
     * @param array|object $filter  Query by which to filter documents
     * @param array|object $update  Update to apply to the matched documents
     * @param array        $options Command options
901
     * @return UpdateResult
902 903 904
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
905
     */
Jeremy Mikola's avatar
Jeremy Mikola committed
906
    public function updateMany($filter, $update, array $options = [])
907
    {
908
        if ( ! isset($options['writeConcern'])) {
909
            $options['writeConcern'] = $this->writeConcern;
910
        }
911

912
        $operation = new UpdateMany($this->databaseName, $this->collectionName, $filter, $update, $options);
913 914 915
        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));

        return $operation->execute($server);
916
    }
917

918
    /**
919
     * Updates at most one document matching the filter.
920
     *
921
     * @see UpdateOne::__construct() for supported options
922
     * @see http://docs.mongodb.org/manual/reference/command/update/
923 924 925
     * @param array|object $filter  Query by which to filter documents
     * @param array|object $update  Update to apply to the matched document
     * @param array        $options Command options
926
     * @return UpdateResult
927 928 929
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
930
     */
Jeremy Mikola's avatar
Jeremy Mikola committed
931
    public function updateOne($filter, $update, array $options = [])
932
    {
933
        if ( ! isset($options['writeConcern'])) {
934
            $options['writeConcern'] = $this->writeConcern;
935 936
        }

937
        $operation = new UpdateOne($this->databaseName, $this->collectionName, $filter, $update, $options);
938
        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
939

940 941 942
        return $operation->execute($server);
    }

943 944
    /**
     * Create a change stream for watching changes to the collection.
945
     *
946 947 948 949
     * @see Watch::__construct() for supported options
     * @param array $pipeline List of pipeline operations
     * @param array $options  Command options
     * @return ChangeStream
950 951 952 953 954 955 956 957 958 959
     * @throws InvalidArgumentException for parameter/option parsing errors
     */
    public function watch(array $pipeline = [], array $options = [])
    {
        if ( ! isset($options['readPreference'])) {
            $options['readPreference'] = $this->readPreference;
        }

        $server = $this->manager->selectServer($options['readPreference']);

960 961 962 963 964 965 966 967
        /* Although change streams require a newer version of the server than
         * read concerns, perform the usual wire version check before inheriting
         * the collection's read concern. In the event that the server is too
         * old, this makes it more likely that users will encounter an error
         * related to change streams being unsupported instead of an
         * UnsupportedException regarding use of the "readConcern" option from
         * the Aggregate operation class. */
        if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
968 969 970
            $options['readConcern'] = $this->readConcern;
        }

971 972 973 974
        if ( ! isset($options['typeMap'])) {
            $options['typeMap'] = $this->typeMap;
        }

975
        $operation = new Watch($this->manager, $this->databaseName, $this->collectionName, $pipeline, $options);
976

977
        return $operation->execute($server);
978
    }
979 980 981 982

    /**
     * Get a clone of this collection with different options.
     *
983
     * @see Collection::__construct() for supported options
984 985
     * @param array $options Collection constructor options
     * @return Collection
986
     * @throws InvalidArgumentException for parameter/option parsing errors
987 988 989
     */
    public function withOptions(array $options = [])
    {
990 991 992
        $options += [
            'readConcern' => $this->readConcern,
            'readPreference' => $this->readPreference,
993
            'typeMap' => $this->typeMap,
994 995
            'writeConcern' => $this->writeConcern,
        ];
996

997
        return new Collection($this->manager, $this->databaseName, $this->collectionName, $options);
998
    }
999
}