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