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