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

Last change on this file since 136 was 136, checked in by mabarracus, 3 years ago

Ported code to xenial

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