Reputation: 5424
I have a shopping cart I built myself that uses session variables to maintain the state of cart across requests. I have an increment and decrement button that allows users to increase or decrease the quantity of a product in their cart. this happens via an Ajax request. The cart class operates by restoring the cart out of the session when constructed, and saving the cart back to the session when destructed.
<?php
class Cart {
/**
* constructor
*/
public function __construct(){
//restore cart
$this->restore();
}
/**
* destructor
*/
public function __destruct(){
//save cart
$this->save();
}
/**
* restore
*/
public function restore(){
//retrieve session info
if(Session::has('cart')){
//get cart
$session = Session::get('cart');
//assign session info
$this->data = ($session['data']);
$this->rates = $session['rates'];
$this->lines = $session['lines'];
}
}
/**
* save
*/
public function save(){
Session::put('cart', $this->forSession());
}
the problem I'm running into is a race condition with multiple Ajax requests. the user can hit the button many times, sending multiple Ajax requests. each request is therefore pulling the current state of the session, doing the operation, and then saving it. the problem is the previous transaction is not necessarily completed and saved when it restore
s the cart. my first fix was to make any subsequent Ajax requests cancel the previous one, to both cut down on unnecessary (immediately overwritten) requests, and also to help avoid this race condition. while it seemed to help, it still acts quirky. So my next thought was to attack it at the source, namely the cart class itself. my idea was to implement some type of 'locking' to prevent the cart from being accessed until a previous operation was completed. The idea looked something like this.
<?php
/**
* is cart locked
*/
public function isCartLocked(){
if(Session::get('cartLock') === 1){
sleep(1);
$this->isCartLocked();
}
}
public function restore(){
Session::put('cartLock', 0);
//check if cart is locked
$this->isCartLocked();
//lock cart
Session::put('cartLock', 1);
...
}
public function save(){
//unlock the cart
Session::put('cartLock', 0);
...
}
now the first question is, SHOULD I be doing something like this, with the locking? And then, if so, is this a decent way to handle it?
After my first attempt at it, the problem I seem to be running into is the destructor is not necessarily called all the time, which is causing my cart to stay locked, and eventually causing a timeout error.
Thanks for any help!
Upvotes: 0
Views: 1444
Reputation: 1289
I would have to say that if you are running into race conditions then you have some issues in regards to your application design, and these sorts of issues may haunt your application for a while since concurrent accesses of the session will be smashing your session store relatively frequently
The easiest way to do something like this in my opinion would be with a database table and instead of updating a field one would be adding a row for each user event ( so a increment action would insert , , , 1. And a decrement would insert a row of -1 )
This way you can simply do a sum operation to tally your counts, and when the users checkout process is complete you can clear up the entire order in the cart table .
Upvotes: 0
Reputation: 405
I'd think that you would actually want to debounce the function call to the AJAX request so that you're not concerning yourself with locking/unlocking or cancelling previous requests - just make one once you're relatively sure they're done adjusting the quantity.
I would recommend this jQuery plugin for throttling or debouncing JavaScript function calls (though jQuery actually isn't required to use this, it's just accessible through the jQuery namespace if available), assuming that's how you're making your AJAX request.
Upvotes: 1