<?php
/*
** The settings in Settings.php should look like this:
// Select a system. Probably want to leave this up to the installer or admin panel.
$cache = array(
// memcache, eaccelerator, mmcache, output_cache, xcache, apc, file, or false.
'system' => '',
// The level of caching. The higher the number, the more gets cached. An integer between 0 and 3 (0 turns caching off).
'level' => 0,
// This is generally only used for memcache. Should be a string of 'server:port, server:port'.
'servers' => '',
// Persistent connection (only of use to memcache).
'persist' => false,
// The directory where you want to store your cache files (if you are using the 'file' system).
'dir' => '',
// Boolean value. If true, it logs how many hits/misses and how long they took.
'debug' => false,
);
*/
$cache = new cache($cache);
class cache
{
public $hits = 0;
public $count = 0;
protected $settings = array();
// Check all of the variables and setup what functions to use.
function __construct ($settings = false)
{
$this->settings = $settings;
if ($this->cache == false)
return;
$this->settings['level'] = !empty($this->settings['level']) ? (int) $this->settings['level'] : false;
$this->settings['system'] = !empty($this->settings['system']) ? $this->settings['system'] : false;
$this->settings['dir'] = !empty($this->settings['dir']) && file_exists($this->settings['dir']) ? $this->settings['dir'] : false;
if (!$this->settings['system'] || !$this->settings['level'])
$this->settings = false;
// Define the Get, Set, and sometimes Remove functions.
// Just added: remove, increment, decrement, status.
switch ($this->settings['system'])
{
case 'apc':
$this->get = 'apc_fetch';
$this->set = 'apc_store';
$this->rm = 'apc_delete';
$this->clear = 'apc_clear_cache';
break;
case 'memcache':
$this->get = 'memcache_get';
$this->set = 'memcache_set';
$this->rm = 'memcache_delete';
$this->clear = 'memcache_flush';
break;
case 'eaccelerator':
$this->get = 'eaccelerator_get';
$this->set = 'eaccelerator_put';
$this->rm = 'eaccelerator_rm';
$this->gc = 'eaccelerator_gc';
// Might have to use eaccelerator_list_keys() and rm all of them.
//$this->clear = 'eaccelerator_gc';
break;
case 'mmcache':
$this->get = 'mmcache_get';
$this->set = 'mmcache_put';
$this->rm = 'mmcache_rm';
$this->gc = 'mmcache_gc';
break;
case 'output_cache':
$this->get = 'output_cache_get';
$this->set = 'output_cache_put';
// Always one that has to be different. We must find those that check for TTL in get.
$this->settings['req_ttl'] = true;
$this->rm = 'output_cache_remove_key';
// No idea how to implement clear() in this one.
break;
case 'xcache':
$this->get = 'xcache_get';
$this->set = 'xcache_set';
$this->rm = 'xcache_unset';
$this->clear = 'xcache_clear_cache';
break;
case: 'file':
$this->get = array($this, 'get');
$this->set = array($this, 'set');
$this->rm = array($this, 'rm');
$this->clear = array($this, 'clear');
//$this->version = array($this, 'version'),
default:
$this->settings = false;
}
// Do we need to connect to the memcached server?
if ($this->settings['system'] == 'memcache')
{
if (!empty($this->settings['servers']))
{
// Not connected yet?
if (empty($this->res))
{
$this->settings['servers'] = explode(',', $this->settings['servers']);
get_memcached_server();
}
if (!$this->res)
$this->settings = false;
}
else
$this->settings = false;
}
// Do we need to do garbage collection?
if (isset($this->gc) && mt_rand(0, 10) == 1)
call_user_func($this->gc);
// Let someone know that caching isn't working.
if ($this->settings === false)
trigger_error('Invalid cache type: ' . $this->settings['system'], E_USER_NOTICE);
if (!is_callable($this->get) || !is_callable($this->set))
trigger_error($this->get .' or ' . $this->set . ' functions not found', E_USER_NOTICE);
}
// Connect to a memcached server.
private function get_memcached_server($level = 3)
{
$server = explode(':', trim($this->settings['servers'][array_rand($this->settings['servers'])]));
// Don't try more times than we have servers!
$level = min(count($this->settings['servers']), $level);
// Don't wait too long. It might be faster to just run without caching.
if ($this->settings['persist'])
$this->res = memcache_connect($server[0], empty($server[1]) ? 11211 : $server[1]);
else
$this->res = memcache_pconnect($server[0], empty($server[1]) ? 11211 : $server[1]);
if (!$this->res && $level > 0)
get_memcached_server($level - 1);
}
// Put an item in the cache.
function put($key, $value, $ttl = 120, $level = 0)
{
if (!$this->settings)
return;
if ($this->settings['debug'])
{
$this->count++;
$this->hits[$this->count] = array('k' => $key, 'd' => 'put', 's' => $value === null ? 0 : strlen(serialize($value)));
$st = microtime();
}
$key = $this->get_key($key);
$value = $value === null ? null : serialize($value);
if ($value === null)
$this->rm($key);
if ($this->settings['system'] == 'file')
{
// Create a PHP file with the info.
if ($value !== null)
file_put_contents($this->settings['dir'] . '/data_' . $key . '.php', '<?php if (' . (time() + $ttl) . ' < time()) $expired = true; else{$expired = false; $value = \'' . addcslashes($value, '\\\'') . '\';}?>', LOCK_EX);
else
$this->rm($this->settings['dir'] . '/data_' . $key . '.php');
}
// Some caches are accessed through a resource.
elseif (isset($this->res))
$cache['put']($this->res, $key, $value, 0, $ttl);
else
$cache['put']($key, $value, $ttl);
if ($this->settings['debug'])
$this->hits[$this->count]['t'] = array_sum(explode(' ', microtime())) - array_sum(explode(' ', $st));
}
// Return a cached item.
function get($key, $ttl = 120, $level = 0)
{
if (!$this->settings)
return;
if ($this->settings['debug'])
{
$this->count++;
$this->hits[$this->count] = array('k' => $key, 'd' => 'get');
$st = microtime();
}
$key = $this->get_key($key);
if ($this->settings['system'] == 'file' && file_exists($this->settings['dir'] . '/data_' . $key . '.php') && filesize($this->settings['dir'] . '/data_' . $key . '.php') > 10)
{
require($this->settings['dir'] . '/data_' . $key . '.php');
if (!empty($expired) && isset($value))
{
$this->rm($this->settings['dir'] . '/data_' . $key . '.php');
unset($value);
}
}
elseif (isset($this->res))
$value = $this->get($this->res, $key);
elseif ($this->settings['req_ttl'])
$value = $this->get($key, $ttl);
else
$value = $this->get($key);
if ($this->settings['debug'])
{
$this->hits[$this->count]['t'] = array_sum(explode(' ', microtime())) - array_sum(explode(' ', $st));
$this->hits[$this->count]['s'] = isset($value) ? strlen($value) : 0;
}
if (!empty($value))
return unserialize($value);
}
// Increment.
function increment($key, $value = 1, $ttl = 120)
{
if (!$this->settings)
return;
if (!isset($this->inc))
{
$cached_value = call_user_func(array($this, 'get'), $key);
if (is_int($cached_value))
{
call_user_func(array($this, 'put'), $cached_value + $value);
return $cached_value + $value;
}
}
elseif ($this->settings['req_ttl'])
return call_user_func($this->inc, $value, $ttl);
else
return call_user_func($this->inc, $value);
}
// Decrement.
function decrement($key, $value = 1, $ttl = 120)
{
if (!$this->settings)
return;
if (!isset($this->dec))
{
$cached_value = call_user_func(array($this, 'get'), $key);
if (is_int($cached_value))
{
call_user_func(array($this, 'put'), $cached_value - $value);
return $cached_value + $value;
}
}
elseif ($this->settings['req_ttl'])
return call_user_func($this->dec, $value, $ttl);
else
return call_user_func($this->dec, $value);
}
// Remove a cached item.
function rm($key)
{
if (!$this->settings)
return;
if ($this->settings['system'] == 'file' && file_exists($this->settings['dir'] . '/data_' . $key . '.php'))
unlink($this->settings['dir'] . '/data_' . $key . '.php');
else
call_user_func($this->rm);
}
// Clears the entire cache. Nothing will be left!
function clear()
{
if (!$this->settings)
return;
if ($this->settings['system'] == 'file')
{
$files = scandir($this->settings['dir']);
foreach ($files as $file)
unlink($this->settings . '/' . $file);
}
// If there is no clear function defined, try to find all of the keys and delete those?
// If we can't delete all of them, try dropping everything and then restarting?
}
// Return the version of the current cache.
function version()
{
if (!$this->settings)
return;
switch($this->settings['system'])
{
case 'apc':
$version = array('title' => 'Alternative PHP Cache', 'version' => phpversion('apc'));
break;
case 'memcache':
$version = array('title' => 'Memcached', 'version' => memcache_get_version($this->resource));
break;
case 'eaccelerator':
$version = array('title' => 'eAccelerator', 'version' => EACCELERATOR_VERSION);
break;
case 'mmcache':
$version = array('title' => 'Turck MMCache', 'version' => MMCACHE_VERSION);
break;
case 'output_cache':
global $_PHPA;
$version = array('title' => 'Zend', 'version' => $_PHPA['VERSION']);
break;
case 'xcache':
$version = array('title' => 'XCache', 'version' => XCACHE_VERSION);
break;
default:
$version = array('title' => 'file', 'version' => 1);
}
return $version;
}
// One function to get the key so we can change how the keys are formatted easily.
private function get_key($key)
{
//return md5(__DIR__ . filemtime(__FILE__) . __FILE__ . strtr($key, ':', '-'));
return strtr($key, ':', '-');
}
}
/* Development Notes:
* I removed the @ operator from unlink because if we can't remove a file, we don't want to fill up our filesystem with cached files.
I'd rather have it fill up an error log and let someone know about it.
* Webpages for support:
MMCache: http://turck-mmcache.sourceforge.net/index_old.html#api
Xcache: http://xcache.lighttpd.net/wiki/XcacheApi
memcache: http://www.php.net/memcache
APC: http://www.php.net/apc
eAccelerator: http://bart.eaccelerator.net/doc/phpdoc/
Zend: http://files.zend.com/help/Zend-Platform/partial_and_preemptive_page_caching.htm
*/
?>