source: moodle/trunk/fuentes/cache/classes/loaders.php @ 1331

Last change on this file since 1331 was 1331, checked in by jrpelegrina, 3 years ago

Updated to moodle 3.0.3

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