Cache and its child classes
/**
* Faking namespace in PHP by implementing utility functions as
* static methods of a Utilities class. This purposefully
* requires no other custom code, so that the application can
* load it before anything else.
*/
class Utilities {
/**
* Returns a Cache object depending on the type requested,
* defaulting to memcache, APC, shmop, and temporary files,
* depending on what the system offers.
*/
public static function getCache($type = null) {
global $cache_engines;
foreach ($cache_engine as $engine => $info) {
if ((!isset($type) || $type == $engine) && extension_loaded($info['extension'])) {
return new $info['class']();
}
}
// No matching cache object found
return false;
}
}
global $cache_engines;
$cache_engines = array(
'memcache' => array(
'extension' => 'memcache',
'class' => 'memcacheCache'
),
'apc' => array(
'extension' => 'apc',
'class' => 'APCCache'
),
'shmop' => array(
'extension' => 'shmop',
'class' => 'shmopCache'
),
'filesystem' => array(
'extension' => 'standard',
'class' => 'filesystemCache'
),
);
/**
* The simple, abstract class lays out the requirements for objects used
* by this application to access caching functionality. Each of the methods
* descibed in this section include a class extending Cache that the
* application can then use transparently.
*/
abstract class Cache {
/**
* Store the given value in cache, identified by the key and optionally
* expiring at a certain time.
* @param string $key The identifier for the cached variable
* @param mixed $value Any non-resource data to store in cache
* @param int $expires An optional timestamp specifying the time at
* which the cached value expires. When not given, the value will never
* expire.
* @return boolean Success
*/
abstract public function setCache($key, $value = null, $expires = null);
/**
* Retrieves from cache a previously cached value, transparently taking
* the expiration into account as necessary.
* @param string $key The identifier for the cached variable
* @return mixed|false Previously cached data, or false if the cache
* either does not exist or has expired.
*/
abstract public function getCache($key);
/**
* Deletes from cache a previously cached value
* @param string $key The identifier for the cached variable
* @return boolean Returns a boolean as to the success of the deletion.
*/
abstract public function deleteCache($key);
}
/**
* An abstraction class for the memcache extension, which
* offers much more functionality than exposed here,
* but this example keeps the object interface generic.
*/
class memcacheCache extends Cache {
/**
* The abstracted memcache instance
* @var Memcache $memcache
*/
protected $memcache;
public function setCache($key, $value = null, $expires = null) {
return $this->memcache->set($key, $value, null, $expires);
}
public function getCache($key) {
return $this->memcache->get($key);
}
public function deleteCache($key) {
return $this->memcache->delete($key);
}
/**
* This simple implementation defaults to one server: localhost.
* It could very easily pull in configuration information for
* any number of memcache servers.
*/
public function __construct() {
$this->memcache = new Memcache();
$this->memcache->connect('127.0.0.1', 11211);
}
}
/**
* An abstraction class for the APC extension
*/
class APCCache extends Cache {
public function setCache($key, $value = null, $expires = null) {
// APC takes a time to live flag rather than a timestamp for expiration
if (isset($expires)) {
$expires = $expires - time();
}
return apc_store($key, $value, $expires);
}
public function getCache($key) {
return apc_fetch($key);
}
public function deleteCache($key) {
return apc_delete($key);
}
}
/**
* An abstraction class for the shmop extension, which
* implements no serialization or expiration of data,
* so this class handles it instead.
*/
class shmopCache extends Cache {
public function setCache($key, $value = null, $expires = null) {
// If the block already exists, remove it
$this->deleteCache($key);
// Create the new block
$shmop_key = $this->shmopKey($key);
// Create the serialized value - spikes memory usage for large values
$shmop_value = serialize($value);
// Value size + 10 for expiration
$shmop_size = strlen($shmop_value) + 10;
// Attempt to open the shmop block, read-only
if ($block = shmop_open($shmop_key, 'n', 0600, $shmop_size)) {
$expires = str_pad((int)$expires, 10, '0', STR_PAD_LEFT);
$written = shmop_write($block, $expires, 0)
&& shmop_write($block, $shmop_value, 10);
shmop_close($block);
return $written;
}
return false;
}
public function getCache($key) {
$shmop_key = $this->shmopKey($key);
// Attempt to open the shmop block, read-only
if ($block = shmop_open($shmop_key, 'a')) {
// This object stored the expiration time stamp here
$expires = (int)shmop_read($block, 0, 10);
$cache = false;
// If the expiration time exceeds the current time,
// return the cache.
if (!$expires || $expires > time()) {
$realsize = shmop_size($block) - 10;
$cache = unserialize(shmop_read($block, 10, $realsize));
} else {
// Close and delete the expired cache
chmop_delete($block);
}
shmop_close($block);
return $cache;
}
return false;
}
public function deleteCache($key) {
$shmop_key = $this->shmopKey($key);
// Attempt to open the shmop block, read-write
if ($block = shmop_open($shmop_key, 'w')) {
$deleted = shmop_delete($block);
shmop_close($block);
return $deleted;
} else {
// Already gone
return true;
}
}
/**
* Keep the key generation all in one place
*/
protected function shmopKey($key) {
return crc32($key);
}
}
/**
* An abstraction class for using temporary files for caching
*/
class filesystemCache extends Cache {
public function setCache($key, $value = null, $expires = null) {
$filepath = $this->filesystemKey($key);
// Write the expiration with an exclusive lock to overwrite it
$expires = str_pad((int)$expires, 10, '0', STR_PAD_LEFT);
$success = (bool)file_put_contents($filepath, $expires, FILE_EX);
if ($success) {
// Append the serialized value to the file
return (bool)file_put_contents($filepath, serialize($value), FILE_EX | FILE_APPEND);
}
return false;
}
public function getCache($key) {
$filepath = $this->filesystemKey($key);
// Attempt to open the file, read-only
if (file_exists($filepath) && $file = fopen($filepath, 'r')) {
// This object stored the expiration time stamp here
$expires = (int)fread($file, 10);
// If the expiration time exceeds the current time,
// return the cache.
if (!$expires || $expires > time()) {
$realsize = filesize($block) - 10;
$cache = '';
// Need to read in a loop, since fread returns after 8192 bytes
while ($chunk = fread($file, $realsize)) {
$cache .= $chunk;
}
fclose($block);
return unserialize($cache);
} else {
// Close and delete the expired cache
fclose($block);
$this->deleteCache($key);
}
}
return false;
}
public function deleteCache($key) {
$filepath = $this->filesystemKey($key);
if (file_exists($filepath)) {
return unlink($filepath);
}
return true;
}
/**
* Keep the key generation all in one place
*/
protected function filesystemKey($key) {
return $this->tempdir . md5($key);
}
public function __construct() {
// Could override this to set another directory
$this->tempdir = sys_get_temp_dir() . md5(__FILE__);
if (!is_dir($this->tempdir)) {
mkdir($this->tempdir);
}
$this->tempdir .= DIRECTORY_SEPARATOR;
}
}