Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
 * The library file for the session cache store.
19
 *
20
 * This file is part of the session cache store, it contains the API for interacting with an instance of the store.
21
 * This is used as a default cache store within the Cache API. It should never be deleted.
22
 *
23
 * @package    cachestore_session
24
 * @category   cache
25
 * @copyright  2012 Sam Hemelryk
26
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27
 */
28
 
1441 ariadna 29
use core_cache\definition;
30
use core_cache\key_aware_cache_interface;
31
use core_cache\searchable_cache_interface;
32
use core_cache\store;
33
 
1 efrain 34
defined('MOODLE_INTERNAL') || die();
35
 
36
/**
37
 * The session data store class.
38
 *
39
 * @copyright  2012 Sam Hemelryk
40
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
41
 */
1441 ariadna 42
abstract class session_data_store extends store {
1 efrain 43
 
44
    /**
45
     * Used for the actual storage.
46
     * @var array
47
     */
48
    private static $sessionstore = null;
49
 
50
    /**
51
     * Returns a static store by reference... REFERENCE SUPER IMPORTANT.
52
     *
53
     * @param string $id
54
     * @return array
55
     */
56
    protected static function &register_store_id($id) {
57
        if (is_null(self::$sessionstore)) {
58
            global $SESSION;
59
            if (!isset($SESSION->cachestore_session)) {
60
                $SESSION->cachestore_session = array();
61
            }
62
            self::$sessionstore =& $SESSION->cachestore_session;
63
        }
64
        if (!array_key_exists($id, self::$sessionstore)) {
65
            self::$sessionstore[$id] = array();
66
        }
67
        return self::$sessionstore[$id];
68
    }
69
 
70
    /**
71
     * Flushes the data belong to the given store id.
72
     * @param string $id
73
     */
74
    protected static function flush_store_by_id($id) {
75
        unset(self::$sessionstore[$id]);
76
        self::$sessionstore[$id] = array();
77
    }
78
 
79
    /**
80
     * Flushes the store of all data.
81
     */
82
    protected static function flush_store() {
83
        $ids = array_keys(self::$sessionstore);
84
        unset(self::$sessionstore);
85
        self::$sessionstore = array();
86
        foreach ($ids as $id) {
87
            self::$sessionstore[$id] = array();
88
        }
89
    }
90
}
91
 
92
/**
93
 * The Session store class.
94
 *
95
 * @copyright  2012 Sam Hemelryk
96
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
97
 */
