diff --git a/lib/classes/StudipCachedArray.php b/lib/classes/StudipCachedArray.php index 221b76c3aafd1ac0b18c7efb23f6138d447b54d1..830128dde4d4a23fbedb22b7c9915b1887d74201 100644 --- a/lib/classes/StudipCachedArray.php +++ b/lib/classes/StudipCachedArray.php @@ -15,21 +15,24 @@ class StudipCachedArray implements ArrayAccess protected $data = []; + protected $hash; + /** * Constructs the cached array * - * @param string $key Cache key where the array is/should be stored - * an int which will be length of the substring - * of the given chache offset or a callable which - * will return the partition key. - * @param int $duration Duration in seconds for which the item shall be - * stored + * @param string $key Cache key where the array is/should be stored + * an int which will be length of the substring + * of the given chache offset or a callable which + * will return the partition key. + * @param int $duration Duration in seconds for which the item shall be + * stored */ public function __construct(string $key, int $duration = StudipCache::DEFAULT_EXPIRATION) { - $this->key = self::class . "/{$key}"; - $this->cache = StudipCacheFactory::getCache(); + $this->key = self::class . "/{$key}"; + $this->cache = StudipCacheFactory::getCache(); $this->duration = $duration; + $this->hash = $this->getHash(); $this->reset(); } @@ -42,10 +45,20 @@ class StudipCachedArray implements ArrayAccess $this->data = []; } + /** + * Removes all values from the cache. + */ + public function expire(): void + { + $this->hash = $this->getHash(true); + $this->reset(); + } + /** * Determines whether an offset exists in the array. * * @param string $offset Offset + * * @return bool */ public function offsetExists($offset): bool @@ -58,6 +71,7 @@ class StudipCachedArray implements ArrayAccess * Returns the value at given offset or null if it doesn't exist. * * @param string $offset Offset + * * @return mixed */ public function offsetGet($offset) @@ -75,7 +89,7 @@ class StudipCachedArray implements ArrayAccess public function offsetSet($offset, $value): void { if ($offset === null) { - throw new Exception('Cannot push to cached array, use StudipCachedArray instead'); + throw new Exception('Cannot push to cached array, use correct offset instead'); } if (!isset($this->data[$offset]) || $this->data[$offset] !== $value) { @@ -120,9 +134,11 @@ class StudipCachedArray implements ArrayAccess */ protected function storeData(string $offset): void { + $data = $this->swapNullAndFalse($this->data[$offset]); + $this->cache->write( $this->getCacheKey($offset), - $this->swapNullAndFalse($this->data[$offset]), + $data, $this->duration ); } @@ -131,11 +147,18 @@ class StudipCachedArray implements ArrayAccess * Returns the cache key for a specific offset. * * @param string $offset Offset of the cached item + * * @return string */ private function getCacheKey(string $offset): string { - return rtrim($this->key, '/') . "/{$offset}"; + $key = rtrim($this->key, '/'); + if ($this->hash) { + $key .= "/{$this->hash}"; + } + $key .= "/{$offset}"; + + return $key; } /** @@ -158,4 +181,21 @@ class StudipCachedArray implements ArrayAccess return $value; } + + /** + * Loads or creates and stores a hash for this cached array. + * + * @return string + */ + private function getHash(bool $recreate = false): string + { + if (!$recreate) { + $hash = $this->cache->read($this->key); + return $hash === false ? '' : $hash; + } + + $hash = md5(uniqid(__CLASS__, true)); + $this->cache->write($this->key, $hash); + return $hash; + } } diff --git a/lib/plugins/db/RolePersistence.class.php b/lib/plugins/db/RolePersistence.class.php index 1b03a0132c91558980bfa3da350e274f2f422309..ff17a94d0bd3b2f5779a8638fdc06df81dcce480 100644 --- a/lib/plugins/db/RolePersistence.class.php +++ b/lib/plugins/db/RolePersistence.class.php @@ -129,9 +129,10 @@ class RolePersistence // sweep roles cache self::expireRolesCache(); + self::expireUserCache(); foreach ($statement as $plugin_id) { - unset(self::getPluginRolesCache()[$plugin_id]); + self::expirePluginCache($plugin_id); } NotificationCenter::postNotification('RoleDidDelete', $id, $name); @@ -338,7 +339,7 @@ class RolePersistence $statement->execute(); } - unset(self::getPluginRolesCache()[$plugin_id]); + self::expirePluginCache($plugin_id); foreach ($role_ids as $role_id) { NotificationCenter::postNotification( @@ -370,7 +371,7 @@ class RolePersistence $statement->execute(); } - unset(self::getPluginRolesCache()[$plugin_id]); + self::expirePluginCache($plugin_id); foreach ($role_ids as $role_id) { NotificationCenter::postNotification( @@ -488,7 +489,7 @@ class RolePersistence private static $user_roles_cache = null; private static $plugin_roles_cache = null; - private static function getUserRolesCache() + private static function getUserRolesCache(): StudipCachedArray { if (self::$user_roles_cache === null) { self::$user_roles_cache = new StudipCachedArray(self::USER_ROLES_CACHE_KEY); @@ -496,7 +497,7 @@ class RolePersistence return self::$user_roles_cache; } - private static function getPluginRolesCache() + private static function getPluginRolesCache(): StudipCachedArray { if (self::$plugin_roles_cache === null) { self::$plugin_roles_cache = new StudipCachedArray(self::PLUGIN_ROLES_CACHE_KEY); @@ -504,13 +505,52 @@ class RolePersistence return self::$plugin_roles_cache; } + /** + * Expires all cached roles. + */ public static function expireRolesCache() { StudipCacheFactory::getCache()->expire(self::ROLES_CACHE_KEY); } - public static function expireUserCache($user_id) + /** + * Expires all cached user role assignments. + * + * @param string|null $user_id Optional user id to expire the cache for. + * If none is given, the whole cache is cleared. + */ + public static function expireUserCache($user_id = null) { - unset(self::getUserRolesCache()[$user_id]); + if ($user_id === null) { + self::getUserRolesCache()->expire(); + } else { + unset(self::getUserRolesCache()[$user_id]); + } + } + + /** + * Expires all cached plugin role assignments. + * + * @param string|int|null $plugin_id Optional plugin id to expire the cache + * for. If none is given, the whole cache + * is cleared. + */ + public static function expirePluginCache($plugin_id = null) + { + if ($plugin_id === null) { + self::getPluginRolesCache()->expire(); + } else { + unset(self::getPluginRolesCache()[$plugin_id]); + } + } + + /** + * Expires all caches + */ + public static function expireCaches(): void + { + self::expireRolesCache(); + self::expireUserCache(); + self::expirePluginCache(); } } diff --git a/tests/unit/lib/classes/StudipCachedArrayTest.php b/tests/unit/lib/classes/StudipCachedArrayTest.php index e473dc631439a19a549f0c34f5834cb7fa069e6d..c98c1bd6b5c151bc9f34656047edec3e4b989e74 100644 --- a/tests/unit/lib/classes/StudipCachedArrayTest.php +++ b/tests/unit/lib/classes/StudipCachedArrayTest.php @@ -48,6 +48,21 @@ class StudipCachedArrayTest extends \Codeception\Test\Unit $this->assertFalse(isset($cache[$key])); } + /** + * @depends testStorage + * @dataProvider StorageProvider + */ + public function testExpiration($key, $value) + { + $cache = $this->getCachedArray(); + + $cache[$key] = $value; + + $cache->expire(); + + $this->assertFalse(isset($cache[$key])); + } + public function StorageProvider(): array { return [