MDL-40893 backup: cache question answers for performance
[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 moodle_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 moodle_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 moodle_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[] = ($usingloader) ? $key : $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                     $pkey = ($usingloader) ? $key : $keysparsed[$key];
434                     $realkey = ($usingloader) ? $parsedkeys[$key] : $key;
435                     $result[$pkey] = $value;
436                     if ($value !== false) {
437                         $this->set($realkey, $value);
438                     }
439                 }
440                 unset($resultmissing);
441             }
442             unset($missingkeys);
443         }
445         // Create an array with the original keys and the found values. This will be what we return.
446         $fullresult = array();
447         foreach ($result as $key => $value) {
448             $fullresult[$parsedkeys[$key]] = $value;
449         }
450         unset($result);
452         // Final step is to check strictness.
453         if ($strictness === MUST_EXIST) {
454             foreach ($keys as $key) {
455                 if (!array_key_exists($key, $fullresult)) {
456                     throw new moodle_exception('Not all the requested keys existed within the cache stores.');
457                 }
458             }
459         }
461         // Return the result. Phew!
462         return $fullresult;
463     }
465     /**
466      * Sends a key => value pair to the cache.
467      *
468      * <code>
469      * // This code will add four entries to the cache, one for each url.
470      * $cache->set('main', 'http://moodle.org');
471      * $cache->set('docs', 'http://docs.moodle.org');
472      * $cache->set('tracker', 'http://tracker.moodle.org');
473      * $cache->set('qa', 'http://qa.moodle.net');
474      * </code>
475      *
476      * @param string|int $key The key for the data being requested.
477      *      It can be any structure although using a scalar string or int is recommended in the interests of performance.
478      *      In advanced cases an array may be useful such as in situations requiring the multi-key functionality.
479      * @param mixed $data The data to set against the key.
480      * @return bool True on success, false otherwise.
481      */
482     public function set($key, $data) {
483         if ($this->perfdebug) {
484             cache_helper::record_cache_set($this->storetype, $this->definition->get_id());
485         }
486         if (is_object($data) && $data instanceof cacheable_object) {
487             $data = new cache_cached_object($data);
488         } else if (!is_scalar($data)) {
489             // If data is an object it will be a reference.
490             // If data is an array if may contain references.
491             // We want to break references so that the cache cannot be modified outside of itself.
492             // Call the function to unreference it (in the best way possible).
493             $data = $this->unref($data);
494         }
495         if ($this->has_a_ttl() && !$this->store_supports_native_ttl()) {
496             $data = new cache_ttl_wrapper($data, $this->definition->get_ttl());
497         }
498         $parsedkey = $this->parse_key($key);
499         if ($this->is_using_persist_cache()) {
500             $this->set_in_persist_cache($parsedkey, $data);
501         }
502         return $this->store->set($parsedkey, $data);
503     }
505     /**
506      * Removes references where required.
507      *
508      * @param stdClass|array $data
509      */
510     protected function unref($data) {
511         if ($this->definition->uses_simple_data()) {
512             return $data;
513         }
514         // Check if it requires serialisation in order to produce a reference free copy.
515         if ($this->requires_serialisation($data)) {
516             // Damn, its going to have to be serialise.
517             $data = serialize($data);
518             // We unserialise immediately so that we don't have to do it every time on get.
519             $data = unserialize($data);
520         } else if (!is_scalar($data)) {
521             // Its safe to clone, lets do it, its going to beat the pants of serialisation.
522             $data = $this->deep_clone($data);
523         }
524         return $data;
525     }
527     /**
528      * Checks to see if a var requires serialisation.
529      *
530      * @param mixed $value The value to check.
531      * @param int $depth Used to ensure we don't enter an endless loop (think recursion).
532      * @return bool Returns true if the value is going to require serialisation in order to ensure a reference free copy
533      *      or false if its safe to clone.
534      */
535     protected function requires_serialisation($value, $depth = 1) {
536         if (is_scalar($value)) {
537             return false;
538         } else if (is_array($value) || $value instanceof stdClass || $value instanceof Traversable) {
539             if ($depth > 5) {
540                 // Skrew it, mega-deep object, developer you suck, we're just going to serialise.
541                 return true;
542             }
543             foreach ($value as $key => $subvalue) {
544                 if ($this->requires_serialisation($subvalue, $depth++)) {
545                     return true;
546                 }
547             }
548         }
549         // Its not scalar, array, or stdClass so we'll need to serialise.
550         return true;
551     }
553     /**
554      * Creates a reference free clone of the given value.
555      *
556      * @param mixed $value
557      * @return mixed
558      */
559     protected function deep_clone($value) {
560         if (is_object($value)) {
561             // Objects must be cloned to begin with.
562             $value = clone $value;
563         }
564         if (is_array($value) || is_object($value)) {
565             foreach ($value as $key => $subvalue) {
566                 $value[$key] = $this->deep_clone($subvalue);
567             }
568         }
569         return $value;
570     }
572     /**
573      * Sends several key => value pairs to the cache.
574      *
575      * Using this function comes with potential performance implications.
576      * Not all cache stores will support get_many/set_many operations and in order to replicate this functionality will call
577      * the equivalent singular method for each item provided.
578      * This should not deter you from using this function as there is a performance benefit in situations where the cache store
579      * does support it, but you should be aware of this fact.
580      *
581      * <code>
582      * // This code will add four entries to the cache, one for each url.
583      * $cache->set_many(array(
584      *     'main' => 'http://moodle.org',
585      *     'docs' => 'http://docs.moodle.org',
586      *     'tracker' => 'http://tracker.moodle.org',
587      *     'qa' => ''http://qa.moodle.net'
588      * ));
589      * </code>
590      *
591      * @param array $keyvaluearray An array of key => value pairs to send to the cache.
592      * @return int The number of items successfully set. It is up to the developer to check this matches the number of items.
593      *      ... if they care that is.
594      */
595     public function set_many(array $keyvaluearray) {
596         $data = array();
597         $simulatettl = $this->has_a_ttl() && !$this->store_supports_native_ttl();
598         $usepersistcache = $this->is_using_persist_cache();
599         foreach ($keyvaluearray as $key => $value) {
600             if (is_object($value) && $value instanceof cacheable_object) {
601                 $value = new cache_cached_object($value);
602             } else if (!is_scalar($value)) {
603                 // If data is an object it will be a reference.
604                 // If data is an array if may contain references.
605                 // We want to break references so that the cache cannot be modified outside of itself.
606                 // Call the function to unreference it (in the best way possible).
607                 $value = $this->unref($value);
608             }
609             if ($simulatettl) {
610                 $value = new cache_ttl_wrapper($value, $this->definition->get_ttl());
611             }
612             $data[$key] = array(
613                 'key' => $this->parse_key($key),
614                 'value' => $value
615             );
616             if ($usepersistcache) {
617                 $this->set_in_persist_cache($data[$key]['key'], $value);
618             }
619         }
620         if ($this->perfdebug) {
621             cache_helper::record_cache_set($this->storetype, $this->definition->get_id());
622         }
623         return $this->store->set_many($data);
624     }
626     /**
627      * Test is a cache has a key.
628      *
629      * The use of the has methods is strongly discouraged. In a high load environment the cache may well change between the
630      * test and any subsequent action (get, set, delete etc).
631      * Instead it is recommended to write your code in such a way they it performs the following steps:
632      * <ol>
633      * <li>Attempt to retrieve the information.</li>
634      * <li>Generate the information.</li>
635      * <li>Attempt to set the information</li>
636      * </ol>
637      *
638      * Its also worth mentioning that not all stores support key tests.
639      * For stores that don't support key tests this functionality is mimicked by using the equivalent get method.
640      * Just one more reason you should not use these methods unless you have a very good reason to do so.
641      *
642      * @param string|int $key
643      * @param bool $tryloadifpossible If set to true, the cache doesn't contain the key, and there is another cache loader or
644      *      data source then the code will try load the key value from the next item in the chain.
645      * @return bool True if the cache has the requested key, false otherwise.
646      */
647     public function has($key, $tryloadifpossible = false) {
648         $parsedkey = $this->parse_key($key);
649         if ($this->is_in_persist_cache($parsedkey)) {
650             // Hoorah, that was easy. It exists in the persist cache so we definitely have it.
651             return true;
652         }
653         if ($this->has_a_ttl() && !$this->store_supports_native_ttl()) {
654             // The data has a TTL and the store doesn't support it natively.
655             // We must fetch the data and expect a ttl wrapper.
656             $data = $this->store->get($parsedkey);
657             $has = ($data instanceof cache_ttl_wrapper && !$data->has_expired());
658         } else if (!$this->store_supports_key_awareness()) {
659             // The store doesn't support key awareness, get the data and check it manually... puke.
660             // Either no TTL is set of the store supports its handling natively.
661             $data = $this->store->get($parsedkey);
662             $has = ($data !== false);
663         } else {
664             // The store supports key awareness, this is easy!
665             // Either no TTL is set of the store supports its handling natively.
666             $has = $this->store->has($parsedkey);
667         }
668         if (!$has && $tryloadifpossible) {
669             if ($this->loader !== false) {
670                 $result = $this->loader->get($parsedkey);
671             } else if ($this->datasource !== null) {
672                 $result = $this->datasource->load_for_cache($key);
673             }
674             $has = ($result !== null);
675             if ($has) {
676                 $this->set($key, $result);
677             }
678         }
679         return $has;
680     }
682     /**
683      * Test is a cache has all of the given keys.
684      *
685      * It is strongly recommended to avoid the use of this function if not absolutely required.
686      * In a high load environment the cache may well change between the test and any subsequent action (get, set, delete etc).
687      *
688      * Its also worth mentioning that not all stores support key tests.
689      * For stores that don't support key tests this functionality is mimicked by using the equivalent get method.
690      * Just one more reason you should not use these methods unless you have a very good reason to do so.
691      *
692      * @param array $keys
693      * @return bool True if the cache has all of the given keys, false otherwise.
694      */
695     public function has_all(array $keys) {
696         if (($this->has_a_ttl() && !$this->store_supports_native_ttl()) || !$this->store_supports_key_awareness()) {
697             foreach ($keys as $key) {
698                 if (!$this->has($key)) {
699                     return false;
700                 }
701             }
702             return true;
703         }
704         $parsedkeys = array_map(array($this, 'parse_key'), $keys);
705         return $this->store->has_all($parsedkeys);
706     }
708     /**
709      * Test if a cache has at least one of the given keys.
710      *
711      * It is strongly recommended to avoid the use of this function if not absolutely required.
712      * In a high load environment the cache may well change between the test and any subsequent action (get, set, delete etc).
713      *
714      * Its also worth mentioning that not all stores support key tests.
715      * For stores that don't support key tests this functionality is mimicked by using the equivalent get method.
716      * Just one more reason you should not use these methods unless you have a very good reason to do so.
717      *
718      * @param array $keys
719      * @return bool True if the cache has at least one of the given keys
720      */
721     public function has_any(array $keys) {
722         if (($this->has_a_ttl() && !$this->store_supports_native_ttl()) || !$this->store_supports_key_awareness()) {
723             foreach ($keys as $key) {
724                 if ($this->has($key)) {
725                     return true;
726                 }
727             }
728             return false;
729         }
731         if ($this->is_using_persist_cache()) {
732             $parsedkeys = array();
733             foreach ($keys as $id => $key) {
734                 $parsedkey = $this->parse_key($key);
735                 if ($this->is_in_persist_cache($parsedkey)) {
736                     return true;
737                 }
738                 $parsedkeys[] = $parsedkey;
739             }
740         } else {
741             $parsedkeys = array_map(array($this, 'parse_key'), $keys);
742         }
743         return $this->store->has_any($parsedkeys);
744     }
746     /**
747      * Delete the given key from the cache.
748      *
749      * @param string|int $key The key to delete.
750      * @param bool $recurse When set to true the key will also be deleted from all stacked cache loaders and their stores.
751      *     This happens by default and ensure that all the caches are consistent. It is NOT recommended to change this.
752      * @return bool True of success, false otherwise.
753      */
754     public function delete($key, $recurse = true) {
755         $parsedkey = $this->parse_key($key);
756         $this->delete_from_persist_cache($parsedkey);
757         if ($recurse && $this->loader !== false) {
758             // Delete from the bottom of the stack first.
759             $this->loader->delete($key, $recurse);
760         }
761         return $this->store->delete($parsedkey);
762     }
764     /**
765      * Delete all of the given keys from the cache.
766      *
767      * @param array $keys The key to delete.
768      * @param bool $recurse When set to true the key will also be deleted from all stacked cache loaders and their stores.
769      *     This happens by default and ensure that all the caches are consistent. It is NOT recommended to change this.
770      * @return int The number of items successfully deleted.
771      */
772     public function delete_many(array $keys, $recurse = true) {
773         $parsedkeys = array_map(array($this, 'parse_key'), $keys);
774         if ($this->is_using_persist_cache()) {
775             foreach ($parsedkeys as $parsedkey) {
776                 $this->delete_from_persist_cache($parsedkey);
777             }
778         }
779         if ($recurse && $this->loader !== false) {
780             // Delete from the bottom of the stack first.
781             $this->loader->delete_many($keys, $recurse);
782         }
783         return $this->store->delete_many($parsedkeys);
784     }
786     /**
787      * Purges the cache store, and loader if there is one.
788      *
789      * @return bool True on success, false otherwise
790      */
791     public function purge() {
792         // 1. Purge the persist cache.
793         $this->persistcache = array();
794         // 2. Purge the store.
795         $this->store->purge();
796         // 3. Optionally pruge any stacked loaders.
797         if ($this->loader) {
798             $this->loader->purge();
799         }
800         return true;
801     }
803     /**
804      * Parses the key turning it into a string (or array is required) suitable to be passed to the cache store.
805      *
806      * @param string|int $key As passed to get|set|delete etc.
807      * @return string|array String unless the store supports multi-identifiers in which case an array if returned.
808      */
809     protected function parse_key($key) {
810         // First up if the store supports multiple keys we'll go with that.
811         if ($this->store->supports_multiple_identifiers()) {
812             $result = $this->definition->generate_multi_key_parts();
813             $result['key'] = $key;
814             return $result;
815         }
816         // If not we need to generate a hash and to for that we use the cache_helper.
817         return cache_helper::hash_key($key, $this->definition);
818     }
820     /**
821      * Returns true if the cache is making use of a ttl.
822      * @return bool
823      */
824     protected function has_a_ttl() {
825         return $this->hasattl;
826     }
828     /**
829      * Returns true if the cache store supports native ttl.
830      * @return bool
831      */
832     protected function store_supports_native_ttl() {
833         if ($this->supportsnativettl === null) {
834             $this->supportsnativettl = ($this->store->supports_native_ttl());
835         }
836         return $this->supportsnativettl;
837     }
839     /**
840      * Returns the cache definition.
841      *
842      * @return cache_definition
843      */
844     protected function get_definition() {
845         return $this->definition;
846     }
848     /**
849      * Returns the cache store
850      *
851      * @return cache_store
852      */
853     protected function get_store() {
854         return $this->store;
855     }
857     /**
858      * Returns the loader associated with this instance.
859      *
860      * @since 2.4.4
861      * @return cache_loader|false
862      */
863     protected function get_loader() {
864         return $this->loader;
865     }
867     /**
868      * Returns the data source associated with this cache.
869      *
870      * @since 2.4.4
871      * @return cache_data_source|false
872      */
873     protected function get_datasource() {
874         return $this->datasource;
875     }
877     /**
878      * Returns true if the store supports key awareness.
879      *
880      * @return bool
881      */
882     protected function store_supports_key_awareness() {
883         if ($this->supportskeyawareness === null) {
884             $this->supportskeyawareness = ($this->store instanceof cache_is_key_aware);
885         }
886         return $this->supportskeyawareness;
887     }
889     /**
890      * Returns true if the store natively supports locking.
891      *
892      * @return bool
893      */
894     protected function store_supports_native_locking() {
895         if ($this->nativelocking === null) {
896             $this->nativelocking = ($this->store instanceof cache_is_lockable);
897         }
898         return $this->nativelocking;
899     }
901     /**
902      * Returns true if this cache is making use of the persist cache.
903      *
904      * @return bool
905      */
906     protected function is_using_persist_cache() {
907         return $this->persist;
908     }
910     /**
911      * Returns true if the requested key exists within the persist cache.
912      *
913      * @param string $key The parsed key
914      * @return bool
915      */
916     protected function is_in_persist_cache($key) {
917         // This method of checking if an array was supplied is faster than is_array.
918         if ($key === (array)$key) {
919             $key = $key['key'];
920         }
921         // This could be written as a single line, however it has been split because the ttl check is faster than the instanceof
922         // and has_expired calls.
923         if (!$this->persist || !array_key_exists($key, $this->persistcache)) {
924             return false;
925         }
926         if ($this->has_a_ttl() && $this->store_supports_native_ttl()) {
927              return !($this->persistcache[$key] instanceof cache_ttl_wrapper && $this->persistcache[$key]->has_expired());
928         }
929         return true;
930     }
932     /**
933      * Returns the item from the persist cache if it exists there.
934      *
935      * @param string $key The parsed key
936      * @return mixed|false The data from the persist cache or false if it wasn't there.
937      */
938     protected function get_from_persist_cache($key) {
939         // This method of checking if an array was supplied is faster than is_array.
940         if ($key === (array)$key) {
941             $key = $key['key'];
942         }
943         // This isset check is faster than array_key_exists but will return false
944         // for null values, meaning null values will come from backing store not
945         // the persist cache. We think this okay because null usage should be
946         // very rare (see comment in MDL-39472).
947         if (!$this->persist || !isset($this->persistcache[$key])) {
948             $result = false;
949         } else {
950             $data = $this->persistcache[$key];
951             if (!$this->has_a_ttl() || !$data instanceof cache_ttl_wrapper) {
952                 if ($data instanceof cache_cached_object) {
953                     $data = $data->restore_object();
954                 }
955                 $result = $data;
956             } else if ($data->has_expired()) {
957                 $this->delete_from_persist_cache($key);
958                 $result = false;
959             } else {
960                 if ($data instanceof cache_cached_object) {
961                     $data = $data->restore_object();
962                 }
963                 $result = $data->data;
964             }
965         }
966         if ($result) {
967             if ($this->perfdebug) {
968                 cache_helper::record_cache_hit('** static persist **', $this->definition->get_id());
969             }
970             if ($this->persistmaxsize > 1 && $this->persistcount > 1) {
971                 // Check to see if this is the last item on the persist keys array.
972                 if (end($this->persistkeys) !== $key) {
973                     // It isn't the last item.
974                     // Move the item to the end of the array so that it is last to be removed.
975                     unset($this->persistkeys[$key]);
976                     $this->persistkeys[$key] = $key;
977                 }
978             }
979             return $result;
980         } else {
981             if ($this->perfdebug) {
982                 cache_helper::record_cache_miss('** static persist **', $this->definition->get_id());
983             }
984             return false;
985         }
986     }
988     /**
989      * Sets a key value pair into the persist cache.
990      *
991      * @param string $key The parsed key
992      * @param mixed $data
993      * @return bool
994      */
995     protected function set_in_persist_cache($key, $data) {
996         // This method of checking if an array was supplied is faster than is_array.
997         if ($key === (array)$key) {
998             $key = $key['key'];
999         }
1000         $this->persistcache[$key] = $data;
1001         if ($this->persistmaxsize !== false) {
1002             $this->persistcount++;
1003             $this->persistkeys[$key] = $key;
1004             if ($this->persistcount > $this->persistmaxsize) {
1005                 $dropkey = array_shift($this->persistkeys);
1006                 unset($this->persistcache[$dropkey]);
1007                 $this->persistcount--;
1008             }
1009         }
1010         return true;
1011     }
1013     /**
1014      * Deletes an item from the persist cache.
1015      *
1016      * @param string|int $key As given to get|set|delete
1017      * @return bool True on success, false otherwise.
1018      */
1019     protected function delete_from_persist_cache($key) {
1020         unset($this->persistcache[$key]);
1021         if ($this->persistmaxsize !== false) {
1022             $dropkey = array_search($key, $this->persistkeys);
1023             if ($dropkey) {
1024                 unset($this->persistkeys[$dropkey]);
1025                 $this->persistcount--;
1026             }
1027         }
1028         return true;
1029     }
1031     /**
1032      * Returns the timestamp from the first request for the time from the cache API.
1033      *
1034      * This stamp needs to be used for all ttl and time based operations to ensure that we don't end up with
1035      * timing issues.
1036      *
1037      * @return int
1038      */
1039     public static function now() {
1040         if (self::$now === null) {
1041             self::$now = time();
1042         }
1043         return self::$now;
1044     }
1047 /**
1048  * An application cache.
1049  *
1050  * This class is used for application caches returned by the cache::make methods.
1051  * On top of the standard functionality it also allows locking to be required and or manually operated.
1052  *
1053  * This cache class should never be interacted with directly. Instead you should always use the cache::make methods.
1054  * It is technically possible to call those methods through this class however there is no guarantee that you will get an
1055  * instance of this class back again.
1056  *
1057  * @internal don't use me directly.
1058  *
1059  * @package    core
1060  * @category   cache
1061  * @copyright  2012 Sam Hemelryk
1062  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1063  */
1064 class cache_application extends cache implements cache_loader_with_locking {
1066     /**
1067      * Lock identifier.
1068      * This is used to ensure the lock belongs to the cache instance + definition + user.
1069      * @var string
1070      */
1071     protected $lockidentifier;
1073     /**
1074      * Gets set to true if the cache's primary store natively supports locking.
1075      * If it does then we use that, otherwise we need to instantiate a second store to use for locking.
1076      * @var cache_store
1077      */
1078     protected $nativelocking = null;
1080     /**
1081      * Gets set to true if the cache is going to be using locking.
1082      * This isn't a requirement, it doesn't need to use locking (most won't) and this bool is used to quickly check things.
1083      * If required then locking will be forced for the get|set|delete operation.
1084      * @var bool
1085      */
1086     protected $requirelocking = false;
1088     /**
1089      * Gets set to true if the cache must use read locking (get|has).
1090      * @var bool
1091      */
1092     protected $requirelockingread = false;
1094     /**
1095      * Gets set to true if the cache must use write locking (set|delete)
1096      * @var bool
1097      */
1098     protected $requirelockingwrite = false;
1100     /**
1101      * Gets set to a cache_store to use for locking if the caches primary store doesn't support locking natively.
1102      * @var cache_lock_interface
1103      */
1104     protected $cachelockinstance;
1106     /**
1107      * Overrides the cache construct method.
1108      *
1109      * You should not call this method from your code, instead you should use the cache::make methods.
1110      *
1111      * @param cache_definition $definition
1112      * @param cache_store $store
1113      * @param cache_loader|cache_data_source $loader
1114      */
1115     public function __construct(cache_definition $definition, cache_store $store, $loader = null) {
1116         parent::__construct($definition, $store, $loader);
1117         $this->nativelocking = $this->store_supports_native_locking();
1118         if ($definition->require_locking()) {
1119             $this->requirelocking = true;
1120             $this->requirelockingread = $definition->require_locking_read();
1121             $this->requirelockingwrite = $definition->require_locking_write();
1122         }
1124         if ($definition->has_invalidation_events()) {
1125             $lastinvalidation = $this->get('lastinvalidation');
1126             if ($lastinvalidation === false) {
1127                 // This is a new session, there won't be anything to invalidate. Set the time of the last invalidation and
1128                 // move on.
1129                 $this->set('lastinvalidation', cache::now());
1130                 return;
1131             } else if ($lastinvalidation == cache::now()) {
1132                 // We've already invalidated during this request.
1133                 return;
1134             }
1136             // Get the event invalidation cache.
1137             $cache = cache::make('core', 'eventinvalidation');
1138             $events = $cache->get_many($definition->get_invalidation_events());
1139             $todelete = array();
1140             $purgeall = false;
1141             // Iterate the returned data for the events.
1142             foreach ($events as $event => $keys) {
1143                 if ($keys === false) {
1144                     // No data to be invalidated yet.
1145                     continue;
1146                 }
1147                 // Look at each key and check the timestamp.
1148                 foreach ($keys as $key => $timestamp) {
1149                     // If the timestamp of the event is more than or equal to the last invalidation (happened between the last
1150                     // invalidation and now)then we need to invaliate the key.
1151                     if ($timestamp >= $lastinvalidation) {
1152                         if ($key === 'purged') {
1153                             $purgeall = true;
1154                             break;
1155                         } else {
1156                             $todelete[] = $key;
1157                         }
1158                     }
1159                 }
1160             }
1161             if ($purgeall) {
1162                 $this->purge();
1163             } else if (!empty($todelete)) {
1164                 $todelete = array_unique($todelete);
1165                 $this->delete_many($todelete);
1166             }
1167             // Set the time of the last invalidation.
1168             $this->set('lastinvalidation', cache::now());
1169         }
1170     }
1172     /**
1173      * Returns the identifier to use
1174      *
1175      * @staticvar int $instances Counts the number of instances. Used as part of the lock identifier.
1176      * @return string
1177      */
1178     public function get_identifier() {
1179         static $instances = 0;
1180         if ($this->lockidentifier === null) {
1181             $this->lockidentifier = md5(
1182                 $this->get_definition()->generate_definition_hash() .
1183                 sesskey() .
1184                 $instances++ .
1185                 'cache_application'
1186             );
1187         }
1188         return $this->lockidentifier;
1189     }
1191     /**
1192      * Fixes the instance up after a clone.
1193      */
1194     public function __clone() {
1195         // Force a new idenfitier.
1196         $this->lockidentifier = null;
1197     }
1199     /**
1200      * Acquires a lock on the given key.
1201      *
1202      * This is done automatically if the definition requires it.
1203      * It is recommended to use a definition if you want to have locking although it is possible to do locking without having
1204      * it required by the definition.
1205      * The problem with such an approach is that you cannot ensure that code will consistently use locking. You will need to
1206      * rely on the integrators review skills.
1207      *
1208      * @param string|int $key The key as given to get|set|delete
1209      * @return bool Returns true if the lock could be acquired, false otherwise.
1210      */
1211     public function acquire_lock($key) {
1212         $key = $this->parse_key($key);
1213         if ($this->nativelocking) {
1214             return $this->get_store()->acquire_lock($key, $this->get_identifier());
1215         } else {
1216             $this->ensure_cachelock_available();
1217             return $this->cachelockinstance->lock($key, $this->get_identifier());
1218         }
1219     }
1221     /**
1222      * Checks if this cache has a lock on the given key.
1223      *
1224      * @param string|int $key The key as given to get|set|delete
1225      * @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
1226      *      someone else has the lock.
1227      */
1228     public function check_lock_state($key) {
1229         $key = $this->parse_key($key);
1230         if ($this->nativelocking) {
1231             return $this->get_store()->check_lock_state($key, $this->get_identifier());
1232         } else {
1233             $this->ensure_cachelock_available();
1234             return $this->cachelockinstance->check_state($key, $this->get_identifier());
1235         }
1236     }
1238     /**
1239      * Releases the lock this cache has on the given key
1240      *
1241      * @param string|int $key
1242      * @return bool True if the operation succeeded, false otherwise.
1243      */
1244     public function release_lock($key) {
1245         $key = $this->parse_key($key);
1246         if ($this->nativelocking) {
1247             return $this->get_store()->release_lock($key, $this->get_identifier());
1248         } else {
1249             $this->ensure_cachelock_available();
1250             return $this->cachelockinstance->unlock($key, $this->get_identifier());
1251         }
1252     }
1254     /**
1255      * Ensure that the dedicated lock store is ready to go.
1256      *
1257      * This should only happen if the cache store doesn't natively support it.
1258      */
1259     protected function ensure_cachelock_available() {
1260         if ($this->cachelockinstance === null) {
1261             $this->cachelockinstance = cache_helper::get_cachelock_for_store($this->get_store());
1262         }
1263     }
1265     /**
1266      * Sends a key => value pair to the cache.
1267      *
1268      * <code>
1269      * // This code will add four entries to the cache, one for each url.
1270      * $cache->set('main', 'http://moodle.org');
1271      * $cache->set('docs', 'http://docs.moodle.org');
1272      * $cache->set('tracker', 'http://tracker.moodle.org');
1273      * $cache->set('qa', 'http://qa.moodle.net');
1274      * </code>
1275      *
1276      * @param string|int $key The key for the data being requested.
1277      * @param mixed $data The data to set against the key.
1278      * @return bool True on success, false otherwise.
1279      */
1280     public function set($key, $data) {
1281         if ($this->requirelockingwrite && !$this->acquire_lock($key)) {
1282             return false;
1283         }
1284         $result = parent::set($key, $data);
1285         if ($this->requirelockingwrite && !$this->release_lock($key)) {
1286             debugging('Failed to release cache lock on set operation... this should not happen.', DEBUG_DEVELOPER);
1287         }
1288         return $result;
1289     }
1291     /**
1292      * Sends several key => value pairs to the cache.
1293      *
1294      * Using this function comes with potential performance implications.
1295      * Not all cache stores will support get_many/set_many operations and in order to replicate this functionality will call
1296      * the equivalent singular method for each item provided.
1297      * This should not deter you from using this function as there is a performance benefit in situations where the cache store
1298      * does support it, but you should be aware of this fact.
1299      *
1300      * <code>
1301      * // This code will add four entries to the cache, one for each url.
1302      * $cache->set_many(array(
1303      *     'main' => 'http://moodle.org',
1304      *     'docs' => 'http://docs.moodle.org',
1305      *     'tracker' => 'http://tracker.moodle.org',
1306      *     'qa' => ''http://qa.moodle.net'
1307      * ));
1308      * </code>
1309      *
1310      * @param array $keyvaluearray An array of key => value pairs to send to the cache.
1311      * @return int The number of items successfully set. It is up to the developer to check this matches the number of items.
1312      *      ... if they care that is.
1313      */
1314     public function set_many(array $keyvaluearray) {
1315         if ($this->requirelockingwrite) {
1316             $locks = array();
1317             foreach ($keyvaluearray as $id => $pair) {
1318                 $key = $pair['key'];
1319                 if ($this->acquire_lock($key)) {
1320                     $locks[] = $key;
1321                 } else {
1322                     unset($keyvaluearray[$id]);
1323                 }
1324             }
1325         }
1326         $result = parent::set_many($keyvaluearray);
1327         if ($this->requirelockingwrite) {
1328             foreach ($locks as $key) {
1329                 if ($this->release_lock($key)) {
1330                     debugging('Failed to release cache lock on set_many operation... this should not happen.', DEBUG_DEVELOPER);
1331                 }
1332             }
1333         }
1334         return $result;
1335     }
1337     /**
1338      * Retrieves the value for the given key from the cache.
1339      *
1340      * @param string|int $key The key for the data being requested.
1341      * @param int $strictness One of IGNORE_MISSING | MUST_EXIST
1342      * @return mixed|false The data from the cache or false if the key did not exist within the cache.
1343      * @throws moodle_exception
1344      */
1345     public function get($key, $strictness = IGNORE_MISSING) {
1346         if ($this->requirelockingread && $this->check_lock_state($key) === false) {
1347             // Read locking required and someone else has the read lock.
1348             return false;
1349         }
1350         return parent::get($key, $strictness);
1351     }
1353     /**
1354      * Retrieves an array of values for an array of keys.
1355      *
1356      * Using this function comes with potential performance implications.
1357      * Not all cache stores will support get_many/set_many operations and in order to replicate this functionality will call
1358      * the equivalent singular method for each item provided.
1359      * This should not deter you from using this function as there is a performance benefit in situations where the cache store
1360      * does support it, but you should be aware of this fact.
1361      *
1362      * @param array $keys The keys of the data being requested.
1363      * @param int $strictness One of IGNORE_MISSING or MUST_EXIST.
1364      * @return array An array of key value pairs for the items that could be retrieved from the cache.
1365      *      If MUST_EXIST was used and not all keys existed within the cache then an exception will be thrown.
1366      *      Otherwise any key that did not exist will have a data value of false within the results.
1367      * @throws moodle_exception
1368      */
1369     public function get_many(array $keys, $strictness = IGNORE_MISSING) {
1370         if ($this->requirelockingread) {
1371             foreach ($keys as $id => $key) {
1372                 $lock =$this->acquire_lock($key);
1373                 if (!$lock) {
1374                     if ($strictness === MUST_EXIST) {
1375                         throw new coding_exception('Could not acquire read locks for all of the items being requested.');
1376                     } else {
1377                         // Can't return this as we couldn't get a read lock.
1378                         unset($keys[$id]);
1379                     }
1380                 }
1382             }
1383         }
1384         return parent::get_many($keys, $strictness);
1385     }
1387     /**
1388      * Delete the given key from the cache.
1389      *
1390      * @param string|int $key The key to delete.
1391      * @param bool $recurse When set to true the key will also be deleted from all stacked cache loaders and their stores.
1392      *     This happens by default and ensure that all the caches are consistent. It is NOT recommended to change this.
1393      * @return bool True of success, false otherwise.
1394      */
1395     public function delete($key, $recurse = true) {
1396         if ($this->requirelockingwrite && !$this->acquire_lock($key)) {
1397             return false;
1398         }
1399         $result = parent::delete($key, $recurse);
1400         if ($this->requirelockingwrite && !$this->release_lock($key)) {
1401             debugging('Failed to release cache lock on delete operation... this should not happen.', DEBUG_DEVELOPER);
1402         }
1403         return $result;
1404     }
1406     /**
1407      * Delete all of the given keys from the cache.
1408      *
1409      * @param array $keys The key to delete.
1410      * @param bool $recurse When set to true the key will also be deleted from all stacked cache loaders and their stores.
1411      *     This happens by default and ensure that all the caches are consistent. It is NOT recommended to change this.
1412      * @return int The number of items successfully deleted.
1413      */
1414     public function delete_many(array $keys, $recurse = true) {
1415         if ($this->requirelockingwrite) {
1416             $locks = array();
1417             foreach ($keys as $id => $key) {
1418                 if ($this->acquire_lock($key)) {
1419                     $locks[] = $key;
1420                 } else {
1421                     unset($keys[$id]);
1422                 }
1423             }
1424         }
1425         $result = parent::delete_many($keys, $recurse);
1426         if ($this->requirelockingwrite) {
1427             foreach ($locks as $key) {
1428                 if ($this->release_lock($key)) {
1429                     debugging('Failed to release cache lock on delete_many operation... this should not happen.', DEBUG_DEVELOPER);
1430                 }
1431             }
1432         }
1433         return $result;
1434     }
1437 /**
1438  * A session cache.
1439  *
1440  * This class is used for session caches returned by the cache::make methods.
1441  *
1442  * It differs from the application loader in a couple of noteable ways:
1443  *    1. Sessions are always expected to be persistent.
1444  *       Because of this we don't ever use the persist cache and instead a session array
1445  *       containing all of the data is maintained by this object.
1446  *    2. Session data for a loader instance (store + definition) is consolidate into a
1447  *       single array for storage within the store.
1448  *       Along with this we embed a lastaccessed time with the data. This way we can
1449  *       check sessions for a last access time.
1450  *    3. Session stores are required to support key searching and must
1451  *       implement cache_is_searchable. This ensures stores used for the cache can be
1452  *       targetted for garbage collection of session data.
1453  *
1454  * This cache class should never be interacted with directly. Instead you should always use the cache::make methods.
1455  * It is technically possible to call those methods through this class however there is no guarantee that you will get an
1456  * instance of this class back again.
1457  *
1458  * @todo we should support locking in the session as well. Should be pretty simple to set up.
1459  *
1460  * @internal don't use me directly.
1461  *
1462  * @package    core
1463  * @category   cache
1464  * @copyright  2012 Sam Hemelryk
1465  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1466  */
1467 class cache_session extends cache {
1468     /**
1469      * The user the session has been established for.
1470      * @var int
1471      */
1472     protected static $loadeduserid = null;
1474     /**
1475      * The userid this cache is currently using.
1476      * @var int
1477      */
1478     protected $currentuserid = null;
1480     /**
1481      * The session id we are currently using.
1482      * @var array
1483      */
1484     protected $sessionid = null;
1486     /**
1487      * The session data for the above session id.
1488      * @var array
1489      */
1490     protected $session = null;
1492     /**
1493      * Constant used to prefix keys.
1494      */
1495     const KEY_PREFIX = 'sess_';
1497     /**
1498      * Override the cache::construct method.
1499      *
1500      * This function gets overriden so that we can process any invalidation events if need be.
1501      * If the definition doesn't have any invalidation events then this occurs exactly as it would for the cache class.
1502      * Otherwise we look at the last invalidation time and then check the invalidation data for events that have occured
1503      * between then now.
1504      *
1505      * You should not call this method from your code, instead you should use the cache::make methods.
1506      *
1507      * @param cache_definition $definition
1508      * @param cache_store $store
1509      * @param cache_loader|cache_data_source $loader
1510      * @return void
1511      */
1512     public function __construct(cache_definition $definition, cache_store $store, $loader = null) {
1513         // First up copy the loadeduserid to the current user id.
1514         $this->currentuserid = self::$loadeduserid;
1515         parent::__construct($definition, $store, $loader);
1516         if ($definition->has_invalidation_events()) {
1517             $lastinvalidation = $this->get('lastsessioninvalidation');
1518             if ($lastinvalidation === false) {
1519                 // This is a new session, there won't be anything to invalidate. Set the time of the last invalidation and
1520                 // move on.
1521                 $this->set('lastsessioninvalidation', cache::now());
1522                 return;
1523             } else if ($lastinvalidation == cache::now()) {
1524                 // We've already invalidated during this request.
1525                 return;
1526             }
1528             // Get the event invalidation cache.
1529             $cache = cache::make('core', 'eventinvalidation');
1530             $events = $cache->get_many($definition->get_invalidation_events());
1531             $todelete = array();
1532             $purgeall = false;
1533             // Iterate the returned data for the events.
1534             foreach ($events as $event => $keys) {
1535                 if ($keys === false) {
1536                     // No data to be invalidated yet.
1537                     continue;
1538                 }
1539                 // Look at each key and check the timestamp.
1540                 foreach ($keys as $key => $timestamp) {
1541                     // If the timestamp of the event is more than or equal to the last invalidation (happened between the last
1542                     // invalidation and now)then we need to invaliate the key.
1543                     if ($timestamp >= $lastinvalidation) {
1544                         if ($key === 'purged') {
1545                             $purgeall = true;
1546                             break;
1547                         } else {
1548                             $todelete[] = $key;
1549                         }
1550                     }
1551                 }
1552             }
1553             if ($purgeall) {
1554                 $this->purge();
1555             } else if (!empty($todelete)) {
1556                 $todelete = array_unique($todelete);
1557                 $this->delete_many($todelete);
1558             }
1559             // Set the time of the last invalidation.
1560             $this->set('lastsessioninvalidation', cache::now());
1561         }
1562     }
1564     /**
1565      * Parses the key turning it into a string (or array is required) suitable to be passed to the cache store.
1566      *
1567      * This function is called for every operation that uses keys. For this reason we use this function to also check
1568      * that the current user is the same as the user who last used this cache.
1569      *
1570      * On top of that if prepends the string 'sess_' to the start of all keys. The _ ensures things are easily identifiable.
1571      *
1572      * @param string|int $key As passed to get|set|delete etc.
1573      * @return string|array String unless the store supports multi-identifiers in which case an array if returned.
1574      */
1575     protected function parse_key($key) {
1576         if ($key === 'lastaccess') {
1577             $key = '__lastaccess__';
1578         }
1579         return 'sess_'.parent::parse_key($key);
1580     }
1582     /**
1583      * Check that this cache instance is tracking the current user.
1584      */
1585     protected function check_tracked_user() {
1586         if (isset($_SESSION['USER']->id)) {
1587             // Get the id of the current user.
1588             $new = $_SESSION['USER']->id;
1589         } else {
1590             // No user set up yet.
1591             $new = 0;
1592         }
1593         if ($new !== self::$loadeduserid) {
1594             // The current user doesn't match the tracked userid for this request.
1595             if (!is_null(self::$loadeduserid)) {
1596                 // Purge the data we have for the old user.
1597                 // This way we don't bloat the session.
1598                 $this->purge();
1599                 // Update the session id just in case!
1600                 $this->sessionid = session_id();
1601             }
1602             self::$loadeduserid = $new;
1603             $this->currentuserid = $new;
1604         } else if ($new !== $this->currentuserid) {
1605             // The current user matches the loaded user but not the user last used by this cache.
1606             $this->purge();
1607             $this->currentuserid = $new;
1608             // Update the session id just in case!
1609             $this->sessionid = session_id();
1610         }
1611     }
1613     /**
1614      * Gets the session data.
1615      *
1616      * @param bool $force If true the session data will be loaded from the store again.
1617      * @return array An array of session data.
1618      */
1619     protected function get_session_data($force = false) {
1620         if ($this->sessionid === null) {
1621             $this->sessionid = session_id();
1622         }
1623         if (is_array($this->session) && !$force) {
1624             return $this->session;
1625         }
1626         $session = parent::get($this->sessionid);
1627         if ($session === false) {
1628             $session = array();
1629         }
1630         // We have to write here to ensure that the lastaccess time is recorded.
1631         // And also in order to ensure the session entry exists as when we save it on __destruct
1632         // $CFG is likely to have already been destroyed.
1633         $this->save_session($session);
1634         return $this->session;
1635     }
1637     /**
1638      * Saves the session data.
1639      *
1640      * This function also updates the last access time.
1641      *
1642      * @param array $session
1643      * @return bool
1644      */
1645     protected function save_session(array $session) {
1646         $session['lastaccess'] = time();
1647         $this->session = $session;
1648         return parent::set($this->sessionid, $this->session);
1649     }
1651     /**
1652      * Retrieves the value for the given key from the cache.
1653      *
1654      * @param string|int $key The key for the data being requested.
1655      *      It can be any structure although using a scalar string or int is recommended in the interests of performance.
1656      *      In advanced cases an array may be useful such as in situations requiring the multi-key functionality.
1657      * @param int $strictness One of IGNORE_MISSING | MUST_EXIST
1658      * @return mixed|false The data from the cache or false if the key did not exist within the cache.
1659      * @throws moodle_exception
1660      */
1661     public function get($key, $strictness = IGNORE_MISSING) {
1662         // Check the tracked user.
1663         $this->check_tracked_user();
1664         // 2. Parse the key.
1665         $parsedkey = $this->parse_key($key);
1666         // 3. Get it from the store.
1667         $result = false;
1668         $session = $this->get_session_data();
1669         if (array_key_exists($parsedkey, $session)) {
1670             $result = $session[$parsedkey];
1671             if ($result instanceof cache_ttl_wrapper) {
1672                 if ($result->has_expired()) {
1673                     $this->get_store()->delete($parsedkey);
1674                     $result = false;
1675                 } else {
1676                     $result = $result->data;
1677                 }
1678             }
1679             if ($result instanceof cache_cached_object) {
1680                 $result = $result->restore_object();
1681             }
1682         }
1683         // 4. Load if from the loader/datasource if we don't already have it.
1684         $setaftervalidation = false;
1685         if ($result === false) {
1686             if ($this->perfdebug) {
1687                 cache_helper::record_cache_miss('**static session**', $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             $setaftervalidation = ($result !== false);
1698         } else if ($this->perfdebug) {
1699             cache_helper::record_cache_hit('**static session**', $this->get_definition()->get_id());
1700         }
1701         // 5. Validate strictness.
1702         if ($strictness === MUST_EXIST && $result === false) {
1703             throw new moodle_exception('Requested key did not exist in any cache stores and could not be loaded.');
1704         }
1705         // 6. Set it to the store if we got it from the loader/datasource.
1706         if ($setaftervalidation) {
1707             $this->set($key, $result);
1708         }
1709         // 7. Make sure we don't pass back anything that could be a reference.
1710         //    We don't want people modifying the data in the cache.
1711         if (!is_scalar($result)) {
1712             // If data is an object it will be a reference.
1713             // If data is an array if may contain references.
1714             // We want to break references so that the cache cannot be modified outside of itself.
1715             // Call the function to unreference it (in the best way possible).
1716             $result = $this->unref($result);
1717         }
1718         return $result;
1719     }
1721     /**
1722      * Sends a key => value pair to the cache.
1723      *
1724      * <code>
1725      * // This code will add four entries to the cache, one for each url.
1726      * $cache->set('main', 'http://moodle.org');
1727      * $cache->set('docs', 'http://docs.moodle.org');
1728      * $cache->set('tracker', 'http://tracker.moodle.org');
1729      * $cache->set('qa', 'http://qa.moodle.net');
1730      * </code>
1731      *
1732      * @param string|int $key The key for the data being requested.
1733      *      It can be any structure although using a scalar string or int is recommended in the interests of performance.
1734      *      In advanced cases an array may be useful such as in situations requiring the multi-key functionality.
1735      * @param mixed $data The data to set against the key.
1736      * @return bool True on success, false otherwise.
1737      */
1738     public function set($key, $data) {
1739         $this->check_tracked_user();
1740         if ($this->perfdebug) {
1741             cache_helper::record_cache_set('**static session**', $this->get_definition()->get_id());
1742         }
1743         if (is_object($data) && $data instanceof cacheable_object) {
1744             $data = new cache_cached_object($data);
1745         } else if (!is_scalar($data)) {
1746             // If data is an object it will be a reference.
1747             // If data is an array if may contain references.
1748             // We want to break references so that the cache cannot be modified outside of itself.
1749             // Call the function to unreference it (in the best way possible).
1750             $data = $this->unref($data);
1751         }
1752         // We dont' support native TTL here as we consolidate data for sessions.
1753         if ($this->has_a_ttl()) {
1754             $data = new cache_ttl_wrapper($data, $this->get_definition()->get_ttl());
1755         }
1756         $session = $this->get_session_data();
1757         $session[$this->parse_key($key)] = $data;
1758         return $this->save_session($session);
1759     }
1761     /**
1762      * Delete the given key from the cache.
1763      *
1764      * @param string|int $key The key to delete.
1765      * @param bool $recurse When set to true the key will also be deleted from all stacked cache loaders and their stores.
1766      *     This happens by default and ensure that all the caches are consistent. It is NOT recommended to change this.
1767      * @return bool True of success, false otherwise.
1768      */
1769     public function delete($key, $recurse = true) {
1770         $this->check_tracked_user();
1771         $parsedkey = $this->parse_key($key);
1772         if ($recurse && $this->get_loader() !== false) {
1773             // Delete from the bottom of the stack first.
1774             $this->get_loader()->delete($key, $recurse);
1775         }
1776         $session = $this->get_session_data();
1777         unset($session[$parsedkey]);
1778         return $this->save_session($session);
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 moodle_exception
1798      */
1799     public function get_many(array $keys, $strictness = IGNORE_MISSING) {
1800         $this->check_tracked_user();
1801         $return = array();
1802         foreach ($keys as $key) {
1803             $return[$key] = $this->get($key, $strictness);
1804         }
1805         return $return;
1806     }
1808     /**
1809      * Delete all of the given keys from the cache.
1810      *
1811      * @param array $keys The key to delete.
1812      * @param bool $recurse When set to true the key will also be deleted from all stacked cache loaders and their stores.
1813      *     This happens by default and ensure that all the caches are consistent. It is NOT recommended to change this.
1814      * @return int The number of items successfully deleted.
1815      */
1816     public function delete_many(array $keys, $recurse = true) {
1817         $this->check_tracked_user();
1818         $parsedkeys = array_map(array($this, 'parse_key'), $keys);
1819         if ($recurse && $this->get_loader() !== false) {
1820             // Delete from the bottom of the stack first.
1821             $this->get_loader()->delete_many($keys, $recurse);
1822         }
1823         $session = $this->get_session_data();
1824         foreach ($parsedkeys as $parsedkey) {
1825             unset($session[$parsedkey]);
1826         }
1827         $this->save_session($session);
1828         return count($keys);
1829     }
1831     /**
1832      * Sends several key => value pairs to the cache.
1833      *
1834      * Using this function comes with potential performance implications.
1835      * Not all cache stores will support get_many/set_many operations and in order to replicate this functionality will call
1836      * the equivalent singular method for each item provided.
1837      * This should not deter you from using this function as there is a performance benefit in situations where the cache store
1838      * does support it, but you should be aware of this fact.
1839      *
1840      * <code>
1841      * // This code will add four entries to the cache, one for each url.
1842      * $cache->set_many(array(
1843      *     'main' => 'http://moodle.org',
1844      *     'docs' => 'http://docs.moodle.org',
1845      *     'tracker' => 'http://tracker.moodle.org',
1846      *     'qa' => ''http://qa.moodle.net'
1847      * ));
1848      * </code>
1849      *
1850      * @param array $keyvaluearray An array of key => value pairs to send to the cache.
1851      * @return int The number of items successfully set. It is up to the developer to check this matches the number of items.
1852      *      ... if they care that is.
1853      */
1854     public function set_many(array $keyvaluearray) {
1855         $this->check_tracked_user();
1856         $session = $this->get_session_data();
1857         $simulatettl = $this->has_a_ttl();
1858         foreach ($keyvaluearray as $key => $value) {
1859             if (is_object($value) && $value instanceof cacheable_object) {
1860                 $value = new cache_cached_object($value);
1861             } else if (!is_scalar($value)) {
1862                 // If data is an object it will be a reference.
1863                 // If data is an array if may contain references.
1864                 // We want to break references so that the cache cannot be modified outside of itself.
1865                 // Call the function to unreference it (in the best way possible).
1866                 $value = $this->unref($value);
1867             }
1868             if ($simulatettl) {
1869                 $value = new cache_ttl_wrapper($value, $this->get_definition()->get_ttl());
1870             }
1871             $parsedkey = $this->parse_key($key);
1872             $session[$parsedkey] = $value;
1873         }
1874         if ($this->perfdebug) {
1875             cache_helper::record_cache_set($this->storetype, $this->get_definition()->get_id());
1876         }
1877         $this->save_session($session);
1878         return count($keyvaluearray);
1879     }
1881     /**
1882      * Purges the cache store, and loader if there is one.
1883      *
1884      * @return bool True on success, false otherwise
1885      */
1886     public function purge() {
1887         // 1. Purge the session object.
1888         $this->session = array();
1889         // 2. Delete the record for this users session from the store.
1890         $this->get_store()->delete($this->sessionid);
1891         // 3. Optionally purge any stacked loaders in the same way.
1892         if ($this->get_loader()) {
1893             $this->get_loader()->delete($this->sessionid);
1894         }
1895         return true;
1896     }
1898     /**
1899      * Test is a cache has a key.
1900      *
1901      * The use of the has methods is strongly discouraged. In a high load environment the cache may well change between the
1902      * test and any subsequent action (get, set, delete etc).
1903      * Instead it is recommended to write your code in such a way they it performs the following steps:
1904      * <ol>
1905      * <li>Attempt to retrieve the information.</li>
1906      * <li>Generate the information.</li>
1907      * <li>Attempt to set the information</li>
1908      * </ol>
1909      *
1910      * Its also worth mentioning that not all stores support key tests.
1911      * For stores that don't support key tests this functionality is mimicked by using the equivalent get method.
1912      * Just one more reason you should not use these methods unless you have a very good reason to do so.
1913      *
1914      * @param string|int $key
1915      * @param bool $tryloadifpossible If set to true, the cache doesn't contain the key, and there is another cache loader or
1916      *      data source then the code will try load the key value from the next item in the chain.
1917      * @return bool True if the cache has the requested key, false otherwise.
1918      */
1919     public function has($key, $tryloadifpossible = false) {
1920         $this->check_tracked_user();
1921         $parsedkey = $this->parse_key($key);
1922         $session = $this->get_session_data();
1923         $has = false;
1924         if ($this->has_a_ttl()) {
1925             // The data has a TTL and the store doesn't support it natively.
1926             // We must fetch the data and expect a ttl wrapper.
1927             if (array_key_exists($parsedkey, $session)) {
1928                 $data = $session[$parsedkey];
1929                 $has = ($data instanceof cache_ttl_wrapper && !$data->has_expired());
1930             }
1931         } else {
1932             $has = array_key_exists($parsedkey, $session);
1933         }
1934         if (!$has && $tryloadifpossible) {
1935             if ($this->get_loader() !== false) {
1936                 $result = $this->get_loader()->get($key);
1937             } else if ($this->get_datasource() !== null) {
1938                 $result = $this->get_datasource()->load_for_cache($key);
1939             }
1940             $has = ($result !== null);
1941             if ($has) {
1942                 $this->set($key, $result);
1943             }
1944         }
1945         return $has;
1946     }
1948     /**
1949      * Test is a cache has all of the given keys.
1950      *
1951      * It is strongly recommended to avoid the use of this function if not absolutely required.
1952      * In a high load environment the cache may well change between the test and any subsequent action (get, set, delete etc).
1953      *
1954      * Its also worth mentioning that not all stores support key tests.
1955      * For stores that don't support key tests this functionality is mimicked by using the equivalent get method.
1956      * Just one more reason you should not use these methods unless you have a very good reason to do so.
1957      *
1958      * @param array $keys
1959      * @return bool True if the cache has all of the given keys, false otherwise.
1960      */
1961     public function has_all(array $keys) {
1962         $this->check_tracked_user();
1963         $session = $this->get_session_data();
1964         foreach ($keys as $key) {
1965             $has = false;
1966             $parsedkey = $this->parse_key($key);
1967             if ($this->has_a_ttl()) {
1968                 // The data has a TTL and the store doesn't support it natively.
1969                 // We must fetch the data and expect a ttl wrapper.
1970                 if (array_key_exists($parsedkey, $session)) {
1971                     $data = $session[$parsedkey];
1972                     $has = ($data instanceof cache_ttl_wrapper && !$data->has_expired());
1973                 }
1974             } else {
1975                 $has = array_key_exists($parsedkey, $session);
1976             }
1977             if (!$has) {
1978                 return false;
1979             }
1980         }
1981         return true;
1982     }
1984     /**
1985      * Test if a cache has at least one of the given keys.
1986      *
1987      * It is strongly recommended to avoid the use of this function if not absolutely required.
1988      * In a high load environment the cache may well change between the test and any subsequent action (get, set, delete etc).
1989      *
1990      * Its also worth mentioning that not all stores support key tests.
1991      * For stores that don't support key tests this functionality is mimicked by using the equivalent get method.
1992      * Just one more reason you should not use these methods unless you have a very good reason to do so.
1993      *
1994      * @param array $keys
1995      * @return bool True if the cache has at least one of the given keys
1996      */
1997     public function has_any(array $keys) {
1998         $this->check_tracked_user();
1999         $session = $this->get_session_data();
2000         foreach ($keys as $key) {
2001             $has = false;
2002             $parsedkey = $this->parse_key($key);
2003             if ($this->has_a_ttl()) {
2004                 // The data has a TTL and the store doesn't support it natively.
2005                 // We must fetch the data and expect a ttl wrapper.
2006                 if (array_key_exists($parsedkey, $session)) {
2007                     $data = $session[$parsedkey];
2008                     $has = ($data instanceof cache_ttl_wrapper && !$data->has_expired());
2009                 }
2010             } else {
2011                 $has = array_key_exists($parsedkey, $session);
2012             }
2013             if ($has) {
2014                 return true;
2015             }
2016         }
2017         return false;
2018     }
2020     /**
2021      * The session loader never uses the persist cache.
2022      * Instead it stores things in the static $session variable. Shared between all session loaders.
2023      *
2024      * @return bool
2025      */
2026     protected function is_using_persist_cache() {
2027         return false;
2028     }
2031 /**
2032  * An request cache.
2033  *
2034  * This class is used for request caches returned by the cache::make methods.
2035  *
2036  * This cache class should never be interacted with directly. Instead you should always use the cache::make methods.
2037  * It is technically possible to call those methods through this class however there is no guarantee that you will get an
2038  * instance of this class back again.
2039  *
2040  * @internal don't use me directly.
2041  *
2042  * @package    core
2043  * @category   cache
2044  * @copyright  2012 Sam Hemelryk
2045  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2046  */
2047 class cache_request extends cache {
2048     // This comment appeases code pre-checker ;) !