diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 71a44a395..4869c6ca0 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -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 - diff --git a/src/Relations/BelongsToMany.php b/src/Relations/BelongsToMany.php index 1d6b84ba8..082f95e06 100644 --- a/src/Relations/BelongsToMany.php +++ b/src/Relations/BelongsToMany.php @@ -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 @@ -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); @@ -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; @@ -217,7 +232,13 @@ 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) { @@ -225,7 +246,7 @@ public function detach($ids = [], $touch = true) } // 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()); diff --git a/tests/HybridRelationsTest.php b/tests/HybridRelationsTest.php index 9ff6264e5..0080a3a47 100644 --- a/tests/HybridRelationsTest.php +++ b/tests/HybridRelationsTest.php @@ -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; @@ -36,6 +37,7 @@ public function tearDown(): void SqlUser::truncate(); SqlBook::truncate(); SqlRole::truncate(); + Skill::truncate(); } public function testSqlRelations() @@ -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()); + } } diff --git a/tests/Models/Skill.php b/tests/Models/Skill.php index c4c1dbd0a..3b9a434ee 100644 --- a/tests/Models/Skill.php +++ b/tests/Models/Skill.php @@ -4,6 +4,7 @@ namespace MongoDB\Laravel\Tests\Models; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; use MongoDB\Laravel\Eloquent\Model as Eloquent; class Skill extends Eloquent @@ -11,4 +12,9 @@ class Skill extends Eloquent protected $connection = 'mongodb'; protected $collection = 'skills'; protected static $unguarded = true; + + public function sqlUsers(): BelongsToMany + { + return $this->belongsToMany(SqlUser::class); + } } diff --git a/tests/Models/SqlUser.php b/tests/Models/SqlUser.php index 1fe11276a..34c65f42e 100644 --- a/tests/Models/SqlUser.php +++ b/tests/Models/SqlUser.php @@ -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; @@ -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); @@ -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) { + $table->foreignIdFor(self::class)->constrained()->cascadeOnDelete(); + $table->string((new Skill())->getForeignKey()); + $table->primary([(new self())->getForeignKey(), (new Skill())->getForeignKey()]); + }); + } } }