MDL-36415 usability: Use new up/down and ordering icons
[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      * Returns true if the cache API can be initialised before Moodle has finished initialising itself.
59      *
60      * This check is essential when trying to cache the likes of configuration information. It checks to make sure that the cache
61      * configuration file has been created which allows use to set up caching when ever is required.
62      *
63      * @return bool
64      */
65     public static function ready_for_early_init() {
66         return cache_config::config_file_exists();
67     }
69     /**
70      * Returns an instance of the cache_helper.
71      *
72      * This is designed for internal use only and acts as a static store.
73      * @staticvar null $instance
74      * @return cache_helper
75      */
76     protected static function instance() {
77         if (is_null(self::$instance)) {
78             self::$instance = new cache_helper();
79         }
80         return self::$instance;
81     }
83     /**
84      * Constructs an instance of the cache_helper class. Again for internal use only.
85      */
86     protected function __construct() {
87         // Nothing to do here, just making sure you can't get an instance of this.
88     }
90     /**
91      * Used as a data store for initialised definitions.
92      * @var array
93      */
94     protected $definitions = array();
96     /**
97      * Used as a data store for initialised cache stores
98      * We use this because we want to avoid establishing multiple instances of a single store.
99      * @var array
100      */
101     protected $stores = array();
103     /**
104      * Returns the class for use as a cache loader for the given mode.
105      *
106      * @param int $mode One of cache_store::MODE_
107      * @return string
108      * @throws coding_exception
109      */
110     public static function get_class_for_mode($mode) {
111         switch ($mode) {
112             case cache_store::MODE_APPLICATION :
113                 return 'cache_application';
114             case cache_store::MODE_REQUEST :
115                 return 'cache_request';
116             case cache_store::MODE_SESSION :
117                 return 'cache_session';
118         }
119         throw new coding_exception('Unknown cache mode passed. Must be one of cache_store::MODE_*');
120     }
122     /**
123      * Returns the cache stores to be used with the given definition.
124      * @param cache_definition $definition
125      * @return array
126      */
127     public static function get_cache_stores(cache_definition $definition) {
128         $instance = cache_config::instance();
129         $stores = $instance->get_stores_for_definition($definition);
130         $stores = self::initialise_cachestore_instances($stores, $definition);
131         return $stores;
132     }
134     /**
135      * Internal function for initialising an array of stores against a given cache definition.
136      *
137      * @param array $stores
138      * @param cache_definition $definition
139      * @return array
140      */
141     protected static function initialise_cachestore_instances(array $stores, cache_definition $definition) {
142         $return = array();
143         $factory = cache_factory::instance();
144         foreach ($stores as $name => $details) {
145             $store = $factory->create_store_from_config($name, $details, $definition);
146             if ($store !== false) {
147                 $return[] = $store;
148             }
149         }
150         return $return;
151     }
153     /**
154      * Returns a cache_lock instance suitable for use with the store.
155      *
156      * @param cache_store $store
157      * @return cache_lock_interface
158      */
159     public static function get_cachelock_for_store(cache_store $store) {
160         $instance = cache_config::instance();
161         $lockconf = $instance->get_lock_for_store($store->my_name());
162         $factory = cache_factory::instance();
163         return $factory->create_lock_instance($lockconf);
164     }
166     /**
167      * Returns an array of plugins without using core methods.
168      *
169      * This function explicitly does NOT use core functions as it will in some circumstances be called before Moodle has
170      * finished initialising. This happens when loading configuration for instance.
171      *
172      * @return string
173      */
174     public static function early_get_cache_plugins() {
175         global $CFG;
176         $result = array();
177         $ignored = array('CVS', '_vti_cnf', 'simpletest', 'db', 'yui', 'tests');
178         $fulldir = $CFG->dirroot.'/cache/stores';
179         $items = new DirectoryIterator($fulldir);
180         foreach ($items as $item) {
181             if ($item->isDot() or !$item->isDir()) {
182                 continue;
183             }
184             $pluginname = $item->getFilename();
185             if (in_array($pluginname, $ignored)) {
186                 continue;
187             }
188             $pluginname = clean_param($pluginname, PARAM_PLUGIN);
189             if (empty($pluginname)) {
190                 // Better ignore plugins with problematic names here.
191                 continue;
192             }
193             $result[$pluginname] = $fulldir.'/'.$pluginname;
194             unset($item);
195         }
196         unset($items);
197         return $result;
198     }
200     /**
201      * Invalidates a given set of keys from a given definition.
202      *
203      * @todo Invalidating by definition should also add to the event cache so that sessions can be invalidated (when required).
204      *
205      * @param string $component
206      * @param string $area
207      * @param array $identifiers
208      * @param array $keys
209      * @return boolean
210      */
211     public static function invalidate_by_definition($component, $area, array $identifiers = array(), $keys = array()) {
212         $cache = cache::make($component, $area, $identifiers);
213         if (is_array($keys)) {
214             $cache->delete_many($keys);
215         } else if (is_scalar($keys)) {
216             $cache->delete($keys);
217         } else {
218             throw new coding_exception('cache_helper::invalidate_by_definition only accepts $keys as array, or scalar.');
219         }
220         return true;
221     }
223     /**
224      * Invalidates a given set of keys by means of an event.
225      *
226      * @todo add support for identifiers to be supplied and utilised.
227      *
228      * @param string $event
229      * @param array $keys
230      */
231     public static function invalidate_by_event($event, array $keys) {
232         $instance = cache_config::instance();
233         $invalidationeventset = false;
234         $factory = cache_factory::instance();
235         foreach ($instance->get_definitions() as $name => $definitionarr) {
236             $definition = cache_definition::load($name, $definitionarr);
237             if ($definition->invalidates_on_event($event)) {
238                 // OK at this point we know that the definition has information to invalidate on the event.
239                 // There are two routes, either its an application cache in which case we can invalidate it now.
240                 // or it is a session cache in which case we need to set something to the "Event invalidation" definition.
241                 // No need to deal with request caches, we don't want to change data half way through a request.
242                 if ($definition->get_mode() === cache_store::MODE_APPLICATION) {
243                     $cache = $factory->create_cache($definition);
244                     $cache->delete_many($keys);
245                 }
247                 // We need to flag the event in the "Event invalidation" cache if it hasn't already happened.
248                 if ($invalidationeventset === false) {
249                     // Get the event invalidation cache.
250                     $cache = cache::make('core', 'eventinvalidation');
251                     // Get any existing invalidated keys for this cache.
252                     $data = $cache->get($event);
253                     if ($data === false) {
254                         // There are none.
255                         $data = array();
256                     }
257                     // Add our keys to them with the current cache timestamp.
258                     foreach ($keys as $key) {
259                         $data[$key] = cache::now();
260                     }
261                     // Set that data back to the cache.
262                     $cache->set($event, $data);
263                     // This only needs to occur once.
264                     $invalidationeventset = true;
265                 }
266             }
267         }
268     }
270     /**
271      * Purges the cache for a specific definition.
272      *
273      * @param string $component
274      * @param string $area
275      * @param array $identifiers
276      * @return bool
277      */
278     public static function purge_by_definition($component, $area, array $identifiers = array()) {
279         // Create the cache.
280         $cache = cache::make($component, $area, $identifiers);
281         // Purge baby, purge.
282         $cache->purge();
283         return true;
284     }
286     /**
287      * Purges a cache of all information on a given event.
288      *
289      * @param string $event
290      */
291     public static function purge_by_event($event) {
292         $instance = cache_config::instance();
293         $invalidationeventset = false;
294         $factory = cache_factory::instance();
295         foreach ($instance->get_definitions() as $name => $definitionarr) {
296             $definition = cache_definition::load($name, $definitionarr);
297             if ($definition->invalidates_on_event($event)) {
298                 // Purge the cache.
299                 $cache = $factory->create_cache($definition);
300                 $cache->purge();
302                 // We need to flag the event in the "Event invalidation" cache if it hasn't already happened.
303                 if ($invalidationeventset === false) {
304                     // Get the event invalidation cache.
305                     $cache = cache::make('core', 'eventinvalidation');
306                     // Create a key to invalidate all.
307                     $data = array(
308                         'purged' => cache::now()
309                     );
310                     // Set that data back to the cache.
311                     $cache->set($event, $data);
312                     // This only needs to occur once.
313                     $invalidationeventset = true;
314                 }
315             }
316         }
317     }
319     /**
320      * Ensure that the stats array is ready to collect information for the given store and definition.
321      * @param string $store
322      * @param string $definition
323      */
324     protected static function ensure_ready_for_stats($store, $definition) {
325         if (!array_key_exists($definition, self::$stats)) {
326             self::$stats[$definition] = array(
327                 $store => array(
328                     'hits' => 0,
329                     'misses' => 0,
330                     'sets' => 0,
331                 )
332             );
333         } else if (!array_key_exists($store, self::$stats[$definition])) {
334             self::$stats[$definition][$store] = array(
335                 'hits' => 0,
336                 'misses' => 0,
337                 'sets' => 0,
338             );
339         }
340     }
342     /**
343      * Record a cache hit in the stats for the given store and definition.
344      *
345      * @param string $store
346      * @param string $definition
347      */
348     public static function record_cache_hit($store, $definition) {
349         self::ensure_ready_for_stats($store, $definition);
350         self::$stats[$definition][$store]['hits']++;
351     }
353     /**
354      * Record a cache miss in the stats for the given store and definition.
355      *
356      * @param string $store
357      * @param string $definition
358      */
359     public static function record_cache_miss($store, $definition) {
360         self::ensure_ready_for_stats($store, $definition);
361         self::$stats[$definition][$store]['misses']++;
362     }
364     /**
365      * Record a cache set in the stats for the given store and definition.
366      *
367      * @param string $store
368      * @param string $definition
369      */
370     public static function record_cache_set($store, $definition) {
371         self::ensure_ready_for_stats($store, $definition);
372         self::$stats[$definition][$store]['sets']++;
373     }
375     /**
376      * Return the stats collected so far.
377      * @return array
378      */
379     public static function get_stats() {
380         return self::$stats;
381     }
383     /**
384      * Purge all of the cache stores of all of their data.
385      *
386      * Think twice before calling this method. It will purge **ALL** caches regardless of whether they have been used recently or
387      * anything. This will involve full setup of the cache + the purge operation. On a site using caching heavily this WILL be
388      * painful.
389      */
390     public static function purge_all() {
391         $config = cache_config::instance();
392         $stores = $config->get_all_stores();
393         $definition = cache_definition::load_adhoc(cache_store::MODE_REQUEST, 'core', 'cache_purge');
394         foreach ($stores as $store) {
395             $class = $store['class'];
396             $instance = new $class($store['name'], $store['configuration']);
397             if (!$instance->is_ready()) {
398                 continue;
399             }
400             $instance->initialise($definition);
401             $instance->purge();
402         }
403     }
405     /**
406      * Purges a store given its name.
407      *
408      * @param string $storename
409      * @return bool
410      */
411     public static function purge_store($storename) {
412         $config = cache_config::instance();
413         foreach ($config->get_all_stores() as $store) {
414             if ($store['name'] !== $storename) {
415                 continue;
416             }
417             $class = $store['class'];
418             $instance = new $class($store['name'], $store['configuration']);
419             if (!$instance->is_ready()) {
420                 continue;
421             }
422             $definition = cache_definition::load_adhoc(cache_store::MODE_REQUEST, 'core', 'cache_purge');
423             $instance->initialise($definition);
424             $instance->purge();
425             return true;
426         }
427         return false;
428     }
430     /**
431      * Returns the translated name of the definition.
432      *
433      * @param cache_definition $definition
434      * @return lang_string
435      */
436     public static function get_definition_name($definition) {
437         if ($definition instanceof cache_definition) {
438             return $definition->get_name();
439         }
440         $identifier = 'cachedef_'.clean_param($definition['area'], PARAM_STRINGID);
441         $component = $definition['component'];
442         if ($component === 'core') {
443             $component = 'cache';
444         }
445         return new lang_string($identifier, $component);
446     }
448     /**
449      * Hashes a descriptive key to make it shorter and still unique.
450      * @param string|int $key
451      * @param cache_definition $definition
452      * @return string
453      */
454     public static function hash_key($key, cache_definition $definition) {
455         if ($definition->uses_simple_keys()) {
456             // We put the key first so that we can be sure the start of the key changes.
457             return (string)$key . '-' . $definition->generate_single_key_prefix();
458         }
459         $key = $definition->generate_single_key_prefix() . '-' . $key;
460         return sha1($key);
461     }
463     /**
464      * Finds all definitions and updates them within the cache config file.
465      *
466      * @param bool $coreonly If set to true only core definitions will be updated.
467      */
468     public static function update_definitions($coreonly = false) {
469         global $CFG;
470         // Include locallib
471         require_once($CFG->dirroot.'/cache/locallib.php');
472         // First update definitions
473         cache_config_writer::update_definitions($coreonly);
474         // Second reset anything we have already initialised to ensure we're all up to date.
475         cache_factory::reset();
476     }