PageRenderTime 47ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/cache/stores/mongodb/lib.php

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