2 // This file is part of Moodle - http://moodle.org/
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
20 * This file is part of Moodle's cache API, affectionately called MUC.
21 * It contains the components that are requried in order to use caching.
25 * @copyright 2012 Sam Hemelryk
26 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
29 defined('MOODLE_INTERNAL') || die();
32 * The cache helper class.
34 * The cache helper class provides common functionality to the cache API and is useful to developers within to interact with
35 * the cache API in a general way.
39 * @copyright 2012 Sam Hemelryk
40 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
45 * Statistics gathered by the cache API during its operation will be used here.
49 protected static $stats = array();
52 * The instance of the cache helper.
55 protected static $instance;
58 * The site identifier used by the cache.
59 * Set the first time get_site_identifier is called.
62 protected static $siteidentifier = null;
65 * Returns true if the cache API can be initialised before Moodle has finished initialising itself.
67 * This check is essential when trying to cache the likes of configuration information. It checks to make sure that the cache
68 * configuration file has been created which allows use to set up caching when ever is required.
72 public static function ready_for_early_init() {
73 return cache_config::config_file_exists();
77 * Returns an instance of the cache_helper.
79 * This is designed for internal use only and acts as a static store.
80 * @staticvar null $instance
81 * @return cache_helper
83 protected static function instance() {
84 if (is_null(self::$instance)) {
85 self::$instance = new cache_helper();
87 return self::$instance;
91 * Constructs an instance of the cache_helper class. Again for internal use only.
93 protected function __construct() {
94 // Nothing to do here, just making sure you can't get an instance of this.
98 * Used as a data store for initialised definitions.
101 protected $definitions = array();
104 * Used as a data store for initialised cache stores
105 * We use this because we want to avoid establishing multiple instances of a single store.
108 protected $stores = array();
111 * Returns the class for use as a cache loader for the given mode.
113 * @param int $mode One of cache_store::MODE_
115 * @throws coding_exception
117 public static function get_class_for_mode($mode) {
119 case cache_store::MODE_APPLICATION :
120 return 'cache_application';
121 case cache_store::MODE_REQUEST :
122 return 'cache_request';
123 case cache_store::MODE_SESSION :
124 return 'cache_session';
126 throw new coding_exception('Unknown cache mode passed. Must be one of cache_store::MODE_*');
130 * Returns the cache stores to be used with the given definition.
131 * @param cache_definition $definition
134 public static function get_cache_stores(cache_definition $definition) {
135 $instance = cache_config::instance();
136 $stores = $instance->get_stores_for_definition($definition);
137 $stores = self::initialise_cachestore_instances($stores, $definition);
142 * Internal function for initialising an array of stores against a given cache definition.
144 * @param array $stores
145 * @param cache_definition $definition
148 protected static function initialise_cachestore_instances(array $stores, cache_definition $definition) {
150 $factory = cache_factory::instance();
151 foreach ($stores as $name => $details) {
152 $store = $factory->create_store_from_config($name, $details, $definition);
153 if ($store !== false) {
161 * Returns a cache_lock instance suitable for use with the store.
163 * @param cache_store $store
164 * @return cache_lock_interface
166 public static function get_cachelock_for_store(cache_store $store) {
167 $instance = cache_config::instance();
168 $lockconf = $instance->get_lock_for_store($store->my_name());
169 $factory = cache_factory::instance();
170 return $factory->create_lock_instance($lockconf);
174 * Returns an array of plugins without using core methods.
176 * This function explicitly does NOT use core functions as it will in some circumstances be called before Moodle has
177 * finished initialising. This happens when loading configuration for instance.
181 public static function early_get_cache_plugins() {
184 $ignored = array('CVS', '_vti_cnf', 'simpletest', 'db', 'yui', 'tests');
185 $fulldir = $CFG->dirroot.'/cache/stores';
186 $items = new DirectoryIterator($fulldir);
187 foreach ($items as $item) {
188 if ($item->isDot() or !$item->isDir()) {
191 $pluginname = $item->getFilename();
192 if (in_array($pluginname, $ignored)) {
195 if (!is_valid_plugin_name($pluginname)) {
196 // Better ignore plugins with problematic names here.
199 $result[$pluginname] = $fulldir.'/'.$pluginname;
207 * Invalidates a given set of keys from a given definition.
209 * @todo Invalidating by definition should also add to the event cache so that sessions can be invalidated (when required).
211 * @param string $component
212 * @param string $area
213 * @param array $identifiers
217 public static function invalidate_by_definition($component, $area, array $identifiers = array(), $keys = array()) {
218 $cache = cache::make($component, $area, $identifiers);
219 if (is_array($keys)) {
220 $cache->delete_many($keys);
221 } else if (is_scalar($keys)) {
222 $cache->delete($keys);
224 throw new coding_exception('cache_helper::invalidate_by_definition only accepts $keys as array, or scalar.');
230 * Invalidates a given set of keys by means of an event.
232 * @todo add support for identifiers to be supplied and utilised.
234 * @param string $event
237 public static function invalidate_by_event($event, array $keys) {
238 $instance = cache_config::instance();
239 $invalidationeventset = false;
240 $factory = cache_factory::instance();
241 foreach ($instance->get_definitions() as $name => $definitionarr) {
242 $definition = cache_definition::load($name, $definitionarr);
243 if ($definition->invalidates_on_event($event)) {
244 // OK at this point we know that the definition has information to invalidate on the event.
245 // There are two routes, either its an application cache in which case we can invalidate it now.
246 // or it is a persistent cache that also needs to be invalidated now.
247 $applicationcache = $definition->get_mode() === cache_store::MODE_APPLICATION;
248 if ($applicationcache || $definition->data_should_be_persistent()) {
249 $cache = $factory->create_cache_from_definition($definition->get_component(), $definition->get_area());
250 $cache->delete_many($keys);
253 // We need to flag the event in the "Event invalidation" cache if it hasn't already happened.
254 if ($invalidationeventset === false) {
255 // Get the event invalidation cache.
256 $cache = cache::make('core', 'eventinvalidation');
257 // Get any existing invalidated keys for this cache.
258 $data = $cache->get($event);
259 if ($data === false) {
263 // Add our keys to them with the current cache timestamp.
264 foreach ($keys as $key) {
265 $data[$key] = cache::now();
267 // Set that data back to the cache.
268 $cache->set($event, $data);
269 // This only needs to occur once.
270 $invalidationeventset = true;
277 * Purges the cache for a specific definition.
279 * If you need to purge a definition that requires identifiers or an aggregate and you don't
280 * know the details of those please use cache_helper::purge_stores_used_by_definition instead.
281 * It is a more aggressive purge and will purge all data within the store, not just the data
282 * belonging to the given definition.
284 * @todo MDL-36660: Change the signature: $aggregate must be added.
286 * @param string $component
287 * @param string $area
288 * @param array $identifiers
291 public static function purge_by_definition($component, $area, array $identifiers = array()) {
293 $cache = cache::make($component, $area, $identifiers);
294 // Initialise, in case of a store.
295 if ($cache instanceof cache_store) {
296 $factory = cache_factory::instance();
297 // TODO MDL-36660: Providing $aggregate is required for purging purposes: $definition->get_id()
298 $definition = $factory->create_definition($component, $area, null);
299 $definition->set_identifiers($identifiers);
300 $cache->initialise($definition);
302 // Purge baby, purge.
308 * Purges a cache of all information on a given event.
310 * @param string $event
312 public static function purge_by_event($event) {
313 $instance = cache_config::instance();
314 $invalidationeventset = false;
315 $factory = cache_factory::instance();
316 foreach ($instance->get_definitions() as $name => $definitionarr) {
317 $definition = cache_definition::load($name, $definitionarr);
318 if ($definition->invalidates_on_event($event)) {
319 // Check if this definition would result in a loader with persistent data being in use.
320 if ($definition->data_should_be_persistent()) {
321 // There may be a persistent cache loader. Lets purge that first so that any persistent data is removed.
322 $cache = $factory->create_cache_from_definition($definition->get_component(), $definition->get_area());
325 // Get all of the store instances that are in use for this store.
326 $stores = $factory->get_store_instances_in_use($definition);
327 foreach ($stores as $store) {
328 // Purge each store individually.
331 // We need to flag the event in the "Event invalidation" cache if it hasn't already happened.
332 if ($invalidationeventset === false) {
333 // Get the event invalidation cache.
334 $cache = cache::make('core', 'eventinvalidation');
335 // Create a key to invalidate all.
337 'purged' => cache::now()
339 // Set that data back to the cache.
340 $cache->set($event, $data);
341 // This only needs to occur once.
342 $invalidationeventset = true;
349 * Ensure that the stats array is ready to collect information for the given store and definition.
350 * @param string $store
351 * @param string $definition
353 protected static function ensure_ready_for_stats($store, $definition) {
354 // This function is performance-sensitive, so exit as quickly as possible
355 // if we do not need to do anything.
356 if (isset(self::$stats[$definition][$store])) {
359 if (!array_key_exists($definition, self::$stats)) {
360 self::$stats[$definition] = array(
367 } else if (!array_key_exists($store, self::$stats[$definition])) {
368 self::$stats[$definition][$store] = array(
377 * Record a cache hit in the stats for the given store and definition.
379 * @param string $store
380 * @param string $definition
382 public static function record_cache_hit($store, $definition) {
383 self::ensure_ready_for_stats($store, $definition);
384 self::$stats[$definition][$store]['hits']++;
388 * Record a cache miss in the stats for the given store and definition.
390 * @param string $store
391 * @param string $definition
393 public static function record_cache_miss($store, $definition) {
394 self::ensure_ready_for_stats($store, $definition);
395 self::$stats[$definition][$store]['misses']++;
399 * Record a cache set in the stats for the given store and definition.
401 * @param string $store
402 * @param string $definition
404 public static function record_cache_set($store, $definition) {
405 self::ensure_ready_for_stats($store, $definition);
406 self::$stats[$definition][$store]['sets']++;
410 * Return the stats collected so far.
413 public static function get_stats() {
418 * Purge all of the cache stores of all of their data.
420 * Think twice before calling this method. It will purge **ALL** caches regardless of whether they have been used recently or
421 * anything. This will involve full setup of the cache + the purge operation. On a site using caching heavily this WILL be
424 * @param bool $usewriter If set to true the cache_config_writer class is used. This class is special as it avoids
425 * it is still usable when caches have been disabled.
426 * Please use this option only if you really must. It's purpose is to allow the cache to be purged when it would be
427 * otherwise impossible.
429 public static function purge_all($usewriter = false) {
430 $factory = cache_factory::instance();
431 $config = $factory->create_config_instance($usewriter);
432 foreach ($config->get_all_stores() as $store) {
433 self::purge_store($store['name'], $config);
438 * Purges a store given its name.
440 * @param string $storename
441 * @param cache_config $config
444 public static function purge_store($storename, cache_config $config = null) {
445 if ($config === null) {
446 $config = cache_config::instance();
449 $stores = $config->get_all_stores();
450 if (!array_key_exists($storename, $stores)) {
451 // The store does not exist.
455 $store = $stores[$storename];
456 $class = $store['class'];
458 // Found the store: is it ready?
459 $instance = new $class($store['name'], $store['configuration']);
460 if (!$instance->is_ready()) {
465 foreach ($config->get_definitions_by_store($storename) as $id => $definition) {
466 $definition = cache_definition::load($id, $definition);
467 $definitioninstance = clone($instance);
468 $definitioninstance->initialise($definition);
469 $definitioninstance->purge();
470 unset($definitioninstance);
477 * Purges all of the stores used by a definition.
479 * Unlike cache_helper::purge_by_definition this purges all of the data from the stores not
480 * just the data relating to the definition.
481 * This function is useful when you must purge a definition that requires setup but you don't
484 * @param string $component
485 * @param string $area
487 public static function purge_stores_used_by_definition($component, $area) {
488 $factory = cache_factory::instance();
489 $config = $factory->create_config_instance();
490 $definition = $factory->create_definition($component, $area);
491 $stores = $config->get_stores_for_definition($definition);
492 foreach ($stores as $store) {
493 self::purge_store($store['name']);
498 * Returns the translated name of the definition.
500 * @param cache_definition $definition
501 * @return lang_string
503 public static function get_definition_name($definition) {
504 if ($definition instanceof cache_definition) {
505 return $definition->get_name();
507 $identifier = 'cachedef_'.clean_param($definition['area'], PARAM_STRINGID);
508 $component = $definition['component'];
509 if ($component === 'core') {
510 $component = 'cache';
512 return new lang_string($identifier, $component);
516 * Hashes a descriptive key to make it shorter and still unique.
517 * @param string|int $key
518 * @param cache_definition $definition
521 public static function hash_key($key, cache_definition $definition) {
522 if ($definition->uses_simple_keys()) {
523 if (debugging() && preg_match('#[^a-zA-Z0-9_]#', $key)) {
524 throw new coding_exception('Cache definition '.$definition->get_id().' requires simple keys. Invalid key provided.', $key);
526 // We put the key first so that we can be sure the start of the key changes.
527 return (string)$key . '-' . $definition->generate_single_key_prefix();
529 $key = $definition->generate_single_key_prefix() . '-' . $key;
534 * Finds all definitions and updates them within the cache config file.
536 * @param bool $coreonly If set to true only core definitions will be updated.
538 public static function update_definitions($coreonly = false) {
541 require_once($CFG->dirroot.'/cache/locallib.php');
542 // First update definitions
543 cache_config_writer::update_definitions($coreonly);
544 // Second reset anything we have already initialised to ensure we're all up to date.
545 cache_factory::reset();
549 * Update the site identifier stored by the cache API.
551 * @param string $siteidentifier
552 * @return string The new site identifier.
554 public static function update_site_identifier($siteidentifier) {
557 require_once($CFG->dirroot.'/cache/locallib.php');
558 $factory = cache_factory::instance();
559 $factory->updating_started();
560 $config = $factory->create_config_instance(true);
561 $siteidentifier = $config->update_site_identifier($siteidentifier);
562 $factory->updating_finished();
563 cache_factory::reset();
564 return $siteidentifier;
568 * Returns the site identifier.
572 public static function get_site_identifier() {
574 if (!is_null(self::$siteidentifier)) {
575 return self::$siteidentifier;
577 // If site identifier hasn't been collected yet attempt to get it from the cache config.
578 $factory = cache_factory::instance();
579 // If the factory is initialising then we don't want to try to get it from the config or we risk
580 // causing the cache to enter an infinite initialisation loop.
581 if (!$factory->is_initialising()) {
582 $config = $factory->create_config_instance();
583 self::$siteidentifier = $config->get_site_identifier();
585 if (is_null(self::$siteidentifier)) {
586 // If the site identifier is still null then config isn't aware of it yet.
587 // We'll see if the CFG is loaded, and if not we will just use unknown.
588 // It's very important here that we don't use get_config. We don't want an endless cache loop!
589 if (!empty($CFG->siteidentifier)) {
590 self::$siteidentifier = self::update_site_identifier($CFG->siteidentifier);
592 // It's not being recorded in MUC's config and the config data hasn't been loaded yet.
593 // Likely we are initialising.
597 return self::$siteidentifier;
601 * Returns the site version.
605 public static function get_site_version() {
607 return (string)$CFG->version;
611 * Runs cron routines for MUC.
613 public static function cron() {
614 self::clean_old_session_data(true);
618 * Cleans old session data from cache stores used for session based definitions.
620 * @param bool $output If set to true output will be given.
622 public static function clean_old_session_data($output = false) {
625 mtrace('Cleaning up stale session data from cache stores.');
627 $factory = cache_factory::instance();
628 $config = $factory->create_config_instance();
629 $definitions = $config->get_definitions();
630 $purgetime = time() - $CFG->sessiontimeout;
631 foreach ($definitions as $definitionarray) {
632 // We are only interested in session caches.
633 if (!($definitionarray['mode'] & cache_store::MODE_SESSION)) {
636 $definition = $factory->create_definition($definitionarray['component'], $definitionarray['area']);
637 $stores = $config->get_stores_for_definition($definition);
638 // Turn them into store instances.
639 $stores = self::initialise_cachestore_instances($stores, $definition);
640 // Initialise all of the stores used for that definition.
641 foreach ($stores as $store) {
642 // If the store doesn't support searching we can skip it.
643 if (!($store instanceof cache_is_searchable)) {
644 debugging('Cache stores used for session definitions should ideally be searchable.', DEBUG_DEVELOPER);
647 // Get all of the keys.
648 $keys = $store->find_by_prefix(cache_session::KEY_PREFIX);
650 foreach ($store->get_many($keys) as $key => $value) {
651 if (strpos($key, cache_session::KEY_PREFIX) !== 0 || !is_array($value) || !isset($value['lastaccess'])) {
654 if ((int)$value['lastaccess'] < $purgetime || true) {
658 if (count($todelete)) {
659 $outcome = (int)$store->delete_many($todelete);
661 $strdef = s($definition->get_id());
662 $strstore = s($store->my_name());
663 mtrace("- Removed {$outcome} old {$strdef} sessions from the '{$strstore}' cache store.");