diff --git a/framework/caching/DbCache.php b/framework/caching/DbCache.php
index 3bd1fcf..ca4198e 100644
--- a/framework/caching/DbCache.php
+++ b/framework/caching/DbCache.php
@@ -16,16 +16,28 @@ use yii\db\dao\Query;
/**
* DbCache implements a cache application component by storing cached data in a database.
*
- * DbCache stores cache data in a DB table named {@link cacheTableName}.
- * If the table does not exist, it will be automatically created.
- * By setting {@link autoCreateCacheTable} to false, you can also manually create the DB table.
+ * DbCache stores cache data in a DB table whose name is specified via [[cacheTableName]].
+ * For MySQL database, the table should be created beforehand as follows :
*
- * DbCache relies on {@link http://www.php.net/manual/en/ref.pdo.php PDO} to access database.
- * By default, it will use a SQLite3 database under the application runtime directory.
- * You can also specify {@link connectionID} so that it makes use of
- * a DB application component to access database.
+ * ~~~
+ * CREATE TABLE tbl_cache (
+ * id char(128) NOT NULL,
+ * expire int(11) DEFAULT NULL,
+ * data LONGBLOB,
+ * PRIMARY KEY (id),
+ * KEY expire (expire)
+ * );
+ * ~~~
*
- * See {@link CCache} manual for common cache operations that are supported by DbCache.
+ * You should replace `LONGBLOB` as follows if you are using a different DBMS:
+ *
+ * - PostgreSQL: `BYTEA`
+ * - SQLite, SQL server, Oracle: `BLOB`
+ *
+ * DbCache connects to the database via the DB connection specified in [[connectionID]]
+ * which must refer to a valid DB application component.
+ *
+ * Please refer to [[Cache]] for common cache operations that are supported by DbCache.
*
* @property Connection $dbConnection The DB connection instance.
*
@@ -35,30 +47,16 @@ use yii\db\dao\Query;
class DbCache extends Cache
{
/**
- * @var string the ID of the {@link Connection} application component. If not set,
- * a SQLite3 database will be automatically created and used. The SQLite database file
- * is protected/runtime/cache-YiiVersion.db.
+ * @var string the ID of the [[Connection|DB connection]] application component.
+ * Defaults to 'db'.
*/
- public $connectionID;
+ public $connectionID = 'db';
/**
- * @var string name of the DB table to store cache content. Defaults to 'YiiCache'.
- * Note, if {@link autoCreateCacheTable} is false and you want to create the DB table
- * manually by yourself, you need to make sure the DB table is of the following structure:
- *
- * (id CHAR(128) PRIMARY KEY, expire INTEGER, value BLOB)
- *
- * Note, some DBMS might not support BLOB type. In this case, replace 'BLOB' with a suitable
- * binary data type (e.g. LONGBLOB in MySQL, BYTEA in PostgreSQL.)
- * @see autoCreateCacheTable
+ * @var string name of the DB table to store cache content. Defaults to 'tbl_cache'.
+ * The table must be created before using this cache component.
*/
public $cacheTableName = 'tbl_cache';
/**
- * @var boolean whether the cache DB table should be created automatically if it does not exist. Defaults to true.
- * If you already have the table created, it is recommended you set this property to be false to improve performance.
- * @see cacheTableName
- */
- public $autoCreateCacheTable = true;
- /**
* @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.
@@ -70,57 +68,6 @@ class DbCache extends Cache
private $_db;
/**
- * Initializes this application component.
- *
- * This method is required by the {@link IApplicationComponent} interface.
- * It ensures the existence of the cache DB table.
- * It also removes expired data items from the cache.
- */
- public function init()
- {
- parent::init();
-
- $db = $this->getDbConnection();
- $db->setActive(true);
- if ($this->autoCreateCacheTable) {
- $sql = "DELETE FROM {$this->cacheTableName} WHERE expire>0 AND expire<" . time();
- try {
- $db->createCommand($sql)->execute();
- } catch (Exception $e) {
- $this->createCacheTable($db, $this->cacheTableName);
- }
- }
- }
-
- /**
- * Creates the cache DB table.
- * @param Connection $db the database connection
- * @param string $tableName the name of the table to be created
- */
- protected function createCacheTable($db, $tableName)
- {
- $driver = $db->getDriverName();
- if ($driver === 'mysql') {
- $blob = 'LONGBLOB';
- } else {
- if ($driver === 'pgsql') {
- $blob = 'BYTEA';
- } else {
- $blob = 'BLOB';
- }
- }
- $sql = <<createCommand($sql)->execute();
- }
-
- /**
* 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.
@@ -128,7 +75,8 @@ EOD;
public function getDbConnection()
{
if ($this->_db === null) {
- if ($this->connectionID !== null && ($db = \Yii::$application->getComponent($this->connectionID)) instanceof Connection) {
+ $db = \Yii::$application->getComponent($this->connectionID);
+ if ($db instanceof Connection) {
$this->_db = $db;
} else {
throw new Exception("DbCache.connectionID must refer to the ID of a DB connection application component.");
@@ -155,10 +103,9 @@ EOD;
protected function getValue($key)
{
$query = new Query;
- $query->select(array('value'))
+ $query->select(array('data'))
->from($this->cacheTableName)
- ->where(array('id' => $key))
- ->andWhere('expire = 0 OR expire > ' . time());
+ ->where('id = :id AND (expire = 0 OR expire > :time)', array(':id' => $key, ':time' => time()));
$db = $this->getDbConnection();
if ($db->queryCachingDuration >= 0) {
$duration = $db->queryCachingDuration;
@@ -181,19 +128,20 @@ EOD;
if (empty($keys)) {
return array();
}
-
- $ids = implode("','", $keys);
- $time = time();
- $sql = "SELECT id, value FROM {$this->cacheTableName} WHERE id IN ('$ids') AND (expire=0 OR expire>$time)";
+ $query = new Query;
+ $query->select(array('id', 'data'))
+ ->from($this->cacheTableName)
+ ->where(array('id' => $keys))
+ ->andWhere("expire = 0 OR expire > " . time() . ")");
$db = $this->getDbConnection();
- if ($db->queryCachingDuration > 0) {
+ if ($db->queryCachingDuration >= 0) {
$duration = $db->queryCachingDuration;
- $db->queryCachingDuration = 0;
- $rows = $db->createCommand($sql)->queryAll();
+ $db->queryCachingDuration = -1;
+ $rows = $query->createCommand($db)->queryAll();
$db->queryCachingDuration = $duration;
} else {
- $rows = $db->createCommand($sql)->queryAll();
+ $rows = $query->createCommand($db)->queryAll();
}
$results = array();
@@ -201,7 +149,7 @@ EOD;
$results[$key] = false;
}
foreach ($rows as $row) {
- $results[$row['id']] = $row['value'];
+ $results[$row['id']] = $row['data'];
}
return $results;
}
@@ -217,9 +165,21 @@ EOD;
*/
protected function setValue($key, $value, $expire)
{
- $this->deleteValue($key);
- return $this->addValue($key, $value, $expire);
- }
+ $query = new Query;
+ $command = $query->update($this->cacheTableName, array(
+ 'expire' => $expire > 0 ? $expire + time() : 0,
+ 'data' => array($value, \PDO::PARAM_LOB),
+ ), array(
+ 'id' => $key,
+ ))->createCommand($this->getDbConnection());
+
+ if ($command->execute()) {
+ $this->gc();
+ return true;
+ } else {
+ return $this->addValue($key, $value, $expire);
+ }
+ }
/**
* Stores a value identified by a key into cache if the cache does not contain this key.
@@ -232,19 +192,21 @@ EOD;
*/
protected function addValue($key, $value, $expire)
{
- if (mt_rand(0, 1000000) < $this->_gcProbability) {
- $this->gc();
- }
+ $this->gc();
if ($expire > 0) {
$expire += time();
} else {
$expire = 0;
}
- $sql = "INSERT INTO {$this->cacheTableName} (id,expire,value) VALUES ('$key',$expire,:value)";
+
+ $query = new Query;
+ $command = $query->insert($this->cacheTableName, array(
+ 'id' => $key,
+ 'expire' => $expire,
+ 'data' => array($value, \PDO::PARAM_LOB),
+ ))->createCommand($this->getDbConnection());
try {
- $command = $this->getDbConnection()->createCommand($sql);
- $command->bindValue(':value', $value, PDO::PARAM_LOB);
$command->execute();
return true;
} catch (Exception $e) {
@@ -260,28 +222,39 @@ EOD;
*/
protected function deleteValue($key)
{
- $sql = "DELETE FROM {$this->cacheTableName} WHERE id='$key'";
- $this->getDbConnection()->createCommand($sql)->execute();
+ $query = new Query;
+ $query->delete($this->cacheTableName, array('id' => $key))
+ ->createCommand($this->getDbConnection())
+ ->execute();
return true;
}
/**
* Removes the expired data values.
+ * @param boolean $force whether to enforce the garbage collection regardless of [[gcProbability]].
+ * Defaults to false, meaning the actual deletion happens with the probability as specified by [[gcProbability]].
*/
- protected function gc()
+ public function gc($force = false)
{
- $this->getDbConnection()->createCommand("DELETE FROM {$this->cacheTableName} WHERE expire>0 AND expire<" . time())->execute();
+ if ($force || mt_rand(0, 1000000) < $this->gcProbability) {
+ $query = new Query;
+ $query->delete($this->cacheTableName, 'expire > 0 AND expire < ' . time())
+ ->createCommand($this->getDbConnection())
+ ->execute();
+ }
}
/**
* Deletes all values from cache.
* This is the implementation of the method declared in the parent class.
* @return boolean whether the flush operation was successful.
- * @since 1.1.5
*/
protected function flushValues()
{
- $this->getDbConnection()->createCommand("DELETE FROM {$this->cacheTableName}")->execute();
+ $query = new Query;
+ $query->delete($this->cacheTableName)
+ ->createCommand($this->getDbConnection())
+ ->execute();
return true;
}
}
diff --git a/framework/caching/FileCache.php b/framework/caching/FileCache.php
index 9de4b7f..2d043b4 100644
--- a/framework/caching/FileCache.php
+++ b/framework/caching/FileCache.php
@@ -1,6 +1,6 @@
* @since 2.0
*/
-class CFileCache extends CCache
+class FileCache extends Cache
{
/**
- * @var string the directory to store cache files. Defaults to null, meaning
- * using 'protected/runtime/cache' as the directory.
+ * @var string the directory to store cache files. You may use path alias here.
*/
- public $cachePath;
+ public $cachePath = '@application/runtime/cache';
/**
* @var string cache file suffix. Defaults to '.bin'.
*/
- public $cacheFileSuffix='.bin';
- /**
- * @var integer the level of sub-directories to store cache files. Defaults to 0,
- * meaning no sub-directories. If the system has huge number of cache files (e.g. 10K+),
- * you may want to set this value to be 1 or 2 so that the file system is not over burdened.
- * The value of this property should not exceed 16 (less than 3 is recommended).
- */
- public $directoryLevel=0;
-
- private $_gcProbability=100;
- private $_gced=false;
-
- /**
- * Initializes this application component.
- * This method is required by the {@link IApplicationComponent} interface.
- * It checks the availability of memcache.
- * @throws CException if APC cache extension is not loaded or is disabled.
- */
- public function init()
- {
- parent::init();
- if($this->cachePath===null)
- $this->cachePath=\Yii::$application->getRuntimePath().DIRECTORY_SEPARATOR.'cache';
- if(!is_dir($this->cachePath))
- mkdir($this->cachePath,0777,true);
- }
-
+ public $cacheFileSuffix = '.bin';
/**
- * @return 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.
+ * @var integer the level of sub-directories to store cache files. Defaults to 1.
+ * If the system has huge number of cache files (e.g. one million), you may use a bigger value
+ * (usually no bigger than 3). Using sub-directories is mainly to ensure the file system
+ * is not over burdened with a single directory having too many files.
*/
- public function getGCProbability()
- {
- return $this->_gcProbability;
- }
-
+ public $directoryLevel = 1;
/**
- * @param integer $value the probability (parts per million) that garbage collection (GC) should be performed
+ * @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.
- */
- public function setGCProbability($value)
- {
- $value=(int)$value;
- if($value<0)
- $value=0;
- if($value>1000000)
- $value=1000000;
- $this->_gcProbability=$value;
- }
+ **/
+ public $gcProbability = 100;
/**
- * Deletes all values from cache.
- * This is the implementation of the method declared in the parent class.
- * @return boolean whether the flush operation was successful.
- * @since 1.1.5
+ * Initializes this component by ensuring the existence of the cache path.
*/
- protected function flushValues()
+ public function init()
{
- $this->gc(false);
- return true;
+ parent::init();
+ $this->cachePath = \Yii::getAlias($this->cachePath);
+ if ($this->cachePath === false) {
+ throw new Exception('FileCache.cachePath must be a valid path alias.');
+ }
+ if (!is_dir($this->cachePath)) {
+ mkdir($this->cachePath, 0777, true);
+ }
}
/**
@@ -105,12 +70,12 @@ class CFileCache extends CCache
*/
protected function getValue($key)
{
- $cacheFile=$this->getCacheFile($key);
- if(($time=@filemtime($cacheFile))>time())
+ $cacheFile = $this->getCacheFile($key);
+ if (($time = @filemtime($cacheFile)) > time()) {
return @file_get_contents($cacheFile);
- else if($time>0)
- @unlink($cacheFile);
- return false;
+ } else {
+ return false;
+ }
}
/**
@@ -122,28 +87,23 @@ class CFileCache extends CCache
* @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
* @return boolean true if the value is successfully stored into cache, false otherwise
*/
- protected function setValue($key,$value,$expire)
+ protected function setValue($key, $value, $expire)
{
- if(!$this->_gced && mt_rand(0,1000000)<$this->_gcProbability)
- {
- $this->gc();
- $this->_gced=true;
+ if ($expire <= 0) {
+ $expire = 31536000; // 1 year
}
+ $expire += time();
- if($expire<=0)
- $expire=31536000; // 1 year
- $expire+=time();
-
- $cacheFile=$this->getCacheFile($key);
- if($this->directoryLevel>0)
- @mkdir(dirname($cacheFile),0777,true);
- if(@file_put_contents($cacheFile,$value,LOCK_EX)!==false)
- {
- @chmod($cacheFile,0777);
- return @touch($cacheFile,$expire);
+ $cacheFile = $this->getCacheFile($key);
+ if ($this->directoryLevel > 0) {
+ @mkdir(dirname($cacheFile), 0777, true);
}
- else
+ if (@file_put_contents($cacheFile, $value, LOCK_EX) !== false) {
+ @chmod($cacheFile, 0777);
+ return @touch($cacheFile, $expire);
+ } else {
return false;
+ }
}
/**
@@ -155,12 +115,13 @@ class CFileCache extends CCache
* @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
* @return boolean true if the value is successfully stored into cache, false otherwise
*/
- protected function addValue($key,$value,$expire)
+ protected function addValue($key, $value, $expire)
{
- $cacheFile=$this->getCacheFile($key);
- if(@filemtime($cacheFile)>time())
+ $cacheFile = $this->getCacheFile($key);
+ if (@filemtime($cacheFile) > time()) {
return false;
- return $this->setValue($key,$value,$expire);
+ }
+ return $this->setValue($key, $value, $expire);
}
/**
@@ -171,7 +132,7 @@ class CFileCache extends CCache
*/
protected function deleteValue($key)
{
- $cacheFile=$this->getCacheFile($key);
+ $cacheFile = $this->getCacheFile($key);
return @unlink($cacheFile);
}
@@ -182,41 +143,69 @@ class CFileCache extends CCache
*/
protected function getCacheFile($key)
{
- if($this->directoryLevel>0)
- {
- $base=$this->cachePath;
- for($i=0;$i<$this->directoryLevel;++$i)
- {
- if(($prefix=substr($key,$i+$i,2))!==false)
- $base.=DIRECTORY_SEPARATOR.$prefix;
+ if ($this->directoryLevel > 0) {
+ $base = $this->cachePath;
+ for ($i = 0; $i < $this->directoryLevel; ++$i) {
+ if (($prefix = substr($key, $i + $i, 2)) !== false) {
+ $base .= DIRECTORY_SEPARATOR . $prefix;
+ }
}
- return $base.DIRECTORY_SEPARATOR.$key.$this->cacheFileSuffix;
+ return $base . DIRECTORY_SEPARATOR . $key . $this->cacheFileSuffix;
+ } else {
+ return $this->cachePath . DIRECTORY_SEPARATOR . $key . $this->cacheFileSuffix;
}
- else
- return $this->cachePath.DIRECTORY_SEPARATOR.$key.$this->cacheFileSuffix;
+ }
+
+ /**
+ * Deletes all values from cache.
+ * This is the implementation of the method declared in the parent class.
+ * @return boolean whether the flush operation was successful.
+ */
+ protected function flushValues()
+ {
+ $this->gc(true, false);
+ return true;
}
/**
* Removes expired cache files.
- * @param boolean $expiredOnly whether to removed expired cache files only. If true, all cache files under {@link cachePath} will be removed.
- * @param string $path the path to clean with. If null, it will be {@link cachePath}.
+ * @param boolean $force whether to enforce the garbage collection regardless of [[gcProbability]].
+ * Defaults to false, meaning the actual deletion happens with the probability as specified by [[gcProbability]].
+ * @param boolean $expiredOnly whether to removed expired cache files only.
+ * If true, all cache files under [[cachePath]] will be removed.
+ */
+ public function gc($force = false, $expiredOnly = true)
+ {
+ if ($force || mt_rand(0, 1000000) < $this->gcProbability) {
+ $this->gcRecursive($this->cachePath, $expiredOnly);
+ }
+ }
+
+ /**
+ * Recursively removing expired cache files under a directory.
+ * This method is mainly used by [[gc()]].
+ * @param string $path the directory under which expired cache files are removed.
+ * @param boolean $expiredOnly whether to only remove expired cache files. If false, all files
+ * under `$path` will be removed.
*/
- public function gc($expiredOnly=true,$path=null)
+ protected function gcRecursive($path, $expiredOnly)
{
- if($path===null)
- $path=$this->cachePath;
- if(($handle=opendir($path))===false)
- return;
- while(($file=readdir($handle))!==false)
- {
- if($file[0]==='.')
- continue;
- $fullPath=$path.DIRECTORY_SEPARATOR.$file;
- if(is_dir($fullPath))
- $this->gc($expiredOnly,$fullPath);
- else if($expiredOnly && @filemtime($fullPath)