[ Index ]

PHP Cross Reference of moodle-2.8

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


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