balismatz/filament-prevent-outdated-record-update
Installation:
composer require balismatz/filament-prevent-outdated-record-update:^5.0
Ensure your project meets requirements: PHP 8.2+, Laravel 11.28+, Filament 5.
First Use Case:
Add the preventOutdatedRecordUpdate() method to an EditAction in your Filament resource:
EditAction::make()
->label('Edit Record')
->preventOutdatedRecordUpdate();
Key Files:
config/filament-prevent-outdated-record-update.php (published via php artisan vendor:publish).resources/lang/ for translations (if published).Hook Integration:
The package leverages Filament’s beforeFormValidated() hook to compare the record’s updated_at timestamp with the server’s current time. If another user has modified the record (or the form was left open), the update is blocked.
Action Integration:
->preventOutdatedRecordUpdate() to any EditAction in your resource:
EditAction::make()
->preventOutdatedRecordUpdate()
->mutateFormDataUsing(fn (array $data) => [...]);
use BalisMatz\FilamentPreventOutdatedRecordUpdate\Facades\PreventOutdatedRecordUpdate;
Action::make('Custom Update')
->action(fn () => PreventOutdatedRecordUpdate::check($record));
Resource-Level Configuration:
Apply globally to all edit actions in a resource by extending the EditAction class:
namespace App\Filament\Resources\YourResource\Actions;
use BalisMatz\FilamentPreventOutdatedRecordUpdate\Actions\EditAction;
class CustomEditAction extends EditAction
{
public function configure(): void
{
$this->preventOutdatedRecordUpdate();
}
}
Form Data Handling:
If your form includes updated_at as a hidden field (e.g., for optimistic locking), the package will ignore it. Ensure your updated_at column is not included in the form data.
Conditional Checks: Disable the check for specific records or actions:
EditAction::make()
->preventOutdatedRecordUpdate(fn ($record) => !$record->isAdminEditable());
Custom Timestamps:
Override the default updated_at check by publishing the config and setting:
'timestamp_column' => 'custom_updated_at',
Integration with Soft Deletes: Exclude soft-deleted records from checks:
EditAction::make()
->preventOutdatedRecordUpdate()
->requiresConfirmation()
->mutateFormDataUsing(fn ($data, $record) => [
// Skip check if record is soft-deleted
'skip_check' => $record->trashed(),
]);
Testing:
Mock the updated_at timestamp in tests:
$record->update(['updated_at' => now()->subMinute()]);
$this->assertSoftDeleted($record); // Simulate concurrent edit
Hook Order:
beforeFormValidated(), call it before preventOutdatedRecordUpdate(). The package relies on the original updated_at value.EditAction::make()
->beforeFormValidated(fn (array $data) => $this->mutateData($data))
->preventOutdatedRecordUpdate(); // Must come after!
Time Synchronization:
updated_at with the server’s time, not the client’s. Ensure your server time is accurate (e.g., via NTP).\Log::debug('Record updated_at:', [$record->updated_at, now()]);
Hidden Fields:
updated_at as a hidden field (e.g., for optimistic locking), the package will not override it. This can cause false positives. Exclude it from form data:
->form([
// Explicitly exclude updated_at
Hidden::make('updated_at')->disabled(),
]);
Database Transactions:
version column).retry helper).Caching:
updated_at values. For high-traffic apps, consider caching the latest updated_at per record (e.g., with Redis) to reduce database load.Custom Notifications: Override the default notification by publishing the views:
php artisan vendor:publish --tag="filament-prevent-outdated-record-update-views"
Then customize resources/views/vendor/filament-prevent-outdated-record-update/notifications/outdated-record.blade.php.
Performance:
SELECT query per update. For bulk operations, disable it:
EditAction::make()
->preventOutdatedRecordUpdate(false); // Disable
Localization:
el.json or en.json to resources/lang/{locale}/filament-prevent-outdated-record-update.json.Debugging:
'debug' => env('APP_DEBUG', false),
[FilamentPreventOutdatedRecordUpdate] Record updated_at is outdated. Current: [now], Record: [updated_at].
Extension Points:
use BalisMatz\FilamentPreventOutdatedRecordUpdate\Contracts\OutdatedRecordChecker;
class CustomChecker implements OutdatedRecordChecker
{
public function isOutdated($record): bool
{
return $record->updated_at->lt(now()->subSeconds(5)); // Custom logic
}
}
Bind it in AppServiceProvider:
$this->app->bind(OutdatedRecordChecker::class, CustomChecker::class);
How can I help you explore Laravel packages today?