wonder95's avatar

Getting _id key instead of _id with mongodb/laravel-mongodb v5x

One of the significant changes in v5 of the mongodb/laravel-mongdob library where supposedly data returned from Mongo queries, return a key of id instead of the Mongo standard of _id to be consistent with Laravel. However, there is an issue with embedded documents]() with the forced conversion of the id.

I am having a related but different issue with the id key returned when attempting to associate an embedded document.

This can be illustrated with two models: PursuitModel and SubtypeModel, where PursuitModel has this relationship:

    public function subtypes(): EmbedsMany
    {
        return $this->embedsMany(SubtypeModel::class);
    }

As an example, if in my observer, I do this:

            $pursuitModel->subtypes()->associate(new SubtypeModel([
                '_id' => $newStage->isUserStage ? 'Active' : $newStage->name,
                'type' => 'pursuitStatus',
            ]));

it gets to EmbedsMany->associate()

    public function associate(Model $model)
    {
        if (! $this->contains($model)) {
            return $this->associateNew($model);
        }

        return $this->associateExisting($model);
    }

and then to associateExisting():

    protected function associateExisting($model)
    {
        // Get existing embedded documents.
        $records = $this->getEmbedded();

        $primaryKey = $this->related->getKeyName();

        $key = $model->getKey();

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

        return $this->setEmbedded($records);
    }

what happens is the $recordsvalues have a key a key of_idinstead ofid`, so this fails

            if ($record[$primaryKey] == $key) {
                $record = $model->getAttributes();
                break;
            }

No matter what I send it to -.>associate() in the original call, even if I send it as id, $key is always _id. and now since $primaryKey is id (the fallback to the base Laravel model since $primaryKey = '_id was removed, it fails.

the embedded db records look like this

    "subtypes" : [
        {
            "_id" : "Active",
            "type" : "pursuitStatus"
        }
    ]

So what I can't figure out is why the data is returning _id if the change that started all this was that id is the returned key. I have tried this suggested fix

    /**
     * The accessors to append to the model's array form.
     *
     * @var array
     */
    protected $appends = ['_id'];

    /**
     * Append _id to arrays
     */
    protected function id(): Attribute
    {
        return new Attribute(
            get: fn (string $value) => $value ?? $this->id,
        );
    }

but I still get the same error.

Questions:

  1. Should that code not return a $records item with a key of id, instead of _id?
  2. Is it possible I'm missing a query override somewhere?
0 likes
1 reply
LaryAI's avatar
Level 58

The issue you're encountering is related to the transition from using _id to id in the mongodb/laravel-mongodb library. This change was made to align with Laravel's conventions, but it seems to be causing issues with embedded documents.

Solution

  1. Understanding the Change: The library now uses id instead of _id for the primary key in models. However, embedded documents might still be using _id due to how they are stored and retrieved from MongoDB.

  2. Customizing the Model: You can customize your model to handle both id and _id by using accessors and mutators. This way, you can ensure that your application logic can work with both keys.

  3. Accessors and Mutators: You can define accessors and mutators in your model to handle the conversion between id and _id.

Here's how you can implement this:

use Jenssegers\Mongodb\Eloquent\Model;

class SubtypeModel extends Model
{
    // Ensure the model uses the correct primary key
    protected $primaryKey = '_id';

    // Define an accessor for 'id'
    public function getIdAttribute()
    {
        return $this->attributes['_id'] ?? null;
    }

    // Define a mutator for 'id'
    public function setIdAttribute($value)
    {
        $this->attributes['_id'] = $value;
    }
}
  1. Adjusting the Relationship: Ensure that your relationship methods are aware of this change. You might need to adjust how you handle the associate method to ensure it checks for both id and _id.

  2. Check for Overrides: Review your codebase for any custom query overrides or model methods that might be affecting how keys are handled. This includes any custom serialization logic or query builders.

  3. Testing: After making these changes, test your application thoroughly to ensure that both id and _id are handled correctly in all parts of your application, especially in embedded documents.

By implementing these changes, you should be able to handle the transition from _id to id more smoothly, especially in the context of embedded documents.

Please or to participate in this conversation.