1441 ariadna 98
class cachestore_session extends session_data_store implements key_aware_cache_interface, searchable_cache_interface {
1 efrain 99
    /**
100
     * The name of the store
101
     * @var store
102
     */
103
    protected $name;
104
 
105
    /**
106
     * The store id (should be unique)
107
     * @var string
108
     */
109
    protected $storeid;
110
 
111
    /**
112
     * The store we use for data.
113
     * @var array
114
     */
115
    protected $store;
116
 
117
    /**
118
     * The ttl if there is one. Hopefully not.
119
     * @var int
120
     */
121
    protected $ttl = 0;
122
 
123
    /**
124
     * The maximum size for the store, or false if there isn't one.
125
     * @var bool|int
126
     */
127
    protected $maxsize = false;
128
 
129
    /**
130
     * The number of items currently being stored.
131
     * @var int
132
     */
133
    protected $storecount = 0;
134
 
135
    /**
136
     * Constructs the store instance.
137
     *
138
     * Noting that this function is not an initialisation. It is used to prepare the store for use.
1441 ariadna 139
     * The store will be initialised when required and will be provided with a definition at that time.
1 efrain 140
     *
141
     * @param string $name
142
     * @param array $configuration
143
     */
144
    public function __construct($name, array $configuration = array()) {
145
        $this->name = $name;
146
    }
147
 
148
    /**
149
     * Returns the supported features as a combined int.
150
     *
151
     * @param array $configuration
152
     * @return int
153
     */
154
    public static function get_supported_features(array $configuration = array()) {
155
        return self::SUPPORTS_DATA_GUARANTEE +
156
               self::SUPPORTS_NATIVE_TTL +
157
               self::IS_SEARCHABLE;
158
    }
159
 
160
    /**
161
     * Returns false as this store does not support multiple identifiers.
162
     * (This optional function is a performance optimisation; it must be
163
     * consistent with the value from get_supported_features.)
164
     *
165
     * @return bool False
166
     */
167
    public function supports_multiple_identifiers() {
168
        return false;
169
    }
170
 
171
    /**
172
     * Returns the supported modes as a combined int.
173
     *
174
     * @param array $configuration
175
     * @return int
176
     */
177
    public static function get_supported_modes(array $configuration = array()) {
178
        return self::MODE_SESSION;
179
    }
180
 
181
    /**
182
     * Returns true if the store requirements are met.
183
     *
184
     * @return bool
185
     */
186
    public static function are_requirements_met() {
187
        return true;
188
    }
189
 
190
    /**
191
     * Returns true if the given mode is supported by this store.
192
     *
1441 ariadna 193
     * @param int $mode One of store::MODE_*
1 efrain 194
     * @return bool
195
     */
196
    public static function is_supported_mode($mode) {
1441 ariadna 197
        return ($mode === store::MODE_SESSION);
1 efrain 198
    }
199
 
200
    /**
201
     * Initialises the cache.
202
     *
203
     * Once this has been done the cache is all set to be used.
204
     *
1441 ariadna 205
     * @param definition $definition
1 efrain 206
     */
1441 ariadna 207
    public function initialise(definition $definition) {
1 efrain 208
        $this->storeid = $definition->generate_definition_hash();
209
        $this->store = &self::register_store_id($this->name.'-'.$definition->get_id());
210
        $this->ttl = $definition->get_ttl();
211
        $maxsize = $definition->get_maxsize();
212
        if ($maxsize !== null) {
213
            // Must be a positive int.
214
            $this->maxsize = abs((int)$maxsize);
215
            $this->storecount = count($this->store);
216
        }
217
        $this->check_ttl();
218
    }
219
 
220
    /**
221
     * Returns true once this instance has been initialised.
222
     *
223
     * @return bool
224
     */
225
    public function is_initialised() {
226
        return (is_array($this->store));
227
    }
228
 
229
    /**
230
     * Retrieves an item from the cache store given its key.
231
     *
232
     * @param string $key The key to retrieve
233
     * @return mixed The data that was associated with the key, or false if the key did not exist.
234
     */
235
    public function get($key) {
236
        if (isset($this->store[$key])) {
237
            if ($this->ttl == 0) {
238
                $value = $this->store[$key][0];
239
                if ($this->maxsize !== false) {
240
                    // Make sure the element is now in the end of array.
241
                    $this->set($key, $value);
242
                }
243
                return $value;
244
            } else if ($this->store[$key][1] >= (cache::now() - $this->ttl)) {
245
                return $this->store[$key][0];
246
            } else {
247
                // Element is present but has expired.
248
                $this->check_ttl();
249
            }
250
        }
251
        return false;
252
    }
253
 
254
    /**
255
     * Retrieves several items from the cache store in a single transaction.
256
     *
257
     * If not all of the items are available in the cache then the data value for those that are missing will be set to false.
258
     *
259
     * @param array $keys The array of keys to retrieve
260
     * @return array An array of items from the cache. There will be an item for each key, those that were not in the store will
261
     *      be set to false.
262
     */
263
    public function get_many($keys) {
264
        $return = array();
265
        $maxtime = 0;
266
        if ($this->ttl != 0) {
267
            $maxtime = cache::now() - $this->ttl;
268
        }
269
 
270
        $hasexpiredelements = false;
271
        foreach ($keys as $key) {
272
            $return[$key] = false;
273
            if (isset($this->store[$key])) {
274
                if ($this->ttl == 0) {
275
                    $return[$key] = $this->store[$key][0];
276
                    if ($this->maxsize !== false) {
277
                        // Make sure the element is now in the end of array.
278
                        $this->set($key, $return[$key], false);
279
                    }
280
                } else if ($this->store[$key][1] >= $maxtime) {
281
                    $return[$key] = $this->store[$key][0];
282
                } else {
283
                    $hasexpiredelements = true;
284
                }
285
            }
286
        }
287
        if ($hasexpiredelements) {
288
            // There are some elements that are present but have expired.
289
            $this->check_ttl();
290
        }
291
        return $return;
292
    }
293
 
294
    /**
295
     * Sets an item in the cache given its key and data value.
296
     *
297
     * @param string $key The key to use.
298
     * @param mixed $data The data to set.
299
     * @param bool $testmaxsize If set to true then we test the maxsize arg and reduce if required. If this is set to false you will
300
     *      need to perform these checks yourself. This allows for bulk set's to be performed and maxsize tests performed once.
301
     * @return bool True if the operation was a success false otherwise.
302
     */
303
    public function set($key, $data, $testmaxsize = true) {
304
        $testmaxsize = ($testmaxsize && $this->maxsize !== false);
305
        $increment = $this->maxsize !== false && !isset($this->store[$key]);
306
        if (($this->maxsize !== false && !$increment) || $this->ttl != 0) {
307
            // Make sure the element is added to the end of $this->store array.
308
            unset($this->store[$key]);
309
        }
310
        if ($this->ttl === 0) {
311
            $this->store[$key] = array($data, 0);
312
        } else {
313
            $this->store[$key] = array($data, cache::now());
314
        }
315
        if ($increment) {
316
            $this->storecount++;
317
        }
318
        if ($testmaxsize && $this->storecount > $this->maxsize) {
319
            $this->reduce_for_maxsize();
320
        }
321
        return true;
322
    }
323
 
324
    /**
325
     * Sets many items in the cache in a single transaction.
326
     *
327
     * @param array $keyvaluearray An array of key value pairs. Each item in the array will be an associative array with two
328
     *      keys, 'key' and 'value'.
329
     * @return int The number of items successfully set. It is up to the developer to check this matches the number of items
330
     *      sent ... if they care that is.
331
     */
332
    public function set_many(array $keyvaluearray) {
333
        $count = 0;
334
        $increment = 0;
335
        foreach ($keyvaluearray as $pair) {
336
            $key = $pair['key'];
337
            $data = $pair['value'];
338
            $count++;
339
            if ($this->maxsize !== false || $this->ttl !== 0) {
340
                // Make sure the element is added to the end of $this->store array.
341
                $this->delete($key);
342
                $increment++;
343
            } else if (!isset($this->store[$key])) {
344
                $increment++;
345
            }
346
            if ($this->ttl === 0) {
347
                $this->store[$key] = array($data, 0);
348
            } else {
349
                $this->store[$key] = array($data, cache::now());
350
            }
351
        }
352
        if ($this->maxsize !== false) {
353
            $this->storecount += $increment;
354
            if ($this->storecount > $this->maxsize) {
355
                $this->reduce_for_maxsize();
356
            }
357
        }
358
        return $count;
359
    }
360
 
361
    /**
362
     * Checks if the store has a record for the given key and returns true if so.
363
     *
364
     * @param string $key
365
     * @return bool
366
     */
367
    public function has($key) {
368
        if (isset($this->store[$key])) {
369
            if ($this->ttl == 0) {
370
                return true;
371
            } else if ($this->store[$key][1] >= (cache::now() - $this->ttl)) {
372
                return true;
373
            }
374
        }
375
        return false;
376
    }
377
 
378
    /**
379
     * Returns true if the store contains records for all of the given keys.
380
     *
381
     * @param array $keys
382
     * @return bool
383
     */
384
    public function has_all(array $keys) {
385
        $maxtime = 0;
386
        if ($this->ttl != 0) {
387
            $maxtime = cache::now() - $this->ttl;
388
        }
389
 
390
        foreach ($keys as $key) {
391
            if (!isset($this->store[$key])) {
392
                return false;
393
            }
394
            if ($this->ttl != 0 && $this->store[$key][1] < $maxtime) {
395
                return false;
396
            }
397
        }
398
        return true;
399
    }
400
 
401
    /**
402
     * Returns true if the store contains records for any of the given keys.
403
     *
404
     * @param array $keys
405
     * @return bool
406
     */
407
    public function has_any(array $keys) {
408
        $maxtime = 0;
409
        if ($this->ttl != 0) {
410
            $maxtime = cache::now() - $this->ttl;
411
        }
412
 
413
        foreach ($keys as $key) {
414
            if (isset($this->store[$key]) && ($this->ttl == 0 || $this->store[$key][1] >= $maxtime)) {
415
                return true;
416
            }
417
        }
418
        return false;
419
    }
420
 
421
    /**
422
     * Deletes an item from the cache store.
423
     *
424
     * @param string $key The key to delete.
425
     * @return bool Returns true if the operation was a success, false otherwise.
426
     */
427
    public function delete($key) {
428
        if (!isset($this->store[$key])) {
429
            return false;
430
        }
431
        unset($this->store[$key]);
432
        if ($this->maxsize !== false) {
433
            $this->storecount--;
434
        }
435
        return true;
436
    }
437
 
438
    /**
439
     * Deletes several keys from the cache in a single action.
440
     *
441
     * @param array $keys The keys to delete
442
     * @return int The number of items successfully deleted.
443
     */
444
    public function delete_many(array $keys) {
445
        // The number of items that have actually being removed.
446
        $reduction = 0;
447
        foreach ($keys as $key) {
448
            if (isset($this->store[$key])) {
449
                $reduction++;
450
            }
451
            unset($this->store[$key]);
452
        }
453
        if ($this->maxsize !== false) {
454
            $this->storecount -= $reduction;
455
        }
456
        return $reduction;
457
    }
458
 
459
    /**
460
     * Purges the cache deleting all items within it.
461
     *
462
     * @return boolean True on success. False otherwise.
463
     */
464
    public function purge() {
465
        $this->store = array();
466
        // Don't worry about checking if we're using max size just set it as thats as fast as the check.
467
        $this->storecount = 0;
468
        return true;
469
    }
470
 
471
    /**
472
     * Reduces the size of the array if maxsize has been hit.
473
     *
474
     * This function reduces the size of the store reducing it by 10% of its maxsize.
475
     * It removes the oldest items in the store when doing this.
476
     * The reason it does this an doesn't use a least recently used system is purely the overhead such a system
477
     * requires. The current approach is focused on speed, MUC already adds enough overhead to static/session caches
478
     * and avoiding more is of benefit.
479
     *
480
     * @return int
481
     */
482
    protected function reduce_for_maxsize() {
483
        $diff = $this->storecount - $this->maxsize;
484
        if ($diff < 1) {
485
            return 0;
486
        }
487
        // Reduce it by an extra 10% to avoid calling this repetitively if we are in a loop.
488
        $diff += floor($this->maxsize / 10);
489
        $this->store = array_slice($this->store, $diff, null, true);
490
        $this->storecount -= $diff;
491
        return $diff;
492
    }
493
 
494
    /**
495
     * Returns true if the user can add an instance of the store plugin.
496
     *
497
     * @return bool
498
     */
499
    public static function can_add_instance() {
500
        return false;
501
    }
502
 
503
    /**
504
     * Performs any necessary clean up when the store instance is being deleted.
505
     */
506
    public function instance_deleted() {
507
        $this->purge();
508
    }
509
 
510
    /**
511
     * Generates an instance of the cache store that can be used for testing.
512
     *
1441 ariadna 513
     * @param definition $definition
1 efrain 514
     * @return cachestore_session
515
     */
1441 ariadna 516
    public static function initialise_test_instance(definition $definition) {
1 efrain 517
        // Do something here perhaps.
518
        $cache = new cachestore_session('Session test');
519
        $cache->initialise($definition);
520
        return $cache;
521
    }
522
 
523
    /**
524
     * Generates the appropriate configuration required for unit testing.
525
     *
526
     * @return array Array of unit test configuration data to be used by initialise().
527
     */
528
    public static function unit_test_configuration() {
529
        return array();
530
    }
531
    /**
532
     * Returns the name of this instance.
533
     * @return string
534
     */
535
    public function my_name() {
536
        return $this->name;
537
    }
538
 
539
    /**
540
     * Removes expired elements.
541
     * @return int number of removed elements
542
     */
543
    protected function check_ttl() {
544
        if ($this->ttl === 0) {
545
            return 0;
546
        }
547
        $maxtime = cache::now() - $this->ttl;
548
        $count = 0;
549
        for ($value = reset($this->store); $value !== false; $value = next($this->store)) {
550
            if ($value[1] >= $maxtime) {
551
                // We know that elements are sorted by ttl so no need to continue.
552
                break;
553
            }
554
            $count++;
555
        }
556
        if ($count) {
557
            // Remove first $count elements as they are expired.
558
            $this->store = array_slice($this->store, $count, null, true);
559
            if ($this->maxsize !== false) {
560
                $this->storecount -= $count;
561
            }
562
        }
563
        return $count;
564
    }
565
 
566
    /**
567
     * Finds all of the keys being stored in the cache store instance.
568
     *
569
     * @return array
570
     */
571
    public function find_all() {
572
        $this->check_ttl();
573
        return array_keys($this->store);
574
    }
575
 
576
    /**
577
     * Finds all of the keys whose keys start with the given prefix.
578
     *
579
     * @param string $prefix
580
     * @return array An array of keys.
581
     */
582
    public function find_by_prefix($prefix) {
583
        $return = array();
584
        foreach ($this->find_all() as $key) {
585
            if (strpos($key, $prefix) === 0) {
586
                $return[] = $key;
587
            }
588
        }
589
        return $return;
590
    }
591
 
592
    /**
593
     * This store supports native TTL handling.
594
     * @return bool
595
     */
596
    public function store_supports_native_ttl() {
597
        return true;
598
    }
599
}