[ Index ] |
PHP Cross Reference of moodle-2.8 |
[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 * The name of the store 49 * @var store 50 */ 51 protected $name; 52 53 /** 54 * The memcached connection 55 * @var Memcached 56 */ 57 protected $connection; 58 59 /** 60 * An array of servers to use during connection 61 * @var array 62 */ 63 protected $servers = array(); 64 65 /** 66 * The options used when establishing the connection 67 * @var array 68 */ 69 protected $options = array(); 70 71 /** 72 * True when this instance is ready to be initialised. 73 * @var bool 74 */ 75 protected $isready = false; 76 77 /** 78 * Set to true when this store instance has been initialised. 79 * @var bool 80 */ 81 protected $isinitialised = false; 82 83 /** 84 * The cache definition this store was initialised with. 85 * @var cache_definition 86 */ 87 protected $definition; 88 89 /** 90 * Set to true when this store is clustered. 91 * @var bool 92 */ 93 protected $clustered = false; 94 95 /** 96 * Array of servers to set when in clustered mode. 97 * @var array 98 */ 99 protected $setservers = array(); 100 101 /** 102 * The an array of memcache connections for the set servers, once established. 103 * @var array 104 */ 105 protected $setconnections = array(); 106 107 /** 108 * Constructs the store instance. 109 * 110 * Noting that this function is not an initialisation. It is used to prepare the store for use. 111 * The store will be initialised when required and will be provided with a cache_definition at that time. 112 * 113 * @param string $name 114 * @param array $configuration 115 */ 116 public function __construct($name, array $configuration = array()) { 117 $this->name = $name; 118 if (!array_key_exists('servers', $configuration) || empty($configuration['servers'])) { 119 // Nothing configured. 120 return; 121 } 122 if (!is_array($configuration['servers'])) { 123 $configuration['servers'] = array($configuration['servers']); 124 } 125 126 $compression = array_key_exists('compression', $configuration) ? (bool)$configuration['compression'] : true; 127 if (array_key_exists('serialiser', $configuration)) { 128 $serialiser = (int)$configuration['serialiser']; 129 } else { 130 $serialiser = Memcached::SERIALIZER_PHP; 131 } 132 $prefix = (!empty($configuration['prefix'])) ? (string)$configuration['prefix'] : crc32($name); 133 $hashmethod = (array_key_exists('hash', $configuration)) ? (int)$configuration['hash'] : Memcached::HASH_DEFAULT; 134 $bufferwrites = array_key_exists('bufferwrites', $configuration) ? (bool)$configuration['bufferwrites'] : false; 135 136 foreach ($configuration['servers'] as $server) { 137 if (!is_array($server)) { 138 $server = explode(':', $server, 3); 139 } 140 if (!array_key_exists(1, $server)) { 141 $server[1] = 11211; 142 $server[2] = 100; 143 } else if (!array_key_exists(2, $server)) { 144 $server[2] = 100; 145 } 146 $this->servers[] = $server; 147 } 148 149 $this->clustered = array_key_exists('clustered', $configuration) ? (bool)$configuration['clustered'] : false; 150 151 if ($this->clustered) { 152 if (!array_key_exists('setservers', $configuration) || (count($configuration['setservers']) < 1)) { 153 // Can't setup clustering without set servers. 154 return; 155 } 156 if (count($this->servers) !== 1) { 157 // Can only setup cluster with exactly 1 get server. 158 return; 159 } 160 foreach ($configuration['setservers'] as $server) { 161 // We do not use weights (3rd part) on these servers. 162 if (!is_array($server)) { 163 $server = explode(':', $server, 3); 164 } 165 if (!array_key_exists(1, $server)) { 166 $server[1] = 11211; 167 } 168 $this->setservers[] = $server; 169 } 170 } 171 172 $this->options[Memcached::OPT_COMPRESSION] = $compression; 173 $this->options[Memcached::OPT_SERIALIZER] = $serialiser; 174 $this->options[Memcached::OPT_PREFIX_KEY] = $prefix; 175 $this->options[Memcached::OPT_HASH] = $hashmethod; 176 $this->options[Memcached::OPT_BUFFER_WRITES] = $bufferwrites; 177 178 $this->connection = new Memcached(crc32($this->name)); 179 $servers = $this->connection->getServerList(); 180 if (empty($servers)) { 181 foreach ($this->options as $key => $value) { 182 $this->connection->setOption($key, $value); 183 } 184 $this->connection->addServers($this->servers); 185 } 186 187 if ($this->clustered) { 188 foreach ($this->setservers as $setserver) { 189 // Since we will have a number of them with the same name, append server and port. 190 $connection = new Memcached(crc32($this->name.$setserver[0].$setserver[1])); 191 foreach ($this->options as $key => $value) { 192 $connection->setOption($key, $value); 193 } 194 $connection->addServer($setserver[0], $setserver[1]); 195 $this->setconnections[] = $connection; 196 } 197 } 198 199 // Test the connection to the main connection. 200 $this->isready = @$this->connection->set("ping", 'ping', 1); 201 } 202 203 /** 204 * Initialises the cache. 205 * 206 * Once this has been done the cache is all set to be used. 207 * 208 * @param cache_definition $definition 209 */ 210 public function initialise(cache_definition $definition) { 211 if ($this->is_initialised()) { 212 throw new coding_exception('This memcached instance has already been initialised.'); 213 } 214 $this->definition = $definition; 215 $this->isinitialised = true; 216 } 217 218 /** 219 * Returns true once this instance has been initialised. 220 * 221 * @return bool 222 */ 223 public function is_initialised() { 224 return ($this->isinitialised); 225 } 226 227 /** 228 * Returns true if this store instance is ready to be used. 229 * @return bool 230 */ 231 public function is_ready() { 232 return $this->isready; 233 } 234 235 /** 236 * Returns true if the store requirements are met. 237 * 238 * @return bool 239 */ 240 public static function are_requirements_met() { 241 return class_exists('Memcached'); 242 } 243 244 /** 245 * Returns true if the given mode is supported by this store. 246 * 247 * @param int $mode One of cache_store::MODE_* 248 * @return bool 249 */ 250 public static function is_supported_mode($mode) { 251 return ($mode === self::MODE_APPLICATION || $mode === self::MODE_SESSION); 252 } 253 254 /** 255 * Returns the supported features as a combined int. 256 * 257 * @param array $configuration 258 * @return int 259 */ 260 public static function get_supported_features(array $configuration = array()) { 261 return self::SUPPORTS_NATIVE_TTL; 262 } 263 264 /** 265 * Returns false as this store does not support multiple identifiers. 266 * (This optional function is a performance optimisation; it must be 267 * consistent with the value from get_supported_features.) 268 * 269 * @return bool False 270 */ 271 public function supports_multiple_identifiers() { 272 return false; 273 } 274 275 /** 276 * Returns the supported modes as a combined int. 277 * 278 * @param array $configuration 279 * @return int 280 */ 281 public static function get_supported_modes(array $configuration = array()) { 282 return self::MODE_APPLICATION; 283 } 284 285 /** 286 * Retrieves an item from the cache store given its key. 287 * 288 * @param string $key The key to retrieve 289 * @return mixed The data that was associated with the key, or false if the key did not exist. 290 */ 291 public function get($key) { 292 return $this->connection->get($key); 293 } 294 295 /** 296 * Retrieves several items from the cache store in a single transaction. 297 * 298 * 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. 299 * 300 * @param array $keys The array of keys to retrieve 301 * @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 302 * be set to false. 303 */ 304 public function get_many($keys) { 305 $return = array(); 306 $result = $this->connection->getMulti($keys); 307 if (!is_array($result)) { 308 $result = array(); 309 } 310 foreach ($keys as $key) { 311 if (!array_key_exists($key, $result)) { 312 $return[$key] = false; 313 } else { 314 $return[$key] = $result[$key]; 315 } 316 } 317 return $return; 318 } 319 320 /** 321 * Sets an item in the cache given its key and data value. 322 * 323 * @param string $key The key to use. 324 * @param mixed $data The data to set. 325 * @return bool True if the operation was a success false otherwise. 326 */ 327 public function set($key, $data) { 328 if ($this->clustered) { 329 $status = true; 330 foreach ($this->setconnections as $connection) { 331 $status = $connection->set($key, $data, $this->definition->get_ttl()) && $status; 332 } 333 return $status; 334 } 335 336 return $this->connection->set($key, $data, $this->definition->get_ttl()); 337 } 338 339 /** 340 * Sets many items in the cache in a single transaction. 341 * 342 * @param array $keyvaluearray An array of key value pairs. Each item in the array will be an associative array with two 343 * keys, 'key' and 'value'. 344 * @return int The number of items successfully set. It is up to the developer to check this matches the number of items 345 * sent ... if they care that is. 346 */ 347 public function set_many(array $keyvaluearray) { 348 $pairs = array(); 349 foreach ($keyvaluearray as $pair) { 350 $pairs[$pair['key']] = $pair['value']; 351 } 352 353 $status = true; 354 if ($this->clustered) { 355 foreach ($this->setconnections as $connection) { 356 $status = $connection->setMulti($pairs, $this->definition->get_ttl()) && $status; 357 } 358 } else { 359 $status = $this->connection->setMulti($pairs, $this->definition->get_ttl()); 360 } 361 362 if ($status) { 363 return count($keyvaluearray); 364 } 365 return 0; 366 } 367 368 /** 369 * Deletes an item from the cache store. 370 * 371 * @param string $key The key to delete. 372 * @return bool Returns true if the operation was a success, false otherwise. 373 */ 374 public function delete($key) { 375 if ($this->clustered) { 376 $status = true; 377 foreach ($this->setconnections as $connection) { 378 $status = $connection->delete($key) && $status; 379 } 380 return $status; 381 } 382 383 return $this->connection->delete($key); 384 } 385 386 /** 387 * Deletes several keys from the cache in a single action. 388 * 389 * @param array $keys The keys to delete 390 * @return int The number of items successfully deleted. 391 */ 392 public function delete_many(array $keys) { 393 if ($this->clustered) { 394 // Get the minimum deleted from any of the connections. 395 $count = count($keys); 396 foreach ($this->setconnections as $connection) { 397 $count = min($this->delete_many_connection($connection, $keys), $count); 398 } 399 return $count; 400 } 401 402 return $this->delete_many_connection($this->connection, $keys); 403 } 404 405 /** 406 * Deletes several keys from the cache in a single action for a specific connection. 407 * 408 * @param Memcached $connection The connection to work on. 409 * @param array $keys The keys to delete 410 * @return int The number of items successfully deleted. 411 */ 412 protected function delete_many_connection(Memcached $connection, array $keys) { 413 $count = 0; 414 foreach ($keys as $key) { 415 if ($connection->delete($key)) { 416 $count++; 417 } 418 } 419 return $count; 420 } 421 422 /** 423 * Purges the cache deleting all items within it. 424 * 425 * @return boolean True on success. False otherwise. 426 */ 427 public function purge() { 428 if ($this->isready) { 429 if ($this->clustered) { 430 foreach ($this->setconnections as $connection) { 431 $connection->flush(); 432 } 433 } else { 434 $this->connection->flush(); 435 } 436 } 437 438 return true; 439 } 440 441 /** 442 * Gets an array of options to use as the serialiser. 443 * @return array 444 */ 445 public static function config_get_serialiser_options() { 446 $options = array( 447 Memcached::SERIALIZER_PHP => get_string('serialiser_php', 'cachestore_memcached') 448 ); 449 if (Memcached::HAVE_JSON) { 450 $options[Memcached::SERIALIZER_JSON] = get_string('serialiser_json', 'cachestore_memcached'); 451 } 452 if (Memcached::HAVE_IGBINARY) { 453 $options[Memcached::SERIALIZER_IGBINARY] = get_string('serialiser_igbinary', 'cachestore_memcached'); 454 } 455 return $options; 456 } 457 458 /** 459 * Gets an array of hash options available during configuration. 460 * @return array 461 */ 462 public static function config_get_hash_options() { 463 $options = array( 464 Memcached::HASH_DEFAULT => get_string('hash_default', 'cachestore_memcached'), 465 Memcached::HASH_MD5 => get_string('hash_md5', 'cachestore_memcached'), 466 Memcached::HASH_CRC => get_string('hash_crc', 'cachestore_memcached'), 467 Memcached::HASH_FNV1_64 => get_string('hash_fnv1_64', 'cachestore_memcached'), 468 Memcached::HASH_FNV1A_64 => get_string('hash_fnv1a_64', 'cachestore_memcached'), 469 Memcached::HASH_FNV1_32 => get_string('hash_fnv1_32', 'cachestore_memcached'), 470 Memcached::HASH_FNV1A_32 => get_string('hash_fnv1a_32', 'cachestore_memcached'), 471 Memcached::HASH_HSIEH => get_string('hash_hsieh', 'cachestore_memcached'), 472 Memcached::HASH_MURMUR => get_string('hash_murmur', 'cachestore_memcached'), 473 ); 474 return $options; 475 } 476 477 /** 478 * Given the data from the add instance form this function creates a configuration array. 479 * 480 * @param stdClass $data 481 * @return array 482 */ 483 public static function config_get_configuration_array($data) { 484 $lines = explode("\n", $data->servers); 485 $servers = array(); 486 foreach ($lines as $line) { 487 // Trim surrounding colons and default whitespace. 488 $line = trim(trim($line), ":"); 489 // Skip blank lines. 490 if ($line === '') { 491 continue; 492 } 493 $servers[] = explode(':', $line, 3); 494 } 495 496 $clustered = false; 497 $setservers = array(); 498 if (isset($data->clustered)) { 499 $clustered = true; 500 501 $lines = explode("\n", $data->setservers); 502 foreach ($lines as $line) { 503 // Trim surrounding colons and default whitespace. 504 $line = trim(trim($line), ":"); 505 if ($line === '') { 506 continue; 507 } 508 $setserver = explode(':', $line, 3); 509 // We don't use weights, so display a debug message. 510 if (count($setserver) > 2) { 511 debugging('Memcached Set Server '.$setserver[0].' has too many parameters.'); 512 } 513 $setservers[] = $setserver; 514 } 515 } 516 517 return array( 518 'servers' => $servers, 519 'compression' => $data->compression, 520 'serialiser' => $data->serialiser, 521 'prefix' => $data->prefix, 522 'hash' => $data->hash, 523 'bufferwrites' => $data->bufferwrites, 524 'clustered' => $clustered, 525 'setservers' => $setservers 526 ); 527 } 528 529 /** 530 * Allows the cache store to set its data against the edit form before it is shown to the user. 531 * 532 * @param moodleform $editform 533 * @param array $config 534 */ 535 public static function config_set_edit_form_data(moodleform $editform, array $config) { 536 $data = array(); 537 if (!empty($config['servers'])) { 538 $servers = array(); 539 foreach ($config['servers'] as $server) { 540 $servers[] = join(":", $server); 541 } 542 $data['servers'] = join("\n", $servers); 543 } 544 if (isset($config['compression'])) { 545 $data['compression'] = (bool)$config['compression']; 546 } 547 if (!empty($config['serialiser'])) { 548 $data['serialiser'] = $config['serialiser']; 549 } 550 if (!empty($config['prefix'])) { 551 $data['prefix'] = $config['prefix']; 552 } 553 if (!empty($config['hash'])) { 554 $data['hash'] = $config['hash']; 555 } 556 if (isset($config['bufferwrites'])) { 557 $data['bufferwrites'] = (bool)$config['bufferwrites']; 558 } 559 if (isset($config['clustered'])) { 560 $data['clustered'] = (bool)$config['clustered']; 561 } 562 if (!empty($config['setservers'])) { 563 $servers = array(); 564 foreach ($config['setservers'] as $server) { 565 $servers[] = join(":", $server); 566 } 567 $data['setservers'] = join("\n", $servers); 568 } 569 $editform->set_data($data); 570 } 571 572 /** 573 * Performs any necessary clean up when the store instance is being deleted. 574 */ 575 public function instance_deleted() { 576 if ($this->connection) { 577 $connection = $this->connection; 578 } else { 579 $connection = new Memcached(crc32($this->name)); 580 $servers = $connection->getServerList(); 581 if (empty($servers)) { 582 foreach ($this->options as $key => $value) { 583 $connection->setOption($key, $value); 584 } 585 $connection->addServers($this->servers); 586 } 587 } 588 @$connection->flush(); 589 unset($connection); 590 unset($this->connection); 591 } 592 593 /** 594 * Generates an instance of the cache store that can be used for testing. 595 * 596 * @param cache_definition $definition 597 * @return cachestore_memcached|false 598 */ 599 public static function initialise_test_instance(cache_definition $definition) { 600 601 if (!self::are_requirements_met()) { 602 return false; 603 } 604 605 $config = get_config('cachestore_memcached'); 606 if (empty($config->testservers)) { 607 return false; 608 } 609 610 $configuration = array(); 611 $configuration['servers'] = explode("\n", $config->testservers); 612 if (!empty($config->testcompression)) { 613 $configuration['compression'] = $config->testcompression; 614 } 615 if (!empty($config->testserialiser)) { 616 $configuration['serialiser'] = $config->testserialiser; 617 } 618 if (!empty($config->testprefix)) { 619 $configuration['prefix'] = $config->testprefix; 620 } 621 if (!empty($config->testhash)) { 622 $configuration['hash'] = $config->testhash; 623 } 624 if (!empty($config->testbufferwrites)) { 625 $configuration['bufferwrites'] = $config->testbufferwrites; 626 } 627 if (!empty($config->testclustered)) { 628 $configuration['clustered'] = $config->testclustered; 629 } 630 if (!empty($config->testsetservers)) { 631 $configuration['setservers'] = explode("\n", $config->testsetservers); 632 } 633 if (!empty($config->testname)) { 634 $name = $config->testname; 635 } else { 636 $name = 'Test memcached'; 637 } 638 639 $store = new cachestore_memcached($name, $configuration); 640 $store->initialise($definition); 641 642 return $store; 643 } 644 645 /** 646 * Creates a test instance for unit tests if possible. 647 * @param cache_definition $definition 648 * @return bool|cachestore_memcached 649 */ 650 public static function initialise_unit_test_instance(cache_definition $definition) { 651 if (!self::are_requirements_met()) { 652 return false; 653 } 654 if (!defined('TEST_CACHESTORE_MEMCACHED_TESTSERVERS')) { 655 return false; 656 } 657 658 $configuration = array(); 659 $configuration['servers'] = explode("\n", TEST_CACHESTORE_MEMCACHED_TESTSERVERS); 660 661 $store = new cachestore_memcached('Test memcached', $configuration); 662 $store->initialise($definition); 663 664 return $store; 665 } 666 667 /** 668 * Returns the name of this instance. 669 * @return string 670 */ 671 public function my_name() { 672 return $this->name; 673 } 674 675 /** 676 * Used to notify of configuration conflicts. 677 * 678 * The warnings returned here will be displayed on the cache configuration screen. 679 * 680 * @return string[] Returns an array of warnings (strings) 681 */ 682 public function get_warnings() { 683 global $CFG; 684 $warnings = array(); 685 if (isset($CFG->session_memcached_save_path) && count($this->servers)) { 686 $bits = explode(':', $CFG->session_memcached_save_path, 3); 687 $host = array_shift($bits); 688 $port = (count($bits)) ? array_shift($bits) : '11211'; 689 690 foreach ($this->servers as $server) { 691 if ((string)$server[0] === $host && (string)$server[1] === $port) { 692 $warnings[] = get_string('sessionhandlerconflict', 'cachestore_memcached', $this->my_name()); 693 break; 694 } 695 } 696 } 697 return $warnings; 698 } 699 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 20:29:05 2014 | Cross-referenced by PHPXref 0.7.1 |