<?php
/*
    Release under the GNU public license
    Copyright Adam Stevenson adamstevenson@ _no_spam_  gmail.com (www.adamstevenson.net)
    If you use this script please send me an email, and let me know how it works for you,
    or any bugs you find. Also, if you want to link to my site I won't complain.

    Basically this is just a hash table over shared memory for caching php pages and 
    serialized objects.  You need to have php compiled with shared memory enabled.
    Simple garbage collection is implemented, but is only run when storing data in the
    cache and not returning pages to improve performance.

    It is very very fast.
    When caching was in place I was getting 55k byte pages returned over a local network
    in 0.02 ms, where before it was about 2.00 seconds.

    There is some support for debugging, but it is all commented out to gain a little
    more in performance.


    Note:  Versions of Windows previous to Windows 2000 do not support shared memory. 
    Under Windows, Shmop will only work when PHP is running as a web server module, 
    such as Apache or IIS (CLI and CGI will not work).

    Basic usage:

    For caching php rendered pages
        include 'mycached.php';
        do_cache();

    do_cache will automatically handle the garbage collection when storing pages.


    For caching php objects, you can use the store_value and get_value functions.

    store_value takes a key and value like any hashtable, and get_value just takes
    the key and returns the value if there is one.

    store_value('my_unique_key', $var);
    $var = get_value('my_unique_key');

    Directly accessing the store_value and get_value function does not do any
    garbage collection.  You can do this yourself by calling garbage_collection();
    in your scripts.


    To clear all the shared memory when testing, or what not you can use the unix
    command ipcs, and ipcrm.  Here is what I use which removes the shared memory
    segments, and also semaphores
    ipcs | cut -d" " -f2 | xargs -n1 ipcrm -s
    ipcs | cut -d" " -f2 | xargs -n1 ipcrm -m


    For even more robust caching, check out http://www.danga.com/memcached/

*/

DEFINE('CACHE_SECONDS'60 60);
DEFINE('LOG'false);
DEFINE('LOG_FILE''/tmp/debug.log' );
DEFINE('HASH_PRIME'2147483647); //2^31 -1
DEFINE('LOCK''/');
DEFINE('HASHID''hash_table_key');
 
function 
log_this($log_string){
    if(!
LOG) return;
    
$file fopen(LOG_FILE"a");
    
fwrite($file$log_string);
    
fclose($file);
    return;
}

function 
delete_mem($id){
    if (!
shmop_delete($id)){
        
//log_this("Couldn't mark shared memory block for deletion.\n");
    
}
}

function &
read_mem($id){
    return 
shmop_read($id0shmop_size($id));
}

function 
write_mem($id$data){
    if(
shmop_size($id)< strlen($data)) return false;
    if(!
shmop_write($id$data0)){
        
//log_this("Could not write to shared memory segment\n");
        
return false;
    }
    return 
true;
}

function 
create_mem($key$size){
    
$id = @shmop_open($key"n"0644$size);
    if (!
$id) {
        
//log_this("Couldn't create shared memory segment with key = $key\n");
    
}
    return 
$id;
}

function 
mem_exist($key){
    if(!
$id = @shmop_open($key"a"0644100)){
        
//log_this("Couldn't find shared memory segment with key = $key\n");
        
return 0;
    }
    
//log_this("Memory segment exists with key = $key\n");
    
return $id;
}


