MDL-78092 Cache: Modinfo locking with Redis store does not work
[moodle.git] / cache / stores / redis / lib.php
bloba226d56c56e88b7e14e7c92be347976a7706195a
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.
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 * Redis Cache Store - Main library
20 * @package cachestore_redis
21 * @copyright 2013 Adam Durana
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25 defined('MOODLE_INTERNAL') || die();
27 /**
28 * Redis Cache Store
30 * To allow separation of definitions in Moodle and faster purging, each cache
31 * is implemented as a Redis hash. That is a trade-off between having functionality of TTL
32 * and being able to manage many caches in a single redis instance. Given the recommendation
33 * not to use TTL if at all possible and the benefits of having many stores in Redis using the
34 * hash configuration, the hash implementation has been used.
36 * @copyright 2013 Adam Durana
37 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
39 class cachestore_redis extends cache_store implements cache_is_key_aware, cache_is_lockable,
40 cache_is_configurable, cache_is_searchable {
41 /**
42 * Compressor: none.
44 const COMPRESSOR_NONE = 0;
46 /**
47 * Compressor: PHP GZip.
49 const COMPRESSOR_PHP_GZIP = 1;
51 /**
52 * Compressor: PHP Zstandard.
54 const COMPRESSOR_PHP_ZSTD = 2;
56 /**
57 * @var string Suffix used on key name (for hash) to store the TTL sorted list
59 const TTL_SUFFIX = '_ttl';
61 /**
62 * @var int Number of items to delete from cache in one batch when expiring old TTL data.
64 const TTL_EXPIRE_BATCH = 10000;
66 /**
67 * Name of this store.
69 * @var string
71 protected $name;
73 /**
74 * The definition hash, used for hash key
76 * @var string
78 protected $hash;
80 /**
81 * Flag for readiness!
83 * @var boolean
85 protected $isready = false;
87 /**
88 * Cache definition for this store.
90 * @var cache_definition
92 protected $definition = null;
94 /**
95 * Connection to Redis for this store.
97 * @var Redis
99 protected $redis;
102 * Serializer for this store.
104 * @var int
106 protected $serializer = Redis::SERIALIZER_PHP;
109 * Compressor for this store.
111 * @var int
113 protected $compressor = self::COMPRESSOR_NONE;
116 * Bytes read or written by last call to set()/get() or set_many()/get_many().
118 * @var int
120 protected $lastiobytes = 0;
122 /** @var int Maximum number of seconds to wait for a lock before giving up. */
123 protected $lockwait = 60;
125 /** @var int Timeout before lock is automatically released (in case of crashes) */
126 protected $locktimeout = 600;
128 /** @var ?array Array of current locks, or null if we haven't registered shutdown function */
129 protected $currentlocks = null;
132 * Determines if the requirements for this type of store are met.
134 * @return bool
136 public static function are_requirements_met() {
137 return class_exists('Redis');
141 * Determines if this type of store supports a given mode.
143 * @param int $mode
144 * @return bool
146 public static function is_supported_mode($mode) {
147 return ($mode === self::MODE_APPLICATION || $mode === self::MODE_SESSION);
151 * Get the features of this type of cache store.
153 * @param array $configuration
154 * @return int
156 public static function get_supported_features(array $configuration = array()) {
157 // Although this plugin now supports TTL I did not add SUPPORTS_NATIVE_TTL here, because
158 // doing so would cause Moodle to stop adding a 'TTL wrapper' to data items which enforces
159 // the precise specified TTL. Unless the scheduled task is set to run rather frequently,
160 // this could cause change in behaviour. Maybe later this should be reconsidered...
161 return self::SUPPORTS_DATA_GUARANTEE + self::DEREFERENCES_OBJECTS + self::IS_SEARCHABLE;
165 * Get the supported modes of this type of cache store.
167 * @param array $configuration
168 * @return int
170 public static function get_supported_modes(array $configuration = array()) {
171 return self::MODE_APPLICATION + self::MODE_SESSION;
175 * Constructs an instance of this type of store.
177 * @param string $name
178 * @param array $configuration
180 public function __construct($name, array $configuration = array()) {
181 $this->name = $name;
183 if (!array_key_exists('server', $configuration) || empty($configuration['server'])) {
184 return;
186 if (array_key_exists('serializer', $configuration)) {
187 $this->serializer = (int)$configuration['serializer'];
189 if (array_key_exists('compressor', $configuration)) {
190 $this->compressor = (int)$configuration['compressor'];
192 $password = !empty($configuration['password']) ? $configuration['password'] : '';
193 $prefix = !empty($configuration['prefix']) ? $configuration['prefix'] : '';
194 if (array_key_exists('lockwait', $configuration)) {
195 $this->lockwait = (int)$configuration['lockwait'];
197 if (array_key_exists('locktimeout', $configuration)) {
198 $this->locktimeout = (int)$configuration['locktimeout'];
200 $this->redis = $this->new_redis($configuration['server'], $prefix, $password);
204 * Create a new Redis instance and
205 * connect to the server.
207 * @param string $server The server connection string
208 * @param string $prefix The key prefix
209 * @param string $password The server connection password
210 * @return Redis
212 protected function new_redis($server, $prefix = '', $password = '') {
213 $redis = new Redis();
214 // Check if it isn't a Unix socket to set default port.
215 $port = ($server[0] === '/') ? null : 6379;
216 if (strpos($server, ':')) {
217 $serverconf = explode(':', $server);
218 $server = $serverconf[0];
219 $port = $serverconf[1];
222 try {
223 if ($redis->connect($server, $port)) {
224 if (!empty($password)) {
225 $redis->auth($password);
227 // If using compressor, serialisation will be done at cachestore level, not php-redis.
228 if ($this->compressor == self::COMPRESSOR_NONE) {
229 $redis->setOption(Redis::OPT_SERIALIZER, $this->serializer);
231 if (!empty($prefix)) {
232 $redis->setOption(Redis::OPT_PREFIX, $prefix);
234 $this->isready = true;
235 } else {
236 $this->isready = false;
238 } catch (\RedisException $e) {
239 $this->isready = false;
242 return $redis;
246 * See if we can ping Redis server
248 * @param Redis $redis
249 * @return bool
251 protected function ping(Redis $redis) {
252 try {
253 if ($redis->ping() === false) {
254 return false;
256 } catch (Exception $e) {
257 return false;
259 return true;
263 * Get the name of the store.
265 * @return string
267 public function my_name() {
268 return $this->name;
272 * Initialize the store.
274 * @param cache_definition $definition
275 * @return bool
277 public function initialise(cache_definition $definition) {
278 $this->definition = $definition;
279 $this->hash = $definition->generate_definition_hash();
280 return true;
284 * Determine if the store is initialized.
286 * @return bool
288 public function is_initialised() {
289 return ($this->definition !== null);
293 * Determine if the store is ready for use.
295 * @return bool
297 public function is_ready() {
298 return $this->isready;
302 * Get the value associated with a given key.
304 * @param string $key The key to get the value of.
305 * @return mixed The value of the key, or false if there is no value associated with the key.
307 public function get($key) {
308 $value = $this->redis->hGet($this->hash, $key);
310 if ($this->compressor == self::COMPRESSOR_NONE) {
311 return $value;
314 // When using compression, values are always strings, so strlen will work.
315 $this->lastiobytes = strlen($value);
317 return $this->uncompress($value);
321 * Get the values associated with a list of keys.
323 * @param array $keys The keys to get the values of.
324 * @return array An array of the values of the given keys.
326 public function get_many($keys) {
327 $values = $this->redis->hMGet($this->hash, $keys);
329 if ($this->compressor == self::COMPRESSOR_NONE) {
330 return $values;
333 $this->lastiobytes = 0;
334 foreach ($values as &$value) {
335 $this->lastiobytes += strlen($value);
336 $value = $this->uncompress($value);
339 return $values;
343 * Gets the number of bytes read from or written to cache as a result of the last action.
345 * If compression is not enabled, this function always returns IO_BYTES_NOT_SUPPORTED. The reason is that
346 * when compression is not enabled, data sent to the cache is not serialized, and we would
347 * need to serialize it to compute the size, which would have a significant performance cost.
349 * @return int Bytes read or written
350 * @since Moodle 4.0
352 public function get_last_io_bytes(): int {
353 if ($this->compressor != self::COMPRESSOR_NONE) {
354 return $this->lastiobytes;
355 } else {
356 // Not supported unless compression is on.
357 return parent::get_last_io_bytes();
362 * Set the value of a key.
364 * @param string $key The key to set the value of.
365 * @param mixed $value The value.
366 * @return bool True if the operation succeeded, false otherwise.
368 public function set($key, $value) {
369 if ($this->compressor != self::COMPRESSOR_NONE) {
370 $value = $this->compress($value);
371 $this->lastiobytes = strlen($value);
374 if ($this->redis->hSet($this->hash, $key, $value) === false) {
375 return false;
377 if ($this->definition->get_ttl()) {
378 // When TTL is enabled, we also store the key name in a list sorted by the current time.
379 $this->redis->zAdd($this->hash . self::TTL_SUFFIX, [], self::get_time(), $key);
380 // The return value to the zAdd function never indicates whether the operation succeeded
381 // (it returns zero when there was no error if the item is already in the list) so we
382 // ignore it.
384 return true;
388 * Set the values of many keys.
390 * @param array $keyvaluearray An array of key/value pairs. Each item in the array is an associative array
391 * with two keys, 'key' and 'value'.
392 * @return int The number of key/value pairs successfuly set.
394 public function set_many(array $keyvaluearray) {
395 $pairs = [];
396 $usettl = false;
397 if ($this->definition->get_ttl()) {
398 $usettl = true;
399 $ttlparams = [];
400 $now = self::get_time();
403 $this->lastiobytes = 0;
404 foreach ($keyvaluearray as $pair) {
405 $key = $pair['key'];
406 if ($this->compressor != self::COMPRESSOR_NONE) {
407 $pairs[$key] = $this->compress($pair['value']);
408 $this->lastiobytes += strlen($pairs[$key]);
409 } else {
410 $pairs[$key] = $pair['value'];
412 if ($usettl) {
413 // When TTL is enabled, we also store the key names in a list sorted by the current
414 // time.
415 $ttlparams[] = $now;
416 $ttlparams[] = $key;
419 if ($usettl) {
420 // Store all the key values with current time.
421 $this->redis->zAdd($this->hash . self::TTL_SUFFIX, [], ...$ttlparams);
422 // The return value to the zAdd function never indicates whether the operation succeeded
423 // (it returns zero when there was no error if the item is already in the list) so we
424 // ignore it.
426 if ($this->redis->hMSet($this->hash, $pairs)) {
427 return count($pairs);
429 return 0;
433 * Delete the given key.
435 * @param string $key The key to delete.
436 * @return bool True if the delete operation succeeds, false otherwise.
438 public function delete($key) {
439 $ok = true;
440 if (!$this->redis->hDel($this->hash, $key)) {
441 $ok = false;
443 if ($this->definition->get_ttl()) {
444 // When TTL is enabled, also remove the key from the TTL list.
445 $this->redis->zRem($this->hash . self::TTL_SUFFIX, $key);
447 return $ok;
451 * Delete many keys.
453 * @param array $keys The keys to delete.
454 * @return int The number of keys successfully deleted.
456 public function delete_many(array $keys) {
457 // If there are no keys to delete, do nothing.
458 if (!$keys) {
459 return 0;
461 $count = $this->redis->hDel($this->hash, ...$keys);
462 if ($this->definition->get_ttl()) {
463 // When TTL is enabled, also remove the keys from the TTL list.
464 $this->redis->zRem($this->hash . self::TTL_SUFFIX, ...$keys);
466 return $count;
470 * Purges all keys from the store.
472 * @return bool
474 public function purge() {
475 if ($this->definition->get_ttl()) {
476 // Purge the TTL list as well.
477 $this->redis->del($this->hash . self::TTL_SUFFIX);
478 // According to documentation, there is no error return for the 'del' command (it
479 // only returns the number of keys deleted, which could be 0 or 1 in this case) so we
480 // do not need to check the return value.
482 return ($this->redis->del($this->hash) !== false);
486 * Cleans up after an instance of the store.
488 public function instance_deleted() {
489 $this->redis->close();
490 unset($this->redis);
494 * Determines if the store has a given key.
496 * @see cache_is_key_aware
497 * @param string $key The key to check for.
498 * @return bool True if the key exists, false if it does not.
500 public function has($key) {
501 return !empty($this->redis->hExists($this->hash, $key));
505 * Determines if the store has any of the keys in a list.
507 * @see cache_is_key_aware
508 * @param array $keys The keys to check for.
509 * @return bool True if any of the keys are found, false none of the keys are found.
511 public function has_any(array $keys) {
512 foreach ($keys as $key) {
513 if ($this->has($key)) {
514 return true;
517 return false;
521 * Determines if the store has all of the keys in a list.
523 * @see cache_is_key_aware
524 * @param array $keys The keys to check for.
525 * @return bool True if all of the keys are found, false otherwise.
527 public function has_all(array $keys) {
528 foreach ($keys as $key) {
529 if (!$this->has($key)) {
530 return false;
533 return true;
537 * Tries to acquire a lock with a given name.
539 * @see cache_is_lockable
540 * @param string $key Name of the lock to acquire.
541 * @param string $ownerid Information to identify owner of lock if acquired.
542 * @return bool True if the lock was acquired, false if it was not.
544 public function acquire_lock($key, $ownerid) {
545 $timelimit = time() + $this->lockwait;
546 do {
547 // If the key doesn't already exist, grab it and return true.
548 if ($this->redis->setnx($key, $ownerid)) {
549 // Ensure Redis deletes the key after a bit in case something goes wrong.
550 $this->redis->expire($key, $this->locktimeout);
551 // If we haven't got it already, better register a shutdown function.
552 if ($this->currentlocks === null) {
553 core_shutdown_manager::register_function([$this, 'shutdown_release_locks']);
554 $this->currentlocks = [];
556 $this->currentlocks[$key] = $ownerid;
557 return true;
559 // Wait 1 second then retry.
560 sleep(1);
561 } while (time() < $timelimit);
562 return false;
566 * Releases any locks when the system shuts down, in case there is a crash or somebody forgets
567 * to use 'try-finally'.
569 * Do not call this function manually (except from unit test).
571 public function shutdown_release_locks() {
572 foreach ($this->currentlocks as $key => $ownerid) {
573 debugging('Automatically releasing Redis cache lock: ' . $key . ' (' . $ownerid .
574 ') - did somebody forget to call release_lock()?', DEBUG_DEVELOPER);
575 $this->release_lock($key, $ownerid);
580 * Checks a lock with a given name and owner information.
582 * @see cache_is_lockable
583 * @param string $key Name of the lock to check.
584 * @param string $ownerid Owner information to check existing lock against.
585 * @return mixed True if the lock exists and the owner information matches, null if the lock does not
586 * exist, and false otherwise.
588 public function check_lock_state($key, $ownerid) {
589 $result = $this->redis->get($key);
590 if ($result === (string)$ownerid) {
591 return true;
593 if ($result === false) {
594 return null;
596 return false;
600 * Finds all of the keys being used by this cache store instance.
602 * @return array of all keys in the hash as a numbered array.
604 public function find_all() {
605 return $this->redis->hKeys($this->hash);
609 * Finds all of the keys whose keys start with the given prefix.
611 * @param string $prefix
613 * @return array List of keys that match this prefix.
615 public function find_by_prefix($prefix) {
616 $return = [];
617 foreach ($this->find_all() as $key) {
618 if (strpos($key, $prefix) === 0) {
619 $return[] = $key;
622 return $return;
626 * Releases a given lock if the owner information matches.
628 * @see cache_is_lockable
629 * @param string $key Name of the lock to release.
630 * @param string $ownerid Owner information to use.
631 * @return bool True if the lock is released, false if it is not.
633 public function release_lock($key, $ownerid) {
634 if ($this->check_lock_state($key, $ownerid)) {
635 unset($this->currentlocks[$key]);
636 return ($this->redis->del($key) !== false);
638 return false;
642 * Runs TTL expiry process for this cache.
644 * This is not part of the standard cache API and is intended for use by the scheduled task
645 * \cachestore_redis\ttl.
647 * @return array Various keys with information about how the expiry went
649 public function expire_ttl(): array {
650 $ttl = $this->definition->get_ttl();
651 if (!$ttl) {
652 throw new \coding_exception('Cache definition ' . $this->definition->get_id() . ' does not use TTL');
654 $limit = self::get_time() - $ttl;
655 $count = 0;
656 $batches = 0;
657 $timebefore = microtime(true);
658 $memorybefore = $this->store_total_size();
659 do {
660 $keys = $this->redis->zRangeByScore($this->hash . self::TTL_SUFFIX, 0, $limit,
661 ['limit' => [0, self::TTL_EXPIRE_BATCH]]);
662 $this->delete_many($keys);
663 $count += count($keys);
664 $batches++;
665 } while (count($keys) === self::TTL_EXPIRE_BATCH);
666 $memoryafter = $this->store_total_size();
667 $timeafter = microtime(true);
669 $result = ['keys' => $count, 'batches' => $batches, 'time' => $timeafter - $timebefore];
670 if ($memorybefore !== null) {
671 $result['memory'] = $memorybefore - $memoryafter;
673 return $result;
677 * Gets the current time for TTL functionality. This wrapper makes it easier to unit-test
678 * the TTL behaviour.
680 * @return int Current time
682 protected static function get_time(): int {
683 global $CFG;
684 if (PHPUNIT_TEST && !empty($CFG->phpunit_cachestore_redis_time)) {
685 return $CFG->phpunit_cachestore_redis_time;
687 return time();
691 * Sets the current time (within unit test) for TTL functionality.
693 * This setting is stored in $CFG so will be automatically reset if you use resetAfterTest.
695 * @param int $time Current time (set 0 to start using real time).
697 public static function set_phpunit_time(int $time = 0): void {
698 global $CFG;
699 if (!PHPUNIT_TEST) {
700 throw new \coding_exception('Function only available during unit test');
702 if ($time) {
703 $CFG->phpunit_cachestore_redis_time = $time;
704 } else {
705 unset($CFG->phpunit_cachestore_redis_time);
710 * Estimates the stored size, taking into account whether compression is turned on.
712 * @param mixed $key Key name
713 * @param mixed $value Value
714 * @return int Approximate stored size
716 public function estimate_stored_size($key, $value): int {
717 if ($this->compressor == self::COMPRESSOR_NONE) {
718 // If uncompressed, use default estimate.
719 return parent::estimate_stored_size($key, $value);
720 } else {
721 // If compressed, compress value.
722 return strlen($this->serialize($key)) + strlen($this->compress($value));
727 * Gets Redis reported memory usage.
729 * @return int|null Memory used by Redis or null if we don't know
731 public function store_total_size(): ?int {
732 try {
733 $details = $this->redis->info('MEMORY');
734 } catch (\RedisException $e) {
735 return null;
737 if (empty($details['used_memory'])) {
738 return null;
739 } else {
740 return (int)$details['used_memory'];
745 * Creates a configuration array from given 'add instance' form data.
747 * @see cache_is_configurable
748 * @param stdClass $data
749 * @return array
751 public static function config_get_configuration_array($data) {
752 return array(
753 'server' => $data->server,
754 'prefix' => $data->prefix,
755 'password' => $data->password,
756 'serializer' => $data->serializer,
757 'compressor' => $data->compressor,
762 * Sets form data from a configuration array.
764 * @see cache_is_configurable
765 * @param moodleform $editform
766 * @param array $config
768 public static function config_set_edit_form_data(moodleform $editform, array $config) {
769 $data = array();
770 $data['server'] = $config['server'];
771 $data['prefix'] = !empty($config['prefix']) ? $config['prefix'] : '';
772 $data['password'] = !empty($config['password']) ? $config['password'] : '';
773 if (!empty($config['serializer'])) {
774 $data['serializer'] = $config['serializer'];
776 if (!empty($config['compressor'])) {
777 $data['compressor'] = $config['compressor'];
779 $editform->set_data($data);
784 * Creates an instance of the store for testing.
786 * @param cache_definition $definition
787 * @return mixed An instance of the store, or false if an instance cannot be created.
789 public static function initialise_test_instance(cache_definition $definition) {
790 if (!self::are_requirements_met()) {
791 return false;
793 $config = get_config('cachestore_redis');
794 if (empty($config->test_server)) {
795 return false;
797 $configuration = array('server' => $config->test_server);
798 if (!empty($config->test_serializer)) {
799 $configuration['serializer'] = $config->test_serializer;
801 if (!empty($config->test_password)) {
802 $configuration['password'] = $config->test_password;
804 // Make it possible to test TTL performance by hacking a copy of the cache definition.
805 if (!empty($config->test_ttl)) {
806 $definition = clone $definition;
807 $property = (new ReflectionClass($definition))->getProperty('ttl');
808 $property->setAccessible(true);
809 $property->setValue($definition, 999);
811 $cache = new cachestore_redis('Redis test', $configuration);
812 $cache->initialise($definition);
814 return $cache;
818 * Return configuration to use when unit testing.
820 * @return array
822 public static function unit_test_configuration() {
823 global $DB;
825 if (!self::are_requirements_met() || !self::ready_to_be_used_for_testing()) {
826 throw new moodle_exception('TEST_CACHESTORE_REDIS_TESTSERVERS not configured, unable to create test configuration');
829 return ['server' => TEST_CACHESTORE_REDIS_TESTSERVERS,
830 'prefix' => $DB->get_prefix(),
835 * Returns true if this cache store instance is both suitable for testing, and ready for testing.
837 * When TEST_CACHESTORE_REDIS_TESTSERVERS is set, then we are ready to be use d for testing.
839 * @return bool
841 public static function ready_to_be_used_for_testing() {
842 return defined('TEST_CACHESTORE_REDIS_TESTSERVERS');
846 * Gets an array of options to use as the serialiser.
847 * @return array
849 public static function config_get_serializer_options() {
850 $options = array(
851 Redis::SERIALIZER_PHP => get_string('serializer_php', 'cachestore_redis')
854 if (defined('Redis::SERIALIZER_IGBINARY')) {
855 $options[Redis::SERIALIZER_IGBINARY] = get_string('serializer_igbinary', 'cachestore_redis');
857 return $options;
861 * Gets an array of options to use as the compressor.
863 * @return array
865 public static function config_get_compressor_options() {
866 $arr = [
867 self::COMPRESSOR_NONE => get_string('compressor_none', 'cachestore_redis'),
868 self::COMPRESSOR_PHP_GZIP => get_string('compressor_php_gzip', 'cachestore_redis'),
871 // Check if the Zstandard PHP extension is installed.
872 if (extension_loaded('zstd')) {
873 $arr[self::COMPRESSOR_PHP_ZSTD] = get_string('compressor_php_zstd', 'cachestore_redis');
876 return $arr;
880 * Compress the given value, serializing it first.
882 * @param mixed $value
883 * @return string
885 private function compress($value) {
886 $value = $this->serialize($value);
888 switch ($this->compressor) {
889 case self::COMPRESSOR_NONE:
890 return $value;
892 case self::COMPRESSOR_PHP_GZIP:
893 return gzencode($value);
895 case self::COMPRESSOR_PHP_ZSTD:
896 return zstd_compress($value);
898 default:
899 debugging("Invalid compressor: {$this->compressor}");
900 return $value;
905 * Uncompresses (deflates) the data, unserialising it afterwards.
907 * @param string $value
908 * @return mixed
910 private function uncompress($value) {
911 if ($value === false) {
912 return false;
915 switch ($this->compressor) {
916 case self::COMPRESSOR_NONE:
917 break;
918 case self::COMPRESSOR_PHP_GZIP:
919 $value = gzdecode($value);
920 break;
921 case self::COMPRESSOR_PHP_ZSTD:
922 $value = zstd_uncompress($value);
923 break;
924 default:
925 debugging("Invalid compressor: {$this->compressor}");
928 return $this->unserialize($value);
932 * Serializes the data according to the configured serializer.
934 * @param mixed $value
935 * @return string
937 private function serialize($value) {
938 switch ($this->serializer) {
939 case Redis::SERIALIZER_NONE:
940 return $value;
941 case Redis::SERIALIZER_PHP:
942 return serialize($value);
943 case defined('Redis::SERIALIZER_IGBINARY') && Redis::SERIALIZER_IGBINARY:
944 return igbinary_serialize($value);
945 default:
946 debugging("Invalid serializer: {$this->serializer}");
947 return $value;
952 * Unserializes the data according to the configured serializer
954 * @param string $value
955 * @return mixed
957 private function unserialize($value) {
958 switch ($this->serializer) {
959 case Redis::SERIALIZER_NONE:
960 return $value;
961 case Redis::SERIALIZER_PHP:
962 return unserialize($value);
963 case defined('Redis::SERIALIZER_IGBINARY') && Redis::SERIALIZER_IGBINARY:
964 return igbinary_unserialize($value);
965 default:
966 debugging("Invalid serializer: {$this->serializer}");
967 return $value;