MDL-37545 cache: fixed bug during initialisation and updating of cache config
[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 instantiated stores.
80      * @var array
81      */
82     protected $stores = array();
84     /**
85      * An array of configuration instances
86      * @var array
87      */
88     protected $configs = array();
90     /**
91      * An array of initialised definitions
92      * @var array
93      */
94     protected $definitions = array();
96     /**
97      * An array of lock plugins.
98      * @var array
99      */
100     protected $lockplugins = null;
102     /**
103      * The current state of the cache API.
104      * @var int
105      */
106     protected $state = 0;
108     /**
109      * Returns an instance of the cache_factor method.
110      *
111      * @param bool $forcereload If set to true a new cache_factory instance will be created and used.
112      * @return cache_factory
113      */
114     public static function instance($forcereload = false) {
115         global $CFG;
116         if ($forcereload || self::$instance === null) {
117             // Initialise a new factory to facilitate our needs.
118             if (defined('CACHE_DISABLE_ALL') && CACHE_DISABLE_ALL !== false) {
119                 // The cache has been disabled. Load disabledlib and start using the factory designed to handle this
120                 // situation. It will use disabled alternatives where available.
121                 require_once($CFG->dirroot.'/cache/disabledlib.php');
122                 self::$instance = new cache_factory_disabled();
123             } else {
124                 // We're using the regular factory.
125                 self::$instance = new cache_factory();
126                 if (defined('CACHE_DISABLE_STORES') && CACHE_DISABLE_STORES !== false) {
127                     // The cache stores have been disabled.
128                     self::$instance->set_state(self::STATE_STORES_DISABLED);
129                 }
130             }
131         }
132         return self::$instance;
133     }
135     /**
136      * Protected constructor, please use the static instance method.
137      */
138     protected function __construct() {
139         // Nothing to do here.
140     }
142     /**
143      * Resets the arrays containing instantiated caches, stores, and config instances.
144      */
145     public static function reset() {
146         $factory = self::instance();
147         $factory->cachesfromdefinitions = array();
148         $factory->cachesfromparams = array();
149         $factory->stores = array();
150         $factory->configs = array();
151         $factory->definitions = array();
152         $factory->lockplugins = null; // MUST be null in order to force its regeneration.
153         // Reset the state to uninitialised.
154         $factory->state = self::STATE_UNINITIALISED;
155     }
157     /**
158      * Creates a cache object given the parameters for a definition.
159      *
160      * If a cache has already been created for the given definition then that cache instance will be returned.
161      *
162      * @param string $component
163      * @param string $area
164      * @param array $identifiers
165      * @param string $aggregate
166      * @return cache_application|cache_session|cache_request
167      */
168     public function create_cache_from_definition($component, $area, array $identifiers = array(), $aggregate = null) {
169         $definitionname = $component.'/'.$area;
170         if (array_key_exists($definitionname, $this->cachesfromdefinitions)) {
171             $cache = $this->cachesfromdefinitions[$definitionname];
172             $cache->set_identifiers($identifiers);
173             return $cache;
174         }
175         $definition = $this->create_definition($component, $area, $aggregate);
176         $definition->set_identifiers($identifiers);
177         $cache = $this->create_cache($definition, $identifiers);
178         if ($definition->should_be_persistent()) {
179             $this->cachesfromdefinitions[$definitionname] = $cache;
180         }
181         return $cache;
182     }
184     /**
185      * Creates an ad-hoc cache from the given param.
186      *
187      * If a cache has already been created using the same params then that cache instance will be returned.
188      *
189      * @param int $mode
190      * @param string $component
191      * @param string $area
192      * @param array $identifiers
193      * @param array $options An array of options, available options are:
194      *   - simplekeys : Set to true if the keys you will use are a-zA-Z0-9_
195      *   - simpledata : Set to true if the type of the data you are going to store is scalar, or an array of scalar vars
196      *   - persistent : If set to true the cache will persist construction requests.
197      * @return cache_application|cache_session|cache_request
198      */
199     public function create_cache_from_params($mode, $component, $area, array $identifiers = array(), array $options = array()) {
200         $key = "{$mode}_{$component}_{$area}";
201         if (array_key_exists($key, $this->cachesfromparams)) {
202             return $this->cachesfromparams[$key];
203         }
204         // Get the class. Note this is a late static binding so we need to use get_called_class.
205         $definition = cache_definition::load_adhoc($mode, $component, $area, $options);
206         $definition->set_identifiers($identifiers);
207         $cache = $this->create_cache($definition, $identifiers);
208         if ($definition->should_be_persistent()) {
209             $this->cachesfromparams[$key] = $cache;
210         }
211         return $cache;
212     }
214     /**
215      * Common public method to create a cache instance given a definition.
216      *
217      * This is used by the static make methods.
218      *
219      * @param cache_definition $definition
220      * @return cache_application|cache_session|cache_store
221      * @throws coding_exception
222      */
223     public function create_cache(cache_definition $definition) {
224         $class = $definition->get_cache_class();
225         if ($this->is_initialising()) {
226             // Do nothing we just want the dummy store.
227             $stores = array();
228         } else {
229             $stores = cache_helper::get_cache_stores($definition);
230         }
231         if (count($stores) === 0) {
232             // Hmm no stores, better provide a dummy store to mimick functionality. The dev will be none the wiser.
233             $stores[] = $this->create_dummy_store($definition);
234         }
235         $loader = null;
236         if ($definition->has_data_source()) {
237             $loader = $definition->get_data_source();
238         }
239         while (($store = array_pop($stores)) !== null) {
240             $loader = new $class($definition, $store, $loader);
241         }
242         return $loader;
243     }
245     /**
246      * Creates a store instance given its name and configuration.
247      *
248      * If the store has already been instantiated then the original objetc will be returned. (reused)
249      *
250      * @param string $name The name of the store (must be unique remember)
251      * @param array $details
252      * @param cache_definition $definition The definition to instantiate it for.
253      * @return boolean|cache_store
254      */
255     public function create_store_from_config($name, array $details, cache_definition $definition) {
256         if (!array_key_exists($name, $this->stores)) {
257             // Properties: name, plugin, configuration, class.
258             $class = $details['class'];
259             $store = new $class($details['name'], $details['configuration']);
260             $this->stores[$name] = $store;
261         }
262         $store = $this->stores[$name];
263         if (!$store->is_ready() || !$store->is_supported_mode($definition->get_mode())) {
264             return false;
265         }
266         $store = clone($this->stores[$name]);
267         $store->initialise($definition);
268         return $store;
269     }
271     /**
272      * Creates a cache config instance with the ability to write if required.
273      *
274      * @param bool $writer If set to true an instance that can update the configuration will be returned.
275      * @return cache_config|cache_config_writer
276      */
277     public function create_config_instance($writer = false) {
278         global $CFG;
280         // Check if we need to create a config file with defaults.
281         $needtocreate = !cache_config::config_file_exists();
283         // The class to use.
284         $class = 'cache_config';
285         if ($writer || $needtocreate) {
286             require_once($CFG->dirroot.'/cache/locallib.php');
287             $class .= '_writer';
288         }
290         // Check if this is a PHPUnit test and redirect to the phpunit config classes if it is.
291         if (defined('PHPUNIT_TEST') && PHPUNIT_TEST) {
292             require_once($CFG->dirroot.'/cache/locallib.php');
293             require_once($CFG->dirroot.'/cache/tests/fixtures/lib.php');
294             // We have just a single class for PHP unit tests. We don't care enough about its
295             // performance to do otherwise and having a single method allows us to inject things into it
296             // while testing.
297             $class = 'cache_config_phpunittest';
298         }
300         $error = false;
301         if ($needtocreate) {
302             // Create the default configuration.
303             // Update the state, we are now initialising the cache.
304             self::set_state(self::STATE_INITIALISING);
305             $configuration = $class::create_default_configuration();
306             if ($configuration !== true) {
307                 // Failed to create the default configuration. Disable the cache stores and update the state.
308                 self::set_state(self::STATE_ERROR_INITIALISING);
309                 $this->configs[$class] = new $class;
310                 $this->configs[$class]->load($configuration);
311                 $error = true;
312             }
313         }
315         if (!array_key_exists($class, $this->configs)) {
316             // Create a new instance and call it to load it.
317             $this->configs[$class] = new $class;
318             $this->configs[$class]->load();
319         }
321         if (!$error) {
322             // The cache is now ready to use. Update the state.
323             self::set_state(self::STATE_READY);
324         }
326         // Return the instance.
327         return $this->configs[$class];
328     }
330     /**
331      * Creates a definition instance or returns the existing one if it has already been created.
332      * @param string $component
333      * @param string $area
334      * @param string $aggregate
335      * @return cache_definition
336      */
337     public function create_definition($component, $area, $aggregate = null) {
338         $id = $component.'/'.$area;
339         if ($aggregate) {
340             $id .= '::'.$aggregate;
341         }
342         if (!array_key_exists($id, $this->definitions)) {
343             // This is the first time this definition has been requested.
344             if ($this->is_initialising()) {
345                 // We're initialising the cache right now. Don't try to create another config instance.
346                 // We'll just use an ad-hoc cache for the time being.
347                 $definition = cache_definition::load_adhoc(cache_store::MODE_REQUEST, $component, $area);
348             } else {
349                 // Load all the known definitions and find the desired one.
350                 $instance = $this->create_config_instance();
351                 $definition = $instance->get_definition_by_id($id);
352                 if (!$definition) {
353                     // Oh-oh the definition doesn't exist.
354                     // There are several things that could be going on here.
355                     // We may be installing/upgrading a site and have hit a definition that hasn't been used before.
356                     // Of the developer may be trying to use a newly created definition.
357                     if ($this->is_updating()) {
358                         // The cache is presently initialising and the requested cache definition has not been found.
359                         // This means that the cache initialisation has requested something from a cache (I had recursive nightmares about this).
360                         // To serve this purpose and avoid errors we are going to make use of an ad-hoc cache rather than
361                         // search for the definition which would possibly cause an infitite loop trying to initialise the cache.
362                         $definition = cache_definition::load_adhoc(cache_store::MODE_REQUEST, $component, $area);
363                         if ($aggregate !== null) {
364                             // If you get here you deserve a warning. We have to use an ad-hoc cache here, so we can't find the definition and therefor
365                             // can't find any information about the datasource or any of its aggregated.
366                             // Best of luck.
367                             debugging('An unknown cache was requested during development with an aggregate that could not be loaded. Ad-hoc cache used instead.', DEBUG_DEVELOPER);
368                             $aggregate = null;
369                         }
370                     } else {
371                         // Either a typo of the developer has just created the definition and is using it for the first time.
372                         $this->reset();
373                         $instance = $this->create_config_instance(true);
374                         $instance->update_definitions();
375                         $definition = $instance->get_definition_by_id($id);
376                         if (!$definition) {
377                             throw new coding_exception('The requested cache definition does not exist.'. $id, $id);
378                         } else {
379                             debugging('Cache definitions reparsed causing cache reset in order to locate definition.
380                                 You should bump the version number to ensure definitions are reprocessed.', DEBUG_DEVELOPER);
381                         }
382                         $definition = cache_definition::load($id, $definition, $aggregate);
383                     }
384                 } else {
385                     $definition = cache_definition::load($id, $definition, $aggregate);
386                 }
387             }
388             $this->definitions[$id] = $definition;
389         }
390         return $this->definitions[$id];
391     }
393     /**
394      * Creates a dummy store object for use when a loader has no potential stores to use.
395      *
396      * @param cache_definition $definition
397      * @return cachestore_dummy
398      */
399     protected function create_dummy_store(cache_definition $definition) {
400         global $CFG;
401         require_once($CFG->dirroot.'/cache/classes/dummystore.php');
402         $store = new cachestore_dummy();
403         $store->initialise($definition);
404         return $store;
405     }
407     /**
408      * Returns a lock instance ready for use.
409      *
410      * @param array $config
411      * @return cache_lock_interface
412      */
413     public function create_lock_instance(array $config) {
414         if (!array_key_exists('name', $config) || !array_key_exists('type', $config)) {
415             throw new coding_exception('Invalid cache lock instance provided');
416         }
417         $name = $config['name'];
418         $type = $config['type'];
419         unset($config['name']);
420         unset($config['type']);
422         if ($this->lockplugins === null) {
423             $this->lockplugins = get_plugin_list_with_class('cachelock', '', 'lib.php');
424         }
425         if (!array_key_exists($type, $this->lockplugins)) {
426             throw new coding_exception('Invalid cache lock type.');
427         }
428         $class = $this->lockplugins[$type];
429         return new $class($name, $config);
430     }
432     /**
433      * Returns the current state of the cache API.
434      *
435      * @return int
436      */
437     public function get_state() {
438         return $this->state;
439     }
441     /**
442      * Updates the state fo the cache API.
443      *
444      * @param int $state
445      * @return bool
446      */
447     public function set_state($state) {
448         if ($state <= $this->state) {
449             return false;
450         }
451         $this->state = $state;
452         return true;
453     }
455     /**
456      * Informs the factory that the cache is currently updating itself.
457      *
458      * This forces the state to upgrading and can only be called once the cache is ready to use.
459      * Calling it ensure we don't try to reinstantite things when requesting cache definitions that don't exist yet.
460      */
461     public function updating_started() {
462         if ($this->state !== self::STATE_READY) {
463             return false;
464         }
465         $this->state = self::STATE_UPDATING;
466         return true;
467     }
469     /**
470      * Informs the factory that the upgrading has finished.
471      *
472      * This forces the state back to ready.
473      */
474     public function updating_finished() {
475         $this->state = self::STATE_READY;
476     }
478     /**
479      * Returns true if the cache API has been disabled.
480      *
481      * @return bool
482      */
483     public function is_disabled() {
484         return $this->state === self::STATE_DISABLED;
485     }
487     /**
488      * Returns true if the cache is currently initialising itself.
489      *
490      * This includes both initialisation and saving the cache config file as part of that initialisation.
491      *
492      * @return bool
493      */
494     public function is_initialising() {
495         return $this->state === self::STATE_INITIALISING || $this->state === self::STATE_SAVING;
496     }
498     /**
499      * Returns true if the cache is currently updating itself.
500      *
501      * @return bool
502      */
503     public function is_updating() {
504         return $this->state === self::STATE_UPDATING;
505     }
507     /**
508      * Disables as much of the cache API as possible.
509      *
510      * All of the magic associated with the disabled cache is wrapped into this function.
511      * In switching out the factory for the disabled factory it gains full control over the initialisation of objects
512      * and can use all of the disabled alternatives.
513      * Simple!
514      *
515      * This function has been marked as protected so that it cannot be abused through the public API presently.
516      * Perhaps in the future we will allow this, however as per the build up to the first release containing
517      * MUC it was decided that this was just to risky and abusable.
518      */
519     protected static function disable() {
520         global $CFG;
521         require_once($CFG->dirroot.'/cache/disabledlib.php');
522         self::$instance = new cache_factory_disabled();
523     }
525     /**
526      * Returns true if the cache stores have been disabled.
527      *
528      * @return bool
529      */
530     public function stores_disabled() {
531         return $this->state === self::STATE_STORES_DISABLED || $this->is_disabled();
532     }
534     /**
535      * Disables cache stores.
536      *
537      * The cache API will continue to function however none of the actual stores will be used.
538      * Instead the dummy store will be provided for all cache requests.
539      * This is useful in situations where you cannot be sure any stores are working.
540      *
541      * In order to re-enable the cache you must call the cache factories static reset method:
542      * <code>
543      * // Disable the cache factory.
544      * cache_factory::disable_stores();
545      * // Re-enable the cache factory by resetting it.
546      * cache_factory::reset();
547      * </code>
548      */
549     public static function disable_stores() {
550         $factory = self::instance();
551         $factory->set_state(self::STATE_STORES_DISABLED);
552     }