See Release Notes
Long Term Support Release
Differences Between: [Versions 39 and 310] [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]
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 * 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) { 121 global $CFG; 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); 136 } 137 } else { 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); 143 } 144 } 145 } 146 return self::$instance; 147 } 148 149 /** 150 * Protected constructor, please use the static instance method. 151 */ 152 protected function __construct() { 153 // Nothing to do here. 154 } 155 156 /** 157 * Resets the arrays containing instantiated caches, stores, and config instances. 158 */ 159 public static function reset() { 160 $factory = self::instance(); 161 $factory->reset_cache_instances(); 162 $factory->configs = array(); 163 $factory->definitions = array(); 164 $factory->definitionstores = array(); 165 $factory->lockplugins = array(); // MUST be null in order to force its regeneration. 166 // Reset the state to uninitialised. 167 $factory->state = self::STATE_UNINITIALISED; 168 } 169 170 /** 171 * Resets the stores, clearing the array of created stores. 172 * 173 * Cache objects still held onto by the code that initialised them will remain as is 174 * however all future requests for a cache/store will lead to a new instance being re-initialised. 175 */ 176 public function reset_cache_instances() { 177 $this->cachesfromdefinitions = array(); 178 $this->cachesfromparams = array(); 179 $this->stores = array(); 180 } 181 182 /** 183 * Creates a cache object given the parameters for a definition. 184 * 185 * If a cache has already been created for the given definition then that cache instance will be returned. 186 * 187 * @param string $component 188 * @param string $area 189 * @param array $identifiers 190 * @param string $unused Used to be data source aggregate however that was removed and this is now unused. 191 * @return cache_application|cache_session|cache_request 192 */ 193 public function create_cache_from_definition($component, $area, array $identifiers = array(), $unused = null) { 194 $identifierstring = empty($identifiers) ? '' : '/'.http_build_query($identifiers); 195 $definitionname = $component.'/'.$area.$identifierstring; 196 if (isset($this->cachesfromdefinitions[$definitionname])) { 197 $cache = $this->cachesfromdefinitions[$definitionname]; 198 return $cache; 199 } 200 $definition = $this->create_definition($component, $area); 201 // Identifiers are cached as part of the cache creation, so we store a cloned version of the cache. 202 $cacheddefinition = clone($definition); 203 $cacheddefinition->set_identifiers($identifiers); 204 $cache = $this->create_cache($cacheddefinition); 205 206 // Loaders are always held onto to speed up subsequent requests. 207 $this->cachesfromdefinitions[$definitionname] = $cache; 208 return $cache; 209 } 210 211 /** 212 * Creates an ad-hoc cache from the given param. 213 * 214 * If a cache has already been created using the same params then that cache instance will be returned. 215 * 216 * @param int $mode 217 * @param string $component 218 * @param string $area 219 * @param array $identifiers 220 * @param array $options An array of options, available options are: 221 * - simplekeys : Set to true if the keys you will use are a-zA-Z0-9_ 222 * - simpledata : Set to true if the type of the data you are going to store is scalar, or an array of scalar vars 223 * - staticacceleration : If set to true the cache will hold onto data passing through it. 224 * - staticaccelerationsize : The maximum number of items to hold onto for acceleration purposes. 225 * @return cache_application|cache_session|cache_request 226 */ 227 public function create_cache_from_params($mode, $component, $area, array $identifiers = array(), array $options = array()) { 228 $identifierstring = empty($identifiers) ? '' : '_'.http_build_query($identifiers); 229 $key = "{$mode}_{$component}_{$area}{$identifierstring}"; 230 if (isset($this->cachesfromparams[$key])) { 231 return $this->cachesfromparams[$key]; 232 } 233 // Regular cache definitions are cached inside create_definition(). This is not the case for Adhoc definitions 234 // using load_adhoc(). They are built as a new object on each call. 235 // We do not need to clone the definition because we know it's new. 236 $definition = cache_definition::load_adhoc($mode, $component, $area, $options); 237 $definition->set_identifiers($identifiers); 238 $cache = $this->create_cache($definition); 239 $this->cachesfromparams[$key] = $cache; 240 return $cache; 241 } 242 243 /** 244 * Common public method to create a cache instance given a definition. 245 * 246 * This is used by the static make methods. 247 * 248 * @param cache_definition $definition 249 * @return cache_application|cache_session|cache_store 250 * @throws coding_exception 251 */ 252 public function create_cache(cache_definition $definition) { 253 $class = $definition->get_cache_class(); 254 $stores = cache_helper::get_stores_suitable_for_definition($definition); 255 foreach ($stores as $key => $store) { 256 if (!$store::are_requirements_met()) { 257 unset($stores[$key]); 258 } 259 } 260 if (count($stores) === 0) { 261 // Hmm still no stores, better provide a dummy store to mimic functionality. The dev will be none the wiser. 262 $stores[] = $this->create_dummy_store($definition); 263 } 264 $loader = null; 265 if ($definition->has_data_source()) { 266 $loader = $definition->get_data_source(); 267 } 268 while (($store = array_pop($stores)) !== null) { 269 $loader = new $class($definition, $store, $loader); 270 } 271 return $loader; 272 } 273 274 /** 275 * Creates a store instance given its name and configuration. 276 * 277 * If the store has already been instantiated then the original object will be returned. (reused) 278 * 279 * @param string $name The name of the store (must be unique remember) 280 * @param array $details 281 * @param cache_definition $definition The definition to instantiate it for. 282 * @return boolean|cache_store 283 */ 284 public function create_store_from_config($name, array $details, cache_definition $definition) { 285 if (!array_key_exists($name, $this->stores)) { 286 // Properties: name, plugin, configuration, class. 287 $class = $details['class']; 288 if (!$class::are_requirements_met()) { 289 return false; 290 } 291 $store = new $class($details['name'], $details['configuration']); 292 $this->stores[$name] = $store; 293 } 294 /* @var cache_store $store */ 295 $store = $this->stores[$name]; 296 // We check are_requirements_met although we expect is_ready is going to check as well. 297 if (!$store::are_requirements_met() || !$store->is_ready() || !$store->is_supported_mode($definition->get_mode())) { 298 return false; 299 } 300 // We always create a clone of the original store. 301 // If we were to clone a store that had already been initialised with a definition then 302 // we'd run into a myriad of issues. 303 // We use a method of the store to create a clone rather than just creating it ourselves 304 // so that if any store out there doesn't handle cloning they can override this method in 305 // order to address the issues. 306 $store = $this->stores[$name]->create_clone($details); 307 $store->initialise($definition); 308 $definitionid = $definition->get_id(); 309 if (!isset($this->definitionstores[$definitionid])) { 310 $this->definitionstores[$definitionid] = array(); 311 } 312 $this->definitionstores[$definitionid][] = $store; 313 return $store; 314 } 315 316 /** 317 * Returns an array of cache stores that have been initialised for use in definitions. 318 * @param cache_definition $definition 319 * @return array 320 */ 321 public function get_store_instances_in_use(cache_definition $definition) { 322 $id = $definition->get_id(); 323 if (!isset($this->definitionstores[$id])) { 324 return array(); 325 } 326 return $this->definitionstores[$id]; 327 } 328 329 /** 330 * Returns the cache instances that have been used within this request. 331 * @since Moodle 2.6 332 * @return array 333 */ 334 public function get_caches_in_use() { 335 return $this->cachesfromdefinitions; 336 } 337 338 /** 339 * Gets all adhoc caches that have been used within this request. 340 * 341 * @return cache_store[] Caches currently in use 342 */ 343 public function get_adhoc_caches_in_use() { 344 return $this->cachesfromparams; 345 } 346 347 /** 348 * Creates a cache config instance with the ability to write if required. 349 * 350 * @param bool $writer If set to true an instance that can update the configuration will be returned. 351 * @return cache_config|cache_config_writer 352 */ 353 public function create_config_instance($writer = false) { 354 global $CFG; 355 356 // The class to use. 357 $class = 'cache_config'; 358 // Are we running tests of some form? 359 $testing = (defined('PHPUNIT_TEST') && PHPUNIT_TEST) || defined('BEHAT_SITE_RUNNING'); 360 361 // Check if this is a PHPUnit test and redirect to the phpunit config classes if it is. 362 if ($testing) { 363 require_once($CFG->dirroot.'/cache/locallib.php'); 364 require_once($CFG->dirroot.'/cache/tests/fixtures/lib.php'); 365 // We have just a single class for PHP unit tests. We don't care enough about its 366 // performance to do otherwise and having a single method allows us to inject things into it 367 // while testing. 368 $class = 'cache_config_testing'; 369 } 370 371 // Check if we need to create a config file with defaults. 372 $needtocreate = !$class::config_file_exists(); 373 374 if ($writer || $needtocreate) { 375 require_once($CFG->dirroot.'/cache/locallib.php'); 376 if (!$testing) { 377 $class .= '_writer'; 378 } 379 } 380 381 $error = false; 382 if ($needtocreate) { 383 // Create the default configuration. 384 // Update the state, we are now initialising the cache. 385 self::set_state(self::STATE_INITIALISING); 386 /** @var cache_config_writer $class */ 387 $configuration = $class::create_default_configuration(); 388 if ($configuration !== true) { 389 // Failed to create the default configuration. Disable the cache stores and update the state. 390 self::set_state(self::STATE_ERROR_INITIALISING); 391 $this->configs[$class] = new $class; 392 $this->configs[$class]->load($configuration); 393 $error = true; 394 } 395 } 396 397 if (!array_key_exists($class, $this->configs)) { 398 // Create a new instance and call it to load it. 399 $this->configs[$class] = new $class; 400 $this->configs[$class]->load(); 401 } 402 403 if (!$error) { 404 // The cache is now ready to use. Update the state. 405 self::set_state(self::STATE_READY); 406 } 407 408 // Return the instance. 409 return $this->configs[$class]; 410 } 411 412 /** 413 * Creates a definition instance or returns the existing one if it has already been created. 414 * @param string $component 415 * @param string $area 416 * @param string $unused This used to be data source aggregate - however that functionality has been removed and 417 * this argument is now unused. 418 * @return cache_definition 419 * @throws coding_exception If the definition cannot be found. 420 */ 421 public function create_definition($component, $area, $unused = null) { 422 $id = $component.'/'.$area; 423 if (!isset($this->definitions[$id])) { 424 // This is the first time this definition has been requested. 425 if ($this->is_initialising()) { 426 // We're initialising the cache right now. Don't try to create another config instance. 427 // We'll just use an ad-hoc cache for the time being. 428 $definition = cache_definition::load_adhoc(cache_store::MODE_REQUEST, $component, $area); 429 } else { 430 // Load all the known definitions and find the desired one. 431 $instance = $this->create_config_instance(); 432 $definition = $instance->get_definition_by_id($id); 433 if (!$definition) { 434 // Oh-oh the definition doesn't exist. 435 // There are several things that could be going on here. 436 // We may be installing/upgrading a site and have hit a definition that hasn't been used before. 437 // Of the developer may be trying to use a newly created definition. 438 if ($this->is_updating()) { 439 // The cache is presently initialising and the requested cache definition has not been found. 440 // This means that the cache initialisation has requested something from a cache (I had recursive nightmares about this). 441 // To serve this purpose and avoid errors we are going to make use of an ad-hoc cache rather than 442 // search for the definition which would possibly cause an infitite loop trying to initialise the cache. 443 $definition = cache_definition::load_adhoc(cache_store::MODE_REQUEST, $component, $area); 444 } else { 445 // Either a typo of the developer has just created the definition and is using it for the first time. 446 $this->reset(); 447 $instance = $this->create_config_instance(true); 448 $instance->update_definitions(); 449 $definition = $instance->get_definition_by_id($id); 450 if (!$definition) { 451 throw new coding_exception('The requested cache definition does not exist.'. $id, $id); 452 } 453 if (!$this->is_disabled()) { 454 debugging('Cache definitions reparsed causing cache reset in order to locate definition. 455 You should bump the version number to ensure definitions are reprocessed.', DEBUG_DEVELOPER); 456 } 457 $definition = cache_definition::load($id, $definition); 458 } 459 } else { 460 $definition = cache_definition::load($id, $definition); 461 } 462 } 463 $this->definitions[$id] = $definition; 464 } 465 return $this->definitions[$id]; 466 } 467 468 /** 469 * Creates a dummy store object for use when a loader has no potential stores to use. 470 * 471 * @param cache_definition $definition 472 * @return cachestore_dummy 473 */ 474 protected function create_dummy_store(cache_definition $definition) { 475 global $CFG; 476 require_once($CFG->dirroot.'/cache/classes/dummystore.php'); 477 $store = new cachestore_dummy(); 478 $store->initialise($definition); 479 return $store; 480 } 481 482 /** 483 * Returns a lock instance ready for use. 484 * 485 * @param array $config 486 * @return cache_lock_interface 487 */ 488 public function create_lock_instance(array $config) { 489 global $CFG; 490 if (!array_key_exists('name', $config) || !array_key_exists('type', $config)) { 491 throw new coding_exception('Invalid cache lock instance provided'); 492 } 493 $name = $config['name']; 494 $type = $config['type']; 495 unset($config['name']); 496 unset($config['type']); 497 498 if (!isset($this->lockplugins[$type])) { 499 $pluginname = substr($type, 10); 500 $file = $CFG->dirroot."/cache/locks/{$pluginname}/lib.php"; 501 if (file_exists($file) && is_readable($file)) { 502 require_once($file); 503 } 504 if (!class_exists($type)) { 505 throw new coding_exception('Invalid lock plugin requested.'); 506 } 507 $this->lockplugins[$type] = $type; 508 } 509 if (!array_key_exists($type, $this->lockplugins)) { 510 throw new coding_exception('Invalid cache lock type.'); 511 } 512 $class = $this->lockplugins[$type]; 513 return new $class($name, $config); 514 } 515 516 /** 517 * Returns the current state of the cache API. 518 * 519 * @return int 520 */ 521 public function get_state() { 522 return $this->state; 523 } 524 525 /** 526 * Updates the state fo the cache API. 527 * 528 * @param int $state 529 * @return bool 530 */ 531 public function set_state($state) { 532 if ($state <= $this->state) { 533 return false; 534 } 535 $this->state = $state; 536 return true; 537 } 538 539 /** 540 * Informs the factory that the cache is currently updating itself. 541 * 542 * This forces the state to upgrading and can only be called once the cache is ready to use. 543 * Calling it ensure we don't try to reinstantite things when requesting cache definitions that don't exist yet. 544 */ 545 public function updating_started() { 546 if ($this->state !== self::STATE_READY) { 547 return false; 548 } 549 $this->state = self::STATE_UPDATING; 550 return true; 551 } 552 553 /** 554 * Informs the factory that the upgrading has finished. 555 * 556 * This forces the state back to ready. 557 */ 558 public function updating_finished() { 559 $this->state = self::STATE_READY; 560 } 561 562 /** 563 * Returns true if the cache API has been disabled. 564 * 565 * @return bool 566 */ 567 public function is_disabled() { 568 return $this->state === self::STATE_DISABLED; 569 } 570 571 /** 572 * Returns true if the cache is currently initialising itself. 573 * 574 * This includes both initialisation and saving the cache config file as part of that initialisation. 575 * 576 * @return bool 577 */ 578 public function is_initialising() { 579 return $this->state === self::STATE_INITIALISING || $this->state === self::STATE_SAVING; 580 } 581 582 /** 583 * Returns true if the cache is currently updating itself. 584 * 585 * @return bool 586 */ 587 public function is_updating() { 588 return $this->state === self::STATE_UPDATING; 589 } 590 591 /** 592 * Disables as much of the cache API as possible. 593 * 594 * All of the magic associated with the disabled cache is wrapped into this function. 595 * In switching out the factory for the disabled factory it gains full control over the initialisation of objects 596 * and can use all of the disabled alternatives. 597 * Simple! 598 * 599 * This function has been marked as protected so that it cannot be abused through the public API presently. 600 * Perhaps in the future we will allow this, however as per the build up to the first release containing 601 * MUC it was decided that this was just to risky and abusable. 602 */ 603 protected static function disable() { 604 global $CFG; 605 require_once($CFG->dirroot.'/cache/disabledlib.php'); 606 self::$instance = new cache_factory_disabled(); 607 } 608 609 /** 610 * Returns true if the cache stores have been disabled. 611 * 612 * @return bool 613 */ 614 public function stores_disabled() { 615 return $this->state === self::STATE_STORES_DISABLED || $this->is_disabled(); 616 } 617 618 /** 619 * Disables cache stores. 620 * 621 * The cache API will continue to function however none of the actual stores will be used. 622 * Instead the dummy store will be provided for all cache requests. 623 * This is useful in situations where you cannot be sure any stores are working. 624 * 625 * In order to re-enable the cache you must call the cache factories static reset method: 626 * <code> 627 * // Disable the cache factory. 628 * cache_factory::disable_stores(); 629 * // Re-enable the cache factory by resetting it. 630 * cache_factory::reset(); 631 * </code> 632 */ 633 public static function disable_stores() { 634 // First reset to clear any static acceleration array. 635 $factory = self::instance(); 636 $factory->reset_cache_instances(); 637 $factory->set_state(self::STATE_STORES_DISABLED); 638 } 639 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body