MongoDB.php 18 KB

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