mirror of
https://github.com/moodle/moodle.git
synced 2025-04-21 08:22:07 +02:00
Merge branch 'wip-MDL-38565-m25' of git://github.com/samhemelryk/moodle
This commit is contained in:
commit
00a850b33e
4
cache/README.md
vendored
4
cache/README.md
vendored
@ -18,6 +18,7 @@ A definition:
|
||||
'requiremultipleidentifiers' => false, // Optional
|
||||
'requirelockingread' => false, // Optional
|
||||
'requirelockingwrite' => false, // Optional
|
||||
'requiresearchable' => false, // Optional
|
||||
'maxsize' => null, // Optional
|
||||
'overrideclass' => null, // Optional
|
||||
'overrideclassfile' => null, // Optional
|
||||
@ -135,6 +136,7 @@ The following optional settings can also be defined:
|
||||
* requiremultipleidentifiers - If set to true then only stores that support multiple identifiers will be used.
|
||||
* requirelockingread - If set to true a lock will be acquired for reading. Don't use this setting unless you have a REALLY good reason to.
|
||||
* requirelockingwrite - If set to true a lock will be acquired before writing to the cache. Avoid this unless necessary.
|
||||
* requiresearchable - If set to true only stores that support key searching will be used for this definition. Its not recommended to use this unless absolutely unavoidable.
|
||||
* maxsize - This gives a cache an indication about the maximum items it should store. Cache stores don't have to use this, it is up to them to decide if its required.
|
||||
* overrideclass - If provided this class will be used for the loader. It must extend one of the core loader classes (based upon mode).
|
||||
* overrideclassfile - Included if required when using the overrideclass param.
|
||||
@ -236,4 +238,4 @@ The following snippet illustates how to configure the three core cache stores th
|
||||
|
||||
define('TEST_CACHESTORE_MEMCACHE_TESTSERVERS', '127.0.0.1:11211');
|
||||
define('TEST_CACHESTORE_MEMCACHED_TESTSERVERS', '127.0.0.1:11211');
|
||||
define('TEST_CACHESTORE_MONGODB_TESTSERVER', 'mongodb://localhost:27017');
|
||||
define('TEST_CACHESTORE_MONGODB_TESTSERVER', 'mongodb://localhost:27017');
|
||||
|
27
cache/classes/definition.php
vendored
27
cache/classes/definition.php
vendored
@ -183,6 +183,13 @@ class cache_definition {
|
||||
*/
|
||||
protected $requirelockingwrite = false;
|
||||
|
||||
/**
|
||||
* Gets set to true if this definition requires searchable stores.
|
||||
* @since 2.4.4
|
||||
* @var bool
|
||||
*/
|
||||
protected $requiresearchable = false;
|
||||
|
||||
/**
|
||||
* Sets the maximum number of items that can exist in the cache.
|
||||
* Please note this isn't a hard limit, and doesn't need to be enforced by the caches. They can choose to do so optionally.
|
||||
@ -307,6 +314,7 @@ class cache_definition {
|
||||
$requiremultipleidentifiers = false;
|
||||
$requirelockingread = false;
|
||||
$requirelockingwrite = false;
|
||||
$requiresearchable = ($mode === cache_store::MODE_SESSION) ? true : false;
|
||||
$maxsize = null;
|
||||
$overrideclass = null;
|
||||
$overrideclassfile = null;
|
||||
@ -342,6 +350,10 @@ class cache_definition {
|
||||
}
|
||||
$requirelocking = $requirelockingwrite || $requirelockingread;
|
||||
|
||||
if (array_key_exists('requiresearchable', $definition)) {
|
||||
$requiresearchable = (bool)$definition['requiresearchable'];
|
||||
}
|
||||
|
||||
if (array_key_exists('maxsize', $definition)) {
|
||||
$maxsize = (int)$definition['maxsize'];
|
||||
}
|
||||
@ -433,6 +445,7 @@ class cache_definition {
|
||||
$cachedefinition->requirelocking = $requirelocking;
|
||||
$cachedefinition->requirelockingread = $requirelockingread;
|
||||
$cachedefinition->requirelockingwrite = $requirelockingwrite;
|
||||
$cachedefinition->requiresearchable = $requiresearchable;
|
||||
$cachedefinition->maxsize = $maxsize;
|
||||
$cachedefinition->overrideclass = $overrideclass;
|
||||
$cachedefinition->overrideclassfile = $overrideclassfile;
|
||||
@ -633,6 +646,15 @@ class cache_definition {
|
||||
return $this->requirelockingwrite;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this definition requires a searchable cache.
|
||||
* @since 2.4.4
|
||||
* @return bool
|
||||
*/
|
||||
public function require_searchable() {
|
||||
return $this->requiresearchable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this definition has an associated data source.
|
||||
* @return bool
|
||||
@ -686,6 +708,9 @@ class cache_definition {
|
||||
if ($this->require_multiple_identifiers()) {
|
||||
$requires += cache_store::SUPPORTS_MULTIPLE_IDENTIFIERS;
|
||||
}
|
||||
if ($this->require_searchable()) {
|
||||
$requires += cache_store::IS_SEARCHABLE;
|
||||
}
|
||||
return $requires;
|
||||
}
|
||||
|
||||
@ -694,7 +719,7 @@ class cache_definition {
|
||||
* @return bool
|
||||
*/
|
||||
public function should_be_persistent() {
|
||||
return $this->persistent;
|
||||
return $this->persistent || $this->mode === cache_store::MODE_SESSION;
|
||||
}
|
||||
|
||||
/**
|
||||
|
2
cache/classes/factory.php
vendored
2
cache/classes/factory.php
vendored
@ -207,9 +207,7 @@ class cache_factory {
|
||||
if (array_key_exists($key, $this->cachesfromparams)) {
|
||||
return $this->cachesfromparams[$key];
|
||||
}
|
||||
// Get the class. Note this is a late static binding so we need to use get_called_class.
|
||||
$definition = cache_definition::load_adhoc($mode, $component, $area, $options);
|
||||
$config = $this->create_config_instance();
|
||||
$definition->set_identifiers($identifiers);
|
||||
$cache = $this->create_cache($definition, $identifiers);
|
||||
if ($definition->should_be_persistent()) {
|
||||
|
67
cache/classes/helper.php
vendored
67
cache/classes/helper.php
vendored
@ -244,10 +244,9 @@ class cache_helper {
|
||||
if ($definition->invalidates_on_event($event)) {
|
||||
// OK at this point we know that the definition has information to invalidate on the event.
|
||||
// There are two routes, either its an application cache in which case we can invalidate it now.
|
||||
// or it is a session cache in which case we need to set something to the "Event invalidation" definition.
|
||||
// No need to deal with request caches, we don't want to change data half way through a request.
|
||||
if ($definition->get_mode() === cache_store::MODE_APPLICATION) {
|
||||
$cache = $factory->create_cache($definition);
|
||||
// or it is a persistent cache that also needs to be invalidated now.
|
||||
if ($definition->get_mode() === cache_store::MODE_APPLICATION || $definition->should_be_persistent()) {
|
||||
$cache = $factory->create_cache_from_definition($definition->get_component(), $definition->get_area());
|
||||
$cache->delete_many($keys);
|
||||
}
|
||||
|
||||
@ -568,4 +567,64 @@ class cache_helper {
|
||||
global $CFG;
|
||||
return (string)$CFG->version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs cron routines for MUC.
|
||||
*/
|
||||
public static function cron() {
|
||||
self::clean_old_session_data(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans old session data from cache stores used for session based definitions.
|
||||
*
|
||||
* @param bool $output If set to true output will be given.
|
||||
*/
|
||||
public static function clean_old_session_data($output = false) {
|
||||
global $CFG;
|
||||
if ($output) {
|
||||
mtrace('Cleaning up stale session data from cache stores.');
|
||||
}
|
||||
$factory = cache_factory::instance();
|
||||
$config = $factory->create_config_instance();
|
||||
$definitions = $config->get_definitions();
|
||||
$purgetime = time() - $CFG->sessiontimeout;
|
||||
foreach ($definitions as $definitionarray) {
|
||||
// We are only interested in session caches.
|
||||
if (!($definitionarray['mode'] & cache_store::MODE_SESSION)) {
|
||||
continue;
|
||||
}
|
||||
$definition = $factory->create_definition($definitionarray['component'], $definitionarray['area']);
|
||||
$stores = $config->get_stores_for_definition($definition);
|
||||
// Turn them into store instances.
|
||||
$stores = self::initialise_cachestore_instances($stores, $definition);
|
||||
// Initialise all of the stores used for that definition.
|
||||
foreach ($stores as $store) {
|
||||
// If the store doesn't support searching we can skip it.
|
||||
if (!($store instanceof cache_is_searchable)) {
|
||||
debugging('Cache stores used for session definitions should ideally be searchable.', DEBUG_DEVELOPER);
|
||||
continue;
|
||||
}
|
||||
// Get all of the keys.
|
||||
$keys = $store->find_by_prefix(cache_session::KEY_PREFIX);
|
||||
$todelete = array();
|
||||
foreach ($store->get_many($keys) as $key => $value) {
|
||||
if (strpos($key, cache_session::KEY_PREFIX) !== 0 || !is_array($value) || !isset($value['lastaccess'])) {
|
||||
continue;
|
||||
}
|
||||
if ((int)$value['lastaccess'] < $purgetime || true) {
|
||||
$todelete[] = $key;
|
||||
}
|
||||
}
|
||||
if (count($todelete)) {
|
||||
$outcome = (int)$store->delete_many($todelete);
|
||||
if ($output) {
|
||||
$strdef = s($definition->get_id());
|
||||
$strstore = s($store->my_name());
|
||||
mtrace("- Removed {$outcome} old {$strdef} sessions from the '{$strstore}' cache store.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
24
cache/classes/interfaces.php
vendored
24
cache/classes/interfaces.php
vendored
@ -338,6 +338,30 @@ interface cache_is_key_aware {
|
||||
public function has_all(array $keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache store feature: keys are searchable.
|
||||
*
|
||||
* Cache stores can choose to implement this interface.
|
||||
* In order for a store to be usable as a session cache it must implement this interface.
|
||||
*
|
||||
* @since 2.4.4
|
||||
*/
|
||||
interface cache_is_searchable {
|
||||
/**
|
||||
* Finds all of the keys being used by the cache store.
|
||||
*
|
||||
* @return array.
|
||||
*/
|
||||
public function find_all();
|
||||
|
||||
/**
|
||||
* Finds all of the keys whose keys start with the given prefix.
|
||||
*
|
||||
* @param string $prefix
|
||||
*/
|
||||
public function find_by_prefix($prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache store feature: configurable.
|
||||
*
|
||||
|
495
cache/classes/loaders.php
vendored
495
cache/classes/loaders.php
vendored
@ -141,7 +141,7 @@ class cache implements cache_loader {
|
||||
* and having it here helps speed up processing.
|
||||
* @var strubg
|
||||
*/
|
||||
private $storetype = 'unknown';
|
||||
protected $storetype = 'unknown';
|
||||
|
||||
/**
|
||||
* Gets set to true if we want to collect performance information about the cache API.
|
||||
@ -365,6 +365,7 @@ class cache implements cache_loader {
|
||||
*/
|
||||
public function get_many(array $keys, $strictness = IGNORE_MISSING) {
|
||||
|
||||
$keysparsed = array();
|
||||
$parsedkeys = array();
|
||||
$resultpersist = array();
|
||||
$resultstore = array();
|
||||
@ -374,6 +375,7 @@ class cache implements cache_loader {
|
||||
$isusingpersist = $this->is_using_persist_cache();
|
||||
foreach ($keys as $key) {
|
||||
$pkey = $this->parse_key($key);
|
||||
$keysparsed[$key] = $pkey;
|
||||
$parsedkeys[$pkey] = $key;
|
||||
$keystofind[$pkey] = $key;
|
||||
if ($isusingpersist) {
|
||||
@ -426,9 +428,11 @@ class cache implements cache_loader {
|
||||
$resultmissing = $this->datasource->load_many_for_cache($missingkeys);
|
||||
}
|
||||
foreach ($resultmissing as $key => $value) {
|
||||
$result[$key] = $value;
|
||||
$pkey = ($usingloader) ? $key : $keysparsed[$key];
|
||||
$realkey = ($usingloader) ? $parsedkeys[$key] : $key;
|
||||
$result[$pkey] = $value;
|
||||
if ($value !== false) {
|
||||
$this->set($parsedkeys[$key], $value);
|
||||
$this->set($realkey, $value);
|
||||
}
|
||||
}
|
||||
unset($resultmissing);
|
||||
@ -748,7 +752,7 @@ class cache implements cache_loader {
|
||||
public function delete($key, $recurse = true) {
|
||||
$parsedkey = $this->parse_key($key);
|
||||
$this->delete_from_persist_cache($parsedkey);
|
||||
if ($recurse && !empty($this->loader)) {
|
||||
if ($recurse && $this->loader !== false) {
|
||||
// Delete from the bottom of the stack first.
|
||||
$this->loader->delete($key, $recurse);
|
||||
}
|
||||
@ -770,7 +774,7 @@ class cache implements cache_loader {
|
||||
$this->delete_from_persist_cache($parsedkey);
|
||||
}
|
||||
}
|
||||
if ($recurse && !empty($this->loader)) {
|
||||
if ($recurse && $this->loader !== false) {
|
||||
// Delete from the bottom of the stack first.
|
||||
$this->loader->delete_many($keys, $recurse);
|
||||
}
|
||||
@ -848,6 +852,26 @@ class cache implements cache_loader {
|
||||
return $this->store;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the loader associated with this instance.
|
||||
*
|
||||
* @since 2.4.4
|
||||
* @return cache_loader|false
|
||||
*/
|
||||
protected function get_loader() {
|
||||
return $this->loader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data source associated with this cache.
|
||||
*
|
||||
* @since 2.4.4
|
||||
* @return cache_data_source|false
|
||||
*/
|
||||
protected function get_datasource() {
|
||||
return $this->datasource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the store supports key awareness.
|
||||
*
|
||||
@ -1396,6 +1420,18 @@ class cache_application extends cache implements cache_loader_with_locking {
|
||||
*
|
||||
* This class is used for session caches returned by the cache::make methods.
|
||||
*
|
||||
* It differs from the application loader in a couple of noteable ways:
|
||||
* 1. Sessions are always expected to be persistent.
|
||||
* Because of this we don't ever use the persist cache and instead a session array
|
||||
* containing all of the data is maintained by this object.
|
||||
* 2. Session data for a loader instance (store + definition) is consolidate into a
|
||||
* single array for storage within the store.
|
||||
* Along with this we embed a lastaccessed time with the data. This way we can
|
||||
* check sessions for a last access time.
|
||||
* 3. Session stores are required to support key searching and must
|
||||
* implement cache_is_searchable. This ensures stores used for the cache can be
|
||||
* targetted for garbage collection of session data.
|
||||
*
|
||||
* This cache class should never be interacted with directly. Instead you should always use the cache::make methods.
|
||||
* It is technically possible to call those methods through this class however there is no guarantee that you will get an
|
||||
* instance of this class back again.
|
||||
@ -1421,6 +1457,24 @@ class cache_session extends cache {
|
||||
* @var int
|
||||
*/
|
||||
protected $currentuserid = null;
|
||||
|
||||
/**
|
||||
* The session id we are currently using.
|
||||
* @var array
|
||||
*/
|
||||
protected $sessionid = null;
|
||||
|
||||
/**
|
||||
* The session data for the above session id.
|
||||
* @var array
|
||||
*/
|
||||
protected $session = null;
|
||||
|
||||
/**
|
||||
* Constant used to prefix keys.
|
||||
*/
|
||||
const KEY_PREFIX = 'sess_';
|
||||
|
||||
/**
|
||||
* Override the cache::construct method.
|
||||
*
|
||||
@ -1494,12 +1548,16 @@ class cache_session extends cache {
|
||||
* This function is called for every operation that uses keys. For this reason we use this function to also check
|
||||
* that the current user is the same as the user who last used this cache.
|
||||
*
|
||||
* On top of that if prepends the string 'sess_' to the start of all keys. The _ ensures things are easily identifiable.
|
||||
*
|
||||
* @param string|int $key As passed to get|set|delete etc.
|
||||
* @return string|array String unless the store supports multi-identifiers in which case an array if returned.
|
||||
*/
|
||||
protected function parse_key($key) {
|
||||
$this->check_tracked_user();
|
||||
return parent::parse_key($key);
|
||||
if ($key === 'lastaccess') {
|
||||
$key = '__lastaccess__';
|
||||
}
|
||||
return 'sess_'.parent::parse_key($key);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1514,11 +1572,13 @@ class cache_session extends cache {
|
||||
$new = 0;
|
||||
}
|
||||
if ($new !== self::$loadeduserid) {
|
||||
// The current user doesn't match the tracker userid for this request.
|
||||
// The current user doesn't match the tracked userid for this request.
|
||||
if (!is_null(self::$loadeduserid)) {
|
||||
// Purge the data we have for the old user.
|
||||
// This way we don't bloat the session.
|
||||
$this->purge();
|
||||
// Update the session id just in case!
|
||||
$this->sessionid = session_id();
|
||||
}
|
||||
self::$loadeduserid = $new;
|
||||
$this->currentuserid = $new;
|
||||
@ -1526,8 +1586,427 @@ class cache_session extends cache {
|
||||
// The current user matches the loaded user but not the user last used by this cache.
|
||||
$this->purge();
|
||||
$this->currentuserid = $new;
|
||||
// Update the session id just in case!
|
||||
$this->sessionid = session_id();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the session data.
|
||||
*
|
||||
* @param bool $force If true the session data will be loaded from the store again.
|
||||
* @return array An array of session data.
|
||||
*/
|
||||
protected function get_session_data($force = false) {
|
||||
if ($this->sessionid === null) {
|
||||
$this->sessionid = session_id();
|
||||
}
|
||||
if (is_array($this->session) && !$force) {
|
||||
return $this->session;
|
||||
}
|
||||
$session = parent::get($this->sessionid);
|
||||
if ($session === false) {
|
||||
$session = array();
|
||||
}
|
||||
// We have to write here to ensure that the lastaccess time is recorded.
|
||||
// And also in order to ensure the session entry exists as when we save it on __destruct
|
||||
// $CFG is likely to have already been destroyed.
|
||||
$this->save_session($session);
|
||||
return $this->session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the session data.
|
||||
*
|
||||
* This function also updates the last access time.
|
||||
*
|
||||
* @param array $session
|
||||
* @return bool
|
||||
*/
|
||||
protected function save_session(array $session) {
|
||||
$session['lastaccess'] = time();
|
||||
$this->session = $session;
|
||||
return parent::set($this->sessionid, $this->session);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the value for the given key from the cache.
|
||||
*
|
||||
* @param string|int $key The key for the data being requested.
|
||||
* It can be any structure although using a scalar string or int is recommended in the interests of performance.
|
||||
* In advanced cases an array may be useful such as in situations requiring the multi-key functionality.
|
||||
* @param int $strictness One of IGNORE_MISSING | MUST_EXIST
|
||||
* @return mixed|false The data from the cache or false if the key did not exist within the cache.
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public function get($key, $strictness = IGNORE_MISSING) {
|
||||
// Check the tracked user.
|
||||
$this->check_tracked_user();
|
||||
// 2. Parse the key.
|
||||
$parsedkey = $this->parse_key($key);
|
||||
// 3. Get it from the store.
|
||||
$result = false;
|
||||
$session = $this->get_session_data();
|
||||
if (array_key_exists($parsedkey, $session)) {
|
||||
$result = $session[$parsedkey];
|
||||
if ($result instanceof cache_ttl_wrapper) {
|
||||
if ($result->has_expired()) {
|
||||
$this->get_store()->delete($parsedkey);
|
||||
$result = false;
|
||||
} else {
|
||||
$result = $result->data;
|
||||
}
|
||||
}
|
||||
if ($result instanceof cache_cached_object) {
|
||||
$result = $result->restore_object();
|
||||
}
|
||||
}
|
||||
// 4. Load if from the loader/datasource if we don't already have it.
|
||||
$setaftervalidation = false;
|
||||
if ($result === false) {
|
||||
if ($this->perfdebug) {
|
||||
cache_helper::record_cache_miss('**static session**', $this->get_definition()->get_id());
|
||||
}
|
||||
if ($this->get_loader() !== false) {
|
||||
// We must pass the original (unparsed) key to the next loader in the chain.
|
||||
// The next loader will parse the key as it sees fit. It may be parsed differently
|
||||
// depending upon the capabilities of the store associated with the loader.
|
||||
$result = $this->get_loader()->get($key);
|
||||
} else if ($this->get_datasource() !== false) {
|
||||
$result = $this->get_datasource()->load_for_cache($key);
|
||||
}
|
||||
$setaftervalidation = ($result !== false);
|
||||
} else if ($this->perfdebug) {
|
||||
cache_helper::record_cache_hit('**static session**', $this->get_definition()->get_id());
|
||||
}
|
||||
// 5. Validate strictness.
|
||||
if ($strictness === MUST_EXIST && $result === false) {
|
||||
throw new moodle_exception('Requested key did not exist in any cache stores and could not be loaded.');
|
||||
}
|
||||
// 6. Set it to the store if we got it from the loader/datasource.
|
||||
if ($setaftervalidation) {
|
||||
$this->set($key, $result);
|
||||
}
|
||||
// 7. Make sure we don't pass back anything that could be a reference.
|
||||
// We don't want people modifying the data in the cache.
|
||||
if (!is_scalar($result)) {
|
||||
// If data is an object it will be a reference.
|
||||
// If data is an array if may contain references.
|
||||
// We want to break references so that the cache cannot be modified outside of itself.
|
||||
// Call the function to unreference it (in the best way possible).
|
||||
$result = $this->unref($result);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a key => value pair to the cache.
|
||||
*
|
||||
* <code>
|
||||
* // This code will add four entries to the cache, one for each url.
|
||||
* $cache->set('main', 'http://moodle.org');
|
||||
* $cache->set('docs', 'http://docs.moodle.org');
|
||||
* $cache->set('tracker', 'http://tracker.moodle.org');
|
||||
* $cache->set('qa', 'http://qa.moodle.net');
|
||||
* </code>
|
||||
*
|
||||
* @param string|int $key The key for the data being requested.
|
||||
* It can be any structure although using a scalar string or int is recommended in the interests of performance.
|
||||
* In advanced cases an array may be useful such as in situations requiring the multi-key functionality.
|
||||
* @param mixed $data The data to set against the key.
|
||||
* @return bool True on success, false otherwise.
|
||||
*/
|
||||
public function set($key, $data) {
|
||||
$this->check_tracked_user();
|
||||
if ($this->perfdebug) {
|
||||
cache_helper::record_cache_set('**static session**', $this->get_definition()->get_id());
|
||||
}
|
||||
if (is_object($data) && $data instanceof cacheable_object) {
|
||||
$data = new cache_cached_object($data);
|
||||
} else if (!is_scalar($data)) {
|
||||
// If data is an object it will be a reference.
|
||||
// If data is an array if may contain references.
|
||||
// We want to break references so that the cache cannot be modified outside of itself.
|
||||
// Call the function to unreference it (in the best way possible).
|
||||
$data = $this->unref($data);
|
||||
}
|
||||
// We dont' support native TTL here as we consolidate data for sessions.
|
||||
if ($this->has_a_ttl()) {
|
||||
$data = new cache_ttl_wrapper($data, $this->get_definition()->get_ttl());
|
||||
}
|
||||
$session = $this->get_session_data();
|
||||
$session[$this->parse_key($key)] = $data;
|
||||
return $this->save_session($session);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the given key from the cache.
|
||||
*
|
||||
* @param string|int $key The key to delete.
|
||||
* @param bool $recurse When set to true the key will also be deleted from all stacked cache loaders and their stores.
|
||||
* This happens by default and ensure that all the caches are consistent. It is NOT recommended to change this.
|
||||
* @return bool True of success, false otherwise.
|
||||
*/
|
||||
public function delete($key, $recurse = true) {
|
||||
$this->check_tracked_user();
|
||||
$parsedkey = $this->parse_key($key);
|
||||
if ($recurse && $this->get_loader() !== false) {
|
||||
// Delete from the bottom of the stack first.
|
||||
$this->get_loader()->delete($key, $recurse);
|
||||
}
|
||||
$session = $this->get_session_data();
|
||||
unset($session[$parsedkey]);
|
||||
return $this->save_session($session);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an array of values for an array of keys.
|
||||
*
|
||||
* Using this function comes with potential performance implications.
|
||||
* Not all cache stores will support get_many/set_many operations and in order to replicate this functionality will call
|
||||
* the equivalent singular method for each item provided.
|
||||
* This should not deter you from using this function as there is a performance benefit in situations where the cache store
|
||||
* does support it, but you should be aware of this fact.
|
||||
*
|
||||
* @param array $keys The keys of the data being requested.
|
||||
* Each key can be any structure although using a scalar string or int is recommended in the interests of performance.
|
||||
* In advanced cases an array may be useful such as in situations requiring the multi-key functionality.
|
||||
* @param int $strictness One of IGNORE_MISSING or MUST_EXIST.
|
||||
* @return array An array of key value pairs for the items that could be retrieved from the cache.
|
||||
* If MUST_EXIST was used and not all keys existed within the cache then an exception will be thrown.
|
||||
* Otherwise any key that did not exist will have a data value of false within the results.
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public function get_many(array $keys, $strictness = IGNORE_MISSING) {
|
||||
$this->check_tracked_user();
|
||||
$return = array();
|
||||
foreach ($keys as $key) {
|
||||
$return[$key] = $this->get($key, $strictness);
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all of the given keys from the cache.
|
||||
*
|
||||
* @param array $keys The key to delete.
|
||||
* @param bool $recurse When set to true the key will also be deleted from all stacked cache loaders and their stores.
|
||||
* This happens by default and ensure that all the caches are consistent. It is NOT recommended to change this.
|
||||
* @return int The number of items successfully deleted.
|
||||
*/
|
||||
public function delete_many(array $keys, $recurse = true) {
|
||||
$this->check_tracked_user();
|
||||
$parsedkeys = array_map(array($this, 'parse_key'), $keys);
|
||||
if ($recurse && $this->get_loader() !== false) {
|
||||
// Delete from the bottom of the stack first.
|
||||
$this->get_loader()->delete_many($keys, $recurse);
|
||||
}
|
||||
$session = $this->get_session_data();
|
||||
foreach ($parsedkeys as $parsedkey) {
|
||||
unset($session[$parsedkey]);
|
||||
}
|
||||
$this->save_session($session);
|
||||
return count($keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends several key => value pairs to the cache.
|
||||
*
|
||||
* Using this function comes with potential performance implications.
|
||||
* Not all cache stores will support get_many/set_many operations and in order to replicate this functionality will call
|
||||
* the equivalent singular method for each item provided.
|
||||
* This should not deter you from using this function as there is a performance benefit in situations where the cache store
|
||||
* does support it, but you should be aware of this fact.
|
||||
*
|
||||
* <code>
|
||||
* // This code will add four entries to the cache, one for each url.
|
||||
* $cache->set_many(array(
|
||||
* 'main' => 'http://moodle.org',
|
||||
* 'docs' => 'http://docs.moodle.org',
|
||||
* 'tracker' => 'http://tracker.moodle.org',
|
||||
* 'qa' => ''http://qa.moodle.net'
|
||||
* ));
|
||||
* </code>
|
||||
*
|
||||
* @param array $keyvaluearray An array of key => value pairs to send to the cache.
|
||||
* @return int The number of items successfully set. It is up to the developer to check this matches the number of items.
|
||||
* ... if they care that is.
|
||||
*/
|
||||
public function set_many(array $keyvaluearray) {
|
||||
$this->check_tracked_user();
|
||||
$session = $this->get_session_data();
|
||||
$simulatettl = $this->has_a_ttl();
|
||||
foreach ($keyvaluearray as $key => $value) {
|
||||
if (is_object($value) && $value instanceof cacheable_object) {
|
||||
$value = new cache_cached_object($value);
|
||||
} else if (!is_scalar($value)) {
|
||||
// If data is an object it will be a reference.
|
||||
// If data is an array if may contain references.
|
||||
// We want to break references so that the cache cannot be modified outside of itself.
|
||||
// Call the function to unreference it (in the best way possible).
|
||||
$value = $this->unref($value);
|
||||
}
|
||||
if ($simulatettl) {
|
||||
$value = new cache_ttl_wrapper($value, $this->get_definition()->get_ttl());
|
||||
}
|
||||
$parsedkey = $this->parse_key($key);
|
||||
$session[$parsedkey] = $value;
|
||||
}
|
||||
if ($this->perfdebug) {
|
||||
cache_helper::record_cache_set($this->storetype, $this->get_definition()->get_id());
|
||||
}
|
||||
$this->save_session($session);
|
||||
return count($keyvaluearray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Purges the cache store, and loader if there is one.
|
||||
*
|
||||
* @return bool True on success, false otherwise
|
||||
*/
|
||||
public function purge() {
|
||||
// 1. Purge the session object.
|
||||
$this->session = array();
|
||||
// 2. Delete the record for this users session from the store.
|
||||
$this->get_store()->delete($this->sessionid);
|
||||
// 3. Optionally purge any stacked loaders in the same way.
|
||||
if ($this->get_loader()) {
|
||||
$this->get_loader()->delete($this->sessionid);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test is a cache has a key.
|
||||
*
|
||||
* The use of the has methods is strongly discouraged. In a high load environment the cache may well change between the
|
||||
* test and any subsequent action (get, set, delete etc).
|
||||
* Instead it is recommended to write your code in such a way they it performs the following steps:
|
||||
* <ol>
|
||||
* <li>Attempt to retrieve the information.</li>
|
||||
* <li>Generate the information.</li>
|
||||
* <li>Attempt to set the information</li>
|
||||
* </ol>
|
||||
*
|
||||
* Its also worth mentioning that not all stores support key tests.
|
||||
* For stores that don't support key tests this functionality is mimicked by using the equivalent get method.
|
||||
* Just one more reason you should not use these methods unless you have a very good reason to do so.
|
||||
*
|
||||
* @param string|int $key
|
||||
* @param bool $tryloadifpossible If set to true, the cache doesn't contain the key, and there is another cache loader or
|
||||
* data source then the code will try load the key value from the next item in the chain.
|
||||
* @return bool True if the cache has the requested key, false otherwise.
|
||||
*/
|
||||
public function has($key, $tryloadifpossible = false) {
|
||||
$this->check_tracked_user();
|
||||
$parsedkey = $this->parse_key($key);
|
||||
$session = $this->get_session_data();
|
||||
$has = false;
|
||||
if ($this->has_a_ttl()) {
|
||||
// The data has a TTL and the store doesn't support it natively.
|
||||
// We must fetch the data and expect a ttl wrapper.
|
||||
if (array_key_exists($parsedkey, $session)) {
|
||||
$data = $session[$parsedkey];
|
||||
$has = ($data instanceof cache_ttl_wrapper && !$data->has_expired());
|
||||
}
|
||||
} else {
|
||||
$has = array_key_exists($parsedkey, $session);
|
||||
}
|
||||
if (!$has && $tryloadifpossible) {
|
||||
if ($this->get_loader() !== false) {
|
||||
$result = $this->get_loader()->get($key);
|
||||
} else if ($this->get_datasource() !== null) {
|
||||
$result = $this->get_datasource()->load_for_cache($key);
|
||||
}
|
||||
$has = ($result !== null);
|
||||
if ($has) {
|
||||
$this->set($key, $result);
|
||||
}
|
||||
}
|
||||
return $has;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test is a cache has all of the given keys.
|
||||
*
|
||||
* It is strongly recommended to avoid the use of this function if not absolutely required.
|
||||
* In a high load environment the cache may well change between the test and any subsequent action (get, set, delete etc).
|
||||
*
|
||||
* Its also worth mentioning that not all stores support key tests.
|
||||
* For stores that don't support key tests this functionality is mimicked by using the equivalent get method.
|
||||
* Just one more reason you should not use these methods unless you have a very good reason to do so.
|
||||
*
|
||||
* @param array $keys
|
||||
* @return bool True if the cache has all of the given keys, false otherwise.
|
||||
*/
|
||||
public function has_all(array $keys) {
|
||||
$this->check_tracked_user();
|
||||
$session = $this->get_session_data();
|
||||
foreach ($keys as $key) {
|
||||
$has = false;
|
||||
$parsedkey = $this->parse_key($key);
|
||||
if ($this->has_a_ttl()) {
|
||||
// The data has a TTL and the store doesn't support it natively.
|
||||
// We must fetch the data and expect a ttl wrapper.
|
||||
if (array_key_exists($parsedkey, $session)) {
|
||||
$data = $session[$parsedkey];
|
||||
$has = ($data instanceof cache_ttl_wrapper && !$data->has_expired());
|
||||
}
|
||||
} else {
|
||||
$has = array_key_exists($parsedkey, $session);
|
||||
}
|
||||
if (!$has) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if a cache has at least one of the given keys.
|
||||
*
|
||||
* It is strongly recommended to avoid the use of this function if not absolutely required.
|
||||
* In a high load environment the cache may well change between the test and any subsequent action (get, set, delete etc).
|
||||
*
|
||||
* Its also worth mentioning that not all stores support key tests.
|
||||
* For stores that don't support key tests this functionality is mimicked by using the equivalent get method.
|
||||
* Just one more reason you should not use these methods unless you have a very good reason to do so.
|
||||
*
|
||||
* @param array $keys
|
||||
* @return bool True if the cache has at least one of the given keys
|
||||
*/
|
||||
public function has_any(array $keys) {
|
||||
$this->check_tracked_user();
|
||||
$session = $this->get_session_data();
|
||||
foreach ($keys as $key) {
|
||||
$has = false;
|
||||
$parsedkey = $this->parse_key($key);
|
||||
if ($this->has_a_ttl()) {
|
||||
// The data has a TTL and the store doesn't support it natively.
|
||||
// We must fetch the data and expect a ttl wrapper.
|
||||
if (array_key_exists($parsedkey, $session)) {
|
||||
$data = $session[$parsedkey];
|
||||
$has = ($data instanceof cache_ttl_wrapper && !$data->has_expired());
|
||||
}
|
||||
} else {
|
||||
$has = array_key_exists($parsedkey, $session);
|
||||
}
|
||||
if ($has) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* The session loader never uses the persist cache.
|
||||
* Instead it stores things in the static $session variable. Shared between all session loaders.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_using_persist_cache() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
14
cache/classes/store.php
vendored
14
cache/classes/store.php
vendored
@ -110,6 +110,11 @@ abstract class cache_store implements cache_store_interface {
|
||||
*/
|
||||
const SUPPORTS_NATIVE_TTL = 4;
|
||||
|
||||
/**
|
||||
* The cache is searchable by key.
|
||||
*/
|
||||
const IS_SEARCHABLE = 8;
|
||||
|
||||
// Constants for the modes of a cache store
|
||||
|
||||
/**
|
||||
@ -307,6 +312,15 @@ abstract class cache_store implements cache_store_interface {
|
||||
return $this::get_supported_features() & self::SUPPORTS_NATIVE_TTL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the store instance is searchable.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_searchable() {
|
||||
return in_array('cache_is_searchable', class_implements($this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a clone of this store instance ready to be initialised.
|
||||
*
|
||||
|
2
cache/forms.php
vendored
2
cache/forms.php
vendored
@ -200,7 +200,7 @@ class cache_mode_mappings_form extends moodleform {
|
||||
);
|
||||
foreach ($stores as $storename => $store) {
|
||||
foreach ($store['modes'] as $mode => $enabled) {
|
||||
if ($enabled) {
|
||||
if ($enabled && ($mode !== cache_store::MODE_SESSION || $store['supports']['searchable'])) {
|
||||
if (empty($store['default'])) {
|
||||
$options[$mode][$storename] = $store['name'];
|
||||
} else {
|
||||
|
74
cache/locallib.php
vendored
74
cache/locallib.php
vendored
@ -340,32 +340,7 @@ class cache_config_writer extends cache_config {
|
||||
require_once($CFG->dirroot.'/cache/stores/static/lib.php');
|
||||
|
||||
$writer = new self;
|
||||
$writer->configstores = array(
|
||||
'default_application' => array(
|
||||
'name' => 'default_application',
|
||||
'plugin' => 'file',
|
||||
'configuration' => array(),
|
||||
'features' => cachestore_file::get_supported_features(),
|
||||
'modes' => cache_store::MODE_APPLICATION,
|
||||
'default' => true,
|
||||
),
|
||||
'default_session' => array(
|
||||
'name' => 'default_session',
|
||||
'plugin' => 'session',
|
||||
'configuration' => array(),
|
||||
'features' => cachestore_session::get_supported_features(),
|
||||
'modes' => cache_store::MODE_SESSION,
|
||||
'default' => true,
|
||||
),
|
||||
'default_request' => array(
|
||||
'name' => 'default_request',
|
||||
'plugin' => 'static',
|
||||
'configuration' => array(),
|
||||
'features' => cachestore_static::get_supported_features(),
|
||||
'modes' => cache_store::MODE_REQUEST,
|
||||
'default' => true,
|
||||
)
|
||||
);
|
||||
$writer->configstores = self::get_default_stores();
|
||||
$writer->configdefinitions = self::locate_definitions();
|
||||
$writer->configmodemappings = array(
|
||||
array(
|
||||
@ -404,6 +379,52 @@ class cache_config_writer extends cache_config {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of default stores for use.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function get_default_stores() {
|
||||
return array(
|
||||
'default_application' => array(
|
||||
'name' => 'default_application',
|
||||
'plugin' => 'file',
|
||||
'configuration' => array(),
|
||||
'features' => cachestore_file::get_supported_features(),
|
||||
'modes' => cachestore_file::get_supported_modes(),
|
||||
'default' => true,
|
||||
),
|
||||
'default_session' => array(
|
||||
'name' => 'default_session',
|
||||
'plugin' => 'session',
|
||||
'configuration' => array(),
|
||||
'features' => cachestore_session::get_supported_features(),
|
||||
'modes' => cachestore_session::get_supported_modes(),
|
||||
'default' => true,
|
||||
),
|
||||
'default_request' => array(
|
||||
'name' => 'default_request',
|
||||
'plugin' => 'static',
|
||||
'configuration' => array(),
|
||||
'features' => cachestore_static::get_supported_features(),
|
||||
'modes' => cachestore_static::get_supported_modes(),
|
||||
'default' => true,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the default stores within the MUC config file.
|
||||
*/
|
||||
public static function update_default_config_stores() {
|
||||
$factory = cache_factory::instance();
|
||||
$factory->updating_started();
|
||||
$config = $factory->create_config_instance(true);
|
||||
$config->configstores = array_merge($config->configstores, self::get_default_stores());
|
||||
$config->config_save();
|
||||
$factory->updating_finished();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the definition in the configuration from those found in the cache files.
|
||||
*
|
||||
@ -581,6 +602,7 @@ abstract class cache_administration_helper extends cache_helper {
|
||||
'nativettl' => $store->supports_native_ttl(),
|
||||
'nativelocking' => ($store instanceof cache_is_lockable),
|
||||
'keyawareness' => ($store instanceof cache_is_key_aware),
|
||||
'searchable' => ($store instanceof cache_is_searchable)
|
||||
)
|
||||
);
|
||||
if (empty($details['default'])) {
|
||||
|
52
cache/stores/file/lib.php
vendored
52
cache/stores/file/lib.php
vendored
@ -37,7 +37,7 @@
|
||||
* @copyright 2012 Sam Hemelryk
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class cachestore_file extends cache_store implements cache_is_key_aware, cache_is_configurable {
|
||||
class cachestore_file extends cache_store implements cache_is_key_aware, cache_is_configurable, cache_is_searchable {
|
||||
|
||||
/**
|
||||
* The name of the store.
|
||||
@ -190,7 +190,8 @@ class cachestore_file extends cache_store implements cache_is_key_aware, cache_i
|
||||
*/
|
||||
public static function get_supported_features(array $configuration = array()) {
|
||||
$supported = self::SUPPORTS_DATA_GUARANTEE +
|
||||
self::SUPPORTS_NATIVE_TTL;
|
||||
self::SUPPORTS_NATIVE_TTL +
|
||||
self::IS_SEARCHABLE;
|
||||
return $supported;
|
||||
}
|
||||
|
||||
@ -257,13 +258,15 @@ class cachestore_file extends cache_store implements cache_is_key_aware, cache_i
|
||||
|
||||
/**
|
||||
* Gets a pattern suitable for use with glob to find all keys in the cache.
|
||||
*
|
||||
* @param string $prefix A prefix to use.
|
||||
* @return string The pattern.
|
||||
*/
|
||||
protected function glob_keys_pattern() {
|
||||
protected function glob_keys_pattern($prefix = '') {
|
||||
if ($this->singledirectory) {
|
||||
return $this->path . '/*.cache';
|
||||
return $this->path . '/'.$prefix.'*.cache';
|
||||
} else {
|
||||
return $this->path . '/*/*.cache';
|
||||
return $this->path . '/*/'.$prefix.'*.cache';
|
||||
}
|
||||
}
|
||||
|
||||
@ -365,7 +368,6 @@ class cachestore_file extends cache_store implements cache_is_key_aware, cache_i
|
||||
public function delete($key) {
|
||||
$filename = $key.'.cache';
|
||||
$file = $this->file_path_for_key($key);
|
||||
|
||||
if (@unlink($file)) {
|
||||
unset($this->keys[$filename]);
|
||||
return true;
|
||||
@ -687,4 +689,42 @@ class cachestore_file extends cache_store implements cache_is_key_aware, cache_i
|
||||
public function my_name() {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all of the keys being used by this cache store instance.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function find_all() {
|
||||
$this->ensure_path_exists();
|
||||
$files = glob($this->glob_keys_pattern(), GLOB_MARK | GLOB_NOSORT);
|
||||
$return = array();
|
||||
if ($files === false) {
|
||||
return $return;
|
||||
}
|
||||
foreach ($files as $file) {
|
||||
$return[] = substr(basename($file), 0, -6);
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all of the keys whose keys start with the given prefix.
|
||||
*
|
||||
* @param string $prefix
|
||||
*/
|
||||
public function find_by_prefix($prefix) {
|
||||
$this->ensure_path_exists();
|
||||
$prefix = preg_replace('#(\*|\?|\[)#', '[$1]', $prefix);
|
||||
$files = glob($this->glob_keys_pattern($prefix), GLOB_MARK | GLOB_NOSORT);
|
||||
$return = array();
|
||||
if ($files === false) {
|
||||
return $return;
|
||||
}
|
||||
foreach ($files as $file) {
|
||||
// Trim off ".cache" from the end.
|
||||
$return[] = substr(basename($file), 0, -6);
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
29
cache/stores/session/lib.php
vendored
29
cache/stores/session/lib.php
vendored
@ -90,7 +90,7 @@ abstract class session_data_store extends cache_store {
|
||||
* @copyright 2012 Sam Hemelryk
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class cachestore_session extends session_data_store implements cache_is_key_aware {
|
||||
class cachestore_session extends session_data_store implements cache_is_key_aware, cache_is_searchable {
|
||||
|
||||
/**
|
||||
* The name of the store
|
||||
@ -137,7 +137,8 @@ class cachestore_session extends session_data_store implements cache_is_key_awar
|
||||
*/
|
||||
public static function get_supported_features(array $configuration = array()) {
|
||||
return self::SUPPORTS_DATA_GUARANTEE +
|
||||
self::SUPPORTS_NATIVE_TTL;
|
||||
self::SUPPORTS_NATIVE_TTL +
|
||||
self::IS_SEARCHABLE;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -403,4 +404,28 @@ class cachestore_session extends session_data_store implements cache_is_key_awar
|
||||
public function my_name() {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all of the keys being stored in the cache store instance.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function find_all() {
|
||||
return array_keys($this->store);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all of the keys whose keys start with the given prefix.
|
||||
*
|
||||
* @param string $prefix
|
||||
*/
|
||||
public function find_by_prefix($prefix) {
|
||||
$return = array();
|
||||
foreach ($this->find_all() as $key) {
|
||||
if (strpos($key, $prefix) === 0) {
|
||||
$return[] = $key;
|
||||
}
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
}
|
386
cache/tests/cache_test.php
vendored
386
cache/tests/cache_test.php
vendored
@ -126,6 +126,18 @@ class cache_phpunit_tests extends advanced_testcase {
|
||||
$cache = cache::make_from_params(cache_store::MODE_APPLICATION, 'phpunit', 'applicationtest');
|
||||
$this->assertInstanceOf('cache_application', $cache);
|
||||
$this->run_on_cache($cache);
|
||||
|
||||
$instance = cache_config_phpunittest::instance(true);
|
||||
$instance->phpunit_add_definition('phpunit/test_default_application_cache', array(
|
||||
'mode' => cache_store::MODE_APPLICATION,
|
||||
'component' => 'phpunit',
|
||||
'area' => 'test_default_application_cache',
|
||||
'persistent' => true,
|
||||
'persistentmaxsize' => 1
|
||||
));
|
||||
$cache = cache::make('phpunit', 'test_default_application_cache');
|
||||
$this->assertInstanceOf('cache_application', $cache);
|
||||
$this->run_on_cache($cache);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -231,7 +243,7 @@ class cache_phpunit_tests extends advanced_testcase {
|
||||
$this->assertEquals('red_ptc_wfc', $result->property1);
|
||||
$this->assertEquals('blue_ptc_wfc', $result->property2);
|
||||
|
||||
// Test array of objects
|
||||
// Test array of objects.
|
||||
$specobject = new cache_phpunit_dummy_object('red', 'blue');
|
||||
$data = new cacheable_object_array(array(
|
||||
clone($specobject),
|
||||
@ -255,6 +267,18 @@ class cache_phpunit_tests extends advanced_testcase {
|
||||
$this->assertTrue($cache->delete('key1'));
|
||||
$this->assertTrue($cache->delete('key2'));
|
||||
|
||||
$cache->set_many(array(
|
||||
'key1' => array(1, 2, 3),
|
||||
'key2' => array(3, 2, 1),
|
||||
));
|
||||
$this->assertInternalType('array', $cache->get('key1'));
|
||||
$this->assertInternalType('array', $cache->get('key2'));
|
||||
$this->assertCount(3, $cache->get('key1'));
|
||||
$this->assertCount(3, $cache->get('key2'));
|
||||
$this->assertInternalType('array', $cache->get_many(array('key1', 'key2')));
|
||||
$this->assertCount(2, $cache->get_many(array('key1', 'key2')));
|
||||
$this->assertEquals(2, $cache->delete_many(array('key1', 'key2')));
|
||||
|
||||
// Test delete many.
|
||||
$this->assertTrue($cache->set('key1', 'data1'));
|
||||
$this->assertTrue($cache->set('key2', 'data2'));
|
||||
@ -333,6 +357,27 @@ class cache_phpunit_tests extends advanced_testcase {
|
||||
$this->assertEquals('value', $var2->key);
|
||||
|
||||
$this->assertTrue($cache->delete('obj'));
|
||||
|
||||
// Test strictness exceptions.
|
||||
try {
|
||||
$cache->get('exception', MUST_EXIST);
|
||||
$this->fail('Exception expected from cache::get using MUST_EXIST');
|
||||
} catch (Exception $e) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
try {
|
||||
$cache->get_many(array('exception1', 'exception2'), MUST_EXIST);
|
||||
$this->fail('Exception expected from cache::get_many using MUST_EXIST');
|
||||
} catch (Exception $e) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
$cache->set('test', 'test');
|
||||
try {
|
||||
$cache->get_many(array('test', 'exception'), MUST_EXIST);
|
||||
$this->fail('Exception expected from cache::get_many using MUST_EXIST');
|
||||
} catch (Exception $e) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -355,13 +400,25 @@ class cache_phpunit_tests extends advanced_testcase {
|
||||
$this->assertTrue($cache->purge());
|
||||
// It won't be there yet.
|
||||
$this->assertFalse($cache->has('Test'));
|
||||
|
||||
// It should load it ;).
|
||||
$this->assertTrue($cache->has('Test', true));
|
||||
|
||||
// Purge it to be sure.
|
||||
$this->assertTrue($cache->purge());
|
||||
$this->assertEquals('Test has no value really.', $cache->get('Test'));
|
||||
|
||||
// Test multiple values.
|
||||
$this->assertTrue($cache->purge());
|
||||
$this->assertTrue($cache->set('b', 'B'));
|
||||
$result = $cache->get_many(array('a', 'b', 'c'));
|
||||
$this->assertInternalType('array', $result);
|
||||
$this->assertCount(3, $result);
|
||||
$this->assertArrayHasKey('a', $result);
|
||||
$this->assertArrayHasKey('b', $result);
|
||||
$this->assertArrayHasKey('c', $result);
|
||||
$this->assertEquals('a has no value really.', $result['a']);
|
||||
$this->assertEquals('B', $result['b']);
|
||||
$this->assertEquals('c has no value really.', $result['c']);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -433,7 +490,10 @@ class cache_phpunit_tests extends advanced_testcase {
|
||||
$this->assertEquals('test data 2', $cache->get('2'));
|
||||
}
|
||||
|
||||
public function test_definition_ttl() {
|
||||
/**
|
||||
* Test a negative TTL on an application cache.
|
||||
*/
|
||||
public function test_application_ttl_negative() {
|
||||
$instance = cache_config_phpunittest::instance(true);
|
||||
$instance->phpunit_add_definition('phpunit/ttltest', array(
|
||||
'mode' => cache_store::MODE_APPLICATION,
|
||||
@ -454,6 +514,127 @@ class cache_phpunit_tests extends advanced_testcase {
|
||||
$this->assertFalse($cache->has('Test'));
|
||||
// Double check by trying to get it.
|
||||
$this->assertFalse($cache->get('Test'));
|
||||
|
||||
// Test with multiple keys.
|
||||
$this->assertEquals(3, $cache->set_many(array('a' => 'A', 'b' => 'B', 'c' => 'C')));
|
||||
$result = $cache->get_many(array('a', 'b', 'c'));
|
||||
$this->assertInternalType('array', $result);
|
||||
$this->assertCount(3, $result);
|
||||
$this->assertArrayHasKey('a', $result);
|
||||
$this->assertArrayHasKey('b', $result);
|
||||
$this->assertArrayHasKey('c', $result);
|
||||
$this->assertFalse($result['a']);
|
||||
$this->assertFalse($result['b']);
|
||||
$this->assertFalse($result['c']);
|
||||
|
||||
// Test with multiple keys including missing ones.
|
||||
$result = $cache->get_many(array('a', 'c', 'e'));
|
||||
$this->assertInternalType('array', $result);
|
||||
$this->assertCount(3, $result);
|
||||
$this->assertArrayHasKey('a', $result);
|
||||
$this->assertArrayHasKey('c', $result);
|
||||
$this->assertArrayHasKey('e', $result);
|
||||
$this->assertFalse($result['a']);
|
||||
$this->assertFalse($result['c']);
|
||||
$this->assertFalse($result['e']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test a positive TTL on an application cache.
|
||||
*/
|
||||
public function test_application_ttl_positive() {
|
||||
$instance = cache_config_phpunittest::instance(true);
|
||||
$instance->phpunit_add_definition('phpunit/ttltest', array(
|
||||
'mode' => cache_store::MODE_APPLICATION,
|
||||
'component' => 'phpunit',
|
||||
'area' => 'ttltest',
|
||||
'ttl' => 86400 // Set to a day in the future to be extra sure.
|
||||
));
|
||||
$cache = cache::make('phpunit', 'ttltest');
|
||||
$this->assertInstanceOf('cache_application', $cache);
|
||||
|
||||
// Purge it to be sure.
|
||||
$this->assertTrue($cache->purge());
|
||||
// It won't be there yet.
|
||||
$this->assertFalse($cache->has('Test'));
|
||||
// Set it now.
|
||||
$this->assertTrue($cache->set('Test', 'Test'));
|
||||
// Check its there.
|
||||
$this->assertTrue($cache->has('Test'));
|
||||
// Double check by trying to get it.
|
||||
$this->assertEquals('Test', $cache->get('Test'));
|
||||
|
||||
// Test with multiple keys.
|
||||
$this->assertEquals(3, $cache->set_many(array('a' => 'A', 'b' => 'B', 'c' => 'C')));
|
||||
$result = $cache->get_many(array('a', 'b', 'c'));
|
||||
$this->assertInternalType('array', $result);
|
||||
$this->assertCount(3, $result);
|
||||
$this->assertArrayHasKey('a', $result);
|
||||
$this->assertArrayHasKey('b', $result);
|
||||
$this->assertArrayHasKey('c', $result);
|
||||
$this->assertEquals('A', $result['a']);
|
||||
$this->assertEquals('B', $result['b']);
|
||||
$this->assertEquals('C', $result['c']);
|
||||
|
||||
// Test with multiple keys including missing ones.
|
||||
$result = $cache->get_many(array('a', 'c', 'e'));
|
||||
$this->assertInternalType('array', $result);
|
||||
$this->assertCount(3, $result);
|
||||
$this->assertArrayHasKey('a', $result);
|
||||
$this->assertArrayHasKey('c', $result);
|
||||
$this->assertArrayHasKey('e', $result);
|
||||
$this->assertEquals('A', $result['a']);
|
||||
$this->assertEquals('C', $result['c']);
|
||||
$this->assertEquals(false, $result['e']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test a negative TTL on an session cache.
|
||||
*/
|
||||
public function test_session_ttl_positive() {
|
||||
$instance = cache_config_phpunittest::instance(true);
|
||||
$instance->phpunit_add_definition('phpunit/ttltest', array(
|
||||
'mode' => cache_store::MODE_SESSION,
|
||||
'component' => 'phpunit',
|
||||
'area' => 'ttltest',
|
||||
'ttl' => 86400 // Set to a day in the future to be extra sure.
|
||||
));
|
||||
$cache = cache::make('phpunit', 'ttltest');
|
||||
$this->assertInstanceOf('cache_session', $cache);
|
||||
|
||||
// Purge it to be sure.
|
||||
$this->assertTrue($cache->purge());
|
||||
// It won't be there yet.
|
||||
$this->assertFalse($cache->has('Test'));
|
||||
// Set it now.
|
||||
$this->assertTrue($cache->set('Test', 'Test'));
|
||||
// Check its there.
|
||||
$this->assertTrue($cache->has('Test'));
|
||||
// Double check by trying to get it.
|
||||
$this->assertEquals('Test', $cache->get('Test'));
|
||||
|
||||
// Test with multiple keys.
|
||||
$this->assertEquals(3, $cache->set_many(array('a' => 'A', 'b' => 'B', 'c' => 'C')));
|
||||
$result = $cache->get_many(array('a', 'b', 'c'));
|
||||
$this->assertInternalType('array', $result);
|
||||
$this->assertCount(3, $result);
|
||||
$this->assertArrayHasKey('a', $result);
|
||||
$this->assertArrayHasKey('b', $result);
|
||||
$this->assertArrayHasKey('c', $result);
|
||||
$this->assertEquals('A', $result['a']);
|
||||
$this->assertEquals('B', $result['b']);
|
||||
$this->assertEquals('C', $result['c']);
|
||||
|
||||
// Test with multiple keys including missing ones.
|
||||
$result = $cache->get_many(array('a', 'c', 'e'));
|
||||
$this->assertInternalType('array', $result);
|
||||
$this->assertCount(3, $result);
|
||||
$this->assertArrayHasKey('a', $result);
|
||||
$this->assertArrayHasKey('c', $result);
|
||||
$this->assertArrayHasKey('e', $result);
|
||||
$this->assertEquals('A', $result['a']);
|
||||
$this->assertEquals('C', $result['c']);
|
||||
$this->assertEquals(false, $result['e']);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -520,6 +701,42 @@ class cache_phpunit_tests extends advanced_testcase {
|
||||
$this->assertFalse($cache->get('testkey2'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests session cache event invalidation
|
||||
*/
|
||||
public function test_session_event_invalidation() {
|
||||
$instance = cache_config_phpunittest::instance();
|
||||
$instance->phpunit_add_definition('phpunit/test_session_event_invalidation', array(
|
||||
'mode' => cache_store::MODE_SESSION,
|
||||
'component' => 'phpunit',
|
||||
'area' => 'test_session_event_invalidation',
|
||||
'invalidationevents' => array(
|
||||
'crazyevent'
|
||||
)
|
||||
));
|
||||
$cache = cache::make('phpunit', 'test_session_event_invalidation');
|
||||
$this->assertInstanceOf('cache_session', $cache);
|
||||
|
||||
$this->assertTrue($cache->set('testkey1', 'test data 1'));
|
||||
$this->assertEquals('test data 1', $cache->get('testkey1'));
|
||||
$this->assertTrue($cache->set('testkey2', 'test data 2'));
|
||||
$this->assertEquals('test data 2', $cache->get('testkey2'));
|
||||
|
||||
// Test invalidating a single entry.
|
||||
cache_helper::invalidate_by_event('crazyevent', array('testkey1'));
|
||||
|
||||
$this->assertFalse($cache->get('testkey1'));
|
||||
$this->assertEquals('test data 2', $cache->get('testkey2'));
|
||||
|
||||
$this->assertTrue($cache->set('testkey1', 'test data 1'));
|
||||
|
||||
// Test invalidating both entries.
|
||||
cache_helper::invalidate_by_event('crazyevent', array('testkey1', 'testkey2'));
|
||||
|
||||
$this->assertFalse($cache->get('testkey1'));
|
||||
$this->assertFalse($cache->get('testkey2'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests application cache definition invalidation
|
||||
*/
|
||||
@ -556,6 +773,45 @@ class cache_phpunit_tests extends advanced_testcase {
|
||||
$this->assertFalse($cache->get('testkey2'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests session cache definition invalidation
|
||||
*/
|
||||
public function test_session_definition_invalidation() {
|
||||
$instance = cache_config_phpunittest::instance();
|
||||
$instance->phpunit_add_definition('phpunit/test_session_definition_invalidation', array(
|
||||
'mode' => cache_store::MODE_SESSION,
|
||||
'component' => 'phpunit',
|
||||
'area' => 'test_session_definition_invalidation'
|
||||
));
|
||||
$cache = cache::make('phpunit', 'test_session_definition_invalidation');
|
||||
$this->assertInstanceOf('cache_session', $cache);
|
||||
$this->assertTrue($cache->set('testkey1', 'test data 1'));
|
||||
$this->assertEquals('test data 1', $cache->get('testkey1'));
|
||||
$this->assertTrue($cache->set('testkey2', 'test data 2'));
|
||||
$this->assertEquals('test data 2', $cache->get('testkey2'));
|
||||
|
||||
cache_helper::invalidate_by_definition('phpunit', 'test_session_definition_invalidation', array(), 'testkey1');
|
||||
|
||||
$this->assertFalse($cache->get('testkey1'));
|
||||
$this->assertEquals('test data 2', $cache->get('testkey2'));
|
||||
|
||||
$this->assertTrue($cache->set('testkey1', 'test data 1'));
|
||||
|
||||
cache_helper::invalidate_by_definition('phpunit', 'test_session_definition_invalidation', array(),
|
||||
array('testkey1'));
|
||||
|
||||
$this->assertFalse($cache->get('testkey1'));
|
||||
$this->assertEquals('test data 2', $cache->get('testkey2'));
|
||||
|
||||
$this->assertTrue($cache->set('testkey1', 'test data 1'));
|
||||
|
||||
cache_helper::invalidate_by_definition('phpunit', 'test_session_definition_invalidation', array(),
|
||||
array('testkey1', 'testkey2'));
|
||||
|
||||
$this->assertFalse($cache->get('testkey1'));
|
||||
$this->assertFalse($cache->get('testkey2'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests application cache event invalidation over a distributed setup.
|
||||
*/
|
||||
@ -865,6 +1121,34 @@ class cache_phpunit_tests extends advanced_testcase {
|
||||
$this->assertFalse($cache->get('test'));
|
||||
$this->assertTrue($cache->set('test', 'test'));
|
||||
$this->assertEquals('test', $cache->get('test'));
|
||||
$this->assertTrue($cache->delete('test'));
|
||||
$this->assertFalse($cache->get('test'));
|
||||
$this->assertTrue($cache->set('test', 'test'));
|
||||
$this->assertTrue($cache->purge());
|
||||
$this->assertFalse($cache->get('test'));
|
||||
|
||||
// Test the many commands.
|
||||
$this->assertEquals(3, $cache->set_many(array('a' => 'A', 'b' => 'B', 'c' => 'C')));
|
||||
$result = $cache->get_many(array('a', 'b', 'c'));
|
||||
$this->assertInternalType('array', $result);
|
||||
$this->assertCount(3, $result);
|
||||
$this->assertArrayHasKey('a', $result);
|
||||
$this->assertArrayHasKey('b', $result);
|
||||
$this->assertArrayHasKey('c', $result);
|
||||
$this->assertEquals('A', $result['a']);
|
||||
$this->assertEquals('B', $result['b']);
|
||||
$this->assertEquals('C', $result['c']);
|
||||
$this->assertEquals($result, $cache->get_many(array('a', 'b', 'c')));
|
||||
$this->assertEquals(2, $cache->delete_many(array('a', 'c')));
|
||||
$result = $cache->get_many(array('a', 'b', 'c'));
|
||||
$this->assertInternalType('array', $result);
|
||||
$this->assertCount(3, $result);
|
||||
$this->assertArrayHasKey('a', $result);
|
||||
$this->assertArrayHasKey('b', $result);
|
||||
$this->assertArrayHasKey('c', $result);
|
||||
$this->assertFalse($result['a']);
|
||||
$this->assertEquals('B', $result['b']);
|
||||
$this->assertFalse($result['c']);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -895,6 +1179,76 @@ class cache_phpunit_tests extends advanced_testcase {
|
||||
$this->assertEquals(false, $cache->get('var'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test switching users with session caches.
|
||||
*/
|
||||
public function test_session_cache_switch_user_application_mapping() {
|
||||
$this->resetAfterTest(true);
|
||||
$instance = cache_config_phpunittest::instance(true);
|
||||
$instance->phpunit_add_file_store('testfilestore');
|
||||
$instance->phpunit_add_definition('phpunit/testappsession', array(
|
||||
'mode' => cache_store::MODE_SESSION,
|
||||
'component' => 'phpunit',
|
||||
'area' => 'testappsession'
|
||||
));
|
||||
$instance->phpunit_add_definition_mapping('phpunit/testappsession', 'testfilestore', 3);
|
||||
$cache = cache::make('phpunit', 'testappsession');
|
||||
$user1 = $this->getDataGenerator()->create_user();
|
||||
$user2 = $this->getDataGenerator()->create_user();
|
||||
|
||||
// Log in as the first user.
|
||||
$this->setUser($user1);
|
||||
$sesskey1 = sesskey();
|
||||
|
||||
// Set a basic value in the cache.
|
||||
$cache->set('var', 1);
|
||||
$this->assertTrue($cache->has('var'));
|
||||
$this->assertEquals(1, $cache->get('var'));
|
||||
|
||||
// Change to the second user.
|
||||
$this->setUser($user2);
|
||||
$sesskey2 = sesskey();
|
||||
|
||||
// Make sure the cache doesn't give us the data for the last user.
|
||||
$this->assertNotEquals($sesskey1, $sesskey2);
|
||||
$this->assertFalse($cache->has('var'));
|
||||
$this->assertEquals(false, $cache->get('var'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test two session caches being used at once to confirm collisions don't occur.
|
||||
*/
|
||||
public function test_dual_session_caches() {
|
||||
$instance = cache_config_phpunittest::instance(true);
|
||||
$instance->phpunit_add_definition('phpunit/testsess1', array(
|
||||
'mode' => cache_store::MODE_SESSION,
|
||||
'component' => 'phpunit',
|
||||
'area' => 'testsess1'
|
||||
));
|
||||
$instance->phpunit_add_definition('phpunit/testsess2', array(
|
||||
'mode' => cache_store::MODE_SESSION,
|
||||
'component' => 'phpunit',
|
||||
'area' => 'testsess2'
|
||||
));
|
||||
$cache1 = cache::make('phpunit', 'testsess1');
|
||||
$cache2 = cache::make('phpunit', 'testsess2');
|
||||
|
||||
$this->assertFalse($cache1->has('test'));
|
||||
$this->assertFalse($cache2->has('test'));
|
||||
|
||||
$this->assertTrue($cache1->set('test', '1'));
|
||||
|
||||
$this->assertTrue($cache1->has('test'));
|
||||
$this->assertFalse($cache2->has('test'));
|
||||
|
||||
$this->assertTrue($cache2->set('test', '2'));
|
||||
|
||||
$this->assertEquals(1, $cache1->get('test'));
|
||||
$this->assertEquals(2, $cache2->get('test'));
|
||||
|
||||
$this->assertTrue($cache1->delete('test'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test multiple session caches when switching user.
|
||||
*/
|
||||
@ -925,4 +1279,30 @@ class cache_phpunit_tests extends advanced_testcase {
|
||||
$this->assertEquals(false, $cache1->get('var'));
|
||||
$this->assertEquals(false, $cache2->get('var'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test application locking.
|
||||
*/
|
||||
public function test_application_locking() {
|
||||
$instance = cache_config_phpunittest::instance(true);
|
||||
$instance->phpunit_add_definition('phpunit/test_application_locking', array(
|
||||
'mode' => cache_store::MODE_APPLICATION,
|
||||
'component' => 'phpunit',
|
||||
'area' => 'test_application_locking',
|
||||
'persistent' => true,
|
||||
'persistentmaxsize' => 1,
|
||||
'requirelockingread' => true,
|
||||
'requirelockingwrite' => true
|
||||
));
|
||||
$cache = cache::make('phpunit', 'test_application_locking');
|
||||
$this->assertInstanceOf('cache_application', $cache);
|
||||
|
||||
$this->assertTrue($cache->set('a', 'A'));
|
||||
$this->assertTrue($cache->set('b', 'B'));
|
||||
$this->assertTrue($cache->set('c', 'C'));
|
||||
$this->assertEquals('A', $cache->get('a'));
|
||||
$this->assertEquals(array('b' => 'B', 'c' => 'C'), $cache->get_many(array('b', 'c')));
|
||||
$this->assertTrue($cache->delete('a'));
|
||||
$this->assertFalse($cache->has('a'));
|
||||
}
|
||||
}
|
||||
|
1
cache/tests/fixtures/lib.php
vendored
1
cache/tests/fixtures/lib.php
vendored
@ -35,6 +35,7 @@ defined('MOODLE_INTERNAL') || die();
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class cache_config_phpunittest extends cache_config_writer {
|
||||
|
||||
/**
|
||||
* Adds a definition to the stack
|
||||
* @param string $area
|
||||
|
@ -135,6 +135,7 @@ $string['supports_dataguarantee'] = 'data guarantee';
|
||||
$string['supports_nativettl'] = 'ttl';
|
||||
$string['supports_nativelocking'] = 'locking';
|
||||
$string['supports_keyawareness'] = 'key awareness';
|
||||
$string['supports_searchable'] = 'searching by key';
|
||||
$string['tested'] = 'Tested';
|
||||
$string['testperformance'] = 'Test performance';
|
||||
$string['unsupportedmode'] = 'Unsupported mode';
|
||||
|
@ -453,6 +453,9 @@ function cron_run() {
|
||||
mtrace('done.');
|
||||
}
|
||||
|
||||
mtrace('Running cache cron routines');
|
||||
cache_helper::cron();
|
||||
mtrace('done.');
|
||||
|
||||
// Run automated backups if required - these may take a long time to execute
|
||||
require_once($CFG->dirroot.'/backup/util/includes/backup_includes.php');
|
||||
|
@ -2117,5 +2117,13 @@ function xmldb_main_upgrade($oldversion) {
|
||||
upgrade_main_savepoint(true, 2013041601.01);
|
||||
}
|
||||
|
||||
if ($oldversion < 2013041900.00) {
|
||||
require_once($CFG->dirroot . '/cache/locallib.php');
|
||||
// The features bin needs updating.
|
||||
cache_config_writer::update_default_config_stores();
|
||||
// Main savepoint reached.
|
||||
upgrade_main_savepoint(true, 2013041900.00);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user