===============
CRUD Operations
===============

.. default-domain:: mongodb

.. contents:: On this page
   :local:
   :backlinks: none
   :depth: 2
   :class: singlecol


CRUD operations *create*, *read*, *update*, and *delete* documents. The
|php-library|'s :phpclass:`MongoDB\\Collection` class implements MongoDB's
cross-driver `CRUD specification
<https://github.com/mongodb/specifications/blob/master/source/crud/crud.rst>`_,
providing access to methods for inserting, finding, updating, and deleting
documents in MongoDB.

This document provides a general introduction to inserting, querying, updating,
and deleting documents using the |php-library|. The MongoDB Manual's
:manual:`CRUD Section </crud>` provides a more thorough introduction to CRUD
operations with MongoDB.

Insert Documents
----------------

Insert One Document
~~~~~~~~~~~~~~~~~~~

The :phpmethod:`MongoDB\\Collection::insertOne()` method inserts a single
document into MongoDB and returns an instance of
:phpclass:`MongoDB\\InsertOneResult`, which you can use to access the ID of the
inserted document.

.. this uses the insertOne example from the method reference:

.. include:: /reference/method/MongoDBCollection-insertOne.txt
   :start-after: start-crud-include
   :end-before: end-crud-include

The output includes the ID of the inserted document.

If you include an ``_id`` value when inserting a document, MongoDB checks to
ensure that the ``_id`` value is unique for the collection. If the ``_id`` value
is not unique, the insert operation fails due to a duplicate key error.

The following example inserts a document while specifying the value for the
``_id``:

.. code-block:: php

   <?php

   $collection = (new MongoDB\Client)->test->users;

   $insertOneResult = $collection->insertOne(['_id' => 1, 'name' => 'Alice']);

   printf("Inserted %d document(s)\n", $insertOneResult->getInsertedCount());

   var_dump($insertOneResult->getInsertedId());

The output would then resemble::

   Inserted 1 document(s)
   int(1)

.. seealso:: :phpmethod:`MongoDB\\Collection::insertOne()`

Insert Many Documents
~~~~~~~~~~~~~~~~~~~~~

The :phpmethod:`MongoDB\\Collection::insertMany()` method allows you to insert
multiple documents in one write operation and returns an instance of
:phpclass:`MongoDB\\InsertManyResult`, which you can use to access the IDs of
the inserted documents.

.. this uses the insertMany example from the method reference:

.. include:: /reference/method/MongoDBCollection-insertMany.txt
   :start-after: start-crud-include
   :end-before: end-crud-include

.. seealso:: :phpmethod:`MongoDB\\Collection::insertMany()`

Query Documents
---------------

The |php-library| provides the :phpmethod:`MongoDB\\Collection::findOne()` and
:phpmethod:`MongoDB\\Collection::find()` methods for querying documents and the
:phpmethod:`MongoDB\\Collection::aggregate()` method for performing
:manual:`aggregation operations </core/aggregation-pipeline>`.

.. include:: /includes/extracts/note-bson-comparison.rst

Find One Document
~~~~~~~~~~~~~~~~~

:phpmethod:`MongoDB\\Collection::findOne()` returns the :term:`first document
<natural order>` that matches the query or ``null`` if no document matches the
query.

The following example searches for the document with ``_id`` of ``"94301"``:

.. code-block:: php

   <?php

   $collection = (new MongoDB\Client)->test->zips;

   $document = $collection->findOne(['_id' => '94301']);

   var_dump($document);

The output would then resemble::

   object(MongoDB\Model\BSONDocument)#13 (1) {
     ["storage":"ArrayObject":private]=>
     array(5) {
       ["_id"]=>
       string(5) "94301"
       ["city"]=>
       string(9) "PALO ALTO"
       ["loc"]=>
       object(MongoDB\Model\BSONArray)#12 (1) {
         ["storage":"ArrayObject":private]=>
         array(2) {
           [0]=>
           float(-122.149685)
           [1]=>
           float(37.444324)
         }
       }
       ["pop"]=>
       int(15965)
       ["state"]=>
       string(2) "CA"
     }
   }

