[ 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 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 }
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 |