MDL-37545 cache: fixed bug during initialisation and updating of cache config
[moodle.git] / cache / classes / factory.php
CommitLineData
8139ad13
SH
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/>.
16
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 */
28
29defined('MOODLE_INTERNAL') || die();
30
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 */
41class cache_factory {
42
7383a7e2
SH
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;
9890ecfc
SH
51 /** The cache is currently updating itself */
52 const STATE_UPDATING = 4;
7383a7e2
SH
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;
82afd05c
SH
57 /** The cache stores have been disabled */
58 const STATE_STORES_DISABLED = 11;
7383a7e2 59
8139ad13
SH
60 /**
61 * An instance of the cache_factory class created upon the first request.
62 * @var cache_factory
63 */
64 protected static $instance;
65
66 /**
67 * An array containing caches created for definitions
68 * @var array
69 */
70 protected $cachesfromdefinitions = array();
71
72 /**
73 * Array of caches created by parameters, ad-hoc definitions will have been used.
74 * @var array
75 */
76 protected $cachesfromparams = array();
77
78 /**
79 * An array of instantiated stores.
80 * @var array
81 */
82 protected $stores = array();
83
84 /**
85 * An array of configuration instances
86 * @var array
87 */
88 protected $configs = array();
89
90 /**
91 * An array of initialised definitions
92 * @var array
93 */
94 protected $definitions = array();
95
34c84c72
SH
96 /**
97 * An array of lock plugins.
98 * @var array
99 */
100 protected $lockplugins = null;
101
7383a7e2
SH
102 /**
103 * The current state of the cache API.
104 * @var int
105 */
106 protected $state = 0;
107
8139ad13
SH
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) {
94ef67cf 115 global $CFG;
8139ad13 116 if ($forcereload || self::$instance === null) {
94ef67cf
SH
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.
086e78da 128 self::$instance->set_state(self::STATE_STORES_DISABLED);
94ef67cf 129 }
7383a7e2 130 }
8139ad13
SH
131 }
132 return self::$instance;
133 }
134
135 /**
136 * Protected constructor, please use the static instance method.
137 */
138 protected function __construct() {
139 // Nothing to do here.
140 }
141
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();
34c84c72 152 $factory->lockplugins = null; // MUST be null in order to force its regeneration.
7383a7e2
SH
153 // Reset the state to uninitialised.
154 $factory->state = self::STATE_UNINITIALISED;
8139ad13
SH
155 }
156
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 }
183
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
2566210c
SH
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.
8139ad13
SH
197 * @return cache_application|cache_session|cache_request
198 */
2566210c 199 public function create_cache_from_params($mode, $component, $area, array $identifiers = array(), array $options = array()) {
8139ad13
SH
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.
2566210c 205 $definition = cache_definition::load_adhoc($mode, $component, $area, $options);
8139ad13
SH
206 $definition->set_identifiers($identifiers);
207 $cache = $this->create_cache($definition, $identifiers);
208 if ($definition->should_be_persistent()) {
8139ad13
SH
209 $this->cachesfromparams[$key] = $cache;
210 }
211 return $cache;
212 }
213
214 /**
34c84c72 215 * Common public method to create a cache instance given a definition.
8139ad13
SH
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();
9890ecfc
SH
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 }
8139ad13
SH
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 }
244
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.
3680c61a 253 * @return boolean|cache_store
8139ad13
SH
254 */
255 public function create_store_from_config($name, array $details, cache_definition $definition) {
256 if (!array_key_exists($name, $this->stores)) {
170f821b 257 // Properties: name, plugin, configuration, class.
8139ad13
SH
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 }
270
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;
279
280 // Check if we need to create a config file with defaults.
281 $needtocreate = !cache_config::config_file_exists();
282
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 }
170f821b 289
8139ad13
SH
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 }
299
7383a7e2 300 $error = false;
8139ad13
SH
301 if ($needtocreate) {
302 // Create the default configuration.
7383a7e2
SH
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 }
8139ad13
SH
313 }
314
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 }
320
7383a7e2
SH
321 if (!$error) {
322 // The cache is now ready to use. Update the state.
323 self::set_state(self::STATE_READY);
324 }
325
8139ad13
SH
326 // Return the instance.
327 return $this->configs[$class];
328 }
329
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)) {
9890ecfc
SH
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();
8139ad13
SH
351 $definition = $instance->get_definition_by_id($id);
352 if (!$definition) {
9890ecfc
SH
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 }
8139ad13 384 } else {
9890ecfc 385 $definition = cache_definition::load($id, $definition, $aggregate);
8139ad13
SH
386 }
387 }
9890ecfc 388 $this->definitions[$id] = $definition;
8139ad13
SH
389 }
390 return $this->definitions[$id];
391 }
392
393 /**
394 * Creates a dummy store object for use when a loader has no potential stores to use.
395 *
396 * @param cache_definition $definition
6fec1820 397 * @return cachestore_dummy
8139ad13
SH
398 */
399 protected function create_dummy_store(cache_definition $definition) {
400 global $CFG;
401 require_once($CFG->dirroot.'/cache/classes/dummystore.php');
6fec1820 402 $store = new cachestore_dummy();
8139ad13
SH
403 $store->initialise($definition);
404 return $store;
405 }
34c84c72
SH
406
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']);
421
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 }
7383a7e2
SH
431
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 }
440
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 }
454
9890ecfc
SH
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 }
468
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 }
477
7383a7e2
SH
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 }
82afd05c 486
9890ecfc
SH
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 }
497
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 }
506
3680c61a
SH
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!
94ef67cf
SH
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.
3680c61a 518 */
94ef67cf 519 protected static function disable() {
3680c61a
SH
520 global $CFG;
521 require_once($CFG->dirroot.'/cache/disabledlib.php');
3680c61a
SH
522 self::$instance = new cache_factory_disabled();
523 }
524
82afd05c
SH
525 /**
526 * Returns true if the cache stores have been disabled.
527 *
528 * @return bool
529 */
530 public function stores_disabled() {
3680c61a 531 return $this->state === self::STATE_STORES_DISABLED || $this->is_disabled();
82afd05c
SH
532 }
533
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 }
086e78da 553}