diff --git a/Config/bootstrap.php b/Config/bootstrap.php index 4b9b566..434041f 100644 --- a/Config/bootstrap.php +++ b/Config/bootstrap.php @@ -3,4 +3,5 @@ // Bootstrap for CakePHP plugin loading // Loads required classes and all engines in the plugin App::uses('FileEngine', 'Cache/Engine'); +require_once(dirname(__FILE__) . '/../src/CacheEnginesHelper.php'); require_once(dirname(__FILE__) . '/../src/Engines.php'); diff --git a/Lib/Cache/Cache.php b/Lib/Cache/Cache.php deleted file mode 100644 index e8716a0..0000000 --- a/Lib/Cache/Cache.php +++ /dev/null @@ -1,638 +0,0 @@ - 'Apc', - * 'prefix' => 'my_app_' - * )); - * ``` - * - * This would configure an APC cache engine to the 'shared' alias. You could then read and write - * to that cache alias by using it for the `$config` parameter in the various Cache methods. In - * general all Cache operations are supported by all cache engines. However, Cache::increment() and - * Cache::decrement() are not supported by File caching. - * - * @package Cake.Cache - */ -class Cache { - -/** - * Cache configuration stack - * Keeps the permanent/default settings for each cache engine. - * These settings are used to reset the engines after temporary modification. - * - * @var array - */ - protected static $_config = array(); - -/** - * Group to Config mapping - * - * @var array - */ - protected static $_groups = array(); - -/** - * Whether to reset the settings with the next call to Cache::set(); - * - * @var array - */ - protected static $_reset = false; - -/** - * Engine instances keyed by configuration name. - * - * @var array - */ - protected static $_engines = array(); - -/** - * Set the cache configuration to use. config() can - * both create new configurations, return the settings for already configured - * configurations. - * - * To create a new configuration, or to modify an existing configuration permanently: - * - * `Cache::config('my_config', array('engine' => 'File', 'path' => TMP));` - * - * If you need to modify a configuration temporarily, use Cache::set(). - * To get the settings for a configuration: - * - * `Cache::config('default');` - * - * There are 5 built-in caching engines: - * - * - `FileEngine` - Uses simple files to store content. Poor performance, but good for - * storing large objects, or things that are not IO sensitive. - * - `ApcEngine` - Uses the APC object cache, one of the fastest caching engines. - * - `MemcacheEngine` - Uses the PECL::Memcache extension and Memcached for storage. - * Fast reads/writes, and benefits from memcache being distributed. - * - `XcacheEngine` - Uses the Xcache extension, an alternative to APC. - * - `WincacheEngine` - Uses Windows Cache Extension for PHP. Supports wincache 1.1.0 and higher. - * - * The following keys are used in core cache engines: - * - * - `duration` Specify how long items in this cache configuration last. - * - `groups` List of groups or 'tags' associated to every key stored in this config. - * handy for deleting a complete group from cache. - * - `prefix` Prefix appended to all entries. Good for when you need to share a keyspace - * with either another cache config or another application. - * - `probability` Probability of hitting a cache gc cleanup. Setting to 0 will disable - * cache::gc from ever being called automatically. - * - `servers' Used by memcache. Give the address of the memcached servers to use. - * - `compress` Used by memcache. Enables memcache's compressed format. - * - `serialize` Used by FileCache. Should cache objects be serialized first. - * - `path` Used by FileCache. Path to where cachefiles should be saved. - * - `lock` Used by FileCache. Should files be locked before writing to them? - * - `user` Used by Xcache. Username for XCache - * - `password` Used by Xcache/Redis. Password for XCache/Redis - * - * @param string $name Name of the configuration - * @param array $settings Optional associative array of settings passed to the engine - * @return array array(engine, settings) on success, false on failure - * @throws CacheException - * @see app/Config/core.php for configuration settings - */ - public static function config($name = null, $settings = array()) { - if (is_array($name)) { - $settings = $name; - } - - $current = array(); - if (isset(static::$_config[$name])) { - $current = static::$_config[$name]; - } - - if (!empty($settings)) { - static::$_config[$name] = $settings + $current; - } - - if (empty(static::$_config[$name]['engine'])) { - return false; - } - - if (!empty(static::$_config[$name]['groups'])) { - foreach (static::$_config[$name]['groups'] as $group) { - static::$_groups[$group][] = $name; - sort(static::$_groups[$group]); - static::$_groups[$group] = array_unique(static::$_groups[$group]); - } - } - - $engine = static::$_config[$name]['engine']; - - if (!isset(static::$_engines[$name])) { - static::_buildEngine($name); - $settings = static::$_config[$name] = static::settings($name); - } elseif ($settings = static::set(static::$_config[$name], null, $name)) { - static::$_config[$name] = $settings; - } - return compact('engine', 'settings'); - } - -/** - * Finds and builds the instance of the required engine class. - * - * @param string $name Name of the config array that needs an engine instance built - * @return bool - * @throws CacheException - */ - protected static function _buildEngine($name) { - $config = static::$_config[$name]; - - list($plugin, $class) = pluginSplit($config['engine'], true); - $cacheClass = $class . 'Engine'; - App::uses($cacheClass, $plugin . 'Cache/Engine'); - if (!class_exists($cacheClass)) { - throw new CacheException(__d('cake_dev', 'Cache engine %s is not available.', $name)); - } - $cacheClass = $class . 'Engine'; - if (!is_subclass_of($cacheClass, 'CacheEngine')) { - throw new CacheException(__d('cake_dev', 'Cache engines must use %s as a base class.', 'CacheEngine')); - } - static::$_engines[$name] = new $cacheClass(); - if (!static::$_engines[$name]->init($config)) { - $msg = __d( - 'cake_dev', - 'Cache engine "%s" is not properly configured. Ensure required extensions are installed, and credentials/permissions are correct', - $name - ); - throw new CacheException($msg); - } - if (static::$_engines[$name]->settings['probability'] && time() % static::$_engines[$name]->settings['probability'] === 0) { - static::$_engines[$name]->gc(); - } - return true; - } - -/** - * Returns an array containing the currently configured Cache settings. - * - * @return array Array of configured Cache config names. - */ - public static function configured() { - return array_keys(static::$_config); - } - -/** - * Drops a cache engine. Deletes the cache configuration information - * If the deleted configuration is the last configuration using a certain engine, - * the Engine instance is also unset. - * - * @param string $name A currently configured cache config you wish to remove. - * @return bool success of the removal, returns false when the config does not exist. - */ - public static function drop($name) { - if (!isset(static::$_config[$name])) { - return false; - } - unset(static::$_config[$name], static::$_engines[$name]); - return true; - } - -/** - * Temporarily change the settings on a cache config. The settings will persist for the next write - * operation (write, decrement, increment, clear). Any reads that are done before the write, will - * use the modified settings. If `$settings` is empty, the settings will be reset to the - * original configuration. - * - * Can be called with 2 or 3 parameters. To set multiple values at once. - * - * `Cache::set(array('duration' => '+30 minutes'), 'my_config');` - * - * Or to set one value. - * - * `Cache::set('duration', '+30 minutes', 'my_config');` - * - * To reset a config back to the originally configured values. - * - * `Cache::set(null, 'my_config');` - * - * @param string|array $settings Optional string for simple name-value pair or array - * @param string $value Optional for a simple name-value pair - * @param string $config The configuration name you are changing. Defaults to 'default' - * @return array Array of settings. - */ - public static function set($settings = array(), $value = null, $config = 'default') { - if (is_array($settings) && $value !== null) { - $config = $value; - } - if (!isset(static::$_config[$config]) || !isset(static::$_engines[$config])) { - return false; - } - if (!empty($settings)) { - static::$_reset = true; - } - - if (static::$_reset === true) { - if (empty($settings)) { - static::$_reset = false; - $settings = static::$_config[$config]; - } else { - if (is_string($settings) && $value !== null) { - $settings = array($settings => $value); - } - $settings += static::$_config[$config]; - if (isset($settings['duration']) && !is_numeric($settings['duration'])) { - $settings['duration'] = strtotime($settings['duration']) - time(); - } - } - static::$_engines[$config]->settings = $settings; - } - return static::settings($config); - } - -/** - * Garbage collection - * - * Permanently remove all expired and deleted data - * - * @param string $config [optional] The config name you wish to have garbage collected. Defaults to 'default' - * @param int $expires [optional] An expires timestamp. Defaults to NULL - * @return bool - */ - public static function gc($config = 'default', $expires = null) { - return static::$_engines[$config]->gc($expires); - } - -/** - * Write data for key into a cache engine. - * - * ### Usage: - * - * Writing to the active cache config: - * - * `Cache::write('cached_data', $data);` - * - * Writing to a specific cache config: - * - * `Cache::write('cached_data', $data, 'long_term');` - * - * @param string $key Identifier for the data - * @param mixed $value Data to be cached - anything except a resource - * @param string $config Optional string configuration name to write to. Defaults to 'default' - * @param string|array $parentKey Optional parent key that data is a dependent child of - * @return bool True if the data was successfully cached, false on failure - */ - public static function write($key, $value, $config = 'default', $parentKey = '') { - $settings = static::settings($config); - - if (empty($settings)) { - return false; - } - if (!static::isInitialized($config)) { - return false; - } - $key = static::$_engines[$config]->key($key); - - if (!$key || is_resource($value)) { - return false; - } - - $success = static::$_engines[$config]->write($settings['prefix'] . $key, $value, $settings['duration'], $parentKey); - static::set(null, $config); - if ($success === false && $value !== '') { - trigger_error( - __d('cake_dev', - "%s cache was unable to write '%s' to %s cache", - $config, - $key, - static::$_engines[$config]->settings['engine'] - ), - E_USER_WARNING - ); - } - return $success; - } - -/** - * Read a key from a cache config. - * - * ### Usage: - * - * Reading from the active cache configuration. - * - * `Cache::read('my_data');` - * - * Reading from a specific cache configuration. - * - * `Cache::read('my_data', 'long_term');` - * - * @param string $key Identifier for the data - * @param string $config optional name of the configuration to use. Defaults to 'default' - * @return mixed The cached data, or false if the data doesn't exist, has expired, or if there was an error fetching it - */ - public static function read($key, $config = 'default') { - $settings = static::settings($config); - - if (empty($settings)) { - return false; - } - if (!static::isInitialized($config)) { - return false; - } - $key = static::$_engines[$config]->key($key); - if (!$key) { - return false; - } - return static::$_engines[$config]->read($settings['prefix'] . $key); - } - -/** - * Increment a number under the key and return incremented value. - * - * @param string $key Identifier for the data - * @param int $offset How much to add - * @param string $config Optional string configuration name. Defaults to 'default' - * @return mixed new value, or false if the data doesn't exist, is not integer, - * or if there was an error fetching it. - */ - public static function increment($key, $offset = 1, $config = 'default') { - $settings = static::settings($config); - - if (empty($settings)) { - return false; - } - if (!static::isInitialized($config)) { - return false; - } - $key = static::$_engines[$config]->key($key); - - if (!$key || !is_int($offset) || $offset < 0) { - return false; - } - $success = static::$_engines[$config]->increment($settings['prefix'] . $key, $offset); - static::set(null, $config); - return $success; - } - -/** - * Decrement a number under the key and return decremented value. - * - * @param string $key Identifier for the data - * @param int $offset How much to subtract - * @param string $config Optional string configuration name. Defaults to 'default' - * @return mixed new value, or false if the data doesn't exist, is not integer, - * or if there was an error fetching it - */ - public static function decrement($key, $offset = 1, $config = 'default') { - $settings = static::settings($config); - - if (empty($settings)) { - return false; - } - if (!static::isInitialized($config)) { - return false; - } - $key = static::$_engines[$config]->key($key); - - if (!$key || !is_int($offset) || $offset < 0) { - return false; - } - $success = static::$_engines[$config]->decrement($settings['prefix'] . $key, $offset); - static::set(null, $config); - return $success; - } - -/** - * Delete a key from the cache. - * - * ### Usage: - * - * Deleting from the active cache configuration. - * - * `Cache::delete('my_data');` - * - * Deleting from a specific cache configuration. - * - * `Cache::delete('my_data', 'long_term');` - * - * @param string $key Identifier for the data - * @param string $config name of the configuration to use. Defaults to 'default' - * @return bool True if the value was successfully deleted, false if it didn't exist or couldn't be removed - */ - public static function delete($key, $config = 'default') { - $settings = static::settings($config); - - if (empty($settings)) { - return false; - } - if (!static::isInitialized($config)) { - return false; - } - $key = static::$_engines[$config]->key($key); - if (!$key) { - return false; - } - - $success = static::$_engines[$config]->delete($settings['prefix'] . $key); - static::set(null, $config); - return $success; - } - -/** - * Delete all keys from the cache. - * - * @param bool $check if true will check expiration, otherwise delete all - * @param string $config name of the configuration to use. Defaults to 'default' - * @return bool True if the cache was successfully cleared, false otherwise - */ - public static function clear($check = false, $config = 'default') { - if (!static::isInitialized($config)) { - return false; - } - $success = static::$_engines[$config]->clear($check); - static::set(null, $config); - return $success; - } - -/** - * Delete all keys from the cache belonging to the same group. - * - * @param string $group name of the group to be cleared - * @param string $config name of the configuration to use. Defaults to 'default' - * @return bool True if the cache group was successfully cleared, false otherwise - */ - public static function clearGroup($group, $config = 'default') { - if (!static::isInitialized($config)) { - return false; - } - $success = static::$_engines[$config]->clearGroup($group); - static::set(null, $config); - return $success; - } - -/** - * Check if Cache has initialized a working config for the given name. - * - * @param string $config name of the configuration to use. Defaults to 'default' - * @return bool Whether or not the config name has been initialized. - */ - public static function isInitialized($config = 'default') { - if (Configure::read('Cache.disable')) { - return false; - } - return isset(static::$_engines[$config]); - } - -/** - * Return the settings for the named cache engine. - * - * @param string $name Name of the configuration to get settings for. Defaults to 'default' - * @return array list of settings for this engine - * @see Cache::config() - */ - public static function settings($name = 'default') { - if (!empty(static::$_engines[$name])) { - return static::$_engines[$name]->settings(); - } - return array(); - } - -/** - * Retrieve group names to config mapping. - * - * ``` - * Cache::config('daily', array( - * 'duration' => '1 day', 'groups' => array('posts') - * )); - * Cache::config('weekly', array( - * 'duration' => '1 week', 'groups' => array('posts', 'archive') - * )); - * $configs = Cache::groupConfigs('posts'); - * ``` - * - * $config will equal to `array('posts' => array('daily', 'weekly'))` - * - * @param string $group group name or null to retrieve all group mappings - * @return array map of group and all configuration that has the same group - * @throws CacheException - */ - public static function groupConfigs($group = null) { - if ($group === null) { - return static::$_groups; - } - if (isset(static::$_groups[$group])) { - return array($group => static::$_groups[$group]); - } - throw new CacheException(__d('cake_dev', 'Invalid cache group %s', $group)); - } - -/** - * Provides the ability to easily do read-through caching. - * - * When called if the $key is not set in $config, the $callable function - * will be invoked. The results will then be stored into the cache config - * at key. - * - * Examples: - * - * Using a Closure to provide data, assume $this is a Model: - * - * ``` - * $model = $this; - * $results = Cache::remember('all_articles', function() use ($model) { - * return $model->find('all'); - * }); - * ``` - * - * @param string $key The cache key to read/store data at. - * @param callable $callable The callable that provides data in the case when - * the cache key is empty. Can be any callable type supported by your PHP. - * @param string $config The cache configuration to use for this operation. - * Defaults to default. - * @return mixed The results of the callable or unserialized results. - */ - public static function remember($key, $callable, $config = 'default') { - $existing = static::read($key, $config); - if ($existing !== false) { - return $existing; - } - $results = call_user_func($callable); - static::write($key, $results, $config); - return $results; - } - -/** - * Write data for key into a cache engine if it doesn't exist already. - * - * ### Usage: - * - * Writing to the active cache config: - * - * `Cache::add('cached_data', $data);` - * - * Writing to a specific cache config: - * - * `Cache::add('cached_data', $data, 'long_term');` - * - * @param string $key Identifier for the data. - * @param mixed $value Data to be cached - anything except a resource. - * @param string $config Optional string configuration name to write to. Defaults to 'default'. - * @return bool True if the data was successfully cached, false on failure. - * Or if the key existed already. - */ - public static function add($key, $value, $config = 'default') { - $settings = self::settings($config); - - if (empty($settings)) { - return false; - } - if (!self::isInitialized($config)) { - return false; - } - $key = self::$_engines[$config]->key($key); - - if (!$key || is_resource($value)) { - return false; - } - - $success = self::$_engines[$config]->add($settings['prefix'] . $key, $value, $settings['duration']); - self::set(null, $config); - return $success; - } - -/** - * Fetch the engine attached to a specific configuration name. - * - * @param string $config Optional string configuration name to get an engine for. Defaults to 'default'. - * @return null|CacheEngine Null if the engine has not been initialized or the engine. - */ - public static function engine($config = 'default') { - if (self::isInitialized($config)) { - return self::$_engines[$config]; - } - - return null; - } -} diff --git a/Lib/Cache/CacheEngine.php b/Lib/Cache/CacheEngine.php deleted file mode 100644 index d182fa0..0000000 --- a/Lib/Cache/CacheEngine.php +++ /dev/null @@ -1,195 +0,0 @@ -settings + array( - 'prefix' => 'cake_', - 'duration' => 3600, - 'probability' => 100, - 'groups' => array() - ); - $this->settings = $settings; - if (!empty($this->settings['groups'])) { - sort($this->settings['groups']); - $this->_groupPrefix = str_repeat('%s_', count($this->settings['groups'])); - } - if (!is_numeric($this->settings['duration'])) { - $this->settings['duration'] = strtotime($this->settings['duration']) - time(); - } - return true; - } - -/** - * Garbage collection - * - * Permanently remove all expired and deleted data - * - * @param int $expires [optional] An expires timestamp, invalidating all data before. - * @return void - */ - public function gc($expires = null) { - } - -/** - * Write value for a key into cache - * - * @param string $key Identifier for the data - * @param mixed $value Data to be cached - * @param int $duration How long to cache for. - * @param string|array $parentKey Optional parent key that data is a dependent child of - * @return bool True if the data was successfully cached, false on failure - */ - abstract public function write($key, $value, $duration, $parentKey = ''); - -/** - * Write value for a key into cache if it doesn't already exist - * - * @param string $key Identifier for the data - * @param mixed $value Data to be cached - * @param int $duration How long to cache for. - * @return bool True if the data was successfully cached, false on failure - */ - public function add($key, $value, $duration) { - } - -/** - * Read a key from the cache - * - * @param string $key Identifier for the data - * @return mixed The cached data, or false if the data doesn't exist, has expired, or if there was an error fetching it - */ - abstract public function read($key); - -/** - * Increment a number under the key and return incremented value - * - * @param string $key Identifier for the data - * @param int $offset How much to add - * @return New incremented value, false otherwise - */ - abstract public function increment($key, $offset = 1); - -/** - * Decrement a number under the key and return decremented value - * - * @param string $key Identifier for the data - * @param int $offset How much to subtract - * @return New incremented value, false otherwise - */ - abstract public function decrement($key, $offset = 1); - -/** - * Delete a key from the cache - * - * @param string $key Identifier for the data - * @return bool True if the value was successfully deleted, false if it didn't exist or couldn't be removed - */ - abstract public function delete($key); - -/** - * Delete all keys from the cache - * - * @param bool $check if true will check expiration, otherwise delete all - * @return bool True if the cache was successfully cleared, false otherwise - */ - abstract public function clear($check); - -/** - * Clears all values belonging to a group. Is up to the implementing engine - * to decide whether actually delete the keys or just simulate it to achieve - * the same result. - * - * @param string $group name of the group to be cleared - * @return bool - */ - public function clearGroup($group) { - return false; - } - -/** - * Does whatever initialization for each group is required - * and returns the `group value` for each of them, this is - * the token representing each group in the cache key - * - * @return array - */ - public function groups() { - return $this->settings['groups']; - } - -/** - * Cache Engine settings - * - * @return array settings - */ - public function settings() { - return $this->settings; - } - -/** - * Generates a safe key for use with cache engine storage engines. - * - * @param string $key the key passed over - * @return mixed string $key or false - */ - public function key($key) { - if (empty($key)) { - return false; - } - - $prefix = ''; - if (!empty($this->_groupPrefix)) { - $prefix = md5(implode('_', $this->groups())); - } - - $key = preg_replace('/[\s]+/', '_', strtolower(trim(str_replace(array(DS, '/', '.'), '_', strval($key))))); - return $prefix . $key; - } -} diff --git a/Lib/Cache/Engine/FileEngine.php b/Lib/Cache/Engine/FileEngine.php deleted file mode 100644 index 5771659..0000000 --- a/Lib/Cache/Engine/FileEngine.php +++ /dev/null @@ -1,462 +0,0 @@ - CACHE - * - prefix = string prefix for filename, default => cake_ - * - lock = enable file locking on write, default => true - * - serialize = serialize the data, default => true - * - * @var array - * @see CacheEngine::__defaults - */ - public $settings = array(); - -/** - * True unless FileEngine::__active(); fails - * - * @var bool - */ - protected $_init = true; - -/** - * Initialize the Cache Engine - * - * Called automatically by the cache frontend - * To reinitialize the settings call Cache::engine('EngineName', [optional] settings = array()); - * - * @param array $settings array of setting for the engine - * @return bool True if the engine has been successfully initialized, false if not - */ - public function init($settings = array()) { - $settings += array( - 'engine' => 'File', - 'path' => CACHE, - 'prefix' => 'cake_', - 'lock' => true, - 'serialize' => true, - 'isWindows' => false, - 'mask' => 0664 - ); - parent::init($settings); - - if (DS === '\\') { - $this->settings['isWindows'] = true; - } - if (substr($this->settings['path'], -1) !== DS) { - $this->settings['path'] .= DS; - } - if (!empty($this->_groupPrefix)) { - $this->_groupPrefix = str_replace('_', DS, $this->_groupPrefix); - } - return $this->_active(); - } - -/** - * Garbage collection. Permanently remove all expired and deleted data - * - * @param int $expires [optional] An expires timestamp, invalidating all data before. - * @return bool True if garbage collection was successful, false on failure - */ - public function gc($expires = null) { - return $this->clear(true); - } - -/** - * Write data for key into cache - * - * @param string $key Identifier for the data - * @param mixed $data Data to be cached - * @param int $duration How long to cache the data, in seconds - * @param string|array $parentKey Unused. - * @return bool True if the data was successfully cached, false on failure - */ - public function write($key, $data, $duration, $parentKey = '') { - if (!$this->_init) { - return false; - } - - if ($this->_setKey($key, true) === false) { - return false; - } - - $lineBreak = "\n"; - - if ($this->settings['isWindows']) { - $lineBreak = "\r\n"; - } - - if (!empty($this->settings['serialize'])) { - if ($this->settings['isWindows']) { - $data = str_replace('\\', '\\\\\\\\', serialize($data)); - } else { - $data = serialize($data); - } - } - - $expires = time() + $duration; - $contents = implode(array($expires, $lineBreak, $data, $lineBreak)); - - if ($this->settings['lock']) { - $this->_File->flock(LOCK_EX); - } - - $this->_File->rewind(); - $success = $this->_File->ftruncate(0) && $this->_File->fwrite($contents) && $this->_File->fflush(); - - if ($this->settings['lock']) { - $this->_File->flock(LOCK_UN); - } - - return $success; - } - -/** - * Read a key from the cache - * - * @param string $key Identifier for the data - * @return mixed The cached data, or false if the data doesn't exist, has expired, or if there was an error fetching it - */ - public function read($key) { - if (!$this->_init || $this->_setKey($key) === false) { - return false; - } - - if ($this->settings['lock']) { - $this->_File->flock(LOCK_SH); - } - - $this->_File->rewind(); - $time = time(); - $cachetime = (int)$this->_File->current(); - - if ($cachetime !== false && ($cachetime < $time || ($time + $this->settings['duration']) < $cachetime)) { - if ($this->settings['lock']) { - $this->_File->flock(LOCK_UN); - } - return false; - } - - $data = ''; - $this->_File->next(); - while ($this->_File->valid()) { - $data .= $this->_File->current(); - $this->_File->next(); - } - - if ($this->settings['lock']) { - $this->_File->flock(LOCK_UN); - } - - $data = trim($data); - - if ($data !== '' && !empty($this->settings['serialize'])) { - if ($this->settings['isWindows']) { - $data = str_replace('\\\\\\\\', '\\', $data); - } - $data = unserialize((string)$data); - } - return $data; - } - -/** - * Delete a key from the cache - * - * @param string $key Identifier for the data - * @return bool True if the value was successfully deleted, false if it didn't exist or couldn't be removed - */ - public function delete($key) { - if ($this->_setKey($key) === false || !$this->_init) { - return false; - } - $path = $this->_File->getRealPath(); - $this->_File = null; - - //@codingStandardsIgnoreStart - return @unlink($path); - //@codingStandardsIgnoreEnd - } - -/** - * Delete all values from the cache - * - * @param bool $check Optional - only delete expired cache items - * @return bool True if the cache was successfully cleared, false otherwise - */ - public function clear($check) { - if (!$this->_init) { - return false; - } - $this->_File = null; - - $threshold = $now = false; - if ($check) { - $now = time(); - $threshold = $now - $this->settings['duration']; - } - - $this->_clearDirectory($this->settings['path'], $now, $threshold); - - $directory = new RecursiveDirectoryIterator($this->settings['path']); - $contents = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::SELF_FIRST); - $cleared = array(); - foreach ($contents as $path) { - if ($path->isFile()) { - continue; - } - - $path = $path->getRealPath() . DS; - if (!in_array($path, $cleared)) { - $this->_clearDirectory($path, $now, $threshold); - $cleared[] = $path; - } - } - return true; - } - -/** - * Used to clear a directory of matching files. - * - * @param string $path The path to search. - * @param int $now The current timestamp - * @param int $threshold Any file not modified after this value will be deleted. - * @return void - */ - protected function _clearDirectory($path, $now, $threshold) { - $prefixLength = strlen($this->settings['prefix']); - - if (!is_dir($path)) { - return; - } - - $dir = dir($path); - if ($dir === false) { - return; - } - - while (($entry = $dir->read()) !== false) { - if (substr($entry, 0, $prefixLength) !== $this->settings['prefix']) { - continue; - } - - try { - $file = new SplFileObject($path . $entry, 'r'); - } catch (Exception $e) { - continue; - } - - if ($threshold) { - $mtime = $file->getMTime(); - - if ($mtime > $threshold) { - continue; - } - $expires = (int)$file->current(); - - if ($expires > $now) { - continue; - } - } - if ($file->isFile()) { - $filePath = $file->getRealPath(); - $file = null; - - //@codingStandardsIgnoreStart - @unlink($filePath); - //@codingStandardsIgnoreEnd - } - } - } - -/** - * Not implemented - * - * @param string $key The key to decrement - * @param int $offset The number to offset - * @return void - * @throws CacheException - */ - public function decrement($key, $offset = 1) { - throw new CacheException(__d('cake_dev', 'Files cannot be atomically decremented.')); - } - -/** - * Not implemented - * - * @param string $key The key to decrement - * @param int $offset The number to offset - * @return void - * @throws CacheException - */ - public function increment($key, $offset = 1) { - throw new CacheException(__d('cake_dev', 'Files cannot be atomically incremented.')); - } - -/** - * Sets the current cache key this class is managing, and creates a writable SplFileObject - * for the cache file the key is referring to. - * - * @param string $key The key - * @param bool $createKey Whether the key should be created if it doesn't exists, or not - * @return bool true if the cache key could be set, false otherwise - */ - protected function _setKey($key, $createKey = false) { - $groups = null; - if (!empty($this->_groupPrefix)) { - $groups = vsprintf($this->_groupPrefix, $this->groups()); - } - $dir = $this->settings['path'] . $groups; - - if (!is_dir($dir)) { - mkdir($dir, 0775, true); - } - $path = new SplFileInfo($dir . $key); - - if (!$createKey && !$path->isFile()) { - return false; - } - if ( - empty($this->_File) || - $this->_File->getBaseName() !== $key || - $this->_File->valid() === false - ) { - $exists = file_exists($path->getPathname()); - try { - $this->_File = $path->openFile('c+'); - } catch (Exception $e) { - trigger_error($e->getMessage(), E_USER_WARNING); - return false; - } - unset($path); - - if (!$exists && !chmod($this->_File->getPathname(), (int)$this->settings['mask'])) { - trigger_error(__d( - 'cake_dev', 'Could not apply permission mask "%s" on cache file "%s"', - array($this->_File->getPathname(), $this->settings['mask'])), E_USER_WARNING); - } - } - return true; - } - -/** - * Determine is cache directory is writable - * - * @return bool - */ - protected function _active() { - $dir = new SplFileInfo($this->settings['path']); - if (Configure::read('debug')) { - $path = $dir->getPathname(); - if (!is_dir($path)) { - mkdir($path, 0775, true); - } - } - if ($this->_init && !($dir->isDir() && $dir->isWritable())) { - $this->_init = false; - trigger_error(__d('cake_dev', '%s is not writable', $this->settings['path']), E_USER_WARNING); - return false; - } - return true; - } - -/** - * Generates a safe key for use with cache engine storage engines. - * - * @param string $key the key passed over - * @return mixed string $key or false - */ - public function key($key) { - if (empty($key)) { - return false; - } - - $key = Inflector::underscore(str_replace(array(DS, '/', '.', '<', '>', '?', ':', '|', '*', '"'), '_', strval($key))); - return $key; - } - -/** - * Recursively deletes all files under any directory named as $group - * - * @param string $group The group to clear. - * @return bool success - */ - public function clearGroup($group) { - $this->_File = null; - $directoryIterator = new RecursiveDirectoryIterator($this->settings['path']); - $contents = new RecursiveIteratorIterator($directoryIterator, RecursiveIteratorIterator::CHILD_FIRST); - foreach ($contents as $object) { - $containsGroup = strpos($object->getPathName(), DS . $group . DS) !== false; - $hasPrefix = true; - if (strlen($this->settings['prefix']) !== 0) { - $hasPrefix = strpos($object->getBaseName(), $this->settings['prefix']) === 0; - } - if ($object->isFile() && $containsGroup && $hasPrefix) { - $path = $object->getPathName(); - $object = null; - //@codingStandardsIgnoreStart - @unlink($path); - //@codingStandardsIgnoreEnd - } - } - return true; - } - -/** - * Write data for key into cache if it doesn't exist already. - * If it already exists, it fails and returns false. - * - * @param string $key Identifier for the data. - * @param mixed $value Data to be cached. - * @param int $duration How long to cache the data, in seconds. - * @return bool True if the data was successfully cached, false on failure. - */ - public function add($key, $value, $duration) { - $cachedValue = $this->read($key); - if ($cachedValue === false) { - return $this->write($key, $value, $duration); - } - return false; - } -} diff --git a/src/CacheEnginesHelper.php b/src/CacheEnginesHelper.php new file mode 100644 index 0000000..d6b9c0c --- /dev/null +++ b/src/CacheEnginesHelper.php @@ -0,0 +1,121 @@ + [ + * 'parent_cache_key_1' + * ], + * 'cache_key_2' => [ + * 'parent_cache_key_2' + * ] + * ] + * );` + * + * Writing to a specific cache config: + * + * `Cache::write('cached_data', $data, 'long_term');` + * + * @param string $key Identifier for the data + * @param mixed $value Data to be cached - anything except a resource + * @param string $config Optional string configuration name to write to + * Defaults to 'default' + * @param string|array $parentKey Parent key that data is a dependent child of + * @return bool True if the data was successfully cached, false on failure + */ + public static function writeWithParent( + $key, + $value, + $config = 'default', + $parentKey = '' + ) { + $settings = Cache::settings($config); + + if (empty($settings)) { + return false; + } + if (!Cache::isInitialized($config)) { + return false; + } + $key = Cache::engine($config)->key($key); + + if (!$key || is_resource($value)) { + return false; + } + + $success = false; + if (method_exists(Cache::engine($config), 'writeWithParent')) { + $success = Cache::engine($config)->writeWithParent( + $settings['prefix'] . $key, + $value, + $settings['duration'], + $parentKey + ); + } + Cache::set(null, $config); + if ($success === false && $value !== '') { + trigger_error( + __d( + 'cake_dev', + "%s cache was unable to write '%s' to %s cache", + $config, + $key, + Cache::$_engines[$config]->settings['engine'] + ), + E_USER_WARNING + ); + } + return $success; + } +} diff --git a/src/FallbackEngine.php b/src/FallbackEngine.php index 746b6e0..546eff3 100644 --- a/src/FallbackEngine.php +++ b/src/FallbackEngine.php @@ -61,16 +61,31 @@ public function init($settings = array()) } - public function write($key, $value, $duration, $parentKey = '') + public function write($key, $value, $duration) { try { - return Cache::write($key, $value, $this->activeCache, $parentKey); + return Cache::write($key, $value, $this->activeCache); } catch (Exception $e) { $this->fallback(); - return Cache::write($key, $value, $this->activeCache, $parentKey); + return Cache::write($key, $value, $this->activeCache); } } + /** + * Write data for key into a cache engine with one or more 'parent'. + * + * @param string $key Identifier for the data + * @param mixed $value Data to be cached + * @param integer $duration How long to cache the data, in seconds + * @param string|array $parentKey Parent key that data is a dependent child of + * @return bool True if the data was successfully cached, false on failure + * @throws Exception + */ + public function writeWithParent($key, $value, $duration, $parentKey = '') + { + return Cache::engine($this->activeCache)->writeWithParent($key, $value, $duration, $parentKey); + } + public function read($key) { try { diff --git a/src/FileTreeEngine.php b/src/FileTreeEngine.php index 782ddcc..eb09a9d 100644 --- a/src/FileTreeEngine.php +++ b/src/FileTreeEngine.php @@ -50,11 +50,10 @@ public function read($key) * @param string $key * @param mixed $data * @param int $duration - * @param string|array $parentKey Unused. * @return bool * @throws Exception */ - public function write($key, $data, $duration, $parentKey = '') + public function write($key, $data, $duration) { //combo keys will be of the form: prefix_[blah,blah]; prefix is prepended by internal Cake code @@ -89,6 +88,25 @@ public function write($key, $data, $duration, $parentKey = '') } + /** + * 'Parents' are not supported by the FileTreeEngine. + * This method performs same action as `write`. + * + * This method exists to gracefully degrade when using + * this engine as a fallback to the RedisTreeEngine. + * + * @param string $key + * @param mixed $data + * @param int $duration + * @param string|array $parentKey Unused. + * @return bool + * @throws Exception + */ + public function writeWithParent($key, $data, $duration, $parentKey = '') + { + return $this->write($key, $data, $duration); + } + public function delete($key) { @@ -152,4 +170,4 @@ public function key($key) return str_replace(array(DS, '/', '.', '<', '>', '?', ':', '|', ' ', '"'), '_', $key); } -} +} \ No newline at end of file diff --git a/src/RedisTreeEngine.php b/src/RedisTreeEngine.php index b03b404..8f2d36d 100644 --- a/src/RedisTreeEngine.php +++ b/src/RedisTreeEngine.php @@ -116,33 +116,32 @@ public function key($key) return $key; } - /** * Write data for key into cache. * * @param string $key Identifier for the data * @param mixed $value Data to be cached * @param integer $duration How long to cache the data, in seconds - * @param string|array $parentKey Optional parent key that data is a dependent child of * @return bool True if the data was successfully cached, false on failure * @throws Exception */ - public function write($key, $value, $duration, $parentKey = '') + public function write($key, $value, $duration) { + return $this->_write($key, $value, $duration); + } - // Cake's Redis cache engine sets a default prefix of null. We'll need to handle both - // a prefix configured by the user or left as null. - if (strpos($key, '[') !== false && substr($key, -1) == ']') { - $keys = $this->parseMultiKey($key); - - if (count($keys) != count($value)) { - throw new Exception('Num keys != num values.'); - } - $key_vals = array_combine($keys, $value); - - return $this->_mwrite($key_vals, $duration, $parentKey); - } - + /** + * Write data for key into a cache engine with one or more 'parent'. + * + * @param string $key Identifier for the data + * @param mixed $value Data to be cached + * @param integer $duration How long to cache the data, in seconds + * @param string|array $parentKey parent key that data is a dependent child of + * @return bool True if the data was successfully cached, false on failure + * @throws Exception + */ + public function writeWithParent($key, $value, $duration, $parentKey = '') + { return $this->_write($key, $value, $duration, $parentKey); } @@ -158,7 +157,7 @@ public function write($key, $value, $duration, $parentKey = '') * the respective index. * @return */ - private function _mwrite($key_value_array, $duration, $parentKey) + private function _mwrite($key_value_array, $duration, $parentKey = '') { foreach ($key_value_array as $key => &$value) { @@ -205,7 +204,7 @@ private function _mwrite($key_value_array, $duration, $parentKey) * @param string|array $parentKey Parent key that data is a dependent child of * @return */ - private function _write($key, $value, $duration, $parentKey) + private function _swrite($key, $value, $duration, $parentKey = '') { if (!is_int($value)) { @@ -228,6 +227,35 @@ private function _write($key, $value, $duration, $parentKey) return $this->redis->exec(); } + /** + * Internal write. + * Write data for key into a cache engine with or without parents. + * + * @param string $key Identifier for the data + * @param mixed $value Data to be cached + * @param integer $duration How long to cache the data, in seconds + * @param string|array $parentKey Optional parent key that data is a dependent child of + * @return bool True if the data was successfully cached, false on failure + * @throws Exception + */ + private function _write($key, $value, $duration, $parentKey = '') + { + // Cake's Redis cache engine sets a default prefix of null. We'll need to handle both + // a prefix configured by the user or left as null. + if (strpos($key, '[') !== false && substr($key, -1) == ']') { + $keys = $this->parseMultiKey($key); + + if (count($keys) != count($value)) { + throw new Exception('Num keys != num values.'); + } + $key_vals = array_combine($keys, $value); + + return $this->_mwrite($key_vals, $duration, $parentKey); + } + + return $this->_swrite($key, $value, $duration, $parentKey); + } + /** * Read a key from the cache * diff --git a/test/CacheEnginesHelper.php b/test/CacheEnginesHelper.php new file mode 100644 index 0000000..28a9c61 --- /dev/null +++ b/test/CacheEnginesHelper.php @@ -0,0 +1,61 @@ +getAdapter('Predis\Client', true); + CacheMock::config( + 'RedisTree', + [ + 'engine' => 'RedisTreeMock', + 'duration' => 4 + ] + ); + CacheMock::setEngine('RedisTree', $redisMock); + + $key = 'CacheEnginesHelper:testWriteWithParent:1'; + $parentKeys = [ + 'CacheEnginesHelper:TestParent:10', + 'CacheEnginesHelper:TestParent:20' + ]; + $value = date('Y-m-d h:m'); + CacheEnginesHelper::writeWithParent($key, $value, $this->cache, $parentKeys); + $this->assertEquals($value, CacheMock::read($key, $this->cache)); + } + + public function testWriteWithParentTriggerError() + { + // use FileEngine, which does not support writeWithParent + CacheMock::config( + 'File', + [ + 'engine' => 'File', + 'duration' => 4 + ] + ); + + $key = 'CacheEnginesHelper:testWriteWithParent:1'; + $parentKeys = [ + 'CacheEnginesHelper:TestParent:10', + 'CacheEnginesHelper:TestParent:20' + ]; + $value = date('Y-m-d h:m'); + try { + CacheEnginesHelper::writeWithParent( + $key, + $value, + 'File', + $parentKeys + ); + } catch (PHPUnit_Framework_Error $e) { + $this->assertTrue(true); + } + } +} diff --git a/test/RedisTreeEngineTest.php b/test/RedisTreeEngineTest.php index 3654554..7816024 100644 --- a/test/RedisTreeEngineTest.php +++ b/test/RedisTreeEngineTest.php @@ -208,11 +208,11 @@ public function testClear() } - public function testMultiWriteReadDeleteWithSharedParent() + public function testWriteWithParentReadDeleteWithSharedParent() { - $key1 = 'RedisTreeEngine:testMultiWriteReadDeleteWithSharedParent:1'; - $key2 = 'RedisTreeEngine:testMultiWriteReadDeleteWithSharedParent:2'; + $key1 = 'RedisTreeEngine:testWriteWithParentReadDeleteWithSharedParent:1'; + $key2 = 'RedisTreeEngine:testWriteWithParentReadDeleteWithSharedParent:2'; $multiKey = '[' . $key1 . ',' . $key2 . ']'; $parentKey = 'RedisTreeEngine:TestParent:-1'; @@ -224,7 +224,7 @@ public function testMultiWriteReadDeleteWithSharedParent() $value2 ); - CacheMock::write($multiKey, $values, $this->cache, $parentKey); + CacheEnginesHelper::writeWithParent($multiKey, $values, $this->cache, $parentKey); $multiVal = CacheMock::read($multiKey, $this->cache); @@ -241,11 +241,11 @@ public function testMultiWriteReadDeleteWithSharedParent() } - public function testMultiWriteReadDeleteWithSharedParents() + public function testWriteWithParentReadDeleteWithSharedParents() { - $key1 = 'RedisTreeEngine:testMultiWriteReadDeleteWithSharedParents:1'; - $key2 = 'RedisTreeEngine:testMultiWriteReadDeleteWithSharedParents:2'; + $key1 = 'RedisTreeEngine:testWriteWithParentReadDeleteWithSharedParents:1'; + $key2 = 'RedisTreeEngine:testWriteWithParentReadDeleteWithSharedParents:2'; $multiKey = '[' . $key1 . ',' . $key2 . ']'; $parentKeys = [ @@ -260,7 +260,7 @@ public function testMultiWriteReadDeleteWithSharedParents() $value2 ); - CacheMock::write($multiKey, $values, $this->cache, $parentKeys); + CacheEnginesHelper::writeWithParent($multiKey, $values, $this->cache, $parentKeys); $multiVal = CacheMock::read($multiKey, $this->cache); @@ -277,11 +277,11 @@ public function testMultiWriteReadDeleteWithSharedParents() } - public function testMultiWriteReadDeleteWithUnqiueParents() + public function testWriteWithParentReadDeleteWithUnqiueParents() { - $key1 = 'RedisTreeEngine:testMultiWriteReadDeleteWithSharedParents:1'; - $key2 = 'RedisTreeEngine:testMultiWriteReadDeleteWithSharedParents:2'; + $key1 = 'RedisTreeEngine:testWriteWithParentReadDeleteWithUnqiueParents:1'; + $key2 = 'RedisTreeEngine:testWriteWithParentReadDeleteWithUnqiueParents:2'; $multiKey = '[' . $key1 . ',' . $key2 . ']'; $parentKeys = [ @@ -302,7 +302,7 @@ public function testMultiWriteReadDeleteWithUnqiueParents() $value2 ); - CacheMock::write($multiKey, $values, $this->cache, $parentKeys); + CacheEnginesHelper::writeWithParent($multiKey, $values, $this->cache, $parentKeys); $multiVal = CacheMock::read($multiKey, $this->cache); @@ -319,16 +319,16 @@ public function testMultiWriteReadDeleteWithUnqiueParents() } - public function testWriteDeleteWithParent() + public function testWriteWithParentDeleteWithParent() { - $key1 = 'RedisTreeEngine:testWriteDeleteWithParent:1'; + $key1 = 'RedisTreeEngine:testWriteWithParentDeleteWithParent:1'; $parentKey = 'RedisTreeEngine:TestParent:30'; $value = date('Y-m-d h:m'); - CacheMock::write($key1, $value, $this->cache, $parentKey); + CacheEnginesHelper::writeWithParent($key1, $value, $this->cache, $parentKey); $this->assertEquals($value, CacheMock::read($key1, $this->cache)); - $key2 = 'RedisTreeEngine:testWriteDeleteWithParent:2'; + $key2 = 'RedisTreeEngine:testWriteWithParentDeleteWithParent:2'; $value2 = date('Y-m-d h:m'); CacheMock::write($key2, $value2, $this->cache); @@ -338,18 +338,18 @@ public function testWriteDeleteWithParent() } - public function testWriteDeleteWithParents() + public function testWriteWithParentDeleteWithParents() { - $key = 'RedisTreeEngine:testWriteDeleteWithParents:1'; + $key = 'RedisTreeEngine:testWriteWithParentDeleteWithParents:1'; $parentKeys = [ 'RedisTreeEngine:TestParent:40', 'RedisTreeEngine:TestParent:50' ]; $value = date('Y-m-d h:m'); - CacheMock::write($key, $value, $this->cache, $parentKeys); + CacheEnginesHelper::writeWithParent($key, $value, $this->cache, $parentKeys); $this->assertEquals($value, CacheMock::read($key, $this->cache)); - $key2 = 'RedisTreeEngine:testWriteDeleteWithParents:2'; + $key2 = 'RedisTreeEngine:testWriteWithParentDeleteWithParents:2'; $value2 = date('Y-m-d h:m'); CacheMock::write($key2, $value2, $this->cache); diff --git a/test/bootstrap.php b/test/bootstrap.php index ce50af6..b031ebb 100644 --- a/test/bootstrap.php +++ b/test/bootstrap.php @@ -11,11 +11,13 @@ require_once(dirname(__FILE__) . '/../vendor/cakephp/cakephp/lib/Cake/Error/exceptions.php'); require_once(dirname(__FILE__) . '/../vendor/cakephp/cakephp/lib/Cake/Utility/Hash.php'); require_once(dirname(__FILE__) . '/../vendor/cakephp/cakephp/lib/Cake/Core/Configure.php'); -require_once(dirname(__FILE__) . '/../Lib/Cache/Cache.php'); -require_once(dirname(__FILE__) . '/../Lib/Cache/CacheEngine.php'); -require_once(dirname(__FILE__) . '/../Lib/Cache/Engine/FileEngine.php'); +require_once(dirname(__FILE__) . '/../vendor/cakephp/cakephp/lib/Cake/Cache/Cache.php'); +require_once(dirname(__FILE__) . '/../vendor/cakephp/cakephp/lib/Cake/Cache/CacheEngine.php'); +require_once(dirname(__FILE__) . '/../vendor/cakephp/cakephp/lib/Cake/Cache/Engine/FileEngine.php'); + // Load all engines +require_once(dirname(__FILE__) . '/../src/CacheEnginesHelper.php'); require_once(dirname(__FILE__) . '/../src/Engines.php'); // Load mock classes for unit tests require_once(dirname(__FILE__) . '/RedisTreeMockEngine.php');