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
146 * @return cache_store[]
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
214 * @param array|string|int $keys
216 * @throws coding_exception
218 public static function invalidate_by_definition($component, $area, array $identifiers = array(), $keys = array()) {
219 $cache = cache
::make($component, $area, $identifiers);
220 if (is_array($keys)) {
221 $cache->delete_many($keys);
222 } else if (is_scalar($keys)) {
223 $cache->delete($keys);
225 throw new coding_exception('cache_helper::invalidate_by_definition only accepts $keys as array, or scalar.');
231 * Invalidates a given set of keys by means of an event.
233 * Events cannot determine what identifiers might need to be cleared. Event based purge and invalidation
234 * are only supported on caches without identifiers.
236 * @param string $event
239 public static function invalidate_by_event($event, array $keys) {
240 $instance = cache_config
::instance();
241 $invalidationeventset = false;
242 $factory = cache_factory
::instance();
243 $inuse = $factory->get_caches_in_use();
245 foreach ($instance->get_definitions() as $name => $definitionarr) {
246 $definition = cache_definition
::load($name, $definitionarr);
247 if ($definition->invalidates_on_event($event)) {
248 // First up check if there is a cache loader for this definition already.
249 // If there is we need to invalidate the keys from there.
250 $definitionkey = $definition->get_component().'/'.$definition->get_area();
251 if (isset($inuse[$definitionkey])) {
252 $inuse[$definitionkey]->delete_many($keys);
255 // We should only log events for application and session caches.
256 // Request caches shouldn't have events as all data is lost at the end of the request.
257 // Events should only be logged once of course and likely several definitions are watching so we
258 // track its logging with $invalidationeventset.
259 $logevent = ($invalidationeventset === false && $definition->get_mode() !== cache_store
::MODE_REQUEST
);
262 // Get the event invalidation cache.
263 $cache = cache
::make('core', 'eventinvalidation');
264 // Get any existing invalidated keys for this cache.
265 $data = $cache->get($event);
266 if ($data === false) {
270 // Add our keys to them with the current cache timestamp.
271 if (null === $purgetoken) {
272 $purgetoken = cache
::get_purge_token(true);
274 foreach ($keys as $key) {
275 $data[$key] = $purgetoken;
277 // Set that data back to the cache.
278 $cache->set($event, $data);
279 // This only needs to occur once.
280 $invalidationeventset = true;
287 * Purges the cache for a specific definition.
289 * @param string $component
290 * @param string $area
291 * @param array $identifiers
294 public static function purge_by_definition($component, $area, array $identifiers = array()) {
296 $cache = cache
::make($component, $area, $identifiers);
297 // Initialise, in case of a store.
298 if ($cache instanceof cache_store
) {
299 $factory = cache_factory
::instance();
300 $definition = $factory->create_definition($component, $area, null);
301 $cacheddefinition = clone $definition;
302 $cacheddefinition->set_identifiers($identifiers);
303 $cache->initialise($cacheddefinition);
305 // Purge baby, purge.
311 * Purges a cache of all information on a given event.
313 * Events cannot determine what identifiers might need to be cleared. Event based purge and invalidation
314 * are only supported on caches without identifiers.
316 * @param string $event
318 public static function purge_by_event($event) {
319 $instance = cache_config
::instance();
320 $invalidationeventset = false;
321 $factory = cache_factory
::instance();
322 $inuse = $factory->get_caches_in_use();
324 foreach ($instance->get_definitions() as $name => $definitionarr) {
325 $definition = cache_definition
::load($name, $definitionarr);
326 if ($definition->invalidates_on_event($event)) {
327 // First up check if there is a cache loader for this definition already.
328 // If there is we need to invalidate the keys from there.
329 $definitionkey = $definition->get_component().'/'.$definition->get_area();
330 if (isset($inuse[$definitionkey])) {
331 $inuse[$definitionkey]->purge();
333 cache
::make($definition->get_component(), $definition->get_area())->purge();
336 // We should only log events for application and session caches.
337 // Request caches shouldn't have events as all data is lost at the end of the request.
338 // Events should only be logged once of course and likely several definitions are watching so we
339 // track its logging with $invalidationeventset.
340 $logevent = ($invalidationeventset === false && $definition->get_mode() !== cache_store
::MODE_REQUEST
);
342 // We need to flag the event in the "Event invalidation" cache if it hasn't already happened.
343 if ($logevent && $invalidationeventset === false) {
344 // Get the event invalidation cache.
345 $cache = cache
::make('core', 'eventinvalidation');
346 // Create a key to invalidate all.
347 if (null === $purgetoken) {
348 $purgetoken = cache
::get_purge_token(true);
351 'purged' => $purgetoken,
353 // Set that data back to the cache.
354 $cache->set($event, $data);
355 // This only needs to occur once.
356 $invalidationeventset = true;
363 * Ensure that the stats array is ready to collect information for the given store and definition.
364 * @param string $store
365 * @param string $storeclass
366 * @param string $definition A string that identifies the definition.
367 * @param int $mode One of cache_store::MODE_*. Since 2.9.
369 protected static function ensure_ready_for_stats($store, $storeclass, $definition, $mode = cache_store
::MODE_APPLICATION
) {
370 // This function is performance-sensitive, so exit as quickly as possible
371 // if we do not need to do anything.
372 if (isset(self
::$stats[$definition]['stores'][$store])) {
376 if (!array_key_exists($definition, self
::$stats)) {
377 self
::$stats[$definition] = array(
381 'class' => $storeclass,
385 'iobytes' => cache_store
::IO_BYTES_NOT_SUPPORTED
,
390 } else if (!array_key_exists($store, self
::$stats[$definition]['stores'])) {
391 self
::$stats[$definition]['stores'][$store] = array(
392 'class' => $storeclass,
396 'iobytes' => cache_store
::IO_BYTES_NOT_SUPPORTED
,
403 * Returns a string to describe the definition.
405 * This method supports the definition as a string due to legacy requirements.
406 * It is backwards compatible when a string is passed but is not accurate.
409 * @param cache_definition|string $definition
412 protected static function get_definition_stat_id_and_mode($definition) {
413 if (!($definition instanceof cache_definition
)) {
414 // All core calls to this method have been updated, this is the legacy state.
415 // We'll use application as the default as that is the most common, really this is not accurate of course but
416 // at this point we can only guess and as it only affects calls to cache stat outside of core (of which there should
417 // be none) I think that is fine.
418 debugging('Please update you cache stat calls to pass the definition rather than just its ID.', DEBUG_DEVELOPER
);
419 return array((string)$definition, cache_store
::MODE_APPLICATION
);
421 return array($definition->get_id(), $definition->get_mode());
425 * Record a cache hit in the stats for the given store and definition.
427 * In Moodle 2.9 the $definition argument changed from accepting only a string to accepting a string or a
428 * cache_definition instance. It is preferable to pass a cache definition instance.
430 * In Moodle 3.9 the first argument changed to also accept a cache_store.
433 * @param string|cache_store $store
434 * @param cache_definition $definition You used to be able to pass a string here, however that is deprecated please pass the
435 * actual cache_definition object now.
436 * @param int $hits The number of hits to record (by default 1)
437 * @param int $readbytes Number of bytes read from the cache or cache_store::IO_BYTES_NOT_SUPPORTED
439 public static function record_cache_hit($store, $definition, int $hits = 1, int $readbytes = cache_store
::IO_BYTES_NOT_SUPPORTED
): void
{
441 if ($store instanceof cache_store
) {
442 $storeclass = get_class($store);
443 $store = $store->my_name();
445 list($definitionstr, $mode) = self
::get_definition_stat_id_and_mode($definition);
446 self
::ensure_ready_for_stats($store, $storeclass, $definitionstr, $mode);
447 self
::$stats[$definitionstr]['stores'][$store]['hits'] +
= $hits;
448 if ($readbytes !== cache_store
::IO_BYTES_NOT_SUPPORTED
) {
449 if (self
::$stats[$definitionstr]['stores'][$store]['iobytes'] === cache_store
::IO_BYTES_NOT_SUPPORTED
) {
450 self
::$stats[$definitionstr]['stores'][$store]['iobytes'] = $readbytes;
452 self
::$stats[$definitionstr]['stores'][$store]['iobytes'] +
= $readbytes;
458 * Record a cache miss in the stats for the given store and definition.
460 * In Moodle 2.9 the $definition argument changed from accepting only a string to accepting a string or a
461 * cache_definition instance. It is preferable to pass a cache definition instance.
463 * In Moodle 3.9 the first argument changed to also accept a cache_store.
466 * @param string|cache_store $store
467 * @param cache_definition $definition You used to be able to pass a string here, however that is deprecated please pass the
468 * actual cache_definition object now.
469 * @param int $misses The number of misses to record (by default 1)
471 public static function record_cache_miss($store, $definition, $misses = 1) {
473 if ($store instanceof cache_store
) {
474 $storeclass = get_class($store);
475 $store = $store->my_name();
477 list($definitionstr, $mode) = self
::get_definition_stat_id_and_mode($definition);
478 self
::ensure_ready_for_stats($store, $storeclass, $definitionstr, $mode);
479 self
::$stats[$definitionstr]['stores'][$store]['misses'] +
= $misses;
483 * Record a cache set in the stats for the given store and definition.
485 * In Moodle 2.9 the $definition argument changed from accepting only a string to accepting a string or a
486 * cache_definition instance. It is preferable to pass a cache definition instance.
488 * In Moodle 3.9 the first argument changed to also accept a cache_store.
491 * @param string|cache_store $store
492 * @param cache_definition $definition You used to be able to pass a string here, however that is deprecated please pass the
493 * actual cache_definition object now.
494 * @param int $sets The number of sets to record (by default 1)
495 * @param int $writebytes Number of bytes written to the cache or cache_store::IO_BYTES_NOT_SUPPORTED
497 public static function record_cache_set($store, $definition, int $sets = 1,
498 int $writebytes = cache_store
::IO_BYTES_NOT_SUPPORTED
) {
500 if ($store instanceof cache_store
) {
501 $storeclass = get_class($store);
502 $store = $store->my_name();
504 list($definitionstr, $mode) = self
::get_definition_stat_id_and_mode($definition);
505 self
::ensure_ready_for_stats($store, $storeclass, $definitionstr, $mode);
506 self
::$stats[$definitionstr]['stores'][$store]['sets'] +
= $sets;
507 if ($writebytes !== cache_store
::IO_BYTES_NOT_SUPPORTED
) {
508 if (self
::$stats[$definitionstr]['stores'][$store]['iobytes'] === cache_store
::IO_BYTES_NOT_SUPPORTED
) {
509 self
::$stats[$definitionstr]['stores'][$store]['iobytes'] = $writebytes;
511 self
::$stats[$definitionstr]['stores'][$store]['iobytes'] +
= $writebytes;
517 * Return the stats collected so far.
520 public static function get_stats() {
525 * Purge all of the cache stores of all of their data.
527 * Think twice before calling this method. It will purge **ALL** caches regardless of whether they have been used recently or
528 * anything. This will involve full setup of the cache + the purge operation. On a site using caching heavily this WILL be
531 * @param bool $usewriter If set to true the cache_config_writer class is used. This class is special as it avoids
532 * it is still usable when caches have been disabled.
533 * Please use this option only if you really must. It's purpose is to allow the cache to be purged when it would be
534 * otherwise impossible.
536 public static function purge_all($usewriter = false) {
537 $factory = cache_factory
::instance();
538 $config = $factory->create_config_instance($usewriter);
539 foreach ($config->get_all_stores() as $store) {
540 self
::purge_store($store['name'], $config);
542 foreach ($factory->get_adhoc_caches_in_use() as $cache) {
548 * Purges a store given its name.
550 * @param string $storename
551 * @param cache_config $config
554 public static function purge_store($storename, cache_config
$config = null) {
555 if ($config === null) {
556 $config = cache_config
::instance();
559 $stores = $config->get_all_stores();
560 if (!array_key_exists($storename, $stores)) {
561 // The store does not exist.
565 $store = $stores[$storename];
566 $class = $store['class'];
569 // We check are_requirements_met although we expect is_ready is going to check as well.
570 if (!$class::are_requirements_met()) {
573 // Found the store: is it ready?
574 /* @var cache_store $instance */
575 $instance = new $class($store['name'], $store['configuration']);
576 if (!$instance->is_ready()) {
580 foreach ($config->get_definitions_by_store($storename) as $id => $definition) {
581 $definition = cache_definition
::load($id, $definition);
582 $definitioninstance = clone($instance);
583 $definitioninstance->initialise($definition);
584 $definitioninstance->purge();
585 unset($definitioninstance);
592 * Purges all of the stores used by a definition.
594 * Unlike cache_helper::purge_by_definition this purges all of the data from the stores not
595 * just the data relating to the definition.
596 * This function is useful when you must purge a definition that requires setup but you don't
599 * @param string $component
600 * @param string $area
602 public static function purge_stores_used_by_definition($component, $area) {
603 $factory = cache_factory
::instance();
604 $config = $factory->create_config_instance();
605 $definition = $factory->create_definition($component, $area);
606 $stores = $config->get_stores_for_definition($definition);
607 foreach ($stores as $store) {
608 self
::purge_store($store['name']);
613 * Returns the translated name of the definition.
615 * @param cache_definition $definition
616 * @return lang_string
618 public static function get_definition_name($definition) {
619 if ($definition instanceof cache_definition
) {
620 return $definition->get_name();
622 $identifier = 'cachedef_'.clean_param($definition['area'], PARAM_STRINGID
);
623 $component = $definition['component'];
624 if ($component === 'core') {
625 $component = 'cache';
627 return new lang_string($identifier, $component);
631 * Hashes a descriptive key to make it shorter and still unique.
632 * @param string|int $key
633 * @param cache_definition $definition
636 public static function hash_key($key, cache_definition
$definition) {
637 if ($definition->uses_simple_keys()) {
638 if (debugging() && preg_match('#[^a-zA-Z0-9_]#', $key ??
'')) {
639 throw new coding_exception('Cache definition '.$definition->get_id().' requires simple keys. Invalid key provided.', $key);
641 // We put the key first so that we can be sure the start of the key changes.
642 return (string)$key . '-' . $definition->generate_single_key_prefix();
644 $key = $definition->generate_single_key_prefix() . '-' . $key;
649 * Finds all definitions and updates them within the cache config file.
651 * @param bool $coreonly If set to true only core definitions will be updated.
653 public static function update_definitions($coreonly = false) {
656 require_once($CFG->dirroot
.'/cache/locallib.php');
657 // First update definitions
658 cache_config_writer
::update_definitions($coreonly);
659 // Second reset anything we have already initialised to ensure we're all up to date.
660 cache_factory
::reset();
664 * Update the site identifier stored by the cache API.
666 * @param string $siteidentifier
667 * @return string The new site identifier.
669 public static function update_site_identifier($siteidentifier) {
672 require_once($CFG->dirroot
.'/cache/locallib.php');
673 $factory = cache_factory
::instance();
674 $factory->updating_started();
675 $config = $factory->create_config_instance(true);
676 $siteidentifier = $config->update_site_identifier($siteidentifier);
677 $factory->updating_finished();
678 cache_factory
::reset();
679 return $siteidentifier;
683 * Returns the site identifier.
687 public static function get_site_identifier() {
689 if (!is_null(self
::$siteidentifier)) {
690 return self
::$siteidentifier;
692 // If site identifier hasn't been collected yet attempt to get it from the cache config.
693 $factory = cache_factory
::instance();
694 // If the factory is initialising then we don't want to try to get it from the config or we risk
695 // causing the cache to enter an infinite initialisation loop.
696 if (!$factory->is_initialising()) {
697 $config = $factory->create_config_instance();
698 self
::$siteidentifier = $config->get_site_identifier();
700 if (is_null(self
::$siteidentifier)) {
701 // If the site identifier is still null then config isn't aware of it yet.
702 // We'll see if the CFG is loaded, and if not we will just use unknown.
703 // It's very important here that we don't use get_config. We don't want an endless cache loop!
704 if (!empty($CFG->siteidentifier
)) {
705 self
::$siteidentifier = self
::update_site_identifier($CFG->siteidentifier
);
707 // It's not being recorded in MUC's config and the config data hasn't been loaded yet.
708 // Likely we are initialising.
712 return self
::$siteidentifier;
716 * Returns the site version.
720 public static function get_site_version() {
722 return (string)$CFG->version
;
726 * Runs cron routines for MUC.
728 public static function cron() {
729 self
::clean_old_session_data(true);
733 * Cleans old session data from cache stores used for session based definitions.
735 * @param bool $output If set to true output will be given.
737 public static function clean_old_session_data($output = false) {
740 mtrace('Cleaning up stale session data from cache stores.');
742 $factory = cache_factory
::instance();
743 $config = $factory->create_config_instance();
744 $definitions = $config->get_definitions();
745 $purgetime = time() - $CFG->sessiontimeout
;
746 foreach ($definitions as $definitionarray) {
747 // We are only interested in session caches.
748 if (!($definitionarray['mode'] & cache_store
::MODE_SESSION
)) {
751 $definition = $factory->create_definition($definitionarray['component'], $definitionarray['area']);
752 $stores = $config->get_stores_for_definition($definition);
753 // Turn them into store instances.
754 $stores = self
::initialise_cachestore_instances($stores, $definition);
755 // Initialise all of the stores used for that definition.
756 foreach ($stores as $store) {
757 // If the store doesn't support searching we can skip it.
758 if (!($store instanceof cache_is_searchable
)) {
759 debugging('Cache stores used for session definitions should ideally be searchable.', DEBUG_DEVELOPER
);
762 // Get all of the keys.
763 $keys = $store->find_by_prefix(cache_session
::KEY_PREFIX
);
765 foreach ($store->get_many($keys) as $key => $value) {
766 if (strpos($key, cache_session
::KEY_PREFIX
) !== 0 ||
!is_array($value) ||
!isset($value['lastaccess'])) {
769 if ((int)$value['lastaccess'] < $purgetime ||
true) {
773 if (count($todelete)) {
774 $outcome = (int)$store->delete_many($todelete);
776 $strdef = s($definition->get_id());
777 $strstore = s($store->my_name());
778 mtrace("- Removed {$outcome} old {$strdef} sessions from the '{$strstore}' cache store.");
786 * Returns an array of stores that would meet the requirements for every definition.
788 * These stores would be 100% suitable to map as defaults for cache modes.
790 * @return array[] An array of stores, keys are the store names.
792 public static function get_stores_suitable_for_mode_default() {
793 $factory = cache_factory
::instance();
794 $config = $factory->create_config_instance();
796 foreach ($config->get_definitions() as $definition) {
797 $definition = cache_definition
::load($definition['component'].'/'.$definition['area'], $definition);
798 $requirements = $requirements |
$definition->get_requirements_bin();
801 foreach ($config->get_all_stores() as $name => $store) {
802 if (!empty($store['features']) && ($store['features'] & $requirements)) {
803 $stores[$name] = $store;
810 * Returns stores suitable for use with a given definition.
812 * @param cache_definition $definition
813 * @return cache_store[]
815 public static function get_stores_suitable_for_definition(cache_definition
$definition) {
816 $factory = cache_factory
::instance();
818 if ($factory->is_initialising() ||
$factory->stores_disabled()) {
819 // No suitable stores here.
822 $stores = self
::get_cache_stores($definition);
823 // If mappingsonly is set, having 0 stores is ok.
824 if ((count($stores) === 0) && (!$definition->is_for_mappings_only())) {
825 // No suitable stores we found for the definition. We need to come up with a sensible default.
826 // If this has happened we can be sure that the user has mapped custom stores to either the
827 // mode of the definition. The first alternative to try is the system default for the mode.
828 // e.g. the default file store instance for application definitions.
829 $config = $factory->create_config_instance();
830 foreach ($config->get_stores($definition->get_mode()) as $name => $details) {
831 if (!empty($details['default'])) {
832 $stores[] = $factory->create_store_from_config($name, $details, $definition);
842 * Returns an array of warnings from the cache API.
844 * The warning returned here are for things like conflicting store instance configurations etc.
845 * These get shown on the admin notifications page for example.
847 * @param array|null $stores An array of stores to get warnings for, or null for all.
850 public static function warnings(array $stores = null) {
852 if ($stores === null) {
853 require_once($CFG->dirroot
.'/cache/locallib.php');
854 $stores = core_cache\administration_helper
::get_store_instance_summaries();
857 foreach ($stores as $store) {
858 if (!empty($store['warnings'])) {
859 $warnings = array_merge($warnings, $store['warnings']);
866 * A helper to determine whether a result was found.
868 * This has been deemed required after people have been confused by the fact that [] == false.
870 * @param mixed $value
873 public static function result_found($value): bool {
874 return $value !== false;