diff --git a/cache/README.md b/cache/README.md
index 621c09c047e..3868c90fdba 100644
--- a/cache/README.md
+++ b/cache/README.md
@@ -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');
\ No newline at end of file
+ define('TEST_CACHESTORE_MONGODB_TESTSERVER', 'mongodb://localhost:27017');
diff --git a/cache/classes/definition.php b/cache/classes/definition.php
index 8c5a16a7c6c..62732f737ce 100644
--- a/cache/classes/definition.php
+++ b/cache/classes/definition.php
@@ -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;
}
/**
diff --git a/cache/classes/factory.php b/cache/classes/factory.php
index 9eee85a11c7..e57d6ef077e 100644
--- a/cache/classes/factory.php
+++ b/cache/classes/factory.php
@@ -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()) {
diff --git a/cache/classes/helper.php b/cache/classes/helper.php
index 075dc416dff..be329f77cde 100644
--- a/cache/classes/helper.php
+++ b/cache/classes/helper.php
@@ -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.");
+ }
+ }
+ }
+ }
+ }
}
diff --git a/cache/classes/interfaces.php b/cache/classes/interfaces.php
index d54834961d0..24996ed4d33 100644
--- a/cache/classes/interfaces.php
+++ b/cache/classes/interfaces.php
@@ -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.
*
diff --git a/cache/classes/loaders.php b/cache/classes/loaders.php
index fd1a3403c23..9f7dc79bcca 100644
--- a/cache/classes/loaders.php
+++ b/cache/classes/loaders.php
@@ -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.
+ *
+ *
+ * // 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');
+ *
+ *
+ * @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.
+ *
+ *
+ * // 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'
+ * ));
+ *
+ *
+ * @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:
+ *