Skip to content

Fix BelongsToMany Relation to Support Array Foreign Keys in Laravel MongoDB #3310

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: 5.x
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions docs/eloquent-models/relationships.txt
Original file line number Diff line number Diff line change
@@ -255,6 +255,26 @@ in the Laravel documentation.
The following section shows an example of how to create a many to many
relationship between model classes.

.. note::

In previous versions, the `foreignPivotKey` in a `BelongsToMany` relationship
was expected to be a single `ObjectId` or `string`. However, in some MongoDB
schemas, it is common to store multiple related IDs as an array. As of this update,
`BelongsToMany` now supports array values for the `foreignPivotKey`.

Example:

.. code-block:: php

class User extends Model {
public function roles() {
return $this->belongsToMany(Role::class, null, 'role_ids', '_id');
}
}

Here, `role_ids` can be an array of `ObjectId`s, and the relationship will work correctly.


Many to Many Example
~~~~~~~~~~~~~~~~~~~~

13 changes: 11 additions & 2 deletions src/Relations/BelongsToMany.php
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany as EloquentBelongsToMany;
use Illuminate\Support\Arr;
use MongoDB\BSON\ObjectId;
use MongoDB\Laravel\Eloquent\Model as DocumentModel;

use function array_diff;
@@ -20,6 +21,7 @@
use function count;
use function in_array;
use function is_numeric;
use function is_object;

/**
* @template TRelatedModel of Model
@@ -208,7 +210,13 @@ public function attach($id, array $attributes = [], $touch = true)

// Attach the new ids to the parent model.
if (\MongoDB\Laravel\Eloquent\Model::isDocumentModel($this->parent)) {
$this->parent->push($this->relatedPivotKey, (array) $id, true);
if ($id instanceof ObjectId) {
$id = [$id];
} else {
$id = (array) $id;
}

$this->parent->push($this->relatedPivotKey, $id, true);
} else {
$instance = new $this->related();
$instance->forceFill([$this->relatedKey => $id]);
@@ -275,7 +283,8 @@ protected function buildDictionary(Collection $results)

foreach ($results as $result) {
foreach ($result->$foreign as $item) {
$dictionary[$item][] = $result;
$key = is_object($item) ? (string) $item : $item;
$dictionary[$key][] = $result;
}
}

6 changes: 6 additions & 0 deletions tests/Models/Role.php
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use MongoDB\Laravel\Eloquent\DocumentModel;
use MongoDB\Laravel\Relations\BelongsToMany;

class Role extends Model
{
@@ -22,6 +23,11 @@ public function user(): BelongsTo
return $this->belongsTo(User::class);
}

public function users(): BelongsToMany
{
return $this->belongsToMany(User::class, null, 'role_id', 'user_id');
}

public function sqlUser(): BelongsTo
{
return $this->belongsTo(SqlUser::class);
6 changes: 6 additions & 0 deletions tests/Models/User.php
Original file line number Diff line number Diff line change
@@ -17,6 +17,7 @@
use MongoDB\Laravel\Eloquent\Builder;
use MongoDB\Laravel\Eloquent\DocumentModel;
use MongoDB\Laravel\Eloquent\MassPrunable;
use MongoDB\Laravel\Relations\BelongsToMany;

/**
* @property string $id
@@ -87,6 +88,11 @@ public function role()
return $this->hasOne(Role::class);
}

public function roles(): BelongsToMany
{
return $this->belongsToMany(Role::class, null, 'user_id', 'role_id');
}

public function sqlRole()
{
return $this->hasOne(SqlRole::class);
16 changes: 16 additions & 0 deletions tests/RelationsTest.php
Original file line number Diff line number Diff line change
@@ -338,6 +338,22 @@ public function testBelongsToManyAttachArray(): void
$this->assertCount(2, $user->clients);
}

public function testBelongsToManyRelationSupportsArrayForeignKeys(): void
{
$user = User::create(['name' => 'John Doe']);
$role1 = Role::create(['name' => 'Admin']);
$role2 = Role::create(['name' => 'Editor']);

$user->roles()->attach([$role1->id, $role2->id]);

$retrievedUser = User::with('roles')->find($user->id);
$this->assertCount(2, $retrievedUser->roles);
$this->assertEqualsCanonicalizing(
[$role1->id, $role2->id],
$retrievedUser->roles->pluck('id')->toArray(),
);
}

public function testBelongsToManyAttachEloquentCollection(): void
{
User::create(['name' => 'John Doe']);