Index: install/install_data.sql =================================================================== --- install/install_data.sql (revision 13168) +++ install/install_data.sql (working copy) @@ -177,6 +177,8 @@ INSERT INTO ConfigurationValues VALUES (DEFAULT, 'CSVExportEncoding', '0', 'In-Portal', 'in-portal:configure_advanced'); INSERT INTO ConfigurationAdmin VALUES ('MemcacheServers', 'la_section_SettingsCaching', 'la_config_MemcacheServers', 'text', '', '', 80.01, 0, 0); INSERT INTO ConfigurationValues VALUES (DEFAULT, 'MemcacheServers', 'localhost:11211', 'In-Portal', 'in-portal:configure_advanced'); +INSERT INTO ConfigurationAdmin VALUES ('CacheHandler', 'la_section_SettingsCaching', 'la_config_CacheHandler', 'select', NULL, 'Fake=la_None,Memcache=+Memcached,Apc=+Alternative PHP Cache', 80.02, 0, 0); +INSERT INTO ConfigurationValues VALUES (DEFAULT, 'CacheHandler', 'Fake', 'In-Portal', 'in-portal:configure_advanced'); # Section "in-portal:configure_users": INSERT INTO ConfigurationAdmin VALUES ('User_Allow_New', 'la_title_General', 'la_users_allow_new', 'radio', '', '1=la_opt_UserInstantRegistration,2=la_opt_UserNotAllowedRegistration,3=la_opt_UserUponApprovalRegistration,4=la_opt_UserEmailActivation', 10.01, 0, 1); Index: install/upgrades.sql =================================================================== --- install/upgrades.sql (revision 13168) +++ install/upgrades.sql (working copy) @@ -1681,4 +1681,7 @@ KEY Prefixes (Prefixes), KEY Cached (Cached), KEY LifeTime (LifeTime) -); \ No newline at end of file +); + +INSERT INTO ConfigurationAdmin VALUES ('CacheHandler', 'la_section_SettingsCaching', 'la_config_CacheHandler', 'select', NULL, 'Fake=la_None,Memcache=+Memcached,Apc=+Alternative PHP Cache', 80.02, 0, 0); +INSERT INTO ConfigurationValues VALUES (DEFAULT, 'CacheHandler', 'Fake', 'In-Portal', 'in-portal:configure_advanced'); \ No newline at end of file Index: kernel/utility/cache.php =================================================================== --- kernel/utility/cache.php (revision 13168) +++ kernel/utility/cache.php (working copy) @@ -14,16 +14,34 @@ defined('FULL_PATH') or die('restricted access!'); + /** + * Manager of all implemented caching handlers + * + */ class kCache extends kBase { /** - * Object, that represents cache storage + * Object of cache handler * - * @var CacheStorage + * @var FakeCacheHandler */ - var $_storage = null; + 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 @@ -37,81 +55,207 @@ */ var $debugCache = false; + /** + * Displays cache usage statistics + * + * @var bool + */ + var $displayCacheStatistics = false; + function kCache() { parent::kBase(); - $this->debugCache = defined('DBG_CACHE') && DBG_CACHE && $this->Application->isDebugMode(); - - if (class_exists('Memcache')) { - $this->_storage = new MemcacheCacheStorage(); + // get cache handler class to use + if (array_key_exists('CacheHandler', $GLOBALS['vars']) && $GLOBALS['vars']['CacheHandler']) { + // for advanced users, who want to save one SQL on each page load + $handler_class = $GLOBALS['vars']['CacheHandler'] . 'CacheHandler'; } else { - $this->_storage = new CacheStorage(); + $handler_class = $this->Application->ConfigValue('CacheHandler') . 'CacheHandler'; } - if (!$this->_storage->isWorking()) { - // when one of above cache storages fails to initialize fallback to memory cache - $this->_storage = new CacheStorage(); + // 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(); } /** - * Adds new value to cache $cache_name and identified by key $name + * Returns caching type of current storage engine * - * @param int $name key name to add to cache - * @param mixed $value value of chached record - * @param int $expires expiration + * @return int */ - function setCache($name, $value, $expires = 0) + function getCachingType() { - return $this->_storage->set($name, $value, $expires); + return $this->cachingType; } - function reset() + + /** + * Stores value to cache + * + * @param string $name + * @param mixed $value + * @param int $expires cache record expiration time in seconds + */ + function setCache($name, $value, $expiration) { - return $this->_storage->reset(); + $name = $this->prepareKeyName($name); + $this->_localStorage[$name] = $value; + + return $this->_handler->set($name, $value, $expiration); } /** - * Returns cached $name value from cache named $cache_name + * Returns value from cache * - * @param int $name key name from cache + * @param string $name * @param bool $store_locally store data locally after retrieved + * @param bool $replace_serials * @return mixed */ - function getCache($name, $store_locally = true) + function getCache($name, $store_locally = true, $replace_serials = true) { - $ret = $this->_storage->get($name, $store_locally); + $name = $this->prepareKeyName($name, $replace_serials); - if ($this->debugCache && $store_locally) { - $this->setStatistics($name, $ret); + if ($store_locally) { + if (array_key_exists($name, $this->_localStorage)) { + if ($this->displayCacheStatistics) { + $this->setStatistics($name, $this->_localStorage[$name]); + } + + return $this->_localStorage[$name]; + } } - return $ret; + $res = $this->_handler->get($name); + + if ($replace_serials && $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 cached $name value from cache named $cache_name + * Deletes value from cache * - * @param int $name key name from cache + * @param string $name * @return mixed */ function delete($name) { - $this->_storage->delete($name); + $name = $this->prepareKeyName($name); + unset($this->_localStorage[$name]); + + return $this->_handler->delete($name); } /** - * Returns caching type of current storage engine + * 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 * - * @return int + * @param string $name + * @param bool $replace_serials + * @return string */ - function getCachingType() + function prepareKeyName($name, $replace_serials = true) { - return $this->_storage->cachingType; + if ($this->cachingType == CACHING_TYPE_TEMPORARY) { + return $name; + } + + // replace serials in key name + if ($replace_serials && preg_match_all('/\[%(.*?)%\]/', $name, $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) { + $name = str_replace('[%' . $serial_name . '%]', '[' . $serial_name . '=' . $this->getCache($serial_name, true, false) . ']', $name); + } + } + + // add site-wide prefix to key + return $this->_cachePrefix() . $name; } + /** + * Returns site-wide caching prefix + * + * @param bool $only_site_key_name + * @return string + */ + function _cachePrefix($only_site_key_name = false) + { + // don't use SERVER_NAME here, because it may be cron, or command line request also + $site_key = 'site_serial:' . crc32(FULL_PATH); + + if ($only_site_key_name) { + return $site_key; + } + + $site_serial = $this->_handler->get($site_key); + + if (!$site_serial) { + $site_serial = 1; + $this->_handler->set($site_key, $site_serial); + } + + return "$site_key:$site_serial:"; + } + function setStatistics($name, $found) { if (strpos($name, ']:') !== false) { @@ -138,9 +282,19 @@ $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->_storage->getStorageSize(); + $cache_size = $this->getStorageSize(); $this->Application->Debugger->appendHTML('Cache Size: ' . formatSize($cache_size) . ' (' . $cache_size . ')'); @@ -157,105 +311,66 @@ } } - class CacheStorage extends Params { + class FakeCacheHandler { + var $cachingType = CACHING_TYPE_TEMPORARY; - /** - * Returns storage size in bytes - * - * @return int - */ - function getStorageSize() + function FakeCacheHandler() { - return strlen( serialize($this->_Params) ); + } /** - * Determines, that cache storage is working fine + * Retrieves value from cache * - * @return bool + * @param string $name + * @return mixed */ - function isWorking() + function get($name) { - return true; + return false; } - function _getKeyInfo($name) - { - if (strpos($name, ':') !== false) { - list ($cache_name, $name) = explode(':', $name, 2); - } - else { - $cache_name = '-'; - } - - return Array ($cache_name, $name); - } - /** - * Stores value to cache + * Stores value in cache * * @param string $name * @param mixed $value - * @param int $expires cache record expiration time in seconds + * @param int $expiration + * @return bool */ - function set($name, $value, $expires) + function set($name, $value, $expiration = 0) { - list ($cache_name, $name) = $this->_getKeyInfo($name); - - $cache = parent::Get($cache_name, Array()); - $cache[$name] = $value; - - parent::Set($cache_name, $cache); + return true; } /** - * Returns value from cache + * Deletes key from cach * * @param string $name - * @param bool $store_locally store data locally after retrieved - * @return mixed + * @return bool */ - function get($name, $store_locally = false) + function delete($name) { - list ($cache_name, $name) = $this->_getKeyInfo($name); - - $cache = parent::Get($cache_name, Array()); - $ret = array_key_exists($name, $cache) ? $cache[$name] : false; - - return $ret; + return true; } /** - * Deletes value from cache + * Determines, that cache storage is working fine * - * @param string $name - * @return mixed + * @return bool */ - function delete($name) + function isWorking() { - list ($cache_name, $name) = $this->_getKeyInfo($name); - - $this->set($cache_name, $name, false, -3600); + return true; } - - function reset() - { - - } } - class MemcacheCacheStorage extends kBase { - var $cachingType = CACHING_TYPE_MEMORY; + class MemcacheCacheHandler { - /** - * Part of what we retrieve will be stored locally (per script run) not to bother memcache a lot - * - * @var Array - */ - var $_localStorage = Array (); + var $_enabled = false; /** * Memcache connection @@ -264,14 +379,10 @@ */ var $_handler = null; - var $_enabled = false; + var $cachingType = CACHING_TYPE_MEMORY; - var $_debugMode = false; - - function MemcacheCacheStorage() + function MemcacheCacheHandler() { - parent::kBase(); - if (array_key_exists('MemcacheServers', $GLOBALS['vars'])) { // for advanced users, who want to save one SQL on each page load $memcached_servers = $GLOBALS['vars']['MemcacheServers']; @@ -290,57 +401,47 @@ $this->_handler->addServer($server, $port); } - // try to set something to cache, if not working - set $this->Memcached to null + // verify, that memcache server is working if (!$this->_handler->set('test', 1)) { - $this->_handler = null; $this->_enabled = false; } } + } - $this->_debugMode = $this->Application->isDebugMode(); - - if ($this->_debugMode) { - $this->Application->Debugger->appendHTML('MemCache Enabled: ' . ($this->_enabled ? 'YES' : 'NO')); - } + /** + * Retrieves value from cache + * + * @param string $name + * @return mixed + */ + function get($name) + { + return $this->_handler->get($name); } /** - * Returns storage size in bytes + * Stores value in cache * - * @return int + * @param string $name + * @param mixed $value + * @param int $expiration + * @return bool */ - function getStorageSize() + function set($name, $value, $expiration = 0) { - return strlen( serialize($this->_localStorage) ); + // 0 - don't use compression + return $this->_handler->set($name, $value, 0, $expiration); } /** - * Returns site-wide caching prefix + * Deletes key from cache * - * @param bool $only_site_key_name - * @return string + * @param string $name + * @return bool */ - function _cachePrefix($only_site_key_name = false) + function delete($name) { - if (!$this->_enabled) { - return false; - } - - // don't use SERVER_NAME here, because it may be cron, or command line request also - $site_key = 'site_serial:' . crc32(FULL_PATH); - - if ($only_site_key_name) { - return $site_key; - } - - $site_serial = $this->_handler->get($site_key); - - if (!$site_serial) { - $site_serial = 1; - $this->_handler->set($site_key, $site_serial); - } - - return "$site_key:$site_serial:"; + return $this->_handler->delete($name); } /** @@ -352,121 +453,67 @@ { return $this->_enabled; } + } - /** - * Stores value to cache - * - * @param string $name - * @param mixed $value - * @param int $expires cache record expiration time in seconds - */ - function set($name, $value, $expiration) + + class ApcCacheHandler { + + var $_enabled = false; + + var $cachingType = CACHING_TYPE_MEMORY; + + function ApcCacheHandler() { - if (!$this->_enabled) { - return false; + $this->_enabled = function_exists('apc_fetch'); + + // verify, that apc is working + if ($this->_enabled && !$this->set('test', 1)) { + $this->_enabled = false; } - - $name = $this->prepareKeyName($name); - unset($this->_localStorage[$name]); - - // 0 - don't use compression - return $this->_handler->set($name, $value, 0, $expiration); } /** - * Returns value from cache + * Retrieves value from cache * * @param string $name - * @param bool $store_locally store data locally after retrieved - * @param bool $replace_serials * @return mixed */ - function get($name, $store_locally = true, $replace_serials = true) + function get($name) { - if (!$this->_enabled) { - return false; - } - - $name = $this->prepareKeyName($name, $replace_serials); - - if ($store_locally) { - if (array_key_exists($name, $this->_localStorage)) { - return $this->_localStorage[$name]; - } - } - - $res = $this->_handler->get($name); - - if ($replace_serials && $this->_debugMode) { - // 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 . ']'); - } - } - - $this->_localStorage[$name] = $res; - - return $res; + return apc_fetch($name); } /** - * Replaces serials in cache variable name + * Stores value in cache * * @param string $name - * @param bool $replace_serials - * @return string + * @param mixed $value + * @param int $expiration + * @return bool */ - function prepareKeyName($name, $replace_serials = true) + function set($name, $value, $expiration = 0) { - // replace serials in key name - if ($replace_serials && preg_match_all('/\[%(.*?)%\]/', $name, $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) { - $name = str_replace('[%' . $serial_name . '%]', '[' . $serial_name . '=' . $this->get($serial_name, true, false) . ']', $name); - } - } - - // add site-wide prefix to key - return $this->_cachePrefix() . $name; + return apc_store($name, $value, $expiration); } /** - * Deletes value from cache + * Deletes key from cache * * @param string $name - * @return mixed + * @return bool */ function delete($name) { - if (!$this->_enabled) { - return false; - } - - $name = $this->prepareKeyName($name); - unset($this->_localStorage[$name]); - - return $this->_handler->delete($name); + return apc_delete($name); } /** - * Reset's all memory cache at once + * Determines, that cache storage is working fine + * + * @return bool */ - function reset() + function isWorking() { - // don't check for enabled, because we maybe need to reset cache anyway - $site_key = $this->_cachePrefix(true); - - $this->_handler->set($site_key, $this->_handler->get($site_key) + 1); + return $this->_enabled; } - } + } \ No newline at end of file