diff --git a/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php b/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php index e125a760410b..fdf796f86ba0 100755 --- a/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php @@ -483,7 +483,14 @@ public function withPivotValue($column, $value = null) $this->pivotValues[] = compact('column', 'value'); - return $this->wherePivot($column, '=', $value); + // Add where clause for filtering but track it separately so it doesn't + // prevent the pivot model observers from working + $this->wherePivot($column, '=', $value); + + // Mark this where clause as coming from withPivotValue + $this->pivotWheres[count($this->pivotWheres) - 1]['from_pivot_value'] = true; + + return $this; } /** diff --git a/src/Illuminate/Database/Eloquent/Relations/Concerns/InteractsWithPivotTable.php b/src/Illuminate/Database/Eloquent/Relations/Concerns/InteractsWithPivotTable.php index 015554ed767a..e880c6b551a4 100644 --- a/src/Illuminate/Database/Eloquent/Relations/Concerns/InteractsWithPivotTable.php +++ b/src/Illuminate/Database/Eloquent/Relations/Concerns/InteractsWithPivotTable.php @@ -208,7 +208,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->getNonPivotValueWhereClauses()) && empty($this->pivotWhereIns) && empty($this->pivotWhereNulls)) { return $this->updateExistingPivotUsingCustomClass($id, $attributes, $touch); @@ -437,7 +437,7 @@ public function detach($ids = null, $touch = true) { if ($this->using && ! empty($ids) && - empty($this->pivotWheres) && + empty($this->getNonPivotValueWhereClauses()) && empty($this->pivotWhereIns) && empty($this->pivotWhereNulls)) { $results = $this->detachUsingCustomClass($ids); @@ -570,7 +570,15 @@ public function newPivotQuery() $query = $this->newPivotStatement(); foreach ($this->pivotWheres as $arguments) { - $query->where(...$arguments); + // Clone the arguments to avoid modifying the original array + $argumentsToApply = $arguments; + + // Remove the from_pivot_value flag before applying to the query + if (isset($argumentsToApply['from_pivot_value'])) { + unset($argumentsToApply['from_pivot_value']); + } + + $query->where(...$argumentsToApply); } foreach ($this->pivotWhereIns as $arguments) { @@ -691,4 +699,18 @@ protected function getTypeSwapValue($type, $value) default => $value, }; } + + /** + * Get the pivot where clauses that are not from withPivotValue. + * + * @return array + */ + protected function getNonPivotValueWhereClauses() + { + return collect($this->pivotWheres) + ->filter(function ($where) { + return empty($where['from_pivot_value'] ?? false); + }) + ->all(); + } } diff --git a/tests/Integration/Database/EloquentPivotEventsTest.php b/tests/Integration/Database/EloquentPivotEventsTest.php index 7a9962982595..ba0fdbd2d3b3 100644 --- a/tests/Integration/Database/EloquentPivotEventsTest.php +++ b/tests/Integration/Database/EloquentPivotEventsTest.php @@ -152,6 +152,31 @@ public function testCustomMorphPivotClassDetachAttributes() $project->equipments()->save($equipment); $equipment->projects()->sync([]); } + + public function testPivotWithPivotValueTriggerEvents() + { + $user = PivotEventsTestUser::forceCreate(['email' => 'taylor@laravel.com']); + $project = PivotEventsTestProject::forceCreate(['name' => 'Test Project']); + + // Attach with a specific role + $project->collaborators()->attach($user, ['role' => 'admin']); + PivotEventsTestCollaborator::$eventsCalled = []; + + // Get the relation with withPivotValue + $adminRelation = $project->belongsToMany( + PivotEventsTestUser::class, 'project_users', 'project_id', 'user_id' + )->using(PivotEventsTestCollaborator::class) + ->withPivotValue('role', 'admin'); + + // Update through the relation with withPivotValue + $adminRelation->updateExistingPivot($user->id, ['permissions' => ['manage']]); + $this->assertEquals(['saving', 'updating', 'updated', 'saved'], PivotEventsTestCollaborator::$eventsCalled, 'Observer events should fire on updateExistingPivot with withPivotValue'); + + // Detach through the relation with withPivotValue + PivotEventsTestCollaborator::$eventsCalled = []; + $adminRelation->detach($user->id); + $this->assertEquals(['deleting', 'deleted'], PivotEventsTestCollaborator::$eventsCalled, 'Observer events should fire on detach with withPivotValue'); + } } class PivotEventsTestUser extends Model