From: Michael M Slusarz Date: Thu, 5 Mar 2009 03:53:00 +0000 (-0700) Subject: Import Horde_Cache from CVS HEAD. X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=9a6fbeb32341f797d31660867c69c97e56cc3f8e;p=horde.git Import Horde_Cache from CVS HEAD. --- diff --git a/framework/Cache/data/cache.sql b/framework/Cache/data/cache.sql new file mode 100644 index 000000000..fcc8ec1cd --- /dev/null +++ b/framework/Cache/data/cache.sql @@ -0,0 +1,9 @@ +CREATE TABLE horde_cache ( + cache_id VARCHAR(32) NOT NULL, + cache_timestamp BIGINT NOT NULL, + cache_data LONGBLOB, +-- Or, on some DBMS systems: +-- cache_data IMAGE, + + PRIMARY KEY (cache_id) +); diff --git a/framework/Cache/lib/Horde/Cache.php b/framework/Cache/lib/Horde/Cache.php new file mode 100644 index 000000000..4d9b3c35a --- /dev/null +++ b/framework/Cache/lib/Horde/Cache.php @@ -0,0 +1,93 @@ + + * @author Chuck Hagenbuch + * @package Horde_Cache + */ +class Horde_Cache +{ + /** + * Singleton instances. + * + * @var array + */ + static protected $_instances = array(); + + /** + * Attempts to return a concrete Horde_Cache instance based on $driver. + * + * @param mixed $driver The type of concrete Horde_Cache subclass to + * return. If $driver is an array, then we will look + * in $driver[0]/lib/Cache/ for the subclass + * implementation named $driver[1].php. + * @param array $params A hash containing any additional configuration + * or connection parameters a subclass might need. + * + * @return Horde_Cache The newly created concrete Horde_Cache instance. + * @throws Horde_Exception + */ + static public function factory($driver, $params = array()) + { + if (is_array($driver)) { + list($app, $driv_name) = $driver; + $driver = basename($driv_name); + } else { + $driver = basename($driver); + } + + if (empty($driver) || $driver == 'none') { + return new Horde_Cache_Null($params); + } + + $class = (empty($app) ? 'Horde' : $app) . '_Cache_' . ucfirst($driver); + + if (class_exists($class)) { + return new $class($params); + } + + throw new Horde_Exception('Class definition of ' . $class . ' not found.'); + } + + /** + * Attempts to return a reference to a concrete Horde_Cache instance + * based on $driver. It will only create a new instance if no + * Horde_Cache instance with the same parameters currently exists. + * + * This should be used if multiple cache backends (and, thus, + * multiple Horde_Cache instances) are required. + * + * This method must be invoked as: + * $var = &Horde_Cache::singleton() + * + * @param mixed $driver The type of concrete Horde_Cache subclass to + * return. If $driver is an array, then we will look + * in $driver[0]/lib/Cache/ for the subclass + * implementation named $driver[1].php. + * @param array $params A hash containing any additional configuration or + * connection parameters a subclass might need. + * + * @return Horde_Cache The concrete Horde_Cache reference. + * @throws Horde_Exception + */ + static public function singleton($driver, $params = array()) + { + ksort($params); + $signature = hash('md5', serialize(array($driver, $params))); + + if (!isset(self::$_instances[$signature])) { + self::$_instances[$signature] = Horde_Cache::factory($driver, $params); + } + + return self::$_instances[$signature]; + } + +} diff --git a/framework/Cache/lib/Horde/Cache/Apc.php b/framework/Cache/lib/Horde/Cache/Apc.php new file mode 100644 index 000000000..c73a2d261 --- /dev/null +++ b/framework/Cache/lib/Horde/Cache/Apc.php @@ -0,0 +1,98 @@ + + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @author Duck + * @package Horde_Cache + */ +class Horde_Cache_Apc extends Horde_Cache_Base +{ + /** + * Attempts to retrieve a piece of cached data and return it to + * the caller. + * + * @param string $key Cache key to fetch. + * @param integer $lifetime Lifetime of the key in seconds. + * + * @return mixed Cached data, or false if none was found. + */ + public function get($key, $lifetime = 1) + { + $this->_setExpire($key, $lifetime); + return apc_fetch($key); + } + + /** + * Attempts to store an object to the cache. + * + * @param string $key Cache key (identifier). + * @param mixed $data Data to store in the cache. + * @param integer $lifetime Data lifetime. + * + * @return boolean True on success, false on failure. + */ + public function set($key, $data, $lifetime = null) + { + $lifetime = $this->_getLifetime($lifetime); + apc_store($key . '_expire', time(), $lifetime); + return apc_store($key, $data, $lifetime); + } + + /** + * Checks if a given key exists in the cache, valid for the given + * lifetime. + * + * @param string $key Cache key to check. + * @param integer $lifetime Lifetime of the key in seconds. + * + * @return boolean Existence. + */ + public function exists($key, $lifetime = 1) + { + $this->_setExpire($key, $lifetime); + return (apc_fetch($key) === false) ? false : true; + } + + /** + * Expire any existing data for the given key. + * + * @param string $key Cache key to expire. + * + * @return boolean Success or failure. + */ + public function expire($key) + { + apc_delete($key . '_expire'); + return apc_delete($key); + } + + /** + * Set expire time on each call since APC sets it on cache creation. + * + * @param string $key Cache key to expire. + * @param integer $lifetime Lifetime of the data in seconds. + */ + protected function _setExpire($key, $lifetime) + { + if ($lifetime == 0) { + // Don't expire. + return; + } + + $expire = apc_fetch($key . '_expire'); + + // Set prune period. + if ($expire + $lifetime < time()) { + // Expired + apc_delete($key); + apc_delete($key . '_expire'); + } + } + +} diff --git a/framework/Cache/lib/Horde/Cache/Base.php b/framework/Cache/lib/Horde/Cache/Base.php new file mode 100644 index 000000000..b0dc48dc1 --- /dev/null +++ b/framework/Cache/lib/Horde/Cache/Base.php @@ -0,0 +1,117 @@ + + * @author Chuck Hagenbuch + * @author Michael Slusarz + * @package Horde_Cache + */ +abstract class Horde_Cache_Base +{ + /** + * Cache parameters. + * + * @var array + */ + protected $_params = array(); + + /** + * Construct a new Horde_Cache object. + * + * @param array $params Parameter array. + */ + public function __construct($params = array()) + { + if (!isset($params['lifetime'])) { + $params['lifetime'] = isset($GLOBALS['conf']['cache']['default_lifetime']) + ? $GLOBALS['conf']['cache']['default_lifetime'] + : 86400; + } + + $this->_params = $params; + } + + /** + * Attempts to retrieve a cached object and return it to the + * caller. + * + * @param string $key Object ID to query. + * @param integer $lifetime Lifetime of the object in seconds. + * + * @return mixed Cached data, or false if none was found. + */ + abstract public function get($key, $lifetime = 1); + + /** + * Attempts to store an object in the cache. + * + * @param string $key Object ID used as the caching key. + * @param mixed $data Data to store in the cache. + * @param integer $lifetime Object lifetime - i.e. the time before the + * data becomes available for garbage + * collection. If null use the default Horde GC + * time. If 0 will not be GC'd. + * + * @return boolean True on success, false on failure. + */ + abstract public function set($key, $data, $lifetime = null); + + /** + * Checks if a given key exists in the cache, valid for the given + * lifetime. + * + * @param string $key Cache key to check. + * @param integer $lifetime Lifetime of the key in seconds. + * + * @return boolean Existence. + */ + abstract public function exists($key, $lifetime = 1); + + /** + * Expire any existing data for the given key. + * + * @param string $key Cache key to expire. + * + * @return boolean Success or failure. + */ + abstract public function expire($key); + + /** + * Attempts to directly output a cached object. + * + * @param string $key Object ID to query. + * @param integer $lifetime Lifetime of the object in seconds. + * + * @return boolean True if output or false if no object was found. + */ + public function output($key, $lifetime = 1) + { + $data = $this->get($key, $lifetime); + if ($data === false) { + return false; + } + + echo $data; + return true; + } + + /** + * Determine the default lifetime for data. + * + * @param mixed $lifetime The lifetime to use or null for default. + * + * @return integer The lifetime, in seconds. + */ + protected function _getLifetime($lifetime) + { + return is_null($lifetime) ? $this->_params['lifetime'] : $lifetime; + } + +} diff --git a/framework/Cache/lib/Horde/Cache/Eaccelerator.php b/framework/Cache/lib/Horde/Cache/Eaccelerator.php new file mode 100644 index 000000000..83e25b855 --- /dev/null +++ b/framework/Cache/lib/Horde/Cache/Eaccelerator.php @@ -0,0 +1,98 @@ + + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @author Duck + * @package Horde_Cache + */ +class Horde_Cache_Eaccelerator extends Horde_Cache_Base +{ + /** + * Attempts to retrieve a piece of cached data and return it to the caller. + * + * @param string $key Cache key to fetch. + * @param integer $lifetime Lifetime of the key in seconds. + * + * @return mixed Cached data, or false if none was found. + */ + public function get($key, $lifetime = 1) + { + $this->_setExpire($key, $lifetime); + return eaccelerator_get($key); + } + + /** + * Attempts to store an object to the cache. + * + * @param string $key Cache key (identifier). + * @param mixed $data Data to store in the cache. + * @param integer $lifetime Data lifetime. + * + * @return boolean True on success, false on failure. + */ + public function set($key, $data, $lifetime = null) + { + $lifetime = $this->_getLifetime($lifetime); + eaccelerator_put($key . '_expire', time(), $lifetime); + return eaccelerator_put($key, $data, $lifetime); + } + + /** + * Checks if a given key exists in the cache, valid for the given + * lifetime. + * + * @param string $key Cache key to check. + * @param integer $lifetime Lifetime of the key in seconds. + * + * @return boolean Existence. + */ + public function exists($key, $lifetime = 1) + { + $this->_setExpire($key, $lifetime); + return (eaccelerator_get($key) === false) ? false : true; + } + + /** + * Expire any existing data for the given key. + * + * @param string $key Cache key to expire. + * + * @return boolean Success or failure. + */ + public function expire($key) + { + eaccelerator_rm($key . '_expire'); + return eaccelerator_rm($key); + } + + /** + * Set expire time on each call since eAccelerator sets it on + * cache creation. + * + * @param string $key Cache key to expire. + * @param integer $lifetime Lifetime of the data in seconds. + */ + protected function _setExpire($key, $lifetime) + { + if ($lifetime == 0) { + // Don't expire. + return; + } + + $expire = eaccelerator_get($key . '_expire'); + + // Set prune period. + if ($expire + $lifetime < time()) { + // Expired + eaccelerator_rm($key); + eaccelerator_rm($key . '_expire'); + } + } + +} diff --git a/framework/Cache/lib/Horde/Cache/File.php b/framework/Cache/lib/Horde/Cache/File.php new file mode 100644 index 000000000..5968a22f6 --- /dev/null +++ b/framework/Cache/lib/Horde/Cache/File.php @@ -0,0 +1,313 @@ + + * 'dir' The base directory to store the cache files in. + * 'prefix' The filename prefix to use for the cache files. + * 'sub' An integer. If non-zero, the number of subdirectories to + * create to store the file (i.e. PHP's session.save_path). + * + * Copyright 1999-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @author Anil Madhavapeddy + * @author Chuck Hagenbuch + * @package Horde_Cache + */ +class Horde_Cache_File extends Horde_Cache_Base +{ + /** + * The location of the temp directory. + * + * @var string + */ + protected $_dir; + + /** + * The filename prefix for cache files. + * + * @var string + */ + protected $_prefix = 'cache_'; + + /** + * The subdirectory level the cache files should live at. + * + * @var integer + */ + protected $_sub = 0; + + /** + * List of key to filename mappings. + * + * @var array + */ + protected $_file = array(); + + /** + * Construct a new Horde_Cache_File object. + * + * @param array $params Parameter array. + */ + public function __construct($params = array()) + { + if (!empty($params['dir']) && @is_dir($params['dir'])) { + $this->_dir = $params['dir']; + } else { + require_once 'Horde/Util.php'; + $this->_dir = Util::getTempDir(); + } + + foreach (array('prefix', 'sub') as $val) { + if (isset($params[$val])) { + $name = '_' . $val; + $this->$name = $params[$val]; + } + } + + parent::__construct($params); + } + + /** + * Destructor. + */ + public function __destruct() + { + /* Only do garbage collection 0.1% of the time we create an object. */ + if (rand(0, 999) != 0) { + return; + } + + $filename = $this->_dir . '/horde_cache_gc'; + $excepts = array(); + + if (file_exists($filename)) { + $flags = defined('FILE_IGNORE_NEW_LINES') + ? FILE_IGNORE_NEW_LINES + : 0; + + $gc_file = file($filename, $flags); + array_pop($gc_file); + reset($gc_file); + while (list(,$data) = each($gc_file)) { + if (!$flags) { + $data = rtrim($data); + } + $parts = explode("\t", $data, 2); + $excepts[$parts[0]] = $parts[1]; + } + } + + try { + $this->_gcDir($this->_dir, $excepts); + } catch (Horde_Exception $e) {} + + $out = ''; + foreach ($excepts as $key => $val) { + $out .= $key . "\t" . $val . "\n"; + } + + file_put_contents($filename, $out); + } + + /** + * Attempts to retrieve cached data from the filesystem and return it to + * the caller. + * + * @param string $key Cache key to fetch. + * @param integer $lifetime Lifetime of the data in seconds. + * + * @return mixed Cached data, or false if none was found. + */ + public function get($key, $lifetime = 1) + { + if (!$this->exists($key, $lifetime)) { + /* Nothing cached, return failure. */ + return false; + } + + $filename = $this->_keyToFile($key); + $size = filesize($filename); + if (!$size) { + return ''; + } + $old_error = error_reporting(0); + $data = file_get_contents($filename); + error_reporting($old_error); + + return $data; + } + + /** + * Attempts to store data to the filesystem. + * + * @param string $key Cache key. + * @param mixed $data Data to store in the cache. (MUST BE A STRING) + * @param integer $lifetime Data lifetime. + * + * @return boolean True on success, false on failure. + */ + public function set($key, $data, $lifetime = null) + { + require_once 'Horde/Util.php'; + $filename = $this->_keyToFile($key, true); + $tmp_file = Util::getTempFile('HordeCache', true, $this->_dir); + if (isset($this->_params['umask'])) { + chmod($tmp_file, 0666 & ~$this->_params['umask']); + } + + if (file_put_contents($tmp_file, $data) === false) { + return false; + } + + @rename($tmp_file, $filename); + + $lifetime = $this->_getLifetime($lifetime); + if ($lifetime != $this->_params['lifetime']) { + // This may result in duplicate entries in horde_cache_gc, but we + // will take care of these whenever we do GC and this is quicker + // than having to check every time we access the file. + $fp = fopen($this->_dir . '/horde_cache_gc', 'a'); + fwrite($fp, $filename . "\t" . (empty($lifetime) ? 0 : time() + $lifetime) . "\n"); + fclose($fp); + } + + return true; + } + + /** + * Checks if a given key exists in the cache, valid for the given + * lifetime. If it exists but is expired, delete the file. + * + * @param string $key Cache key to check. + * @param integer $lifetime Lifetime of the key in seconds. + * + * @return boolean Existence. + */ + public function exists($key, $lifetime = 1) + { + $filename = $this->_keyToFile($key); + + /* Key exists in the cache */ + if (file_exists($filename)) { + /* 0 means no expire. */ + if ($lifetime == 0) { + return true; + } + + /* If the file was been created after the supplied value, + * the data is valid (fresh). */ + if (time() - $lifetime <= filemtime($filename)) { + return true; + } else { + @unlink($filename); + } + } + + return false; + } + + /** + * Expire any existing data for the given key. + * + * @param string $key Cache key to expire. + * + * @return boolean Success or failure. + */ + public function expire($key) + { + $filename = $this->_keyToFile($key); + return @unlink($filename); + } + + /** + * Attempts to directly output a cached object. + * + * @param string $key Object ID to query. + * @param integer $lifetime Lifetime of the object in seconds. + * + * @return boolean True if output or false if no object was found. + */ + public function output($key, $lifetime = 1) + { + if (!$this->exists($key, $lifetime)) { + return false; + } + + $filename = $this->_keyToFile($key); + return @readfile($filename); + } + + /** + * Map a cache key to a unique filename. + * + * @param string $key Cache key. + * @param string $create Create path if it doesn't exist? + * + * @return string Fully qualified filename. + */ + protected function _keyToFile($key, $create = false) + { + if ($create || !isset($this->_file[$key])) { + $dir = $this->_dir . '/'; + $sub = ''; + $md5 = md5($key); + if (!empty($this->_sub)) { + $max = min($this->_sub, strlen($md5)); + for ($i = 0; $i < $max; $i++) { + $sub .= $md5[$i]; + if ($create && !is_dir($dir . $sub)) { + if (!mkdir($dir . $sub)) { + $sub = ''; + break; + } + } + $sub .= '/'; + } + } + $this->_file[$key] = $dir . $sub . $this->_prefix . $md5; + } + + return $this->_file[$key]; + } + + /** + * TODO + * + * @throws Horde_Exception + */ + protected function _gcDir($dir, &$excepts) + { + $d = @dir($dir); + if (!$d) { + throw new Horde_Exception('Permission denied to ' . $dir); + } + + $c_time = time(); + + while (($entry = $d->read()) !== false) { + $path = $dir . '/' . $entry; + if (($entry == '.') || ($entry == '..')) { + continue; + } + + if (strpos($entry, $this->_prefix) === 0) { + $d_time = isset($excepts[$path]) ? $excepts[$path] : $this->_params['lifetime']; + if (!empty($d_time) && + (($c_time - $d_time) > filemtime($path))) { + @unlink($path); + unset($excepts[$path]); + } + } elseif (!empty($this->_sub) && is_dir($path)) { + $this->_gcDir($path, $excepts); + } + } + $d->close(); + } + +} + diff --git a/framework/Cache/lib/Horde/Cache/Memcache.php b/framework/Cache/lib/Horde/Cache/Memcache.php new file mode 100644 index 000000000..1cecf8174 --- /dev/null +++ b/framework/Cache/lib/Horde/Cache/Memcache.php @@ -0,0 +1,126 @@ + + * Copyright 2007-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @author Duck + * @author Michael Slusarz + * @category Horde + * @package Horde_Cache + */ +class Horde_Cache_Memcache extends Horde_Cache_Base +{ + /** + * Horde_memcache object. + * + * @var Horde_Memcache + */ + protected $_memcache; + + /** + * Cache results of expire() calls (since we will get the entire object + * on an expire() call anyway). + */ + protected $_expirecache = array(); + + /** + * Construct a new Horde_Cache_Memcache object. + * + * @param array $params Parameter array. + */ + public function __construct($params = array()) + { + $this->_memcache = &Horde_Memcache::singleton(); + + parent::__construct($params); + } + + /** + * Attempts to retrieve cached data from the memcache and return it to + * the caller. + * + * @param string $key Cache key to fetch. + * @param integer $lifetime Lifetime of the data in seconds. + * + * @return mixed Cached data, or false if none was found. + */ + public function get($key, $lifetime = 1) + { + if (isset($this->_expirecache[$key])) { + return $this->_expirecache[$key]; + } + + $key_list = array($key); + if (!empty($lifetime)) { + $key_list[] = $key . '_e'; + } + + $res = $this->_memcache->get($key_list); + + if ($res === false) { + unset($this->_expirecache[$key]); + } else { + // If we can't find the expire time, assume we have exceeded it. + if (empty($lifetime) || + (($res[$key . '_e'] !== false) && ($res[$key . '_e'] + $lifetime > time()))) { + $this->_expirecache[$key] = $res[$key]; + } else { + $res[$key] = false; + $this->expire($key); + } + } + + return $res[$key]; + } + + /** + * Attempts to store data to the memcache. + * + * @param string $key Cache key. + * @param mixed $data Data to store in the cache. + * @param integer $lifetime Data lifetime. + * + * @return boolean True on success, false on failure. + */ + public function set($key, $data, $lifetime = null) + { + $lifetime = $this->_getLifetime($lifetime); + return ($this->_memcache->set($key . '_e', time(), $lifetime) === false) + ? false + : $this->_memcache->set($key, $data, $lifetime); + } + + /** + * Checks if a given key exists in the cache. + * + * @param string $key Cache key to check. + * @param integer $lifetime Lifetime of the key in seconds. + * + * @return boolean Existence. + */ + public function exists($key, $lifetime = 1) + { + return ($this->_get($key, $lifetime) !== false); + } + + /** + * Expire any existing data for the given key. + * + * @param string $key Cache key to expire. + * + * @return boolean Success or failure. + */ + public function expire($key) + { + unset($this->_expirecache[$key]); + $this->_memcache->delete($key . '_e'); + return $this->_memcache->delete($key); + } + +} diff --git a/framework/Cache/lib/Horde/Cache/Null.php b/framework/Cache/lib/Horde/Cache/Null.php new file mode 100644 index 000000000..da9c085ac --- /dev/null +++ b/framework/Cache/lib/Horde/Cache/Null.php @@ -0,0 +1,70 @@ + + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @author Duck + * @package Horde_Cache + */ +class Horde_Cache_Apc extends Horde_Cache_Base +{ + /** + * Attempts to retrieve a piece of cached data and return it to + * the caller. + * + * @param string $key Cache key to fetch. + * @param integer $lifetime Lifetime of the key in seconds. + * + * @return mixed Cached data, or false if none was found. + */ + public function get($key, $lifetime = 1) + { + return false; + } + + /** + * Attempts to store an object to the cache. + * + * @param string $key Cache key (identifier). + * @param mixed $data Data to store in the cache. + * @param integer $lifetime Data lifetime. + * + * @return boolean True on success, false on failure. + */ + public function set($key, $data, $lifetime = null) + { + return false; + } + + /** + * Checks if a given key exists in the cache, valid for the given + * lifetime. + * + * @param string $key Cache key to check. + * @param integer $lifetime Lifetime of the key in seconds. + * + * @return boolean Existence. + */ + public function exists($key, $lifetime = 1) + { + return false; + } + + /** + * Expire any existing data for the given key. + * + * @param string $key Cache key to expire. + * + * @return boolean Success or failure. + */ + public function expire($key) + { + return false; + } + +} diff --git a/framework/Cache/lib/Horde/Cache/Sql.php b/framework/Cache/lib/Horde/Cache/Sql.php new file mode 100644 index 000000000..9b28c789e --- /dev/null +++ b/framework/Cache/lib/Horde/Cache/Sql.php @@ -0,0 +1,406 @@ + + * 'phptype' The database type (ie. 'pgsql', 'mysql', etc.). + * + * Required by some database implementations:
+ *   'database'     The name of the database.
+ *   'hostspec'     The hostname of the database server.
+ *   'username'     The username with which to connect to the database.
+ *   'password'     The password associated with 'username'.
+ *   'options'      Additional options to pass to the database.
+ *   'tty'          The TTY on which to connect to the database.
+ *   'port'         The port on which to connect to the database.
+ * + * Optional parameters:
+ *   'table'               The name of the cache table in 'database'.
+ *                         Defaults to 'horde_cache'.
+ *   'use_memorycache'     Use a Horde_Cache:: memory caching driver to cache
+ *                         the data (to avoid DB accesses).  Either empty or
+ *                         'none' if not needed, or else the name of a valid
+ *                         Horde_Cache:: driver.
+ * + * Optional values when using separate reading and writing servers, for example + * in replication settings:
+ *   'splitread'   Boolean, whether to implement the separation or not.
+ *   'read'        Array containing the parameters which are different for
+ *                 the read database connection, currently supported
+ *                 only 'hostspec' and 'port' parameters.
+ * + * The table structure for the cache is as follows: + *
+ * CREATE TABLE horde_cache (
+ *     cache_id          VARCHAR(32) NOT NULL,
+ *     cache_timestamp   BIGINT NOT NULL,
+ *     cache_data        LONGBLOB,
+ *     (Or on PostgreSQL:)
+ *     cache_data        TEXT,
+ *     (Or on some other DBMS systems:)
+ *     cache_data        IMAGE,
+ *
+ *     PRIMARY KEY (cache_id)
+ * );
+ * 
+ * + * Copyright 2007-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @author Michael Slusarz + * @author Ben Klang + * @package Horde_Cache + */ +class Horde_Cache_Sql extends Horde_Cache_Base +{ + /** + * Handle for the current database connection. + * + * @var DB + */ + protected $_db; + + /** + * Handle for the current database connection, used for writing. Defaults + * to the same handle as $_db if a separate write database isn't required. + * + * @var DB + */ + protected $_write_db; + + /** + * Boolean indicating whether or not we're connected to the SQL server. + * + * @var boolean + */ + protected $_connected = false; + + /** + * The memory cache object to use, if configured. + * + * @var Horde_Cache + */ + protected $_mc = null; + + /** + * Constructs a new Horde_Cache_Sql object. + * + * @param array $params A hash containing configuration parameters. + */ + public function __construct($params = array()) + { + $options = array( + 'database' => '', + 'username' => '', + 'password' => '', + 'hostspec' => '', + 'table' => '', + ); + $this->_params = array_merge($options, $params); + if (empty($this->_params['table'])) { + $this->_params['table'] = 'horde_cache'; + } + + /* Create the memory cache object, if configured. */ + if (!empty($this->_params['use_memorycache'])) { + $this->_mc = &Horde_Cache::singleton($params['use_memorycache'], !empty($conf['cache'][$params['use_memorycache']]) ? $conf['cache'][$params['use_memorycache']] : array()); + } + + parent::__construct($this->_params); + } + + /** + * Destructor. + */ + public function __destruct() + { + /* Only do garbage collection 0.1% of the time we create an object. */ + if (rand(0, 999) != 0) { + return; + } + + try { + $this->_connect(); + } catch (Horde_Exception $e) { + Horde::logMessage($e, __FILE__, __LINE__, PEAR_LOG_ERR); + return; + } + + $query = 'DELETE FROM ' . $this->_params['table'] . + ' WHERE cache_expiration < ? AND cache_expiration <> 0'; + $values = array(time()); + + $result = $this->_write_db->query($query, $values); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); + } + } + + /** + * Attempts to retrieve cached data. + * + * @param string $key Cache key to fetch. + * @param integer $lifetime Maximum age of the data in seconds or + * 0 for any object. + * + * @return mixed Cached data, or false if none was found. + */ + public function get($key, $lifetime = 1) + { + $okey = $key; + $key = hash('md5', $key); + + if ($this->_mc) { + $data = $this->_mc->get($key, $lifetime); + if ($data !== false) { + return $data; + } + } + + try { + $this->_connect(); + } catch (Horde_Exception $e) { + Horde::logMessage($e, __FILE__, __LINE__, PEAR_LOG_ERR); + return false; + } + + $timestamp = time(); + $maxage = $timestamp - $lifetime; + + /* Build SQL query. */ + $query = 'SELECT cache_data FROM ' . $this->_params['table'] . + ' WHERE cache_id = ?'; + $values = array($key); + + // 0 lifetime checks for objects which have no expiration + if ($lifetime != 0) { + $query .= ' AND cache_timestamp >= ?'; + $values[] = $maxage; + } + + $result = $this->_db->getOne($query, $values); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); + return false; + } elseif (is_null($result)) { + /* No rows were found - cache miss */ + Horde::logMessage(sprintf('Cache miss: %s (Id %s newer than %d)', $okey, $key, $maxage), __FILE__, __LINE__, PEAR_LOG_DEBUG); + return false; + } + + if ($this->_mc) { + $this->_mc->set($key, $result); + } + Horde::logMessage(sprintf('Cache hit: %s (Id %s newer than %d)', $okey, $key, $maxage), __FILE__, __LINE__, PEAR_LOG_DEBUG); + + return $result; + } + + /** + * Attempts to store data. + * + * @param string $key Cache key. + * @param mixed $data Data to store in the cache. (MUST BE A STRING) + * @param integer $lifetime Maximum data life span or 0 for a + * non-expiring object. + * + * @return boolean True on success, false on failure. + */ + public function set($key, $data, $lifetime = null) + { + $okey = $key; + $key = hash('md5', $key); + + if ($this->_mc) { + $this->_mc->set($key, $data); + } + + try { + $this->_connect(); + } catch (Horde_Exception $e) { + Horde::logMessage($e, __FILE__, __LINE__, PEAR_LOG_ERR); + return false; + } + + $timestamp = time(); + + // 0 lifetime indicates the object should not be GC'd. + $expiration = ($lifetime === 0) + ? 0 + : $this->_getLifetime($lifetime) + $timestamp; + + Horde::logMessage(sprintf('Cache set: %s (Id %s set at %d expires at %d)', $okey, $key, $timestamp, $expiration), __FILE__, __LINE__, PEAR_LOG_DEBUG); + + // Remove any old cache data and prevent duplicate keys + $query = 'DELETE FROM ' . $this->_params['table'] . ' WHERE cache_id=?'; + $values = array($key); + $this->_write_db->query($query, $values); + + /* Build SQL query. */ + $query = 'INSERT INTO ' . $this->_params['table'] . + ' (cache_id, cache_timestamp, cache_expiration, cache_data)' . + ' VALUES (?, ?, ?, ?)'; + $values = array($key, $timestamp, $expiration, $data); + + $result = $this->_write_db->query($query, $values); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); + return false; + } + + return true; + } + + /** + * Checks if a given key exists in the cache, valid for the given + * lifetime. + * + * @param string $key Cache key to check. + * @param integer $lifetime Maximum age of the key in seconds or 0 for + * any object. + * + * @return boolean Existence. + */ + public function exists($key, $lifetime = 1) + { + $okey = $key; + $key = hash('md5', $key); + + if ($this->_mc && $this->_mc->exists($key, $lifetime)) { + return true; + } + + try { + $this->_connect(); + } catch (Horde_Exception $e) { + Horde::logMessage($e, __FILE__, __LINE__, PEAR_LOG_ERR); + return false; + } + + /* Build SQL query. */ + $query = 'SELECT 1 FROM ' . $this->_params['table'] . + ' WHERE cache_id = ?'; + $values = array($key); + + // 0 lifetime checks for objects which have no expiration + if ($lifetime != 0) { + $query .= ' AND cache_timestamp >= ?'; + $values[] = time() - $lifetime; + } + + $result = $this->_db->getRow($query, $values); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); + return false; + } + + $timestamp = time(); + if (empty($result)) { + Horde::logMessage(sprintf('Cache exists() miss: %s (Id %s newer than %d)', $okey, $key, $timestamp), __FILE__, __LINE__, PEAR_LOG_DEBUG); + return false; + } + Horde::logMessage(sprintf('Cache exists() hit: %s (Id %s newer than %d)', $okey, $key, $timestamp), __FILE__, __LINE__, PEAR_LOG_DEBUG); + + return true; + } + + /** + * Expire any existing data for the given key. + * + * @param string $key Cache key to expire. + * + * @return boolean Success or failure. + */ + public function expire($key) + { + $key = hash('md5', $key); + + if ($this->_mc) { + $this->_mc->expire($key); + } + + try { + $this->_connect(); + } catch (Horde_Exception $e) { + Horde::logMessage($e, __FILE__, __LINE__, PEAR_LOG_ERR); + return false; + } + + $query = 'DELETE FROM ' . $this->_params['table'] . + ' WHERE cache_id = ?'; + $values = array($key); + + $result = $this->_write_db->query($query, $values); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); + return false; + } + + return true; + } + + /** + * Opens a connection to the SQL server. + * + * @throws Horde_Exception + */ + protected function _connect() + { + if ($this->_connected) { + return true; + } + + $result = Util::assertDriverConfig($this->_params, array('phptype'), + 'cache SQL', array('driver' => 'cache')); + if (is_a($result, 'PEAR_Error')) { + throw new Horde_Exception($result->getMessage()); + } + + require_once 'DB.php'; + $this->_write_db = &DB::connect( + $this->_params, + array('persistent' => !empty($this->_params['persistent']), + 'ssl' => !empty($this->_params['ssl'])) + ); + if (is_a($this->_write_db, 'PEAR_Error')) { + throw new Horde_Exception($this->_write_db->getMessage()); + } + + // Set DB portability options. + $portability = DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS; + if ($this->_write_db->phptype) { + $portability |= DB_PORTABILITY_RTRIM; + } + $this->_write_db->setOption('portability', $portability); + + /* Check if we need to set up the read DB connection + * seperately. */ + if (!empty($this->_params['splitread'])) { + $params = array_merge($this->_params, $this->_params['read']); + $this->_db = &DB::connect( + $params, + array('persistent' => !empty($params['persistent']), + 'ssl' => !empty($params['ssl'])) + ); + if (is_a($this->_db, 'PEAR_Error')) { + throw new Horde_Exception($this->_db->getMessage()); + } + + // Set DB portability options. + $portability = DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS; + if ($this->_db->phptype) { + $portability |= DB_PORTABILITY_RTRIM; + } + $this->_db->setOption('portability', $portability); + } else { + /* Default to the same DB handle for read. */ + $this->_db = $this->_write_db; + } + + $this->_connected = true; + } + +} diff --git a/framework/Cache/lib/Horde/Cache/Xcache.php b/framework/Cache/lib/Horde/Cache/Xcache.php new file mode 100644 index 000000000..861ec11de --- /dev/null +++ b/framework/Cache/lib/Horde/Cache/Xcache.php @@ -0,0 +1,121 @@ + + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @author Duck + * @package Horde_Cache + */ +class Horde_Cache_Xcache extends Horde_Cache_Base +{ + /** + * Construct a new Horde_Cache_Xcache object. + * + * @param array $params Parameter array. + */ + public function __construct($params = array()) + { + parent::__construct($params); + + if (empty($this->_params['prefix'])) { + $this->_params['prefix'] = $_SERVER['SERVER_NAME'] + ? $_SERVER['SERVER_NAME'] + : $_SERVER['HOSTNAME']; + } + } + + /** + * Attempts to retrieve a piece of cached data and return it to the caller. + * + * @param string $key Cache key to fetch. + * @param integer $lifetime Lifetime of the key in seconds. + * + * @return mixed Cached data, or false if none was found. + */ + public function get($key, $lifetime = 1) + { + $key = $this->_params['prefix'] . $key; + $this->_setExpire($key, $lifetime); + $result = xcache_get($key); + + return empty($result) + ? false + : $result; + } + + /** + * Attempts to store an object to the cache. + * + * @param string $key Cache key (identifier). + * @param mixed $data Data to store in the cache. + * @param integer $lifetime Data lifetime. + * + * @return boolean True on success, false on failure. + */ + public function set($key, $data, $lifetime = null) + { + $key = $this->_params['prefix'] . $key; + $lifetime = $this->_getLifetime($lifetime); + xcache_set($key . '_expire', time(), $lifetime); + return xcache_set($key, $data, $lifetime); + } + + /** + * Checks if a given key exists in the cache, valid for the given + * lifetime. + * + * @param string $key Cache key to check. + * @param integer $lifetime Lifetime of the key in seconds. + * + * @return boolean Existence. + */ + public function exists($key, $lifetime = 1) + { + $key = $this->_params['prefix'] . $key; + $this->_setExpire($key, $lifetime); + return xcache_isset($key); + } + + /** + * Expire any existing data for the given key. + * + * @param string $key Cache key to expire. + * + * @return boolean Success or failure. + */ + public function expire($key) + { + $key = $this->_params['prefix'] . $key; + xcache_unset($key . '_expire'); + return xcache_unset($key); + } + + /** + * Set expire time on each call since memcache sets it on cache creation. + * + * @param string $key Cache key to expire. + * @param integer $lifetime Lifetime of the data in seconds. + */ + protected function _setExpire($key, $lifetime) + { + if ($lifetime == 0) { + // don't expire + return; + } + + $expire = xcache_get($key . '_expire'); + + // set prune period + if ($expire + $lifetime < time()) { + // Expired + xcache_unset($key . '_expire'); + xcache_unset($key); + } + } + +} diff --git a/framework/Cache/lib/Horde/Cache/Zps4.php b/framework/Cache/lib/Horde/Cache/Zps4.php new file mode 100644 index 000000000..701f189a5 --- /dev/null +++ b/framework/Cache/lib/Horde/Cache/Zps4.php @@ -0,0 +1,73 @@ + + * @package Horde_Cache + */ +class Horde_Cache_Zps4 extends Horde_Cache_Base +{ + /** + * Attempts to retrieve a piece of cached data and return it to the + * caller. + * + * @param string $key Cache key to fetch. + * @param integer $lifetime Lifetime of the key in seconds. + * + * @return mixed Cached data, or false if none was found. + */ + public function get($key, $lifetime = 1) + { + return output_cache_get($key, $lifetime); + } + + /** + * Attempts to store an object to the cache. + * + * @param string $key Cache key (identifier). + * @param mixed $data Data to store in the cache. + * @param integer $lifetime Data lifetime. [Not used] + * + * @return boolean True on success, false on failure. + */ + public function set($key, $data, $lifetime = null) + { + output_cache_put($key, $data); + return true; + } + + /** + * Checks if a given key exists in the cache, valid for the given + * lifetime. + * + * @param string $key Cache key to check. + * @param integer $lifetime Lifetime of the key in seconds. + * + * @return boolean Existence. + */ + public function exists($key, $lifetime = 1) + { + $exists = output_cache_exists($key, $lifetime); + output_cache_stop(); + return $exists; + } + + /** + * Expire any existing data for the given key. + * + * @param string $key Cache key to expire. + * + * @return boolean Success or failure. + */ + public function expire($key) + { + return output_cache_remove_key($key); + } + +} diff --git a/framework/Cache/package.xml b/framework/Cache/package.xml new file mode 100644 index 000000000..4ac63a6f6 --- /dev/null +++ b/framework/Cache/package.xml @@ -0,0 +1,131 @@ + + + Cache + pear.horde.org + Horde Caching API + This package provides a simple, functional caching API, +with the option to store the cached data on the filesystem, in one of +the PHP opcode cache systems (APC, eAcclerator, XCache, or Zend +Performance Suite's content cache), memcached, or an SQL table. + + + Chuck Hagenbuch + chuck + chuck@horde.org + yes + + + Michael Slusarz + slusarz + slusarz@horde.org + yes + + 2008-03-04 + + 0.2.0 + 0.2.0 + + + beta + beta + + LGPL + * Initial Horde 4 package. + + + + + + + + + + + + + + + + + + + + + + + + + + + 5.2.0 + + + 1.5.0 + + + Util + pear.horde.org + + + + + apc + + + eaccelerator + + + memcache + + + + + + + + + + + + + + + + + + + + 2006-05-08 + + 0.1.0 + 0.1.0 + + + beta + beta + + LGPL + * Add SQL backend. +* Converted to package.xml 2.0 for pear.horde.org. +* Add APC, eAccelerator, and XCache backends (duck@obala.net). + + + + + 0.0.1 + 0.0.1 + + + alpha + alpha + + 2004-01-01 + LGPL + Initial packaging. + + + +