Merge branch 'MDL-36900-master' of git://github.com/FMCorz/moodle
[moodle.git] / cache / classes / helper.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
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.
8 //
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.
13 //
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/>.
17 /**
18  * Cache helper class
19  *
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.
22  *
23  * @package    core
24  * @category   cache
25  * @copyright  2012 Sam Hemelryk
26  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27  */
29 defined('MOODLE_INTERNAL') || die();
31 /**
32  * The cache helper class.
33  *
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.
36  *
37  * @package    core
38  * @category   cache
39  * @copyright  2012 Sam Hemelryk
40  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
41  */
42 class cache_helper {
44     /**
45      * Statistics gathered by the cache API during its operation will be used here.
46      * @static
47      * @var array
48      */
49     protected static $stats = array();
51     /**
52      * The instance of the cache helper.
53      * @var cache_helper
54      */
55     protected static $instance;
57     /**
58      * The site identifier used by the cache.
59      * Set the first time get_site_identifier is called.
60      * @var string
61      */
62     protected static $siteidentifier = null;
64     /**
65      * Returns true if the cache API can be initialised before Moodle has finished initialising itself.
66      *
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.
69      *
70      * @return bool
71      */
72     public static function ready_for_early_init() {
73         return cache_config::config_file_exists();
74     }
76     /**
77      * Returns an instance of the cache_helper.
78      *
79      * This is designed for internal use only and acts as a static store.
80      * @staticvar null $instance
81      * @return cache_helper
82      */
83     protected static function instance() {
84         if (is_null(self::$instance)) {
85             self::$instance = new cache_helper();
86         }
87         return self::$instance;
88     }
90     /**
91      * Constructs an instance of the cache_helper class. Again for internal use only.
92      */
93     protected function __construct() {
94         // Nothing to do here, just making sure you can't get an instance of this.
95     }
97     /**
98      * Used as a data store for initialised definitions.
99      * @var array
100      */
101     protected $definitions = array();
103     /**
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.
106      * @var array
107      */
108     protected $stores = array();
110     /**
111      * Returns the class for use as a cache loader for the given mode.
112      *
113      * @param int $mode One of cache_store::MODE_
114      * @return string
115      * @throws coding_exception
116      */
117     public static function get_class_for_mode($mode) {
118         switch ($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';
125         }
126         throw new coding_exception('Unknown cache mode passed. Must be one of cache_store::MODE_*');
127     }
129     /**
130      * Returns the cache stores to be used with the given definition.
131      * @param cache_definition $definition
132      * @return array
133      */
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);
138         return $stores;
139     }
141     /**
142      * Internal function for initialising an array of stores against a given cache definition.
143      *
144      * @param array $stores
145      * @param cache_definition $definition
146      * @return array
147      */
148     protected static function initialise_cachestore_instances(array $stores, cache_definition $definition) {
149         $return = array();
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) {
154                 $return[] = $store;
155             }
156         }
157         return $return;
158     }
160     /**
161      * Returns a cache_lock instance suitable for use with the store.
162      *
163      * @param cache_store $store
164      * @return cache_lock_interface
165      */
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);
171     }
173     /**
174      * Returns an array of plugins without using core methods.
175      *
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.
178      *
179      * @return string
180      */
181     public static function early_get_cache_plugins() {
182         global $CFG;
183         $result = array();
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()) {
189                 continue;
190             }
191             $pluginname = $item->getFilename();
192             if (in_array($pluginname, $ignored)) {
193                 continue;
194             }
195             $pluginname = clean_param($pluginname, PARAM_PLUGIN);
196             if (empty($pluginname)) {
197                 // Better ignore plugins with problematic names here.
198                 continue;
199             }
200             $result[$pluginname] = $fulldir.'/'.$pluginname;
201             unset($item);
202         }
203         unset($items);
204         return $result;
205     }
207     /**
208      * Invalidates a given set of keys from a given definition.
209      *
210      * @todo Invalidating by definition should also add to the event cache so that sessions can be invalidated (when required).
211      *
212      * @param string $component
213      * @param string $area
214      * @param array $identifiers
215      * @param array $keys
216      * @return boolean
217      */
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);
224         } else {
225             throw new coding_exception('cache_helper::invalidate_by_definition only accepts $keys as array, or scalar.');
226         }
227         return true;
228     }
230     /**
231      * Invalidates a given set of keys by means of an event.
232      *
233      * @todo add support for identifiers to be supplied and utilised.
234      *
235      * @param string $event
236      * @param array $keys
237      */
238     public static function invalidate_by_event($event, array $keys) {
239         $instance = cache_config::instance();
240         $invalidationeventset = false;
241         $factory = cache_factory::instance();
242         foreach ($instance->get_definitions() as $name => $definitionarr) {
243             $definition = cache_definition::load($name, $definitionarr);
244             if ($definition->invalidates_on_event($event)) {
245                 // OK at this point we know that the definition has information to invalidate on the event.
246                 // There are two routes, either its an application cache in which case we can invalidate it now.
247                 // or it is a session cache in which case we need to set something to the "Event invalidation" definition.
248                 // No need to deal with request caches, we don't want to change data half way through a request.
249                 if ($definition->get_mode() === cache_store::MODE_APPLICATION) {
250                     $cache = $factory->create_cache($definition);
251                     $cache->delete_many($keys);
252                 }
254                 // We need to flag the event in the "Event invalidation" cache if it hasn't already happened.
255                 if ($invalidationeventset === false) {
256                     // Get the event invalidation cache.
257                     $cache = cache::make('core', 'eventinvalidation');
258                     // Get any existing invalidated keys for this cache.
259                     $data = $cache->get($event);
260                     if ($data === false) {
261                         // There are none.
262                         $data = array();
263                     }
264                     // Add our keys to them with the current cache timestamp.
265                     foreach ($keys as $key) {
266                         $data[$key] = cache::now();
267                     }
268                     // Set that data back to the cache.
269                     $cache->set($event, $data);
270                     // This only needs to occur once.
271                     $invalidationeventset = true;
272                 }
273             }
274         }
275     }
277     /**
278      * Purges the cache for a specific definition.
279      *
280      * @todo MDL-36660: Change the signature: $aggregate must be added.
281      *
282      * @param string $component
283      * @param string $area
284      * @param array $identifiers
285      * @return bool
286      */
287     public static function purge_by_definition($component, $area, array $identifiers = array()) {
288         // Create the cache.
289         $cache = cache::make($component, $area, $identifiers);
290         // Initialise, in case of a store.
291         if ($cache instanceof cache_store) {
292             $factory = cache_factory::instance();
293             // TODO MDL-36660: Providing $aggregate is required for purging purposes: $definition->get_id()
294             $definition = $factory->create_definition($component, $area, null);
295             $definition->set_identifiers($identifiers);
296             $cache->initialise($definition);
297         }
298         // Purge baby, purge.
299         $cache->purge();
300         return true;
301     }
303     /**
304      * Purges a cache of all information on a given event.
305      *
306      * @param string $event
307      */
308     public static function purge_by_event($event) {
309         $instance = cache_config::instance();
310         $invalidationeventset = false;
311         $factory = cache_factory::instance();
312         foreach ($instance->get_definitions() as $name => $definitionarr) {
313             $definition = cache_definition::load($name, $definitionarr);
314             if ($definition->invalidates_on_event($event)) {
315                 // Check if this definition would result in a persistent loader being in use.
316                 if ($definition->should_be_persistent()) {
317                     // There may be a persistent cache loader. Lets purge that first so that any persistent data is removed.
318                     $cache = $factory->create_cache_from_definition($definition->get_component(), $definition->get_area());
319                     $cache->purge();
320                 }
321                 // Get all of the store instances that are in use for this store.
322                 $stores = $factory->get_store_instances_in_use($definition);
323                 foreach ($stores as $store) {
324                     // Purge each store individually.
325                     $store->purge();
326                 }
327                 // We need to flag the event in the "Event invalidation" cache if it hasn't already happened.
328                 if ($invalidationeventset === false) {
329                     // Get the event invalidation cache.
330                     $cache = cache::make('core', 'eventinvalidation');
331                     // Create a key to invalidate all.
332                     $data = array(
333                         'purged' => cache::now()
334                     );
335                     // Set that data back to the cache.
336                     $cache->set($event, $data);
337                     // This only needs to occur once.
338                     $invalidationeventset = true;
339                 }
340             }
341         }
342     }
344     /**
345      * Ensure that the stats array is ready to collect information for the given store and definition.
346      * @param string $store
347      * @param string $definition
348      */
349     protected static function ensure_ready_for_stats($store, $definition) {
350         if (!array_key_exists($definition, self::$stats)) {
351             self::$stats[$definition] = array(
352                 $store => array(
353                     'hits' => 0,
354                     'misses' => 0,
355                     'sets' => 0,
356                 )
357             );
358         } else if (!array_key_exists($store, self::$stats[$definition])) {
359             self::$stats[$definition][$store] = array(
360                 'hits' => 0,
361                 'misses' => 0,
362                 'sets' => 0,
363             );
364         }
365     }
367     /**
368      * Record a cache hit in the stats for the given store and definition.
369      *
370      * @param string $store
371      * @param string $definition
372      */
373     public static function record_cache_hit($store, $definition) {
374         self::ensure_ready_for_stats($store, $definition);
375         self::$stats[$definition][$store]['hits']++;
376     }
378     /**
379      * Record a cache miss in the stats for the given store and definition.
380      *
381      * @param string $store
382      * @param string $definition
383      */
384     public static function record_cache_miss($store, $definition) {
385         self::ensure_ready_for_stats($store, $definition);
386         self::$stats[$definition][$store]['misses']++;
387     }
389     /**
390      * Record a cache set in the stats for the given store and definition.
391      *
392      * @param string $store
393      * @param string $definition
394      */
395     public static function record_cache_set($store, $definition) {
396         self::ensure_ready_for_stats($store, $definition);
397         self::$stats[$definition][$store]['sets']++;
398     }
400     /**
401      * Return the stats collected so far.
402      * @return array
403      */
404     public static function get_stats() {
405         return self::$stats;
406     }
408     /**
409      * Purge all of the cache stores of all of their data.
410      *
411      * Think twice before calling this method. It will purge **ALL** caches regardless of whether they have been used recently or
412      * anything. This will involve full setup of the cache + the purge operation. On a site using caching heavily this WILL be
413      * painful.
414      */
415     public static function purge_all() {
416         $config = cache_config::instance();
418         foreach ($config->get_all_stores() as $store) {
419             self::purge_store($store['name']);
420         }
421     }
423     /**
424      * Purges a store given its name.
425      *
426      * @param string $storename
427      * @return bool
428      */
429     public static function purge_store($storename) {
430         $config = cache_config::instance();
432         $stores = $config->get_all_stores();
433         if (!array_key_exists($storename, $stores)) {
434             // The store does not exist.
435             return false;
436         }
438         $store = $stores[$storename];
439         $class = $store['class'];
441         // Found the store: is it ready?
442         $instance = new $class($store['name'], $store['configuration']);
443         if (!$instance->is_ready()) {
444             unset($instance);
445             return false;
446         }
448         foreach ($config->get_definitions_by_store($storename) as $id => $definition) {
449             $definition = cache_definition::load($id, $definition);
450             $instance = new $class($store['name'], $store['configuration']);
451             $instance->initialise($definition);
452             $instance->purge();
453             unset($instance);
454         }
456         return true;
457     }
459     /**
460      * Returns the translated name of the definition.
461      *
462      * @param cache_definition $definition
463      * @return lang_string
464      */
465     public static function get_definition_name($definition) {
466         if ($definition instanceof cache_definition) {
467             return $definition->get_name();
468         }
469         $identifier = 'cachedef_'.clean_param($definition['area'], PARAM_STRINGID);
470         $component = $definition['component'];
471         if ($component === 'core') {
472             $component = 'cache';
473         }
474         return new lang_string($identifier, $component);
475     }
477     /**
478      * Hashes a descriptive key to make it shorter and still unique.
479      * @param string|int $key
480      * @param cache_definition $definition
481      * @return string
482      */
483     public static function hash_key($key, cache_definition $definition) {
484         if ($definition->uses_simple_keys()) {
485             if (debugging() && preg_match('#[^a-zA-Z0-9_]#', $key)) {
486                 throw new coding_exception('Cache definition '.$definition->get_id().' requires simple keys. Invalid key provided.', $key);
487             }
488             // We put the key first so that we can be sure the start of the key changes.
489             return (string)$key . '-' . $definition->generate_single_key_prefix();
490         }
491         $key = $definition->generate_single_key_prefix() . '-' . $key;
492         return sha1($key);
493     }
495     /**
496      * Finds all definitions and updates them within the cache config file.
497      *
498      * @param bool $coreonly If set to true only core definitions will be updated.
499      */
500     public static function update_definitions($coreonly = false) {
501         global $CFG;
502         // Include locallib.
503         require_once($CFG->dirroot.'/cache/locallib.php');
504         // First update definitions
505         cache_config_writer::update_definitions($coreonly);
506         // Second reset anything we have already initialised to ensure we're all up to date.
507         cache_factory::reset();
508     }
510     /**
511      * Update the site identifier stored by the cache API.
512      *
513      * @param string $siteidentifier
514      * @return string The new site identifier.
515      */
516     public static function update_site_identifier($siteidentifier) {
517         global $CFG;
518         // Include locallib.
519         require_once($CFG->dirroot.'/cache/locallib.php');
520         $factory = cache_factory::instance();
521         $factory->updating_started();
522         $config = $factory->create_config_instance(true);
523         $siteidentifier = $config->update_site_identifier($siteidentifier);
524         $factory->updating_finished();
525         cache_factory::reset();
526         return $siteidentifier;
527     }
529     /**
530      * Returns the site identifier.
531      *
532      * @return string
533      */
534     public static function get_site_identifier() {
535         global $CFG;
536         if (!is_null(self::$siteidentifier)) {
537             return self::$siteidentifier;
538         }
539         // If site identifier hasn't been collected yet attempt to get it from the cache config.
540         $factory = cache_factory::instance();
541         // If the factory is initialising then we don't want to try to get it from the config or we risk
542         // causing the cache to enter an infinite initialisation loop.
543         if (!$factory->is_initialising()) {
544             $config = $factory->create_config_instance();
545             self::$siteidentifier = $config->get_site_identifier();
546         }
547         if (is_null(self::$siteidentifier)) {
548             // If the site identifier is still null then config isn't aware of it yet.
549             // We'll see if the CFG is loaded, and if not we will just use unknown.
550             // It's very important here that we don't use get_config. We don't want an endless cache loop!
551             if (isset($CFG->siteidentifier)) {
552                 self::$siteidentifier = self::update_site_identifier($CFG->siteidentifier);
553             } else {
554                 // It's not being recorded in MUC's config and the config data hasn't been loaded yet.
555                 // Likely we are initialising.
556                 return 'unknown';
557             }
558         }
559         return self::$siteidentifier;
560     }
562     /**
563      * Returns the site version.
564      *
565      * @return string
566      */
567     public static function get_site_version() {
568         global $CFG;
569         return (string)$CFG->version;
570     }