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; } }