From 1ea6e8f219f73cdeb5e2b97cc6290800dc3cf934 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Thu, 20 Feb 2025 09:53:09 +0100 Subject: [PATCH 01/42] Basic eager cache implementation --- .../cache/eager/AbstractEagerCache.class.php | 65 +++++++++++++++++++ .../system/cache/eager/IEagerCache.class.php | 23 +++++++ 2 files changed, 88 insertions(+) create mode 100644 wcfsetup/install/files/lib/system/cache/eager/AbstractEagerCache.class.php create mode 100644 wcfsetup/install/files/lib/system/cache/eager/IEagerCache.class.php diff --git a/wcfsetup/install/files/lib/system/cache/eager/AbstractEagerCache.class.php b/wcfsetup/install/files/lib/system/cache/eager/AbstractEagerCache.class.php new file mode 100644 index 00000000000..93f6f1a19cd --- /dev/null +++ b/wcfsetup/install/files/lib/system/cache/eager/AbstractEagerCache.class.php @@ -0,0 +1,65 @@ + + * @since 6.2 + */ +abstract class AbstractEagerCache implements IEagerCache +{ + private static array $caches = []; + private string $cacheName; + + #[\Override] + public function getCache(): array | object + { + $key = $this->getCacheKey(); + + if (!\array_key_exists($key, AbstractEagerCache::$caches)) { + AbstractEagerCache::$caches[$key] = CacheHandler::getInstance()->getCacheSource()->get($key, 0); + if (AbstractEagerCache::$caches[$key] === null) { + $this->reset(); + } + } + + return AbstractEagerCache::$caches[$key]; + } + + private function getCacheKey(): string + { + if (!isset($this->cacheName)) { + /* @see CacheHandler::getCacheName() */ + $reflection = new \ReflectionClass($this); + $this->cacheName = \str_replace( + ['\\', 'system_cache_eager_'], + ['_', ''], + \get_class($this) + ); + + $parameters = []; + foreach ($reflection->getProperties() as $property) { + $parameters[$property->getName()] = $property->getValue($this); + } + if ($parameters !== []) { + $this->cacheName .= '-' . CacheHandler::getInstance()->getCacheIndex($parameters); + } + } + + return $this->cacheName; + } + + #[\Override] + public function reset(): void + { + $key = $this->getCacheKey(); + AbstractEagerCache::$caches[$key] = $this->rebuild(); + CacheHandler::getInstance()->getCacheSource()->set($key, AbstractEagerCache::$caches[$key], 0); + } + + abstract protected function rebuild(): mixed; +} diff --git a/wcfsetup/install/files/lib/system/cache/eager/IEagerCache.class.php b/wcfsetup/install/files/lib/system/cache/eager/IEagerCache.class.php new file mode 100644 index 00000000000..f3d9aa6d546 --- /dev/null +++ b/wcfsetup/install/files/lib/system/cache/eager/IEagerCache.class.php @@ -0,0 +1,23 @@ + + * @since 6.2 + */ +interface IEagerCache +{ + /** + * Returns the cache. + */ + public function getCache(): array | object; + + /** + * Rebuilds the cache and stores it in the cache source. + * This method MUST NOT rely on any (runtime) cache at any point because those could be stale. + */ + public function reset(): void; +} From 2dde79eaa577ec76cf85ddcfb30d097d9145b113 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Thu, 20 Feb 2025 10:31:59 +0100 Subject: [PATCH 02/42] Remove unnecessary `IEagerCache` --- .../cache/eager/AbstractEagerCache.class.php | 35 ++++++++++++++----- .../system/cache/eager/IEagerCache.class.php | 23 ------------ 2 files changed, 27 insertions(+), 31 deletions(-) delete mode 100644 wcfsetup/install/files/lib/system/cache/eager/IEagerCache.class.php diff --git a/wcfsetup/install/files/lib/system/cache/eager/AbstractEagerCache.class.php b/wcfsetup/install/files/lib/system/cache/eager/AbstractEagerCache.class.php index 93f6f1a19cd..eb609630225 100644 --- a/wcfsetup/install/files/lib/system/cache/eager/AbstractEagerCache.class.php +++ b/wcfsetup/install/files/lib/system/cache/eager/AbstractEagerCache.class.php @@ -9,21 +9,32 @@ * @copyright 2001-2025 WoltLab GmbH * @license GNU Lesser General Public License * @since 6.2 + * + * @template T of array|object */ -abstract class AbstractEagerCache implements IEagerCache +abstract class AbstractEagerCache { + /** + * @var array + */ private static array $caches = []; private string $cacheName; - #[\Override] - public function getCache(): array | object + /** + * Returns the cache. + * + * @return T + */ + final public function getCache(): array | object { $key = $this->getCacheKey(); if (!\array_key_exists($key, AbstractEagerCache::$caches)) { - AbstractEagerCache::$caches[$key] = CacheHandler::getInstance()->getCacheSource()->get($key, 0); - if (AbstractEagerCache::$caches[$key] === null) { + $cache = CacheHandler::getInstance()->getCacheSource()->get($key, 0); + if ($cache === null) { $this->reset(); + } else { + AbstractEagerCache::$caches[$key] = $cache; } } @@ -53,13 +64,21 @@ private function getCacheKey(): string return $this->cacheName; } - #[\Override] - public function reset(): void + /** + * Rebuilds the cache and stores it in the cache source. + */ + final public function reset(): void { $key = $this->getCacheKey(); AbstractEagerCache::$caches[$key] = $this->rebuild(); CacheHandler::getInstance()->getCacheSource()->set($key, AbstractEagerCache::$caches[$key], 0); } - abstract protected function rebuild(): mixed; + /** + * Rebuilds the cache and returns it. + * This method MUST NOT rely on any (runtime) cache at any point because those could be stale. + * + * @return T + */ + abstract protected function rebuild(): array | object; } diff --git a/wcfsetup/install/files/lib/system/cache/eager/IEagerCache.class.php b/wcfsetup/install/files/lib/system/cache/eager/IEagerCache.class.php deleted file mode 100644 index f3d9aa6d546..00000000000 --- a/wcfsetup/install/files/lib/system/cache/eager/IEagerCache.class.php +++ /dev/null @@ -1,23 +0,0 @@ - - * @since 6.2 - */ -interface IEagerCache -{ - /** - * Returns the cache. - */ - public function getCache(): array | object; - - /** - * Rebuilds the cache and stores it in the cache source. - * This method MUST NOT rely on any (runtime) cache at any point because those could be stale. - */ - public function reset(): void; -} From 7378ebc428c3fbb7dfd4775ac6ecfb53dd6bd223 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Thu, 20 Feb 2025 10:34:13 +0100 Subject: [PATCH 03/42] Implement the core object cache as an eager cache --- .../install/files/lib/system/WCF.class.php | 15 +++++--- .../builder/CoreObjectCacheBuilder.class.php | 35 ------------------ .../cache/eager/CoreObjectCache.class.php | 36 +++++++++++++++++++ ...eObjectPackageInstallationPlugin.class.php | 4 +-- 4 files changed, 48 insertions(+), 42 deletions(-) delete mode 100644 wcfsetup/install/files/lib/system/cache/builder/CoreObjectCacheBuilder.class.php create mode 100644 wcfsetup/install/files/lib/system/cache/eager/CoreObjectCache.class.php diff --git a/wcfsetup/install/files/lib/system/WCF.class.php b/wcfsetup/install/files/lib/system/WCF.class.php index 836a94a9984..83b69237a21 100644 --- a/wcfsetup/install/files/lib/system/WCF.class.php +++ b/wcfsetup/install/files/lib/system/WCF.class.php @@ -12,8 +12,8 @@ use wcf\system\application\IApplication; use wcf\system\benchmark\Benchmark; use wcf\system\box\BoxHandler; -use wcf\system\cache\builder\CoreObjectCacheBuilder; use wcf\system\cache\builder\PackageUpdateCacheBuilder; +use wcf\system\cache\eager\CoreObjectCache; use wcf\system\database\MySQLDatabase; use wcf\system\event\EventHandler; use wcf\system\exception\ErrorException; @@ -140,9 +140,10 @@ class WCF /** * list of cached core objects - * @var string[] + * + * @var CoreObjectCache */ - protected static $coreObjectCache = []; + protected static CoreObjectCache $coreObjectCache; /** * database object @@ -748,7 +749,7 @@ protected function initCoreObjects(): void return; } - self::$coreObjectCache = CoreObjectCacheBuilder::getInstance()->getData(); + self::$coreObjectCache = new CoreObjectCache(); } /** @@ -933,7 +934,11 @@ final public static function __callStatic(string $name, array $arguments) */ final protected static function getCoreObject(string $className) { - return self::$coreObjectCache[$className] ?? null; + if (!isset(self::$coreObjectCache)) { + return null; + } + + return self::$coreObjectCache->getCache()[$className] ?? null; } /** diff --git a/wcfsetup/install/files/lib/system/cache/builder/CoreObjectCacheBuilder.class.php b/wcfsetup/install/files/lib/system/cache/builder/CoreObjectCacheBuilder.class.php deleted file mode 100644 index c851abdd72b..00000000000 --- a/wcfsetup/install/files/lib/system/cache/builder/CoreObjectCacheBuilder.class.php +++ /dev/null @@ -1,35 +0,0 @@ - - */ -class CoreObjectCacheBuilder extends AbstractCacheBuilder -{ - /** - * @inheritDoc - */ - public function rebuild(array $parameters) - { - $data = []; - - $coreObjectList = new CoreObjectList(); - $coreObjectList->readObjects(); - $coreObjects = $coreObjectList->getObjects(); - - foreach ($coreObjects as $coreObject) { - $tmp = \explode('\\', $coreObject->objectName); - $className = \array_pop($tmp); - $data[$className] = $coreObject->objectName; - } - - return $data; - } -} diff --git a/wcfsetup/install/files/lib/system/cache/eager/CoreObjectCache.class.php b/wcfsetup/install/files/lib/system/cache/eager/CoreObjectCache.class.php new file mode 100644 index 00000000000..e80b276b6d0 --- /dev/null +++ b/wcfsetup/install/files/lib/system/cache/eager/CoreObjectCache.class.php @@ -0,0 +1,36 @@ + + * @since 6.2 + * + * @extends AbstractEagerCache>> + */ +final class CoreObjectCache extends AbstractEagerCache +{ + #[\Override] + protected function rebuild(): array + { + $coreObjectList = new CoreObjectList(); + $coreObjectList->readObjects(); + $coreObjects = $coreObjectList->getObjects(); + + $data = []; + foreach ($coreObjects as $coreObject) { + $tmp = \explode('\\', $coreObject->objectName); + $className = \array_pop($tmp); + $data[$className] = $coreObject->objectName; + } + + return $data; + } +} diff --git a/wcfsetup/install/files/lib/system/package/plugin/CoreObjectPackageInstallationPlugin.class.php b/wcfsetup/install/files/lib/system/package/plugin/CoreObjectPackageInstallationPlugin.class.php index 44fc8716c1b..abbccae3964 100644 --- a/wcfsetup/install/files/lib/system/package/plugin/CoreObjectPackageInstallationPlugin.class.php +++ b/wcfsetup/install/files/lib/system/package/plugin/CoreObjectPackageInstallationPlugin.class.php @@ -4,7 +4,7 @@ use wcf\data\core\object\CoreObjectEditor; use wcf\data\core\object\CoreObjectList; -use wcf\system\cache\builder\CoreObjectCacheBuilder; +use wcf\system\cache\eager\CoreObjectCache; use wcf\system\devtools\pip\IDevtoolsPipEntryList; use wcf\system\devtools\pip\IGuiPackageInstallationPlugin; use wcf\system\devtools\pip\TXmlGuiPackageInstallationPlugin; @@ -85,7 +85,7 @@ protected function findExistingItem(array $data) */ protected function cleanup() { - CoreObjectCacheBuilder::getInstance()->reset(); + (new CoreObjectCache())->reset(); } /** From 86d2c0c5ccd8e1301b1aba72477cfbead66c57da Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Thu, 20 Feb 2025 11:15:01 +0100 Subject: [PATCH 04/42] Implement the language cache as an eager cache --- .../LanguageMultilingualismForm.class.php | 4 +- .../data/language/LanguageEditor.class.php | 4 +- .../files/lib/system/WCFSetup.class.php | 4 +- .../cache/eager/LanguageCache.class.php | 76 +++++++++++++++++++ .../eager/data/LanguageCacheData.class.php | 67 ++++++++++++++++ .../system/language/LanguageFactory.class.php | 68 ++++++----------- 6 files changed, 171 insertions(+), 52 deletions(-) create mode 100644 wcfsetup/install/files/lib/system/cache/eager/LanguageCache.class.php create mode 100644 wcfsetup/install/files/lib/system/cache/eager/data/LanguageCacheData.class.php diff --git a/wcfsetup/install/files/lib/acp/form/LanguageMultilingualismForm.class.php b/wcfsetup/install/files/lib/acp/form/LanguageMultilingualismForm.class.php index 991a382cb04..b3cc21816ca 100644 --- a/wcfsetup/install/files/lib/acp/form/LanguageMultilingualismForm.class.php +++ b/wcfsetup/install/files/lib/acp/form/LanguageMultilingualismForm.class.php @@ -5,7 +5,7 @@ use wcf\data\language\Language; use wcf\data\language\LanguageEditor; use wcf\form\AbstractForm; -use wcf\system\cache\builder\LanguageCacheBuilder; +use wcf\system\cache\eager\LanguageCache; use wcf\system\exception\UserInputException; use wcf\system\language\LanguageFactory; use wcf\system\WCF; @@ -116,7 +116,7 @@ public function save() LanguageEditor::enableMultilingualism(($this->enable == 1 ? $this->languageIDs : [])); // clear cache - LanguageCacheBuilder::getInstance()->reset(); + (new LanguageCache())->reset(); $this->saved(); // show success message diff --git a/wcfsetup/install/files/lib/data/language/LanguageEditor.class.php b/wcfsetup/install/files/lib/data/language/LanguageEditor.class.php index fc968cebb35..56ffb9b0f76 100644 --- a/wcfsetup/install/files/lib/data/language/LanguageEditor.class.php +++ b/wcfsetup/install/files/lib/data/language/LanguageEditor.class.php @@ -10,7 +10,7 @@ use wcf\data\language\item\LanguageItemList; use wcf\data\page\PageEditor; use wcf\event\language\LanguageContentCopying; -use wcf\system\cache\builder\LanguageCacheBuilder; +use wcf\system\cache\eager\LanguageCache; use wcf\system\database\util\PreparedStatementConditionBuilder; use wcf\system\event\EventHandler; use wcf\system\exception\SystemException; @@ -935,7 +935,7 @@ public function setAsDefault() */ public function clearCache() { - LanguageCacheBuilder::getInstance()->reset(); + (new LanguageCache())->reset(); } /** diff --git a/wcfsetup/install/files/lib/system/WCFSetup.class.php b/wcfsetup/install/files/lib/system/WCFSetup.class.php index f75c87e8c32..861bf9cd973 100644 --- a/wcfsetup/install/files/lib/system/WCFSetup.class.php +++ b/wcfsetup/install/files/lib/system/WCFSetup.class.php @@ -10,7 +10,7 @@ use wcf\data\package\installation\queue\PackageInstallationQueueEditor; use wcf\data\user\User; use wcf\data\user\UserAction; -use wcf\system\cache\builder\LanguageCacheBuilder; +use wcf\system\cache\eager\LanguageCache; use wcf\system\database\Database; use wcf\system\database\exception\DatabaseException; use wcf\system\database\MySQLDatabase; @@ -779,7 +779,7 @@ protected function installLanguage(): ResponseInterface LanguageFactory::getInstance()->makeDefault($language->languageID); // rebuild language cache - LanguageCacheBuilder::getInstance()->reset(); + (new LanguageCache())->reset(); return $this->gotoNextStep('createUser'); } diff --git a/wcfsetup/install/files/lib/system/cache/eager/LanguageCache.class.php b/wcfsetup/install/files/lib/system/cache/eager/LanguageCache.class.php new file mode 100644 index 00000000000..c0c88928597 --- /dev/null +++ b/wcfsetup/install/files/lib/system/cache/eager/LanguageCache.class.php @@ -0,0 +1,76 @@ + + * @since 6.2 + * + * @extends AbstractEagerCache + */ +final class LanguageCache extends AbstractEagerCache +{ + #[\Override] + protected function rebuild(): LanguageCacheData + { + $languageList = new LanguageList(); + $languageList->getConditionBuilder()->add('language.isDisabled = ?', [0]); + $languageList->readObjects(); + + $languages = $languageList->getObjects(); + $default = 0; + $multilingualismEnabled = false; + $codes = []; + $countryCodes = []; + + foreach ($languageList->getObjects() as $language) { + // default language + if ($language->isDefault) { + $default = $language->languageID; + } + + // multilingualism + if ($language->hasContent) { + $multilingualismEnabled = true; + } + + // language code to language id + $codes[$language->languageCode] = $language->languageID; + + // country code to language id + $countryCodes[$language->languageID] = $language->countryCode; + } + + DatabaseObject::sort($languages, 'languageName'); + + // get language categories + $languageCategoryList = new LanguageCategoryList(); + $languageCategoryList->readObjects(); + + $categories = []; + $categoryIDs = []; + foreach ($languageCategoryList->getObjects() as $languageCategory) { + $categories[$languageCategory->languageCategory] = $languageCategory; + $categoryIDs[$languageCategory->languageCategoryID] = $languageCategory->languageCategory; + } + + return new LanguageCacheData( + $codes, + $countryCodes, + $languages, + $default, + $categories, + $categoryIDs, + $multilingualismEnabled + ); + } +} diff --git a/wcfsetup/install/files/lib/system/cache/eager/data/LanguageCacheData.class.php b/wcfsetup/install/files/lib/system/cache/eager/data/LanguageCacheData.class.php new file mode 100644 index 00000000000..c4e52b6ab3d --- /dev/null +++ b/wcfsetup/install/files/lib/system/cache/eager/data/LanguageCacheData.class.php @@ -0,0 +1,67 @@ + + * @since 6.2 + */ +final class LanguageCacheData +{ + public function __construct( + /** @var array */ + public readonly array $codes, + /** @var array */ + public readonly array $countryCodes, + /** @var array */ + public readonly array $languages, + public readonly int $default, + /** @var array */ + public readonly array $categories, + /** @var array */ + public readonly array $categoryIDs, + public readonly bool $multilingualismEnabled + ) { + \assert($this->default > 0); + \assert(\array_key_exists($this->default, $this->languages)); + } + + /** + * Returns the default language. + */ + public function getDefaultLanguage(): Language + { + return $this->languages[$this->default]; + } + + /** + * Returns the language with the given language id. + */ + public function getLanguage(int $languageID): ?Language + { + return $this->languages[$languageID] ?? null; + } + + /** + * Returns the language category with the given category name. + */ + public function getLanguageCategory(string $categoryName): ?LanguageCategory + { + return $this->categories[$categoryName] ?? null; + } + + /** + * Returns the language category with the given category id. + */ + public function getCategoryByID(int $languageCategoryID): ?LanguageCategory + { + return $this->categories[$this->categoryIDs[$languageCategoryID] ?? null] ?? null; + } +} diff --git a/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php b/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php index 3ab54d2806b..1809bdd4e09 100644 --- a/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php +++ b/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php @@ -7,7 +7,8 @@ use wcf\data\language\category\LanguageCategory; use wcf\data\language\Language; use wcf\data\language\LanguageEditor; -use wcf\system\cache\builder\LanguageCacheBuilder; +use wcf\system\cache\eager\data\LanguageCacheData; +use wcf\system\cache\eager\LanguageCache; use wcf\system\SingletonFactory; use wcf\system\template\TemplateScriptingCompiler; use wcf\system\WCF; @@ -23,15 +24,8 @@ class LanguageFactory extends SingletonFactory { /** * language cache - * @var mixed[] */ - protected $cache; - - /** - * initialized languages - * @var Language[] - */ - protected $languages = []; + protected LanguageCacheData $cache; /** * active template scripting compiler @@ -49,21 +43,10 @@ protected function init() /** * Returns a Language object for the language with the given id. - * - * @param int $languageID - * @return Language|null */ - public function getLanguage($languageID) + public function getLanguage(int $languageID): ?Language { - if (!isset($this->languages[$languageID])) { - if (!isset($this->cache['languages'][$languageID])) { - return null; - } - - $this->languages[$languageID] = $this->cache['languages'][$languageID]; - } - - return $this->languages[$languageID]; + return $this->cache->getLanguage($languageID); } /** @@ -96,7 +79,7 @@ public function getUserLanguage($languageID = 0) public function getLanguageByCode($languageCode) { // called within WCFSetup - if (empty($this->cache['codes'])) { + if (empty($this->cache->codes)) { $sql = "SELECT languageID FROM wcf1_language WHERE languageCode = ?"; @@ -106,8 +89,8 @@ public function getLanguageByCode($languageCode) if (isset($row['languageID'])) { return new Language($row['languageID']); } - } elseif (isset($this->cache['codes'][$languageCode])) { - return $this->getLanguage($this->cache['codes'][$languageCode]); + } elseif (isset($this->cache->codes[$languageCode])) { + return $this->getLanguage($this->cache->codes[$languageCode]); } return null; @@ -121,18 +104,15 @@ public function getLanguageByCode($languageCode) */ public function isValidCategory($categoryName) { - return isset($this->cache['categories'][$categoryName]); + return isset($this->cache->categories[$categoryName]); } /** * Returns the language category with the given name. - * - * @param string $categoryName - * @return LanguageCategory|null */ - public function getCategory($categoryName) + public function getCategory($categoryName): ?LanguageCategory { - return $this->cache['categories'][$categoryName] ?? null; + return $this->cache->getLanguageCategory($categoryName); } /** @@ -143,11 +123,7 @@ public function getCategory($categoryName) */ public function getCategoryByID($languageCategoryID) { - if (isset($this->cache['categoryIDs'][$languageCategoryID])) { - return $this->cache['categories'][$this->cache['categoryIDs'][$languageCategoryID]]; - } - - return null; + return $this->cache->getCategoryByID($languageCategoryID); } /** @@ -157,7 +133,7 @@ public function getCategoryByID($languageCategoryID) */ public function getCategories() { - return $this->cache['categories']; + return $this->cache->categories; } /** @@ -172,13 +148,13 @@ protected function findPreferredLanguage() } // get default language - $defaultLanguageCode = $this->cache['languages'][$this->cache['default']]->languageCode; + $defaultLanguageCode = $this->cache->languages[$this->cache->default]->languageCode; // get preferred language $languageCode = self::getPreferredLanguage($availableLanguageCodes, $defaultLanguageCode); // get language id of preferred language - foreach ($this->cache['languages'] as $key => $language) { + foreach ($this->cache->languages as $key => $language) { if ($language->languageCode == $languageCode) { return $key; } @@ -232,7 +208,7 @@ public function getScriptingCompiler() */ protected function loadCache() { - $this->cache = LanguageCacheBuilder::getInstance()->getData(); + $this->cache = (new LanguageCache())->getCache(); } /** @@ -240,7 +216,7 @@ protected function loadCache() */ public function clearCache() { - LanguageCacheBuilder::getInstance()->reset(); + (new LanguageCache())->reset(); } /** @@ -263,7 +239,7 @@ public static function fixLanguageCode($languageCode) */ public function getDefaultLanguage() { - return $this->getLanguage($this->cache['default']); + return $this->cache->getDefaultLanguage(); } /** @@ -273,7 +249,7 @@ public function getDefaultLanguage() */ public function getDefaultLanguageID() { - return $this->cache['default']; + return $this->cache->default; } /** @@ -283,7 +259,7 @@ public function getDefaultLanguageID() */ public function getLanguages() { - return $this->cache['languages']; + return $this->cache->languages; } /** @@ -353,7 +329,7 @@ public function deleteLanguageCache() { LanguageEditor::deleteLanguageFiles(); - foreach ($this->cache['languages'] as $language) { + foreach ($this->cache->languages as $language) { $languageEditor = new LanguageEditor($language); $languageEditor->deleteCompiledTemplates(); } @@ -366,7 +342,7 @@ public function deleteLanguageCache() */ public function multilingualismEnabled() { - return $this->cache['multilingualismEnabled']; + return $this->cache->multilingualismEnabled; } /** From 7b111be00f69b11e7977b6df1d1632c4c77b9cd1 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Thu, 20 Feb 2025 11:17:01 +0100 Subject: [PATCH 05/42] Rename function `getCategoryByID()` to `getLanguageCategoryByID()` --- .../lib/system/cache/eager/data/LanguageCacheData.class.php | 2 +- .../install/files/lib/system/language/LanguageFactory.class.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/wcfsetup/install/files/lib/system/cache/eager/data/LanguageCacheData.class.php b/wcfsetup/install/files/lib/system/cache/eager/data/LanguageCacheData.class.php index c4e52b6ab3d..9d92bbb31fe 100644 --- a/wcfsetup/install/files/lib/system/cache/eager/data/LanguageCacheData.class.php +++ b/wcfsetup/install/files/lib/system/cache/eager/data/LanguageCacheData.class.php @@ -60,7 +60,7 @@ public function getLanguageCategory(string $categoryName): ?LanguageCategory /** * Returns the language category with the given category id. */ - public function getCategoryByID(int $languageCategoryID): ?LanguageCategory + public function getLanguageCategoryByID(int $languageCategoryID): ?LanguageCategory { return $this->categories[$this->categoryIDs[$languageCategoryID] ?? null] ?? null; } diff --git a/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php b/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php index 1809bdd4e09..867a03c9197 100644 --- a/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php +++ b/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php @@ -123,7 +123,7 @@ public function getCategory($categoryName): ?LanguageCategory */ public function getCategoryByID($languageCategoryID) { - return $this->cache->getCategoryByID($languageCategoryID); + return $this->cache->getLanguageCategoryByID($languageCategoryID); } /** From 4f1c2955f5307a9f3589ab8020b79f537b96fb4d Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Thu, 20 Feb 2025 11:18:55 +0100 Subject: [PATCH 06/42] Remove old `LanguageCacheBuilder` --- .../builder/LanguageCacheBuilder.class.php | 68 ------------------- 1 file changed, 68 deletions(-) delete mode 100644 wcfsetup/install/files/lib/system/cache/builder/LanguageCacheBuilder.class.php diff --git a/wcfsetup/install/files/lib/system/cache/builder/LanguageCacheBuilder.class.php b/wcfsetup/install/files/lib/system/cache/builder/LanguageCacheBuilder.class.php deleted file mode 100644 index 9912756fa4f..00000000000 --- a/wcfsetup/install/files/lib/system/cache/builder/LanguageCacheBuilder.class.php +++ /dev/null @@ -1,68 +0,0 @@ - - */ -class LanguageCacheBuilder extends AbstractCacheBuilder -{ - /** - * @inheritDoc - */ - public function rebuild(array $parameters) - { - $data = [ - 'codes' => [], - 'countryCodes' => [], - 'languages' => [], - 'default' => 0, - 'categories' => [], - 'categoryIDs' => [], - 'multilingualismEnabled' => false, - ]; - - // get languages - $languageList = new LanguageList(); - $languageList->getConditionBuilder()->add('language.isDisabled = ?', [0]); - $languageList->readObjects(); - $data['languages'] = $languageList->getObjects(); - foreach ($languageList->getObjects() as $language) { - // default language - if ($language->isDefault) { - $data['default'] = $language->languageID; - } - - // multilingualism - if ($language->hasContent) { - $data['multilingualismEnabled'] = true; - } - - // language code to language id - $data['codes'][$language->languageCode] = $language->languageID; - - // country code to language id - $data['countryCode'][$language->languageID] = $language->countryCode; - } - - DatabaseObject::sort($data['languages'], 'languageName'); - - // get language categories - $languageCategoryList = new LanguageCategoryList(); - $languageCategoryList->readObjects(); - foreach ($languageCategoryList->getObjects() as $languageCategory) { - $data['categories'][$languageCategory->languageCategory] = $languageCategory; - $data['categoryIDs'][$languageCategory->languageCategoryID] = $languageCategory->languageCategory; - } - - return $data; - } -} From e471fae580eec0d643dcf3d631794004c5e85a64 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Thu, 20 Feb 2025 11:31:46 +0100 Subject: [PATCH 07/42] Use more unique function names in `AbstractEagerCache` --- .../acp/form/LanguageMultilingualismForm.class.php | 2 +- .../files/lib/data/language/LanguageEditor.class.php | 2 +- wcfsetup/install/files/lib/system/WCFSetup.class.php | 2 +- .../system/cache/eager/AbstractEagerCache.class.php | 12 ++++++------ .../lib/system/cache/eager/CoreObjectCache.class.php | 2 +- .../lib/system/cache/eager/LanguageCache.class.php | 2 +- .../lib/system/language/LanguageFactory.class.php | 2 +- .../CoreObjectPackageInstallationPlugin.class.php | 2 +- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/wcfsetup/install/files/lib/acp/form/LanguageMultilingualismForm.class.php b/wcfsetup/install/files/lib/acp/form/LanguageMultilingualismForm.class.php index b3cc21816ca..3cc52c05dfd 100644 --- a/wcfsetup/install/files/lib/acp/form/LanguageMultilingualismForm.class.php +++ b/wcfsetup/install/files/lib/acp/form/LanguageMultilingualismForm.class.php @@ -116,7 +116,7 @@ public function save() LanguageEditor::enableMultilingualism(($this->enable == 1 ? $this->languageIDs : [])); // clear cache - (new LanguageCache())->reset(); + (new LanguageCache())->rebuild(); $this->saved(); // show success message diff --git a/wcfsetup/install/files/lib/data/language/LanguageEditor.class.php b/wcfsetup/install/files/lib/data/language/LanguageEditor.class.php index 56ffb9b0f76..2a208e05331 100644 --- a/wcfsetup/install/files/lib/data/language/LanguageEditor.class.php +++ b/wcfsetup/install/files/lib/data/language/LanguageEditor.class.php @@ -935,7 +935,7 @@ public function setAsDefault() */ public function clearCache() { - (new LanguageCache())->reset(); + (new LanguageCache())->rebuild(); } /** diff --git a/wcfsetup/install/files/lib/system/WCFSetup.class.php b/wcfsetup/install/files/lib/system/WCFSetup.class.php index 861bf9cd973..00508fb9aa4 100644 --- a/wcfsetup/install/files/lib/system/WCFSetup.class.php +++ b/wcfsetup/install/files/lib/system/WCFSetup.class.php @@ -779,7 +779,7 @@ protected function installLanguage(): ResponseInterface LanguageFactory::getInstance()->makeDefault($language->languageID); // rebuild language cache - (new LanguageCache())->reset(); + (new LanguageCache())->rebuild(); return $this->gotoNextStep('createUser'); } diff --git a/wcfsetup/install/files/lib/system/cache/eager/AbstractEagerCache.class.php b/wcfsetup/install/files/lib/system/cache/eager/AbstractEagerCache.class.php index eb609630225..39b1db18d5e 100644 --- a/wcfsetup/install/files/lib/system/cache/eager/AbstractEagerCache.class.php +++ b/wcfsetup/install/files/lib/system/cache/eager/AbstractEagerCache.class.php @@ -32,7 +32,7 @@ final public function getCache(): array | object if (!\array_key_exists($key, AbstractEagerCache::$caches)) { $cache = CacheHandler::getInstance()->getCacheSource()->get($key, 0); if ($cache === null) { - $this->reset(); + $this->rebuild(); } else { AbstractEagerCache::$caches[$key] = $cache; } @@ -65,20 +65,20 @@ private function getCacheKey(): string } /** - * Rebuilds the cache and stores it in the cache source. + * Rebuilds the cache data and stores the updated data. */ - final public function reset(): void + final public function rebuild(): void { $key = $this->getCacheKey(); - AbstractEagerCache::$caches[$key] = $this->rebuild(); + AbstractEagerCache::$caches[$key] = $this->getCacheData(); CacheHandler::getInstance()->getCacheSource()->set($key, AbstractEagerCache::$caches[$key], 0); } /** - * Rebuilds the cache and returns it. + * Generates the cache data and returns it. * This method MUST NOT rely on any (runtime) cache at any point because those could be stale. * * @return T */ - abstract protected function rebuild(): array | object; + abstract protected function getCacheData(): array | object; } diff --git a/wcfsetup/install/files/lib/system/cache/eager/CoreObjectCache.class.php b/wcfsetup/install/files/lib/system/cache/eager/CoreObjectCache.class.php index e80b276b6d0..8a05b2dd21a 100644 --- a/wcfsetup/install/files/lib/system/cache/eager/CoreObjectCache.class.php +++ b/wcfsetup/install/files/lib/system/cache/eager/CoreObjectCache.class.php @@ -18,7 +18,7 @@ final class CoreObjectCache extends AbstractEagerCache { #[\Override] - protected function rebuild(): array + protected function getCacheData(): array { $coreObjectList = new CoreObjectList(); $coreObjectList->readObjects(); diff --git a/wcfsetup/install/files/lib/system/cache/eager/LanguageCache.class.php b/wcfsetup/install/files/lib/system/cache/eager/LanguageCache.class.php index c0c88928597..bec8d02b322 100644 --- a/wcfsetup/install/files/lib/system/cache/eager/LanguageCache.class.php +++ b/wcfsetup/install/files/lib/system/cache/eager/LanguageCache.class.php @@ -20,7 +20,7 @@ final class LanguageCache extends AbstractEagerCache { #[\Override] - protected function rebuild(): LanguageCacheData + protected function getCacheData(): LanguageCacheData { $languageList = new LanguageList(); $languageList->getConditionBuilder()->add('language.isDisabled = ?', [0]); diff --git a/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php b/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php index 867a03c9197..1340b85d4c2 100644 --- a/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php +++ b/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php @@ -216,7 +216,7 @@ protected function loadCache() */ public function clearCache() { - (new LanguageCache())->reset(); + (new LanguageCache())->rebuild(); } /** diff --git a/wcfsetup/install/files/lib/system/package/plugin/CoreObjectPackageInstallationPlugin.class.php b/wcfsetup/install/files/lib/system/package/plugin/CoreObjectPackageInstallationPlugin.class.php index abbccae3964..cf00b583d34 100644 --- a/wcfsetup/install/files/lib/system/package/plugin/CoreObjectPackageInstallationPlugin.class.php +++ b/wcfsetup/install/files/lib/system/package/plugin/CoreObjectPackageInstallationPlugin.class.php @@ -85,7 +85,7 @@ protected function findExistingItem(array $data) */ protected function cleanup() { - (new CoreObjectCache())->reset(); + (new CoreObjectCache())->rebuild(); } /** From 867b7221b6c96ed4dd3896166f2d869f2c21afdc Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Thu, 20 Feb 2025 11:55:17 +0100 Subject: [PATCH 08/42] Always use the current language cache --- .../system/language/LanguageFactory.class.php | 37 +++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php b/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php index 1340b85d4c2..ade54608c7a 100644 --- a/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php +++ b/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php @@ -7,7 +7,6 @@ use wcf\data\language\category\LanguageCategory; use wcf\data\language\Language; use wcf\data\language\LanguageEditor; -use wcf\system\cache\eager\data\LanguageCacheData; use wcf\system\cache\eager\LanguageCache; use wcf\system\SingletonFactory; use wcf\system\template\TemplateScriptingCompiler; @@ -25,7 +24,7 @@ class LanguageFactory extends SingletonFactory /** * language cache */ - protected LanguageCacheData $cache; + protected LanguageCache $cache; /** * active template scripting compiler @@ -46,7 +45,7 @@ protected function init() */ public function getLanguage(int $languageID): ?Language { - return $this->cache->getLanguage($languageID); + return $this->cache->getCache()->getLanguage($languageID); } /** @@ -79,7 +78,7 @@ public function getUserLanguage($languageID = 0) public function getLanguageByCode($languageCode) { // called within WCFSetup - if (empty($this->cache->codes)) { + if (empty($this->cache->getCache()->codes)) { $sql = "SELECT languageID FROM wcf1_language WHERE languageCode = ?"; @@ -89,8 +88,8 @@ public function getLanguageByCode($languageCode) if (isset($row['languageID'])) { return new Language($row['languageID']); } - } elseif (isset($this->cache->codes[$languageCode])) { - return $this->getLanguage($this->cache->codes[$languageCode]); + } elseif (isset($this->cache->getCache()->codes[$languageCode])) { + return $this->getLanguage($this->cache->getCache()->codes[$languageCode]); } return null; @@ -104,7 +103,7 @@ public function getLanguageByCode($languageCode) */ public function isValidCategory($categoryName) { - return isset($this->cache->categories[$categoryName]); + return isset($this->cache->getCache()->categories[$categoryName]); } /** @@ -112,7 +111,7 @@ public function isValidCategory($categoryName) */ public function getCategory($categoryName): ?LanguageCategory { - return $this->cache->getLanguageCategory($categoryName); + return $this->cache->getCache()->getLanguageCategory($categoryName); } /** @@ -123,7 +122,7 @@ public function getCategory($categoryName): ?LanguageCategory */ public function getCategoryByID($languageCategoryID) { - return $this->cache->getLanguageCategoryByID($languageCategoryID); + return $this->cache->getCache()->getLanguageCategoryByID($languageCategoryID); } /** @@ -133,7 +132,7 @@ public function getCategoryByID($languageCategoryID) */ public function getCategories() { - return $this->cache->categories; + return $this->cache->getCache()->categories; } /** @@ -148,13 +147,13 @@ protected function findPreferredLanguage() } // get default language - $defaultLanguageCode = $this->cache->languages[$this->cache->default]->languageCode; + $defaultLanguageCode = $this->cache->getCache()->languages[$this->cache->getCache()->default]->languageCode; // get preferred language $languageCode = self::getPreferredLanguage($availableLanguageCodes, $defaultLanguageCode); // get language id of preferred language - foreach ($this->cache->languages as $key => $language) { + foreach ($this->cache->getCache()->languages as $key => $language) { if ($language->languageCode == $languageCode) { return $key; } @@ -208,7 +207,7 @@ public function getScriptingCompiler() */ protected function loadCache() { - $this->cache = (new LanguageCache())->getCache(); + $this->cache = new LanguageCache(); } /** @@ -216,7 +215,7 @@ protected function loadCache() */ public function clearCache() { - (new LanguageCache())->rebuild(); + $this->cache->rebuild(); } /** @@ -239,7 +238,7 @@ public static function fixLanguageCode($languageCode) */ public function getDefaultLanguage() { - return $this->cache->getDefaultLanguage(); + return $this->cache->getCache()->getDefaultLanguage(); } /** @@ -249,7 +248,7 @@ public function getDefaultLanguage() */ public function getDefaultLanguageID() { - return $this->cache->default; + return $this->cache->getCache()->default; } /** @@ -259,7 +258,7 @@ public function getDefaultLanguageID() */ public function getLanguages() { - return $this->cache->languages; + return $this->cache->getCache()->languages; } /** @@ -329,7 +328,7 @@ public function deleteLanguageCache() { LanguageEditor::deleteLanguageFiles(); - foreach ($this->cache->languages as $language) { + foreach ($this->cache->getCache()->languages as $language) { $languageEditor = new LanguageEditor($language); $languageEditor->deleteCompiledTemplates(); } @@ -342,7 +341,7 @@ public function deleteLanguageCache() */ public function multilingualismEnabled() { - return $this->cache->multilingualismEnabled; + return $this->cache->getCache()->multilingualismEnabled; } /** From b3f827b90ae6144bf93c3f614060a21424caf9a0 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Thu, 20 Feb 2025 11:58:43 +0100 Subject: [PATCH 09/42] Disabled flag must be reset beforehand, otherwise the default language cannot be loaded in the `LanguageCache`. --- .../install/files/lib/data/language/LanguageEditor.class.php | 2 +- .../controller/core/languages/SetAsDefaultLanguage.class.php | 4 ---- .../files/lib/system/language/LanguageFactory.class.php | 3 ++- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/wcfsetup/install/files/lib/data/language/LanguageEditor.class.php b/wcfsetup/install/files/lib/data/language/LanguageEditor.class.php index 2a208e05331..87153a46b03 100644 --- a/wcfsetup/install/files/lib/data/language/LanguageEditor.class.php +++ b/wcfsetup/install/files/lib/data/language/LanguageEditor.class.php @@ -925,7 +925,7 @@ public function setAsDefault() $statement->execute([0]); // set current language as default language - $this->update(['isDefault' => 1]); + $this->update(['isDefault' => 1, 'isDisabled' => 0]); $this->clearCache(); } diff --git a/wcfsetup/install/files/lib/system/endpoint/controller/core/languages/SetAsDefaultLanguage.class.php b/wcfsetup/install/files/lib/system/endpoint/controller/core/languages/SetAsDefaultLanguage.class.php index 79b6c1fca86..f0e583ea3a1 100644 --- a/wcfsetup/install/files/lib/system/endpoint/controller/core/languages/SetAsDefaultLanguage.class.php +++ b/wcfsetup/install/files/lib/system/endpoint/controller/core/languages/SetAsDefaultLanguage.class.php @@ -34,10 +34,6 @@ public function __invoke(ServerRequestInterface $request, array $variables): Res $languageEditor = new LanguageEditor($language); $languageEditor->setAsDefault(); - if ($languageEditor->isDisabled) { - $languageEditor->update(['isDisabled' => 0]); - } - return new JsonResponse([]); } diff --git a/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php b/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php index ade54608c7a..8dfa6e2f4dd 100644 --- a/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php +++ b/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php @@ -312,7 +312,8 @@ public function makeDefault($languageID) // make this language to default $sql = "UPDATE wcf1_language - SET isDefault = 1 + SET isDefault = 1, + isDisabled = 0 WHERE languageID = ?"; $statement = WCF::getDB()->prepare($sql); $statement->execute([$languageID]); From 09a81d3d6f9a506ca29426ae7148592668efbe63 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Thu, 20 Feb 2025 12:09:00 +0100 Subject: [PATCH 10/42] Do not use the members that have not been set(`null`). --- .../lib/system/cache/eager/AbstractEagerCache.class.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/wcfsetup/install/files/lib/system/cache/eager/AbstractEagerCache.class.php b/wcfsetup/install/files/lib/system/cache/eager/AbstractEagerCache.class.php index 39b1db18d5e..709e396e347 100644 --- a/wcfsetup/install/files/lib/system/cache/eager/AbstractEagerCache.class.php +++ b/wcfsetup/install/files/lib/system/cache/eager/AbstractEagerCache.class.php @@ -54,8 +54,14 @@ private function getCacheKey(): string $parameters = []; foreach ($reflection->getProperties() as $property) { - $parameters[$property->getName()] = $property->getValue($this); + $value = $property->getValue($this); + if ($value === null) { + continue; + } + + $parameters[$property->getName()] = $value; } + if ($parameters !== []) { $this->cacheName .= '-' . CacheHandler::getInstance()->getCacheIndex($parameters); } From fa8d2845a8dc0a0105cbb1c82db985999a52b63a Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Thu, 20 Feb 2025 12:21:04 +0100 Subject: [PATCH 11/42] Prevent access to uninitialized members or static variables --- .../lib/system/cache/eager/AbstractEagerCache.class.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/wcfsetup/install/files/lib/system/cache/eager/AbstractEagerCache.class.php b/wcfsetup/install/files/lib/system/cache/eager/AbstractEagerCache.class.php index 709e396e347..1e4540d06ed 100644 --- a/wcfsetup/install/files/lib/system/cache/eager/AbstractEagerCache.class.php +++ b/wcfsetup/install/files/lib/system/cache/eager/AbstractEagerCache.class.php @@ -53,7 +53,11 @@ private function getCacheKey(): string ); $parameters = []; - foreach ($reflection->getProperties() as $property) { + foreach ($reflection->getProperties(\ReflectionProperty::IS_READONLY) as $property) { + if (!$property->isInitialized($this)) { + continue; + } + $value = $property->getValue($this); if ($value === null) { continue; From eeafaec0c07cb38d5a7766164f31491622a764c7 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Fri, 21 Feb 2025 08:42:10 +0100 Subject: [PATCH 12/42] Clean up code --- .../lib/system/cache/eager/LanguageCache.class.php | 11 +++++------ .../cache/eager/data/LanguageCacheData.class.php | 6 +++--- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/wcfsetup/install/files/lib/system/cache/eager/LanguageCache.class.php b/wcfsetup/install/files/lib/system/cache/eager/LanguageCache.class.php index bec8d02b322..dd14cc5d339 100644 --- a/wcfsetup/install/files/lib/system/cache/eager/LanguageCache.class.php +++ b/wcfsetup/install/files/lib/system/cache/eager/LanguageCache.class.php @@ -6,6 +6,7 @@ use wcf\data\language\category\LanguageCategoryList; use wcf\data\language\LanguageList; use wcf\system\cache\eager\data\LanguageCacheData; +use wcf\system\exception\SystemException; /** * Eager cache implementation for languages. @@ -33,26 +34,20 @@ protected function getCacheData(): LanguageCacheData $countryCodes = []; foreach ($languageList->getObjects() as $language) { - // default language if ($language->isDefault) { $default = $language->languageID; } - // multilingualism if ($language->hasContent) { $multilingualismEnabled = true; } - // language code to language id $codes[$language->languageCode] = $language->languageID; - - // country code to language id $countryCodes[$language->languageID] = $language->countryCode; } DatabaseObject::sort($languages, 'languageName'); - // get language categories $languageCategoryList = new LanguageCategoryList(); $languageCategoryList->readObjects(); @@ -63,6 +58,10 @@ protected function getCacheData(): LanguageCacheData $categoryIDs[$languageCategory->languageCategoryID] = $languageCategory->languageCategory; } + if (!isset($languages[$default])) { + throw new SystemException('No default language defined!'); + } + return new LanguageCacheData( $codes, $countryCodes, diff --git a/wcfsetup/install/files/lib/system/cache/eager/data/LanguageCacheData.class.php b/wcfsetup/install/files/lib/system/cache/eager/data/LanguageCacheData.class.php index 9d92bbb31fe..44ba9ebc0fa 100644 --- a/wcfsetup/install/files/lib/system/cache/eager/data/LanguageCacheData.class.php +++ b/wcfsetup/install/files/lib/system/cache/eager/data/LanguageCacheData.class.php @@ -29,8 +29,6 @@ public function __construct( public readonly array $categoryIDs, public readonly bool $multilingualismEnabled ) { - \assert($this->default > 0); - \assert(\array_key_exists($this->default, $this->languages)); } /** @@ -62,6 +60,8 @@ public function getLanguageCategory(string $categoryName): ?LanguageCategory */ public function getLanguageCategoryByID(int $languageCategoryID): ?LanguageCategory { - return $this->categories[$this->categoryIDs[$languageCategoryID] ?? null] ?? null; + $categoryName = $this->categoryIDs[$languageCategoryID] ?? null; + + return $this->categories[$categoryName] ?? null; } } From 331328790b2a625c16914d4345158c0193c17291 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Fri, 21 Feb 2025 08:47:36 +0100 Subject: [PATCH 13/42] Use the cache data directly instead of always fetching the current cache data and working with it --- .../install/files/lib/system/WCF.class.php | 19 +++------- .../system/language/LanguageFactory.class.php | 37 ++++++++++--------- 2 files changed, 24 insertions(+), 32 deletions(-) diff --git a/wcfsetup/install/files/lib/system/WCF.class.php b/wcfsetup/install/files/lib/system/WCF.class.php index 83b69237a21..ef2a1473780 100644 --- a/wcfsetup/install/files/lib/system/WCF.class.php +++ b/wcfsetup/install/files/lib/system/WCF.class.php @@ -18,7 +18,6 @@ use wcf\system\event\EventHandler; use wcf\system\exception\ErrorException; use wcf\system\exception\IPrintableException; -use wcf\system\exception\ParentClassException; use wcf\system\exception\SystemException; use wcf\system\language\LanguageFactory; use wcf\system\package\command\RebuildBootstrapper; @@ -141,9 +140,9 @@ class WCF /** * list of cached core objects * - * @var CoreObjectCache + * @var array> */ - protected static CoreObjectCache $coreObjectCache; + protected static array $coreObjectCache; /** * database object @@ -749,7 +748,7 @@ protected function initCoreObjects(): void return; } - self::$coreObjectCache = new CoreObjectCache(); + self::$coreObjectCache = (new CoreObjectCache())->getCache(); } /** @@ -915,10 +914,6 @@ final public static function __callStatic(string $name, array $arguments) } if (\class_exists($objectName)) { - if (!\is_subclass_of($objectName, SingletonFactory::class)) { - throw new ParentClassException($objectName, SingletonFactory::class); - } - self::$coreObject[$className] = \call_user_func([$objectName, 'getInstance']); return self::$coreObject[$className]; @@ -930,15 +925,11 @@ final public static function __callStatic(string $name, array $arguments) /** * Searches for cached core object definition. * - * @return string|null + * @return class-string|null */ final protected static function getCoreObject(string $className) { - if (!isset(self::$coreObjectCache)) { - return null; - } - - return self::$coreObjectCache->getCache()[$className] ?? null; + return self::$coreObjectCache[$className] ?? null; } /** diff --git a/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php b/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php index 8dfa6e2f4dd..613a60461e0 100644 --- a/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php +++ b/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php @@ -7,6 +7,7 @@ use wcf\data\language\category\LanguageCategory; use wcf\data\language\Language; use wcf\data\language\LanguageEditor; +use wcf\system\cache\eager\data\LanguageCacheData; use wcf\system\cache\eager\LanguageCache; use wcf\system\SingletonFactory; use wcf\system\template\TemplateScriptingCompiler; @@ -24,7 +25,7 @@ class LanguageFactory extends SingletonFactory /** * language cache */ - protected LanguageCache $cache; + protected LanguageCacheData $cache; /** * active template scripting compiler @@ -45,7 +46,7 @@ protected function init() */ public function getLanguage(int $languageID): ?Language { - return $this->cache->getCache()->getLanguage($languageID); + return $this->cache->getLanguage($languageID); } /** @@ -78,7 +79,7 @@ public function getUserLanguage($languageID = 0) public function getLanguageByCode($languageCode) { // called within WCFSetup - if (empty($this->cache->getCache()->codes)) { + if (empty($this->cache->codes)) { $sql = "SELECT languageID FROM wcf1_language WHERE languageCode = ?"; @@ -88,8 +89,8 @@ public function getLanguageByCode($languageCode) if (isset($row['languageID'])) { return new Language($row['languageID']); } - } elseif (isset($this->cache->getCache()->codes[$languageCode])) { - return $this->getLanguage($this->cache->getCache()->codes[$languageCode]); + } elseif (isset($this->cache->codes[$languageCode])) { + return $this->getLanguage($this->cache->codes[$languageCode]); } return null; @@ -103,7 +104,7 @@ public function getLanguageByCode($languageCode) */ public function isValidCategory($categoryName) { - return isset($this->cache->getCache()->categories[$categoryName]); + return isset($this->cache->categories[$categoryName]); } /** @@ -111,7 +112,7 @@ public function isValidCategory($categoryName) */ public function getCategory($categoryName): ?LanguageCategory { - return $this->cache->getCache()->getLanguageCategory($categoryName); + return $this->cache->getLanguageCategory($categoryName); } /** @@ -122,7 +123,7 @@ public function getCategory($categoryName): ?LanguageCategory */ public function getCategoryByID($languageCategoryID) { - return $this->cache->getCache()->getLanguageCategoryByID($languageCategoryID); + return $this->cache->getLanguageCategoryByID($languageCategoryID); } /** @@ -132,7 +133,7 @@ public function getCategoryByID($languageCategoryID) */ public function getCategories() { - return $this->cache->getCache()->categories; + return $this->cache->categories; } /** @@ -147,13 +148,13 @@ protected function findPreferredLanguage() } // get default language - $defaultLanguageCode = $this->cache->getCache()->languages[$this->cache->getCache()->default]->languageCode; + $defaultLanguageCode = $this->cache->languages[$this->cache->default]->languageCode; // get preferred language $languageCode = self::getPreferredLanguage($availableLanguageCodes, $defaultLanguageCode); // get language id of preferred language - foreach ($this->cache->getCache()->languages as $key => $language) { + foreach ($this->cache->languages as $key => $language) { if ($language->languageCode == $languageCode) { return $key; } @@ -207,7 +208,7 @@ public function getScriptingCompiler() */ protected function loadCache() { - $this->cache = new LanguageCache(); + $this->cache = (new LanguageCache())->getCache(); } /** @@ -215,7 +216,7 @@ protected function loadCache() */ public function clearCache() { - $this->cache->rebuild(); + (new LanguageCache())->rebuild(); } /** @@ -238,7 +239,7 @@ public static function fixLanguageCode($languageCode) */ public function getDefaultLanguage() { - return $this->cache->getCache()->getDefaultLanguage(); + return $this->cache->getDefaultLanguage(); } /** @@ -248,7 +249,7 @@ public function getDefaultLanguage() */ public function getDefaultLanguageID() { - return $this->cache->getCache()->default; + return $this->cache->default; } /** @@ -258,7 +259,7 @@ public function getDefaultLanguageID() */ public function getLanguages() { - return $this->cache->getCache()->languages; + return $this->cache->languages; } /** @@ -329,7 +330,7 @@ public function deleteLanguageCache() { LanguageEditor::deleteLanguageFiles(); - foreach ($this->cache->getCache()->languages as $language) { + foreach ($this->cache->languages as $language) { $languageEditor = new LanguageEditor($language); $languageEditor->deleteCompiledTemplates(); } @@ -342,7 +343,7 @@ public function deleteLanguageCache() */ public function multilingualismEnabled() { - return $this->cache->getCache()->multilingualismEnabled; + return $this->cache->multilingualismEnabled; } /** From 5ebecc0cfad5b9ea1fbf2d2d481d270b5fd6ddc5 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Fri, 21 Feb 2025 09:08:32 +0100 Subject: [PATCH 14/42] Use helper functions of `LanguageCacheData` --- .../eager/data/LanguageCacheData.class.php | 56 +++++++++ .../system/language/LanguageFactory.class.php | 116 ++++++------------ 2 files changed, 91 insertions(+), 81 deletions(-) diff --git a/wcfsetup/install/files/lib/system/cache/eager/data/LanguageCacheData.class.php b/wcfsetup/install/files/lib/system/cache/eager/data/LanguageCacheData.class.php index 44ba9ebc0fa..209375c3681 100644 --- a/wcfsetup/install/files/lib/system/cache/eager/data/LanguageCacheData.class.php +++ b/wcfsetup/install/files/lib/system/cache/eager/data/LanguageCacheData.class.php @@ -64,4 +64,60 @@ public function getLanguageCategoryByID(int $languageCategoryID): ?LanguageCateg return $this->categories[$categoryName] ?? null; } + + /** + * Returns `true` if the language category with the given category name exists. + */ + public function languageCategoryExists(string $categoryName): bool + { + return isset($this->categories[$categoryName]); + } + + /** + * Return all content languages. + * + * @return list + */ + public function getContentLanguages(): array + { + return \array_filter( + $this->languages, + static fn(Language $language) => \boolval($language->hasContent) + ); + } + + /** + * Return all content languages IDs. + * + * @return list + */ + public function getContentLanguageIDs(): array + { + return \array_map( + static fn(Language $language) => $language->languageID, + $this->getContentLanguages() + ); + } + + /** + * Return all language codes. + * + * @return list + */ + public function getLanguageCodes(): array + { + return \array_keys($this->codes); + } + + /** + * Return language by given language code. + */ + public function getLanguageByCode(string $languageCode): ?Language + { + if (isset($this->codes[$languageCode])) { + return $this->languages[$this->codes[$languageCode]]; + } + + return null; + } } diff --git a/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php b/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php index 613a60461e0..654bdbc01db 100644 --- a/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php +++ b/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php @@ -9,6 +9,7 @@ use wcf\data\language\LanguageEditor; use wcf\system\cache\eager\data\LanguageCacheData; use wcf\system\cache\eager\LanguageCache; +use wcf\system\exception\SystemException; use wcf\system\SingletonFactory; use wcf\system\template\TemplateScriptingCompiler; use wcf\system\WCF; @@ -51,14 +52,11 @@ public function getLanguage(int $languageID): ?Language /** * Returns the preferred language of the current user. - * - * @param int $languageID - * @return Language */ - public function getUserLanguage($languageID = 0) + public function getUserLanguage(int $languageID = 0): Language { if ($languageID) { - $language = $this->getLanguage($languageID); + $language = $this->cache->getLanguage($languageID); if ($language !== null) { return $language; } @@ -66,62 +64,49 @@ public function getUserLanguage($languageID = 0) $languageID = $this->findPreferredLanguage(); - return $this->getLanguage($languageID); + return $this->cache->getLanguage($languageID); } /** * Returns the language with the given language code or null if no such * language exists. - * - * @param string $languageCode - * @return ?Language */ - public function getLanguageByCode($languageCode) + public function getLanguageByCode(string $languageCode): ?Language { - // called within WCFSetup - if (empty($this->cache->codes)) { - $sql = "SELECT languageID + $language = $this->getLanguageByCode($languageCode); + if ($language === null) { + // called within WCFSetup + $sql = "SELECT * FROM wcf1_language WHERE languageCode = ?"; $statement = WCF::getDB()->prepare($sql); $statement->execute([$languageCode]); - $row = $statement->fetchArray(); - if (isset($row['languageID'])) { - return new Language($row['languageID']); - } - } elseif (isset($this->cache->codes[$languageCode])) { - return $this->getLanguage($this->cache->codes[$languageCode]); + $language = $statement->fetchObject(Language::class); } - return null; + return $language; } /** * Returns true if the language category with the given name exists. - * - * @param string $categoryName - * @return bool */ - public function isValidCategory($categoryName) + public function isValidCategory(string $categoryName): bool { - return isset($this->cache->categories[$categoryName]); + return $this->cache->languageCategoryExists($categoryName); } /** * Returns the language category with the given name. */ - public function getCategory($categoryName): ?LanguageCategory + public function getCategory(string $categoryName): ?LanguageCategory { return $this->cache->getLanguageCategory($categoryName); } /** * Returns language category by id. - * - * @param int $languageCategoryID - * @return LanguageCategory|null */ - public function getCategoryByID($languageCategoryID) + public function getCategoryByID(int $languageCategoryID): ?LanguageCategory { return $this->cache->getLanguageCategoryByID($languageCategoryID); } @@ -131,7 +116,7 @@ public function getCategoryByID($languageCategoryID) * * @return LanguageCategory[] */ - public function getCategories() + public function getCategories(): array { return $this->cache->categories; } @@ -139,19 +124,12 @@ public function getCategories() /** * Searches the preferred language of the current user. */ - protected function findPreferredLanguage() + protected function findPreferredLanguage(): int { - // get available language codes - $availableLanguageCodes = []; - foreach ($this->getLanguages() as $language) { - $availableLanguageCodes[] = $language->languageCode; - } - - // get default language - $defaultLanguageCode = $this->cache->languages[$this->cache->default]->languageCode; + $defaultLanguageCode = $this->cache->getDefaultLanguage()->languageCode; // get preferred language - $languageCode = self::getPreferredLanguage($availableLanguageCodes, $defaultLanguageCode); + $languageCode = self::getPreferredLanguage($this->cache->getLanguageCodes(), $defaultLanguageCode); // get language id of preferred language foreach ($this->cache->languages as $key => $language) { @@ -159,6 +137,8 @@ protected function findPreferredLanguage() return $key; } } + + throw new SystemException("No language found for language code '{$languageCode}'"); } /** @@ -191,10 +171,8 @@ public static function getPreferredLanguage(array $availableLanguageCodes, strin /** * Returns the active scripting compiler object. - * - * @return TemplateScriptingCompiler */ - public function getScriptingCompiler() + public function getScriptingCompiler(): TemplateScriptingCompiler { if ($this->scriptingCompiler === null) { $this->scriptingCompiler = new TemplateScriptingCompiler(WCF::getTPL()); @@ -206,7 +184,7 @@ public function getScriptingCompiler() /** * Loads the language cache. */ - protected function loadCache() + protected function loadCache(): void { $this->cache = (new LanguageCache())->getCache(); } @@ -214,7 +192,7 @@ protected function loadCache() /** * Clears languages cache. */ - public function clearCache() + public function clearCache(): void { (new LanguageCache())->rebuild(); } @@ -233,21 +211,17 @@ public static function fixLanguageCode($languageCode) /** * Returns the default language object. - * - * @return Language * @since 3.0 */ - public function getDefaultLanguage() + public function getDefaultLanguage(): Language { return $this->cache->getDefaultLanguage(); } /** * Returns the default language id - * - * @return int */ - public function getDefaultLanguageID() + public function getDefaultLanguageID(): int { return $this->cache->default; } @@ -257,7 +231,7 @@ public function getDefaultLanguageID() * * @return Language[] */ - public function getLanguages() + public function getLanguages(): array { return $this->cache->languages; } @@ -267,16 +241,9 @@ public function getLanguages() * * @return Language[] */ - public function getContentLanguages() + public function getContentLanguages(): array { - $availableLanguages = []; - foreach ($this->getLanguages() as $languageID => $language) { - if ($language->hasContent) { - $availableLanguages[$languageID] = $language; - } - } - - return $availableLanguages; + return $this->cache->getContentLanguages(); } /** @@ -285,24 +252,15 @@ public function getContentLanguages() * @return int[] * @since 3.1 */ - public function getContentLanguageIDs() + public function getContentLanguageIDs(): array { - $languageIDs = []; - foreach ($this->getLanguages() as $language) { - if ($language->hasContent) { - $languageIDs[] = $language->languageID; - } - } - - return $languageIDs; + return $this->cache->getContentLanguageIDs(); } /** * Makes given language the default language. - * - * @param int $languageID */ - public function makeDefault($languageID) + public function makeDefault(int $languageID): void { // remove old default language $sql = "UPDATE wcf1_language @@ -326,7 +284,7 @@ public function makeDefault($languageID) /** * Removes language cache and compiled templates. */ - public function deleteLanguageCache() + public function deleteLanguageCache(): void { LanguageEditor::deleteLanguageFiles(); @@ -338,20 +296,16 @@ public function deleteLanguageCache() /** * Returns true if multilingualism is enabled. - * - * @return bool */ - public function multilingualismEnabled() + public function multilingualismEnabled(): bool { return $this->cache->multilingualismEnabled; } /** * Returns the number of phrases that have been automatically disabled in the past 7 days. - * - * @return int */ - public function countRecentlyDisabledCustomValues() + public function countRecentlyDisabledCustomValues(): int { $sql = "SELECT COUNT(*) AS count FROM wcf1_language_item From a81e06ae89c4b2537fac65a963498dc9164032cf Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Fri, 21 Feb 2025 09:10:06 +0100 Subject: [PATCH 15/42] Change default value to `null` --- .../install/files/lib/system/language/LanguageFactory.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php b/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php index 654bdbc01db..798eea30e0f 100644 --- a/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php +++ b/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php @@ -53,7 +53,7 @@ public function getLanguage(int $languageID): ?Language /** * Returns the preferred language of the current user. */ - public function getUserLanguage(int $languageID = 0): Language + public function getUserLanguage(?int $languageID = null): Language { if ($languageID) { $language = $this->cache->getLanguage($languageID); From 1992e1824d971b7804189264c6fd32ae88cdab16 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Fri, 21 Feb 2025 09:40:51 +0100 Subject: [PATCH 16/42] Implementation of a legacy cache builder for the migrated caches to the eager cache --- .../AbstractLegacyCacheBuilder.class.php | 51 +++++++++++++++++++ .../builder/CoreObjectCacheBuilder.class.php | 29 +++++++++++ .../builder/LanguageCacheBuilder.class.php | 39 ++++++++++++++ 3 files changed, 119 insertions(+) create mode 100644 wcfsetup/install/files/lib/system/cache/builder/AbstractLegacyCacheBuilder.class.php create mode 100644 wcfsetup/install/files/lib/system/cache/builder/CoreObjectCacheBuilder.class.php create mode 100644 wcfsetup/install/files/lib/system/cache/builder/LanguageCacheBuilder.class.php diff --git a/wcfsetup/install/files/lib/system/cache/builder/AbstractLegacyCacheBuilder.class.php b/wcfsetup/install/files/lib/system/cache/builder/AbstractLegacyCacheBuilder.class.php new file mode 100644 index 00000000000..8c0b1bf1ec3 --- /dev/null +++ b/wcfsetup/install/files/lib/system/cache/builder/AbstractLegacyCacheBuilder.class.php @@ -0,0 +1,51 @@ + + * + * @since 6.2 + * @deprecated 6.2 + */ +abstract class AbstractLegacyCacheBuilder extends SingletonFactory implements ICacheBuilder +{ + /** + * maximum cache lifetime in seconds, '0' equals infinite + */ + protected int $maxLifetime = 0; + + #[\Override] + public function getData(array $parameters = [], $arrayIndex = '') + { + $cache = $this->rebuild($parameters); + + if (!empty($arrayIndex)) { + if (!\array_key_exists($arrayIndex, $cache)) { + throw new SystemException("array index '" . $arrayIndex . "' does not exist in cache resource"); + } + + return $cache[$arrayIndex]; + } + + return $cache; + } + + /** + * Rebuilds cache for current resource. + */ + abstract protected function rebuild(array $parameters): array; + + #[\Override] + public function getMaxLifetime() + { + return $this->maxLifetime; + } +} diff --git a/wcfsetup/install/files/lib/system/cache/builder/CoreObjectCacheBuilder.class.php b/wcfsetup/install/files/lib/system/cache/builder/CoreObjectCacheBuilder.class.php new file mode 100644 index 00000000000..edccdb1580c --- /dev/null +++ b/wcfsetup/install/files/lib/system/cache/builder/CoreObjectCacheBuilder.class.php @@ -0,0 +1,29 @@ + + * + * @deprecated 6.2 use `CoreObjectCache` instead + */ +class CoreObjectCacheBuilder extends AbstractLegacyCacheBuilder +{ + #[\Override] + public function reset(array $parameters = []) + { + (new CoreObjectCache())->rebuild(); + } + + #[\Override] + public function rebuild(array $parameters): array + { + return (new CoreObjectCache())->getCache(); + } +} diff --git a/wcfsetup/install/files/lib/system/cache/builder/LanguageCacheBuilder.class.php b/wcfsetup/install/files/lib/system/cache/builder/LanguageCacheBuilder.class.php new file mode 100644 index 00000000000..a790ddcdb40 --- /dev/null +++ b/wcfsetup/install/files/lib/system/cache/builder/LanguageCacheBuilder.class.php @@ -0,0 +1,39 @@ + + * + * @deprecated 6.2 use `LanguageCache` instead + */ +class LanguageCacheBuilder extends AbstractLegacyCacheBuilder +{ + #[\Override] + public function reset(array $parameters = []) + { + (new LanguageCache())->rebuild(); + } + + #[\Override] + public function rebuild(array $parameters): array + { + $cacheData = (new LanguageCache())->getCache(); + + return [ + 'codes' => $cacheData->codes, + 'countryCodes' => $cacheData->countryCodes, + 'languages' => $cacheData->languages, + 'default' => $cacheData->default, + 'categories' => $cacheData->categories, + 'categoryIDs' => $cacheData->categoryIDs, + 'multilingualismEnabled' => $cacheData->multilingualismEnabled, + ]; + } +} From 67589a1b0bb4679003d5b904bc5ba15ee3a72d33 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Fri, 21 Feb 2025 09:47:52 +0100 Subject: [PATCH 17/42] `MaxLifetime` is no longer needed and mapped to zero(`0`) --- .../cache/builder/AbstractLegacyCacheBuilder.class.php | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/wcfsetup/install/files/lib/system/cache/builder/AbstractLegacyCacheBuilder.class.php b/wcfsetup/install/files/lib/system/cache/builder/AbstractLegacyCacheBuilder.class.php index 8c0b1bf1ec3..ee1cad61601 100644 --- a/wcfsetup/install/files/lib/system/cache/builder/AbstractLegacyCacheBuilder.class.php +++ b/wcfsetup/install/files/lib/system/cache/builder/AbstractLegacyCacheBuilder.class.php @@ -17,11 +17,6 @@ */ abstract class AbstractLegacyCacheBuilder extends SingletonFactory implements ICacheBuilder { - /** - * maximum cache lifetime in seconds, '0' equals infinite - */ - protected int $maxLifetime = 0; - #[\Override] public function getData(array $parameters = [], $arrayIndex = '') { @@ -44,8 +39,8 @@ public function getData(array $parameters = [], $arrayIndex = '') abstract protected function rebuild(array $parameters): array; #[\Override] - public function getMaxLifetime() + final public function getMaxLifetime() { - return $this->maxLifetime; + return 0; } } From 9e645acd9f25ab6344aa6d121fb8f50959bcafe0 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Fri, 21 Feb 2025 09:54:41 +0100 Subject: [PATCH 18/42] simplify the code --- .../system/cache/eager/data/LanguageCacheData.class.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/wcfsetup/install/files/lib/system/cache/eager/data/LanguageCacheData.class.php b/wcfsetup/install/files/lib/system/cache/eager/data/LanguageCacheData.class.php index 209375c3681..9e7a052aa31 100644 --- a/wcfsetup/install/files/lib/system/cache/eager/data/LanguageCacheData.class.php +++ b/wcfsetup/install/files/lib/system/cache/eager/data/LanguageCacheData.class.php @@ -114,10 +114,11 @@ public function getLanguageCodes(): array */ public function getLanguageByCode(string $languageCode): ?Language { - if (isset($this->codes[$languageCode])) { - return $this->languages[$this->codes[$languageCode]]; + $languageID = $this->codes[$languageCode] ?? null; + if ($languageID === null) { + return null; } - return null; + return $this->getLanguage($languageID); } } From 5b956a2770928860eb80210e7195bacb3dbb3d70 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Fri, 21 Feb 2025 09:57:38 +0100 Subject: [PATCH 19/42] simplify the code --- .../system/cache/eager/data/LanguageCacheData.class.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/wcfsetup/install/files/lib/system/cache/eager/data/LanguageCacheData.class.php b/wcfsetup/install/files/lib/system/cache/eager/data/LanguageCacheData.class.php index 9e7a052aa31..1790347f692 100644 --- a/wcfsetup/install/files/lib/system/cache/eager/data/LanguageCacheData.class.php +++ b/wcfsetup/install/files/lib/system/cache/eager/data/LanguageCacheData.class.php @@ -61,8 +61,11 @@ public function getLanguageCategory(string $categoryName): ?LanguageCategory public function getLanguageCategoryByID(int $languageCategoryID): ?LanguageCategory { $categoryName = $this->categoryIDs[$languageCategoryID] ?? null; + if ($categoryName === null) { + return null; + } - return $this->categories[$categoryName] ?? null; + return $this->getLanguageCategory($categoryName); } /** @@ -70,7 +73,7 @@ public function getLanguageCategoryByID(int $languageCategoryID): ?LanguageCateg */ public function languageCategoryExists(string $categoryName): bool { - return isset($this->categories[$categoryName]); + return \array_key_exists($categoryName, $this->categories); } /** From b7d0b6e2510ea7235a636bfec27285b9bff945ac Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Fri, 21 Feb 2025 10:59:09 +0100 Subject: [PATCH 20/42] Ensure that Eager caches are only created once at runtime for the same parameters --- .../builder/AbstractLegacyCacheBuilder.class.php | 11 ++++++++++- .../system/cache/eager/AbstractEagerCache.class.php | 11 +++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/wcfsetup/install/files/lib/system/cache/builder/AbstractLegacyCacheBuilder.class.php b/wcfsetup/install/files/lib/system/cache/builder/AbstractLegacyCacheBuilder.class.php index ee1cad61601..da845cd6373 100644 --- a/wcfsetup/install/files/lib/system/cache/builder/AbstractLegacyCacheBuilder.class.php +++ b/wcfsetup/install/files/lib/system/cache/builder/AbstractLegacyCacheBuilder.class.php @@ -2,6 +2,7 @@ namespace wcf\system\cache\builder; +use wcf\system\cache\CacheHandler; use wcf\system\exception\SystemException; use wcf\system\SingletonFactory; @@ -17,10 +18,18 @@ */ abstract class AbstractLegacyCacheBuilder extends SingletonFactory implements ICacheBuilder { + protected array $cache = []; + #[\Override] public function getData(array $parameters = [], $arrayIndex = '') { - $cache = $this->rebuild($parameters); + $index = CacheHandler::getInstance()->getCacheIndex($parameters); + if (isset($this->cache[$index])) { + $cache = $this->cache[$index]; + } else { + $cache = $this->rebuild($parameters); + $this->cache[$index] = $cache; + } if (!empty($arrayIndex)) { if (!\array_key_exists($arrayIndex, $cache)) { diff --git a/wcfsetup/install/files/lib/system/cache/eager/AbstractEagerCache.class.php b/wcfsetup/install/files/lib/system/cache/eager/AbstractEagerCache.class.php index 1e4540d06ed..5d7bfe98ec5 100644 --- a/wcfsetup/install/files/lib/system/cache/eager/AbstractEagerCache.class.php +++ b/wcfsetup/install/files/lib/system/cache/eager/AbstractEagerCache.class.php @@ -80,8 +80,15 @@ private function getCacheKey(): string final public function rebuild(): void { $key = $this->getCacheKey(); - AbstractEagerCache::$caches[$key] = $this->getCacheData(); - CacheHandler::getInstance()->getCacheSource()->set($key, AbstractEagerCache::$caches[$key], 0); + $newCacheData = $this->getCacheData(); + + // The existing cache must not be overwritten, otherwise this can cause errors at runtime. + // The new data will be available at the next request. + if (!\array_key_exists($key, AbstractEagerCache::$caches)) { + AbstractEagerCache::$caches[$key] = $newCacheData; + } + + CacheHandler::getInstance()->getCacheSource()->set($key, $newCacheData, 0); } /** From 0c572ae07440c03877e6b6595f52497d273dac4f Mon Sep 17 00:00:00 2001 From: Olaf Braun Date: Fri, 21 Feb 2025 13:34:39 +0100 Subject: [PATCH 21/42] Apply suggestions from code review Co-authored-by: Alexander Ebert --- .../cache/eager/data/LanguageCacheData.class.php | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/wcfsetup/install/files/lib/system/cache/eager/data/LanguageCacheData.class.php b/wcfsetup/install/files/lib/system/cache/eager/data/LanguageCacheData.class.php index 1790347f692..fffd4760b1e 100644 --- a/wcfsetup/install/files/lib/system/cache/eager/data/LanguageCacheData.class.php +++ b/wcfsetup/install/files/lib/system/cache/eager/data/LanguageCacheData.class.php @@ -68,9 +68,6 @@ public function getLanguageCategoryByID(int $languageCategoryID): ?LanguageCateg return $this->getLanguageCategory($categoryName); } - /** - * Returns `true` if the language category with the given category name exists. - */ public function languageCategoryExists(string $categoryName): bool { return \array_key_exists($categoryName, $this->categories); @@ -79,7 +76,7 @@ public function languageCategoryExists(string $categoryName): bool /** * Return all content languages. * - * @return list + * @return array */ public function getContentLanguages(): array { @@ -96,10 +93,7 @@ public function getContentLanguages(): array */ public function getContentLanguageIDs(): array { - return \array_map( - static fn(Language $language) => $language->languageID, - $this->getContentLanguages() - ); + return \array_keys($this->getContentLanguages()); } /** From d0732ff6a667399e2968a278981f881389a78e29 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Fri, 21 Feb 2025 13:52:36 +0100 Subject: [PATCH 22/42] Implement `ICacheSource::getCacheLifetime()`` --- .../cache/source/DiskCacheSource.class.php | 16 ++++++++++++++++ .../system/cache/source/ICacheSource.class.php | 5 +++++ .../cache/source/RedisCacheSource.class.php | 16 ++++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/wcfsetup/install/files/lib/system/cache/source/DiskCacheSource.class.php b/wcfsetup/install/files/lib/system/cache/source/DiskCacheSource.class.php index 11e84a6d0ba..8a81e98cdc1 100644 --- a/wcfsetup/install/files/lib/system/cache/source/DiskCacheSource.class.php +++ b/wcfsetup/install/files/lib/system/cache/source/DiskCacheSource.class.php @@ -157,4 +157,20 @@ private function readCache(string $filename): mixed throw new \Exception("Failed to unserialize the cache contents.", 0, $e); } } + + #[\Override] + public function getCacheLifetime(string $cacheName): ?int + { + $filename = $this->getFilename($cacheName); + + if (!\file_exists($filename)) { + return null; + } + + if (!@\filesize($filename)) { + return null; + } + + return \filemtime($filename); + } } diff --git a/wcfsetup/install/files/lib/system/cache/source/ICacheSource.class.php b/wcfsetup/install/files/lib/system/cache/source/ICacheSource.class.php index 9c8c3897832..4e66662389d 100644 --- a/wcfsetup/install/files/lib/system/cache/source/ICacheSource.class.php +++ b/wcfsetup/install/files/lib/system/cache/source/ICacheSource.class.php @@ -41,4 +41,9 @@ public function get($cacheName, $maxLifetime); * @param int $maxLifetime */ public function set($cacheName, $value, $maxLifetime); + + /** + * Returns the cache lifetime for a specific cache. + */ + public function getCacheLifetime(string $cacheName): ?int; } diff --git a/wcfsetup/install/files/lib/system/cache/source/RedisCacheSource.class.php b/wcfsetup/install/files/lib/system/cache/source/RedisCacheSource.class.php index c33f966f1f1..267f40ea124 100644 --- a/wcfsetup/install/files/lib/system/cache/source/RedisCacheSource.class.php +++ b/wcfsetup/install/files/lib/system/cache/source/RedisCacheSource.class.php @@ -184,4 +184,20 @@ public function getRedis() { return $this->redis; } + + #[\Override] + public function getCacheLifetime(string $cacheName): ?int + { + $parts = \explode('-', $cacheName, 2); + + if (isset($parts[1])) { + $ttl = $this->redis->ttl($this->getCacheName($parts[0])); + } else { + $ttl = $this->redis->ttl($this->getCacheName($cacheName)); + } + + // -2 means that the key does not exist + // -1 means that the key exists but does not have an expiration date. + return $ttl > 0 ? $ttl : null; + } } From 4cacf609b8099ac985ca9835e7180f161decfd24 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Fri, 21 Feb 2025 14:06:21 +0100 Subject: [PATCH 23/42] Implement `AbstractTolerantCache` and the background job for it --- ...olerantCacheRebuildBackgroundJob.class.php | 55 +++++++ .../tolerant/AbstractTolerantCache.class.php | 134 ++++++++++++++++++ 2 files changed, 189 insertions(+) create mode 100644 wcfsetup/install/files/lib/system/background/job/TolerantCacheRebuildBackgroundJob.class.php create mode 100644 wcfsetup/install/files/lib/system/cache/tolerant/AbstractTolerantCache.class.php diff --git a/wcfsetup/install/files/lib/system/background/job/TolerantCacheRebuildBackgroundJob.class.php b/wcfsetup/install/files/lib/system/background/job/TolerantCacheRebuildBackgroundJob.class.php new file mode 100644 index 00000000000..bb4f9889271 --- /dev/null +++ b/wcfsetup/install/files/lib/system/background/job/TolerantCacheRebuildBackgroundJob.class.php @@ -0,0 +1,55 @@ + + * @since 6.2 + */ +final class TolerantCacheRebuildBackgroundJob extends AbstractUniqueBackgroundJob +{ + public function __construct( + /** @var class-string */ + public readonly string $cacheClass, + /** @var array */ + public readonly array $parameters = [] + ) { + } + + #[\Override] + public function newInstance(): static + { + return new TolerantCacheRebuildBackgroundJob($this->cacheClass, $this->parameters); + } + + #[\Override] + public function queueAgain(): bool + { + return \class_exists($this->cacheClass); + } + + #[\Override] + public function retryAfter() + { + return (new $this->cacheClass(...$this->parameters))->getLifetime(); + } + + #[\Override] + public function perform() + { + if (!\class_exists($this->cacheClass)) { + return; + } + + $asyncCache = new $this->cacheClass(...$this->parameters); + if (!$asyncCache->needsRebuild()) { + return; + } + + $asyncCache->rebuild(); + } +} diff --git a/wcfsetup/install/files/lib/system/cache/tolerant/AbstractTolerantCache.class.php b/wcfsetup/install/files/lib/system/cache/tolerant/AbstractTolerantCache.class.php new file mode 100644 index 00000000000..fc69657ef14 --- /dev/null +++ b/wcfsetup/install/files/lib/system/cache/tolerant/AbstractTolerantCache.class.php @@ -0,0 +1,134 @@ + + * @since 6.2 + * + * @template T of array | object + */ +abstract class AbstractTolerantCache +{ + /** + * @var T + */ + private array | object $cache; + private string $cacheName; + + /** + * @return T + */ + final public function getCache(): array | object + { + if (!isset($this->cache)) { + $cache = CacheHandler::getInstance()->getCacheSource()->get( + $this->getCacheKey(), + 0, + ); + + if ($cache === null) { + $this->rebuild(); + } else { + $this->cache = $cache; + } + + if ($this->needsRebuild()) { + BackgroundQueueHandler::getInstance()->enqueueIn( + [new TolerantCacheRebuildBackgroundJob(\get_class($this), $this->getProperties())] + ); + } + } + return $this->cache; + } + + private function getCacheKey(): string + { + if (!isset($this->cacheName)) { + /* @see AbstractEagerCache::getCacheKey() */ + $reflection = new \ReflectionClass($this); + $this->cacheName = \str_replace( + ['\\', 'system_cache_tolerant_'], + ['_', ''], + \get_class($this) + ); + + $parameters = $this->getProperties(); + + if ($parameters !== []) { + $this->cacheName .= '-' . CacheHandler::getInstance()->getCacheIndex($parameters); + } + } + + return $this->cacheName; + } + + /** + * @return array + */ + private function getProperties(): array + { + $reflection = new \ReflectionClass($this); + $properties = []; + foreach ($reflection->getProperties(\ReflectionProperty::IS_READONLY) as $property) { + if (!$property->isInitialized($this)) { + continue; + } + + if ($property->getValue($this) === null) { + continue; + } + + $properties[$property->getName()] = $property->getValue($this); + } + + return $properties; + } + + final public function rebuild(): void + { + $newCacheData = $this->rebuildCacheData(); + + if (!isset($this->cache)) { + $this->cache = $newCacheData; + } + + CacheHandler::getInstance()->getCacheSource()->set( + $this->getCacheKey(), + $newCacheData, + 0 + ); + + BackgroundQueueHandler::getInstance()->enqueueIn( + [new TolerantCacheRebuildBackgroundJob(\get_class($this), $this->getProperties())], + $this->nextRebuildTime() + ); + } + + /** + * @return T + */ + abstract protected function rebuildCacheData(): array | object; + + final public function nextRebuildTime(): int + { + $cacheTime = CacheHandler::getInstance()->getCacheSource()->getCacheLifetime($this->getCacheKey()); + if ($cacheTime === null) { + return \TIME_NOW; + } + return $cacheTime + ($this->getLifetime() - 60); + } + + abstract public function getLifetime(): int; + + final public function needsRebuild(): bool + { + return TIME_NOW >= $this->nextRebuildTime(); + } +} From b4c1de2db8820d3aefa92a13830832fd2bc8d9a7 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Fri, 21 Feb 2025 14:08:39 +0100 Subject: [PATCH 24/42] Remove indentation in copyright block --- .../cache/builder/AbstractLegacyCacheBuilder.class.php | 10 +++++----- .../cache/builder/CoreObjectCacheBuilder.class.php | 8 ++++---- .../cache/builder/LanguageCacheBuilder.class.php | 8 ++++---- .../system/cache/eager/AbstractEagerCache.class.php | 8 ++++---- .../lib/system/cache/eager/CoreObjectCache.class.php | 8 ++++---- .../lib/system/cache/eager/LanguageCache.class.php | 8 ++++---- .../cache/eager/data/LanguageCacheData.class.php | 8 ++++---- .../lib/system/language/LanguageFactory.class.php | 4 ++-- 8 files changed, 31 insertions(+), 31 deletions(-) diff --git a/wcfsetup/install/files/lib/system/cache/builder/AbstractLegacyCacheBuilder.class.php b/wcfsetup/install/files/lib/system/cache/builder/AbstractLegacyCacheBuilder.class.php index da845cd6373..70fc9468c51 100644 --- a/wcfsetup/install/files/lib/system/cache/builder/AbstractLegacyCacheBuilder.class.php +++ b/wcfsetup/install/files/lib/system/cache/builder/AbstractLegacyCacheBuilder.class.php @@ -9,12 +9,12 @@ /** * Legacy implementation of the ICacheBuilder interface that has been migrated to a new EagerCache or AsyncCache. * - * @author Olaf Braun - * @copyright 2001-2025 WoltLab GmbH - * @license GNU Lesser General Public License + * @author Olaf Braun + * @copyright 2001-2025 WoltLab GmbH + * @license GNU Lesser General Public License * - * @since 6.2 - * @deprecated 6.2 + * @since 6.2 + * @deprecated 6.2 */ abstract class AbstractLegacyCacheBuilder extends SingletonFactory implements ICacheBuilder { diff --git a/wcfsetup/install/files/lib/system/cache/builder/CoreObjectCacheBuilder.class.php b/wcfsetup/install/files/lib/system/cache/builder/CoreObjectCacheBuilder.class.php index edccdb1580c..ceb97819a41 100644 --- a/wcfsetup/install/files/lib/system/cache/builder/CoreObjectCacheBuilder.class.php +++ b/wcfsetup/install/files/lib/system/cache/builder/CoreObjectCacheBuilder.class.php @@ -7,11 +7,11 @@ /** * Caches the core objects. * - * @author Alexander Ebert - * @copyright 2001-2019 WoltLab GmbH - * @license GNU Lesser General Public License + * @author Olaf Braun, Alexander Ebert + * @copyright 2001-2025 WoltLab GmbH + * @license GNU Lesser General Public License * - * @deprecated 6.2 use `CoreObjectCache` instead + * @deprecated 6.2 use `CoreObjectCache` instead */ class CoreObjectCacheBuilder extends AbstractLegacyCacheBuilder { diff --git a/wcfsetup/install/files/lib/system/cache/builder/LanguageCacheBuilder.class.php b/wcfsetup/install/files/lib/system/cache/builder/LanguageCacheBuilder.class.php index a790ddcdb40..3d71ed0a23c 100644 --- a/wcfsetup/install/files/lib/system/cache/builder/LanguageCacheBuilder.class.php +++ b/wcfsetup/install/files/lib/system/cache/builder/LanguageCacheBuilder.class.php @@ -7,11 +7,11 @@ /** * Caches languages and the id of the default language. * - * @author Olaf Braun, Marcel Werk - * @copyright 2001-2025 WoltLab GmbH - * @license GNU Lesser General Public License + * @author Olaf Braun, Marcel Werk + * @copyright 2001-2025 WoltLab GmbH + * @license GNU Lesser General Public License * - * @deprecated 6.2 use `LanguageCache` instead + * @deprecated 6.2 use `LanguageCache` instead */ class LanguageCacheBuilder extends AbstractLegacyCacheBuilder { diff --git a/wcfsetup/install/files/lib/system/cache/eager/AbstractEagerCache.class.php b/wcfsetup/install/files/lib/system/cache/eager/AbstractEagerCache.class.php index 5d7bfe98ec5..f1d7e95f3a4 100644 --- a/wcfsetup/install/files/lib/system/cache/eager/AbstractEagerCache.class.php +++ b/wcfsetup/install/files/lib/system/cache/eager/AbstractEagerCache.class.php @@ -5,10 +5,10 @@ use wcf\system\cache\CacheHandler; /** - * @author Olaf Braun - * @copyright 2001-2025 WoltLab GmbH - * @license GNU Lesser General Public License - * @since 6.2 + * @author Olaf Braun + * @copyright 2001-2025 WoltLab GmbH + * @license GNU Lesser General Public License + * @since 6.2 * * @template T of array|object */ diff --git a/wcfsetup/install/files/lib/system/cache/eager/CoreObjectCache.class.php b/wcfsetup/install/files/lib/system/cache/eager/CoreObjectCache.class.php index 8a05b2dd21a..c75b56a63b9 100644 --- a/wcfsetup/install/files/lib/system/cache/eager/CoreObjectCache.class.php +++ b/wcfsetup/install/files/lib/system/cache/eager/CoreObjectCache.class.php @@ -8,10 +8,10 @@ /** * Eager cache implementation for core objects. * - * @author Olaf Braun - * @copyright 2001-2025 WoltLab GmbH - * @license GNU Lesser General Public License - * @since 6.2 + * @author Olaf Braun + * @copyright 2001-2025 WoltLab GmbH + * @license GNU Lesser General Public License + * @since 6.2 * * @extends AbstractEagerCache>> */ diff --git a/wcfsetup/install/files/lib/system/cache/eager/LanguageCache.class.php b/wcfsetup/install/files/lib/system/cache/eager/LanguageCache.class.php index dd14cc5d339..d8d37c40053 100644 --- a/wcfsetup/install/files/lib/system/cache/eager/LanguageCache.class.php +++ b/wcfsetup/install/files/lib/system/cache/eager/LanguageCache.class.php @@ -11,10 +11,10 @@ /** * Eager cache implementation for languages. * - * @author Olaf Braun - * @copyright 2001-2025 WoltLab GmbH - * @license GNU Lesser General Public License - * @since 6.2 + * @author Olaf Braun + * @copyright 2001-2025 WoltLab GmbH + * @license GNU Lesser General Public License + * @since 6.2 * * @extends AbstractEagerCache */ diff --git a/wcfsetup/install/files/lib/system/cache/eager/data/LanguageCacheData.class.php b/wcfsetup/install/files/lib/system/cache/eager/data/LanguageCacheData.class.php index fffd4760b1e..1a6bdc04e0d 100644 --- a/wcfsetup/install/files/lib/system/cache/eager/data/LanguageCacheData.class.php +++ b/wcfsetup/install/files/lib/system/cache/eager/data/LanguageCacheData.class.php @@ -8,10 +8,10 @@ /** * Language cache data structure. * - * @author Olaf Braun - * @copyright 2001-2025 WoltLab GmbH - * @license GNU Lesser General Public License - * @since 6.2 + * @author Olaf Braun + * @copyright 2001-2025 WoltLab GmbH + * @license GNU Lesser General Public License + * @since 6.2 */ final class LanguageCacheData { diff --git a/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php b/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php index 798eea30e0f..4995e5c2fae 100644 --- a/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php +++ b/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php @@ -17,8 +17,8 @@ /** * Handles language related functions. * - * @author Alexander Ebert - * @copyright 2001-2019 WoltLab GmbH + * @author Olaf Braun, Alexander Ebert + * @copyright 2001-2025 WoltLab GmbH * @license GNU Lesser General Public License */ class LanguageFactory extends SingletonFactory From 422f572712066c362079cc90071e966fd0e4ed24 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Fri, 21 Feb 2025 14:11:38 +0100 Subject: [PATCH 25/42] Add php doc for `AbstractLegacyCacheBuilder::$cache` --- .../cache/builder/AbstractLegacyCacheBuilder.class.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/wcfsetup/install/files/lib/system/cache/builder/AbstractLegacyCacheBuilder.class.php b/wcfsetup/install/files/lib/system/cache/builder/AbstractLegacyCacheBuilder.class.php index 70fc9468c51..1526be1198d 100644 --- a/wcfsetup/install/files/lib/system/cache/builder/AbstractLegacyCacheBuilder.class.php +++ b/wcfsetup/install/files/lib/system/cache/builder/AbstractLegacyCacheBuilder.class.php @@ -18,7 +18,10 @@ */ abstract class AbstractLegacyCacheBuilder extends SingletonFactory implements ICacheBuilder { - protected array $cache = []; + /** + * @var array> + */ + private array $cache = []; #[\Override] public function getData(array $parameters = [], $arrayIndex = '') From 77919e32c303c6238a0b59bd57c7fc4fd8085052 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Fri, 21 Feb 2025 14:14:57 +0100 Subject: [PATCH 26/42] Use `\RuntimeException` instead of `SystemException` --- .../files/lib/system/cache/eager/LanguageCache.class.php | 3 +-- .../files/lib/system/language/LanguageFactory.class.php | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/wcfsetup/install/files/lib/system/cache/eager/LanguageCache.class.php b/wcfsetup/install/files/lib/system/cache/eager/LanguageCache.class.php index d8d37c40053..ef0934af1b2 100644 --- a/wcfsetup/install/files/lib/system/cache/eager/LanguageCache.class.php +++ b/wcfsetup/install/files/lib/system/cache/eager/LanguageCache.class.php @@ -6,7 +6,6 @@ use wcf\data\language\category\LanguageCategoryList; use wcf\data\language\LanguageList; use wcf\system\cache\eager\data\LanguageCacheData; -use wcf\system\exception\SystemException; /** * Eager cache implementation for languages. @@ -59,7 +58,7 @@ protected function getCacheData(): LanguageCacheData } if (!isset($languages[$default])) { - throw new SystemException('No default language defined!'); + throw new \RuntimeException('No default language defined!'); } return new LanguageCacheData( diff --git a/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php b/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php index 4995e5c2fae..8d8b88c490e 100644 --- a/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php +++ b/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php @@ -9,7 +9,6 @@ use wcf\data\language\LanguageEditor; use wcf\system\cache\eager\data\LanguageCacheData; use wcf\system\cache\eager\LanguageCache; -use wcf\system\exception\SystemException; use wcf\system\SingletonFactory; use wcf\system\template\TemplateScriptingCompiler; use wcf\system\WCF; @@ -138,7 +137,7 @@ protected function findPreferredLanguage(): int } } - throw new SystemException("No language found for language code '{$languageCode}'"); + throw new \RuntimeException("No language found for language code '{$languageCode}'"); } /** From 1837afe5b5988d32c930827ed62952b928422141 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Fri, 21 Feb 2025 14:16:52 +0100 Subject: [PATCH 27/42] Remove the `Language` from all functions that are used for the language categories --- .../system/cache/eager/data/LanguageCacheData.class.php | 8 ++++---- .../files/lib/system/language/LanguageFactory.class.php | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/wcfsetup/install/files/lib/system/cache/eager/data/LanguageCacheData.class.php b/wcfsetup/install/files/lib/system/cache/eager/data/LanguageCacheData.class.php index 1a6bdc04e0d..8ade4cc321f 100644 --- a/wcfsetup/install/files/lib/system/cache/eager/data/LanguageCacheData.class.php +++ b/wcfsetup/install/files/lib/system/cache/eager/data/LanguageCacheData.class.php @@ -50,7 +50,7 @@ public function getLanguage(int $languageID): ?Language /** * Returns the language category with the given category name. */ - public function getLanguageCategory(string $categoryName): ?LanguageCategory + public function getCategory(string $categoryName): ?LanguageCategory { return $this->categories[$categoryName] ?? null; } @@ -58,17 +58,17 @@ public function getLanguageCategory(string $categoryName): ?LanguageCategory /** * Returns the language category with the given category id. */ - public function getLanguageCategoryByID(int $languageCategoryID): ?LanguageCategory + public function getCategoryByID(int $languageCategoryID): ?LanguageCategory { $categoryName = $this->categoryIDs[$languageCategoryID] ?? null; if ($categoryName === null) { return null; } - return $this->getLanguageCategory($categoryName); + return $this->getCategory($categoryName); } - public function languageCategoryExists(string $categoryName): bool + public function hasCategory(string $categoryName): bool { return \array_key_exists($categoryName, $this->categories); } diff --git a/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php b/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php index 8d8b88c490e..b34204c9e81 100644 --- a/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php +++ b/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php @@ -91,7 +91,7 @@ public function getLanguageByCode(string $languageCode): ?Language */ public function isValidCategory(string $categoryName): bool { - return $this->cache->languageCategoryExists($categoryName); + return $this->cache->hasCategory($categoryName); } /** @@ -99,7 +99,7 @@ public function isValidCategory(string $categoryName): bool */ public function getCategory(string $categoryName): ?LanguageCategory { - return $this->cache->getLanguageCategory($categoryName); + return $this->cache->getCategory($categoryName); } /** @@ -107,7 +107,7 @@ public function getCategory(string $categoryName): ?LanguageCategory */ public function getCategoryByID(int $languageCategoryID): ?LanguageCategory { - return $this->cache->getLanguageCategoryByID($languageCategoryID); + return $this->cache->getCategoryByID($languageCategoryID); } /** From 9ba8815e41e5f6891e7e529705f2b13971460c84 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Fri, 21 Feb 2025 14:19:16 +0100 Subject: [PATCH 28/42] Use the code only when in WCFSetup --- .../files/lib/system/language/LanguageFactory.class.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php b/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php index b34204c9e81..f6b2b426a97 100644 --- a/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php +++ b/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php @@ -72,18 +72,17 @@ public function getUserLanguage(?int $languageID = null): Language */ public function getLanguageByCode(string $languageCode): ?Language { - $language = $this->getLanguageByCode($languageCode); - if ($language === null) { + if ($this->cache->codes === []) { // called within WCFSetup $sql = "SELECT * FROM wcf1_language WHERE languageCode = ?"; $statement = WCF::getDB()->prepare($sql); $statement->execute([$languageCode]); - $language = $statement->fetchObject(Language::class); + return $statement->fetchObject(Language::class); + } else { + return $this->getLanguageByCode($languageCode); } - - return $language; } /** From 29b3e3fa5e749695606109e915b4a9c24f9bba79 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Fri, 21 Feb 2025 14:39:24 +0100 Subject: [PATCH 29/42] Implement `WhoWasOnlineCache` as tolerant cache --- .../files/lib/acp/form/BoxAddForm.class.php | 2 +- .../box/WhoWasOnlineBoxController.class.php | 9 +--- .../AbstractLegacyCacheBuilder.class.php | 2 +- .../WhoWasOnlineCacheBuilder.class.php | 42 ++++++----------- .../tolerant/AbstractTolerantCache.class.php | 3 +- .../tolerant/WhoWasOnlineCache.class.php | 45 +++++++++++++++++++ 6 files changed, 63 insertions(+), 40 deletions(-) create mode 100644 wcfsetup/install/files/lib/system/cache/tolerant/WhoWasOnlineCache.class.php diff --git a/wcfsetup/install/files/lib/acp/form/BoxAddForm.class.php b/wcfsetup/install/files/lib/acp/form/BoxAddForm.class.php index cf5dc35ab78..74fd7c29967 100644 --- a/wcfsetup/install/files/lib/acp/form/BoxAddForm.class.php +++ b/wcfsetup/install/files/lib/acp/form/BoxAddForm.class.php @@ -620,7 +620,7 @@ public function save() 'cssClassName' => $this->cssClassName, 'showHeader' => $this->showHeader, 'isDisabled' => $this->isDisabled ? 1 : 0, - 'linkPageID' => $this->linkPageID, + 'linkPageID' => $this->linkPageID ?: null, 'linkPageObjectID' => $this->linkPageObjectID ?: 0, 'externalURL' => $this->externalURL, 'identifier' => '', diff --git a/wcfsetup/install/files/lib/system/box/WhoWasOnlineBoxController.class.php b/wcfsetup/install/files/lib/system/box/WhoWasOnlineBoxController.class.php index 87734375c06..36ac3b39d60 100644 --- a/wcfsetup/install/files/lib/system/box/WhoWasOnlineBoxController.class.php +++ b/wcfsetup/install/files/lib/system/box/WhoWasOnlineBoxController.class.php @@ -6,8 +6,8 @@ use wcf\data\user\online\UserOnline; use wcf\data\user\online\UsersOnlineList; use wcf\data\user\UserProfile; -use wcf\system\cache\builder\WhoWasOnlineCacheBuilder; use wcf\system\cache\runtime\UserProfileRuntimeCache; +use wcf\system\cache\tolerant\WhoWasOnlineCache; use wcf\system\event\EventHandler; use wcf\system\WCF; use wcf\util\DateUtil; @@ -97,14 +97,9 @@ protected function readObjects() { EventHandler::getInstance()->fireAction($this, 'readObjects'); - $userIDs = WhoWasOnlineCacheBuilder::getInstance()->getData(); + $userIDs = (new WhoWasOnlineCache())->getCache(); if (!empty($userIDs)) { - if (WCF::getUser()->userID && !\in_array(WCF::getUser()->userID, $userIDs)) { - // current user is missing in cache -> reset cache - WhoWasOnlineCacheBuilder::getInstance()->reset(); - } - $this->users = \array_filter( UserProfileRuntimeCache::getInstance()->getObjects($userIDs), static function ($user) { diff --git a/wcfsetup/install/files/lib/system/cache/builder/AbstractLegacyCacheBuilder.class.php b/wcfsetup/install/files/lib/system/cache/builder/AbstractLegacyCacheBuilder.class.php index da845cd6373..f68ba358ee2 100644 --- a/wcfsetup/install/files/lib/system/cache/builder/AbstractLegacyCacheBuilder.class.php +++ b/wcfsetup/install/files/lib/system/cache/builder/AbstractLegacyCacheBuilder.class.php @@ -7,7 +7,7 @@ use wcf\system\SingletonFactory; /** - * Legacy implementation of the ICacheBuilder interface that has been migrated to a new EagerCache or AsyncCache. + * Legacy implementation of the ICacheBuilder interface that has been migrated to a new Eager-Cache or Tolerant-Cache. * * @author Olaf Braun * @copyright 2001-2025 WoltLab GmbH diff --git a/wcfsetup/install/files/lib/system/cache/builder/WhoWasOnlineCacheBuilder.class.php b/wcfsetup/install/files/lib/system/cache/builder/WhoWasOnlineCacheBuilder.class.php index d498afcb509..5be0c6eced6 100644 --- a/wcfsetup/install/files/lib/system/cache/builder/WhoWasOnlineCacheBuilder.class.php +++ b/wcfsetup/install/files/lib/system/cache/builder/WhoWasOnlineCacheBuilder.class.php @@ -2,44 +2,28 @@ namespace wcf\system\cache\builder; -use wcf\system\WCF; +use wcf\system\cache\tolerant\WhoWasOnlineCache; /** * Caches a list of users that visited the website in last 24 hours. * - * @author Marcel Werk + * @author Olaf Braun, Marcel Werk * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License + * + * @deprecated 6.2 use `WhoWasOnlineCache` instead */ -class WhoWasOnlineCacheBuilder extends AbstractCacheBuilder +class WhoWasOnlineCacheBuilder extends AbstractLegacyCacheBuilder { - /** - * @inheritDoc - */ - protected $maxLifetime = 600; - - /** - * @inheritDoc - */ - protected function rebuild(array $parameters) + #[\Override] + protected function rebuild(array $parameters): array { - $userIDs = []; - $sql = "( - SELECT userID - FROM wcf1_user - WHERE lastActivityTime > ? - ) UNION ( - SELECT userID - FROM wcf1_session - WHERE userID IS NOT NULL - AND lastActivityTime > ? - )"; - $statement = WCF::getDB()->prepare($sql); - $statement->execute([TIME_NOW - 86400, TIME_NOW - USER_ONLINE_TIMEOUT]); - while ($userID = $statement->fetchColumn()) { - $userIDs[] = $userID; - } + return (new WhoWasOnlineCache())->getCache(); + } - return $userIDs; + #[\Override] + public function reset(array $parameters = []) + { + (new WhoWasOnlineCache())->rebuild(); } } diff --git a/wcfsetup/install/files/lib/system/cache/tolerant/AbstractTolerantCache.class.php b/wcfsetup/install/files/lib/system/cache/tolerant/AbstractTolerantCache.class.php index fc69657ef14..f39ab468fa6 100644 --- a/wcfsetup/install/files/lib/system/cache/tolerant/AbstractTolerantCache.class.php +++ b/wcfsetup/install/files/lib/system/cache/tolerant/AbstractTolerantCache.class.php @@ -52,7 +52,6 @@ private function getCacheKey(): string { if (!isset($this->cacheName)) { /* @see AbstractEagerCache::getCacheKey() */ - $reflection = new \ReflectionClass($this); $this->cacheName = \str_replace( ['\\', 'system_cache_tolerant_'], ['_', ''], @@ -105,7 +104,7 @@ final public function rebuild(): void 0 ); - BackgroundQueueHandler::getInstance()->enqueueIn( + BackgroundQueueHandler::getInstance()->enqueueAt( [new TolerantCacheRebuildBackgroundJob(\get_class($this), $this->getProperties())], $this->nextRebuildTime() ); diff --git a/wcfsetup/install/files/lib/system/cache/tolerant/WhoWasOnlineCache.class.php b/wcfsetup/install/files/lib/system/cache/tolerant/WhoWasOnlineCache.class.php new file mode 100644 index 00000000000..9bb9140b94a --- /dev/null +++ b/wcfsetup/install/files/lib/system/cache/tolerant/WhoWasOnlineCache.class.php @@ -0,0 +1,45 @@ + + * @since 6.2 + * + * @extends AbstractTolerantCache> + */ +final class WhoWasOnlineCache extends AbstractTolerantCache +{ + #[\Override] + public function getLifetime(): int + { + return 600; + } + + #[\Override] + protected function rebuildCacheData(): array + { + $userIDs = []; + $sql = "( + SELECT userID + FROM wcf1_user + WHERE lastActivityTime > ? + ) UNION ( + SELECT userID + FROM wcf1_session + WHERE userID IS NOT NULL + AND lastActivityTime > ? + )"; + $statement = WCF::getDB()->prepare($sql); + $statement->execute([TIME_NOW - 86400, TIME_NOW - USER_ONLINE_TIMEOUT]); + while ($userID = $statement->fetchColumn()) { + $userIDs[] = $userID; + } + + return $userIDs; + } +} From 3cad83be622f29731ef5c4ab4d394bff9ed53760 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Fri, 21 Feb 2025 15:17:56 +0100 Subject: [PATCH 30/42] Implement `UserBirthdayCache` as tolerant cache --- .../files/lib/acp/form/BoxEditForm.class.php | 2 +- ...olerantCacheRebuildBackgroundJob.class.php | 11 ++++ .../UserBirthdayCacheBuilder.class.php | 46 ++++++---------- .../tolerant/UserBirthdayCache.class.php | 54 +++++++++++++++++++ .../tolerant/WhoWasOnlineCache.class.php | 6 +-- .../system/user/UserBirthdayCache.class.php | 34 +++--------- 6 files changed, 90 insertions(+), 63 deletions(-) create mode 100644 wcfsetup/install/files/lib/system/cache/tolerant/UserBirthdayCache.class.php diff --git a/wcfsetup/install/files/lib/acp/form/BoxEditForm.class.php b/wcfsetup/install/files/lib/acp/form/BoxEditForm.class.php index f22011d6dbd..7112f1f27aa 100644 --- a/wcfsetup/install/files/lib/acp/form/BoxEditForm.class.php +++ b/wcfsetup/install/files/lib/acp/form/BoxEditForm.class.php @@ -119,7 +119,7 @@ public function save() 'cssClassName' => $this->cssClassName, 'showHeader' => $this->showHeader, 'isDisabled' => $this->isDisabled ? 1 : 0, - 'linkPageID' => $this->linkPageID, + 'linkPageID' => $this->linkPageID ?: null, 'linkPageObjectID' => $this->linkPageObjectID ?: 0, 'externalURL' => $this->externalURL, 'invertPermissions' => $this->invertPermissions, diff --git a/wcfsetup/install/files/lib/system/background/job/TolerantCacheRebuildBackgroundJob.class.php b/wcfsetup/install/files/lib/system/background/job/TolerantCacheRebuildBackgroundJob.class.php index bb4f9889271..f594d920abf 100644 --- a/wcfsetup/install/files/lib/system/background/job/TolerantCacheRebuildBackgroundJob.class.php +++ b/wcfsetup/install/files/lib/system/background/job/TolerantCacheRebuildBackgroundJob.class.php @@ -2,6 +2,7 @@ namespace wcf\system\background\job; +use wcf\system\cache\CacheHandler; use wcf\system\cache\tolerant\AbstractTolerantCache; /** @@ -20,6 +21,16 @@ public function __construct( ) { } + public function identifier(): string + { + $identifier = $this->cacheClass; + if (!empty($this->parameters)) { + $identifier .= '-' . CacheHandler::getInstance()->getCacheIndex($this->parameters); + } + + return $identifier; + } + #[\Override] public function newInstance(): static { diff --git a/wcfsetup/install/files/lib/system/cache/builder/UserBirthdayCacheBuilder.class.php b/wcfsetup/install/files/lib/system/cache/builder/UserBirthdayCacheBuilder.class.php index 728949fc413..ec6881fbd41 100644 --- a/wcfsetup/install/files/lib/system/cache/builder/UserBirthdayCacheBuilder.class.php +++ b/wcfsetup/install/files/lib/system/cache/builder/UserBirthdayCacheBuilder.class.php @@ -2,8 +2,7 @@ namespace wcf\system\cache\builder; -use wcf\data\user\User; -use wcf\system\WCF; +use wcf\system\cache\tolerant\UserBirthdayCache; /** * Caches user birthdays (one cache file per month). @@ -11,40 +10,25 @@ * @author Marcel Werk * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License + * + * @deprecated 6.2 use `UserBirthdayCache` instead */ -class UserBirthdayCacheBuilder extends AbstractCacheBuilder +class UserBirthdayCacheBuilder extends AbstractLegacyCacheBuilder { - /** - * @inheritDoc - */ - protected $maxLifetime = 3600; - - /** - * @inheritDoc - */ - protected function rebuild(array $parameters) + #[\Override] + protected function rebuild(array $parameters): array { - $userOptionID = User::getUserOptionID('birthday'); - if ($userOptionID === null) { - // birthday profile field missing; skip - return []; + $cache = []; + foreach ((new UserBirthdayCache($parameters['month']))->getCache() as $day => $userIDs) { + $cache[\sprintf("%02d-%02d", $parameters['month'], $day)] = $userIDs; } - $data = []; - $birthday = 'userOption' . $userOptionID; - $sql = "SELECT userID, " . $birthday . " - FROM wcf1_user_option_value - WHERE " . $birthday . " LIKE ?"; - $statement = WCF::getDB()->prepare($sql); - $statement->execute(['%-' . ($parameters['month'] < 10 ? '0' : '') . $parameters['month'] . '-%']); - while ($row = $statement->fetchArray()) { - [, $month, $day] = \explode('-', $row[$birthday]); - if (!isset($data[$month . '-' . $day])) { - $data[$month . '-' . $day] = []; - } - $data[$month . '-' . $day][] = $row['userID']; - } + return $cache; + } - return $data; + #[\Override] + public function reset(array $parameters = []) + { + (new UserBirthdayCache($parameters['month']))->rebuild(); } } diff --git a/wcfsetup/install/files/lib/system/cache/tolerant/UserBirthdayCache.class.php b/wcfsetup/install/files/lib/system/cache/tolerant/UserBirthdayCache.class.php new file mode 100644 index 00000000000..9c83ed89139 --- /dev/null +++ b/wcfsetup/install/files/lib/system/cache/tolerant/UserBirthdayCache.class.php @@ -0,0 +1,54 @@ + + * @since 6.2 + * + * @extends AbstractTolerantCache>> + */ +final class UserBirthdayCache extends AbstractTolerantCache +{ + public function __construct(public readonly int $month) + { + } + + #[\Override] + public function getLifetime(): int + { + return 3600; + } + + #[\Override] + protected function rebuildCacheData(): array + { + $userOptionID = User::getUserOptionID('birthday'); + if ($userOptionID === null) { + // birthday profile field missing; skip + return []; + } + + $data = []; + $birthday = 'userOption' . $userOptionID; + $sql = "SELECT userID, " . $birthday . " + FROM wcf1_user_option_value + WHERE " . $birthday . " LIKE ?"; + $statement = WCF::getDB()->prepare($sql); + $statement->execute(['%-' . ($this->month < 10 ? '0' : '') . $this->month . '-%']); + while ($row = $statement->fetchArray()) { + [, , $day] = \explode('-', $row[$birthday]); + if (!isset($data[$day])) { + $data[$day] = []; + } + $data[\intval($day)][] = $row['userID']; + } + + return $data; + } +} diff --git a/wcfsetup/install/files/lib/system/cache/tolerant/WhoWasOnlineCache.class.php b/wcfsetup/install/files/lib/system/cache/tolerant/WhoWasOnlineCache.class.php index 9bb9140b94a..92b641f182c 100644 --- a/wcfsetup/install/files/lib/system/cache/tolerant/WhoWasOnlineCache.class.php +++ b/wcfsetup/install/files/lib/system/cache/tolerant/WhoWasOnlineCache.class.php @@ -23,7 +23,6 @@ public function getLifetime(): int #[\Override] protected function rebuildCacheData(): array { - $userIDs = []; $sql = "( SELECT userID FROM wcf1_user @@ -36,10 +35,7 @@ protected function rebuildCacheData(): array )"; $statement = WCF::getDB()->prepare($sql); $statement->execute([TIME_NOW - 86400, TIME_NOW - USER_ONLINE_TIMEOUT]); - while ($userID = $statement->fetchColumn()) { - $userIDs[] = $userID; - } - return $userIDs; + return $statement->fetchAll(\PDO::FETCH_COLUMN); } } diff --git a/wcfsetup/install/files/lib/system/user/UserBirthdayCache.class.php b/wcfsetup/install/files/lib/system/user/UserBirthdayCache.class.php index 009ef4ef2af..53bd95ac8cd 100644 --- a/wcfsetup/install/files/lib/system/user/UserBirthdayCache.class.php +++ b/wcfsetup/install/files/lib/system/user/UserBirthdayCache.class.php @@ -2,7 +2,6 @@ namespace wcf\system\user; -use wcf\system\cache\builder\UserBirthdayCacheBuilder; use wcf\system\event\EventHandler; use wcf\system\SingletonFactory; @@ -15,31 +14,22 @@ */ class UserBirthdayCache extends SingletonFactory { - /** - * loaded months - * @var int[] - */ - protected $monthsLoaded = []; - /** * user birthdays - * @var int[][] + * + * @var array>> */ - protected $birthdays = []; + protected array $birthdays = []; /** * Loads the birthday cache. * * @param int $month */ - protected function loadMonth($month) + protected function loadMonth(int $month): void { - if (!isset($this->monthsLoaded[$month])) { - $this->birthdays = \array_merge( - $this->birthdays, - UserBirthdayCacheBuilder::getInstance()->getData(['month' => $month]) - ); - $this->monthsLoaded[$month] = true; + if (!\array_key_exists($month, $this->birthdays)) { + $this->birthdays[$month] = (new \wcf\system\cache\tolerant\UserBirthdayCache($month))->getCache(); $data = [ 'birthdays' => $this->birthdays, @@ -52,20 +42,12 @@ protected function loadMonth($month) /** * Returns the user birthdays for a specific day. - * - * @param int $month - * @param int $day * @return int[] list of user ids */ - public function getBirthdays($month, $day) + public function getBirthdays(int $month, int $day): array { $this->loadMonth($month); - $index = ($month < 10 ? '0' : '') . $month . '-' . ($day < 10 ? '0' : '') . $day; - if (isset($this->birthdays[$index])) { - return $this->birthdays[$index]; - } - - return []; + return $this->birthdays[$month][$day] ?? []; } } From 71418e80759e1a073272a6ea35cd8487876c99a6 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Fri, 21 Feb 2025 15:21:33 +0100 Subject: [PATCH 31/42] Add php doc for `getLifetime()` --- .../lib/system/cache/tolerant/AbstractTolerantCache.class.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/wcfsetup/install/files/lib/system/cache/tolerant/AbstractTolerantCache.class.php b/wcfsetup/install/files/lib/system/cache/tolerant/AbstractTolerantCache.class.php index f39ab468fa6..ca46cd541be 100644 --- a/wcfsetup/install/files/lib/system/cache/tolerant/AbstractTolerantCache.class.php +++ b/wcfsetup/install/files/lib/system/cache/tolerant/AbstractTolerantCache.class.php @@ -124,6 +124,9 @@ final public function nextRebuildTime(): int return $cacheTime + ($this->getLifetime() - 60); } + /** + * Return the lifetime of the cache in seconds. + */ abstract public function getLifetime(): int; final public function needsRebuild(): bool From c54c4838ca7594b01779cee94a2ddc06a8986c6e Mon Sep 17 00:00:00 2001 From: Olaf Braun Date: Fri, 21 Feb 2025 19:04:44 +0100 Subject: [PATCH 32/42] Update wcfsetup/install/files/lib/system/cache/builder/AbstractLegacyCacheBuilder.class.php Co-authored-by: Alexander Ebert --- .../system/cache/builder/AbstractLegacyCacheBuilder.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wcfsetup/install/files/lib/system/cache/builder/AbstractLegacyCacheBuilder.class.php b/wcfsetup/install/files/lib/system/cache/builder/AbstractLegacyCacheBuilder.class.php index f68ba358ee2..e34097b4b46 100644 --- a/wcfsetup/install/files/lib/system/cache/builder/AbstractLegacyCacheBuilder.class.php +++ b/wcfsetup/install/files/lib/system/cache/builder/AbstractLegacyCacheBuilder.class.php @@ -7,7 +7,7 @@ use wcf\system\SingletonFactory; /** - * Legacy implementation of the ICacheBuilder interface that has been migrated to a new Eager-Cache or Tolerant-Cache. + * Provides a backwards compatible interface for ICacheBuilder classes that have been migrated to the eager or tolerant cache implementations. * * @author Olaf Braun * @copyright 2001-2025 WoltLab GmbH From a2b73f89f4c428616d51cc0778dc4b19b20e0713 Mon Sep 17 00:00:00 2001 From: Olaf Braun Date: Fri, 21 Feb 2025 19:11:19 +0100 Subject: [PATCH 33/42] Let the cache go stale more often and queue the refresh only when necessary --- .../background/job/TolerantCacheRebuildBackgroundJob.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wcfsetup/install/files/lib/system/background/job/TolerantCacheRebuildBackgroundJob.class.php b/wcfsetup/install/files/lib/system/background/job/TolerantCacheRebuildBackgroundJob.class.php index f594d920abf..cc65217954e 100644 --- a/wcfsetup/install/files/lib/system/background/job/TolerantCacheRebuildBackgroundJob.class.php +++ b/wcfsetup/install/files/lib/system/background/job/TolerantCacheRebuildBackgroundJob.class.php @@ -40,7 +40,7 @@ public function newInstance(): static #[\Override] public function queueAgain(): bool { - return \class_exists($this->cacheClass); + return false; } #[\Override] From c8193e538c49243c762b8dc35cce07e004518683 Mon Sep 17 00:00:00 2001 From: Olaf Braun Date: Fri, 21 Feb 2025 19:12:08 +0100 Subject: [PATCH 34/42] Use the default retry value of 60 seconds, not the long lifetime of the cache --- .../job/TolerantCacheRebuildBackgroundJob.class.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/wcfsetup/install/files/lib/system/background/job/TolerantCacheRebuildBackgroundJob.class.php b/wcfsetup/install/files/lib/system/background/job/TolerantCacheRebuildBackgroundJob.class.php index cc65217954e..e492ebea5f8 100644 --- a/wcfsetup/install/files/lib/system/background/job/TolerantCacheRebuildBackgroundJob.class.php +++ b/wcfsetup/install/files/lib/system/background/job/TolerantCacheRebuildBackgroundJob.class.php @@ -43,12 +43,6 @@ public function queueAgain(): bool return false; } - #[\Override] - public function retryAfter() - { - return (new $this->cacheClass(...$this->parameters))->getLifetime(); - } - #[\Override] public function perform() { From 017fa577f751ebc51a866681019427d983a3ee45 Mon Sep 17 00:00:00 2001 From: Olaf Braun Date: Fri, 21 Feb 2025 19:30:28 +0100 Subject: [PATCH 35/42] `ICacheSource::getCacheLifetime()` requires a second parameter to calculate the expiration time of the cache --- .../system/cache/source/DiskCacheSource.class.php | 8 ++++++-- .../lib/system/cache/source/ICacheSource.class.php | 6 ++++-- .../system/cache/source/RedisCacheSource.class.php | 12 ++++++++++-- .../cache/tolerant/AbstractTolerantCache.class.php | 12 ++++++------ 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/wcfsetup/install/files/lib/system/cache/source/DiskCacheSource.class.php b/wcfsetup/install/files/lib/system/cache/source/DiskCacheSource.class.php index 8a81e98cdc1..dc1ec118e6a 100644 --- a/wcfsetup/install/files/lib/system/cache/source/DiskCacheSource.class.php +++ b/wcfsetup/install/files/lib/system/cache/source/DiskCacheSource.class.php @@ -159,8 +159,12 @@ private function readCache(string $filename): mixed } #[\Override] - public function getCacheLifetime(string $cacheName): ?int + public function getExpirationTime(string $cacheName, int $maxLifetime): ?int { + if ($maxLifetime === 0) { + return \PHP_INT_MAX; + } + $filename = $this->getFilename($cacheName); if (!\file_exists($filename)) { @@ -171,6 +175,6 @@ public function getCacheLifetime(string $cacheName): ?int return null; } - return \filemtime($filename); + return \filemtime($filename) + $maxLifetime; } } diff --git a/wcfsetup/install/files/lib/system/cache/source/ICacheSource.class.php b/wcfsetup/install/files/lib/system/cache/source/ICacheSource.class.php index 4e66662389d..801c5f3e571 100644 --- a/wcfsetup/install/files/lib/system/cache/source/ICacheSource.class.php +++ b/wcfsetup/install/files/lib/system/cache/source/ICacheSource.class.php @@ -43,7 +43,9 @@ public function get($cacheName, $maxLifetime); public function set($cacheName, $value, $maxLifetime); /** - * Returns the cache lifetime for a specific cache. + * Returns the timestamp when the cache expires. + * Or `null` if the cache does not exist or is empty. + * `\PHP_INT_MAX` means the cache never expires. */ - public function getCacheLifetime(string $cacheName): ?int; + public function getExpirationTime(string $cacheName, int $maxLifetime): ?int; } diff --git a/wcfsetup/install/files/lib/system/cache/source/RedisCacheSource.class.php b/wcfsetup/install/files/lib/system/cache/source/RedisCacheSource.class.php index 267f40ea124..d851595775d 100644 --- a/wcfsetup/install/files/lib/system/cache/source/RedisCacheSource.class.php +++ b/wcfsetup/install/files/lib/system/cache/source/RedisCacheSource.class.php @@ -186,7 +186,7 @@ public function getRedis() } #[\Override] - public function getCacheLifetime(string $cacheName): ?int + public function getExpirationTime(string $cacheName, int $maxLifetime): ?int { $parts = \explode('-', $cacheName, 2); @@ -197,7 +197,15 @@ public function getCacheLifetime(string $cacheName): ?int } // -2 means that the key does not exist + if ($ttl === -2) { + return null; + } + // -1 means that the key exists but does not have an expiration date. - return $ttl > 0 ? $ttl : null; + if ($ttl === -1) { + return \PHP_INT_MAX; + } + + return $ttl; } } diff --git a/wcfsetup/install/files/lib/system/cache/tolerant/AbstractTolerantCache.class.php b/wcfsetup/install/files/lib/system/cache/tolerant/AbstractTolerantCache.class.php index ca46cd541be..512c1a9fd6d 100644 --- a/wcfsetup/install/files/lib/system/cache/tolerant/AbstractTolerantCache.class.php +++ b/wcfsetup/install/files/lib/system/cache/tolerant/AbstractTolerantCache.class.php @@ -103,11 +103,6 @@ final public function rebuild(): void $newCacheData, 0 ); - - BackgroundQueueHandler::getInstance()->enqueueAt( - [new TolerantCacheRebuildBackgroundJob(\get_class($this), $this->getProperties())], - $this->nextRebuildTime() - ); } /** @@ -117,10 +112,15 @@ abstract protected function rebuildCacheData(): array | object; final public function nextRebuildTime(): int { - $cacheTime = CacheHandler::getInstance()->getCacheSource()->getCacheLifetime($this->getCacheKey()); + $cacheTime = CacheHandler::getInstance()->getCacheSource()->getExpirationTime( + $this->getCacheKey(), + $this->getLifetime() + ); + if ($cacheTime === null) { return \TIME_NOW; } + return $cacheTime + ($this->getLifetime() - 60); } From 0f563eac4bf0145d9d1d71e5edf9d93268b4f980 Mon Sep 17 00:00:00 2001 From: Olaf Braun Date: Fri, 21 Feb 2025 19:35:16 +0100 Subject: [PATCH 36/42] Remove whitespaces from union types --- .../files/lib/system/cache/eager/AbstractEagerCache.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wcfsetup/install/files/lib/system/cache/eager/AbstractEagerCache.class.php b/wcfsetup/install/files/lib/system/cache/eager/AbstractEagerCache.class.php index f1d7e95f3a4..c74dbe47d1d 100644 --- a/wcfsetup/install/files/lib/system/cache/eager/AbstractEagerCache.class.php +++ b/wcfsetup/install/files/lib/system/cache/eager/AbstractEagerCache.class.php @@ -25,7 +25,7 @@ abstract class AbstractEagerCache * * @return T */ - final public function getCache(): array | object + final public function getCache(): array|object { $key = $this->getCacheKey(); @@ -97,5 +97,5 @@ final public function rebuild(): void * * @return T */ - abstract protected function getCacheData(): array | object; + abstract protected function getCacheData(): array|object; } From 08d3da92d36fee7a974576048f9b8ee68b9d85b0 Mon Sep 17 00:00:00 2001 From: Olaf Braun Date: Fri, 21 Feb 2025 19:37:27 +0100 Subject: [PATCH 37/42] Remove whitespaces from union types --- .../system/cache/tolerant/AbstractTolerantCache.class.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wcfsetup/install/files/lib/system/cache/tolerant/AbstractTolerantCache.class.php b/wcfsetup/install/files/lib/system/cache/tolerant/AbstractTolerantCache.class.php index 512c1a9fd6d..d15da67fdc8 100644 --- a/wcfsetup/install/files/lib/system/cache/tolerant/AbstractTolerantCache.class.php +++ b/wcfsetup/install/files/lib/system/cache/tolerant/AbstractTolerantCache.class.php @@ -12,20 +12,20 @@ * @license GNU Lesser General Public License * @since 6.2 * - * @template T of array | object + * @template T of array|object */ abstract class AbstractTolerantCache { /** * @var T */ - private array | object $cache; + private array|object $cache; private string $cacheName; /** * @return T */ - final public function getCache(): array | object + final public function getCache(): array|object { if (!isset($this->cache)) { $cache = CacheHandler::getInstance()->getCacheSource()->get( @@ -108,7 +108,7 @@ final public function rebuild(): void /** * @return T */ - abstract protected function rebuildCacheData(): array | object; + abstract protected function rebuildCacheData(): array|object; final public function nextRebuildTime(): int { From 5da722a061dcd9fc7bf3988d96f620f51d8f437d Mon Sep 17 00:00:00 2001 From: Olaf Braun Date: Fri, 21 Feb 2025 19:46:47 +0100 Subject: [PATCH 38/42] We cannot determine for sure the expiration date of the cache, therefore the timestamp of creation is returned --- .../lib/system/cache/source/DiskCacheSource.class.php | 8 ++------ .../files/lib/system/cache/source/ICacheSource.class.php | 5 ++--- .../lib/system/cache/source/RedisCacheSource.class.php | 6 +++--- .../system/cache/tolerant/AbstractTolerantCache.class.php | 2 +- 4 files changed, 8 insertions(+), 13 deletions(-) diff --git a/wcfsetup/install/files/lib/system/cache/source/DiskCacheSource.class.php b/wcfsetup/install/files/lib/system/cache/source/DiskCacheSource.class.php index dc1ec118e6a..8fd2a3c444e 100644 --- a/wcfsetup/install/files/lib/system/cache/source/DiskCacheSource.class.php +++ b/wcfsetup/install/files/lib/system/cache/source/DiskCacheSource.class.php @@ -159,12 +159,8 @@ private function readCache(string $filename): mixed } #[\Override] - public function getExpirationTime(string $cacheName, int $maxLifetime): ?int + public function getCreationTime(string $cacheName, int $maxLifetime): ?int { - if ($maxLifetime === 0) { - return \PHP_INT_MAX; - } - $filename = $this->getFilename($cacheName); if (!\file_exists($filename)) { @@ -175,6 +171,6 @@ public function getExpirationTime(string $cacheName, int $maxLifetime): ?int return null; } - return \filemtime($filename) + $maxLifetime; + return \filemtime($filename); } } diff --git a/wcfsetup/install/files/lib/system/cache/source/ICacheSource.class.php b/wcfsetup/install/files/lib/system/cache/source/ICacheSource.class.php index 801c5f3e571..969f3b541bf 100644 --- a/wcfsetup/install/files/lib/system/cache/source/ICacheSource.class.php +++ b/wcfsetup/install/files/lib/system/cache/source/ICacheSource.class.php @@ -43,9 +43,8 @@ public function get($cacheName, $maxLifetime); public function set($cacheName, $value, $maxLifetime); /** - * Returns the timestamp when the cache expires. + * Returns the timestamp when the cache was created. * Or `null` if the cache does not exist or is empty. - * `\PHP_INT_MAX` means the cache never expires. */ - public function getExpirationTime(string $cacheName, int $maxLifetime): ?int; + public function getCreationTime(string $cacheName, int $maxLifetime): ?int; } diff --git a/wcfsetup/install/files/lib/system/cache/source/RedisCacheSource.class.php b/wcfsetup/install/files/lib/system/cache/source/RedisCacheSource.class.php index d851595775d..087a8d12e14 100644 --- a/wcfsetup/install/files/lib/system/cache/source/RedisCacheSource.class.php +++ b/wcfsetup/install/files/lib/system/cache/source/RedisCacheSource.class.php @@ -186,7 +186,7 @@ public function getRedis() } #[\Override] - public function getExpirationTime(string $cacheName, int $maxLifetime): ?int + public function getCreationTime(string $cacheName, int $maxLifetime): ?int { $parts = \explode('-', $cacheName, 2); @@ -203,9 +203,9 @@ public function getExpirationTime(string $cacheName, int $maxLifetime): ?int // -1 means that the key exists but does not have an expiration date. if ($ttl === -1) { - return \PHP_INT_MAX; + return \TIME_NOW; } - return $ttl; + return $ttl - $maxLifetime; } } diff --git a/wcfsetup/install/files/lib/system/cache/tolerant/AbstractTolerantCache.class.php b/wcfsetup/install/files/lib/system/cache/tolerant/AbstractTolerantCache.class.php index d15da67fdc8..e6047fb8646 100644 --- a/wcfsetup/install/files/lib/system/cache/tolerant/AbstractTolerantCache.class.php +++ b/wcfsetup/install/files/lib/system/cache/tolerant/AbstractTolerantCache.class.php @@ -112,7 +112,7 @@ abstract protected function rebuildCacheData(): array|object; final public function nextRebuildTime(): int { - $cacheTime = CacheHandler::getInstance()->getCacheSource()->getExpirationTime( + $cacheTime = CacheHandler::getInstance()->getCacheSource()->getCreationTime( $this->getCacheKey(), $this->getLifetime() ); From 91165bc26413373ccb8913def374b8b90f067cab Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Mon, 17 Mar 2025 15:01:16 +0100 Subject: [PATCH 39/42] Use probabilistic early expiration for tolerant cache --- ...olerantCacheRebuildBackgroundJob.class.php | 4 +- .../cache/eager/AbstractEagerCache.class.php | 15 +------ .../tolerant/AbstractTolerantCache.class.php | 45 +++++++------------ .../system/language/LanguageFactory.class.php | 2 - .../files/lib/util/ClassUtil.class.php | 23 ++++++++++ 5 files changed, 42 insertions(+), 47 deletions(-) diff --git a/wcfsetup/install/files/lib/system/background/job/TolerantCacheRebuildBackgroundJob.class.php b/wcfsetup/install/files/lib/system/background/job/TolerantCacheRebuildBackgroundJob.class.php index e492ebea5f8..2c3ab7c2895 100644 --- a/wcfsetup/install/files/lib/system/background/job/TolerantCacheRebuildBackgroundJob.class.php +++ b/wcfsetup/install/files/lib/system/background/job/TolerantCacheRebuildBackgroundJob.class.php @@ -51,9 +51,7 @@ public function perform() } $asyncCache = new $this->cacheClass(...$this->parameters); - if (!$asyncCache->needsRebuild()) { - return; - } + \assert($asyncCache instanceof AbstractTolerantCache); $asyncCache->rebuild(); } diff --git a/wcfsetup/install/files/lib/system/cache/eager/AbstractEagerCache.class.php b/wcfsetup/install/files/lib/system/cache/eager/AbstractEagerCache.class.php index c74dbe47d1d..90cf08f6463 100644 --- a/wcfsetup/install/files/lib/system/cache/eager/AbstractEagerCache.class.php +++ b/wcfsetup/install/files/lib/system/cache/eager/AbstractEagerCache.class.php @@ -3,6 +3,7 @@ namespace wcf\system\cache\eager; use wcf\system\cache\CacheHandler; +use wcf\util\ClassUtil; /** * @author Olaf Braun @@ -52,19 +53,7 @@ private function getCacheKey(): string \get_class($this) ); - $parameters = []; - foreach ($reflection->getProperties(\ReflectionProperty::IS_READONLY) as $property) { - if (!$property->isInitialized($this)) { - continue; - } - - $value = $property->getValue($this); - if ($value === null) { - continue; - } - - $parameters[$property->getName()] = $value; - } + $parameters = ClassUtil::getConstructorProperties($this); if ($parameters !== []) { $this->cacheName .= '-' . CacheHandler::getInstance()->getCacheIndex($parameters); diff --git a/wcfsetup/install/files/lib/system/cache/tolerant/AbstractTolerantCache.class.php b/wcfsetup/install/files/lib/system/cache/tolerant/AbstractTolerantCache.class.php index e6047fb8646..f34a9e98a67 100644 --- a/wcfsetup/install/files/lib/system/cache/tolerant/AbstractTolerantCache.class.php +++ b/wcfsetup/install/files/lib/system/cache/tolerant/AbstractTolerantCache.class.php @@ -5,6 +5,7 @@ use wcf\system\background\BackgroundQueueHandler; use wcf\system\background\job\TolerantCacheRebuildBackgroundJob; use wcf\system\cache\CacheHandler; +use wcf\util\ClassUtil; /** * @author Olaf Braun @@ -40,9 +41,13 @@ final public function getCache(): array|object } if ($this->needsRebuild()) { - BackgroundQueueHandler::getInstance()->enqueueIn( - [new TolerantCacheRebuildBackgroundJob(\get_class($this), $this->getProperties())] - ); + BackgroundQueueHandler::getInstance()->enqueueIn([ + new TolerantCacheRebuildBackgroundJob( + \get_class($this), + ClassUtil::getConstructorProperties($this) + ) + ]); + BackgroundQueueHandler::getInstance()->forceCheck(); } } return $this->cache; @@ -58,7 +63,7 @@ private function getCacheKey(): string \get_class($this) ); - $parameters = $this->getProperties(); + $parameters = ClassUtil::getConstructorProperties($this); if ($parameters !== []) { $this->cacheName .= '-' . CacheHandler::getInstance()->getCacheIndex($parameters); @@ -68,28 +73,6 @@ private function getCacheKey(): string return $this->cacheName; } - /** - * @return array - */ - private function getProperties(): array - { - $reflection = new \ReflectionClass($this); - $properties = []; - foreach ($reflection->getProperties(\ReflectionProperty::IS_READONLY) as $property) { - if (!$property->isInitialized($this)) { - continue; - } - - if ($property->getValue($this) === null) { - continue; - } - - $properties[$property->getName()] = $property->getValue($this); - } - - return $properties; - } - final public function rebuild(): void { $newCacheData = $this->rebuildCacheData(); @@ -121,7 +104,7 @@ final public function nextRebuildTime(): int return \TIME_NOW; } - return $cacheTime + ($this->getLifetime() - 60); + return $cacheTime + $this->getLifetime(); } /** @@ -129,8 +112,12 @@ final public function nextRebuildTime(): int */ abstract public function getLifetime(): int; - final public function needsRebuild(): bool + private function needsRebuild(): bool { - return TIME_NOW >= $this->nextRebuildTime(); + // Probabilistic early expiration + // https://en.wikipedia.org/wiki/Cache_stampede#Probabilistic_early_expiration + + return TIME_NOW - 10 * \log(\random_int(1, \PHP_INT_MAX) / \PHP_INT_MAX) + >= $this->nextRebuildTime(); } } diff --git a/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php b/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php index 5f3f5168020..5ecd2932e0d 100644 --- a/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php +++ b/wcfsetup/install/files/lib/system/language/LanguageFactory.class.php @@ -19,13 +19,11 @@ * @author Olaf Braun, Alexander Ebert * @copyright 2001-2025 WoltLab GmbH * @license GNU Lesser General Public License - * @phpstan-import-type LanguageCache from LanguageCacheBuilder */ class LanguageFactory extends SingletonFactory { /** * language cache - * @var LanguageCache */ protected LanguageCacheData $cache; diff --git a/wcfsetup/install/files/lib/util/ClassUtil.class.php b/wcfsetup/install/files/lib/util/ClassUtil.class.php index 3e317b88953..022a0ce695e 100644 --- a/wcfsetup/install/files/lib/util/ClassUtil.class.php +++ b/wcfsetup/install/files/lib/util/ClassUtil.class.php @@ -97,6 +97,29 @@ public static function isDecoratedInstanceOf($className, $targetClass) return false; } + /** + * Returns the properties as a key-value array to construct the given object. + * + * @return array + */ + public static function getConstructorProperties(object $object): array + { + $reflection = new \ReflectionClass($object); + + $properties = []; + foreach ($reflection->getConstructor()?->getParameters() ?? [] as $parameter) { + $property = $reflection->getProperty($parameter->getName()); + + if (!$property->isInitialized($object)) { + continue; + } + + $properties[$property->getName()] = $property->getValue($object); + } + + return $properties; + } + /** * Forbid creation of ClassUtil objects. */ From dc43423b864133c3e48f15e764143f1e46231abd Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Tue, 18 Mar 2025 09:16:56 +0100 Subject: [PATCH 40/42] Fix phpstan type --- .../lib/system/cache/builder/LanguageCacheBuilder.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wcfsetup/install/files/lib/system/cache/builder/LanguageCacheBuilder.class.php b/wcfsetup/install/files/lib/system/cache/builder/LanguageCacheBuilder.class.php index 9aa2bdc7697..69a3083eb63 100644 --- a/wcfsetup/install/files/lib/system/cache/builder/LanguageCacheBuilder.class.php +++ b/wcfsetup/install/files/lib/system/cache/builder/LanguageCacheBuilder.class.php @@ -2,9 +2,9 @@ namespace wcf\system\cache\builder; -use wcf\system\cache\eager\LanguageCache; use wcf\data\language\category\LanguageCategory; use wcf\data\language\Language; +use wcf\system\cache\eager\LanguageCache; /** * Caches languages and the id of the default language. @@ -12,7 +12,7 @@ * @author Olaf Braun, Marcel Werk * @copyright 2001-2025 WoltLab GmbH * @license GNU Lesser General Public License - * @phpstan-type LanguageCache array{ + * @phpstan-type LanguageCacheData array{ * codes: array, * countryCodes: array, * languages: array, From e1d71a8d43e00d24affade8139876ac4bca5fd90 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Tue, 18 Mar 2025 09:34:06 +0100 Subject: [PATCH 41/42] Add php class docs --- .../job/TolerantCacheRebuildBackgroundJob.class.php | 2 ++ .../files/lib/system/cache/eager/AbstractEagerCache.class.php | 2 ++ .../lib/system/cache/tolerant/AbstractTolerantCache.class.php | 4 ++++ 3 files changed, 8 insertions(+) diff --git a/wcfsetup/install/files/lib/system/background/job/TolerantCacheRebuildBackgroundJob.class.php b/wcfsetup/install/files/lib/system/background/job/TolerantCacheRebuildBackgroundJob.class.php index 2c3ab7c2895..be0d194a9f1 100644 --- a/wcfsetup/install/files/lib/system/background/job/TolerantCacheRebuildBackgroundJob.class.php +++ b/wcfsetup/install/files/lib/system/background/job/TolerantCacheRebuildBackgroundJob.class.php @@ -6,6 +6,8 @@ use wcf\system\cache\tolerant\AbstractTolerantCache; /** + * Rebuilds the cache data of a tolerant cache. + * * @author Olaf Braun * @copyright 2001-2025 WoltLab GmbH * @license GNU Lesser General Public License diff --git a/wcfsetup/install/files/lib/system/cache/eager/AbstractEagerCache.class.php b/wcfsetup/install/files/lib/system/cache/eager/AbstractEagerCache.class.php index 90cf08f6463..26f3213ac90 100644 --- a/wcfsetup/install/files/lib/system/cache/eager/AbstractEagerCache.class.php +++ b/wcfsetup/install/files/lib/system/cache/eager/AbstractEagerCache.class.php @@ -6,6 +6,8 @@ use wcf\util\ClassUtil; /** + * Eager caches are caches that do not expire and must be renewed manually if the data in the cache has changed. + * * @author Olaf Braun * @copyright 2001-2025 WoltLab GmbH * @license GNU Lesser General Public License diff --git a/wcfsetup/install/files/lib/system/cache/tolerant/AbstractTolerantCache.class.php b/wcfsetup/install/files/lib/system/cache/tolerant/AbstractTolerantCache.class.php index f34a9e98a67..746d0aca778 100644 --- a/wcfsetup/install/files/lib/system/cache/tolerant/AbstractTolerantCache.class.php +++ b/wcfsetup/install/files/lib/system/cache/tolerant/AbstractTolerantCache.class.php @@ -8,6 +8,10 @@ use wcf\util\ClassUtil; /** + * Tolerant caches are caches that are rebuilt in the background when they are about to expire or have already expired. + * The cache data can be outdated, this must not be a problem when using these caches. + * The lifetime MUST BE `>= 300`. + * * @author Olaf Braun * @copyright 2001-2025 WoltLab GmbH * @license GNU Lesser General Public License From f1f9028f96809e300c564b77510cf73e06b26da1 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Tue, 18 Mar 2025 09:34:37 +0100 Subject: [PATCH 42/42] Make sure that $lifetime is `>= 300` --- .../system/cache/tolerant/AbstractTolerantCache.class.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/wcfsetup/install/files/lib/system/cache/tolerant/AbstractTolerantCache.class.php b/wcfsetup/install/files/lib/system/cache/tolerant/AbstractTolerantCache.class.php index 746d0aca778..eaaf88ba428 100644 --- a/wcfsetup/install/files/lib/system/cache/tolerant/AbstractTolerantCache.class.php +++ b/wcfsetup/install/files/lib/system/cache/tolerant/AbstractTolerantCache.class.php @@ -99,16 +99,19 @@ abstract protected function rebuildCacheData(): array|object; final public function nextRebuildTime(): int { + $lifetime = $this->getLifetime(); + \assert($lifetime >= 300); + $cacheTime = CacheHandler::getInstance()->getCacheSource()->getCreationTime( $this->getCacheKey(), - $this->getLifetime() + $lifetime ); if ($cacheTime === null) { return \TIME_NOW; } - return $cacheTime + $this->getLifetime(); + return $cacheTime + $lifetime; } /**