Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 39 and 311]

   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  }