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