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 * 22 * @package cachestore_memcached 23 * @copyright 2012 Sam Hemelryk 24 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 25 */ 26 27 defined('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 */ 46 class cachestore_memcached extends cache_store implements cache_is_configurable { 47 48 /** 49 * The minimum required version of memcached in order to use this store. 50 */ 51 const REQUIRED_VERSION = '2.0.0'; 52 53 /** 54 * The name of the store 55 * @var store 56 */ 57 protected $name; 58 59 /** 60 * The memcached connection 61 * @var Memcached 62 */ 63 protected $connection; 64 65 /** 66 * An array of servers to use during connection 67 * @var array 68 */ 69 protected $servers = array(); 70 71 /** 72 * The options used when establishing the connection 73 * @var array 74 */ 75 protected $options = array(); 76 77 /** 78 * True when this instance is ready to be initialised. 79 * @var bool 80 */ 81 protected $isready = false; 82 83 /** 84 * Set to true when this store instance has been initialised. 85 * @var bool 86 */ 87 protected $isinitialised = false; 88 89 /** 90 * The cache definition this store was initialised with. 91 * @var cache_definition 92 */ 93 protected $definition; 94 95 /** 96 * Set to true when this store is clustered. 97 * @var bool 98 */ 99 protected $clustered = false; 100 101 /** 102 * Array of servers to set when in clustered mode. 103 * @var array 104 */ 105 protected $setservers = array(); 106 107 /** 108 * The an array of memcache connections for the set servers, once established. 109 * @var array 110 */ 111 protected $setconnections = array(); 112 113 /** 114 * The prefix to use on all keys. 115 * @var string 116 */ 117 protected $prefix = ''; 118 119 /** 120 * True if Memcached::deleteMulti can be used, false otherwise. 121 * This required extension version 2.0.0 or greater. 122 * @var bool 123 */ 124 protected $candeletemulti = false; 125 126 /** 127 * True if the memcached server is shared, false otherwise. 128 * This required extension version 2.0.0 or greater. 129 * @var bool 130 */ 131 protected $isshared = false; 132 133 /** 134 * Constructs the store instance. 135 * 136 * Noting that this function is not an initialisation. It is used to prepare the store for use. 137 * The store will be initialised when required and will be provided with a cache_definition at that time. 138 * 139 * @param string $name 140 * @param array $configuration 141 */ 142 public function __construct($name, array $configuration = array()) { 143 $this->name = $name; 144 if (!array_key_exists('servers', $configuration) || empty($configuration['servers'])) { 145 // Nothing configured. 146 return; 147 } 148 if (!is_array($configuration['servers'])) { 149 $configuration['servers'] = array($configuration['servers']); 150 } 151 152 $compression = array_key_exists('compression', $configuration) ? (bool)$configuration['compression'] : true; 153 if (array_key_exists('serialiser', $configuration)) { 154 $serialiser = (int)$configuration['serialiser']; 155 } else { 156 $serialiser = Memcached::SERIALIZER_PHP; 157 } 158 $prefix = (!empty($configuration['prefix'])) ? (string)$configuration['prefix'] : crc32($name); 159 $hashmethod = (array_key_exists('hash', $configuration)) ? (int)$configuration['hash'] : Memcached::HASH_DEFAULT; 160 $bufferwrites = array_key_exists('bufferwrites', $configuration) ? (bool)$configuration['bufferwrites'] : false; 161 162 foreach ($configuration['servers'] as $server) { 163 if (!is_array($server)) { 164 $server = explode(':', $server, 3); 165 } 166 if (!array_key_exists(1, $server)) { 167 $server[1] = 11211; 168 $server[2] = 100; 169 } else if (!array_key_exists(2, $server)) { 170 $server[2] = 100; 171 } 172 $this->servers[] = $server; 173 } 174 175 $this->clustered = array_key_exists('clustered', $configuration) ? (bool)$configuration['clustered'] : false; 176 177 if ($this->clustered) { 178 if (!array_key_exists('setservers', $configuration) || (count($configuration['setservers']) < 1)) { 179 // Can't setup clustering without set servers. 180 return; 181 } 182 if (count($this->servers) !== 1) { 183 // Can only setup cluster with exactly 1 get server. 184 return; 185 } 186 foreach ($configuration['setservers'] as $server) { 187 // We do not use weights (3rd part) on these servers. 188 if (!is_array($server)) { 189 $server = explode(':', $server, 3); 190 } 191 if (!array_key_exists(1, $server)) { 192 $server[1] = 11211; 193 } 194 $this->setservers[] = $server; 195 } 196 } 197 198 $this->options[Memcached::OPT_COMPRESSION] = $compression; 199 $this->options[Memcached::OPT_SERIALIZER] = $serialiser; 200 $this->options[Memcached::OPT_PREFIX_KEY] = $this->prefix = (string)$prefix; 201 $this->options[Memcached::OPT_HASH] = $hashmethod; 202 $this->options[Memcached::OPT_BUFFER_WRITES] = $bufferwrites; 203 204 $this->connection = new Memcached(crc32($this->name)); 205 $servers = $this->connection->getServerList(); 206 if (empty($servers)) { 207 foreach ($this->options as $key => $value) { 208 $this->connection->setOption($key, $value); 209 } 210 $this->connection->addServers($this->servers); 211 } 212 213 if ($this->clustered) { 214 foreach ($this->setservers as $setserver) { 215 // Since we will have a number of them with the same name, append server and port. 216 $connection = new Memcached(crc32($this->name.$setserver[0].$setserver[1])); 217 foreach ($this->options as $key => $value) { 218 $connection->setOption($key, $value); 219 } 220 $connection->addServer($setserver[0], $setserver[1]); 221 $this->setconnections[] = $connection; 222 } 223 } 224 225 if (isset($configuration['isshared'])) { 226 $this->isshared = $configuration['isshared']; 227 } 228 229 $version = phpversion('memcached'); 230 $this->candeletemulti = ($version && version_compare($version, self::REQUIRED_VERSION, '>=')); 231 232 $this->isready = $this->is_connection_ready(); 233 } 234 235 /** 236 * Confirm whether the connection is ready and usable. 237 * 238 * @return boolean 239 */ 240 public function is_connection_ready() { 241 if (!@$this->connection->set("ping", 'ping', 1)) { 242 // Test the connection to the server. 243 return false; 244 } 245 246 if ($this->isshared) { 247 // There is a bug in libmemcached which means that it is not possible to purge the cache in a shared cache 248 // configuration. 249 // This issue currently affects: 250 // - memcached 1.4.23+ with php-memcached <= 2.2.0 251 // The following combinations are not affected: 252 // - memcached <= 1.4.22 with any version of php-memcached 253 // - any version of memcached with php-memcached >= 3.0.1 254 255 256 // This check is cheapest as it does not involve connecting to the server at all. 257 $safecombination = false; 258 $extension = new ReflectionExtension('memcached'); 259 if ((version_compare($extension->getVersion(), '3.0.1') >= 0)) { 260 // This is php-memcached version >= 3.0.1 which is a safe combination. 261 $safecombination = true; 262 } 263 264 if (!$safecombination) { 265 $allsafe = true; 266 foreach ($this->connection->getVersion() as $version) { 267 $allsafe = $allsafe && (version_compare($version, '1.4.22') <= 0); 268 } 269 // All memcached servers connected are version <= 1.4.22 which is a safe combination. 270 $safecombination = $allsafe; 271 } 272 273 if (!$safecombination) { 274 // This is memcached 1.4.23+ and php-memcached < 3.0.1. 275 // The issue may have been resolved in a subsequent update to any of the three libraries. 276 // The only way to safely determine if the combination is safe is to call getAllKeys. 277 // A safe combination will return an array, whilst an affected combination will return false. 278 // This is the most expensive check. 279 if (!is_array($this->connection->getAllKeys())) { 280 return false; 281 } 282 } 283 } 284 285 return true; 286 } 287 288 /** 289 * Initialises the cache. 290 * 291 * Once this has been done the cache is all set to be used. 292 * 293 * @throws coding_exception if the instance has already been initialised. 294 * @param cache_definition $definition 295 */ 296 public function initialise(cache_definition $definition) { 297 if ($this->is_initialised()) { 298 throw new coding_exception('This memcached instance has already been initialised.'); 299 } 300 $this->definition = $definition; 301 $this->isinitialised = true; 302 } 303 304 /** 305 * Returns true once this instance has been initialised. 306 * 307 * @return bool 308 */ 309 public function is_initialised() { 310 return ($this->isinitialised); 311 } 312 313 /** 314 * Returns true if this store instance is ready to be used. 315 * @return bool 316 */ 317 public function is_ready() { 318 return $this->isready; 319 } 320 321 /** 322 * Returns true if the store requirements are met. 323 * 324 * @return bool 325 */ 326 public static function are_requirements_met() { 327 return extension_loaded('memcached') && class_exists('Memcached'); 328 } 329 330 /** 331 * Returns true if the given mode is supported by this store. 332 * 333 * @param int $mode One of cache_store::MODE_* 334 * @return bool 335 */ 336 public static function is_supported_mode($mode) { 337 return ($mode === self::MODE_APPLICATION || $mode === self::MODE_SESSION); 338 } 339 340 /** 341 * Returns the supported features as a combined int. 342 * 343 * @param array $configuration 344 * @return int 345 */ 346 public static function get_supported_features(array $configuration = array()) { 347 return self::SUPPORTS_NATIVE_TTL + self::DEREFERENCES_OBJECTS; 348 } 349 350 /** 351 * Returns false as this store does not support multiple identifiers. 352 * (This optional function is a performance optimisation; it must be 353 * consistent with the value from get_supported_features.) 354 * 355 * @return bool False 356 */ 357 public function supports_multiple_identifiers() { 358 return false; 359 } 360 361 /** 362 * Returns the supported modes as a combined int. 363 * 364 * @param array $configuration 365 * @return int 366 */ 367 public static function get_supported_modes(array $configuration = array()) { 368 return self::MODE_APPLICATION; 369 } 370 371 /** 372 * Retrieves an item from the cache store given its key. 373 * 374 * @param string $key The key to retrieve 375 * @return mixed The data that was associated with the key, or false if the key did not exist. 376 */ 377 public function get($key) { 378 return $this->connection->get($key); 379 } 380 381 /** 382 * Retrieves several items from the cache store in a single transaction. 383 * 384 * 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. 385 * 386 * @param array $keys The array of keys to retrieve 387 * @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 388 * be set to false. 389 */ 390 public function get_many($keys) { 391 $return = array(); 392 $result = $this->connection->getMulti($keys); 393 if (!is_array($result)) { 394 $result = array(); 395 } 396 foreach ($keys as $key) { 397 if (!array_key_exists($key, $result)) { 398 $return[$key] = false; 399 } else { 400 $return[$key] = $result[$key]; 401 } 402 } 403 return $return; 404 } 405 406 /** 407 * Sets an item in the cache given its key and data value. 408 * 409 * @param string $key The key to use. 410 * @param mixed $data The data to set. 411 * @return bool True if the operation was a success false otherwise. 412 */ 413 public function set($key, $data) { 414 if ($this->clustered) { 415 $status = true; 416 foreach ($this->setconnections as $connection) { 417 $status = $connection->set($key, $data, $this->definition->get_ttl()) && $status; 418 } 419 return $status; 420 } 421 422 return $this->connection->set($key, $data, $this->definition->get_ttl()); 423 } 424 425 /** 426 * Sets many items in the cache in a single transaction. 427 * 428 * @param array $keyvaluearray An array of key value pairs. Each item in the array will be an associative array with two 429 * keys, 'key' and 'value'. 430 * @return int The number of items successfully set. It is up to the developer to check this matches the number of items 431 * sent ... if they care that is. 432 */ 433 public function set_many(array $keyvaluearray) { 434 $pairs = array(); 435 foreach ($keyvaluearray as $pair) { 436 $pairs[$pair['key']] = $pair['value']; 437 } 438 439 $status = true; 440 if ($this->clustered) { 441 foreach ($this->setconnections as $connection) { 442 $status = $connection->setMulti($pairs, $this->definition->get_ttl()) && $status; 443 } 444 } else { 445 $status = $this->connection->setMulti($pairs, $this->definition->get_ttl()); 446 } 447 448 if ($status) { 449 return count($keyvaluearray); 450 } 451 return 0; 452 } 453 454 /** 455 * Deletes an item from the cache store. 456 * 457 * @param string $key The key to delete. 458 * @return bool Returns true if the operation was a success, false otherwise. 459 */ 460 public function delete($key) { 461 if ($this->clustered) { 462 $status = true; 463 foreach ($this->setconnections as $connection) { 464 $status = $connection->delete($key) && $status; 465 } 466 return $status; 467 } 468 469 return $this->connection->delete($key); 470 } 471 472 /** 473 * Deletes several keys from the cache in a single action. 474 * 475 * @param array $keys The keys to delete 476 * @return int The number of items successfully deleted. 477 */ 478 public function delete_many(array $keys) { 479 if ($this->clustered) { 480 // Get the minimum deleted from any of the connections. 481 $count = count($keys); 482 foreach ($this->setconnections as $connection) { 483 $count = min($this->delete_many_connection($connection, $keys), $count); 484 } 485 return $count; 486 } 487 488 return $this->delete_many_connection($this->connection, $keys); 489 } 490 491 /** 492 * Deletes several keys from the cache in a single action for a specific connection. 493 * 494 * @param Memcached $connection The connection to work on. 495 * @param array $keys The keys to delete 496 * @return int The number of items successfully deleted. 497 */ 498 protected function delete_many_connection(Memcached $connection, array $keys) { 499 $count = 0; 500 if ($this->candeletemulti) { 501 // We can use deleteMulti, this is a bit faster yay! 502 $result = $connection->deleteMulti($keys); 503 foreach ($result as $key => $outcome) { 504 if ($outcome === true) { 505 $count++; 506 } 507 } 508 return $count; 509 } 510 511 // They are running an older version of the php memcached extension. 512 foreach ($keys as $key) { 513 if ($connection->delete($key)) { 514 $count++; 515 } 516 } 517 return $count; 518 } 519 520 /** 521 * Purges the cache deleting all items within it. 522 * 523 * @return boolean True on success. False otherwise. 524 */ 525 public function purge() { 526 if ($this->isready) { 527 // Only use delete multi if we have the correct extension installed and if the memcached 528 // server is shared (flushing the cache is quicker otherwise). 529 $candeletemulti = ($this->candeletemulti && $this->isshared); 530 531 if ($this->clustered) { 532 foreach ($this->setconnections as $connection) { 533 if ($candeletemulti) { 534 $keys = self::get_prefixed_keys($connection, $this->prefix); 535 $connection->deleteMulti($keys); 536 } else { 537 // Oh damn, this isn't multi-site safe. 538 $connection->flush(); 539 } 540 } 541 } else if ($candeletemulti) { 542 $keys = self::get_prefixed_keys($this->connection, $this->prefix); 543 $this->connection->deleteMulti($keys); 544 } else { 545 // Oh damn, this isn't multi-site safe. 546 $this->connection->flush(); 547 } 548 } 549 // It never fails. Ever. 550 return true; 551 } 552 553 /** 554 * Returns all of the keys in the given connection that belong to this cache store instance. 555 * 556 * Requires php memcached extension version 2.0.0 or greater. 557 * 558 * @param Memcached $connection 559 * @param string $prefix 560 * @return array 561 */ 562 protected static function get_prefixed_keys(Memcached $connection, $prefix) { 563 $connkeys = $connection->getAllKeys(); 564 if (empty($connkeys)) { 565 return array(); 566 } 567 568 $keys = array(); 569 $start = strlen($prefix); 570 foreach ($connkeys as $key) { 571 if (strpos($key, $prefix) === 0) { 572 $keys[] = substr($key, $start); 573 } 574 } 575 return $keys; 576 } 577 578 /** 579 * Gets an array of options to use as the serialiser. 580 * @return array 581 */ 582 public static function config_get_serialiser_options() { 583 $options = array( 584 Memcached::SERIALIZER_PHP => get_string('serialiser_php', 'cachestore_memcached') 585 ); 586 if (Memcached::HAVE_JSON) { 587 $options[Memcached::SERIALIZER_JSON] = get_string('serialiser_json', 'cachestore_memcached'); 588 } 589 if (Memcached::HAVE_IGBINARY) { 590 $options[Memcached::SERIALIZER_IGBINARY] = get_string('serialiser_igbinary', 'cachestore_memcached'); 591 } 592 return $options; 593 } 594 595 /** 596 * Gets an array of hash options available during configuration. 597 * @return array 598 */ 599 public static function config_get_hash_options() { 600 $options = array( 601 Memcached::HASH_DEFAULT => get_string('hash_default', 'cachestore_memcached'), 602 Memcached::HASH_MD5 => get_string('hash_md5', 'cachestore_memcached'), 603 Memcached::HASH_CRC => get_string('hash_crc', 'cachestore_memcached'), 604 Memcached::HASH_FNV1_64 => get_string('hash_fnv1_64', 'cachestore_memcached'), 605 Memcached::HASH_FNV1A_64 => get_string('hash_fnv1a_64', 'cachestore_memcached'), 606 Memcached::HASH_FNV1_32 => get_string('hash_fnv1_32', 'cachestore_memcached'), 607 Memcached::HASH_FNV1A_32 => get_string('hash_fnv1a_32', 'cachestore_memcached'), 608 Memcached::HASH_HSIEH => get_string('hash_hsieh', 'cachestore_memcached'), 609 Memcached::HASH_MURMUR => get_string('hash_murmur', 'cachestore_memcached'), 610 ); 611 return $options; 612 } 613 614 /** 615 * Given the data from the add instance form this function creates a configuration array. 616 * 617 * @param stdClass $data 618 * @return array 619 */ 620 public static function config_get_configuration_array($data) { 621 $lines = explode("\n", $data->servers); 622 $servers = array(); 623 foreach ($lines as $line) { 624 // Trim surrounding colons and default whitespace. 625 $line = trim(trim($line), ":"); 626 // Skip blank lines. 627 if ($line === '') { 628 continue; 629 } 630 $servers[] = explode(':', $line, 3); 631 } 632 633 $clustered = false; 634 $setservers = array(); 635 if (isset($data->clustered)) { 636 $clustered = true; 637 638 $lines = explode("\n", $data->setservers); 639 foreach ($lines as $line) { 640 // Trim surrounding colons and default whitespace. 641 $line = trim(trim($line), ":"); 642 if ($line === '') { 643 continue; 644 } 645 $setserver = explode(':', $line, 3); 646 // We don't use weights, so display a debug message. 647 if (count($setserver) > 2) { 648 debugging('Memcached Set Server '.$setserver[0].' has too many parameters.'); 649 } 650 $setservers[] = $setserver; 651 } 652 } 653 654 $isshared = false; 655 if (isset($data->isshared)) { 656 $isshared = $data->isshared; 657 } 658 659 return array( 660 'servers' => $servers, 661 'compression' => $data->compression, 662 'serialiser' => $data->serialiser, 663 'prefix' => $data->prefix, 664 'hash' => $data->hash, 665 'bufferwrites' => $data->bufferwrites, 666 'clustered' => $clustered, 667 'setservers' => $setservers, 668 'isshared' => $isshared 669 ); 670 } 671 672 /** 673 * Allows the cache store to set its data against the edit form before it is shown to the user. 674 * 675 * @param moodleform $editform 676 * @param array $config 677 */ 678 public static function config_set_edit_form_data(moodleform $editform, array $config) { 679 $data = array(); 680 if (!empty($config['servers'])) { 681 $servers = array(); 682 foreach ($config['servers'] as $server) { 683 $servers[] = join(":", $server); 684 } 685 $data['servers'] = join("\n", $servers); 686 } 687 if (isset($config['compression'])) { 688 $data['compression'] = (bool)$config['compression']; 689 } 690 if (!empty($config['serialiser'])) { 691 $data['serialiser'] = $config['serialiser']; 692 } 693 if (!empty($config['prefix'])) { 694 $data['prefix'] = $config['prefix']; 695 } 696 if (!empty($config['hash'])) { 697 $data['hash'] = $config['hash']; 698 } 699 if (isset($config['bufferwrites'])) { 700 $data['bufferwrites'] = (bool)$config['bufferwrites']; 701 } 702 if (isset($config['clustered'])) { 703 $data['clustered'] = (bool)$config['clustered']; 704 } 705 if (!empty($config['setservers'])) { 706 $servers = array(); 707 foreach ($config['setservers'] as $server) { 708 $servers[] = join(":", $server); 709 } 710 $data['setservers'] = join("\n", $servers); 711 } 712 if (isset($config['isshared'])) { 713 $data['isshared'] = $config['isshared']; 714 } 715 $editform->set_data($data); 716 } 717 718 /** 719 * Performs any necessary clean up when the store instance is being deleted. 720 */ 721 public function instance_deleted() { 722 if ($this->connection) { 723 $connection = $this->connection; 724 } else { 725 $connection = new Memcached(crc32($this->name)); 726 $servers = $connection->getServerList(); 727 if (empty($servers)) { 728 foreach ($this->options as $key => $value) { 729 $connection->setOption($key, $value); 730 } 731 $connection->addServers($this->servers); 732 } 733 } 734 // We have to flush here to be sure we are completely cleaned up. 735 // Bad for performance but this is incredibly rare. 736 @$connection->flush(); 737 unset($connection); 738 unset($this->connection); 739 } 740 741 /** 742 * Generates an instance of the cache store that can be used for testing. 743 * 744 * @param cache_definition $definition 745 * @return cachestore_memcached|false 746 */ 747 public static function initialise_test_instance(cache_definition $definition) { 748 749 if (!self::are_requirements_met()) { 750 return false; 751 } 752 753 $config = get_config('cachestore_memcached'); 754 if (empty($config->testservers)) { 755 return false; 756 } 757 758 $configuration = array(); 759 $configuration['servers'] = explode("\n", $config->testservers); 760 if (!empty($config->testcompression)) { 761 $configuration['compression'] = $config->testcompression; 762 } 763 if (!empty($config->testserialiser)) { 764 $configuration['serialiser'] = $config->testserialiser; 765 } 766 if (!empty($config->testprefix)) { 767 $configuration['prefix'] = $config->testprefix; 768 } 769 if (!empty($config->testhash)) { 770 $configuration['hash'] = $config->testhash; 771 } 772 if (!empty($config->testbufferwrites)) { 773 $configuration['bufferwrites'] = $config->testbufferwrites; 774 } 775 if (!empty($config->testclustered)) { 776 $configuration['clustered'] = $config->testclustered; 777 } 778 if (!empty($config->testsetservers)) { 779 $configuration['setservers'] = explode("\n", $config->testsetservers); 780 } 781 if (!empty($config->testname)) { 782 $name = $config->testname; 783 } else { 784 $name = 'Test memcached'; 785 } 786 787 $store = new cachestore_memcached($name, $configuration); 788 // If store is ready then only initialise. 789 if ($store->is_ready()) { 790 $store->initialise($definition); 791 } 792 793 return $store; 794 } 795 796 /** 797 * Generates the appropriate configuration required for unit testing. 798 * 799 * @return array Array of unit test configuration data to be used by initialise(). 800 */ 801 public static function unit_test_configuration() { 802 // If the configuration is not defined correctly, return only the configuration know about. 803 if (!defined('TEST_CACHESTORE_MEMCACHED_TESTSERVERS')) { 804 return []; 805 } 806 return ['servers' => explode("\n", TEST_CACHESTORE_MEMCACHED_TESTSERVERS)]; 807 } 808 809 /** 810 * Returns the name of this instance. 811 * @return string 812 */ 813 public function my_name() { 814 return $this->name; 815 } 816 817 /** 818 * Used to notify of configuration conflicts. 819 * 820 * The warnings returned here will be displayed on the cache configuration screen. 821 * 822 * @return string[] Returns an array of warnings (strings) 823 */ 824 public function get_warnings() { 825 global $CFG; 826 $warnings = array(); 827 if (isset($CFG->session_memcached_save_path) && count($this->servers)) { 828 $bits = explode(':', $CFG->session_memcached_save_path, 3); 829 $host = array_shift($bits); 830 $port = (count($bits)) ? array_shift($bits) : '11211'; 831 832 foreach ($this->servers as $server) { 833 if ((string)$server[0] === $host && (string)$server[1] === $port) { 834 $warnings[] = get_string('sessionhandlerconflict', 'cachestore_memcached', $this->my_name()); 835 break; 836 } 837 } 838 } 839 return $warnings; 840 } 841 842 /** 843 * Returns true if this cache store instance is both suitable for testing, and ready for testing. 844 * 845 * Cache stores that support being used as the default store for unit and acceptance testing should 846 * override this function and return true if there requirements have been met. 847 * 848 * @return bool 849 */ 850 public static function ready_to_be_used_for_testing() { 851 return defined('TEST_CACHESTORE_MEMCACHED_TESTSERVERS'); 852 } 853 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body