[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/cache/classes/ -> loaders.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   * Cache loaders
  19   *
  20   * This file is part of Moodle's cache API, affectionately called MUC.
  21   * It contains the components that are required 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 main cache class.
  33   *
  34   * This class if the first class that any end developer will interact with.
  35   * In order to create an instance of a cache that they can work with they must call one of the static make methods belonging
  36   * to this class.
  37   *
  38   * @package    core
  39   * @category   cache
  40   * @copyright  2012 Sam Hemelryk
  41   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  42   */
  43  class cache implements cache_loader {
  44  
  45      /**
  46       * We need a timestamp to use within the cache API.
  47       * This stamp needs to be used for all ttl and time based operations to ensure that we don't end up with
  48       * timing issues.
  49       * @var int
  50       */
  51      protected static $now;
  52  
  53      /**
  54       * The definition used when loading this cache if there was one.
  55       * @var cache_definition
  56       */
  57      private $definition = false;
  58  
  59      /**
  60       * The cache store that this loader will make use of.
  61       * @var cache_store
  62       */
  63      private $store;
  64  
  65      /**
  66       * The next cache loader in the chain if there is one.
  67       * If a cache request misses for the store belonging to this loader then the loader
  68       * stored here will be checked next.
  69       * If there is a loader here then $datasource must be false.
  70       * @var cache_loader|false
  71       */
  72      private $loader = false;
  73  
  74      /**
  75       * The data source to use if we need to load data (because if doesn't exist in the cache store).
  76       * If there is a data source here then $loader above must be false.
  77       * @var cache_data_source|false
  78       */
  79      private $datasource = false;
  80  
  81      /**
  82       * Used to quickly check if the store supports key awareness.
  83       * This is set when the cache is initialised and is used to speed up processing.
  84       * @var bool
  85       */
  86      private $supportskeyawareness = null;
  87  
  88      /**
  89       * Used to quickly check if the store supports ttl natively.
  90       * This is set when the cache is initialised and is used to speed up processing.
  91       * @var bool
  92       */
  93      private $supportsnativettl = null;
  94  
  95      /**
  96       * Gets set to true if the cache is going to be using a static array for acceleration.
  97       * The array statically caches items used during the lifetime of the request. This greatly speeds up interaction
  98       * with the cache in areas where it will be repetitively hit for the same information such as with strings.
  99       * There are several other variables to control how this static acceleration array works.
 100       * @var bool
 101       */
 102      private $staticacceleration = false;
 103  
 104      /**
 105       * The static acceleration array.
 106       * Items will be stored in this cache as they were provided. This ensure there is no unnecessary processing taking place.
 107       * @var array
 108       */
 109      private $staticaccelerationarray = array();
 110  
 111      /**
 112       * The number of items in the static acceleration array. Avoids count calls like you wouldn't believe.
 113       * @var int
 114       */
 115      private $staticaccelerationcount = 0;
 116  
 117      /**
 118       * An array containing just the keys being used in the static acceleration array.
 119       * This seems redundant perhaps but is used when managing the size of the static acceleration array.
 120       * Items are added to the end of the array and the when we need to reduce the size of the cache we use the
 121       * key that is first on this array.
 122       * @var array
 123       */
 124      private $staticaccelerationkeys = array();
 125  
 126      /**
 127       * The maximum size of the static acceleration array.
 128       *
 129       * If set to false there is no max size.
 130       * Caches that make use of static acceleration should seriously consider setting this to something reasonably small, but
 131       * still large enough to offset repetitive calls.
 132       *
 133       * @var int|false
 134       */
 135      private $staticaccelerationsize = false;
 136  
 137      /**
 138       * Gets set to true during initialisation if the definition is making use of a ttl.
 139       * Used to speed up processing.
 140       * @var bool
 141       */
 142      private $hasattl = false;
 143  
 144      /**
 145       * Gets set to the class name of the store during initialisation. This is used several times in the cache class internally
 146       * and having it here helps speed up processing.
 147       * @var strubg
 148       */
 149      protected $storetype = 'unknown';
 150  
 151      /**
 152       * Gets set to true if we want to collect performance information about the cache API.
 153       * @var bool
 154       */
 155      protected $perfdebug = false;
 156  
 157      /**
 158       * Determines if this loader is a sub loader, not the top of the chain.
 159       * @var bool
 160       */
 161      protected $subloader = false;
 162  
 163      /**
 164       * Creates a new cache instance for a pre-defined definition.
 165       *
 166       * @param string $component The component for the definition
 167       * @param string $area The area for the definition
 168       * @param array $identifiers Any additional identifiers that should be provided to the definition.
 169       * @param string $unused Used to be datasourceaggregate but that was removed and this is now unused.
 170       * @return cache_application|cache_session|cache_store
 171       */
 172      public static function make($component, $area, array $identifiers = array(), $unused = null) {
 173          $factory = cache_factory::instance();
 174          return $factory->create_cache_from_definition($component, $area, $identifiers);
 175      }
 176  
 177      /**
 178       * Creates a new cache instance based upon the given params.
 179       *
 180       * @param int $mode One of cache_store::MODE_*
 181       * @param string $component The component this cache relates to.
 182       * @param string $area The area this cache relates to.
 183       * @param array $identifiers Any additional identifiers that should be provided to the definition.
 184       * @param array $options An array of options, available options are:
 185       *   - simplekeys : Set to true if the keys you will use are a-zA-Z0-9_
 186       *   - simpledata : Set to true if the type of the data you are going to store is scalar, or an array of scalar vars
 187       *   - staticacceleration : If set to true the cache will hold onto data passing through it.
 188       *   - staticaccelerationsize : The max size for the static acceleration array.
 189       * @return cache_application|cache_session|cache_store
 190       */
 191      public static function make_from_params($mode, $component, $area, array $identifiers = array(), array $options = array()) {
 192          $factory = cache_factory::instance();
 193          return $factory->create_cache_from_params($mode, $component, $area, $identifiers, $options);
 194      }
 195  
 196      /**
 197       * Constructs a new cache instance.
 198       *
 199       * You should not call this method from your code, instead you should use the cache::make methods.
 200       *
 201       * This method is public so that the cache_factory is able to instantiate cache instances.
 202       * Ideally we would make this method protected and expose its construction to the factory method internally somehow.
 203       * The factory class is responsible for this in order to centralise the storage of instances once created. This way if needed
 204       * we can force a reset of the cache API (used during unit testing).
 205       *
 206       * @param cache_definition $definition The definition for the cache instance.
 207       * @param cache_store $store The store that cache should use.
 208       * @param cache_loader|cache_data_source $loader The next loader in the chain or the data source if there is one and there
 209       *      are no other cache_loaders in the chain.
 210       */
 211      public function __construct(cache_definition $definition, cache_store $store, $loader = null) {
 212          global $CFG;
 213          $this->definition = $definition;
 214          $this->store = $store;
 215          $this->storetype = get_class($store);
 216          $this->perfdebug = !empty($CFG->perfdebug);
 217          if ($loader instanceof cache_loader) {
 218              $this->loader = $loader;
 219              // Mark the loader as a sub (chained) loader.
 220              $this->loader->set_is_sub_loader(true);
 221          } else if ($loader instanceof cache_data_source) {
 222              $this->datasource = $loader;
 223          }
 224          $this->definition->generate_definition_hash();
 225          $this->staticacceleration = $this->definition->use_static_acceleration();
 226          if ($this->staticacceleration) {
 227              $this->staticaccelerationsize = $this->definition->get_static_acceleration_size();
 228          }
 229          $this->hasattl = ($this->definition->get_ttl() > 0);
 230      }
 231  
 232      /**
 233       * Used to inform the loader of its state as a sub loader, or as the top of the chain.
 234       *
 235       * This is important as it ensures that we do not have more than one loader keeping static acceleration data.
 236       * Subloaders need to be "pure" loaders in the sense that they are used to store and retrieve information from stores or the
 237       * next loader/data source in the chain.
 238       * Nothing fancy, nothing flash.
 239       *
 240       * @param bool $setting
 241       */
 242      protected function set_is_sub_loader($setting = true) {
 243          if ($setting) {
 244              $this->subloader = true;
 245              // Subloaders should not keep static acceleration data.
 246              $this->staticacceleration = false;
 247              $this->staticaccelerationsize = false;
 248          } else {
 249              $this->subloader = true;
 250              $this->staticacceleration = $this->definition->use_static_acceleration();
 251              if ($this->staticacceleration) {
 252                  $this->staticaccelerationsize = $this->definition->get_static_acceleration_size();
 253              }
 254          }
 255      }
 256  
 257      /**
 258       * Alters the identifiers that have been provided to the definition.
 259       *
 260       * This is an advanced method and should not be used unless really needed.
 261       * It allows the developer to slightly alter the definition without having to re-establish the cache.
 262       * It will cause more processing as the definition will need to clear and reprepare some of its properties.
 263       *
 264       * @param array $identifiers
 265       */
 266      public function set_identifiers(array $identifiers) {
 267          if ($this->definition->set_identifiers($identifiers)) {
 268              // As static acceleration uses input keys and not parsed keys
 269              // it much be cleared when the identifier set is changed.
 270              $this->staticaccelerationarray = array();
 271              if ($this->staticaccelerationsize !== false) {
 272                  $this->staticaccelerationkeys = array();
 273                  $this->staticaccelerationcount = 0;
 274              }
 275          }
 276      }
 277  
 278      /**
 279       * Retrieves the value for the given key from the cache.
 280       *
 281       * @param string|int $key The key for the data being requested.
 282       *      It can be any structure although using a scalar string or int is recommended in the interests of performance.
 283       *      In advanced cases an array may be useful such as in situations requiring the multi-key functionality.
 284       * @param int $strictness One of IGNORE_MISSING | MUST_EXIST
 285       * @return mixed|false The data from the cache or false if the key did not exist within the cache.
 286       * @throws coding_exception
 287       */
 288      public function get($key, $strictness = IGNORE_MISSING) {
 289          // 1. Get it from the static acceleration array if we can (only when it is enabled and it has already been requested/set).
 290          $usesstaticacceleration = $this->use_static_acceleration();
 291  
 292          if ($usesstaticacceleration) {
 293              $result = $this->static_acceleration_get($key);
 294              if ($result !== false) {
 295                  return $result;
 296              }
 297          }
 298  
 299          // 2. Parse the key.
 300          $parsedkey = $this->parse_key($key);
 301  
 302          // 3. Get it from the store. Obviously wasn't in the static acceleration array.
 303          $result = $this->store->get($parsedkey);
 304          if ($result !== false) {
 305              if ($result instanceof cache_ttl_wrapper) {
 306                  if ($result->has_expired()) {
 307                      $this->store->delete($parsedkey);
 308                      $result = false;
 309                  } else {
 310                      $result = $result->data;
 311                  }
 312              }
 313              if ($result instanceof cache_cached_object) {
 314                  $result = $result->restore_object();
 315              }
 316              if ($usesstaticacceleration) {
 317                  $this->static_acceleration_set($key, $result);
 318              }
 319          }
 320  
 321          // 4. Load if from the loader/datasource if we don't already have it.
 322          $setaftervalidation = false;
 323          if ($result === false) {
 324              if ($this->perfdebug) {
 325                  cache_helper::record_cache_miss($this->storetype, $this->definition);
 326              }
 327              if ($this->loader !== false) {
 328                  // We must pass the original (unparsed) key to the next loader in the chain.
 329                  // The next loader will parse the key as it sees fit. It may be parsed differently
 330                  // depending upon the capabilities of the store associated with the loader.
 331                  $result = $this->loader->get($key);
 332              } else if ($this->datasource !== false) {
 333                  $result = $this->datasource->load_for_cache($key);
 334              }
 335              $setaftervalidation = ($result !== false);
 336          } else if ($this->perfdebug) {
 337              cache_helper::record_cache_hit($this->storetype, $this->definition);
 338          }
 339          // 5. Validate strictness.
 340          if ($strictness === MUST_EXIST && $result === false) {
 341              throw new coding_exception('Requested key did not exist in any cache stores and could not be loaded.');
 342          }
 343          // 6. Set it to the store if we got it from the loader/datasource.
 344          if ($setaftervalidation) {
 345              $this->set($key, $result);
 346          }
 347          // 7. Make sure we don't pass back anything that could be a reference.
 348          //    We don't want people modifying the data in the cache.
 349          if (!$this->store->supports_dereferencing_objects() && !is_scalar($result)) {
 350              // If data is an object it will be a reference.
 351              // If data is an array if may contain references.
 352              // We want to break references so that the cache cannot be modified outside of itself.
 353              // Call the function to unreference it (in the best way possible).
 354              $result = $this->unref($result);
 355          }
 356          return $result;
 357      }
 358  
 359      /**
 360       * Retrieves an array of values for an array of keys.
 361       *
 362       * Using this function comes with potential performance implications.
 363       * Not all cache stores will support get_many/set_many operations and in order to replicate this functionality will call
 364       * the equivalent singular method for each item provided.
 365       * This should not deter you from using this function as there is a performance benefit in situations where the cache store
 366       * does support it, but you should be aware of this fact.
 367       *
 368       * @param array $keys The keys of the data being requested.
 369       *      Each key can be any structure although using a scalar string or int is recommended in the interests of performance.
 370       *      In advanced cases an array may be useful such as in situations requiring the multi-key functionality.
 371       * @param int $strictness One of IGNORE_MISSING or MUST_EXIST.
 372       * @return array An array of key value pairs for the items that could be retrieved from the cache.
 373       *      If MUST_EXIST was used and not all keys existed within the cache then an exception will be thrown.
 374       *      Otherwise any key that did not exist will have a data value of false within the results.
 375       * @throws coding_exception
 376       */
 377      public function get_many(array $keys, $strictness = IGNORE_MISSING) {
 378  
 379          $keysparsed = array();
 380          $parsedkeys = array();
 381          $resultpersist = array();
 382          $resultstore = array();
 383          $keystofind = array();
 384  
 385          // First up check the persist cache for each key.
 386          $isusingpersist = $this->use_static_acceleration();
 387          foreach ($keys as $key) {
 388              $pkey = $this->parse_key($key);
 389              $keysparsed[$key] = $pkey;
 390              $parsedkeys[$pkey] = $key;
 391              $keystofind[$pkey] = $key;
 392              if ($isusingpersist) {
 393                  $value = $this->static_acceleration_get($key);
 394                  if ($value !== false) {
 395                      $resultpersist[$pkey] = $value;
 396                      unset($keystofind[$pkey]);
 397                  }
 398              }
 399          }
 400  
 401          // Next assuming we didn't find all of the keys in the persist cache try loading them from the store.
 402          if (count($keystofind)) {
 403              $resultstore = $this->store->get_many(array_keys($keystofind));
 404              // Process each item in the result to "unwrap" it.
 405              foreach ($resultstore as $key => $value) {
 406                  if ($value instanceof cache_ttl_wrapper) {
 407                      if ($value->has_expired()) {
 408                          $value = false;
 409                      } else {
 410                          $value = $value->data;
 411                      }
 412                  }
 413                  if ($value instanceof cache_cached_object) {
 414                      $value = $value->restore_object();
 415                  }
 416                  if ($value !== false && $this->use_static_acceleration()) {
 417                      $this->static_acceleration_set($keystofind[$key], $value);
 418                  }
 419                  $resultstore[$key] = $value;
 420              }
 421          }
 422  
 423          // Merge the result from the persis cache with the results from the store load.
 424          $result = $resultpersist + $resultstore;
 425          unset($resultpersist);
 426          unset($resultstore);
 427  
 428          // Next we need to find any missing values and load them from the loader/datasource next in the chain.
 429          $usingloader = ($this->loader !== false);
 430          $usingsource = (!$usingloader && ($this->datasource !== false));
 431          if ($usingloader || $usingsource) {
 432              $missingkeys = array();
 433              foreach ($result as $key => $value) {
 434                  if ($value === false) {
 435                      $missingkeys[] = $parsedkeys[$key];
 436                  }
 437              }
 438              if (!empty($missingkeys)) {
 439                  if ($usingloader) {
 440                      $resultmissing = $this->loader->get_many($missingkeys);
 441                  } else {
 442                      $resultmissing = $this->datasource->load_many_for_cache($missingkeys);
 443                  }
 444                  foreach ($resultmissing as $key => $value) {
 445                      $result[$keysparsed[$key]] = $value;
 446                      if ($value !== false) {
 447                          $this->set($key, $value);
 448                      }
 449                  }
 450                  unset($resultmissing);
 451              }
 452              unset($missingkeys);
 453          }
 454  
 455          // Create an array with the original keys and the found values. This will be what we return.
 456          $fullresult = array();
 457          foreach ($result as $key => $value) {
 458              if (!is_scalar($value)) {
 459                  // If data is an object it will be a reference.
 460                  // If data is an array if may contain references.
 461                  // We want to break references so that the cache cannot be modified outside of itself.
 462                  // Call the function to unreference it (in the best way possible).
 463                  $value = $this->unref($value);
 464              }
 465              $fullresult[$parsedkeys[$key]] = $value;
 466          }
 467          unset($result);
 468  
 469          // Final step is to check strictness.
 470          if ($strictness === MUST_EXIST) {
 471              foreach ($keys as $key) {
 472                  if (!array_key_exists($key, $fullresult)) {
 473                      throw new coding_exception('Not all the requested keys existed within the cache stores.');
 474                  }
 475              }
 476          }
 477  
 478          if ($this->perfdebug) {
 479              $hits = 0;
 480              $misses = 0;
 481              foreach ($fullresult as $value) {
 482                  if ($value === false) {
 483                      $misses++;
 484                  } else {
 485                      $hits++;
 486                  }
 487              }
 488              cache_helper::record_cache_hit($this->storetype, $this->definition, $hits);
 489              cache_helper::record_cache_miss($this->storetype, $this->definition, $misses);
 490          }
 491  
 492          // Return the result. Phew!
 493          return $fullresult;
 494      }
 495  
 496      /**
 497       * Sends a key => value pair to the cache.
 498       *
 499       * <code>
 500       * // This code will add four entries to the cache, one for each url.
 501       * $cache->set('main', 'http://moodle.org');
 502       * $cache->set('docs', 'http://docs.moodle.org');
 503       * $cache->set('tracker', 'http://tracker.moodle.org');
 504       * $cache->set('qa', 'http://qa.moodle.net');
 505       * </code>
 506       *
 507       * @param string|int $key The key for the data being requested.
 508       *      It can be any structure although using a scalar string or int is recommended in the interests of performance.
 509       *      In advanced cases an array may be useful such as in situations requiring the multi-key functionality.
 510       * @param mixed $data The data to set against the key.
 511       * @return bool True on success, false otherwise.
 512       */
 513      public function set($key, $data) {
 514          if ($this->perfdebug) {
 515              cache_helper::record_cache_set($this->storetype, $this->definition);
 516          }
 517          if ($this->loader !== false) {
 518              // We have a loader available set it there as well.
 519              // We have to let the loader do its own parsing of data as it may be unique.
 520              $this->loader->set($key, $data);
 521          }
 522          $usestaticacceleration = $this->use_static_acceleration();
 523  
 524          if (is_object($data) && $data instanceof cacheable_object) {
 525              $data = new cache_cached_object($data);
 526          } else if (!$this->store->supports_dereferencing_objects() && !is_scalar($data)) {
 527              // If data is an object it will be a reference.
 528              // If data is an array if may contain references.
 529              // We want to break references so that the cache cannot be modified outside of itself.
 530              // Call the function to unreference it (in the best way possible).
 531              $data = $this->unref($data);
 532          }
 533  
 534          if ($usestaticacceleration) {
 535              $this->static_acceleration_set($key, $data);
 536          }
 537  
 538          if ($this->has_a_ttl() && !$this->store_supports_native_ttl()) {
 539              $data = new cache_ttl_wrapper($data, $this->definition->get_ttl());
 540          }
 541          $parsedkey = $this->parse_key($key);
 542  
 543          return $this->store->set($parsedkey, $data);
 544      }
 545  
 546      /**
 547       * Removes references where required.
 548       *
 549       * @param stdClass|array $data
 550       * @return mixed What ever was put in but without any references.
 551       */
 552      protected function unref($data) {
 553          if ($this->definition->uses_simple_data()) {
 554              return $data;
 555          }
 556          // Check if it requires serialisation in order to produce a reference free copy.
 557          if ($this->requires_serialisation($data)) {
 558              // Damn, its going to have to be serialise.
 559              $data = serialize($data);
 560              // We unserialise immediately so that we don't have to do it every time on get.
 561              $data = unserialize($data);
 562          } else if (!is_scalar($data)) {
 563              // Its safe to clone, lets do it, its going to beat the pants of serialisation.
 564              $data = $this->deep_clone($data);
 565          }
 566          return $data;
 567      }
 568  
 569      /**
 570       * Checks to see if a var requires serialisation.
 571       *
 572       * @param mixed $value The value to check.
 573       * @param int $depth Used to ensure we don't enter an endless loop (think recursion).
 574       * @return bool Returns true if the value is going to require serialisation in order to ensure a reference free copy
 575       *      or false if its safe to clone.
 576       */
 577      protected function requires_serialisation($value, $depth = 1) {
 578          if (is_scalar($value)) {
 579              return false;
 580          } else if (is_array($value) || $value instanceof stdClass || $value instanceof Traversable) {
 581              if ($depth > 5) {
 582                  // Skrew it, mega-deep object, developer you suck, we're just going to serialise.
 583                  return true;
 584              }
 585              foreach ($value as $key => $subvalue) {
 586                  if ($this->requires_serialisation($subvalue, $depth++)) {
 587                      return true;
 588                  }
 589              }
 590          }
 591          // Its not scalar, array, or stdClass so we'll need to serialise.
 592          return true;
 593      }
 594  
 595      /**
 596       * Creates a reference free clone of the given value.
 597       *
 598       * @param mixed $value
 599       * @return mixed
 600       */
 601      protected function deep_clone($value) {
 602          if (is_object($value)) {
 603              // Objects must be cloned to begin with.
 604              $value = clone $value;
 605          }
 606          if (is_array($value) || is_object($value)) {
 607              foreach ($value as $key => $subvalue) {
 608                  $value[$key] = $this->deep_clone($subvalue);
 609              }
 610          }
 611          return $value;
 612      }
 613  
 614      /**
 615       * Sends several key => value pairs to the cache.
 616       *
 617       * Using this function comes with potential performance implications.
 618       * Not all cache stores will support get_many/set_many operations and in order to replicate this functionality will call
 619       * the equivalent singular method for each item provided.
 620       * This should not deter you from using this function as there is a performance benefit in situations where the cache store
 621       * does support it, but you should be aware of this fact.
 622       *
 623       * <code>
 624       * // This code will add four entries to the cache, one for each url.
 625       * $cache->set_many(array(
 626       *     'main' => 'http://moodle.org',
 627       *     'docs' => 'http://docs.moodle.org',
 628       *     'tracker' => 'http://tracker.moodle.org',
 629       *     'qa' => ''http://qa.moodle.net'
 630       * ));
 631       * </code>
 632       *
 633       * @param array $keyvaluearray An array of key => value pairs to send to the cache.
 634       * @return int The number of items successfully set. It is up to the developer to check this matches the number of items.
 635       *      ... if they care that is.
 636       */
 637      public function set_many(array $keyvaluearray) {
 638          if ($this->loader !== false) {
 639              // We have a loader available set it there as well.
 640              // We have to let the loader do its own parsing of data as it may be unique.
 641              $this->loader->set_many($keyvaluearray);
 642          }
 643          $data = array();
 644          $simulatettl = $this->has_a_ttl() && !$this->store_supports_native_ttl();
 645          $usestaticaccelerationarray = $this->use_static_acceleration();
 646          $needsdereferencing = !$this->store->supports_dereferencing_objects();
 647          foreach ($keyvaluearray as $key => $value) {
 648              if (is_object($value) && $value instanceof cacheable_object) {
 649                  $value = new cache_cached_object($value);
 650              } else if ($needsdereferencing && !is_scalar($value)) {
 651                  // If data is an object it will be a reference.
 652                  // If data is an array if may contain references.
 653                  // We want to break references so that the cache cannot be modified outside of itself.
 654                  // Call the function to unreference it (in the best way possible).
 655                  $value = $this->unref($value);
 656              }
 657              if ($usestaticaccelerationarray) {
 658                  $this->static_acceleration_set($key, $value);
 659              }
 660              if ($simulatettl) {
 661                  $value = new cache_ttl_wrapper($value, $this->definition->get_ttl());
 662              }
 663              $data[$key] = array(
 664                  'key' => $this->parse_key($key),
 665                  'value' => $value
 666              );
 667          }
 668          $successfullyset = $this->store->set_many($data);
 669          if ($this->perfdebug && $successfullyset) {
 670              cache_helper::record_cache_set($this->storetype, $this->definition, $successfullyset);
 671          }
 672          return $successfullyset;
 673      }
 674  
 675      /**
 676       * Test is a cache has a key.
 677       *
 678       * The use of the has methods is strongly discouraged. In a high load environment the cache may well change between the
 679       * test and any subsequent action (get, set, delete etc).
 680       * Instead it is recommended to write your code in such a way they it performs the following steps:
 681       * <ol>
 682       * <li>Attempt to retrieve the information.</li>
 683       * <li>Generate the information.</li>
 684       * <li>Attempt to set the information</li>
 685       * </ol>
 686       *
 687       * Its also worth mentioning that not all stores support key tests.
 688       * For stores that don't support key tests this functionality is mimicked by using the equivalent get method.
 689       * Just one more reason you should not use these methods unless you have a very good reason to do so.
 690       *
 691       * @param string|int $key
 692       * @param bool $tryloadifpossible If set to true, the cache doesn't contain the key, and there is another cache loader or
 693       *      data source then the code will try load the key value from the next item in the chain.
 694       * @return bool True if the cache has the requested key, false otherwise.
 695       */
 696      public function has($key, $tryloadifpossible = false) {
 697          if ($this->static_acceleration_has($key)) {
 698              // Hoorah, that was easy. It exists in the static acceleration array so we definitely have it.
 699              return true;
 700          }
 701          $parsedkey = $this->parse_key($key);
 702  
 703          if ($this->has_a_ttl() && !$this->store_supports_native_ttl()) {
 704              // The data has a TTL and the store doesn't support it natively.
 705              // We must fetch the data and expect a ttl wrapper.
 706              $data = $this->store->get($parsedkey);
 707              $has = ($data instanceof cache_ttl_wrapper && !$data->has_expired());
 708          } else if (!$this->store_supports_key_awareness()) {
 709              // The store doesn't support key awareness, get the data and check it manually... puke.
 710              // Either no TTL is set of the store supports its handling natively.
 711              $data = $this->store->get($parsedkey);
 712              $has = ($data !== false);
 713          } else {
 714              // The store supports key awareness, this is easy!
 715              // Either no TTL is set of the store supports its handling natively.
 716              $has = $this->store->has($parsedkey);
 717          }
 718          if (!$has && $tryloadifpossible) {
 719              if ($this->loader !== false) {
 720                  $result = $this->loader->get($parsedkey);
 721              } else if ($this->datasource !== null) {
 722                  $result = $this->datasource->load_for_cache($key);
 723              }
 724              $has = ($result !== null);
 725              if ($has) {
 726                  $this->set($key, $result);
 727              }
 728          }
 729          return $has;
 730      }
 731  
 732      /**
 733       * Test is a cache has all of the given keys.
 734       *
 735       * It is strongly recommended to avoid the use of this function if not absolutely required.
 736       * In a high load environment the cache may well change between the test and any subsequent action (get, set, delete etc).
 737       *
 738       * Its also worth mentioning that not all stores support key tests.
 739       * For stores that don't support key tests this functionality is mimicked by using the equivalent get method.
 740       * Just one more reason you should not use these methods unless you have a very good reason to do so.
 741       *
 742       * @param array $keys
 743       * @return bool True if the cache has all of the given keys, false otherwise.
 744       */
 745      public function has_all(array $keys) {
 746          if (($this->has_a_ttl() && !$this->store_supports_native_ttl()) || !$this->store_supports_key_awareness()) {
 747              foreach ($keys as $key) {
 748                  if (!$this->has($key)) {
 749                      return false;
 750                  }
 751              }
 752              return true;
 753          }
 754          $parsedkeys = array_map(array($this, 'parse_key'), $keys);
 755          return $this->store->has_all($parsedkeys);
 756      }
 757  
 758      /**
 759       * Test if a cache has at least one of the given keys.
 760       *
 761       * It is strongly recommended to avoid the use of this function if not absolutely required.
 762       * In a high load environment the cache may well change between the test and any subsequent action (get, set, delete etc).
 763       *
 764       * Its also worth mentioning that not all stores support key tests.
 765       * For stores that don't support key tests this functionality is mimicked by using the equivalent get method.
 766       * Just one more reason you should not use these methods unless you have a very good reason to do so.
 767       *
 768       * @param array $keys
 769       * @return bool True if the cache has at least one of the given keys
 770       */
 771      public function has_any(array $keys) {
 772          if (($this->has_a_ttl() && !$this->store_supports_native_ttl()) || !$this->store_supports_key_awareness()) {
 773              foreach ($keys as $key) {
 774                  if ($this->has($key)) {
 775                      return true;
 776                  }
 777              }
 778              return false;
 779          }
 780  
 781          if ($this->use_static_acceleration()) {
 782              foreach ($keys as $id => $key) {
 783                  if ($this->static_acceleration_has($key)) {
 784                      return true;
 785                  }
 786              }
 787          }
 788          $parsedkeys = array_map(array($this, 'parse_key'), $keys);
 789          return $this->store->has_any($parsedkeys);
 790      }
 791  
 792      /**
 793       * Delete the given key from the cache.
 794       *
 795       * @param string|int $key The key to delete.
 796       * @param bool $recurse When set to true the key will also be deleted from all stacked cache loaders and their stores.
 797       *     This happens by default and ensure that all the caches are consistent. It is NOT recommended to change this.
 798       * @return bool True of success, false otherwise.
 799       */
 800      public function delete($key, $recurse = true) {
 801          $this->static_acceleration_delete($key);
 802          if ($recurse && $this->loader !== false) {
 803              // Delete from the bottom of the stack first.
 804              $this->loader->delete($key, $recurse);
 805          }
 806          $parsedkey = $this->parse_key($key);
 807          return $this->store->delete($parsedkey);
 808      }
 809  
 810      /**
 811       * Delete all of the given keys from the cache.
 812       *
 813       * @param array $keys The key to delete.
 814       * @param bool $recurse When set to true the key will also be deleted from all stacked cache loaders and their stores.
 815       *     This happens by default and ensure that all the caches are consistent. It is NOT recommended to change this.
 816       * @return int The number of items successfully deleted.
 817       */
 818      public function delete_many(array $keys, $recurse = true) {
 819          if ($this->use_static_acceleration()) {
 820              foreach ($keys as $key) {
 821                  $this->static_acceleration_delete($key);
 822              }
 823          }
 824          if ($recurse && $this->loader !== false) {
 825              // Delete from the bottom of the stack first.
 826              $this->loader->delete_many($keys, $recurse);
 827          }
 828          $parsedkeys = array_map(array($this, 'parse_key'), $keys);
 829          return $this->store->delete_many($parsedkeys);
 830      }
 831  
 832      /**
 833       * Purges the cache store, and loader if there is one.
 834       *
 835       * @return bool True on success, false otherwise
 836       */
 837      public function purge() {
 838          // 1. Purge the static acceleration array.
 839          $this->staticaccelerationarray = array();
 840          if ($this->staticaccelerationsize !== false) {
 841              $this->staticaccelerationkeys = array();
 842              $this->staticaccelerationcount = 0;
 843          }
 844          // 2. Purge the store.
 845          $this->store->purge();
 846          // 3. Optionally pruge any stacked loaders.
 847          if ($this->loader) {
 848              $this->loader->purge();
 849          }
 850          return true;
 851      }
 852  
 853      /**
 854       * Parses the key turning it into a string (or array is required) suitable to be passed to the cache store.
 855       *
 856       * @param string|int $key As passed to get|set|delete etc.
 857       * @return string|array String unless the store supports multi-identifiers in which case an array if returned.
 858       */
 859      protected function parse_key($key) {
 860          // First up if the store supports multiple keys we'll go with that.
 861          if ($this->store->supports_multiple_identifiers()) {
 862              $result = $this->definition->generate_multi_key_parts();
 863              $result['key'] = $key;
 864              return $result;
 865          }
 866          // If not we need to generate a hash and to for that we use the cache_helper.
 867          return cache_helper::hash_key($key, $this->definition);
 868      }
 869  
 870      /**
 871       * Returns true if the cache is making use of a ttl.
 872       * @return bool
 873       */
 874      protected function has_a_ttl() {
 875          return $this->hasattl;
 876      }
 877  
 878      /**
 879       * Returns true if the cache store supports native ttl.
 880       * @return bool
 881       */
 882      protected function store_supports_native_ttl() {
 883          if ($this->supportsnativettl === null) {
 884              $this->supportsnativettl = ($this->store->supports_native_ttl());
 885          }
 886          return $this->supportsnativettl;
 887      }
 888  
 889      /**
 890       * Returns the cache definition.
 891       *
 892       * @return cache_definition
 893       */
 894      protected function get_definition() {
 895          return $this->definition;
 896      }
 897  
 898      /**
 899       * Returns the cache store
 900       *
 901       * @return cache_store
 902       */
 903      protected function get_store() {
 904          return $this->store;
 905      }
 906  
 907      /**
 908       * Returns the loader associated with this instance.
 909       *
 910       * @since Moodle 2.4.4
 911       * @return cache|false
 912       */
 913      protected function get_loader() {
 914          return $this->loader;
 915      }
 916  
 917      /**
 918       * Returns the data source associated with this cache.
 919       *
 920       * @since Moodle 2.4.4
 921       * @return cache_data_source|false
 922       */
 923      protected function get_datasource() {
 924          return $this->datasource;
 925      }
 926  
 927      /**
 928       * Returns true if the store supports key awareness.
 929       *
 930       * @return bool
 931       */
 932      protected function store_supports_key_awareness() {
 933          if ($this->supportskeyawareness === null) {
 934              $this->supportskeyawareness = ($this->store instanceof cache_is_key_aware);
 935          }
 936          return $this->supportskeyawareness;
 937      }
 938  
 939      /**
 940       * Returns true if the store natively supports locking.
 941       *
 942       * @return bool
 943       */
 944      protected function store_supports_native_locking() {
 945          if ($this->nativelocking === null) {
 946              $this->nativelocking = ($this->store instanceof cache_is_lockable);
 947          }
 948          return $this->nativelocking;
 949      }
 950  
 951      /**
 952       * @deprecated since 2.6
 953       * @see cache::use_static_acceleration()
 954       */
 955      protected function is_using_persist_cache() {
 956          throw new coding_exception('cache::is_using_persist_cache() can not be used anymore.' .
 957              ' Please use cache::use_static_acceleration() instead.');
 958      }
 959  
 960      /**
 961       * Returns true if this cache is making use of the static acceleration array.
 962       *
 963       * @return bool
 964       */
 965      protected function use_static_acceleration() {
 966          return $this->staticacceleration;
 967      }
 968  
 969      /**
 970       * @see cache::static_acceleration_has
 971       * @deprecated since 2.6
 972       */
 973      protected function is_in_persist_cache() {
 974          throw new coding_exception('cache::is_in_persist_cache() can not be used anymore.' .
 975              ' Please use cache::static_acceleration_has() instead.');
 976      }
 977  
 978      /**
 979       * Returns true if the requested key exists within the static acceleration array.
 980       *
 981       * @param string $key The parsed key
 982       * @return bool
 983       */
 984      protected function static_acceleration_has($key) {
 985          // This could be written as a single line, however it has been split because the ttl check is faster than the instanceof
 986          // and has_expired calls.
 987          if (!$this->staticacceleration || !isset($this->staticaccelerationarray[$key])) {
 988              return false;
 989          }
 990          return true;
 991      }
 992  
 993      /**
 994       * @deprecated since 2.6
 995       * @see cache::static_acceleration_get
 996       */
 997      protected function get_from_persist_cache() {
 998          throw new coding_exception('cache::get_from_persist_cache() can not be used anymore.' .
 999              ' Please use cache::static_acceleration_get() instead.');
1000      }
1001  
1002      /**
1003       * Returns the item from the static acceleration array if it exists there.
1004       *
1005       * @param string $key The parsed key
1006       * @return mixed|false Dereferenced data from the static acceleration array or false if it wasn't there.
1007       */
1008      protected function static_acceleration_get($key) {
1009          if (!$this->staticacceleration || !isset($this->staticaccelerationarray[$key])) {
1010              $result = false;
1011          } else {
1012              $data = $this->staticaccelerationarray[$key]['data'];
1013  
1014              if ($data instanceof cache_cached_object) {
1015                  $result = $data->restore_object();
1016              } else if ($this->staticaccelerationarray[$key]['serialized']) {
1017                  $result = unserialize($data);
1018              } else {
1019                  $result = $data;
1020              }
1021          }
1022          if ($result) {
1023              if ($this->perfdebug) {
1024                  cache_helper::record_cache_hit('** static acceleration **', $this->definition);
1025              }
1026              if ($this->staticaccelerationsize > 1 && $this->staticaccelerationcount > 1) {
1027                  // Check to see if this is the last item on the static acceleration keys array.
1028                  if (end($this->staticaccelerationkeys) !== $key) {
1029                      // It isn't the last item.
1030                      // Move the item to the end of the array so that it is last to be removed.
1031                      unset($this->staticaccelerationkeys[$key]);
1032                      $this->staticaccelerationkeys[$key] = $key;
1033                  }
1034              }
1035              return $result;
1036          } else {
1037              if ($this->perfdebug) {
1038                  cache_helper::record_cache_miss('** static acceleration **', $this->definition);
1039              }
1040              return false;
1041          }
1042      }
1043  
1044      /**
1045       * @deprecated since 2.6
1046       * @see cache::static_acceleration_set
1047       */
1048      protected function set_in_persist_cache() {
1049          throw new coding_exception('cache::set_in_persist_cache() can not be used anymore.' .
1050              ' Please use cache::static_acceleration_set() instead.');
1051      }
1052  
1053      /**
1054       * Sets a key value pair into the static acceleration array.
1055       *
1056       * @param string $key The parsed key
1057       * @param mixed $data
1058       * @return bool
1059       */
1060      protected function static_acceleration_set($key, $data) {
1061          if ($this->staticaccelerationsize !== false && isset($this->staticaccelerationkeys[$key])) {
1062              $this->staticaccelerationcount--;
1063              unset($this->staticaccelerationkeys[$key]);
1064          }
1065  
1066          // We serialize anything that's not;
1067          // 1. A known scalar safe value.
1068          // 2. A definition that says it's simpledata.  We trust it that it doesn't contain dangerous references.
1069          // 3. An object that handles dereferencing by itself.
1070          if (is_scalar($data) || $this->definition->uses_simple_data()
1071                  || $data instanceof cache_cached_object) {
1072              $this->staticaccelerationarray[$key]['data'] = $data;
1073              $this->staticaccelerationarray[$key]['serialized'] = false;
1074          } else {
1075              $this->staticaccelerationarray[$key]['data'] = serialize($data);
1076              $this->staticaccelerationarray[$key]['serialized'] = true;
1077          }
1078          if ($this->staticaccelerationsize !== false) {
1079              $this->staticaccelerationcount++;
1080              $this->staticaccelerationkeys[$key] = $key;
1081              if ($this->staticaccelerationcount > $this->staticaccelerationsize) {
1082                  $dropkey = array_shift($this->staticaccelerationkeys);
1083                  unset($this->staticaccelerationarray[$dropkey]);
1084                  $this->staticaccelerationcount--;
1085              }
1086          }
1087          return true;
1088      }
1089  
1090      /**
1091       * @deprecated since 2.6
1092       * @see cache::static_acceleration_delete()
1093       */
1094      protected function delete_from_persist_cache() {
1095          throw new coding_exception('cache::delete_from_persist_cache() can not be used anymore.' .
1096              ' Please use cache::static_acceleration_delete() instead.');
1097      }
1098  
1099      /**
1100       * Deletes an item from the static acceleration array.
1101       *
1102       * @param string|int $key As given to get|set|delete
1103       * @return bool True on success, false otherwise.
1104       */
1105      protected function static_acceleration_delete($key) {
1106          unset($this->staticaccelerationarray[$key]);
1107          if ($this->staticaccelerationsize !== false && isset($this->staticaccelerationkeys[$key])) {
1108              unset($this->staticaccelerationkeys[$key]);
1109              $this->staticaccelerationcount--;
1110          }
1111          return true;
1112      }
1113  
1114      /**
1115       * Returns the timestamp from the first request for the time from the cache API.
1116       *
1117       * This stamp needs to be used for all ttl and time based operations to ensure that we don't end up with
1118       * timing issues.
1119       *
1120       * @return int
1121       */
1122      public static function now() {
1123          if (self::$now === null) {
1124              self::$now = time();
1125          }
1126          return self::$now;
1127      }
1128  }
1129  
1130  /**
1131   * An application cache.
1132   *
1133   * This class is used for application caches returned by the cache::make methods.
1134   * On top of the standard functionality it also allows locking to be required and or manually operated.
1135   *
1136   * This cache class should never be interacted with directly. Instead you should always use the cache::make methods.
1137   * It is technically possible to call those methods through this class however there is no guarantee that you will get an
1138   * instance of this class back again.
1139   *
1140   * @internal don't use me directly.
1141   *
1142   * @package    core
1143   * @category   cache
1144   * @copyright  2012 Sam Hemelryk
1145   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1146   */
1147  class cache_application extends cache implements cache_loader_with_locking {
1148  
1149      /**
1150       * Lock identifier.
1151       * This is used to ensure the lock belongs to the cache instance + definition + user.
1152       * @var string
1153       */
1154      protected $lockidentifier;
1155  
1156      /**
1157       * Gets set to true if the cache's primary store natively supports locking.
1158       * If it does then we use that, otherwise we need to instantiate a second store to use for locking.
1159       * @var cache_store
1160       */
1161      protected $nativelocking = null;
1162  
1163      /**
1164       * Gets set to true if the cache is going to be using locking.
1165       * This isn't a requirement, it doesn't need to use locking (most won't) and this bool is used to quickly check things.
1166       * If required then locking will be forced for the get|set|delete operation.
1167       * @var bool
1168       */
1169      protected $requirelocking = false;
1170  
1171      /**
1172       * Gets set to true if the cache must use read locking (get|has).
1173       * @var bool
1174       */
1175      protected $requirelockingread = false;
1176  
1177      /**
1178       * Gets set to true if the cache must use write locking (set|delete)
1179       * @var bool
1180       */
1181      protected $requirelockingwrite = false;
1182  
1183      /**
1184       * Gets set to a cache_store to use for locking if the caches primary store doesn't support locking natively.
1185       * @var cache_lock_interface
1186       */
1187      protected $cachelockinstance;
1188  
1189      /**
1190       * Overrides the cache construct method.
1191       *
1192       * You should not call this method from your code, instead you should use the cache::make methods.
1193       *
1194       * @param cache_definition $definition
1195       * @param cache_store $store
1196       * @param cache_loader|cache_data_source $loader
1197       */
1198      public function __construct(cache_definition $definition, cache_store $store, $loader = null) {
1199          parent::__construct($definition, $store, $loader);
1200          $this->nativelocking = $this->store_supports_native_locking();
1201          if ($definition->require_locking()) {
1202              $this->requirelocking = true;
1203              $this->requirelockingread = $definition->require_locking_read();
1204              $this->requirelockingwrite = $definition->require_locking_write();
1205          }
1206  
1207          if ($definition->has_invalidation_events()) {
1208              $lastinvalidation = $this->get('lastinvalidation');
1209              if ($lastinvalidation === false) {
1210                  // This is a new session, there won't be anything to invalidate. Set the time of the last invalidation and
1211                  // move on.
1212                  $this->set('lastinvalidation', cache::now());
1213                  return;
1214              } else if ($lastinvalidation == cache::now()) {
1215                  // We've already invalidated during this request.
1216                  return;
1217              }
1218  
1219              // Get the event invalidation cache.
1220              $cache = cache::make('core', 'eventinvalidation');
1221              $events = $cache->get_many($definition->get_invalidation_events());
1222              $todelete = array();
1223              $purgeall = false;
1224              // Iterate the returned data for the events.
1225              foreach ($events as $event => $keys) {
1226                  if ($keys === false) {
1227                      // No data to be invalidated yet.
1228                      continue;
1229                  }
1230                  // Look at each key and check the timestamp.
1231                  foreach ($keys as $key => $timestamp) {
1232                      // If the timestamp of the event is more than or equal to the last invalidation (happened between the last
1233                      // invalidation and now)then we need to invaliate the key.
1234                      if ($timestamp >= $lastinvalidation) {
1235                          if ($key === 'purged') {
1236                              $purgeall = true;
1237                              break;
1238                          } else {
1239                              $todelete[] = $key;
1240                          }
1241                      }
1242                  }
1243              }
1244              if ($purgeall) {
1245                  $this->purge();
1246              } else if (!empty($todelete)) {
1247                  $todelete = array_unique($todelete);
1248                  $this->delete_many($todelete);
1249              }
1250              // Set the time of the last invalidation.
1251              if ($purgeall || !empty($todelete)) {
1252                  $this->set('lastinvalidation', cache::now());
1253              }
1254          }
1255      }
1256  
1257      /**
1258       * Returns the identifier to use
1259       *
1260       * @staticvar int $instances Counts the number of instances. Used as part of the lock identifier.
1261       * @return string
1262       */
1263      public function get_identifier() {
1264          static $instances = 0;
1265          if ($this->lockidentifier === null) {
1266              $this->lockidentifier = md5(
1267                  $this->get_definition()->generate_definition_hash() .
1268                  sesskey() .
1269                  $instances++ .
1270                  'cache_application'
1271              );
1272          }
1273          return $this->lockidentifier;
1274      }
1275  
1276      /**
1277       * Fixes the instance up after a clone.
1278       */
1279      public function __clone() {
1280          // Force a new idenfitier.
1281          $this->lockidentifier = null;
1282      }
1283  
1284      /**
1285       * Acquires a lock on the given key.
1286       *
1287       * This is done automatically if the definition requires it.
1288       * It is recommended to use a definition if you want to have locking although it is possible to do locking without having
1289       * it required by the definition.
1290       * The problem with such an approach is that you cannot ensure that code will consistently use locking. You will need to
1291       * rely on the integrators review skills.
1292       *
1293       * @param string|int $key The key as given to get|set|delete
1294       * @return bool Returns true if the lock could be acquired, false otherwise.
1295       */
1296      public function acquire_lock($key) {
1297          $key = $this->parse_key($key);
1298          if ($this->nativelocking) {
1299              return $this->get_store()->acquire_lock($key, $this->get_identifier());
1300          } else {
1301              $this->ensure_cachelock_available();
1302              return $this->cachelockinstance->lock($key, $this->get_identifier());
1303          }
1304      }
1305  
1306      /**
1307       * Checks if this cache has a lock on the given key.
1308       *
1309       * @param string|int $key The key as given to get|set|delete
1310       * @return bool|null Returns true if there is a lock and this cache has it, null if no one has a lock on that key, false if
1311       *      someone else has the lock.
1312       */
1313      public function check_lock_state($key) {
1314          $key = $this->parse_key($key);
1315          if ($this->nativelocking) {
1316              return $this->get_store()->check_lock_state($key, $this->get_identifier());
1317          } else {
1318              $this->ensure_cachelock_available();
1319              return $this->cachelockinstance->check_state($key, $this->get_identifier());
1320          }
1321      }
1322  
1323      /**
1324       * Releases the lock this cache has on the given key
1325       *
1326       * @param string|int $key
1327       * @return bool True if the operation succeeded, false otherwise.
1328       */
1329      public function release_lock($key) {
1330          $key = $this->parse_key($key);
1331          if ($this->nativelocking) {
1332              return $this->get_store()->release_lock($key, $this->get_identifier());
1333          } else {
1334              $this->ensure_cachelock_available();
1335              return $this->cachelockinstance->unlock($key, $this->get_identifier());
1336          }
1337      }
1338  
1339      /**
1340       * Ensure that the dedicated lock store is ready to go.
1341       *
1342       * This should only happen if the cache store doesn't natively support it.
1343       */
1344      protected function ensure_cachelock_available() {
1345          if ($this->cachelockinstance === null) {
1346              $this->cachelockinstance = cache_helper::get_cachelock_for_store($this->get_store());
1347          }
1348      }
1349  
1350      /**
1351       * Sends a key => value pair to the cache.
1352       *
1353       * <code>
1354       * // This code will add four entries to the cache, one for each url.
1355       * $cache->set('main', 'http://moodle.org');
1356       * $cache->set('docs', 'http://docs.moodle.org');
1357       * $cache->set('tracker', 'http://tracker.moodle.org');
1358       * $cache->set('qa', 'http://qa.moodle.net');
1359       * </code>
1360       *
1361       * @param string|int $key The key for the data being requested.
1362       * @param mixed $data The data to set against the key.
1363       * @return bool True on success, false otherwise.
1364       */
1365      public function set($key, $data) {
1366          if ($this->requirelockingwrite && !$this->acquire_lock($key)) {
1367              return false;
1368          }
1369          $result = parent::set($key, $data);
1370          if ($this->requirelockingwrite && !$this->release_lock($key)) {
1371              debugging('Failed to release cache lock on set operation... this should not happen.', DEBUG_DEVELOPER);
1372          }
1373          return $result;
1374      }
1375  
1376      /**
1377       * Sends several key => value pairs to the cache.
1378       *
1379       * Using this function comes with potential performance implications.
1380       * Not all cache stores will support get_many/set_many operations and in order to replicate this functionality will call
1381       * the equivalent singular method for each item provided.
1382       * This should not deter you from using this function as there is a performance benefit in situations where the cache store
1383       * does support it, but you should be aware of this fact.
1384       *
1385       * <code>
1386       * // This code will add four entries to the cache, one for each url.
1387       * $cache->set_many(array(
1388       *     'main' => 'http://moodle.org',
1389       *     'docs' => 'http://docs.moodle.org',
1390       *     'tracker' => 'http://tracker.moodle.org',
1391       *     'qa' => ''http://qa.moodle.net'
1392       * ));
1393       * </code>
1394       *
1395       * @param array $keyvaluearray An array of key => value pairs to send to the cache.
1396       * @return int The number of items successfully set. It is up to the developer to check this matches the number of items.
1397       *      ... if they care that is.
1398       */
1399      public function set_many(array $keyvaluearray) {
1400          if ($this->requirelockingwrite) {
1401              $locks = array();
1402              foreach ($keyvaluearray as $id => $pair) {
1403                  $key = $pair['key'];
1404                  if ($this->acquire_lock($key)) {
1405                      $locks[] = $key;
1406                  } else {
1407                      unset($keyvaluearray[$id]);
1408                  }
1409              }
1410          }
1411          $result = parent::set_many($keyvaluearray);
1412          if ($this->requirelockingwrite) {
1413              foreach ($locks as $key) {
1414                  if ($this->release_lock($key)) {
1415                      debugging('Failed to release cache lock on set_many operation... this should not happen.', DEBUG_DEVELOPER);
1416                  }
1417              }
1418          }
1419          return $result;
1420      }
1421  
1422      /**
1423       * Retrieves the value for the given key from the cache.
1424       *
1425       * @param string|int $key The key for the data being requested.
1426       * @param int $strictness One of IGNORE_MISSING | MUST_EXIST
1427       * @return mixed|false The data from the cache or false if the key did not exist within the cache.
1428       */
1429      public function get($key, $strictness = IGNORE_MISSING) {
1430          if ($this->requirelockingread && $this->check_lock_state($key) === false) {
1431              // Read locking required and someone else has the read lock.
1432              return false;
1433          }
1434          return parent::get($key, $strictness);
1435      }
1436  
1437      /**
1438       * Retrieves an array of values for an array of keys.
1439       *
1440       * Using this function comes with potential performance implications.
1441       * Not all cache stores will support get_many/set_many operations and in order to replicate this functionality will call
1442       * the equivalent singular method for each item provided.
1443       * This should not deter you from using this function as there is a performance benefit in situations where the cache store
1444       * does support it, but you should be aware of this fact.
1445       *
1446       * @param array $keys The keys of the data being requested.
1447       * @param int $strictness One of IGNORE_MISSING or MUST_EXIST.
1448       * @return array An array of key value pairs for the items that could be retrieved from the cache.
1449       *      If MUST_EXIST was used and not all keys existed within the cache then an exception will be thrown.
1450       *      Otherwise any key that did not exist will have a data value of false within the results.
1451       * @throws coding_exception
1452       */
1453      public function get_many(array $keys, $strictness = IGNORE_MISSING) {
1454          if ($this->requirelockingread) {
1455              foreach ($keys as $id => $key) {
1456                  $lock =$this->acquire_lock($key);
1457                  if (!$lock) {
1458                      if ($strictness === MUST_EXIST) {
1459                          throw new coding_exception('Could not acquire read locks for all of the items being requested.');
1460                      } else {
1461                          // Can't return this as we couldn't get a read lock.
1462                          unset($keys[$id]);
1463                      }
1464                  }
1465  
1466              }
1467          }
1468          return parent::get_many($keys, $strictness);
1469      }
1470  
1471      /**
1472       * Delete the given key from the cache.
1473       *
1474       * @param string|int $key The key to delete.
1475       * @param bool $recurse When set to true the key will also be deleted from all stacked cache loaders and their stores.
1476       *     This happens by default and ensure that all the caches are consistent. It is NOT recommended to change this.
1477       * @return bool True of success, false otherwise.
1478       */
1479      public function delete($key, $recurse = true) {
1480          if ($this->requirelockingwrite && !$this->acquire_lock($key)) {
1481              return false;
1482          }
1483          $result = parent::delete($key, $recurse);
1484          if ($this->requirelockingwrite && !$this->release_lock($key)) {
1485              debugging('Failed to release cache lock on delete operation... this should not happen.', DEBUG_DEVELOPER);
1486          }
1487          return $result;
1488      }
1489  
1490      /**
1491       * Delete all of the given keys from the cache.
1492       *
1493       * @param array $keys The key to delete.
1494       * @param bool $recurse When set to true the key will also be deleted from all stacked cache loaders and their stores.
1495       *     This happens by default and ensure that all the caches are consistent. It is NOT recommended to change this.
1496       * @return int The number of items successfully deleted.
1497       */
1498      public function delete_many(array $keys, $recurse = true) {
1499          if ($this->requirelockingwrite) {
1500              $locks = array();
1501              foreach ($keys as $id => $key) {
1502                  if ($this->acquire_lock($key)) {
1503                      $locks[] = $key;
1504                  } else {
1505                      unset($keys[$id]);
1506                  }
1507              }
1508          }
1509          $result = parent::delete_many($keys, $recurse);
1510          if ($this->requirelockingwrite) {
1511              foreach ($locks as $key) {
1512                  if ($this->release_lock($key)) {
1513                      debugging('Failed to release cache lock on delete_many operation... this should not happen.', DEBUG_DEVELOPER);
1514                  }
1515              }
1516          }
1517          return $result;
1518      }
1519  }
1520  
1521  /**
1522   * A session cache.
1523   *
1524   * This class is used for session caches returned by the cache::make methods.
1525   *
1526   * It differs from the application loader in a couple of noteable ways:
1527   *    1. Sessions are always expected to exist.
1528   *       Because of this we don't ever use the static acceleration array.
1529   *    2. Session data for a loader instance (store + definition) is consolidate into a
1530   *       single array for storage within the store.
1531   *       Along with this we embed a lastaccessed time with the data. This way we can
1532   *       check sessions for a last access time.
1533   *    3. Session stores are required to support key searching and must
1534   *       implement cache_is_searchable. This ensures stores used for the cache can be
1535   *       targetted for garbage collection of session data.
1536   *
1537   * This cache class should never be interacted with directly. Instead you should always use the cache::make methods.
1538   * It is technically possible to call those methods through this class however there is no guarantee that you will get an
1539   * instance of this class back again.
1540   *
1541   * @todo we should support locking in the session as well. Should be pretty simple to set up.
1542   *
1543   * @internal don't use me directly.
1544   * @method cache_store|cache_is_searchable get_store() Returns the cache store which must implement both cache_is_searchable.
1545   *
1546   * @package    core
1547   * @category   cache
1548   * @copyright  2012 Sam Hemelryk
1549   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1550   */
1551  class cache_session extends cache {
1552      /**
1553       * The user the session has been established for.
1554       * @var int
1555       */
1556      protected static $loadeduserid = null;
1557  
1558      /**
1559       * The userid this cache is currently using.
1560       * @var int
1561       */
1562      protected $currentuserid = null;
1563  
1564      /**
1565       * The session id we are currently using.
1566       * @var array
1567       */
1568      protected $sessionid = null;
1569  
1570      /**
1571       * The session data for the above session id.
1572       * @var array
1573       */
1574      protected $session = null;
1575  
1576      /**
1577       * Constant used to prefix keys.
1578       */
1579      const KEY_PREFIX = 'sess_';
1580  
1581      /**
1582       * This is the key used to track last access.
1583       */
1584      const LASTACCESS = '__lastaccess__';
1585  
1586      /**
1587       * Override the cache::construct method.
1588       *
1589       * This function gets overriden so that we can process any invalidation events if need be.
1590       * If the definition doesn't have any invalidation events then this occurs exactly as it would for the cache class.
1591       * Otherwise we look at the last invalidation time and then check the invalidation data for events that have occured
1592       * between then now.
1593       *
1594       * You should not call this method from your code, instead you should use the cache::make methods.
1595       *
1596       * @param cache_definition $definition
1597       * @param cache_store $store
1598       * @param cache_loader|cache_data_source $loader
1599       */
1600      public function __construct(cache_definition $definition, cache_store $store, $loader = null) {
1601          // First up copy the loadeduserid to the current user id.
1602          $this->currentuserid = self::$loadeduserid;
1603          parent::__construct($definition, $store, $loader);
1604  
1605          // This will trigger check tracked user. If this gets removed a call to that will need to be added here in its place.
1606          $this->set(self::LASTACCESS, cache::now());
1607  
1608          if ($definition->has_invalidation_events()) {
1609              $lastinvalidation = $this->get('lastsessioninvalidation');
1610              if ($lastinvalidation === false) {
1611                  // This is a new session, there won't be anything to invalidate. Set the time of the last invalidation and
1612                  // move on.
1613                  $this->set('lastsessioninvalidation', cache::now());
1614                  return;
1615              } else if ($lastinvalidation == cache::now()) {
1616                  // We've already invalidated during this request.
1617                  return;
1618              }
1619  
1620              // Get the event invalidation cache.
1621              $cache = cache::make('core', 'eventinvalidation');
1622              $events = $cache->get_many($definition->get_invalidation_events());
1623              $todelete = array();
1624              $purgeall = false;
1625              // Iterate the returned data for the events.
1626              foreach ($events as $event => $keys) {
1627                  if ($keys === false) {
1628                      // No data to be invalidated yet.
1629                      continue;
1630                  }
1631                  // Look at each key and check the timestamp.
1632                  foreach ($keys as $key => $timestamp) {
1633                      // If the timestamp of the event is more than or equal to the last invalidation (happened between the last
1634                      // invalidation and now)then we need to invaliate the key.
1635                      if ($timestamp >= $lastinvalidation) {
1636                          if ($key === 'purged') {
1637                              $purgeall = true;
1638                              break;
1639                          } else {
1640                              $todelete[] = $key;
1641                          }
1642                      }
1643                  }
1644              }
1645              if ($purgeall) {
1646                  $this->purge();
1647              } else if (!empty($todelete)) {
1648                  $todelete = array_unique($todelete);
1649                  $this->delete_many($todelete);
1650              }
1651              // Set the time of the last invalidation.
1652              if ($purgeall || !empty($todelete)) {
1653                  $this->set('lastsessioninvalidation', cache::now());
1654              }
1655          }
1656      }
1657  
1658      /**
1659       * Sets the session id for the loader.
1660       */
1661      protected function set_session_id() {
1662          $this->sessionid = preg_replace('#[^a-zA-Z0-9_]#', '_', session_id());
1663      }
1664  
1665      /**
1666       * Returns the prefix used for all keys.
1667       * @return string
1668       */
1669      protected function get_key_prefix() {
1670          return 'u'.$this->currentuserid.'_'.$this->sessionid;
1671      }
1672  
1673      /**
1674       * Parses the key turning it into a string (or array is required) suitable to be passed to the cache store.
1675       *
1676       * This function is called for every operation that uses keys. For this reason we use this function to also check
1677       * that the current user is the same as the user who last used this cache.
1678       *
1679       * On top of that if prepends the string 'sess_' to the start of all keys. The _ ensures things are easily identifiable.
1680       *
1681       * @param string|int $key As passed to get|set|delete etc.
1682       * @return string|array String unless the store supports multi-identifiers in which case an array if returned.
1683       */
1684      protected function parse_key($key) {
1685          $prefix = $this->get_key_prefix();
1686          if ($key === self::LASTACCESS) {
1687              return $key.$prefix;
1688          }
1689          return $prefix.'_'.parent::parse_key($key);
1690      }
1691  
1692      /**
1693       * Check that this cache instance is tracking the current user.
1694       */
1695      protected function check_tracked_user() {
1696          if (isset($_SESSION['USER']->id) && $_SESSION['USER']->id !== null) {
1697              // Get the id of the current user.
1698              $new = $_SESSION['USER']->id;
1699          } else {
1700              // No user set up yet.
1701              $new = 0;
1702          }
1703          if ($new !== self::$loadeduserid) {
1704              // The current user doesn't match the tracked userid for this request.
1705              if (!is_null(self::$loadeduserid)) {
1706                  // Purge the data we have for the old user.
1707                  // This way we don't bloat the session.
1708                  $this->purge();
1709                  // Update the session id just in case!
1710                  $this->set_session_id();
1711              }
1712              self::$loadeduserid = $new;
1713              $this->currentuserid = $new;
1714          } else if ($new !== $this->currentuserid) {
1715              // The current user matches the loaded user but not the user last used by this cache.
1716              $this->purge_current_user();
1717              $this->currentuserid = $new;
1718              // Update the session id just in case!
1719              $this->set_session_id();
1720          }
1721      }
1722  
1723      /**
1724       * Purges the session cache of all data belonging to the current user.
1725       */
1726      public function purge_current_user() {
1727          $keys = $this->get_store()->find_by_prefix($this->get_key_prefix());
1728          $this->get_store()->delete_many($keys);
1729      }
1730  
1731      /**
1732       * Retrieves the value for the given key from the cache.
1733       *
1734       * @param string|int $key The key for the data being requested.
1735       *      It can be any structure although using a scalar string or int is recommended in the interests of performance.
1736       *      In advanced cases an array may be useful such as in situations requiring the multi-key functionality.
1737       * @param int $strictness One of IGNORE_MISSING | MUST_EXIST
1738       * @return mixed|false The data from the cache or false if the key did not exist within the cache.
1739       * @throws coding_exception
1740       */
1741      public function get($key, $strictness = IGNORE_MISSING) {
1742          // Check the tracked user.
1743          $this->check_tracked_user();
1744          // 2. Parse the key.
1745          $parsedkey = $this->parse_key($key);
1746          // 3. Get it from the store.
1747          $result = $this->get_store()->get($parsedkey);
1748          if ($result !== false) {
1749              if ($result instanceof cache_ttl_wrapper) {
1750                  if ($result->has_expired()) {
1751                      $this->get_store()->delete($parsedkey);
1752                      $result = false;
1753                  } else {
1754                      $result = $result->data;
1755                  }
1756              }
1757              if ($result instanceof cache_cached_object) {
1758                  $result = $result->restore_object();
1759              }
1760          }
1761          // 4. Load if from the loader/datasource if we don't already have it.
1762          if ($result === false) {
1763              if ($this->perfdebug) {
1764                  cache_helper::record_cache_miss($this->storetype, $this->get_definition());
1765              }
1766              if ($this->get_loader() !== false) {
1767                  // We must pass the original (unparsed) key to the next loader in the chain.
1768                  // The next loader will parse the key as it sees fit. It may be parsed differently
1769                  // depending upon the capabilities of the store associated with the loader.
1770                  $result = $this->get_loader()->get($key);
1771              } else if ($this->get_datasource() !== false) {
1772                  $result = $this->get_datasource()->load_for_cache($key);
1773              }
1774              // 5. Set it to the store if we got it from the loader/datasource.
1775              if ($result !== false) {
1776                  $this->set($key, $result);
1777              }
1778          } else if ($this->perfdebug) {
1779              cache_helper::record_cache_hit($this->storetype, $this->get_definition());
1780          }
1781          // 5. Validate strictness.
1782          if ($strictness === MUST_EXIST && $result === false) {
1783              throw new coding_exception('Requested key did not exist in any cache stores and could not be loaded.');
1784          }
1785          // 6. Make sure we don't pass back anything that could be a reference.
1786          //    We don't want people modifying the data in the cache.
1787          if (!$this->get_store()->supports_dereferencing_objects() && !is_scalar($result)) {
1788              // If data is an object it will be a reference.
1789              // If data is an array if may contain references.
1790              // We want to break references so that the cache cannot be modified outside of itself.
1791              // Call the function to unreference it (in the best way possible).
1792              $result = $this->unref($result);
1793          }
1794          return $result;
1795      }
1796  
1797      /**
1798       * Sends a key => value pair to the cache.
1799       *
1800       * <code>
1801       * // This code will add four entries to the cache, one for each url.
1802       * $cache->set('main', 'http://moodle.org');
1803       * $cache->set('docs', 'http://docs.moodle.org');
1804       * $cache->set('tracker', 'http://tracker.moodle.org');
1805       * $cache->set('qa', 'http://qa.moodle.net');
1806       * </code>
1807       *
1808       * @param string|int $key The key for the data being requested.
1809       *      It can be any structure although using a scalar string or int is recommended in the interests of performance.
1810       *      In advanced cases an array may be useful such as in situations requiring the multi-key functionality.
1811       * @param mixed $data The data to set against the key.
1812       * @return bool True on success, false otherwise.
1813       */
1814      public function set($key, $data) {
1815          $this->check_tracked_user();
1816          $loader = $this->get_loader();
1817          if ($loader !== false) {
1818              // We have a loader available set it there as well.
1819              // We have to let the loader do its own parsing of data as it may be unique.
1820              $loader->set($key, $data);
1821          }
1822          if ($this->perfdebug) {
1823              cache_helper::record_cache_set($this->storetype, $this->get_definition());
1824          }
1825          if (is_object($data) && $data instanceof cacheable_object) {
1826              $data = new cache_cached_object($data);
1827          } else if (!$this->get_store()->supports_dereferencing_objects() && !is_scalar($data)) {
1828              // If data is an object it will be a reference.
1829              // If data is an array if may contain references.
1830              // We want to break references so that the cache cannot be modified outside of itself.
1831              // Call the function to unreference it (in the best way possible).
1832              $data = $this->unref($data);
1833          }
1834          // We dont' support native TTL here as we consolidate data for sessions.
1835          if ($this->has_a_ttl() && !$this->store_supports_native_ttl()) {
1836              $data = new cache_ttl_wrapper($data, $this->get_definition()->get_ttl());
1837          }
1838          return $this->get_store()->set($this->parse_key($key), $data);
1839      }
1840  
1841      /**
1842       * Delete the given key from the cache.
1843       *
1844       * @param string|int $key The key to delete.
1845       * @param bool $recurse When set to true the key will also be deleted from all stacked cache loaders and their stores.
1846       *     This happens by default and ensure that all the caches are consistent. It is NOT recommended to change this.
1847       * @return bool True of success, false otherwise.
1848       */
1849      public function delete($key, $recurse = true) {
1850          $parsedkey = $this->parse_key($key);
1851          if ($recurse && $this->get_loader() !== false) {
1852              // Delete from the bottom of the stack first.
1853              $this->get_loader()->delete($key, $recurse);
1854          }
1855          return $this->get_store()->delete($parsedkey);
1856      }
1857  
1858      /**
1859       * Retrieves an array of values for an array of keys.
1860       *
1861       * Using this function comes with potential performance implications.
1862       * Not all cache stores will support get_many/set_many operations and in order to replicate this functionality will call
1863       * the equivalent singular method for each item provided.
1864       * This should not deter you from using this function as there is a performance benefit in situations where the cache store
1865       * does support it, but you should be aware of this fact.
1866       *
1867       * @param array $keys The keys of the data being requested.
1868       *      Each key can be any structure although using a scalar string or int is recommended in the interests of performance.
1869       *      In advanced cases an array may be useful such as in situations requiring the multi-key functionality.
1870       * @param int $strictness One of IGNORE_MISSING or MUST_EXIST.
1871       * @return array An array of key value pairs for the items that could be retrieved from the cache.
1872       *      If MUST_EXIST was used and not all keys existed within the cache then an exception will be thrown.
1873       *      Otherwise any key that did not exist will have a data value of false within the results.
1874       * @throws coding_exception
1875       */
1876      public function get_many(array $keys, $strictness = IGNORE_MISSING) {
1877          $this->check_tracked_user();
1878          $parsedkeys = array();
1879          $keymap = array();
1880          foreach ($keys as $key) {
1881              $parsedkey = $this->parse_key($key);
1882              $parsedkeys[$key] = $parsedkey;
1883              $keymap[$parsedkey] = $key;
1884          }
1885          $result = $this->get_store()->get_many($parsedkeys);
1886          $return = array();
1887          $missingkeys = array();
1888          $hasmissingkeys = false;
1889          foreach ($result as $parsedkey => $value) {
1890              $key = $keymap[$parsedkey];
1891              if ($value instanceof cache_ttl_wrapper) {
1892                  /* @var cache_ttl_wrapper $value */
1893                  if ($value->has_expired()) {
1894                      $this->delete($keymap[$parsedkey]);
1895                      $value = false;
1896                  } else {
1897                      $value = $value->data;
1898                  }
1899              }
1900              if ($value instanceof cache_cached_object) {
1901                  /* @var cache_cached_object $value */
1902                  $value = $value->restore_object();
1903              } else if (!$this->get_store()->supports_dereferencing_objects() && !is_scalar($value)) {
1904                  // If data is an object it will be a reference.
1905                  // If data is an array if may contain references.
1906                  // We want to break references so that the cache cannot be modified outside of itself.
1907                  // Call the function to unreference it (in the best way possible).
1908                  $value = $this->unref($value);
1909              }
1910              $return[$key] = $value;
1911              if ($value === false) {
1912                  $hasmissingkeys = true;
1913                  $missingkeys[$parsedkey] = $key;
1914              }
1915          }
1916          if ($hasmissingkeys) {
1917              // We've got missing keys - we've got to check any loaders or data sources.
1918              $loader = $this->get_loader();
1919              $datasource = $this->get_datasource();
1920              if ($loader !== false) {
1921                  foreach ($loader->get_many($missingkeys) as $key => $value) {
1922                      if ($value !== false) {
1923                          $return[$key] = $value;
1924                          unset($missingkeys[$parsedkeys[$key]]);
1925                      }
1926                  }
1927              }
1928              $hasmissingkeys = count($missingkeys) > 0;
1929              if ($datasource !== false && $hasmissingkeys) {
1930                  // We're still missing keys but we've got a datasource.
1931                  foreach ($datasource->load_many_for_cache($missingkeys) as $key => $value) {
1932                      if ($value !== false) {
1933                          $return[$key] = $value;
1934                          unset($missingkeys[$parsedkeys[$key]]);
1935                      }
1936                  }
1937                  $hasmissingkeys = count($missingkeys) > 0;
1938              }
1939          }
1940          if ($hasmissingkeys && $strictness === MUST_EXIST) {
1941              throw new coding_exception('Requested key did not exist in any cache stores and could not be loaded.');
1942          }
1943          if ($this->perfdebug) {
1944              $hits = 0;
1945              $misses = 0;
1946              foreach ($return as $value) {
1947                  if ($value === false) {
1948                      $misses++;
1949                  } else {
1950                      $hits++;
1951                  }
1952              }
1953              cache_helper::record_cache_hit($this->storetype, $this->get_definition(), $hits);
1954              cache_helper::record_cache_miss($this->storetype, $this->get_definition(), $misses);
1955          }
1956          return $return;
1957  
1958      }
1959  
1960      /**
1961       * Delete all of the given keys from the cache.
1962       *
1963       * @param array $keys The key to delete.
1964       * @param bool $recurse When set to true the key will also be deleted from all stacked cache loaders and their stores.
1965       *     This happens by default and ensure that all the caches are consistent. It is NOT recommended to change this.
1966       * @return int The number of items successfully deleted.
1967       */
1968      public function delete_many(array $keys, $recurse = true) {
1969          $parsedkeys = array_map(array($this, 'parse_key'), $keys);
1970          if ($recurse && $this->get_loader() !== false) {
1971              // Delete from the bottom of the stack first.
1972              $this->get_loader()->delete_many($keys, $recurse);
1973          }
1974          return $this->get_store()->delete_many($parsedkeys);
1975      }
1976  
1977      /**
1978       * Sends several key => value pairs to the cache.
1979       *
1980       * Using this function comes with potential performance implications.
1981       * Not all cache stores will support get_many/set_many operations and in order to replicate this functionality will call
1982       * the equivalent singular method for each item provided.
1983       * This should not deter you from using this function as there is a performance benefit in situations where the cache store
1984       * does support it, but you should be aware of this fact.
1985       *
1986       * <code>
1987       * // This code will add four entries to the cache, one for each url.
1988       * $cache->set_many(array(
1989       *     'main' => 'http://moodle.org',
1990       *     'docs' => 'http://docs.moodle.org',
1991       *     'tracker' => 'http://tracker.moodle.org',
1992       *     'qa' => ''http://qa.moodle.net'
1993       * ));
1994       * </code>
1995       *
1996       * @param array $keyvaluearray An array of key => value pairs to send to the cache.
1997       * @return int The number of items successfully set. It is up to the developer to check this matches the number of items.
1998       *      ... if they care that is.
1999       */
2000      public function set_many(array $keyvaluearray) {
2001          $this->check_tracked_user();
2002          $loader = $this->get_loader();
2003          if ($loader !== false) {
2004              // We have a loader available set it there as well.
2005              // We have to let the loader do its own parsing of data as it may be unique.
2006              $loader->set_many($keyvaluearray);
2007          }
2008          $data = array();
2009          $definitionid = $this->get_definition()->get_ttl();
2010          $simulatettl = $this->has_a_ttl() && !$this->store_supports_native_ttl();
2011          foreach ($keyvaluearray as $key => $value) {
2012              if (is_object($value) && $value instanceof cacheable_object) {
2013                  $value = new cache_cached_object($value);
2014              } else if (!$this->get_store()->supports_dereferencing_objects() && !is_scalar($value)) {
2015                  // If data is an object it will be a reference.
2016                  // If data is an array if may contain references.
2017                  // We want to break references so that the cache cannot be modified outside of itself.
2018                  // Call the function to unreference it (in the best way possible).
2019                  $value = $this->unref($value);
2020              }
2021              if ($simulatettl) {
2022                  $value = new cache_ttl_wrapper($value, $definitionid);
2023              }
2024              $data[$key] = array(
2025                  'key' => $this->parse_key($key),
2026                  'value' => $value
2027              );
2028          }
2029          $successfullyset = $this->get_store()->set_many($data);
2030          if ($this->perfdebug && $successfullyset) {
2031              cache_helper::record_cache_set($this->storetype, $this->get_definition(), $successfullyset);
2032          }
2033          return $successfullyset;
2034      }
2035  
2036      /**
2037       * Purges the cache store, and loader if there is one.
2038       *
2039       * @return bool True on success, false otherwise
2040       */
2041      public function purge() {
2042          $this->get_store()->purge();
2043          if ($this->get_loader()) {
2044              $this->get_loader()->purge();
2045          }
2046          return true;
2047      }
2048  
2049      /**
2050       * Test is a cache has a key.
2051       *
2052       * The use of the has methods is strongly discouraged. In a high load environment the cache may well change between the
2053       * test and any subsequent action (get, set, delete etc).
2054       * Instead it is recommended to write your code in such a way they it performs the following steps:
2055       * <ol>
2056       * <li>Attempt to retrieve the information.</li>
2057       * <li>Generate the information.</li>
2058       * <li>Attempt to set the information</li>
2059       * </ol>
2060       *
2061       * Its also worth mentioning that not all stores support key tests.
2062       * For stores that don't support key tests this functionality is mimicked by using the equivalent get method.
2063       * Just one more reason you should not use these methods unless you have a very good reason to do so.
2064       *
2065       * @param string|int $key
2066       * @param bool $tryloadifpossible If set to true, the cache doesn't contain the key, and there is another cache loader or
2067       *      data source then the code will try load the key value from the next item in the chain.
2068       * @return bool True if the cache has the requested key, false otherwise.
2069       */
2070      public function has($key, $tryloadifpossible = false) {
2071          $this->check_tracked_user();
2072          $parsedkey = $this->parse_key($key);
2073          $store = $this->get_store();
2074          if ($this->has_a_ttl() && !$this->store_supports_native_ttl()) {
2075              // The data has a TTL and the store doesn't support it natively.
2076              // We must fetch the data and expect a ttl wrapper.
2077              $data = $store->get($parsedkey);
2078              $has = ($data instanceof cache_ttl_wrapper && !$data->has_expired());
2079          } else if (!$this->store_supports_key_awareness()) {
2080              // The store doesn't support key awareness, get the data and check it manually... puke.
2081              // Either no TTL is set of the store supports its handling natively.
2082              $data = $store->get($parsedkey);
2083              $has = ($data !== false);
2084          } else {
2085              // The store supports key awareness, this is easy!
2086              // Either no TTL is set of the store supports its handling natively.
2087              /* @var cache_store|cache_is_key_aware $store */
2088              $has = $store->has($parsedkey);
2089          }
2090          if (!$has && $tryloadifpossible) {
2091              $result = null;
2092              if ($this->get_loader() !== false) {
2093                  $result = $this->get_loader()->get($parsedkey);
2094              } else if ($this->get_datasource() !== null) {
2095                  $result = $this->get_datasource()->load_for_cache($key);
2096              }
2097              $has = ($result !== null);
2098              if ($has) {
2099                  $this->set($key, $result);
2100              }
2101          }
2102          return $has;
2103      }
2104  
2105      /**
2106       * Test is a cache has all of the given keys.
2107       *
2108       * It is strongly recommended to avoid the use of this function if not absolutely required.
2109       * In a high load environment the cache may well change between the test and any subsequent action (get, set, delete etc).
2110       *
2111       * Its also worth mentioning that not all stores support key tests.
2112       * For stores that don't support key tests this functionality is mimicked by using the equivalent get method.
2113       * Just one more reason you should not use these methods unless you have a very good reason to do so.
2114       *
2115       * @param array $keys
2116       * @return bool True if the cache has all of the given keys, false otherwise.
2117       */
2118      public function has_all(array $keys) {
2119          $this->check_tracked_user();
2120          if (($this->has_a_ttl() && !$this->store_supports_native_ttl()) || !$this->store_supports_key_awareness()) {
2121              foreach ($keys as $key) {
2122                  if (!$this->has($key)) {
2123                      return false;
2124                  }
2125              }
2126              return true;
2127          }
2128          // The cache must be key aware and if support native ttl if it a ttl is set.
2129          /* @var cache_store|cache_is_key_aware $store */
2130          $store = $this->get_store();
2131          return $store->has_all(array_map(array($this, 'parse_key'), $keys));
2132      }
2133  
2134      /**
2135       * Test if a cache has at least one of the given keys.
2136       *
2137       * It is strongly recommended to avoid the use of this function if not absolutely required.
2138       * In a high load environment the cache may well change between the test and any subsequent action (get, set, delete etc).
2139       *
2140       * Its also worth mentioning that not all stores support key tests.
2141       * For stores that don't support key tests this functionality is mimicked by using the equivalent get method.
2142       * Just one more reason you should not use these methods unless you have a very good reason to do so.
2143       *
2144       * @param array $keys
2145       * @return bool True if the cache has at least one of the given keys
2146       */
2147      public function has_any(array $keys) {
2148          if (($this->has_a_ttl() && !$this->store_supports_native_ttl()) || !$this->store_supports_key_awareness()) {
2149              foreach ($keys as $key) {
2150                  if ($this->has($key)) {
2151                      return true;
2152                  }
2153              }
2154              return false;
2155          }
2156          /* @var cache_store|cache_is_key_aware $store */
2157          $store = $this->get_store();
2158          return $store->has_any(array_map(array($this, 'parse_key'), $keys));
2159      }
2160  
2161      /**
2162       * The session loader never uses static acceleration.
2163       * Instead it stores things in the static $session variable. Shared between all session loaders.
2164       *
2165       * @return bool
2166       */
2167      protected function use_static_acceleration() {
2168          return false;
2169      }
2170  }
2171  
2172  /**
2173   * An request cache.
2174   *
2175   * This class is used for request caches returned by the cache::make methods.
2176   *
2177   * This cache class should never be interacted with directly. Instead you should always use the cache::make methods.
2178   * It is technically possible to call those methods through this class however there is no guarantee that you will get an
2179   * instance of this class back again.
2180   *
2181   * @internal don't use me directly.
2182   *
2183   * @package    core
2184   * @category   cache
2185   * @copyright  2012 Sam Hemelryk
2186   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2187   */
2188  class cache_request extends cache {
2189      // This comment appeases code pre-checker ;) !
2190  }


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1