Differences Between: [Versions 310 and 400] [Versions 311 and 400] [Versions 39 and 400] [Versions 400 and 401] [Versions 400 and 402] [Versions 400 and 403]
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 * Redis Cache Store - Main library 19 * 20 * @package cachestore_redis 21 * @copyright 2013 Adam Durana 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 defined('MOODLE_INTERNAL') || die(); 26 27 /** 28 * Redis Cache Store 29 * 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. 35 * 36 * @copyright 2013 Adam Durana 37 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 38 */ 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. 43 */ 44 const COMPRESSOR_NONE = 0; 45 46 /** 47 * Compressor: PHP GZip. 48 */ 49 const COMPRESSOR_PHP_GZIP = 1; 50 51 /** 52 * Compressor: PHP Zstandard. 53 */ 54 const COMPRESSOR_PHP_ZSTD = 2; 55 56 /** 57 * @var string Suffix used on key name (for hash) to store the TTL sorted list 58 */ 59 const TTL_SUFFIX = '_ttl'; 60 61 /** 62 * @var int Number of items to delete from cache in one batch when expiring old TTL data. 63 */ 64 const TTL_EXPIRE_BATCH = 10000; 65 66 /** 67 * Name of this store. 68 * 69 * @var string 70 */ 71 protected $name; 72 73 /** 74 * The definition hash, used for hash key 75 * 76 * @var string 77 */ 78 protected $hash; 79 80 /** 81 * Flag for readiness! 82 * 83 * @var boolean 84 */ 85 protected $isready = false; 86 87 /** 88 * Cache definition for this store. 89 * 90 * @var cache_definition 91 */ 92 protected $definition = null; 93 94 /** 95 * Connection to Redis for this store. 96 * 97 * @var Redis 98 */ 99 protected $redis; 100 101 /** 102 * Serializer for this store. 103 * 104 * @var int 105 */ 106 protected $serializer = Redis::SERIALIZER_PHP; 107 108 /** 109 * Compressor for this store. 110 * 111 * @var int 112 */ 113 protected $compressor = self::COMPRESSOR_NONE; 114 115 /** 116 * Bytes read or written by last call to set()/get() or set_many()/get_many(). 117 * 118 * @var int 119 */ 120 protected $lastiobytes = 0; 121 122 /** 123 * Determines if the requirements for this type of store are met. 124 * 125 * @return bool 126 */ 127 public static function are_requirements_met() { 128 return class_exists('Redis'); 129 } 130 131 /** 132 * Determines if this type of store supports a given mode. 133 * 134 * @param int $mode 135 * @return bool 136 */ 137 public static function is_supported_mode($mode) { 138 return ($mode === self::MODE_APPLICATION || $mode === self::MODE_SESSION); 139 } 140 141 /** 142 * Get the features of this type of cache store. 143 * 144 * @param array $configuration 145 * @return int 146 */ 147 public static function get_supported_features(array $configuration = array()) { 148 // Although this plugin now supports TTL I did not add SUPPORTS_NATIVE_TTL here, because 149 // doing so would cause Moodle to stop adding a 'TTL wrapper' to data items which enforces 150 // the precise specified TTL. Unless the scheduled task is set to run rather frequently, 151 // this could cause change in behaviour. Maybe later this should be reconsidered... 152 return self::SUPPORTS_DATA_GUARANTEE + self::DEREFERENCES_OBJECTS + self::IS_SEARCHABLE; 153 } 154 155 /** 156 * Get the supported modes of this type of cache store. 157 * 158 * @param array $configuration 159 * @return int 160 */ 161 public static function get_supported_modes(array $configuration = array()) { 162 return self::MODE_APPLICATION + self::MODE_SESSION; 163 } 164 165 /** 166 * Constructs an instance of this type of store. 167 * 168 * @param string $name 169 * @param array $configuration 170 */ 171 public function __construct($name, array $configuration = array()) { 172 $this->name = $name; 173 174 if (!array_key_exists('server', $configuration) || empty($configuration['server'])) { 175 return; 176 } 177 if (array_key_exists('serializer', $configuration)) { 178 $this->serializer = (int)$configuration['serializer']; 179 } 180 if (array_key_exists('compressor', $configuration)) { 181 $this->compressor = (int)$configuration['compressor']; 182 } 183 $password = !empty($configuration['password']) ? $configuration['password'] : ''; 184 $prefix = !empty($configuration['prefix']) ? $configuration['prefix'] : ''; 185 $this->redis = $this->new_redis($configuration['server'], $prefix, $password); 186 } 187 188 /** 189 * Create a new Redis instance and 190 * connect to the server. 191 * 192 * @param string $server The server connection string 193 * @param string $prefix The key prefix 194 * @param string $password The server connection password 195 * @return Redis 196 */ 197 protected function new_redis($server, $prefix = '', $password = '') { 198 $redis = new Redis(); 199 // Check if it isn't a Unix socket to set default port. 200 $port = ($server[0] === '/') ? null : 6379; 201 if (strpos($server, ':')) { 202 $serverconf = explode(':', $server); 203 $server = $serverconf[0]; 204 $port = $serverconf[1]; 205 } 206 207 try { 208 if ($redis->connect($server, $port)) { 209 if (!empty($password)) { 210 $redis->auth($password); 211 } 212 // If using compressor, serialisation will be done at cachestore level, not php-redis. 213 if ($this->compressor == self::COMPRESSOR_NONE) { 214 $redis->setOption(Redis::OPT_SERIALIZER, $this->serializer); 215 } 216 if (!empty($prefix)) { 217 $redis->setOption(Redis::OPT_PREFIX, $prefix); 218 } 219 // Database setting option... 220 $this->isready = $this->ping($redis); 221 } else { 222 $this->isready = false; 223 } 224 } catch (\RedisException $e) { 225 $this->isready = false; 226 } 227 228 return $redis; 229 } 230 231 /** 232 * See if we can ping Redis server 233 * 234 * @param Redis $redis 235 * @return bool 236 */ 237 protected function ping(Redis $redis) { 238 try { 239 if ($redis->ping() === false) { 240 return false; 241 } 242 } catch (Exception $e) { 243 return false; 244 } 245 return true; 246 } 247 248 /** 249 * Get the name of the store. 250 * 251 * @return string 252 */ 253 public function my_name() { 254 return $this->name; 255 } 256 257 /** 258 * Initialize the store. 259 * 260 * @param cache_definition $definition 261 * @return bool 262 */ 263 public function initialise(cache_definition $definition) { 264 $this->definition = $definition; 265 $this->hash = $definition->generate_definition_hash(); 266 return true; 267 } 268 269 /** 270 * Determine if the store is initialized. 271 * 272 * @return bool 273 */ 274 public function is_initialised() { 275 return ($this->definition !== null); 276 } 277 278 /** 279 * Determine if the store is ready for use. 280 * 281 * @return bool 282 */ 283 public function is_ready() { 284 return $this->isready; 285 } 286 287 /** 288 * Get the value associated with a given key. 289 * 290 * @param string $key The key to get the value of. 291 * @return mixed The value of the key, or false if there is no value associated with the key. 292 */ 293 public function get($key) { 294 $value = $this->redis->hGet($this->hash, $key); 295 296 if ($this->compressor == self::COMPRESSOR_NONE) { 297 return $value; 298 } 299 300 // When using compression, values are always strings, so strlen will work. 301 $this->lastiobytes = strlen($value); 302 303 return $this->uncompress($value); 304 } 305 306 /** 307 * Get the values associated with a list of keys. 308 * 309 * @param array $keys The keys to get the values of. 310 * @return array An array of the values of the given keys. 311 */ 312 public function get_many($keys) { 313 $values = $this->redis->hMGet($this->hash, $keys); 314 315 if ($this->compressor == self::COMPRESSOR_NONE) { 316 return $values; 317 } 318 319 $this->lastiobytes = 0; 320 foreach ($values as &$value) { 321 $this->lastiobytes += strlen($value); 322 $value = $this->uncompress($value); 323 } 324 325 return $values; 326 } 327 328 /** 329 * Gets the number of bytes read from or written to cache as a result of the last action. 330 * 331 * If compression is not enabled, this function always returns IO_BYTES_NOT_SUPPORTED. The reason is that 332 * when compression is not enabled, data sent to the cache is not serialized, and we would 333 * need to serialize it to compute the size, which would have a significant performance cost. 334 * 335 * @return int Bytes read or written 336 * @since Moodle 4.0 337 */ 338 public function get_last_io_bytes(): int { 339 if ($this->compressor != self::COMPRESSOR_NONE) { 340 return $this->lastiobytes; 341 } else { 342 // Not supported unless compression is on. 343 return parent::get_last_io_bytes(); 344 } 345 } 346 347 /** 348 * Set the value of a key. 349 * 350 * @param string $key The key to set the value of. 351 * @param mixed $value The value. 352 * @return bool True if the operation succeeded, false otherwise. 353 */ 354 public function set($key, $value) { 355 if ($this->compressor != self::COMPRESSOR_NONE) { 356 $value = $this->compress($value); 357 $this->lastiobytes = strlen($value); 358 } 359 360 if ($this->redis->hSet($this->hash, $key, $value) === false) { 361 return false; 362 } 363 if ($this->definition->get_ttl()) { 364 // When TTL is enabled, we also store the key name in a list sorted by the current time. 365 $this->redis->zAdd($this->hash . self::TTL_SUFFIX, [], self::get_time(), $key); 366 // The return value to the zAdd function never indicates whether the operation succeeded 367 // (it returns zero when there was no error if the item is already in the list) so we 368 // ignore it. 369 } 370 return true; 371 } 372 373 /** 374 * Set the values of many keys. 375 * 376 * @param array $keyvaluearray An array of key/value pairs. Each item in the array is an associative array 377 * with two keys, 'key' and 'value'. 378 * @return int The number of key/value pairs successfuly set. 379 */ 380 public function set_many(array $keyvaluearray) { 381 $pairs = []; 382 $usettl = false; 383 if ($this->definition->get_ttl()) { 384 $usettl = true; 385 $ttlparams = []; 386 $now = self::get_time(); 387 } 388 389 $this->lastiobytes = 0; 390 foreach ($keyvaluearray as $pair) { 391 $key = $pair['key']; 392 if ($this->compressor != self::COMPRESSOR_NONE) { 393 $pairs[$key] = $this->compress($pair['value']); 394 $this->lastiobytes += strlen($pairs[$key]); 395 } else { 396 $pairs[$key] = $pair['value']; 397 } 398 if ($usettl) { 399 // When TTL is enabled, we also store the key names in a list sorted by the current 400 // time. 401 $ttlparams[] = $now; 402 $ttlparams[] = $key; 403 } 404 } 405 if ($usettl) { 406 // Store all the key values with current time. 407 $this->redis->zAdd($this->hash . self::TTL_SUFFIX, [], ...$ttlparams); 408 // The return value to the zAdd function never indicates whether the operation succeeded 409 // (it returns zero when there was no error if the item is already in the list) so we 410 // ignore it. 411 } 412 if ($this->redis->hMSet($this->hash, $pairs)) { 413 return count($pairs); 414 } 415 return 0; 416 } 417 418 /** 419 * Delete the given key. 420 * 421 * @param string $key The key to delete. 422 * @return bool True if the delete operation succeeds, false otherwise. 423 */ 424 public function delete($key) { 425 $ok = true; 426 if (!$this->redis->hDel($this->hash, $key)) { 427 $ok = false; 428 } 429 if ($this->definition->get_ttl()) { 430 // When TTL is enabled, also remove the key from the TTL list. 431 $this->redis->zRem($this->hash . self::TTL_SUFFIX, $key); 432 } 433 return $ok; 434 } 435 436 /** 437 * Delete many keys. 438 * 439 * @param array $keys The keys to delete. 440 * @return int The number of keys successfully deleted. 441 */ 442 public function delete_many(array $keys) { 443 // If there are no keys to delete, do nothing. 444 if (!$keys) { 445 return 0; 446 } 447 $count = $this->redis->hDel($this->hash, ...$keys); 448 if ($this->definition->get_ttl()) { 449 // When TTL is enabled, also remove the keys from the TTL list. 450 $this->redis->zRem($this->hash . self::TTL_SUFFIX, ...$keys); 451 } 452 return $count; 453 } 454 455 /** 456 * Purges all keys from the store. 457 * 458 * @return bool 459 */ 460 public function purge() { 461 if ($this->definition->get_ttl()) { 462 // Purge the TTL list as well. 463 $this->redis->del($this->hash . self::TTL_SUFFIX); 464 // According to documentation, there is no error return for the 'del' command (it 465 // only returns the number of keys deleted, which could be 0 or 1 in this case) so we 466 // do not need to check the return value. 467 } 468 return ($this->redis->del($this->hash) !== false); 469 } 470 471 /** 472 * Cleans up after an instance of the store. 473 */ 474 public function instance_deleted() { 475 $this->redis->close(); 476 unset($this->redis); 477 } 478 479 /** 480 * Determines if the store has a given key. 481 * 482 * @see cache_is_key_aware 483 * @param string $key The key to check for. 484 * @return bool True if the key exists, false if it does not. 485 */ 486 public function has($key) { 487 return !empty($this->redis->hExists($this->hash, $key)); 488 } 489 490 /** 491 * Determines if the store has any of the keys in a list. 492 * 493 * @see cache_is_key_aware 494 * @param array $keys The keys to check for. 495 * @return bool True if any of the keys are found, false none of the keys are found. 496 */ 497 public function has_any(array $keys) { 498 foreach ($keys as $key) { 499 if ($this->has($key)) { 500 return true; 501 } 502 } 503 return false; 504 } 505 506 /** 507 * Determines if the store has all of the keys in a list. 508 * 509 * @see cache_is_key_aware 510 * @param array $keys The keys to check for. 511 * @return bool True if all of the keys are found, false otherwise. 512 */ 513 public function has_all(array $keys) { 514 foreach ($keys as $key) { 515 if (!$this->has($key)) { 516 return false; 517 } 518 } 519 return true; 520 } 521 522 /** 523 * Tries to acquire a lock with a given name. 524 * 525 * @see cache_is_lockable 526 * @param string $key Name of the lock to acquire. 527 * @param string $ownerid Information to identify owner of lock if acquired. 528 * @return bool True if the lock was acquired, false if it was not. 529 */ 530 public function acquire_lock($key, $ownerid) { 531 return $this->redis->setnx($key, $ownerid); 532 } 533 534 /** 535 * Checks a lock with a given name and owner information. 536 * 537 * @see cache_is_lockable 538 * @param string $key Name of the lock to check. 539 * @param string $ownerid Owner information to check existing lock against. 540 * @return mixed True if the lock exists and the owner information matches, null if the lock does not 541 * exist, and false otherwise. 542 */ 543 public function check_lock_state($key, $ownerid) { 544 $result = $this->redis->get($key); 545 if ($result === $ownerid) { 546 return true; 547 } 548 if ($result === false) { 549 return null; 550 } 551 return false; 552 } 553 554 /** 555 * Finds all of the keys being used by this cache store instance. 556 * 557 * @return array of all keys in the hash as a numbered array. 558 */ 559 public function find_all() { 560 return $this->redis->hKeys($this->hash); 561 } 562 563 /** 564 * Finds all of the keys whose keys start with the given prefix. 565 * 566 * @param string $prefix 567 * 568 * @return array List of keys that match this prefix. 569 */ 570 public function find_by_prefix($prefix) { 571 $return = []; 572 foreach ($this->find_all() as $key) { 573 if (strpos($key, $prefix) === 0) { 574 $return[] = $key; 575 } 576 } 577 return $return; 578 } 579 580 /** 581 * Releases a given lock if the owner information matches. 582 * 583 * @see cache_is_lockable 584 * @param string $key Name of the lock to release. 585 * @param string $ownerid Owner information to use. 586 * @return bool True if the lock is released, false if it is not. 587 */ 588 public function release_lock($key, $ownerid) { 589 if ($this->check_lock_state($key, $ownerid)) { 590 return ($this->redis->del($key) !== false); 591 } 592 return false; 593 } 594 595 /** 596 * Runs TTL expiry process for this cache. 597 * 598 * This is not part of the standard cache API and is intended for use by the scheduled task 599 * \cachestore_redis\ttl. 600 * 601 * @return array Various keys with information about how the expiry went 602 */ 603 public function expire_ttl(): array { 604 $ttl = $this->definition->get_ttl(); 605 if (!$ttl) { 606 throw new \coding_exception('Cache definition ' . $this->definition->get_id() . ' does not use TTL'); 607 } 608 $limit = self::get_time() - $ttl; 609 $count = 0; 610 $batches = 0; 611 $timebefore = microtime(true); 612 $memorybefore = $this->store_total_size(); 613 do { 614 $keys = $this->redis->zRangeByScore($this->hash . self::TTL_SUFFIX, 0, $limit, 615 ['limit' => [0, self::TTL_EXPIRE_BATCH]]); 616 $this->delete_many($keys); 617 $count += count($keys); 618 $batches++; 619 } while (count($keys) === self::TTL_EXPIRE_BATCH); 620 $memoryafter = $this->store_total_size(); 621 $timeafter = microtime(true); 622 623 $result = ['keys' => $count, 'batches' => $batches, 'time' => $timeafter - $timebefore]; 624 if ($memorybefore !== null) { 625 $result['memory'] = $memorybefore - $memoryafter; 626 } 627 return $result; 628 } 629 630 /** 631 * Gets the current time for TTL functionality. This wrapper makes it easier to unit-test 632 * the TTL behaviour. 633 * 634 * @return int Current time 635 */ 636 protected static function get_time(): int { 637 global $CFG; 638 if (PHPUNIT_TEST && !empty($CFG->phpunit_cachestore_redis_time)) { 639 return $CFG->phpunit_cachestore_redis_time; 640 } 641 return time(); 642 } 643 644 /** 645 * Sets the current time (within unit test) for TTL functionality. 646 * 647 * This setting is stored in $CFG so will be automatically reset if you use resetAfterTest. 648 * 649 * @param int $time Current time (set 0 to start using real time). 650 */ 651 public static function set_phpunit_time(int $time = 0): void { 652 global $CFG; 653 if (!PHPUNIT_TEST) { 654 throw new \coding_exception('Function only available during unit test'); 655 } 656 if ($time) { 657 $CFG->phpunit_cachestore_redis_time = $time; 658 } else { 659 unset($CFG->phpunit_cachestore_redis_time); 660 } 661 } 662 663 /** 664 * Estimates the stored size, taking into account whether compression is turned on. 665 * 666 * @param mixed $key Key name 667 * @param mixed $value Value 668 * @return int Approximate stored size 669 */ 670 public function estimate_stored_size($key, $value): int { 671 if ($this->compressor == self::COMPRESSOR_NONE) { 672 // If uncompressed, use default estimate. 673 return parent::estimate_stored_size($key, $value); 674 } else { 675 // If compressed, compress value. 676 return strlen($this->serialize($key)) + strlen($this->compress($value)); 677 } 678 } 679 680 /** 681 * Gets Redis reported memory usage. 682 * 683 * @return int|null Memory used by Redis or null if we don't know 684 */ 685 public function store_total_size(): ?int { 686 try { 687 $details = $this->redis->info('MEMORY'); 688 } catch (\RedisException $e) { 689 return null; 690 } 691 if (empty($details['used_memory'])) { 692 return null; 693 } else { 694 return (int)$details['used_memory']; 695 } 696 } 697 698 /** 699 * Creates a configuration array from given 'add instance' form data. 700 * 701 * @see cache_is_configurable 702 * @param stdClass $data 703 * @return array 704 */ 705 public static function config_get_configuration_array($data) { 706 return array( 707 'server' => $data->server, 708 'prefix' => $data->prefix, 709 'password' => $data->password, 710 'serializer' => $data->serializer, 711 'compressor' => $data->compressor, 712 ); 713 } 714 715 /** 716 * Sets form data from a configuration array. 717 * 718 * @see cache_is_configurable 719 * @param moodleform $editform 720 * @param array $config 721 */ 722 public static function config_set_edit_form_data(moodleform $editform, array $config) { 723 $data = array(); 724 $data['server'] = $config['server']; 725 $data['prefix'] = !empty($config['prefix']) ? $config['prefix'] : ''; 726 $data['password'] = !empty($config['password']) ? $config['password'] : ''; 727 if (!empty($config['serializer'])) { 728 $data['serializer'] = $config['serializer']; 729 } 730 if (!empty($config['compressor'])) { 731 $data['compressor'] = $config['compressor']; 732 } 733 $editform->set_data($data); 734 } 735 736 737 /** 738 * Creates an instance of the store for testing. 739 * 740 * @param cache_definition $definition 741 * @return mixed An instance of the store, or false if an instance cannot be created. 742 */ 743 public static function initialise_test_instance(cache_definition $definition) { 744 if (!self::are_requirements_met()) { 745 return false; 746 } 747 $config = get_config('cachestore_redis'); 748 if (empty($config->test_server)) { 749 return false; 750 } 751 $configuration = array('server' => $config->test_server); 752 if (!empty($config->test_serializer)) { 753 $configuration['serializer'] = $config->test_serializer; 754 } 755 if (!empty($config->test_password)) { 756 $configuration['password'] = $config->test_password; 757 } 758 // Make it possible to test TTL performance by hacking a copy of the cache definition. 759 if (!empty($config->test_ttl)) { 760 $definition = clone $definition; 761 $property = (new ReflectionClass($definition))->getProperty('ttl'); 762 $property->setAccessible(true); 763 $property->setValue($definition, 999); 764 } 765 $cache = new cachestore_redis('Redis test', $configuration); 766 $cache->initialise($definition); 767 768 return $cache; 769 } 770 771 /** 772 * Return configuration to use when unit testing. 773 * 774 * @return array 775 */ 776 public static function unit_test_configuration() { 777 global $DB; 778 779 if (!self::are_requirements_met() || !self::ready_to_be_used_for_testing()) { 780 throw new moodle_exception('TEST_CACHESTORE_REDIS_TESTSERVERS not configured, unable to create test configuration'); 781 } 782 783 return ['server' => TEST_CACHESTORE_REDIS_TESTSERVERS, 784 'prefix' => $DB->get_prefix(), 785 ]; 786 } 787 788 /** 789 * Returns true if this cache store instance is both suitable for testing, and ready for testing. 790 * 791 * When TEST_CACHESTORE_REDIS_TESTSERVERS is set, then we are ready to be use d for testing. 792 * 793 * @return bool 794 */ 795 public static function ready_to_be_used_for_testing() { 796 return defined('TEST_CACHESTORE_REDIS_TESTSERVERS'); 797 } 798 799 /** 800 * Gets an array of options to use as the serialiser. 801 * @return array 802 */ 803 public static function config_get_serializer_options() { 804 $options = array( 805 Redis::SERIALIZER_PHP => get_string('serializer_php', 'cachestore_redis') 806 ); 807 808 if (defined('Redis::SERIALIZER_IGBINARY')) { 809 $options[Redis::SERIALIZER_IGBINARY] = get_string('serializer_igbinary', 'cachestore_redis'); 810 } 811 return $options; 812 } 813 814 /** 815 * Gets an array of options to use as the compressor. 816 * 817 * @return array 818 */ 819 public static function config_get_compressor_options() { 820 $arr = [ 821 self::COMPRESSOR_NONE => get_string('compressor_none', 'cachestore_redis'), 822 self::COMPRESSOR_PHP_GZIP => get_string('compressor_php_gzip', 'cachestore_redis'), 823 ]; 824 825 // Check if the Zstandard PHP extension is installed. 826 if (extension_loaded('zstd')) { 827 $arr[self::COMPRESSOR_PHP_ZSTD] = get_string('compressor_php_zstd', 'cachestore_redis'); 828 } 829 830 return $arr; 831 } 832 833 /** 834 * Compress the given value, serializing it first. 835 * 836 * @param mixed $value 837 * @return string 838 */ 839 private function compress($value) { 840 $value = $this->serialize($value); 841 842 switch ($this->compressor) { 843 case self::COMPRESSOR_NONE: 844 return $value; 845 846 case self::COMPRESSOR_PHP_GZIP: 847 return gzencode($value); 848 849 case self::COMPRESSOR_PHP_ZSTD: 850 return zstd_compress($value); 851 852 default: 853 debugging("Invalid compressor: {$this->compressor}"); 854 return $value; 855 } 856 } 857 858 /** 859 * Uncompresses (deflates) the data, unserialising it afterwards. 860 * 861 * @param string $value 862 * @return mixed 863 */ 864 private function uncompress($value) { 865 if ($value === false) { 866 return false; 867 } 868 869 switch ($this->compressor) { 870 case self::COMPRESSOR_NONE: 871 break; 872 case self::COMPRESSOR_PHP_GZIP: 873 $value = gzdecode($value); 874 break; 875 case self::COMPRESSOR_PHP_ZSTD: 876 $value = zstd_uncompress($value); 877 break; 878 default: 879 debugging("Invalid compressor: {$this->compressor}"); 880 } 881 882 return $this->unserialize($value); 883 } 884 885 /** 886 * Serializes the data according to the configured serializer. 887 * 888 * @param mixed $value 889 * @return string 890 */ 891 private function serialize($value) { 892 switch ($this->serializer) { 893 case Redis::SERIALIZER_NONE: 894 return $value; 895 case Redis::SERIALIZER_PHP: 896 return serialize($value); 897 case defined('Redis::SERIALIZER_IGBINARY') && Redis::SERIALIZER_IGBINARY: 898 return igbinary_serialize($value); 899 default: 900 debugging("Invalid serializer: {$this->serializer}"); 901 return $value; 902 } 903 } 904 905 /** 906 * Unserializes the data according to the configured serializer 907 * 908 * @param string $value 909 * @return mixed 910 */ 911 private function unserialize($value) { 912 switch ($this->serializer) { 913 case Redis::SERIALIZER_NONE: 914 return $value; 915 case Redis::SERIALIZER_PHP: 916 return unserialize($value); 917 case defined('Redis::SERIALIZER_IGBINARY') && Redis::SERIALIZER_IGBINARY: 918 return igbinary_unserialize($value); 919 default: 920 debugging("Invalid serializer: {$this->serializer}"); 921 return $value; 922 } 923 } 924 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body