<?php /** * Elgg cache * Cache file interface for caching data. * * @package Elgg * @subpackage API * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html GNU Public License version 2 * @author Curverider Ltd <info@elgg.com> * @copyright Curverider Ltd 2008-2009 * @link http://elgg.org/ */ /** * ElggCache The elgg cache superclass. * This defines the interface for a cache (wherever that cache is stored). * * @author Curverider Ltd <info@elgg.com> * @package Elgg * @subpackage API */ abstract class ElggCache implements ArrayAccess // Override for array access { /** * Variables for the cache object. * * @var array */ private $variables; /** * Set the constructor. */ function __construct() { $this->variables = array(); } /** * Set a cache variable. * * @param string $variable * @param string $value */ public function set_variable($variable, $value) { if (!is_array($this->variables)) $this->variables = array(); $this->variables[$variable] = $value; } /** * Get variables for this cache. * * @param string $variable * @return mixed The variable or null; */ public function get_variable($variable) { if (isset($this->variables[$variable])) return $this->variables[$variable]; return null; } /** * Class member get overloading, returning key using $this->load defaults. * * @param string $key * @return mixed */ function __get($key) { return $this->load($key); } /** * Class member set overloading, setting a key using $this->save defaults. * * @param string $key * @param mixed $value * @return mixed */ function __set($key, $value) { return $this->save($key, $value); } /** * Supporting isset, using $this->load() with default values. * * @param string $key The name of the attribute or metadata. * @return bool */ function __isset($key) { return (bool)$this->load($key); } /** * Supporting unsetting of magic attributes. * * @param string $key The name of the attribute or metadata. */ function __unset($key) { return $this->delete($key); } /** * Save data in a cache. * * @param string $key * @param string $data * @return bool */ abstract public function save($key, $data); /** * Load data from the cache using a given key. * * @param string $key * @param int $offset * @param int $limit * @return mixed The stored data or false. */ abstract public function load($key, $offset = 0, $limit = null); /** * Invalidate a key * * @param string $key * @return bool */ abstract public function delete($key); /** * Clear out all the contents of the cache. * */ abstract public function clear(); /** * Add a key only if it doesn't already exist. * Implemented simply here, if you extend this class and your caching engine provides a better way then * override this accordingly. * * @param string $key * @param string $data * @return bool */ public function add($key, $data) { if (!isset($this[$key])) return $this->save($key, $data); return false; } // ARRAY ACCESS INTERFACE ////////////////////////////////////////////////////////// function offsetSet($key, $value) { $this->save($key, $value); } function offsetGet($key) { return $this->load($key); } function offsetUnset($key) { if ( isset($this->key) ) { unset($this->key); } } function offsetExists($offset) { return isset($this->$offset); } } /** * Shared memory cache description. * Extends ElggCache with functions useful to shared memory style caches (static variables, memcache etc) */ abstract class ElggSharedMemoryCache extends ElggCache { /** * Namespace variable used to keep various bits of the cache * separate. * * @var string */ private $namespace; /** * Set the namespace of this cache. * This is useful for cache types (like memcache or static variables) where there is one large * flat area of memory shared across all instances of the cache. * * @param string $namespace */ public function setNamespace($namespace = "default") { $this->namespace = $namespace; } /** * Get the namespace currently defined. * * @return string */ public function getNamespace() { return $this->namespace; } } /** * ElggStaticVariableCache * Dummy cache which stores values in a static array. Using this makes future replacements to other caching back * ends (eg memcache) much easier. * * @author Curverider Ltd <info@elgg.com> * @package Elgg * @subpackage API */ class ElggStaticVariableCache extends ElggSharedMemoryCache { /** * The cache. * * @var unknown_type */ private static $__cache; /** * Create the variable cache. * * This function creates a variable cache in a static variable in memory, optionally with a given namespace (to avoid overlap). * * @param string $namespace The namespace for this cache to write to - note, namespaces of the same name are shared! */ function __construct($namespace = 'default') { $this->setNamespace($namespace); $this->clear(); } public function save($key, $data) { $namespace = $this->getNamespace(); ElggStaticVariableCache::$__cache[$namespace][$key] = $data; return true; } public function load($key, $offset = 0, $limit = null) { $namespace = $this->getNamespace(); if (isset(ElggStaticVariableCache::$__cache[$namespace][$key])) return ElggStaticVariableCache::$__cache[$namespace][$key]; return false; } public function delete($key) { $namespace = $this->getNamespace(); unset(ElggStaticVariableCache::$__cache[$namespace][$key]); return true; } public function clear() { $namespace = $this->getNamespace(); if (!isset(ElggStaticVariableCache::$__cache)) ElggStaticVariableCache::$__cache = array(); //if (!isset(ElggStaticVariableCache::$__cache[$namespace])) ElggStaticVariableCache::$__cache[$namespace] = array(); } } /** * ElggFileCache * Store cached data in a file store. * * @author Curverider Ltd <info@elgg.com> * @package Elgg * @subpackage API */ class ElggFileCache extends ElggCache { /** * Set the Elgg cache. * * @param string $cache_path The cache path. * @param int $max_age Maximum age in seconds, 0 if no limit. * @param int $max_size Maximum size of cache in seconds, 0 if no limit. */ function __construct($cache_path, $max_age = 0, $max_size = 0) { $this->set_variable("cache_path", $cache_path); $this->set_variable("max_age", $max_age); $this->set_variable("max_size", $max_size); if ($cache_path=="") throw new ConfigurationException(elgg_echo('ConfigurationException:NoCachePath')); } /** * Create and return a handle to a file. * * @param string $filename * @param string $rw */ protected function create_file($filename, $rw = "rb") { // Create a filename matrix $matrix = ""; $depth = strlen($filename); if ($depth > 5) $depth = 5; // for ($n = 0; $n < $depth; $n++) // $matrix .= $filename[$n] . "/"; // Create full path $path = $this->get_variable("cache_path") . $matrix; // if (!mkdir($path, 0700, true)) throw new IOException("Could not make $path"); // Open the file if ((!file_exists($path . $filename)) && ($rw=="rb")) return false; return fopen($path . $filename, $rw); } /** * Create a sanitised filename for the file. * * @param string $filename */ protected function sanitise_filename($filename) { // TODO : Writeme return $filename; } /** * Save a key * * @param string $key * @param string $data * @return boolean */ public function save($key, $data) { $f = $this->create_file($this->sanitise_filename($key), "wb"); if ($f) { $result = fwrite($f, $data); fclose($f); return $result; } return false; } /** * Load a key * * @param string $key * @param int $offset * @param int $limit * @return string */ public function load($key, $offset = 0, $limit = null) { $f = $this->create_file($this->sanitise_filename($key)); if ($f) { //fseek($f, $offset); if (!$limit) $limit = -1; $data = stream_get_contents($f, $limit, $offset); fclose($f); return $data; } return false; } /** * Invalidate a given key. * * @param string $key * @return bool */ public function delete($key) { $dir = $this->get_variable("cache_path"); return unlink($dir.$key); } public function clear() { // TODO : writeme } public function __destruct() { // TODO: Check size and age, clean up accordingly $size = 0; $dir = $this->get_variable("cache_path"); // Short circuit if both size and age are unlimited if (($this->get_variable("max_age")==0) && ($this->get_variable("max_size")==0)) return; $exclude = array(".",".."); $files = scandir($dir); if (!$files) throw new IOException(sprintf(elgg_echo('IOException:NotDirectory'), $dir)); // Perform cleanup foreach ($files as $f) { if (!in_array($f, $exclude)) { $stat = stat($dir.$f); // Add size $size .= $stat['size']; // Is this older than my maximum date? if (($this->get_variable("max_age")>0) && (time() - $stat['mtime'] > $this->get_variable("max_age"))) unlink($dir.$f); // TODO: Size } } } } ?>