Merge branch 'MDL-65976-master' of git://github.com/jleyva/moodle into master
[moodle.git] / cache / classes / factory.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  * This file contains the cache factory 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 factory class.
33  *
34  * This factory class is important because it stores instances of objects used by the cache API and returns them upon requests.
35  * This allows us to both reuse objects saving on overhead, and gives us an easy place to "reset" the cache API in situations that
36  * we need such as unit testing.
37  *
38  * @copyright  2012 Sam Hemelryk
39  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
40  */
41 class cache_factory {
43     /** The cache has not been initialised yet. */
44     const STATE_UNINITIALISED = 0;
45     /** The cache is in the process of initialising itself. */
46     const STATE_INITIALISING = 1;
47     /** The cache is in the process of saving its configuration file. */
48     const STATE_SAVING = 2;
49     /** The cache is ready to use. */
50     const STATE_READY = 3;
51     /** The cache is currently updating itself */
52     const STATE_UPDATING = 4;
53     /** The cache encountered an error while initialising. */
54     const STATE_ERROR_INITIALISING = 9;
55     /** The cache has been disabled. */
56     const STATE_DISABLED = 10;
57     /** The cache stores have been disabled */
58     const STATE_STORES_DISABLED = 11;
60     /**
61      * An instance of the cache_factory class created upon the first request.
62      * @var cache_factory
63      */
64     protected static $instance;
66     /**
67      * An array containing caches created for definitions
68      * @var array
69      */
70     protected $cachesfromdefinitions = array();
72     /**
73      * Array of caches created by parameters, ad-hoc definitions will have been used.
74      * @var array
75      */
76     protected $cachesfromparams = array();
78     /**
79      * An array of stores organised by definitions.
80      * @var array
81      */
82     protected $definitionstores = array();
84     /**
85      * An array of instantiated stores.
86      * @var array
87      */
88     protected $stores = array();
90     /**
91      * An array of configuration instances
92      * @var array
93      */
94     protected $configs = array();
96     /**
97      * An array of initialised definitions
98      * @var array
99      */
100     protected $definitions = array();
102     /**
103      * An array of lock plugins.
104      * @var array
105      */
106     protected $lockplugins = array();
108     /**
109      * The current state of the cache API.
110      * @var int
111      */
112     protected $state = 0;
114     /**
115      * The current cache display helper.
116      * @var core_cache\local\administration_display_helper
117      */
118     protected static $displayhelper = null;
120     /**
121      * Returns an instance of the cache_factory class.
122      *
123      * @param bool $forcereload If set to true a new cache_factory instance will be created and used.
124      * @return cache_factory
125      */
126     public static function instance($forcereload = false) {
127         global $CFG;
128         if ($forcereload || self::$instance === null) {
129             // Initialise a new factory to facilitate our needs.
130             if (defined('CACHE_DISABLE_ALL') && CACHE_DISABLE_ALL !== false) {
131                 // The cache has been disabled. Load disabledlib and start using the factory designed to handle this
132                 // situation. It will use disabled alternatives where available.
133                 require_once($CFG->dirroot.'/cache/disabledlib.php');
134                 self::$instance = new cache_factory_disabled();
135             } else if ((defined('PHPUNIT_TEST') && PHPUNIT_TEST) || defined('BEHAT_SITE_RUNNING')) {
136                 // We're using the test factory.
137                 require_once($CFG->dirroot.'/cache/tests/fixtures/lib.php');
138                 self::$instance = new cache_phpunit_factory();
139                 if (defined('CACHE_DISABLE_STORES') && CACHE_DISABLE_STORES !== false) {
140                     // The cache stores have been disabled.
141                     self::$instance->set_state(self::STATE_STORES_DISABLED);
142                 }
144             } else if (!empty($CFG->alternative_cache_factory_class)) {
145                 $factoryclass = $CFG->alternative_cache_factory_class;
146                 self::$instance = new $factoryclass();
147             } else {
148                 // We're using the regular factory.
149                 self::$instance = new cache_factory();
150                 if (defined('CACHE_DISABLE_STORES') && CACHE_DISABLE_STORES !== false) {
151                     // The cache stores have been disabled.
152                     self::$instance->set_state(self::STATE_STORES_DISABLED);
153                 }
154             }
155         }
156         return self::$instance;
157     }
159     /**
160      * Protected constructor, please use the static instance method.
161      */
162     protected function __construct() {
163         // Nothing to do here.
164     }
166     /**
167      * Resets the arrays containing instantiated caches, stores, and config instances.
168      */
169     public static function reset() {
170         $factory = self::instance();
171         $factory->reset_cache_instances();
172         $factory->configs = array();
173         $factory->definitions = array();
174         $factory->definitionstores = array();
175         $factory->lockplugins = array(); // MUST be null in order to force its regeneration.
176         // Reset the state to uninitialised.
177         $factory->state = self::STATE_UNINITIALISED;
178     }
180     /**
181      * Resets the stores, clearing the array of created stores.
182      *
183      * Cache objects still held onto by the code that initialised them will remain as is
184      * however all future requests for a cache/store will lead to a new instance being re-initialised.
185      */
186     public function reset_cache_instances() {
187         $this->cachesfromdefinitions = array();
188         $this->cachesfromparams = array();
189         $this->stores = array();
190     }
192     /**
193      * Creates a cache object given the parameters for a definition.
194      *
195      * If a cache has already been created for the given definition then that cache instance will be returned.
196      *
197      * @param string $component
198      * @param string $area
199      * @param array $identifiers
200      * @param string $unused Used to be data source aggregate however that was removed and this is now unused.
201      * @return cache_application|cache_session|cache_request
202      */
203     public function create_cache_from_definition($component, $area, array $identifiers = array(), $unused = null) {
204         $identifierstring = empty($identifiers) ? '' : '/'.http_build_query($identifiers);
205         $definitionname = $component.'/'.$area.$identifierstring;
206         if (isset($this->cachesfromdefinitions[$definitionname])) {
207             $cache = $this->cachesfromdefinitions[$definitionname];
208             return $cache;
209         }
210         $definition = $this->create_definition($component, $area);
211         // Identifiers are cached as part of the cache creation, so we store a cloned version of the cache.
212         $cacheddefinition = clone($definition);
213         $cacheddefinition->set_identifiers($identifiers);
214         $cache = $this->create_cache($cacheddefinition);
216         // Loaders are always held onto to speed up subsequent requests.
217         $this->cachesfromdefinitions[$definitionname] = $cache;
218         return $cache;
219     }
221     /**
222      * Creates an ad-hoc cache from the given param.
223      *
224      * If a cache has already been created using the same params then that cache instance will be returned.
225      *
226      * @param int $mode
227      * @param string $component
228      * @param string $area
229      * @param array $identifiers
230      * @param array $options An array of options, available options are:
231      *   - simplekeys : Set to true if the keys you will use are a-zA-Z0-9_
232      *   - simpledata : Set to true if the type of the data you are going to store is scalar, or an array of scalar vars
233      *   - staticacceleration : If set to true the cache will hold onto data passing through it.
234      *   - staticaccelerationsize : The maximum number of items to hold onto for acceleration purposes.
235      * @return cache_application|cache_session|cache_request
236      */
237     public function create_cache_from_params($mode, $component, $area, array $identifiers = array(), array $options = array()) {
238         $identifierstring = empty($identifiers) ? '' : '_'.http_build_query($identifiers);
239         $key = "{$mode}_{$component}_{$area}{$identifierstring}";
240         if (isset($this->cachesfromparams[$key])) {
241             return $this->cachesfromparams[$key];
242         }
243         // Regular cache definitions are cached inside create_definition().  This is not the case for Adhoc definitions
244         // using load_adhoc().  They are built as a new object on each call.
245         // We do not need to clone the definition because we know it's new.
246         $definition = cache_definition::load_adhoc($mode, $component, $area, $options);
247         $definition->set_identifiers($identifiers);
248         $cache = $this->create_cache($definition);
249         $this->cachesfromparams[$key] = $cache;
250         return $cache;
251     }
253     /**
254      * Common public method to create a cache instance given a definition.
255      *
256      * This is used by the static make methods.
257      *
258      * @param cache_definition $definition
259      * @return cache_application|cache_session|cache_store
260      * @throws coding_exception
261      */
262     public function create_cache(cache_definition $definition) {
263         $class = $definition->get_cache_class();
264         $stores = cache_helper::get_stores_suitable_for_definition($definition);
265         foreach ($stores as $key => $store) {
266             if (!$store::are_requirements_met()) {
267                 unset($stores[$key]);
268             }
269         }
270         if (count($stores) === 0) {
271             // Hmm still no stores, better provide a dummy store to mimic functionality. The dev will be none the wiser.
272             $stores[] = $this->create_dummy_store($definition);
273         }
274         $loader = null;
275         if ($definition->has_data_source()) {
276             $loader = $definition->get_data_source();
277         }
278         while (($store = array_pop($stores)) !== null) {
279             $loader = new $class($definition, $store, $loader);
280         }
281         return $loader;
282     }
284     /**
285      * Creates a store instance given its name and configuration.
286      *
287      * If the store has already been instantiated then the original object will be returned. (reused)
288      *
289      * @param string $name The name of the store (must be unique remember)
290      * @param array $details
291      * @param cache_definition $definition The definition to instantiate it for.
292      * @return boolean|cache_store
293      */
294     public function create_store_from_config($name, array $details, cache_definition $definition) {
295         if (!array_key_exists($name, $this->stores)) {
296             // Properties: name, plugin, configuration, class.
297             $class = $details['class'];
298             if (!$class::are_requirements_met()) {
299                 return false;
300             }
301             $store = new $class($details['name'], $details['configuration']);
302             $this->stores[$name] = $store;
303         }
304         /* @var cache_store $store */
305         $store = $this->stores[$name];
306         // We check are_requirements_met although we expect is_ready is going to check as well.
307         if (!$store::are_requirements_met() || !$store->is_ready() || !$store->is_supported_mode($definition->get_mode())) {
308             return false;
309         }
310         // We always create a clone of the original store.
311         // If we were to clone a store that had already been initialised with a definition then
312         // we'd run into a myriad of issues.
313         // We use a method of the store to create a clone rather than just creating it ourselves
314         // so that if any store out there doesn't handle cloning they can override this method in
315         // order to address the issues.
316         $store = $this->stores[$name]->create_clone($details);
317         $store->initialise($definition);
318         $definitionid = $definition->get_id();
319         if (!isset($this->definitionstores[$definitionid])) {
320             $this->definitionstores[$definitionid] = array();
321         }
322         $this->definitionstores[$definitionid][] = $store;
323         return $store;
324     }
326     /**
327      * Returns an array of cache stores that have been initialised for use in definitions.
328      * @param cache_definition $definition
329      * @return array
330      */
331     public function get_store_instances_in_use(cache_definition $definition) {
332         $id = $definition->get_id();
333         if (!isset($this->definitionstores[$id])) {
334             return array();
335         }
336         return $this->definitionstores[$id];
337     }
339     /**
340      * Returns the cache instances that have been used within this request.
341      * @since Moodle 2.6
342      * @return array
343      */
344     public function get_caches_in_use() {
345         return $this->cachesfromdefinitions;
346     }
348     /**
349      * Gets all adhoc caches that have been used within this request.
350      *
351      * @return cache_store[] Caches currently in use
352      */
353     public function get_adhoc_caches_in_use() {
354         return $this->cachesfromparams;
355     }
357     /**
358      * Creates a cache config instance with the ability to write if required.
359      *
360      * @param bool $writer If set to true an instance that can update the configuration will be returned.
361      * @return cache_config|cache_config_writer
362      */
363     public function create_config_instance($writer = false) {
364         global $CFG;
366         // The class to use.
367         $class = 'cache_config';
368         // Are we running tests of some form?
369         $testing = (defined('PHPUNIT_TEST') && PHPUNIT_TEST) || defined('BEHAT_SITE_RUNNING');
371         // Check if this is a PHPUnit test and redirect to the phpunit config classes if it is.
372         if ($testing) {
373             require_once($CFG->dirroot.'/cache/locallib.php');
374             require_once($CFG->dirroot.'/cache/tests/fixtures/lib.php');
375             // We have just a single class for PHP unit tests. We don't care enough about its
376             // performance to do otherwise and having a single method allows us to inject things into it
377             // while testing.
378             $class = 'cache_config_testing';
379         }
381         // Check if we need to create a config file with defaults.
382         $needtocreate = !$class::config_file_exists();
384         if ($writer || $needtocreate) {
385             require_once($CFG->dirroot.'/cache/locallib.php');
386             if (!$testing) {
387                 $class .= '_writer';
388             }
389         }
391         $error = false;
392         if ($needtocreate) {
393             // Create the default configuration.
394             // Update the state, we are now initialising the cache.
395             self::set_state(self::STATE_INITIALISING);
396             /** @var cache_config_writer $class */
397             $configuration = $class::create_default_configuration();
398             if ($configuration !== true) {
399                 // Failed to create the default configuration. Disable the cache stores and update the state.
400                 self::set_state(self::STATE_ERROR_INITIALISING);
401                 $this->configs[$class] = new $class;
402                 $this->configs[$class]->load($configuration);
403                 $error = true;
404             }
405         }
407         if (!array_key_exists($class, $this->configs)) {
408             // Create a new instance and call it to load it.
409             $this->configs[$class] = new $class;
410             $this->configs[$class]->load();
411         }
413         if (!$error) {
414             // The cache is now ready to use. Update the state.
415             self::set_state(self::STATE_READY);
416         }
418         // Return the instance.
419         return $this->configs[$class];
420     }
422     /**
423      * Creates a definition instance or returns the existing one if it has already been created.
424      * @param string $component
425      * @param string $area
426      * @param string $unused This used to be data source aggregate - however that functionality has been removed and
427      *        this argument is now unused.
428      * @return cache_definition
429      * @throws coding_exception If the definition cannot be found.
430      */
431     public function create_definition($component, $area, $unused = null) {
432         $id = $component.'/'.$area;
433         if (!isset($this->definitions[$id])) {
434             // This is the first time this definition has been requested.
435             if ($this->is_initialising()) {
436                 // We're initialising the cache right now. Don't try to create another config instance.
437                 // We'll just use an ad-hoc cache for the time being.
438                 $definition = cache_definition::load_adhoc(cache_store::MODE_REQUEST, $component, $area);
439             } else {
440                 // Load all the known definitions and find the desired one.
441                 $instance = $this->create_config_instance();
442                 $definition = $instance->get_definition_by_id($id);
443                 if (!$definition) {
444                     // Oh-oh the definition doesn't exist.
445                     // There are several things that could be going on here.
446                     // We may be installing/upgrading a site and have hit a definition that hasn't been used before.
447                     // Of the developer may be trying to use a newly created definition.
448                     if ($this->is_updating()) {
449                         // The cache is presently initialising and the requested cache definition has not been found.
450                         // This means that the cache initialisation has requested something from a cache (I had recursive nightmares about this).
451                         // To serve this purpose and avoid errors we are going to make use of an ad-hoc cache rather than
452                         // search for the definition which would possibly cause an infitite loop trying to initialise the cache.
453                         $definition = cache_definition::load_adhoc(cache_store::MODE_REQUEST, $component, $area);
454                     } else {
455                         // Either a typo of the developer has just created the definition and is using it for the first time.
456                         $this->reset();
457                         $instance = $this->create_config_instance(true);
458                         $instance->update_definitions();
459                         $definition = $instance->get_definition_by_id($id);
460                         if (!$definition) {
461                             throw new coding_exception('The requested cache definition does not exist.'. $id, $id);
462                         }
463                         if (!$this->is_disabled()) {
464                             debugging('Cache definitions reparsed causing cache reset in order to locate definition.
465                                 You should bump the version number to ensure definitions are reprocessed.', DEBUG_DEVELOPER);
466                         }
467                         $definition = cache_definition::load($id, $definition);
468                     }
469                 } else {
470                     $definition = cache_definition::load($id, $definition);
471                 }
472             }
473             $this->definitions[$id] = $definition;
474         }
475         return $this->definitions[$id];
476     }
478     /**
479      * Creates a dummy store object for use when a loader has no potential stores to use.
480      *
481      * @param cache_definition $definition
482      * @return cachestore_dummy
483      */
484     protected function create_dummy_store(cache_definition $definition) {
485         global $CFG;
486         require_once($CFG->dirroot.'/cache/classes/dummystore.php');
487         $store = new cachestore_dummy();
488         $store->initialise($definition);
489         return $store;
490     }
492     /**
493      * Returns a lock instance ready for use.
494      *
495      * @param array $config
496      * @return cache_lock_interface
497      */
498     public function create_lock_instance(array $config) {
499         global $CFG;
500         if (!array_key_exists('name', $config) || !array_key_exists('type', $config)) {
501             throw new coding_exception('Invalid cache lock instance provided');
502         }
503         $name = $config['name'];
504         $type = $config['type'];
505         unset($config['name']);
506         unset($config['type']);
508         if (!isset($this->lockplugins[$type])) {
509             $pluginname = substr($type, 10);
510             $file = $CFG->dirroot."/cache/locks/{$pluginname}/lib.php";
511             if (file_exists($file) && is_readable($file)) {
512                 require_once($file);
513             }
514             if (!class_exists($type)) {
515                 throw new coding_exception('Invalid lock plugin requested.');
516             }
517             $this->lockplugins[$type] = $type;
518         }
519         if (!array_key_exists($type, $this->lockplugins)) {
520             throw new coding_exception('Invalid cache lock type.');
521         }
522         $class = $this->lockplugins[$type];
523         return new $class($name, $config);
524     }
526     /**
527      * Returns the current state of the cache API.
528      *
529      * @return int
530      */
531     public function get_state() {
532         return $this->state;
533     }
535     /**
536      * Updates the state fo the cache API.
537      *
538      * @param int $state
539      * @return bool
540      */
541     public function set_state($state) {
542         if ($state <= $this->state) {
543             return false;
544         }
545         $this->state = $state;
546         return true;
547     }
549     /**
550      * Informs the factory that the cache is currently updating itself.
551      *
552      * This forces the state to upgrading and can only be called once the cache is ready to use.
553      * Calling it ensure we don't try to reinstantite things when requesting cache definitions that don't exist yet.
554      */
555     public function updating_started() {
556         if ($this->state !== self::STATE_READY) {
557             return false;
558         }
559         $this->state = self::STATE_UPDATING;
560         return true;
561     }
563     /**
564      * Informs the factory that the upgrading has finished.
565      *
566      * This forces the state back to ready.
567      */
568     public function updating_finished() {
569         $this->state = self::STATE_READY;
570     }
572     /**
573      * Returns true if the cache API has been disabled.
574      *
575      * @return bool
576      */
577     public function is_disabled() {
578         return $this->state === self::STATE_DISABLED;
579     }
581     /**
582      * Returns true if the cache is currently initialising itself.
583      *
584      * This includes both initialisation and saving the cache config file as part of that initialisation.
585      *
586      * @return bool
587      */
588     public function is_initialising() {
589         return $this->state === self::STATE_INITIALISING || $this->state === self::STATE_SAVING;
590     }
592     /**
593      * Returns true if the cache is currently updating itself.
594      *
595      * @return bool
596      */
597     public function is_updating() {
598         return $this->state === self::STATE_UPDATING;
599     }
601     /**
602      * Disables as much of the cache API as possible.
603      *
604      * All of the magic associated with the disabled cache is wrapped into this function.
605      * In switching out the factory for the disabled factory it gains full control over the initialisation of objects
606      * and can use all of the disabled alternatives.
607      * Simple!
608      *
609      * This function has been marked as protected so that it cannot be abused through the public API presently.
610      * Perhaps in the future we will allow this, however as per the build up to the first release containing
611      * MUC it was decided that this was just to risky and abusable.
612      */
613     protected static function disable() {
614         global $CFG;
615         require_once($CFG->dirroot.'/cache/disabledlib.php');
616         self::$instance = new cache_factory_disabled();
617     }
619     /**
620      * Returns true if the cache stores have been disabled.
621      *
622      * @return bool
623      */
624     public function stores_disabled() {
625         return $this->state === self::STATE_STORES_DISABLED || $this->is_disabled();
626     }
628     /**
629      * Disables cache stores.
630      *
631      * The cache API will continue to function however none of the actual stores will be used.
632      * Instead the dummy store will be provided for all cache requests.
633      * This is useful in situations where you cannot be sure any stores are working.
634      *
635      * In order to re-enable the cache you must call the cache factories static reset method:
636      * <code>
637      * // Disable the cache factory.
638      * cache_factory::disable_stores();
639      * // Re-enable the cache factory by resetting it.
640      * cache_factory::reset();
641      * </code>
642      */
643     public static function disable_stores() {
644         // First reset to clear any static acceleration array.
645         $factory = self::instance();
646         $factory->reset_cache_instances();
647         $factory->set_state(self::STATE_STORES_DISABLED);
648     }
650     /**
651      * Returns an instance of the current display_helper.
652      *
653      * @return core_cache\administration_helper
654      */
655     public static function get_administration_display_helper() : core_cache\administration_helper {
656         if (is_null(self::$displayhelper)) {
657             self::$displayhelper = new \core_cache\local\administration_display_helper();
658         }
659         return self::$displayhelper;
660     }