Merge branch 'MDL-81457-main' of https://github.com/andrewnicols/moodle
[moodle.git] / cache / classes / loaders.php
blob36fdb4da40ec759f8f0a9c22b3d3f30da4806eb8
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.
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/>.
17 /**
18 * Cache loaders
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.
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
29 defined('MOODLE_INTERNAL') || die();
31 /**
32 * The main cache class.
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.
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
43 class cache implements cache_loader {
45 /**
46 * @var int Constant for cache entries that do not have a version number
48 const VERSION_NONE = -1;
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
56 protected static $now;
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>.
62 * @var string
64 protected static $purgetoken;
66 /**
67 * The definition used when loading this cache if there was one.
68 * @var cache_definition
70 private $definition = false;
72 /**
73 * The cache store that this loader will make use of.
74 * @var cache_store
76 private $store;
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
85 private $loader = false;
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
92 private $datasource = false;
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
99 private $supportskeyawareness = null;
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
106 private $supportsnativettl = null;
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
115 private $staticacceleration = false;
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
122 private $staticaccelerationarray = array();
125 * The number of items in the static acceleration array. Avoids count calls like you wouldn't believe.
126 * @var int
128 private $staticaccelerationcount = 0;
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
137 private $staticaccelerationkeys = array();
140 * The maximum size of the static acceleration array.
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.
146 * @var int|false
148 private $staticaccelerationsize = false;
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
155 private $hasattl = false;
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
162 protected $storetype = 'unknown';
165 * Gets set to true if we want to collect performance information about the cache API.
166 * @var bool
168 protected $perfdebug = false;
171 * Determines if this loader is a sub loader, not the top of the chain.
172 * @var bool
174 protected $subloader = false;
177 * Gets set to true if the cache writes (set|delete) must have a manual lock created first.
178 * @var bool
180 protected $requirelockingbeforewrite = false;
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
187 protected $nativelocking = null;
190 * Creates a new cache instance for a pre-defined definition.
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
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);
204 * Creates a new cache instance based upon the given params.
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
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);
223 * Constructs a new cache instance.
225 * You should not call this method from your code, instead you should use the cache::make methods.
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).
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.
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);
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();
253 $this->hasattl = ($this->definition->get_ttl() > 0);
257 * Set the loader for this cache.
259 * @param cache_loader $loader
261 protected function set_loader(cache_loader $loader): void {
262 $this->loader = $loader;
264 // Mark the loader as a sub (chained) loader.
265 $this->loader->set_is_sub_loader(true);
269 * Set the data source for this cache.
271 * @param cache_data_source $datasource
273 protected function set_data_source(cache_data_source $datasource): void {
274 $this->datasource = $datasource;
278 * Used to inform the loader of its state as a sub loader, or as the top of the chain.
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.
285 * @param bool $setting
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();
303 * Alters the identifiers that have been provided to the definition.
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.
309 * @param array $identifiers
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;
324 * Process any outstanding invalidation events for the cache we are registering,
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.
329 protected function handle_invalidation_events() {
330 if (!$this->definition->has_invalidation_events()) {
331 return;
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;
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:
351 * eventname => [
352 * keyname => purgetoken,
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.
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.
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.
369 $cache = self::make('core', 'eventinvalidation');
370 $events = $cache->get_many($this->definition->get_invalidation_events());
371 $todelete = array();
372 $purgeall = false;
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;
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;
395 if ($purgeall) {
396 $this->purge();
397 } else if (!empty($todelete)) {
398 $todelete = array_unique($todelete);
399 $this->delete_many($todelete);
401 // Set the time of the last invalidation.
402 if ($purgeall || !empty($todelete)) {
403 $this->set('lastinvalidation', self::get_purge_token(true));
408 * Retrieves the value for the given key from the cache.
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
417 public function get($key, $strictness = IGNORE_MISSING) {
418 return $this->get_implementation($key, self::VERSION_NONE, $strictness);
422 * Retrieves the value and actual version for the given key, with at least the required version.
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).
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.
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.
439 * You must use this function if you use set_versioned.
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
448 public function get_versioned($key, int $requiredversion, int $strictness = IGNORE_MISSING, &$actualversion = null) {
449 return $this->get_implementation($key, $requiredversion, $strictness, $actualversion);
453 * Checks returned data to see if it matches the specified version number.
455 * For versioned data, this returns the version_wrapper object (or false). For other
456 * data, it returns the actual data (or false).
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
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;
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;
476 if (!($result instanceof \core_cache\version_wrapper)) {
477 throw new \coding_exception('Unexpectedly found non-versioned cache entry');
479 // If the result doesn't match the required version tag, return false.
480 if ($result->version < $requiredversion) {
481 return false;
483 // The version meets the requirement.
484 return true;
489 * Retrieves the value for the given key from the cache.
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
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();
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;
516 // 2. Parse the key.
517 $parsedkey = $this->parse_key($key);
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();
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;
540 if (!$validversion) {
541 // If the result was too old, don't use it.
542 $result = false;
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);
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;
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;
570 if ($usesstaticacceleration) {
571 $this->static_acceleration_set($key, $result);
573 // Remove version wrapper if necessary.
574 if ($requiredversion !== self::VERSION_NONE) {
575 $actualversion = $result->version;
576 $result = $result->data;
578 if ($result instanceof cache_cached_object) {
579 $result = $result->restore_object();
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);
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);
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');
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');
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);
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.');
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 = false;
624 try {
625 // Only try to acquire a lock for this cache if we do not already have one.
626 if (!empty($this->requirelockingbeforewrite) && !$this->check_lock_state($key)) {
627 $this->acquire_lock($key);
628 $lock = true;
630 if ($requiredversion === self::VERSION_NONE) {
631 $this->set_implementation($key, self::VERSION_NONE, $result, false);
632 } else {
633 $this->set_implementation($key, $actualversion, $result, false);
635 } finally {
636 if ($lock) {
637 $this->release_lock($key);
641 // 7. Make sure we don't pass back anything that could be a reference.
642 // We don't want people modifying the data in the cache.
643 if (!$this->store->supports_dereferencing_objects() && !is_scalar($result)) {
644 // If data is an object it will be a reference.
645 // If data is an array if may contain references.
646 // We want to break references so that the cache cannot be modified outside of itself.
647 // Call the function to unreference it (in the best way possible).
648 $result = $this->unref($result);
650 return $result;
654 * Retrieves an array of values for an array of keys.
656 * Using this function comes with potential performance implications.
657 * Not all cache stores will support get_many/set_many operations and in order to replicate this functionality will call
658 * the equivalent singular method for each item provided.
659 * This should not deter you from using this function as there is a performance benefit in situations where the cache store
660 * does support it, but you should be aware of this fact.
662 * @param array $keys The keys of the data being requested.
663 * Each key can be any structure although using a scalar string or int is recommended in the interests of performance.
664 * In advanced cases an array may be useful such as in situations requiring the multi-key functionality.
665 * @param int $strictness One of IGNORE_MISSING or MUST_EXIST.
666 * @return array An array of key value pairs for the items that could be retrieved from the cache.
667 * If MUST_EXIST was used and not all keys existed within the cache then an exception will be thrown.
668 * Otherwise any key that did not exist will have a data value of false within the results.
669 * @throws coding_exception
671 public function get_many(array $keys, $strictness = IGNORE_MISSING) {
673 $keysparsed = array();
674 $parsedkeys = array();
675 $resultpersist = array();
676 $resultstore = array();
677 $keystofind = array();
678 $readbytes = cache_store::IO_BYTES_NOT_SUPPORTED;
680 // First up check the persist cache for each key.
681 $isusingpersist = $this->use_static_acceleration();
682 foreach ($keys as $key) {
683 $pkey = $this->parse_key($key);
684 if (is_array($pkey)) {
685 $pkey = $pkey['key'];
687 $keysparsed[$key] = $pkey;
688 $parsedkeys[$pkey] = $key;
689 $keystofind[$pkey] = $key;
690 if ($isusingpersist) {
691 $value = $this->static_acceleration_get($key);
692 if ($value !== false) {
693 $resultpersist[$pkey] = $value;
694 unset($keystofind[$pkey]);
699 // Next assuming we didn't find all of the keys in the persist cache try loading them from the store.
700 if (count($keystofind)) {
701 $resultstore = $this->store->get_many(array_keys($keystofind));
702 if ($this->perfdebug) {
703 $readbytes = $this->store->get_last_io_bytes();
705 // Process each item in the result to "unwrap" it.
706 foreach ($resultstore as $key => $value) {
707 if ($value instanceof cache_ttl_wrapper) {
708 if ($value->has_expired()) {
709 $value = false;
710 } else {
711 $value = $value->data;
714 if ($value !== false && $this->use_static_acceleration()) {
715 $this->static_acceleration_set($keystofind[$key], $value);
717 if ($value instanceof cache_cached_object) {
718 $value = $value->restore_object();
720 $resultstore[$key] = $value;
724 // Merge the result from the persis cache with the results from the store load.
725 $result = $resultpersist + $resultstore;
726 unset($resultpersist);
727 unset($resultstore);
729 // Next we need to find any missing values and load them from the loader/datasource next in the chain.
730 $usingloader = ($this->loader !== false);
731 $usingsource = (!$usingloader && ($this->datasource !== false));
732 if ($usingloader || $usingsource) {
733 $missingkeys = array();
734 foreach ($result as $key => $value) {
735 if ($value === false) {
736 $missingkeys[] = $parsedkeys[$key];
739 if (!empty($missingkeys)) {
740 if ($usingloader) {
741 $resultmissing = $this->loader->get_many($missingkeys);
742 } else {
743 $resultmissing = $this->datasource->load_many_for_cache($missingkeys);
745 foreach ($resultmissing as $key => $value) {
746 $result[$keysparsed[$key]] = $value;
747 $lock = false;
748 try {
749 if (!empty($this->requirelockingbeforewrite)) {
750 $this->acquire_lock($key);
751 $lock = true;
753 if ($value !== false) {
754 $this->set($key, $value);
756 } finally {
757 if ($lock) {
758 $this->release_lock($key);
762 unset($resultmissing);
764 unset($missingkeys);
767 // Create an array with the original keys and the found values. This will be what we return.
768 $fullresult = array();
769 foreach ($result as $key => $value) {
770 if (!is_scalar($value)) {
771 // If data is an object it will be a reference.
772 // If data is an array if may contain references.
773 // We want to break references so that the cache cannot be modified outside of itself.
774 // Call the function to unreference it (in the best way possible).
775 $value = $this->unref($value);
777 $fullresult[$parsedkeys[$key]] = $value;
779 unset($result);
781 // Final step is to check strictness.
782 if ($strictness === MUST_EXIST) {
783 foreach ($keys as $key) {
784 if (!array_key_exists($key, $fullresult)) {
785 throw new coding_exception('Not all the requested keys existed within the cache stores.');
790 if ($this->perfdebug) {
791 $hits = 0;
792 $misses = 0;
793 foreach ($fullresult as $value) {
794 if ($value === false) {
795 $misses++;
796 } else {
797 $hits++;
800 cache_helper::record_cache_hit($this->store, $this->definition, $hits, $readbytes);
801 cache_helper::record_cache_miss($this->store, $this->definition, $misses);
804 // Return the result. Phew!
805 return $fullresult;
809 * Sends a key => value pair to the cache.
811 * <code>
812 * // This code will add four entries to the cache, one for each url.
813 * $cache->set('main', 'http://moodle.org');
814 * $cache->set('docs', 'http://docs.moodle.org');
815 * $cache->set('tracker', 'http://tracker.moodle.org');
816 * $cache->set('qa', 'http://qa.moodle.net');
817 * </code>
819 * @param string|int $key The key for the data being requested.
820 * It can be any structure although using a scalar string or int is recommended in the interests of performance.
821 * In advanced cases an array may be useful such as in situations requiring the multi-key functionality.
822 * @param mixed $data The data to set against the key.
823 * @return bool True on success, false otherwise.
825 public function set($key, $data) {
826 return $this->set_implementation($key, self::VERSION_NONE, $data);
830 * Sets the value for the given key with the given version.
832 * The cache does not store multiple versions - any existing version will be overwritten with
833 * this one. This function should only be used if there is a known 'current version' (e.g.
834 * stored in a database table). It only ensures that the cache does not return outdated data.
836 * This function can be used to help implement localisable caches (where the cache could be
837 * stored on a local server as well as a shared cache). The version will be recorded alongside
838 * the item and get_versioned will always return the correct version.
840 * The version number must be an integer that always increases. This could be based on the
841 * current time, or a stored value that increases by 1 each time it changes, etc.
843 * If you use this function you must use get_versioned to retrieve the data.
845 * @param string|int $key The key for the data being set.
846 * @param int $version Integer for the version of the data
847 * @param mixed $data The data to set against the key.
848 * @return bool True on success, false otherwise.
850 public function set_versioned($key, int $version, $data): bool {
851 return $this->set_implementation($key, $version, $data);
855 * Sets the value for the given key, optionally with a version tag.
857 * @param string|int $key The key for the data being set.
858 * @param int $version Version number for the data or cache::VERSION_NONE if none
859 * @param mixed $data The data to set against the key.
860 * @param bool $setparents If true, sets all parent loaders, otherwise only this one
861 * @return bool True on success, false otherwise.
863 protected function set_implementation($key, int $version, $data, bool $setparents = true): bool {
864 if ($this->loader !== false && $setparents) {
865 // We have a loader available set it there as well.
866 // We have to let the loader do its own parsing of data as it may be unique.
867 if ($version === self::VERSION_NONE) {
868 $this->loader->set($key, $data);
869 } else {
870 $this->loader->set_versioned($key, $version, $data);
873 $usestaticacceleration = $this->use_static_acceleration();
875 if (is_object($data) && $data instanceof cacheable_object) {
876 $data = new cache_cached_object($data);
877 } else if (!$this->store->supports_dereferencing_objects() && !is_scalar($data)) {
878 // If data is an object it will be a reference.
879 // If data is an array if may contain references.
880 // We want to break references so that the cache cannot be modified outside of itself.
881 // Call the function to unreference it (in the best way possible).
882 $data = $this->unref($data);
885 if ($usestaticacceleration) {
886 // Static acceleration cache should include the cache version wrapper, but not TTL.
887 if ($version === self::VERSION_NONE) {
888 $this->static_acceleration_set($key, $data);
889 } else {
890 $this->static_acceleration_set($key, new \core_cache\version_wrapper($data, $version));
894 if ($this->has_a_ttl() && !$this->store_supports_native_ttl()) {
895 $data = new cache_ttl_wrapper($data, $this->definition->get_ttl());
897 $parsedkey = $this->parse_key($key);
899 if ($version !== self::VERSION_NONE) {
900 $data = new \core_cache\version_wrapper($data, $version);
903 $success = $this->store->set($parsedkey, $data);
904 if ($this->perfdebug) {
905 cache_helper::record_cache_set($this->store, $this->definition, 1,
906 $this->store->get_last_io_bytes());
908 return $success;
912 * Removes references where required.
914 * @param stdClass|array $data
915 * @return mixed What ever was put in but without any references.
917 protected function unref($data) {
918 if ($this->definition->uses_simple_data()) {
919 return $data;
921 // Check if it requires serialisation in order to produce a reference free copy.
922 if ($this->requires_serialisation($data)) {
923 // Damn, its going to have to be serialise.
924 $data = serialize($data);
925 // We unserialise immediately so that we don't have to do it every time on get.
926 $data = unserialize($data);
927 } else if (!is_scalar($data)) {
928 // Its safe to clone, lets do it, its going to beat the pants of serialisation.
929 $data = $this->deep_clone($data);
931 return $data;
935 * Checks to see if a var requires serialisation.
937 * @param mixed $value The value to check.
938 * @param int $depth Used to ensure we don't enter an endless loop (think recursion).
939 * @return bool Returns true if the value is going to require serialisation in order to ensure a reference free copy
940 * or false if its safe to clone.
942 protected function requires_serialisation($value, $depth = 1) {
943 if (is_scalar($value)) {
944 return false;
945 } else if (is_array($value) || $value instanceof stdClass || $value instanceof Traversable) {
946 if ($depth > 5) {
947 // Skrew it, mega-deep object, developer you suck, we're just going to serialise.
948 return true;
950 foreach ($value as $key => $subvalue) {
951 if ($this->requires_serialisation($subvalue, $depth++)) {
952 return true;
956 // Its not scalar, array, or stdClass so we'll need to serialise.
957 return true;
961 * Creates a reference free clone of the given value.
963 * @param mixed $value
964 * @return mixed
966 protected function deep_clone($value) {
967 if (is_object($value)) {
968 // Objects must be cloned to begin with.
969 $value = clone $value;
971 if (is_array($value) || is_object($value)) {
972 foreach ($value as $key => $subvalue) {
973 $value[$key] = $this->deep_clone($subvalue);
976 return $value;
980 * Sends several key => value pairs to the cache.
982 * Using this function comes with potential performance implications.
983 * Not all cache stores will support get_many/set_many operations and in order to replicate this functionality will call
984 * the equivalent singular method for each item provided.
985 * This should not deter you from using this function as there is a performance benefit in situations where the cache store
986 * does support it, but you should be aware of this fact.
988 * <code>
989 * // This code will add four entries to the cache, one for each url.
990 * $cache->set_many(array(
991 * 'main' => 'http://moodle.org',
992 * 'docs' => 'http://docs.moodle.org',
993 * 'tracker' => 'http://tracker.moodle.org',
994 * 'qa' => ''http://qa.moodle.net'
995 * ));
996 * </code>
998 * @param array $keyvaluearray An array of key => value pairs to send to the cache.
999 * @return int The number of items successfully set. It is up to the developer to check this matches the number of items.
1000 * ... if they care that is.
1002 public function set_many(array $keyvaluearray) {
1003 if ($this->loader !== false) {
1004 // We have a loader available set it there as well.
1005 // We have to let the loader do its own parsing of data as it may be unique.
1006 $this->loader->set_many($keyvaluearray);
1008 $data = array();
1009 $simulatettl = $this->has_a_ttl() && !$this->store_supports_native_ttl();
1010 $usestaticaccelerationarray = $this->use_static_acceleration();
1011 $needsdereferencing = !$this->store->supports_dereferencing_objects();
1012 foreach ($keyvaluearray as $key => $value) {
1013 if (is_object($value) && $value instanceof cacheable_object) {
1014 $value = new cache_cached_object($value);
1015 } else if ($needsdereferencing && !is_scalar($value)) {
1016 // If data is an object it will be a reference.
1017 // If data is an array if may contain references.
1018 // We want to break references so that the cache cannot be modified outside of itself.
1019 // Call the function to unreference it (in the best way possible).
1020 $value = $this->unref($value);
1022 if ($usestaticaccelerationarray) {
1023 $this->static_acceleration_set($key, $value);
1025 if ($simulatettl) {
1026 $value = new cache_ttl_wrapper($value, $this->definition->get_ttl());
1028 $data[$key] = array(
1029 'key' => $this->parse_key($key),
1030 'value' => $value
1033 $successfullyset = $this->store->set_many($data);
1034 if ($this->perfdebug && $successfullyset) {
1035 cache_helper::record_cache_set($this->store, $this->definition, $successfullyset,
1036 $this->store->get_last_io_bytes());
1038 return $successfullyset;
1042 * Test is a cache has a key.
1044 * The use of the has methods is strongly discouraged. In a high load environment the cache may well change between the
1045 * test and any subsequent action (get, set, delete etc).
1046 * Instead it is recommended to write your code in such a way they it performs the following steps:
1047 * <ol>
1048 * <li>Attempt to retrieve the information.</li>
1049 * <li>Generate the information.</li>
1050 * <li>Attempt to set the information</li>
1051 * </ol>
1053 * Its also worth mentioning that not all stores support key tests.
1054 * For stores that don't support key tests this functionality is mimicked by using the equivalent get method.
1055 * Just one more reason you should not use these methods unless you have a very good reason to do so.
1057 * @param string|int $key
1058 * @param bool $tryloadifpossible If set to true, the cache doesn't contain the key, and there is another cache loader or
1059 * data source then the code will try load the key value from the next item in the chain.
1060 * @return bool True if the cache has the requested key, false otherwise.
1062 public function has($key, $tryloadifpossible = false) {
1063 if ($this->static_acceleration_has($key)) {
1064 // Hoorah, that was easy. It exists in the static acceleration array so we definitely have it.
1065 return true;
1067 $parsedkey = $this->parse_key($key);
1069 if ($this->has_a_ttl() && !$this->store_supports_native_ttl()) {
1070 // The data has a TTL and the store doesn't support it natively.
1071 // We must fetch the data and expect a ttl wrapper.
1072 $data = $this->store->get($parsedkey);
1073 $has = ($data instanceof cache_ttl_wrapper && !$data->has_expired());
1074 } else if (!$this->store_supports_key_awareness()) {
1075 // The store doesn't support key awareness, get the data and check it manually... puke.
1076 // Either no TTL is set of the store supports its handling natively.
1077 $data = $this->store->get($parsedkey);
1078 $has = ($data !== false);
1079 } else {
1080 // The store supports key awareness, this is easy!
1081 // Either no TTL is set of the store supports its handling natively.
1082 $has = $this->store->has($parsedkey);
1084 if (!$has && $tryloadifpossible) {
1085 if ($this->loader !== false) {
1086 $result = $this->loader->get($parsedkey);
1087 } else if ($this->datasource !== null) {
1088 $result = $this->datasource->load_for_cache($key);
1090 $has = ($result !== null);
1091 if ($has) {
1092 $this->set($key, $result);
1095 return $has;
1099 * Test is a cache has all of the given keys.
1101 * It is strongly recommended to avoid the use of this function if not absolutely required.
1102 * In a high load environment the cache may well change between the test and any subsequent action (get, set, delete etc).
1104 * Its also worth mentioning that not all stores support key tests.
1105 * For stores that don't support key tests this functionality is mimicked by using the equivalent get method.
1106 * Just one more reason you should not use these methods unless you have a very good reason to do so.
1108 * @param array $keys
1109 * @return bool True if the cache has all of the given keys, false otherwise.
1111 public function has_all(array $keys) {
1112 if (($this->has_a_ttl() && !$this->store_supports_native_ttl()) || !$this->store_supports_key_awareness()) {
1113 foreach ($keys as $key) {
1114 if (!$this->has($key)) {
1115 return false;
1118 return true;
1120 $parsedkeys = array_map(array($this, 'parse_key'), $keys);
1121 return $this->store->has_all($parsedkeys);
1125 * Test if a cache has at least one of the given keys.
1127 * It is strongly recommended to avoid the use of this function if not absolutely required.
1128 * In a high load environment the cache may well change between the test and any subsequent action (get, set, delete etc).
1130 * Its also worth mentioning that not all stores support key tests.
1131 * For stores that don't support key tests this functionality is mimicked by using the equivalent get method.
1132 * Just one more reason you should not use these methods unless you have a very good reason to do so.
1134 * @param array $keys
1135 * @return bool True if the cache has at least one of the given keys
1137 public function has_any(array $keys) {
1138 if (($this->has_a_ttl() && !$this->store_supports_native_ttl()) || !$this->store_supports_key_awareness()) {
1139 foreach ($keys as $key) {
1140 if ($this->has($key)) {
1141 return true;
1144 return false;
1147 if ($this->use_static_acceleration()) {
1148 foreach ($keys as $id => $key) {
1149 if ($this->static_acceleration_has($key)) {
1150 return true;
1154 $parsedkeys = array_map(array($this, 'parse_key'), $keys);
1155 return $this->store->has_any($parsedkeys);
1159 * Delete the given key from the cache.
1161 * @param string|int $key The key to delete.
1162 * @param bool $recurse When set to true the key will also be deleted from all stacked cache loaders and their stores.
1163 * This happens by default and ensure that all the caches are consistent. It is NOT recommended to change this.
1164 * @return bool True of success, false otherwise.
1166 public function delete($key, $recurse = true) {
1167 $this->static_acceleration_delete($key);
1168 if ($recurse && $this->loader !== false) {
1169 // Delete from the bottom of the stack first.
1170 $this->loader->delete($key, $recurse);
1172 $parsedkey = $this->parse_key($key);
1173 return $this->store->delete($parsedkey);
1177 * Delete all of the given keys from the cache.
1179 * @param array $keys The key to delete.
1180 * @param bool $recurse When set to true the key will also be deleted from all stacked cache loaders and their stores.
1181 * This happens by default and ensure that all the caches are consistent. It is NOT recommended to change this.
1182 * @return int The number of items successfully deleted.
1184 public function delete_many(array $keys, $recurse = true) {
1185 if ($this->use_static_acceleration()) {
1186 foreach ($keys as $key) {
1187 $this->static_acceleration_delete($key);
1190 if ($recurse && $this->loader !== false) {
1191 // Delete from the bottom of the stack first.
1192 $this->loader->delete_many($keys, $recurse);
1194 $parsedkeys = array_map(array($this, 'parse_key'), $keys);
1195 return $this->store->delete_many($parsedkeys);
1199 * Purges the cache store, and loader if there is one.
1201 * @return bool True on success, false otherwise
1203 public function purge() {
1204 // 1. Purge the static acceleration array.
1205 $this->static_acceleration_purge();
1206 // 2. Purge the store.
1207 $this->store->purge();
1208 // 3. Optionally pruge any stacked loaders.
1209 if ($this->loader) {
1210 $this->loader->purge();
1212 return true;
1216 * Parses the key turning it into a string (or array is required) suitable to be passed to the cache store.
1218 * @param string|int $key As passed to get|set|delete etc.
1219 * @return string|array String unless the store supports multi-identifiers in which case an array if returned.
1221 protected function parse_key($key) {
1222 // First up if the store supports multiple keys we'll go with that.
1223 if ($this->store->supports_multiple_identifiers()) {
1224 $result = $this->definition->generate_multi_key_parts();
1225 $result['key'] = $key;
1226 return $result;
1228 // If not we need to generate a hash and to for that we use the cache_helper.
1229 return cache_helper::hash_key($key, $this->definition);
1233 * Returns true if the cache is making use of a ttl.
1234 * @return bool
1236 protected function has_a_ttl() {
1237 return $this->hasattl;
1241 * Returns true if the cache store supports native ttl.
1242 * @return bool
1244 protected function store_supports_native_ttl() {
1245 if ($this->supportsnativettl === null) {
1246 $this->supportsnativettl = ($this->store->supports_native_ttl());
1248 return $this->supportsnativettl;
1252 * Returns the cache definition.
1254 * @return cache_definition
1256 protected function get_definition() {
1257 return $this->definition;
1261 * Returns the cache store
1263 * @return cache_store
1265 protected function get_store() {
1266 return $this->store;
1270 * Returns the loader associated with this instance.
1272 * @since Moodle 2.4.4
1273 * @return cache|false
1275 protected function get_loader() {
1276 return $this->loader;
1280 * Returns the data source associated with this cache.
1282 * @since Moodle 2.4.4
1283 * @return cache_data_source|false
1285 protected function get_datasource() {
1286 return $this->datasource;
1290 * Returns true if the store supports key awareness.
1292 * @return bool
1294 protected function store_supports_key_awareness() {
1295 if ($this->supportskeyawareness === null) {
1296 $this->supportskeyawareness = ($this->store instanceof cache_is_key_aware);
1298 return $this->supportskeyawareness;
1302 * Returns true if the store natively supports locking.
1304 * @return bool
1306 protected function store_supports_native_locking() {
1307 if ($this->nativelocking === null) {
1308 $this->nativelocking = ($this->store instanceof cache_is_lockable);
1310 return $this->nativelocking;
1314 * @deprecated since 2.6
1315 * @see cache::use_static_acceleration()
1317 protected function is_using_persist_cache() {
1318 throw new coding_exception('cache::is_using_persist_cache() can not be used anymore.' .
1319 ' Please use cache::use_static_acceleration() instead.');
1323 * Returns true if this cache is making use of the static acceleration array.
1325 * @return bool
1327 protected function use_static_acceleration() {
1328 return $this->staticacceleration;
1332 * @see cache::static_acceleration_has
1333 * @deprecated since 2.6
1335 protected function is_in_persist_cache() {
1336 throw new coding_exception('cache::is_in_persist_cache() can not be used anymore.' .
1337 ' Please use cache::static_acceleration_has() instead.');
1341 * Returns true if the requested key exists within the static acceleration array.
1343 * @param string $key The parsed key
1344 * @return bool
1346 protected function static_acceleration_has($key) {
1347 // This could be written as a single line, however it has been split because the ttl check is faster than the instanceof
1348 // and has_expired calls.
1349 if (!$this->staticacceleration || !isset($this->staticaccelerationarray[$key])) {
1350 return false;
1352 return true;
1356 * @deprecated since 2.6
1357 * @see cache::static_acceleration_get
1359 protected function get_from_persist_cache() {
1360 throw new coding_exception('cache::get_from_persist_cache() can not be used anymore.' .
1361 ' Please use cache::static_acceleration_get() instead.');
1365 * Returns the item from the static acceleration array if it exists there.
1367 * @param string $key The parsed key
1368 * @return mixed|false Dereferenced data from the static acceleration array or false if it wasn't there.
1370 protected function static_acceleration_get($key) {
1371 if (!$this->staticacceleration || !isset($this->staticaccelerationarray[$key])) {
1372 $result = false;
1373 } else {
1374 $data = $this->staticaccelerationarray[$key]['data'];
1376 if ($data instanceof cache_cached_object) {
1377 $result = $data->restore_object();
1378 } else if ($this->staticaccelerationarray[$key]['serialized']) {
1379 $result = unserialize($data);
1380 } else {
1381 $result = $data;
1384 if (cache_helper::result_found($result)) {
1385 if ($this->perfdebug) {
1386 cache_helper::record_cache_hit(cache_store::STATIC_ACCEL, $this->definition);
1388 if ($this->staticaccelerationsize > 1 && $this->staticaccelerationcount > 1) {
1389 // Check to see if this is the last item on the static acceleration keys array.
1390 if (end($this->staticaccelerationkeys) !== $key) {
1391 // It isn't the last item.
1392 // Move the item to the end of the array so that it is last to be removed.
1393 unset($this->staticaccelerationkeys[$key]);
1394 $this->staticaccelerationkeys[$key] = $key;
1397 return $result;
1398 } else {
1399 if ($this->perfdebug) {
1400 cache_helper::record_cache_miss(cache_store::STATIC_ACCEL, $this->definition);
1402 return false;
1407 * @deprecated since 2.6
1408 * @see cache::static_acceleration_set
1410 protected function set_in_persist_cache() {
1411 throw new coding_exception('cache::set_in_persist_cache() can not be used anymore.' .
1412 ' Please use cache::static_acceleration_set() instead.');
1416 * Sets a key value pair into the static acceleration array.
1418 * @param string $key The parsed key
1419 * @param mixed $data
1420 * @return bool
1422 protected function static_acceleration_set($key, $data) {
1423 if ($this->staticaccelerationsize !== false && isset($this->staticaccelerationkeys[$key])) {
1424 $this->staticaccelerationcount--;
1425 unset($this->staticaccelerationkeys[$key]);
1428 // We serialize anything that's not;
1429 // 1. A known scalar safe value.
1430 // 2. A definition that says it's simpledata. We trust it that it doesn't contain dangerous references.
1431 // 3. An object that handles dereferencing by itself.
1432 if (is_scalar($data) || $this->definition->uses_simple_data()
1433 || $data instanceof cache_cached_object) {
1434 $this->staticaccelerationarray[$key]['data'] = $data;
1435 $this->staticaccelerationarray[$key]['serialized'] = false;
1436 } else {
1437 $this->staticaccelerationarray[$key]['data'] = serialize($data);
1438 $this->staticaccelerationarray[$key]['serialized'] = true;
1440 if ($this->staticaccelerationsize !== false) {
1441 $this->staticaccelerationcount++;
1442 $this->staticaccelerationkeys[$key] = $key;
1443 if ($this->staticaccelerationcount > $this->staticaccelerationsize) {
1444 $dropkey = array_shift($this->staticaccelerationkeys);
1445 unset($this->staticaccelerationarray[$dropkey]);
1446 $this->staticaccelerationcount--;
1449 return true;
1453 * @deprecated since 2.6
1454 * @see cache::static_acceleration_delete()
1456 protected function delete_from_persist_cache() {
1457 throw new coding_exception('cache::delete_from_persist_cache() can not be used anymore.' .
1458 ' Please use cache::static_acceleration_delete() instead.');
1462 * Deletes an item from the static acceleration array.
1464 * @param string|int $key As given to get|set|delete
1465 * @return bool True on success, false otherwise.
1467 protected function static_acceleration_delete($key) {
1468 unset($this->staticaccelerationarray[$key]);
1469 if ($this->staticaccelerationsize !== false && isset($this->staticaccelerationkeys[$key])) {
1470 unset($this->staticaccelerationkeys[$key]);
1471 $this->staticaccelerationcount--;
1473 return true;
1477 * Purge the static acceleration cache.
1479 protected function static_acceleration_purge() {
1480 $this->staticaccelerationarray = array();
1481 if ($this->staticaccelerationsize !== false) {
1482 $this->staticaccelerationkeys = array();
1483 $this->staticaccelerationcount = 0;
1488 * Returns the timestamp from the first request for the time from the cache API.
1490 * This stamp needs to be used for all ttl and time based operations to ensure that we don't end up with
1491 * timing issues.
1493 * @param bool $float Whether to use floating precision accuracy.
1494 * @return int|float
1496 public static function now($float = false) {
1497 if (self::$now === null) {
1498 self::$now = microtime(true);
1501 if ($float) {
1502 return self::$now;
1503 } else {
1504 return (int) self::$now;
1509 * Get a 'purge' token used for cache invalidation handling.
1511 * Note: This function is intended for use from within the Cache API only and not by plugins, or cache stores.
1513 * @param bool $reset Whether to reset the token and generate a new one.
1514 * @return string
1516 public static function get_purge_token($reset = false) {
1517 if (self::$purgetoken === null || $reset) {
1518 self::$now = null;
1519 self::$purgetoken = self::now(true) . '-' . uniqid('', true);
1522 return self::$purgetoken;
1526 * Compare a pair of purge tokens.
1528 * If the two tokens are identical, then the return value is 0.
1529 * If the time component of token A is newer than token B, then a positive value is returned.
1530 * If the time component of token B is newer than token A, then a negative value is returned.
1532 * Note: This function is intended for use from within the Cache API only and not by plugins, or cache stores.
1534 * @param string $tokena
1535 * @param string $tokenb
1536 * @return int
1538 public static function compare_purge_tokens($tokena, $tokenb) {
1539 if ($tokena === $tokenb) {
1540 // There is an exact match.
1541 return 0;
1544 // The token for when the cache was last invalidated.
1545 list($atime) = explode('-', "{$tokena}-", 2);
1547 // The token for this cache.
1548 list($btime) = explode('-', "{$tokenb}-", 2);
1550 if ($atime >= $btime) {
1551 // Token A is newer.
1552 return 1;
1553 } else {
1554 // Token A is older.
1555 return -1;
1560 * Subclasses may support purging cache of all data belonging to the
1561 * current user.
1563 public function purge_current_user() {
1568 * An application cache.
1570 * This class is used for application caches returned by the cache::make methods.
1571 * On top of the standard functionality it also allows locking to be required and or manually operated.
1573 * This cache class should never be interacted with directly. Instead you should always use the cache::make methods.
1574 * It is technically possible to call those methods through this class however there is no guarantee that you will get an
1575 * instance of this class back again.
1577 * @internal don't use me directly.
1579 * @package core
1580 * @category cache
1581 * @copyright 2012 Sam Hemelryk
1582 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1584 class cache_application extends cache implements cache_loader_with_locking {
1587 * Lock identifier.
1588 * This is used to ensure the lock belongs to the cache instance + definition + user.
1589 * @var string
1591 protected $lockidentifier;
1594 * Gets set to true if the cache's primary store natively supports locking.
1595 * If it does then we use that, otherwise we need to instantiate a second store to use for locking.
1596 * @var cache_store
1598 protected $nativelocking = null;
1601 * Gets set to true if the cache is going to be using locking.
1602 * This isn't a requirement, it doesn't need to use locking (most won't) and this bool is used to quickly check things.
1603 * If required then locking will be forced for the get|set|delete operation.
1604 * @var bool
1606 protected $requirelocking = false;
1609 * Gets set to true if the cache writes (set|delete) must have a manual lock created first
1610 * @var bool
1612 protected $requirelockingbeforewrite = false;
1615 * Gets set to a cache_store to use for locking if the caches primary store doesn't support locking natively.
1616 * @var cache_lock_interface
1618 protected $cachelockinstance;
1621 * Store a list of locks acquired by this process.
1622 * @var array
1624 protected $locks;
1627 * Overrides the cache construct method.
1629 * You should not call this method from your code, instead you should use the cache::make methods.
1631 * @param cache_definition $definition
1632 * @param cache_store $store
1633 * @param cache_loader|cache_data_source $loader
1635 public function __construct(cache_definition $definition, cache_store $store, $loader = null) {
1636 parent::__construct($definition, $store, $loader);
1637 $this->nativelocking = $this->store_supports_native_locking();
1638 if ($definition->require_locking()) {
1639 $this->requirelocking = true;
1640 $this->requirelockingbeforewrite = $definition->require_locking_before_write();
1643 $this->handle_invalidation_events();
1647 * Returns the identifier to use
1649 * @staticvar int $instances Counts the number of instances. Used as part of the lock identifier.
1650 * @return string
1652 public function get_identifier() {
1653 static $instances = 0;
1654 if ($this->lockidentifier === null) {
1655 $this->lockidentifier = md5(
1656 $this->get_definition()->generate_definition_hash() .
1657 sesskey() .
1658 $instances++ .
1659 'cache_application'
1662 return $this->lockidentifier;
1666 * Fixes the instance up after a clone.
1668 public function __clone() {
1669 // Force a new idenfitier.
1670 $this->lockidentifier = null;
1674 * Acquires a lock on the given key.
1676 * This is done automatically if the definition requires it.
1677 * It is recommended to use a definition if you want to have locking although it is possible to do locking without having
1678 * it required by the definition.
1679 * The problem with such an approach is that you cannot ensure that code will consistently use locking. You will need to
1680 * rely on the integrators review skills.
1682 * @param string|int $key The key as given to get|set|delete
1683 * @return bool Always returns true
1684 * @throws moodle_exception If the lock cannot be obtained
1686 public function acquire_lock($key) {
1687 $releaseparent = false;
1688 try {
1689 if ($this->get_loader() !== false) {
1690 $this->get_loader()->acquire_lock($key);
1691 // We need to release this lock later if the lock is not successful.
1692 $releaseparent = true;
1694 $hashedkey = cache_helper::hash_key($key, $this->get_definition());
1695 $before = microtime(true);
1696 if ($this->nativelocking) {
1697 $lock = $this->get_store()->acquire_lock($hashedkey, $this->get_identifier());
1698 } else {
1699 $this->ensure_cachelock_available();
1700 $lock = $this->cachelockinstance->lock($hashedkey, $this->get_identifier());
1702 $after = microtime(true);
1703 if ($lock) {
1704 $this->locks[$hashedkey] = $lock;
1705 if (MDL_PERF || $this->perfdebug) {
1706 \core\lock\timing_wrapper_lock_factory::record_lock_data($after, $before,
1707 $this->get_definition()->get_id(), $hashedkey, $lock, $this->get_identifier() . $hashedkey);
1709 $releaseparent = false;
1710 return true;
1711 } else {
1712 throw new moodle_exception('ex_unabletolock', 'cache', '', null,
1713 'store: ' . get_class($this->get_store()) . ', lock: ' . $hashedkey);
1715 } finally {
1716 // Release the parent lock if we acquired it, then threw an exception.
1717 if ($releaseparent) {
1718 $this->get_loader()->release_lock($key);
1724 * Checks if this cache has a lock on the given key.
1726 * @param string|int $key The key as given to get|set|delete
1727 * @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
1728 * someone else has the lock.
1730 public function check_lock_state($key) {
1731 $key = cache_helper::hash_key($key, $this->get_definition());
1732 if (!empty($this->locks[$key])) {
1733 return true; // Shortcut to save having to make a call to the cache store if the lock is held by this process.
1735 if ($this->nativelocking) {
1736 return $this->get_store()->check_lock_state($key, $this->get_identifier());
1737 } else {
1738 $this->ensure_cachelock_available();
1739 return $this->cachelockinstance->check_state($key, $this->get_identifier());
1744 * Releases the lock this cache has on the given key
1746 * @param string|int $key
1747 * @return bool True if the operation succeeded, false otherwise.
1749 public function release_lock($key) {
1750 $loaderkey = $key;
1751 $key = cache_helper::hash_key($key, $this->get_definition());
1752 if ($this->nativelocking) {
1753 $released = $this->get_store()->release_lock($key, $this->get_identifier());
1754 } else {
1755 $this->ensure_cachelock_available();
1756 $released = $this->cachelockinstance->unlock($key, $this->get_identifier());
1758 if ($released && array_key_exists($key, $this->locks)) {
1759 unset($this->locks[$key]);
1760 if (MDL_PERF || $this->perfdebug) {
1761 \core\lock\timing_wrapper_lock_factory::record_lock_released_data($this->get_identifier() . $key);
1764 if ($this->get_loader() !== false) {
1765 $this->get_loader()->release_lock($loaderkey);
1767 return $released;
1771 * Ensure that the dedicated lock store is ready to go.
1773 * This should only happen if the cache store doesn't natively support it.
1775 protected function ensure_cachelock_available() {
1776 if ($this->cachelockinstance === null) {
1777 $this->cachelockinstance = cache_helper::get_cachelock_for_store($this->get_store());
1782 * Sends a key => value pair to the cache.
1784 * <code>
1785 * // This code will add four entries to the cache, one for each url.
1786 * $cache->set('main', 'http://moodle.org');
1787 * $cache->set('docs', 'http://docs.moodle.org');
1788 * $cache->set('tracker', 'http://tracker.moodle.org');
1789 * $cache->set('qa', 'http://qa.moodle.net');
1790 * </code>
1792 * @param string|int $key The key for the data being requested.
1793 * @param int $version Version number
1794 * @param mixed $data The data to set against the key.
1795 * @param bool $setparents If true, sets all parent loaders, otherwise only this one
1796 * @return bool True on success, false otherwise.
1797 * @throws coding_exception If a required lock has not beeen acquired
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());
1804 return parent::set_implementation($key, $version, $data, $setparents);
1808 * Sends several key => value pairs to the cache.
1810 * Using this function comes with potential performance implications.
1811 * Not all cache stores will support get_many/set_many operations and in order to replicate this functionality will call
1812 * the equivalent singular method for each item provided.
1813 * This should not deter you from using this function as there is a performance benefit in situations where the cache store
1814 * does support it, but you should be aware of this fact.
1816 * <code>
1817 * // This code will add four entries to the cache, one for each url.
1818 * $cache->set_many(array(
1819 * 'main' => 'http://moodle.org',
1820 * 'docs' => 'http://docs.moodle.org',
1821 * 'tracker' => 'http://tracker.moodle.org',
1822 * 'qa' => ''http://qa.moodle.net'
1823 * ));
1824 * </code>
1826 * @param array $keyvaluearray An array of key => value pairs to send to the cache.
1827 * @return int The number of items successfully set. It is up to the developer to check this matches the number of items.
1828 * ... if they care that is.
1829 * @throws coding_exception If a required lock has not beeen acquired
1831 public function set_many(array $keyvaluearray) {
1832 if ($this->requirelockingbeforewrite) {
1833 foreach ($keyvaluearray as $key => $value) {
1834 if (!$this->check_lock_state($key)) {
1835 throw new coding_exception('Attempted to set cache key "' . $key . '" without a lock. '
1836 . 'Locking before writes is required for ' . $this->get_definition()->get_id());
1840 return parent::set_many($keyvaluearray);
1844 * Delete the given key from the cache.
1846 * @param string|int $key The key to delete.
1847 * @param bool $recurse When set to true the key will also be deleted from all stacked cache loaders and their stores.
1848 * This happens by default and ensure that all the caches are consistent. It is NOT recommended to change this.
1849 * @return bool True of success, false otherwise.
1850 * @throws coding_exception If a required lock has not beeen acquired
1852 public function delete($key, $recurse = true) {
1853 if ($this->requirelockingbeforewrite && !$this->check_lock_state($key)) {
1854 throw new coding_exception('Attempted to delete cache key "' . $key . '" without a lock. '
1855 . 'Locking before writes is required for ' . $this->get_definition()->get_id());
1857 return parent::delete($key, $recurse);
1861 * Delete all of the given keys from the cache.
1863 * @param array $keys The key to delete.
1864 * @param bool $recurse When set to true the key will also be deleted from all stacked cache loaders and their stores.
1865 * This happens by default and ensure that all the caches are consistent. It is NOT recommended to change this.
1866 * @return int The number of items successfully deleted.
1867 * @throws coding_exception If a required lock has not beeen acquired
1869 public function delete_many(array $keys, $recurse = true) {
1870 if ($this->requirelockingbeforewrite) {
1871 foreach ($keys as $key) {
1872 if (!$this->check_lock_state($key)) {
1873 throw new coding_exception('Attempted to delete cache key "' . $key . '" without a lock. '
1874 . 'Locking before writes is required for ' . $this->get_definition()->get_id());
1878 return parent::delete_many($keys, $recurse);
1883 * A session cache.
1885 * This class is used for session caches returned by the cache::make methods.
1887 * It differs from the application loader in a couple of noteable ways:
1888 * 1. Sessions are always expected to exist.
1889 * Because of this we don't ever use the static acceleration array.
1890 * 2. Session data for a loader instance (store + definition) is consolidate into a
1891 * single array for storage within the store.
1892 * Along with this we embed a lastaccessed time with the data. This way we can
1893 * check sessions for a last access time.
1894 * 3. Session stores are required to support key searching and must
1895 * implement cache_is_searchable. This ensures stores used for the cache can be
1896 * targetted for garbage collection of session data.
1898 * This cache class should never be interacted with directly. Instead you should always use the cache::make methods.
1899 * It is technically possible to call those methods through this class however there is no guarantee that you will get an
1900 * instance of this class back again.
1902 * @todo we should support locking in the session as well. Should be pretty simple to set up.
1904 * @internal don't use me directly.
1905 * @method cache_store|cache_is_searchable get_store() Returns the cache store which must implement both cache_is_searchable.
1907 * @package core
1908 * @category cache
1909 * @copyright 2012 Sam Hemelryk
1910 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1912 class cache_session extends cache {
1914 * The user the session has been established for.
1915 * @var int
1917 protected static $loadeduserid = null;
1920 * The userid this cache is currently using.
1921 * @var int
1923 protected $currentuserid = null;
1926 * The session id we are currently using.
1927 * @var array
1929 protected $sessionid = null;
1932 * The session data for the above session id.
1933 * @var array
1935 protected $session = null;
1938 * Constant used to prefix keys.
1940 const KEY_PREFIX = 'sess_';
1943 * This is the key used to track last access.
1945 const LASTACCESS = '__lastaccess__';
1948 * Override the cache::construct method.
1950 * This function gets overriden so that we can process any invalidation events if need be.
1951 * If the definition doesn't have any invalidation events then this occurs exactly as it would for the cache class.
1952 * Otherwise we look at the last invalidation time and then check the invalidation data for events that have occured
1953 * between then now.
1955 * You should not call this method from your code, instead you should use the cache::make methods.
1957 * @param cache_definition $definition
1958 * @param cache_store $store
1959 * @param cache_loader|cache_data_source $loader
1961 public function __construct(cache_definition $definition, cache_store $store, $loader = null) {
1962 // First up copy the loadeduserid to the current user id.
1963 $this->currentuserid = self::$loadeduserid;
1964 $this->set_session_id();
1965 parent::__construct($definition, $store, $loader);
1967 // This will trigger check tracked user. If this gets removed a call to that will need to be added here in its place.
1968 $this->set(self::LASTACCESS, cache::now());
1970 $this->handle_invalidation_events();
1974 * Sets the session id for the loader.
1976 protected function set_session_id() {
1977 $this->sessionid = preg_replace('#[^a-zA-Z0-9_]#', '_', session_id());
1981 * Returns the prefix used for all keys.
1982 * @return string
1984 protected function get_key_prefix() {
1985 return 'u'.$this->currentuserid.'_'.$this->sessionid;
1989 * Parses the key turning it into a string (or array is required) suitable to be passed to the cache store.
1991 * This function is called for every operation that uses keys. For this reason we use this function to also check
1992 * that the current user is the same as the user who last used this cache.
1994 * On top of that if prepends the string 'sess_' to the start of all keys. The _ ensures things are easily identifiable.
1996 * @param string|int $key As passed to get|set|delete etc.
1997 * @return string|array String unless the store supports multi-identifiers in which case an array if returned.
1999 protected function parse_key($key) {
2000 $prefix = $this->get_key_prefix();
2001 if ($key === self::LASTACCESS) {
2002 return $key.$prefix;
2004 return $prefix.'_'.parent::parse_key($key);
2008 * Check that this cache instance is tracking the current user.
2010 protected function check_tracked_user() {
2011 if (isset($_SESSION['USER']->id) && $_SESSION['USER']->id !== null) {
2012 // Get the id of the current user.
2013 $new = $_SESSION['USER']->id;
2014 } else {
2015 // No user set up yet.
2016 $new = 0;
2018 if ($new !== self::$loadeduserid) {
2019 // The current user doesn't match the tracked userid for this request.
2020 if (!is_null(self::$loadeduserid)) {
2021 // Purge the data we have for the old user.
2022 // This way we don't bloat the session.
2023 $this->purge();
2025 self::$loadeduserid = $new;
2026 $this->currentuserid = $new;
2027 } else if ($new !== $this->currentuserid) {
2028 // The current user matches the loaded user but not the user last used by this cache.
2029 $this->purge_current_user();
2030 $this->currentuserid = $new;
2035 * Purges the session cache of all data belonging to the current user.
2037 public function purge_current_user() {
2038 $keys = $this->get_store()->find_by_prefix($this->get_key_prefix());
2039 $this->get_store()->delete_many($keys);
2043 * Retrieves the value for the given key from the cache.
2045 * @param string|int $key The key for the data being requested.
2046 * It can be any structure although using a scalar string or int is recommended in the interests of performance.
2047 * In advanced cases an array may be useful such as in situations requiring the multi-key functionality.
2048 * @param int $requiredversion Minimum required version of the data or cache::VERSION_NONE
2049 * @param int $strictness One of IGNORE_MISSING | MUST_EXIST
2050 * @param mixed &$actualversion If specified, will be set to the actual version number retrieved
2051 * @return mixed|false The data from the cache or false if the key did not exist within the cache.
2052 * @throws coding_exception
2054 protected function get_implementation($key, int $requiredversion, int $strictness, &$actualversion = null) {
2055 // Check the tracked user.
2056 $this->check_tracked_user();
2058 // Use parent code.
2059 return parent::get_implementation($key, $requiredversion, $strictness, $actualversion);
2063 * Sends a key => value pair to the cache.
2065 * <code>
2066 * // This code will add four entries to the cache, one for each url.
2067 * $cache->set('main', 'http://moodle.org');
2068 * $cache->set('docs', 'http://docs.moodle.org');
2069 * $cache->set('tracker', 'http://tracker.moodle.org');
2070 * $cache->set('qa', 'http://qa.moodle.net');
2071 * </code>
2073 * @param string|int $key The key for the data being requested.
2074 * It can be any structure although using a scalar string or int is recommended in the interests of performance.
2075 * In advanced cases an array may be useful such as in situations requiring the multi-key functionality.
2076 * @param mixed $data The data to set against the key.
2077 * @return bool True on success, false otherwise.
2079 public function set($key, $data) {
2080 $this->check_tracked_user();
2081 $loader = $this->get_loader();
2082 if ($loader !== false) {
2083 // We have a loader available set it there as well.
2084 // We have to let the loader do its own parsing of data as it may be unique.
2085 $loader->set($key, $data);
2087 if (is_object($data) && $data instanceof cacheable_object) {
2088 $data = new cache_cached_object($data);
2089 } else if (!$this->get_store()->supports_dereferencing_objects() && !is_scalar($data)) {
2090 // If data is an object it will be a reference.
2091 // If data is an array if may contain references.
2092 // We want to break references so that the cache cannot be modified outside of itself.
2093 // Call the function to unreference it (in the best way possible).
2094 $data = $this->unref($data);
2096 // We dont' support native TTL here as we consolidate data for sessions.
2097 if ($this->has_a_ttl() && !$this->store_supports_native_ttl()) {
2098 $data = new cache_ttl_wrapper($data, $this->get_definition()->get_ttl());
2100 $success = $this->get_store()->set($this->parse_key($key), $data);
2101 if ($this->perfdebug) {
2102 cache_helper::record_cache_set($this->get_store(), $this->get_definition(), 1,
2103 $this->get_store()->get_last_io_bytes());
2105 return $success;
2109 * Delete the given key from the cache.
2111 * @param string|int $key The key to delete.
2112 * @param bool $recurse When set to true the key will also be deleted from all stacked cache loaders and their stores.
2113 * This happens by default and ensure that all the caches are consistent. It is NOT recommended to change this.
2114 * @return bool True of success, false otherwise.
2116 public function delete($key, $recurse = true) {
2117 $parsedkey = $this->parse_key($key);
2118 if ($recurse && $this->get_loader() !== false) {
2119 // Delete from the bottom of the stack first.
2120 $this->get_loader()->delete($key, $recurse);
2122 return $this->get_store()->delete($parsedkey);
2126 * Retrieves an array of values for an array of keys.
2128 * Using this function comes with potential performance implications.
2129 * Not all cache stores will support get_many/set_many operations and in order to replicate this functionality will call
2130 * the equivalent singular method for each item provided.
2131 * This should not deter you from using this function as there is a performance benefit in situations where the cache store
2132 * does support it, but you should be aware of this fact.
2134 * @param array $keys The keys of the data being requested.
2135 * Each key can be any structure although using a scalar string or int is recommended in the interests of performance.
2136 * In advanced cases an array may be useful such as in situations requiring the multi-key functionality.
2137 * @param int $strictness One of IGNORE_MISSING or MUST_EXIST.
2138 * @return array An array of key value pairs for the items that could be retrieved from the cache.
2139 * If MUST_EXIST was used and not all keys existed within the cache then an exception will be thrown.
2140 * Otherwise any key that did not exist will have a data value of false within the results.
2141 * @throws coding_exception
2143 public function get_many(array $keys, $strictness = IGNORE_MISSING) {
2144 $this->check_tracked_user();
2145 $parsedkeys = array();
2146 $keymap = array();
2147 foreach ($keys as $key) {
2148 $parsedkey = $this->parse_key($key);
2149 $parsedkeys[$key] = $parsedkey;
2150 $keymap[$parsedkey] = $key;
2152 $result = $this->get_store()->get_many($parsedkeys);
2153 if ($this->perfdebug) {
2154 $readbytes = $this->get_store()->get_last_io_bytes();
2156 $return = array();
2157 $missingkeys = array();
2158 $hasmissingkeys = false;
2159 foreach ($result as $parsedkey => $value) {
2160 $key = $keymap[$parsedkey];
2161 if ($value instanceof cache_ttl_wrapper) {
2162 /* @var cache_ttl_wrapper $value */
2163 if ($value->has_expired()) {
2164 $this->delete($keymap[$parsedkey]);
2165 $value = false;
2166 } else {
2167 $value = $value->data;
2170 if ($value instanceof cache_cached_object) {
2171 /* @var cache_cached_object $value */
2172 $value = $value->restore_object();
2173 } else if (!$this->get_store()->supports_dereferencing_objects() && !is_scalar($value)) {
2174 // If data is an object it will be a reference.
2175 // If data is an array if may contain references.
2176 // We want to break references so that the cache cannot be modified outside of itself.
2177 // Call the function to unreference it (in the best way possible).
2178 $value = $this->unref($value);
2180 $return[$key] = $value;
2181 if ($value === false) {
2182 $hasmissingkeys = true;
2183 $missingkeys[$parsedkey] = $key;
2186 if ($hasmissingkeys) {
2187 // We've got missing keys - we've got to check any loaders or data sources.
2188 $loader = $this->get_loader();
2189 $datasource = $this->get_datasource();
2190 if ($loader !== false) {
2191 foreach ($loader->get_many($missingkeys) as $key => $value) {
2192 if ($value !== false) {
2193 $return[$key] = $value;
2194 unset($missingkeys[$parsedkeys[$key]]);
2198 $hasmissingkeys = count($missingkeys) > 0;
2199 if ($datasource !== false && $hasmissingkeys) {
2200 // We're still missing keys but we've got a datasource.
2201 foreach ($datasource->load_many_for_cache($missingkeys) as $key => $value) {
2202 if ($value !== false) {
2203 $return[$key] = $value;
2204 unset($missingkeys[$parsedkeys[$key]]);
2207 $hasmissingkeys = count($missingkeys) > 0;
2210 if ($hasmissingkeys && $strictness === MUST_EXIST) {
2211 throw new coding_exception('Requested key did not exist in any cache stores and could not be loaded.');
2213 if ($this->perfdebug) {
2214 $hits = 0;
2215 $misses = 0;
2216 foreach ($return as $value) {
2217 if ($value === false) {
2218 $misses++;
2219 } else {
2220 $hits++;
2223 cache_helper::record_cache_hit($this->get_store(), $this->get_definition(), $hits, $readbytes);
2224 cache_helper::record_cache_miss($this->get_store(), $this->get_definition(), $misses);
2226 return $return;
2231 * Delete all of the given keys from the cache.
2233 * @param array $keys The key to delete.
2234 * @param bool $recurse When set to true the key will also be deleted from all stacked cache loaders and their stores.
2235 * This happens by default and ensure that all the caches are consistent. It is NOT recommended to change this.
2236 * @return int The number of items successfully deleted.
2238 public function delete_many(array $keys, $recurse = true) {
2239 $parsedkeys = array_map(array($this, 'parse_key'), $keys);
2240 if ($recurse && $this->get_loader() !== false) {
2241 // Delete from the bottom of the stack first.
2242 $this->get_loader()->delete_many($keys, $recurse);
2244 return $this->get_store()->delete_many($parsedkeys);
2248 * Sends several key => value pairs to the cache.
2250 * Using this function comes with potential performance implications.
2251 * Not all cache stores will support get_many/set_many operations and in order to replicate this functionality will call
2252 * the equivalent singular method for each item provided.
2253 * This should not deter you from using this function as there is a performance benefit in situations where the cache store
2254 * does support it, but you should be aware of this fact.
2256 * <code>
2257 * // This code will add four entries to the cache, one for each url.
2258 * $cache->set_many(array(
2259 * 'main' => 'http://moodle.org',
2260 * 'docs' => 'http://docs.moodle.org',
2261 * 'tracker' => 'http://tracker.moodle.org',
2262 * 'qa' => ''http://qa.moodle.net'
2263 * ));
2264 * </code>
2266 * @param array $keyvaluearray An array of key => value pairs to send to the cache.
2267 * @return int The number of items successfully set. It is up to the developer to check this matches the number of items.
2268 * ... if they care that is.
2270 public function set_many(array $keyvaluearray) {
2271 $this->check_tracked_user();
2272 $loader = $this->get_loader();
2273 if ($loader !== false) {
2274 // We have a loader available set it there as well.
2275 // We have to let the loader do its own parsing of data as it may be unique.
2276 $loader->set_many($keyvaluearray);
2278 $data = array();
2279 $definitionid = $this->get_definition()->get_ttl();
2280 $simulatettl = $this->has_a_ttl() && !$this->store_supports_native_ttl();
2281 foreach ($keyvaluearray as $key => $value) {
2282 if (is_object($value) && $value instanceof cacheable_object) {
2283 $value = new cache_cached_object($value);
2284 } else if (!$this->get_store()->supports_dereferencing_objects() && !is_scalar($value)) {
2285 // If data is an object it will be a reference.
2286 // If data is an array if may contain references.
2287 // We want to break references so that the cache cannot be modified outside of itself.
2288 // Call the function to unreference it (in the best way possible).
2289 $value = $this->unref($value);
2291 if ($simulatettl) {
2292 $value = new cache_ttl_wrapper($value, $definitionid);
2294 $data[$key] = array(
2295 'key' => $this->parse_key($key),
2296 'value' => $value
2299 $successfullyset = $this->get_store()->set_many($data);
2300 if ($this->perfdebug && $successfullyset) {
2301 cache_helper::record_cache_set($this->get_store(), $this->get_definition(), $successfullyset,
2302 $this->get_store()->get_last_io_bytes());
2304 return $successfullyset;
2308 * Purges the cache store, and loader if there is one.
2310 * @return bool True on success, false otherwise
2312 public function purge() {
2313 $this->get_store()->purge();
2314 if ($this->get_loader()) {
2315 $this->get_loader()->purge();
2317 return true;
2321 * Test is a cache has a key.
2323 * The use of the has methods is strongly discouraged. In a high load environment the cache may well change between the
2324 * test and any subsequent action (get, set, delete etc).
2325 * Instead it is recommended to write your code in such a way they it performs the following steps:
2326 * <ol>
2327 * <li>Attempt to retrieve the information.</li>
2328 * <li>Generate the information.</li>
2329 * <li>Attempt to set the information</li>
2330 * </ol>
2332 * Its also worth mentioning that not all stores support key tests.
2333 * For stores that don't support key tests this functionality is mimicked by using the equivalent get method.
2334 * Just one more reason you should not use these methods unless you have a very good reason to do so.
2336 * @param string|int $key
2337 * @param bool $tryloadifpossible If set to true, the cache doesn't contain the key, and there is another cache loader or
2338 * data source then the code will try load the key value from the next item in the chain.
2339 * @return bool True if the cache has the requested key, false otherwise.
2341 public function has($key, $tryloadifpossible = false) {
2342 $this->check_tracked_user();
2343 $parsedkey = $this->parse_key($key);
2344 $store = $this->get_store();
2345 if ($this->has_a_ttl() && !$this->store_supports_native_ttl()) {
2346 // The data has a TTL and the store doesn't support it natively.
2347 // We must fetch the data and expect a ttl wrapper.
2348 $data = $store->get($parsedkey);
2349 $has = ($data instanceof cache_ttl_wrapper && !$data->has_expired());
2350 } else if (!$this->store_supports_key_awareness()) {
2351 // The store doesn't support key awareness, get the data and check it manually... puke.
2352 // Either no TTL is set of the store supports its handling natively.
2353 $data = $store->get($parsedkey);
2354 $has = ($data !== false);
2355 } else {
2356 // The store supports key awareness, this is easy!
2357 // Either no TTL is set of the store supports its handling natively.
2358 /* @var cache_store|cache_is_key_aware $store */
2359 $has = $store->has($parsedkey);
2361 if (!$has && $tryloadifpossible) {
2362 $result = null;
2363 if ($this->get_loader() !== false) {
2364 $result = $this->get_loader()->get($parsedkey);
2365 } else if ($this->get_datasource() !== null) {
2366 $result = $this->get_datasource()->load_for_cache($key);
2368 $has = ($result !== null);
2369 if ($has) {
2370 $this->set($key, $result);
2373 return $has;
2377 * Test is a cache has all of the given keys.
2379 * It is strongly recommended to avoid the use of this function if not absolutely required.
2380 * In a high load environment the cache may well change between the test and any subsequent action (get, set, delete etc).
2382 * Its also worth mentioning that not all stores support key tests.
2383 * For stores that don't support key tests this functionality is mimicked by using the equivalent get method.
2384 * Just one more reason you should not use these methods unless you have a very good reason to do so.
2386 * @param array $keys
2387 * @return bool True if the cache has all of the given keys, false otherwise.
2389 public function has_all(array $keys) {
2390 $this->check_tracked_user();
2391 if (($this->has_a_ttl() && !$this->store_supports_native_ttl()) || !$this->store_supports_key_awareness()) {
2392 foreach ($keys as $key) {
2393 if (!$this->has($key)) {
2394 return false;
2397 return true;
2399 // The cache must be key aware and if support native ttl if it a ttl is set.
2400 /* @var cache_store|cache_is_key_aware $store */
2401 $store = $this->get_store();
2402 return $store->has_all(array_map(array($this, 'parse_key'), $keys));
2406 * Test if a cache has at least one of the given keys.
2408 * It is strongly recommended to avoid the use of this function if not absolutely required.
2409 * In a high load environment the cache may well change between the test and any subsequent action (get, set, delete etc).
2411 * Its also worth mentioning that not all stores support key tests.
2412 * For stores that don't support key tests this functionality is mimicked by using the equivalent get method.
2413 * Just one more reason you should not use these methods unless you have a very good reason to do so.
2415 * @param array $keys
2416 * @return bool True if the cache has at least one of the given keys
2418 public function has_any(array $keys) {
2419 if (($this->has_a_ttl() && !$this->store_supports_native_ttl()) || !$this->store_supports_key_awareness()) {
2420 foreach ($keys as $key) {
2421 if ($this->has($key)) {
2422 return true;
2425 return false;
2427 /* @var cache_store|cache_is_key_aware $store */
2428 $store = $this->get_store();
2429 return $store->has_any(array_map(array($this, 'parse_key'), $keys));
2433 * The session loader never uses static acceleration.
2434 * Instead it stores things in the static $session variable. Shared between all session loaders.
2436 * @return bool
2438 protected function use_static_acceleration() {
2439 return false;
2444 * An request cache.
2446 * This class is used for request caches returned by the cache::make methods.
2448 * This cache class should never be interacted with directly. Instead you should always use the cache::make methods.
2449 * It is technically possible to call those methods through this class however there is no guarantee that you will get an
2450 * instance of this class back again.
2452 * @internal don't use me directly.
2454 * @package core
2455 * @category cache
2456 * @copyright 2012 Sam Hemelryk
2457 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2459 class cache_request extends cache {
2460 // This comment appeases code pre-checker ;) !