diff --git a/lib/cache/cache.class.php b/lib/cache/cache.class.php new file mode 100644 index 0000000000..7d17873b08 --- /dev/null +++ b/lib/cache/cache.class.php @@ -0,0 +1,53 @@ + + * @package cache + * @link http://www.zentao.net + */ + +helper::import(dirname(__FILE__) . DS . 'simple-cache' . DS . 'CacheInterface.php'); +helper::import(dirname(__FILE__) . DS . 'simple-cache' . DS . 'CacheException.php'); +helper::import(dirname(__FILE__) . DS . 'simple-cache' . DS . 'InvalidArgumentException.php'); +helper::import(dirname(__FILE__) . DS . 'driver' . DS . 'ApcuDriver.php'); +helper::import(dirname(__FILE__) . DS . 'driver' . DS . 'YacDriver.php'); + +use ZenTao\Cache\SimpleCache\InvalidArgumentException; + +class cache +{ + /** + * @var ZenTao\Cache\SimpleCache\CacheInterface + */ + protected $client; + + public function __construct($driver = 'Apcu', $namespace = '', $defaultLifetime = 0) + { + $driver = ucfirst(strtolower($driver)); + switch($driver) + { + case 'Apcu': + $className = 'ZenTao\Cache\Driver\ApcuDriver'; + break; + case 'Yac': + $className = 'ZenTao\Cache\Driver\YacDriver'; + break; + default: + throw new InvalidArgumentException("Driver {$driver} is not supported."); + } + + if(!extension_loaded($driver)) throw new InvalidArgumentException("Driver ext-{$driver} is not loaded."); + + $this->client = new $className($namespace, $defaultLifetime); + } + + public function __call($name, $arguments) + { + if(!method_exists($this->client, $name)) throw new InvalidArgumentException("Method {$name} does not exist."); + + return call_user_func_array(array($this->client, $name), $arguments); + } +} diff --git a/lib/cache/driver/ApcuDriver.php b/lib/cache/driver/ApcuDriver.php new file mode 100644 index 0000000000..7c2def717a --- /dev/null +++ b/lib/cache/driver/ApcuDriver.php @@ -0,0 +1,173 @@ + + * @package cache + * @link http://www.zentao.net + */ + +namespace ZenTao\Cache\Driver; + +use ZenTao\Cache\SimpleCache\CacheInterface; +use ZenTao\Cache\SimpleCache\InvalidArgumentException; + +class ApcuDriver implements CacheInterface +{ + /** + * @var string + */ + private $namespace; + + /** + * @var int + */ + private $defaultLifetime; + + public function __construct($namespace = '', $defaultLifetime = 0) + { + $this->namespace = $namespace; + $this->defaultLifetime = $defaultLifetime; + } + + public function get($key, $default = null) + { + $this->assertKeyName($key); + $key = $this->buildKeyName($key); + + $value = apcu_fetch($key, $success); + + return $success === false ? $default : $value; + } + + public function set($key, $value, $ttl = null) + { + $this->assertKeyName($key); + $key = $this->buildKeyName($key); + + $ttl = is_null($ttl) ? $this->defaultLifetime : $ttl; + + return apcu_store($key, $value, (int) $ttl); + } + + public function delete($key) + { + $this->assertKeyName($key); + $key = $this->buildKeyName($key); + + return apcu_delete($key); + } + + public function clear() + { + return apcu_clear_cache(); + } + + public function getMultiple($keys, $default = null) + { + $this->assertKeyNames($keys); + $keys = $this->buildKeyNames($keys); + + $result = apcu_fetch($keys); + + if(!is_null($default) && is_array($result) && count($keys) > count($result)) + { + $notFoundKeys = array_diff($keys, array_keys($result)); + $result = array_merge($result, array_fill_keys($notFoundKeys, $default)); + } + + $mappedResult = array(); + + foreach($result as $key => $value) + { + $key = preg_replace("/^$this->namespace/", '', $key); + + $mappedResult[$key] = $value; + } + + return $mappedResult; + } + + public function setMultiple($values, $ttl = null) + { + $this->assertKeyNames(array_keys($values)); + + $mappedByNamespaceValues = array(); + + foreach($values as $key => $value) + { + $mappedByNamespaceValues[$this->buildKeyName($key)] = $value; + } + + $ttl = is_null($ttl) ? $this->defaultLifetime : $ttl; + + $result = apcu_store($mappedByNamespaceValues, (int) $ttl); + + return $result === true ? true : (is_array($result) && count($result) == 0 ? true : false); + } + + public function deleteMultiple($keys) + { + $this->assertKeyNames($keys); + $keys = $this->buildKeyNames($keys); + + $result = apcu_delete($keys); + + return count($result) === count($keys) ? false : true; + } + + public function has($key) + { + $this->assertKeyName($key); + $key = $this->buildKeyName($key); + + return (bool) apcu_exists($key); + } + + /** + * @param string $key + * + * @return string + */ + private function buildKeyName($key) + { + return $this->namespace . $key; + } + + /** + * @param string[] $keys + * + * @return string[] + */ + private function buildKeyNames(array $keys) + { + return array_map(function($key) { + return $this->buildKeyName($key); + }, $keys); + + } + + /** + * @param mixed $key + * + * @throws InvalidArgumentException + */ + private function assertKeyName($key) + { + if(!is_scalar($key) || is_bool($key)) throw new InvalidArgumentException(); + } + + /** + * @param string[] $keys + * + * @throws InvalidArgumentException + */ + private function assertKeyNames(array $keys) + { + array_map(function ($value) { + $this->assertKeyName($value); + }, $keys); + } +} diff --git a/lib/cache/driver/YacDriver.php b/lib/cache/driver/YacDriver.php new file mode 100644 index 0000000000..739582c2fa --- /dev/null +++ b/lib/cache/driver/YacDriver.php @@ -0,0 +1,197 @@ + + * @package cache + * @link http://www.zentao.net + */ + +namespace ZenTao\Cache\Driver; + +use ZenTao\Cache\SimpleCache\CacheInterface; +use ZenTao\Cache\SimpleCache\InvalidArgumentException; + +class YacDriver implements CacheInterface +{ + /** + * @var string + */ + private $namespace; + + /** + * @var int + */ + private $defaultLifetime; + + /** + * yac client + * + * @var \Yac + */ + protected $yac; + + /** + * if your key is longer than this, maybe you can use md5 result as the key + */ + const KEY_MAX_LEN = 48; + + public function __construct($namespace = '', $defaultLifetime = 0) + { + $this->namespace = $namespace; + $this->defaultLifetime = $defaultLifetime; + $this->yac = new \Yac($namespace); + } + + public function get($key, $default = null) + { + $this->assertKeyName($key); + $key = $this->buildKeyName($key); + + return $this->yac->get($key) ?: $default; + } + + public function set($key, $value, $ttl = null) + { + $this->assertKeyName($key); + $key = $this->buildKeyName($key); + + $ttl = is_null($ttl) ? $this->defaultLifetime : $ttl; + + return $this->yac->set($key, $value, (int)$ttl); + } + + public function delete($key) + { + $this->assertKeyName($key); + $key = $this->buildKeyName($key); + + return $this->yac->delete($key); + } + + public function clear() + { + return $this->yac->flush(); + } + + public function getMultiple($keys, $default = null) + { + if(!is_array($keys)) { + return array(); + } + + $hashKeyMap = array(); + foreach($keys as $index => $key) + { + $this->assertKeyName($key); + if(strlen($key) > self::KEY_MAX_LEN) + { + $keys[$index] = $this->buildKeyName($key); + $hashKeyMap[$keys[$index]] = $key; + } + } + + $results = $this->yac->get($keys); + if($results !== false) + { + foreach($results as $key => $value) + { + if(isset($hashKeyMap[$key])) + { + $results[$hashKeyMap[$key]] = $value; + unset($results[$key]); + } + } + return $results; + } + + $results = array(); + foreach($keys as $key) + { + $results[$key] = $default; + } + return $results; + } + + public function setMultiple($values, $ttl = null) + { + if(!is_array($values)) return false; + + foreach($values as $key => $value) + { + if(strlen($key) > self::KEY_MAX_LEN) + { + $values[$this->buildKeyName($key)] = $value; + unset($values[$key]); + } + } + + $ttl = is_null($ttl) ? $this->defaultLifetime : $ttl; + + return $this->yac->set($values, $ttl); + } + + public function deleteMultiple($keys) + { + foreach($keys as $index => $key) + { + $keys[$index] = $this->buildKeyName($key); + } + return $this->yac->delete($keys); + } + + public function has($key) + { + return $this->get($key) !== null; + } + + /** + * @param string $key + * + * @return string + */ + private function buildKeyName($key) + { + if(strlen($key) > self::KEY_MAX_LEN) + { + $key = md5($key); + } + return $key; + } + + /** + * @param string[] $keys + * + * @return string[] + */ + private function buildKeyNames(array $keys) + { + return array_map(function ($key) { + return $this->buildKeyName($key); + }, $keys); + } + + /** + * @param mixed $key + * + * @throws InvalidArgumentException + */ + private function assertKeyName($key) + { + if(!is_scalar($key) || is_bool($key)) throw new InvalidArgumentException(); + } + + /** + * @param string[] $keys + * + * @throws InvalidArgumentException + */ + private function assertKeyNames(array $keys) + { + array_map(function ($value) { + $this->assertKeyName($value); + }, $keys); + } +} diff --git a/lib/cache/simple-cache/CacheException.php b/lib/cache/simple-cache/CacheException.php new file mode 100644 index 0000000000..330b06084b --- /dev/null +++ b/lib/cache/simple-cache/CacheException.php @@ -0,0 +1,7 @@ + $keys A list of keys that can be obtained in a single operation. + * @param mixed $default Default value to return for keys that do not exist. + * + * @return iterable A list of key => value pairs. Cache keys that do not exist or are stale will have $default as value. + * + * @throws InvalidArgumentException + * MUST be thrown if $keys is neither an array nor a Traversable, + * or if any of the $keys are not a legal value. + */ + public function getMultiple($keys, $default = null); + + /** + * Persists a set of key => value pairs in the cache, with an optional TTL. + * + * @param iterable $values A list of key => value pairs for a multiple-set operation. + * @param null|int|\DateInterval $ttl Optional. The TTL value of this item. If no value is sent and + * the driver supports TTL then the library may set a default value + * for it or let the driver take care of that. + * + * @return bool True on success and false on failure. + * + * @throws InvalidArgumentException + * MUST be thrown if $values is neither an array nor a Traversable, + * or if any of the $values are not a legal value. + */ + public function setMultiple($values, $ttl = null); + + /** + * Deletes multiple cache items in a single operation. + * + * @param iterable $keys A list of string-based keys to be deleted. + * + * @return bool True if the items were successfully removed. False if there was an error. + * + * @throws InvalidArgumentException + * MUST be thrown if $keys is neither an array nor a Traversable, + * or if any of the $keys are not a legal value. + */ + public function deleteMultiple($keys); + + /** + * Determines whether an item is present in the cache. + * + * NOTE: It is recommended that has() is only to be used for cache warming type purposes + * and not to be used within your live applications operations for get/set, as this method + * is subject to a race condition where your has() will return true and immediately after, + * another script can remove it making the state of your app out of date. + * + * @param string $key The cache item key. + * + * @return bool + * + * @throws InvalidArgumentException + * MUST be thrown if the $key string is not a legal value. + */ + public function has($key); +} diff --git a/lib/cache/simple-cache/InvalidArgumentException.php b/lib/cache/simple-cache/InvalidArgumentException.php new file mode 100644 index 0000000000..825bcdf380 --- /dev/null +++ b/lib/cache/simple-cache/InvalidArgumentException.php @@ -0,0 +1,7 @@ +