2 / 4
Mar 24

I’m converting a Laravel 11 app that uses mongodb/laravel-mongodb v4.8 to Laravel 12 with v5 of the mongo library. In the breaking changes for v5 part of the docs, it says this
``
id is an alias for the _id field in MongoDB documents, and the library automatically converts between id and _id when querying data. The query result object includes an id field to represent the document's _id field. Because of this behavior, you cannot have two separate id and _id fields in your documents.

So we have tons of records in Mongo going back more than a decade, all with `_id` as the key for each record (my guess is to be consistent with Laravel's `id` default). As I read this, all queries will now return `id` in a query result. Am I reading that correctly? If so, in my code, where we refer to `_id``` in a document result, I have to convert it to look for `id`?

That’s the exact reason we added the aliasing in the MongoDB Query Builder: ensure maximum compatibility with Laravel and community packages.

In model classes, you can still refer to the primary key using $model->_id , this is pointing to the same internal attribute id. You don’t need to update all your codebase.

This is a breaking change only if you use the Database Query Builder, not the Eloquent Builder, nor with the Raw MongoDB query.
With Database Query Builder, you need to change $result['id'] to $result->id.

We plan to add an option to disable this automatic conversion for projects that have a id field in an embedded document.

In addition, it seems that there are restrictions on writing data to Mongo with embedsMany relationships on update. For instance, in my SubtypeModel, in addition to _id, I have a type field, with records that look like this when embedded in a parent model

"subtypes" : [ { "_id" : "Small Business", "type" : "SBABusinessType" }, { "_id" : "Firm Fixed Price", "type" : "pricingType" }, ],

When attempting to update an existing embedded model via save(), no matter what is passed in, this code in WEmbedsMany->associateMany() chokes:

foreach ($records as &$record) { // @phpcs:ignore SlevomatCodingStandard.Operators.DisallowEqualOperators if ($record[$primaryKey] == $key) { $record = $model->getAttributes(); break; }

with this error

ErrorException: Undefined array key "id"

because, as it indicates, the $recordarray only has an_idvalue, notid`. IT can be demonstrated with this unit test

public function testEmbeddedSubtype() { $fca = factory(FederalContractAwardModel::class)->create(); $subtype = new SubtypeModel([ '_id' => 'Time and Materials', 'type' => 'pricingType' ]); $fca->subtypes()->save($subtype); $subtype = $fca->subtypes()->first(); $subtype->update([ "_id" => "Time and Materials", 'type' => 'packageType' ]); $this->assertEquals( $subtype->_id, 'packageType', 'ID does not match the subtype' ); }

even if I pass in id, it still errors out. From what we can see it git, this file hasn’t changed, However, since the core EntityModel now has $primaryKey defined as id, there is no match with the data.

How do we resolve this?

Along the same lines (sorry if it’s not relevant to my initial question), but one undocumented change that I’ve run into with the upgrade is an issue with how it handles embedded documents (i.e. using an embedsMany() relationship). I have a SubtpyeModel that is embedded in other models using the embedsMany() relationship. In the db, the embedded records has an “_id” value and a “type” key that looks like this

"subtypes" : [ { "_id" : "Corporate Entity (Tax Exempt)", "type" : "entityStructure" }, { "_id" : "Non-Profit Organization", "type" : "businessType" } ]

However, when returning the data in an accessor like this

public function getNameAttribute(): string { return $this->_id; }

there is no _id`` (or id`) value, just the “type” value, resulting in an error. v5 of the mentioned mongodb library supposedly handles conversion of _id to id, but not even that is there.

So far, the way I’ve addressed this is by manually populating _id in a __construct() method in my `SubtypeModel``:

public function __construct(array $attributes = []) { if (isset($attributes['id']) && !isset($attributes['_id'])) { $attributes['_id'] = $attributes['id']; } parent::__construct($attributes); }
  1. Am I seeing this issue because in v5, when using embedsMany() with embedded documents whose _id values are strings, the automatic mapping of _id to id does not happen properly, and _id might not even be included in the model at all?
  2. Is using __construct() the best way to handle it?