MongoDB.php 16 KB

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