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 * We need a timestamp to use within the cache API.
47 * This stamp needs to be used for all ttl and time based operations to ensure that we don't end up with
51 protected static $now;
54 * A purge token used to distinguish between multiple cache purges in the same second.
55 * This is in the format <microtime>-<random string>.
59 protected static $purgetoken;
62 * The definition used when loading this cache if there was one.
63 * @var cache_definition
65 private $definition = false;
68 * The cache store that this loader will make use of.
74 * The next cache loader in the chain if there is one.
75 * If a cache request misses for the store belonging to this loader then the loader
76 * stored here will be checked next.
77 * If there is a loader here then $datasource must be false.
78 * @var cache_loader|false
80 private $loader = false;
83 * The data source to use if we need to load data (because if doesn't exist in the cache store).
84 * If there is a data source here then $loader above must be false.
85 * @var cache_data_source|false
87 private $datasource = false;
90 * Used to quickly check if the store supports key awareness.
91 * This is set when the cache is initialised and is used to speed up processing.
94 private $supportskeyawareness = null;
97 * Used to quickly check if the store supports ttl natively.
98 * This is set when the cache is initialised and is used to speed up processing.
101 private $supportsnativettl = null;
104 * Gets set to true if the cache is going to be using a static array for acceleration.
105 * The array statically caches items used during the lifetime of the request. This greatly speeds up interaction
106 * with the cache in areas where it will be repetitively hit for the same information such as with strings.
107 * There are several other variables to control how this static acceleration array works.
110 private $staticacceleration = false;
113 * The static acceleration array.
114 * Items will be stored in this cache as they were provided. This ensure there is no unnecessary processing taking place.
117 private $staticaccelerationarray = array();
120 * The number of items in the static acceleration array. Avoids count calls like you wouldn't believe.
123 private $staticaccelerationcount = 0;
126 * An array containing just the keys being used in the static acceleration array.
127 * This seems redundant perhaps but is used when managing the size of the static acceleration array.
128 * Items are added to the end of the array and the when we need to reduce the size of the cache we use the
129 * key that is first on this array.
132 private $staticaccelerationkeys = array();
135 * The maximum size of the static acceleration array.
137 * If set to false there is no max size.
138 * Caches that make use of static acceleration should seriously consider setting this to something reasonably small, but
139 * still large enough to offset repetitive calls.
143 private $staticaccelerationsize = false;
146 * Gets set to true during initialisation if the definition is making use of a ttl.
147 * Used to speed up processing.
150 private $hasattl = false;
153 * Gets set to the class name of the store during initialisation. This is used several times in the cache class internally
154 * and having it here helps speed up processing.
157 protected $storetype = 'unknown';
160 * Gets set to true if we want to collect performance information about the cache API.
163 protected $perfdebug = false;
166 * Determines if this loader is a sub loader, not the top of the chain.
169 protected $subloader = false;
172 * Creates a new cache instance for a pre-defined definition.
174 * @param string $component The component for the definition
175 * @param string $area The area for the definition
176 * @param array $identifiers Any additional identifiers that should be provided to the definition.
177 * @param string $unused Used to be datasourceaggregate but that was removed and this is now unused.
178 * @return cache_application|cache_session|cache_store
180 public static function make($component, $area, array $identifiers = array(), $unused = null) {
181 $factory = cache_factory
::instance();
182 return $factory->create_cache_from_definition($component, $area, $identifiers);
186 * Creates a new cache instance based upon the given params.
188 * @param int $mode One of cache_store::MODE_*
189 * @param string $component The component this cache relates to.
190 * @param string $area The area this cache relates to.
191 * @param array $identifiers Any additional identifiers that should be provided to the definition.
192 * @param array $options An array of options, available options are:
193 * - simplekeys : Set to true if the keys you will use are a-zA-Z0-9_
194 * - simpledata : Set to true if the type of the data you are going to store is scalar, or an array of scalar vars
195 * - staticacceleration : If set to true the cache will hold onto data passing through it.
196 * - staticaccelerationsize : The max size for the static acceleration array.
197 * @return cache_application|cache_session|cache_store
199 public static function make_from_params($mode, $component, $area, array $identifiers = array(), array $options = array()) {
200 $factory = cache_factory
::instance();
201 return $factory->create_cache_from_params($mode, $component, $area, $identifiers, $options);
205 * Constructs a new cache instance.
207 * You should not call this method from your code, instead you should use the cache::make methods.
209 * This method is public so that the cache_factory is able to instantiate cache instances.
210 * Ideally we would make this method protected and expose its construction to the factory method internally somehow.
211 * The factory class is responsible for this in order to centralise the storage of instances once created. This way if needed
212 * we can force a reset of the cache API (used during unit testing).
214 * @param cache_definition $definition The definition for the cache instance.
215 * @param cache_store $store The store that cache should use.
216 * @param cache_loader|cache_data_source $loader The next loader in the chain or the data source if there is one and there
217 * are no other cache_loaders in the chain.
219 public function __construct(cache_definition
$definition, cache_store
$store, $loader = null) {
221 $this->definition
= $definition;
222 $this->store
= $store;
223 $this->storetype
= get_class($store);
224 $this->perfdebug
= (!empty($CFG->perfdebug
) and $CFG->perfdebug
> 7);
225 if ($loader instanceof cache_loader
) {
226 $this->set_loader($loader);
227 } else if ($loader instanceof cache_data_source
) {
228 $this->set_data_source($loader);
230 $this->definition
->generate_definition_hash();
231 $this->staticacceleration
= $this->definition
->use_static_acceleration();
232 if ($this->staticacceleration
) {
233 $this->staticaccelerationsize
= $this->definition
->get_static_acceleration_size();
235 $this->hasattl
= ($this->definition
->get_ttl() > 0);
239 * Set the loader for this cache.
241 * @param cache_loader $loader
243 protected function set_loader(cache_loader
$loader): void
{
244 $this->loader
= $loader;
246 // Mark the loader as a sub (chained) loader.
247 $this->loader
->set_is_sub_loader(true);
251 * Set the data source for this cache.
253 * @param cache_data_source $datasource
255 protected function set_data_source(cache_data_source
$datasource): void
{
256 $this->datasource
= $datasource;
260 * Used to inform the loader of its state as a sub loader, or as the top of the chain.
262 * This is important as it ensures that we do not have more than one loader keeping static acceleration data.
263 * Subloaders need to be "pure" loaders in the sense that they are used to store and retrieve information from stores or the
264 * next loader/data source in the chain.
265 * Nothing fancy, nothing flash.
267 * @param bool $setting
269 protected function set_is_sub_loader($setting = true) {
271 $this->subloader
= true;
272 // Subloaders should not keep static acceleration data.
273 $this->staticacceleration
= false;
274 $this->staticaccelerationsize
= false;
276 $this->subloader
= true;
277 $this->staticacceleration
= $this->definition
->use_static_acceleration();
278 if ($this->staticacceleration
) {
279 $this->staticaccelerationsize
= $this->definition
->get_static_acceleration_size();
285 * Alters the identifiers that have been provided to the definition.
287 * This is an advanced method and should not be used unless really needed.
288 * It allows the developer to slightly alter the definition without having to re-establish the cache.
289 * It will cause more processing as the definition will need to clear and reprepare some of its properties.
291 * @param array $identifiers
293 public function set_identifiers(array $identifiers) {
294 if ($this->definition
->set_identifiers($identifiers)) {
295 // As static acceleration uses input keys and not parsed keys
296 // it much be cleared when the identifier set is changed.
297 $this->staticaccelerationarray
= array();
298 if ($this->staticaccelerationsize
!== false) {
299 $this->staticaccelerationkeys
= array();
300 $this->staticaccelerationcount
= 0;
306 * Process any outstanding invalidation events for the cache we are registering,
308 * Identifiers and event invalidation are not compatible with each other at this time.
309 * As a result the cache does not need to consider identifiers when working out what to invalidate.
311 protected function handle_invalidation_events() {
312 if (!$this->definition
->has_invalidation_events()) {
316 // Each cache stores the current 'lastinvalidation' value within the cache itself.
317 $lastinvalidation = $this->get('lastinvalidation');
318 if ($lastinvalidation === false) {
319 // There is currently no value for the lastinvalidation token, therefore the token is not set, and there
320 // can be nothing to invalidate.
321 // Set the lastinvalidation value to the current purge token and return early.
322 $this->set('lastinvalidation', self
::get_purge_token());
324 } else if ($lastinvalidation == self
::get_purge_token()) {
325 // The current purge request has already been fully handled by this cache.
330 * Now that the whole cache check is complete, we check the meaning of any specific cache invalidation events.
331 * These are stored in the core/eventinvalidation cache as an multi-dimensinoal array in the form:
334 * keyname => purgetoken,
338 * The 'keyname' value is used to delete a specific key in the cache.
339 * If the keyname is set to the special value 'purged', then the whole cache is purged instead.
341 * The 'purgetoken' is the token that this key was last purged.
342 * a) If the purgetoken matches the last invalidation, then the key/cache is not purged.
343 * b) If the purgetoken is newer than the last invalidation, then the key/cache is not purged.
344 * c) If the purge token is older than the last invalidation, or it has a different token component, then the
347 * Option b should not happen under normal operation, but may happen in race condition whereby a long-running
348 * request's cache is cleared in another process during that request, and prior to that long-running request
349 * creating the cache. In such a condition, it would be incorrect to clear that cache.
351 $cache = self
::make('core', 'eventinvalidation');
352 $events = $cache->get_many($this->definition
->get_invalidation_events());
356 // Iterate the returned data for the events.
357 foreach ($events as $event => $keys) {
358 if ($keys === false) {
359 // No data to be invalidated yet.
363 // Look at each key and check the timestamp.
364 foreach ($keys as $key => $purgetoken) {
365 // If the timestamp of the event is more than or equal to the last invalidation (happened between the last
366 // invalidation and now), then we need to invaliate the key.
367 if (self
::compare_purge_tokens($purgetoken, $lastinvalidation) > 0) {
368 if ($key === 'purged') {
379 } else if (!empty($todelete)) {
380 $todelete = array_unique($todelete);
381 $this->delete_many($todelete);
383 // Set the time of the last invalidation.
384 if ($purgeall ||
!empty($todelete)) {
385 $this->set('lastinvalidation', self
::get_purge_token(true));
390 * Retrieves the value for the given key from the cache.
392 * @param string|int $key The key for the data being requested.
393 * It can be any structure although using a scalar string or int is recommended in the interests of performance.
394 * In advanced cases an array may be useful such as in situations requiring the multi-key functionality.
395 * @param int $strictness One of IGNORE_MISSING | MUST_EXIST
396 * @return mixed|false The data from the cache or false if the key did not exist within the cache.
397 * @throws coding_exception
399 public function get($key, $strictness = IGNORE_MISSING
) {
400 // 1. Get it from the static acceleration array if we can (only when it is enabled and it has already been requested/set).
401 $usesstaticacceleration = $this->use_static_acceleration();
403 if ($usesstaticacceleration) {
404 $result = $this->static_acceleration_get($key);
405 if ($result !== false) {
411 $parsedkey = $this->parse_key($key);
413 // 3. Get it from the store. Obviously wasn't in the static acceleration array.
414 $result = $this->store
->get($parsedkey);
415 if ($result !== false) {
416 if ($result instanceof cache_ttl_wrapper
) {
417 if ($result->has_expired()) {
418 $this->store
->delete($parsedkey);
421 $result = $result->data
;
424 if ($usesstaticacceleration) {
425 $this->static_acceleration_set($key, $result);
427 if ($result instanceof cache_cached_object
) {
428 $result = $result->restore_object();
432 // 4. Load if from the loader/datasource if we don't already have it.
433 $setaftervalidation = false;
434 if ($result === false) {
435 if ($this->perfdebug
) {
436 cache_helper
::record_cache_miss($this->store
, $this->definition
);
438 if ($this->loader
!== false) {
439 // We must pass the original (unparsed) key to the next loader in the chain.
440 // The next loader will parse the key as it sees fit. It may be parsed differently
441 // depending upon the capabilities of the store associated with the loader.
442 $result = $this->loader
->get($key);
443 } else if ($this->datasource
!== false) {
444 $result = $this->datasource
->load_for_cache($key);
446 $setaftervalidation = ($result !== false);
447 } else if ($this->perfdebug
) {
448 cache_helper
::record_cache_hit($this->store
, $this->definition
);
450 // 5. Validate strictness.
451 if ($strictness === MUST_EXIST
&& $result === false) {
452 throw new coding_exception('Requested key did not exist in any cache stores and could not be loaded.');
454 // 6. Set it to the store if we got it from the loader/datasource.
455 if ($setaftervalidation) {
456 $this->set($key, $result);
458 // 7. Make sure we don't pass back anything that could be a reference.
459 // We don't want people modifying the data in the cache.
460 if (!$this->store
->supports_dereferencing_objects() && !is_scalar($result)) {
461 // If data is an object it will be a reference.
462 // If data is an array if may contain references.
463 // We want to break references so that the cache cannot be modified outside of itself.
464 // Call the function to unreference it (in the best way possible).
465 $result = $this->unref($result);
471 * Retrieves an array of values for an array of keys.
473 * Using this function comes with potential performance implications.
474 * Not all cache stores will support get_many/set_many operations and in order to replicate this functionality will call
475 * the equivalent singular method for each item provided.
476 * This should not deter you from using this function as there is a performance benefit in situations where the cache store
477 * does support it, but you should be aware of this fact.
479 * @param array $keys The keys of the data being requested.
480 * Each key can be any structure although using a scalar string or int is recommended in the interests of performance.
481 * In advanced cases an array may be useful such as in situations requiring the multi-key functionality.
482 * @param int $strictness One of IGNORE_MISSING or MUST_EXIST.
483 * @return array An array of key value pairs for the items that could be retrieved from the cache.
484 * If MUST_EXIST was used and not all keys existed within the cache then an exception will be thrown.
485 * Otherwise any key that did not exist will have a data value of false within the results.
486 * @throws coding_exception
488 public function get_many(array $keys, $strictness = IGNORE_MISSING
) {
490 $keysparsed = array();
491 $parsedkeys = array();
492 $resultpersist = array();
493 $resultstore = array();
494 $keystofind = array();
496 // First up check the persist cache for each key.
497 $isusingpersist = $this->use_static_acceleration();
498 foreach ($keys as $key) {
499 $pkey = $this->parse_key($key);
500 if (is_array($pkey)) {
501 $pkey = $pkey['key'];
503 $keysparsed[$key] = $pkey;
504 $parsedkeys[$pkey] = $key;
505 $keystofind[$pkey] = $key;
506 if ($isusingpersist) {
507 $value = $this->static_acceleration_get($key);
508 if ($value !== false) {
509 $resultpersist[$pkey] = $value;
510 unset($keystofind[$pkey]);
515 // Next assuming we didn't find all of the keys in the persist cache try loading them from the store.
516 if (count($keystofind)) {
517 $resultstore = $this->store
->get_many(array_keys($keystofind));
518 // Process each item in the result to "unwrap" it.
519 foreach ($resultstore as $key => $value) {
520 if ($value instanceof cache_ttl_wrapper
) {
521 if ($value->has_expired()) {
524 $value = $value->data
;
527 if ($value !== false && $this->use_static_acceleration()) {
528 $this->static_acceleration_set($keystofind[$key], $value);
530 if ($value instanceof cache_cached_object
) {
531 $value = $value->restore_object();
533 $resultstore[$key] = $value;
537 // Merge the result from the persis cache with the results from the store load.
538 $result = $resultpersist +
$resultstore;
539 unset($resultpersist);
542 // Next we need to find any missing values and load them from the loader/datasource next in the chain.
543 $usingloader = ($this->loader
!== false);
544 $usingsource = (!$usingloader && ($this->datasource
!== false));
545 if ($usingloader ||
$usingsource) {
546 $missingkeys = array();
547 foreach ($result as $key => $value) {
548 if ($value === false) {
549 $missingkeys[] = $parsedkeys[$key];
552 if (!empty($missingkeys)) {
554 $resultmissing = $this->loader
->get_many($missingkeys);
556 $resultmissing = $this->datasource
->load_many_for_cache($missingkeys);
558 foreach ($resultmissing as $key => $value) {
559 $result[$keysparsed[$key]] = $value;
560 if ($value !== false) {
561 $this->set($key, $value);
564 unset($resultmissing);
569 // Create an array with the original keys and the found values. This will be what we return.
570 $fullresult = array();
571 foreach ($result as $key => $value) {
572 if (!is_scalar($value)) {
573 // If data is an object it will be a reference.
574 // If data is an array if may contain references.
575 // We want to break references so that the cache cannot be modified outside of itself.
576 // Call the function to unreference it (in the best way possible).
577 $value = $this->unref($value);
579 $fullresult[$parsedkeys[$key]] = $value;
583 // Final step is to check strictness.
584 if ($strictness === MUST_EXIST
) {
585 foreach ($keys as $key) {
586 if (!array_key_exists($key, $fullresult)) {
587 throw new coding_exception('Not all the requested keys existed within the cache stores.');
592 if ($this->perfdebug
) {
595 foreach ($fullresult as $value) {
596 if ($value === false) {
602 cache_helper
::record_cache_hit($this->store
, $this->definition
, $hits);
603 cache_helper
::record_cache_miss($this->store
, $this->definition
, $misses);
606 // Return the result. Phew!
611 * Sends a key => value pair to the cache.
614 * // This code will add four entries to the cache, one for each url.
615 * $cache->set('main', 'http://moodle.org');
616 * $cache->set('docs', 'http://docs.moodle.org');
617 * $cache->set('tracker', 'http://tracker.moodle.org');
618 * $cache->set('qa', 'http://qa.moodle.net');
621 * @param string|int $key The key for the data being requested.
622 * It can be any structure although using a scalar string or int is recommended in the interests of performance.
623 * In advanced cases an array may be useful such as in situations requiring the multi-key functionality.
624 * @param mixed $data The data to set against the key.
625 * @return bool True on success, false otherwise.
627 public function set($key, $data) {
628 if ($this->perfdebug
) {
629 cache_helper
::record_cache_set($this->store
, $this->definition
);
631 if ($this->loader
!== false) {
632 // We have a loader available set it there as well.
633 // We have to let the loader do its own parsing of data as it may be unique.
634 $this->loader
->set($key, $data);
636 $usestaticacceleration = $this->use_static_acceleration();
638 if (is_object($data) && $data instanceof cacheable_object
) {
639 $data = new cache_cached_object($data);
640 } else if (!$this->store
->supports_dereferencing_objects() && !is_scalar($data)) {
641 // If data is an object it will be a reference.
642 // If data is an array if may contain references.
643 // We want to break references so that the cache cannot be modified outside of itself.
644 // Call the function to unreference it (in the best way possible).
645 $data = $this->unref($data);
648 if ($usestaticacceleration) {
649 $this->static_acceleration_set($key, $data);
652 if ($this->has_a_ttl() && !$this->store_supports_native_ttl()) {
653 $data = new cache_ttl_wrapper($data, $this->definition
->get_ttl());
655 $parsedkey = $this->parse_key($key);
657 return $this->store
->set($parsedkey, $data);
661 * Removes references where required.
663 * @param stdClass|array $data
664 * @return mixed What ever was put in but without any references.
666 protected function unref($data) {
667 if ($this->definition
->uses_simple_data()) {
670 // Check if it requires serialisation in order to produce a reference free copy.
671 if ($this->requires_serialisation($data)) {
672 // Damn, its going to have to be serialise.
673 $data = serialize($data);
674 // We unserialise immediately so that we don't have to do it every time on get.
675 $data = unserialize($data);
676 } else if (!is_scalar($data)) {
677 // Its safe to clone, lets do it, its going to beat the pants of serialisation.
678 $data = $this->deep_clone($data);
684 * Checks to see if a var requires serialisation.
686 * @param mixed $value The value to check.
687 * @param int $depth Used to ensure we don't enter an endless loop (think recursion).
688 * @return bool Returns true if the value is going to require serialisation in order to ensure a reference free copy
689 * or false if its safe to clone.
691 protected function requires_serialisation($value, $depth = 1) {
692 if (is_scalar($value)) {
694 } else if (is_array($value) ||
$value instanceof stdClass ||
$value instanceof Traversable
) {
696 // Skrew it, mega-deep object, developer you suck, we're just going to serialise.
699 foreach ($value as $key => $subvalue) {
700 if ($this->requires_serialisation($subvalue, $depth++
)) {
705 // Its not scalar, array, or stdClass so we'll need to serialise.
710 * Creates a reference free clone of the given value.
712 * @param mixed $value
715 protected function deep_clone($value) {
716 if (is_object($value)) {
717 // Objects must be cloned to begin with.
718 $value = clone $value;
720 if (is_array($value) ||
is_object($value)) {
721 foreach ($value as $key => $subvalue) {
722 $value[$key] = $this->deep_clone($subvalue);
729 * Sends several key => value pairs to the cache.
731 * Using this function comes with potential performance implications.
732 * Not all cache stores will support get_many/set_many operations and in order to replicate this functionality will call
733 * the equivalent singular method for each item provided.
734 * This should not deter you from using this function as there is a performance benefit in situations where the cache store
735 * does support it, but you should be aware of this fact.
738 * // This code will add four entries to the cache, one for each url.
739 * $cache->set_many(array(
740 * 'main' => 'http://moodle.org',
741 * 'docs' => 'http://docs.moodle.org',
742 * 'tracker' => 'http://tracker.moodle.org',
743 * 'qa' => ''http://qa.moodle.net'
747 * @param array $keyvaluearray An array of key => value pairs to send to the cache.
748 * @return int The number of items successfully set. It is up to the developer to check this matches the number of items.
749 * ... if they care that is.
751 public function set_many(array $keyvaluearray) {
752 if ($this->loader
!== false) {
753 // We have a loader available set it there as well.
754 // We have to let the loader do its own parsing of data as it may be unique.
755 $this->loader
->set_many($keyvaluearray);
758 $simulatettl = $this->has_a_ttl() && !$this->store_supports_native_ttl();
759 $usestaticaccelerationarray = $this->use_static_acceleration();
760 $needsdereferencing = !$this->store
->supports_dereferencing_objects();
761 foreach ($keyvaluearray as $key => $value) {
762 if (is_object($value) && $value instanceof cacheable_object
) {
763 $value = new cache_cached_object($value);
764 } else if ($needsdereferencing && !is_scalar($value)) {
765 // If data is an object it will be a reference.
766 // If data is an array if may contain references.
767 // We want to break references so that the cache cannot be modified outside of itself.
768 // Call the function to unreference it (in the best way possible).
769 $value = $this->unref($value);
771 if ($usestaticaccelerationarray) {
772 $this->static_acceleration_set($key, $value);
775 $value = new cache_ttl_wrapper($value, $this->definition
->get_ttl());
778 'key' => $this->parse_key($key),
782 $successfullyset = $this->store
->set_many($data);
783 if ($this->perfdebug
&& $successfullyset) {
784 cache_helper
::record_cache_set($this->store
, $this->definition
, $successfullyset);
786 return $successfullyset;
790 * Test is a cache has a key.
792 * The use of the has methods is strongly discouraged. In a high load environment the cache may well change between the
793 * test and any subsequent action (get, set, delete etc).
794 * Instead it is recommended to write your code in such a way they it performs the following steps:
796 * <li>Attempt to retrieve the information.</li>
797 * <li>Generate the information.</li>
798 * <li>Attempt to set the information</li>
801 * Its also worth mentioning that not all stores support key tests.
802 * For stores that don't support key tests this functionality is mimicked by using the equivalent get method.
803 * Just one more reason you should not use these methods unless you have a very good reason to do so.
805 * @param string|int $key
806 * @param bool $tryloadifpossible If set to true, the cache doesn't contain the key, and there is another cache loader or
807 * data source then the code will try load the key value from the next item in the chain.
808 * @return bool True if the cache has the requested key, false otherwise.
810 public function has($key, $tryloadifpossible = false) {
811 if ($this->static_acceleration_has($key)) {
812 // Hoorah, that was easy. It exists in the static acceleration array so we definitely have it.
815 $parsedkey = $this->parse_key($key);
817 if ($this->has_a_ttl() && !$this->store_supports_native_ttl()) {
818 // The data has a TTL and the store doesn't support it natively.
819 // We must fetch the data and expect a ttl wrapper.
820 $data = $this->store
->get($parsedkey);
821 $has = ($data instanceof cache_ttl_wrapper
&& !$data->has_expired());
822 } else if (!$this->store_supports_key_awareness()) {
823 // The store doesn't support key awareness, get the data and check it manually... puke.
824 // Either no TTL is set of the store supports its handling natively.
825 $data = $this->store
->get($parsedkey);
826 $has = ($data !== false);
828 // The store supports key awareness, this is easy!
829 // Either no TTL is set of the store supports its handling natively.
830 $has = $this->store
->has($parsedkey);
832 if (!$has && $tryloadifpossible) {
833 if ($this->loader
!== false) {
834 $result = $this->loader
->get($parsedkey);
835 } else if ($this->datasource
!== null) {
836 $result = $this->datasource
->load_for_cache($key);
838 $has = ($result !== null);
840 $this->set($key, $result);
847 * Test is a cache has all of the given keys.
849 * It is strongly recommended to avoid the use of this function if not absolutely required.
850 * In a high load environment the cache may well change between the test and any subsequent action (get, set, delete etc).
852 * Its also worth mentioning that not all stores support key tests.
853 * For stores that don't support key tests this functionality is mimicked by using the equivalent get method.
854 * Just one more reason you should not use these methods unless you have a very good reason to do so.
857 * @return bool True if the cache has all of the given keys, false otherwise.
859 public function has_all(array $keys) {
860 if (($this->has_a_ttl() && !$this->store_supports_native_ttl()) ||
!$this->store_supports_key_awareness()) {
861 foreach ($keys as $key) {
862 if (!$this->has($key)) {
868 $parsedkeys = array_map(array($this, 'parse_key'), $keys);
869 return $this->store
->has_all($parsedkeys);
873 * Test if a cache has at least one of the given keys.
875 * It is strongly recommended to avoid the use of this function if not absolutely required.
876 * In a high load environment the cache may well change between the test and any subsequent action (get, set, delete etc).
878 * Its also worth mentioning that not all stores support key tests.
879 * For stores that don't support key tests this functionality is mimicked by using the equivalent get method.
880 * Just one more reason you should not use these methods unless you have a very good reason to do so.
883 * @return bool True if the cache has at least one of the given keys
885 public function has_any(array $keys) {
886 if (($this->has_a_ttl() && !$this->store_supports_native_ttl()) ||
!$this->store_supports_key_awareness()) {
887 foreach ($keys as $key) {
888 if ($this->has($key)) {
895 if ($this->use_static_acceleration()) {
896 foreach ($keys as $id => $key) {
897 if ($this->static_acceleration_has($key)) {
902 $parsedkeys = array_map(array($this, 'parse_key'), $keys);
903 return $this->store
->has_any($parsedkeys);
907 * Delete the given key from the cache.
909 * @param string|int $key The key to delete.
910 * @param bool $recurse When set to true the key will also be deleted from all stacked cache loaders and their stores.
911 * This happens by default and ensure that all the caches are consistent. It is NOT recommended to change this.
912 * @return bool True of success, false otherwise.
914 public function delete($key, $recurse = true) {
915 $this->static_acceleration_delete($key);
916 if ($recurse && $this->loader
!== false) {
917 // Delete from the bottom of the stack first.
918 $this->loader
->delete($key, $recurse);
920 $parsedkey = $this->parse_key($key);
921 return $this->store
->delete($parsedkey);
925 * Delete all of the given keys from the cache.
927 * @param array $keys The key to delete.
928 * @param bool $recurse When set to true the key will also be deleted from all stacked cache loaders and their stores.
929 * This happens by default and ensure that all the caches are consistent. It is NOT recommended to change this.
930 * @return int The number of items successfully deleted.
932 public function delete_many(array $keys, $recurse = true) {
933 if ($this->use_static_acceleration()) {
934 foreach ($keys as $key) {
935 $this->static_acceleration_delete($key);
938 if ($recurse && $this->loader
!== false) {
939 // Delete from the bottom of the stack first.
940 $this->loader
->delete_many($keys, $recurse);
942 $parsedkeys = array_map(array($this, 'parse_key'), $keys);
943 return $this->store
->delete_many($parsedkeys);
947 * Purges the cache store, and loader if there is one.
949 * @return bool True on success, false otherwise
951 public function purge() {
952 // 1. Purge the static acceleration array.
953 $this->static_acceleration_purge();
954 // 2. Purge the store.
955 $this->store
->purge();
956 // 3. Optionally pruge any stacked loaders.
958 $this->loader
->purge();
964 * Parses the key turning it into a string (or array is required) suitable to be passed to the cache store.
966 * @param string|int $key As passed to get|set|delete etc.
967 * @return string|array String unless the store supports multi-identifiers in which case an array if returned.
969 protected function parse_key($key) {
970 // First up if the store supports multiple keys we'll go with that.
971 if ($this->store
->supports_multiple_identifiers()) {
972 $result = $this->definition
->generate_multi_key_parts();
973 $result['key'] = $key;
976 // If not we need to generate a hash and to for that we use the cache_helper.
977 return cache_helper
::hash_key($key, $this->definition
);
981 * Returns true if the cache is making use of a ttl.
984 protected function has_a_ttl() {
985 return $this->hasattl
;
989 * Returns true if the cache store supports native ttl.
992 protected function store_supports_native_ttl() {
993 if ($this->supportsnativettl
=== null) {
994 $this->supportsnativettl
= ($this->store
->supports_native_ttl());
996 return $this->supportsnativettl
;
1000 * Returns the cache definition.
1002 * @return cache_definition
1004 protected function get_definition() {
1005 return $this->definition
;
1009 * Returns the cache store
1011 * @return cache_store
1013 protected function get_store() {
1014 return $this->store
;
1018 * Returns the loader associated with this instance.
1020 * @since Moodle 2.4.4
1021 * @return cache|false
1023 protected function get_loader() {
1024 return $this->loader
;
1028 * Returns the data source associated with this cache.
1030 * @since Moodle 2.4.4
1031 * @return cache_data_source|false
1033 protected function get_datasource() {
1034 return $this->datasource
;
1038 * Returns true if the store supports key awareness.
1042 protected function store_supports_key_awareness() {
1043 if ($this->supportskeyawareness
=== null) {
1044 $this->supportskeyawareness
= ($this->store
instanceof cache_is_key_aware
);
1046 return $this->supportskeyawareness
;
1050 * Returns true if the store natively supports locking.
1054 protected function store_supports_native_locking() {
1055 if ($this->nativelocking
=== null) {
1056 $this->nativelocking
= ($this->store
instanceof cache_is_lockable
);
1058 return $this->nativelocking
;
1062 * @deprecated since 2.6
1063 * @see cache::use_static_acceleration()
1065 protected function is_using_persist_cache() {
1066 throw new coding_exception('cache::is_using_persist_cache() can not be used anymore.' .
1067 ' Please use cache::use_static_acceleration() instead.');
1071 * Returns true if this cache is making use of the static acceleration array.
1075 protected function use_static_acceleration() {
1076 return $this->staticacceleration
;
1080 * @see cache::static_acceleration_has
1081 * @deprecated since 2.6
1083 protected function is_in_persist_cache() {
1084 throw new coding_exception('cache::is_in_persist_cache() can not be used anymore.' .
1085 ' Please use cache::static_acceleration_has() instead.');
1089 * Returns true if the requested key exists within the static acceleration array.
1091 * @param string $key The parsed key
1094 protected function static_acceleration_has($key) {
1095 // This could be written as a single line, however it has been split because the ttl check is faster than the instanceof
1096 // and has_expired calls.
1097 if (!$this->staticacceleration ||
!isset($this->staticaccelerationarray
[$key])) {
1104 * @deprecated since 2.6
1105 * @see cache::static_acceleration_get
1107 protected function get_from_persist_cache() {
1108 throw new coding_exception('cache::get_from_persist_cache() can not be used anymore.' .
1109 ' Please use cache::static_acceleration_get() instead.');
1113 * Returns the item from the static acceleration array if it exists there.
1115 * @param string $key The parsed key
1116 * @return mixed|false Dereferenced data from the static acceleration array or false if it wasn't there.
1118 protected function static_acceleration_get($key) {
1119 if (!$this->staticacceleration ||
!isset($this->staticaccelerationarray
[$key])) {
1122 $data = $this->staticaccelerationarray
[$key]['data'];
1124 if ($data instanceof cache_cached_object
) {
1125 $result = $data->restore_object();
1126 } else if ($this->staticaccelerationarray
[$key]['serialized']) {
1127 $result = unserialize($data);
1132 if ($result !== false) {
1133 if ($this->perfdebug
) {
1134 cache_helper
::record_cache_hit(cache_store
::STATIC_ACCEL
, $this->definition
);
1136 if ($this->staticaccelerationsize
> 1 && $this->staticaccelerationcount
> 1) {
1137 // Check to see if this is the last item on the static acceleration keys array.
1138 if (end($this->staticaccelerationkeys
) !== $key) {
1139 // It isn't the last item.
1140 // Move the item to the end of the array so that it is last to be removed.
1141 unset($this->staticaccelerationkeys
[$key]);
1142 $this->staticaccelerationkeys
[$key] = $key;
1147 if ($this->perfdebug
) {
1148 cache_helper
::record_cache_miss(cache_store
::STATIC_ACCEL
, $this->definition
);
1155 * @deprecated since 2.6
1156 * @see cache::static_acceleration_set
1158 protected function set_in_persist_cache() {
1159 throw new coding_exception('cache::set_in_persist_cache() can not be used anymore.' .
1160 ' Please use cache::static_acceleration_set() instead.');
1164 * Sets a key value pair into the static acceleration array.
1166 * @param string $key The parsed key
1167 * @param mixed $data
1170 protected function static_acceleration_set($key, $data) {
1171 if ($this->staticaccelerationsize
!== false && isset($this->staticaccelerationkeys
[$key])) {
1172 $this->staticaccelerationcount
--;
1173 unset($this->staticaccelerationkeys
[$key]);
1176 // We serialize anything that's not;
1177 // 1. A known scalar safe value.
1178 // 2. A definition that says it's simpledata. We trust it that it doesn't contain dangerous references.
1179 // 3. An object that handles dereferencing by itself.
1180 if (is_scalar($data) ||
$this->definition
->uses_simple_data()
1181 ||
$data instanceof cache_cached_object
) {
1182 $this->staticaccelerationarray
[$key]['data'] = $data;
1183 $this->staticaccelerationarray
[$key]['serialized'] = false;
1185 $this->staticaccelerationarray
[$key]['data'] = serialize($data);
1186 $this->staticaccelerationarray
[$key]['serialized'] = true;
1188 if ($this->staticaccelerationsize
!== false) {
1189 $this->staticaccelerationcount++
;
1190 $this->staticaccelerationkeys
[$key] = $key;
1191 if ($this->staticaccelerationcount
> $this->staticaccelerationsize
) {
1192 $dropkey = array_shift($this->staticaccelerationkeys
);
1193 unset($this->staticaccelerationarray
[$dropkey]);
1194 $this->staticaccelerationcount
--;
1201 * @deprecated since 2.6
1202 * @see cache::static_acceleration_delete()
1204 protected function delete_from_persist_cache() {
1205 throw new coding_exception('cache::delete_from_persist_cache() can not be used anymore.' .
1206 ' Please use cache::static_acceleration_delete() instead.');
1210 * Deletes an item from the static acceleration array.
1212 * @param string|int $key As given to get|set|delete
1213 * @return bool True on success, false otherwise.
1215 protected function static_acceleration_delete($key) {
1216 unset($this->staticaccelerationarray
[$key]);
1217 if ($this->staticaccelerationsize
!== false && isset($this->staticaccelerationkeys
[$key])) {
1218 unset($this->staticaccelerationkeys
[$key]);
1219 $this->staticaccelerationcount
--;
1225 * Purge the static acceleration cache.
1227 protected function static_acceleration_purge() {
1228 $this->staticaccelerationarray
= array();
1229 if ($this->staticaccelerationsize
!== false) {
1230 $this->staticaccelerationkeys
= array();
1231 $this->staticaccelerationcount
= 0;
1236 * Returns the timestamp from the first request for the time from the cache API.
1238 * This stamp needs to be used for all ttl and time based operations to ensure that we don't end up with
1241 * @param bool $float Whether to use floating precision accuracy.
1244 public static function now($float = false) {
1245 if (self
::$now === null) {
1246 self
::$now = microtime(true);
1252 return (int) self
::$now;
1257 * Get a 'purge' token used for cache invalidation handling.
1259 * Note: This function is intended for use from within the Cache API only and not by plugins, or cache stores.
1261 * @param bool $reset Whether to reset the token and generate a new one.
1264 public static function get_purge_token($reset = false) {
1265 if (self
::$purgetoken === null ||
$reset) {
1267 self
::$purgetoken = self
::now(true) . '-' . uniqid('', true);
1270 return self
::$purgetoken;
1274 * Compare a pair of purge tokens.
1276 * If the two tokens are identical, then the return value is 0.
1277 * If the time component of token A is newer than token B, then a positive value is returned.
1278 * If the time component of token B is newer than token A, then a negative value is returned.
1280 * Note: This function is intended for use from within the Cache API only and not by plugins, or cache stores.
1282 * @param string $tokena
1283 * @param string $tokenb
1286 public static function compare_purge_tokens($tokena, $tokenb) {
1287 if ($tokena === $tokenb) {
1288 // There is an exact match.
1292 // The token for when the cache was last invalidated.
1293 list($atime) = explode('-', "{$tokena}-", 2);
1295 // The token for this cache.
1296 list($btime) = explode('-', "{$tokenb}-", 2);
1298 if ($atime >= $btime) {
1299 // Token A is newer.
1302 // Token A is older.
1308 * Subclasses may support purging cache of all data belonging to the
1311 public function purge_current_user() {
1316 * An application cache.
1318 * This class is used for application caches returned by the cache::make methods.
1319 * On top of the standard functionality it also allows locking to be required and or manually operated.
1321 * This cache class should never be interacted with directly. Instead you should always use the cache::make methods.
1322 * It is technically possible to call those methods through this class however there is no guarantee that you will get an
1323 * instance of this class back again.
1325 * @internal don't use me directly.
1329 * @copyright 2012 Sam Hemelryk
1330 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1332 class cache_application
extends cache
implements cache_loader_with_locking
{
1336 * This is used to ensure the lock belongs to the cache instance + definition + user.
1339 protected $lockidentifier;
1342 * Gets set to true if the cache's primary store natively supports locking.
1343 * If it does then we use that, otherwise we need to instantiate a second store to use for locking.
1346 protected $nativelocking = null;
1349 * Gets set to true if the cache is going to be using locking.
1350 * This isn't a requirement, it doesn't need to use locking (most won't) and this bool is used to quickly check things.
1351 * If required then locking will be forced for the get|set|delete operation.
1354 protected $requirelocking = false;
1357 * Gets set to true if the cache must use read locking (get|has).
1360 protected $requirelockingread = false;
1363 * Gets set to true if the cache must use write locking (set|delete)
1366 protected $requirelockingwrite = false;
1369 * Gets set to a cache_store to use for locking if the caches primary store doesn't support locking natively.
1370 * @var cache_lock_interface
1372 protected $cachelockinstance;
1375 * Overrides the cache construct method.
1377 * You should not call this method from your code, instead you should use the cache::make methods.
1379 * @param cache_definition $definition
1380 * @param cache_store $store
1381 * @param cache_loader|cache_data_source $loader
1383 public function __construct(cache_definition
$definition, cache_store
$store, $loader = null) {
1384 parent
::__construct($definition, $store, $loader);
1385 $this->nativelocking
= $this->store_supports_native_locking();
1386 if ($definition->require_locking()) {
1387 $this->requirelocking
= true;
1388 $this->requirelockingread
= $definition->require_locking_read();
1389 $this->requirelockingwrite
= $definition->require_locking_write();
1392 $this->handle_invalidation_events();
1396 * Returns the identifier to use
1398 * @staticvar int $instances Counts the number of instances. Used as part of the lock identifier.
1401 public function get_identifier() {
1402 static $instances = 0;
1403 if ($this->lockidentifier
=== null) {
1404 $this->lockidentifier
= md5(
1405 $this->get_definition()->generate_definition_hash() .
1411 return $this->lockidentifier
;
1415 * Fixes the instance up after a clone.
1417 public function __clone() {
1418 // Force a new idenfitier.
1419 $this->lockidentifier
= null;
1423 * Acquires a lock on the given key.
1425 * This is done automatically if the definition requires it.
1426 * It is recommended to use a definition if you want to have locking although it is possible to do locking without having
1427 * it required by the definition.
1428 * The problem with such an approach is that you cannot ensure that code will consistently use locking. You will need to
1429 * rely on the integrators review skills.
1431 * @param string|int $key The key as given to get|set|delete
1432 * @return bool Returns true if the lock could be acquired, false otherwise.
1434 public function acquire_lock($key) {
1435 $key = $this->parse_key($key);
1436 if ($this->nativelocking
) {
1437 return $this->get_store()->acquire_lock($key, $this->get_identifier());
1439 $this->ensure_cachelock_available();
1440 return $this->cachelockinstance
->lock($key, $this->get_identifier());
1445 * Checks if this cache has a lock on the given key.
1447 * @param string|int $key The key as given to get|set|delete
1448 * @return bool|null Returns true if there is a lock and this cache has it, null if no one has a lock on that key, false if
1449 * someone else has the lock.
1451 public function check_lock_state($key) {
1452 $key = $this->parse_key($key);
1453 if ($this->nativelocking
) {
1454 return $this->get_store()->check_lock_state($key, $this->get_identifier());
1456 $this->ensure_cachelock_available();
1457 return $this->cachelockinstance
->check_state($key, $this->get_identifier());
1462 * Releases the lock this cache has on the given key
1464 * @param string|int $key
1465 * @return bool True if the operation succeeded, false otherwise.
1467 public function release_lock($key) {
1468 $key = $this->parse_key($key);
1469 if ($this->nativelocking
) {
1470 return $this->get_store()->release_lock($key, $this->get_identifier());
1472 $this->ensure_cachelock_available();
1473 return $this->cachelockinstance
->unlock($key, $this->get_identifier());
1478 * Ensure that the dedicated lock store is ready to go.
1480 * This should only happen if the cache store doesn't natively support it.
1482 protected function ensure_cachelock_available() {
1483 if ($this->cachelockinstance
=== null) {
1484 $this->cachelockinstance
= cache_helper
::get_cachelock_for_store($this->get_store());
1489 * Sends a key => value pair to the cache.
1492 * // This code will add four entries to the cache, one for each url.
1493 * $cache->set('main', 'http://moodle.org');
1494 * $cache->set('docs', 'http://docs.moodle.org');
1495 * $cache->set('tracker', 'http://tracker.moodle.org');
1496 * $cache->set('qa', 'http://qa.moodle.net');
1499 * @param string|int $key The key for the data being requested.
1500 * @param mixed $data The data to set against the key.
1501 * @return bool True on success, false otherwise.
1503 public function set($key, $data) {
1504 if ($this->requirelockingwrite
&& !$this->acquire_lock($key)) {
1507 $result = parent
::set($key, $data);
1508 if ($this->requirelockingwrite
&& !$this->release_lock($key)) {
1509 debugging('Failed to release cache lock on set operation... this should not happen.', DEBUG_DEVELOPER
);
1515 * Sends several key => value pairs to the cache.
1517 * Using this function comes with potential performance implications.
1518 * Not all cache stores will support get_many/set_many operations and in order to replicate this functionality will call
1519 * the equivalent singular method for each item provided.
1520 * This should not deter you from using this function as there is a performance benefit in situations where the cache store
1521 * does support it, but you should be aware of this fact.
1524 * // This code will add four entries to the cache, one for each url.
1525 * $cache->set_many(array(
1526 * 'main' => 'http://moodle.org',
1527 * 'docs' => 'http://docs.moodle.org',
1528 * 'tracker' => 'http://tracker.moodle.org',
1529 * 'qa' => ''http://qa.moodle.net'
1533 * @param array $keyvaluearray An array of key => value pairs to send to the cache.
1534 * @return int The number of items successfully set. It is up to the developer to check this matches the number of items.
1535 * ... if they care that is.
1537 public function set_many(array $keyvaluearray) {
1538 if ($this->requirelockingwrite
) {
1540 foreach ($keyvaluearray as $id => $pair) {
1541 $key = $pair['key'];
1542 if ($this->acquire_lock($key)) {
1545 unset($keyvaluearray[$id]);
1549 $result = parent
::set_many($keyvaluearray);
1550 if ($this->requirelockingwrite
) {
1551 foreach ($locks as $key) {
1552 if ($this->release_lock($key)) {
1553 debugging('Failed to release cache lock on set_many operation... this should not happen.', DEBUG_DEVELOPER
);
1561 * Retrieves the value for the given key from the cache.
1563 * @param string|int $key The key for the data being requested.
1564 * @param int $strictness One of IGNORE_MISSING | MUST_EXIST
1565 * @return mixed|false The data from the cache or false if the key did not exist within the cache.
1567 public function get($key, $strictness = IGNORE_MISSING
) {
1568 if ($this->requirelockingread
&& $this->check_lock_state($key) === false) {
1569 // Read locking required and someone else has the read lock.
1572 return parent
::get($key, $strictness);
1576 * Retrieves an array of values for an array of keys.
1578 * Using this function comes with potential performance implications.
1579 * Not all cache stores will support get_many/set_many operations and in order to replicate this functionality will call
1580 * the equivalent singular method for each item provided.
1581 * This should not deter you from using this function as there is a performance benefit in situations where the cache store
1582 * does support it, but you should be aware of this fact.
1584 * @param array $keys The keys of the data being requested.
1585 * @param int $strictness One of IGNORE_MISSING or MUST_EXIST.
1586 * @return array An array of key value pairs for the items that could be retrieved from the cache.
1587 * If MUST_EXIST was used and not all keys existed within the cache then an exception will be thrown.
1588 * Otherwise any key that did not exist will have a data value of false within the results.
1589 * @throws coding_exception
1591 public function get_many(array $keys, $strictness = IGNORE_MISSING
) {
1592 if ($this->requirelockingread
) {
1593 foreach ($keys as $id => $key) {
1594 $lock =$this->acquire_lock($key);
1596 if ($strictness === MUST_EXIST
) {
1597 throw new coding_exception('Could not acquire read locks for all of the items being requested.');
1599 // Can't return this as we couldn't get a read lock.
1606 return parent
::get_many($keys, $strictness);
1610 * Delete the given key from the cache.
1612 * @param string|int $key The key to delete.
1613 * @param bool $recurse When set to true the key will also be deleted from all stacked cache loaders and their stores.
1614 * This happens by default and ensure that all the caches are consistent. It is NOT recommended to change this.
1615 * @return bool True of success, false otherwise.
1617 public function delete($key, $recurse = true) {
1618 if ($this->requirelockingwrite
&& !$this->acquire_lock($key)) {
1621 $result = parent
::delete($key, $recurse);
1622 if ($this->requirelockingwrite
&& !$this->release_lock($key)) {
1623 debugging('Failed to release cache lock on delete operation... this should not happen.', DEBUG_DEVELOPER
);
1629 * Delete all of the given keys from the cache.
1631 * @param array $keys The key to delete.
1632 * @param bool $recurse When set to true the key will also be deleted from all stacked cache loaders and their stores.
1633 * This happens by default and ensure that all the caches are consistent. It is NOT recommended to change this.
1634 * @return int The number of items successfully deleted.
1636 public function delete_many(array $keys, $recurse = true) {
1637 if ($this->requirelockingwrite
) {
1639 foreach ($keys as $id => $key) {
1640 if ($this->acquire_lock($key)) {
1647 $result = parent
::delete_many($keys, $recurse);
1648 if ($this->requirelockingwrite
) {
1649 foreach ($locks as $key) {
1650 if ($this->release_lock($key)) {
1651 debugging('Failed to release cache lock on delete_many operation... this should not happen.', DEBUG_DEVELOPER
);
1662 * This class is used for session caches returned by the cache::make methods.
1664 * It differs from the application loader in a couple of noteable ways:
1665 * 1. Sessions are always expected to exist.
1666 * Because of this we don't ever use the static acceleration array.
1667 * 2. Session data for a loader instance (store + definition) is consolidate into a
1668 * single array for storage within the store.
1669 * Along with this we embed a lastaccessed time with the data. This way we can
1670 * check sessions for a last access time.
1671 * 3. Session stores are required to support key searching and must
1672 * implement cache_is_searchable. This ensures stores used for the cache can be
1673 * targetted for garbage collection of session data.
1675 * This cache class should never be interacted with directly. Instead you should always use the cache::make methods.
1676 * It is technically possible to call those methods through this class however there is no guarantee that you will get an
1677 * instance of this class back again.
1679 * @todo we should support locking in the session as well. Should be pretty simple to set up.
1681 * @internal don't use me directly.
1682 * @method cache_store|cache_is_searchable get_store() Returns the cache store which must implement both cache_is_searchable.
1686 * @copyright 2012 Sam Hemelryk
1687 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1689 class cache_session
extends cache
{
1691 * The user the session has been established for.
1694 protected static $loadeduserid = null;
1697 * The userid this cache is currently using.
1700 protected $currentuserid = null;
1703 * The session id we are currently using.
1706 protected $sessionid = null;
1709 * The session data for the above session id.
1712 protected $session = null;
1715 * Constant used to prefix keys.
1717 const KEY_PREFIX
= 'sess_';
1720 * This is the key used to track last access.
1722 const LASTACCESS
= '__lastaccess__';
1725 * Override the cache::construct method.
1727 * This function gets overriden so that we can process any invalidation events if need be.
1728 * If the definition doesn't have any invalidation events then this occurs exactly as it would for the cache class.
1729 * Otherwise we look at the last invalidation time and then check the invalidation data for events that have occured
1732 * You should not call this method from your code, instead you should use the cache::make methods.
1734 * @param cache_definition $definition
1735 * @param cache_store $store
1736 * @param cache_loader|cache_data_source $loader
1738 public function __construct(cache_definition
$definition, cache_store
$store, $loader = null) {
1739 // First up copy the loadeduserid to the current user id.
1740 $this->currentuserid
= self
::$loadeduserid;
1741 $this->set_session_id();
1742 parent
::__construct($definition, $store, $loader);
1744 // This will trigger check tracked user. If this gets removed a call to that will need to be added here in its place.
1745 $this->set(self
::LASTACCESS
, cache
::now());
1747 $this->handle_invalidation_events();
1751 * Sets the session id for the loader.
1753 protected function set_session_id() {
1754 $this->sessionid
= preg_replace('#[^a-zA-Z0-9_]#', '_', session_id());
1758 * Returns the prefix used for all keys.
1761 protected function get_key_prefix() {
1762 return 'u'.$this->currentuserid
.'_'.$this->sessionid
;
1766 * Parses the key turning it into a string (or array is required) suitable to be passed to the cache store.
1768 * This function is called for every operation that uses keys. For this reason we use this function to also check
1769 * that the current user is the same as the user who last used this cache.
1771 * On top of that if prepends the string 'sess_' to the start of all keys. The _ ensures things are easily identifiable.
1773 * @param string|int $key As passed to get|set|delete etc.
1774 * @return string|array String unless the store supports multi-identifiers in which case an array if returned.
1776 protected function parse_key($key) {
1777 $prefix = $this->get_key_prefix();
1778 if ($key === self
::LASTACCESS
) {
1779 return $key.$prefix;
1781 return $prefix.'_'.parent
::parse_key($key);
1785 * Check that this cache instance is tracking the current user.
1787 protected function check_tracked_user() {
1788 if (isset($_SESSION['USER']->id
) && $_SESSION['USER']->id
!== null) {
1789 // Get the id of the current user.
1790 $new = $_SESSION['USER']->id
;
1792 // No user set up yet.
1795 if ($new !== self
::$loadeduserid) {
1796 // The current user doesn't match the tracked userid for this request.
1797 if (!is_null(self
::$loadeduserid)) {
1798 // Purge the data we have for the old user.
1799 // This way we don't bloat the session.
1802 self
::$loadeduserid = $new;
1803 $this->currentuserid
= $new;
1804 } else if ($new !== $this->currentuserid
) {
1805 // The current user matches the loaded user but not the user last used by this cache.
1806 $this->purge_current_user();
1807 $this->currentuserid
= $new;
1812 * Purges the session cache of all data belonging to the current user.
1814 public function purge_current_user() {
1815 $keys = $this->get_store()->find_by_prefix($this->get_key_prefix());
1816 $this->get_store()->delete_many($keys);
1820 * Retrieves the value for the given key from the cache.
1822 * @param string|int $key The key for the data being requested.
1823 * It can be any structure although using a scalar string or int is recommended in the interests of performance.
1824 * In advanced cases an array may be useful such as in situations requiring the multi-key functionality.
1825 * @param int $strictness One of IGNORE_MISSING | MUST_EXIST
1826 * @return mixed|false The data from the cache or false if the key did not exist within the cache.
1827 * @throws coding_exception
1829 public function get($key, $strictness = IGNORE_MISSING
) {
1830 // Check the tracked user.
1831 $this->check_tracked_user();
1832 // 2. Parse the key.
1833 $parsedkey = $this->parse_key($key);
1834 // 3. Get it from the store.
1835 $result = $this->get_store()->get($parsedkey);
1836 if ($result !== false) {
1837 if ($result instanceof cache_ttl_wrapper
) {
1838 if ($result->has_expired()) {
1839 $this->get_store()->delete($parsedkey);
1842 $result = $result->data
;
1845 if ($result instanceof cache_cached_object
) {
1846 $result = $result->restore_object();
1849 // 4. Load if from the loader/datasource if we don't already have it.
1850 if ($result === false) {
1851 if ($this->perfdebug
) {
1852 cache_helper
::record_cache_miss($this->get_store(), $this->get_definition());
1854 if ($this->get_loader() !== false) {
1855 // We must pass the original (unparsed) key to the next loader in the chain.
1856 // The next loader will parse the key as it sees fit. It may be parsed differently
1857 // depending upon the capabilities of the store associated with the loader.
1858 $result = $this->get_loader()->get($key);
1859 } else if ($this->get_datasource() !== false) {
1860 $result = $this->get_datasource()->load_for_cache($key);
1862 // 5. Set it to the store if we got it from the loader/datasource.
1863 if ($result !== false) {
1864 $this->set($key, $result);
1866 } else if ($this->perfdebug
) {
1867 cache_helper
::record_cache_hit($this->get_store(), $this->get_definition());
1869 // 5. Validate strictness.
1870 if ($strictness === MUST_EXIST
&& $result === false) {
1871 throw new coding_exception('Requested key did not exist in any cache stores and could not be loaded.');
1873 // 6. Make sure we don't pass back anything that could be a reference.
1874 // We don't want people modifying the data in the cache.
1875 if (!$this->get_store()->supports_dereferencing_objects() && !is_scalar($result)) {
1876 // If data is an object it will be a reference.
1877 // If data is an array if may contain references.
1878 // We want to break references so that the cache cannot be modified outside of itself.
1879 // Call the function to unreference it (in the best way possible).
1880 $result = $this->unref($result);
1886 * Sends a key => value pair to the cache.
1889 * // This code will add four entries to the cache, one for each url.
1890 * $cache->set('main', 'http://moodle.org');
1891 * $cache->set('docs', 'http://docs.moodle.org');
1892 * $cache->set('tracker', 'http://tracker.moodle.org');
1893 * $cache->set('qa', 'http://qa.moodle.net');
1896 * @param string|int $key The key for the data being requested.
1897 * It can be any structure although using a scalar string or int is recommended in the interests of performance.
1898 * In advanced cases an array may be useful such as in situations requiring the multi-key functionality.
1899 * @param mixed $data The data to set against the key.
1900 * @return bool True on success, false otherwise.
1902 public function set($key, $data) {
1903 $this->check_tracked_user();
1904 $loader = $this->get_loader();
1905 if ($loader !== false) {
1906 // We have a loader available set it there as well.
1907 // We have to let the loader do its own parsing of data as it may be unique.
1908 $loader->set($key, $data);
1910 if ($this->perfdebug
) {
1911 cache_helper
::record_cache_set($this->get_store(), $this->get_definition());
1913 if (is_object($data) && $data instanceof cacheable_object
) {
1914 $data = new cache_cached_object($data);
1915 } else if (!$this->get_store()->supports_dereferencing_objects() && !is_scalar($data)) {
1916 // If data is an object it will be a reference.
1917 // If data is an array if may contain references.
1918 // We want to break references so that the cache cannot be modified outside of itself.
1919 // Call the function to unreference it (in the best way possible).
1920 $data = $this->unref($data);
1922 // We dont' support native TTL here as we consolidate data for sessions.
1923 if ($this->has_a_ttl() && !$this->store_supports_native_ttl()) {
1924 $data = new cache_ttl_wrapper($data, $this->get_definition()->get_ttl());
1926 return $this->get_store()->set($this->parse_key($key), $data);
1930 * Delete the given key from the cache.
1932 * @param string|int $key The key to delete.
1933 * @param bool $recurse When set to true the key will also be deleted from all stacked cache loaders and their stores.
1934 * This happens by default and ensure that all the caches are consistent. It is NOT recommended to change this.
1935 * @return bool True of success, false otherwise.
1937 public function delete($key, $recurse = true) {
1938 $parsedkey = $this->parse_key($key);
1939 if ($recurse && $this->get_loader() !== false) {
1940 // Delete from the bottom of the stack first.
1941 $this->get_loader()->delete($key, $recurse);
1943 return $this->get_store()->delete($parsedkey);
1947 * Retrieves an array of values for an array of keys.
1949 * Using this function comes with potential performance implications.
1950 * Not all cache stores will support get_many/set_many operations and in order to replicate this functionality will call
1951 * the equivalent singular method for each item provided.
1952 * This should not deter you from using this function as there is a performance benefit in situations where the cache store
1953 * does support it, but you should be aware of this fact.
1955 * @param array $keys The keys of the data being requested.
1956 * Each key can be any structure although using a scalar string or int is recommended in the interests of performance.
1957 * In advanced cases an array may be useful such as in situations requiring the multi-key functionality.
1958 * @param int $strictness One of IGNORE_MISSING or MUST_EXIST.
1959 * @return array An array of key value pairs for the items that could be retrieved from the cache.
1960 * If MUST_EXIST was used and not all keys existed within the cache then an exception will be thrown.
1961 * Otherwise any key that did not exist will have a data value of false within the results.
1962 * @throws coding_exception
1964 public function get_many(array $keys, $strictness = IGNORE_MISSING
) {
1965 $this->check_tracked_user();
1966 $parsedkeys = array();
1968 foreach ($keys as $key) {
1969 $parsedkey = $this->parse_key($key);
1970 $parsedkeys[$key] = $parsedkey;
1971 $keymap[$parsedkey] = $key;
1973 $result = $this->get_store()->get_many($parsedkeys);
1975 $missingkeys = array();
1976 $hasmissingkeys = false;
1977 foreach ($result as $parsedkey => $value) {
1978 $key = $keymap[$parsedkey];
1979 if ($value instanceof cache_ttl_wrapper
) {
1980 /* @var cache_ttl_wrapper $value */
1981 if ($value->has_expired()) {
1982 $this->delete($keymap[$parsedkey]);
1985 $value = $value->data
;
1988 if ($value instanceof cache_cached_object
) {
1989 /* @var cache_cached_object $value */
1990 $value = $value->restore_object();
1991 } else if (!$this->get_store()->supports_dereferencing_objects() && !is_scalar($value)) {
1992 // If data is an object it will be a reference.
1993 // If data is an array if may contain references.
1994 // We want to break references so that the cache cannot be modified outside of itself.
1995 // Call the function to unreference it (in the best way possible).
1996 $value = $this->unref($value);
1998 $return[$key] = $value;
1999 if ($value === false) {
2000 $hasmissingkeys = true;
2001 $missingkeys[$parsedkey] = $key;
2004 if ($hasmissingkeys) {
2005 // We've got missing keys - we've got to check any loaders or data sources.
2006 $loader = $this->get_loader();
2007 $datasource = $this->get_datasource();
2008 if ($loader !== false) {
2009 foreach ($loader->get_many($missingkeys) as $key => $value) {
2010 if ($value !== false) {
2011 $return[$key] = $value;
2012 unset($missingkeys[$parsedkeys[$key]]);
2016 $hasmissingkeys = count($missingkeys) > 0;
2017 if ($datasource !== false && $hasmissingkeys) {
2018 // We're still missing keys but we've got a datasource.
2019 foreach ($datasource->load_many_for_cache($missingkeys) as $key => $value) {
2020 if ($value !== false) {
2021 $return[$key] = $value;
2022 unset($missingkeys[$parsedkeys[$key]]);
2025 $hasmissingkeys = count($missingkeys) > 0;
2028 if ($hasmissingkeys && $strictness === MUST_EXIST
) {
2029 throw new coding_exception('Requested key did not exist in any cache stores and could not be loaded.');
2031 if ($this->perfdebug
) {
2034 foreach ($return as $value) {
2035 if ($value === false) {
2041 cache_helper
::record_cache_hit($this->get_store(), $this->get_definition(), $hits);
2042 cache_helper
::record_cache_miss($this->get_store(), $this->get_definition(), $misses);
2049 * Delete all of the given keys from the cache.
2051 * @param array $keys The key to delete.
2052 * @param bool $recurse When set to true the key will also be deleted from all stacked cache loaders and their stores.
2053 * This happens by default and ensure that all the caches are consistent. It is NOT recommended to change this.
2054 * @return int The number of items successfully deleted.
2056 public function delete_many(array $keys, $recurse = true) {
2057 $parsedkeys = array_map(array($this, 'parse_key'), $keys);
2058 if ($recurse && $this->get_loader() !== false) {
2059 // Delete from the bottom of the stack first.
2060 $this->get_loader()->delete_many($keys, $recurse);
2062 return $this->get_store()->delete_many($parsedkeys);
2066 * Sends several key => value pairs to the cache.
2068 * Using this function comes with potential performance implications.
2069 * Not all cache stores will support get_many/set_many operations and in order to replicate this functionality will call
2070 * the equivalent singular method for each item provided.
2071 * This should not deter you from using this function as there is a performance benefit in situations where the cache store
2072 * does support it, but you should be aware of this fact.
2075 * // This code will add four entries to the cache, one for each url.
2076 * $cache->set_many(array(
2077 * 'main' => 'http://moodle.org',
2078 * 'docs' => 'http://docs.moodle.org',
2079 * 'tracker' => 'http://tracker.moodle.org',
2080 * 'qa' => ''http://qa.moodle.net'
2084 * @param array $keyvaluearray An array of key => value pairs to send to the cache.
2085 * @return int The number of items successfully set. It is up to the developer to check this matches the number of items.
2086 * ... if they care that is.
2088 public function set_many(array $keyvaluearray) {
2089 $this->check_tracked_user();
2090 $loader = $this->get_loader();
2091 if ($loader !== false) {
2092 // We have a loader available set it there as well.
2093 // We have to let the loader do its own parsing of data as it may be unique.
2094 $loader->set_many($keyvaluearray);
2097 $definitionid = $this->get_definition()->get_ttl();
2098 $simulatettl = $this->has_a_ttl() && !$this->store_supports_native_ttl();
2099 foreach ($keyvaluearray as $key => $value) {
2100 if (is_object($value) && $value instanceof cacheable_object
) {
2101 $value = new cache_cached_object($value);
2102 } else if (!$this->get_store()->supports_dereferencing_objects() && !is_scalar($value)) {
2103 // If data is an object it will be a reference.
2104 // If data is an array if may contain references.
2105 // We want to break references so that the cache cannot be modified outside of itself.
2106 // Call the function to unreference it (in the best way possible).
2107 $value = $this->unref($value);
2110 $value = new cache_ttl_wrapper($value, $definitionid);
2112 $data[$key] = array(
2113 'key' => $this->parse_key($key),
2117 $successfullyset = $this->get_store()->set_many($data);
2118 if ($this->perfdebug
&& $successfullyset) {
2119 cache_helper
::record_cache_set($this->get_store(), $this->get_definition(), $successfullyset);
2121 return $successfullyset;
2125 * Purges the cache store, and loader if there is one.
2127 * @return bool True on success, false otherwise
2129 public function purge() {
2130 $this->get_store()->purge();
2131 if ($this->get_loader()) {
2132 $this->get_loader()->purge();
2138 * Test is a cache has a key.
2140 * The use of the has methods is strongly discouraged. In a high load environment the cache may well change between the
2141 * test and any subsequent action (get, set, delete etc).
2142 * Instead it is recommended to write your code in such a way they it performs the following steps:
2144 * <li>Attempt to retrieve the information.</li>
2145 * <li>Generate the information.</li>
2146 * <li>Attempt to set the information</li>
2149 * Its also worth mentioning that not all stores support key tests.
2150 * For stores that don't support key tests this functionality is mimicked by using the equivalent get method.
2151 * Just one more reason you should not use these methods unless you have a very good reason to do so.
2153 * @param string|int $key
2154 * @param bool $tryloadifpossible If set to true, the cache doesn't contain the key, and there is another cache loader or
2155 * data source then the code will try load the key value from the next item in the chain.
2156 * @return bool True if the cache has the requested key, false otherwise.
2158 public function has($key, $tryloadifpossible = false) {
2159 $this->check_tracked_user();
2160 $parsedkey = $this->parse_key($key);
2161 $store = $this->get_store();
2162 if ($this->has_a_ttl() && !$this->store_supports_native_ttl()) {
2163 // The data has a TTL and the store doesn't support it natively.
2164 // We must fetch the data and expect a ttl wrapper.
2165 $data = $store->get($parsedkey);
2166 $has = ($data instanceof cache_ttl_wrapper
&& !$data->has_expired());
2167 } else if (!$this->store_supports_key_awareness()) {
2168 // The store doesn't support key awareness, get the data and check it manually... puke.
2169 // Either no TTL is set of the store supports its handling natively.
2170 $data = $store->get($parsedkey);
2171 $has = ($data !== false);
2173 // The store supports key awareness, this is easy!
2174 // Either no TTL is set of the store supports its handling natively.
2175 /* @var cache_store|cache_is_key_aware $store */
2176 $has = $store->has($parsedkey);
2178 if (!$has && $tryloadifpossible) {
2180 if ($this->get_loader() !== false) {
2181 $result = $this->get_loader()->get($parsedkey);
2182 } else if ($this->get_datasource() !== null) {
2183 $result = $this->get_datasource()->load_for_cache($key);
2185 $has = ($result !== null);
2187 $this->set($key, $result);
2194 * Test is a cache has all of the given keys.
2196 * It is strongly recommended to avoid the use of this function if not absolutely required.
2197 * In a high load environment the cache may well change between the test and any subsequent action (get, set, delete etc).
2199 * Its also worth mentioning that not all stores support key tests.
2200 * For stores that don't support key tests this functionality is mimicked by using the equivalent get method.
2201 * Just one more reason you should not use these methods unless you have a very good reason to do so.
2203 * @param array $keys
2204 * @return bool True if the cache has all of the given keys, false otherwise.
2206 public function has_all(array $keys) {
2207 $this->check_tracked_user();
2208 if (($this->has_a_ttl() && !$this->store_supports_native_ttl()) ||
!$this->store_supports_key_awareness()) {
2209 foreach ($keys as $key) {
2210 if (!$this->has($key)) {
2216 // The cache must be key aware and if support native ttl if it a ttl is set.
2217 /* @var cache_store|cache_is_key_aware $store */
2218 $store = $this->get_store();
2219 return $store->has_all(array_map(array($this, 'parse_key'), $keys));
2223 * Test if a cache has at least one of the given keys.
2225 * It is strongly recommended to avoid the use of this function if not absolutely required.
2226 * In a high load environment the cache may well change between the test and any subsequent action (get, set, delete etc).
2228 * Its also worth mentioning that not all stores support key tests.
2229 * For stores that don't support key tests this functionality is mimicked by using the equivalent get method.
2230 * Just one more reason you should not use these methods unless you have a very good reason to do so.
2232 * @param array $keys
2233 * @return bool True if the cache has at least one of the given keys
2235 public function has_any(array $keys) {
2236 if (($this->has_a_ttl() && !$this->store_supports_native_ttl()) ||
!$this->store_supports_key_awareness()) {
2237 foreach ($keys as $key) {
2238 if ($this->has($key)) {
2244 /* @var cache_store|cache_is_key_aware $store */
2245 $store = $this->get_store();
2246 return $store->has_any(array_map(array($this, 'parse_key'), $keys));
2250 * The session loader never uses static acceleration.
2251 * Instead it stores things in the static $session variable. Shared between all session loaders.
2255 protected function use_static_acceleration() {
2263 * This class is used for request caches returned by the cache::make methods.
2265 * This cache class should never be interacted with directly. Instead you should always use the cache::make methods.
2266 * It is technically possible to call those methods through this class however there is no guarantee that you will get an
2267 * instance of this class back again.
2269 * @internal don't use me directly.
2273 * @copyright 2012 Sam Hemelryk
2274 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2276 class cache_request
extends cache
{
2277 // This comment appeases code pre-checker ;) !