diff --git a/.github/workflows/build-ci.yml b/.github/workflows/build-ci.yml index 6ff13a7f0..6fec7bb28 100644 --- a/.github/workflows/build-ci.yml +++ b/.github/workflows/build-ci.yml @@ -34,20 +34,24 @@ jobs: - "11.*" - "12.*" driver: - - 1 + - 2 include: - php: "8.1" laravel: "10.*" mongodb: "5.0" mode: "low-deps" os: "ubuntu-latest" - driver: 1.x - driver_version: "1.21.0" + driver: 1 + - php: "8.3" + laravel: "11.*" + mongodb: "8.0" + os: "ubuntu-latest" + driver: 1 - php: "8.4" laravel: "12.*" mongodb: "8.0" os: "ubuntu-latest" - driver: 2 + driver: 1 exclude: - php: "8.1" laravel: "11.*" diff --git a/docs/compatibility.txt b/docs/compatibility.txt index 5580e6856..5197deab7 100644 --- a/docs/compatibility.txt +++ b/docs/compatibility.txt @@ -15,7 +15,7 @@ Compatibility :class: singlecol .. meta:: - :keywords: laravel 9, laravel 10, laravel 11, laravel 12, 4.0, 4.1, 4.2, 5.0, 5.1, 5.2 + :keywords: laravel 9, laravel 10, laravel 11, laravel 12, 4.0, 4.1, 4.2, 5.0, 5.1, 5.2, 5.3 Laravel Compatibility --------------------- diff --git a/docs/fundamentals/connection/connection-options.txt b/docs/fundamentals/connection/connection-options.txt index 03e98ed06..1a2cdb085 100644 --- a/docs/fundamentals/connection/connection-options.txt +++ b/docs/fundamentals/connection/connection-options.txt @@ -32,6 +32,7 @@ This guide covers the following topics: - :ref:`laravel-connection-auth-options` - :ref:`laravel-driver-options` +- :ref:`laravel-disable-id-alias` .. _laravel-connection-auth-options: @@ -349,3 +350,47 @@ item, as shown in the following example: See the `$driverOptions: array `__ section of the {+php-library+} documentation for a list of driver options. + +.. _laravel-disable-id-alias: + +Disable Use of id Field Name Conversion +--------------------------------------- + +Starting in {+odm-long+} v5.0, ``id`` is an alias for the ``_id`` field +in MongoDB documents, and the library automatically converts ``id`` +to ``_id`` for both top level and embedded fields when querying and +storing data. + +When using {+odm-long+} v5.3 or later, you can disable the automatic +conversion of ``id`` to ``_id`` for embedded documents. To do so, +perform either of the following actions: + +1. Set the ``rename_embedded_id_field`` setting to ``false`` in your + ``config/database.php`` file: + + .. code-block:: php + :emphasize-lines: 6 + + 'connections' => [ + 'mongodb' => [ + 'dsn' => 'mongodb+srv://mongodb0.example.com/', + 'driver' => 'mongodb', + 'database' => 'sample_mflix', + 'rename_embedded_id_field' => false, + // Other settings + ], + ], + +#. Pass ``false`` to the ``setRenameEmbeddedIdField()`` method in your + application: + + .. code-block:: php + + DB::connection('mongodb')->setRenameEmbeddedIdField(false); + +.. important:: + + We recommend using this option only to provide backwards + compatibility with existing document schemas. In new projects, + avoid using ``id`` for field names in embedded documents so that + you can maintain {+odm-long+}'s default behavior. diff --git a/docs/includes/framework-compatibility-laravel.rst b/docs/includes/framework-compatibility-laravel.rst index c642a6763..e8d59469d 100644 --- a/docs/includes/framework-compatibility-laravel.rst +++ b/docs/includes/framework-compatibility-laravel.rst @@ -8,7 +8,7 @@ - Laravel 10.x - Laravel 9.x - * - 5.2 + * - 5.2 to 5.3 - ✓ - ✓ - ✓ diff --git a/docs/query-builder.txt b/docs/query-builder.txt index 68a9b2102..a73d5e791 100644 --- a/docs/query-builder.txt +++ b/docs/query-builder.txt @@ -195,7 +195,7 @@ the value of the ``title`` field is ``"Back to the Future"``: :start-after: begin query orWhere :end-before: end query orWhere -.. note:: +.. note:: id Alias You can use the ``id`` alias in your queries to represent the ``_id`` field in MongoDB documents, as shown in the preceding @@ -208,6 +208,9 @@ the value of the ``title`` field is ``"Back to the Future"``: Because of this behavior, you cannot have two separate ``id`` and ``_id`` fields in your documents. + To learn how to disable this behavior for embedded documents, see the + :ref:`laravel-disable-id-alias` section of the Connection Options guide. + .. _laravel-query-builder-logical-and: Logical AND Example diff --git a/docs/upgrade.txt b/docs/upgrade.txt index a87d314a2..3c6ec40a4 100644 --- a/docs/upgrade.txt +++ b/docs/upgrade.txt @@ -127,6 +127,11 @@ This library version introduces the following breaking changes: method results before hydrating a Model instance. When passing a complex query filter, use the ``DB::where()`` method instead of ``Model::raw()``. + Starting in v5.3, you can disable automatic conversion of ``id`` to + ``_id`` for embedded documents. To learn more, see the + :ref:`laravel-disable-id-alias` section of the Connection Options + guide. + - Removes support for the ``$collection`` property. The following code shows how to assign a MongoDB collection to a variable in your ``User`` class in older versions compared to v5.0: diff --git a/src/Query/Builder.php b/src/Query/Builder.php index 7d0fdce74..6823998fd 100644 --- a/src/Query/Builder.php +++ b/src/Query/Builder.php @@ -1795,7 +1795,7 @@ private function aliasIdForQuery(array $values, bool $root = true): array } // ".id" subfield are alias for "._id" - if (str_ends_with($key, '.id') && ($root || $this->connection->getRenameEmbeddedIdField())) { + if (str_ends_with($key, '.id') && $this->connection->getRenameEmbeddedIdField()) { $newkey = substr($key, 0, -3) . '._id'; if (array_key_exists($newkey, $values) && $value !== $values[$newkey]) { throw new InvalidArgumentException(sprintf('Cannot have both "%s" and "%s" fields.', $key, $newkey)); diff --git a/tests/Query/BuilderTest.php b/tests/Query/BuilderTest.php index 6e68d42c7..7595976f3 100644 --- a/tests/Query/BuilderTest.php +++ b/tests/Query/BuilderTest.php @@ -1599,29 +1599,86 @@ public static function getEloquentMethodsNotSupported() yield 'orWhereIntegerNotInRaw' => [fn (Builder $builder) => $builder->orWhereIntegerNotInRaw('id', ['1a', 2])]; } - public function testRenameEmbeddedIdFieldCanBeDisabled() + #[DataProvider('provideDisableRenameEmbeddedIdField')] + public function testDisableRenameEmbeddedIdField(array $expected, Closure $build) { $builder = $this->getBuilder(false); $this->assertFalse($builder->getConnection()->getRenameEmbeddedIdField()); - $mql = $builder - ->where('id', '=', 10) - ->where('nested.id', '=', 20) - ->where('embed', '=', ['id' => 30]) - ->toMql(); - - $this->assertEquals([ - 'find' => [ - [ - '$and' => [ - ['_id' => 10], - ['nested.id' => 20], - ['embed' => ['id' => 30]], + $mql = $build($builder)->toMql(); + + $this->assertEquals($expected, $mql); + } + + public static function provideDisableRenameEmbeddedIdField() + { + yield 'rename embedded id field' => [ + [ + 'find' => [ + [ + '$and' => [ + ['_id' => 10], + ['nested.id' => 20], + ['embed' => ['id' => 30]], + ], + ], + ['typeMap' => ['root' => 'object', 'document' => 'array']], + ], + ], + fn (Builder $builder) => $builder->where('id', '=', 10) + ->where('nested.id', '=', 20) + ->where('embed', '=', ['id' => 30]), + ]; + + yield 'rename root id' => [ + ['find' => [['_id' => 10], ['typeMap' => ['root' => 'object', 'document' => 'array']]]], + fn (Builder $builder) => $builder->where('id', '=', 10), + ]; + + yield 'nested id not renamed' => [ + ['find' => [['nested.id' => 20], ['typeMap' => ['root' => 'object', 'document' => 'array']]]], + fn (Builder $builder) => $builder->where('nested.id', '=', 20), + ]; + + yield 'embed id not renamed' => [ + ['find' => [['embed' => ['id' => 30]], ['typeMap' => ['root' => 'object', 'document' => 'array']]]], + fn (Builder $builder) => $builder->where('embed', '=', ['id' => 30]), + ]; + + yield 'nested $and in $or' => [ + [ + 'find' => [ + [ + '$or' => [ + [ + '$and' => [ + ['_id' => 10], + ['nested.id' => 20], + ['embed' => ['id' => 30]], + ], + ], + [ + '$and' => [ + ['_id' => 40], + ['nested.id' => 50], + ['embed' => ['id' => 60]], + ], + ], + ], ], + ['typeMap' => ['root' => 'object', 'document' => 'array']], ], - ['typeMap' => ['root' => 'object', 'document' => 'array']], ], - ], $mql); + fn (Builder $builder) => $builder->orWhere(function (Builder $builder) { + return $builder->where('id', '=', 10) + ->where('nested.id', '=', 20) + ->where('embed', '=', ['id' => 30]); + })->orWhere(function (Builder $builder) { + return $builder->where('id', '=', 40) + ->where('nested.id', '=', 50) + ->where('embed', '=', ['id' => 60]); + }), + ]; } private function getBuilder(bool $renameEmbeddedIdField = true): Builder