See Release Notes
Long Term Support Release
Differences Between: [Versions 39 and 401]
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 29 defined('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 */ 41 class cache_factory { 42 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; 59 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 stores organised by definitions. 80 * @var array 81 */ 82 protected $definitionstores = array(); 83 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 102 /** 103 * An array of lock plugins. 104 * @var array 105 */ 106 protected $lockplugins = array(); 107 108 /** 109 * The current state of the cache API. 110 * @var int 111 */ 112 protected $state = 0; 113 114 /** 115 * The current cache display helper. 116 * @var core_cache\local\administration_display_helper 117 */ 118 protected static $displayhelper = null; 119 120 /** 121 * Returns an instance of the cache_factory class. 122 * 123 * @param bool $forcereload If set to true a new cache_factory instance will be created and used. 124 * @return cache_factory 125 */ 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); 142 } 143 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); 153 } 154 } 155 } 156 return self::$instance; 157 } 158 159 /** 160 * Protected constructor, please use the static instance method. 161 */ 162 protected function __construct() { 163 // Nothing to do here. 164 } 165 166 /** 167 * Resets the arrays containing instantiated caches, stores, and config instances. 168 */ 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; 178 } 179 180 /** 181 * Resets the stores, clearing the array of created stores. 182 * 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. 185 */ 186 public function reset_cache_instances() { 187 $this->cachesfromdefinitions = array(); 188 $this->cachesfromparams = array(); 189 $this->stores = array(); 190 } 191 192 /** 193 * Creates a cache object given the parameters for a definition. 194 * 195 * If a cache has already been created for the given definition then that cache instance will be returned. 196 * 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 202 */ 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; 209 } 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); 215 216 // Loaders are always held onto to speed up subsequent requests. 217 $this->cachesfromdefinitions[$definitionname] = $cache; 218 return $cache; 219 } 220 221 /** 222 * Creates an ad-hoc cache from the given param. 223 * 224 * If a cache has already been created using the same params then that cache instance will be returned. 225 * 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 236 */ 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]; 242 } 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; 251 } 252 253 /** 254 * Common public method to create a cache instance given a definition. 255 * 256 * This is used by the static make methods. 257 * 258 * @param cache_definition $definition 259 * @return cache_application|cache_session|cache_store 260 * @throws coding_exception 261 */ 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]); 268 } 269 } 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); 273 } 274 $loader = null; 275 if ($definition->has_data_source()) { 276 $loader = $definition->get_data_source(); 277 } 278 while (($store = array_pop($stores)) !== null) { 279 $loader = new $class($definition, $store, $loader); 280 } 281 return $loader; 282 } 283 284 /** 285 * Creates a store instance given its name and configuration. 286 * 287 * If the store has already been instantiated then the original object will be returned. (reused) 288 * 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 293 */ 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; 300 } 301 $store = new $class($details['name'], $details['configuration']); 302 $this->stores[$name] = $store; 303 } 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; 309 } 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(); 321 } 322 $this->definitionstores[$definitionid][] = $store; 323 return $store; 324 } 325 326 /** 327 * Returns an array of cache stores that have been initialised for use in definitions. 328 * @param cache_definition $definition 329 * @return array 330 */ 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(); 335 } 336 return $this->definitionstores[$id]; 337 } 338 339 /** 340 * Returns the cache instances that have been used within this request. 341 * @since Moodle 2.6 342 * @return array 343 */ 344 public function get_caches_in_use() { 345 return $this->cachesfromdefinitions; 346 } 347 348 /** 349 * Gets all adhoc caches that have been used within this request. 350 * 351 * @return cache_store[] Caches currently in use 352 */ 353 public function get_adhoc_caches_in_use() { 354 return $this->cachesfromparams; 355 } 356 357 /** 358 * Creates a cache config instance with the ability to write if required. 359 * 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 362 */ 363 public function create_config_instance($writer = false) { 364 global $CFG; 365 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'); 370 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'; 379 } 380 381 // Check if we need to create a config file with defaults. 382 $needtocreate = !$class::config_file_exists(); 383 384 if ($writer || $needtocreate) { 385 require_once($CFG->dirroot.'/cache/locallib.php'); 386 if (!$testing) { 387 $class .= '_writer'; 388 } 389 } 390 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; 404 } 405 } 406 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(); 411 } 412 413 if (!$error) { 414 // The cache is now ready to use. Update the state. 415 self::set_state(self::STATE_READY); 416 } 417 418 // Return the instance. 419 return $this->configs[$class]; 420 } 421 422 /** 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. 430 */ 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); 462 } 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); 466 } 467 $definition = cache_definition::load($id, $definition); 468 } 469 } else { 470 $definition = cache_definition::load($id, $definition); 471 } 472 } 473 $this->definitions[$id] = $definition; 474 } 475 return $this->definitions[$id]; 476 } 477 478 /** 479 * Creates a dummy store object for use when a loader has no potential stores to use. 480 * 481 * @param cache_definition $definition 482 * @return cachestore_dummy 483 */ 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; 490 } 491 492 /** 493 * Returns a lock instance ready for use. 494 * 495 * @param array $config 496 * @return cache_lock_interface 497 */ 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'); 502 } 503 $name = $config['name']; 504 $type = $config['type']; 505 unset($config['name']); 506 unset($config['type']); 507 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); 513 } 514 if (!class_exists($type)) { 515 throw new coding_exception('Invalid lock plugin requested.'); 516 } 517 $this->lockplugins[$type] = $type; 518 } 519 if (!array_key_exists($type, $this->lockplugins)) { 520 throw new coding_exception('Invalid cache lock type.'); 521 } 522 $class = $this->lockplugins[$type]; 523 return new $class($name, $config); 524 } 525 526 /** 527 * Returns the current state of the cache API. 528 * 529 * @return int 530 */ 531 public function get_state() { 532 return $this->state; 533 } 534 535 /** 536 * Updates the state fo the cache API. 537 * 538 * @param int $state 539 * @return bool 540 */ 541 public function set_state($state) { 542 if ($state <= $this->state) { 543 return false; 544 } 545 $this->state = $state; 546 return true; 547 } 548 549 /** 550 * Informs the factory that the cache is currently updating itself. 551 * 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. 554 */ 555 public function updating_started() { 556 if ($this->state !== self::STATE_READY) { 557 return false; 558 } 559 $this->state = self::STATE_UPDATING; 560 return true; 561 } 562 563 /** 564 * Informs the factory that the upgrading has finished. 565 * 566 * This forces the state back to ready. 567 */ 568 public function updating_finished() { 569 $this->state = self::STATE_READY; 570 } 571 572 /** 573 * Returns true if the cache API has been disabled. 574 * 575 * @return bool 576 */ 577 public function is_disabled() { 578 return $this->state === self::STATE_DISABLED; 579 } 580 581 /** 582 * Returns true if the cache is currently initialising itself. 583 * 584 * This includes both initialisation and saving the cache config file as part of that initialisation. 585 * 586 * @return bool 587 */ 588 public function is_initialising() { 589 return $this->state === self::STATE_INITIALISING || $this->state === self::STATE_SAVING; 590 } 591 592 /** 593 * Returns true if the cache is currently updating itself. 594 * 595 * @return bool 596 */ 597 public function is_updating() { 598 return $this->state === self::STATE_UPDATING; 599 } 600 601 /** 602 * Disables as much of the cache API as possible. 603 * 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! 608 * 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. 612 */ 613 protected static function disable() { 614 global $CFG; 615 require_once($CFG->dirroot.'/cache/disabledlib.php'); 616 self::$instance = new cache_factory_disabled(); 617 } 618 619 /** 620 * Returns true if the cache stores have been disabled. 621 * 622 * @return bool 623 */ 624 public function stores_disabled() { 625 return $this->state === self::STATE_STORES_DISABLED || $this->is_disabled(); 626 } 627 628 /** 629 * Disables cache stores. 630 * 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. 634 * 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> 642 */ 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); 648 } 649 650 /** 651 * Returns an instance of the current display_helper. 652 * 653 * @return core_cache\administration_helper 654 */ 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(); 658 } 659 return self::$displayhelper; 660 } 661 662 /** 663 * Gets the cache_config_writer to use when caching is disabled. 664 * This should only be called from cache_factory_disabled. 665 * 666 * @return cache_config_writer 667 */ 668 public static function get_disabled_writer(): cache_config_writer { 669 global $CFG; 670 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; 676 } 677 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); 688 } 689 690 return new cache_config_writer(); 691 } 692 } 693 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body