siteKeyName = 'site_serial:' . crc32(SQL_TYPE . '://' . SQL_USER . ':' . SQL_PASS . '@' . SQL_SERVER . ':' . TABLE_PREFIX . ':' . SQL_DB);
// get cache handler class to use
if ( isset($vars['CacheHandler']) ) {
// for advanced users, who want to save one SQL on each page load
$handler_class = $vars['CacheHandler'] . 'CacheHandler';
}
else {
$this->Application->Conn->nextQueryFromMaster = true;
$handler_class = $this->Application->ConfigValue('CacheHandler') . 'CacheHandler';
}
// defined cache handler doen't exist -> use default
if (!class_exists($handler_class)) {
$handler_class = 'FakeCacheHandler';
}
$handler = new $handler_class();
if (!$handler->isWorking()) {
// defined cache handler is not working -> use default
trigger_error('Failed to initialize "' . $handler_class . '" caching handler.', E_USER_WARNING);
$handler = new FakeCacheHandler();
}
elseif ($this->Application->isDebugMode() && ($handler->cachingType == CACHING_TYPE_MEMORY)) {
$this->Application->Debugger->appendHTML('Memory Caching: "' . $handler_class . '"');
}
$this->_handler =& $handler;
$this->cachingType = $handler->cachingType;
$this->debugCache = $handler->cachingType == CACHING_TYPE_MEMORY && $this->Application->isDebugMode();
$this->displayCacheStatistics = defined('DBG_CACHE') && DBG_CACHE && $this->Application->isDebugMode();
}
/**
* Returns caching type of current storage engine
*
* @return int
*/
function getCachingType()
{
return $this->cachingType;
}
/**
* Stores value to cache
*
* @param string $name
* @param mixed $value
* @param int $expiration cache record expiration time in seconds
* @return bool
*/
function setCache($name, $value, $expiration)
{
// 1. stores current version of serial for given cache key
$this->_setCache($name . '_serials', $this->replaceSerials($name), $expiration);
// 2. remove rebuilding mark
$this->delete($name . '_rebuilding');
return $this->_setCache($name, $value, $expiration);
}
/**
* Stores value to cache
*
* @param string $name
* @param mixed $value
* @param int $expiration cache record expiration time in seconds
* @return bool
*/
function _setCache($name, $value, $expiration)
{
$prepared_name = $this->prepareKeyName($name);
$this->_localStorage[$prepared_name] = $value;
return $this->_handler->set($prepared_name, $value, $expiration);
}
/**
* Sets rebuilding mode for given cache
*
* @param string $name
* @param int $mode
* @param int $max_rebuilding_time
*/
function rebuildCache($name, $mode = null, $max_rebuilding_time = 0)
{
if ( !isset($mode) || $mode == self::REBUILD_NOW ) {
$this->_setCache($name . '_rebuilding', 1, $max_rebuilding_time);
$this->delete($name . '_rebuild');
}
elseif ( $mode == self::REBUILD_LATER ) {
$this->_setCache($name . '_rebuild', 1, 0);
$this->delete($name . '_rebuilding');
}
}
/**
* Returns value from cache
*
* @param string $name
* @param bool $store_locally store data locally after retrieved
* @param int $max_rebuild_seconds
* @return mixed
*/
function getCache($name, $store_locally = true, $max_rebuild_seconds = 0)
{
if ( $this->_getCache($name . '_rebuild') ) {
// cache rebuild requested -> rebuild now
$this->delete($name . '_rebuild');
return false;
}
$new_serial = $this->replaceSerials($name);
$old_serial = $this->_getCache($name . '_serials');
if ( $name == $new_serial || $new_serial != $old_serial ) {
// no serials in cache key OR cache is outdated
$wait_seconds = $max_rebuild_seconds;
while (true) {
$cache = $this->_getCache($name, $store_locally);
$rebuilding = $this->_getCache($name . '_rebuilding', false);
if ( ($cache === false) && (!$rebuilding || $wait_seconds == 0) ) {
// cache missing and nobody rebuilding it -> rebuild; enough waiting for cache to be ready
return false;
}
elseif ( $cache !== false ) {
// cache present (if other user is rebuilding it, then it's outdated cache) -> return it
return $rebuilding || $new_serial == $old_serial ? $cache : false;
}
$wait_seconds -= self::WAIT_STEP;
sleep(self::WAIT_STEP);
}
return $cache;
}
return $this->_getCache($name, $store_locally);
}
/**
* Returns value from cache
*
* @param string $name
* @param bool $store_locally store data locally after retrieved
* @return mixed
*/
function _getCache($name, $store_locally = true)
{
$name = $this->prepareKeyName($name);
if ($store_locally) {
if ( array_key_exists($name, $this->_localStorage) ) {
if ( $this->displayCacheStatistics ) {
$this->setStatistics($name, $this->_localStorage[$name]);
}
return $this->_localStorage[$name];
}
}
$res = $this->_handler->get($name);
if ($this->debugCache) {
// don't display subsequent serial cache retrievals (ones, that are part of keys)
if (is_array($res)) {
$this->Application->Debugger->appendHTML('Restoring key "' . $name . '". Type: ' . gettype($res) . '.');
}
else {
$res_display = strip_tags($res);
if (strlen($res_display) > 200) {
$res_display = substr($res_display, 0, 50) . ' ...';
}
$this->Application->Debugger->appendHTML('Restoring key "' . $name . '" resulted [' . $res_display . ']');
}
}
if ( $store_locally && ($res !== false) ) {
$this->_localStorage[$name] = $res;
if ( $this->displayCacheStatistics ) {
$this->setStatistics($name, $res);
}
}
return $res;
}
/**
* Deletes value from cache
*
* @param string $name
* @return mixed
*/
function delete($name)
{
$name = $this->prepareKeyName($name);
unset($this->_localStorage[$name]);
return $this->_handler->delete($name);
}
/**
* Reset's all memory cache at once
*/
function reset()
{
// don't check for enabled, because we maybe need to reset cache anyway
if ($this->cachingType == CACHING_TYPE_TEMPORARY) {
return ;
}
$site_key = $this->_cachePrefix(true);
$this->_handler->set($site_key, $this->_handler->get($site_key) + 1);
}
/**
* Replaces serials and adds unique site prefix to cache variable name
*
* @param string $name
* @return string
*/
protected function prepareKeyName($name)
{
if ( $this->cachingType == CACHING_TYPE_TEMPORARY ) {
return $name;
}
// add site-wide prefix to key
return $this->_cachePrefix() . $name;
}
/**
* Replaces serials within given string
*
* @param string $value
* @return string
* @access protected
*/
protected function replaceSerials($value)
{
if ( preg_match_all('/\[%(.*?)%\]/', $value, $regs) ) {
// [%LangSerial%] - prefix-wide serial in case of any change in "lang" prefix
// [%LangIDSerial:5%] - one id-wide serial in case of data, associated with given id was changed
// [%CiIDSerial:ItemResourceId:5%] - foreign key-based serial in case of data, associated with given foreign key was changed
foreach ($regs[1] as $serial_name) {
$value = str_replace('[%' . $serial_name . '%]', '[' . $serial_name . '=' . $this->_getCache($serial_name, true) . ']', $value);
}
}
return $value;
}
/**
* Returns site-wide caching prefix
*
* @param bool $only_site_key_name
* @return string
*/
function _cachePrefix($only_site_key_name = false)
{
if ($only_site_key_name) {
return $this->siteKeyName;
}
if ( !isset($this->siteKeyValue) ) {
$this->siteKeyValue = $this->_handler->get($this->siteKeyName);
if (!$this->siteKeyValue) {
$this->siteKeyValue = 1;
$this->_handler->set($this->siteKeyName, $this->siteKeyValue);
}
}
return "{$this->siteKeyName}:{$this->siteKeyValue}:";
}
function setStatistics($name, $found)
{
if (strpos($name, ']:') !== false) {
list ($cache_name, $name) = explode(']:', $name, 2);
}
else {
$cache_name = '-';
}
if (!array_key_exists($cache_name, $this->statistics)) {
$this->statistics[$cache_name] = Array ();
}
if (!array_key_exists($name, $this->statistics[$cache_name])) {
$this->statistics[$cache_name][$name] = Array ();
}
$status_key = $found ? 'found' : 'not_found';
if (!isset($this->statistics[$cache_name][$name][$status_key])) {
$this->statistics[$cache_name][$name][$status_key] = 0;
}
$this->statistics[$cache_name][$name][$status_key]++;
}
/**
* Returns storage size in bytes
*
* @return int
*/
function getStorageSize()
{
return strlen( serialize($this->_localStorage) );
}
function printStatistics()
{
$cache_size = $this->getStorageSize();
$this->Application->Debugger->appendHTML('Cache Size: ' . kUtil::formatSize($cache_size) . ' (' . $cache_size . ')');
foreach ($this->statistics as $cache_name => $cache_data) {
foreach ($cache_data as $key => $value) {
if (!array_key_exists('found', $value) || $value['found'] == 1) {
// remove cached records, that were used only 1 or 2 times
unset($this->statistics[$cache_name][$key]);
}
}
}
kUtil::print_r($this->statistics, 'Cache Statistics:');
}
}
class FakeCacheHandler {
var $cachingType = CACHING_TYPE_TEMPORARY;
function FakeCacheHandler()
{
}
/**
* Retrieves value from cache
*
* @param string $name
* @return mixed
*/
function get($name)
{
return false;
}
/**
* Stores value in cache
*
* @param string $name
* @param mixed $value
* @param int $expiration
* @return bool
*/
function set($name, $value, $expiration = 0)
{
return true;
}
/**
* Deletes key from cach
*
* @param string $name
* @return bool
*/
function delete($name)
{
return true;
}
/**
* Determines, that cache storage is working fine
*
* @return bool
*/
function isWorking()
{
return true;
}
}
class MemcacheCacheHandler {
var $_enabled = false;
/**
* Memcache connection
*
* @var Memcache
*/
var $_handler = null;
var $cachingType = CACHING_TYPE_MEMORY;
function MemcacheCacheHandler()
{
$vars = kUtil::getConfigVars();
if ( array_key_exists('MemcacheServers', $vars) ) {
// for advanced users, who want to save one SQL on each page load
$memcached_servers = $vars['MemcacheServers'];
}
else {
$application =& kApplication::Instance();
$memcached_servers = $application->ConfigValue('MemcacheServers');
}
if ($memcached_servers && class_exists('Memcache')) {
$this->_enabled = true;
$this->_handler = new Memcache();
$servers = explode(';', $memcached_servers);
foreach ($servers as $server) {
if ( preg_match('/(.*):([\d]+)$/', $server, $regs) ) {
// "hostname:port" OR "unix:///path/to/socket:0"
$server = $regs[1];
$port = $regs[2];
}
else {
$port = 11211;
}
$this->_handler->addServer($server, $port);
}
// verify, that memcache server is working
if (!$this->_handler->set('test', 1)) {
$this->_enabled = false;
}
}
}
/**
* Retrieves value from cache
*
* @param string $name
* @return mixed
*/
function get($name)
{
return $this->_handler->get($name);
}
/**
* Stores value in cache
*
* @param string $name
* @param mixed $value
* @param int $expiration
* @return bool
*/
function set($name, $value, $expiration = 0)
{
// 0 - don't use compression
return $this->_handler->set($name, $value, 0, $expiration);
}
/**
* Deletes key from cache
*
* @param string $name
* @return bool
*/
function delete($name)
{
return $this->_handler->delete($name, 0);
}
/**
* Determines, that cache storage is working fine
*
* @return bool
*/
function isWorking()
{
return $this->_enabled;
}
}
class ApcCacheHandler {
var $_enabled = false;
var $cachingType = CACHING_TYPE_MEMORY;
function ApcCacheHandler()
{
$this->_enabled = function_exists('apc_fetch');
// verify, that apc is working
if ($this->_enabled && !$this->set('test', 1)) {
$this->_enabled = false;
}
}
/**
* Retrieves value from cache
*
* @param string $name
* @return mixed
*/
function get($name)
{
return apc_fetch($name);
}
/**
* Stores value in cache
*
* @param string $name
* @param mixed $value
* @param int $expiration
* @return bool
*/
function set($name, $value, $expiration = 0)
{
return apc_store($name, $value, $expiration);
}
/**
* Deletes key from cache
*
* @param string $name
* @return bool
*/
function delete($name)
{
return apc_delete($name);
}
/**
* Determines, that cache storage is working fine
*
* @return bool
*/
function isWorking()
{
return $this->_enabled;
}
}
class XCacheCacheHandler {
var $_enabled = false;
var $cachingType = CACHING_TYPE_MEMORY;
function XCacheCacheHandler()
{
$this->_enabled = function_exists('xcache_get');
// verify, that xcache is working
if ($this->_enabled && !$this->set('test', 1)) {
$this->_enabled = false;
}
}
/**
* Retrieves value from cache
*
* @param string $name
* @return mixed
*/
function get($name)
{
return xcache_isset($name) ? xcache_get($name) : false;
}
/**
* Stores value in cache
*
* @param string $name
* @param mixed $value
* @param int $expiration
* @return bool
*/
function set($name, $value, $expiration = 0)
{
return xcache_set($name, $value, $expiration);
}
/**
* Deletes key from cache
*
* @param string $name
* @return bool
*/
function delete($name)
{
return xcache_unset($name);
}
/**
* Determines, that cache storage is working fine
*
* @return bool
*/
function isWorking()
{
return $this->_enabled;
}
}