memcached:wcf.acp.option.cache_source_type.memcached
apc:wcf.acp.option.cache_source_type.apc
no:wcf.acp.option.cache_source_type.no]]></selectoptions>
- <enableoptions><![CDATA[disk:!cache_source_memcached_host,!cache_source_memcached_use_pconnect
-memcached:cache_source_memcached_host,cache_source_memcached_use_pconnect
-apc:!cache_source_memcached_host,!cache_source_memcached_use_pconnect
-no:!cache_source_memcached_host,!cache_source_memcached_use_pconnect]]></enableoptions>
+ <enableoptions><![CDATA[disk:!cache_source_memcached_host
+memcached:cache_source_memcached_host
+apc:!cache_source_memcached_host
+no:!cache_source_memcached_host]]></enableoptions>
</option>
<option name="cache_source_memcached_host">
* @var object
*/
_delay: {
- show: 250,
+ show: 800,
hide: 500
},
y: 'top'
};
this._delay = {
- show: 250,
+ show: 800,
hide: 500
};
this._hoverElement = false;
self::getSession()->update();
}
- // close cache source
- if (CacheHandler::isInitialized() && is_object(CacheHandler::getInstance()) && is_object(CacheHandler::getInstance()->getCacheSource())) {
- CacheHandler::getInstance()->getCacheSource()->close();
- }
-
// execute shutdown actions of user storage handler
UserStorageHandler::getInstance()->shutdown();
}
<?php
namespace wcf\system\cache;
+use wcf\system\cache\builder\ICacheBuilder;
use wcf\system\cache\source\DiskCacheSource;
use wcf\system\exception\SystemException;
use wcf\system\SingletonFactory;
+use wcf\util\StringUtil;
/**
- * CacheHandler holds all registered cache resources.
+ * Manages transparent cache access.
*
- * @author Marcel Werk
- * @copyright 2001-2012 WoltLab GmbH
+ * @author Alexander Ebert, Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package com.woltlab.wcf
* @subpackage system.cache
* @category Community Framework
*/
class CacheHandler extends SingletonFactory {
- /**
- * registered cache resources
- * @var array
- */
- protected $cacheResources = array();
-
/**
* cache source object
* @var wcf\system\cache\source\ICacheSource
}
/**
- * Registers a new cache resource.
+ * Flushed cache for given resource.
*
- * @param string $cache name of this resource
- * @param string $file data file for this resource
- * @param string $className
- * @param integer $maxLifetime
+ * @param wcf\system\cache\builder\ICacheBuilder $cacheBuilder
+ * @param array $parameters
*/
- public function addResource($cache, $file, $className, $maxLifetime = 0) {
- $this->cacheResources[$cache] = array(
- 'cache' => $cache,
- 'file' => $file,
- 'className' => $className,
- 'maxLifetime' => $maxLifetime
- );
+ public function flush(ICacheBuilder $cacheBuilder, array $parameters) {
+ $useWildCard = (empty($parameters)) ? false : true;
+ $this->getCacheSource()->flush($this->getCacheName($cacheBuilder), $useWildCard);
}
/**
- * Deletes a registered cache resource.
+ * Returns cached value for given resource, false if no cache exists.
*
- * @param string $cache
+ * @param wcf\system\cache\builder\ICacheBuilder $cacheBuilder
+ * @param array $parameters
+ * @return mixed
*/
- public function clearResource($cache) {
- if (!isset($this->cacheResources[$cache])) {
- throw new SystemException("cache resource '".$cache."' does not exist");
- }
-
- $this->getCacheSource()->delete($this->cacheResources[$cache]);
+ public function get(ICacheBuilder $cacheBuilder, array $parameters) {
+ return $this->getCacheSource()->get($this->getCacheName($cacheBuilder, $parameters), $cacheBuilder->getMaxLifetime());
}
/**
- * Marks cached files as obsolete.
+ * Caches a value for given resource,
*
- * @param string $directory
- * @param string $filepattern
+ * @param wcf\system\cache\builder\ICacheBuilder $cacheBuilder
+ * @param array $parameters
+ * @param array $data
*/
- public function clear($directory, $filepattern) {
- $this->getCacheSource()->clear($directory, $filepattern);
+ public function set(ICacheBuilder $cacheBuilder, array $parameters, array $data) {
+ $this->getCacheSource()->set($this->getCacheName($cacheBuilder, $parameters), $data, $cacheBuilder->getMaxLifetime());
}
/**
- * Returns a cached variable.
+ * Returns cache index hash.
*
- * @param string $cache
- * @param string $variable
- * @return mixed $value
+ * @param array $parameters
+ * @return string
*/
- public function get($cache, $variable = '') {
- if (!isset($this->cacheResources[$cache])) {
- throw new SystemException("unknown cache resource '".$cache."'");
- }
-
- // try to get value
- $value = $this->getCacheSource()->get($this->cacheResources[$cache]);
- if ($value === null) {
- // rebuild cache
- $this->rebuild($this->cacheResources[$cache]);
-
- // try to get value again
- $value = $this->getCacheSource()->get($this->cacheResources[$cache]);
- if ($value === null) {
- throw new SystemException("cache resource '".$cache."' does not exist");
- }
- }
-
- // return value
- if (!empty($variable)) {
- if (!isset($value[$variable])) {
- throw new SystemException("variable '".$variable."' does not exist in cache resource '".$cache."'");
- }
-
- return $value[$variable];
- }
- else {
- return $value;
- }
+ public function getCacheIndex(array $parameters) {
+ return sha1(serialize($this->orderParameters($parameters)));
}
/**
- * Rebuilds a cache resource.
+ * Builds cache name.
*
- * @param array $cacheResource
- * @return boolean result
+ * @param wcf\system\cache\builder\ICacheBuilder $cacheBuilder
+ * @param array $parameters
+ * @return string
*/
- public function rebuild($cacheResource) {
- // instance cache class
- if (!class_exists($cacheResource['className'])) {
- throw new SystemException("Unable to find class '".$cacheResource['className']."'");
- }
-
- // update file last modified time to avoid multiple users rebuilding cache at the same time
- if (get_class($this->getCacheSource()) == 'wcf\system\cache\source\DiskCacheSource') {
- @touch($cacheResource['file']);
+ protected function getCacheName(ICacheBuilder $cacheBuilder, array $parameters = array()) {
+ $className = explode('\\', get_class($cacheBuilder));
+ $application = array_shift($className);
+ $cacheName = StringUtil::replace('CacheBuilder', '', array_pop($className));
+ if (!empty($parameters)) {
+ $cacheName .= '-' . $this->getCacheIndex($parameters);
}
- // build cache
- $cacheBuilder = new $cacheResource['className'];
- $value = $cacheBuilder->getData($cacheResource);
-
- // save cache
- $this->getCacheSource()->set($cacheResource, $value);
-
- return true;
+ return $application . '_' . StringUtil::firstCharToLowerCase($cacheName);
}
/**
public function getCacheSource() {
return $this->cacheSource;
}
+
+ /**
+ * Unifys parameter order, numeric indizes will be discarded.
+ *
+ * @param array $parameters
+ * @return array
+ */
+ protected function orderParameters($parameters) {
+ if (!empty($parameters)) {
+ array_multisort($parameters);
+ }
+
+ return $parameters;
+ }
}
--- /dev/null
+<?php
+namespace wcf\system\cache\builder;
+use wcf\system\cache\CacheHandler;
+use wcf\system\exception\SystemException;
+use wcf\system\SingletonFactory;
+
+/**
+ * Default implementation for cache builders.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage system.cache.builder
+ * @category Community Framework
+ */
+abstract class AbstractCacheBuilder extends SingletonFactory implements ICacheBuilder {
+ /**
+ * list of cache resources by index
+ * @var array<array>
+ */
+ protected $cache = array();
+
+ /**
+ * maximum cache lifetime in seconds, '0' equals infinite
+ * @var integer
+ */
+ protected $maxLifetime = 0;
+
+ /**
+ * @see wcf\system\cache\builder\ICacheBuilder::getData()
+ */
+ public function getData(array $parameters = array(), $arrayIndex = '') {
+ $index = CacheHandler::getInstance()->getCacheIndex($parameters);
+
+ if (!isset($this->cache[$index])) {
+ // fetch cache or rebuild if missing
+ $this->cache[$index] = CacheHandler::getInstance()->get($this, $parameters);
+ if ($this->cache[$index] === null) {
+ $this->cache[$index] = $this->rebuild($parameters);
+
+ // update cache
+ CacheHandler::getInstance()->set($this, $parameters, $this->cache[$index]);
+ }
+ }
+
+ if (!empty($arrayIndex)) {
+ if (!isset($this->cache[$index][$arrayIndex])) {
+ throw new SystemException("array index '".$arrayIndex."' does not exist in cache resource");
+ }
+
+ return $this->cache[$index][$arrayIndex];
+ }
+
+ return $this->cache[$index];
+ }
+
+ /**
+ * @see wcf\system\cache\builder\ICacheBuilder::getMaxLifetime()
+ */
+ public function getMaxLifetime() {
+ return $this->maxLifetime;
+ }
+
+ /**
+ * @see wcf\system\cache\builder\ICacheBuilder::reset()
+ */
+ public function reset(array $parameters = array()) {
+ CacheHandler::getInstance()->flush($this, $parameters);
+ }
+
+ /**
+ * Rebuilds cache for current resource.
+ *
+ * @param array $parameters
+ */
+ abstract protected function rebuild(array $parameters);
+}
/**
* A cache builder provides data for the cache handler that ought to be cached.
*
- * @author Marcel Werk
- * @copyright 2001-2012 WoltLab GmbH
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package com.woltlab.wcf
- * @subpackage system.cache
+ * @subpackage system.cache.builder
* @category Community Framework
*/
interface ICacheBuilder {
/**
* Returns the data that ought to be cached.
*
- * @param array $cacheResource
+ * @param array $parameters
+ * @param string $arrayIndex
* @return array
*/
- public function getData(array $cacheResource);
+ public function getData(array $parameters = array(), $arrayIndex = '');
+
+ /**
+ * Returns maximum lifetime for cache resource.
+ *
+ * @return integer
+ */
+ public function getMaxLifetime();
+
+ /**
+ * Flushes cache. If no parameters are given, all caches starting with
+ * the same cache name will be flushed too.
+ *
+ * @param array $parameters
+ */
+ public function reset(array $parameters = array());
}
<?php
namespace wcf\system\cache\source;
use wcf\system\exception\SystemException;
+use wcf\system\Regex;
use wcf\util\FileUtil;
+use wcf\util\StringUtil;
/**
* ApcCacheSource is an implementation of CacheSource that uses APC to store cached variables.
*
- * @author Markus Bartz
- * @copyright 2011 Markus Bartz
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package com.woltlab.wcf
* @subpackage system.cache.source
* @category Community Framework
*/
class ApcCacheSource implements ICacheSource {
+ /**
+ * key prefix
+ * @var string
+ */
+ protected $prefix = '';
+
/**
* Creates a new ApcCacheSource object.
*/
if (!function_exists('apc_store')) {
throw new SystemException('APC support is not enabled.');
}
+
+ // set variable prefix to prevent collision
+ $this->prefix = substr(sha1(WCF_DIR), 0, 8) . '_';
+ }
+
+ /**
+ * @see wcf\system\cache\source\ICacheSource::flush()
+ */
+ public function flush($cacheName, $useWildcard) {
+ if ($useWildcard) {
+ $this->removeKeys($this->prefix . $cacheName . '(\-[a-f0-9]+)?');
+ }
+ else {
+ apc_delete($this->prefix . $cacheName);
+ }
+ }
+
+ /**
+ * @see wcf\system\cache\source\ICacheSource::flushAll()
+ */
+ public function flushAll() {
+ $this->removeKeys();
}
/**
* @see wcf\system\cache\source\ICacheSource::get()
*/
- public function get(array $cacheResource) {
- if (($data = apc_fetch($cacheResource['file'])) === false) {
+ public function get($cacheName, $maxLifetime) {
+ if (($data = apc_fetch($this->prefix . $cacheName)) === false) {
return null;
}
/**
* @see wcf\system\cache\source\ICacheSource::set()
*/
- public function set(array $cacheResource, $value) {
- apc_store($cacheResource['file'], $value, $cacheResource['maxLifetime']);
+ public function set($cacheName, $value, $maxLifetime) {
+ apc_store($this->prefix . $cacheName, $value, $this->getTTL($maxLifetime));
}
/**
- * @see wcf\system\cache\source\ICacheSource::delete()
+ * Returns time to live in seconds, defaults to 3 days.
+ *
+ * @param integer $maxLifetime
+ * @return integer
*/
- public function delete(array $cacheResource) {
- apc_delete($cacheResource['file']);
+ protected function getTTL($maxLifetime = 0) {
+ if ($maxLifetime) {
+ // max lifetime is a timestamp, discard (similar to http://www.php.net/manual/en/memcached.expiration.php)
+ if ($maxLifetime > (60 * 60 * 24 * 30)) {
+ $maxLifetime = 0;
+ }
+ }
+
+ if ($maxLifetime) {
+ return $maxLifetime;
+ }
+
+ // default TTL: 3 days
+ return (60 * 60 * 24 * 3);
}
/**
* @see wcf\system\cache\source\ICacheSource::clear()
*/
- public function clear($directory, $filepattern) {
- $pattern = preg_quote(FileUtil::addTrailingSlash($directory), '%').str_replace('*', '.*', str_replace('.', '\.', $filepattern));
+ public function removeKeys($pattern = null) {
+ $regex = null;
+ if ($pattern !== null) {
+ $regex = new Regex('^'.$pattern.'$');
+ }
- $apcinfo = apc_cache_info('user');
- $cacheList = $apcinfo['cache_list'];
- foreach ($cacheList as $cache) {
- if (preg_match('%^'.$pattern.'$%i', $cache['info'])) {
+ $apcCacheInfo = apc_cache_info('user');
+ foreach ($apcCacheInfo['cache_list'] as $cache) {
+ if ($regex === null) {
+ if (StringUtil::startsWith($cache['info'], $this->prefix)) {
+ apc_delete($cache['info']);
+ }
+ }
+ else if ($regex->match($cache['info'])) {
apc_delete($cache['info']);
}
}
}
-
- /**
- * @see wcf\system\cache\source\ICacheSource::close()
- */
- public function close() {
- // does nothing
- }
-
- /**
- * @see wcf\system\cache\source\ICacheSource::flush()
- */
- public function flush() {
- apc_clear_cache('user');
- }
}
/**
* DiskCacheSource is an implementation of CacheSource that stores the cache as simple files in the file system.
*
- * @author Marcel Werk
- * @copyright 2001-2012 WoltLab GmbH
+ * @author Alexander Ebert, Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package com.woltlab.wcf
* @subpackage system.cache.source
*/
class DiskCacheSource implements ICacheSource {
/**
- * loaded cache
- * @var array
+ * @see wcf\system\cache\source\ICacheSource::flush()
*/
- protected $cache = null;
+ public function flush($cacheName, $useWildcard) {
+ if ($useWildcard) {
+ $this->removeFiles('cache.'.$cacheName.'(-[a-f0-9]+)?.php');
+ }
+ else {
+ $this->removeFiles('cache.'.$cacheName.'.php');
+ }
+ }
/**
- * list of loaded resources
- * @var array
+ * @see wcf\system\cache\source\ICacheSource::flushAll()
*/
- protected $loaded = array();
+ public function flushAll() {
+ DirectoryUtil::getInstance(WCF_DIR.'cache/')->removePattern(new Regex('.*\.php$'));
+ }
/**
* @see wcf\system\cache\source\ICacheSource::get()
*/
- public function get(array $cacheResource) {
- if (!isset($this->cache[$cacheResource['cache']])) {
- // check for rebuilt
- if ($this->needRebuild($cacheResource)) {
- return null;
- }
-
- // load resource
- if (!$this->load($cacheResource)) {
- return null;
- }
-
- if (!isset($this->cache[$cacheResource['cache']])) {
- return null;
- }
+ public function get($cacheName, $maxLifetime) {
+ $filename = $this->getFilename($cacheName);
+ if ($this->needRebuild($filename, $maxLifetime)) {
+ return null;
}
- return $this->cache[$cacheResource['cache']];
+ // load cache
+ try {
+ return $this->readCache($filename);
+ }
+ catch (\Exception $e) {
+ return null;
+ }
}
/**
* @see wcf\system\cache\source\ICacheSource::set()
*/
- public function set(array $cacheResource, $value) {
- // write cache
- $targetFile = new File($cacheResource['file']);
- $targetFile->write("<?php exit; /* cache: ".$cacheResource['cache']." (generated at ".gmdate('r').") DO NOT EDIT THIS FILE */ ?>\n");
- $targetFile->write(serialize($value));
- $targetFile->close();
-
- // add value
- $this->cache[$cacheResource['cache']] = $value;
- $this->loaded[$cacheResource['file']] = true;
+ public function set($cacheName, $value, $maxLifetime) {
+ $file = new File($this->getFilename($cacheName));
+ $file->write("<?php exit; /* cache: ".$cacheName." (generated at ".gmdate('r').") DO NOT EDIT THIS FILE */ ?>\n");
+ $file->write(serialize($value));
+ $file->close();
}
/**
- * @see wcf\system\cache\source\ICacheSource::delete()
+ * Returns cache filename.
+ *
+ * @param string $cacheName
+ * @return string
*/
- public function delete(array $cacheResource) {
- if (file_exists($cacheResource['file'])) {
- if (!@touch($cacheResource['file'], 1)) {
- @unlink($cacheResource['file']);
- }
-
- // reset open cache
- if (isset($this->cache[$cacheResource['cache']])) {
- unset($this->cache[$cacheResource['cache']]);
- }
- if (isset($this->loaded[$cacheResource['file']])) {
- unset($this->loaded[$cacheResource['file']]);
- }
- }
+ protected function getFilename($cacheName) {
+ return WCF_DIR.'cache/cache.'.$cacheName.'.php';
}
/**
- * @see wcf\system\cache\source\ICacheSource::clear()
+ * Removes files matching given pattern.
+ *
+ * @param string $pattern
*/
- public function clear($directory, $filepattern) {
- // unify parameters
- $directory = FileUtil::unifyDirSeperator($directory);
- $filepattern = FileUtil::unifyDirSeperator($filepattern);
-
- $filepattern = str_replace('*', '.*', str_replace('.', '\.', $filepattern));
- if (substr($directory, -1) != '/') {
- $directory .= '/';
- }
+ protected function removeFiles($pattern) {
+ $directory = FileUtil::unifyDirSeperator(WCF_DIR.'cache/');
+ $pattern = str_replace('*', '.*', str_replace('.', '\.', $pattern));
DirectoryUtil::getInstance($directory)->executeCallback(new Callback(function ($filename) {
if (!@touch($filename, 1)) {
@unlink($filename);
}
- }), new Regex('^'.$directory.$filepattern.'$', Regex::CASE_INSENSITIVE));
+ }), new Regex('^'.$directory.$pattern.'$', Regex::CASE_INSENSITIVE));
}
/**
* Determines wheater the cache needs to be rebuild or not.
*
- * @param array $cacheResource
+ * @param string $filename
+ * @param integer $maxLifetime
* @return boolean
*/
- protected function needRebuild(array $cacheResource) {
+ protected function needRebuild($filename, $maxLifetime) {
// cache does not exist
- if (!file_exists($cacheResource['file'])) {
+ if (!file_exists($filename)) {
return true;
}
// cache is empty
- if (!@filesize($cacheResource['file'])) {
+ if (!@filesize($filename)) {
return true;
}
// cache resource was marked as obsolete
- if (($mtime = filemtime($cacheResource['file'])) <= 1) {
+ if (($mtime = filemtime($filename)) <= 1) {
return true;
}
// maxlifetime expired
- if ($cacheResource['maxLifetime'] > 0 && (TIME_NOW - $mtime) > $cacheResource['maxLifetime']) {
+ if ($maxLifetime > 0 && (TIME_NOW - $mtime) > $filename) {
return true;
}
return false;
}
- /**
- * Loads a cached resource.
- *
- * @param array $cacheResource
- */
- public function load(array $cacheResource) {
- if (!isset($this->loaded[$cacheResource['file']])) {
- try {
- // load cache file
- $this->loadCacheFile($cacheResource);
- }
- catch (\Exception $e) {
- return false;
- }
-
- $this->loaded[$cacheResource['file']] = true;
- }
-
- return true;
- }
-
/**
* Loads the file of a cached resource.
*
- * @param array $cacheResource
+ * @param string $cacheName
+ * @param string $filename
+ * @return mixed
*/
- protected function loadCacheFile(array $cacheResource) {
+ protected function readCache($cacheName, $filename) {
// get file contents
- $contents = file_get_contents($cacheResource['file']);
+ $contents = file_get_contents($filename);
// find first newline
$position = strpos($contents, "\n");
- if ($position === false) throw new SystemException("Unable to load cache resource '".$cacheResource['cache']."'");
+ if ($position === false) {
+ throw new SystemException("Unable to load cache resource '".$cacheName."'");
+ }
// cut contents
$contents = substr($contents, $position + 1);
// unserialize
- $this->cache[$cacheResource['cache']] = @unserialize($contents);
- if ($this->cache[$cacheResource['cache']] === false) throw new SystemException("Unable to load cache resource '".$cacheResource['cache']."'");
- }
-
- /**
- * @see wcf\system\cache\source\ICacheSource::close()
- */
- public function close() {
- // does nothing
- }
-
- /**
- * @see wcf\system\cache\source\ICacheSource::flush()
- */
- public function flush() {
- $sql = "SELECT packageDir
- FROM wcf".WCF_N."_package
- WHERE isApplication = ?";
- $statement = WCF::getDB()->prepareStatement($sql);
- $statement->execute(array(1));
- while ($row = $statement->fetchArray()) {
- $packageDir = FileUtil::getRealPath(WCF_DIR.$row['packageDir']);
- $cacheDir = $packageDir.'cache';
- DirectoryUtil::getInstance($cacheDir)->removePattern(new Regex('.*\.php$'));
+ $value = @unserialize($contents);
+ if ($value === false) {
+ throw new SystemException("Unable to load cache resource '".$cacheName."'");
}
+
+ return $value;
}
}
/**
* Any cache sources should implement this interface.
*
- * @author Marcel Werk
- * @copyright 2001-2012 WoltLab GmbH
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package com.woltlab.wcf
* @subpackage system.cache.source
*/
interface ICacheSource {
/**
- * Returns a cached variable.
- *
- * @param array $cacheResource
- * @return mixed
+ * Flushes a specific cache, optionally removing caches which share the same name.
+ *
+ * @param string $cacheName
+ * @param boolean $useWildcard
*/
- public function get(array $cacheResource);
+ public function flush($cacheName, $useWildcard);
/**
- * Stores a variable in the cache.
- *
- * @param array $cacheResource
- * @param mixed $value
+ * Clears the cache completely.
*/
- public function set(array $cacheResource, $value);
+ public function flushAll();
/**
- * Deletes a variable in the cache.
+ * Returns a cached variable.
*
- * @param array $cacheResource
+ * @param string $cacheName
+ * @param integer $maxLifetime
+ * @return mixed
*/
- public function delete(array $cacheResource);
+ public function get($cacheName, $maxLifetime);
/**
- * Marks cached files as obsolete.
+ * Stores a variable in the cache.
*
- * @param string $directory
- * @param string $filepattern
- */
- public function clear($directory, $filepattern);
-
- /**
- * Closes this cache source.
- */
- public function close();
-
- /**
- * Clears the cache completely.
+ * @param string $cacheName
+ * @param mixed $value
+ * @param integer $maxLifetime
*/
- public function flush();
+ public function set($cacheName, $value, $maxLifetime);
}
+++ /dev/null
-<?php
-namespace wcf\system\cache\source;
-use wcf\system\exception\SystemException;
-use wcf\system\SingletonFactory;
-use wcf\util\StringUtil;
-
-/**
- * Provides a global adapter for accessing the memcached server.
- *
- * @author Alexander Ebert
- * @copyright 2001-2013 WoltLab GmbH
- * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package com.woltlab.wcf
- * @subpackage system.cache.source
- * @category Community Framework
- */
-class MemcachedAdapter extends SingletonFactory {
- /**
- * memcached object
- * @var \Memcached
- */
- private $memcached = null;
-
- /**
- * @see wcf\system\SingletonFactory::init()
- */
- protected function init() {
- if (!class_exists('Memcached')) {
- throw new SystemException('memcached support is not enabled.');
- }
-
- // init memcached
- if (CACHE_SOURCE_MEMCACHED_USE_PCONNECT) {
- $this->memcached = new \Memcached('wcf'.WCF_N.'_memcached');
- }
- else {
- $this->memcached = new \Memcached();
- }
-
- // add servers
- $tmp = explode("\n", StringUtil::unifyNewlines(CACHE_SOURCE_MEMCACHED_HOST));
- $servers = array();
- $defaultWeight = floor(100 / count($tmp));
- foreach ($tmp as $server) {
- $server = StringUtil::trim($server);
- if (!empty($server)) {
- $host = $server;
- $port = 11211; // default memcached port
- $weight = $defaultWeight;
-
- // get port
- if (strpos($host, ':')) {
- $parsedHost = explode(':', $host);
- $host = $parsedHost[0];
- $port = $parsedHost[1];
-
- if (isset($parsedHost[2])) {
- $weight = $parsedHost[2];
- }
- }
-
- $servers[] = array($host, $port, $weight);
- }
- }
-
- $this->memcached->addServers($servers);
-
- // test connection
- $this->memcached->get('testing');
- }
-
- /**
- * Returns the memcached object.
- *
- * @return \Memcached
- */
- public function getMemcached() {
- return $this->memcached;
- }
-}
<?php
namespace wcf\system\cache\source;
-use wcf\system\WCF;
-use wcf\util\FileUtil;
+use wcf\system\exception\SystemException;
+use wcf\util\StringUtil;
/**
* MemcachedCacheSource is an implementation of CacheSource that uses a Memcached server to store cached variables.
*
- * @author Marcel Werk
+ * @author Alexander Ebert
* @copyright 2001-2013 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package com.woltlab.wcf
*/
class MemcachedCacheSource implements ICacheSource {
/**
- * MemcachedAdapter object
- * @var wcf\system\cache\source\MemcachedAdapter
+ * memcached object
+ * @var \Memcached
*/
- protected $adapter = null;
+ protected $memcached = null;
/**
- * list of cache resources
- * @var array<string>
+ * key prefix
+ * @var string
*/
- protected $cacheResources = null;
+ protected $prefix = '';
/**
- * list of new cache resources
- * @var array<string>
- */
- protected $newLogEntries = array();
-
- /**
- * list of obsolete resources
- * @var array<string>
- */
- protected $obsoleteLogEntries = array();
-
- /**
- * Creates a new MemcachedCacheSource object.
+ * Creates a new instance of memcached.
*/
public function __construct() {
- $this->adapter = MemcachedAdapter::getInstance();
- }
-
- /**
- * Returns the memcached adapter.
- *
- * @return wcf\system\cache\source\MemcachedAdapter
- */
- public function getAdapter() {
- return $this->adapter;
- }
-
- // internal log functions
- /**
- * Loads the cache log.
- */
- protected function loadLog() {
- if ($this->cacheResources === null) {
- $this->cacheResources = array();
- $sql = "SELECT *
- FROM wcf".WCF_N."_cache_resource";
- $statement = WCF::getDB()->prepareStatement($sql);
- $statement->execute();
- while ($row = $statement->fetchArray()) {
- $this->cacheResources[] = $row['cacheResource'];
- }
- }
- }
-
- /**
- * Saves modifications of the cache log.
- */
- protected function updateLog() {
- if (!empty($this->newLogEntries)) {
- $sql = "DELETE FROM wcf".WCF_N."_cache_resource
- WHERE cacheResource = ?";
- $statement = WCF::getDB()->prepareStatement($sql);
- foreach ($this->newLogEntries as $entry) {
- $statement->execute(array($entry));
- }
-
- $sql = "INSERT INTO wcf".WCF_N."_cache_resource
- (cacheResource)
- VALUES (?)";
- $statement = WCF::getDB()->prepareStatement($sql);
- foreach ($this->newLogEntries as $entry) {
- $statement->execute(array($entry));
- }
-
+ if (!class_exists('Memcached')) {
+ throw new SystemException('memcached support is not enabled.');
}
- if (!empty($this->obsoleteLogEntries)) {
- $sql = "DELETE FROM wcf".WCF_N."_cache_resource
- WHERE cacheResource = ?";
- $statement = WCF::getDB()->prepareStatement($sql);
- foreach ($this->obsoleteLogEntries as $entry) {
- $statement->execute(array($entry));
+ // init memcached
+ $this->memcached = new \Memcached();
+
+ // add servers
+ $tmp = explode("\n", StringUtil::unifyNewlines(CACHE_SOURCE_MEMCACHED_HOST));
+ $servers = array();
+ $defaultWeight = floor(100 / count($tmp));
+ foreach ($tmp as $server) {
+ $server = StringUtil::trim($server);
+ if (!empty($server)) {
+ $host = $server;
+ $port = 11211; // default memcached port
+ $weight = $defaultWeight;
+
+ // get port
+ if (strpos($host, ':')) {
+ $parsedHost = explode(':', $host);
+ $host = $parsedHost[0];
+ $port = $parsedHost[1];
+
+ if (isset($parsedHost[2])) {
+ $weight = $parsedHost[2];
+ }
+ }
+
+ $servers[] = array($host, $port, $weight);
}
}
+
+ $this->memcached->addServers($servers);
+
+ // test connection
+ $this->memcached->get('testing');
+
+ // set variable prefix to prevent collision
+ $this->prefix = substr(sha1(WCF_DIR), 0, 8) . '_';
}
/**
- * Adds a cache resource to cache log.
- *
- * @param string $cacheResource
+ * @see wcf\system\cache\source\ICacheSource::flush()
*/
- protected function addToLog($cacheResource) {
- $this->newLogEntries[] = $cacheResource;
+ public function flush($cacheName, $useWildcard) {
+ $cacheName = $this->prefix . $cacheName;
+ $this->memcached->delete($cacheName);
+
+ $this->updateMaster(null, $cacheName);
}
/**
- * Removes an obsolete cache resource from cache log.
- *
- * @param string $cacheResource
+ * @see wcf\system\cache\source\ICacheSource::flushAll()
*/
- protected function removeFromLog($cacheResource) {
- $this->obsoleteLogEntries[] = $cacheResource;
+ public function flushAll() {
+ // read all keys
+ $availableKeys = $this->memcached->get($this->prefix . 'master');
+ if ($availableKeys !== false) {
+ $keys = @unserialize($availableKeys);
+ if ($keys !== false) {
+ $this->memcached->deleteMulti($keys);
+ }
+ }
+
+ // flush master
+ $this->memcached->set($this->prefix . 'master', serialize(array()), $this->getTTL());
}
- // CacheSource implementations
/**
* @see wcf\system\cache\source\ICacheSource::get()
*/
- public function get(array $cacheResource) {
- $value = $this->getAdapter()->getMemcached()->get($cacheResource['file']);
+ public function get($cacheName, $maxLifetime) {
+ $cacheName = $this->prefix . $cacheName;
+ $value = $this->memcached->get($cacheName);
+
if ($value === false) {
- // check if result code if return values is a boolean value instead of no result
- if ($this->getAdapter()->getMemcached()->getResultCode() == \Memcached::RES_NOTFOUND) {
+ // check if value does not exist
+ if ($this->memcached->getResultCode() !== \Memcached::RES_SUCCESS) {
+ $this->updateMaster(null, $cacheName);
return null;
}
}
+ $this->updateMaster($cacheName);
return $value;
}
/**
* @see wcf\system\cache\source\ICacheSource::set()
*/
- public function set(array $cacheResource, $value) {
- $this->getAdapter()->getMemcached()->set($cacheResource['file'], $value, $cacheResource['maxLifetime']);
- $this->addToLog($cacheResource['file']);
- }
-
- /**
- * @see wcf\system\cache\source\ICacheSource::delete()
- */
- public function delete(array $cacheResource) {
- $this->getAdapter()->getMemcached()->delete($cacheResource['file']);
- $this->removeFromLog($cacheResource['file']);
+ public function set($cacheName, $value, $maxLifetime) {
+ $cacheName = $this->prefix . $cacheName;
+ $this->memcached->set($cacheName, $value, $this->getTTL($maxLifetime));
+
+ $this->updateMaster($cacheName);
}
/**
- * @see wcf\system\cache\source\ICacheSource::clear()
+ * Updates master record for cached resources.
+ *
+ * @param string $addResource
+ * @param string $removeResource
*/
- public function clear($directory, $filepattern) {
- $this->loadLog();
- $pattern = preg_quote(FileUtil::addTrailingSlash($directory), '%').str_replace('*', '.*', str_replace('.', '\.', $filepattern));
- foreach ($this->cacheResources as $cacheResource) {
- if (preg_match('%^'.$pattern.'$%i', $cacheResource)) {
- $this->getAdapter()->getMemcached()->delete($cacheResource);
- $this->removeFromLog($cacheResource);
+ protected function updateMaster($addResource = null, $removeResource = null) {
+ if ($addResource === null && $removeResource === null) {
+ return;
+ }
+
+ $master = $this->memcached->get($this->prefix . 'master');
+ $update = false;
+
+ // master record missing
+ if ($master === false) {
+ $update = true;
+ $master = array();
+ }
+ else {
+ $master = @unserialize($master);
+
+ // master record is broken
+ if ($master === false) {
+ $update = true;
+ $master = array();
}
+ else {
+ foreach ($master as $index => $key) {
+ if ($addResource !== null) {
+ // key is already tracked
+ if ($key === $addResource) {
+ $addResource = null;
+
+ if ($removeResource === null) {
+ break;
+ }
+ }
+ }
+
+ if ($removeResource !== null) {
+ if ($key === $removeResource) {
+ $update = true;
+ unset($master[$index]);
+
+ if ($addResource === null) {
+ break;
+ }
+ else {
+ $removeResource = null;
+ }
+ }
+ }
+ }
+
+ if ($addResource !== null) {
+ $update = true;
+ $master[] = $addResource;
+ }
+ }
+ }
+
+ // update master record
+ if ($update) {
+ $this->memcached->set($this->prefix . 'master', serialize($master), $this->getTTL());
}
}
/**
- * @see wcf\system\cache\source\ICacheSource::flush()
- */
- public function flush() {
- // clear cache
- $this->getAdapter()->getMemcached()->flush();
+ * Returns time to live in seconds, defaults to 3 days.
+ *
+ * @param integer $maxLifetime
+ * @return integer
+ */
+ protected function getTTL($maxLifetime = 0) {
+ if ($maxLifetime) {
+ // max lifetime is a timestamp -> http://www.php.net/manual/en/memcached.expiration.php
+ if ($maxLifetime > (60 * 60 * 24 * 30)) {
+ // timestamp is in the past, discard
+ if ($maxLifetime < TIME_NOW) {
+ $maxLifetime = 0;
+ }
+ }
+ }
- // clear log
- $this->newLogEntries = $this->obsoleteLogEntries = array();
+ if ($maxLifetime) {
+ return $maxLifetime;
+ }
- $sql = "DELETE FROM wcf".WCF_N."_cache_resource";
- $statement = WCF::getDB()->prepareStatement($sql);
- $statement->execute();
- }
-
- /**
- * @see wcf\system\cache\source\ICacheSource::close()
- */
- public function close() {
- // update log
- $this->updateLog();
+ // default TTL: 3 days
+ return (60 * 60 * 24 * 3);
}
}
<?php
namespace wcf\system\cache\source;
+use wcf\util\StringUtil;
/**
* NoCacheSource is an implementation of CacheSource that does not store any data.
*
- * @author Tim Düsterhus
- * @copyright 2011 Tim Düsterhus
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package com.woltlab.wcf
* @subpackage system.cache.source
*/
class NoCacheSource implements ICacheSource {
/**
- * @see wcf\system\cache\source\ICacheSource::get()
- */
- public function get(array $cacheResource) {
- if (!isset($this->cache[$cacheResource['cache']])) return null;
-
- return $this->cache[$cacheResource['cache']];
- }
-
- /**
- * @see wcf\system\cache\source\ICacheSource::set()
+ * list of cached values
+ * @var array<array>
*/
- public function set(array $cacheResource, $value) {
- // we have to keep it temporarily
- $this->cache[$cacheResource['cache']] = $value;
- $this->loaded[$cacheResource['file']] = true;
- }
+ protected $cache = array();
/**
- * @see wcf\system\cache\source\ICacheSource::delete()
+ * @see wcf\system\cache\source\ICacheSource::flush()
*/
- public function delete(array $cacheResource) {
- // reset open cache
- if (isset($this->cache[$cacheResource['cache']])) unset($this->cache[$cacheResource['cache']]);
- if (isset($this->loaded[$cacheResource['file']])) unset($this->loaded[$cacheResource['file']]);
-
- return;
+ public function flush($cacheName, $useWildcard) {
+ if (isset($this->cache[$cacheName])) {
+ unset($this->cache[$cacheName]);
+ }
+
+ if ($useWildcard) {
+ $cacheName .= '-';
+ foreach (array_keys($this->cache) as $key) {
+ if (StringUtil::startsWith($key, $cacheName)) {
+ unset($this->cache[$key]);
+ }
+ }
+ }
}
/**
- * @see wcf\system\cache\source\ICacheSource::clear()
+ * @see wcf\system\cache\source\ICacheSource::flushAll()
*/
- public function clear($directory, $filepattern) {
- return;
+ public function flushAll() {
+ $this->cache = array();
}
/**
- * @see wcf\system\cache\source\ICacheSource::close()
+ * @see wcf\system\cache\source\ICacheSource::get()
*/
- public function close() {
- return;
+ public function get($cacheName, $maxLifetime) {
+ if (isset($this->cache[$cacheName])) {
+ return $this->cache[$cacheName];
+ }
+
+ return null;
}
/**
- * @see wcf\system\cache\source\ICacheSource::flush()
+ * @see wcf\system\cache\source\ICacheSource::set()
*/
- public function flush() {
- return;
+ public function set($cacheName, $value, $maxLifetime) {
+ $this->cache[$cacheName] = $value;
}
}
<item name="wcf.acp.option.blacklist_user_agents.description"><![CDATA[Eine Browser-Kennung (User-Agent) pro Zeile]]></item>
<item name="wcf.acp.option.cache_source_memcached_host"><![CDATA[Memcached-Server]]></item>
<item name="wcf.acp.option.cache_source_memcached_host.description"><![CDATA[Mehrere Server können zeilenweise angegeben werden, die Gewichtung kann als dritter Parameter angegeben werden, zum Beispiel „localhost:11211:67“ oder „10.0.13.37:31337:33“.]]></item>
- <item name="wcf.acp.option.cache_source_memcached_use_pconnect"><![CDATA[Persistente Verbindungen aktivieren]]></item>
<item name="wcf.acp.option.cache_source_type"><![CDATA[Cache-Methode]]></item>
<item name="wcf.acp.option.cache_source_type.apc"><![CDATA[Alternative PHP Cache (experimentell)]]></item>
<item name="wcf.acp.option.cache_source_type.description"><![CDATA[Beachten Sie, dass einige Methoden spezielle Anforderungen an das Server-System stellen und nicht auf jedem Server zur Verfügung stehen.]]></item>
<item name="wcf.acp.option.blacklist_user_agents.description"><![CDATA[Enter one user agent each row.]]></item>
<item name="wcf.acp.option.cache_source_memcache_host"><![CDATA[Memcache server]]></item>
<item name="wcf.acp.option.cache_source_memcache_host.description"><![CDATA[You can enter one server each row.]]></item>
- <item name="wcf.acp.option.cache_source_memcache_use_pconnect"><![CDATA[Use persistent connections]]></item>
<item name="wcf.acp.option.cache_source_type"><![CDATA[Cache type]]></item>
<item name="wcf.acp.option.cache_source_type.apc"><![CDATA[Alternative PHP Cache (experimental)]]></item>
<item name="wcf.acp.option.cache_source_type.description"><![CDATA[Some of the types have special requirements to the server system and cannot be used in any environment.]]></item>
isPrimary TINYINT(1) NOT NULL DEFAULT 0
);
-DROP TABLE IF EXISTS wcf1_cache_resource;
-CREATE TABLE wcf1_cache_resource (
- cacheResource VARCHAR(255) NOT NULL PRIMARY KEY
-);
-
DROP TABLE IF EXISTS wcf1_category;
CREATE TABLE wcf1_category (
categoryID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,