Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.
   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   * The library file for the static cache store.
  19   *
  20   * This file is part of the static cache store, it contains the API for interacting with an instance of the store.
  21   * This is used as a default cache store within the Cache API. It should never be deleted.
  22   *
  23   * @package    cachestore_static
  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 static data store class
  33   *
  34   * @copyright  2012 Sam Hemelryk
  35   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  36   */
  37  abstract class static_data_store extends cache_store {
  38  
  39      /**
  40       * An array for storage.
  41       * @var array
  42       */
  43      private static $staticstore = array();
  44  
  45      /**
  46       * Returns a static store by reference... REFERENCE SUPER IMPORTANT.
  47       *
  48       * @param string $id
  49       * @return array
  50       */
  51      protected static function &register_store_id($id) {
  52          if (!array_key_exists($id, self::$staticstore)) {
  53              self::$staticstore[$id] = array();
  54          }
  55          return self::$staticstore[$id];
  56      }
  57  
  58      /**
  59       * Flushes the store of all values for belonging to the store with the given id.
  60       * @param string $id
  61       */
  62      protected static function flush_store_by_id($id) {
  63          unset(self::$staticstore[$id]);
  64          self::$staticstore[$id] = array();
  65      }
  66  
  67      /**
  68       * Flushes all of the values from all stores.
  69       *
  70       * @copyright  2012 Sam Hemelryk
  71       * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  72       */
  73      protected static function flush_store() {
  74          $ids = array_keys(self::$staticstore);
  75          unset(self::$staticstore);
  76          self::$staticstore = array();
  77          foreach ($ids as $id) {
  78              self::$staticstore[$id] = array();
  79          }
  80      }
  81  }
  82  
  83  /**
  84   * The static store class.
  85   *
  86   * @copyright  2012 Sam Hemelryk
  87   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  88   */
  89  class cachestore_static extends static_data_store implements cache_is_key_aware, cache_is_searchable {
  90  
  91      /**
  92       * The name of the store
  93       * @var store
  94       */
  95      protected $name;
  96  
  97      /**
  98       * The store id (should be unique)
  99       * @var string
 100       */
 101      protected $storeid;
 102  
 103      /**
 104       * The store we use for data.
 105       * @var array
 106       */
 107      protected $store;
 108  
 109      /**
 110       * The maximum size for the store, or false if there isn't one.
 111       * @var bool
 112       */
 113      protected $maxsize = false;
 114  
 115      /**
 116       * Where this cache uses simpledata and we don't need to serialize it.
 117       * @var bool
 118       */
 119      protected $simpledata = false;
 120  
 121      /**
 122       * The number of items currently being stored.
 123       * @var int
 124       */
 125      protected $storecount = 0;
 126  
 127      /**
 128       * igbinary extension available.
 129       * @var bool
 130       */
 131      protected $igbinaryfound = false;
 132  
 133      /**
 134       * Constructs the store instance.
 135       *
 136       * Noting that this function is not an initialisation. It is used to prepare the store for use.
 137       * The store will be initialised when required and will be provided with a cache_definition at that time.
 138       *
 139       * @param string $name
 140       * @param array $configuration
 141       */
 142      public function __construct($name, array $configuration = array()) {
 143          $this->name = $name;
 144      }
 145  
 146      /**
 147       * Returns the supported features as a combined int.
 148       *
 149       * @param array $configuration
 150       * @return int
 151       */
 152      public static function get_supported_features(array $configuration = array()) {
 153          return self::SUPPORTS_DATA_GUARANTEE +
 154                 self::SUPPORTS_NATIVE_TTL +
 155                 self::IS_SEARCHABLE +
 156                 self::SUPPORTS_MULTIPLE_IDENTIFIERS +
 157                 self::DEREFERENCES_OBJECTS;
 158      }
 159  
 160      /**
 161       * Returns true as this store does support multiple identifiers.
 162       * (This optional function is a performance optimisation; it must be
 163       * consistent with the value from get_supported_features.)
 164       *
 165       * @return bool true
 166       */
 167      public function supports_multiple_identifiers() {
 168          return true;
 169      }
 170  
 171      /**
 172       * Returns the supported modes as a combined int.
 173       *
 174       * @param array $configuration
 175       * @return int
 176       */
 177      public static function get_supported_modes(array $configuration = array()) {
 178          return self::MODE_REQUEST;
 179      }
 180  
 181      /**
 182       * Returns true if the store requirements are met.
 183       *
 184       * @return bool
 185       */
 186      public static function are_requirements_met() {
 187          return true;
 188      }
 189  
 190      /**
 191       * Returns true if the given mode is supported by this store.
 192       *
 193       * @param int $mode One of cache_store::MODE_*
 194       * @return bool
 195       */
 196      public static function is_supported_mode($mode) {
 197          return ($mode === self::MODE_REQUEST);
 198      }
 199  
 200      /**
 201       * Initialises the cache.
 202       *
 203       * Once this has been done the cache is all set to be used.
 204       *
 205       * @param cache_definition $definition
 206       */
 207      public function initialise(cache_definition $definition) {
 208          $keyarray = $definition->generate_multi_key_parts();
 209          $this->storeid = $keyarray['mode'].'/'.$keyarray['component'].'/'.$keyarray['area'].'/'.$keyarray['siteidentifier'];
 210          $this->store = &self::register_store_id($this->storeid);
 211          $maxsize = $definition->get_maxsize();
 212          $this->simpledata = $definition->uses_simple_data();
 213          $this->igbinaryfound = extension_loaded('igbinary');
 214          if ($maxsize !== null) {
 215              // Must be a positive int.
 216              $this->maxsize = abs((int)$maxsize);
 217              $this->storecount = count($this->store);
 218          }
 219      }
 220  
 221      /**
 222       * Returns true once this instance has been initialised.
 223       *
 224       * @return bool
 225       */
 226      public function is_initialised() {
 227          return (is_array($this->store));
 228      }
 229  
 230      /**
 231       * Uses igbinary serializer if igbinary extension is loaded.
 232       * Fallback to PHP serializer.
 233       *
 234       * @param mixed $data
 235       * The value to be serialized.
 236       * @return string a string containing a byte-stream representation of
 237       * value that can be stored anywhere.
 238       */
 239      protected function serialize($data) {
 240          if ($this->igbinaryfound) {
 241              return igbinary_serialize($data);
 242          } else {
 243              return serialize($data);
 244          }
 245      }
 246  
 247      /**
 248       * Uses igbinary unserializer if igbinary extension is loaded.
 249       * Fallback to PHP unserializer.
 250       *
 251       * @param string $str
 252       * The serialized string.
 253       * @return mixed The converted value is returned, and can be a boolean,
 254       * integer, float, string,
 255       * array or object.
 256       */
 257      protected function unserialize($str) {
 258          if ($this->igbinaryfound) {
 259              return igbinary_unserialize($str);
 260          } else {
 261              return unserialize($str);
 262          }
 263      }
 264  
 265      /**
 266       * Retrieves an item from the cache store given its key.
 267       *
 268       * @param string $key The key to retrieve
 269       * @return mixed The data that was associated with the key, or false if the key did not exist.
 270       */
 271      public function get($key) {
 272          if (!is_array($key)) {
 273              $key = array('key' => $key);
 274          }
 275  
 276          $key = $key['key'];
 277          if (isset($this->store[$key])) {
 278              if ($this->store[$key]['serialized']) {
 279                  return $this->unserialize($this->store[$key]['data']);
 280              } else {
 281                  return $this->store[$key]['data'];
 282              }
 283          }
 284          return false;
 285      }
 286  
 287      /**
 288       * Retrieves several items from the cache store in a single transaction.
 289       *
 290       * If not all of the items are available in the cache then the data value for those that are missing will be set to false.
 291       *
 292       * @param array $keys The array of keys to retrieve
 293       * @return array An array of items from the cache. There will be an item for each key, those that were not in the store will
 294       *      be set to false.
 295       */
 296      public function get_many($keys) {
 297          $return = array();
 298  
 299          foreach ($keys as $key) {
 300              if (!is_array($key)) {
 301                  $key = array('key' => $key);
 302              }
 303              $key = $key['key'];
 304              $return[$key] = false;
 305              if (isset($this->store[$key])) {
 306                  if ($this->store[$key]['serialized']) {
 307                      $return[$key] = $this->unserialize($this->store[$key]['data']);
 308                  } else {
 309                      $return[$key] = $this->store[$key]['data'];
 310                  }
 311              }
 312          }
 313          return $return;
 314      }
 315  
 316      /**
 317       * Sets an item in the cache given its key and data value.
 318       *
 319       * @param string $key The key to use.
 320       * @param mixed $data The data to set.
 321       * @param bool $testmaxsize If set to true then we test the maxsize arg and reduce if required.
 322       * @return bool True if the operation was a success false otherwise.
 323       */
 324      public function set($key, $data, $testmaxsize = true) {
 325          if (!is_array($key)) {
 326              $key = array('key' => $key);
 327          }
 328          $key = $key['key'];
 329          $testmaxsize = ($testmaxsize && $this->maxsize !== false);
 330          if ($testmaxsize) {
 331              $increment = (!isset($this->store[$key]));
 332          }
 333  
 334          if ($this->simpledata || is_scalar($data)) {
 335              $this->store[$key]['data'] = $data;
 336              $this->store[$key]['serialized'] = false;
 337          } else {
 338              $this->store[$key]['data'] = $this->serialize($data);
 339              $this->store[$key]['serialized'] = true;
 340          }
 341  
 342          if ($testmaxsize && $increment) {
 343              $this->storecount++;
 344              if ($this->storecount > $this->maxsize) {
 345                  $this->reduce_for_maxsize();
 346              }
 347          }
 348          return true;
 349      }
 350  
 351      /**
 352       * Sets many items in the cache in a single transaction.
 353       *
 354       * @param array $keyvaluearray An array of key value pairs. Each item in the array will be an associative array with two
 355       *      keys, 'key' and 'value'.
 356       * @return int The number of items successfully set. It is up to the developer to check this matches the number of items
 357       *      sent ... if they care that is.
 358       */
 359      public function set_many(array $keyvaluearray) {
 360          $count = 0;
 361          foreach ($keyvaluearray as $pair) {
 362              if (!is_array($pair['key'])) {
 363                  $pair['key'] = array('key' => $pair['key']);
 364              }
 365              // Don't test the maxsize here. We'll do it once when we are done.
 366              $this->set($pair['key']['key'], $pair['value'], false);
 367              $count++;
 368          }
 369          if ($this->maxsize !== false) {
 370              $this->storecount += $count;
 371              if ($this->storecount > $this->maxsize) {
 372                  $this->reduce_for_maxsize();
 373              }
 374          }
 375          return $count;
 376      }
 377  
 378      /**
 379       * Checks if the store has a record for the given key and returns true if so.
 380       *
 381       * @param string $key
 382       * @return bool
 383       */
 384      public function has($key) {
 385          if (is_array($key)) {
 386              $key = $key['key'];
 387          }
 388          return isset($this->store[$key]);
 389      }
 390  
 391      /**
 392       * Returns true if the store contains records for all of the given keys.
 393       *
 394       * @param array $keys
 395       * @return bool
 396       */
 397      public function has_all(array $keys) {
 398          foreach ($keys as $key) {
 399              if (!is_array($key)) {
 400                  $key = array('key' => $key);
 401              }
 402              $key = $key['key'];
 403              if (!isset($this->store[$key])) {
 404                  return false;
 405              }
 406          }
 407          return true;
 408      }
 409  
 410      /**
 411       * Returns true if the store contains records for any of the given keys.
 412       *
 413       * @param array $keys
 414       * @return bool
 415       */
 416      public function has_any(array $keys) {
 417          foreach ($keys as $key) {
 418              if (!is_array($key)) {
 419                  $key = array('key' => $key);
 420              }
 421              $key = $key['key'];
 422  
 423              if (isset($this->store[$key])) {
 424                  return true;
 425              }
 426          }
 427          return false;
 428      }
 429  
 430      /**
 431       * Deletes an item from the cache store.
 432       *
 433       * @param string $key The key to delete.
 434       * @return bool Returns true if the operation was a success, false otherwise.
 435       */
 436      public function delete($key) {
 437          if (!is_array($key)) {
 438              $key = array('key' => $key);
 439          }
 440          $key = $key['key'];
 441          $result = isset($this->store[$key]);
 442          unset($this->store[$key]);
 443          if ($this->maxsize !== false) {
 444              $this->storecount--;
 445          }
 446          return $result;
 447      }
 448  
 449      /**
 450       * Deletes several keys from the cache in a single action.
 451       *
 452       * @param array $keys The keys to delete
 453       * @return int The number of items successfully deleted.
 454       */
 455      public function delete_many(array $keys) {
 456          $count = 0;
 457          foreach ($keys as $key) {
 458              if (!is_array($key)) {
 459                  $key = array('key' => $key);
 460              }
 461              $key = $key['key'];
 462              if (isset($this->store[$key])) {
 463                  $count++;
 464              }
 465              unset($this->store[$key]);
 466          }
 467          if ($this->maxsize !== false) {
 468              $this->storecount -= $count;
 469          }
 470          return $count;
 471      }
 472  
 473      /**
 474       * Purges the cache deleting all items within it.
 475       *
 476       * @return boolean True on success. False otherwise.
 477       */
 478      public function purge() {
 479          $this->flush_store_by_id($this->storeid);
 480          $this->store = &self::register_store_id($this->storeid);
 481          // Don't worry about checking if we're using max size just set it as thats as fast as the check.
 482          $this->storecount = 0;
 483          return true;
 484      }
 485  
 486      /**
 487       * Reduces the size of the array if maxsize has been hit.
 488       *
 489       * This function reduces the size of the store reducing it by 10% of its maxsize.
 490       * It removes the oldest items in the store when doing this.
 491       * The reason it does this an doesn't use a least recently used system is purely the overhead such a system
 492       * requires. The current approach is focused on speed, MUC already adds enough overhead to static/session caches
 493       * and avoiding more is of benefit.
 494       *
 495       * @return int
 496       */
 497      protected function reduce_for_maxsize() {
 498          $diff = $this->storecount - $this->maxsize;
 499          if ($diff < 1) {
 500              return 0;
 501          }
 502          // Reduce it by an extra 10% to avoid calling this repetitively if we are in a loop.
 503          $diff += floor($this->maxsize / 10);
 504          $this->store = array_slice($this->store, $diff, null, true);
 505          $this->storecount -= $diff;
 506          return $diff;
 507      }
 508  
 509      /**
 510       * Returns true if the user can add an instance of the store plugin.
 511       *
 512       * @return bool
 513       */
 514      public static function can_add_instance() {
 515          return false;
 516      }
 517  
 518      /**
 519       * Performs any necessary clean up when the store instance is being deleted.
 520       */
 521      public function instance_deleted() {
 522          $this->purge();
 523      }
 524  
 525      /**
 526       * Generates an instance of the cache store that can be used for testing.
 527       *
 528       * @param cache_definition $definition
 529       * @return cachestore_static
 530       */
 531      public static function initialise_test_instance(cache_definition $definition) {
 532          // Do something here perhaps.
 533          $cache = new cachestore_static('Static store');
 534          $cache->initialise($definition);
 535          return $cache;
 536      }
 537  
 538      /**
 539       * Generates the appropriate configuration required for unit testing.
 540       *
 541       * @return array Array of unit test configuration data to be used by initialise().
 542       */
 543      public static function unit_test_configuration() {
 544          return array();
 545      }
 546  
 547      /**
 548       * Returns the name of this instance.
 549       * @return string
 550       */
 551      public function my_name() {
 552          return $this->name;
 553      }
 554  
 555      /**
 556       * Finds all of the keys being stored in the cache store instance.
 557       *
 558       * @return array
 559       */
 560      public function find_all() {
 561          return array_keys($this->store);
 562      }
 563  
 564      /**
 565       * Finds all of the keys whose keys start with the given prefix.
 566       *
 567       * @param string $prefix
 568       */
 569      public function find_by_prefix($prefix) {
 570          $return = array();
 571          foreach ($this->find_all() as $key) {
 572              if (strpos($key, $prefix) === 0) {
 573                  $return[] = $key;
 574              }
 575          }
 576          return $return;
 577      }
 578  }