Search code examples
phplaravelconcurrencylaravel-session

Laravel - Stop concurrent access to record


In Laravel is there any way to stop interacting with the same record simultaneously. For example, if User A is editing a record then at the same time I need to stop User B from editing same record.

Note: I am using SESSION_DRIVER=file & Laravel 5.2

There are around 500+ users using the system currently and it is kind of Admin panel where admins interact with the records but I need to stop concurrent access. what would be the optimum solution.


Solution

  • Here is how I am able to achieve this.

    1) Create a migration to add 2 columns in the model is_editing and editing_by and updated_at (no need to created updated_at if already there)

    2) Make middleware php artisan make:middleware StopConcurrentOperations.

    3) Register the middleware in app/Http/Kernel.php under $routeMiddleware.

    4) Apply middleware to the controller

    $this->middleware('concurrent.operations',["only"=>["edit","update"]]);
    

    5) Block concurrent edits in middleware

    <?php
    
    namespace App\Http\Middleware;
    
    use App\OperationActivity;
    use Carbon\Carbon;
    use Closure;
    use Illuminate\Support\Facades\Auth;
    
    class StopConcurrentOperations
    {
        /**
         * Handle an incoming request.
         *
         * @param  \Illuminate\Http\Request  $request
         * @param  \Closure  $next
         * @return mixed
         */
        public function handle($request, Closure $next, $guard = null)
        {
            $operation_id = current($request->route()->parameters());
            $user_id = Auth::guard('admin')->user()->id;
            if(Auth::guard('admin')->user() && !empty($operation_id)){
                $activity = OperationActivity::firstOrNew(["operation_id" => $operation_id]);
                if($activity->is_editing && $activity->updated_at->diffInMinutes(Carbon::now())<5 && $activity->editing_by!=$user_id)
                    abort(409);
                else {
                    $activity->is_editing = true;
                    $activity->editing_by = $user_id;
                    $activity->save();
                }
            }
            return $next($request);
        }
    }
    

    here I am checking if someone already editing the record and checking the time when was editing performed.

    6) Optionally on the client side if the editing user closes the windows then to unblock the record like

      window.onbeforeunload = function() {
        var url = "{{route('unlock_route_name',$operation->id)}}";
        $.ajax({ url: url, method: 'post' })
      };
    

    to complete the process from the client side you have to create a route and a method in the controller and just unlock the record like

          $operation = Operation::findOrFail($id);
          $activity = $operation->activities()->where("is_editing",true)->first();
          $activity->is_editing = false;
          $activity->save();
    

    Note: In my example, I have different table to log activites alerady OperationActivity and likely Model name is Operation