diff --git a/packages/forms/docs/12-repeater.md b/packages/forms/docs/12-repeater.md index 2fb0be88d97..642b8619328 100644 --- a/packages/forms/docs/12-repeater.md +++ b/packages/forms/docs/12-repeater.md @@ -439,6 +439,71 @@ Repeater::make('qualifications') You can inject various utilities into the function passed to `mutateRelationshipDataBeforeSaveUsing()` as parameters. +### Running code after creating a related item + +You may run code after a new related item is created in the database using the `afterCreate()` method. This method accepts a closure that receives the current item's data in a `$data` variable and the newly created record in a `$record` variable. This is useful when you need the record's ID to perform additional operations, such as attaching pivot data: + +```php +use Filament\Forms\Components\Repeater; +use Illuminate\Database\Eloquent\Model; + +Repeater::make('variants') + ->relationship() + ->schema([ + // ... + ]) + ->afterCreate(function (array $data, Model $record): void { + if (isset($data['attributes'])) { + $record->attributes()->attach($data['attributes']); + } + }) +``` + +You can inject various utilities into the function passed to `afterCreate()` as parameters. + +### Running code after updating a related item + +You may run code after an existing related item is updated in the database using the `afterUpdate()` method. This method accepts a closure that receives the current item's data in a `$data` variable and the updated record in a `$record` variable: + +```php +use Filament\Forms\Components\Repeater; +use Illuminate\Database\Eloquent\Model; + +Repeater::make('variants') + ->relationship() + ->schema([ + // ... + ]) + ->afterUpdate(function (array $data, Model $record): void { + if (isset($data['attributes'])) { + $record->attributes()->sync($data['attributes']); + } + }) +``` + +You can inject various utilities into the function passed to `afterUpdate()` as parameters. + +### Running code after deleting a related item + +You may run code after a related item is deleted from the database using the `afterDelete()` method. This method accepts a closure that receives the record that was just deleted in a `$record` variable. The record will no longer exist in the database at this point, but you can still access its attributes, such as its ID: + +```php +use Filament\Forms\Components\Repeater; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Facades\Storage; + +Repeater::make('attachments') + ->relationship() + ->schema([ + // ... + ]) + ->afterDelete(function (Model $record): void { + Storage::delete($record->file_path); + }) +``` + +You can inject various utilities into the function passed to `afterDelete()` as parameters. + ### Modifying related records after retrieval You may filter or modify the related records of a repeater after they are retrieved from the database using the `modifyRecordsUsing` argument. This method accepts a function that receives a `Collection` of related records. You should return the modified collection. diff --git a/packages/forms/src/Components/Repeater.php b/packages/forms/src/Components/Repeater.php index ee7a0318621..967409b47b3 100644 --- a/packages/forms/src/Components/Repeater.php +++ b/packages/forms/src/Components/Repeater.php @@ -102,6 +102,12 @@ class Repeater extends Field implements CanConcealComponents, HasExtraItemAction protected ?Closure $mutateRelationshipDataBeforeSaveUsing = null; + protected ?Closure $afterCreate = null; + + protected ?Closure $afterUpdate = null; + + protected ?Closure $afterDelete = null; + /** * @var array | null */ @@ -956,7 +962,10 @@ public function relationship(string | Closure | null $name = null, ?Closure $mod $relationship ->whereKey($recordsToDelete) ->get() - ->each(static fn (Model $record) => $record->delete()); + ->each(static function (Model $record) use ($component): void { + $record->delete(); + $component->callAfterDelete($record); + }); } $itemOrder = 1; @@ -984,6 +993,8 @@ public function relationship(string | Closure | null $name = null, ?Closure $mod $translatableContentDriver->updateRecord($record, $itemData) : $record->fill($itemData)->save(); + $component->callAfterUpdate($itemData, $record); + continue; } @@ -1004,6 +1015,7 @@ public function relationship(string | Closure | null $name = null, ?Closure $mod $record = $relationship->save($record); $item->model($record)->saveRelationships(); + $component->callAfterCreate($itemData, $record); $existingRecords->push($record); } @@ -1353,6 +1365,83 @@ public function mutateRelationshipDataBeforeSave(array $data, Model $record): ?a return $data; } + public function afterCreate(?Closure $callback): static + { + $this->afterCreate = $callback; + + return $this; + } + + public function afterUpdate(?Closure $callback): static + { + $this->afterUpdate = $callback; + + return $this; + } + + public function afterDelete(?Closure $callback): static + { + $this->afterDelete = $callback; + + return $this; + } + + /** + * @param array $data + */ + protected function callAfterCreate(array $data, Model $record): void + { + if ($this->afterCreate instanceof Closure) { + $this->evaluate( + $this->afterCreate, + namedInjections: [ + 'data' => $data, + 'record' => $record, + ], + typedInjections: [ + Model::class => $record, + $record::class => $record, + ], + ); + } + } + + /** + * @param array $data + */ + protected function callAfterUpdate(array $data, Model $record): void + { + if ($this->afterUpdate instanceof Closure) { + $this->evaluate( + $this->afterUpdate, + namedInjections: [ + 'data' => $data, + 'record' => $record, + ], + typedInjections: [ + Model::class => $record, + $record::class => $record, + ], + ); + } + } + + protected function callAfterDelete(Model $record): void + { + if ($this->afterDelete instanceof Closure) { + $this->evaluate( + $this->afterDelete, + namedInjections: [ + 'record' => $record, + ], + typedInjections: [ + Model::class => $record, + $record::class => $record, + ], + ); + } + } + public function canConcealComponents(): bool { return $this->isCollapsible();