2 // This file is part of Moodle - http://moodle.org/
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.
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/>.
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.
25 * @copyright 2012 Sam Hemelryk
26 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
29 defined('MOODLE_INTERNAL') ||
die();
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
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
{
46 * @var int Constant for cache entries that do not have a version number
48 const VERSION_NONE
= -1;
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
56 protected static $now;
59 * A purge token used to distinguish between multiple cache purges in the same second.
60 * This is in the format <microtime>-<random string>.
64 protected static $purgetoken;
67 * The definition used when loading this cache if there was one.
68 * @var cache_definition
70 private $definition = false;
73 * The cache store that this loader will make use of.
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;
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;
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.
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.
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.
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.
122 private $staticaccelerationarray = array();
125 * The number of items in the static acceleration array. Avoids count calls like you wouldn't believe.
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.
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.
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.
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.
162 protected $storetype = 'unknown';
165 * Gets set to true if we want to collect performance information about the cache API.
168 protected $perfdebug = false;
171 * Determines if this loader is a sub loader, not the top of the chain.
174 protected $subloader = false;
177 * Gets set to true if the cache writes (set|delete) must have a manual lock created first.
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) {
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) {
289 $this->subloader
= true;
290 // Subloaders should not keep static acceleration data.
291 $this->staticacceleration
= false;
292 $this->staticaccelerationsize
= false;
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()) {
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());
342 } else if ($lastinvalidation == self
::get_purge_token()) {
343 // The current purge request has already been fully handled by this cache.
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:
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
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());
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.
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') {
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
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
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');
468 // No version checks, so version is always correct.
472 // If there's no result, obviously it doesn't meet the required version.
473 if (!cache_helper
::result_found($result)) {
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) {
483 // The version meets the requirement.
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
) {
510 $actualversion = $result->version
;
511 return $result->data
;
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.
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);
540 if (!$validversion) {
541 // If the result was too old, don't use it.
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
;
556 $ttlconsider = $result;
558 if ($ttlconsider instanceof cache_ttl_wrapper
) {
559 if ($ttlconsider->has_expired()) {
560 $this->store
->delete($parsedkey);
562 } else if ($requiredversion === self
::VERSION_NONE
) {
563 // Use the data inside the TTL wrapper as the result.
564 $result = $ttlconsider->data
;
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);
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);
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) {
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);
630 if ($requiredversion === self
::VERSION_NONE
) {
631 $this->set_implementation($key, self
::VERSION_NONE
, $result, false);
633 $this->set_implementation($key, $actualversion, $result, false);
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);
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()) {
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);
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)) {
741 $resultmissing = $this->loader
->get_many($missingkeys);
743 $resultmissing = $this->datasource
->load_many_for_cache($missingkeys);
745 foreach ($resultmissing as $key => $value) {
746 $result[$keysparsed[$key]] = $value;
749 if (!empty($this->requirelockingbeforewrite
)) {
750 $this->acquire_lock($key);
753 if ($value !== false) {
754 $this->set($key, $value);
758 $this->release_lock($key);
762 unset($resultmissing);
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;
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
) {
793 foreach ($fullresult as $value) {
794 if ($value === false) {
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!
809 * Sends a key => value pair to the cache.
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');
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);
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);
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());
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()) {
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);
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)) {
945 } else if (is_array($value) ||
$value instanceof stdClass ||
$value instanceof Traversable
) {
947 // Skrew it, mega-deep object, developer you suck, we're just going to serialise.
950 foreach ($value as $key => $subvalue) {
951 if ($this->requires_serialisation($subvalue, $depth++
)) {
956 // Its not scalar, array, or stdClass so we'll need to serialise.
961 * Creates a reference free clone of the given value.
963 * @param mixed $value
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);
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.
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'
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);
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);
1026 $value = new cache_ttl_wrapper($value, $this->definition
->get_ttl());
1028 $data[$key] = array(
1029 'key' => $this->parse_key($key),
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:
1048 * <li>Attempt to retrieve the information.</li>
1049 * <li>Generate the information.</li>
1050 * <li>Attempt to set the information</li>
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.
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);
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);
1092 $this->set($key, $result);
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)) {
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)) {
1147 if ($this->use_static_acceleration()) {
1148 foreach ($keys as $id => $key) {
1149 if ($this->static_acceleration_has($key)) {
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();
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;
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.
1236 protected function has_a_ttl() {
1237 return $this->hasattl
;
1241 * Returns true if the cache store supports native ttl.
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.
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.
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.
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
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])) {
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])) {
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);
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;
1399 if ($this->perfdebug
) {
1400 cache_helper
::record_cache_miss(cache_store
::STATIC_ACCEL
, $this->definition
);
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
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;
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
--;
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
--;
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
1493 * @param bool $float Whether to use floating precision accuracy.
1496 public static function now($float = false) {
1497 if (self
::$now === null) {
1498 self
::$now = microtime(true);
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.
1516 public static function get_purge_token($reset = false) {
1517 if (self
::$purgetoken === null ||
$reset) {
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
1538 public static function compare_purge_tokens($tokena, $tokenb) {
1539 if ($tokena === $tokenb) {
1540 // There is an exact match.
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.
1554 // Token A is older.
1560 * Subclasses may support purging cache of all data belonging to the
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.
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
{
1588 * This is used to ensure the lock belongs to the cache instance + definition + user.
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.
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.
1606 protected $requirelocking = false;
1609 * Gets set to true if the cache writes (set|delete) must have a manual lock created first
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.
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.
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() .
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;
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());
1699 $this->ensure_cachelock_available();
1700 $lock = $this->cachelockinstance
->lock($hashedkey, $this->get_identifier());
1702 $after = microtime(true);
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;
1712 throw new moodle_exception('ex_unabletolock', 'cache', '', null,
1713 'store: ' . get_class($this->get_store()) . ', lock: ' . $hashedkey);
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());
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) {
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());
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);
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.
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');
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.
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'
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);
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.
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.
1917 protected static $loadeduserid = null;
1920 * The userid this cache is currently using.
1923 protected $currentuserid = null;
1926 * The session id we are currently using.
1929 protected $sessionid = null;
1932 * The session data for the above session id.
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
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.
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
;
2015 // No user set up yet.
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.
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();
2059 return parent
::get_implementation($key, $requiredversion, $strictness, $actualversion);
2063 * Sends a key => value pair to the cache.
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');
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());
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();
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();
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]);
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
) {
2216 foreach ($return as $value) {
2217 if ($value === false) {
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);
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.
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'
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);
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);
2292 $value = new cache_ttl_wrapper($value, $definitionid);
2294 $data[$key] = array(
2295 'key' => $this->parse_key($key),
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();
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:
2327 * <li>Attempt to retrieve the information.</li>
2328 * <li>Generate the information.</li>
2329 * <li>Attempt to set the information</li>
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);
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) {
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);
2370 $this->set($key, $result);
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)) {
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)) {
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.
2438 protected function use_static_acceleration() {
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.
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 ;) !