I am working on a small class that will allow me to write queue data to a file. Similar idea to PHP $_SESSION.
I thought the following steps will do the trick
- Open a file using
fopen()
in'a+'
mode - Lock the file using
flock(
) withLOCK_EX
type to prevent another process from using the same file - Read the file's existing content using
fread()
. Then put the data into array usingjson_decode(array, true)
- Now the data is in array. If the key exists in the array update its value, otherwise insert the key to the array.
- After create an array with all the data that need to go on the file, I truncate the file using
ftruncate()
and write the new data to the file usingfwrite()
- Unlock the file using
flock(
) withLOCK_UN
type to allow another process to use it. - Finally Close the file
I believe I wrote the code to satisfy the above step in my updateCache()
method. But it does not seems to work properly. It does not keep track of the data.
Here is my class
<?php
namespace ICWS;
use \ICWS\showVar;
use \DirectoryIterator;
/**
* CacheHandler
*
* @package ICWS
*/
class CacheHandler {
/* Max time second allowed to keep a session File */
private $maxTimeAllowed = 300;
private $filename = '';
private $handle;
public function __construct( $filename )
{
$this->filename = $filename;
// 5% chance to collect garbage when the class is initialized.
if(mt_rand(1, 100) <= 5){
$this->collectGarbage();
}
}
private function print_me($a)
{
echo '<pre>';
print_r($a);
echo '</pre>';
}
/**
* Add/Update the cached array in the session
*
* @param string $key
* @param bigint $id
* @param array $field
* @return void
*/
public function updateCache($key, $id, array $field)
{
$currentVal = $field;
$this->openFile();
$this->lockFile();
$storage = $this->readFile();
//create a new if the $id does not exists in the cache
if( isset($storage[$key]) && array_key_exists($id, $storage[$key]) ){
$currentVal = $storage[$key][$id];
foreach($field as $k => $v){
$currentVal[$k] = $v; //update existing key or add a new one
}
}
$storage[$key][$id] = $currentVal;
$this->updateFile($storage);
$this->unlockFile();
$this->closeFile();
}
/**
* gets the current cache/session for a giving $key
*
* @param string $key. If null is passed it will return everything in the cache
* @return object or boolean
*/
public function getCache($key = null)
{
$value = false;
$this->openFile();
rewind($this->handle);
$storage = $this->readFile();
if(!is_null($key) && isset($storage[$key])){
$value = $storage[$key];
}
if(is_null($key)){
$value = $storage;
}
$this->closeFile();
return $value;
}
/**
* removes the $id from the cache/session
*
* @param string $key
* @param bigint $id
* @return boolean
*/
public function removeCache($key, $id)
{
$this->openFile();
$this->lockFile();
$storage = $this->readFile();
if( !isset($storage[$key][$id])){
$this->unlockFile();
$this->closeFile();
return false;
}
unset($storage[$key][$id]);
$this->updateFile($storage);
$this->unlockFile();
$this->closeFile();
return true;
}
/**
* unset a session
*
* @param argument $keys
* @return void
*/
public function truncateCache()
{
if(file_exists($this->filename)){
unlink($this->filename);
}
}
/**
* Open a file in a giving mode
*
* @params string $mode
* @return void
*/
private function openFile( $mode = "a+")
{
$this->handle = fopen($this->filename, $mode);
if(!$this->handle){
throw new exception('The File could not be opened!');
}
}
/**
* Close the file
* @return void
*/
private function closeFile()
{
fclose($this->handle);
$this->handle = null;
}
/**
* Update the file with array data
*
* @param array $data the array in that has the data
* @return void
*/
private function updateFile(array $data = array() )
{
$raw = json_encode($data);
$this->truncateFile();
fwrite($this->handle, $raw);
}
/**
* Delete the file content;
*
* @return void
*/
private function truncateFile( )
{
ftruncate($this->handle, 0);
rewind($this->handle);
}
/**
* Read the data from the opned file
*
* @params string $mode
* @return array of the data found in the file
*/
private function readFile()
{
$length = filesize($this->filename);
if($length > 0){
rewind($this->handle);
$raw = fread($this->handle, $length);
return json_decode($raw, true);
}
return array();
}
/**
* Lock the file
*
* @return void
*/
private function lockFile( $maxAttempts = 100, $lockType = LOCK_EX)
{
$i = 0;
while($i <= $maxAttempts){
// acquire an exclusive lock
if( flock($this->handle, LOCK_EX) ){
break;
}
++$i;
}
}
/**
* Unlock the file
*
* @return void
*/
private function unlockFile()
{
fflush($this->handle);
flock($this->handle, LOCK_UN);
}
/**
* Remove add cache files
*
* @return void
*/
private function collectGarbage()
{
$mydir = dirname($this->filename);
$dir = new DirectoryIterator( $mydir );
$time = strtotime("now");
foreach ($dir as $file) {
if ( !$file->isDot()
&& $file->isFile()
&& ($time - $file->getATime()) > $this->maxTimeAllowed
&& $file->getFilename() != 'index.html'
) {
unlink($file->getPathName());
}
}
}
function addSlashes($str)
{
return addslashes($str);
}
}
This is how I use it
<?php
require 'autoloader.php';
try {
$cache = new \ICWS\CacheHandler('cache/879');
$field = array('name' => 'Mike A', 'Address' => '123 S Main', 'phone' => '2152456245', 'ext' => 123);
$cache->updateCache('NewKey', 'first', $field);
$cache->updateCache('NewKey', 'second', $field);
$field = array('Address' => '987 S Main', 'phone' => '000000000000', 'ext' => 5555);
$cache->updateCache('NewKey', 'first', $field);
$field = array('locations' => array('S' => 'South', 'N' => 'North', 'E' => 'East'));
$cache->updateCache('NewKey', 'first', $field);
echo '<pre>';
print_r($cache->getCache());
echo '</pre>';
} catch (exception $e){
echo $e->getMessage();
}
?>
I am expecting the array to look like this
Array
(
[first] => stdClass Object
(
[name] => Mike A
[Address] => 987 S Main
[phone] => 000000000000
[ext] => 5555
[locations] => Array
(
[S] => South
[N] => North
[E] => East
)
)
[second] => stdClass Object
(
[name] => Mike A
[Address] => 123 S Main
[phone] => 2152456245
[ext] => 123
)
)
But after executing the first time, I get blank array. Then after I execute the script again I get the following array.
Array
(
[NewKey] => Array
(
[first] => Array
(
[locations] => Array
(
[S] => South
[N] => North
[E] => East
)
)
)
)
What could be causing the data not to update correctly?
I wrote my updateCache()
using $_SESSION
which gave me the correct output but I need to do this without sessions
Here is my sessions bases method
public function updateCache($key, $id, array $field)
{
$currentVal = (object) $field;
//create a new if the $id does not exists in the cache
if( isset($_SESSION[$key]) && array_key_exists($id, $_SESSION[$key]) ){
$currentVal = (object) $_SESSION[$key][$id];
foreach($field as $k => $v){
$currentVal->$k = $v; //update existing key or add a new one
}
}
$_SESSION[$key][$id] = $currentVal;
}
The problem lies here:
The documentation of
filesize()
states:Because the file is empty when you start, it would have cached that information and your branch is skipped entirely until the next script execution. This should fix it: