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; } }