Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
2 changes: 1 addition & 1 deletion phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ parameters:
ignoreErrors:
-
message: "#^Method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:push\\(\\) invoked with 3 parameters, 0 required\\.$#"
count: 3
count: 2
path: src/Relations/BelongsToMany.php

-
Expand Down
29 changes: 25 additions & 4 deletions src/Relations/BelongsToMany.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use function array_values;
use function assert;
use function count;
use function in_array;
use function is_numeric;

class BelongsToMany extends EloquentBelongsToMany
Expand Down Expand Up @@ -124,7 +125,14 @@ public function sync($ids, $detaching = true)
// First we need to attach any of the associated models that are not currently
// in this joining table. We'll spin through the given IDs, checking to see
// if they exist in the array of current ones, and if not we will insert.
$current = $this->parent->{$this->relatedPivotKey} ?: [];
$current = match ($this->parent instanceof \MongoDB\Laravel\Eloquent\Model) {
true => $this->parent->{$this->relatedPivotKey} ?: [],
false => $this->parent->{$this->relationName} ?: [],
};

if ($current instanceof Collection) {
$current = $this->parseIds($current);
}

$records = $this->formatRecordsList($ids);

Expand Down Expand Up @@ -193,7 +201,14 @@ public function attach($id, array $attributes = [], $touch = true)
}

// Attach the new ids to the parent model.
$this->parent->push($this->relatedPivotKey, (array) $id, true);
if ($this->parent instanceof \MongoDB\Laravel\Eloquent\Model) {
$this->parent->push($this->relatedPivotKey, (array) $id, true);
} else {
$instance = new $this->related();
$instance->forceFill([$this->relatedKey => $id]);
$relationData = $this->parent->{$this->relationName}->push($instance)->unique($this->relatedKey);
$this->parent->setRelation($this->relationName, $relationData);
}

if (! $touch) {
return;
Expand All @@ -217,15 +232,21 @@ public function detach($ids = [], $touch = true)
$ids = (array) $ids;

// Detach all ids from the parent model.
$this->parent->pull($this->relatedPivotKey, $ids);
if ($this->parent instanceof \MongoDB\Laravel\Eloquent\Model) {
$this->parent->pull($this->relatedPivotKey, $ids);
} else {
$value = $this->parent->{$this->relationName}
->filter(fn ($rel) => ! in_array($rel->{$this->relatedKey}, $ids));
$this->parent->setRelation($this->relationName, $value);
}

// Prepare the query to select all related objects.
if (count($ids) > 0) {
$query->whereIn($this->related->getKeyName(), $ids);
}

// Remove the relation to the parent.
assert($this->parent instanceof \MongoDB\Laravel\Eloquent\Model);
assert($this->parent instanceof Model);
assert($query instanceof \MongoDB\Laravel\Eloquent\Builder);
$query->pull($this->foreignPivotKey, $this->parent->getKey());

Expand Down
51 changes: 51 additions & 0 deletions tests/HybridRelationsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Illuminate\Support\Facades\DB;
use MongoDB\Laravel\Tests\Models\Book;
use MongoDB\Laravel\Tests\Models\Role;
use MongoDB\Laravel\Tests\Models\Skill;
use MongoDB\Laravel\Tests\Models\SqlBook;
use MongoDB\Laravel\Tests\Models\SqlRole;
use MongoDB\Laravel\Tests\Models\SqlUser;
Expand Down Expand Up @@ -36,6 +37,7 @@ public function tearDown(): void
SqlUser::truncate();
SqlBook::truncate();
SqlRole::truncate();
Skill::truncate();
}

public function testSqlRelations()
Expand Down Expand Up @@ -210,4 +212,53 @@ public function testHybridWith()
$this->assertEquals($user->id, $user->books->count());
});
}

public function testHybridBelongsToMany()
{
$user = new SqlUser();
$user2 = new SqlUser();
$this->assertInstanceOf(SqlUser::class, $user);
$this->assertInstanceOf(SQLiteConnection::class, $user->getConnection());
$this->assertInstanceOf(SqlUser::class, $user2);
$this->assertInstanceOf(SQLiteConnection::class, $user2->getConnection());

// Create Mysql Users
$user->fill(['name' => 'John Doe'])->save();
$user = SqlUser::query()->find($user->id);

$user2->fill(['name' => 'Maria Doe'])->save();
$user2 = SqlUser::query()->find($user2->id);

// Create Mongodb Skills
$skill = Skill::query()->create(['name' => 'Laravel']);
$skill2 = Skill::query()->create(['name' => 'MongoDB']);

// sync (pivot is empty)
$skill->sqlUsers()->sync([$user->id, $user2->id]);
$check = Skill::query()->find($skill->_id);
$this->assertEquals(2, $check->sqlUsers->count());

// sync (pivot is not empty)
$skill->sqlUsers()->sync($user);
$check = Skill::query()->find($skill->_id);
$this->assertEquals(1, $check->sqlUsers->count());

// Inverse sync (pivot is empty)
$user->skills()->sync([$skill->_id, $skill2->_id]);
$check = SqlUser::find($user->id);
$this->assertEquals(2, $check->skills->count());

// Inverse sync (pivot is not empty)
$user->skills()->sync($skill);
$check = SqlUser::find($user->id);
$this->assertEquals(1, $check->skills->count());

// Inverse attach
$user->skills()->sync([]);
$check = SqlUser::find($user->id);
$this->assertEquals(0, $check->skills->count());
$user->skills()->attach($skill);
$check = SqlUser::find($user->id);
$this->assertEquals(1, $check->skills->count());
}
}
6 changes: 6 additions & 0 deletions tests/Models/Skill.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,17 @@

namespace MongoDB\Laravel\Tests\Models;

use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use MongoDB\Laravel\Eloquent\Model as Eloquent;

class Skill extends Eloquent
{
protected $connection = 'mongodb';
protected $collection = 'skills';
protected static $unguarded = true;

public function sqlUsers(): BelongsToMany
{
return $this->belongsToMany(SqlUser::class);
}
}
13 changes: 13 additions & 0 deletions tests/Models/SqlUser.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace MongoDB\Laravel\Tests\Models;

use Illuminate\Database\Eloquent\Model as EloquentModel;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Schema\Blueprint;
Expand Down Expand Up @@ -32,6 +33,11 @@ public function role(): HasOne
return $this->hasOne(Role::class);
}

public function skills(): BelongsToMany
{
return $this->belongsToMany(Skill::class, relatedPivotKey: 'skills');
}

public function sqlBooks(): HasMany
{
return $this->hasMany(SqlBook::class);
Expand All @@ -51,5 +57,12 @@ public static function executeSchema(): void
$table->string('name');
$table->timestamps();
});
if (! $schema->hasTable('skill_sql_user')) {
$schema->create('skill_sql_user', function (Blueprint $table) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really want to have a pivot table here? If the relations are already stored in the MongoDB document, it should not be duplicated in an SQL table.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// Check if it is a relation with an original model.
if (! is_subclass_of($related, MongoDBModel::class)) {
return parent::belongsToMany(
$related,
$collection,
$foreignPivotKey,
$relatedPivotKey,
$parentKey,
$relatedKey,
$relation,
);
}

The sql models don't reach our BelongsToMany class. So, we can't control this behavior.
As I know, the sql models, store their relations data in a pivot tabel and mongo models, store theirs in a pivot column.

$table->foreignIdFor(self::class)->constrained()->cascadeOnDelete();
$table->string((new Skill())->getForeignKey());
$table->primary([(new self())->getForeignKey(), (new Skill())->getForeignKey()]);
});
}
}
}