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