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