From bf1a0ed8a9aeda9d65e89667f91fb42462fb7a9b Mon Sep 17 00:00:00 2001
From: Mohammad Mortazavi <s.mohammad027@live.com>
Date: Mon, 27 Nov 2023 14:27:08 +0330
Subject: [PATCH 1/8] Add support for hybrid relationships;

Co-Authored-By: Junio Hyago <35033754+juniohyago@users.noreply.github.com>
---
 src/Relations/BelongsToMany.php | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/src/Relations/BelongsToMany.php b/src/Relations/BelongsToMany.php
index 1d6b84ba8..91ef73eae 100644
--- a/src/Relations/BelongsToMany.php
+++ b/src/Relations/BelongsToMany.php
@@ -125,6 +125,10 @@ public function sync($ids, $detaching = true)
         // 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} ?: [];
+        // Hybrid relationships support
+        if ($current instanceof Collection) {
+            $current = $this->parseIds($current);
+        }
 
         $records = $this->formatRecordsList($ids);
 
@@ -216,8 +220,10 @@ public function detach($ids = [], $touch = true)
         // We'll return the numbers of affected rows when we do the deletes.
         $ids = (array) $ids;
 
-        // Detach all ids from the parent model.
-        $this->parent->pull($this->relatedPivotKey, $ids);
+        if ($this->parent instanceof \MongoDB\Laravel\Eloquent\Model) {
+            // Detach all ids from the parent model.
+            $this->parent->pull($this->relatedPivotKey, $ids);
+        }
 
         // Prepare the query to select all related objects.
         if (count($ids) > 0) {
@@ -225,7 +231,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());
 

From bc5636dd79fc0a6a5273312ef5b0fe6b4c7e067b Mon Sep 17 00:00:00 2001
From: Mohammad Mortazavi <s.mohammad027@live.com>
Date: Mon, 27 Nov 2023 14:27:46 +0330
Subject: [PATCH 2/8] Add test for hybrid BelongsToMany hybrid relationship;

Co-Authored-By: Junio Hyago <35033754+juniohyago@users.noreply.github.com>
---
 tests/HybridRelationsTest.php | 43 +++++++++++++++++++++++++++++++++++
 tests/Models/Skill.php        |  6 +++++
 tests/Models/SqlUser.php      | 13 +++++++++++
 3 files changed, 62 insertions(+)

diff --git a/tests/HybridRelationsTest.php b/tests/HybridRelationsTest.php
index 9ff6264e5..e41b1edf6 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,45 @@ public function testHybridWith()
                 $this->assertEquals($user->id, $user->books->count());
             });
     }
+
+    public function testHybridSync()
+    {
+        $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 Clients
+        $skill = Skill::query()->create(['name' => 'Laravel']);
+        $skill2 = Skill::query()->create(['name' => 'MongoDB']);
+
+        // sync (pivot is empty)
+        $skill->sqlUsers()->sync([$user->id, $user2->id]);
+        $skill = Skill::query()->find($skill->_id);
+        $this->assertEquals(2, $skill->sqlUsers->count());
+
+        // sync (pivot is not empty)
+        $skill->sqlUsers()->sync($user);
+        $skill = Skill::query()->find($skill->_id);
+        $this->assertEquals(1, $skill->sqlUsers->count());
+
+        // sync (pivot is empty)
+        $user->skills()->sync([$skill->_id, $skill2->_id]);
+        $user = SqlUser::find($user->id);
+        $this->assertEquals(2, $user->skills->count());
+
+        // sync (pivot is not empty)
+        $user->skills()->sync($skill);
+        $user = SqlUser::find($user->id);
+        $this->assertEquals(1, $user->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()]);
+            });
+        }
     }
 }

From 3f9ea17fa2f231770d75f497ca752309387c7f56 Mon Sep 17 00:00:00 2001
From: hans-thomas <hans-thomas@users.noreply.github.com>
Date: Mon, 27 Nov 2023 10:58:36 +0000
Subject: [PATCH 3/8] apply phpcbf formatting

---
 tests/HybridRelationsTest.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/HybridRelationsTest.php b/tests/HybridRelationsTest.php
index e41b1edf6..697c6d2d4 100644
--- a/tests/HybridRelationsTest.php
+++ b/tests/HybridRelationsTest.php
@@ -216,7 +216,7 @@ public function testHybridWith()
     public function testHybridSync()
     {
         $user = new SqlUser();
-        $user2 = new SqlUser;
+        $user2 = new SqlUser();
         $this->assertInstanceOf(SqlUser::class, $user);
         $this->assertInstanceOf(SQLiteConnection::class, $user->getConnection());
         $this->assertInstanceOf(SqlUser::class, $user2);

From eafa5ff6a61ccd9e7c438c63e794517cea3427fb Mon Sep 17 00:00:00 2001
From: Mohammad Mortazavi <s.mohammad027@live.com>
Date: Mon, 27 Nov 2023 16:38:45 +0330
Subject: [PATCH 4/8] WIP

---
 tests/HybridRelationsTest.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/HybridRelationsTest.php b/tests/HybridRelationsTest.php
index 697c6d2d4..25b8673b7 100644
--- a/tests/HybridRelationsTest.php
+++ b/tests/HybridRelationsTest.php
@@ -213,7 +213,7 @@ public function testHybridWith()
             });
     }
 
