[ Index ]

PHP Cross Reference of moodle-2.8

title

Body

[close]

/cache/stores/memcached/ -> lib.php (source)

   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  }


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1