CDbCache.php 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. <?php
  2. /**
  3. * CDbCache class file
  4. *
  5. * @author Qiang Xue <qiang.xue@gmail.com>
  6. * @link http://www.yiiframework.com/
  7. * @copyright 2008-2013 Yii Software LLC
  8. * @license http://www.yiiframework.com/license/
  9. */
  10. /**
  11. * CDbCache implements a cache application component by storing cached data in a database.
  12. *
  13. * CDbCache stores cache data in a DB table named {@link cacheTableName}.
  14. * If the table does not exist, it will be automatically created.
  15. * By setting {@link autoCreateCacheTable} to false, you can also manually create the DB table.
  16. *
  17. * CDbCache relies on {@link http://www.php.net/manual/en/ref.pdo.php PDO} to access database.
  18. * By default, it will use a SQLite3 database under the application runtime directory.
  19. * You can also specify {@link connectionID} so that it makes use of
  20. * a DB application component to access database.
  21. *
  22. * See {@link CCache} manual for common cache operations that are supported by CDbCache.
  23. *
  24. * @property integer $gCProbability The probability (parts per million) that garbage collection (GC) should be performed
  25. * when storing a piece of data in the cache. Defaults to 100, meaning 0.01% chance.
  26. * @property CDbConnection $dbConnection The DB connection instance.
  27. *
  28. * @author Qiang Xue <qiang.xue@gmail.com>
  29. * @package system.caching
  30. * @since 1.0
  31. */
  32. class CDbCache extends CCache
  33. {
  34. /**
  35. * @var string the ID of the {@link CDbConnection} application component. If not set,
  36. * a SQLite3 database will be automatically created and used. The SQLite database file
  37. * is <code>protected/runtime/cache-YiiVersion.db</code>.
  38. */
  39. public $connectionID;
  40. /**
  41. * @var string name of the DB table to store cache content. Defaults to 'YiiCache'.
  42. * Note, if {@link autoCreateCacheTable} is false and you want to create the DB table
  43. * manually by yourself, you need to make sure the DB table is of the following structure:
  44. * <pre>
  45. * (id CHAR(128) PRIMARY KEY, expire INTEGER, value BLOB)
  46. * </pre>
  47. * Note, some DBMS might not support BLOB type. In this case, replace 'BLOB' with a suitable
  48. * binary data type (e.g. LONGBLOB in MySQL, BYTEA in PostgreSQL.)
  49. * @see autoCreateCacheTable
  50. */
  51. public $cacheTableName='YiiCache';
  52. /**
  53. * @var boolean whether the cache DB table should be created automatically if it does not exist. Defaults to true.
  54. * If you already have the table created, it is recommended you set this property to be false to improve performance.
  55. * @see cacheTableName
  56. */
  57. public $autoCreateCacheTable=true;
  58. /**
  59. * @var CDbConnection the DB connection instance
  60. */
  61. private $_db;
  62. private $_gcProbability=100;
  63. private $_gced=false;
  64. /**
  65. * Initializes this application component.
  66. *
  67. * This method is required by the {@link IApplicationComponent} interface.
  68. * It ensures the existence of the cache DB table.
  69. * It also removes expired data items from the cache.
  70. */
  71. public function init()
  72. {
  73. parent::init();
  74. $db=$this->getDbConnection();
  75. $db->setActive(true);
  76. if($this->autoCreateCacheTable)
  77. {
  78. $sql="DELETE FROM {$this->cacheTableName} WHERE expire>0 AND expire<".time();
  79. try
  80. {
  81. $db->createCommand($sql)->execute();
  82. }
  83. catch(Exception $e)
  84. {
  85. $this->createCacheTable($db,$this->cacheTableName);
  86. }
  87. }
  88. }
  89. /**
  90. * @return integer the probability (parts per million) that garbage collection (GC) should be performed
  91. * when storing a piece of data in the cache. Defaults to 100, meaning 0.01% chance.
  92. */
  93. public function getGCProbability()
  94. {
  95. return $this->_gcProbability;
  96. }
  97. /**
  98. * @param integer $value the probability (parts per million) that garbage collection (GC) should be performed
  99. * when storing a piece of data in the cache. Defaults to 100, meaning 0.01% chance.
  100. * This number should be between 0 and 1000000. A value 0 meaning no GC will be performed at all.
  101. */
  102. public function setGCProbability($value)
  103. {
  104. $value=(int)$value;
  105. if($value<0)
  106. $value=0;
  107. if($value>1000000)
  108. $value=1000000;
  109. $this->_gcProbability=$value;
  110. }
  111. /**
  112. * Creates the cache DB table.
  113. * @param CDbConnection $db the database connection
  114. * @param string $tableName the name of the table to be created
  115. */
  116. protected function createCacheTable($db,$tableName)
  117. {
  118. $driver=$db->getDriverName();
  119. if($driver==='mysql')
  120. $blob='LONGBLOB';
  121. elseif($driver==='pgsql')
  122. $blob='BYTEA';
  123. else
  124. $blob='BLOB';
  125. $sql=<<<EOD
  126. CREATE TABLE $tableName
  127. (
  128. id CHAR(128) PRIMARY KEY,
  129. expire INTEGER,
  130. value $blob
  131. )
  132. EOD;
  133. $db->createCommand($sql)->execute();
  134. }
  135. /**
  136. * @return CDbConnection the DB connection instance
  137. * @throws CException if {@link connectionID} does not point to a valid application component.
  138. */
  139. public function getDbConnection()
  140. {
  141. if($this->_db!==null)
  142. return $this->_db;
  143. elseif(($id=$this->connectionID)!==null)
  144. {
  145. if(($this->_db=Yii::app()->getComponent($id)) instanceof CDbConnection)
  146. return $this->_db;
  147. else
  148. throw new CException(Yii::t('yii','CDbCache.connectionID "{id}" is invalid. Please make sure it refers to the ID of a CDbConnection application component.',
  149. array('{id}'=>$id)));
  150. }
  151. else
  152. {
  153. $dbFile=Yii::app()->getRuntimePath().DIRECTORY_SEPARATOR.'cache-'.Yii::getVersion().'.db';
  154. return $this->_db=new CDbConnection('sqlite:'.$dbFile);
  155. }
  156. }
  157. /**
  158. * Sets the DB connection used by the cache component.
  159. * @param CDbConnection $value the DB connection instance
  160. * @since 1.1.5
  161. */
  162. public function setDbConnection($value)
  163. {
  164. $this->_db=$value;
  165. }
  166. /**
  167. * Retrieves a value from cache with a specified key.
  168. * This is the implementation of the method declared in the parent class.
  169. * @param string $key a unique key identifying the cached value
  170. * @return string|boolean the value stored in cache, false if the value is not in the cache or expired.
  171. */
  172. protected function getValue($key)
  173. {
  174. $time=time();
  175. $sql="SELECT value FROM {$this->cacheTableName} WHERE id='$key' AND (expire=0 OR expire>$time)";
  176. $db=$this->getDbConnection();
  177. if($db->queryCachingDuration>0)
  178. {
  179. $duration=$db->queryCachingDuration;
  180. $db->queryCachingDuration=0;
  181. $result=$db->createCommand($sql)->queryScalar();
  182. $db->queryCachingDuration=$duration;
  183. return $result;
  184. }
  185. else
  186. return $db->createCommand($sql)->queryScalar();
  187. }
  188. /**
  189. * Retrieves multiple values from cache with the specified keys.
  190. * @param array $keys a list of keys identifying the cached values
  191. * @return array a list of cached values indexed by the keys
  192. */
  193. protected function getValues($keys)
  194. {
  195. if(empty($keys))
  196. return array();
  197. $ids=implode("','",$keys);
  198. $time=time();
  199. $sql="SELECT id, value FROM {$this->cacheTableName} WHERE id IN ('$ids') AND (expire=0 OR expire>$time)";
  200. $db=$this->getDbConnection();
  201. if($db->queryCachingDuration>0)
  202. {
  203. $duration=$db->queryCachingDuration;
  204. $db->queryCachingDuration=0;
  205. $rows=$db->createCommand($sql)->queryAll();
  206. $db->queryCachingDuration=$duration;
  207. }
  208. else
  209. $rows=$db->createCommand($sql)->queryAll();
  210. $results=array();
  211. foreach($keys as $key)
  212. $results[$key]=false;
  213. foreach($rows as $row)
  214. $results[$row['id']]=$row['value'];
  215. return $results;
  216. }
  217. /**
  218. * Stores a value identified by a key in cache.
  219. * This is the implementation of the method declared in the parent class.
  220. *
  221. * @param string $key the key identifying the value to be cached
  222. * @param string $value the value to be cached
  223. * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
  224. * @return boolean true if the value is successfully stored into cache, false otherwise
  225. */
  226. protected function setValue($key,$value,$expire)
  227. {
  228. $this->deleteValue($key);
  229. return $this->addValue($key,$value,$expire);
  230. }
  231. /**
  232. * Stores a value identified by a key into cache if the cache does not contain this key.
  233. * This is the implementation of the method declared in the parent class.
  234. *
  235. * @param string $key the key identifying the value to be cached
  236. * @param string $value the value to be cached
  237. * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
  238. * @return boolean true if the value is successfully stored into cache, false otherwise
  239. */
  240. protected function addValue($key,$value,$expire)
  241. {
  242. if(!$this->_gced && mt_rand(0,1000000)<$this->_gcProbability)
  243. {
  244. $this->gc();
  245. $this->_gced=true;
  246. }
  247. if($expire>0)
  248. $expire+=time();
  249. else
  250. $expire=0;
  251. $sql="INSERT INTO {$this->cacheTableName} (id,expire,value) VALUES ('$key',$expire,:value)";
  252. try
  253. {
  254. $command=$this->getDbConnection()->createCommand($sql);
  255. $command->bindValue(':value',$value,PDO::PARAM_LOB);
  256. $command->execute();
  257. return true;
  258. }
  259. catch(Exception $e)
  260. {
  261. return false;
  262. }
  263. }
  264. /**
  265. * Deletes a value with the specified key from cache
  266. * This is the implementation of the method declared in the parent class.
  267. * @param string $key the key of the value to be deleted
  268. * @return boolean if no error happens during deletion
  269. */
  270. protected function deleteValue($key)
  271. {
  272. $sql="DELETE FROM {$this->cacheTableName} WHERE id='$key'";
  273. $this->getDbConnection()->createCommand($sql)->execute();
  274. return true;
  275. }
  276. /**
  277. * Removes the expired data values.
  278. */
  279. protected function gc()
  280. {
  281. $this->getDbConnection()->createCommand("DELETE FROM {$this->cacheTableName} WHERE expire>0 AND expire<".time())->execute();
  282. }
  283. /**
  284. * Deletes all values from cache.
  285. * This is the implementation of the method declared in the parent class.
  286. * @return boolean whether the flush operation was successful.
  287. * @since 1.1.5
  288. */
  289. protected function flushValues()
  290. {
  291. $this->getDbConnection()->createCommand("DELETE FROM {$this->cacheTableName}")->execute();
  292. return true;
  293. }
  294. }