diff --git a/framework/caching/Cache.php b/framework/caching/Cache.php index 20631a7..a35785c 100644 --- a/framework/caching/Cache.php +++ b/framework/caching/Cache.php @@ -9,8 +9,7 @@ namespace yii\caching; -use yii\base\ApplicationComponent; -use yii\base\InvalidCallException; +use yii\base\Component; /** * Cache is the base class for cache classes supporting different cache storage implementation. @@ -21,13 +20,16 @@ use yii\base\InvalidCallException; * can also be specified when calling [[set()]]. If the data item expires or the dependency * changes at the time of calling [[get()]], the cache will return no data. * - * Derived classes should implement the following methods: + * A typical usage pattern of cache is like the following: * - * - [[getValue]]: retrieve the value with a key (if any) from cache - * - [[setValue]]: store the value with a key into cache - * - [[addValue]]: store the value only if the cache does not have this key before - * - [[deleteValue]]: delete the value with the specified key from cache - * - [[flushValues]]: delete all values from cache + * ~~~ + * $key = 'demo'; + * $data = $cache->get($key); + * if ($data === false) { + * // ...generate $data here... + * $cache->set($key, $data, $expire, $dependency); + * } + * ~~~ * * Because Cache implements the ArrayAccess interface, it can be used like an array. For example, * @@ -36,10 +38,19 @@ use yii\base\InvalidCallException; * echo $cache['foo']; * ~~~ * + * Derived classes should implement the following methods: + * + * - [[getValue()]]: retrieve the value with a key (if any) from cache + * - [[setValue()]]: store the value with a key into cache + * - [[addValue()]]: store the value only if the cache does not have this key before + * - [[deleteValue()]]: delete the value with the specified key from cache + * - [[flushValues()]]: delete all values from cache + * + * * @author Qiang Xue * @since 2.0 */ -abstract class Cache extends ApplicationComponent implements \ArrayAccess +abstract class Cache extends Component implements \ArrayAccess { /** * @var string a string prefixed to every cache key so that it is unique. Defaults to null, meaning using @@ -59,64 +70,45 @@ abstract class Cache extends ApplicationComponent implements \ArrayAccess */ public $serializer; - /** - * Initializes the application component. - * This method overrides the parent implementation by setting default cache key prefix. - */ - public function init() - { - parent::init(); - if ($this->keyPrefix === null) { - $this->keyPrefix = \Yii::$application->id; - } - } /** - * Generates a cache key from one or multiple parameters. - * The cache key generated is safe to be used to access cache via methods such as [[get()]], [[set()]]. - * For example: + * Builds a normalized cache key from one or multiple parameters. + * + * The generated key contains letters and digits only, and its length is no more than 32. + * + * If only one parameter is given and it is already a normalized key, then + * it will be returned back without change. Otherwise, a normalized key + * is generated by serializing all given parameters and applying MD5 hashing. + * + * The following example builds a cache key using three parameters: * * ~~~ * $key = $cache->buildKey($className, $method, $id); * ~~~ * - * @param string $id the - * @return string the cache key - * @throws InvalidCallException if the method receives no parameter + * @param string $key the first parameter + * @return string the generated cache key */ - public function generateKey($id) + public function buildKey($key) { - $n = func_num_args(); - if ($n === 1 && is_string($id) && ctype_alnum($id) && strlen($id) <= 32) { - return $this->keyPrefix . $id; - } elseif ($n < 1) { - throw new InvalidCallException(__METHOD__ . ' requires at least one parameter.'); + if (func_num_args() === 1 && ctype_alnum($key) && strlen($key) <= 32) { + return (string)$key; } else { $params = func_get_args(); - return $this->keyPrefix . md5(serialize($params)); + return md5(serialize($params)); } } /** - * Generates a normalized key from a given key. - * The normalized key is obtained by hashing the given key via MD5 and then prefixing it with [[keyPrefix]]. - * @param string $key a key identifying a value to be cached - * @return string a key generated from the provided key which ensures the uniqueness across applications - */ - protected function generateKey($key) - { - return $this->keyPrefix . md5($key); - } - - /** * Retrieves a value from cache with a specified key. - * @param string $id a key identifying the cached value + * @param string $key a key identifying the cached value * @return mixed the value stored in cache, false if the value is not in the cache, expired, * or the dependency associated with the cached data has changed. */ - public function get($id) + public function get($key) { - $value = $this->getValue($this->generateKey($id)); + $key = $this->keyPrefix . $this->buildKey($key); + $value = $this->getValue($key); if ($value === false || $this->serializer === false) { return $value; } elseif ($this->serializer === null) { @@ -124,7 +116,7 @@ abstract class Cache extends ApplicationComponent implements \ArrayAccess } else { $value = call_user_func($this->serializer[1], $value); } - if (is_array($value) && ($value[1] instanceof Dependency) || !$value[1]->getHasChanged()) { + if (is_array($value) && !($value[1] instanceof Dependency && $value[1]->getHasChanged())) { return $value[0]; } else { return false; @@ -136,30 +128,30 @@ abstract class Cache extends ApplicationComponent implements \ArrayAccess * Some caches (such as memcache, apc) allow retrieving multiple cached values at the same time, * which may improve the performance. In case a cache does not support this feature natively, * this method will try to simulate it. - * @param array $ids list of keys identifying the cached values + * @param array $keys list of keys identifying the cached values * @return array list of cached values corresponding to the specified keys. The array * is returned in terms of (key,value) pairs. * If a value is not cached or expired, the corresponding array value will be false. */ - public function mget($ids) + public function mget($keys) { - $uids = array(); - foreach ($ids as $id) { - $uids[$id] = $this->generateKey($id); + $keyMap = array(); + foreach ($keys as $key) { + $keyMap[$key] = $this->keyPrefix . $this->buildKey($key); } - $values = $this->getValues($uids); + $values = $this->getValues(array_values($keyMap)); $results = array(); - if ($this->serializer === false) { - foreach ($uids as $id => $uid) { - $results[$id] = isset($values[$uid]) ? $values[$uid] : false; - } - } else { - foreach ($uids as $id => $uid) { - $results[$id] = false; - if (isset($values[$uid])) { - $value = $this->serializer === null ? unserialize($values[$uid]) : call_user_func($this->serializer[1], $values[$uid]); - if (is_array($value) && (!($value[1] instanceof Dependency) || !$value[1]->getHasChanged())) { - $results[$id] = $value[0]; + foreach ($keyMap as $key => $newKey) { + $results[$key] = false; + if (isset($values[$newKey])) { + if ($this->serializer === false) { + $results[$key] = $values[$newKey]; + } else { + $value = $this->serializer === null ? unserialize($values[$newKey]) + : call_user_func($this->serializer[1], $values[$newKey]); + + if (is_array($value) && !($value[1] instanceof Dependency && $value[1]->getHasChanged())) { + $results[$key] = $value[0]; } } } @@ -172,14 +164,15 @@ abstract class Cache extends ApplicationComponent implements \ArrayAccess * If the cache already contains such a key, the existing value and * expiration time will be replaced with the new ones, respectively. * - * @param string $id the key identifying the value to be cached + * @param string $key the key identifying the value to be cached * @param mixed $value the value to be cached * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire. * @param Dependency $dependency dependency of the cached item. If the dependency changes, * the corresponding value in the cache will be invalidated when it is fetched via [[get()]]. + * This parameter is ignored if [[serializer]] is false. * @return boolean whether the value is successfully stored into cache */ - public function set($id, $value, $expire = 0, $dependency = null) + public function set($key, $value, $expire = 0, $dependency = null) { if ($dependency !== null && $this->serializer !== false) { $dependency->evaluateDependency(); @@ -189,20 +182,22 @@ abstract class Cache extends ApplicationComponent implements \ArrayAccess } elseif ($this->serializer !== false) { $value = call_user_func($this->serializer[0], array($value, $dependency)); } - return $this->setValue($this->generateKey($id), $value, $expire); + $key = $this->keyPrefix . $this->buildKey($key); + return $this->setValue($key, $value, $expire); } /** * Stores a value identified by a key into cache if the cache does not contain this key. * Nothing will be done if the cache already contains the key. - * @param string $id the key identifying the value to be cached + * @param string $key the key identifying the value to be cached * @param mixed $value the value to be cached * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire. * @param Dependency $dependency dependency of the cached item. If the dependency changes, * the corresponding value in the cache will be invalidated when it is fetched via [[get()]]. + * This parameter is ignored if [[serializer]] is false. * @return boolean whether the value is successfully stored into cache */ - public function add($id, $value, $expire = 0, $dependency = null) + public function add($key, $value, $expire = 0, $dependency = null) { if ($dependency !== null && $this->serializer !== false) { $dependency->evaluateDependency(); @@ -212,17 +207,19 @@ abstract class Cache extends ApplicationComponent implements \ArrayAccess } elseif ($this->serializer !== false) { $value = call_user_func($this->serializer[0], array($value, $dependency)); } - return $this->addValue($this->generateKey($id), $value, $expire); + $key = $this->keyPrefix . $this->buildKey($key); + return $this->addValue($key, $value, $expire); } /** * Deletes a value with the specified key from cache - * @param string $id the key of the value to be deleted + * @param string $key the key of the value to be deleted * @return boolean if no error happens during deletion */ - public function delete($id) + public function delete($key) { - return $this->deleteValue($this->generateKey($id)); + $key = $this->keyPrefix . $this->buildKey($key); + return $this->deleteValue($key); } /** @@ -301,23 +298,23 @@ abstract class Cache extends ApplicationComponent implements \ArrayAccess /** * Returns whether there is a cache entry with a specified key. * This method is required by the interface ArrayAccess. - * @param string $id a key identifying the cached value + * @param string $key a key identifying the cached value * @return boolean */ - public function offsetExists($id) + public function offsetExists($key) { - return $this->get($id) !== false; + return $this->get($key) !== false; } /** * Retrieves the value from cache with a specified key. * This method is required by the interface ArrayAccess. - * @param string $id a key identifying the cached value + * @param string $key a key identifying the cached value * @return mixed the value stored in cache, false if the value is not in the cache or expired. */ - public function offsetGet($id) + public function offsetGet($key) { - return $this->get($id); + return $this->get($key); } /** @@ -325,21 +322,21 @@ abstract class Cache extends ApplicationComponent implements \ArrayAccess * If the cache already contains such a key, the existing value will be * replaced with the new ones. To add expiration and dependencies, use the [[set()]] method. * This method is required by the interface ArrayAccess. - * @param string $id the key identifying the value to be cached + * @param string $key the key identifying the value to be cached * @param mixed $value the value to be cached */ - public function offsetSet($id, $value) + public function offsetSet($key, $value) { - $this->set($id, $value); + $this->set($key, $value); } /** * Deletes the value with the specified key from cache * This method is required by the interface ArrayAccess. - * @param string $id the key of the value to be deleted + * @param string $key the key of the value to be deleted */ - public function offsetUnset($id) + public function offsetUnset($key) { - $this->delete($id); + $this->delete($key); } } \ No newline at end of file diff --git a/framework/caching/ChainedDependency.php b/framework/caching/ChainedDependency.php index e885308..570715d 100644 --- a/framework/caching/ChainedDependency.php +++ b/framework/caching/ChainedDependency.php @@ -81,9 +81,9 @@ class ChainedDependency extends Dependency */ public function getHasChanged() { - foreach ($this->dependencies as $dependency) { + foreach ($this->dependencies as $i => $dependency) { if (!$dependency instanceof Dependency) { - $dependency = \Yii::createObject($dependency); + $this->dependencies[$i] = $dependency = \Yii::createObject($dependency); } if ($this->dependOnAll && $dependency->getHasChanged()) { return true; diff --git a/framework/caching/DbCache.php b/framework/caching/DbCache.php index 23d8b1f..4b84bfd 100644 --- a/framework/caching/DbCache.php +++ b/framework/caching/DbCache.php @@ -9,7 +9,7 @@ namespace yii\caching; -use yii\base\Exception; +use yii\base\InvalidConfigException; use yii\db\Connection; use yii\db\Query; @@ -21,11 +21,11 @@ use yii\db\Query; * * ~~~ * CREATE TABLE tbl_cache ( - * id char(128) NOT NULL, - * expire int(11) DEFAULT NULL, - * data LONGBLOB, - * PRIMARY KEY (id), - * KEY expire (expire) + * id char(128) NOT NULL, + * expire int(11) DEFAULT NULL, + * data LONGBLOB, + * PRIMARY KEY (id), + * KEY expire (expire) * ); * ~~~ * @@ -57,7 +57,7 @@ class DbCache extends Cache public $cacheTableName = 'tbl_cache'; /** * @var integer the probability (parts per million) that garbage collection (GC) should be performed - * when storing a piece of data in the cache. Defaults to 100, meaning 0.01% chance. + * when storing a piece of data in the cache. Defaults to 10, meaning 0.001% chance. * This number should be between 0 and 1000000. A value 0 meaning no GC will be performed at all. **/ public $gcProbability = 100; @@ -69,7 +69,7 @@ class DbCache extends Cache /** * Returns the DB connection instance used for caching purpose. * @return Connection the DB connection instance - * @throws Exception if [[connectionID]] does not point to a valid application component. + * @throws InvalidConfigException if [[connectionID]] does not point to a valid application component. */ public function getDb() { @@ -78,7 +78,7 @@ class DbCache extends Cache if ($db instanceof Connection) { $this->_db = $db; } else { - throw new Exception("DbCache.connectionID must refer to the ID of a DB connection application component."); + throw new InvalidConfigException("DbCache::connectionID must refer to the ID of a DB application component."); } } return $this->_db; @@ -163,13 +163,13 @@ class DbCache extends Cache */ protected function setValue($key, $value, $expire) { - $query = new Query; - $command = $query->update($this->cacheTableName, array( + $command = $this->getDb()->createCommand(); + $command->update($this->cacheTableName, array( 'expire' => $expire > 0 ? $expire + time() : 0, 'data' => array($value, \PDO::PARAM_LOB), ), array( 'id' => $key, - ))->createCommand($this->getDb()); + ));; if ($command->execute()) { $this->gc(); @@ -198,16 +198,16 @@ class DbCache extends Cache $expire = 0; } - $query = new Query; - $command = $query->insert($this->cacheTableName, array( + $command = $this->getDb()->createCommand(); + $command->insert($this->cacheTableName, array( 'id' => $key, 'expire' => $expire, 'data' => array($value, \PDO::PARAM_LOB), - ))->createCommand($this->getDb()); + )); try { $command->execute(); return true; - } catch (Exception $e) { + } catch (\Exception $e) { return false; } } @@ -220,10 +220,8 @@ class DbCache extends Cache */ protected function deleteValue($key) { - $query = new Query; - $query->delete($this->cacheTableName, array('id' => $key)) - ->createCommand($this->getDb()) - ->execute(); + $command = $this->getDb()->createCommand(); + $command->delete($this->cacheTableName, array('id' => $key))->execute(); return true; } @@ -235,10 +233,8 @@ class DbCache extends Cache public function gc($force = false) { if ($force || mt_rand(0, 1000000) < $this->gcProbability) { - $query = new Query; - $query->delete($this->cacheTableName, 'expire > 0 AND expire < ' . time()) - ->createCommand($this->getDb()) - ->execute(); + $command = $this->getDb()->createCommand(); + $command->delete($this->cacheTableName, 'expire > 0 AND expire < ' . time())->execute(); } } @@ -249,10 +245,8 @@ class DbCache extends Cache */ protected function flushValues() { - $query = new Query; - $query->delete($this->cacheTableName) - ->createCommand($this->getDb()) - ->execute(); + $command = $this->getDb()->createCommand(); + $command->delete($this->cacheTableName)->execute(); return true; } } diff --git a/framework/caching/DbDependency.php b/framework/caching/DbDependency.php index dcc7e37..7ffdb4e 100644 --- a/framework/caching/DbDependency.php +++ b/framework/caching/DbDependency.php @@ -9,7 +9,7 @@ namespace yii\caching; -use yii\base\Exception; +use yii\base\InvalidConfigException; use yii\db\Connection; use yii\db\Query; @@ -68,6 +68,9 @@ class DbDependency extends Dependency protected function generateDependencyData() { $db = $this->getDb(); + /** + * @var \yii\db\Command $command + */ $command = $this->query->createCommand($db); if ($db->enableQueryCache) { // temporarily disable and re-enable query caching @@ -83,7 +86,7 @@ class DbDependency extends Dependency /** * Returns the DB connection instance used for caching purpose. * @return Connection the DB connection instance - * @throws Exception if [[connectionID]] does not point to a valid application component. + * @throws InvalidConfigException if [[connectionID]] does not point to a valid application component. */ public function getDb() { @@ -92,7 +95,7 @@ class DbDependency extends Dependency if ($db instanceof Connection) { $this->_db = $db; } else { - throw new Exception("DbDependency.connectionID must refer to the ID of a DB connection application component."); + throw new InvalidConfigException("DbCache::connectionID must refer to the ID of a DB application component."); } } return $this->_db; diff --git a/framework/caching/FileCache.php b/framework/caching/FileCache.php index 330ef86..f97861f 100644 --- a/framework/caching/FileCache.php +++ b/framework/caching/FileCache.php @@ -9,7 +9,7 @@ namespace yii\caching; -use yii\base\Exception; +use yii\base\InvalidConfigException; /** * FileCache implements a cache component using files. @@ -42,10 +42,10 @@ class FileCache extends Cache public $directoryLevel = 1; /** * @var integer the probability (parts per million) that garbage collection (GC) should be performed - * when storing a piece of data in the cache. Defaults to 100, meaning 0.01% chance. - * This number should be between 0 and 1000000. A value 0 meaning no GC will be performed at all. + * when storing a piece of data in the cache. Defaults to 10, meaning 0.001% chance. + * This number should be between 0 and 1000000. A value 0 means no GC will be performed at all. **/ - public $gcProbability = 100; + public $gcProbability = 10; /** * Initializes this component by ensuring the existence of the cache path. @@ -55,7 +55,7 @@ class FileCache extends Cache parent::init(); $this->cachePath = \Yii::getAlias($this->cachePath); if ($this->cachePath === false) { - throw new Exception('FileCache.cachePath must be a valid path alias.'); + throw new InvalidConfigException('FileCache.cachePath must be a valid path alias.'); } if (!is_dir($this->cachePath)) { mkdir($this->cachePath, 0777, true); diff --git a/framework/caching/MemCache.php b/framework/caching/MemCache.php index 1c153d9..288c3ee 100644 --- a/framework/caching/MemCache.php +++ b/framework/caching/MemCache.php @@ -10,6 +10,7 @@ namespace yii\caching; use yii\base\Exception; +use yii\base\InvalidConfigException; /** * MemCache implements a cache application component based on [memcache](http://pecl.php.net/package/memcache) @@ -109,12 +110,12 @@ class MemCache extends Cache * @return \Memcache|\Memcached the memcache (or memcached) object used by this cache component. * @throws Exception if memcache or memcached extension is not loaded */ - public function getMemCache() + public function getMemcache() { if ($this->_cache === null) { $extension = $this->useMemcached ? 'memcached' : 'memcache'; if (!extension_loaded($extension)) { - throw new Exception("MemCache requires PHP $extension extension to be loaded."); + throw new InvalidConfigException("MemCache requires PHP $extension extension to be loaded."); } $this->_cache = $this->useMemcached ? new \Memcached : new \Memcache; } diff --git a/framework/caching/ZendDataCache.php b/framework/caching/ZendDataCache.php index b9941e5..8716a36 100644 --- a/framework/caching/ZendDataCache.php +++ b/framework/caching/ZendDataCache.php @@ -31,7 +31,7 @@ class ZendDataCache extends Cache protected function getValue($key) { $result = zend_shm_cache_fetch($key); - return $result !== NULL ? $result : false; + return $result === null ? false : $result; } /**