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