MDL-35558 mod_data: Show only own entries while there are required pending.
[moodle.git] / cache / stores / memcached / lib.php
CommitLineData
47d89ccf
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 * The library file for the memcached cache store.
19 *
20 * This file is part of the memcached cache store, it contains the API for interacting with an instance of the store.
21 *
6fec1820 22 * @package cachestore_memcached
47d89ccf
SH
23 * @copyright 2012 Sam Hemelryk
24 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25 */
26
27defined('MOODLE_INTERNAL') || die();
28
29/**
30 * The memcached store.
31 *
32 * (Not to be confused with the memcache store)
33 *
34 * Configuration options:
35 * servers: string: host:port:weight , ...
36 * compression: true, false
37 * serialiser: SERIALIZER_PHP, SERIALIZER_JSON, SERIALIZER_IGBINARY
38 * prefix: string: defaults to instance name
39 * hashmethod: HASH_DEFAULT, HASH_MD5, HASH_CRC, HASH_FNV1_64, HASH_FNV1A_64, HASH_FNV1_32,
40 * HASH_FNV1A_32, HASH_HSIEH, HASH_MURMUR
41 * bufferwrites: true, false
42 *
43 * @copyright 2012 Sam Hemelryk
44 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
45 */
6fec1820 46class cachestore_memcached implements cache_store {
47d89ccf
SH
47
48 /**
49 * The name of the store
50 * @var store
51 */
52 protected $name;
53
54 /**
55 * The memcached connection
56 * @var Memcached
57 */
58 protected $connection;
59
60 /**
61 * An array of servers to use during connection
62 * @var array
63 */
64 protected $servers = array();
65
66 /**
67 * The options used when establishing the connection
68 * @var array
69 */
70 protected $options = array();
71
72 /**
73 * True when this instance is ready to be initialised.
74 * @var bool
75 */
76 protected $isready = false;
170f821b 77
47d89ccf
SH
78 /**
79 * The cache definition this store was initialised with.
80 * @var cache_definition
81 */
82 protected $definition;
83
84 /**
85 * Constructs the store instance.
86 *
87 * Noting that this function is not an initialisation. It is used to prepare the store for use.
88 * The store will be initialised when required and will be provided with a cache_definition at that time.
89 *
90 * @param string $name
91 * @param array $configuration
92 */
93 public function __construct($name, array $configuration = array()) {
94 $this->name = $name;
95 if (!array_key_exists('servers', $configuration) || empty($configuration['servers'])) {
96 // Nothing configured.
97 return;
98 }
42f5d164
SH
99 if (!is_array($configuration['servers'])) {
100 $configuration['servers'] = array($configuration['servers']);
101 }
47d89ccf
SH
102
103 $compression = array_key_exists('compression', $configuration) ? (bool)$configuration['compression'] : true;
170f821b
SH
104 if (array_key_exists('serialiser', $configuration)) {
105 $serialiser = (int)$configuration['serialiser'];
106 } else {
107 $serialiser = Memcached::SERIALIZER_PHP;
108 }
47d89ccf
SH
109 $prefix = (!empty($configuration['prefix'])) ? (string)$configuration['prefix'] : crc32($name);
110 $hashmethod = (array_key_exists('hash', $configuration)) ? (int)$configuration['hash'] : Memcached::HASH_DEFAULT;
111 $bufferwrites = array_key_exists('bufferwrites', $configuration) ? (bool)$configuration['bufferwrites'] : false;
112
113 foreach ($configuration['servers'] as $server) {
114 if (!is_array($server)) {
115 $server = explode(':', $server, 3);
116 }
117 if (!array_key_exists(1, $server)) {
118 $server[1] = 11211;
119 $server[2] = 100;
120 } else if (!array_key_exists(2, $server)) {
121 $server[2] = 100;
122 }
123 $this->servers[] = $server;
124 }
125 $this->options[Memcached::OPT_COMPRESSION] = $compression;
126 $this->options[Memcached::OPT_SERIALIZER] = $serialiser;
127 $this->options[Memcached::OPT_PREFIX_KEY] = $prefix;
128 $this->options[Memcached::OPT_HASH] = $hashmethod;
129 $this->options[Memcached::OPT_BUFFER_WRITES] = $bufferwrites;
130
131 $this->isready = true;
132 }
133
134 /**
135 * Initialises the cache.
136 *
137 * Once this has been done the cache is all set to be used.
138 *
139 * @param cache_definition $definition
140 */
141 public function initialise(cache_definition $definition) {
142 if ($this->is_initialised()) {
143 throw new coding_exception('This memcached instance has already been initialised.');
144 }
145 $this->definition = $definition;
146 $this->connection = new Memcached(crc32($this->name));
147 $servers = $this->connection->getServerList();
148 if (empty($servers)) {
149 foreach ($this->options as $key => $value) {
150 $this->connection->setOption($key, $value);
151 }
152 $this->connection->addServers($this->servers);
153 }
154 }
155
156 /**
157 * Returns true once this instance has been initialised.
158 *
159 * @return bool
160 */
161 public function is_initialised() {
162 return ($this->connection !== null);
163 }
164
165 /**
166 * Returns true if this store instance is ready to be used.
167 * @return bool
168 */
169 public function is_ready() {
170 return $this->isready;
171 }
172
173 /**
174 * Returns true if the store requirements are met.
175 *
176 * @return bool
177 */
178 public static function are_requirements_met() {
179 return class_exists('Memcached');
180 }
181
182 /**
183 * Returns true if the given mode is supported by this store.
184 *
185 * @param int $mode One of cache_store::MODE_*
186 * @return bool
187 */
188 public static function is_supported_mode($mode) {
189 return ($mode === self::MODE_APPLICATION || $mode === self::MODE_SESSION);
190 }
191
192 /**
193 * Returns the supported features as a combined int.
194 *
195 * @param array $configuration
196 * @return int
197 */
198 public static function get_supported_features(array $configuration = array()) {
199 return self::SUPPORTS_NATIVE_TTL;
200 }
201
202 /**
203 * Returns true if the store instance supports multiple identifiers.
204 *
205 * @return bool
206 */
207 public function supports_multiple_indentifiers() {
208 return false;
209 }
210
211 /**
212 * Returns true if the store instance guarantees data.
213 *
214 * @return bool
215 */
216 public function supports_data_guarantee() {
217 return false;
218 }
219
220 /**
221 * Returns true if the store instance supports native ttl.
222 *
223 * @return bool
224 */
225 public function supports_native_ttl() {
226 return true;
227 }
228
229 /**
230 * Returns the supported modes as a combined int.
231 *
232 * @param array $configuration
233 * @return int
234 */
235 public static function get_supported_modes(array $configuration = array()) {
236 return self::MODE_APPLICATION + self::MODE_SESSION;
237 }
238
239 /**
240 * Retrieves an item from the cache store given its key.
241 *
242 * @param string $key The key to retrieve
243 * @return mixed The data that was associated with the key, or false if the key did not exist.
244 */
245 public function get($key) {
246 return $this->connection->get($key);
247 }
248
249 /**
250 * Retrieves several items from the cache store in a single transaction.
251 *
252 * 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.
253 *
254 * @param array $keys The array of keys to retrieve
255 * @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
256 * be set to false.
257 */
258 public function get_many($keys) {
259 $result = $this->connection->getMulti($keys);
260 if (!is_array($result)) {
261 $result = array();
262 }
263 foreach ($keys as $key) {
264 if (!array_key_exists($key, $result)) {
265 $result[$key] = false;
266 }
267 }
268 return $result;
269 }
270
271 /**
272 * Sets an item in the cache given its key and data value.
273 *
274 * @param string $key The key to use.
275 * @param mixed $data The data to set.
276 * @return bool True if the operation was a success false otherwise.
277 */
278 public function set($key, $data) {
279 return $this->connection->set($key, $data, $this->definition->get_ttl());
280 }
281
282 /**
283 * Sets many items in the cache in a single transaction.
284 *
285 * @param array $keyvaluearray An array of key value pairs. Each item in the array will be an associative array with two
286 * keys, 'key' and 'value'.
287 * @return int The number of items successfully set. It is up to the developer to check this matches the number of items
288 * sent ... if they care that is.
289 */
290 public function set_many(array $keyvaluearray) {
291 $pairs = array();
292 foreach ($keyvaluearray as $pair) {
293 $pairs[$pair['key']] = $pair['value'];
294 }
295 if ($this->connection->setMulti($pairs, $this->definition->get_ttl())) {
296 return count($keyvaluearray);
297 }
298 return 0;
299 }
300
301 /**
302 * Deletes an item from the cache store.
303 *
304 * @param string $key The key to delete.
305 * @return bool Returns true if the operation was a success, false otherwise.
306 */
307 public function delete($key) {
308 return $this->connection->delete($key);
309 }
310
311 /**
312 * Deletes several keys from the cache in a single action.
313 *
314 * @param array $keys The keys to delete
315 * @return int The number of items successfully deleted.
316 */
317 public function delete_many(array $keys) {
318 $count = 0;
319 foreach ($keys as $key) {
320 if ($this->connection->delete($key)) {
321 $count++;
322 }
323 }
324 return $count;
325 }
326
327 /**
328 * Purges the cache deleting all items within it.
329 *
330 * @return boolean True on success. False otherwise.
331 */
332 public function purge() {
333 $this->connection->flush();
334 return true;
335 }
336
337 /**
338 * Gets an array of options to use as the serialiser.
339 * @return array
340 */
341 public static function config_get_serialiser_options() {
342 $options = array(
6fec1820 343 Memcached::SERIALIZER_PHP => get_string('serialiser_php', 'cachestore_memcached')
47d89ccf
SH
344 );
345 if (Memcached::HAVE_JSON) {
6fec1820 346 $options[Memcached::SERIALIZER_JSON] = get_string('serialiser_json', 'cachestore_memcached');
47d89ccf
SH
347 }
348 if (Memcached::HAVE_IGBINARY) {
6fec1820 349 $options[Memcached::SERIALIZER_IGBINARY] = get_string('serialiser_php', 'cachestore_memcached');
47d89ccf
SH
350 }
351 return $options;
352 }
353
354 /**
355 * Gets an array of hash options available during configuration.
356 * @return array
357 */
358 public static function config_get_hash_options() {
359 $options = array(
6fec1820
SH
360 Memcached::HASH_DEFAULT => get_string('hash_default', 'cachestore_memcached'),
361 Memcached::HASH_MD5 => get_string('hash_md5', 'cachestore_memcached'),
362 Memcached::HASH_CRC => get_string('hash_crc', 'cachestore_memcached'),
363 Memcached::HASH_FNV1_64 => get_string('hash_fnv1_64', 'cachestore_memcached'),
364 Memcached::HASH_FNV1A_64 => get_string('hash_fnv1a_64', 'cachestore_memcached'),
365 Memcached::HASH_FNV1_32 => get_string('hash_fnv1_32', 'cachestore_memcached'),
366 Memcached::HASH_FNV1A_32 => get_string('hash_fnv1a_32', 'cachestore_memcached'),
367 Memcached::HASH_HSIEH => get_string('hash_hsieh', 'cachestore_memcached'),
368 Memcached::HASH_MURMUR => get_string('hash_murmur', 'cachestore_memcached'),
47d89ccf
SH
369 );
370 return $options;
371 }
372
373 /**
374 * Given the data from the add instance form this function creates a configuration array.
375 *
376 * @param stdClass $data
377 * @return array
378 */
379 public static function config_get_configuration_array($data) {
380 $lines = explode("\n", $data->servers);
381 $servers = array();
382 foreach ($lines as $line) {
383 $line = trim($line, ':');
384 $servers[] = explode(':', $line, 3);
385 }
386 return array(
387 'servers' => $servers,
388 'compression' => $data->compression,
389 'serialiser' => $data->serialiser,
390 'prefix' => $data->prefix,
391 'hash' => $data->hash,
392 'bufferwrites' => $data->bufferwrites,
393 );
394 }
395
396 /**
397 * Returns true if the user can add an instance of the store plugin.
398 *
399 * @return bool
400 */
401 public static function can_add_instance() {
402 return true;
403 }
404
405 /**
406 * Performs any necessary clean up when the store instance is being deleted.
407 */
408 public function cleanup() {
409 $this->purge();
410 }
411
412 /**
413 * Generates an instance of the cache store that can be used for testing.
414 *
415 * @param cache_definition $definition
416 * @return false
417 */
418 public static function initialise_test_instance(cache_definition $definition) {
419
420 if (!self::are_requirements_met()) {
421 return false;
422 }
423
6fec1820 424 $config = get_config('cachestore_memcached');
47d89ccf
SH
425 if (empty($config->testservers)) {
426 return false;
427 }
428
429 $configuration = array();
430 $configuration['servers'] = $config->testservers;
431 if (!empty($config->testcompression)) {
432 $configuration['compression'] = $config->testcompression;
433 }
434 if (!empty($config->testserialiser)) {
435 $configuration['serialiser'] = $config->testserialiser;
436 }
437 if (!empty($config->testprefix)) {
438 $configuration['prefix'] = $config->testprefix;
439 }
440 if (!empty($config->testhash)) {
441 $configuration['hash'] = $config->testhash;
442 }
443 if (!empty($config->testbufferwrites)) {
444 $configuration['bufferwrites'] = $config->testbufferwrites;
445 }
446
6fec1820 447 $store = new cachestore_memcached('Test memcached', $configuration);
47d89ccf
SH
448 $store->initialise($definition);
449
450 return $store;
451 }
34c84c72
SH
452
453 /**
454 * Returns the name of this instance.
455 * @return string
456 */
457 public function my_name() {
458 return $this->name;
459 }
47d89ccf 460}