c71f1080160d9ae3a75494f5dfc490c8e7a8e3c8
[moodle.git] / cache / stores / memcache / lib.php
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/>.
17 /**
18  * The library file for the memcache cache store.
19  *
20  * This file is part of the memcache cache store, it contains the API for interacting with an instance of the store.
21  *
22  * @package    cachestore_memcache
23  * @copyright  2012 Sam Hemelryk
24  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25  */
27 defined('MOODLE_INTERNAL') || die();
29 /**
30  * The memcache store class.
31  *
32  * (Not to be confused with memcached store)
33  *
34  * Configuration options:
35  *      servers:        string: host:port:weight , ...
36  *
37  * @copyright  2012 Sam Hemelryk
38  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
39  */
40 class cachestore_memcache extends cache_store implements cache_is_configurable {
42     /**
43      * The name of the store
44      * @var store
45      */
46     protected $name;
48     /**
49      * The memcache connection once established.
50      * @var Memcache
51      */
52     protected $connection;
54     /**
55      * Key prefix for this memcache.
56      * @var string
57      */
58     protected $prefix;
60     /**
61      * An array of servers to use in the connection args.
62      * @var array
63      */
64     protected $servers = array();
66     /**
67      * An array of options used when establishing the connection.
68      * @var array
69      */
70     protected $options = array();
72     /**
73      * Set to true when things are ready to be initialised.
74      * @var bool
75      */
76     protected $isready = false;
78     /**
79      * Set to true once this store instance has been initialised.
80      * @var bool
81      */
82     protected $isinitialised = false;
84     /**
85      * The cache definition this store was initialised for.
86      * @var cache_definition
87      */
88     protected $definition;
90     /**
91      * Default prefix for key names.
92      * @var string
93      */
94     const DEFAULT_PREFIX = 'mdl_';
96     /**
97      * Constructs the store instance.
98      *
99      * Noting that this function is not an initialisation. It is used to prepare the store for use.
100      * The store will be initialised when required and will be provided with a cache_definition at that time.
101      *
102      * @param string $name
103      * @param array $configuration
104      */
105     public function __construct($name, array $configuration = array()) {
106         $this->name = $name;
107         if (!array_key_exists('servers', $configuration) || empty($configuration['servers'])) {
108             // Nothing configured.
109             return;
110         }
111         if (!is_array($configuration['servers'])) {
112             $configuration['servers'] = array($configuration['servers']);
113         }
114         foreach ($configuration['servers'] as $server) {
115             if (!is_array($server)) {
116                 $server = explode(':', $server, 3);
117             }
118             if (!array_key_exists(1, $server)) {
119                 $server[1] = 11211;
120                 $server[2] = 100;
121             } else if (!array_key_exists(2, $server)) {
122                 $server[2] = 100;
123             }
124             $this->servers[] = $server;
125         }
126         if (empty($configuration['prefix'])) {
127             $this->prefix = self::DEFAULT_PREFIX;
128         } else {
129             $this->prefix = $configuration['prefix'];
130         }
132         $this->connection = new Memcache;
133         foreach ($this->servers as $server) {
134             $this->connection->addServer($server[0], $server[1], true, $server[2]);
135         }
136         // Test the connection to the pool of servers.
137         $this->isready = @$this->connection->set($this->parse_key('ping'), 'ping', MEMCACHE_COMPRESSED, 1);
138     }
140     /**
141      * Initialises the cache.
142      *
143      * Once this has been done the cache is all set to be used.
144      *
145      * @param cache_definition $definition
146      */
147     public function initialise(cache_definition $definition) {
148         if ($this->is_initialised()) {
149             throw new coding_exception('This memcache instance has already been initialised.');
150         }
151         $this->definition = $definition;
152         $this->isinitialised = true;
153     }
155     /**
156      * Returns true once this instance has been initialised.
157      *
158      * @return bool
159      */
160     public function is_initialised() {
161         return ($this->isinitialised);
162     }
164     /**
165      * Returns true if this store instance is ready to be used.
166      * @return bool
167      */
168     public function is_ready() {
169         return $this->isready;
170     }
172     /**
173      * Returns true if the store requirements are met.
174      *
175      * @return bool
176      */
177     public static function are_requirements_met() {
178         return class_exists('Memcache');
179     }
181     /**
182      * Returns true if the given mode is supported by this store.
183      *
184      * @param int $mode One of cache_store::MODE_*
185      * @return bool
186      */
187     public static function is_supported_mode($mode) {
188         return ($mode === self::MODE_APPLICATION || $mode === self::MODE_SESSION);
189     }
191     /**
192      * Returns the supported features as a combined int.
193      *
194      * @param array $configuration
195      * @return int
196      */
197     public static function get_supported_features(array $configuration = array()) {
198         return self::SUPPORTS_NATIVE_TTL;
199     }
201     /**
202      * Returns false as this store does not support multiple identifiers.
203      * (This optional function is a performance optimisation; it must be
204      * consistent with the value from get_supported_features.)
205      *
206      * @return bool False
207      */
208     public function supports_multiple_identifiers() {
209         return false;
210     }
212     /**
213      * Returns the supported modes as a combined int.
214      *
215      * @param array $configuration
216      * @return int
217      */
218     public static function get_supported_modes(array $configuration = array()) {
219         return self::MODE_APPLICATION + self::MODE_SESSION;
220     }
222     /**
223      * Parses the given key to make it work for this memcache backend.
224      *
225      * @param string $key The raw key.
226      * @return string The resulting key.
227      */
228     protected function parse_key($key) {
229         if (strlen($key) > 245) {
230             $key = '_sha1_'.sha1($key);
231         }
232         $key = $this->prefix . $key;
233         return $key;
234     }
236     /**
237      * Retrieves an item from the cache store given its key.
238      *
239      * @param string $key The key to retrieve
240      * @return mixed The data that was associated with the key, or false if the key did not exist.
241      */
242     public function get($key) {
243         return $this->connection->get($this->parse_key($key));
244     }
246     /**
247      * Retrieves several items from the cache store in a single transaction.
248      *
249      * 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.
250      *
251      * @param array $keys The array of keys to retrieve
252      * @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
253      *      be set to false.
254      */
255     public function get_many($keys) {
256         $mkeys = array();
257         foreach ($keys as $key) {
258             $mkeys[$key] = $this->parse_key($key);
259         }
260         $result = $this->connection->get($mkeys);
261         if (!is_array($result)) {
262             $result = array();
263         }
264         $return = array();
265         foreach ($mkeys as $key => $mkey) {
266             if (!array_key_exists($mkey, $result)) {
267                 $return[$key] = false;
268             } else {
269                 $return[$key] = $result[$mkey];
270             }
271         }
272         return $return;
273     }
275     /**
276      * Sets an item in the cache given its key and data value.
277      *
278      * @param string $key The key to use.
279      * @param mixed $data The data to set.
280      * @return bool True if the operation was a success false otherwise.
281      */
282     public function set($key, $data) {
283         return $this->connection->set($this->parse_key($key), $data, MEMCACHE_COMPRESSED, $this->definition->get_ttl());
284     }
286     /**
287      * Sets many items in the cache in a single transaction.
288      *
289      * @param array $keyvaluearray An array of key value pairs. Each item in the array will be an associative array with two
290      *      keys, 'key' and 'value'.
291      * @return int The number of items successfully set. It is up to the developer to check this matches the number of items
292      *      sent ... if they care that is.
293      */
294     public function set_many(array $keyvaluearray) {
295         $count = 0;
296         foreach ($keyvaluearray as $pair) {
297             if ($this->connection->set($this->parse_key($pair['key']), $pair['value'], MEMCACHE_COMPRESSED, $this->definition->get_ttl())) {
298                 $count++;
299             }
300         }
301         return $count;
302     }
304     /**
305      * Deletes an item from the cache store.
306      *
307      * @param string $key The key to delete.
308      * @return bool Returns true if the operation was a success, false otherwise.
309      */
310     public function delete($key) {
311         return $this->connection->delete($this->parse_key($key));
312     }
314     /**
315      * Deletes several keys from the cache in a single action.
316      *
317      * @param array $keys The keys to delete
318      * @return int The number of items successfully deleted.
319      */
320     public function delete_many(array $keys) {
321         $count = 0;
322         foreach ($keys as $key) {
323             if ($this->delete($key)) {
324                 $count++;
325             }
326         }
327         return $count;
328     }
330     /**
331      * Purges the cache deleting all items within it.
332      *
333      * @return boolean True on success. False otherwise.
334      */
335     public function purge() {
336         if ($this->isready) {
337             $this->connection->flush();
338         }
340         return true;
341     }
343     /**
344      * Given the data from the add instance form this function creates a configuration array.
345      *
346      * @param stdClass $data
347      * @return array
348      */
349     public static function config_get_configuration_array($data) {
350         $lines = explode("\n", $data->servers);
351         $servers = array();
352         foreach ($lines as $line) {
353             // Trim surrounding colons and default whitespace.
354             $line = trim(trim($line), ":");
355             // Skip blank lines.
356             if ($line === '') {
357                 continue;
358             }
359             $servers[] = explode(':', $line, 3);
360         }
361         return array(
362             'servers' => $servers,
363             'prefix' => $data->prefix,
364         );
365     }
367     /**
368      * Allows the cache store to set its data against the edit form before it is shown to the user.
369      *
370      * @param moodleform $editform
371      * @param array $config
372      */
373     public static function config_set_edit_form_data(moodleform $editform, array $config) {
374         $data = array();
375         if (!empty($config['servers'])) {
376             $servers = array();
377             foreach ($config['servers'] as $server) {
378                 $servers[] = join(":", $server);
379             }
380             $data['servers'] = join("\n", $servers);
381         }
382         if (!empty($config['prefix'])) {
383             $data['prefix'] = $config['prefix'];
384         } else {
385             $data['prefix'] = self::DEFAULT_PREFIX;
386         }
388         $editform->set_data($data);
389     }
391     /**
392      * Performs any necessary clean up when the store instance is being deleted.
393      */
394     public function instance_deleted() {
395         if ($this->connection) {
396             $connection = $this->connection;
397         } else {
398             $connection = new Memcache;
399             foreach ($this->servers as $server) {
400                 $connection->addServer($server[0], $server[1], true, $server[2]);
401             }
402         }
403         @$connection->flush();
404         unset($connection);
405         unset($this->connection);
406     }
408     /**
409      * Generates an instance of the cache store that can be used for testing.
410      *
411      * @param cache_definition $definition
412      * @return cachestore_memcache|false
413      */
414     public static function initialise_test_instance(cache_definition $definition) {
415         if (!self::are_requirements_met()) {
416             return false;
417         }
419         $config = get_config('cachestore_memcache');
420         if (empty($config->testservers)) {
421             return false;
422         }
424         $configuration = array();
425         $configuration['servers'] = explode("\n", $config->testservers);
427         $store = new cachestore_memcache('Test memcache', $configuration);
428         $store->initialise($definition);
430         return $store;
431     }
433     /**
434      * Returns the name of this instance.
435      * @return string
436      */
437     public function my_name() {
438         return $this->name;
439     }