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