weekly release 4.5dev
[moodle.git] / cache / classes / factory.php
blob116b1b362584a75ebcdc3bdcb8cf5abce5149325
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.
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.
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.
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
29 defined('MOODLE_INTERNAL') || die();
31 /**
32 * The cache factory class.
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.
38 * @copyright 2012 Sam Hemelryk
39 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
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
64 protected static $instance;
66 /**
67 * An array containing caches created for definitions
68 * @var array
70 protected $cachesfromdefinitions = array();
72 /**
73 * Array of caches created by parameters, ad-hoc definitions will have been used.
74 * @var array
76 protected $cachesfromparams = array();
78 /**
79 * An array of stores organised by definitions.
80 * @var array
82 protected $definitionstores = array();
84 /**
85 * An array of instantiated stores.
86 * @var array
88 protected $stores = array();
90 /**
91 * An array of configuration instances
92 * @var array
94 protected $configs = array();
96 /**
97 * An array of initialised definitions
98 * @var array
100 protected $definitions = array();
103 * An array of lock plugins.
104 * @var array
106 protected $lockplugins = array();
109 * The current state of the cache API.
110 * @var int
112 protected $state = 0;
115 * The current cache display helper.
116 * @var core_cache\local\administration_display_helper
118 protected static $displayhelper = null;
121 * Returns an instance of the cache_factory class.
123 * @param bool $forcereload If set to true a new cache_factory instance will be created and used.
124 * @return cache_factory
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);
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);
156 return self::$instance;
160 * Protected constructor, please use the static instance method.
162 protected function __construct() {
163 // Nothing to do here.
167 * Resets the arrays containing instantiated caches, stores, and config instances.
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;
181 * Resets the stores, clearing the array of created stores.
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.
186 public function reset_cache_instances() {
187 $this->cachesfromdefinitions = array();
188 $this->cachesfromparams = array();
189 $this->stores = array();
193 * Creates a cache object given the parameters for a definition.
195 * If a cache has already been created for the given definition then that cache instance will be returned.
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
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;
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;
222 * Creates an ad-hoc cache from the given param.
224 * If a cache has already been created using the same params then that cache instance will be returned.
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
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];
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;
254 * Common public method to create a cache instance given a definition.
256 * This is used by the static make methods.
258 * @param cache_definition $definition
259 * @return cache_application|cache_session|cache_store
260 * @throws coding_exception
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]);
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);
274 $loader = null;
275 if ($definition->has_data_source()) {
276 $loader = $definition->get_data_source();
278 while (($store = array_pop($stores)) !== null) {
279 $loader = new $class($definition, $store, $loader);
281 return $loader;
285 * Creates a store instance given its name and configuration.
287 * If the store has already been instantiated then the original object will be returned. (reused)
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
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;
301 $store = new $class($details['name'], $details['configuration']);
302 $this->stores[$name] = $store;
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;
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();
322 $this->definitionstores[$definitionid][] = $store;
323 return $store;
327 * Returns an array of cache stores that have been initialised for use in definitions.
328 * @param cache_definition $definition
329 * @return array
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();
336 return $this->definitionstores[$id];
340 * Returns the cache instances that have been used within this request.
341 * @since Moodle 2.6
342 * @return array
344 public function get_caches_in_use() {
345 return $this->cachesfromdefinitions;
349 * Gets all adhoc caches that have been used within this request.
351 * @return cache_store[] Caches currently in use
353 public function get_adhoc_caches_in_use() {
354 return $this->cachesfromparams;
358 * Creates a cache config instance with the ability to write if required.
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
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';
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';
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;
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();
413 if (!$error) {
414 // The cache is now ready to use. Update the state.
415 self::set_state(self::STATE_READY);
418 // Return the instance.
419 return $this->configs[$class];
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.
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);
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);
467 $definition = cache_definition::load($id, $definition);
469 } else {
470 $definition = cache_definition::load($id, $definition);
473 $this->definitions[$id] = $definition;
475 return $this->definitions[$id];
479 * Creates a dummy store object for use when a loader has no potential stores to use.
481 * @param cache_definition $definition
482 * @return cachestore_dummy
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;
493 * Returns a lock instance ready for use.
495 * @param array $config
496 * @return cache_lock_interface
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');
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);
514 if (!class_exists($type)) {
515 throw new coding_exception('Invalid lock plugin requested.');
517 $this->lockplugins[$type] = $type;
519 if (!array_key_exists($type, $this->lockplugins)) {
520 throw new coding_exception('Invalid cache lock type.');
522 $class = $this->lockplugins[$type];
523 return new $class($name, $config);
527 * Returns the current state of the cache API.
529 * @return int
531 public function get_state() {
532 return $this->state;
536 * Updates the state fo the cache API.
538 * @param int $state
539 * @return bool
541 public function set_state($state) {
542 if ($state <= $this->state) {
543 return false;
545 $this->state = $state;
546 return true;
550 * Informs the factory that the cache is currently updating itself.
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.
555 public function updating_started() {
556 if ($this->state !== self::STATE_READY) {
557 return false;
559 $this->state = self::STATE_UPDATING;
560 return true;
564 * Informs the factory that the upgrading has finished.
566 * This forces the state back to ready.
568 public function updating_finished() {
569 $this->state = self::STATE_READY;
573 * Returns true if the cache API has been disabled.
575 * @return bool
577 public function is_disabled() {
578 return $this->state === self::STATE_DISABLED;
582 * Returns true if the cache is currently initialising itself.
584 * This includes both initialisation and saving the cache config file as part of that initialisation.
586 * @return bool
588 public function is_initialising() {
589 return $this->state === self::STATE_INITIALISING || $this->state === self::STATE_SAVING;
593 * Returns true if the cache is currently updating itself.
595 * @return bool
597 public function is_updating() {
598 return $this->state === self::STATE_UPDATING;
602 * Disables as much of the cache API as possible.
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!
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.
613 protected static function disable() {
614 global $CFG;
615 require_once($CFG->dirroot.'/cache/disabledlib.php');
616 self::$instance = new cache_factory_disabled();
620 * Returns true if the cache stores have been disabled.
622 * @return bool
624 public function stores_disabled() {
625 return $this->state === self::STATE_STORES_DISABLED || $this->is_disabled();
629 * Disables cache stores.
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.
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>
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);
651 * Returns an instance of the current display_helper.
653 * @return core_cache\administration_helper
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();
659 return self::$displayhelper;
663 * Gets the cache_config_writer to use when caching is disabled.
664 * This should only be called from cache_factory_disabled.
666 * @return cache_config_writer
668 public static function get_disabled_writer(): cache_config_writer {
669 global $CFG;
671 // Figure out if we are in a recursive loop using late static binding.
672 // This happens when get_disabled_writer is not overridden. We just want the default.
673 $loop = false;
674 if (!empty($CFG->alternative_cache_factory_class)) {
675 $loop = get_called_class() === $CFG->alternative_cache_factory_class;
678 if (!$loop && !empty($CFG->alternative_cache_factory_class)) {
679 // Get the class to use from the alternative factory.
680 $factoryinstance = new $CFG->alternative_cache_factory_class();
681 return $factoryinstance::get_disabled_writer();
682 } else {
683 // We got here from cache_factory_disabled.
684 // We should use the default writer here.
685 // Make sure we have a default config if needed.
686 if (!cache_config::config_file_exists()) {
687 cache_config_writer::create_default_configuration(true);
690 return new cache_config_writer();