[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
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 // Test the connection to the main connection. 233 $this->isready = @$this->connection->set("ping", 'ping', 1); 234 } 235 236 /** 237 * Initialises the cache. 238 * 239 * Once this has been done the cache is all set to be used. 240 * 241 * @throws coding_exception if the instance has already been initialised. 242 * @param cache_definition $definition 243 */ 244 public function initialise(cache_definition $definition) { 245 if ($this->is_initialised()) { 246 throw new coding_exception('This memcached instance has already been initialised.'); 247 } 248 $this->definition = $definition; 249 $this->isinitialised = true; 250 } 251 252 /** 253 * Returns true once this instance has been initialised. 254 * 255 * @return bool 256 */ 257 public function is_initialised() { 258 return ($this->isinitialised); 259 } 260 261 /** 262 * Returns true if this store instance is ready to be used. 263 * @return bool 264 */ 265 public function is_ready() { 266 return $this->isready; 267 } 268 269 /** 270 * Returns true if the store requirements are met. 271 * 272 * @return bool 273 */ 274 public static function are_requirements_met() { 275 return extension_loaded('memcached') && class_exists('Memcached'); 276 } 277 278 /** 279 * Returns true if the given mode is supported by this store. 280 * 281 * @param int $mode One of cache_store::MODE_* 282 * @return bool 283 */ 284 public static function is_supported_mode($mode) { 285 return ($mode === self::MODE_APPLICATION || $mode === self::MODE_SESSION); 286 } 287 288 /** 289 * Returns the supported features as a combined int. 290 * 291 * @param array $configuration 292 * @return int 293 */ 294 public static function get_supported_features(array $configuration = array()) { 295 return self::SUPPORTS_NATIVE_TTL + self::DEREFERENCES_OBJECTS; 296 } 297 298 /** 299 * Returns false as this store does not support multiple identifiers. 300 * (This optional function is a performance optimisation; it must be 301 * consistent with the value from get_supported_features.) 302 * 303 * @return bool False 304 */ 305 public function supports_multiple_identifiers() { 306 return false; 307 } 308 309 /** 310 * Returns the supported modes as a combined int. 311 * 312 * @param array $configuration 313 * @return int 314 */ 315 public static function get_supported_modes(array $configuration = array()) { 316 return self::MODE_APPLICATION; 317 } 318 319 /** 320 * Retrieves an item from the cache store given its key. 321 * 322 * @param string $key The key to retrieve 323 * @return mixed The data that was associated with the key, or false if the key did not exist. 324 */ 325 public function get($key) { 326 return $this->connection->get($key); 327 } 328 329 /** 330 * Retrieves several items from the cache store in a single transaction. 331 * 332 * 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. 333 * 334 * @param array $keys The array of keys to retrieve 335 * @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 336 * be set to false. 337 */ 338 public function get_many($keys) { 339 $return = array(); 340 $result = $this->connection->getMulti($keys); 341 if (!is_array($result)) { 342 $result = array(); 343 } 344 foreach ($keys as $key) { 345 if (!array_key_exists($key, $result)) { 346 $return[$key] = false; 347 } else { 348 $return[$key] = $result[$key]; 349 } 350 } 351 return $return; 352 } 353 354 /** 355 * Sets an item in the cache given its key and data value. 356 * 357 * @param string $key The key to use. 358 * @param mixed $data The data to set. 359 * @return bool True if the operation was a success false otherwise. 360 */ 361 public function set($key, $data) { 362 if ($this->clustered) { 363 $status = true; 364 foreach ($this->setconnections as $connection) { 365 $status = $connection->set($key, $data, $this->definition->get_ttl()) && $status; 366 } 367 return $status; 368 } 369 370 return $this->connection->set($key, $data, $this->definition->get_ttl()); 371 } 372 373 /** 374 * Sets many items in the cache in a single transaction. 375 * 376 * @param array $keyvaluearray An array of key value pairs. Each item in the array will be an associative array with two 377 * keys, 'key' and 'value'. 378 * @return int The number of items successfully set. It is up to the developer to check this matches the number of items 379 * sent ... if they care that is. 380 */ 381 public function set_many(array $keyvaluearray) { 382 $pairs = array(); 383 foreach ($keyvaluearray as $pair) { 384 $pairs[$pair['key']] = $pair['value']; 385 } 386 387 $status = true; 388 if ($this->clustered) { 389 foreach ($this->setconnections as $connection) { 390 $status = $connection->setMulti($pairs, $this->definition->get_ttl()) && $status; 391 } 392 } else { 393 $status = $this->connection->setMulti($pairs, $this->definition->get_ttl()); 394 } 395 396 if ($status) { 397 return count($keyvaluearray); 398 } 399 return 0; 400 } 401 402 /** 403 * Deletes an item from the cache store. 404 * 405 * @param string $key The key to delete. 406 * @return bool Returns true if the operation was a success, false otherwise. 407 */ 408 public function delete($key) { 409 if ($this->clustered) { 410 $status = true; 411 foreach ($this->setconnections as $connection) { 412 $status = $connection->delete($key) && $status; 413 } 414 return $status; 415 } 416 417 return $this->connection->delete($key); 418 } 419 420 /** 421 * Deletes several keys from the cache in a single action. 422 * 423 * @param array $keys The keys to delete 424 * @return int The number of items successfully deleted. 425 */ 426 public function delete_many(array $keys) { 427 if ($this->clustered) { 428 // Get the minimum deleted from any of the connections. 429 $count = count($keys); 430 foreach ($this->setconnections as $connection) { 431 $count = min($this->delete_many_connection($connection, $keys), $count); 432 } 433 return $count; 434 } 435 436 return $this->delete_many_connection($this->connection, $keys); 437 } 438 439 /** 440 * Deletes several keys from the cache in a single action for a specific connection. 441 * 442 * @param Memcached $connection The connection to work on. 443 * @param array $keys The keys to delete 444 * @return int The number of items successfully deleted. 445 */ 446 protected function delete_many_connection(Memcached $connection, array $keys) { 447 $count = 0; 448 if ($this->candeletemulti) { 449 // We can use deleteMulti, this is a bit faster yay! 450 $result = $connection->deleteMulti($keys); 451 foreach ($result as $key => $outcome) { 452 if ($outcome === true) { 453 $count++; 454 } 455 } 456 return $count; 457 } 458 459 // They are running an older version of the php memcached extension. 460 foreach ($keys as $key) { 461 if ($connection->delete($key)) { 462 $count++; 463 } 464 } 465 return $count; 466 } 467 468 /** 469 * Purges the cache deleting all items within it. 470 * 471 * @return boolean True on success. False otherwise. 472 */ 473 public function purge() { 474 if ($this->isready) { 475 // Only use delete multi if we have the correct extension installed and if the memcached 476 // server is shared (flushing the cache is quicker otherwise). 477 $candeletemulti = ($this->candeletemulti && $this->isshared); 478 479 if ($this->clustered) { 480 foreach ($this->setconnections as $connection) { 481 if ($candeletemulti) { 482 $keys = self::get_prefixed_keys($connection, $this->prefix); 483 $connection->deleteMulti($keys); 484 } else { 485 // Oh damn, this isn't multi-site safe. 486 $connection->flush(); 487 } 488 } 489 } else if ($candeletemulti) { 490 $keys = self::get_prefixed_keys($this->connection, $this->prefix); 491 $this->connection->deleteMulti($keys); 492 } else { 493 // Oh damn, this isn't multi-site safe. 494 $this->connection->flush(); 495 } 496 } 497 // It never fails. Ever. 498 return true; 499 } 500 501 /** 502 * Returns all of the keys in the given connection that belong to this cache store instance. 503 * 504 * Requires php memcached extension version 2.0.0 or greater. 505 * 506 * @param Memcached $connection 507 * @param string $prefix 508 * @return array 509 */ 510 protected static function get_prefixed_keys(Memcached $connection, $prefix) { 511 $connkeys = $connection->getAllKeys(); 512 if (empty($connkeys)) { 513 return array(); 514 } 515 516 $keys = array(); 517 $start = strlen($prefix); 518 foreach ($connkeys as $key) { 519 if (strpos($key, $prefix) === 0) { 520 $keys[] = substr($key, $start); 521 } 522 } 523 return $keys; 524 } 525 526 /** 527 * Gets an array of options to use as the serialiser. 528 * @return array 529 */ 530 public static function config_get_serialiser_options() { 531 $options = array( 532 Memcached::SERIALIZER_PHP => get_string('serialiser_php', 'cachestore_memcached') 533 ); 534 if (Memcached::HAVE_JSON) { 535 $options[Memcached::SERIALIZER_JSON] = get_string('serialiser_json', 'cachestore_memcached'); 536 } 537 if (Memcached::HAVE_IGBINARY) { 538 $options[Memcached::SERIALIZER_IGBINARY] = get_string('serialiser_igbinary', 'cachestore_memcached'); 539 } 540 return $options; 541 } 542 543 /** 544 * Gets an array of hash options available during configuration. 545 * @return array 546 */ 547 public static function config_get_hash_options() { 548 $options = array( 549 Memcached::HASH_DEFAULT => get_string('hash_default', 'cachestore_memcached'), 550 Memcached::HASH_MD5 => get_string('hash_md5', 'cachestore_memcached'), 551 Memcached::HASH_CRC => get_string('hash_crc', 'cachestore_memcached'), 552 Memcached::HASH_FNV1_64 => get_string('hash_fnv1_64', 'cachestore_memcached'), 553 Memcached::HASH_FNV1A_64 => get_string('hash_fnv1a_64', 'cachestore_memcached'), 554 Memcached::HASH_FNV1_32 => get_string('hash_fnv1_32', 'cachestore_memcached'), 555 Memcached::HASH_FNV1A_32 => get_string('hash_fnv1a_32', 'cachestore_memcached'), 556 Memcached::HASH_HSIEH => get_string('hash_hsieh', 'cachestore_memcached'), 557 Memcached::HASH_MURMUR => get_string('hash_murmur', 'cachestore_memcached'), 558 ); 559 return $options; 560 } 561 562 /** 563 * Given the data from the add instance form this function creates a configuration array. 564 * 565 * @param stdClass $data 566 * @return array 567 */ 568 public static function config_get_configuration_array($data) { 569 $lines = explode("\n", $data->servers); 570 $servers = array(); 571 foreach ($lines as $line) { 572 // Trim surrounding colons and default whitespace. 573 $line = trim(trim($line), ":"); 574 // Skip blank lines. 575 if ($line === '') { 576 continue; 577 } 578 $servers[] = explode(':', $line, 3); 579 } 580 581 $clustered = false; 582 $setservers = array(); 583 if (isset($data->clustered)) { 584 $clustered = true; 585 586 $lines = explode("\n", $data->setservers); 587 foreach ($lines as $line) { 588 // Trim surrounding colons and default whitespace. 589 $line = trim(trim($line), ":"); 590 if ($line === '') { 591 continue; 592 } 593 $setserver = explode(':', $line, 3); 594 // We don't use weights, so display a debug message. 595 if (count($setserver) > 2) { 596 debugging('Memcached Set Server '.$setserver[0].' has too many parameters.'); 597 } 598 $setservers[] = $setserver; 599 } 600 } 601 602 $isshared = false; 603 if (isset($data->isshared)) { 604 $isshared = $data->isshared; 605 } 606 607 return array( 608 'servers' => $servers, 609 'compression' => $data->compression, 610 'serialiser' => $data->serialiser, 611 'prefix' => $data->prefix, 612 'hash' => $data->hash, 613 'bufferwrites' => $data->bufferwrites, 614 'clustered' => $clustered, 615 'setservers' => $setservers, 616 'isshared' => $isshared 617 ); 618 } 619 620 /** 621 * Allows the cache store to set its data against the edit form before it is shown to the user. 622 * 623 * @param moodleform $editform 624 * @param array $config 625 */ 626 public static function config_set_edit_form_data(moodleform $editform, array $config) { 627 $data = array(); 628 if (!empty($config['servers'])) { 629 $servers = array(); 630 foreach ($config['servers'] as $server) { 631 $servers[] = join(":", $server); 632 } 633 $data['servers'] = join("\n", $servers); 634 } 635 if (isset($config['compression'])) { 636 $data['compression'] = (bool)$config['compression']; 637 } 638 if (!empty($config['serialiser'])) { 639 $data['serialiser'] = $config['serialiser']; 640 } 641 if (!empty($config['prefix'])) { 642 $data['prefix'] = $config['prefix']; 643 } 644 if (!empty($config['hash'])) { 645 $data['hash'] = $config['hash']; 646 } 647 if (isset($config['bufferwrites'])) { 648 $data['bufferwrites'] = (bool)$config['bufferwrites']; 649 } 650 if (isset($config['clustered'])) { 651 $data['clustered'] = (bool)$config['clustered']; 652 } 653 if (!empty($config['setservers'])) { 654 $servers = array(); 655 foreach ($config['setservers'] as $server) { 656 $servers[] = join(":", $server); 657 } 658 $data['setservers'] = join("\n", $servers); 659 } 660 if (isset($config['isshared'])) { 661 $data['isshared'] = $config['isshared']; 662 } 663 $editform->set_data($data); 664 } 665 666 /** 667 * Performs any necessary clean up when the store instance is being deleted. 668 */ 669 public function instance_deleted() { 670 if ($this->connection) { 671 $connection = $this->connection; 672 } else { 673 $connection = new Memcached(crc32($this->name)); 674 $servers = $connection->getServerList(); 675 if (empty($servers)) { 676 foreach ($this->options as $key => $value) { 677 $connection->setOption($key, $value); 678 } 679 $connection->addServers($this->servers); 680 } 681 } 682 // We have to flush here to be sure we are completely cleaned up. 683 // Bad for performance but this is incredibly rare. 684 @$connection->flush(); 685 unset($connection); 686 unset($this->connection); 687 } 688 689 /** 690 * Generates an instance of the cache store that can be used for testing. 691 * 692 * @param cache_definition $definition 693 * @return cachestore_memcached|false 694 */ 695 public static function initialise_test_instance(cache_definition $definition) { 696 697 if (!self::are_requirements_met()) { 698 return false; 699 } 700 701 $config = get_config('cachestore_memcached'); 702 if (empty($config->testservers)) { 703 return false; 704 } 705 706 $configuration = array(); 707 $configuration['servers'] = explode("\n", $config->testservers); 708 if (!empty($config->testcompression)) { 709 $configuration['compression'] = $config->testcompression; 710 } 711 if (!empty($config->testserialiser)) { 712 $configuration['serialiser'] = $config->testserialiser; 713 } 714 if (!empty($config->testprefix)) { 715 $configuration['prefix'] = $config->testprefix; 716 } 717 if (!empty($config->testhash)) { 718 $configuration['hash'] = $config->testhash; 719 } 720 if (!empty($config->testbufferwrites)) { 721 $configuration['bufferwrites'] = $config->testbufferwrites; 722 } 723 if (!empty($config->testclustered)) { 724 $configuration['clustered'] = $config->testclustered; 725 } 726 if (!empty($config->testsetservers)) { 727 $configuration['setservers'] = explode("\n", $config->testsetservers); 728 } 729 if (!empty($config->testname)) { 730 $name = $config->testname; 731 } else { 732 $name = 'Test memcached'; 733 } 734 735 $store = new cachestore_memcached($name, $configuration); 736 $store->initialise($definition); 737 738 return $store; 739 } 740 741 /** 742 * Creates a test instance for unit tests if possible. 743 * @param cache_definition $definition 744 * @return bool|cachestore_memcached 745 */ 746 public static function initialise_unit_test_instance(cache_definition $definition) { 747 if (!self::are_requirements_met()) { 748 return false; 749 } 750 if (!defined('TEST_CACHESTORE_MEMCACHED_TESTSERVERS')) { 751 return false; 752 } 753 754 $configuration = array(); 755 $configuration['servers'] = explode("\n", TEST_CACHESTORE_MEMCACHED_TESTSERVERS); 756 757 $store = new cachestore_memcached('Test memcached', $configuration); 758 $store->initialise($definition); 759 760 return $store; 761 } 762 763 /** 764 * Returns the name of this instance. 765 * @return string 766 */ 767 public function my_name() { 768 return $this->name; 769 } 770 771 /** 772 * Used to notify of configuration conflicts. 773 * 774 * The warnings returned here will be displayed on the cache configuration screen. 775 * 776 * @return string[] Returns an array of warnings (strings) 777 */ 778 public function get_warnings() { 779 global $CFG; 780 $warnings = array(); 781 if (isset($CFG->session_memcached_save_path) && count($this->servers)) { 782 $bits = explode(':', $CFG->session_memcached_save_path, 3); 783 $host = array_shift($bits); 784 $port = (count($bits)) ? array_shift($bits) : '11211'; 785 786 foreach ($this->servers as $server) { 787 if ((string)$server[0] === $host && (string)$server[1] === $port) { 788 $warnings[] = get_string('sessionhandlerconflict', 'cachestore_memcached', $this->my_name()); 789 break; 790 } 791 } 792 } 793 return $warnings; 794 } 795 796 /** 797 * Returns true if this cache store instance is both suitable for testing, and ready for testing. 798 * 799 * Cache stores that support being used as the default store for unit and acceptance testing should 800 * override this function and return true if there requirements have been met. 801 * 802 * @return bool 803 */ 804 public static function ready_to_be_used_for_testing() { 805 return defined('TEST_CACHESTORE_MEMCACHED_TESTSERVERS'); 806 } 807 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |