Hi all haven’t posted in a while have a couple of posts to catch up on
I’ve recently have the need to perform locking in php on the server level. Basically I want just a single php script to execute a piece of code at any time. This requirement was brought about by race conditions whereby two php scripts would go and execute the same piece of code which would consequently conflict with one another. I needed to stop this. The solution is nice and easy and works a treat.
Whenever a php script comes in and requests a lock on a piece of code it attempts to create a directory using phps mkdir with a specific name myLock1. If the directory already exists then the mkdir returns false and the locking fails. If the mkdir completes then the php script has locked the section of code. Now when another seperate php script comes in and tries to create the directory mkdir will return false and the locking will fail.
When the php script is finished with a piece of code then it unlocks by deleting the directory using rmdir. Now a new php script is free to come in lock the piece of code again.
This locking works across all php scripts (i.e. is server wide) and provides named locks as you can give the directories different names. So one section of code may be locking a myLock1 directory and another part be locking myLock2 directory but these won’t interfere with one another. I’ve attached the code for this simple locking below and highlighed how it would be used to lock a piece of code.
class FLock
{ private $files = array();
private $oUUID = "";
function __construct()
{ $this->oUUID = new UUID();
}
function __destruct()
{ foreach($this->files as $file)
{
fclose($this->files['$file']);
}
}
private function saveFileHandle($filepath)
{
if(!array_key_exists("$filepath",$this->files))
{
$this->files["$filepath"] = array();
$this->files["$filepath"]['directory'] = $filepath;
$this->files["$filepath"]['locked'] = false;
}
}
public function attemptFileLock($filepath=false, $lockFile=true)
{
global $fileLockingLocation;
$this->saveFileHandle($filepath);
$canLock = false;
if($lockFile == true)
{ if(!$this->files["$filepath"]['locked'])
{ try
{
$canLock = mkdir("$filepath");
if($canLock)
{
$this->files["$filepath"]['locked'] = true;
}
}
catch(Exception $ex)
{
LogError($ex);
}
}
}
else
{ if($this->files["$filepath"]['locked'])
{
rmDir("$filepath");
$this->files["$filepath"]['locked'] = false;
//TODO - Fix
return true;
}
}
return $canLock;
}
public function removeAllFileLocks()
{ foreach($this->files as $file)
{ rmDir($file['directory']);
$this->files[$file['directory']]['locked'] = false;
}
}
}
To lock the some code you need to just do like what I’ve done below.
$this->oFL = new fLock();
$fileLockResult = $this->oFL->attemptFileLock("/fileLocks/".$lockName."_lock",true);
if($fileLockResult)
{
//Perform mySQL inserts,deletes etc.
$fileLockResult = $this->oFL->attemptFileLock("/fileLocks/".$lockName."_lock",false);
}
else
{
//Sleep between 0.5 - 1 second
usleep(500000+(mt_rand(1,10))*500000);
}
I’ve had to perform php redirects of late also, its nice and easy but as Michael found out, even when you put a redirect in a chunk of code, the rest of the script gets parsed and the redirection doesn’t occur straight away. So you may want to put in a die() or something after the redirect if you wish to abort the rest of the page’s processing.
header( 'Location: http://www.yoursite.com/new_page.html' ) ;
Performing error logging with PHP is another task I’ve wanted to do of late. Basically I’ve created a php wrapper page that takes in a script location from the Asterisk dial plan and executes the passed in script within a try catch block. Wrapping the script execution within the try provides a simple error logging mechanism. Anywhere I want to log errors that are fatal to the application I can just perform a throw within a php script and the top-most wrapper will always catch and log the error.
#!/usr/bin/php -q
<?php
require_once $phpScriptPath.'common/phpagi.php';
require_once ("utility/logging.php");
global $agi;
try
{ if(!isset($agi))
{ $agi = new AGI();
}
$scriptName = $agi->get_variable("scriptName");
$agi->exec("AGI",$scriptName['data']);
}
catch(Exception $ex)
{ if(!isset($agi))
{ $agi = new AGI();
}
$errorFileName = 'error-'."date-".date("dmy_His");
$errorInfo = "Error message: ".$ex->getMessage();
$errorCode = "Error code: ".$ex->getCode();
$errorFile = "File: ".$ex->getFile();
$errorLineNo = "Line number: ".$ex->getLine();
if(!isset($agi))
{
$errorCode = $errorCode." - AGI cannot be created.";
$errorFile = $errorFile." - applicationWrapper.php catch block.";
}
//Log to error log directory
writeVariableToFile($errorInfo,$errorFileName);
writeVariableToFile($errorCode,$errorFileName,true);
writeVariableToFile($errorFile,$errorFileName,true);
writeVariableToFile($errorLineNo,$errorFileName,true);
if(isset($agi))
{ //Log to terminal if possible
$agi->verbose($errorInfo);
$agi->verbose($errorCode);
$agi->verbose($errorFile);
$agi->verbose($errorLineNo);
$agi->text2wav($ex->getMessage());
}
//Log to php error log
error_log($errorInfo);
error_log($errorCode);
error_log($errorFile);
error_log($errorLineNo);
exit(0);
}
exit(0);
?>
The error_log is a built in php error logging system. You can specify where PHP logs errors to within you php.ini config file, I set it up to log to the system log by uncommenting the error_log = syslog line but you can easily setup your own location for logging errors.
From the Asterisk dial plan i.e. the extensions.conf you basically have something like
exten => s,n,Set(scriptName=/my/Path/To/File/myfile.php)
exten => s,n,AGI(/path/to/wrapper/applicationWrapper.php)
exten => s,n,Hangup()