From: Michael M Slusarz Date: Tue, 16 Nov 2010 02:30:50 +0000 (-0700) Subject: Abstracted storage code in Horde_Cache X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=3cc051e1b6523fa7ae776e1cbb889de0c0044ef0;p=horde.git Abstracted storage code in Horde_Cache --- diff --git a/framework/Cache/lib/Horde/Cache.php b/framework/Cache/lib/Horde/Cache.php index b443fed15..7c9e07e43 100644 --- a/framework/Cache/lib/Horde/Cache.php +++ b/framework/Cache/lib/Horde/Cache.php @@ -1,7 +1,6 @@ * @author Michael Slusarz * @category Horde + * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @package Cache */ -abstract class Horde_Cache +class Horde_Cache { /** * Cache parameters. @@ -23,8 +23,7 @@ abstract class Horde_Cache */ protected $_params = array( 'compress' => false, - 'lifetime' => 86400, - 'prefix' => '', + 'lifetime' => 86400 ); /** @@ -35,9 +34,17 @@ abstract class Horde_Cache protected $_logger; /** - * Construct a new Horde_Cache object. + * Storage object. * - * @param array $params Parameter array: + * @var Horde_Cache_Storage + */ + protected $_storage; + + /** + * Constructor. + * + * @param Horde_Cache_Storage $storage The storage object. + * @param array $params Parameter array: *
      * 'compress' - (boolean) Compress data? Requires the 'lzf' PECL
      *              extension.
@@ -47,11 +54,14 @@ abstract class Horde_Cache
      * 'logger' - (Horde_Log_Logger) Log object to use for log/debug messages.
      * 
*/ - public function __construct($params = array()) + public function __construct(Horde_Cache_Storage $storage, + array $params = array()) { if (isset($params['logger'])) { $this->_logger = $params['logger']; unset($params['logger']); + + $storage->setLogger($this->_logger); } if (!empty($params['compress']) && !extension_loaded('lzf')) { @@ -59,6 +69,7 @@ abstract class Horde_Cache } $this->_params = array_merge($this->_params, $params); + $this->_storage = $storage; } /** @@ -90,7 +101,7 @@ abstract class Horde_Cache */ public function get($key, $lifetime = 1) { - $res = $this->_get($key, $lifetime); + $res = $this->_storage->get($key, $lifetime); return ($this->_params['compress'] && ($res !== false)) // lzf_decompress() returns false on error @@ -99,41 +110,28 @@ abstract class Horde_Cache } /** - * @see get() - */ - abstract protected function _get($key, $lifetime); - - /** * 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 string $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. - * - * @throws Horde_Cache_Exception */ public function set($key, $data, $lifetime = null) { - if (!is_string($data)) { - throw new Horde_Cache_Exception('Cache data must be a string.'); - } - if ($this->_params['compress']) { $data = lzf_compress($data); } + $lifetime = is_null($lifetime) + ? $this->_params['lifetime'] + : $lifetime; - $res = $this->_set($key, $data, $lifetime); + $this->_storage->set($key, $data, $lifetime); } /** - * @see set() - */ - abstract protected function _set($key, $data, $lifetime); - - /** * Checks if a given key exists in the cache, valid for the given * lifetime. * @@ -142,7 +140,10 @@ abstract class Horde_Cache * * @return boolean Existence. */ - abstract public function exists($key, $lifetime = 1); + public function exists($key, $lifetime = 1) + { + return $this->_storage->exists($key, $lifetime); + } /** * Expire any existing data for the given key. @@ -151,18 +152,9 @@ abstract class Horde_Cache * * @return boolean Success or failure. */ - abstract public function expire($key); - - /** - * 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) + public function expire($key) { - return is_null($lifetime) ? $this->_params['lifetime'] : $lifetime; + return $this->_storage->expire($key); } } diff --git a/framework/Cache/lib/Horde/Cache/Apc.php b/framework/Cache/lib/Horde/Cache/Apc.php deleted file mode 100644 index 324ceb4b6..000000000 --- a/framework/Cache/lib/Horde/Cache/Apc.php +++ /dev/null @@ -1,91 +0,0 @@ - - * - * 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 - * @category Horde - * @package Cache - */ -class Horde_Cache_Apc extends Horde_Cache -{ - /** - */ - protected function _get($key, $lifetime) - { - $key = $this->_params['prefix'] . $key; - $this->_setExpire($key, $lifetime); - return apc_fetch($key); - } - - /** - */ - protected function _set($key, $data, $lifetime) - { - $key = $this->_params['prefix'] . $key; - $lifetime = $this->_getLifetime($lifetime); - if (apc_store($key . '_expire', time(), $lifetime)) { - 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) - { - $key = $this->_params['prefix'] . $key; - $this->_setExpire($key, $lifetime); - return (apc_fetch($key) !== 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) - { - $key = $this->_params['prefix'] . $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) - { - $key = $this->_params['prefix'] . $key; - 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/Eaccelerator.php b/framework/Cache/lib/Horde/Cache/Eaccelerator.php deleted file mode 100644 index 2a911f8ad..000000000 --- a/framework/Cache/lib/Horde/Cache/Eaccelerator.php +++ /dev/null @@ -1,108 +0,0 @@ - - * - * 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 - * @category Horde - * @package Cache - */ -class Horde_Cache_Eaccelerator extends Horde_Cache -{ - /** - * Construct a new Horde_Cache object. - * - * @param array $params Parameter array. - * - * @throws Horde_Cache_Exception - */ - public function __construct($params = array()) - { - if (!function_exists('eaccelerator_gc')) { - throw new Horde_Cache_Exception('eAccelerator must be compiled with support for shared memory to use as caching backend.'); - } - - parent::__construct($params); - } - - /** - */ - protected function _get($key, $lifetime) - { - $key = $this->_params['prefix'] . $key; - $this->_setExpire($key, $lifetime); - return eaccelerator_get($key); - } - - /** - */ - protected function _set($key, $data, $lifetime) - { - $key = $this->_params['prefix'] . $key; - $lifetime = $this->_getLifetime($lifetime); - if (eaccelerator_put($key . '_expire', time(), $lifetime)) { - 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) - { - $key = $this->_params['prefix'] . $key; - $this->_setExpire($key, $lifetime); - return eaccelerator_get($key) !== 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) - { - $key = $this->_params['prefix'] . $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; - } - - $key = $this->_params['prefix'] . $key; - $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 deleted file mode 100644 index d8fae0913..000000000 --- a/framework/Cache/lib/Horde/Cache/File.php +++ /dev/null @@ -1,249 +0,0 @@ - - * @author Chuck Hagenbuch - * @category Horde - * @license http://www.fsf.org/copyleft/lgpl.html LGPL - * @package Cache - */ -class Horde_Cache_File extends Horde_Cache -{ - /* Location of the garbage collection data file. */ - const GC_FILE = 'horde_cache_gc'; - - /** - * The location of the temp directory. - * - * @var string - */ - protected $_dir; - - /** - * List of key to filename mappings. - * - * @var array - */ - protected $_file = array(); - - /** - * Constructor. - * - * @param array $params Optional parameters: - *
-     * 'dir' - (string) The base directory to store the cache files in.
-     *         DEFAULT: System default
-     * 'prefix' - (string) The filename prefix to use for the cache files.
-     *            DEFAULT: 'cache_'
-     * 'sub' - (integer) If non-zero, the number of subdirectories to create
-     *         to store the file (i.e. PHP's session.save_path).
-     *         DEFAULT: 0
-     * 
- */ - public function __construct(array $params = array()) - { - $params = array_merge(array( - 'prefix' => 'cache_', - 'sub' => 0 - ), $params); - - $this->_dir = (isset($params['dir']) && @is_dir($params['dir'])) - ? $params['dir'] - : Horde_Util::getTempDir(); - - 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 . '/' . self::GC_FILE; - $excepts = array(); - - if (file_exists($filename)) { - $gc_file = file($filename, FILE_IGNORE_NEW_LINES); - reset($gc_file); - next($gc_file); - while (list(,$data) = each($gc_file)) { - $parts = explode("\t", $data, 2); - $excepts[$parts[0]] = $parts[1]; - } - } - - try { - $it = empty($this->_params['sub']) - ? new DirectoryIterator($this->_dir) - : new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->_dir), RecursiveIteratorIterator::CHILD_FIRST); - } catch (UnexpectedValueException $e) { - return; - } - - $c_time = time(); - - foreach ($it as $key => $val) { - if (!$val->isDir() && - (strpos($val->getFilename(), $this->_params['prefix']) === 0)) { - $d_time = isset($excepts[$key]) - ? $excepts[$key] - : $this->_params['lifetime']; - - if (!empty($d_time) && - (($c_time - $d_time) > filemtime($key))) { - @unlink($key); - unset($excepts[$key]); - } - } - } - - $fp = fopen($filename, 'w'); - foreach ($excepts as $key => $val) { - fwrite($fp, $key . "\t" . $val . "\n"); - } - fclose($fp); - } - - /** - */ - protected function _get($key, $lifetime) - { - if (!$this->exists($key, $lifetime)) { - /* Nothing cached, return failure. */ - return false; - } - - $filename = $this->_keyToFile($key); - $size = filesize($filename); - - return $size - ? @file_get_contents($filename) - : ''; - } - - /** - */ - protected function _set($key, $data, $lifetime) - { - $filename = $this->_keyToFile($key, true); - $tmp_file = Horde_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) { - throw new Horde_Cache_Exception('Cannot write to cache directory ' . $this->_dir); - } - - @rename($tmp_file, $filename); - - $lifetime = $this->_getLifetime($lifetime); - if (($lifetime != $this->_params['lifetime']) && - ($fp = @fopen($this->_dir . '/horde_cache_gc', 'a'))) { - // 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. - fwrite($fp, $filename . "\t" . (empty($lifetime) ? 0 : time() + $lifetime) . "\n"); - fclose($fp); - } - } - - /** - */ - public function exists($key, $lifetime = 1) - { - $filename = $this->_keyToFile($key); - - /* Key exists in the cache */ - if (file_exists($filename)) { - /* 0 means no expire. - * Also, If the file was been created after the supplied value, - * the data is valid (fresh). */ - if (($lifetime == 0) || - (time() - $lifetime <= filemtime($filename))) { - return true; - } - - @unlink($filename); - } - - return false; - } - - /** - */ - public function expire($key) - { - $filename = $this->_keyToFile($key); - return @unlink($filename); - } - - /** - */ - 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 . '/'; - $md5 = hash('md5', $key); - $sub = ''; - - if (!empty($this->_params['sub'])) { - $max = min($this->_params['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->_params['prefix'] . $md5; - } - - return $this->_file[$key]; - } - - /** - * TODO - * - * @throws Horde_Cache_Exception - */ - protected function _gcDir($dir, &$excepts) - { - } - -} diff --git a/framework/Cache/lib/Horde/Cache/Memcache.php b/framework/Cache/lib/Horde/Cache/Memcache.php deleted file mode 100644 index bb90bc82b..000000000 --- a/framework/Cache/lib/Horde/Cache/Memcache.php +++ /dev/null @@ -1,137 +0,0 @@ - - * Copyright 2007-2010 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 Cache - */ -class Horde_Cache_Memcache extends Horde_Cache -{ - /** - * 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: - *
-     * 'memcache' - (Horde_Memcache) A Horde_Memcache object.
-     * 
- * - * @throws InvalidArgumentException - */ - public function __construct($params = array()) - { - if (!isset($params['memcache'])) { - throw new InvalidArgumentException('Missing memcache object'); - } - - $this->_memcache = $params['memcache']; - - parent::__construct($params); - } - - /** - * Do cleanup prior to serialization and provide a list of variables - * to serialize. - */ - public function __sleep() - { - return array('_memcache'); - } - - /** - */ - protected function _get($key, $lifetime) - { - $key = $this->_params['prefix'] . $key; - 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]; - } - - /** - */ - protected function _set($key, $data, $lifetime) - { - $key = $this->_params['prefix'] . $key; - $lifetime = $this->_getLifetime($lifetime); - - if ($this->_memcache->set($key . '_e', time(), $lifetime) !== 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) - { - $key = $this->_params['prefix'] . $key; - - 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) - { - $key = $this->_params['prefix'] . $key; - unset($this->_expirecache[$key]); - $this->_memcache->delete($key . '_e'); - - return $this->_memcache->delete($key); - } - -} diff --git a/framework/Cache/lib/Horde/Cache/Mock.php b/framework/Cache/lib/Horde/Cache/Mock.php deleted file mode 100644 index 234997d4d..000000000 --- a/framework/Cache/lib/Horde/Cache/Mock.php +++ /dev/null @@ -1,79 +0,0 @@ - - * @category Horde - * @license http://www.fsf.org/copyleft/lgpl.html LGPL - * @link http://pear.horde.org/index.php?package=Cache - * @package Cache - */ -class Horde_Cache_Mock extends Horde_Cache -{ - /** - * The storage location for this cache. - * - * @var array - */ - private $_cache = array(); - - /** - * Construct a new Horde_Cache_Mock object. - * - * @param array $params Configuration parameters: - */ - public function __construct($params = array()) - { - parent::__construct($params); - } - - /** - */ - protected function _get($key, $lifetime) - { - return isset($this->_cache[$key]) - ? $this->_cache[$key] - : false; - } - - /** - */ - protected function _set($key, $data, $lifetime) - { - $this->_cache[$key] = $data; - } - - /** - * 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 isset($this->_cache[$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) - { - unset($this->_cache[$key]); - } - -} diff --git a/framework/Cache/lib/Horde/Cache/Null.php b/framework/Cache/lib/Horde/Cache/Null.php deleted file mode 100644 index a332f858d..000000000 --- a/framework/Cache/lib/Horde/Cache/Null.php +++ /dev/null @@ -1,56 +0,0 @@ - - * - * 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 - * @category Horde - * @package Cache - */ -class Horde_Cache_Null extends Horde_Cache -{ - /** - */ - protected function _get($key, $lifetime) - { - return false; - } - - /** - */ - protected function _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) - { - 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/Session.php b/framework/Cache/lib/Horde/Cache/Session.php deleted file mode 100644 index 6aeb6b5c8..000000000 --- a/framework/Cache/lib/Horde/Cache/Session.php +++ /dev/null @@ -1,108 +0,0 @@ - - * @category Horde - * @license http://www.fsf.org/copyleft/lgpl.html LGPL - * @package Cache - */ -class Horde_Cache_Session extends Horde_Cache -{ - /** - * Pointer to the session entry. - * - * @var array - */ - protected $_sess; - - /** - * Constructor. - * - * @param array $params Optional parameters: - *
-     * 'session' - (string) Store session data in this entry.
-     *             DEFAULT: 'horde_cache_session'
-     * 
- */ - public function __construct(array $params = array()) - { - $params = array_merge(array( - 'sess_name' => 'horde_cache_session' - ), $params); - - parent::__construct($params); - - if (!isset($_SESSION[$this->_params['sess_name']])) { - $_SESSION[$this->_params['sess_name']] = array(); - } - $this->_sess = &$_SESSION[$this->_params['sess_name']]; - } - - /** - */ - protected function _get($key, $lifetime) - { - return $this->exists($key, $lifetime) - ? $this->_sess[$key]['d'] - : false; - } - - /** - */ - protected function _set($key, $data, $lifetime) - { - $this->_sess[$key] = array( - 'd' => $data, - 'l' => $this->_getLifetime($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) - { - if (isset($this->_sess[$key])) { - /* 0 means no expire. */ - if (($lifetime == 0) || - ((time() - $lifetime) <= $this->_sess[$key]['l'])) { - return true; - } - - unset($this->_sess[$key]); - } - - 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) - { - if (isset($this->_sess[$key])) { - unset($this->_sess[$key]); - return true; - } - - return false; - } - -} diff --git a/framework/Cache/lib/Horde/Cache/Sql.php b/framework/Cache/lib/Horde/Cache/Sql.php deleted file mode 100644 index ead301c0d..000000000 --- a/framework/Cache/lib/Horde/Cache/Sql.php +++ /dev/null @@ -1,237 +0,0 @@ - - * 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-2010 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 - * @category Horde - * @package Cache - */ -class Horde_Cache_Sql extends Horde_Cache -{ - /** - * Handle for the current database connection. - * - * @var Horde_Db_Adapter - */ - protected $_db; - - /** - * Constructor. - * - * @param array $params Parameters: - *
-     * 'db' - (Horde_Db_Adapter) [REQUIRED] The DB instance.
-     * 'table' - (string) The name of the cache table.
-     *           DEFAULT: 'horde_cache'
-     * 
- * - * @throws Horde_Cache_Exception - */ - public function __construct($params = array()) - { - if (!isset($params['db'])) { - throw new Horde_Cache_Exception('Missing db parameter.'); - } - $this->_db = $params['db']; - unset($params['db']); - - $params = array_merge(array( - 'table' => 'horde_cache', - ), $params); - - 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; - } - - $query = 'DELETE FROM ' . $this->_params['table'] . - ' WHERE cache_expiration < ? AND cache_expiration <> 0'; - $values = array(time()); - - try { - $this->_db->delete($query, $values); - } catch (Horde_Db_Exception $e) {} - } - - /** - */ - protected function _get($key, $lifetime) - { - $okey = $key; - $key = hash('md5', $key); - - $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; - } - - try { - $result = $this->_db->selectValue($query, $values); - } catch (Horde_Db_Exception $e) { - return false; - } - - if (is_null($result)) { - /* No rows were found - cache miss */ - if ($this->_logger) { - $this->_logger->log(sprintf('Cache miss: %s (Id %s newer than %d)', $okey, $key, $maxage), 'DEBUG'); - } - return false; - } - - if ($this->_logger) { - $this->_logger->log(sprintf('Cache hit: %s (Id %s newer than %d)', $okey, $key, $maxage), 'DEBUG'); - } - - return $result; - } - - /** - */ - protected function _set($key, $data, $lifetime) - { - $okey = $key; - $key = hash('md5', $key); - - $timestamp = time(); - - // 0 lifetime indicates the object should not be GC'd. - $expiration = ($lifetime === 0) - ? 0 - : $this->_getLifetime($lifetime) + $timestamp; - - if ($this->_logger) { - $this->_logger->log(sprintf('Cache set: %s (Id %s set at %d expires at %d)', $okey, $key, $timestamp, $expiration), 'DEBUG'); - } - - // Remove any old cache data and prevent duplicate keys - $query = 'DELETE FROM ' . $this->_params['table'] . ' WHERE cache_id=?'; - $values = array($key); - try { - $this->_db->delete($query, $values); - } catch (Horde_Db_Exception $e) {} - - /* Build SQL query. */ - $query = 'INSERT INTO ' . $this->_params['table'] . - ' (cache_id, cache_timestamp, cache_expiration, cache_data)' . - ' VALUES (?, ?, ?, ?)'; - $values = array($key, $timestamp, $expiration, $data); - - try { - $this->_db->insert($query, $values); - } catch (Horde_Db_Exception $e) { - throw new Horde_Cache_Exception($e); - } - } - - /** - * 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); - - /* 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; - } - - try { - $result = $this->_db->selectValue($query, $values); - } catch (Horde_Db_Exception $e) { - return false; - } - - $timestamp = time(); - if (empty($result)) { - if ($this->_logger) { - $this->_logger->log(sprintf('Cache exists() miss: %s (Id %s newer than %d)', $okey, $key, $timestamp), 'DEBUG'); - } - return false; - } - - if ($this->_logger) { - $this->_logger->log(sprintf('Cache exists() hit: %s (Id %s newer than %d)', $okey, $key, $timestamp), '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); - - $query = 'DELETE FROM ' . $this->_params['table'] . - ' WHERE cache_id = ?'; - $values = array($key); - - try { - $this->_db->delete($query, $values); - } catch (Horde_Db_Exception $e) { - return false; - } - - return true; - } - -} diff --git a/framework/Cache/lib/Horde/Cache/Stack.php b/framework/Cache/lib/Horde/Cache/Stack.php deleted file mode 100644 index 55bc6bac5..000000000 --- a/framework/Cache/lib/Horde/Cache/Stack.php +++ /dev/null @@ -1,128 +0,0 @@ - - * @category Horde - * @package Cache - */ -class Horde_Cache_Stack extends Horde_Cache -{ - /** - * Stack of cache drivers. - * - * @var string - */ - protected $_stack = array(); - - /** - * Constructor. - * - * @param array $params Parameters: - *
-     * 'stack' - (array) [REQUIRED] An array of Cache instances to loop
-     *           through, in order of priority. The last entry is considered
-     *           the 'master' driver, for purposes of writes.
-     * 
- * - * @throws InvalidArgumentException - */ - public function __construct(array $params = array()) - { - if (!isset($params['stack'])) { - throw new InvalidArgumentException('Missing stack parameter.'); - } - $this->_stack[] = $params['stack']; - - unset($params['stack']); - parent::__construct($params); - } - - /** - */ - protected function _get($key, $lifetime) - { - foreach ($this->_stack as $val) { - $result = $val->get($key, $lifetime); - if ($result !== false) { - return $result; - } - } - - return false; - } - - /** - */ - protected function _set($key, $data, $lifetime) - { - /* Do writes in *reverse* order - it is OK if a write to one of the - * non-master backends fails. */ - $master = true; - - foreach (array_reverse($this->_stack) as $val) { - $result = $val->set($key, $data, $lifetime); - if ($result === false) { - if ($master) { - return; - } - - /* Attempt to invalidate cache if write failed. */ - $val->expire($id); - } - $master = 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) - { - foreach ($this->_stack as $val) { - $result = $val->exists($key, $lifetime); - if ($result === true) { - break; - } - } - - return $result; - } - - /** - * Expire any existing data for the given key. - * - * @param string $key Cache key to expire. - * - * @return boolean Success or failure. - */ - public function expire($key) - { - /* Only report success from master. */ - $master = $success = true; - - foreach (array_reverse($this->_stack) as $val) { - $result = $val->expire($id); - if ($master && ($result === false)) { - $success = false; - } - $master = false; - } - - return $success; - } - -} diff --git a/framework/Cache/lib/Horde/Cache/Storage.php b/framework/Cache/lib/Horde/Cache/Storage.php new file mode 100644 index 000000000..6db05d2c5 --- /dev/null +++ b/framework/Cache/lib/Horde/Cache/Storage.php @@ -0,0 +1,93 @@ + + * @category Horde + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @package Cache + */ +abstract class Horde_Cache_Storage +{ + /** + * Logger. + * + * @var Horde_Log_Logger + */ + protected $_logger; + + /** + * Parameters. + * + * @var array + */ + protected $_params = array(); + + /** + * Constructor. + * + * @param array $params Configuration parameters. + */ + public function __construct(array $params = array()) + { + $this->_params = array_merge($this->_params, $params); + } + + /** + * Set the logging object. + * + * @param Horde_Log_Logger $logger Log object. + */ + public function setLogger($logger) + { + $this->_logger = $logger; + } + + /** + * Retrieve cached data. + * + * @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); + + /** + * 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 0 will not be GC'd. + */ + abstract public function 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. + */ + abstract public function exists($key, $lifetime); + + /** + * 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); + +} diff --git a/framework/Cache/lib/Horde/Cache/Storage/Apc.php b/framework/Cache/lib/Horde/Cache/Storage/Apc.php new file mode 100644 index 000000000..12ea3a3a5 --- /dev/null +++ b/framework/Cache/lib/Horde/Cache/Storage/Apc.php @@ -0,0 +1,78 @@ + + * + * 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 + * @category Horde + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @package Cache + */ +class Horde_Cache_Storage_Apc extends Horde_Cache_Storage +{ + /** + */ + public function get($key, $lifetime) + { + $key = $this->_params['prefix'] . $key; + $this->_setExpire($key, $lifetime); + return apc_fetch($key); + } + + /** + */ + public function set($key, $data, $lifetime) + { + $key = $this->_params['prefix'] . $key; + if (apc_store($key . '_expire', time(), $lifetime)) { + apc_store($key, $data, $lifetime); + } + } + + /** + */ + public function exists($key, $lifetime) + { + $key = $this->_params['prefix'] . $key; + $this->_setExpire($key, $lifetime); + return (apc_fetch($key) !== false); + } + + /** + */ + public function expire($key) + { + $key = $this->_params['prefix'] . $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) + { + $key = $this->_params['prefix'] . $key; + 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/Storage/Eaccelerator.php b/framework/Cache/lib/Horde/Cache/Storage/Eaccelerator.php new file mode 100644 index 000000000..1a58a1b1c --- /dev/null +++ b/framework/Cache/lib/Horde/Cache/Storage/Eaccelerator.php @@ -0,0 +1,91 @@ + + * + * 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 + * @category Horde + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @package Cache + */ +class Horde_Cache_Storage_Eaccelerator extends Horde_Cache_Storage +{ + /** + * @throws Horde_Cache_Exception + */ + public function __construct(array $params = array()) + { + if (!function_exists('eaccelerator_gc')) { + throw new Horde_Cache_Exception('eAccelerator must be compiled with support for shared memory to use as caching backend.'); + } + + parent::__construct($params); + } + + /** + */ + public function get($key, $lifetime) + { + $key = $this->_params['prefix'] . $key; + $this->_setExpire($key, $lifetime); + return eaccelerator_get($key); + } + + /** + */ + public function set($key, $data, $lifetime) + { + $key = $this->_params['prefix'] . $key; + if (eaccelerator_put($key . '_expire', time(), $lifetime)) { + eaccelerator_put($key, $data, $lifetime); + } + } + + /** + */ + public function exists($key, $lifetime) + { + $key = $this->_params['prefix'] . $key; + $this->_setExpire($key, $lifetime); + return eaccelerator_get($key) !== false; + } + + /** + */ + public function expire($key) + { + $key = $this->_params['prefix'] . $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; + } + + $key = $this->_params['prefix'] . $key; + $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/Storage/File.php b/framework/Cache/lib/Horde/Cache/Storage/File.php new file mode 100644 index 000000000..84cae41db --- /dev/null +++ b/framework/Cache/lib/Horde/Cache/Storage/File.php @@ -0,0 +1,227 @@ + + * @author Chuck Hagenbuch + * @author Michael Slusarz + * @category Horde + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @package Cache + */ +class Horde_Cache_Storage_File extends Horde_Cache_Storage +{ + /* Location of the garbage collection data file. */ + const GC_FILE = 'horde_cache_gc'; + + /** + * The location of the temp directory. + * + * @var string + */ + protected $_dir; + + /** + * List of key to filename mappings. + * + * @var array + */ + protected $_file = array(); + + /** + * Constructor. + * + * @param array $params Optional parameters: + *
+     * 'dir' - (string) The base directory to store the cache files in.
+     *         DEFAULT: System default
+     * 'prefix' - (string) The filename prefix to use for the cache files.
+     *            DEFAULT: 'cache_'
+     * 'sub' - (integer) If non-zero, the number of subdirectories to create
+     *         to store the file (i.e. PHP's session.save_path).
+     *         DEFAULT: 0
+     * 
+ */ + public function __construct(array $params = array()) + { + $params = array_merge(array( + 'prefix' => 'cache_', + 'sub' => 0 + ), $params); + + $this->_dir = (isset($params['dir']) && @is_dir($params['dir'])) + ? $params['dir'] + : Horde_Util::getTempDir(); + + 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 . '/' . self::GC_FILE; + $excepts = array(); + + if (file_exists($filename)) { + $gc_file = file($filename, FILE_IGNORE_NEW_LINES); + reset($gc_file); + next($gc_file); + while (list(,$data) = each($gc_file)) { + $parts = explode("\t", $data, 2); + $excepts[$parts[0]] = $parts[1]; + } + } + + try { + $it = empty($this->_params['sub']) + ? new DirectoryIterator($this->_dir) + : new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->_dir), RecursiveIteratorIterator::CHILD_FIRST); + } catch (UnexpectedValueException $e) { + return; + } + + $c_time = time(); + + foreach ($it as $key => $val) { + if (!$val->isDir() && + (strpos($val->getFilename(), $this->_params['prefix']) === 0)) { + $d_time = isset($excepts[$key]) + ? $excepts[$key] + : $this->_params['lifetime']; + + if (!empty($d_time) && + (($c_time - $d_time) > filemtime($key))) { + @unlink($key); + unset($excepts[$key]); + } + } + } + + $fp = fopen($filename, 'w'); + foreach ($excepts as $key => $val) { + fwrite($fp, $key . "\t" . $val . "\n"); + } + fclose($fp); + } + + /** + */ + public function get($key, $lifetime) + { + if (!$this->exists($key, $lifetime)) { + /* Nothing cached, return failure. */ + return false; + } + + $filename = $this->_keyToFile($key); + $size = filesize($filename); + + return $size + ? @file_get_contents($filename) + : ''; + } + + /** + */ + public function set($key, $data, $lifetime) + { + $filename = $this->_keyToFile($key, true); + $tmp_file = Horde_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) { + throw new Horde_Cache_Exception('Cannot write to cache directory ' . $this->_dir); + } + + @rename($tmp_file, $filename); + + if (($lifetime != $this->_params['lifetime']) && + ($fp = @fopen($this->_dir . '/horde_cache_gc', 'a'))) { + // 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. + fwrite($fp, $filename . "\t" . (empty($lifetime) ? 0 : time() + $lifetime) . "\n"); + fclose($fp); + } + } + + /** + */ + public function exists($key, $lifetime) + { + $filename = $this->_keyToFile($key); + + /* Key exists in the cache */ + if (file_exists($filename)) { + /* 0 means no expire. + * Also, If the file was been created after the supplied value, + * the data is valid (fresh). */ + if (($lifetime == 0) || + (time() - $lifetime <= filemtime($filename))) { + return true; + } + + @unlink($filename); + } + + return false; + } + + /** + */ + public function expire($key) + { + $filename = $this->_keyToFile($key); + return @unlink($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 . '/'; + $md5 = hash('md5', $key); + $sub = ''; + + if (!empty($this->_params['sub'])) { + $max = min($this->_params['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->_params['prefix'] . $md5; + } + + return $this->_file[$key]; + } + +} diff --git a/framework/Cache/lib/Horde/Cache/Storage/Memcache.php b/framework/Cache/lib/Horde/Cache/Storage/Memcache.php new file mode 100644 index 000000000..484d41cb3 --- /dev/null +++ b/framework/Cache/lib/Horde/Cache/Storage/Memcache.php @@ -0,0 +1,136 @@ + + * Copyright 2007-2010 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 + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @package Cache + */ +class Horde_Cache_Storage_Memcache extends Horde_Cache_Storage implements Serializable +{ + /** + * Cache results of expire() calls (since we will get the entire object + * on an expire() call anyway). + * + * @var array + */ + protected $_expirecache = array(); + + /** + * Memcache object. + * + * @var Horde_Memcache + */ + protected $_memcache; + + /** + * Construct a new Horde_Cache_Memcache object. + * + * @param array $params Parameter array: + *
+     * 'memcache' - (Horde_Memcache) A Horde_Memcache object.
+     * 
+ * + * @throws InvalidArgumentException + */ + public function __construct($params = array()) + { + if (!isset($params['memcache'])) { + throw new InvalidArgumentException('Missing memcache object'); + } + + $this->_memcache = $params['memcache']; + unset($params['memcache']); + + parent::__construct($params); + } + + /** + */ + public function get($key, $lifetime) + { + $key = $this->_params['prefix'] . $key; + 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]; + } + + /** + */ + public function _set($key, $data, $lifetime) + { + $key = $this->_params['prefix'] . $key; + + if ($this->_memcache->set($key . '_e', time(), $lifetime) !== false) { + $this->_memcache->set($key, $data, $lifetime); + } + } + + /** + */ + public function exists($key, $lifetime) + { + $key = $this->_params['prefix'] . $key; + + return ($this->get($key, $lifetime) !== false); + } + + /** + */ + public function expire($key) + { + $key = $this->_params['prefix'] . $key; + unset($this->_expirecache[$key]); + $this->_memcache->delete($key . '_e'); + + return $this->_memcache->delete($key); + } + + /* Serializable methods. */ + + /** + */ + public function serialize() + { + return serialize($this->_memcache); + } + + /** + */ + public function unserialize($data) + { + $this->_memcache = unserialize($data); + } + +} diff --git a/framework/Cache/lib/Horde/Cache/Storage/Mock.php b/framework/Cache/lib/Horde/Cache/Storage/Mock.php new file mode 100644 index 000000000..735241f74 --- /dev/null +++ b/framework/Cache/lib/Horde/Cache/Storage/Mock.php @@ -0,0 +1,57 @@ + + * @category Horde + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Cache + * @package Cache + */ +class Horde_Cache_Storage_Mock extends Horde_Cache_Storage +{ + /** + * The storage location for this cache. + * + * @var array + */ + private $_cache = array(); + + /** + */ + public function get($key, $lifetime) + { + return isset($this->_cache[$key]) + ? $this->_cache[$key] + : false; + } + + /** + */ + public function set($key, $data, $lifetime) + { + $this->_cache[$key] = $data; + } + + /** + */ + public function exists($key, $lifetime = 1) + { + return isset($this->_cache[$key]); + } + + /** + */ + public function expire($key) + { + unset($this->_cache[$key]); + } + +} diff --git a/framework/Cache/lib/Horde/Cache/Storage/Null.php b/framework/Cache/lib/Horde/Cache/Storage/Null.php new file mode 100644 index 000000000..36b88ec09 --- /dev/null +++ b/framework/Cache/lib/Horde/Cache/Storage/Null.php @@ -0,0 +1,44 @@ + + * + * 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 + * @category Horde + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @package Cache + */ +class Horde_Cache_Storage_Null extends Horde_Cache_Storage +{ + /** + */ + public function get($key, $lifetime) + { + return false; + } + + /** + */ + public function set($key, $data, $lifetime) + { + } + + /** + */ + public function exists($key, $lifetime) + { + return false; + } + + /** + */ + public function expire($key) + { + return false; + } + +} diff --git a/framework/Cache/lib/Horde/Cache/Storage/Session.php b/framework/Cache/lib/Horde/Cache/Storage/Session.php new file mode 100644 index 000000000..3a5d75fcf --- /dev/null +++ b/framework/Cache/lib/Horde/Cache/Storage/Session.php @@ -0,0 +1,95 @@ + + * @category Horde + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @package Cache + */ +class Horde_Cache_Storage_Session extends Horde_Cache_Storage +{ + /** + * Pointer to the session entry. + * + * @var array + */ + protected $_sess; + + /** + * Constructor. + * + * @param array $params Optional parameters: + *
+     * 'session' - (string) Store session data in this entry.
+     *             DEFAULT: 'horde_cache_session'
+     * 
+ */ + public function __construct(array $params = array()) + { + $params = array_merge(array( + 'sess_name' => 'horde_cache_session' + ), $params); + + parent::__construct($params); + + if (!isset($_SESSION[$this->_params['sess_name']])) { + $_SESSION[$this->_params['sess_name']] = array(); + } + $this->_sess = &$_SESSION[$this->_params['sess_name']]; + } + + /** + */ + public function get($key, $lifetime) + { + return $this->exists($key, $lifetime) + ? $this->_sess[$key]['d'] + : false; + } + + /** + */ + public function set($key, $data, $lifetime) + { + $this->_sess[$key] = array( + 'd' => $data, + 'l' => $lifetime + ); + } + + /** + */ + public function exists($key, $lifetime) + { + if (isset($this->_sess[$key])) { + /* 0 means no expire. */ + if (($lifetime == 0) || + ((time() - $lifetime) <= $this->_sess[$key]['l'])) { + return true; + } + + unset($this->_sess[$key]); + } + + return false; + } + + /** + */ + public function expire($key) + { + if (isset($this->_sess[$key])) { + unset($this->_sess[$key]); + return true; + } + + return false; + } + +} diff --git a/framework/Cache/lib/Horde/Cache/Storage/Sql.php b/framework/Cache/lib/Horde/Cache/Storage/Sql.php new file mode 100644 index 000000000..c17e3d415 --- /dev/null +++ b/framework/Cache/lib/Horde/Cache/Storage/Sql.php @@ -0,0 +1,224 @@ + + * 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-2010 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 Ben Klang + * @author Michael Slusarz + * @category Horde + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @package Cache + */ +class Horde_Cache_Storage_Sql extends Horde_Cache_Storage +{ + /** + * Handle for the current database connection. + * + * @var Horde_Db_Adapter + */ + protected $_db; + + /** + * Constructor. + * + * @param array $params Parameters: + *
+     * 'db' - (Horde_Db_Adapter) [REQUIRED] The DB instance.
+     * 'table' - (string) The name of the cache table.
+     *           DEFAULT: 'horde_cache'
+     * 
+ * + * @throws Horde_Cache_Exception + */ + public function __construct($params = array()) + { + if (!isset($params['db'])) { + throw new Horde_Cache_Exception('Missing db parameter.'); + } + $this->_db = $params['db']; + unset($params['db']); + + $params = array_merge(array( + 'table' => 'horde_cache', + ), $params); + + 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; + } + + $query = 'DELETE FROM ' . $this->_params['table'] . + ' WHERE cache_expiration < ? AND cache_expiration <> 0'; + $values = array(time()); + + try { + $this->_db->delete($query, $values); + } catch (Horde_Db_Exception $e) {} + } + + /** + */ + public function get($key, $lifetime) + { + $okey = $key; + $key = hash('md5', $key); + + $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; + } + + try { + $result = $this->_db->selectValue($query, $values); + } catch (Horde_Db_Exception $e) { + return false; + } + + if (is_null($result)) { + /* No rows were found - cache miss */ + if ($this->_logger) { + $this->_logger->log(sprintf('Cache miss: %s (Id %s newer than %d)', $okey, $key, $maxage), 'DEBUG'); + } + return false; + } + + if ($this->_logger) { + $this->_logger->log(sprintf('Cache hit: %s (Id %s newer than %d)', $okey, $key, $maxage), 'DEBUG'); + } + + return $result; + } + + /** + */ + public function _set($key, $data, $lifetime) + { + $okey = $key; + $key = hash('md5', $key); + + $timestamp = time(); + + // 0 lifetime indicates the object should not be GC'd. + $expiration = ($lifetime === 0) + ? 0 + : ($lifetime + $timestamp); + + if ($this->_logger) { + $this->_logger->log(sprintf('Cache set: %s (Id %s set at %d expires at %d)', $okey, $key, $timestamp, $expiration), 'DEBUG'); + } + + // Remove any old cache data and prevent duplicate keys + $query = 'DELETE FROM ' . $this->_params['table'] . ' WHERE cache_id=?'; + $values = array($key); + try { + $this->_db->delete($query, $values); + } catch (Horde_Db_Exception $e) {} + + /* Build SQL query. */ + $query = 'INSERT INTO ' . $this->_params['table'] . + ' (cache_id, cache_timestamp, cache_expiration, cache_data)' . + ' VALUES (?, ?, ?, ?)'; + $values = array($key, $timestamp, $expiration, $data); + + try { + $this->_db->insert($query, $values); + } catch (Horde_Db_Exception $e) { + throw new Horde_Cache_Exception($e); + } + } + + /** + */ + public function exists($key, $lifetime) + { + $okey = $key; + $key = hash('md5', $key); + + /* 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; + } + + try { + $result = $this->_db->selectValue($query, $values); + } catch (Horde_Db_Exception $e) { + return false; + } + + $timestamp = time(); + if (empty($result)) { + if ($this->_logger) { + $this->_logger->log(sprintf('Cache exists() miss: %s (Id %s newer than %d)', $okey, $key, $timestamp), 'DEBUG'); + } + return false; + } + + if ($this->_logger) { + $this->_logger->log(sprintf('Cache exists() hit: %s (Id %s newer than %d)', $okey, $key, $timestamp), 'DEBUG'); + } + + return true; + } + + /** + */ + public function expire($key) + { + $key = hash('md5', $key); + + $query = 'DELETE FROM ' . $this->_params['table'] . + ' WHERE cache_id = ?'; + $values = array($key); + + try { + $this->_db->delete($query, $values); + } catch (Horde_Db_Exception $e) { + return false; + } + + return true; + } + +} diff --git a/framework/Cache/lib/Horde/Cache/Storage/Stack.php b/framework/Cache/lib/Horde/Cache/Storage/Stack.php new file mode 100644 index 000000000..6362a61c2 --- /dev/null +++ b/framework/Cache/lib/Horde/Cache/Storage/Stack.php @@ -0,0 +1,117 @@ + + * @category Horde + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @package Cache + */ +class Horde_Cache_Storage_Stack extends Horde_Cache_Storage +{ + /** + * Stack of cache drivers. + * + * @var string + */ + protected $_stack = array(); + + /** + * Constructor. + * + * @param array $params Parameters: + *
+     * 'stack' - (array) [REQUIRED] An array of storage instances to loop
+     *           through, in order of priority. The last entry is considered
+     *           the 'master' driver, for purposes of writes.
+     * 
+ * + * @throws InvalidArgumentException + */ + public function __construct(array $params = array()) + { + if (!isset($params['stack'])) { + throw new InvalidArgumentException('Missing stack parameter.'); + } + $this->_stack[] = $params['stack']; + unset($params['stack']); + + parent::__construct($params); + } + + /** + */ + public function get($key, $lifetime) + { + foreach ($this->_stack as $val) { + $result = $val->get($key, $lifetime); + if ($result !== false) { + return $result; + } + } + + return false; + } + + /** + */ + public function set($key, $data, $lifetime) + { + /* Do writes in *reverse* order - it is OK if a write to one of the + * non-master backends fails. */ + $master = true; + + foreach (array_reverse($this->_stack) as $val) { + $result = $val->set($key, $data, $lifetime); + if ($result === false) { + if ($master) { + return; + } + + /* Attempt to invalidate cache if write failed. */ + $val->expire($id); + } + $master = false; + } + } + + /** + */ + public function exists($key, $lifetime) + { + foreach ($this->_stack as $val) { + $result = $val->exists($key, $lifetime); + if ($result === true) { + break; + } + } + + return $result; + } + + /** + */ + public function expire($key) + { + /* Only report success from master. */ + $master = $success = true; + + foreach (array_reverse($this->_stack) as $val) { + $result = $val->expire($id); + if ($master && ($result === false)) { + $success = false; + } + $master = false; + } + + return $success; + } + +} diff --git a/framework/Cache/lib/Horde/Cache/Storage/Xcache.php b/framework/Cache/lib/Horde/Cache/Storage/Xcache.php new file mode 100644 index 000000000..0c71248b7 --- /dev/null +++ b/framework/Cache/lib/Horde/Cache/Storage/Xcache.php @@ -0,0 +1,81 @@ + + * + * 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 + * @category Horde + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @package Cache + */ +class Horde_Cache_Storage_Xcache extends Horde_Cache_Storage +{ + /** + */ + public function get($key, $lifetime) + { + $key = $this->_params['prefix'] . $key; + $this->_setExpire($key, $lifetime); + $result = xcache_get($key); + + return empty($result) + ? false + : $result; + } + + /** + */ + public function set($key, $data, $lifetime) + { + $key = $this->_params['prefix'] . $key; + if (xcache_set($key . '_expire', time(), $lifetime)) { + xcache_set($key, $data, $lifetime); + } + } + + /** + */ + public function exists($key, $lifetime) + { + $key = $this->_params['prefix'] . $key; + $this->_setExpire($key, $lifetime); + return xcache_isset($key); + } + + /** + */ + 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; + } + $key = $this->_params['prefix'] . $key; + $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/Xcache.php b/framework/Cache/lib/Horde/Cache/Xcache.php deleted file mode 100644 index 2366bfa81..000000000 --- a/framework/Cache/lib/Horde/Cache/Xcache.php +++ /dev/null @@ -1,94 +0,0 @@ - - * - * 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 - * @category Horde - * @package Cache - */ -class Horde_Cache_Xcache extends Horde_Cache -{ - /** - */ - protected function _get($key, $lifetime) - { - $key = $this->_params['prefix'] . $key; - $this->_setExpire($key, $lifetime); - $result = xcache_get($key); - - return empty($result) - ? false - : $result; - } - - /** - */ - protected function _set($key, $data, $lifetime) - { - $key = $this->_params['prefix'] . $key; - $lifetime = $this->_getLifetime($lifetime); - if (xcache_set($key . '_expire', time(), $lifetime)) { - 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; - } - $key = $this->_params['prefix'] . $key; - $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/package.xml b/framework/Cache/package.xml index 4cf25aa64..82c69399a 100644 --- a/framework/Cache/package.xml +++ b/framework/Cache/package.xml @@ -33,7 +33,8 @@ Performance Suite's content cache), memcached, or an SQL table. beta LGPL - * Add option to transparently compress cache data using lzf. + * Abstracted storage-specific code into 'Storage' drivers. + * Add option to transparently compress cache data using lzf. * Added Horde_Cache_Session::. * Horde_Cache::set() no longer returns a boolean result. * Added Horde_Cache_Exception::. @@ -47,17 +48,20 @@ Performance Suite's content cache), memcached, or an SQL table. - - + + + + + + + + + + + + - - - - - - - - + @@ -109,17 +113,18 @@ Performance Suite's content cache), memcached, or an SQL table. - - + + + + + + + + + + - - - - - - - - + diff --git a/framework/Core/lib/Horde/Core/Factory/Cache.php b/framework/Core/lib/Horde/Core/Factory/Cache.php index 28352df1e..b59baf4dd 100644 --- a/framework/Core/lib/Horde/Core/Factory/Cache.php +++ b/framework/Core/lib/Horde/Core/Factory/Cache.php @@ -46,51 +46,51 @@ class Horde_Core_Factory_Cache if (isset($GLOBALS['conf']['cache']['default_lifetime'])) { $params['lifetime'] = $GLOBALS['conf']['cache']['default_lifetime']; } + $params['logger'] = $injector->getInstance('Horde_Log_Logger'); - $logger = $injector->getInstance('Horde_Log_Logger'); - $params['logger'] = $logger; - - $base_params = $params; - - if (strcasecmp($driver, 'Memcache') === 0) { + $lc_driver = Horde_String::lower($driver); + switch ($lc_driver) { + case 'Memcache': $params['memcache'] = $injector->getInstance('Horde_Memcache'); - } elseif (strcasecmp($driver, 'Sql') === 0) { + break; + + case 'Sql': $params['db'] = $injector->getInstance('Horde_Db_Adapter'); + break; } + $storage = $this->_getStorage($driver, $params); + if (!empty($GLOBALS['conf']['cache']['use_memorycache']) && - ((strcasecmp($driver, 'Sql') === 0) || - (strcasecmp($driver, 'File') === 0))) { + in_array($lc_driver, array('File', 'Sql'))) { if (strcasecmp($GLOBALS['conf']['cache']['use_memorycache'], 'Memcache') === 0) { - $base_params['memcache'] = $injector->getInstance('Horde_Memcache'); + $params['memcache'] = $injector->getInstance('Horde_Memcache'); } - $class1 = $this->_driverToClassname($GLOBALS['conf']['cache']['use_memorycache']); - $class2 = $this->_driverToClassname($driver); - $params = array( + $cname = $this->_driverToClassname($GLOBALS['conf']['cache']['use_memorycache']); + $storage = new Horde_Cache_Storage_Stack(array( 'stack' => array( - new $class1($base_params), - new $class2($params), + $this->_getStorage($GLOBALS['conf']['cache']['use_memorycache'], $params), + $storage ) - ); - $driver = 'Stack'; + )); } - $classname = $this->_driverToClassname($driver); - return new $classname($params); + return new Horde_Cache($storage, $params); } /** */ - protected function _driverToClassname($driver) + protected function _getStorage($driver, $params) { $driver = ucfirst(basename($driver)); - $classname = 'Horde_Cache_' . $driver; + $classname = 'Horde_Cache_Storage_' . $driver; + if (!class_exists($classname)) { - $classname = 'Horde_Cache_Null'; + $classname = 'Horde_Cache_Storage_Null'; } - return $classname; + return new $classname($params); } }