function 
close_mem($id){
    if(
$id!=0shmop_close($id);
}

function &
get_value($key){
    
$hash_id =& hash($key);
    
$value = array();
    
$id =& mem_exist($hash_id);
    while(
$value['key']!=$key){
        if(
$id!=0){
            
$value =& unserialize(read_mem($id));
            if(
$value['key']!=$key$id =& mem_exist(hash($hash_id));
        } else {
            
//log_this("no key in hash table\n");
            
close_mem($id);
            return 
"";
        }
    }
    
close_mem($id);
    return 
$value['value'];
}

function 
store_value($key,$value){
    
$SHM_KEY ftok(LOCK'R');
    
$shmid = @sem_get($SHM_KEY10240644 IPC_CREAT);
    
sem_acquire($shmid);
    
$hash_id hash($key);
    
$store_value = array();
    
$store_value['key'] = trim($key);
    
$store_value['value'] = $value;
    
$store_value['time'] = time();
    
$id mem_exist($hash_id);
    while(
$id!=0){
        
$value unserialize(read_mem($id));
        if(
$value['key']==$key){
            
//log_this("Key " . $key . "   $hash_id(" . dechex($hash_id) .") already in hash table, replacing with new contents\n");
            
delete_mem($id);
            
close_mem($id);
            
$contents serialize($store_value);
            
$id create_mem($hash_id,strlen($contents));
            
write_mem($id$contents); // place into memory
            
close_mem($id);
            
sem_release($shmid);
            return 
$hash_id;
        }
        
close_mem($id);
        
//log_this("Collision while trying to store key=$key in hash table\n");
        
$hash_id hash($hash_id);
        
$id mem_exist($hash_id);
    }
    
$contents serialize($store_value);
    
$id create_mem($hash_id,strlen($contents));
    
write_mem($id, &$contents); // place into memory
    
close_mem($id);
    
sem_release($shmid);
    return 
$hash_id;
}

function 
update_keys($key$id){
    
$SHM_KEY ftok(LOCK'R');
    
$shmid = @sem_get($SHM_KEY10240644 IPC_CREAT);
    
sem_acquire($shmid);
    
$temp =& get_value(HASHID);
    
$temp[$key] = array('shmid' => $id'time' => time());
    
store_value(HASHID,$temp);
    
sem_release($shmid);
}

function &
hash($hash_string){
    
$hash =& fmod(hexdec(md5($hash_string)), HASH_PRIME);
    
//log_this("Hashing " . $hash_string . " to " . $hash . "\n"); 
    
return $hash;
}

function 
cache_end($contents){
    if(
trim($contents)){
        
$datasize strlen($contents);
        
$hash_string "http://".$_SERVER["SERVER_NAME"].$_SERVER["REQUEST_URI"].serialize($_POST);
        
$shmid store_value($hash_string,$contents);
        
update_keys($hash_string,$shmid);
    }
        return 
$contents//display
}

function 
delete_key($key){
    
$SHM_KEY ftok(LOCK'R');
    
$shmid = @sem_get($SHM_KEY10240644 IPC_CREAT);
    
sem_acquire($shmid);
    
$data get_value(HASHID);
    if(isset(
$data[$key])){
        
$v $data[$key];
        
$id mem_exist($v['shmid']);
        
delete_mem($id);
        
close_mem($id);
        unset(
$data[$key]);
        
store_value(HASHID$data);
    }
    
sem_release($shmid);
}

function 
clear_cache(){
    
$SHM_KEY ftok(LOCK'R');
    
$shmid = @sem_get($SHM_KEY10240644 IPC_CREAT);
    
sem_acquire($shmid);
    
$data get_value(HASHID);
    
print_r($data);
    foreach (
$data as $k => $v){
        
$id mem_exist($v['shmid']);
        
delete_mem($id);
        
close_mem($id);
    }
    
$data = array();
    
store_value(HASHID$data);
    
sem_release($shmid);
}


function 
garbage_collection(){
    
$SHM_KEY ftok(LOCK'R');
    
$shmid = @sem_get($SHM_KEY10240644 IPC_CREAT);
    
sem_acquire($shmid);
    
$data get_value(HASHID);
    foreach (
$data as $k => $v){
        if(
time() - $v['time'] > CACHE_SECONDS){
            
//log_this("garbage collection found expired key $k, value $v[shmid] in hash table... deleting\n");
            
$id mem_exist($v['shmid']);
            
delete_mem($id);
            
close_mem($id);
            unset(
$data[$k]);
        }
    
store_value(HASHID$data);
    }
    
sem_release($shmid);
}

function 
do_cache(){
    
$key "http://".$_SERVER["SERVER_NAME"].$_SERVER["REQUEST_URI"].serialize($_POST);
    
$contents =& get_value($key);
    if(
$contents){
        
//log_this("Cache hit for " . $key . "\n");
        
print $contents;
        exit;
    }
    
garbage_collection();
    
ob_start("cache_end"); // callback
}

?>