[ Index ]

PHP Cross Reference of moodle-2.8

title

Body

[close]

/cache/classes/ -> factory.php (source)

   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) {
 130                  // We're using the regular 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->lockplugins = array(); // MUST be null in order to force its regeneration.
 165          // Reset the state to uninitialised.
 166          $factory->state = self::STATE_UNINITIALISED;
 167      }
 168  
 169      /**
 170       * Resets the stores, clearing the array of created stores.
 171       *
 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.
 174       */
 175      public function reset_cache_instances() {
 176          $this->cachesfromdefinitions = array();
 177          $this->cachesfromparams = array();
 178          $this->stores = array();
 179      }
 180  
 181      /**
 182       * Creates a cache object given the parameters for a definition.
 183       *
 184       * If a cache has already been created for the given definition then that cache instance will be returned.
 185       *
 186       * @param string $component
 187       * @param string $area
 188       * @param array $identifiers
 189       * @param string $aggregate
 190       * @return cache_application|cache_session|cache_request
 191       */
 192      public function create_cache_from_definition($component, $area, array $identifiers = array(), $aggregate = null) {
 193          $definitionname = $component.'/'.$area;
 194          if (isset($this->cachesfromdefinitions[$definitionname])) {
 195              $cache = $this->cachesfromdefinitions[$definitionname];
 196              $cache->set_identifiers($identifiers);
 197              return $cache;
 198          }
 199          $definition = $this->create_definition($component, $area, $aggregate);
 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;
 204          return $cache;
 205      }
 206  
 207      /**
 208       * Creates an ad-hoc cache from the given param.
 209       *
 210       * If a cache has already been created using the same params then that cache instance will be returned.
 211       *
 212       * @param int $mode
 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
 222       */
 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];
 227          }
 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;
 232          return $cache;
 233      }
 234  
 235      /**
 236       * Common public method to create a cache instance given a definition.
 237       *
 238       * This is used by the static make methods.
 239       *
 240       * @param cache_definition $definition
 241       * @return cache_application|cache_session|cache_store
 242       * @throws coding_exception
 243       */
 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]);
 250              }
 251          }
 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);
 255          }
 256          $loader = null;
 257          if ($definition->has_data_source()) {
 258              $loader = $definition->get_data_source();
 259          }
 260          while (($store = array_pop($stores)) !== null) {
 261              $loader = new $class($definition, $store, $loader);
 262          }
 263          return $loader;
 264      }
 265  
 266      /**
 267       * Creates a store instance given its name and configuration.
 268       *
 269       * If the store has already been instantiated then the original object will be returned. (reused)
 270       *
 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
 275       */
 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;
 282          }
 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())) {
 287              return false;
 288          }
 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();
 300          }
 301          $this->definitionstores[$definitionid][] = $store;
 302          return $store;
 303      }
 304  
 305      /**
 306       * Returns an array of cache stores that have been initialised for use in definitions.
 307       * @param cache_definition $definition
 308       * @return array
 309       */
 310      public function get_store_instances_in_use(cache_definition $definition) {
 311          $id = $definition->get_id();
 312          if (!isset($this->definitionstores[$id])) {
 313              return array();
 314          }
 315          return $this->definitionstores[$id];
 316      }
 317  
 318      /**
 319       * Returns the cache instances that have been used within this request.
 320       * @since Moodle 2.6
 321       * @return array
 322       */
 323      public function get_caches_in_use() {
 324          return $this->cachesfromdefinitions;
 325      }
 326  
 327      /**
 328       * Creates a cache config instance with the ability to write if required.
 329       *
 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
 332       */
 333      public function create_config_instance($writer = false) {
 334          global $CFG;
 335  
 336          // The class to use.
 337          $class = 'cache_config';
 338          $unittest = defined('PHPUNIT_TEST') && PHPUNIT_TEST;
 339  
 340          // Check if this is a PHPUnit test and redirect to the phpunit config classes if it is.
 341          if ($unittest) {
 342              require_once($CFG->dirroot.'/cache/locallib.php');
 343              require_once($CFG->dirroot.'/cache/tests/fixtures/lib.php');
 344              // We have just a single class for PHP unit tests. We don't care enough about its
 345              // performance to do otherwise and having a single method allows us to inject things into it
 346              // while testing.
 347              $class = 'cache_config_phpunittest';
 348          }
 349  
 350          // Check if we need to create a config file with defaults.
 351          $needtocreate = !$class::config_file_exists();
 352  
 353          if ($writer || $needtocreate) {
 354              require_once($CFG->dirroot.'/cache/locallib.php');
 355              if (!$unittest) {
 356                  $class .= '_writer';
 357              }
 358          }
 359  
 360          $error = false;
 361          if ($needtocreate) {
 362              // Create the default configuration.
 363              // Update the state, we are now initialising the cache.
 364              self::set_state(self::STATE_INITIALISING);
 365              /** @var cache_config_writer $class */
 366              $configuration = $class::create_default_configuration();
 367              if ($configuration !== true) {
 368                  // Failed to create the default configuration. Disable the cache stores and update the state.
 369                  self::set_state(self::STATE_ERROR_INITIALISING);
 370                  $this->configs[$class] = new $class;
 371                  $this->configs[$class]->load($configuration);
 372                  $error = true;
 373              }
 374          }
 375  
 376          if (!array_key_exists($class, $this->configs)) {
 377              // Create a new instance and call it to load it.
 378              $this->configs[$class] = new $class;
 379              $this->configs[$class]->load();
 380          }
 381  
 382          if (!$error) {
 383              // The cache is now ready to use. Update the state.
 384              self::set_state(self::STATE_READY);
 385          }
 386  
 387          // Return the instance.
 388          return $this->configs[$class];
 389      }
 390  
 391      /**
 392       * Creates a definition instance or returns the existing one if it has already been created.
 393       * @param string $component
 394       * @param string $area
 395       * @param string $aggregate
 396       * @return cache_definition
 397       */
 398      public function create_definition($component, $area, $aggregate = null) {
 399          $id = $component.'/'.$area;
 400          if ($aggregate) {
 401              $id .= '::'.$aggregate;
 402          }
 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);
 409              } else {
 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);
 413                  if (!$definition) {
 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);
 424                          if ($aggregate !== null) {
 425                              // If you get here you deserve a warning. We have to use an ad-hoc cache here, so we can't find the definition and therefor
 426                              // can't find any information about the datasource or any of its aggregated.
 427                              // Best of luck.
 428                              debugging('An unknown cache was requested during development with an aggregate that could not be loaded. Ad-hoc cache used instead.', DEBUG_DEVELOPER);
 429                              $aggregate = null;
 430                          }
 431                      } else {
 432                          // Either a typo of the developer has just created the definition and is using it for the first time.
 433                          $this->reset();
 434                          $instance = $this->create_config_instance(true);
 435                          $instance->update_definitions();
 436                          $definition = $instance->get_definition_by_id($id);
 437                          if (!$definition) {
 438                              throw new coding_exception('The requested cache definition does not exist.'. $id, $id);
 439                          } else if (!$this->is_disabled()) {
 440                              debugging('Cache definitions reparsed causing cache reset in order to locate definition.
 441                                  You should bump the version number to ensure definitions are reprocessed.', DEBUG_DEVELOPER);
 442                          }
 443                          $definition = cache_definition::load($id, $definition, $aggregate);
 444                      }
 445                  } else {
 446                      $definition = cache_definition::load($id, $definition, $aggregate);
 447                  }
 448              }
 449              $this->definitions[$id] = $definition;
 450          }
 451          return $this->definitions[$id];
 452      }
 453  
 454      /**
 455       * Creates a dummy store object for use when a loader has no potential stores to use.
 456       *
 457       * @param cache_definition $definition
 458       * @return cachestore_dummy
 459       */
 460      protected function create_dummy_store(cache_definition $definition) {
 461          global $CFG;
 462          require_once($CFG->dirroot.'/cache/classes/dummystore.php');
 463          $store = new cachestore_dummy();
 464          $store->initialise($definition);
 465          return $store;
 466      }
 467  
 468      /**
 469       * Returns a lock instance ready for use.
 470       *
 471       * @param array $config
 472       * @return cache_lock_interface
 473       */
 474      public function create_lock_instance(array $config) {
 475          global $CFG;
 476          if (!array_key_exists('name', $config) || !array_key_exists('type', $config)) {
 477              throw new coding_exception('Invalid cache lock instance provided');
 478          }
 479          $name = $config['name'];
 480          $type = $config['type'];
 481          unset($config['name']);
 482          unset($config['type']);
 483  
 484          if (!isset($this->lockplugins[$type])) {
 485              $pluginname = substr($type, 10);
 486              $file = $CFG->dirroot."/cache/locks/{$pluginname}/lib.php";
 487              if (file_exists($file) && is_readable($file)) {
 488                  require_once($file);
 489              }
 490              if (!class_exists($type)) {
 491                  throw new coding_exception('Invalid lock plugin requested.');
 492              }
 493              $this->lockplugins[$type] = $type;
 494          }
 495          if (!array_key_exists($type, $this->lockplugins)) {
 496              throw new coding_exception('Invalid cache lock type.');
 497          }
 498          $class = $this->lockplugins[$type];
 499          return new $class($name, $config);
 500      }
 501  
 502      /**
 503       * Returns the current state of the cache API.
 504       *
 505       * @return int
 506       */
 507      public function get_state() {
 508          return $this->state;
 509      }
 510  
 511      /**
 512       * Updates the state fo the cache API.
 513       *
 514       * @param int $state
 515       * @return bool
 516       */
 517      public function set_state($state) {
 518          if ($state <= $this->state) {
 519              return false;
 520          }
 521          $this->state = $state;
 522          return true;
 523      }
 524  
 525      /**
 526       * Informs the factory that the cache is currently updating itself.
 527       *
 528       * This forces the state to upgrading and can only be called once the cache is ready to use.
 529       * Calling it ensure we don't try to reinstantite things when requesting cache definitions that don't exist yet.
 530       */
 531      public function updating_started() {
 532          if ($this->state !== self::STATE_READY) {
 533              return false;
 534          }
 535          $this->state = self::STATE_UPDATING;
 536          return true;
 537      }
 538  
 539      /**
 540       * Informs the factory that the upgrading has finished.
 541       *
 542       * This forces the state back to ready.
 543       */
 544      public function updating_finished() {
 545          $this->state = self::STATE_READY;
 546      }
 547  
 548      /**
 549       * Returns true if the cache API has been disabled.
 550       *
 551       * @return bool
 552       */
 553      public function is_disabled() {
 554          return $this->state === self::STATE_DISABLED;
 555      }
 556  
 557      /**
 558       * Returns true if the cache is currently initialising itself.
 559       *
 560       * This includes both initialisation and saving the cache config file as part of that initialisation.
 561       *
 562       * @return bool
 563       */
 564      public function is_initialising() {
 565          return $this->state === self::STATE_INITIALISING || $this->state === self::STATE_SAVING;
 566      }
 567  
 568      /**
 569       * Returns true if the cache is currently updating itself.
 570       *
 571       * @return bool
 572       */
 573      public function is_updating() {
 574          return $this->state === self::STATE_UPDATING;
 575      }
 576  
 577      /**
 578       * Disables as much of the cache API as possible.
 579       *
 580       * All of the magic associated with the disabled cache is wrapped into this function.
 581       * In switching out the factory for the disabled factory it gains full control over the initialisation of objects
 582       * and can use all of the disabled alternatives.
 583       * Simple!
 584       *
 585       * This function has been marked as protected so that it cannot be abused through the public API presently.
 586       * Perhaps in the future we will allow this, however as per the build up to the first release containing
 587       * MUC it was decided that this was just to risky and abusable.
 588       */
 589      protected static function disable() {
 590          global $CFG;
 591          require_once($CFG->dirroot.'/cache/disabledlib.php');
 592          self::$instance = new cache_factory_disabled();
 593      }
 594  
 595      /**
 596       * Returns true if the cache stores have been disabled.
 597       *
 598       * @return bool
 599       */
 600      public function stores_disabled() {
 601          return $this->state === self::STATE_STORES_DISABLED || $this->is_disabled();
 602      }
 603  
 604      /**
 605       * Disables cache stores.
 606       *
 607       * The cache API will continue to function however none of the actual stores will be used.
 608       * Instead the dummy store will be provided for all cache requests.
 609       * This is useful in situations where you cannot be sure any stores are working.
 610       *
 611       * In order to re-enable the cache you must call the cache factories static reset method:
 612       * <code>
 613       * // Disable the cache factory.
 614       * cache_factory::disable_stores();
 615       * // Re-enable the cache factory by resetting it.
 616       * cache_factory::reset();
 617       * </code>
 618       */
 619      public static function disable_stores() {
 620          // First reset to clear any static acceleration array.
 621          $factory = self::instance();
 622          $factory->reset_cache_instances();
 623          $factory->set_state(self::STATE_STORES_DISABLED);
 624      }
 625  }


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1