MongoDB.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. <?php
  2. /*
  3. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  4. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  5. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  6. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  7. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  8. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  9. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  10. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  11. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  12. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  13. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  14. */
  15. use Alcaeus\MongoDbAdapter\Helper;
  16. use Alcaeus\MongoDbAdapter\TypeConverter;
  17. use Alcaeus\MongoDbAdapter\ExceptionConverter;
  18. use MongoDB\Model\CollectionInfo;
  19. /**
  20. * Instances of this class are used to interact with a database.
  21. * @link http://www.php.net/manual/en/class.mongodb.php
  22. */
  23. class MongoDB
  24. {
  25. use Helper\ReadPreference;
  26. use Helper\SlaveOkay;
  27. use Helper\WriteConcern;
  28. const PROFILING_OFF = 0;
  29. const PROFILING_SLOW = 1;
  30. const PROFILING_ON = 2;
  31. /**
  32. * @var MongoClient
  33. */
  34. protected $connection;
  35. /**
  36. * @var \MongoDB\Database
  37. */
  38. protected $db;
  39. /**
  40. * @var string
  41. */
  42. protected $name;
  43. /**
  44. * Creates a new database
  45. *
  46. * This method is not meant to be called directly. The preferred way to create an instance of MongoDB is through {@see Mongo::__get()} or {@see Mongo::selectDB()}.
  47. * @link http://www.php.net/manual/en/mongodb.construct.php
  48. * @param MongoClient $conn Database connection.
  49. * @param string $name Database name.
  50. * @throws Exception
  51. */
  52. public function __construct(MongoClient $conn, $name)
  53. {
  54. $this->checkDatabaseName($name);
  55. $this->connection = $conn;
  56. $this->name = $name;
  57. $this->setReadPreferenceFromArray($conn->getReadPreference());
  58. $this->setWriteConcernFromArray($conn->getWriteConcern());
  59. $this->createDatabaseObject();
  60. }
  61. /**
  62. * @return \MongoDB\Database
  63. * @internal This method is not part of the ext-mongo API
  64. */
  65. public function getDb()
  66. {
  67. return $this->db;
  68. }
  69. /**
  70. * The name of this database
  71. *
  72. * @link http://www.php.net/manual/en/mongodb.--tostring.php
  73. * @return string Returns this database's name.
  74. */
  75. public function __toString()
  76. {
  77. return $this->name;
  78. }
  79. /**
  80. * Gets a collection
  81. *
  82. * @link http://www.php.net/manual/en/mongodb.get.php
  83. * @param string $name The name of the collection.
  84. * @return MongoCollection
  85. */
  86. public function __get($name)
  87. {
  88. // Handle w and wtimeout properties that replicate data stored in $readPreference
  89. if ($name === 'w' || $name === 'wtimeout') {
  90. return $this->getWriteConcern()[$name];
  91. }
  92. return $this->selectCollection($name);
  93. }
  94. /**
  95. * @param string $name
  96. * @param mixed $value
  97. */
  98. public function __set($name, $value)
  99. {
  100. if ($name === 'w' || $name === 'wtimeout') {
  101. trigger_error("The '{$name}' property is read-only", E_DEPRECATED);
  102. }
  103. }
  104. /**
  105. * Returns information about collections in this database
  106. *
  107. * @link http://www.php.net/manual/en/mongodb.getcollectioninfo.php
  108. * @param array $options An array of options for listing the collections.
  109. * @return array
  110. */
  111. public function getCollectionInfo(array $options = [])
  112. {
  113. // The includeSystemCollections option is no longer supported
  114. if (isset($options['includeSystemCollections'])) {
  115. unset($options['includeSystemCollections']);
  116. }
  117. try {
  118. $collections = $this->db->listCollections($options);
  119. } catch (\MongoDB\Driver\Exception\Exception $e) {
  120. throw ExceptionConverter::toLegacy($e);
  121. }
  122. $getCollectionInfo = function (CollectionInfo $collectionInfo) {
  123. return [
  124. 'name' => $collectionInfo->getName(),
  125. 'options' => $collectionInfo->getOptions(),
  126. ];
  127. };
  128. return array_map($getCollectionInfo, iterator_to_array($collections));
  129. }
  130. /**
  131. * Get all collections from this database
  132. *
  133. * @link http://www.php.net/manual/en/mongodb.getcollectionnames.php
  134. * @param array $options An array of options for listing the collections.
  135. * @return array Returns the names of the all the collections in the database as an array
  136. */
  137. public function getCollectionNames(array $options = [])
  138. {
  139. // The includeSystemCollections option is no longer supported
  140. if (isset($options['includeSystemCollections'])) {
  141. unset($options['includeSystemCollections']);
  142. }
  143. try {
  144. $collections = $this->db->listCollections($options);
  145. } catch (\MongoDB\Driver\Exception\Exception $e) {
  146. throw ExceptionConverter::toLegacy($e);
  147. }
  148. $getCollectionName = function (CollectionInfo $collectionInfo) {
  149. return $collectionInfo->getName();
  150. };
  151. return array_map($getCollectionName, iterator_to_array($collections));
  152. }
  153. /**
  154. * @return MongoClient
  155. * @internal This method is not part of the ext-mongo API
  156. */
  157. public function getConnection()
  158. {
  159. return $this->connection;
  160. }
  161. /**
  162. * Fetches toolkit for dealing with files stored in this database
  163. *
  164. * @link http://www.php.net/manual/en/mongodb.getgridfs.php
  165. * @param string $prefix The prefix for the files and chunks collections.
  166. * @return MongoGridFS Returns a new gridfs object for this database.
  167. */
  168. public function getGridFS($prefix = "fs")
  169. {
  170. return new \MongoGridFS($this, $prefix);
  171. }
  172. /**
  173. * Gets this database's profiling level
  174. *
  175. * @link http://www.php.net/manual/en/mongodb.getprofilinglevel.php
  176. * @return int Returns the profiling level.
  177. */
  178. public function getProfilingLevel()
  179. {
  180. $result = $this->command(['profile' => -1]);
  181. return ($result['ok'] && isset($result['was'])) ? $result['was'] : 0;
  182. }
  183. /**
  184. * Sets this database's profiling level
  185. *
  186. * @link http://www.php.net/manual/en/mongodb.setprofilinglevel.php
  187. * @param int $level Profiling level.
  188. * @return int Returns the previous profiling level.
  189. */
  190. public function setProfilingLevel($level)
  191. {
  192. $result = $this->command(['profile' => $level]);
  193. return ($result['ok'] && isset($result['was'])) ? $result['was'] : 0;
  194. }
  195. /**
  196. * Drops this database
  197. *
  198. * @link http://www.php.net/manual/en/mongodb.drop.php
  199. * @return array Returns the database response.
  200. */
  201. public function drop()
  202. {
  203. return TypeConverter::toLegacy($this->db->drop());
  204. }
  205. /**
  206. * Repairs and compacts this database
  207. *
  208. * @link http://www.php.net/manual/en/mongodb.repair.php
  209. * @param bool $preserve_cloned_files [optional] <p>If cloned files should be kept if the repair fails.</p>
  210. * @param bool $backup_original_files [optional] <p>If original files should be backed up.</p>
  211. * @return array <p>Returns db response.</p>
  212. */
  213. public function repair($preserve_cloned_files = FALSE, $backup_original_files = FALSE)
  214. {
  215. $command = [
  216. 'repairDatabase' => 1,
  217. 'preserveClonedFilesOnFailure' => $preserve_cloned_files,
  218. 'backupOriginalFiles' => $backup_original_files,
  219. ];
  220. return $this->command($command);
  221. }
  222. /**
  223. * Gets a collection
  224. *
  225. * @link http://www.php.net/manual/en/mongodb.selectcollection.php
  226. * @param string $name <b>The collection name.</b>
  227. * @throws Exception if the collection name is invalid.
  228. * @return MongoCollection Returns a new collection object.
  229. */
  230. public function selectCollection($name)
  231. {
  232. return new MongoCollection($this, $name);
  233. }
  234. /**
  235. * Creates a collection
  236. *
  237. * @link http://www.php.net/manual/en/mongodb.createcollection.php
  238. * @param string $name The name of the collection.
  239. * @param array $options
  240. * @return MongoCollection Returns a collection object representing the new collection.
  241. */
  242. public function createCollection($name, $options)
  243. {
  244. try {
  245. if (isset($options['capped'])) {
  246. $options['capped'] = (bool) $options['capped'];
  247. }
  248. $this->db->createCollection($name, $options);
  249. } catch (\MongoDB\Driver\Exception\Exception $e) {
  250. return false;
  251. }
  252. return $this->selectCollection($name);
  253. }
  254. /**
  255. * Drops a collection
  256. *
  257. * @link http://www.php.net/manual/en/mongodb.dropcollection.php
  258. * @param MongoCollection|string $coll MongoCollection or name of collection to drop.
  259. * @return array Returns the database response.
  260. *
  261. * @deprecated Use MongoCollection::drop() instead.
  262. */
  263. public function dropCollection($coll)
  264. {
  265. if ($coll instanceof MongoCollection) {
  266. $coll = $coll->getName();
  267. }
  268. return TypeConverter::toLegacy($this->db->dropCollection((string) $coll));
  269. }
  270. /**
  271. * Get a list of collections in this database
  272. *
  273. * @link http://www.php.net/manual/en/mongodb.listcollections.php
  274. * @param array $options
  275. * @return MongoCollection[] Returns a list of MongoCollections.
  276. */
  277. public function listCollections(array $options = [])
  278. {
  279. return array_map([$this, 'selectCollection'], $this->getCollectionNames($options));
  280. }
  281. /**
  282. * Creates a database reference
  283. *
  284. * @link http://www.php.net/manual/en/mongodb.createdbref.php
  285. * @param string $collection The collection to which the database reference will point.
  286. * @param mixed $document_or_id
  287. * @return array Returns a database reference array.
  288. */
  289. public function createDBRef($collection, $document_or_id)
  290. {
  291. if ($document_or_id instanceof \MongoId) {
  292. $id = $document_or_id;
  293. } elseif (is_object($document_or_id)) {
  294. if (! isset($document_or_id->_id)) {
  295. $id = $document_or_id;
  296. } else {
  297. $id = $document_or_id->_id;
  298. }
  299. } elseif (is_array($document_or_id)) {
  300. if (! isset($document_or_id['_id'])) {
  301. return null;
  302. }
  303. $id = $document_or_id['_id'];
  304. } else {
  305. $id = $document_or_id;
  306. }
  307. return MongoDBRef::create($collection, $id);
  308. }
  309. /**
  310. * Fetches the document pointed to by a database reference
  311. *
  312. * @link http://www.php.net/manual/en/mongodb.getdbref.php
  313. * @param array $ref A database reference.
  314. * @return array Returns the document pointed to by the reference.
  315. */
  316. public function getDBRef(array $ref)
  317. {
  318. return MongoDBRef::get($this, $ref);
  319. }
  320. /**
  321. * Runs JavaScript code on the database server.
  322. *
  323. * @link http://www.php.net/manual/en/mongodb.execute.php
  324. * @param MongoCode|string $code Code to execute.
  325. * @param array $args [optional] Arguments to be passed to code.
  326. * @return array Returns the result of the evaluation.
  327. */
  328. public function execute($code, array $args = [])
  329. {
  330. return $this->command(['eval' => $code, 'args' => $args]);
  331. }
  332. /**
  333. * Execute a database command
  334. *
  335. * @link http://www.php.net/manual/en/mongodb.command.php
  336. * @param array $data The query to send.
  337. * @param array $options
  338. * @return array Returns database response.
  339. */
  340. public function command(array $data, $options = [], &$hash = null)
  341. {
  342. try {
  343. $cursor = new \MongoCommandCursor($this->connection, $this->name, $data);
  344. $cursor->setReadPreference($this->getReadPreference());
  345. return iterator_to_array($cursor)[0];
  346. } catch (\MongoDB\Driver\Exception\Exception $e) {
  347. return [
  348. 'ok' => 0.0,
  349. 'errmsg' => $e->getMessage(),
  350. 'code' => $e->getCode(),
  351. ];
  352. }
  353. }
  354. /**
  355. * Check if there was an error on the most recent db operation performed
  356. *
  357. * @link http://www.php.net/manual/en/mongodb.lasterror.php
  358. * @return array Returns the error, if there was one.
  359. */
  360. public function lastError()
  361. {
  362. return $this->command(array('getLastError' => 1));
  363. }
  364. /**
  365. * Checks for the last error thrown during a database operation
  366. *
  367. * @link http://www.php.net/manual/en/mongodb.preverror.php
  368. * @return array Returns the error and the number of operations ago it occurred.
  369. */
  370. public function prevError()
  371. {
  372. return $this->command(array('getPrevError' => 1));
  373. }
  374. /**
  375. * Clears any flagged errors on the database
  376. *
  377. * @link http://www.php.net/manual/en/mongodb.reseterror.php
  378. * @return array Returns the database response.
  379. */
  380. public function resetError()
  381. {
  382. return $this->command(array('resetError' => 1));
  383. }
  384. /**
  385. * Creates a database error
  386. *
  387. * @link http://www.php.net/manual/en/mongodb.forceerror.php
  388. * @return boolean Returns the database response.
  389. */
  390. public function forceError()
  391. {
  392. return $this->command(array('forceerror' => 1));
  393. }
  394. /**
  395. * Log in to this database
  396. *
  397. * @link http://www.php.net/manual/en/mongodb.authenticate.php
  398. * @param string $username The username.
  399. * @param string $password The password (in plaintext).
  400. * @return array Returns database response. If the login was successful, it will return 1.
  401. *
  402. * @deprecated This method is not implemented, supply authentication credentials through the connection string instead.
  403. */
  404. public function authenticate($username, $password)
  405. {
  406. throw new \Exception('The MongoDB::authenticate method is not supported. Please supply authentication credentials through the connection string');
  407. }
  408. /**
  409. * {@inheritdoc}
  410. */
  411. public function setReadPreference($readPreference, $tags = null)
  412. {
  413. $result = $this->setReadPreferenceFromParameters($readPreference, $tags);
  414. $this->createDatabaseObject();
  415. return $result;
  416. }
  417. /**
  418. * {@inheritdoc}
  419. */
  420. public function setWriteConcern($wstring, $wtimeout = 0)
  421. {
  422. $result = $this->setWriteConcernFromParameters($wstring, $wtimeout);
  423. $this->createDatabaseObject();
  424. return $result;
  425. }
  426. protected function notImplemented()
  427. {
  428. throw new \Exception('Not implemented');
  429. }
  430. /**
  431. * @return \MongoDB\Database
  432. */
  433. private function createDatabaseObject()
  434. {
  435. $options = [
  436. 'readPreference' => $this->readPreference,
  437. 'writeConcern' => $this->writeConcern,
  438. ];
  439. if ($this->db === null) {
  440. $this->db = $this->connection->getClient()->selectDatabase($this->name, $options);
  441. } else {
  442. $this->db = $this->db->withOptions($options);
  443. }
  444. }
  445. private function checkDatabaseName($name)
  446. {
  447. if (empty($name)) {
  448. throw new \Exception('Database name cannot be empty');
  449. }
  450. if (strlen($name) >= 64) {
  451. throw new \Exception('Database name cannot exceed 63 characters');
  452. }
  453. if (strpos($name, chr(0)) !== false) {
  454. throw new \Exception('Database name cannot contain null bytes');
  455. }
  456. $invalidCharacters = ['.', '$', '/', ' ', '\\'];
  457. foreach ($invalidCharacters as $char) {
  458. if (strchr($name, $char) !== false) {
  459. throw new \Exception('Database name contains invalid characters');
  460. }
  461. }
  462. }
  463. }