diff --git a/cache/classes/application_cache.php b/cache/classes/application_cache.php new file mode 100644 index 00000000000..ee7a3b62f5e --- /dev/null +++ b/cache/classes/application_cache.php @@ -0,0 +1,330 @@ +. + +/** + * An application cache. + * + * This class is used for application caches returned by the cache::make methods. + * On top of the standard functionality it also allows locking to be required and or manually operated. + * + * 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. + * + * @internal don't use me directly. + * + * @package core + * @category cache + * @copyright 2012 Sam Hemelryk + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class cache_application extends cache implements cache_loader_with_locking { + + /** + * Lock identifier. + * This is used to ensure the lock belongs to the cache instance + definition + user. + * @var string + */ + protected $lockidentifier; + + /** + * Gets set to true if the cache's primary store natively supports locking. + * If it does then we use that, otherwise we need to instantiate a second store to use for locking. + * @var cache_store + */ + protected $nativelocking = null; + + /** + * Gets set to true if the cache is going to be using locking. + * This isn't a requirement, it doesn't need to use locking (most won't) and this bool is used to quickly check things. + * If required then locking will be forced for the get|set|delete operation. + * @var bool + */ + protected $requirelocking = false; + + /** + * Gets set to true if the cache writes (set|delete) must have a manual lock created first + * @var bool + */ + protected $requirelockingbeforewrite = false; + + /** + * Gets set to a cache_store to use for locking if the caches primary store doesn't support locking natively. + * @var cache_lock_interface + */ + protected $cachelockinstance; + + /** + * Store a list of locks acquired by this process. + * @var array + */ + protected $locks; + + /** + * Overrides the cache construct method. + * + * You should not call this method from your code, instead you should use the cache::make methods. + * + * @param cache_definition $definition + * @param cache_store $store + * @param cache_loader|cache_data_source $loader + */ + public function __construct(cache_definition $definition, cache_store $store, $loader = null) { + parent::__construct($definition, $store, $loader); + $this->nativelocking = $this->store_supports_native_locking(); + if ($definition->require_locking()) { + $this->requirelocking = true; + $this->requirelockingbeforewrite = $definition->require_locking_before_write(); + } + + $this->handle_invalidation_events(); + } + + /** + * Returns the identifier to use + * + * @staticvar int $instances Counts the number of instances. Used as part of the lock identifier. + * @return string + */ + public function get_identifier() { + static $instances = 0; + if ($this->lockidentifier === null) { + $this->lockidentifier = md5( + $this->get_definition()->generate_definition_hash() . + sesskey() . + $instances++ . + 'cache_application' + ); + } + return $this->lockidentifier; + } + + /** + * Fixes the instance up after a clone. + */ + public function __clone() { + // Force a new idenfitier. + $this->lockidentifier = null; + } + + /** + * Acquires a lock on the given key. + * + * This is done automatically if the definition requires it. + * It is recommended to use a definition if you want to have locking although it is possible to do locking without having + * it required by the definition. + * The problem with such an approach is that you cannot ensure that code will consistently use locking. You will need to + * rely on the integrators review skills. + * + * @param string|int $key The key as given to get|set|delete + * @return bool Always returns true + * @throws moodle_exception If the lock cannot be obtained + */ + public function acquire_lock($key) { + $releaseparent = false; + try { + if ($this->get_loader() !== false) { + $this->get_loader()->acquire_lock($key); + // We need to release this lock later if the lock is not successful. + $releaseparent = true; + } + $hashedkey = cache_helper::hash_key($key, $this->get_definition()); + $before = microtime(true); + if ($this->nativelocking) { + $lock = $this->get_store()->acquire_lock($hashedkey, $this->get_identifier()); + } else { + $this->ensure_cachelock_available(); + $lock = $this->cachelockinstance->lock($hashedkey, $this->get_identifier()); + } + $after = microtime(true); + if ($lock) { + $this->locks[$hashedkey] = $lock; + if (MDL_PERF || $this->perfdebug) { + \core\lock\timing_wrapper_lock_factory::record_lock_data($after, $before, + $this->get_definition()->get_id(), $hashedkey, $lock, $this->get_identifier() . $hashedkey); + } + $releaseparent = false; + return true; + } else { + throw new moodle_exception('ex_unabletolock', 'cache', '', null, + 'store: ' . get_class($this->get_store()) . ', lock: ' . $hashedkey); + } + } finally { + // Release the parent lock if we acquired it, then threw an exception. + if ($releaseparent) { + $this->get_loader()->release_lock($key); + } + } + } + + /** + * Checks if this cache has a lock on the given key. + * + * @param string|int $key The key as given to get|set|delete + * @return bool|null Returns true if there is a lock and this cache has it, null if no one has a lock on that key, false if + * someone else has the lock. + */ + public function check_lock_state($key) { + $key = cache_helper::hash_key($key, $this->get_definition()); + if (!empty($this->locks[$key])) { + return true; // Shortcut to save having to make a call to the cache store if the lock is held by this process. + } + if ($this->nativelocking) { + return $this->get_store()->check_lock_state($key, $this->get_identifier()); + } else { + $this->ensure_cachelock_available(); + return $this->cachelockinstance->check_state($key, $this->get_identifier()); + } + } + + /** + * Releases the lock this cache has on the given key + * + * @param string|int $key + * @return bool True if the operation succeeded, false otherwise. + */ + public function release_lock($key) { + $loaderkey = $key; + $key = cache_helper::hash_key($key, $this->get_definition()); + if ($this->nativelocking) { + $released = $this->get_store()->release_lock($key, $this->get_identifier()); + } else { + $this->ensure_cachelock_available(); + $released = $this->cachelockinstance->unlock($key, $this->get_identifier()); + } + if ($released && array_key_exists($key, $this->locks)) { + unset($this->locks[$key]); + if (MDL_PERF || $this->perfdebug) { + \core\lock\timing_wrapper_lock_factory::record_lock_released_data($this->get_identifier() . $key); + } + } + if ($this->get_loader() !== false) { + $this->get_loader()->release_lock($loaderkey); + } + return $released; + } + + /** + * Ensure that the dedicated lock store is ready to go. + * + * This should only happen if the cache store doesn't natively support it. + */ + protected function ensure_cachelock_available() { + if ($this->cachelockinstance === null) { + $this->cachelockinstance = cache_helper::get_cachelock_for_store($this->get_store()); + } + } + + /** + * 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. + * @param int $version Version number + * @param mixed $data The data to set against the key. + * @param bool $setparents If true, sets all parent loaders, otherwise only this one + * @return bool True on success, false otherwise. + * @throws coding_exception If a required lock has not beeen acquired + */ + protected function set_implementation($key, int $version, $data, bool $setparents = true): bool { + if ($this->requirelockingbeforewrite && !$this->check_lock_state($key)) { + throw new coding_exception('Attempted to set cache key "' . $key . '" without a lock. ' + . 'Locking before writes is required for ' . $this->get_definition()->get_id()); + } + return parent::set_implementation($key, $version, $data, $setparents); + } + + /** + * 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. + * @throws coding_exception If a required lock has not beeen acquired + */ + public function set_many(array $keyvaluearray) { + if ($this->requirelockingbeforewrite) { + foreach ($keyvaluearray as $key => $value) { + if (!$this->check_lock_state($key)) { + throw new coding_exception('Attempted to set cache key "' . $key . '" without a lock. ' + . 'Locking before writes is required for ' . $this->get_definition()->get_id()); + } + } + } + return parent::set_many($keyvaluearray); + } + + /** + * 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. + * @throws coding_exception If a required lock has not beeen acquired + */ + public function delete($key, $recurse = true) { + if ($this->requirelockingbeforewrite && !$this->check_lock_state($key)) { + throw new coding_exception('Attempted to delete cache key "' . $key . '" without a lock. ' + . 'Locking before writes is required for ' . $this->get_definition()->get_id()); + } + return parent::delete($key, $recurse); + } + + /** + * 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. + * @throws coding_exception If a required lock has not beeen acquired + */ + public function delete_many(array $keys, $recurse = true) { + if ($this->requirelockingbeforewrite) { + foreach ($keys as $key) { + if (!$this->check_lock_state($key)) { + throw new coding_exception('Attempted to delete cache key "' . $key . '" without a lock. ' + . 'Locking before writes is required for ' . $this->get_definition()->get_id()); + } + } + } + return parent::delete_many($keys, $recurse); + } +} diff --git a/cache/classes/loaders.php b/cache/classes/cache.php similarity index 62% rename from cache/classes/loaders.php rename to cache/classes/cache.php index 36fdb4da40e..72469ff9552 100644 --- a/cache/classes/loaders.php +++ b/cache/classes/cache.php @@ -14,20 +14,6 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - * Cache loaders - * - * This file is part of Moodle's cache API, affectionately called MUC. - * It contains the components that are required in order to use caching. - * - * @package core - * @category cache - * @copyright 2012 Sam Hemelryk - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - /** * The main cache class. * @@ -1563,899 +1549,3 @@ class cache implements cache_loader { public function purge_current_user() { } } - -/** - * An application cache. - * - * This class is used for application caches returned by the cache::make methods. - * On top of the standard functionality it also allows locking to be required and or manually operated. - * - * 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. - * - * @internal don't use me directly. - * - * @package core - * @category cache - * @copyright 2012 Sam Hemelryk - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class cache_application extends cache implements cache_loader_with_locking { - - /** - * Lock identifier. - * This is used to ensure the lock belongs to the cache instance + definition + user. - * @var string - */ - protected $lockidentifier; - - /** - * Gets set to true if the cache's primary store natively supports locking. - * If it does then we use that, otherwise we need to instantiate a second store to use for locking. - * @var cache_store - */ - protected $nativelocking = null; - - /** - * Gets set to true if the cache is going to be using locking. - * This isn't a requirement, it doesn't need to use locking (most won't) and this bool is used to quickly check things. - * If required then locking will be forced for the get|set|delete operation. - * @var bool - */ - protected $requirelocking = false; - - /** - * Gets set to true if the cache writes (set|delete) must have a manual lock created first - * @var bool - */ - protected $requirelockingbeforewrite = false; - - /** - * Gets set to a cache_store to use for locking if the caches primary store doesn't support locking natively. - * @var cache_lock_interface - */ - protected $cachelockinstance; - - /** - * Store a list of locks acquired by this process. - * @var array - */ - protected $locks; - - /** - * Overrides the cache construct method. - * - * You should not call this method from your code, instead you should use the cache::make methods. - * - * @param cache_definition $definition - * @param cache_store $store - * @param cache_loader|cache_data_source $loader - */ - public function __construct(cache_definition $definition, cache_store $store, $loader = null) { - parent::__construct($definition, $store, $loader); - $this->nativelocking = $this->store_supports_native_locking(); - if ($definition->require_locking()) { - $this->requirelocking = true; - $this->requirelockingbeforewrite = $definition->require_locking_before_write(); - } - - $this->handle_invalidation_events(); - } - - /** - * Returns the identifier to use - * - * @staticvar int $instances Counts the number of instances. Used as part of the lock identifier. - * @return string - */ - public function get_identifier() { - static $instances = 0; - if ($this->lockidentifier === null) { - $this->lockidentifier = md5( - $this->get_definition()->generate_definition_hash() . - sesskey() . - $instances++ . - 'cache_application' - ); - } - return $this->lockidentifier; - } - - /** - * Fixes the instance up after a clone. - */ - public function __clone() { - // Force a new idenfitier. - $this->lockidentifier = null; - } - - /** - * Acquires a lock on the given key. - * - * This is done automatically if the definition requires it. - * It is recommended to use a definition if you want to have locking although it is possible to do locking without having - * it required by the definition. - * The problem with such an approach is that you cannot ensure that code will consistently use locking. You will need to - * rely on the integrators review skills. - * - * @param string|int $key The key as given to get|set|delete - * @return bool Always returns true - * @throws moodle_exception If the lock cannot be obtained - */ - public function acquire_lock($key) { - $releaseparent = false; - try { - if ($this->get_loader() !== false) { - $this->get_loader()->acquire_lock($key); - // We need to release this lock later if the lock is not successful. - $releaseparent = true; - } - $hashedkey = cache_helper::hash_key($key, $this->get_definition()); - $before = microtime(true); - if ($this->nativelocking) { - $lock = $this->get_store()->acquire_lock($hashedkey, $this->get_identifier()); - } else { - $this->ensure_cachelock_available(); - $lock = $this->cachelockinstance->lock($hashedkey, $this->get_identifier()); - } - $after = microtime(true); - if ($lock) { - $this->locks[$hashedkey] = $lock; - if (MDL_PERF || $this->perfdebug) { - \core\lock\timing_wrapper_lock_factory::record_lock_data($after, $before, - $this->get_definition()->get_id(), $hashedkey, $lock, $this->get_identifier() . $hashedkey); - } - $releaseparent = false; - return true; - } else { - throw new moodle_exception('ex_unabletolock', 'cache', '', null, - 'store: ' . get_class($this->get_store()) . ', lock: ' . $hashedkey); - } - } finally { - // Release the parent lock if we acquired it, then threw an exception. - if ($releaseparent) { - $this->get_loader()->release_lock($key); - } - } - } - - /** - * Checks if this cache has a lock on the given key. - * - * @param string|int $key The key as given to get|set|delete - * @return bool|null Returns true if there is a lock and this cache has it, null if no one has a lock on that key, false if - * someone else has the lock. - */ - public function check_lock_state($key) { - $key = cache_helper::hash_key($key, $this->get_definition()); - if (!empty($this->locks[$key])) { - return true; // Shortcut to save having to make a call to the cache store if the lock is held by this process. - } - if ($this->nativelocking) { - return $this->get_store()->check_lock_state($key, $this->get_identifier()); - } else { - $this->ensure_cachelock_available(); - return $this->cachelockinstance->check_state($key, $this->get_identifier()); - } - } - - /** - * Releases the lock this cache has on the given key - * - * @param string|int $key - * @return bool True if the operation succeeded, false otherwise. - */ - public function release_lock($key) { - $loaderkey = $key; - $key = cache_helper::hash_key($key, $this->get_definition()); - if ($this->nativelocking) { - $released = $this->get_store()->release_lock($key, $this->get_identifier()); - } else { - $this->ensure_cachelock_available(); - $released = $this->cachelockinstance->unlock($key, $this->get_identifier()); - } - if ($released && array_key_exists($key, $this->locks)) { - unset($this->locks[$key]); - if (MDL_PERF || $this->perfdebug) { - \core\lock\timing_wrapper_lock_factory::record_lock_released_data($this->get_identifier() . $key); - } - } - if ($this->get_loader() !== false) { - $this->get_loader()->release_lock($loaderkey); - } - return $released; - } - - /** - * Ensure that the dedicated lock store is ready to go. - * - * This should only happen if the cache store doesn't natively support it. - */ - protected function ensure_cachelock_available() { - if ($this->cachelockinstance === null) { - $this->cachelockinstance = cache_helper::get_cachelock_for_store($this->get_store()); - } - } - - /** - * 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. - * @param int $version Version number - * @param mixed $data The data to set against the key. - * @param bool $setparents If true, sets all parent loaders, otherwise only this one - * @return bool True on success, false otherwise. - * @throws coding_exception If a required lock has not beeen acquired - */ - protected function set_implementation($key, int $version, $data, bool $setparents = true): bool { - if ($this->requirelockingbeforewrite && !$this->check_lock_state($key)) { - throw new coding_exception('Attempted to set cache key "' . $key . '" without a lock. ' - . 'Locking before writes is required for ' . $this->get_definition()->get_id()); - } - return parent::set_implementation($key, $version, $data, $setparents); - } - - /** - * 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. - * @throws coding_exception If a required lock has not beeen acquired - */ - public function set_many(array $keyvaluearray) { - if ($this->requirelockingbeforewrite) { - foreach ($keyvaluearray as $key => $value) { - if (!$this->check_lock_state($key)) { - throw new coding_exception('Attempted to set cache key "' . $key . '" without a lock. ' - . 'Locking before writes is required for ' . $this->get_definition()->get_id()); - } - } - } - return parent::set_many($keyvaluearray); - } - - /** - * 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. - * @throws coding_exception If a required lock has not beeen acquired - */ - public function delete($key, $recurse = true) { - if ($this->requirelockingbeforewrite && !$this->check_lock_state($key)) { - throw new coding_exception('Attempted to delete cache key "' . $key . '" without a lock. ' - . 'Locking before writes is required for ' . $this->get_definition()->get_id()); - } - return parent::delete($key, $recurse); - } - - /** - * 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. - * @throws coding_exception If a required lock has not beeen acquired - */ - public function delete_many(array $keys, $recurse = true) { - if ($this->requirelockingbeforewrite) { - foreach ($keys as $key) { - if (!$this->check_lock_state($key)) { - throw new coding_exception('Attempted to delete cache key "' . $key . '" without a lock. ' - . 'Locking before writes is required for ' . $this->get_definition()->get_id()); - } - } - } - return parent::delete_many($keys, $recurse); - } -} - -/** - * A session cache. - * - * 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 exist. - * Because of this we don't ever use the static acceleration array. - * 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. - * - * @todo we should support locking in the session as well. Should be pretty simple to set up. - * - * @internal don't use me directly. - * @method cache_store|cache_is_searchable get_store() Returns the cache store which must implement both cache_is_searchable. - * - * @package core - * @category cache - * @copyright 2012 Sam Hemelryk - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class cache_session extends cache { - /** - * The user the session has been established for. - * @var int - */ - protected static $loadeduserid = null; - - /** - * The userid this cache is currently using. - * @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_'; - - /** - * This is the key used to track last access. - */ - const LASTACCESS = '__lastaccess__'; - - /** - * Override the cache::construct method. - * - * This function gets overriden so that we can process any invalidation events if need be. - * If the definition doesn't have any invalidation events then this occurs exactly as it would for the cache class. - * Otherwise we look at the last invalidation time and then check the invalidation data for events that have occured - * between then now. - * - * You should not call this method from your code, instead you should use the cache::make methods. - * - * @param cache_definition $definition - * @param cache_store $store - * @param cache_loader|cache_data_source $loader - */ - public function __construct(cache_definition $definition, cache_store $store, $loader = null) { - // First up copy the loadeduserid to the current user id. - $this->currentuserid = self::$loadeduserid; - $this->set_session_id(); - parent::__construct($definition, $store, $loader); - - // This will trigger check tracked user. If this gets removed a call to that will need to be added here in its place. - $this->set(self::LASTACCESS, cache::now()); - - $this->handle_invalidation_events(); - } - - /** - * Sets the session id for the loader. - */ - protected function set_session_id() { - $this->sessionid = preg_replace('#[^a-zA-Z0-9_]#', '_', session_id()); - } - - /** - * Returns the prefix used for all keys. - * @return string - */ - protected function get_key_prefix() { - return 'u'.$this->currentuserid.'_'.$this->sessionid; - } - - /** - * Parses the key turning it into a string (or array is required) suitable to be passed to the cache store. - * - * 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) { - $prefix = $this->get_key_prefix(); - if ($key === self::LASTACCESS) { - return $key.$prefix; - } - return $prefix.'_'.parent::parse_key($key); - } - - /** - * Check that this cache instance is tracking the current user. - */ - protected function check_tracked_user() { - if (isset($_SESSION['USER']->id) && $_SESSION['USER']->id !== null) { - // Get the id of the current user. - $new = $_SESSION['USER']->id; - } else { - // No user set up yet. - $new = 0; - } - if ($new !== self::$loadeduserid) { - // 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(); - } - self::$loadeduserid = $new; - $this->currentuserid = $new; - } else if ($new !== $this->currentuserid) { - // The current user matches the loaded user but not the user last used by this cache. - $this->purge_current_user(); - $this->currentuserid = $new; - } - } - - /** - * Purges the session cache of all data belonging to the current user. - */ - public function purge_current_user() { - $keys = $this->get_store()->find_by_prefix($this->get_key_prefix()); - $this->get_store()->delete_many($keys); - } - - /** - * 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 $requiredversion Minimum required version of the data or cache::VERSION_NONE - * @param int $strictness One of IGNORE_MISSING | MUST_EXIST - * @param mixed &$actualversion If specified, will be set to the actual version number retrieved - * @return mixed|false The data from the cache or false if the key did not exist within the cache. - * @throws coding_exception - */ - protected function get_implementation($key, int $requiredversion, int $strictness, &$actualversion = null) { - // Check the tracked user. - $this->check_tracked_user(); - - // Use parent code. - return parent::get_implementation($key, $requiredversion, $strictness, $actualversion); - } - - /** - * 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(); - $loader = $this->get_loader(); - if ($loader !== false) { - // We have a loader available set it there as well. - // We have to let the loader do its own parsing of data as it may be unique. - $loader->set($key, $data); - } - if (is_object($data) && $data instanceof cacheable_object) { - $data = new cache_cached_object($data); - } else if (!$this->get_store()->supports_dereferencing_objects() && !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() && !$this->store_supports_native_ttl()) { - $data = new cache_ttl_wrapper($data, $this->get_definition()->get_ttl()); - } - $success = $this->get_store()->set($this->parse_key($key), $data); - if ($this->perfdebug) { - cache_helper::record_cache_set($this->get_store(), $this->get_definition(), 1, - $this->get_store()->get_last_io_bytes()); - } - return $success; - } - - /** - * 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) { - $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); - } - return $this->get_store()->delete($parsedkey); - } - - /** - * 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 coding_exception - */ - public function get_many(array $keys, $strictness = IGNORE_MISSING) { - $this->check_tracked_user(); - $parsedkeys = array(); - $keymap = array(); - foreach ($keys as $key) { - $parsedkey = $this->parse_key($key); - $parsedkeys[$key] = $parsedkey; - $keymap[$parsedkey] = $key; - } - $result = $this->get_store()->get_many($parsedkeys); - if ($this->perfdebug) { - $readbytes = $this->get_store()->get_last_io_bytes(); - } - $return = array(); - $missingkeys = array(); - $hasmissingkeys = false; - foreach ($result as $parsedkey => $value) { - $key = $keymap[$parsedkey]; - if ($value instanceof cache_ttl_wrapper) { - /* @var cache_ttl_wrapper $value */ - if ($value->has_expired()) { - $this->delete($keymap[$parsedkey]); - $value = false; - } else { - $value = $value->data; - } - } - if ($value instanceof cache_cached_object) { - /* @var cache_cached_object $value */ - $value = $value->restore_object(); - } else if (!$this->get_store()->supports_dereferencing_objects() && !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); - } - $return[$key] = $value; - if ($value === false) { - $hasmissingkeys = true; - $missingkeys[$parsedkey] = $key; - } - } - if ($hasmissingkeys) { - // We've got missing keys - we've got to check any loaders or data sources. - $loader = $this->get_loader(); - $datasource = $this->get_datasource(); - if ($loader !== false) { - foreach ($loader->get_many($missingkeys) as $key => $value) { - if ($value !== false) { - $return[$key] = $value; - unset($missingkeys[$parsedkeys[$key]]); - } - } - } - $hasmissingkeys = count($missingkeys) > 0; - if ($datasource !== false && $hasmissingkeys) { - // We're still missing keys but we've got a datasource. - foreach ($datasource->load_many_for_cache($missingkeys) as $key => $value) { - if ($value !== false) { - $return[$key] = $value; - unset($missingkeys[$parsedkeys[$key]]); - } - } - $hasmissingkeys = count($missingkeys) > 0; - } - } - if ($hasmissingkeys && $strictness === MUST_EXIST) { - throw new coding_exception('Requested key did not exist in any cache stores and could not be loaded.'); - } - if ($this->perfdebug) { - $hits = 0; - $misses = 0; - foreach ($return as $value) { - if ($value === false) { - $misses++; - } else { - $hits++; - } - } - cache_helper::record_cache_hit($this->get_store(), $this->get_definition(), $hits, $readbytes); - cache_helper::record_cache_miss($this->get_store(), $this->get_definition(), $misses); - } - 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) { - $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); - } - return $this->get_store()->delete_many($parsedkeys); - } - - /** - * 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(); - $loader = $this->get_loader(); - if ($loader !== false) { - // We have a loader available set it there as well. - // We have to let the loader do its own parsing of data as it may be unique. - $loader->set_many($keyvaluearray); - } - $data = array(); - $definitionid = $this->get_definition()->get_ttl(); - $simulatettl = $this->has_a_ttl() && !$this->store_supports_native_ttl(); - foreach ($keyvaluearray as $key => $value) { - if (is_object($value) && $value instanceof cacheable_object) { - $value = new cache_cached_object($value); - } else if (!$this->get_store()->supports_dereferencing_objects() && !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, $definitionid); - } - $data[$key] = array( - 'key' => $this->parse_key($key), - 'value' => $value - ); - } - $successfullyset = $this->get_store()->set_many($data); - if ($this->perfdebug && $successfullyset) { - cache_helper::record_cache_set($this->get_store(), $this->get_definition(), $successfullyset, - $this->get_store()->get_last_io_bytes()); - } - return $successfullyset; - } - - /** - * Purges the cache store, and loader if there is one. - * - * @return bool True on success, false otherwise - */ - public function purge() { - $this->get_store()->purge(); - 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: - *
    - *
  1. Attempt to retrieve the information.
  2. - *
  3. Generate the information.
  4. - *
  5. Attempt to set the information
  6. - *
- * - * 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); - $store = $this->get_store(); - if ($this->has_a_ttl() && !$this->store_supports_native_ttl()) { - // The data has a TTL and the store doesn't support it natively. - // We must fetch the data and expect a ttl wrapper. - $data = $store->get($parsedkey); - $has = ($data instanceof cache_ttl_wrapper && !$data->has_expired()); - } else if (!$this->store_supports_key_awareness()) { - // The store doesn't support key awareness, get the data and check it manually... puke. - // Either no TTL is set of the store supports its handling natively. - $data = $store->get($parsedkey); - $has = ($data !== false); - } else { - // The store supports key awareness, this is easy! - // Either no TTL is set of the store supports its handling natively. - /* @var cache_store|cache_is_key_aware $store */ - $has = $store->has($parsedkey); - } - if (!$has && $tryloadifpossible) { - $result = null; - if ($this->get_loader() !== false) { - $result = $this->get_loader()->get($parsedkey); - } 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(); - if (($this->has_a_ttl() && !$this->store_supports_native_ttl()) || !$this->store_supports_key_awareness()) { - foreach ($keys as $key) { - if (!$this->has($key)) { - return false; - } - } - return true; - } - // The cache must be key aware and if support native ttl if it a ttl is set. - /* @var cache_store|cache_is_key_aware $store */ - $store = $this->get_store(); - return $store->has_all(array_map(array($this, 'parse_key'), $keys)); - } - - /** - * 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) { - if (($this->has_a_ttl() && !$this->store_supports_native_ttl()) || !$this->store_supports_key_awareness()) { - foreach ($keys as $key) { - if ($this->has($key)) { - return true; - } - } - return false; - } - /* @var cache_store|cache_is_key_aware $store */ - $store = $this->get_store(); - return $store->has_any(array_map(array($this, 'parse_key'), $keys)); - } - - /** - * The session loader never uses static acceleration. - * Instead it stores things in the static $session variable. Shared between all session loaders. - * - * @return bool - */ - protected function use_static_acceleration() { - return false; - } -} - -/** - * An request cache. - * - * This class is used for request caches returned by the cache::make methods. - * - * 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. - * - * @internal don't use me directly. - * - * @package core - * @category cache - * @copyright 2012 Sam Hemelryk - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class cache_request extends cache { - // This comment appeases code pre-checker ;) ! -} diff --git a/cache/classes/cache_lock_interface.php b/cache/classes/cache_lock_interface.php new file mode 100644 index 00000000000..fc7f8400380 --- /dev/null +++ b/cache/classes/cache_lock_interface.php @@ -0,0 +1,74 @@ +. + +/** + * Cache lock interface + * + * This interface needs to be inherited by all cache lock plugins. + */ +interface cache_lock_interface { + /** + * Constructs an instance of the cache lock given its name and its configuration data + * + * @param string $name The unique name of the lock instance + * @param array $configuration + */ + public function __construct($name, array $configuration = array()); + + /** + * Acquires a lock on a given key. + * + * @param string $key The key to acquire a lock for. + * @param string $ownerid An unique identifier for the owner of this lock. It is entirely optional for the cache lock plugin + * to use this. Each implementation can decide for themselves. + * @param bool $block If set to true the application will wait until a lock can be acquired + * @return bool True if the lock can be acquired false otherwise. + */ + public function lock($key, $ownerid, $block = false); + + /** + * Releases the lock held on a certain key. + * + * @param string $key The key to release the lock for. + * @param string $ownerid An unique identifier for the owner of this lock. It is entirely optional for the cache lock plugin + * to use this. Each implementation can decide for themselves. + * @param bool $forceunlock If set to true the lock will be removed if it exists regardless of whether or not we own it. + */ + public function unlock($key, $ownerid, $forceunlock = false); + + /** + * Checks the state of the given key. + * + * Returns true if the key is locked and belongs to the ownerid. + * Returns false if the key is locked but does not belong to the ownerid. + * Returns null if there is no lock + * + * @param string $key The key we are checking for. + * @param string $ownerid The identifier so we can check if we have the lock or if it is someone else. + * @return bool True if this code has the lock, false if there is a lock but this code doesn't have it, null if there + * is no lock. + */ + public function check_state($key, $ownerid); + + /** + * Cleans up any left over locks. + * + * This function MUST clean up any locks that have been acquired and not released during processing. + * Although the situation of acquiring a lock and not releasing it should be insanely rare we need to deal with it. + * Things such as unfortunate timeouts etc could cause this situation. + */ + public function __destruct(); +} diff --git a/cache/classes/cacheable_object_array.php b/cache/classes/cacheable_object_array.php new file mode 100644 index 00000000000..0267291337d --- /dev/null +++ b/cache/classes/cacheable_object_array.php @@ -0,0 +1,92 @@ +. + +/** + * An array of cacheable objects. + * + * This class allows a developer to create an array of cacheable objects and store that. + * The cache API doesn't check items within an array to see whether they are cacheable. Such a check would be very costly to both + * arrays using cacheable object and those that don't. + * Instead the developer must explicitly use a cacheable_object_array instance. + * + * The following is one example of how this class can be used. + * + * $data = array(); + * $data[] = new cacheable_object('one'); + * $data[] = new cacheable_object('two'); + * $data[] = new cacheable_object('three'); + * $cache->set(new cacheable_object_array($data)); + * + * Another example would be + * + * $data = new cacheable_object_array(); + * $data[] = new cacheable_object('one'); + * $data[] = new cacheable_object('two'); + * $data[] = new cacheable_object('three'); + * $cache->set($data); + * + * + * @copyright 2012 Sam Hemelryk + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class cacheable_object_array extends ArrayObject implements cacheable_object { + + /** + * Constructs a new array object instance. + * @param array $items + */ + final public function __construct(array $items = array()) { + parent::__construct($items, ArrayObject::STD_PROP_LIST); + } + + /** + * Returns the data to cache for this object. + * + * @return array An array of cache_cached_object instances. + * @throws coding_exception + */ + final public function prepare_to_cache() { + $result = array(); + foreach ($this as $key => $value) { + if ($value instanceof cacheable_object) { + $value = new cache_cached_object($value); + } else { + throw new coding_exception('Only cacheable_object instances can be added to a cacheable_array'); + } + $result[$key] = $value; + } + return $result; + } + + /** + * Returns the cacheable_object_array that was originally sent to the cache. + * + * @param array $data + * @return cacheable_object_array + * @throws coding_exception + */ + final public static function wake_from_cache($data) { + if (!is_array($data)) { + throw new coding_exception('Invalid data type when reviving cacheable_array data'); + } + $result = array(); + foreach ($data as $key => $value) { + $result[$key] = $value->restore_object(); + } + $class = __CLASS__; + return new $class($result); + } +} diff --git a/cache/classes/cacheable_object_interface.php b/cache/classes/cacheable_object_interface.php new file mode 100644 index 00000000000..a4fd5e9a419 --- /dev/null +++ b/cache/classes/cacheable_object_interface.php @@ -0,0 +1,43 @@ +. + +/** + * Cacheable object. + * + * This interface can be implemented by any class that is going to be passed into a cache and allows it to take control of the + * structure and the information about to be cached, as well as how to deal with it when it is retrieved from a cache. + * Think of it like serialisation and the __sleep and __wakeup methods. + * This is used because cache stores are responsible for how they interact with data and what they do when storing it. This + * interface ensures there is always a guaranteed action. + */ +interface cacheable_object { + + /** + * Prepares the object for caching. Works like the __sleep method. + * + * @return mixed The data to cache, can be anything except a class that implements the cacheable_object... that would + * be dumb. + */ + public function prepare_to_cache(); + + /** + * Takes the data provided by prepare_to_cache and reinitialises an instance of the associated from it. + * + * @param mixed $data + * @return object The instance for the given data. + */ + public static function wake_from_cache($data); +} diff --git a/cache/classes/cached_object.php b/cache/classes/cached_object.php new file mode 100644 index 00000000000..aec2f847d85 --- /dev/null +++ b/cache/classes/cached_object.php @@ -0,0 +1,58 @@ +. + +/** + * A cached object wrapper. + * + * This class gets used when the data is an object that has implemented the cacheable_object interface. + * + * @package core + * @category cache + * @copyright 2012 Sam Hemelryk + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class cache_cached_object { + + /** + * The class of the cacheable object + * @var string + */ + protected $class; + + /** + * The data returned by the cacheable_object prepare_to_cache method. + * @var mixed + */ + protected $data; + + /** + * Constructs a cached object wrapper. + * @param cacheable_object $obj + */ + public function __construct(cacheable_object $obj) { + $this->class = get_class($obj); + $this->data = $obj->prepare_to_cache(); + } + + /** + * Restores the data as an instance of the cacheable_object class. + * @return object + */ + public function restore_object() { + $class = $this->class; + return $class::wake_from_cache($this->data); + } +} diff --git a/cache/classes/config.php b/cache/classes/config.php index ad192cae74b..d51e9322713 100644 --- a/cache/classes/config.php +++ b/cache/classes/config.php @@ -14,20 +14,6 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - * Cache configuration reader - * - * This file is part of Moodle's cache API, affectionately called MUC. - * It contains the components that are requried in order to use caching. - * - * @package core - * @category cache - * @copyright 2012 Sam Hemelryk - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - /** * Cache configuration reader. * @@ -478,10 +464,10 @@ class cache_config { /** * Gets all of the stores that are to be used for the given definition. * - * @param cache_definition $definition - * @return array + * @param definition $definition + * @return array */ - public function get_stores_for_definition(cache_definition $definition) { + public function get_stores_for_definition(definition $definition) { // Check if MUC has been disabled. $factory = cache_factory::instance(); if ($factory->stores_disabled()) { diff --git a/cache/classes/configurable_cache_interface.php b/cache/classes/configurable_cache_interface.php new file mode 100644 index 00000000000..4cc554382d9 --- /dev/null +++ b/cache/classes/configurable_cache_interface.php @@ -0,0 +1,44 @@ +. + +/** + * Cache store feature: configurable. + * + * This feature should be implemented by all cache stores that are configurable when adding an instance. + * It requires the implementation of methods required to convert form data into the a configuration array for the + * store instance, and then the reverse converting configuration data into an array that can be used to set the + * data for the edit form. + * + * Can be implemented by classes already implementing cache_store. + */ +interface cache_is_configurable { + + /** + * Given the data from the add instance form this function creates a configuration array. + * + * @param stdClass $data + * @return array + */ + public static function config_get_configuration_array($data); + + /** + * Allows the cache store to set its data against the edit form before it is shown to the user. + * + * @param moodleform $editform + * @param array $config + */ + public static function config_set_edit_form_data(moodleform $editform, array $config); +} diff --git a/cache/classes/data_source_interface.php b/cache/classes/data_source_interface.php new file mode 100644 index 00000000000..a96a8d277b4 --- /dev/null +++ b/cache/classes/data_source_interface.php @@ -0,0 +1,60 @@ +. + +/** + * Cache Data Source. + * + * The cache data source interface can be implemented by any class within Moodle. + * If implemented then the class can be reference in a cache definition and will be used to load information that cannot be + * retrieved from the cache. As part of its retrieval that information will also be loaded into the cache. + * + * This allows developers to created a complete cache solution that can be used through code ensuring consistent cache + * interaction and loading. Allowing them in turn to centralise code and help keeps things more easily maintainable. + * + * Can be implemented by any class. + * + * @package core + * @category cache + * @copyright 2012 Sam Hemelryk + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +interface cache_data_source { + + /** + * Returns an instance of the data source class that the cache can use for loading data using the other methods + * specified by this interface. + * + * @param cache_definition $definition + * @return object + */ + public static function get_instance_for_cache(cache_definition $definition); + + /** + * Loads the data for the key provided ready formatted for caching. + * + * @param string|int $key The key to load. + * @return mixed What ever data should be returned, or false if it can't be loaded. + */ + public function load_for_cache($key); + + /** + * Loads several keys for the cache. + * + * @param array $keys An array of keys each of which will be string|int. + * @return array An array of matching data items. + */ + public function load_many_for_cache(array $keys); +} diff --git a/cache/classes/exception/cache_exception.php b/cache/classes/exception/cache_exception.php new file mode 100644 index 00000000000..3e1ae88c865 --- /dev/null +++ b/cache/classes/exception/cache_exception.php @@ -0,0 +1,39 @@ +. + +/** + * A cache exception class. Just allows people to catch cache exceptions. + * + * @package core + * @category cache + * @copyright 2012 Sam Hemelryk + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class cache_exception extends moodle_exception { + /** + * Constructs a new exception + * + * @param string $errorcode + * @param string $module + * @param string $link + * @param mixed $a + * @param mixed $debuginfo + */ + public function __construct($errorcode, $module = 'cache', $link = '', $a = null, $debuginfo = null) { + // This may appear like a useless override but you will notice that we have set a MUCH more useful default for $module. + parent::__construct($errorcode, $module, $link, $a, $debuginfo); + } +} diff --git a/cache/classes/factory.php b/cache/classes/factory.php index 116b1b36258..d2991a5d7f7 100644 --- a/cache/classes/factory.php +++ b/cache/classes/factory.php @@ -14,20 +14,6 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - * This file contains the cache factory class. - * - * This file is part of Moodle's cache API, affectionately called MUC. - * It contains the components that are requried in order to use caching. - * - * @package core - * @category cache - * @copyright 2012 Sam Hemelryk - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - /** * The cache factory class. * diff --git a/cache/classes/helper.php b/cache/classes/helper.php index a88372e77e1..689e5475c3d 100644 --- a/cache/classes/helper.php +++ b/cache/classes/helper.php @@ -14,20 +14,6 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - * Cache helper class - * - * This file is part of Moodle's cache API, affectionately called MUC. - * It contains the components that are requried in order to use caching. - * - * @package core - * @category cache - * @copyright 2012 Sam Hemelryk - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - /** * The cache helper class. * diff --git a/cache/classes/interfaces.php b/cache/classes/interfaces.php deleted file mode 100644 index e0dff287f64..00000000000 --- a/cache/classes/interfaces.php +++ /dev/null @@ -1,605 +0,0 @@ -. - -/** - * Cache API interfaces - * - * This file is part of Moodle's cache API, affectionately called MUC. - * It contains the components that are requried in order to use caching. - * - * @package core - * @category cache - * @copyright 2012 Sam Hemelryk - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - -/** - * Cache Loader. - * - * This cache loader interface provides the required structure for classes that wish to be interacted with as a - * means of accessing and interacting with a cache. - * - * Can be implemented by any class wishing to be a cache loader. - */ -interface cache_loader { - - /** - * Retrieves the value for the given key from the cache. - * - * @param string|int $key The key for the data being requested. - * @param int $strictness One of IGNORE_MISSING or MUST_EXIST. - * @return mixed The data retrieved from the cache, or false if the key did not exist within the cache. - * If MUST_EXIST was used then an exception will be thrown if the key does not exist within the cache. - */ - public function get($key, $strictness = IGNORE_MISSING); - - /** - * Retrieves the value and actual version for the given key, with at least the required version. - * - * If there is no value for the key, or there is a value but it doesn't have the required - * version, then this function will return false (or throw an exception if you set strictness - * to MUST_EXIST). - * - * This function can be used to make it easier to support localisable caches (where the cache - * could be stored on a local server as well as a shared cache). Specifying the version means - * that it will automatically retrieve the correct version if available, either from the local - * server or [if that has an older version] from the shared server. - * - * If the cached version is newer than specified version, it will be returned regardless. For - * example, if you request version 4, but the locally cached version is 5, it will be returned. - * If you request version 6, and the locally cached version is 5, then the system will look in - * higher-level caches (if any); if there still isn't a version 6 or greater, it will return - * null. - * - * You must use this function if you use set_versioned. - * - * @param string|int $key The key for the data being requested. - * @param int $requiredversion Minimum required version of the data - * @param int $strictness One of IGNORE_MISSING or MUST_EXIST. - * @param mixed $actualversion If specified, will be set to the actual version number retrieved - * @return mixed Data from the cache, or false if the key did not exist or was too old - */ - public function get_versioned($key, int $requiredversion, int $strictness = IGNORE_MISSING, &$actualversion = null); - - /** - * 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. - * @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. - */ - public function get_many(array $keys, $strictness = IGNORE_MISSING); - - /** - * 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. - * @param mixed $data The data to set against the key. - * @return bool True on success, false otherwise. - */ - public function set($key, $data); - - /** - * Sets the value for the given key with the given version. - * - * The cache does not store multiple versions - any existing version will be overwritten with - * this one. This function should only be used if there is a known 'current version' (e.g. - * stored in a database table). It only ensures that the cache does not return outdated data. - * - * This function can be used to help implement localisable caches (where the cache could be - * stored on a local server as well as a shared cache). The version will be recorded alongside - * the item and get_versioned will always return the correct version. - * - * The version number must be an integer that always increases. This could be based on the - * current time, or a stored value that increases by 1 each time it changes, etc. - * - * If you use this function you must use get_versioned to retrieve the data. - * - * @param string|int $key The key for the data being set. - * @param int $version Integer for the version of the data - * @param mixed $data The data to set against the key. - * @return bool True on success, false otherwise. - */ - public function set_versioned($key, int $version, $data): bool; - - /** - * 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); - - /** - * 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: - *
    - *
  1. Attempt to retrieve the information.
  2. - *
  3. Generate the information.
  4. - *
  5. Attempt to set the information
  6. - *
- * - * 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 - * @return bool True if the cache has the requested key, false otherwise. - */ - public function has($key); - - /** - * 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); - - /** - * 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); - - /** - * 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); - - /** - * 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); -} - -/** - * Cache Loader supporting locking. - * - * This interface should be given to classes already implementing cache_loader that also wish to support locking. - * It outlines the required structure for utilising locking functionality when using a cache. - * - * Can be implemented by any class already implementing the cache_loader interface. - */ -interface cache_loader_with_locking { - - /** - * Acquires a lock for the given key. - * - * Please note that this happens automatically if the cache definition requires locking. - * it is still made a public method so that adhoc caches can use it if they choose. - * However this doesn't guarantee consistent access. It will become the responsibility of the calling code to ensure - * locks are acquired, checked, and released. - * - * Prior to Moodle 4,3 this function used to return false if the lock cannot be obtained. It - * now always returns true, and throws an exception if the lock cannot be obtained. - * - * @param string|int $key - * @return bool Always returns true (for backwards compatibility) - * @throws moodle_exception If the lock cannot be obtained after a timeout - */ - public function acquire_lock($key); - - /** - * Checks if the cache loader owns the lock for the given key. - * - * Please note that this happens automatically if the cache definition requires locking. - * it is still made a public method so that adhoc caches can use it if they choose. - * However this doesn't guarantee consistent access. It will become the responsibility of the calling code to ensure - * locks are acquired, checked, and released. - * - * @param string|int $key - * @return bool True if this code has the lock, false if there is a lock but this code doesn't have it, - * null if there is no lock. - */ - public function check_lock_state($key); - - /** - * Releases the lock for the given key. - * - * Please note that this happens automatically if the cache definition requires locking. - * it is still made a public method so that adhoc caches can use it if they choose. - * However this doesn't guarantee consistent access. It will become the responsibility of the calling code to ensure - * locks are acquired, checked, and released. - * - * @param string|int $key - * @return bool True if the lock has been released, false if there was a problem releasing the lock. - */ - public function release_lock($key); -} - -/** - * Cache store feature: locking - * - * This is a feature that cache stores can implement if they wish to support locking themselves rather - * than having the cache loader handle it for them. - * - * Can be implemented by classes already implementing cache_store. - */ -interface cache_is_lockable { - - /** - * Acquires a lock on the given key for the given identifier. - * - * @param string $key The key we are locking. - * @param string $ownerid The identifier so we can check if we have the lock or if it is someone else. - * The use of this property is entirely optional and implementations can act as they like upon it. - * @return bool True if the lock could be acquired, false otherwise. - */ - public function acquire_lock($key, $ownerid); - - /** - * Test if there is already a lock for the given key and if there is whether it belongs to the calling code. - * - * @param string $key The key we are locking. - * @param string $ownerid The identifier so we can check if we have the lock or if it is someone else. - * @return bool True if this code has the lock, false if there is a lock but this code doesn't have it, null if there - * is no lock. - */ - public function check_lock_state($key, $ownerid); - - /** - * Releases the lock on the given key. - * - * @param string $key The key we are locking. - * @param string $ownerid The identifier so we can check if we have the lock or if it is someone else. - * The use of this property is entirely optional and implementations can act as they like upon it. - * @return bool True if the lock has been released, false if there was a problem releasing the lock. - */ - public function release_lock($key, $ownerid); -} - -/** - * Cache store feature: key awareness. - * - * This is a feature that cache stores and cache loaders can both choose to implement. - * If a cache store implements this then it will be made responsible for tests for items within the cache. - * If the cache store being used doesn't implement this then it will be the responsibility of the cache loader to use the - * equivalent get methods to mimick the functionality of these tests. - * - * Cache stores should only override these methods if they natively support such features or if they have a better performing - * means of performing these tests than the handling that would otherwise take place in the cache_loader. - * - * Can be implemented by classes already implementing cache_store. - */ -interface cache_is_key_aware { - - /** - * 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: - *
    - *
  1. Attempt to retrieve the information.
  2. - *
  3. Generate the information.
  4. - *
  5. Attempt to set the information
  6. - *
- * - * 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 - * @return bool True if the cache has the requested key, false otherwise. - */ - public function has($key); - - /** - * 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); - - /** - * 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); -} - -/** - * 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 Moodle 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. - * - * This feature should be implemented by all cache stores that are configurable when adding an instance. - * It requires the implementation of methods required to convert form data into the a configuration array for the - * store instance, and then the reverse converting configuration data into an array that can be used to set the - * data for the edit form. - * - * Can be implemented by classes already implementing cache_store. - */ -interface cache_is_configurable { - - /** - * Given the data from the add instance form this function creates a configuration array. - * - * @param stdClass $data - * @return array - */ - public static function config_get_configuration_array($data); - - /** - * Allows the cache store to set its data against the edit form before it is shown to the user. - * - * @param moodleform $editform - * @param array $config - */ - public static function config_set_edit_form_data(moodleform $editform, array $config); -} - -/** - * Cache Data Source. - * - * The cache data source interface can be implemented by any class within Moodle. - * If implemented then the class can be reference in a cache definition and will be used to load information that cannot be - * retrieved from the cache. As part of its retrieval that information will also be loaded into the cache. - * - * This allows developers to created a complete cache solution that can be used through code ensuring consistent cache - * interaction and loading. Allowing them in turn to centralise code and help keeps things more easily maintainable. - * - * Can be implemented by any class. - * - * @package core - * @category cache - * @copyright 2012 Sam Hemelryk - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -interface cache_data_source { - - /** - * Returns an instance of the data source class that the cache can use for loading data using the other methods - * specified by this interface. - * - * @param cache_definition $definition - * @return object - */ - public static function get_instance_for_cache(cache_definition $definition); - - /** - * Loads the data for the key provided ready formatted for caching. - * - * @param string|int $key The key to load. - * @return mixed What ever data should be returned, or false if it can't be loaded. - */ - public function load_for_cache($key); - - /** - * Loads several keys for the cache. - * - * @param array $keys An array of keys each of which will be string|int. - * @return array An array of matching data items. - */ - public function load_many_for_cache(array $keys); -} - -/** - * Versionable cache data source. - * - * This interface extends the main cache data source interface to add an extra required method if - * the data source is to be used for a versioned cache. - * - * @package core_cache - */ -interface cache_data_source_versionable extends cache_data_source { - /** - * Loads the data for the key provided ready formatted for caching. - * - * If there is no data for that key, or if the data for the required key has an older version - * than the specified $requiredversion, then this returns null. - * - * If there is data then $actualversion should be set to the actual version number retrieved - * (may be the same as $requiredversion or newer). - * - * @param string|int $key The key to load. - * @param int $requiredversion Minimum required version - * @param mixed $actualversion Should be set to the actual version number retrieved - * @return mixed What ever data should be returned, or false if it can't be loaded. - */ - public function load_for_cache_versioned($key, int $requiredversion, &$actualversion); -} - -/** - * Cacheable object. - * - * This interface can be implemented by any class that is going to be passed into a cache and allows it to take control of the - * structure and the information about to be cached, as well as how to deal with it when it is retrieved from a cache. - * Think of it like serialisation and the __sleep and __wakeup methods. - * This is used because cache stores are responsible for how they interact with data and what they do when storing it. This - * interface ensures there is always a guaranteed action. - */ -interface cacheable_object { - - /** - * Prepares the object for caching. Works like the __sleep method. - * - * @return mixed The data to cache, can be anything except a class that implements the cacheable_object... that would - * be dumb. - */ - public function prepare_to_cache(); - - /** - * Takes the data provided by prepare_to_cache and reinitialises an instance of the associated from it. - * - * @param mixed $data - * @return object The instance for the given data. - */ - public static function wake_from_cache($data); -} - -/** - * Cache lock interface - * - * This interface needs to be inherited by all cache lock plugins. - */ -interface cache_lock_interface { - /** - * Constructs an instance of the cache lock given its name and its configuration data - * - * @param string $name The unique name of the lock instance - * @param array $configuration - */ - public function __construct($name, array $configuration = array()); - - /** - * Acquires a lock on a given key. - * - * @param string $key The key to acquire a lock for. - * @param string $ownerid An unique identifier for the owner of this lock. It is entirely optional for the cache lock plugin - * to use this. Each implementation can decide for themselves. - * @param bool $block If set to true the application will wait until a lock can be acquired - * @return bool True if the lock can be acquired false otherwise. - */ - public function lock($key, $ownerid, $block = false); - - /** - * Releases the lock held on a certain key. - * - * @param string $key The key to release the lock for. - * @param string $ownerid An unique identifier for the owner of this lock. It is entirely optional for the cache lock plugin - * to use this. Each implementation can decide for themselves. - * @param bool $forceunlock If set to true the lock will be removed if it exists regardless of whether or not we own it. - */ - public function unlock($key, $ownerid, $forceunlock = false); - - /** - * Checks the state of the given key. - * - * Returns true if the key is locked and belongs to the ownerid. - * Returns false if the key is locked but does not belong to the ownerid. - * Returns null if there is no lock - * - * @param string $key The key we are checking for. - * @param string $ownerid The identifier so we can check if we have the lock or if it is someone else. - * @return bool True if this code has the lock, false if there is a lock but this code doesn't have it, null if there - * is no lock. - */ - public function check_state($key, $ownerid); - - /** - * Cleans up any left over locks. - * - * This function MUST clean up any locks that have been acquired and not released during processing. - * Although the situation of acquiring a lock and not releasing it should be insanely rare we need to deal with it. - * Things such as unfortunate timeouts etc could cause this situation. - */ - public function __destruct(); -} diff --git a/cache/classes/key_aware_cache_interface.php b/cache/classes/key_aware_cache_interface.php new file mode 100644 index 00000000000..e23f3439fef --- /dev/null +++ b/cache/classes/key_aware_cache_interface.php @@ -0,0 +1,82 @@ +. + +/** + * Cache store feature: key awareness. + * + * This is a feature that cache stores and cache loaders can both choose to implement. + * If a cache store implements this then it will be made responsible for tests for items within the cache. + * If the cache store being used doesn't implement this then it will be the responsibility of the cache loader to use the + * equivalent get methods to mimick the functionality of these tests. + * + * Cache stores should only override these methods if they natively support such features or if they have a better performing + * means of performing these tests than the handling that would otherwise take place in the cache_loader. + * + * Can be implemented by classes already implementing cache_store. + */ +interface cache_is_key_aware { + + /** + * 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: + *
    + *
  1. Attempt to retrieve the information.
  2. + *
  3. Generate the information.
  4. + *
  5. Attempt to set the information
  6. + *
+ * + * 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 + * @return bool True if the cache has the requested key, false otherwise. + */ + public function has($key); + + /** + * 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); + + /** + * 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); +} diff --git a/cache/classes/loader_interface.php b/cache/classes/loader_interface.php new file mode 100644 index 00000000000..ad250e22b9d --- /dev/null +++ b/cache/classes/loader_interface.php @@ -0,0 +1,217 @@ +. + +/** + * Cache Loader. + * + * This cache loader interface provides the required structure for classes that wish to be interacted with as a + * means of accessing and interacting with a cache. + * + * Can be implemented by any class wishing to be a cache loader. + */ +interface cache_loader { + + /** + * Retrieves the value for the given key from the cache. + * + * @param string|int $key The key for the data being requested. + * @param int $strictness One of IGNORE_MISSING or MUST_EXIST. + * @return mixed The data retrieved from the cache, or false if the key did not exist within the cache. + * If MUST_EXIST was used then an exception will be thrown if the key does not exist within the cache. + */ + public function get($key, $strictness = IGNORE_MISSING); + + /** + * Retrieves the value and actual version for the given key, with at least the required version. + * + * If there is no value for the key, or there is a value but it doesn't have the required + * version, then this function will return false (or throw an exception if you set strictness + * to MUST_EXIST). + * + * This function can be used to make it easier to support localisable caches (where the cache + * could be stored on a local server as well as a shared cache). Specifying the version means + * that it will automatically retrieve the correct version if available, either from the local + * server or [if that has an older version] from the shared server. + * + * If the cached version is newer than specified version, it will be returned regardless. For + * example, if you request version 4, but the locally cached version is 5, it will be returned. + * If you request version 6, and the locally cached version is 5, then the system will look in + * higher-level caches (if any); if there still isn't a version 6 or greater, it will return + * null. + * + * You must use this function if you use set_versioned. + * + * @param string|int $key The key for the data being requested. + * @param int $requiredversion Minimum required version of the data + * @param int $strictness One of IGNORE_MISSING or MUST_EXIST. + * @param mixed $actualversion If specified, will be set to the actual version number retrieved + * @return mixed Data from the cache, or false if the key did not exist or was too old + */ + public function get_versioned($key, int $requiredversion, int $strictness = IGNORE_MISSING, &$actualversion = null); + + /** + * 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. + * @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. + */ + public function get_many(array $keys, $strictness = IGNORE_MISSING); + + /** + * 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. + * @param mixed $data The data to set against the key. + * @return bool True on success, false otherwise. + */ + public function set($key, $data); + + /** + * Sets the value for the given key with the given version. + * + * The cache does not store multiple versions - any existing version will be overwritten with + * this one. This function should only be used if there is a known 'current version' (e.g. + * stored in a database table). It only ensures that the cache does not return outdated data. + * + * This function can be used to help implement localisable caches (where the cache could be + * stored on a local server as well as a shared cache). The version will be recorded alongside + * the item and get_versioned will always return the correct version. + * + * The version number must be an integer that always increases. This could be based on the + * current time, or a stored value that increases by 1 each time it changes, etc. + * + * If you use this function you must use get_versioned to retrieve the data. + * + * @param string|int $key The key for the data being set. + * @param int $version Integer for the version of the data + * @param mixed $data The data to set against the key. + * @return bool True on success, false otherwise. + */ + public function set_versioned($key, int $version, $data): bool; + + /** + * 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); + + /** + * 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: + *
    + *
  1. Attempt to retrieve the information.
  2. + *
  3. Generate the information.
  4. + *
  5. Attempt to set the information
  6. + *
+ * + * 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 + * @return bool True if the cache has the requested key, false otherwise. + */ + public function has($key); + + /** + * 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); + + /** + * 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); + + /** + * 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); + + /** + * 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); +} diff --git a/cache/classes/loader_with_locking_interface.php b/cache/classes/loader_with_locking_interface.php new file mode 100644 index 00000000000..f2cf04b3a28 --- /dev/null +++ b/cache/classes/loader_with_locking_interface.php @@ -0,0 +1,69 @@ +. + +/** + * Cache Loader supporting locking. + * + * This interface should be given to classes already implementing cache_loader that also wish to support locking. + * It outlines the required structure for utilising locking functionality when using a cache. + * + * Can be implemented by any class already implementing the cache_loader interface. + */ +interface cache_loader_with_locking { + /** + * Acquires a lock for the given key. + * + * Please note that this happens automatically if the cache definition requires locking. + * it is still made a public method so that adhoc caches can use it if they choose. + * However this doesn't guarantee consistent access. It will become the responsibility of the calling code to ensure + * locks are acquired, checked, and released. + * + * Prior to Moodle 4,3 this function used to return false if the lock cannot be obtained. It + * now always returns true, and throws an exception if the lock cannot be obtained. + * + * @param string|int $key + * @return bool Always returns true (for backwards compatibility) + * @throws moodle_exception If the lock cannot be obtained after a timeout + */ + public function acquire_lock($key); + + /** + * Checks if the cache loader owns the lock for the given key. + * + * Please note that this happens automatically if the cache definition requires locking. + * it is still made a public method so that adhoc caches can use it if they choose. + * However this doesn't guarantee consistent access. It will become the responsibility of the calling code to ensure + * locks are acquired, checked, and released. + * + * @param string|int $key + * @return bool True if this code has the lock, false if there is a lock but this code doesn't have it, + * null if there is no lock. + */ + public function check_lock_state($key); + + /** + * Releases the lock for the given key. + * + * Please note that this happens automatically if the cache definition requires locking. + * it is still made a public method so that adhoc caches can use it if they choose. + * However this doesn't guarantee consistent access. It will become the responsibility of the calling code to ensure + * locks are acquired, checked, and released. + * + * @param string|int $key + * @return bool True if the lock has been released, false if there was a problem releasing the lock. + */ + public function release_lock($key); +} diff --git a/cache/classes/lockable_cache_interface.php b/cache/classes/lockable_cache_interface.php new file mode 100644 index 00000000000..42541db9258 --- /dev/null +++ b/cache/classes/lockable_cache_interface.php @@ -0,0 +1,56 @@ +. + +/** + * Cache store feature: locking + * + * This is a feature that cache stores can implement if they wish to support locking themselves rather + * than having the cache loader handle it for them. + * + * Can be implemented by classes already implementing cache_store. + */ +interface cache_is_lockable { + + /** + * Acquires a lock on the given key for the given identifier. + * + * @param string $key The key we are locking. + * @param string $ownerid The identifier so we can check if we have the lock or if it is someone else. + * The use of this property is entirely optional and implementations can act as they like upon it. + * @return bool True if the lock could be acquired, false otherwise. + */ + public function acquire_lock($key, $ownerid); + + /** + * Test if there is already a lock for the given key and if there is whether it belongs to the calling code. + * + * @param string $key The key we are locking. + * @param string $ownerid The identifier so we can check if we have the lock or if it is someone else. + * @return bool True if this code has the lock, false if there is a lock but this code doesn't have it, null if there + * is no lock. + */ + public function check_lock_state($key, $ownerid); + + /** + * Releases the lock on the given key. + * + * @param string $key The key we are locking. + * @param string $ownerid The identifier so we can check if we have the lock or if it is someone else. + * The use of this property is entirely optional and implementations can act as they like upon it. + * @return bool True if the lock has been released, false if there was a problem releasing the lock. + */ + public function release_lock($key, $ownerid); +} diff --git a/cache/classes/request_cache.php b/cache/classes/request_cache.php new file mode 100644 index 00000000000..760a2b0275f --- /dev/null +++ b/cache/classes/request_cache.php @@ -0,0 +1,35 @@ +. + +/** + * An request cache. + * + * This class is used for request caches returned by the cache::make methods. + * + * 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. + * + * @internal don't use me directly. + * + * @package core + * @category cache + * @copyright 2012 Sam Hemelryk + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class cache_request extends cache { + // This comment appeases code pre-checker ;) ! +} diff --git a/cache/classes/searchable_cache_interface.php b/cache/classes/searchable_cache_interface.php new file mode 100644 index 00000000000..55563798134 --- /dev/null +++ b/cache/classes/searchable_cache_interface.php @@ -0,0 +1,39 @@ +. + +/** + * 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 Moodle 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); +} diff --git a/cache/classes/session_cache.php b/cache/classes/session_cache.php new file mode 100644 index 00000000000..6a54ceb1a17 --- /dev/null +++ b/cache/classes/session_cache.php @@ -0,0 +1,576 @@ +. + +/** + * A session cache. + * + * 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 exist. + * Because of this we don't ever use the static acceleration array. + * 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. + * + * @todo we should support locking in the session as well. Should be pretty simple to set up. + * + * @internal don't use me directly. + * @method cache_store|cache_is_searchable get_store() Returns the cache store which must implement both cache_is_searchable. + * + * @package core + * @category cache + * @copyright 2012 Sam Hemelryk + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class cache_session extends cache { + /** + * The user the session has been established for. + * @var int + */ + protected static $loadeduserid = null; + + /** + * The userid this cache is currently using. + * @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_'; + + /** + * This is the key used to track last access. + */ + const LASTACCESS = '__lastaccess__'; + + /** + * Override the cache::construct method. + * + * This function gets overriden so that we can process any invalidation events if need be. + * If the definition doesn't have any invalidation events then this occurs exactly as it would for the cache class. + * Otherwise we look at the last invalidation time and then check the invalidation data for events that have occured + * between then now. + * + * You should not call this method from your code, instead you should use the cache::make methods. + * + * @param cache_definition $definition + * @param cache_store $store + * @param cache_loader|cache_data_source $loader + */ + public function __construct(cache_definition $definition, cache_store $store, $loader = null) { + // First up copy the loadeduserid to the current user id. + $this->currentuserid = self::$loadeduserid; + $this->set_session_id(); + parent::__construct($definition, $store, $loader); + + // This will trigger check tracked user. If this gets removed a call to that will need to be added here in its place. + $this->set(self::LASTACCESS, cache::now()); + + $this->handle_invalidation_events(); + } + + /** + * Sets the session id for the loader. + */ + protected function set_session_id() { + $this->sessionid = preg_replace('#[^a-zA-Z0-9_]#', '_', session_id()); + } + + /** + * Returns the prefix used for all keys. + * @return string + */ + protected function get_key_prefix() { + return 'u'.$this->currentuserid.'_'.$this->sessionid; + } + + /** + * Parses the key turning it into a string (or array is required) suitable to be passed to the cache store. + * + * 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) { + $prefix = $this->get_key_prefix(); + if ($key === self::LASTACCESS) { + return $key.$prefix; + } + return $prefix.'_'.parent::parse_key($key); + } + + /** + * Check that this cache instance is tracking the current user. + */ + protected function check_tracked_user() { + if (isset($_SESSION['USER']->id) && $_SESSION['USER']->id !== null) { + // Get the id of the current user. + $new = $_SESSION['USER']->id; + } else { + // No user set up yet. + $new = 0; + } + if ($new !== self::$loadeduserid) { + // 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(); + } + self::$loadeduserid = $new; + $this->currentuserid = $new; + } else if ($new !== $this->currentuserid) { + // The current user matches the loaded user but not the user last used by this cache. + $this->purge_current_user(); + $this->currentuserid = $new; + } + } + + /** + * Purges the session cache of all data belonging to the current user. + */ + public function purge_current_user() { + $keys = $this->get_store()->find_by_prefix($this->get_key_prefix()); + $this->get_store()->delete_many($keys); + } + + /** + * 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 $requiredversion Minimum required version of the data or cache::VERSION_NONE + * @param int $strictness One of IGNORE_MISSING | MUST_EXIST + * @param mixed &$actualversion If specified, will be set to the actual version number retrieved + * @return mixed|false The data from the cache or false if the key did not exist within the cache. + * @throws coding_exception + */ + protected function get_implementation($key, int $requiredversion, int $strictness, &$actualversion = null) { + // Check the tracked user. + $this->check_tracked_user(); + + // Use parent code. + return parent::get_implementation($key, $requiredversion, $strictness, $actualversion); + } + + /** + * 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(); + $loader = $this->get_loader(); + if ($loader !== false) { + // We have a loader available set it there as well. + // We have to let the loader do its own parsing of data as it may be unique. + $loader->set($key, $data); + } + if (is_object($data) && $data instanceof cacheable_object) { + $data = new cache_cached_object($data); + } else if (!$this->get_store()->supports_dereferencing_objects() && !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() && !$this->store_supports_native_ttl()) { + $data = new cache_ttl_wrapper($data, $this->get_definition()->get_ttl()); + } + $success = $this->get_store()->set($this->parse_key($key), $data); + if ($this->perfdebug) { + cache_helper::record_cache_set($this->get_store(), $this->get_definition(), 1, + $this->get_store()->get_last_io_bytes()); + } + return $success; + } + + /** + * 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) { + $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); + } + return $this->get_store()->delete($parsedkey); + } + + /** + * 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 coding_exception + */ + public function get_many(array $keys, $strictness = IGNORE_MISSING) { + $this->check_tracked_user(); + $parsedkeys = array(); + $keymap = array(); + foreach ($keys as $key) { + $parsedkey = $this->parse_key($key); + $parsedkeys[$key] = $parsedkey; + $keymap[$parsedkey] = $key; + } + $result = $this->get_store()->get_many($parsedkeys); + if ($this->perfdebug) { + $readbytes = $this->get_store()->get_last_io_bytes(); + } + $return = array(); + $missingkeys = array(); + $hasmissingkeys = false; + foreach ($result as $parsedkey => $value) { + $key = $keymap[$parsedkey]; + if ($value instanceof cache_ttl_wrapper) { + /* @var cache_ttl_wrapper $value */ + if ($value->has_expired()) { + $this->delete($keymap[$parsedkey]); + $value = false; + } else { + $value = $value->data; + } + } + if ($value instanceof cache_cached_object) { + /* @var cache_cached_object $value */ + $value = $value->restore_object(); + } else if (!$this->get_store()->supports_dereferencing_objects() && !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); + } + $return[$key] = $value; + if ($value === false) { + $hasmissingkeys = true; + $missingkeys[$parsedkey] = $key; + } + } + if ($hasmissingkeys) { + // We've got missing keys - we've got to check any loaders or data sources. + $loader = $this->get_loader(); + $datasource = $this->get_datasource(); + if ($loader !== false) { + foreach ($loader->get_many($missingkeys) as $key => $value) { + if ($value !== false) { + $return[$key] = $value; + unset($missingkeys[$parsedkeys[$key]]); + } + } + } + $hasmissingkeys = count($missingkeys) > 0; + if ($datasource !== false && $hasmissingkeys) { + // We're still missing keys but we've got a datasource. + foreach ($datasource->load_many_for_cache($missingkeys) as $key => $value) { + if ($value !== false) { + $return[$key] = $value; + unset($missingkeys[$parsedkeys[$key]]); + } + } + $hasmissingkeys = count($missingkeys) > 0; + } + } + if ($hasmissingkeys && $strictness === MUST_EXIST) { + throw new coding_exception('Requested key did not exist in any cache stores and could not be loaded.'); + } + if ($this->perfdebug) { + $hits = 0; + $misses = 0; + foreach ($return as $value) { + if ($value === false) { + $misses++; + } else { + $hits++; + } + } + cache_helper::record_cache_hit($this->get_store(), $this->get_definition(), $hits, $readbytes); + cache_helper::record_cache_miss($this->get_store(), $this->get_definition(), $misses); + } + 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) { + $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); + } + return $this->get_store()->delete_many($parsedkeys); + } + + /** + * 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(); + $loader = $this->get_loader(); + if ($loader !== false) { + // We have a loader available set it there as well. + // We have to let the loader do its own parsing of data as it may be unique. + $loader->set_many($keyvaluearray); + } + $data = array(); + $definitionid = $this->get_definition()->get_ttl(); + $simulatettl = $this->has_a_ttl() && !$this->store_supports_native_ttl(); + foreach ($keyvaluearray as $key => $value) { + if (is_object($value) && $value instanceof cacheable_object) { + $value = new cache_cached_object($value); + } else if (!$this->get_store()->supports_dereferencing_objects() && !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, $definitionid); + } + $data[$key] = array( + 'key' => $this->parse_key($key), + 'value' => $value + ); + } + $successfullyset = $this->get_store()->set_many($data); + if ($this->perfdebug && $successfullyset) { + cache_helper::record_cache_set($this->get_store(), $this->get_definition(), $successfullyset, + $this->get_store()->get_last_io_bytes()); + } + return $successfullyset; + } + + /** + * Purges the cache store, and loader if there is one. + * + * @return bool True on success, false otherwise + */ + public function purge() { + $this->get_store()->purge(); + 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: + *
    + *
  1. Attempt to retrieve the information.
  2. + *
  3. Generate the information.
  4. + *
  5. Attempt to set the information
  6. + *
+ * + * 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); + $store = $this->get_store(); + if ($this->has_a_ttl() && !$this->store_supports_native_ttl()) { + // The data has a TTL and the store doesn't support it natively. + // We must fetch the data and expect a ttl wrapper. + $data = $store->get($parsedkey); + $has = ($data instanceof cache_ttl_wrapper && !$data->has_expired()); + } else if (!$this->store_supports_key_awareness()) { + // The store doesn't support key awareness, get the data and check it manually... puke. + // Either no TTL is set of the store supports its handling natively. + $data = $store->get($parsedkey); + $has = ($data !== false); + } else { + // The store supports key awareness, this is easy! + // Either no TTL is set of the store supports its handling natively. + /* @var cache_store|cache_is_key_aware $store */ + $has = $store->has($parsedkey); + } + if (!$has && $tryloadifpossible) { + $result = null; + if ($this->get_loader() !== false) { + $result = $this->get_loader()->get($parsedkey); + } 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(); + if (($this->has_a_ttl() && !$this->store_supports_native_ttl()) || !$this->store_supports_key_awareness()) { + foreach ($keys as $key) { + if (!$this->has($key)) { + return false; + } + } + return true; + } + // The cache must be key aware and if support native ttl if it a ttl is set. + /* @var cache_store|cache_is_key_aware $store */ + $store = $this->get_store(); + return $store->has_all(array_map(array($this, 'parse_key'), $keys)); + } + + /** + * 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) { + if (($this->has_a_ttl() && !$this->store_supports_native_ttl()) || !$this->store_supports_key_awareness()) { + foreach ($keys as $key) { + if ($this->has($key)) { + return true; + } + } + return false; + } + /* @var cache_store|cache_is_key_aware $store */ + $store = $this->get_store(); + return $store->has_any(array_map(array($this, 'parse_key'), $keys)); + } + + /** + * The session loader never uses static acceleration. + * Instead it stores things in the static $session variable. Shared between all session loaders. + * + * @return bool + */ + protected function use_static_acceleration() { + return false; + } +} diff --git a/cache/classes/ttl_wrapper.php b/cache/classes/ttl_wrapper.php new file mode 100644 index 00000000000..97abbe98905 --- /dev/null +++ b/cache/classes/ttl_wrapper.php @@ -0,0 +1,59 @@ +. + +/** + * A wrapper class used to handle ttl when the cache store doesn't natively support it. + * + * This class is exactly why you should use event driving invalidation of cache data rather than relying on ttl. + * + * @package core + * @category cache + * @copyright 2012 Sam Hemelryk + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class cache_ttl_wrapper { + + /** + * The data being stored. + * @var mixed + */ + public $data; + + /** + * When the cache data expires as a timestamp. + * @var int + */ + public $expires; + + /** + * Constructs a ttl cache wrapper. + * + * @param mixed $data + * @param int $ttl The time to live in seconds. + */ + public function __construct($data, $ttl) { + $this->data = $data; + $this->expires = cache::now() + (int)$ttl; + } + + /** + * Returns true if the data has expired. + * @return int + */ + public function has_expired() { + return ($this->expires < cache::now()); + } +} diff --git a/cache/classes/versionable_data_source_interface.php b/cache/classes/versionable_data_source_interface.php new file mode 100644 index 00000000000..48fa1719883 --- /dev/null +++ b/cache/classes/versionable_data_source_interface.php @@ -0,0 +1,41 @@ +. + +/** + * Versionable cache data source. + * + * This interface extends the main cache data source interface to add an extra required method if + * the data source is to be used for a versioned cache. + * + * @package core_cache + */ +interface cache_data_source_versionable extends cache_data_source { + /** + * Loads the data for the key provided ready formatted for caching. + * + * If there is no data for that key, or if the data for the required key has an older version + * than the specified $requiredversion, then this returns null. + * + * If there is data then $actualversion should be set to the actual version number retrieved + * (may be the same as $requiredversion or newer). + * + * @param string|int $key The key to load. + * @param int $requiredversion Minimum required version + * @param mixed $actualversion Should be set to the actual version number retrieved + * @return mixed What ever data should be returned, or false if it can't be loaded. + */ + public function load_for_cache_versioned($key, int $requiredversion, &$actualversion); +} diff --git a/cache/lib.php b/cache/lib.php index c11b67a6193..c57ea19e0a8 100644 --- a/cache/lib.php +++ b/cache/lib.php @@ -31,198 +31,5 @@ defined('MOODLE_INTERNAL') || die(); // Include the required classes. -require_once($CFG->dirroot.'/cache/classes/interfaces.php'); -require_once($CFG->dirroot.'/cache/classes/config.php'); -require_once($CFG->dirroot.'/cache/classes/helper.php'); -require_once($CFG->dirroot.'/cache/classes/factory.php'); -require_once($CFG->dirroot.'/cache/classes/loaders.php'); require_once($CFG->dirroot.'/cache/classes/store.php'); require_once($CFG->dirroot.'/cache/classes/definition.php'); - -/** - * A cached object wrapper. - * - * This class gets used when the data is an object that has implemented the cacheable_object interface. - * - * @package core - * @category cache - * @copyright 2012 Sam Hemelryk - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class cache_cached_object { - - /** - * The class of the cacheable object - * @var string - */ - protected $class; - - /** - * The data returned by the cacheable_object prepare_to_cache method. - * @var mixed - */ - protected $data; - - /** - * Constructs a cached object wrapper. - * @param cacheable_object $obj - */ - public function __construct(cacheable_object $obj) { - $this->class = get_class($obj); - $this->data = $obj->prepare_to_cache(); - } - - /** - * Restores the data as an instance of the cacheable_object class. - * @return object - */ - public function restore_object() { - $class = $this->class; - return $class::wake_from_cache($this->data); - } -} - -/** - * A wrapper class used to handle ttl when the cache store doesn't natively support it. - * - * This class is exactly why you should use event driving invalidation of cache data rather than relying on ttl. - * - * @package core - * @category cache - * @copyright 2012 Sam Hemelryk - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class cache_ttl_wrapper { - - /** - * The data being stored. - * @var mixed - */ - public $data; - - /** - * When the cache data expires as a timestamp. - * @var int - */ - public $expires; - - /** - * Constructs a ttl cache wrapper. - * - * @param mixed $data - * @param int $ttl The time to live in seconds. - */ - public function __construct($data, $ttl) { - $this->data = $data; - $this->expires = cache::now() + (int)$ttl; - } - - /** - * Returns true if the data has expired. - * @return int - */ - public function has_expired() { - return ($this->expires < cache::now()); - } -} - -/** - * A cache exception class. Just allows people to catch cache exceptions. - * - * @package core - * @category cache - * @copyright 2012 Sam Hemelryk - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class cache_exception extends moodle_exception { - /** - * Constructs a new exception - * - * @param string $errorcode - * @param string $module - * @param string $link - * @param mixed $a - * @param mixed $debuginfo - */ - public function __construct($errorcode, $module = 'cache', $link = '', $a = null, $debuginfo = null) { - // This may appear like a useless override but you will notice that we have set a MUCH more useful default for $module. - parent::__construct($errorcode, $module, $link, $a, $debuginfo); - } -} - -/** - * An array of cacheable objects. - * - * This class allows a developer to create an array of cacheable objects and store that. - * The cache API doesn't check items within an array to see whether they are cacheable. Such a check would be very costly to both - * arrays using cacheable object and those that don't. - * Instead the developer must explicitly use a cacheable_object_array instance. - * - * The following is one example of how this class can be used. - * - * $data = array(); - * $data[] = new cacheable_object('one'); - * $data[] = new cacheable_object('two'); - * $data[] = new cacheable_object('three'); - * $cache->set(new cacheable_object_array($data)); - * - * Another example would be - * - * $data = new cacheable_object_array(); - * $data[] = new cacheable_object('one'); - * $data[] = new cacheable_object('two'); - * $data[] = new cacheable_object('three'); - * $cache->set($data); - * - * - * @copyright 2012 Sam Hemelryk - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class cacheable_object_array extends ArrayObject implements cacheable_object { - - /** - * Constructs a new array object instance. - * @param array $items - */ - final public function __construct(array $items = array()) { - parent::__construct($items, ArrayObject::STD_PROP_LIST); - } - - /** - * Returns the data to cache for this object. - * - * @return array An array of cache_cached_object instances. - * @throws coding_exception - */ - final public function prepare_to_cache() { - $result = array(); - foreach ($this as $key => $value) { - if ($value instanceof cacheable_object) { - $value = new cache_cached_object($value); - } else { - throw new coding_exception('Only cacheable_object instances can be added to a cacheable_array'); - } - $result[$key] = $value; - } - return $result; - } - - /** - * Returns the cacheable_object_array that was originally sent to the cache. - * - * @param array $data - * @return cacheable_object_array - * @throws coding_exception - */ - final public static function wake_from_cache($data) { - if (!is_array($data)) { - throw new coding_exception('Invalid data type when reviving cacheable_array data'); - } - $result = array(); - foreach ($data as $key => $value) { - $result[$key] = $value->restore_object(); - } - $class = __CLASS__; - return new $class($result); - } -} diff --git a/lib/db/legacyclasses.php b/lib/db/legacyclasses.php index 6022f731d47..6bf2dad6faf 100644 --- a/lib/db/legacyclasses.php +++ b/lib/db/legacyclasses.php @@ -50,6 +50,31 @@ $legacyclasses = [ \required_capability_exception::class => 'exception/required_capability_exception.php', \webservice_parameter_exception::class => 'exception/webservice_parameter_exception.php', + // Cache API. + \cache::class => 'cache.php', + \cache_application::class => 'application_cache.php', + \cache_request::class => 'request_cache.php', + \cache_session::class => 'session_cache.php', + \cache_cached_object::class => 'cached_object.php', + \cache_config::class => 'config.php', + \cache_data_source::class => 'data_source_interface.php', + \cache_data_source_versionable::class => 'versionable_data_source_interface.php', + \cache_exception::class => 'exception/cache_exception.php', + \cache_factory::class => 'factory.php', + \cache_helper::class => 'helper.php', + \cache_is_key_aware::class => 'key_aware_cache_interface.php', + \cache_is_lockable::class => 'lockable_cache_interface.php', + \cache_is_searchable::class => 'searchable_cache_interface.php', + \cache_is_configurable::class => 'configurable_cache_interface.php', + \cache_loader::class => 'loader_interface.php', + \cache_loader_with_locking::class => 'loader_with_locking_interface.php', + \cache_lock_interface::class => 'cache_lock_interface.php', + \cache_store::class => 'store.php', + \cache_ttl_wrapper::class => 'ttl_wrapper.php', + \cache_store_interface::class => 'store_interface.php', + \cacheable_object::class => 'cacheable_object_interface.php', + \cacheable_object_array::class => 'cacheable_object_array.php', + // Output API. \theme_config::class => 'output/theme_config.php', \xhtml_container_stack::class => 'output/xhtml_container_stack.php',