Proyectos de Subversion Moodle

Rev

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