PHP MySQL Row Lock function

I just started on a project that I knew was going to need some sort of data lock that would prevent 2 users from editing the same data at the same time.  At first I thought that i would have to write a function that behaved like the C function flock().  I ran across this forum post http://www.dmcinsights.com/phorum/read.php?13,30233,30260 which I have to give credit to is the inspiration for the code below.

In my setup I have a master include file that is included every time the script runs.  It checks the session to see if a lock code has been assigned to the use.  If so then it declares DB_LOCK_KEY as a constant

//### setup the lock key for locking db rows ####
if(empty($_SESSION['lock_key'])){
	$lock_key=mkUniqueCode();//see below for function declaration
	$_SESSION['lock_key']=$lock_key;
	define('DB_LOCK_KEY',$lock_key);
}else{
	define('DB_LOCK_KEY',$_SESSION['lock_key']);
}

There are a couple of functions used in dbLockRow() that aren't standard php functions.  Here is the run down of what they do

  • dbQuery() - queries the database | just swap it out with your own function
  • addMessage() - is  a function that add a string to a session variable
  • location() -  same as:session_write_close(); header('location: '.$location);

Here is a sample a table that this function could be used

CREATE TABLE `content_location` (
  `content_location_id` int(11) NOT NULL auto_increment,
  `content_location_title` varchar(255) NOT NULL default '',
  `content_location_desc` text NOT NULL,
  `content_location_lock_key` varchar(255) NOT NULL default '',
  `content_location_lock_expiry_time` timestamp NOT NULL default '0000-00-00 00:00:00',
  PRIMARY KEY  (`content_location_id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=3 ;

I have modified the query a little bit from the forum post. I added this: OR {$table}_lock_key='" . DB_LOCK_KEY . "' AND {$table}_id=" . $id This allows the same user to access the row before the lock_expiry_time runs out. I added it because if the user chose to edit a row and accidentally hit the back button they would be locked out of the row.

/**
 * dbLockRow()
 *  
 * @author Jesse Baird <jebaird@gmail.com> 
 * @version .01
 * @since 1.3  
 * 
 * @param string $table | name of the target table
 * @param integer $id 
 * @param string $dataTypeDesc | user, setting, claim etc 
 * @param bool $sendHeaders | true add message and send the user to $location 
 * @param string $message | $dataTypeDesc and $duration are tacked on the begianing and end of this string
 * @param string $location | name of the php file that you would like the user to be directed to if locking fails
 * @param string $duration | mysql timestamp for how long you would like the row to be locked
 * @return
 */
function dbLockRow($table = '', $id = 0, $dataTypeDesc = '', $sendHeaders = true, $location = this_php, $duration =
    '00:05:00', $message = 'is currently being edited by another user.  Please check try again in') {
    $res = dbQuery("UPDATE $table SET {$table}_lock_expiry_time = ADDTIME(NOW(),'" . $duration . "'),{$table}_lock_key='" .
        DB_LOCK_KEY . "' WHERE NOW() > {$table}_lock_expiry_time AND {$table}_id=" . $id ." OR {$table}_lock_key='" .
        DB_LOCK_KEY . "' AND {$table}_id=" . $id);

    if(mysql_affected_rows() == 0) {
        //lock failed - let the user know that they can't get access to the record right now
        if($sendHeaders) {
            addMessage('This '.$dataTypeDesc . ' ' . $message . ' ' . $duration); //let the user know that the row is locked
            location($location); // send the user to a different url
        } else
            return false;

    } else {
        //echo 'row locked';
        return true;

    }
}
/**
 * mkUniqueCode()
 * @uses creates a random sting of chars and nums based on the md5 hash of unipid(rand())
 * @author unknown
 * @param integer $length
 * @return mixed
 */
function mkUniqueCode($length = 10)
{
    $code = md5(uniqid(rand(), true));
    if($length != "")
        return substr($code, 0, $length);
    else
        return $code;
}