From 477d4885fa6e779153a668fb845edb7125e71d8e Mon Sep 17 00:00:00 2001 From: Amir Alizadeh Date: Sat, 5 Apr 2025 13:49:47 +0330 Subject: [PATCH] Fix pivot model events not working when using the `withPivotValue` --- .../Concerns/InteractsWithPivotTable.php | 74 +++++++++++-------- .../Database/EloquentPivotEventsTest.php | 35 +++++++++ 2 files changed, 80 insertions(+), 29 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Relations/Concerns/InteractsWithPivotTable.php b/src/Illuminate/Database/Eloquent/Relations/Concerns/InteractsWithPivotTable.php index 015554ed767a..3fc068388f43 100644 --- a/src/Illuminate/Database/Eloquent/Relations/Concerns/InteractsWithPivotTable.php +++ b/src/Illuminate/Database/Eloquent/Relations/Concerns/InteractsWithPivotTable.php @@ -207,10 +207,7 @@ protected function attachNew(array $records, array $current, $touch = true) */ public function updateExistingPivot($id, array $attributes, $touch = true) { - if ($this->using && - empty($this->pivotWheres) && - empty($this->pivotWhereIns) && - empty($this->pivotWhereNulls)) { + if ($this->using) { return $this->updateExistingPivotUsingCustomClass($id, $attributes, $touch); } @@ -218,7 +215,7 @@ public function updateExistingPivot($id, array $attributes, $touch = true) $attributes = $this->addTimestampsToAttachment($attributes, true); } - $updated = $this->newPivotStatementForId($this->parseId($id))->update( + $updated = $this->newPivotStatementForId($id)->update( $this->castAttributes($attributes) ); @@ -239,10 +236,7 @@ public function updateExistingPivot($id, array $attributes, $touch = true) */ protected function updateExistingPivotUsingCustomClass($id, array $attributes, $touch) { - $pivot = $this->getCurrentlyAttachedPivots() - ->where($this->foreignPivotKey, $this->parent->{$this->parentKey}) - ->where($this->relatedPivotKey, $this->parseId($id)) - ->first(); + $pivot = $this->getCurrentlyAttachedPivotsForIds($id)->first(); $updated = $pivot ? $pivot->fill($attributes)->isDirty() : false; @@ -435,11 +429,7 @@ public function hasPivotColumn($column) */ public function detach($ids = null, $touch = true) { - if ($this->using && - ! empty($ids) && - empty($this->pivotWheres) && - empty($this->pivotWhereIns) && - empty($this->pivotWhereNulls)) { + if ($this->using) { $results = $this->detachUsingCustomClass($ids); } else { $query = $this->newPivotQuery(); @@ -480,16 +470,50 @@ protected function detachUsingCustomClass($ids) { $results = 0; - foreach ($this->parseIds($ids) as $id) { - $results += $this->newPivot([ - $this->foreignPivotKey => $this->parent->{$this->parentKey}, - $this->relatedPivotKey => $id, - ], true)->delete(); + if ( + ! empty($this->pivotWheres) || + ! empty($this->pivotWhereIns) || + ! empty($this->pivotWhereNulls) + ) { + $records = $this->getCurrentlyAttachedPivotsForIds($ids); + + foreach ($records as $record) { + $results += $record->delete(); + } + } else { + foreach ($this->parseIds($ids) as $id) { + $results += $this->newPivot([ + $this->foreignPivotKey => $this->parent->{$this->parentKey}, + $this->relatedPivotKey => $id, + ], true)->delete(); + } } return $results; } + /** + * Get the pivot models that are currently attached, filtered by related model keys. + * + * @param mixed $ids + * @return \Illuminate\Support\Collection + */ + protected function getCurrentlyAttachedPivotsForIds($ids = null) + { + return $this->newPivotQuery() + ->when(! is_null($ids), fn ($query) => $query->whereIn($this->getQualifiedRelatedPivotKeyName(), $this->parseIds($ids))) + ->get() + ->map(function ($record) { + $class = $this->using ?: Pivot::class; + + $pivot = $class::fromRawAttributes($this->parent, (array) $record, $this->getTable(), true); + + return $pivot + ->setPivotKeys($this->foreignPivotKey, $this->relatedPivotKey) + ->setRelatedModel($this->related); + }); + } + /** * Get the pivot models that are currently attached. * @@ -497,15 +521,7 @@ protected function detachUsingCustomClass($ids) */ protected function getCurrentlyAttachedPivots() { - return $this->newPivotQuery()->get()->map(function ($record) { - $class = $this->using ?: Pivot::class; - - $pivot = $class::fromRawAttributes($this->parent, (array) $record, $this->getTable(), true); - - return $pivot - ->setPivotKeys($this->foreignPivotKey, $this->relatedPivotKey) - ->setRelatedModel($this->related); - }); + return $this->getCurrentlyAttachedPivotsForIds(); } /** @@ -557,7 +573,7 @@ public function newPivotStatement() */ public function newPivotStatementForId($id) { - return $this->newPivotQuery()->whereIn($this->relatedPivotKey, $this->parseIds($id)); + return $this->newPivotQuery()->whereIn($this->getQualifiedRelatedPivotKeyName(), $this->parseIds($id)); } /** diff --git a/tests/Integration/Database/EloquentPivotEventsTest.php b/tests/Integration/Database/EloquentPivotEventsTest.php index 7a9962982595..98996f04147c 100644 --- a/tests/Integration/Database/EloquentPivotEventsTest.php +++ b/tests/Integration/Database/EloquentPivotEventsTest.php @@ -74,6 +74,34 @@ public function testPivotWillTriggerEventsToBeFired() $this->assertEquals(['deleting', 'deleted'], PivotEventsTestCollaborator::$eventsCalled); } + public function testPivotWithPivotValueWillTriggerEventsToBeFired() + { + $user = PivotEventsTestUser::forceCreate(['email' => 'taylor@laravel.com']); + $user2 = PivotEventsTestUser::forceCreate(['email' => 'ralph@ralphschindler.com']); + $project = PivotEventsTestProject::forceCreate(['name' => 'Test Project']); + + $project->managers()->attach($user); + $this->assertEquals(['saving', 'creating', 'created', 'saved'], PivotEventsTestCollaborator::$eventsCalled); + $project->managers()->attach($user2); + + PivotEventsTestCollaborator::$eventsCalled = []; + $project->managers()->updateExistingPivot($user->id, ['permissions' => ['foo', 'bar']]); + $this->assertEquals(['saving', 'updating', 'updated', 'saved'], PivotEventsTestCollaborator::$eventsCalled); + $project->managers()->detach($user2); + + PivotEventsTestCollaborator::$eventsCalled = []; + $project->managers()->sync([$user2->id]); + $this->assertEquals(['deleting', 'deleted', 'saving', 'creating', 'created', 'saved'], PivotEventsTestCollaborator::$eventsCalled); + + PivotEventsTestCollaborator::$eventsCalled = []; + $project->managers()->sync([$user->id => ['permissions' => ['foo']], $user2->id => ['permissions' => ['bar']]]); + $this->assertEquals(['saving', 'creating', 'created', 'saved', 'saving', 'updating', 'updated', 'saved'], PivotEventsTestCollaborator::$eventsCalled); + + PivotEventsTestCollaborator::$eventsCalled = []; + $project->managers()->detach($user); + $this->assertEquals(['deleting', 'deleted'], PivotEventsTestCollaborator::$eventsCalled); + } + public function testPivotWithPivotCriteriaTriggerEventsToBeFiredOnCreateUpdateNoneOnDetach() { $user = PivotEventsTestUser::forceCreate(['email' => 'taylor@laravel.com']); @@ -192,6 +220,13 @@ public function contributors() ->wherePivot('role', 'contributor'); } + public function managers() + { + return $this->belongsToMany(PivotEventsTestUser::class, 'project_users', 'project_id', 'user_id') + ->using(PivotEventsTestCollaborator::class) + ->withPivotValue('role', 'manager'); + } + public function equipments() { return $this->morphToMany(PivotEventsTestEquipment::class, 'equipmentable')->using(PivotEventsTestModelEquipment::class);