Page MenuHomeIn-Portal Phabricator

in-portal
No OneTemporary

File Metadata

Created
Thu, Oct 30, 11:13 AM

in-portal

Index: branches/5.2.x/core/kernel/managers/cache_manager.php
===================================================================
--- branches/5.2.x/core/kernel/managers/cache_manager.php (revision 15094)
+++ branches/5.2.x/core/kernel/managers/cache_manager.php (revision 15095)
@@ -1,802 +1,813 @@
<?php
/**
* @version $Id$
* @package In-Portal
* @copyright Copyright (C) 1997 - 2011 Intechnic. All rights reserved.
* @license GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/
defined('FULL_PATH') or die('restricted access!');
class kCacheManager extends kBase implements kiCacheable {
/**
* Used variables from SystemSettings table
*
* @var Array
* @access protected
*/
protected $configVariables = Array();
/**
* Used variables from SystemSettings table retrieved from unit cache
*
* @var Array
* @access protected
*/
protected $originalConfigVariables = Array ();
/**
* IDs of config variables used in current run (for caching)
*
* @var Array
* @access protected
*/
protected $configIDs = Array ();
/**
* IDs of config variables retrieved from unit cache
*
* @var Array
* @access protected
*/
protected $originalConfigIDs = Array ();
/**
* Object of memory caching class
*
* @var kCache
* @access protected
*/
protected $cacheHandler = null;
protected $temporaryCache = Array (
'registerAggregateTag' => Array (),
'registerScheduledTask' => Array (),
'registerHook' => Array (),
'registerBuildEvent' => Array (),
'registerAggregateTag' => Array (),
);
/**
* Name of database table, where configuration settings are stored
*
* @var string
* @access protected
*/
protected $settingTableName = '';
/**
* Set's references to kApplication and kDBConnection class instances
*
* @param kApplication $application
* @access public
*/
public function __construct($application = null)
{
parent::__construct($application);
$this->settingTableName = TABLE_PREFIX . 'SystemSettings';
if ( defined('IS_INSTALL') && IS_INSTALL ) {
// table substitution required, so "root" can perform login to upgrade to 5.2.0, where setting table was renamed
if ( !$this->Application->TableFound(TABLE_PREFIX . 'SystemSettings') ) {
$this->settingTableName = TABLE_PREFIX . 'ConfigurationValues';
}
}
}
/**
* Creates caching manager instance
*
* @access public
*/
public function InitCache()
{
$this->cacheHandler =& $this->Application->makeClass('kCache');
}
/**
* Returns cache key, used to cache phrase and configuration variable IDs used on current page
*
* @return string
* @access protected
*/
protected function getCacheKey()
{
// TODO: maybe language part isn't required, since same phrase from different languages have one ID now
return $this->Application->GetVar('t') . $this->Application->GetVar('m_theme') . $this->Application->GetVar('m_lang') . $this->Application->isAdmin;
}
/**
* Loads phrases and configuration variables, that were used on this template last time
*
* @access public
*/
public function LoadApplicationCache()
{
$phrase_ids = $config_ids = Array ();
$sql = 'SELECT PhraseList, ConfigVariables
FROM ' . TABLE_PREFIX . 'PhraseCache
WHERE Template = ' . $this->Conn->qstr( md5($this->getCacheKey()) );
$res = $this->Conn->GetRow($sql);
if ($res) {
if ( $res['PhraseList'] ) {
$phrase_ids = explode(',', $res['PhraseList']);
}
if ( $res['ConfigVariables'] ) {
$config_ids = array_diff( explode(',', $res['ConfigVariables']), $this->originalConfigIDs);
}
}
$this->Application->Phrases->Init('phrases', '', null, $phrase_ids);
$this->configIDs = $this->originalConfigIDs = $config_ids;
$this->InitConfig();
}
/**
* Updates phrases and configuration variables, that were used on this template
*
* @access public
*/
public function UpdateApplicationCache()
{
$update = false;
//something changed
$update = $update || $this->Application->Phrases->NeedsCacheUpdate();
$update = $update || (count($this->configIDs) && $this->configIDs != $this->originalConfigIDs);
if ($update) {
$fields_hash = Array (
'PhraseList' => implode(',', $this->Application->Phrases->Ids),
'CacheDate' => adodb_mktime(),
'Template' => md5( $this->getCacheKey() ),
'ConfigVariables' => implode(',', array_unique($this->configIDs)),
);
$this->Conn->doInsert($fields_hash, TABLE_PREFIX . 'PhraseCache', 'REPLACE');
}
}
/**
* Loads configuration variables, that were used on this template last time
*
* @access protected
*/
protected function InitConfig()
{
if (!$this->originalConfigIDs) {
return ;
}
$sql = 'SELECT VariableValue, VariableName
FROM ' . $this->settingTableName . '
WHERE VariableId IN (' . implode(',', $this->originalConfigIDs) . ')';
$config_variables = $this->Conn->GetCol($sql, 'VariableName');
$this->configVariables = array_merge($this->configVariables, $config_variables);
}
/**
* Returns configuration option value by name
*
* @param string $name
* @return string
* @access public
*/
public function ConfigValue($name)
{
$site_domain_override = Array (
'DefaultEmailSender' => 'AdminEmail',
'DefaultEmailRecipients' => 'DefaultEmailRecipients',
);
if ( isset($site_domain_override[$name]) ) {
$res = $this->Application->siteDomainField($site_domain_override[$name]);
if ( $res ) {
return $res;
}
}
if ( array_key_exists($name, $this->configVariables) ) {
return $this->configVariables[$name];
}
if ( defined('IS_INSTALL') && IS_INSTALL && !$this->Application->TableFound($this->settingTableName, true) ) {
return false;
}
$this->Conn->nextQueryCachable = true;
$sql = 'SELECT VariableId, VariableValue
FROM ' . $this->settingTableName . '
WHERE VariableName = ' . $this->Conn->qstr($name);
$res = $this->Conn->GetRow($sql);
if ( $res !== false ) {
$this->configIDs[] = $res['VariableId'];
$this->configVariables[$name] = $res['VariableValue'];
return $res['VariableValue'];
}
trigger_error('Usage of undefined configuration variable "<strong>' . $name . '</strong>"', E_USER_NOTICE);
return false;
}
/**
* Changes value of individual configuration variable (+resets cache, when needed)
*
* @param string $name
* @param string $value
* @param bool $local_cache_only
* @return string
* @access public
*/
public function SetConfigValue($name, $value, $local_cache_only = false)
{
$this->configVariables[$name] = $value;
if ( $local_cache_only ) {
return;
}
$fields_hash = Array ('VariableValue' => $value);
$this->Conn->doUpdate($fields_hash, $this->settingTableName, 'VariableName = ' . $this->Conn->qstr($name));
if ( array_key_exists($name, $this->originalConfigVariables) && $value != $this->originalConfigVariables[$name] ) {
$this->DeleteUnitCache();
}
}
/**
* Loads data, that was cached during unit config parsing
*
* @return bool
* @access public
*/
public function LoadUnitCache()
{
if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) {
$data = $this->Application->getCache('master:configs_parsed', false, CacheSettings::$unitCacheRebuildTime);
}
else {
$data = $this->Application->getDBCache('configs_parsed', CacheSettings::$unitCacheRebuildTime);
}
if ( $data ) {
$cache = unserialize($data); // 126 KB all modules
unset($data);
$this->Application->InitManagers();
$this->Application->setFromCache($cache);
$aggregator =& $this->Application->recallObject('TagsAggregator', 'kArray');
/* @var $aggregator kArray */
$aggregator->setFromCache($cache);
$this->setFromCache($cache);
unset($cache);
return true;
}
if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) {
$this->Application->rebuildCache('master:configs_parsed', kCache::REBUILD_NOW, CacheSettings::$unitCacheRebuildTime);
}
else {
$this->Application->rebuildDBCache('configs_parsed', kCache::REBUILD_NOW, CacheSettings::$unitCacheRebuildTime);
}
return false;
}
/**
* Empties factory and event manager cache (without storing changes)
*/
public function EmptyUnitCache()
{
// maybe discover keys automatically from corresponding classes
$cache_keys = Array (
'Factory.Files', 'Factory.realClasses', 'Factory.Dependencies',
'ConfigReader.prefixFiles',
'EventManager.beforeHooks', 'EventManager.afterHooks', 'EventManager.scheduledTasks', 'EventManager.buildEvents',
'Application.ReplacementTemplates', 'Application.RewriteListeners', 'Application.ModuleInfo',
'Application.ConfigHash', 'Application.ConfigCacheIds',
);
$empty_cache = Array ();
foreach ($cache_keys as $cache_key) {
$empty_cache[$cache_key] = Array ();
}
$this->Application->setFromCache($empty_cache);
$this->setFromCache($empty_cache);
// otherwise ModulesHelper indirectly used from includeConfigFiles won't work
$this->Application->RegisterDefaultClasses();
}
/**
* Updates data, that was parsed from unit configs this time
*
* @access public
*/
public function UpdateUnitCache()
{
$aggregator =& $this->Application->recallObject('TagsAggregator', 'kArray');
/* @var $aggregator kArray */
$this->preloadConfigVars(); // preloading will put to cache
$cache = array_merge(
$this->Application->getToCache(),
$aggregator->getToCache(),
$this->getToCache()
);
$cache_rebuild_by = SERVER_NAME . ' (' . getenv('REMOTE_ADDR') . ') - ' . adodb_date('d/m/Y H:i:s');
if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) {
$this->Application->setCache('master:configs_parsed', serialize($cache));
$this->Application->setCache('master:last_cache_rebuild', $cache_rebuild_by);
}
else {
$this->Application->setDBCache('configs_parsed', serialize($cache));
$this->Application->setDBCache('last_cache_rebuild', $cache_rebuild_by);
}
}
public function delayUnitProcessing($method, $params)
{
if ($this->Application->InitDone) {
// init already done -> call immediately (happens during installation)
$function = Array (&$this->Application, $method);
call_user_func_array($function, $params);
return ;
}
$this->temporaryCache[$method][] = $params;
}
public function applyDelayedUnitProcessing()
{
foreach ($this->temporaryCache as $method => $method_calls) {
$function = Array (&$this->Application, $method);
foreach ($method_calls as $method_call) {
call_user_func_array($function, $method_call);
}
$this->temporaryCache[$method] = Array ();
}
}
/**
* Deletes all data, that was cached during unit config parsing (excluding unit config locations)
*
* @param Array $config_variables
* @access public
*/
public function DeleteUnitCache($config_variables = null)
{
if ( isset($config_variables) && !array_intersect(array_keys($this->originalConfigVariables), $config_variables) ) {
// prevent cache reset, when given config variables are not in unit cache
return;
}
if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) {
$this->Application->rebuildCache('master:configs_parsed', kCache::REBUILD_LATER, CacheSettings::$unitCacheRebuildTime);
}
else {
$this->Application->rebuildDBCache('configs_parsed', kCache::REBUILD_LATER, CacheSettings::$unitCacheRebuildTime);
}
}
/**
* Deletes cached section tree, used during permission checking and admin console tree display
*
* @return void
* @access public
*/
public function DeleteSectionCache()
{
if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) {
$this->Application->rebuildCache('master:sections_parsed', kCache::REBUILD_LATER, CacheSettings::$sectionsParsedRebuildTime);
}
else {
$this->Application->rebuildDBCache('sections_parsed', kCache::REBUILD_LATER, CacheSettings::$sectionsParsedRebuildTime);
}
}
/**
* Preloads 21 widely used configuration variables, so they will get to cache for sure
*
* @access protected
*/
protected function preloadConfigVars()
{
$config_vars = Array (
// session related
'SessionTimeout', 'SessionCookieName', 'SessionCookieDomains', 'SessionBrowserSignatureCheck',
'SessionIPAddressCheck', 'CookieSessions', 'KeepSessionOnBrowserClose', 'User_GuestGroup',
'User_LoggedInGroup', 'RegistrationUsernameRequired',
// output related
'UseModRewrite', 'UseContentLanguageNegotiation', 'UseOutputCompression', 'OutputCompressionLevel',
'Config_Site_Time', 'SystemTagCache',
// tracking related
'UseChangeLog', 'UseVisitorTracking', 'ModRewriteUrlEnding', 'ForceModRewriteUrlEnding',
'RunScheduledTasksFromCron',
);
$escaped_config_vars = $this->Conn->qstrArray($config_vars);
$sql = 'SELECT VariableId, VariableName, VariableValue
FROM ' . $this->settingTableName . '
WHERE VariableName IN (' . implode(',', $escaped_config_vars) . ')';
$data = $this->Conn->Query($sql, 'VariableId');
foreach ($data as $variable_id => $variable_info) {
$this->configIDs[] = $variable_id;
$this->configVariables[ $variable_info['VariableName'] ] = $variable_info['VariableValue'];
}
}
/**
* Sets data from cache to object
*
* Used for cases, when ConfigValue is called before LoadApplicationCache method (e.g. session init, url engine init)
*
* @param Array $data
* @access public
*/
public function setFromCache(&$data)
{
$this->configVariables = $this->originalConfigVariables = $data['Application.ConfigHash'];
$this->configIDs = $this->originalConfigIDs = $data['Application.ConfigCacheIds'];
}
/**
* Gets object data for caching
* The following caches should be reset based on admin interaction (adjusting config, enabling modules etc)
*
* @access public
* @return Array
*/
public function getToCache()
{
return Array (
'Application.ConfigHash' => $this->configVariables,
'Application.ConfigCacheIds' => $this->configIDs,
// not in use, since it only represents template specific values, not global ones
// 'Application.Caches.ConfigVariables' => $this->originalConfigIDs,
);
}
/**
* Returns caching type (none, memory, temporary)
*
* @param int $caching_type
* @return bool
* @access public
*/
public function isCachingType($caching_type)
{
return $this->cacheHandler->getCachingType() == $caching_type;
}
/**
* Prints caching statistics
*
* @access public
*/
public function printStatistics()
{
$this->cacheHandler->printStatistics();
}
/**
* Returns cached $key value from cache named $cache_name
*
* @param int $key key name from cache
* @param bool $store_locally store data locally after retrieved
* @param int $max_rebuild_seconds
* @return mixed
* @access public
*/
public function getCache($key, $store_locally = true, $max_rebuild_seconds = 0)
{
return $this->cacheHandler->getCache($key, $store_locally, $max_rebuild_seconds);
}
/**
* Adds new value to cache $cache_name and identified by key $key
*
* @param int $key key name to add to cache
* @param mixed $value value of cached record
* @param int $expiration when value expires (0 - doesn't expire)
* @return bool
* @access public
*/
public function setCache($key, $value, $expiration = 0)
{
return $this->cacheHandler->setCache($key, $value, $expiration);
}
/**
* Sets rebuilding mode for given cache
*
* @param string $name
* @param int $mode
* @param int $max_rebuilding_time
* @return void
* @access public
*/
public function rebuildCache($name, $mode = null, $max_rebuilding_time = 0)
{
$this->cacheHandler->rebuildCache($name, $mode, $max_rebuilding_time);
}
/**
* Deletes key from cache
*
* @param string $key
* @return void
* @access public
*/
public function deleteCache($key)
{
$this->cacheHandler->delete($key);
}
/**
* Reset's all memory cache at once
*
* @return void
* @access public
*/
public function resetCache()
{
$this->cacheHandler->reset();
}
/**
* Returns value from database cache
*
* @param string $name key name
* @param int $max_rebuild_seconds
* @return mixed
* @access public
*/
public function getDBCache($name, $max_rebuild_seconds = 0)
{
- if ( $this->_getDBCache($name . '_rebuild') ) {
- // cache rebuild requested -> rebuild now
- $this->deleteDBCache($name . '_rebuild');
-
- return false;
- }
-
// no serials in cache key OR cache is outdated
$wait_seconds = $max_rebuild_seconds;
while (true) {
- $cache = $this->_getDBCache($name);
- $rebuilding = $this->_getDBCache($name . '_rebuilding');
+ $cached_data = $this->_getDBCache(Array ($name, $name . '_rebuilding', $name . '_rebuild'));
+
+ if ( $cached_data[$name . '_rebuild'] ) {
+ // cache rebuild requested -> rebuild now
+ $this->deleteDBCache($name . '_rebuild');
+
+ return false;
+ }
+
+ $cache = $cached_data[$name];
+ $rebuilding = $cached_data[$name . '_rebuilding'];
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 -> return it
return $cache;
}
$wait_seconds -= kCache::WAIT_STEP;
sleep(kCache::WAIT_STEP);
}
return false;
}
/**
* Returns value from database cache
*
- * @param string $name key name
+ * @param string|Array $names key name
* @return mixed
* @access protected
*/
- protected function _getDBCache($name)
+ protected function _getDBCache($names)
{
+ $res = Array ();
+ $names = (array)$names;
$this->Conn->nextQueryCachable = true;
- $sql = 'SELECT Data, Cached, LifeTime
+ $sql = 'SELECT Data, Cached, LifeTime, VarName
FROM ' . TABLE_PREFIX . 'SystemCache
- WHERE VarName = ' . $this->Conn->qstr($name);
- $data = $this->Conn->GetRow($sql);
+ WHERE VarName IN (' . implode(',', $this->Conn->qstrArray($names)) . ')';
+ $cached_data = $this->Conn->Query($sql, 'VarName');
+
+ foreach ($names as $name) {
+ if ( !isset($cached_data[$name]) ) {
+ $res[$name] = false;
+ continue;
+ }
+
+ $lifetime = (int)$cached_data[$name]['LifeTime']; // in seconds
- if ($data) {
- $lifetime = (int)$data['LifeTime']; // in seconds
- if (($lifetime > 0) && ($data['Cached'] + $lifetime < adodb_mktime())) {
+ if ( ($lifetime > 0) && ($cached_data[$name]['Cached'] + $lifetime < adodb_mktime()) ) {
// delete expired
$this->Conn->nextQueryCachable = true;
$sql = 'DELETE FROM ' . TABLE_PREFIX . 'SystemCache
WHERE VarName = ' . $this->Conn->qstr($name);
$this->Conn->Query($sql);
- return false;
+ $res[$name] = false;
+ continue;
}
- return $data['Data'];
+ $res[$name] = $cached_data[$name]['Data'];
}
- return false;
+ return count($res) == 1 ? array_pop($res) : $res;
}
/**
* Sets value to database cache
*
* @param string $name
* @param mixed $value
* @param int|bool $expiration
* @return void
* @access public
*/
public function setDBCache($name, $value, $expiration = false)
{
$this->deleteDBCache($name . '_rebuilding');
$this->_setDBCache($name, $value, $expiration);
}
/**
* Sets value to database cache
*
* @param string $name
* @param mixed $value
* @param int|bool $expiration
* @access protected
*/
protected function _setDBCache($name, $value, $expiration = false)
{
if ((int)$expiration <= 0) {
$expiration = -1;
}
$fields_hash = Array (
'VarName' => $name,
'Data' => &$value,
'Cached' => adodb_mktime(),
'LifeTime' => (int)$expiration,
);
$this->Conn->nextQueryCachable = true;
$this->Conn->doInsert($fields_hash, TABLE_PREFIX . 'SystemCache', 'REPLACE');
}
/**
* Sets rebuilding mode for given cache
*
* @param string $name
* @param int $mode
* @param int $max_rebuilding_time
* @return void
* @access public
*/
public function rebuildDBCache($name, $mode = null, $max_rebuilding_time = 0)
{
if ( !isset($mode) || $mode == kCache::REBUILD_NOW ) {
$this->_setDBCache($name . '_rebuilding', 1, $max_rebuilding_time);
$this->deleteDBCache($name . '_rebuild');
}
elseif ( $mode == kCache::REBUILD_LATER ) {
$this->_setDBCache($name . '_rebuild', 1, 0);
$this->deleteDBCache($name . '_rebuilding');
}
}
/**
* Deletes key from database cache
*
* @param string $name
* @return void
* @access public
*/
public function deleteDBCache($name)
{
$sql = 'DELETE FROM ' . TABLE_PREFIX . 'SystemCache
WHERE VarName = ' . $this->Conn->qstr($name);
$this->Conn->Query($sql);
}
/**
* Increments serial based on prefix and it's ID (optional)
*
* @param string $prefix
* @param int $id ID (value of IDField) or ForeignKeyField:ID
* @param bool $increment
* @return string
* @access public
*/
public function incrementCacheSerial($prefix, $id = null, $increment = true)
{
$pascal_case_prefix = implode('', array_map('ucfirst', explode('-', $prefix)));
$serial_name = $pascal_case_prefix . (isset($id) ? 'IDSerial:' . $id : 'Serial');
if ($increment) {
if (defined('DEBUG_MODE') && DEBUG_MODE && $this->Application->isDebugMode()) {
$this->Application->Debugger->appendHTML('Incrementing serial: <strong>' . $serial_name . '</strong>.');
}
$this->setCache($serial_name, (int)$this->getCache($serial_name) + 1);
if (!defined('IS_INSTALL') || !IS_INSTALL) {
// delete cached mod-rewrite urls related to given prefix and id
$delete_clause = isset($id) ? $prefix . ':' . $id : $prefix;
$sql = 'DELETE FROM ' . TABLE_PREFIX . 'CachedUrls
WHERE Prefixes LIKE ' . $this->Conn->qstr('%|' . $delete_clause . '|%');
$this->Conn->Query($sql);
}
}
return $serial_name;
}
/**
* Returns cached category informaton by given cache name. All given category
* information is recached, when at least one of 4 caches is missing.
*
* @param int $category_id
* @param string $name cache name = {filenames, category_designs, category_tree}
* @return string
* @access public
*/
public function getCategoryCache($category_id, $name)
{
$serial_name = '[%CIDSerial:' . $category_id . '%]';
$cache_key = $name . $serial_name;
$ret = $this->getCache($cache_key);
if ($ret === false) {
if (!$category_id) {
// don't query database for "Home" category (ID = 0), because it doesn't exist in database
return false;
}
// this allows to save 2 sql queries for each category
$this->Conn->nextQueryCachable = true;
$sql = 'SELECT NamedParentPath, CachedTemplate, TreeLeft, TreeRight
FROM ' . TABLE_PREFIX . 'Categories
WHERE CategoryId = ' . (int)$category_id;
$category_data = $this->Conn->GetRow($sql);
if ($category_data !== false) {
// only direct links to category pages work (symlinks, container pages and so on won't work)
$this->setCache('filenames' . $serial_name, $category_data['NamedParentPath']);
$this->setCache('category_designs' . $serial_name, ltrim($category_data['CachedTemplate'], '/'));
$this->setCache('category_tree' . $serial_name, $category_data['TreeLeft'] . ';' . $category_data['TreeRight']);
}
}
return $this->getCache($cache_key);
}
}
\ No newline at end of file
Index: branches/5.2.x/core/kernel/utility/cache.php
===================================================================
--- branches/5.2.x/core/kernel/utility/cache.php (revision 15094)
+++ branches/5.2.x/core/kernel/utility/cache.php (revision 15095)
@@ -1,714 +1,787 @@
<?php
/**
* @version $Id$
* @package In-Portal
* @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/
defined('FULL_PATH') or die('restricted access!');
/**
* Manager of all implemented caching handlers
*
*/
class kCache extends kBase {
/**
* Rebuild cache now
*
*/
const REBUILD_NOW = 1;
/**
* Rebuild cache later
*
*/
const REBUILD_LATER = 2;
/**
* Cache waiting step (in seconds)
*
*/
const WAIT_STEP = 2;
/**
* Object of cache handler
*
* @var FakeCacheHandler
*/
var $_handler = null;
/**
* Part of what we retrieve will be stored locally (per script run) not to bother memcache a lot
*
* @var Array
*/
var $_localStorage = Array ();
/**
* What type of caching is being used
*
* @var int
*/
var $cachingType = CACHING_TYPE_NONE;
/**
* Cache usage statistics (per script run)
*
* @var Array
*/
var $statistics = Array ();
/**
* Debug cache usage
*
* @var bool
*/
var $debugCache = false;
/**
* Displays cache usage statistics
*
* @var bool
*/
var $displayCacheStatistics = false;
/**
* Site key name
* Prepended to each cached key name
*
* @var string
*/
var $siteKeyName = '';
/**
* Site key value
* Prepended to each cached key name
*
* @var string
*/
var $siteKeyValue = null;
/**
* Creates cache manager
*
* @access public
*/
public function __construct()
{
parent::__construct();
$vars = kUtil::getConfigVars();
$this->siteKeyName = 'site_serial:' . crc32(SQL_TYPE . '://' . SQL_USER . ':' . SQL_PASS . '@' . SQL_SERVER . ':' . TABLE_PREFIX . ':' . SQL_DB);
// get cache handler class to use
$handler_class = (isset($vars['CacheHandler']) ? $vars['CacheHandler'] : '') . 'CacheHandler';
// defined cache handler doesn'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 "<strong>' . $handler_class . '</strong>" caching handler.', E_USER_WARNING);
$handler = new FakeCacheHandler();
}
elseif ( $this->Application->isDebugMode() && ($handler->cachingType == CACHING_TYPE_MEMORY) ) {
$this->Application->Debugger->appendHTML('Memory Caching: "<strong>' . $handler_class . '</strong>"');
}
$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') ) {
+ $cached_data = $this->_getCache(Array ($name . '_rebuild', $name . '_serials'), Array (true, true));
+
+ if ( $cached_data[$name . '_rebuild'] ) {
// cache rebuild requested -> rebuild now
$this->delete($name . '_rebuild');
return false;
}
$new_serial = $this->replaceSerials($name);
- $old_serial = $this->_getCache($name . '_serials');
+ $old_serial = $cached_data[$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);
+ $cached_data = $this->_getCache(Array ($name, $name . '_rebuilding'), Array ($store_locally, false));
+ $cache = $cached_data[$name];
+ $rebuilding = $cached_data[$name . '_rebuilding'];
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
+ * @param string|Array $names
+ * @param bool|Array $store_locally store data locally after retrieved
* @return mixed
*/
- function _getCache($name, $store_locally = true)
+ function _getCache($names, $store_locally = true)
{
- $name = $this->prepareKeyName($name);
+ static $request_number = 1;
+
+ $res = Array ();
+ $names = (array)$names;
+ $store_locally = (array)$store_locally;
+ $to_get = $prepared_names = array_map(Array (&$this, 'prepareKeyName'), $names);
+
+ foreach ($prepared_names as $index => $prepared_name) {
+ $name = $names[$index];
- if ($store_locally) {
- if ( array_key_exists($name, $this->_localStorage) ) {
+ if ( $store_locally[$index] && array_key_exists($prepared_name, $this->_localStorage) ) {
if ( $this->displayCacheStatistics ) {
- $this->setStatistics($name, $this->_localStorage[$name]);
+ $this->setStatistics($prepared_name, $this->_localStorage[$prepared_name]);
}
- return $this->_localStorage[$name];
+ $res[$name] = $this->_localStorage[$prepared_name];
+ unset($to_get[$index]);
+ }
+ }
+
+ $multi_res = $this->_handler->get($to_get);
+
+ foreach ($to_get as $index => $prepared_name) {
+ $name = $names[$index];
+
+ if ( array_key_exists($prepared_name, $multi_res) ) {
+ $res[$name] =& $multi_res[$prepared_name];
}
+ else {
+ $res[$name] = false;
+ }
+
+ $this->_postProcessGetCache($prepared_name, $res[$name], $store_locally[$index], $request_number);
}
- $res = $this->_handler->get($name);
+ $request_number++;
- if ($this->debugCache) {
+ return count($res) == 1 ? array_pop($res) : $res;
+ }
+
+ /**
+ * Stores variable in local cache & collects debug info about cache
+ *
+ * @param string $name
+ * @param mixed $res
+ * @param bool $store_locally
+ * @param int $request_number
+ * @return void
+ * @access protected
+ */
+ protected function _postProcessGetCache($name, &$res, $store_locally = true, $request_number)
+ {
+ 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) . '.');
+ if ( is_array($res) ) {
+ $this->Application->Debugger->appendHTML('r' . $request_number . ': Restoring key "' . $name . '". Type: ' . gettype($res) . '.');
}
else {
$res_display = strip_tags($res);
- if (strlen($res_display) > 200) {
+ if ( strlen($res_display) > 200 ) {
$res_display = substr($res_display, 0, 50) . ' ...';
}
- $this->Application->Debugger->appendHTML('Restoring key "' . $name . '" resulted [' . $res_display . ']');
+ $this->Application->Debugger->appendHTML('r' . $request_number . ': 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);
+ $serial_names = $regs[1];
+ $serial_count = count($serial_names);
+ $store_locally = Array ();
+
+ for ($i = 0; $i < $serial_count; $i++) {
+ $store_locally[] = true;
+ }
+
+ $serial_values = $this->_getCache($serial_names, $store_locally);
+
+ if ( !is_array($serial_values) ) {
+ $serial_values[ current($serial_names) ] = $serial_values;
+ }
+
+ foreach ($serial_names as $serial_name) {
+ $value = str_replace('[%' . $serial_name . '%]', '[' . $serial_name . '=' . $serial_values[$serial_name] . ']', $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('<strong>Cache Size:</strong> ' . 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
+ * @param string $names
* @return mixed
*/
- function get($name)
+ function get($names)
{
+ if ( is_array($names) ) {
+ $res = Array ();
+
+ foreach ($names as $name) {
+ $res[$name] = false;
+ }
+
+ return $res;
+ }
+
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($default_servers = '')
{
$vars = kUtil::getConfigVars();
$memcached_servers = isset($vars['MemcacheServers']) ? $vars['MemcacheServers'] : $default_servers;
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
+ * @param string|Array $names
* @return mixed
*/
- function get($name)
+ function get($names)
{
- return xcache_isset($name) ? xcache_get($name) : false;
+ if ( is_array($names) ) {
+ $res = Array ();
+
+ foreach ($names as $name) {
+ $res[$name] = $this->get($name);
+ }
+
+ return $res;
+ }
+
+ return xcache_isset($names) ? xcache_get($names) : 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;
}
}
\ No newline at end of file

Event Timeline