-    public function testHybridSync()
+    public function testHybridBelongsToMany()
     {
         $user = new SqlUser();
         $user2 = new SqlUser();

From 55a5dff1b6e2da5c850a566641cd54baea3c299f Mon Sep 17 00:00:00 2001
From: Mohammad Mortazavi <s.mohammad027@live.com>
Date: Mon, 27 Nov 2023 17:14:52 +0330
Subject: [PATCH 5/8] WIP

---
 tests/HybridRelationsTest.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/HybridRelationsTest.php b/tests/HybridRelationsTest.php
index 25b8673b7..8c3ebdf02 100644
--- a/tests/HybridRelationsTest.php
+++ b/tests/HybridRelationsTest.php
@@ -229,7 +229,7 @@ public function testHybridBelongsToMany()
         $user2->fill(['name' => 'Maria Doe'])->save();
         $user2 = SqlUser::query()->find($user2->id);
 
-        // Create Mongodb Clients
+        // Create Mongodb Skills
         $skill = Skill::query()->create(['name' => 'Laravel']);
         $skill2 = Skill::query()->create(['name' => 'MongoDB']);
 

From 0d727aae75cb0d39279a392fc460da49fda6c3c2 Mon Sep 17 00:00:00 2001
From: Mohammad Mortazavi <s.mohammad027@live.com>
Date: Wed, 29 Nov 2023 15:02:09 +0330
Subject: [PATCH 6/8] Update sql model instance;

---
 src/Relations/BelongsToMany.php | 22 ++++++++++++++++++----
 tests/HybridRelationsTest.php   | 28 ++++++++++++++++++----------
 2 files changed, 36 insertions(+), 14 deletions(-)

diff --git a/src/Relations/BelongsToMany.php b/src/Relations/BelongsToMany.php
index 91ef73eae..92d68df1c 100644
--- a/src/Relations/BelongsToMany.php
+++ b/src/Relations/BelongsToMany.php
@@ -124,8 +124,11 @@ 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} ?: [];
-        // Hybrid relationships support
+        $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);
         }
@@ -197,7 +200,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;
@@ -220,9 +230,13 @@ public function detach($ids = [], $touch = true)
         // We'll return the numbers of affected rows when we do the deletes.
         $ids = (array) $ids;
 
+        // Detach all ids from the parent model.
         if ($this->parent instanceof \MongoDB\Laravel\Eloquent\Model) {
-            // Detach all ids from the parent 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.
diff --git a/tests/HybridRelationsTest.php b/tests/HybridRelationsTest.php
index 8c3ebdf02..0080a3a47 100644
--- a/tests/HybridRelationsTest.php
+++ b/tests/HybridRelationsTest.php
@@ -235,22 +235,30 @@ public function testHybridBelongsToMany()
 
         // sync (pivot is empty)
         $skill->sqlUsers()->sync([$user->id, $user2->id]);
-        $skill = Skill::query()->find($skill->_id);
-        $this->assertEquals(2, $skill->sqlUsers->count());
+        $check = Skill::query()->find($skill->_id);
+        $this->assertEquals(2, $check->sqlUsers->count());
 
         // sync (pivot is not empty)
         $skill->sqlUsers()->sync($user);
-        $skill = Skill::query()->find($skill->_id);
-        $this->assertEquals(1, $skill->sqlUsers->count());
+        $check = Skill::query()->find($skill->_id);
+        $this->assertEquals(1, $check->sqlUsers->count());
 
-        // sync (pivot is empty)
+        // Inverse sync (pivot is empty)
         $user->skills()->sync([$skill->_id, $skill2->_id]);
-        $user = SqlUser::find($user->id);
-        $this->assertEquals(2, $user->skills->count());
+        $check = SqlUser::find($user->id);
+        $this->assertEquals(2, $check->skills->count());
 
-        // sync (pivot is not empty)
+        // Inverse sync (pivot is not empty)
         $user->skills()->sync($skill);
-        $user = SqlUser::find($user->id);
-        $this->assertEquals(1, $user->skills->count());
+        $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());
     }
 }

From bf3c33da546e876f7c98bc4a5808e50182949312 Mon Sep 17 00:00:00 2001
From: hans-thomas <hans-thomas@users.noreply.github.com>
Date: Wed, 29 Nov 2023 11:32:45 +0000
Subject: [PATCH 7/8] apply phpcbf formatting

---
 src/Relations/BelongsToMany.php | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/src/Relations/BelongsToMany.php b/src/Relations/BelongsToMany.php
index 92d68df1c..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
@@ -200,9 +201,9 @@ public function attach($id, array $attributes = [], $touch = true)
         }
 
         // Attach the new ids to the parent model.
-        if ($this->parent instanceof \MongoDB\Laravel\Eloquent\Model) { 
+        if ($this->parent instanceof \MongoDB\Laravel\Eloquent\Model) {
             $this->parent->push($this->relatedPivotKey, (array) $id, true);
-        }else{
+        } else {
             $instance = new $this->related();
             $instance->forceFill([$this->relatedKey => $id]);
             $relationData = $this->parent->{$this->relationName}->push($instance)->unique($this->relatedKey);
@@ -233,7 +234,7 @@ public function detach($ids = [], $touch = true)
         // Detach all ids from the parent model.
         if ($this->parent instanceof \MongoDB\Laravel\Eloquent\Model) {
             $this->parent->pull($this->relatedPivotKey, $ids);
-        }else{
+        } else {
             $value = $this->parent->{$this->relationName}
                 ->filter(fn ($rel) => ! in_array($rel->{$this->relatedKey}, $ids));
             $this->parent->setRelation($this->relationName, $value);

From 64fca124c0ff2d7f23b76a0f6771a163f1a7acfa Mon Sep 17 00:00:00 2001
From: Mohammad Mortazavi <s.mohammad027@live.com>
Date: Wed, 29 Nov 2023 15:05:49 +0330
Subject: [PATCH 8/8] Update phpstan-baseline.neon

---
 phpstan-baseline.neon | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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
 
 		-