.. note::

   The criteria in this example matched an ``_id`` with a string value of
   ``"94301"``. The same criteria would not have matched a document with an
   integer value of ``94301`` due to MongoDB's :manual:`comparison rules for
   BSON types </reference/bson-type-comparison-order>`. Similarly, users should
   use a :php:`MongoDB\\BSON\\ObjectId <class.mongodb-bson-objectid>` object
   when matching an ``_id`` with an :manual:`ObjectId </reference/object-id/>`
   value, as strings and ObjectIds are not directly comparable.

.. seealso:: :phpmethod:`MongoDB\\Collection::findOne()`

.. _php-find-many-documents:

Find Many Documents
~~~~~~~~~~~~~~~~~~~

:phpmethod:`MongoDB\\Collection::find()` returns a
:php:`MongoDB\\Driver\\Cursor <mongodb-driver-cursor>` object, which you can
iterate upon to access all matched documents.

The following example lists the documents in the ``zips`` collection with the
specified city and state values:

.. code-block:: php

   <?php

   $collection = (new MongoDB\Client)->test->zips;

   $cursor = $collection->find(['city' => 'JERSEY CITY', 'state' => 'NJ']);

   foreach ($cursor as $document) {
       echo $document['_id'], "\n";
   }

The output would resemble::

   07302
   07304
   07305
   07306
   07307
   07310

.. seealso:: :phpmethod:`MongoDB\\Collection::find()`

.. _php-query-projection:

Query Projection
~~~~~~~~~~~~~~~~

By default, queries in MongoDB return all fields in matching documents. To limit
the amount of data that MongoDB sends to applications, you can include a
:manual:`projection document </tutorial/project-fields-from-query-results>` in
the query operation.

.. note::

   MongoDB includes the ``_id`` field by default unless you explicitly exclude
   it in a projection document.

The following example finds restaurants based on the ``cuisine`` and ``borough``
fields and uses a :manual:`projection
</tutorial/project-fields-from-query-results>` to limit the fields that are
returned. It also limits the results to 5 documents.

.. code-block:: php

   <?php

   $collection = (new MongoDB\Client)->test->restaurants;

   $cursor = $collection->find(
       [
           'cuisine' => 'Italian',
           'borough' => 'Manhattan',
       ],
       [
           'projection' => [
               'name' => 1,
               'borough' => 1,
               'cuisine' => 1,
           ],
           'limit' => 4,
       ]
   );

   foreach($cursor as $restaurant) {
      var_dump($restaurant);
   };

The output would then resemble::

   object(MongoDB\Model\BSONDocument)#10 (1) {
     ["storage":"ArrayObject":private]=>
     array(4) {
       ["_id"]=>
       object(MongoDB\BSON\ObjectId)#8 (1) {
         ["oid"]=>
         string(24) "576023c6b02fa9281da3f983"
       }
       ["borough"]=>
       string(9) "Manhattan"
       ["cuisine"]=>
       string(7) "Italian"
       ["name"]=>
       string(23) "Isle Of Capri Resturant"
     }
   }
   object(MongoDB\Model\BSONDocument)#13 (1) {
     ["storage":"ArrayObject":private]=>
     array(4) {
       ["_id"]=>
       object(MongoDB\BSON\ObjectId)#12 (1) {
         ["oid"]=>
         string(24) "576023c6b02fa9281da3f98d"
       }
       ["borough"]=>
       string(9) "Manhattan"
       ["cuisine"]=>
       string(7) "Italian"
       ["name"]=>
       string(18) "Marchis Restaurant"
     }
   }
   object(MongoDB\Model\BSONDocument)#8 (1) {
     ["storage":"ArrayObject":private]=>
     array(4) {
       ["_id"]=>
       object(MongoDB\BSON\ObjectId)#10 (1) {
         ["oid"]=>
         string(24) "576023c6b02fa9281da3f99b"
       }
       ["borough"]=>
       string(9) "Manhattan"
       ["cuisine"]=>
       string(7) "Italian"
       ["name"]=>
       string(19) "Forlinis Restaurant"
     }
   }
   object(MongoDB\Model\BSONDocument)#12 (1) {
     ["storage":"ArrayObject":private]=>
     array(4) {
       ["_id"]=>
       object(MongoDB\BSON\ObjectId)#13 (1) {
         ["oid"]=>
         string(24) "576023c6b02fa9281da3f9a8"
       }
       ["borough"]=>
       string(9) "Manhattan"
       ["cuisine"]=>
       string(7) "Italian"
       ["name"]=>
       string(22) "Angelo Of Mulberry St."
     }
   }

Limit, Sort, and Skip Options
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

In addition to :ref:`projection criteria <php-query-projection>`, you can
specify options to limit, sort, and skip documents during queries.

The following example uses the ``limit`` and ``sort`` options to query for the
five most populous zip codes in the United States:

.. code-block:: php

   <?php

   $collection = (new MongoDB\Client)->test->zips;

   $cursor = $collection->find(
       [],
       [
           'limit' => 5,
           'sort' => ['pop' => -1],
       ]
   );

   foreach ($cursor as $document) {
       printf("%s: %s, %s\n", $document['_id'], $document['city'], $document['state']);
   }

The output would then resemble::

   60623: CHICAGO, IL
   11226: BROOKLYN, NY
   10021: NEW YORK, NY
   10025: NEW YORK, NY
   90201: BELL GARDENS, CA

Regular Expressions
~~~~~~~~~~~~~~~~~~~

Filter criteria may include regular expressions, either by using the
:php:`MongoDB\\BSON\\Regex <mongodb-bson-regex>` class directory or the
:query:`$regex` operator.

The following example lists documents in the ``zips`` collection where the city
name starts with "garden" and the state is Texas:

.. code-block:: php

   <?php

   $collection = (new MongoDB\Client)->test->zips;

   $cursor = $collection->find([
       'city' => new MongoDB\BSON\Regex('^garden', 'i'),
       'state' => 'TX',
   ]);

   foreach ($cursor as $document) {
      printf("%s: %s, %s\n", $document['_id'], $document['city'], $document['state']);
   }

The output would then resemble::

   78266: GARDEN RIDGE, TX
   79739: GARDEN CITY, TX
   79758: GARDENDALE, TX

An equivalent filter could be constructed using the :query:`$regex` operator:

.. code-block:: php

   [
       'city' => ['$regex' => '^garden', '$options' => 'i'],
       'state' => 'TX',
   ]

.. seealso:: :manual:`$regex </reference/operator/query/regex>` in the MongoDB manual

Although MongoDB's regular expression syntax is not exactly the same as PHP's
:php:`PCRE <manual/en/book.pcre.php>` syntax, :php:`preg_quote() <preg_quote>`
may be used to escape special characters that should be matched as-is. The
following example finds restaurants whose name starts with "(Library)":

.. code-block:: php

   <?php

   $collection = (new MongoDB\Client)->test->restaurants;

   $cursor = $collection->find([
       'name' => new MongoDB\BSON\Regex('^' . preg_quote('(Library)')),
   ]);

.. _php-aggregation:

Complex Queries with Aggregation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

MongoDB's :manual:`Aggregation Framework </core/aggregation-pipeline>` allows
you to issue complex queries that filter, transform, and group collection data.
The |php-library|\'s :phpmethod:`MongoDB\\Collection::aggregate()` method
returns a :php:`Traversable <traversable>` object, which you can iterate upon to
access the results of the aggregation operation. Refer to the
:phpmethod:`MongoDB\\Collection::aggregate()` method's :ref:`behavior
reference <php-coll-agg-method-behavior>` for more about the method's output.

The following example lists the 5 US states with the most zip codes associated
with them:

.. code-block:: php

   <?php

   $collection = (new MongoDB\Client)->test->zips;

   $cursor = $collection->aggregate([
       ['$group' => ['_id' => '$state', 'count' => ['$sum' => 1]]],
       ['$sort' => ['count' => -1]],
       ['$limit' => 5],
   ]);

   foreach ($cursor as $state) {
       printf("%s has %d zip codes\n", $state['_id'], $state['count']);
   }

The output would then resemble::

   TX has 1671 zip codes
   NY has 1595 zip codes
   CA has 1516 zip codes
   PA has 1458 zip codes
   IL has 1237 zip codes

.. seealso:: :phpmethod:`MongoDB\\Collection::aggregate()`

Update Documents
----------------

Update One Document
~~~~~~~~~~~~~~~~~~~

Use the :phpmethod:`MongoDB\\Collection::updateOne()` method to update a single
document matching a filter. :phpmethod:`MongoDB\\Collection::updateOne()`
returns a :phpclass:`MongoDB\\UpdateResult` object, which you can use to access
statistics about the update operation.

Update methods have two required parameters: the query filter that identifies
the document or documents to update, and an update document that specifies what
updates to perform. The :phpmethod:`MongoDB\\Collection::updateOne()` reference
describes each parameter in detail.

The following example inserts two documents into an empty ``users`` collection
in the ``test`` database using the :phpmethod:`MongoDB\\Collection::insertOne()`
method, and then updates the documents where the value for the ``state`` field
is ``"ny"`` to include a ``country`` field set to ``"us"``:

.. code-block:: php

   <?php

   $collection = (new MongoDB\Client)->test->users;
   $collection->drop();

   $collection->insertOne(['name' => 'Bob', 'state' => 'ny']);
   $collection->insertOne(['name' => 'Alice', 'state' => 'ny']);
   $updateResult = $collection->updateOne(
       ['state' => 'ny'],
       ['$set' => ['country' => 'us']]
   );

   printf("Matched %d document(s)\n", $updateResult->getMatchedCount());
   printf("Modified %d document(s)\n", $updateResult->getModifiedCount());

Since the update operation uses the
:phpmethod:`MongoDB\\Collection::updateOne()` method, which updates the first
document to match the filter criteria, the results would then resemble::

   Matched 1 document(s)
   Modified 1 document(s)

It is possible for a document to match the filter but *not be modified* by an
update, as is the case where the update sets a field's value to its existing
value, as in this example:

.. code-block:: php

   <?php

   $collection = (new MongoDB\Client)->test->users;
   $collection->drop();

   $collection->insertOne(['name' => 'Bob', 'state' => 'ny']);
   $updateResult = $collection->updateOne(
       ['name' => 'Bob'],
       ['$set' => ['state' => 'ny']]
   );

   printf("Matched %d document(s)\n", $updateResult->getMatchedCount());
   printf("Modified %d document(s)\n", $updateResult->getModifiedCount());

The number of matched documents and the number of *modified* documents would
therefore not be equal, and the output from the operation would resemble::

   Matched 1 document(s)
   Modified 0 document(s)

.. seealso::

   - :phpmethod:`MongoDB\\Collection::updateOne()`
   - :phpmethod:`MongoDB\\Collection::findOneAndUpdate()`

Update Many Documents
~~~~~~~~~~~~~~~~~~~~~

:phpmethod:`MongoDB\\Collection::updateMany()` updates one or more documents
matching the filter criteria and returns a :phpclass:`MongoDB\\UpdateResult`
object, which you can use to access statistics about the update operation.

Update methods have two required parameters: the query filter that identifies
the document or documents to update, and an update document that specifies what
updates to perform. The :phpmethod:`MongoDB\\Collection::updateMany()` reference
describes each parameter in detail.

The following example inserts three documents into an empty ``users`` collection
in the ``test`` database and then uses the :query:`$set` operator to update the
documents matching the filter criteria to include the ``country`` field with
value ``"us"``:

.. code-block:: php

   <?php

   $collection = (new MongoDB\Client)->test->users;
   $collection->drop();

   $collection->insertOne(['name' => 'Bob', 'state' => 'ny', 'country' => 'us']);
   $collection->insertOne(['name' => 'Alice', 'state' => 'ny']);
   $collection->insertOne(['name' => 'Sam', 'state' => 'ny']);
   $updateResult = $collection->updateMany(
       ['state' => 'ny'],
       ['$set' => ['country' => 'us']]
   );

   printf("Matched %d document(s)\n", $updateResult->getMatchedCount());
   printf("Modified %d document(s)\n", $updateResult->getModifiedCount());

If an update operation results in no change to a document, such as setting the
value of the field to its current value, the number of modified documents can be
less than the number of *matched* documents. Since the update document with
``name`` of ``"Bob"`` results in no changes to the document, the output of the
operation therefore resembles::

   Matched 3 document(s)
   Modified 2 document(s)

.. seealso:: :phpmethod:`MongoDB\\Collection::updateMany()`

Replace Documents
~~~~~~~~~~~~~~~~~

Replacement operations are similar to update operations, but instead of updating
a document to include new fields or new field values, a replacement operation
replaces the entire document with a new document, but retains the original
document's ``_id`` value.

The :phpmethod:`MongoDB\\Collection::replaceOne()` method replaces a single
document that matches the filter criteria and returns an instance of
:phpclass:`MongoDB\\UpdateResult`, which you can use to access statistics about
the replacement operation.

:phpmethod:`MongoDB\\Collection::replaceOne()` has two required parameters: the
query filter that identifies the document or documents to replace, and a
replacement document that will replace the original document in MongoDB. The
:phpmethod:`MongoDB\\Collection::replaceOne()` reference describes each
parameter in detail.

.. important:: 

   Replacement operations replace all of the fields in a document except the
   ``_id`` value. To avoid accidentally overwriting or deleting desired fields,
   use the :phpmethod:`MongoDB\\Collection::updateOne()` or
   :phpmethod:`MongoDB\\Collection::updateMany()` methods to update individual
   fields in a document rather than replacing the entire document.

The following example inserts one document into an empty ``users`` collection in
the ``test`` database, and then replaces that document with a new one:

.. code-block:: php

   <?php

   $collection = (new MongoDB\Client)->test->users;
   $collection->drop();

   $collection->insertOne(['name' => 'Bob', 'state' => 'ny']);
   $updateResult = $collection->replaceOne(
       ['name' => 'Bob'],
       ['name' => 'Robert', 'state' => 'ca']
   );

   printf("Matched %d document(s)\n", $updateResult->getMatchedCount());
   printf("Modified %d document(s)\n", $updateResult->getModifiedCount());

The output would then resemble::

   Matched 1 document(s)
   Modified 1 document(s)

.. seealso:: 

   - :phpmethod:`MongoDB\\Collection::replaceOne()`
   - :phpmethod:`MongoDB\\Collection::findOneAndReplace()`

Upsert
~~~~~~

Update and replace operations support an :manual:`upsert
</tutorial/update-documents/#upsert-option>` option. When ``upsert`` is ``true``
*and* no documents match the specified filter, the operation creates a new
document and inserts it. If there *are* matching documents, then the operation
modifies or replaces the matching document or documents.

