diff --git a/app/controllers/admin/cache.php b/app/controllers/admin/cache.php index 329aab7a968a1ce98ce8c2b55d588233ff4b575e..3e9d970a76d739fdc53be9eb410de27e717f3be7 100644 --- a/app/controllers/admin/cache.php +++ b/app/controllers/admin/cache.php @@ -111,7 +111,7 @@ class Admin_CacheController extends AuthenticatedController // Store settings to global config. if (Config::get()->store('SYSTEMCACHE', $settings)) { PageLayout::postSuccess(_('Die Einstellungen wurden gespeichert.')); - StudipCacheFactory::unconfigure(); + \Studip\Cache\Factory::unconfigure(); } $this->relocate('admin/cache/settings'); @@ -122,7 +122,7 @@ class Admin_CacheController extends AuthenticatedController */ public function flush_action() { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $cache->flush(); PageLayout::postSuccess(_('Die Inhalte des Caches wurden gelöscht.')); @@ -135,7 +135,7 @@ class Admin_CacheController extends AuthenticatedController */ public function stats_action() { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $this->stats = $cache->getStats(); } diff --git a/app/controllers/file.php b/app/controllers/file.php index 65344efaa8918ef5f0fc8a9f70544a75d58722d5..bebfe70ebf8253214244f45aaad40e3c48f11dbe 100644 --- a/app/controllers/file.php +++ b/app/controllers/file.php @@ -364,7 +364,7 @@ class FileController extends AuthenticatedController //The file system object is a folder. //Calculate the files and the folder size: - list($this->folder_size, $this->folder_file_amount) = $this->getFolderSize($this->folder); + [$this->folder_size, $this->folder_file_amount] = $this->getFolderSize($this->folder); PageLayout::setTitle($this->folder->name); $this->render_action('folder_details'); } @@ -1128,7 +1128,7 @@ class FileController extends AuthenticatedController $this->search_id = md5(json_encode($search_parameters)); - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $merged_results = LibrarySearchManager::search( $search_parameters, @@ -1187,7 +1187,7 @@ class FileController extends AuthenticatedController $this->search_id = Request::get('search_id'); $this->page = Request::get('page'); - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $cache_data = $cache->read($this->search_id); $results = $cache_data['results']; $this->total_results = count($results); @@ -1247,7 +1247,7 @@ class FileController extends AuthenticatedController } if ($item_id) { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $documents = $cache->read($search_id); $document = $documents['results'][$item_id]; if (!($document instanceof LibraryDocument)) { @@ -1255,7 +1255,7 @@ class FileController extends AuthenticatedController } $file = LibraryFile::createFromLibraryDocument($document, $folder_id); } else { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $search = $cache->read($search_id); if (!$search) { throw new Exception('Search not found in cache!'); @@ -2036,7 +2036,7 @@ class FileController extends AuthenticatedController PageLayout::postMessage($result); } } - list($this->folder_size, $this->folder_file_amount) = $this->getFolderSize($folder); + [$this->folder_size, $this->folder_file_amount] = $this->getFolderSize($folder); } public function delete_folder_action($folder_id) diff --git a/composer.json b/composer.json index e4d1902d0a51b672eff7cf9f513fd7091f12cf11..009a7a07122ee2538e5a9217e9cf856f4b4f1a27 100644 --- a/composer.json +++ b/composer.json @@ -62,7 +62,8 @@ "psy/psysh": "^0.12.2", "okvpn/clock-lts": "^1.0", "vlucas/phpdotenv": "^5.6", - "edu-sharing/auth-plugin": "8.0.x-dev" + "edu-sharing/auth-plugin": "8.0.x-dev", + "psr/cache": "^1.0" }, "replace": { "symfony/polyfill-php73": "*", diff --git a/composer.lock b/composer.lock index ffde9bab8885566d3daae2a25cedeb1bbf637c71..b93431af4c4150d2f296607585d018f687c227f5 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "535218b1c9cca9acd1f2c554c59e5113", + "content-hash": "30db4609d0f74dac659c0fcd53db3d0a", "packages": [ { "name": "algo26-matthias/idna-convert", @@ -2889,6 +2889,55 @@ }, "time": "2023-02-11T17:10:30+00:00" }, + { + "name": "psr/cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/master" + }, + "time": "2016-08-06T20:24:11+00:00" + }, { "name": "psr/clock", "version": "1.0.0", diff --git a/db/migrations/1.154_recalculate_score.php b/db/migrations/1.154_recalculate_score.php index 158582ee2918fd214f70e52800e6894a17b9f7ca..5568363ea30e9a6283001dcccab7f241e58aa908 100644 --- a/db/migrations/1.154_recalculate_score.php +++ b/db/migrations/1.154_recalculate_score.php @@ -38,7 +38,7 @@ class RecalculateScore extends Migration { private static function getScore($user_id) { $user_id || $user_id = $GLOBALS['user']->id; - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); if ($cache->read("user_score_of_".$user_id)) { return $cache->read("user_score_of_".$user_id); } @@ -142,4 +142,3 @@ class RecalculateScore extends Migration { } } - diff --git a/db/migrations/1.224_db_cache_table.php b/db/migrations/1.224_db_cache_table.php index 66227d0b9e2b59bd4944068d641ce6a8204291b9..9abe7e3dc6fe525afa7909d4f14ce84bab95155c 100644 --- a/db/migrations/1.224_db_cache_table.php +++ b/db/migrations/1.224_db_cache_table.php @@ -17,7 +17,7 @@ class DbCacheTable extends Migration PRIMARY KEY (cache_key) ) ENGINE=InnoDB ROW_FORMAT=DYNAMIC'); - StudipCacheFactory::getCache()->flush(); + \Studip\Cache\Factory::getCache()->flush(); } public function down() diff --git a/db/migrations/1.318_step_00353_cache.php b/db/migrations/1.318_step_00353_cache.php index 901db6045234293e8f608bb3a777b5d1a1fcef50..5959945db68aa14742a113ddcd5b200ac066c5f2 100644 --- a/db/migrations/1.318_step_00353_cache.php +++ b/db/migrations/1.318_step_00353_cache.php @@ -1,5 +1,10 @@ <?php +use Studip\Cache\DbCache; +use Studip\Cache\FileCache; +use Studip\Cache\MemcachedCache; +use Studip\Cache\RedisCache; + class Step00353Cache extends Migration { public function description() @@ -20,10 +25,10 @@ class Step00353Cache extends Migration ) ENGINE=InnoDB ROW_FORMAT=DYNAMIC"); $types = [ - 'StudipDbCache', - 'StudipFileCache', - 'StudipMemcachedCache', - 'StudipRedisCache' + DbCache::class, + FileCache::class, + MemcachedCache::class, + RedisCache::class, ]; // Insert pre-defined cache types in to database diff --git a/db/migrations/5.5.26_tic3193_no_unlimited_courses.php b/db/migrations/5.5.26_tic3193_no_unlimited_courses.php index ef45bced3abcbbfa41e3a18bf52f8dabccde46af..21a80ca39b4d014548f676de9bec60d8ecfa423c 100644 --- a/db/migrations/5.5.26_tic3193_no_unlimited_courses.php +++ b/db/migrations/5.5.26_tic3193_no_unlimited_courses.php @@ -12,15 +12,14 @@ final class Tic3193NoUnlimitedCourses extends Migration DBManager::get()->exec(" ALTER TABLE `sem_classes` ADD `unlimited_forbidden` TINYINT UNSIGNED NOT NULL DEFAULT 0 AFTER `is_group` "); - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $cache->expire('DB_SEM_CLASSES_ARRAY'); } public function down() { DBManager::get()->exec("ALTER TABLE `sem_classes` DROP `unlimited_forbidden`"); - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $cache->expire('DB_SEM_CLASSES_ARRAY'); } } - diff --git a/db/migrations/6.0.3_adjust_cache_configuration.php b/db/migrations/6.0.3_adjust_cache_configuration.php new file mode 100644 index 0000000000000000000000000000000000000000..2432e8474ff4a438cdabb777e004440dfece76d7 --- /dev/null +++ b/db/migrations/6.0.3_adjust_cache_configuration.php @@ -0,0 +1,38 @@ +<?php +return new class extends Migration +{ + private const MAPPING = [ + StudipDbCache::class => Studip\Cache\DbCache::class, + StudipFileCache::class => Studip\Cache\FileCache::class, + StudipMemcachedCache::class => Studip\Cache\MemcachedCache::class, + StudipRedisCache::class => Studip\Cache\RedisCache::class, + ]; + + public function description() + { + return 'Replaces the renamed cache classes in system configuration'; + } + + protected function up() + { + foreach (self::MAPPING as $old => $new) { + self::replaceCache($old, $new); + } + } + + protected function down() + { + foreach (self::MAPPING as $old => $new) { + self::replaceCache($new, $old); + } + } + + private function replaceCache(string $old, string $new): void + { + $query = "UPDATE `config_values` + SET `value` = JSON_REPLACE(`value`, '$.type', ?) + WHERE `field` = 'SYSTEMCACHE' + AND JSON_CONTAINS(`value`, JSON_QUOTE(?), '$.type')"; + DBManager::get()->execute($query, [$new, $old]); + } +}; diff --git a/lib/activities/Activity.php b/lib/activities/Activity.php index 3508e5a2170a37f88d0b7f7b9c31e5fb74f225df..e1c918d0aa7401837000f9093a969cb8d24b0b45 100644 --- a/lib/activities/Activity.php +++ b/lib/activities/Activity.php @@ -163,7 +163,7 @@ class Activity extends \SimpleORMap ); //Expire Cache - \StudipCacheFactory::getCache()->expire('activity/oldest_activity'); + \Studip\Cache\Factory::getCache()->expire('activity/oldest_activity'); } /** @@ -173,7 +173,7 @@ class Activity extends \SimpleORMap */ public static function getOldestActivity() { - $cache = \StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $cache_key = 'activity/oldest-activity'; if (!$activity = unserialize($cache->read($cache_key))) { diff --git a/lib/bootstrap-autoload.php b/lib/bootstrap-autoload.php index 4e63115f5eb6ccb3a413e017d2ce4798042c2f70..21219fd325eb4d704f66fdc0c9412b520a44a7b9 100644 --- a/lib/bootstrap-autoload.php +++ b/lib/bootstrap-autoload.php @@ -20,6 +20,11 @@ StudipAutoloader::addAutoloadPath('lib/classes/admission'); StudipAutoloader::addAutoloadPath('lib/classes/admission/userfilter'); StudipAutoloader::addAutoloadPath('lib/classes/auth_plugins'); StudipAutoloader::addAutoloadPath('lib/classes/calendar'); + +StudipAutoloader::addAutoloadPath('lib/classes/cache', 'Studip\\Cache'); +class_alias(\Studip\Cache\Factory::class, 'StudipCacheFactory'); +class_alias(\Studip\Cache\Cache::class, 'StudipCache'); + StudipAutoloader::addAutoloadPath('lib/classes/exportdocument'); StudipAutoloader::addAutoloadPath('lib/classes/forms'); StudipAutoloader::addAutoloadPath('lib/classes/globalsearch'); diff --git a/lib/bootstrap-definitions.php b/lib/bootstrap-definitions.php index 17f35d16d6478b302201cedd06c7d08eebbc0241..ee80135be09cabd42e8dded7cfb1d982fe53b4d7 100644 --- a/lib/bootstrap-definitions.php +++ b/lib/bootstrap-definitions.php @@ -14,8 +14,8 @@ return [ ), ]); }), - StudipCache::class => DI\factory(function () { - return StudipCacheFactory::getCache(); + \Studip\Cache\Cache::class => DI\factory(function () { + return \Studip\Cache\Factory::getCache(); }), StudipPDO::class => DI\factory(function () { return DBManager::get(); diff --git a/lib/bootstrap.php b/lib/bootstrap.php index 364f9d9f69fa47504ab5d323fb6544eebf5beb53..9afc16a7a6843eea0dfeeb1d6afb220512c1cc99 100644 --- a/lib/bootstrap.php +++ b/lib/bootstrap.php @@ -170,7 +170,7 @@ if (isset($_SERVER['REQUEST_METHOD'])) { // bootstrap because the stud.ip cache needs to have a db conenction) if ($GLOBALS['CACHING_ENABLE']) { $lookup_hash = null; - $cached = StudipCacheFactory::getCache()->read('STUDIP#autoloader-classes'); + $cached = \Studip\Cache\Factory::getCache()->read('STUDIP#autoloader-classes'); if ($cached) { $class_lookup = json_decode($cached, true); if (is_array($class_lookup)) { @@ -182,7 +182,7 @@ if ($GLOBALS['CACHING_ENABLE']) { register_shutdown_function(function () use ($lookup_hash) { $cached = json_encode(StudipAutoloader::$class_lookup, JSON_UNESCAPED_UNICODE); if (md5($cached) !== $lookup_hash) { - StudipCacheFactory::getCache()->write( + \Studip\Cache\Factory::getCache()->write( 'STUDIP#autoloader-classes', $cached, 7 * 24 * 60 * 60 diff --git a/lib/classes/MvvPerm.php b/lib/classes/MvvPerm.php index 93f9f4404201e1c9e0e10cda3cd7ce265accc0d2..91e1e35b1a56617cbceff1461888e406536ae5fa 100644 --- a/lib/classes/MvvPerm.php +++ b/lib/classes/MvvPerm.php @@ -563,7 +563,7 @@ class MvvPerm { private static function getPrivileges($mvv_table) { if (self::$privileges === null) { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); self::$privileges = unserialize($cache->read(MVV::CACHE_KEY . '/privileges')); } @@ -576,7 +576,7 @@ class MvvPerm { include $config_file; // Defines $privileges self::$privileges[$mvv_table] = $privileges ?? []; } - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $cache->write(MVV::CACHE_KEY . '/privileges', serialize(self::$privileges)); } } diff --git a/lib/classes/Score.class.php b/lib/classes/Score.class.php index 8f038f9fb94c3b5219aff84dcfa96a4042174278..f7d8ea34271e927f88a8b61497ce5ef90484ee05 100644 --- a/lib/classes/Score.class.php +++ b/lib/classes/Score.class.php @@ -121,7 +121,7 @@ class Score public static function GetMyScore($user_or_id = null) { $user = $user_or_id ? User::toObject($user_or_id) : User::findCurrent(); - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); if ($cache->read("user_score_of_".$user->id)) { return $cache->read("user_score_of_".$user->id); } diff --git a/lib/classes/SemClass.class.php b/lib/classes/SemClass.class.php index 63be92091559a94ac64aacbd1a030c0b191e4d27..2a038e207874a15fee33049dd68bff4106bdb0ee 100644 --- a/lib/classes/SemClass.class.php +++ b/lib/classes/SemClass.class.php @@ -389,7 +389,7 @@ class SemClass implements ArrayAccess "chdate = UNIX_TIMESTAMP() " . "WHERE id = :id ". ""); - StudipCacheFactory::getCache()->expire('DB_SEM_CLASSES_ARRAY'); + \Studip\Cache\Factory::getCache()->expire('DB_SEM_CLASSES_ARRAY'); return $statement->execute([ 'id' => $this->data['id'], 'name' => $this->data['name'], @@ -453,7 +453,7 @@ class SemClass implements ArrayAccess DELETE FROM sem_classes WHERE id = :id "); - StudipCacheFactory::getCache()->expire('DB_SEM_CLASSES_ARRAY'); + \Studip\Cache\Factory::getCache()->expire('DB_SEM_CLASSES_ARRAY'); return $statement->execute([ 'id' => $this->data['id'] ]); @@ -552,7 +552,7 @@ class SemClass implements ArrayAccess $db = DBManager::get(); self::$sem_classes = []; - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $class_array = unserialize($cache->read('DB_SEM_CLASSES_ARRAY')); if (!$class_array) { @@ -564,7 +564,7 @@ class SemClass implements ArrayAccess $class_array = $statement->fetchAll(PDO::FETCH_ASSOC); if ($class_array) { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $cache->write('DB_SEM_CLASSES_ARRAY', serialize($class_array)); } } catch (PDOException $e) { @@ -593,7 +593,7 @@ class SemClass implements ArrayAccess */ static public function refreshClasses() { - StudipCacheFactory::getCache()->expire('DB_SEM_CLASSES_ARRAY'); + \Studip\Cache\Factory::getCache()->expire('DB_SEM_CLASSES_ARRAY'); self::$sem_classes = null; return self::getClasses(); } diff --git a/lib/classes/SemType.class.php b/lib/classes/SemType.class.php index 2365c4e8e937a8fb4ccb09205406709a476ccba5..5be1f19cd071bfe907ff3cf5e7a634df029f4e9c 100644 --- a/lib/classes/SemType.class.php +++ b/lib/classes/SemType.class.php @@ -68,7 +68,7 @@ class SemType implements ArrayAccess "chdate = UNIX_TIMESTAMP() " . "WHERE id = :id ". ""); - StudipCacheFactory::getCache()->expire('DB_SEM_TYPES_ARRAY'); + \Studip\Cache\Factory::getCache()->expire('DB_SEM_TYPES_ARRAY'); return $statement->execute([ 'id' => $this->data['id'], 'name' => $this->data['name'], @@ -89,7 +89,7 @@ class SemType implements ArrayAccess DELETE FROM sem_types WHERE id = :id "); - StudipCacheFactory::getCache()->expire('DB_SEM_TYPES_ARRAY'); + \Studip\Cache\Factory::getCache()->expire('DB_SEM_TYPES_ARRAY'); return $statement->execute([ 'id' => $this->data['id'] ]); @@ -175,7 +175,7 @@ class SemType implements ArrayAccess $db = DBManager::get(); self::$sem_types = []; - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $types_array = unserialize($cache->read('DB_SEM_TYPES_ARRAY')); if (!$types_array) { try { @@ -185,7 +185,7 @@ class SemType implements ArrayAccess $statement->execute(); $types_array = $statement->fetchAll(PDO::FETCH_ASSOC); if ($types_array) { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $cache->write('DB_SEM_TYPES_ARRAY', serialize($types_array)); } } catch (PDOException $e) { @@ -210,7 +210,7 @@ class SemType implements ArrayAccess static public function refreshTypes() { self::$sem_types = null; - StudipCacheFactory::getCache()->expire('DB_SEM_TYPES_ARRAY'); + \Studip\Cache\Factory::getCache()->expire('DB_SEM_TYPES_ARRAY'); return self::getTypes(); } diff --git a/lib/classes/Seminar.class.php b/lib/classes/Seminar.class.php index 054c337d4a1323f87bed7789511531b3f4202e0c..dda25ee0ffd59a515756b4f9f404e69d4566e66c 100644 --- a/lib/classes/Seminar.class.php +++ b/lib/classes/Seminar.class.php @@ -303,7 +303,7 @@ class Seminar { // Caching - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $cache_key = 'course/undecorated_data/'. $this->id; if ($filter) { @@ -745,7 +745,7 @@ class Seminar StudipLog::log("SEM_ADD_SINGLEDATE", $this->getId(), $singledate->toString(), 'SingleDateID: '.$singledate->getTerminID()); // logging <<<<<< - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $cache->expire('course/undecorated_data/'. $this->getId()); $this->readSingleDates(); diff --git a/lib/classes/SimpleORMap.class.php b/lib/classes/SimpleORMap.class.php index 26060860f639231616f72a3308058ed4eab2c4bc..499149c29b465c6c10c3619d8bfd43b98781c15c 100644 --- a/lib/classes/SimpleORMap.class.php +++ b/lib/classes/SimpleORMap.class.php @@ -277,7 +277,7 @@ class SimpleORMap implements ArrayAccess, Countable, IteratorAggregate $relation = $a_config[0] ?? ''; $relation_field = $a_config[1] ?? ''; if (!$relation) { - list($relation, $relation_field) = explode('_', $a_field); + [$relation, $relation_field] = explode('_', $a_field); } if (!$relation_field || !$relation) { throw new UnexpectedValueException('no relation found for autoget/set additional field: ' . $a_field); @@ -415,7 +415,7 @@ class SimpleORMap implements ArrayAccess, Countable, IteratorAggregate public static function tableScheme($db_table) { if (self::$schemes === null) { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); self::$schemes = unserialize($cache->read('DB_TABLE_SCHEMES')); } if (!isset(self::$schemes[$db_table])) { @@ -434,7 +434,7 @@ class SimpleORMap implements ArrayAccess, Countable, IteratorAggregate } self::$schemes[$db_table]['db_fields'] = $db_fields; self::$schemes[$db_table]['pk'] = $pk; - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $cache->write('DB_TABLE_SCHEMES', serialize(self::$schemes)); } return isset(self::$schemes[$db_table]); @@ -446,7 +446,7 @@ class SimpleORMap implements ArrayAccess, Countable, IteratorAggregate */ public static function expireTableScheme() { - StudipCacheFactory::getCache()->expire('DB_TABLE_SCHEMES'); + \Studip\Cache\Factory::getCache()->expire('DB_TABLE_SCHEMES'); self::$schemes = null; self::$config = []; } @@ -993,7 +993,7 @@ class SimpleORMap implements ArrayAccess, Countable, IteratorAggregate */ protected function _getAdditionalValueFromRelation($field) { - list($relation, $relation_field) = [$this->additional_fields()[$field]['relation'], + [$relation, $relation_field] = [$this->additional_fields()[$field]['relation'], $this->additional_fields()[$field]['relation_field']]; if (!array_key_exists($field, $this->additional_data)) { $this->_setAdditionalValue($field, $this->getRelationValue($relation, $relation_field)); @@ -1010,7 +1010,7 @@ class SimpleORMap implements ArrayAccess, Countable, IteratorAggregate */ protected function _setAdditionalValueFromRelation($field, $value) { - list($relation, $relation_field) = [$this->additional_fields()[$field]['relation'], + [$relation, $relation_field] = [$this->additional_fields()[$field]['relation'], $this->additional_fields()[$field]['relation_field']]; $this->$relation->$field = $value; unset($this->additional_data[$field]); diff --git a/lib/classes/Siteinfo.php b/lib/classes/Siteinfo.php index ba7386b8b61817b74951be61bd3a59111afd0408..247b836867dad7bb172dc0d0e2604278949de57b 100644 --- a/lib/classes/Siteinfo.php +++ b/lib/classes/Siteinfo.php @@ -419,7 +419,7 @@ class SiteinfoMarkupEngine { } function coregroup() { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); if (!($remotefile = $cache->read('coregroup'))) { $remotefile = file_get_contents('https://develop.studip.de/studip/extern.php?module=Persons&config_id=8d1dafc3afca2bce6125d57d4119b631&range_id=4498a5bc62d7974d0a0ac3e97aca5296', false, get_default_http_stream_context('https://develop.studip.de')); $cache->write('coregroup', $remotefile); @@ -428,7 +428,7 @@ class SiteinfoMarkupEngine { } function toplist($item) { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); if ($found_in_cache = $cache->read(__METHOD__ . $item)) { return $found_in_cache; } @@ -531,7 +531,7 @@ class SiteinfoMarkupEngine { } function indicator($key) { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); if ($found_in_cache = $cache->read(__METHOD__ . $key)) { return $found_in_cache; } diff --git a/lib/classes/StudipCache.class.php b/lib/classes/StudipCache.class.php deleted file mode 100644 index ba929f9bcffe9d764ebd80a3b651673fe2dc2a44..0000000000000000000000000000000000000000 --- a/lib/classes/StudipCache.class.php +++ /dev/null @@ -1,94 +0,0 @@ -<?php -/** - * An interface which has to be implemented by instances returned from - * StudipCacheFactory#getCache - * - * @package studip - * @subpackage lib - * - * @author Marco Diedrich (mdiedric@uos) - * @author Marcus Lunzenauer (mlunzena@uos.de) - * @copyright (c) Authors - * @since 1.6 - * @license GPL2 or any later version - */ - -interface StudipCache -{ - const DEFAULT_EXPIRATION = 12 * 60 * 60; // 12 hours - - /** - * Expire item from the cache. - * - * Example: - * - * # expires foo - * $cache->expire('foo'); - * - * @param string $arg a single key - */ - public function expire($arg); - - /**1 - * Expire all items from the cache. - */ - public function flush(); - - /** - * Retrieve item from the server. - * - * Example: - * - * # reads foo - * $foo = $cache->reads('foo'); - * - * @param string $arg a single key - * - * @return mixed the previously stored data if an item with such a key - * exists on the server or FALSE on failure. - */ - public function read($arg); - - /** - * Store data at the server. - * - * @param string $name the item's key. - * @param mixed $content the item's content (will be serialized if necessary). - * @param int $expires the item's expiry time in seconds. Optional, defaults to 12h. - * - * @return bool returns TRUE on success or FALSE on failure. - */ - public function write($name, $content, $expires = self::DEFAULT_EXPIRATION); - - /** - * @return string A translateable display name for this cache class. - */ - public static function getDisplayName(): string; - - /** - * Get some statistics from cache, like number of entries, hit rate or - * whatever the underlying cache provides. - * Results are returned in form of an array like - * "[ - * [ - * 'name' => <displayable name> - * 'value' => <value of the current stat> - * ] - * ]" - * - * @return array - */ - public function getStats(): array; - - /** - * Return the Vue component name and props that handle configuration. - * The associative array is of the form - * [ - * 'component' => <Vue component name>, - * 'props' => <Properties for component> - * ] - * - * @return array - */ - public static function getConfig(): array; -} diff --git a/lib/classes/StudipCachedArray.php b/lib/classes/StudipCachedArray.php index 1232d00950d499eb43c450df82835db42b313ebf..46723f447fc465345fdc4316420d2942d071d842 100644 --- a/lib/classes/StudipCachedArray.php +++ b/lib/classes/StudipCachedArray.php @@ -27,10 +27,10 @@ class StudipCachedArray implements ArrayAccess * @param int $duration Duration in seconds for which the item shall be * stored */ - public function __construct(string $key, int $duration = StudipCache::DEFAULT_EXPIRATION) + public function __construct(string $key, int $duration = \Studip\Cache\Cache::DEFAULT_EXPIRATION) { $this->key = self::class . "/{$key}"; - $this->cache = StudipCacheFactory::getCache(); + $this->cache = \Studip\Cache\Factory::getCache(); $this->duration = $duration; $this->hash = $this->getHash(); @@ -116,11 +116,14 @@ class StudipCachedArray implements ArrayAccess protected function loadData(string $offset) { if (!array_key_exists($offset, $this->data)) { - $cached = $this->cache->read($this->getCacheKey($offset)); - $this->data[$offset] = $this->swapNullAndFalse($cached); + // Get the cache item from the cache: + $item = $this->cache->getItem($this->getCacheKey($offset)); + if ($item->isHit()) { + $this->data[$offset] = $this->swapNullAndFalse($item->get()); + } } - return $this->data[$offset]; + return $this->data[$offset] ?? null; } /** @@ -132,13 +135,12 @@ class StudipCachedArray implements ArrayAccess */ protected function storeData(string $offset): void { - $data = $this->swapNullAndFalse($this->data[$offset]); - - $this->cache->write( + $item = new \Studip\Cache\Item( $this->getCacheKey($offset), - $data, + $this->swapNullAndFalse($this->data[$offset]), $this->duration ); + $this->cache->save($item); } /** diff --git a/lib/classes/StudipDbCache.class.php b/lib/classes/StudipDbCache.class.php deleted file mode 100644 index 865825e25880fb1fa72fe9ef0584c609a52add4c..0000000000000000000000000000000000000000 --- a/lib/classes/StudipDbCache.class.php +++ /dev/null @@ -1,123 +0,0 @@ -<?php -/** - * StudipCache implementation using database table - * - * @package studip - * @subpackage cache - * - * @author Elmar Ludwig <elmar.ludwig@uos.de> - */ -class StudipDbCache implements StudipCache -{ - - /** - * @return string A translateable display name for this cache class. - */ - public static function getDisplayName(): string - { - return _('Datenbank'); - } - - /** - * Expire item from the cache. - * - * @param string $arg a single key - */ - public function expire($arg) - { - $db = DBManager::get(); - - $stmt = $db->prepare('DELETE FROM cache WHERE cache_key = ?'); - $stmt->execute([$arg]); - } - - /** - * Expire all items from the cache. - */ - public function flush() - { - $db = DBManager::get(); - - $db->exec('TRUNCATE TABLE cache'); - } - - /** - * Delete all expired items from the cache. - */ - public function purge() - { - $db = DBManager::get(); - - $stmt = $db->prepare('DELETE FROM cache WHERE expires < ?'); - $stmt->execute([time()]); - } - - /** - * Retrieve item from the server. - * - * @param string $arg a single key - * - * @return mixed the previously stored data if an item with such a key - * exists on the server or FALSE on failure. - */ - public function read($arg) - { - $db = DBManager::get(); - - $stmt = $db->prepare('SELECT content FROM cache WHERE cache_key = ? AND expires > ?'); - $stmt->execute([$arg, time()]); - $result = $stmt->fetchColumn(); - - return $result !== false ? unserialize($result) : false; - } - - /** - * Store data at the server. - * - * @param string $name the item's key. - * @param mixed $content the item's content (will be serialized if necessary). - * @param int $expired the item's expiry time in seconds. Optional, defaults to 12h. - * - * @return bool returns TRUE on success or FALSE on failure. - */ - public function write($name, $content, $expires = self::DEFAULT_EXPIRATION) - { - $db = DBManager::get(); - - $stmt = $db->prepare('REPLACE INTO cache VALUES(?, ?, ?)'); - return $stmt->execute([$name, serialize($content), time() + $expires]); - } - - /** - * Return statistics. - * - * @see StudipCache::getStats() - * - * @return array|array[] - */ - public function getStats(): array - { - return [ - __CLASS__ => [ - 'name' => _('Anzahl Einträge'), - 'value' => DBManager::get()->fetchColumn("SELECT COUNT(*) FROM `cache`") - ] - ]; - } - - /** - * Return the Vue component name and props that handle configuration. - * - * @see StudipCache::getConfig() - * - * @return array - */ - public static function getConfig(): array - { - return [ - 'component' => null, - 'props' => [] - ]; - } - -} diff --git a/lib/classes/StudipKing.class.php b/lib/classes/StudipKing.class.php index 2d1f15ce883faada0e5e1033262ea116c2833103..3a61c577f987ddd01dd9aa2542f9a258444e5b71 100644 --- a/lib/classes/StudipKing.class.php +++ b/lib/classes/StudipKing.class.php @@ -63,7 +63,7 @@ class StudipKing { private static function get_kings() { if (self::$kings === null) { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); # read cache (unserializing a cache miss - FALSE - does not matter) $kings = unserialize($cache->read(self::CACHE_KEY)); diff --git a/lib/classes/StudipMemoryCache.class.php b/lib/classes/StudipMemoryCache.class.php deleted file mode 100644 index d38385a8ec40fbae2e97a505f3b3a85036a06cd7..0000000000000000000000000000000000000000 --- a/lib/classes/StudipMemoryCache.class.php +++ /dev/null @@ -1,84 +0,0 @@ -<?php -/** - * The php memory implementation of the StudipCache interface. - * - * @author Jan-Hendrik Willms <tleilax+studip@gmail.com> - * @license GPL2 or any later version - * @since Stud.IP 5.0 - */ -class StudipMemoryCache implements StudipCache -{ - protected $memory_cache = []; - - /** - * Expires just a single key. - * - * @param string the key - */ - public function expire($key) - { - unset($this->memory_cache[$key]); - } - - /** - * Expire all items from the cache. - */ - public function flush() - { - $this->memory_cache = []; - } - - /** - * Reads just a single key from the cache. - * - * @param string the key - * - * @return mixed the corresponding value - */ - public function read($key) - { - if (!isset($this->memory_cache[$key])) { - return false; - } - if ($this->memory_cache[$key]['expires'] < time()) { - $this->expire($key); - return false; - } - return $this->memory_cache[$key]['data']; - } - - /** - * Store data at the server. - * - * @param string the item's key. - * @param mixed the item's content (will be serialized if necessary). - * @param int the item's expiry time in seconds. Defaults to 12h. - * - * @returns mixed returns TRUE on success or FALSE on failure. - * - */ - public function write($name, $content, $expires = self::DEFAULT_EXPIRATION) - { - $this->memory_cache[$name] = [ - 'expires' => time() + $expires, - 'data' => $content, - ]; - - return true; - } - - public static function getDisplayName(): string - { - return 'Memory cache'; - } - - public function getStats(): array - { - return []; - } - - public static function getConfig(): array - { - return []; - } -} diff --git a/lib/classes/UserLookup.class.php b/lib/classes/UserLookup.class.php index ddf9276bc0a7f5064ac4451397f00a0c35a9af7e..bdd9fc368c0ad6281122e9e7300df16560775df4 100644 --- a/lib/classes/UserLookup.class.php +++ b/lib/classes/UserLookup.class.php @@ -239,7 +239,7 @@ class UserLookup return call_user_func(self::$types[$type]['values']); } - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $cache_key = "UserLookup/{$type}/values"; $cached_values = $cache->read($cache_key); if ($cached_values) { diff --git a/lib/classes/assets/SASSCompiler.php b/lib/classes/assets/SASSCompiler.php index 2dcda2dc50bb541d72de5630a7a27de22771fc5e..0b03a8cd2fa24b55a8dbfb55475ec535cd0f4c60 100644 --- a/lib/classes/assets/SASSCompiler.php +++ b/lib/classes/assets/SASSCompiler.php @@ -2,7 +2,7 @@ namespace Assets; use Assets; -use StudipCacheFactory; +use Studip\Cache\Factory; use Studip; use ScssPhp\ScssPhp\Compiler as ScssCompiler; @@ -82,7 +82,7 @@ class SASSCompiler implements Compiler */ private function getPrefix() { - $cache = StudipCacheFactory::getCache(); + $cache = Studip\Cache\Factory::getCache(); $prefix = $cache->read(self::CACHE_KEY); diff --git a/lib/classes/cache/Cache.class.php b/lib/classes/cache/Cache.class.php new file mode 100644 index 0000000000000000000000000000000000000000..366945dbcf6e4209c081a846f35dbf4494097de6 --- /dev/null +++ b/lib/classes/cache/Cache.class.php @@ -0,0 +1,208 @@ +<?php + +namespace Studip\Cache; + +use Psr\Cache\CacheItemInterface; +use Psr\Cache\CacheItemPoolInterface; + +/** + * An abstract class which has to be extended by instances returned from + * \Studip\Cache\Factory#getCache + * + * @author Marco Diedrich (mdiedric@uos) + * @author Marcus Lunzenauer (mlunzena@uos.de) + * @author Moritz Strohm <strohm@data-quest.de> + * @copyright (c) Authors + * @since 1.6 + * @license GPL2 or any later version + */ +abstract class Cache implements CacheItemPoolInterface +{ + const DEFAULT_EXPIRATION = 12 * 60 * 60; // 12 hours + + /** + * @return string A translateable display name for this cache class. + */ + abstract public static function getDisplayName(): string; + + /** + * Get some statistics from cache, like number of entries, hit rate or + * whatever the underlying cache provides. + * Results are returned in form of an array like + * "[ + * [ + * 'name' => <displayable name> + * 'value' => <value of the current stat> + * ] + * ]" + * + * @return array + */ + abstract public function getStats(): array; + + /** + * Return the Vue component name and props that handle configuration. + * The associative array is of the form + * [ + * 'component' => <Vue component name>, + * 'props' => <Properties for component> + * ] + * + * @return array + */ + abstract public static function getConfig(): array; + + /** + * Expire item from the cache. + * + * Example: + * + * # expires foo + * $cache->expire('foo'); + * + * @param string $arg a single key + */ + abstract public function expire($arg); + + /** + * Expire all items from the cache. + */ + abstract public function flush(); + + /** + * @see CacheItemPoolInterface::getItem + */ + abstract public function getItem($key); + + /** + * @see CacheItemPoolInterface::hasItem + */ + abstract public function hasItem($key); + + /** + * @var array An array of deferred items that shall be saved only + * when commit() is called. This is only used in PSR-6 cache methods. + */ + protected array $deferred_items = []; + + /** + * Retrieve item from the server. + * + * Example: + * + * # reads foo + * $foo = $cache->reads('foo'); + * + * @param string $arg a single key + * + * @return mixed the previously stored data if an item with such a key + * exists on the server or FALSE on failure. + * + * @deprecated To be removed with Stud.IP 7.0. + */ + public function read($arg) + { + $item = $this->getItem($arg); + if ($item->isHit()) { + return $item->get(); + } + return false; + } + + /** + * Store data at the server. + * + * @param string $name the item's key. + * @param mixed $content the item's content (will be serialized if necessary). + * @param int $expires the item's expiry time in seconds. Optional, defaults to 12h. + * + * @return bool returns TRUE on success or FALSE on failure. + + * @deprecated To be removed with Stud.IP 7.0. + */ + public function write($name, $content, $expires = self::DEFAULT_EXPIRATION) + { + $item = new Item($name, $content, $expires); + + return $this->save($item); + } + + /** + * Calculates the expiration by a cache item. If that cannot be determined, + * the default expiration period is returned. + * + * @param Item $item The item from which to get the expiration time. + * + * @return int The time from now until the expiration in seconds. + */ + public function getExpiration(CacheItemInterface $item) : int + { + $expiration = self::DEFAULT_EXPIRATION; + if ($item instanceof Item) { + $expiration = $item->getExpirationInSeconds(); + } + return $expiration; + } + + // PSR-6 CacheItemPoolInterface: + + /** + * @see CacheItemPoolInterface::getItems + */ + public function getItems(array $keys = []) + { + $items = []; + foreach ($keys as $key) { + $item = $this->getItem($key); + if ($item instanceof Item) { + $items[] = $item; + } + } + return $items; + } + + /** + * @see CacheItemPoolInterface::clear + */ + public function clear() + { + $this->deferred_items = []; + $this->flush(); + } + + /** + * @see CacheItemPoolInterface::deleteItem + */ + public function deleteItem($key) + { + $this->expire($key); + } + + /** + * @see CacheItemPoolInterface::deleteItems + */ + public function deleteItems(array $keys) + { + foreach ($keys as $key) { + $this->expire($key); + } + } + + /** + * @see CacheItemPoolInterface::saveDeferred + */ + public function saveDeferred(CacheItemInterface $item) + { + $this->deferred_items[] = $item; + } + + /** + * @see CacheItemPoolInterface::commit + */ + public function commit() + { + foreach ($this->deferred_items as $item) { + $this->save($item); + } + } +} diff --git a/lib/classes/cache/DbCache.class.php b/lib/classes/cache/DbCache.class.php new file mode 100644 index 0000000000000000000000000000000000000000..3361af05a86fbba1127eaa79de8678e810cedbb8 --- /dev/null +++ b/lib/classes/cache/DbCache.class.php @@ -0,0 +1,142 @@ +<?php + +namespace Studip\Cache; + +use DBManager; + +/** + * StudipCache implementation using database table + * + * @author Elmar Ludwig <elmar.ludwig@uos.de> + */ +class DbCache extends Cache +{ + /** + * @return string A display name (that can be translated) for this cache class. + */ + public static function getDisplayName(): string + { + return _('Datenbank'); + } + + /** + * Expire item from the cache. + * + * @param string $arg a single key + */ + public function expire($arg) + { + $db = DBManager::get(); + + $stmt = $db->prepare('DELETE FROM cache WHERE cache_key = ?'); + $stmt->execute([$arg]); + } + + /** + * Expire all items from the cache. + */ + public function flush() + { + $db = DBManager::get(); + + $db->exec('TRUNCATE TABLE cache'); + } + + /** + * Delete all expired items from the cache. + */ + public function purge() + { + $db = DBManager::get(); + + $stmt = $db->prepare('DELETE FROM cache WHERE expires < ?'); + $stmt->execute([time()]); + } + + /** + * Return statistics. + * + * @return array|array[] + *@see Cache::getStats() + * + */ + public function getStats(): array + { + return [ + __CLASS__ => [ + 'name' => _('Anzahl Einträge'), + 'value' => DBManager::get()->fetchColumn("SELECT COUNT(*) FROM `cache`") + ] + ]; + } + + /** + * Return the Vue component name and props that handle configuration. + * + * @return array + *@see Cache::getConfig() + * + */ + public static function getConfig(): array + { + return [ + 'component' => null, + 'props' => [] + ]; + } + + /** + * @inheritDoc + */ + public function getItem($key) + { + $query = "SELECT `content`, `expires` + FROM `cache` + WHERE `cache_key` = :key + AND `expires` > UNIX_TIMESTAMP()"; + $result = DBManager::get()->fetchOne($query, [':key' => $key]); + + $item = new Item($key); + if (!empty($result)) { + $item->setHit(); + if ($result['content']) { + $item->set(unserialize($result['content'])); + } + if ($result['expires']) { + $expiration = new \DateTime(); + $expiration->setTimestamp($result['expires']); + $item->expiresAt($expiration); + } + } + return $item; + } + + /** + * @inheritDoc + */ + public function hasItem($key) + { + $query = "SELECT 1 + FROM `cache` + WHERE `cache_key` = :key + AND `expires` > UNIX_TIMESTAMP()"; + return (bool) DBManager::get()->fetchColumn($query, [':key' => $key]); + } + + /** + * @inheritDoc + */ + public function save(\Psr\Cache\CacheItemInterface $item) + { + $expiration = $this->getExpiration($item); + if ($expiration < 1) { + // The item would expire immediately. + return false; + } + + return DBManager::get()->execute( + 'REPLACE INTO `cache` VALUES (?, ?, ?)', + [$item->getKey(), serialize($item->get()), $expiration] + ); + } +} diff --git a/lib/classes/cache/Exception.php b/lib/classes/cache/Exception.php new file mode 100644 index 0000000000000000000000000000000000000000..64ee364087391da65ebf2f78450d6ba76abf4ddd --- /dev/null +++ b/lib/classes/cache/Exception.php @@ -0,0 +1,27 @@ +<?php +/* + * CacheException.class.php + * This file is part of Stud.IP. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * @author Moritz Strohm <strohm@data-quest.de> + * @copyright 2024 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 6.0 + */ + +namespace Studip\Cache; + +/** + * The CacheException class is an implementation of the CacheException interface + * of PSR-6 that behaves like a StudipException. + */ +class Exception extends \StudipException implements \Psr\Cache\CacheException +{ + //Nothing here, since there is nothing to implement. +} diff --git a/lib/classes/StudipCacheFactory.class.php b/lib/classes/cache/Factory.class.php similarity index 81% rename from lib/classes/StudipCacheFactory.class.php rename to lib/classes/cache/Factory.class.php index 77c5973ac1a7474627846fa1fca7c5f97c1156aa..b5c835951a6e7add5544f565265bf29fdd30bd9f 100644 --- a/lib/classes/StudipCacheFactory.class.php +++ b/lib/classes/cache/Factory.class.php @@ -1,4 +1,15 @@ <?php + +namespace Studip\Cache; + +use Config; +use DBSchemaVersion; +use MessageBox; +use PageLayout; +use ReflectionClass; +use StudipCacheOperation; +use UnexpectedValueException; + /** * This factory retrieves the instance of StudipCache configured for use in * this Stud.IP installation. @@ -12,30 +23,29 @@ * @since 1.6 * @license GPL2 or any later version */ - -class StudipCacheFactory +class Factory { /** * the default cache class * * @var string */ - const DEFAULT_CACHE_CLASS = StudipDbCache::class; + const DEFAULT_CACHE_CLASS = DbCache::class; /** * singleton instance * - * @var StudipCache + * @var Cache|null */ - private static $cache; + private static ?Cache $cache = null; /** * config instance * - * @var Config + * @var Config|null */ - private static $config = null; + private static ?Config $config = null; /** @@ -49,7 +59,7 @@ class StudipCacheFactory */ public static function getConfig() { - return is_null(self::$config) ? Config::getInstance() : self::$config; + return self::$config ?? Config::getInstance(); } @@ -58,7 +68,7 @@ class StudipCacheFactory * determine the class of the implementation of interface * StudipCache */ - public static function setConfig($config) + public static function setConfig(Config $config) { self::$config = $config; self::$cache = NULL; @@ -77,15 +87,15 @@ class StudipCacheFactory * * @param bool $apply_proxied_operations Whether or not to apply any * proxied (disable this in tests!) - * @return StudipCache the cache instance + * @return Cache the cache instance */ - public static function getCache($apply_proxied_operations = true) + public static function getCache(bool $apply_proxied_operations = true): Cache { - if (is_null(self::$cache)) { + if (self::$cache === null) { $proxied = false; if (!$GLOBALS['CACHING_ENABLE']) { - self::$cache = new StudipMemoryCache(); + self::$cache = new MemoryCache(); // Proxy cache operations if CACHING_ENABLE is different from the globally set // caching value. This should only be the case in cli mode. @@ -98,7 +108,7 @@ class StudipCacheFactory $args = self::retrieveConstructorArguments(); self::$cache = self::instantiateCache($class, $args); - } catch (Exception $e) { + } catch (\Exception $e) { error_log(__METHOD__ . ': ' . $e->getMessage()); PageLayout::addBodyElements(MessageBox::error(__METHOD__ . ': ' . $e->getMessage())); $class = self::DEFAULT_CACHE_CLASS; @@ -109,7 +119,7 @@ class StudipCacheFactory // If proxy should be used, inject it. Otherwise apply pending // operations, if any. if ($proxied) { - self::$cache = new StudipCacheProxy(self::$cache); + self::$cache = new Proxy(self::$cache); } elseif ($GLOBALS['CACHING_ENABLE'] && $apply_proxied_operations) { // Even if the above condition will try to eliminate most // failures, the following operation still needs to be wrapped @@ -118,7 +128,7 @@ class StudipCacheFactory // for said operation. try { StudipCacheOperation::apply(self::$cache); - } catch (Exception $e) { + } catch (\Exception $e) { } } } @@ -174,10 +184,11 @@ class StudipCacheFactory * memory cache is instantiated, the cache will be wrapped in a wrapper * class that uses a memory cache to reduce accesses to the cache. * - * @param string $class the name of the class - * @param array $arguments an array of arguments to be used by the constructor + * @param string $class the name of the class + * @param array $arguments an array of arguments to be used by the constructor * - * @return StudipCache an instance of the specified class + * @return Cache an instance of the specified class + * @throws \ReflectionException */ public static function instantiateCache($class, $arguments) { @@ -186,8 +197,8 @@ class StudipCacheFactory ? $reflection_class->newInstanceArgs($arguments['config']) : $reflection_class->newInstance(); - if ($class !== StudipMemoryCache::class) { - return new StudipCacheWrapper($cache); + if ($class !== MemoryCache::class) { + return new Wrapper($cache); } return $cache; diff --git a/lib/classes/StudipFileCache.class.php b/lib/classes/cache/FileCache.class.php similarity index 55% rename from lib/classes/StudipFileCache.class.php rename to lib/classes/cache/FileCache.class.php index 9eae66c1fba9967f099a6e56310791895c08d9eb..e94395ac46166c1993285de8301966c4a2b6794c 100644 --- a/lib/classes/StudipFileCache.class.php +++ b/lib/classes/cache/FileCache.class.php @@ -1,46 +1,27 @@ <?php -# Lifter010: TODO -// +--------------------------------------------------------------------------+ -// This file is part of Stud.IP -// StudipFileCache.class.php -// -// -// -// Copyright (c) 2007 André Noack <noack@data-quest.de> -// +--------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +--------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +--------------------------------------------------------------------------+ + +namespace Studip\Cache; + +use Config; +use Exception; /** - * StudipCache implementation using files - * - * @package studip - * @subpackage cache + * Cache implementation using files * * @author André Noack <noack@data-quest.de> - * @version 2 + * @copyright 2007 André Noack <noack@data-quest.de> + * @license GPL2 or any later version */ -class StudipFileCache implements StudipCache +class FileCache extends Cache { - use StudipCacheKeyTrait; + use KeyTrait; /** * full path to cache directory * * @var string */ - private $dir; + private string $dir; /** * @return string A translateable display name for this cache class. @@ -55,26 +36,28 @@ class StudipFileCache implements StudipCache * $CACHING_FILECACHE_PATH or is set to * $TMP_PATH/studip_cache * - * @param string the path to use - * @return void - * @throws exception if the directory does not exist or could not be + * @param string $path the path to use + * @throws Exception if the directory does not exist or could not be * created */ - public function __construct($path = '') + public function __construct(string $path = '') { $this->dir = $path - ?: (Config::get()->SYSTEMCACHE['type'] == 'StudipFileCache' ? - Config::get()->SYSTEMCACHE['config']['path'] : '') + ?: ( + Config::get()->SYSTEMCACHE['type'] === self::class + ? Config::get()->SYSTEMCACHE['config']['path'] + : '' + ) ?: $GLOBALS['CACHING_FILECACHE_PATH'] ?: ($GLOBALS['TMP_PATH'] . '/' . 'studip_cache'); $this->dir = rtrim($this->dir, '\\/') . '/'; if (!is_dir($this->dir) && !@mkdir($this->dir, 0700)) { - throw new Exception('Could not create directory: ' . $this->dir); + throw new \Exception('Could not create directory: ' . $this->dir); } if (!is_writable($this->dir)) { - throw new Exception('Can not write to directory: ' . $this->dir); + throw new \Exception('Can not write to directory: ' . $this->dir); } } @@ -91,9 +74,11 @@ class StudipFileCache implements StudipCache /** * expire cache item * - * @see StudipCache::expire() * @param string $arg + * * @return void + * @throws Exception + * @see Cache::expire() */ public function expire($arg) { @@ -112,62 +97,21 @@ class StudipFileCache implements StudipCache rmdirr($this->dir); } - /** - * retrieve cache item from filesystem - * tests first if item is expired - * - * @see StudipCache::read() - * @param string $arg a cache key - * @return string|bool - */ - public function read($arg) - { - $key = $this->getCacheKey($arg); - - if ($file = $this->check($key)){ - $f = @fopen($file, 'rb'); - if ($f) { - @flock($f, LOCK_SH); - $result = stream_get_contents($f); - @fclose($f); - } - return unserialize($result); - } - return false; - } - - /** - * store data as cache item in filesystem - * - * @see StudipCache::write() - * @param string $arg a cache key - * @param mixed $content data to store - * @param int $expire expiry time in seconds, default 12h - * @return int|bool the number of bytes that were written to the file, - * or false on failure - */ - public function write($arg, $content, $expire = self::DEFAULT_EXPIRATION) - { - $key = $this->getCacheKey($arg); - - $this->expire($key); - $file = $this->getPathAndFile($key, $expire); - return @file_put_contents($file, serialize($content), LOCK_EX); - } - /** * checks if specified cache item is expired * if expired the cache file is deleted * * @param string $key a cache key to check - * @return string|bool the path to the cache file or false if expired + * + * @return array|bool the path to the cache file or false if expired + * @throws Exception */ private function check($key) { if ($file = $this->getPathAndFile($key)){ - list($id, $expire) = explode('-', basename($file)); + [$id, $expire] = explode('-', basename($file)); if (time() < $expire) { - return $file; + return [$file, $expire]; } else { @unlink($file); } @@ -183,16 +127,18 @@ class StudipFileCache implements StudipCache * the filename is constructed from the hashed cache key * and the timestamp of expiration * - * @param string $key a cache key - * @param int $expire expiry time in seconds + * @param string $key a cache key + * @param int|null $expire expiry time in seconds + * * @return string|bool full path to cache item or false on failure + * @throws Exception */ - private function getPathAndFile($key, $expire = null) + private function getPathAndFile(string $key, ?int $expire = null): bool|string { $id = hash('md5', $key); $path = $this->dir . mb_substr($id, 0, 2); if (!is_dir($path) && !@mkdir($path, 0700)) { - throw new Exception('Could not create directory: ' . $path); + throw new \Exception('Could not create directory: ' . $path); } if (!is_null($expire)){ return $path . '/' . $id . '-' . (time() + $expire); @@ -208,16 +154,17 @@ class StudipFileCache implements StudipCache /** * purges expired entries from the cache directory * - * @param bool echo messages if set to false + * @param bool $be_quiet echo messages if set to false + * * @return int the number of deleted files */ - public function purge($be_quiet = true) + public function purge(bool $be_quiet = true): int { $now = time(); $deleted = 0; foreach (@glob($this->dir . '*', GLOB_ONLYDIR) as $current_dir){ foreach (@glob("{$current_dir}/*") as $file){ - list($id, $expire) = explode('-', basename($file)); + [$id, $expire] = explode('-', basename($file)); if ($expire < $now) { if (@unlink($file)) { ++$deleted; @@ -225,8 +172,8 @@ class StudipFileCache implements StudipCache echo "File: {$file} deleted.\n"; } } - } else if (!$be_quiet){ - echo "File: {$file} expires on " . strftime('%x %X', $expire) . "\n"; + } else if (!$be_quiet) { + echo "File: {$file} expires on " . date('Y-m-d H:i:s', $expire) . "\n"; } } } @@ -243,7 +190,7 @@ class StudipFileCache implements StudipCache return [ __CLASS__ => [ 'name' => _('Anzahl Einträge'), - 'value' => DBManager::get()->fetchColumn("SELECT COUNT(*) FROM `cache`") + 'value' => \DBManager::get()->fetchColumn("SELECT COUNT(*) FROM `cache`") ] ]; } @@ -273,4 +220,58 @@ class StudipFileCache implements StudipCache ]; } + /** + * @inheritDoc + */ + public function getItem($key) + { + $real_key = $this->getCacheKey($key); + + $item = new \Studip\Cache\Item($key); + + $file_data = $this->check($real_key); + if ($file_data) { + $file = $file_data[0]; + $expire = $file_data[1]; + $f = @fopen($file, 'rb'); + if ($f) { + @flock($f, LOCK_SH); + $result = stream_get_contents($f); + @fclose($f); + } + $item->setHit(); + $item->set(unserialize($result)); + $expiration = new \DateTime(); + $expiration->setTimestamp($expire); + $item->expiresAt($expiration); + } + return $item; + } + + /** + * @inheritDoc + */ + public function hasItem($key) + { + $real_key = $this->getCacheKey($key); + $file_data = $this->check($real_key); + return $file_data !== false; + } + + /** + * @inheritDoc + */ + public function save(\Psr\Cache\CacheItemInterface $item) + { + $expiration = $this->getExpiration($item); + if ($expiration < 1) { + //The item would expire immediately. + return false; + } + + $real_key = $this->getCacheKey($item->getKey()); + $this->expire($real_key); + $file = $this->getPathAndFile($real_key, $expiration); + return @file_put_contents($file, serialize($item->get()), LOCK_EX); + } } diff --git a/lib/classes/cache/InvalidCacheArgumentException.php b/lib/classes/cache/InvalidCacheArgumentException.php new file mode 100644 index 0000000000000000000000000000000000000000..e85c6b2c1380e6633aeaa2a599a29e35d0e834bb --- /dev/null +++ b/lib/classes/cache/InvalidCacheArgumentException.php @@ -0,0 +1,28 @@ +<?php +/* + * CacheException.class.php + * This file is part of Stud.IP. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * @author Moritz Strohm <strohm@data-quest.de> + * @copyright 2024 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 6.0 + */ + +namespace Studip\Cache; + + +/** + * The InvalidCacheArgumentException is an implementation of the InvalidArgumentException interface + * of PSR-6 that behaves like a StudipException. + */ +class InvalidCacheArgumentException extends \StudipException implements \Psr\Cache\InvalidArgumentException +{ + //Nothing here, since there is nothing to implement. +} diff --git a/lib/classes/cache/Item.class.php b/lib/classes/cache/Item.class.php new file mode 100644 index 0000000000000000000000000000000000000000..1a09f1ddd471a5005b9d8cd4eb8a9df43145ae09 --- /dev/null +++ b/lib/classes/cache/Item.class.php @@ -0,0 +1,163 @@ +<?php +/** + * Item.class.php + * This file is part of Stud.IP. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * @author Moritz Strohm <strohm@data-quest.de> + * @copyright 2024 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 6.0 + */ + +namespace Studip\Cache; + +use DateInterval; +use DateTime; +use Psr\Cache\CacheItemInterface; + +/** + * \Studip\Cache\CacheItem implements the CacheItemInterface of PSR-6. It holds the value and the + * key of a cache item and also provides additional methods to get the expiration of the item. + */ +class Item implements CacheItemInterface +{ + /** + * @var string The key of the item in the cache. + */ + protected string $key; + + /** + * @var mixed The value of the item. + */ + protected mixed $value; + + /** + * @var DateTime|null The expiration as DateTime object or null if the expiration is not defined. + */ + protected ?DateTime $expiration = null; + + /** + * @var bool An indicator whether the item has been found in the cache (true) or not (false). + */ + protected bool $cache_hit = false; + + /** + * The constructor of \Studip\Cache\CacheItem. + * + * @param string $key The key of the item in the cache. + * @param mixed $value The value of the item. + * @param int|null $expiration The expiration of the item in seconds, if applicable. + * @param bool $cache_hit Whether the item shall be constructed as cache hit (true) or not (false). + * + */ + public function __construct( + string $key, + mixed $value = null, + ?int $expiration = null, + bool $cache_hit = false + ) { + $this->key = $key; + $this->value = $value; + $this->cache_hit = $cache_hit; + $this->expiresAfter($expiration); + } + + /** + * @inheritDoc + */ + public function getKey() + { + return $this->key; + } + + /** + * @inheritDoc + */ + public function get() + { + return $this->value; + } + + /** + * @inheritDoc + */ + public function isHit() + { + return $this->cache_hit; + } + + /** + * @inheritDoc + */ + public function set($value) + { + $this->value = $value; + } + + /** + * @inheritDoc + */ + public function expiresAt($expiration) + { + $this->expiration = $expiration; + return $this; + } + + /** + * @inheritDoc + */ + public function expiresAfter($time) + { + $this->expiration = new DateTime(); + if ($time instanceof DateInterval) { + $this->expiration = $this->expiration->add($time); + } elseif (is_integer($time)) { + $this->expiration->setTimestamp(time() + $time); + } else { + $this->expiration->setTimestamp(time() + Cache::DEFAULT_EXPIRATION); + } + return $this; + } + + // \Studip\Cache\CacheItem specific methods: + + /** + * Sets the item to be a cache hit. + * + * @return void + */ + public function setHit() : void + { + $this->cache_hit = true; + } + + /** + * Returns the expiration, if set. + * + * @return DateTime|null A DateTime object with the expiration date and time + * or null if the expiration is not defined. + */ + public function getExpiration() : ?DateTime + { + return $this->expiration; + } + + /** + * Returns the seconds from the current timestamp until the expiration of the item. + * + * @return int The seconds until the item expires + */ + public function getExpirationInSeconds() : int + { + if ($this->expiration) { + return $this->expiration->getTimestamp() - time(); + } + return 0; + } +} diff --git a/lib/classes/StudipCacheKeyTrait.php b/lib/classes/cache/KeyTrait.php similarity index 77% rename from lib/classes/StudipCacheKeyTrait.php rename to lib/classes/cache/KeyTrait.php index 62eb142f3b02c533eee84c0eeff228a0f48d093f..021aaba3966574f13fad4428e6cf71a68fcd0efc 100644 --- a/lib/classes/StudipCacheKeyTrait.php +++ b/lib/classes/cache/KeyTrait.php @@ -1,4 +1,6 @@ <?php +namespace Studip\Cache; + /** * Trait for unique cache hashes per key for each system based on db configuration which should * be sufficient to eliminate cache mishaps. @@ -9,9 +11,9 @@ * @subpackage cache * @since Stud.IP 5.0 */ -trait StudipCacheKeyTrait +trait KeyTrait { - protected $cache_prefix = null; + protected ?string $cache_prefix = null; /** * Returns a prefix cache key based on db configuration. @@ -19,11 +21,11 @@ trait StudipCacheKeyTrait * @param string $offset * @return string */ - protected function getCacheKey($offset) + protected function getCacheKey(string $offset): string { if ($this->cache_prefix === null) { $this->cache_prefix = md5("{$GLOBALS['DB_STUDIP_HOST']}|{$GLOBALS['DB_STUDIP_DATABASE']}"); } - return "{$this->cache_prefix}/{$offset}"; + return "$this->cache_prefix/$offset"; } } diff --git a/lib/classes/StudipMemcachedCache.php b/lib/classes/cache/MemcachedCache.class.php similarity index 51% rename from lib/classes/StudipMemcachedCache.php rename to lib/classes/cache/MemcachedCache.class.php index 0e44dd57e1c5176468afc0db610ff47cd2099bea..5d9033be9b8c71f947cea88260e26f1fe8a8d09c 100644 --- a/lib/classes/StudipMemcachedCache.php +++ b/lib/classes/cache/MemcachedCache.class.php @@ -1,31 +1,23 @@ <?php -/** - * Copyright (C) 2007 - Marcus Lunzenauer <mlunzena@uos.de> - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - */ +namespace Studip\Cache; +use Memcached; +use Psr\Cache\CacheItemInterface; /** * Cache implementation using memcached. * - * @package studip - * @subpackage cache - * - * @author mlunzena + * @author Marcus Lunzenauer <mlunzena@uos.de> * @copyright (c) Authors + * @license GPL2 or any later version * @since 5.0 */ - -class StudipMemcachedCache implements StudipCache +class MemcachedCache extends Cache { - use StudipCacheKeyTrait; + use KeyTrait; - private $memcache; + private Memcached $memcache; /** * @return string A translateable display name for this cache class. @@ -38,18 +30,18 @@ class StudipMemcachedCache implements StudipCache public function __construct($servers) { if (!extension_loaded('memcached')) { - throw new Exception('Memcache extension missing.'); + throw new \Exception('Memcache extension missing.'); } - $prefix = Config::get()->STUDIP_INSTALLATION_ID; - $this->memcache = new Memcached('studip' . $prefix ? '-' . $prefix : ''); + $prefix = \Config::get()->STUDIP_INSTALLATION_ID; + $this->memcache = new Memcached('studip' . ($prefix ? '-' . $prefix : '')); if (count($this->memcache->getServerList()) === 0) { foreach ($servers as $server) { $status = $this->memcache->addServer($server['hostname'], (int) $server['port']); if (!$status) { - throw new Exception("Could not add server: {$server['hostname']} @ port {$server['port']}"); + throw new \Exception("Could not add server: {$server['hostname']} @ port {$server['port']}"); } } } @@ -80,40 +72,6 @@ class StudipMemcachedCache implements StudipCache $this->memcache->flush(); } - /** - * Retrieve item from the server. - * - * Example: - * - * # reads foo - * $foo = $cache->reads('foo'); - * - * @param string $arg a single key - * @returns mixed the previously stored data if an item with such a key - * exists on the server or FALSE on failure. - */ - public function read($arg) - { - $key = $this->getCacheKey($arg); - return $this->memcache->get($key); - } - - /** - * Store data at the server. - * - * @param string $arg the item's key. - * @param string $content the item's content. - * @param int $expire the item's expiry time in seconds. Defaults to 12h. - * - * @returns mixed returns TRUE on success or FALSE on failure. - * - */ - public function write($arg, $content, $expire = self::DEFAULT_EXPIRATION) - { - $key = $this->getCacheKey($arg); - return $this->memcache->set($key, $content, $expire); - } - /** * Return statistics. * @@ -123,20 +81,19 @@ class StudipMemcachedCache implements StudipCache */ public function getStats(): array { - $stats = $this->memcache->getStats(); - return $stats; + return $this->memcache->getStats(); } /** * Return the Vue component name and props that handle configuration. * - * @see StudipCache::getConfig() + * @see Cache::getConfig() * * @return array */ public static function getConfig(): array { - $currentCache = Config::get()->SYSTEMCACHE; + $currentCache = \Config::get()->SYSTEMCACHE; // Set default config for this cache $currentConfig = [ @@ -154,4 +111,41 @@ class StudipMemcachedCache implements StudipCache ]; } + /** + * @inheritDoc + */ + public function getItem($key) + { + $item = new Item($key); + $value = $this->memcache->get($this->getCacheKey($key)); + if ($this->memcache->getResultCode() !== Memcached::RES_NOTFOUND) { + // Set the value, even if it is the boolean value false: + $item->setHit(); + $item->set($value); + } + return $item; + } + + /** + * @inheritDoc + */ + public function hasItem($key) + { + return $this->memcache->checkKey($this->getCacheKey($key)); + } + + /** + * @inheritDoc + */ + public function save(CacheItemInterface $item) + { + $expiration = $this->getExpiration($item); + if ($expiration < 1) { + // The item would expire immediately. + return false; + } + + $real_key = $this->getCacheKey($item->getKey()); + return $this->memcache->set($real_key, $item->get(), $expiration); + } } diff --git a/lib/classes/cache/MemoryCache.class.php b/lib/classes/cache/MemoryCache.class.php new file mode 100644 index 0000000000000000000000000000000000000000..93f6bbd66aa9429e62a8fc48d755b95fab2f24eb --- /dev/null +++ b/lib/classes/cache/MemoryCache.class.php @@ -0,0 +1,98 @@ +<?php + +namespace Studip\Cache; + +use DateTime; +use Psr\Cache\CacheItemInterface; + +/** + * The php memory implementation of the StudipCache interface. + * + * @author Jan-Hendrik Willms <tleilax+studip@gmail.com> + * @license GPL2 or any later version + * @since Stud.IP 5.0 + */ +class MemoryCache extends Cache +{ + protected array $memory_cache = []; + + /** + * Expires just a single key. + * + * @param string the key + */ + public function expire($key) + { + unset($this->memory_cache[$key]); + } + + /** + * Expire all items from the cache. + */ + public function flush() + { + $this->memory_cache = []; + } + + public static function getDisplayName(): string + { + return 'Memory cache'; + } + + public function getStats(): array + { + return []; + } + + public static function getConfig(): array + { + return []; + } + + /** + * @inheritDoc + */ + public function getItem($key) + { + $item = new Item($key); + if (!isset($this->memory_cache[$key])) { + return $item; + } + if ($this->memory_cache[$key]['expires'] < time()) { + $this->expire($key); + return $item; + } + $item->setHit(); + $item->set($this->memory_cache[$key]['data']); + if (!empty($this->memory_cache[$key]['expires'])) { + $expiration = new DateTime(); + $expiration->setTimestamp($this->memory_cache[$key]['expires']); + $item->expiresAt($expiration); + } + return $item; + } + + /** + * @inheritDoc + */ + public function hasItem($key) + { + return isset($this->memory_cache[$key]) + && $this->memory_cache[$key]['expires'] < time(); + } + + /** + * @inheritDoc + */ + public function save(CacheItemInterface $item) + { + $expiration = $this->getExpiration($item); + + $this->memory_cache[$item->getKey()] = [ + 'expires' => $expiration + time(), + 'data' => $item->get(), + ]; + + return true; + } +} diff --git a/lib/classes/StudipCacheProxy.php b/lib/classes/cache/Proxy.class.php similarity index 57% rename from lib/classes/StudipCacheProxy.php rename to lib/classes/cache/Proxy.class.php index 686f8129d687e130f474167b32ff4f14a2096532..6791fb72c93a245ed7c6338848163c3a358c4af4 100644 --- a/lib/classes/StudipCacheProxy.php +++ b/lib/classes/cache/Proxy.class.php @@ -1,4 +1,9 @@ <?php + +namespace Studip\Cache; + +use StudipCacheOperation; + /** * Proxies a StudipCache and stores the expire operation in the database. * These operations are lateron applied to the cache they should have @@ -8,18 +13,18 @@ * @license GPL2 or any later version * @since Stud.IP 3.3 */ -class StudipCacheProxy implements StudipCache +class Proxy extends Cache { - protected $actual_cache; - protected $proxy_these; + protected Cache $actual_cache; + protected array $proxy_these; /** - * @param StudipCache $cache The actual cache object - * @param mixed $proxy_these List of operations to proxy (should be - * an array but a space seperated string - * is also valid) + * @param Cache $cache The actual cache object + * @param mixed $proxy_these List of operations to proxy (should be an + * array but a space seperated string is also + * valid) */ - public function __construct(StudipCache $cache, $proxy_these = ['expire']) + public function __construct(Cache $cache, $proxy_these = ['expire']) { if (!is_array($proxy_these)) { $proxy_these = words($proxy_these); @@ -43,7 +48,7 @@ class StudipCacheProxy implements StudipCache $operation = new StudipCacheOperation([$key, 'expire']); $operation->parameters = serialize([]); $operation->store(); - } catch (Exception $e) { + } catch (\Exception $e) { } } @@ -60,58 +65,58 @@ class StudipCacheProxy implements StudipCache $operation = new StudipCacheOperation(['', 'flush']); $operation->parameters = serialize([]); $operation->store(); - } catch (Exception $e) { + } catch (\Exception $e) { } } return $this->actual_cache->flush(); } - /** - * Reads just a single key from the cache. - * - * @param string $key The item's key - * @return mixed The corresponding value - */ - public function read($key) + public static function getDisplayName(): string { - return $this->actual_cache->read($key); + return static::class; } - /** - * Store data at the server. - * - * @param string $key The item's key - * @param string $content The item's conten - * @param int $expires The item's expiry time in seconds, defaults to 12h - * @return bool Returns TRUE on success or FALSE on failure - */ - public function write($key, $content, $expires = self::DEFAULT_EXPIRATION) + public function getStats(): array { - if (in_array('write', $this->proxy_these)) { - try { - $operation = new StudipCacheOperation([$key, 'write']); - $operation->parameters = serialize([$content, $expires]); - $operation->store(); - } catch (Exception $e) { - } - } + return $this->actual_cache->getStats(); + } - return $this->actual_cache->write($key, $content, $expires); + public static function getConfig(): array + { + return []; } - public static function getDisplayName(): string + /** + * @inheritDoc + */ + public function getItem($key) { - return static::class; + return $this->actual_cache->getItem($key); } - public function getStats(): array + /** + * @inheritDoc + */ + public function hasItem($key) { - return $this->actual_cache->getStats(); + return $this->actual_cache->hasItem($key); } - public static function getConfig(): array + /** + * @inheritDoc + */ + public function save(\Psr\Cache\CacheItemInterface $item) { - return []; + if (in_array('save', $this->proxy_these)) { + try { + $operation = new StudipCacheOperation([$item->getKey(), 'save']); + $operation->parameters = serialize([$item]); + $operation->store(); + } catch (\Exception $e) { + } + } + + return $this->actual_cache->save($item); } } diff --git a/lib/classes/StudipRedisCache.class.php b/lib/classes/cache/RedisCache.class.php similarity index 73% rename from lib/classes/StudipRedisCache.class.php rename to lib/classes/cache/RedisCache.class.php index 7b9570bdd0c76605dbb000f59f2298648440be89..9ea871146222b71444b0d1c48248323c46df83d3 100644 --- a/lib/classes/StudipRedisCache.class.php +++ b/lib/classes/cache/RedisCache.class.php @@ -1,4 +1,15 @@ <?php + +namespace Studip\Cache; + +use BadMethodCallException; +use Config; +use DateTime; +use Exception; +use Psr\Cache\CacheItemInterface; +use Redis; +use RedisException; + /** * Cache implementation using redis. * @@ -8,9 +19,9 @@ * @subpackage cache * @since Stud.IP 5.0 */ -class StudipRedisCache implements StudipCache +class RedisCache extends Cache { - use StudipCacheKeyTrait; + use KeyTrait; private $redis; @@ -28,6 +39,8 @@ class StudipRedisCache implements StudipCache * @param string $hostname Hostname of redis server * @param int $port Port of redis server * @param string $auth Optional auth token/password + * + * @throws RedisException */ public function __construct($hostname, $port, string $auth = '') { @@ -73,41 +86,6 @@ class StudipRedisCache implements StudipCache $this->redis->unlink($key); } - /** - * Retrieve item from the server. - * - * Example: - * - * # reads foo - * $foo = $cache->reads('foo'); - * - * @param string $arg a single key - * @return mixed the previously stored data if an item with such a key - * exists on the server or FALSE on failure. - */ - public function read($arg) - { - $key = $this->getCacheKey($arg); - - $result = $this->redis->get($key); - - return ($result === null) ? null : unserialize($result); - } - - /** - * Store data at the server. - * - * @param string the item's key. - * @param string the item's content. - * @param int the item's expiry time in seconds. Defaults to 12h. - * @return mixed returns TRUE on success or FALSE on failure. - */ - public function write($name, $content, $expire = self::DEFAULT_EXPIRATION) - { - $key = $this->getCacheKey($name); - return $this->redis->setEx($key, $expire, serialize($content)); - } - /** * Expire all items from the cache. */ @@ -174,4 +152,47 @@ class StudipRedisCache implements StudipCache 'props' => $currentConfig ]; } + + /** + * @inheritDoc + */ + public function getItem($key) + { + $item = new Item($key); + $real_key = $this->getCacheKey($key); + $result = $this->redis->get($real_key); + if ($result === null) { + return $item; + } + $item->setHit(); + $item->set(unserialize($result)); + $expiration = new DateTime(); + $expiration->setTimestamp($this->redis->expiretime($real_key)); + $item->expiresAt($expiration); + return $item; + } + + /** + * @inheritDoc + */ + public function hasItem($key) + { + $real_key = $this->getCacheKey($key); + return $this->redis->get($real_key) !== null; + } + + /** + * @inheritDoc + */ + public function save(CacheItemInterface $item) + { + $expiration = $this->getExpiration($item); + if ($expiration < 1) { + // The item would expire immediately. + return false; + } + + $real_key = $this->getCacheKey($item->getKey()); + return $this->redis->setEx($real_key, $expiration, serialize($item->get())); + } } diff --git a/lib/classes/StudipCacheWrapper.php b/lib/classes/cache/Wrapper.class.php similarity index 56% rename from lib/classes/StudipCacheWrapper.php rename to lib/classes/cache/Wrapper.class.php index 6c75c01a1203bff23c2c5715c1eb8a7ff9f477d7..744893270b1ccb2e05987b87992921686282356c 100644 --- a/lib/classes/StudipCacheWrapper.php +++ b/lib/classes/cache/Wrapper.class.php @@ -1,5 +1,9 @@ <?php +namespace Studip\Cache; + +use Psr\Cache\CacheItemInterface; + /** * The cache wrapper wraps a memory cache around another cache. This should * reduce the accesses to the actual cache. @@ -8,17 +12,15 @@ * @license GPL2 or any later version * @since Stud.IP 5.4 */ -class StudipCacheWrapper implements StudipCache +class Wrapper extends Cache { - const DEFAULT_MEMORY_EXPIRATION = 60; - - protected $actual_cache; - protected $memory_cache; + protected Cache $actual_cache; + protected MemoryCache $memory_cache; - public function __construct(StudipCache $actual_cache) + public function __construct(Cache $actual_cache) { $this->actual_cache = $actual_cache; - $this->memory_cache = new StudipMemoryCache(); + $this->memory_cache = new MemoryCache(); } /** @@ -39,47 +41,55 @@ class StudipCacheWrapper implements StudipCache $this->actual_cache->flush(); } + public static function getDisplayName(): string + { + return static::class; + } + + public function getStats(): array + { + return $this->actual_cache->getStats(); + } + + public static function getConfig(): array + { + return []; + } + /** - * @inheritdoc + * @inheritDoc */ - public function read($arg) + public function getItem($key) { - $cached = $this->memory_cache->read($arg); - if ($cached !== false) { + $cached = $this->memory_cache->getItem($key); + if ($cached->isHit()) { return $cached; } - $cached = $this->actual_cache->read($arg); - if ($cached !== false) { - $this->memory_cache->write($arg, $cached, self::DEFAULT_MEMORY_EXPIRATION); + $cached = $this->actual_cache->getItem($key); + if ($cached->isHit()) { + $this->memory_cache->save($cached); } return $cached; } /** - * @inheritdoc + * @inheritDoc */ - public function write($name, $content, $expires = self::DEFAULT_EXPIRATION) + public function hasItem($key) { - if ($this->actual_cache->write($name, $content, $expires)) { - return $this->memory_cache->write($name, $content, $expires); - } else { - return false; - } - } - - public static function getDisplayName(): string - { - return static::class; - } - - public function getStats(): array - { - return $this->actual_cache->getStats(); + return $this->actual_cache->hasItem($key); } - public static function getConfig(): array + /** + * @inheritDoc + */ + public function save(CacheItemInterface $item) { - return []; + if ($this->actual_cache->save($item)) { + return $this->memory_cache->save($item); + } else { + return false; + } } } diff --git a/lib/classes/cas/CAS_PGTStorage_Cache.php b/lib/classes/cas/CAS_PGTStorage_Cache.php index 284b59134f3f6e11430c5b1bd1a013d0e218bb7a..61ce9fa6ad0f7066c868eb936bf115509698acfb 100644 --- a/lib/classes/cas/CAS_PGTStorage_Cache.php +++ b/lib/classes/cas/CAS_PGTStorage_Cache.php @@ -43,7 +43,7 @@ class CAS_PGTStorage_Cache extends CAS_PGTStorage_AbstractStorage */ public function write($pgt, $pgt_iou) { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $cache_key = 'pgtiou/' . $pgt_iou; return $cache->write($cache_key, $pgt); } @@ -58,7 +58,7 @@ class CAS_PGTStorage_Cache extends CAS_PGTStorage_AbstractStorage */ public function read($pgt_iou) { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $cache_key = 'pgtiou/' . $pgt_iou; $pgt = $cache->read($cache_key); $cache->expire($cache_key); diff --git a/lib/cronjobs/purge_cache.class.php b/lib/cronjobs/purge_cache.class.php index 4e5682f2b26d19f02d383f119dec4a2abee22262..d2e3697db8d464cb362fc5c786e866c90e45f1e8 100644 --- a/lib/cronjobs/purge_cache.class.php +++ b/lib/cronjobs/purge_cache.class.php @@ -70,7 +70,7 @@ class PurgeCacheJob extends CronJob */ public function setUp() { - require_once 'lib/classes/StudipFileCache.class.php'; + require_once 'lib/classes/cache/FileCache.class.php'; } /** @@ -86,7 +86,7 @@ class PurgeCacheJob extends CronJob */ public function execute($last_result, $parameters = []) { - $cache = new StudipFileCache(); + $cache = new \Studip\Cache\FileCache(); $cache->purge(empty($parameters['verbose'])); } } diff --git a/lib/functions.php b/lib/functions.php index 0e806aa7f47dff028d3ad9323351d9bec87bf58d..7d9f4f2defba00312b50ba76e729a0bf19d1a881 100644 --- a/lib/functions.php +++ b/lib/functions.php @@ -636,7 +636,7 @@ function get_users_online($active_time = 5, $name_format = 'full_rev') */ function get_users_online_count($active_time = 10) { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $online_count = $cache->read("online_count/{$active_time}"); if ($online_count === false) { $query = "SELECT COUNT(*) FROM user_online diff --git a/lib/models/CourseDate.class.php b/lib/models/CourseDate.class.php index 59fbb9c3a2584db0e0a3164c33f92504a9dfb7a9..eadeb0a020e3b0006bc74fc668aefd2236c0198c 100644 --- a/lib/models/CourseDate.class.php +++ b/lib/models/CourseDate.class.php @@ -364,7 +364,7 @@ class CourseDate extends SimpleORMap implements PrivacyObject, Event // load room-booking, if any $this->room_booking; - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $cache->expire('course/undecorated_data/'. $this->range_id); return parent::store(); } @@ -376,7 +376,7 @@ class CourseDate extends SimpleORMap implements PrivacyObject, Event */ public function delete() { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $cache->expire('course/undecorated_data/'. $this->range_id); return parent::delete(); } @@ -433,7 +433,7 @@ class CourseDate extends SimpleORMap implements PrivacyObject, Event $folders = array_merge($folders, $topic->folders->getArrayCopy()); } foreach ($folders as $folder) { - list($files, $typed_folders) = array_values(FileManager::getFolderFilesRecursive($folder->getTypedFolder(), $user_id)); + [$files, $typed_folders] = array_values(FileManager::getFolderFilesRecursive($folder->getTypedFolder(), $user_id)); foreach ($files as $file) { $all_files[$file->id] = $file; } diff --git a/lib/models/OERMaterial.php b/lib/models/OERMaterial.php index 6ec7de84485cf07bc2a44f5c8e8c4be14f2deef9..8411d149d84a2bee9884f6729704fe8e085ba69a 100644 --- a/lib/models/OERMaterial.php +++ b/lib/models/OERMaterial.php @@ -164,7 +164,7 @@ class OERMaterial extends SimpleORMap public static function fetchRemoteSearch($text, $tag = false) { $cache_name = "oer_remote_searched_for_".md5($text)."_".($tag ? 1 : 0); - $already_searched = (bool) StudipCacheFactory::getCache()->read($cache_name); + $already_searched = (bool) \Studip\Cache\Factory::getCache()->read($cache_name); if (!$already_searched) { $hosts = OERHost::findBySQL("index_server = '1' AND allowed_as_index_server = '1' ORDER BY RAND()"); foreach ($hosts as $host) { @@ -172,7 +172,7 @@ class OERMaterial extends SimpleORMap $host->fetchRemoteSearch($text, $tag); } } - StudipCacheFactory::getCache()->write($cache_name, "1", 60); + \Studip\Cache\Factory::getCache()->write($cache_name, "1", 60); } } diff --git a/lib/models/PersonalNotifications.class.php b/lib/models/PersonalNotifications.class.php index 721038cd63d0d31e377bcfbc46cf3e196548eb51..12d9c1b14c6f2bdac20a2de51d7f4d507aba6632 100644 --- a/lib/models/PersonalNotifications.class.php +++ b/lib/models/PersonalNotifications.class.php @@ -262,7 +262,7 @@ class PersonalNotifications extends SimpleORMap */ protected static function getCache($user_id) { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $hash = self::getCacheHash($user_id); $cached = $cache->read($hash); @@ -281,7 +281,7 @@ class PersonalNotifications extends SimpleORMap */ protected static function setCache($user_id, $items) { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $hash = self::getCacheHash($user_id); $cache->write($hash, serialize($items), self::CACHE_DURATION); } @@ -293,7 +293,7 @@ class PersonalNotifications extends SimpleORMap */ protected static function expireCache($user_id) { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $hash = self::getCacheHash($user_id); $cache->expire($hash); } diff --git a/lib/models/Semester.class.php b/lib/models/Semester.class.php index 4eefa3b886cd9424bd3ac8c7a4258cc9060e8dde..03243a57d04049020951bd5d376a1b4153179a70 100644 --- a/lib/models/Semester.class.php +++ b/lib/models/Semester.class.php @@ -181,7 +181,7 @@ class Semester extends SimpleORMap if (!is_array(self::$semester_cache) || $force_reload) { self::$semester_cache = []; if (!$force_reload) { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $semester_data_array = unserialize($cache->read('DB_SEMESTER_DATA')); if ($semester_data_array) { foreach ($semester_data_array as $semester_data) { @@ -202,7 +202,7 @@ class Semester extends SimpleORMap } $semester_data[] = $semester->toRawArray(); } - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $cache->write('DB_SEMESTER_DATA', serialize($semester_data)); } } @@ -471,7 +471,7 @@ class Semester extends SimpleORMap */ public function refreshCache() { - StudipCacheFactory::getCache()->expire('DB_SEMESTER_DATA'); + \Studip\Cache\Factory::getCache()->expire('DB_SEMESTER_DATA'); } /* diff --git a/lib/models/StudipCacheOperation.php b/lib/models/StudipCacheOperation.php index e9c8738371e9d9cb2f583fd6f4085ddbb18d0140..d2287c32a5f662e5c98af4cf764d334f51e549ec 100644 --- a/lib/models/StudipCacheOperation.php +++ b/lib/models/StudipCacheOperation.php @@ -42,7 +42,7 @@ class StudipCacheOperation extends SimpleORMap */ public static function apply(StudipCache $cache) { - self::findEachBySQL(function ($item) use ($cache) { + self::findEachBySQL(function (StudipCacheOperation $item) use ($cache): void { $parameters = unserialize($item->parameters); array_unshift($parameters, $item->cache_key); call_user_func_array([$cache, $item->operation], $parameters); diff --git a/lib/phplib/CT_Cache.class.php b/lib/phplib/CT_Cache.class.php index 311b27cba5526c1ea3203b1ae973f29806f0d6f0..d4eccfa0a2702b7055e5c7197010a1c8b9e95d1c 100644 --- a/lib/phplib/CT_Cache.class.php +++ b/lib/phplib/CT_Cache.class.php @@ -14,7 +14,7 @@ class CT_Cache public function ac_start() { - $this->cache = StudipCacheFactory::getCache(); + $this->cache = \Studip\Cache\Factory::getCache(); } public function ac_get_lock() diff --git a/lib/plugins/db/RolePersistence.class.php b/lib/plugins/db/RolePersistence.class.php index b11fa4ad8fd0d066700f4945e77d5d1a72c0abe4..df63a773debc8e975e553354270ecef6c2b8e304 100644 --- a/lib/plugins/db/RolePersistence.class.php +++ b/lib/plugins/db/RolePersistence.class.php @@ -29,7 +29,7 @@ class RolePersistence { if (self::$all_roles === null) { // read cache - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); // cache miss, retrieve from database self::$all_roles = $cache->read(self::ROLES_CACHE_KEY); @@ -675,7 +675,7 @@ class RolePersistence public static function expireRolesCache() { self::$all_roles = null; - StudipCacheFactory::getCache()->expire(self::ROLES_CACHE_KEY); + \Studip\Cache\Factory::getCache()->expire(self::ROLES_CACHE_KEY); } /** diff --git a/lib/plugins/engine/PluginRepository.class.php b/lib/plugins/engine/PluginRepository.class.php index 14415f42bd097602608bcb936048b7f8f90b6886..235e9bd97b36087e2e666ef3eef6ae653488a437 100644 --- a/lib/plugins/engine/PluginRepository.class.php +++ b/lib/plugins/engine/PluginRepository.class.php @@ -55,7 +55,7 @@ class PluginRepository */ public function readMetadata($url) { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $cache_key = 'plugin_metadata/'.$url; $metadata = $cache->read($cache_key); diff --git a/lib/raumzeit/SingleDate.class.php b/lib/raumzeit/SingleDate.class.php index 82a89db6852cda2e10533efa4d99e01716aa412c..1a58695bd28b55c7167e84096c75bae740b08e25 100644 --- a/lib/raumzeit/SingleDate.class.php +++ b/lib/raumzeit/SingleDate.class.php @@ -292,7 +292,7 @@ class SingleDate function delete() { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $cache->expire('course/undecorated_data/' . $this->range_id); $this->chdate = time(); @@ -304,7 +304,7 @@ class SingleDate function store() { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $cache->expire('course/undecorated_data/' . $this->range_id); $this->chdate = time(); diff --git a/tests/functional/_bootstrap.php b/tests/functional/_bootstrap.php index 49f332495c8e160e62cd58f0f031d07fdaecd3b1..8a9125b45a43e57c2d0c47054354a470a347a174 100644 --- a/tests/functional/_bootstrap.php +++ b/tests/functional/_bootstrap.php @@ -30,6 +30,8 @@ StudipAutoloader::register(); StudipAutoloader::addAutoloadPath($STUDIP_BASE_PATH . '/lib/calendar', 'Studip\\Calendar'); StudipAutoloader::addAutoloadPath($STUDIP_BASE_PATH . '/lib/classes'); StudipAutoloader::addAutoloadPath($STUDIP_BASE_PATH . '/lib/classes', 'Studip'); +StudipAutoloader::addAutoloadPath($STUDIP_BASE_PATH . '/lib/classes/cache'); +StudipAutoloader::addAutoloadPath($STUDIP_BASE_PATH . '/lib/classes/cache', 'Studip'); StudipAutoloader::addAutoloadPath($STUDIP_BASE_PATH . '/lib/exceptions'); StudipAutoloader::addAutoloadPath($STUDIP_BASE_PATH . '/lib/exceptions/resources'); StudipAutoloader::addAutoloadPath($STUDIP_BASE_PATH . '/lib/filesystem'); @@ -74,7 +76,7 @@ if (!class_exists('StudipTestHelper')) { { static function set_up_tables($tables) { - $cache = StudipCacheFactory::getCache(false); + $cache = \Studip\Cache\Factory::getCache(false); // second step, expire table scheme SimpleORMap::expireTableScheme(); diff --git a/tests/jsonapi/_bootstrap.php b/tests/jsonapi/_bootstrap.php index 82ae54e514aa29dc2c319848c0d9865d855a7b4d..baf37409845a3eb27f27e6349fafa4e31c3042c5 100644 --- a/tests/jsonapi/_bootstrap.php +++ b/tests/jsonapi/_bootstrap.php @@ -53,6 +53,8 @@ StudipAutoloader::addAutoloadPath($GLOBALS['STUDIP_BASE_PATH'].'/lib/plugins/eng StudipAutoloader::addAutoloadPath($GLOBALS['STUDIP_BASE_PATH'].'/lib/calendar'); StudipAutoloader::addAutoloadPath($GLOBALS['STUDIP_BASE_PATH'].'/lib/calendar', 'Studip\\Calendar'); +StudipAutoloader::addAutoloadPath($GLOBALS['STUDIP_BASE_PATH'].'lib/classes/cache'); +StudipAutoloader::addAutoloadPath($GLOBALS['STUDIP_BASE_PATH'].'lib/classes/cache', 'Studip'); StudipAutoloader::addAutoloadPath($GLOBALS['STUDIP_BASE_PATH'].'/lib/calendar/lib'); StudipAutoloader::addAutoloadPath($GLOBALS['STUDIP_BASE_PATH'].'/lib/exceptions'); diff --git a/tests/unit/_bootstrap.php b/tests/unit/_bootstrap.php index 3aa1144f66872c84576d8bea631eee01ee697c72..af956074d8511d123290bd9e95e47f802231b67b 100644 --- a/tests/unit/_bootstrap.php +++ b/tests/unit/_bootstrap.php @@ -54,6 +54,8 @@ StudipAutoloader::addAutoloadPath('lib/models'); StudipAutoloader::addAutoloadPath('lib/classes'); StudipAutoloader::addAutoloadPath('lib/classes', 'Studip'); StudipAutoloader::addAutoloadPath('lib/exTpl', 'exTpl'); +StudipAutoloader::addAutoloadPath('lib/classes/cache'); +StudipAutoloader::addAutoloadPath('lib/classes/cache', 'Studip'); StudipAutoloader::addAutoloadPath('lib/exceptions'); StudipAutoloader::addAutoloadPath('lib/classes/sidebar'); StudipAutoloader::addAutoloadPath('lib/classes/helpbar'); @@ -108,7 +110,7 @@ if (!class_exists('StudipTestHelper')) { static function set_up_tables($tables) { // first step, set fake cache - $cache = StudipCacheFactory::getCache(false); + $cache = \Studip\Cache\Factory::getCache(false); // second step, expire table scheme SimpleORMap::expireTableScheme(); diff --git a/tests/unit/lib/classes/MigrationTest.php b/tests/unit/lib/classes/MigrationTest.php index 4a45e71598c2396b1275c8fc3e499b2e1f20dc23..12df1201c7ee64f52ef3f7406acf8434ddc31003 100644 --- a/tests/unit/lib/classes/MigrationTest.php +++ b/tests/unit/lib/classes/MigrationTest.php @@ -16,11 +16,6 @@ class MigrationTest extends \Codeception\Test\Unit $this->before = $GLOBALS['CACHING_ENABLE'] ?? null; $GLOBALS['CACHING_ENABLE'] = false; - require_once 'lib/classes/SimpleORMap.class.php'; - require_once 'lib/classes/StudipCache.class.php'; - require_once 'lib/classes/StudipMemoryCache.class.php'; - require_once 'lib/classes/StudipCacheFactory.class.php'; - require_once 'lib/migrations/Migration.php'; require_once 'lib/migrations/Migrator.php'; require_once 'lib/migrations/SchemaVersion.php'; diff --git a/tests/unit/lib/classes/StudipCachedArrayTest.php b/tests/unit/lib/classes/StudipCachedArrayTest.php index c98c1bd6b5c151bc9f34656047edec3e4b989e74..5fd39f2d7e93ec71672cdad795a9f4add9ae6e0b 100644 --- a/tests/unit/lib/classes/StudipCachedArrayTest.php +++ b/tests/unit/lib/classes/StudipCachedArrayTest.php @@ -1,4 +1,7 @@ <?php + +use Studip\Cache\MemoryCache; + /** * StudipCachedArrayTest.php - unit tests for the StudipCachedArray class * @@ -6,7 +9,7 @@ * @license GPL2 or any later version * * @covers StudipCachedArray - * @uses StudipMemoryCache + * @uses MemoryCache */ class StudipCachedArrayTest extends \Codeception\Test\Unit