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

Model Locking Laravel Package

sofa/model-locking

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Installation:

    composer require sofa/model-locking:"~5.3"
    php artisan vendor:publish --provider="Sofa\ModelLocking\ServiceProvider"
    php artisan migrate
    
    • For Laravel 5.5+, the service provider auto-registers. For 5.3–5.4, manually add Sofa\ModelLocking\ServiceProvider::class to config/app.php.
  2. Enable Locking: Add the Locking trait to your Eloquent model:

    use Sofa\ModelLocking\Locking;
    
    class Post extends Model
    {
        use Locking;
    }
    
  3. First Use Case: Lock a model in a controller to prevent concurrent edits:

    public function edit(Post $post)
    {
        if ($post->isLocked()) {
            return response()->json(['error' => 'Post is locked'], 423);
        }
        $post->lock();
        // Proceed with edit logic...
    }
    

Where to Look First

  • Configuration: config/model_locking.php (default: 30-minute lock duration, locks table name).
  • Events: Broadcasted LockAcquired/LockReleased events (extendable via Locking trait).
  • Migrations: database/migrations/[timestamp]_create_model_locks_table.php (customize if needed).

Implementation Patterns

Core Workflows

  1. Locking a Model:

    // Optimistic lock (checks before locking)
    if (!$post->isLocked()) {
        $post->lock(); // Creates a record in `locks` table
    }
    
    // Force lock (overrides existing locks)
    $post->forceLock();
    
  2. Handling Locks in Controllers:

    public function update(Request $request, Post $post)
    {
        if ($post->isLocked()) {
            return back()->with('error', 'Post is locked by another user');
        }
        $post->lock();
        try {
            $post->update($request->all());
        } finally {
            $post->releaseLock(); // Critical: Always release!
        }
    }
    
  3. Broadcasting Lock Events: Subscribe to events in EventServiceProvider:

    protected $listen = [
        'Sofa\ModelLocking\Events\LockAcquired' => [
            'App\Listeners\NotifyUserOfLock',
        ],
    ];
    
  4. Custom Lock Logic: Override trait methods in your model:

    class Post extends Model
    {
        use Locking;
    
        protected function getLockDuration()
        {
            return now()->addMinutes(60); // Custom duration
        }
    }
    

Integration Tips

  • Queue Lock Releases: Use Laravel queues to defer releaseLock() for long-running operations:

    $post->lock();
    dispatch(new ReleaseLockJob($post))->delay(now()->addMinutes(10));
    
  • Lock Validation Middleware: Create middleware to auto-check locks:

    public function handle($request, Closure $next)
    {
        if ($request->post->isLocked()) {
            abort(423, 'Resource locked');
        }
        return $next($request);
    }
    
  • Soft Deletes: Extend the Locking trait to handle soft-deleted models:

    protected function getLockQuery()
    {
        return $this->newQuery()->whereNull('deleted_at');
    }
    

Gotchas and Tips

Pitfalls

  1. Forgetting to Release Locks:

    • Issue: Unreleased locks accumulate, blocking other users.
    • Fix: Use finally blocks or queues to ensure release:
      try {
          $post->lock();
          // Critical section
      } finally {
          $post->releaseLock();
      }
      
  2. Race Conditions:

    • Issue: Two requests may check isLocked() simultaneously and both proceed to lock.
    • Fix: Use forceLock() if you need to override existing locks (but handle conflicts gracefully).
  3. Lock Table Bloat:

    • Issue: Old lock records linger if not cleaned up.
    • Fix: Run a scheduled job to purge expired locks:
      // app/Console/Commands/CleanLocks.php
      public function handle()
      {
          \Sofa\ModelLocking\Lock::where('expires_at', '<', now())
              ->delete();
      }
      
  4. Broadcasting Delays:

    • Issue: Events may fire after the lock is released if using queues.
    • Fix: Dispatch events synchronously for immediate notifications:
      event(new LockAcquired($this));
      

Debugging

  • Check Locks Table:
    SELECT * FROM locks WHERE model_type = 'App\Post' AND model_id = 1;
    
  • Log Events: Add a listener to log lock acquisitions/releases:
    public function handle(LockAcquired $event)
    {
        Log::info('Lock acquired', ['model' => $event->model]);
    }
    

Extension Points

  1. Custom Lock Storage: Override getLockQuery() to use a different table or database:

    protected function getLockQuery()
    {
        return \DB::connection('mysql_replica')->table('locks')->where(...);
    }
    
  2. Lock Conditions: Add dynamic lock logic (e.g., user-specific locks):

    public function lockForUser($userId)
    {
        $this->lock();
        \DB::table('user_locks')->insert([
            'model_type' => $this->getMorphClass(),
            'model_id' => $this->id,
            'user_id' => $userId,
            'expires_at' => now()->addMinutes(30),
        ]);
    }
    
  3. Lock Expiration Callbacks: Extend the Locking trait to trigger actions on expiration:

    protected function onLockExpired()
    {
        // Notify user, log, or trigger cleanup
    }
    

Config Quirks

  • Lock Duration: Default is 30 minutes (config/model_locking.php). Adjust based on workflow needs (e.g., 5 minutes for checkout flows).
  • Table Name: Default is locks. Change via locks_table in config if conflicts arise.
  • Event Broadcasting: Ensure QUEUE_CONNECTION in .env is configured if using queues for events.
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.
jayeshmepani/jpl-moshier-ephemeris-php
elnasnato/laraliveui
labrodev/rest-sdk
sampaui/sampaui
babelqueue/php-sdk
facebook/capi-param-builder-php
babelqueue/symfony
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