When a document is upserted, the ID is accessible via
:phpmethod:`MongoDB\\UpdateResult::getUpsertedId()`.

The following example uses :phpmethod:`MongoDB\\Collection::updateOne()` with
the ``upsert`` option set to ``true`` and an empty ``users`` collection in the
``test`` database, therefore inserting the document into the database:

.. code-block:: php

   <?php

   $collection = (new MongoDB\Client)->test->users;
   $collection->drop();

   $updateResult = $collection->updateOne(
       ['name' => 'Bob'],
       ['$set' => ['state' => 'ny']],
       ['upsert' => true]
   );

   printf("Matched %d document(s)\n", $updateResult->getMatchedCount());
   printf("Modified %d document(s)\n", $updateResult->getModifiedCount());
   printf("Upserted %d document(s)\n", $updateResult->getUpsertedCount());

   $upsertedDocument = $collection->findOne([
       '_id' => $updateResult->getUpsertedId(),
   ]);

   var_dump($upsertedDocument);

The output would then resemble::

   Matched 0 document(s)
   Modified 0 document(s)
   Upserted 1 document(s)
   object(MongoDB\Model\BSONDocument)#16 (1) {
     ["storage":"ArrayObject":private]=>
     array(3) {
       ["_id"]=>
       object(MongoDB\BSON\ObjectId)#15 (1) {
         ["oid"]=>
         string(24) "57509c4406d7241dad86e7c3"
       }
       ["name"]=>
       string(3) "Bob"
       ["state"]=>
       string(2) "ny"
     }
   }

