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