PHP Classes
elePHPant
Icontem

File: itemCache.inc.php

Recommend this page to a friend!
  Classes of Colin McKinnon  >  Item Cache  >  itemCache.inc.php  >  Download  
File: itemCache.inc.php
Role: Class source
Content type: text/plain
Description: Class definition
Class: Item Cache
Store values in cache with underflow and overflow
Author: By
Last change: Bug fix
Date: 4 years ago
Size: 6,036 bytes
 

Contents

Class file image Download
<?php
/**
 * @author Colin McKinnon
 * @licence LGPL version 3
 * @package item cache
 *
 * The class implements a key store cache for PHP code
 * By default it will discard items based on least-recently-used but
 * can be configured to pass the discard entries to a callback function.
 * Further, it can evict chunks of entities at a time - so if the discarded
 * items are written to storage, this should reduce I/O
 *
 * The flush method evicts all entries (invoking the callback with chunks
 * of entries where appropriate)
 *
 * It is also possible (via a callback mechanism) to let an external function
 * (or method) find data not currently in the cache. The external function is passed
 * a reference to $this and should call $this->add() where it finds the item
 *
 * method sigs:
 *
 * __construct($max_entries=100, $overflow=false, $overflowChunk=1, $underflow=false)
 * add($key, $val)
 * get($key, $refresh_cache=true)
 * expire($key)
 * flush($sorted=false)
 */

define('ICACHE_NO_CHANGE', 0);
define('ICACHE_UPDATED', 1);
define('ICACHE_INSERTED', 2);

class
itemCache {
private
$curr_size;
private
$max_count;
private
$data;
private
$serial;
private
$overflow;
private
$overflowChunk;
private
$hits;
private
$misses;
private
$evicts;
/**
 * Constructor
 *
 * @param int max_entries - size of cache in entries
 * @param callback overflow - callback to handle items evicted from cache or false if none
 * @param int overflowChunk - number of entries to evict at a time
 * @param callback underflow - callack to invoke when item not found in cache
 *
 * A size of more than about 300 items is likely to have an adverse effect on performance
 * if it's possible to fetch items from a database using the $underflow callback
 */
public function __construct($max_entries=100, $overflow=false, $overflowChunk=1, $underflow=false)
{
   
$this->max_count=$max_entries;
   
$this->serial=0;
   
$this->curr_size=0;
   
$this->data=array();
    if (
$overflow && $overflowChunk>0) {
       
$this->overflow=$overflow;
       
$this->overflowChunk=($overflowChunk > $max_entries/3) ? $max_entries/3 : $overflowChunk;
    }
   
$this->underflow=$underflow;
}
/**
 * report on usage
 */
public function stats()
{
    return array(
'hits'=>$this->hits, 'misses'=>$this->misses
       
, 'updates'=>$this->serial, 'evicts'=>$this->evicts);
}
/**
 * Add an item to the cache, if cache full, oldest $overflowChunk items will be evicted
 *
 * @param mixed $key
 * @param mixed $val
 * @param bool $nowriteback - don't pass this entry to the overflow handler
 * @return int
 *
 * returned integer will be ICACHE_NO_CHANGE if the key is already in the cache with the same value
 * ICACHE_UPDATED if the key is present but held a different value
 * ICACHE_INSERTED if the key was added
 */
public function add($key, $val, $writeback=true)
{
   
$already=array_key_exists($key, $this->data);
    if (
$already) { $this->hits++; } else { $this->misses++; }
    if (
count($this->data)>$this->max_count && !$already) {
       
$this->removeOldest();
    }
    if (
$already) {
        if (
serialize($val)===serialize($this->data[$key]['v'])) {
           
$this->data[$key]['s']=$this->serial++;
           
$this->data[$key]['o']=$writeback;
            return
ICACHE_NO_CHANGE;
        }
       
$this->data[$key]=array(
           
's'=>$this->serial++,
           
'v'=>$val,
           
'o'=>$writeback);
        return
ICACHE_UPDATED;
    }
   
$this->data[$key]=array(
       
's'=>$this->serial++,
       
'v'=>$val,
       
'o'=>$writeback);
    return
ICACHE_INSERTED;
}
/**
 * attempt to retrieve the item from the cache
 *
 * @param mixed $key
 * @param bool $refresh_cache - if set to false the item will not be marked as freshly accessed
 * @return mixed - the value set for the key
 */
public function get($key, $refresh_cache=true)
{
   
$in_array=array_key_exists($key, $this->data);
    if (
$in_array) { $this->hits++; } else { $this->misses++; };
    if (
$refresh_cache && $in_array) {
       
$this->data[$key]['s']++;
        return
$this->data[$key]['v'];
    }
    if (!
$in_array && $this->underflow) {
       
call_user_func($this->underflow, $key, $this);
    }
    return
$this->data[$key][$v];
}
public function
expire($key)
{
    unset(
$this_data[$key]);
}
/**
 * removes the oldest N items from the cache
 * where N is the overflow chunk size
 *
 * if overflow callback is defined, this will be invoked with
 * callback(array( $key[1]=>$value[1],....));
 */
protected function removeOldest()
{
   
// move oldest to start
   
uasort($this->data, array($this, 'sortAge'));
   
// get the value (array_unshift will change numeric keys!)
   
$chunk=array();
   
$count=0;
    foreach (
$this->data as $key=>$entry) {
        if (
$entry['o']) {
           
$chunk[$key]=$entry['v'];
        }
        unset(
$this->data[$key]);
        if (
$count++>=$this->overflowChunk) break;
    }
   
$this->evicts+=$count;
    if (
$this->overflow && count($chunk)) {
        return
call_user_func($this->overflow, $chunk);
    } else {
        return
true;
    }
}
/**
 * removes all items from the cache
 *
 * @param bool $sorted - if true then the data is sorted by age (oldest first) before being presented to the overflow callback
 *
 * If overflow callback is defined this will be called with chunks of data
 * callback(array( $key[1]=>$value[1],....));
 *
 * sorting will impact performance
 */
public function flush($sorted=false)
{
   
$this->evicts+=count($this->data);
    if (
$this->overflow===false) {
       
$this->data=array();
        return
true;
    }
    if (
$sorted) {
       
uasort($this->data, array($this, 'sortAge'));
    }
    while (
count($this->data)) {
       
$chunk=array();
       
$count=0;
        foreach (
$this->data as $key=>$entry) {
            if (
$entry['o']) {
               
$chunk[$key]=$entry['v'];
            }
            unset(
$this->data[$key]);
            if (
$count++>=$this->overflowChunk) break;
        }
        if (
count($chunk)) {
           
call_user_func($this->overflow, $chunk);
        }
    }
    return
true;
}

/**
 * internal callback for sorting by age
 */
protected function sortAge($a, $b)
{
    if (
$a['s']==$b['s']) return 0;
    return (
$a['s'] < $b['s']) ? -1 : 1;
}

}