Search moodle.org's
Developer Documentation


  • Bug fixes for general core bugs in 2.8.x ended 9 November 2015 (12 months).
  • Bug fixes for security issues in 2.8.x ended 9 May 2016 (18 months).
  • minimum PHP 5.4.4 (always use latest PHP 5.4.x or 5.5.x on Windows - http://windows.php.net/download/), PHP 7 is NOT supported
  • Differences Between: [Versions 28 and 30] [Versions 28 and 31] [Versions 28 and 32] [Versions 28 and 33] [Versions 28 and 34] [Versions 28 and 35] [Versions 28 and 36] [Versions 28 and 37]

       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->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 $unused Used to be data source aggregate however that was removed and this is now unused.
     190       * @return cache_application|cache_session|cache_request
     191       */
     192      public function create_cache_from_definition($component, $area, array $identifiers = array(), $unused = null) {
     193          $definitionname = $component.'/'.$area;
     194          if (isset($this->cachesfromdefinitions[$definitionname])) {
     195              $cache = $this->cachesfromdefinitions[$definitionname];
     196              $cache->set_identifiers($identifiers);
     197              return $cache;
     198          }
     199          $definition = $this->create_definition($component, $area);
     200          $definition->set_identifiers($identifiers);
     201          $cache = $this->create_cache($definition, $identifiers);
     202          // Loaders are always held onto to speed up subsequent requests.
     203          $this->cachesfromdefinitions[$definitionname] = $cache;
     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          // Are we running tests of some form?
     339          $testing = (defined('PHPUNIT_TEST') && PHPUNIT_TEST) || defined('BEHAT_SITE_RUNNING');
     340  
     341          // Check if this is a PHPUnit test and redirect to the phpunit config classes if it is.
     342          if ($testing) {
     343              require_once($CFG->dirroot.'/cache/locallib.php');
     344              require_once($CFG->dirroot.'/cache/tests/fixtures/lib.php');
     345              // We have just a single class for PHP unit tests. We don't care enough about its
     346              // performance to do otherwise and having a single method allows us to inject things into it
     347              // while testing.
     348              $class = 'cache_config_testing';
     349          }
     350  
     351          // Check if we need to create a config file with defaults.
     352          $needtocreate = !$class::config_file_exists();
     353  
     354          if ($writer || $needtocreate) {
     355              require_once($CFG->dirroot.'/cache/locallib.php');
     356              if (!$testing) {
     357                  $class .= '_writer';
     358              }
     359          }
     360  
     361          $error = false;
     362          if ($needtocreate) {
     363              // Create the default configuration.
     364              // Update the state, we are now initialising the cache.
     365              self::set_state(self::STATE_INITIALISING);
     366              /** @var cache_config_writer $class */
     367              $configuration = $class::create_default_configuration();
     368              if ($configuration !== true) {
     369                  // Failed to create the default configuration. Disable the cache stores and update the state.
     370                  self::set_state(self::STATE_ERROR_INITIALISING);
     371                  $this->configs[$class] = new $class;
     372                  $this->configs[$class]->load($configuration);
     373                  $error = true;
     374              }
     375          }
     376  
     377          if (!array_key_exists($class, $this->configs)) {
     378              // Create a new instance and call it to load it.
     379              $this->configs[$class] = new $class;
     380              $this->configs[$class]->load();
     381          }
     382  
     383          if (!$error) {
     384              // The cache is now ready to use. Update the state.
     385              self::set_state(self::STATE_READY);
     386          }
     387  
     388          // Return the instance.
     389          return $this->configs[$class];
     390      }
     391  
     392      /**
     393       * Creates a definition instance or returns the existing one if it has already been created.
     394       * @param string $component
     395       * @param string $area
     396       * @param string $unused This used to be data source aggregate - however that functionality has been removed and
     397       *        this argument is now unused.
     398       * @return cache_definition
     399       * @throws coding_exception If the definition cannot be found.
     400       */
     401      public function create_definition($component, $area, $unused = null) {
     402          $id = $component.'/'.$area;
     403          if (!isset($this->definitions[$id])) {
     404              // This is the first time this definition has been requested.
     405              if ($this->is_initialising()) {
     406                  // We're initialising the cache right now. Don't try to create another config instance.
     407                  // We'll just use an ad-hoc cache for the time being.
     408                  $definition = cache_definition::load_adhoc(cache_store::MODE_REQUEST, $component, $area);
     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                      } else {
     425                          // Either a typo of the developer has just created the definition and is using it for the first time.
     426                          $this->reset();
     427                          $instance = $this->create_config_instance(true);
     428                          $instance->update_definitions();
     429                          $definition = $instance->get_definition_by_id($id);
     430                          if (!$definition) {
     431                              throw new coding_exception('The requested cache definition does not exist.'. $id, $id);
     432                          } else if (!$this->is_disabled()) {
     433                              debugging('Cache definitions reparsed causing cache reset in order to locate definition.
     434                                  You should bump the version number to ensure definitions are reprocessed.', DEBUG_DEVELOPER);
     435                          }
     436                          $definition = cache_definition::load($id, $definition);
     437                      }
     438                  } else {
     439                      $definition = cache_definition::load($id, $definition);
     440                  }
     441              }
     442              $this->definitions[$id] = $definition;
     443          }
     444          return $this->definitions[$id];
     445      }
     446  
     447      /**
     448       * Creates a dummy store object for use when a loader has no potential stores to use.
     449       *
     450       * @param cache_definition $definition
     451       * @return cachestore_dummy
     452       */
     453      protected function create_dummy_store(cache_definition $definition) {
     454          global $CFG;
     455          require_once($CFG->dirroot.'/cache/classes/dummystore.php');
     456          $store = new cachestore_dummy();
     457          $store->initialise($definition);
     458          return $store;
     459      }
     460  
     461      /**
     462       * Returns a lock instance ready for use.
     463       *
     464       * @param array $config
     465       * @return cache_lock_interface
     466       */
     467      public function create_lock_instance(array $config) {
     468          global $CFG;
     469          if (!array_key_exists('name', $config) || !array_key_exists('type', $config)) {
     470              throw new coding_exception('Invalid cache lock instance provided');
     471          }
     472          $name = $config['name'];
     473          $type = $config['type'];
     474          unset($config['name']);
     475          unset($config['type']);
     476  
     477          if (!isset($this->lockplugins[$type])) {
     478              $pluginname = substr($type, 10);
     479              $file = $CFG->dirroot."/cache/locks/{$pluginname}/lib.php";
     480              if (file_exists($file) && is_readable($file)) {
     481                  require_once($file);
     482              }
     483              if (!class_exists($type)) {
     484                  throw new coding_exception('Invalid lock plugin requested.');
     485              }
     486              $this->lockplugins[$type] = $type;
     487          }
     488          if (!array_key_exists($type, $this->lockplugins)) {
     489              throw new coding_exception('Invalid cache lock type.');
     490          }
     491          $class = $this->lockplugins[$type];
     492          return new $class($name, $config);
     493      }
     494  
     495      /**
     496       * Returns the current state of the cache API.
     497       *
     498       * @return int
     499       */
     500      public function get_state() {
     501          return $this->state;
     502      }
     503  
     504      /**
     505       * Updates the state fo the cache API.
     506       *
     507       * @param int $state
     508       * @return bool
     509       */
     510      public function set_state($state) {
     511          if ($state <= $this->state) {
     512              return false;
     513          }
     514          $this->state = $state;
     515          return true;
     516      }
     517  
     518      /**
     519       * Informs the factory that the cache is currently updating itself.
     520       *
     521       * This forces the state to upgrading and can only be called once the cache is ready to use.
     522       * Calling it ensure we don't try to reinstantite things when requesting cache definitions that don't exist yet.
     523       */
     524      public function updating_started() {
     525          if ($this->state !== self::STATE_READY) {
     526              return false;
     527          }
     528          $this->state = self::STATE_UPDATING;
     529          return true;
     530      }
     531  
     532      /**
     533       * Informs the factory that the upgrading has finished.
     534       *
     535       * This forces the state back to ready.
     536       */
     537      public function updating_finished() {
     538          $this->state = self::STATE_READY;
     539      }
     540  
     541      /**
     542       * Returns true if the cache API has been disabled.
     543       *
     544       * @return bool
     545       */
     546      public function is_disabled() {
     547          return $this->state === self::STATE_DISABLED;
     548      }
     549  
     550      /**
     551       * Returns true if the cache is currently initialising itself.
     552       *
     553       * This includes both initialisation and saving the cache config file as part of that initialisation.
     554       *
     555       * @return bool
     556       */
     557      public function is_initialising() {
     558          return $this->state === self::STATE_INITIALISING || $this->state === self::STATE_SAVING;
     559      }
     560  
     561      /**
     562       * Returns true if the cache is currently updating itself.
     563       *
     564       * @return bool
     565       */
     566      public function is_updating() {
     567          return $this->state === self::STATE_UPDATING;
     568      }
     569  
     570      /**
     571       * Disables as much of the cache API as possible.
     572       *
     573       * All of the magic associated with the disabled cache is wrapped into this function.
     574       * In switching out the factory for the disabled factory it gains full control over the initialisation of objects
     575       * and can use all of the disabled alternatives.
     576       * Simple!
     577       *
     578       * This function has been marked as protected so that it cannot be abused through the public API presently.
     579       * Perhaps in the future we will allow this, however as per the build up to the first release containing
     580       * MUC it was decided that this was just to risky and abusable.
     581       */
     582      protected static function disable() {
     583          global $CFG;
     584          require_once($CFG->dirroot.'/cache/disabledlib.php');
     585          self::$instance = new cache_factory_disabled();
     586      }
     587  
     588      /**
     589       * Returns true if the cache stores have been disabled.
     590       *
     591       * @return bool
     592       */
     593      public function stores_disabled() {
     594          return $this->state === self::STATE_STORES_DISABLED || $this->is_disabled();
     595      }
     596  
     597      /**
     598       * Disables cache stores.
     599       *
     600       * The cache API will continue to function however none of the actual stores will be used.
     601       * Instead the dummy store will be provided for all cache requests.
     602       * This is useful in situations where you cannot be sure any stores are working.
     603       *
     604       * In order to re-enable the cache you must call the cache factories static reset method:
     605       * <code>
     606       * // Disable the cache factory.
     607       * cache_factory::disable_stores();
     608       * // Re-enable the cache factory by resetting it.
     609       * cache_factory::reset();
     610       * </code>
     611       */
     612      public static function disable_stores() {
     613          // First reset to clear any static acceleration array.
     614          $factory = self::instance();
     615          $factory->reset_cache_instances();
     616          $factory->set_state(self::STATE_STORES_DISABLED);
     617      }
     618  }
    

    Search This Site: