2 // This file is part of Moodle - http://moodle.org/
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.
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/>.
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.
25 * @copyright 2012 Sam Hemelryk
26 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
29 defined('MOODLE_INTERNAL') ||
die();
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
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;
61 * An instance of the cache_factory class created upon the first request.
64 protected static $instance;
67 * An array containing caches created for definitions
70 protected $cachesfromdefinitions = array();
73 * Array of caches created by parameters, ad-hoc definitions will have been used.
76 protected $cachesfromparams = array();
79 * An array of stores organised by definitions.
82 protected $definitionstores = array();
85 * An array of instantiated stores.
88 protected $stores = array();
91 * An array of configuration instances
94 protected $configs = array();
97 * An array of initialised definitions
100 protected $definitions = array();
103 * An array of lock plugins.
106 protected $lockplugins = array();
109 * The current state of the cache API.
112 protected $state = 0;
115 * Returns an instance of the cache_factor method.
117 * @param bool $forcereload If set to true a new cache_factory instance will be created and used.
118 * @return cache_factory
120 public static function instance($forcereload = false) {
122 if ($forcereload || self
::$instance === null) {
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 if ((defined('PHPUNIT_TEST') && PHPUNIT_TEST
) ||
defined('BEHAT_SITE_RUNNING')) {
130 // We're using the test factory.
131 require_once($CFG->dirroot
.'/cache/tests/fixtures/lib.php');
132 self
::$instance = new cache_phpunit_factory();
133 if (defined('CACHE_DISABLE_STORES') && CACHE_DISABLE_STORES
!== false) {
134 // The cache stores have been disabled.
135 self
::$instance->set_state(self
::STATE_STORES_DISABLED
);
138 // We're using the regular factory.
139 self
::$instance = new cache_factory();
140 if (defined('CACHE_DISABLE_STORES') && CACHE_DISABLE_STORES
!== false) {
141 // The cache stores have been disabled.
142 self
::$instance->set_state(self
::STATE_STORES_DISABLED
);
146 return self
::$instance;
150 * Protected constructor, please use the static instance method.
152 protected function __construct() {
153 // Nothing to do here.
157 * Resets the arrays containing instantiated caches, stores, and config instances.
159 public static function reset() {
160 $factory = self
::instance();
161 $factory->reset_cache_instances();
162 $factory->configs
= array();
163 $factory->definitions
= array();
164 $factory->lockplugins
= array(); // MUST be null in order to force its regeneration.
165 // Reset the state to uninitialised.
166 $factory->state
= self
::STATE_UNINITIALISED
;
170 * Resets the stores, clearing the array of created stores.
172 * Cache objects still held onto by the code that initialised them will remain as is
173 * however all future requests for a cache/store will lead to a new instance being re-initialised.
175 public function reset_cache_instances() {
176 $this->cachesfromdefinitions
= array();
177 $this->cachesfromparams
= array();
178 $this->stores
= array();
182 * Creates a cache object given the parameters for a definition.
184 * If a cache has already been created for the given definition then that cache instance will be returned.
186 * @param string $component
187 * @param string $area
188 * @param array $identifiers
189 * @param string $unused Used to be data source aggregate however that was removed and this is now unused.
190 * @return cache_application|cache_session|cache_request
192 public function create_cache_from_definition($component, $area, array $identifiers = array(), $unused = null) {
193 $definitionname = $component.'/'.$area;
194 if (isset($this->cachesfromdefinitions
[$definitionname])) {
195 $cache = $this->cachesfromdefinitions
[$definitionname];
196 $cache->set_identifiers($identifiers);
199 $definition = $this->create_definition($component, $area);
200 $definition->set_identifiers($identifiers);
201 $cache = $this->create_cache($definition, $identifiers);
202 // Loaders are always held onto to speed up subsequent requests.
203 $this->cachesfromdefinitions
[$definitionname] = $cache;
208 * Creates an ad-hoc cache from the given param.
210 * If a cache has already been created using the same params then that cache instance will be returned.
213 * @param string $component
214 * @param string $area
215 * @param array $identifiers
216 * @param array $options An array of options, available options are:
217 * - simplekeys : Set to true if the keys you will use are a-zA-Z0-9_
218 * - simpledata : Set to true if the type of the data you are going to store is scalar, or an array of scalar vars
219 * - staticacceleration : If set to true the cache will hold onto data passing through it.
220 * - staticaccelerationsize : The maximum number of items to hold onto for acceleration purposes.
221 * @return cache_application|cache_session|cache_request
223 public function create_cache_from_params($mode, $component, $area, array $identifiers = array(), array $options = array()) {
224 $key = "{$mode}_{$component}_{$area}";
225 if (array_key_exists($key, $this->cachesfromparams
)) {
226 return $this->cachesfromparams
[$key];
228 $definition = cache_definition
::load_adhoc($mode, $component, $area, $options);
229 $definition->set_identifiers($identifiers);
230 $cache = $this->create_cache($definition, $identifiers);
231 $this->cachesfromparams
[$key] = $cache;
236 * Common public method to create a cache instance given a definition.
238 * This is used by the static make methods.
240 * @param cache_definition $definition
241 * @return cache_application|cache_session|cache_store
242 * @throws coding_exception
244 public function create_cache(cache_definition
$definition) {
245 $class = $definition->get_cache_class();
246 $stores = cache_helper
::get_stores_suitable_for_definition($definition);
247 foreach ($stores as $key => $store) {
248 if (!$store::are_requirements_met()) {
249 unset($stores[$key]);
252 if (count($stores) === 0) {
253 // Hmm still no stores, better provide a dummy store to mimic functionality. The dev will be none the wiser.
254 $stores[] = $this->create_dummy_store($definition);
257 if ($definition->has_data_source()) {
258 $loader = $definition->get_data_source();
260 while (($store = array_pop($stores)) !== null) {
261 $loader = new $class($definition, $store, $loader);
267 * Creates a store instance given its name and configuration.
269 * If the store has already been instantiated then the original object will be returned. (reused)
271 * @param string $name The name of the store (must be unique remember)
272 * @param array $details
273 * @param cache_definition $definition The definition to instantiate it for.
274 * @return boolean|cache_store
276 public function create_store_from_config($name, array $details, cache_definition
$definition) {
277 if (!array_key_exists($name, $this->stores
)) {
278 // Properties: name, plugin, configuration, class.
279 $class = $details['class'];
280 $store = new $class($details['name'], $details['configuration']);
281 $this->stores
[$name] = $store;
283 /* @var cache_store $store */
284 $store = $this->stores
[$name];
285 // We check are_requirements_met although we expect is_ready is going to check as well.
286 if (!$store::are_requirements_met() ||
!$store->is_ready() ||
!$store->is_supported_mode($definition->get_mode())) {
289 // We always create a clone of the original store.
290 // If we were to clone a store that had already been initialised with a definition then
291 // we'd run into a myriad of issues.
292 // We use a method of the store to create a clone rather than just creating it ourselves
293 // so that if any store out there doesn't handle cloning they can override this method in
294 // order to address the issues.
295 $store = $this->stores
[$name]->create_clone($details);
296 $store->initialise($definition);
297 $definitionid = $definition->get_id();
298 if (!isset($this->definitionstores
[$definitionid])) {
299 $this->definitionstores
[$definitionid] = array();
301 $this->definitionstores
[$definitionid][] = $store;
306 * Returns an array of cache stores that have been initialised for use in definitions.
307 * @param cache_definition $definition
310 public function get_store_instances_in_use(cache_definition
$definition) {
311 $id = $definition->get_id();
312 if (!isset($this->definitionstores
[$id])) {
315 return $this->definitionstores
[$id];
319 * Returns the cache instances that have been used within this request.
323 public function get_caches_in_use() {
324 return $this->cachesfromdefinitions
;
328 * Creates a cache config instance with the ability to write if required.
330 * @param bool $writer If set to true an instance that can update the configuration will be returned.
331 * @return cache_config|cache_config_writer
333 public function create_config_instance($writer = false) {
337 $class = 'cache_config';
338 // Are we running tests of some form?
339 $testing = (defined('PHPUNIT_TEST') && PHPUNIT_TEST
) ||
defined('BEHAT_SITE_RUNNING');
341 // Check if this is a PHPUnit test and redirect to the phpunit config classes if it is.
343 require_once($CFG->dirroot
.'/cache/locallib.php');
344 require_once($CFG->dirroot
.'/cache/tests/fixtures/lib.php');
345 // We have just a single class for PHP unit tests. We don't care enough about its
346 // performance to do otherwise and having a single method allows us to inject things into it
348 $class = 'cache_config_testing';
351 // Check if we need to create a config file with defaults.
352 $needtocreate = !$class::config_file_exists();
354 if ($writer ||
$needtocreate) {
355 require_once($CFG->dirroot
.'/cache/locallib.php');
363 // Create the default configuration.
364 // Update the state, we are now initialising the cache.
365 self
::set_state(self
::STATE_INITIALISING
);
366 /** @var cache_config_writer $class */
367 $configuration = $class::create_default_configuration();
368 if ($configuration !== true) {
369 // Failed to create the default configuration. Disable the cache stores and update the state.
370 self
::set_state(self
::STATE_ERROR_INITIALISING
);
371 $this->configs
[$class] = new $class;
372 $this->configs
[$class]->load($configuration);
377 if (!array_key_exists($class, $this->configs
)) {
378 // Create a new instance and call it to load it.
379 $this->configs
[$class] = new $class;
380 $this->configs
[$class]->load();
384 // The cache is now ready to use. Update the state.
385 self
::set_state(self
::STATE_READY
);
388 // Return the instance.
389 return $this->configs
[$class];
393 * Creates a definition instance or returns the existing one if it has already been created.
394 * @param string $component
395 * @param string $area
396 * @param string $unused This used to be data source aggregate - however that functionality has been removed and
397 * this argument is now unused.
398 * @return cache_definition
399 * @throws coding_exception If the definition cannot be found.
401 public function create_definition($component, $area, $unused = null) {
402 $id = $component.'/'.$area;
403 if (!isset($this->definitions
[$id])) {
404 // This is the first time this definition has been requested.
405 if ($this->is_initialising()) {
406 // We're initialising the cache right now. Don't try to create another config instance.
407 // We'll just use an ad-hoc cache for the time being.
408 $definition = cache_definition
::load_adhoc(cache_store
::MODE_REQUEST
, $component, $area);
410 // Load all the known definitions and find the desired one.
411 $instance = $this->create_config_instance();
412 $definition = $instance->get_definition_by_id($id);
414 // Oh-oh the definition doesn't exist.
415 // There are several things that could be going on here.
416 // We may be installing/upgrading a site and have hit a definition that hasn't been used before.
417 // Of the developer may be trying to use a newly created definition.
418 if ($this->is_updating()) {
419 // The cache is presently initialising and the requested cache definition has not been found.
420 // This means that the cache initialisation has requested something from a cache (I had recursive nightmares about this).
421 // To serve this purpose and avoid errors we are going to make use of an ad-hoc cache rather than
422 // search for the definition which would possibly cause an infitite loop trying to initialise the cache.
423 $definition = cache_definition
::load_adhoc(cache_store
::MODE_REQUEST
, $component, $area);
425 // Either a typo of the developer has just created the definition and is using it for the first time.
427 $instance = $this->create_config_instance(true);
428 $instance->update_definitions();
429 $definition = $instance->get_definition_by_id($id);
431 throw new coding_exception('The requested cache definition does not exist.'. $id, $id);
432 } else if (!$this->is_disabled()) {
433 debugging('Cache definitions reparsed causing cache reset in order to locate definition.
434 You should bump the version number to ensure definitions are reprocessed.', DEBUG_DEVELOPER
);
436 $definition = cache_definition
::load($id, $definition);
439 $definition = cache_definition
::load($id, $definition);
442 $this->definitions
[$id] = $definition;
444 return $this->definitions
[$id];
448 * Creates a dummy store object for use when a loader has no potential stores to use.
450 * @param cache_definition $definition
451 * @return cachestore_dummy
453 protected function create_dummy_store(cache_definition
$definition) {
455 require_once($CFG->dirroot
.'/cache/classes/dummystore.php');
456 $store = new cachestore_dummy();
457 $store->initialise($definition);
462 * Returns a lock instance ready for use.
464 * @param array $config
465 * @return cache_lock_interface
467 public function create_lock_instance(array $config) {
469 if (!array_key_exists('name', $config) ||
!array_key_exists('type', $config)) {
470 throw new coding_exception('Invalid cache lock instance provided');
472 $name = $config['name'];
473 $type = $config['type'];
474 unset($config['name']);
475 unset($config['type']);
477 if (!isset($this->lockplugins
[$type])) {
478 $pluginname = substr($type, 10);
479 $file = $CFG->dirroot
."/cache/locks/{$pluginname}/lib.php";
480 if (file_exists($file) && is_readable($file)) {
483 if (!class_exists($type)) {
484 throw new coding_exception('Invalid lock plugin requested.');
486 $this->lockplugins
[$type] = $type;
488 if (!array_key_exists($type, $this->lockplugins
)) {
489 throw new coding_exception('Invalid cache lock type.');
491 $class = $this->lockplugins
[$type];
492 return new $class($name, $config);
496 * Returns the current state of the cache API.
500 public function get_state() {
505 * Updates the state fo the cache API.
510 public function set_state($state) {
511 if ($state <= $this->state
) {
514 $this->state
= $state;
519 * Informs the factory that the cache is currently updating itself.
521 * This forces the state to upgrading and can only be called once the cache is ready to use.
522 * Calling it ensure we don't try to reinstantite things when requesting cache definitions that don't exist yet.
524 public function updating_started() {
525 if ($this->state
!== self
::STATE_READY
) {
528 $this->state
= self
::STATE_UPDATING
;
533 * Informs the factory that the upgrading has finished.
535 * This forces the state back to ready.
537 public function updating_finished() {
538 $this->state
= self
::STATE_READY
;
542 * Returns true if the cache API has been disabled.
546 public function is_disabled() {
547 return $this->state
=== self
::STATE_DISABLED
;
551 * Returns true if the cache is currently initialising itself.
553 * This includes both initialisation and saving the cache config file as part of that initialisation.
557 public function is_initialising() {
558 return $this->state
=== self
::STATE_INITIALISING ||
$this->state
=== self
::STATE_SAVING
;
562 * Returns true if the cache is currently updating itself.
566 public function is_updating() {
567 return $this->state
=== self
::STATE_UPDATING
;
571 * Disables as much of the cache API as possible.
573 * All of the magic associated with the disabled cache is wrapped into this function.
574 * In switching out the factory for the disabled factory it gains full control over the initialisation of objects
575 * and can use all of the disabled alternatives.
578 * This function has been marked as protected so that it cannot be abused through the public API presently.
579 * Perhaps in the future we will allow this, however as per the build up to the first release containing
580 * MUC it was decided that this was just to risky and abusable.
582 protected static function disable() {
584 require_once($CFG->dirroot
.'/cache/disabledlib.php');
585 self
::$instance = new cache_factory_disabled();
589 * Returns true if the cache stores have been disabled.
593 public function stores_disabled() {
594 return $this->state
=== self
::STATE_STORES_DISABLED ||
$this->is_disabled();
598 * Disables cache stores.
600 * The cache API will continue to function however none of the actual stores will be used.
601 * Instead the dummy store will be provided for all cache requests.
602 * This is useful in situations where you cannot be sure any stores are working.
604 * In order to re-enable the cache you must call the cache factories static reset method:
606 * // Disable the cache factory.
607 * cache_factory::disable_stores();
608 * // Re-enable the cache factory by resetting it.
609 * cache_factory::reset();
612 public static function disable_stores() {
613 // First reset to clear any static acceleration array.
614 $factory = self
::instance();
615 $factory->reset_cache_instances();
616 $factory->set_state(self
::STATE_STORES_DISABLED
);