diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml index 30012e46..18961eb7 100644 --- a/.github/workflows/coding-standards.yml +++ b/.github/workflows/coding-standards.yml @@ -8,9 +8,7 @@ on: env: PHP_VERSION: "8.2" - # TODO: change to "stable" once 1.20.0 is released - # DRIVER_VERSION: "stable" - DRIVER_VERSION: "mongodb/mongo-php-driver@master" + DRIVER_VERSION: "stable" jobs: phpcs: diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index ef58d7c5..3a655b5f 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -14,9 +14,7 @@ on: env: PHP_VERSION: "8.2" - # TODO: change to "stable" once 1.20.0 is released - # DRIVER_VERSION: "stable" - DRIVER_VERSION: "mongodb/mongo-php-driver@master" + DRIVER_VERSION: "stable" jobs: psalm: diff --git a/snooty.toml b/snooty.toml index f8b6d380..2d2e7384 100644 --- a/snooty.toml +++ b/snooty.toml @@ -41,6 +41,7 @@ php-library = "MongoDB PHP Library" [constants] php-library = "MongoDB PHP Library" version = "1.20" +source-gh-branch = "v1.x" full-version = "{+version+}.0" extension-short = "PHP extension" mdb-server = "MongoDB Server" diff --git a/source/aggregation.txt b/source/aggregation.txt index 527a9626..2ec29c8f 100644 --- a/source/aggregation.txt +++ b/source/aggregation.txt @@ -47,8 +47,8 @@ The **aggregation pipeline** is the assembly line, **aggregation stages** are th assembly stations, and **operator expressions** are the specialized tools. -Aggregation Versus Find Operations -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Compare Aggregation and Find Operations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You can use find operations to perform the following actions: @@ -72,6 +72,7 @@ Consider the following limitations when performing aggregation operations: - Returned documents cannot violate the :manual:`BSON document size limit ` of 16 megabytes. + - Pipeline stages have a memory limit of 100 megabytes by default. You can exceed this limit by creating an options array that sets the ``allowDiskUse`` option to ``true`` and passing the array to the ``MongoDB\Collection::aggregate()`` method. @@ -82,37 +83,63 @@ Consider the following limitations when performing aggregation operations: ` stage has a strict memory limit of 100 megabytes and ignores the ``allowDiskUse`` option. -.. _php-aggregation-example: +Aggregation APIs +---------------- -Aggregation Example -------------------- +The {+library-short+} provides the following APIs to create aggregation +pipelines: + +- :ref:`php-aggregation-array-api`: Create aggregation pipelines by + passing arrays that specify the aggregation stages. +- :ref:`php-aggregation-builder-api`: Create aggregation pipelines by + using factory methods to make your application more type-safe and debuggable. + +The following sections describe each API and provide examples for +creating aggregation pipelines. -.. note:: +.. _php-aggregation-array-api: - The examples in this guide use the ``restaurants`` collection in the ``sample_restaurants`` - database from the :atlas:`Atlas sample datasets `. To learn how to create a - free MongoDB Atlas cluster and load the sample datasets, see the :atlas:`Get Started with Atlas - ` guide. +Array API +--------- -To perform an aggregation, pass an array containing the pipeline stages to -the ``MongoDB\Collection::aggregate()`` method. +To perform an aggregation, pass an array containing the pipeline stages +as BSON documents to the ``MongoDB\Collection::aggregate()`` method, as +shown in the following code: + +.. code-block:: php + + $pipeline = [ + ['' => ], + ['' => ], + ... + ]; + + $cursor = $collection->aggregate($pipeline); + +The examples in this section use the ``restaurants`` collection in the ``sample_restaurants`` +database from the :atlas:`Atlas sample datasets `. To learn how to create a +free MongoDB Atlas cluster and load the sample datasets, see the :atlas:`Get Started with Atlas +` guide. + +Filter and Group Example +~~~~~~~~~~~~~~~~~~~~~~~~ The following code example produces a count of the number of bakeries in each borough of New York. To do so, it uses an aggregation pipeline that contains the following stages: -- :manual:`$match ` stage to filter for documents - in which the ``cuisine`` field contains the value ``'Bakery'`` +1. :manual:`$match ` stage to filter for documents + in which the ``cuisine`` field contains the value ``'Bakery'`` -- :manual:`$group ` stage to group the matching - documents by the ``borough`` field, accumulating a count of documents for each distinct - value +#. :manual:`$group ` stage to group the matching + documents by the ``borough`` field, accumulating a count of documents for each distinct + value .. io-code-block:: :copyable: .. input:: /includes/aggregation/aggregation.php - :start-after: start-match-group - :end-before: end-match-group + :start-after: start-array-match-group + :end-before: end-array-match-group :language: php :dedent: @@ -141,14 +168,14 @@ and pass the database, collection, and pipeline stages as parameters. Then, pass ``MongoDB\Operation\Aggregate`` object to the ``MongoDB\Collection::explain()`` method. The following example instructs MongoDB to explain the aggregation operation -from the preceding :ref:`php-aggregation-example`: +from the preceding section: .. io-code-block:: :copyable: .. input:: /includes/aggregation/aggregation.php - :start-after: start-explain - :end-before: end-explain + :start-after: start-array-explain + :end-before: end-array-explain :language: php :dedent: @@ -161,6 +188,158 @@ from the preceding :ref:`php-aggregation-example`: "maxIndexedAndSolutionsReached":false,"maxScansToExplodeReached":false,"winningPlan":{ ... } +.. _php-aggregation-builder-api: + +Aggregation Builder +------------------- + +To create an aggregation pipeline by using the Aggregation Builder, +perform the following actions: + +1. Create an array to store the pipeline stages. + +#. For each stage, call the a factory method from the + ``Stage`` that shares the same name as your desired aggregation + stage. For example, to create an ``$unwind`` stage, call the + ``Stage::unwind()`` method. + +#. Within the body of the ``Stage`` method, use methods from other + builder classes such as ``Query``, ``Expression``, or ``Accumulator`` + to express your aggregation specifications. + +The following code demonstrates the template for constructing +aggregation pipelines: + +.. code-block:: php + + $pipeline = [ + Stage::( + + ), + Stage::( + + ), + ... + ]; + + $cursor = $collection->aggregate($pipeline); + +The examples in this section are adapted from the {+mdb-server+} manual. +Each example provides a link to the sample data that you can insert into +your database to test the aggregation operation. + +Filter and Group Example +~~~~~~~~~~~~~~~~~~~~~~~~ + +This example uses the sample data given in the :manual:`Calculate Count, +Sum, and Average ` +section of the ``$group`` stage reference in the Server manual. + +The following code example calculates the total sales amount, average +sales quantity, and sale count for each day in the year 2014. To do so, +it uses an aggregation pipeline that contains the following stages: + +1. :manual:`$match ` stage to + filter for documents that contain a ``date`` field in which the year is + 2014 + +#. :manual:`$group ` stage to + group the documents by date and calculate the total sales amount, + average sales quantity, and sale count for each group + +#. :manual:`$sort ` stage to + sort the results by the total sale amount for each group in descending + order + +.. io-code-block:: + :copyable: + + .. input:: /includes/aggregation/aggregation.php + :start-after: start-builder-match-group + :end-before: end-builder-match-group + :language: php + :dedent: + + .. output:: + :visible: false + + {"_id":"2014-04-04","totalSaleAmount":{"$numberDecimal":"200"},"averageQuantity":15,"count":2} + {"_id":"2014-03-15","totalSaleAmount":{"$numberDecimal":"50"},"averageQuantity":10,"count":1} + {"_id":"2014-03-01","totalSaleAmount":{"$numberDecimal":"40"},"averageQuantity":1.5,"count":2} + +Unwind Embedded Arrays Example +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This example uses the sample data given in the :manual:`Unwind Embedded Arrays +` +section of the ``$unwind`` stage reference in the Server manual. + +The following code example groups sold items by their tags and +calculates the total sales amount for each tag. To do so, +it uses an aggregation pipeline that contains the following stages: + +1. :manual:`$unwind ` stage to + output a separate document for each element in the ``items`` array + +#. :manual:`$unwind ` stage to + output a separate document for each element in the ``items.tags`` arrays + +#. :manual:`$group ` stage to + group the documents by the tag value and calculate the total sales + amount of items that have each tag + +.. io-code-block:: + :copyable: + + .. input:: /includes/aggregation/aggregation.php + :start-after: start-builder-unwind + :end-before: end-builder-unwind + :language: php + :dedent: + + .. output:: + :visible: false + + {"_id":"office","totalSalesAmount":{"$numberDecimal":"1019.60"}} + {"_id":"school","totalSalesAmount":{"$numberDecimal":"104.85"}} + {"_id":"stationary","totalSalesAmount":{"$numberDecimal":"264.45"}} + {"_id":"electronics","totalSalesAmount":{"$numberDecimal":"800.00"}} + {"_id":"writing","totalSalesAmount":{"$numberDecimal":"60.00"}} + +Single Equality Join Example +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This example uses the sample data given in the :manual:`Perform a Single +Equality Join with $lookup +` +section of the ``$lookup`` stage reference in the Server manual. + +The following code example joins the documents from the ``orders`` +collection with the documents from the ``inventory`` collection by using +the ``item`` field from the ``orders`` collection and the ``sku`` field +from the ``inventory`` collection. + +To do so, the example uses an aggregation pipeline that contains a +:manual:`$lookup ` stage that +specifies the collection to retrieve data from and the local and +foreign field names. + +.. io-code-block:: + :copyable: + + .. input:: /includes/aggregation/aggregation.php + :start-after: start-builder-lookup + :end-before: end-builder-lookup + :language: php + :dedent: + + .. output:: + :visible: false + + {"_id":1,"item":"almonds","price":12,"quantity":2,"inventory_docs":[{"_id":1,"sku":"almonds","description":"product 1","instock":120}]} + {"_id":2,"item":"pecans","price":20,"quantity":1,"inventory_docs":[{"_id":4,"sku":"pecans","description":"product 4","instock":70}]} + {"_id":3,"inventory_docs":[{"_id":5,"sku":null,"description":"Incomplete"},{"_id":6}]} + Additional Information ---------------------- @@ -169,6 +348,11 @@ pipelines, see `Complex Aggregation Pipelines with Vanilla PHP and MongoDB `__ in the MongoDB Developer Center. +To view more examples of aggregation pipelines built by using the Aggregation +Builder, see the :github:`Stage class test suite +` in the +{+library-short+} source code on GitHub. + MongoDB Server Manual ~~~~~~~~~~~~~~~~~~~~~ diff --git a/source/aggregation/atlas-search.txt b/source/aggregation/atlas-search.txt index d47ac750..8846459f 100644 --- a/source/aggregation/atlas-search.txt +++ b/source/aggregation/atlas-search.txt @@ -22,8 +22,7 @@ Overview In this guide, you can learn how to perform searches on your documents by using the Atlas Search feature. The {+library-short+} allows you to -perform Atlas Search queries by using the :ref:`Aggregation Builder API -`. +perform Atlas Search queries by using the :ref:`php-aggregation-builder-api`. .. note:: Deployment Compatibility @@ -66,12 +65,12 @@ Search queries by using the Aggregation Builder: To create a ``$search`` stage in your aggregation pipeline, perform the following actions: -1. Create an array to store the pipeline stages +1. Create an array to store the pipeline stages. -#. Call the ``Stage::search()`` method to create the Atlas Search stage +#. Call the ``Stage::search()`` method to create the Atlas Search stage. #. Within the body of the ``search()`` method, use methods from the - ``Search`` builder class to construct your Search query criteria + ``Search`` builder class to construct your Search query criteria. The following code demonstrates the template for constructing basic Atlas Search queries: diff --git a/source/aggregation/vector-search.txt b/source/aggregation/vector-search.txt index b9ca56dc..c1396ac7 100644 --- a/source/aggregation/vector-search.txt +++ b/source/aggregation/vector-search.txt @@ -22,8 +22,7 @@ Overview In this guide, you can learn how to perform searches on your documents by using the Atlas Vector Search feature. The {+library-short+} allows you to -perform Atlas Vector Search queries by using the :ref:`Aggregation Builder API -`. +perform Atlas Vector Search queries by using the :ref:`php-aggregation-builder-api`. .. note:: Deployment Compatibility @@ -67,13 +66,13 @@ Search queries by using the Aggregation Builder: To create a ``$vectorSearch`` stage in your aggregation pipeline, perform the following actions: -1. Create an array to store the pipeline stages +1. Create an array to store the pipeline stages. #. Call the ``Stage::vectorSearch()`` method to create the Atlas Vector - Search stage + Search stage. #. Within the body of the ``vectorSearch()`` method, specify the - criteria for your vector query + criteria for your vector query. The following code demonstrates the template for constructing basic Atlas Search queries: diff --git a/source/includes/aggregation/aggregation.php b/source/includes/aggregation/aggregation.php index e8b1a7f9..5aa56684 100644 --- a/source/includes/aggregation/aggregation.php +++ b/source/includes/aggregation/aggregation.php @@ -1,4 +1,5 @@ ['cuisine' => 'Bakery']], ['$group' => ['_id' => '$borough', 'count' => ['$sum' => 1]]], @@ -19,10 +20,10 @@ foreach ($cursor as $doc) { echo json_encode($doc), PHP_EOL; } -// end-match-group +// end-array-match-group // Performs the same aggregation operation as above but asks MongoDB to explain it -// start-explain +// start-array-explain $pipeline = [ ['$match' => ['cuisine' => 'Bakery']], ['$group' => ['_id' => '$borough', 'count' => ['$sum' => 1]]], @@ -36,5 +37,79 @@ $result = $collection->explain($aggregate); echo json_encode($result), PHP_EOL; -// end-explain +// end-array-explain + +// start-builder-match-group +$pipeline = [ + MongoDB\Builder\Stage::match( + date: [ + MongoDB\Builder\Query::gte(new MongoDB\BSON\UTCDateTime(new DateTimeImmutable('2014-01-01'))), + MongoDB\Builder\Query::lt(new MongoDB\BSON\UTCDateTime(new DateTimeImmutable('2015-01-01'))), + ], + ), + MongoDB\Builder\Stage::group( + _id: MongoDB\Builder\Expression::dateToString(MongoDB\Builder\Expression::dateFieldPath('date'), '%Y-%m-%d'), + totalSaleAmount: MongoDB\Builder\Accumulator::sum( + MongoDB\Builder\Expression::multiply( + MongoDB\Builder\Expression::numberFieldPath('price'), + MongoDB\Builder\Expression::numberFieldPath('quantity'), + ), + ), + averageQuantity: MongoDB\Builder\Accumulator::avg( + MongoDB\Builder\Expression::numberFieldPath('quantity'), + ), + count: MongoDB\Builder\Accumulator::sum(1), + ), + MongoDB\Builder\Stage::sort( + totalSaleAmount: MongoDB\Builder\Type\Sort::Desc, + ), +]; + +$cursor = $collection->aggregate($pipeline); + +foreach ($cursor as $doc) { + echo json_encode($doc), PHP_EOL; +} +// end-builder-match-group + +// start-builder-unwind +$pipeline = [ + MongoDB\Builder\Stage::unwind(MongoDB\Builder\Expression::arrayFieldPath('items')), + MongoDB\Builder\Stage::unwind(MongoDB\Builder\Expression::arrayFieldPath('items.tags')), + MongoDB\Builder\Stage::group( + _id: MongoDB\Builder\Expression::fieldPath('items.tags'), + totalSalesAmount: MongoDB\Builder\Accumulator::sum( + MongoDB\Builder\Expression::multiply( + MongoDB\Builder\Expression::numberFieldPath('items.price'), + MongoDB\Builder\Expression::numberFieldPath('items.quantity'), + ), + ), + ), +]; + +$cursor = $collection->aggregate($pipeline); + +foreach ($cursor as $doc) { + echo json_encode($doc), PHP_EOL; +} +// end-builder-unwind + +$collection = $client->db->orders; + +// start-builder-lookup +$pipeline = [ + MongoDB\Builder\Stage::lookup( + from: 'inventory', + localField: 'item', + foreignField: 'sku', + as: 'inventory_docs', + ), +]; + +/* Performs the aggregation on the orders collection */ +$cursor = $collection->aggregate($pipeline); +foreach ($cursor as $doc) { + echo json_encode($doc), PHP_EOL; +} +// end-builder-lookup diff --git a/source/indexes/atlas-search-index.txt b/source/indexes/atlas-search-index.txt index c3d8fce4..8b6a1c50 100644 --- a/source/indexes/atlas-search-index.txt +++ b/source/indexes/atlas-search-index.txt @@ -86,13 +86,11 @@ Vector Search indexes in one call: :end-before: end-create-multiple-indexes After you create Atlas Search or Atlas Vector Search indexes, you can -perform the corresponding query types on your documents. +perform the corresponding query types on your documents. To learn more, +see the following guides: -.. - TODO uncomment when https://github.com/mongodb/docs-php-library/pull/197 is merged - To learn more, see the following guides: - - :ref:`php-atlas-search` - - :ref:`php-vector-search` +- :ref:`php-atlas-search` guide +- :ref:`php-vector-search` guide .. _php-atlas-search-index-list: diff --git a/source/whats-new.txt b/source/whats-new.txt index 62b87004..ab6a7a9e 100644 --- a/source/whats-new.txt +++ b/source/whats-new.txt @@ -38,6 +38,11 @@ What's New in 1.21 The {+library-short+} v1.21 release includes the following features, improvements, and fixes: +- Introduces the Aggregation Builder, an API to build aggregation + pipelines in a more type-safe way. To learn more and + view examples, see the :ref:`php-aggregation-builder-api` section of + the Aggregation guide. + - Adds the following methods: - :phpmethod:`MongoDB\Client::getDatabase()`: alias for :phpmethod:`MongoDB\Client::selectDatabase()`