5 * @author Qiang Xue <qiang.xue@gmail.com>
6 * @link http://www.pradosoft.com/
7 * @copyright Copyright © 2005-2014 PradoSoft
8 * @license http://www.pradosoft.com/license/
9 * @package System.Caching
12 Prado::using('System.Data.TDbConnection');
17 * TDbCache implements a cache application module by storing cached data in a database.
19 * TDbCache relies on {@link http://www.php.net/manual/en/ref.pdo.php PDO} to retrieve
20 * data from databases. In order to use TDbCache, you need to enable the PDO extension
21 * as well as the corresponding PDO DB driver. For example, to use SQLite database
22 * to store cached data, you need both php_pdo and php_pdo_sqlite extensions.
24 * By default, TDbCache creates and uses an SQLite database under the application
25 * runtime directory. You may change this default setting by specifying the following
27 * - {@link setConnectionID ConnectionID} or
28 * - {@link setConnectionString ConnectionString}, {@link setUsername Username} and {@link setPassword Pasword}.
30 * The cached data is stored in a table in the specified database.
31 * By default, the name of the table is called 'pradocache'. If the table does not
32 * exist in the database, it will be automatically created with the following structure:
34 * CREATE TABLE pradocache (itemkey CHAR(128), value BLOB, expire INT)
35 * CREATE INDEX IX_itemkey ON pradocache (itemkey)
36 * CREATE INDEX IX_expire ON pradocache (expire)
39 * Note, some DBMS might not support BLOB type. In this case, replace 'BLOB' with a suitable
40 * binary data type (e.g. LONGBLOB in MySQL, BYTEA in PostgreSQL.)
42 * Important: Make sure that the indices are non-unique!
44 * If you want to change the cache table name, or if you want to create the table by yourself,
45 * you may set {@link setCacheTableName CacheTableName} and {@link setAutoCreateCacheTable AutoCreateCacheTableName} properties.
47 * {@link setFlushInterval FlushInterval} control how often expired items will be removed from cache.
48 * If you prefer to remove expired items manualy e.g. via cronjob you can disable automatic deletion by setting FlushInterval to '0'.
50 * The following basic cache operations are implemented:
51 * - {@link get} : retrieve the value with a key (if any) from cache
52 * - {@link set} : store the value with a key into cache
53 * - {@link add} : store the value only if cache does not have this key
54 * - {@link delete} : delete the value with the specified key from cache
55 * - {@link flush} : delete all values from cache
57 * Each value is associated with an expiration time. The {@link get} operation
58 * ensures that any expired value will not be returned. The expiration time by
59 * the number of seconds. A expiration time 0 represents never expire.
61 * By definition, cache does not ensure the existence of a value
62 * even if it never expires. Cache is not meant to be an persistent storage.
64 * Do not use the same database file for multiple applications using TDbCache.
65 * Also note, cache is shared by all user sessions of an application.
67 * Some usage examples of TDbCache are as follows,
69 * $cache=new TDbCache; // TDbCache may also be loaded as a Prado application module
71 * $cache->add('object',$object);
72 * $object2=$cache->get('object');
75 * If loaded, TDbCache will register itself with {@link TApplication} as the
76 * cache module. It can be accessed via {@link TApplication::getCache()}.
78 * TDbCache may be configured in application configuration file as follows
80 * <module id="cache" class="System.Caching.TDbCache" />
83 * @author Qiang Xue <qiang.xue@gmail.com>
84 * @package System.Caching
87 class TDbCache extends TCache
90 * @var string the ID of TDataSourceConfig module
94 * @var TDbConnection the DB connection instance
98 * @var string name of the DB cache table
100 private $_cacheTable='pradocache';
102 * @var integer Interval expired items will be removed from cache
104 private $_flushInterval=60;
108 private $_cacheInitialized = false;
112 private $_createCheck= false;
114 * @var boolean whether the cache DB table should be created automatically
116 private $_autoCreate=true;
117 private $_username='';
118 private $_password='';
119 private $_connectionString='';
123 * Disconnect the db connection.
125 public function __destruct()
127 if($this->_db!==null)
128 $this->_db->setActive(false);
132 * Initializes this module.
133 * This method is required by the IModule interface.
134 * attach {@link doInitializeCache} to TApplication.OnLoadStateComplete event
135 * attach {@link doFlushCacheExpired} to TApplication.OnSaveState event
137 * @param TXmlElement configuration for this module, can be null
139 public function init($config)
141 $this -> getApplication() -> attachEventHandler('OnLoadStateComplete', array($this, 'doInitializeCache'));
142 $this -> getApplication() -> attachEventHandler('OnSaveState', array($this, 'doFlushCacheExpired'));
143 parent::init($config);
147 * Event listener for TApplication.OnSaveState
150 * @see flushCacheExpired
152 public function doFlushCacheExpired()
154 $this->flushCacheExpired(false);
158 * Event listener for TApplication.OnLoadStateComplete
162 * @see initializeCache
164 public function doInitializeCache()
166 $this->initializeCache();
170 * Initialize TDbCache
172 * If {@link setAutoCreateCacheTable AutoCreateCacheTableName} is 'true' check existence of cache table
173 * and create table if does not exist.
175 * @param boolean Force override global state check
177 * @throws TConfigurationException if any error happens during creating database or cache table.
180 protected function initializeCache($force=false)
182 if($this->_cacheInitialized && !$force) return;
183 $db=$this->getDbConnection();
186 $key = 'TDbCache:' . $this->_cacheTable . ':created';
188 $this -> _createCheck = false;
190 $this -> _createCheck = $this -> getApplication() -> getGlobalState($key, 0);
192 if($this->_autoCreate && !$this -> _createCheck) {
194 Prado::trace(($force ? 'Force initializing: ' : 'Initializing: ') . $this -> id . ', ' . $this->_cacheTable, 'System.Caching.TDbCache');
196 $sql='SELECT 1 FROM '.$this->_cacheTable.' WHERE 0=1';
197 $db->createCommand($sql)->queryScalar();
199 $this -> _createCheck = true;
200 $this -> getApplication() -> setGlobalState($key, time());
205 // DB table not exists
206 if($this->_autoCreate)
208 Prado::trace('Autocreate: ' . $this->_cacheTable, 'System.Caching.TDbCache');
210 $driver=$db->getDriverName();
211 if($driver==='mysql')
213 else if($driver==='pgsql')
218 $sql='CREATE TABLE '.$this->_cacheTable." (itemkey CHAR(128) PRIMARY KEY, value $blob, expire INTEGER)";
219 $db->createCommand($sql)->execute();
221 $sql='CREATE INDEX IX_expire ON ' . $this->_cacheTable . ' (expire)';
222 $db->createCommand($sql)->execute();
224 $this -> _createCheck = true;
225 $this -> getApplication() -> setGlobalState($key, time());
228 throw new TConfigurationException('db_cachetable_inexistent',$this->_cacheTable);
230 $this->_cacheInitialized = true;
234 * Flush expired values from cache depending on {@link setFlushInterval FlushInterval}
235 * @param boolean override {@link setFlushInterval FlushInterval} and force deletion of expired items
239 public function flushCacheExpired($force=false)
241 $interval = $this -> getFlushInterval();
242 if(!$force && $interval === 0) return;
244 $key = 'TDbCache:' . $this->_cacheTable . ':flushed';
246 $next = $interval + (integer)$this -> getApplication() -> getGlobalState($key, 0);
248 if($force || $next <= $now)
250 if(!$this->_cacheInitialized) $this->initializeCache();
251 Prado::trace(($force ? 'Force flush of expired items: ' : 'Flush expired items: ') . $this -> id . ', ' . $this->_cacheTable, 'System.Caching.TDbCache');
252 $sql='DELETE FROM '.$this->_cacheTable.' WHERE expire<>0 AND expire<'.$now;
253 $this->getDbConnection()->createCommand($sql)->execute();
254 $this -> getApplication() -> setGlobalState($key, $now);
259 * @return integer Interval in sec expired items will be removed from cache. Default to 60
262 public function getFlushInterval()
264 return $this->_flushInterval;
268 * Sets interval expired items will be removed from cache
270 * To disable automatic deletion of expired items,
271 * e.g. for external flushing via cron you can set value to '0'
273 * @param integer Interval in sec
276 public function setFlushInterval($value)
278 $this->_flushInterval = (integer) $value;
282 * Creates the DB connection.
283 * @param string the module ID for TDataSourceConfig
284 * @return TDbConnection the created DB connection
285 * @throws TConfigurationException if module ID is invalid or empty
287 protected function createDbConnection()
289 if($this->_connID!=='')
291 $config=$this->getApplication()->getModule($this->_connID);
292 if($config instanceof TDataSourceConfig)
293 return $config->getDbConnection();
295 throw new TConfigurationException('dbcache_connectionid_invalid',$this->_connID);
299 $db=new TDbConnection;
300 if($this->_connectionString!=='')
302 $db->setConnectionString($this->_connectionString);
303 if($this->_username!=='')
304 $db->setUsername($this->_username);
305 if($this->_password!=='')
306 $db->setPassword($this->_password);
310 // default to SQLite3 database
311 $dbFile=$this->getApplication()->getRuntimePath().'/sqlite3.cache';
312 $db->setConnectionString('sqlite:'.$dbFile);
319 * @return TDbConnection the DB connection instance
321 public function getDbConnection()
323 if($this->_db===null)
324 $this->_db=$this->createDbConnection();
326 $this->_db->setActive(true);
331 * @return string the ID of a {@link TDataSourceConfig} module. Defaults to empty string, meaning not set.
334 public function getConnectionID()
336 return $this->_connID;
340 * Sets the ID of a TDataSourceConfig module.
341 * The datasource module will be used to establish the DB connection for this cache module.
342 * The database connection can also be specified via {@link setConnectionString ConnectionString}.
343 * When both ConnectionID and ConnectionString are specified, the former takes precedence.
344 * @param string ID of the {@link TDataSourceConfig} module
347 public function setConnectionID($value)
349 $this->_connID=$value;
353 * @return string The Data Source Name, or DSN, contains the information required to connect to the database.
355 public function getConnectionString()
357 return $this->_connectionString;
361 * @param string The Data Source Name, or DSN, contains the information required to connect to the database.
362 * @see http://www.php.net/manual/en/function.pdo-construct.php
364 public function setConnectionString($value)
366 $this->_connectionString=$value;
370 * @return string the username for establishing DB connection. Defaults to empty string.
372 public function getUsername()
374 return $this->_username;
378 * @param string the username for establishing DB connection
380 public function setUsername($value)
382 $this->_username=$value;
386 * @return string the password for establishing DB connection. Defaults to empty string.
388 public function getPassword()
390 return $this->_password;
394 * @param string the password for establishing DB connection
396 public function setPassword($value)
398 $this->_password=$value;
402 * @return string the name of the DB table to store cache content. Defaults to 'pradocache'.
403 * @see setAutoCreateCacheTable
405 public function getCacheTableName()
407 return $this->_cacheTable;
411 * Sets the name of the DB table to store cache content.
412 * Note, if {@link setAutoCreateCacheTable AutoCreateCacheTable} is false
413 * and you want to create the DB table manually by yourself,
414 * you need to make sure the DB table is of the following structure:
416 * CREATE TABLE pradocache (itemkey CHAR(128), value BLOB, expire INT)
417 * CREATE INDEX IX_itemkey ON pradocache (itemkey)
418 * CREATE INDEX IX_expire ON pradocache (expire)
421 * Note, some DBMS might not support BLOB type. In this case, replace 'BLOB' with a suitable
422 * binary data type (e.g. LONGBLOB in MySQL, BYTEA in PostgreSQL.)
424 * Important: Make sure that the indices are non-unique!
426 * @param string the name of the DB table to store cache content
427 * @see setAutoCreateCacheTable
429 public function setCacheTableName($value)
431 $this->_cacheTable=$value;
435 * @return boolean whether the cache DB table should be automatically created if not exists. Defaults to true.
436 * @see setAutoCreateCacheTable
438 public function getAutoCreateCacheTable()
440 return $this->_autoCreate;
444 * @param boolean whether the cache DB table should be automatically created if not exists.
445 * @see setCacheTableName
447 public function setAutoCreateCacheTable($value)
449 $this->_autoCreate=TPropertyValue::ensureBoolean($value);
453 * Retrieves a value from cache with a specified key.
454 * This is the implementation of the method declared in the parent class.
455 * @param string a unique key identifying the cached value
456 * @return string the value stored in cache, false if the value is not in the cache or expired.
458 protected function getValue($key)
460 if(!$this->_cacheInitialized) $this->initializeCache();
462 $sql='SELECT value FROM '.$this->_cacheTable.' WHERE itemkey=\''.$key.'\' AND (expire=0 OR expire>'.time().') ORDER BY expire DESC';
463 $command=$this->getDbConnection()->createCommand($sql);
464 return Prado::unserialize($command->queryScalar());
468 $this->initializeCache(true);
469 return Prado::unserialize($command->queryScalar());
474 * Stores a value identified by a key in cache.
475 * This is the implementation of the method declared in the parent class.
477 * @param string the key identifying the value to be cached
478 * @param string the value to be cached
479 * @param integer the number of seconds in which the cached value will expire. 0 means never expire.
480 * @return boolean true if the value is successfully stored into cache, false otherwise
482 protected function setValue($key,$value,$expire)
484 $this->deleteValue($key);
485 return $this->addValue($key,$value,$expire);
489 * Stores a value identified by a key into cache if the cache does not contain this key.
490 * This is the implementation of the method declared in the parent class.
492 * @param string the key identifying the value to be cached
493 * @param string the value to be cached
494 * @param integer the number of seconds in which the cached value will expire. 0 means never expire.
495 * @return boolean true if the value is successfully stored into cache, false otherwise
497 protected function addValue($key,$value,$expire)
499 if(!$this->_cacheInitialized) $this->initializeCache();
500 $expire=($expire<=0)?0:time()+$expire;
501 $sql="INSERT INTO {$this->_cacheTable} (itemkey,value,expire) VALUES(:key,:value,$expire)";
504 $command=$this->getDbConnection()->createCommand($sql);
505 $command->bindValue(':key',$key,PDO::PARAM_STR);
506 $command->bindValue(':value',Prado::serialize($value),PDO::PARAM_LOB);
514 $this->initializeCache(true);
526 * Deletes a value with the specified key from cache
527 * This is the implementation of the method declared in the parent class.
528 * @param string the key of the value to be deleted
529 * @return boolean if no error happens during deletion
531 protected function deleteValue($key)
533 if(!$this->_cacheInitialized) $this->initializeCache();
536 $command=$this->getDbConnection()->createCommand("DELETE FROM {$this->_cacheTable} WHERE itemkey=:key");
537 $command->bindValue(':key',$key,PDO::PARAM_STR);
543 $this->initializeCache(true);
550 * Deletes all values from cache.
551 * Be careful of performing this operation if the cache is shared by multiple applications.
553 public function flush()
555 if(!$this->_cacheInitialized) $this->initializeCache();
558 $command = $this->getDbConnection()->createCommand("DELETE FROM {$this->_cacheTable}");
565 $this->initializeCache(true);