Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Filament Prevent Outdated Record Update Laravel Package

balismatz/filament-prevent-outdated-record-update

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require balismatz/filament-prevent-outdated-record-update:^5.0
    

    Ensure your project meets requirements: PHP 8.2+, Laravel 11.28+, Filament 5.

  2. First Use Case: Add the preventOutdatedRecordUpdate() method to an EditAction in your Filament resource:

    EditAction::make()
        ->label('Edit Record')
        ->preventOutdatedRecordUpdate();
    
  3. Key Files:

    • Review config/filament-prevent-outdated-record-update.php (published via php artisan vendor:publish).
    • Check resources/lang/ for translations (if published).

Implementation Patterns

Core Workflow

  1. 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.

  2. Action Integration:

    • Edit Actions: Add ->preventOutdatedRecordUpdate() to any EditAction in your resource:
      EditAction::make()
          ->preventOutdatedRecordUpdate()
          ->mutateFormDataUsing(fn (array $data) => [...]);
      
    • Custom Actions: For non-standard actions, manually call the service:
      use BalisMatz\FilamentPreventOutdatedRecordUpdate\Facades\PreventOutdatedRecordUpdate;
      
      Action::make('Custom Update')
          ->action(fn () => PreventOutdatedRecordUpdate::check($record));
      
  3. 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();
        }
    }
    
  4. 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.


Advanced Patterns

  1. Conditional Checks: Disable the check for specific records or actions:

    EditAction::make()
        ->preventOutdatedRecordUpdate(fn ($record) => !$record->isAdminEditable());
    
  2. Custom Timestamps: Override the default updated_at check by publishing the config and setting:

    'timestamp_column' => 'custom_updated_at',
    
  3. 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(),
        ]);
    
  4. Testing: Mock the updated_at timestamp in tests:

    $record->update(['updated_at' => now()->subMinute()]);
    $this->assertSoftDeleted($record); // Simulate concurrent edit
    

Gotchas and Tips

Pitfalls

  1. Hook Order:

    • Critical: If you use beforeFormValidated(), call it before preventOutdatedRecordUpdate(). The package relies on the original updated_at value.
    • Example:
      EditAction::make()
          ->beforeFormValidated(fn (array $data) => $this->mutateData($data))
          ->preventOutdatedRecordUpdate(); // Must come after!
      
  2. Time Synchronization:

    • The check compares updated_at with the server’s time, not the client’s. Ensure your server time is accurate (e.g., via NTP).
    • Debugging: Add a log to verify timestamps:
      \Log::debug('Record updated_at:', [$record->updated_at, now()]);
      
  3. Hidden Fields:

    • If your form includes 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(),
      ]);
      
  4. Database Transactions:

    • The check runs before the update transaction. If another user’s update commits between the check and your update, the package cannot detect it. For critical systems, consider:
      • Database-level optimistic locking (e.g., version column).
      • Retrying failed updates (e.g., with Laravel’s retry helper).
  5. Caching:

    • The package does not cache updated_at values. For high-traffic apps, consider caching the latest updated_at per record (e.g., with Redis) to reduce database load.

Tips

  1. 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.

  2. Performance:

    • The check adds a single SELECT query per update. For bulk operations, disable it:
      EditAction::make()
          ->preventOutdatedRecordUpdate(false); // Disable
      
  3. Localization:

    • Add translations for your language by copying el.json or en.json to resources/lang/{locale}/filament-prevent-outdated-record-update.json.
  4. Debugging:

    • Enable debug mode in config to log failed updates:
      'debug' => env('APP_DEBUG', false),
      
    • Check logs for entries like:
      [FilamentPreventOutdatedRecordUpdate] Record updated_at is outdated. Current: [now], Record: [updated_at].
      
  5. Extension Points:

    • Custom Logic: Extend the service to add pre-check logic:
      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);
      
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
hamzi/corewatch
minionfactory/raw-hydrator
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle
atriumphp/atrium
sandermuller/package-boost-laravel
sandermuller/boost-skills
redaxo/core
yusufgenc/filament-api-forge
l3aro/rating-star-for-filament
leek/filament-subtenant-scope