Delete Documents
----------------

Delete One Document
~~~~~~~~~~~~~~~~~~~

The :phpmethod:`MongoDB\\Collection::deleteOne()` method deletes a single
document that matches the filter criteria and returns a
:phpclass:`MongoDB\\DeleteResult`, which you can use to access statistics about
the delete operation.

If multiple documents match the filter criteria,
:phpmethod:`MongoDB\\Collection::deleteOne()` deletes the :term:`first
<natural order>` matching document.

:phpmethod:`MongoDB\\Collection::deleteOne()` has one required parameter: a
query filter that specifies the document to delete. Refer to the
:phpmethod:`MongoDB\\Collection::deleteOne()` reference for full method
documentation.

The following operation deletes the first document where the ``state`` field's
value is ``"ny"``:

.. code-block:: php

   <?php

   $collection = (new MongoDB\Client)->test->users;
   $collection->drop();

   $collection->insertOne(['name' => 'Bob', 'state' => 'ny']);
   $collection->insertOne(['name' => 'Alice', 'state' => 'ny']);
   $deleteResult = $collection->deleteOne(['state' => 'ny']);

   printf("Deleted %d document(s)\n", $deleteResult->getDeletedCount());

The output would then resemble::

   Deleted 1 document(s)

.. seealso:: :phpmethod:`MongoDB\\Collection::deleteOne()`

Delete Many Documents
~~~~~~~~~~~~~~~~~~~~~

:phpmethod:`MongoDB\\Collection::deleteMany()` deletes all of the documents that
match the filter criteria and returns a :phpclass:`MongoDB\\DeleteResult`, which
you can use to access statistics about the delete operation.

:phpmethod:`MongoDB\\Collection::deleteMany()` has one required parameter: a
query filter that specifies the document to delete. Refer to the
:phpmethod:`MongoDB\\Collection::deleteMany()` reference for full method
documentation.

The following operation deletes all of the documents where the ``state`` field's
value is ``"ny"``:

.. code-block:: php

   <?php

   $collection = (new MongoDB\Client)->test->users;
   $collection->drop();

   $collection->insertOne(['name' => 'Bob', 'state' => 'ny']);
   $collection->insertOne(['name' => 'Alice', 'state' => 'ny']);
   $deleteResult = $collection->deleteMany(['state' => 'ny']);

   printf("Deleted %d document(s)\n", $deleteResult->getDeletedCount());

The output would then resemble::

   Deleted 2 document(s)

.. seealso:: :phpmethod:`MongoDB\\Collection::deleteMany()`
