[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/cache/stores/mongodb/ -> 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 MongoDB store plugin.
  19   *
  20   * This file is part of the MongoDB store plugin, it contains the API for interacting with an instance of the store.
  21   *
  22   * @package    cachestore_mongodb
  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 MongoDB Cache store.
  31   *
  32   * This cache store uses the MongoDB Native Driver.
  33   * For installation instructions have a look at the following two links:
  34   *  - {@link http://www.php.net/manual/en/mongo.installation.php}
  35   *  - {@link http://www.mongodb.org/display/DOCS/PHP+Language+Center}
  36   *
  37   * @copyright  2012 Sam Hemelryk
  38   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  39   */
  40  class cachestore_mongodb extends cache_store implements cache_is_configurable {
  41  
  42      /**
  43       * The name of the store
  44       * @var string
  45       */
  46      protected $name;
  47  
  48      /**
  49       * The server connection string. Comma separated values.
  50       * @var string
  51       */
  52      protected $server = 'mongodb://127.0.0.1:27017';
  53  
  54      /**
  55       * The database connection options
  56       * @var array
  57       */
  58      protected $options = array();
  59  
  60      /**
  61       * The name of the database to use.
  62       * @var string
  63       */
  64      protected $databasename = 'mcache';
  65  
  66      /**
  67       * The Connection object
  68       * @var Mongo
  69       */
  70      protected $connection = false;
  71  
  72      /**
  73       * The Database Object
  74       * @var MongoDB
  75       */
  76      protected $database;
  77  
  78      /**
  79       * The Collection object
  80       * @var MongoCollection
  81       */
  82      protected $collection;
  83  
  84      /**
  85       * Determines if and what safe setting is to be used.
  86       * @var bool|int
  87       */
  88      protected $usesafe = true;
  89  
  90      /**
  91       * If set to true then multiple identifiers will be requested and used.
  92       * @var bool
  93       */
  94      protected $extendedmode = false;
  95  
  96      /**
  97       * The definition has which is used in the construction of the collection.
  98       * @var string
  99       */
 100      protected $definitionhash = null;
 101  
 102      /**
 103       * Set to true once this store is ready to be initialised and used.
 104       * @var bool
 105       */
 106      protected $isready = false;
 107  
 108      /**
 109       * Set to true if the Mongo extension is < version 1.3.
 110       * If this is the case we must use the legacy Mongo class instead of MongoClient.
 111       * Mongo is backwards compatible, although obviously deprecated.
 112       * @var bool
 113       */
 114      protected $legacymongo = false;
 115  
 116      /**
 117       * Constructs a new instance of the Mongo store.
 118       *
 119       * Noting that this function is not an initialisation. It is used to prepare the store for use.
 120       * The store will be initialised when required and will be provided with a cache_definition at that time.
 121       *
 122       * @param string $name
 123       * @param array $configuration
 124       */
 125      public function __construct($name, array $configuration = array()) {
 126          $this->name = $name;
 127  
 128          if (array_key_exists('server', $configuration)) {
 129              $this->server = $configuration['server'];
 130          }
 131  
 132          if (array_key_exists('replicaset', $configuration)) {
 133              $this->options['replicaSet'] = (string)$configuration['replicaset'];
 134          }
 135          if (array_key_exists('username', $configuration) && !empty($configuration['username'])) {
 136              $this->options['username'] = (string)$configuration['username'];
 137          }
 138          if (array_key_exists('password', $configuration) && !empty($configuration['password'])) {
 139              $this->options['password'] = (string)$configuration['password'];
 140          }
 141          if (array_key_exists('database', $configuration)) {
 142              $this->databasename = (string)$configuration['database'];
 143          }
 144          if (array_key_exists('usesafe', $configuration)) {
 145              $this->usesafe = $configuration['usesafe'];
 146          }
 147          if (array_key_exists('extendedmode', $configuration)) {
 148              $this->extendedmode = $configuration['extendedmode'];
 149          }
 150  
 151          // Test if the MongoClient class exists, if not we need to switch to legacy classes.
 152          $this->legacymongo = (!class_exists('MongoClient'));
 153  
 154          // MongoClient from Mongo 1.3 onwards. Mongo for earlier versions.
 155          $class = ($this->legacymongo) ? 'Mongo' : 'MongoClient';
 156          try {
 157              $this->connection = new $class($this->server, $this->options);
 158              $this->isready = true;
 159          } catch (MongoConnectionException $e) {
 160              // We only want to catch MongoConnectionExceptions here.
 161          }
 162      }
 163  
 164      /**
 165       * Returns true if the requirements of this store have been met.
 166       * @return bool
 167       */
 168      public static function are_requirements_met() {
 169          return class_exists('MongoClient') || class_exists('Mongo');
 170      }
 171  
 172      /**
 173       * Returns the supported features.
 174       * @param array $configuration
 175       * @return int
 176       */
 177      public static function get_supported_features(array $configuration = array()) {
 178          $supports = self::SUPPORTS_DATA_GUARANTEE + self::DEREFERENCES_OBJECTS;
 179          if (array_key_exists('extendedmode', $configuration) && $configuration['extendedmode']) {
 180              $supports += self::SUPPORTS_MULTIPLE_IDENTIFIERS;
 181          }
 182          return $supports;
 183      }
 184  
 185      /**
 186       * Returns an int describing the supported modes.
 187       * @param array $configuration
 188       * @return int
 189       */
 190      public static function get_supported_modes(array $configuration = array()) {
 191          return self::MODE_APPLICATION;
 192      }
 193  
 194      /**
 195       * Initialises the store instance for use.
 196       *
 197       * Once this has been done the cache is all set to be used.
 198       *
 199       * @param cache_definition $definition
 200       * @throws coding_exception
 201       */
 202      public function initialise(cache_definition $definition) {
 203          if ($this->is_initialised()) {
 204              throw new coding_exception('This mongodb instance has already been initialised.');
 205          }
 206          $this->database = $this->connection->selectDB($this->databasename);
 207          $this->definitionhash = 'm'.$definition->generate_definition_hash();
 208          $this->collection = $this->database->selectCollection($this->definitionhash);
 209  
 210          $options = array('name' => 'idx_key');
 211          if ($this->legacymongo) {
 212              $options['safe'] = $this->usesafe;
 213          } else {
 214              $options['w'] = $this->usesafe ? 1 : 0;
 215          }
 216          $this->collection->ensureIndex(array('key' => 1), $options);
 217      }
 218  
 219      /**
 220       * Returns true if this store instance has been initialised.
 221       * @return bool
 222       */
 223      public function is_initialised() {
 224          return ($this->database instanceof MongoDB);
 225      }
 226  
 227      /**
 228       * Returns true if this store instance is ready to use.
 229       * @return bool
 230       */
 231      public function is_ready() {
 232          return $this->isready;
 233      }
 234  
 235      /**
 236       * Returns true if the given mode is supported by this store.
 237       * @param int $mode
 238       * @return bool
 239       */
 240      public static function is_supported_mode($mode) {
 241          return ($mode == self::MODE_APPLICATION || $mode == self::MODE_SESSION);
 242      }
 243  
 244      /**
 245       * Returns true if this store is making use of multiple identifiers.
 246       * @return bool
 247       */
 248      public function supports_multiple_identifiers() {
 249          return $this->extendedmode;
 250      }
 251  
 252      /**
 253       * Retrieves an item from the cache store given its key.
 254       *
 255       * @param string $key The key to retrieve
 256       * @return mixed The data that was associated with the key, or false if the key did not exist.
 257       */
 258      public function get($key) {
 259          if (!is_array($key)) {
 260              $key = array('key' => $key);
 261          }
 262  
 263          $result = $this->collection->findOne($key);
 264          if ($result === null || !array_key_exists('data', $result)) {
 265              return false;
 266          }
 267          $data = @unserialize($result['data']);
 268          return $data;
 269      }
 270  
 271      /**
 272       * Retrieves several items from the cache store in a single transaction.
 273       *
 274       * 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.
 275       *
 276       * @param array $keys The array of keys to retrieve
 277       * @return array An array of items from the cache.
 278       */
 279      public function get_many($keys) {
 280          if ($this->extendedmode) {
 281              $query = $this->get_many_extendedmode_query($keys);
 282              $keyarray = array();
 283              foreach ($keys as $key) {
 284                  $keyarray[] = $key['key'];
 285              }
 286              $keys = $keyarray;
 287              $query = array('key' => array('$in' => $keys));
 288          } else {
 289              $query = array('key' => array('$in' => $keys));
 290          }
 291          $cursor = $this->collection->find($query);
 292          $results = array();
 293          foreach ($cursor as $result) {
 294              $id = (string)$result['key'];
 295              $results[$id] = unserialize($result['data']);
 296          }
 297          foreach ($keys as $key) {
 298              if (!array_key_exists($key, $results)) {
 299                  $results[$key] = false;
 300              }
 301          }
 302          return $results;
 303      }
 304  
 305      /**
 306       * Sets an item in the cache given its key and data value.
 307       *
 308       * @param string $key The key to use.
 309       * @param mixed $data The data to set.
 310       * @return bool True if the operation was a success false otherwise.
 311       */
 312      public function set($key, $data) {
 313          if (!is_array($key)) {
 314              $record = array(
 315                  'key' => $key
 316              );
 317          } else {
 318              $record = $key;
 319          }
 320          $record['data'] = serialize($data);
 321          $options = array('upsert' => true);
 322          if ($this->legacymongo) {
 323              $options['safe'] = $this->usesafe;
 324          } else {
 325              $options['w'] = $this->usesafe ? 1 : 0;
 326          }
 327          $this->delete($key);
 328          $result = $this->collection->insert($record, $options);
 329          if ($result === true) {
 330              // Safe mode is off.
 331              return true;
 332          } else if (is_array($result)) {
 333              if (empty($result['ok']) || isset($result['err'])) {
 334                  return false;
 335              }
 336              return true;
 337          }
 338          // Who knows?
 339          return false;
 340      }
 341  
 342      /**
 343       * Sets many items in the cache in a single transaction.
 344       *
 345       * @param array $keyvaluearray An array of key value pairs. Each item in the array will be an associative array with two
 346       *      keys, 'key' and 'value'.
 347       * @return int The number of items successfully set. It is up to the developer to check this matches the number of items
 348       *      sent ... if they care that is.
 349       */
 350      public function set_many(array $keyvaluearray) {
 351          $count = 0;
 352          foreach ($keyvaluearray as $pair) {
 353              $result = $this->set($pair['key'], $pair['value']);
 354              if ($result === true) {
 355                   $count++;
 356              }
 357          }
 358          return $count;
 359      }
 360  
 361      /**
 362       * Deletes an item from the cache store.
 363       *
 364       * @param string $key The key to delete.
 365       * @return bool Returns true if the operation was a success, false otherwise.
 366       */
 367      public function delete($key) {
 368          if (!is_array($key)) {
 369              $criteria = array(
 370                  'key' => $key
 371              );
 372          } else {
 373              $criteria = $key;
 374          }
 375          $options = array('justOne' => false);
 376          if ($this->legacymongo) {
 377              $options['safe'] = $this->usesafe;
 378          } else {
 379              $options['w'] = $this->usesafe ? 1 : 0;
 380          }
 381          $result = $this->collection->remove($criteria, $options);
 382  
 383          if ($result === true) {
 384              // Safe mode.
 385              return true;
 386          } else if (is_array($result)) {
 387              if (empty($result['ok']) || isset($result['err'])) {
 388                  return false;
 389              } else if (empty($result['n'])) {
 390                  // Nothing was removed.
 391                  return false;
 392              }
 393              return true;
 394          }
 395          // Who knows?
 396          return false;
 397      }
 398  
 399      /**
 400       * Deletes several keys from the cache in a single action.
 401       *
 402       * @param array $keys The keys to delete
 403       * @return int The number of items successfully deleted.
 404       */
 405      public function delete_many(array $keys) {
 406          $count = 0;
 407          foreach ($keys as $key) {
 408              if ($this->delete($key)) {
 409                  $count++;
 410              }
 411          }
 412          return $count;
 413      }
 414  
 415      /**
 416       * Purges the cache deleting all items within it.
 417       *
 418       * @return boolean True on success. False otherwise.
 419       */
 420      public function purge() {
 421          if ($this->isready) {
 422              $this->collection->drop();
 423              $this->collection = $this->database->selectCollection($this->definitionhash);
 424          }
 425  
 426          return true;
 427      }
 428  
 429      /**
 430       * Takes the object from the add instance store and creates a configuration array that can be used to initialise an instance.
 431       *
 432       * @param stdClass $data
 433       * @return array
 434       */
 435      public static function config_get_configuration_array($data) {
 436          $return = array(
 437              'server' => $data->server,
 438              'database' => $data->database,
 439              'extendedmode' => (!empty($data->extendedmode))
 440          );
 441          if (!empty($data->username)) {
 442              $return['username'] = $data->username;
 443          }
 444          if (!empty($data->password)) {
 445              $return['password'] = $data->password;
 446          }
 447          if (!empty($data->replicaset)) {
 448              $return['replicaset'] = $data->replicaset;
 449          }
 450          if (!empty($data->usesafe)) {
 451              $return['usesafe'] = true;
 452              if (!empty($data->usesafevalue)) {
 453                  $return['usesafe'] = (int)$data->usesafevalue;
 454                  $return['usesafevalue'] = $return['usesafe'];
 455              }
 456          }
 457          return $return;
 458      }
 459  
 460      /**
 461       * Allows the cache store to set its data against the edit form before it is shown to the user.
 462       *
 463       * @param moodleform $editform
 464       * @param array $config
 465       */
 466      public static function config_set_edit_form_data(moodleform $editform, array $config) {
 467          $data = array();
 468          if (!empty($config['server'])) {
 469              $data['server'] = $config['server'];
 470          }
 471          if (!empty($config['database'])) {
 472              $data['database'] = $config['database'];
 473          }
 474          if (isset($config['extendedmode'])) {
 475              $data['extendedmode'] = (bool)$config['extendedmode'];
 476          }
 477          if (!empty($config['username'])) {
 478              $data['username'] = $config['username'];
 479          }
 480          if (!empty($config['password'])) {
 481              $data['password'] = $config['password'];
 482          }
 483          if (!empty($config['replicaset'])) {
 484              $data['replicaset'] = $config['replicaset'];
 485          }
 486          if (isset($config['usesafevalue'])) {
 487              $data['usesafe'] = true;
 488              $data['usesafevalue'] = (int)$data['usesafe'];
 489          } else if (isset($config['usesafe'])) {
 490              $data['usesafe'] = (bool)$config['usesafe'];
 491          }
 492          $editform->set_data($data);
 493      }
 494  
 495      /**
 496       * Performs any necessary clean up when the store instance is being deleted.
 497       */
 498      public function instance_deleted() {
 499          // We can't use purge here that acts upon a collection.
 500          // Instead we must drop the named database.
 501          if ($this->connection) {
 502              $connection = $this->connection;
 503          } else {
 504              try {
 505                  // MongoClient from Mongo 1.3 onwards. Mongo for earlier versions.
 506                  $class = ($this->legacymongo) ? 'Mongo' : 'MongoClient';
 507                  $connection = new $class($this->server, $this->options);
 508              } catch (MongoConnectionException $e) {
 509                  // We only want to catch MongoConnectionExceptions here.
 510                  // If the server cannot be connected to we cannot clean it.
 511                  return;
 512              }
 513          }
 514          $database = $connection->selectDB($this->databasename);
 515          $database->drop();
 516          $connection = null;
 517          $database = null;
 518          // Explicitly unset things to cause a close.
 519          $this->collection = null;
 520          $this->database = null;
 521          $this->connection = null;
 522      }
 523  
 524      /**
 525       * Generates an instance of the cache store that can be used for testing.
 526       *
 527       * @param cache_definition $definition
 528       * @return false
 529       */
 530      public static function initialise_test_instance(cache_definition $definition) {
 531          if (!self::are_requirements_met()) {
 532              return false;
 533          }
 534  
 535          $config = get_config('cachestore_mongodb');
 536          if (empty($config->testserver)) {
 537              return false;
 538          }
 539          $configuration = array();
 540          $configuration['server'] = $config->testserver;
 541          if (!empty($config->testreplicaset)) {
 542              $configuration['replicaset'] = $config->testreplicaset;
 543          }
 544          if (!empty($config->testusername)) {
 545              $configuration['username'] = $config->testusername;
 546          }
 547          if (!empty($config->testpassword)) {
 548              $configuration['password'] = $config->testpassword;
 549          }
 550          if (!empty($config->testdatabase)) {
 551              $configuration['database'] = $config->testdatabase;
 552          }
 553          $configuration['usesafe'] = 1;
 554          if (!empty($config->testextendedmode)) {
 555              $configuration['extendedmode'] = (bool)$config->testextendedmode;
 556          }
 557  
 558          $store = new cachestore_mongodb('Test mongodb', $configuration);
 559          if (!$store->is_ready()) {
 560              return false;
 561          }
 562          $store->initialise($definition);
 563  
 564          return $store;
 565      }
 566  
 567  
 568      /**
 569       * Generates an instance of the cache store that can be used for testing.
 570       *
 571       * @param cache_definition $definition
 572       * @return false
 573       */
 574      public static function initialise_unit_test_instance(cache_definition $definition) {
 575          if (!self::are_requirements_met()) {
 576              return false;
 577          }
 578          if (!defined('TEST_CACHESTORE_MONGODB_TESTSERVER')) {
 579              return false;
 580          }
 581  
 582          $configuration = array();
 583          $configuration['servers'] = explode("\n", TEST_CACHESTORE_MONGODB_TESTSERVER);
 584          $configuration['usesafe'] = 1;
 585  
 586          $store = new cachestore_mongodb('Test mongodb', $configuration);
 587          if (!$store->is_ready()) {
 588              return false;
 589          }
 590          $store->initialise($definition);
 591  
 592          return $store;
 593      }
 594  
 595      /**
 596       * Returns the name of this instance.
 597       * @return string
 598       */
 599      public function my_name() {
 600          return $this->name;
 601      }
 602  
 603      /**
 604       * Returns true if this cache store instance is both suitable for testing, and ready for testing.
 605       *
 606       * Cache stores that support being used as the default store for unit and acceptance testing should
 607       * override this function and return true if there requirements have been met.
 608       *
 609       * @return bool
 610       */
 611      public static function ready_to_be_used_for_testing() {
 612          return defined('TEST_CACHESTORE_MONGODB_TESTSERVER');
 613      }
 614  }


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1