MDL-38565 cache: session caches now function as expected
This change is a large change to the way sessions are handled within MUC after it was discovered that session did not function as expected when any store other than the default session store was being used. As part of this change the session loader has been largely customised in order to consolidate session data for the loader. The unit tests have also being greatly increased to provide better coverage for sessions.
This commit is contained in:
Vendored
+3
-1
@@ -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');
|
||||
|
||||
Vendored
+26
-1
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Vendored
-2
@@ -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()) {
|
||||
|
||||
Vendored
+3
-4
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Vendored
+24
@@ -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.
|
||||
*
|
||||
|
||||
Vendored
+480
-5
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1528,6 +1586,423 @@ class cache_session extends cache {
|
||||
$this->currentuserid = $new;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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($this->storetype, $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($this->storetype, $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($this->storetype, $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. Purge the store.
|
||||
$this->get_store()->purge();
|
||||
// 3. Optionally pruge any stacked loaders.
|
||||
if ($this->get_loader()) {
|
||||
$this->get_loader()->purge();
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Vendored
+14
@@ -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.
|
||||
*
|
||||
|
||||
Vendored
+1
-1
@@ -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 {
|
||||
|
||||
Vendored
+4
-3
@@ -346,7 +346,7 @@ class cache_config_writer extends cache_config {
|
||||
'plugin' => 'file',
|
||||
'configuration' => array(),
|
||||
'features' => cachestore_file::get_supported_features(),
|
||||
'modes' => cache_store::MODE_APPLICATION,
|
||||
'modes' => cachestore_file::get_supported_modes(),
|
||||
'default' => true,
|
||||
),
|
||||
'default_session' => array(
|
||||
@@ -354,7 +354,7 @@ class cache_config_writer extends cache_config {
|
||||
'plugin' => 'session',
|
||||
'configuration' => array(),
|
||||
'features' => cachestore_session::get_supported_features(),
|
||||
'modes' => cache_store::MODE_SESSION,
|
||||
'modes' => cachestore_session::get_supported_modes(),
|
||||
'default' => true,
|
||||
),
|
||||
'default_request' => array(
|
||||
@@ -362,7 +362,7 @@ class cache_config_writer extends cache_config {
|
||||
'plugin' => 'static',
|
||||
'configuration' => array(),
|
||||
'features' => cachestore_static::get_supported_features(),
|
||||
'modes' => cache_store::MODE_REQUEST,
|
||||
'modes' => cachestore_static::get_supported_modes(),
|
||||
'default' => true,
|
||||
)
|
||||
);
|
||||
@@ -581,6 +581,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'])) {
|
||||
|
||||
Vendored
+46
-6
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+27
-2
@@ -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;
|
||||
}
|
||||
}
|
||||
Vendored
+384
-4
@@ -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_EXUST);
|
||||
$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_EXUST);
|
||||
$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'));
|
||||
}
|
||||
}
|
||||
Vendored
+1
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user