MongoClient.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  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('MongoClient', false)) {
  16. return;
  17. }
  18. use Alcaeus\MongoDbAdapter\Helper;
  19. use Alcaeus\MongoDbAdapter\ExceptionConverter;
  20. use MongoDB\Client;
  21. /**
  22. * A connection between PHP and MongoDB. This class is used to create and manage connections
  23. * See MongoClient::__construct() and the section on connecting for more information about creating connections.
  24. * @link http://www.php.net/manual/en/class.mongoclient.php
  25. */
  26. class MongoClient
  27. {
  28. use Helper\ReadPreference;
  29. use Helper\WriteConcern;
  30. const VERSION = '1.6.12';
  31. const DEFAULT_HOST = "localhost" ;
  32. const DEFAULT_PORT = 27017 ;
  33. const RP_PRIMARY = "primary" ;
  34. const RP_PRIMARY_PREFERRED = "primaryPreferred" ;
  35. const RP_SECONDARY = "secondary" ;
  36. const RP_SECONDARY_PREFERRED = "secondaryPreferred" ;
  37. const RP_NEAREST = "nearest" ;
  38. /**
  39. * @var bool
  40. * @deprecated This will not properly work as the underlying driver connects lazily
  41. */
  42. public $connected = false;
  43. /**
  44. * @var
  45. */
  46. public $status;
  47. /**
  48. * @var string
  49. */
  50. protected $server;
  51. /**
  52. * @var
  53. */
  54. protected $persistent;
  55. /**
  56. * @var Client
  57. */
  58. private $client;
  59. /**
  60. * @var \MongoDB\Driver\Manager
  61. */
  62. private $manager;
  63. /**
  64. * Creates a new database connection object
  65. *
  66. * @link http://php.net/manual/en/mongo.construct.php
  67. * @param string $server The server name.
  68. * @param array $options An array of options for the connection.
  69. * @param array $driverOptions An array of options for the MongoDB driver.
  70. * @throws MongoConnectionException
  71. */
  72. public function __construct($server = 'default', array $options = ['connect' => true], array $driverOptions = [])
  73. {
  74. if ($server === 'default') {
  75. $server = 'mongodb://' . self::DEFAULT_HOST . ':' . self::DEFAULT_PORT;
  76. }
  77. $this->applyConnectionOptions($server, $options);
  78. $this->server = $server;
  79. if (false === strpos($this->server, 'mongodb://')) {
  80. $this->server = 'mongodb://'.$this->server;
  81. }
  82. $this->client = new Client($this->server, $options, $driverOptions);
  83. $info = $this->client->__debugInfo();
  84. $this->manager = $info['manager'];
  85. if (isset($options['connect']) && $options['connect']) {
  86. $this->connect();
  87. }
  88. }
  89. /**
  90. * Closes this database connection
  91. *
  92. * @link http://www.php.net/manual/en/mongoclient.close.php
  93. * @param boolean|string $connection
  94. * @return boolean If the connection was successfully closed.
  95. */
  96. public function close($connection = null)
  97. {
  98. $this->connected = false;
  99. return false;
  100. }
  101. /**
  102. * Connects to a database server
  103. *
  104. * @link http://www.php.net/manual/en/mongoclient.connect.php
  105. *
  106. * @throws MongoConnectionException
  107. * @return boolean If the connection was successful.
  108. */
  109. public function connect()
  110. {
  111. $this->connected = true;
  112. return true;
  113. }
  114. /**
  115. * Drops a database
  116. *
  117. * @link http://www.php.net/manual/en/mongoclient.dropdb.php
  118. * @param mixed $db The database to drop. Can be a MongoDB object or the name of the database.
  119. * @return array The database response.
  120. * @deprecated Use MongoDB::drop() instead.
  121. */
  122. public function dropDB($db)
  123. {
  124. return $this->selectDB($db)->drop();
  125. }
  126. /**
  127. * Gets a database
  128. *
  129. * @link http://php.net/manual/en/mongoclient.get.php
  130. * @param string $dbname The database name.
  131. * @return MongoDB The database name.
  132. */
  133. public function __get($dbname)
  134. {
  135. return $this->selectDB($dbname);
  136. }
  137. /**
  138. * Gets the client for this object
  139. *
  140. * @internal This part is not of the ext-mongo API and should not be used
  141. * @return Client
  142. */
  143. public function getClient()
  144. {
  145. return $this->client;
  146. }
  147. /**
  148. * Get connections
  149. *
  150. * Returns an array of all open connections, and information about each of the servers
  151. *
  152. * @return array
  153. */
  154. public static function getConnections()
  155. {
  156. return [];
  157. }
  158. /**
  159. * Get hosts
  160. *
  161. * This method is only useful with a connection to a replica set. It returns the status of all of the hosts in the
  162. * set. Without a replica set, it will just return an array with one element containing the host that you are
  163. * connected to.
  164. *
  165. * @return array
  166. */
  167. public function getHosts()
  168. {
  169. $this->forceConnect();
  170. $results = [];
  171. try {
  172. $servers = $this->manager->getServers();
  173. } catch (\MongoDB\Driver\Exception\Exception $e) {
  174. throw ExceptionConverter::toLegacy($e);
  175. }
  176. foreach ($servers as $server) {
  177. $key = sprintf('%s:%d;-;.;%d', $server->getHost(), $server->getPort(), getmypid());
  178. $info = $server->getInfo();
  179. switch ($server->getType()) {
  180. case \MongoDB\Driver\Server::TYPE_RS_PRIMARY:
  181. $state = 1;
  182. break;
  183. case \MongoDB\Driver\Server::TYPE_RS_SECONDARY:
  184. $state = 2;
  185. break;
  186. default:
  187. $state = 0;
  188. }
  189. $results[$key] = [
  190. 'host' => $server->getHost(),
  191. 'port' => $server->getPort(),
  192. 'health' => (int) $info['ok'],
  193. 'state' => $state,
  194. 'ping' => $server->getLatency(),
  195. 'lastPing' => null,
  196. ];
  197. }
  198. return $results;
  199. }
  200. /**
  201. * Kills a specific cursor on the server
  202. *
  203. * @link http://www.php.net/manual/en/mongoclient.killcursor.php
  204. * @param string $server_hash The server hash that has the cursor.
  205. * @param int|MongoInt64 $id The ID of the cursor to kill.
  206. * @return bool
  207. */
  208. public function killCursor($server_hash, $id)
  209. {
  210. $this->notImplemented();
  211. }
  212. /**
  213. * Lists all of the databases available
  214. *
  215. * @link http://php.net/manual/en/mongoclient.listdbs.php
  216. * @return array Returns an associative array containing three fields. The first field is databases, which in turn contains an array. Each element of the array is an associative array corresponding to a database, giving the database's name, size, and if it's empty. The other two fields are totalSize (in bytes) and ok, which is 1 if this method ran successfully.
  217. */
  218. public function listDBs()
  219. {
  220. try {
  221. $databaseInfoIterator = $this->client->listDatabases();
  222. } catch (\MongoDB\Driver\Exception\Exception $e) {
  223. throw ExceptionConverter::toLegacy($e);
  224. }
  225. $databases = [
  226. 'databases' => [],
  227. 'totalSize' => 0,
  228. 'ok' => 1.0,
  229. ];
  230. foreach ($databaseInfoIterator as $databaseInfo) {
  231. $databases['databases'][] = [
  232. 'name' => $databaseInfo->getName(),
  233. 'empty' => $databaseInfo->isEmpty(),
  234. 'sizeOnDisk' => $databaseInfo->getSizeOnDisk(),
  235. ];
  236. $databases['totalSize'] += $databaseInfo->getSizeOnDisk();
  237. }
  238. return $databases;
  239. }
  240. /**
  241. * Gets a database collection
  242. *
  243. * @link http://www.php.net/manual/en/mongoclient.selectcollection.php
  244. * @param string $db The database name.
  245. * @param string $collection The collection name.
  246. * @return MongoCollection Returns a new collection object.
  247. * @throws Exception Throws Exception if the database or collection name is invalid.
  248. */
  249. public function selectCollection($db, $collection)
  250. {
  251. return new MongoCollection($this->selectDB($db), $collection);
  252. }
  253. /**
  254. * Gets a database
  255. *
  256. * @link http://www.php.net/manual/en/mongo.selectdb.php
  257. * @param string $name The database name.
  258. * @return MongoDB Returns a new db object.
  259. * @throws InvalidArgumentException
  260. */
  261. public function selectDB($name)
  262. {
  263. return new MongoDB($this, $name);
  264. }
  265. /**
  266. * {@inheritdoc}
  267. */
  268. public function setReadPreference($readPreference, $tags = null)
  269. {
  270. return $this->setReadPreferenceFromParameters($readPreference, $tags);
  271. }
  272. /**
  273. * {@inheritdoc}
  274. */
  275. public function setWriteConcern($wstring, $wtimeout = 0)
  276. {
  277. return $this->setWriteConcernFromParameters($wstring, $wtimeout);
  278. }
  279. /**
  280. * String representation of this connection
  281. *
  282. * @link http://www.php.net/manual/en/mongoclient.tostring.php
  283. * @return string Returns hostname and port for this connection.
  284. */
  285. public function __toString()
  286. {
  287. return $this->server;
  288. }
  289. /**
  290. * Forces a connection by executing the ping command
  291. */
  292. private function forceConnect()
  293. {
  294. $command = new \MongoDB\Driver\Command(['ping' => 1]);
  295. $this->manager->executeCommand('db', $command);
  296. }
  297. private function notImplemented()
  298. {
  299. throw new \Exception('Not implemented');
  300. }
  301. /**
  302. * @return array
  303. */
  304. function __sleep()
  305. {
  306. return [
  307. 'connected', 'status', 'server', 'persistent'
  308. ];
  309. }
  310. /**
  311. * @param $server
  312. * @return array
  313. */
  314. private function extractUrlOptions($server)
  315. {
  316. $queryOptions = explode('&', parse_url($server, PHP_URL_QUERY));
  317. $options = [];
  318. foreach ($queryOptions as $option) {
  319. if (strpos($option, '=') === false) {
  320. continue;
  321. }
  322. $keyValue = explode('=', $option);
  323. if ($keyValue[0] === 'readPreferenceTags') {
  324. $options[$keyValue[0]][] = $this->getReadPreferenceTags($keyValue[1]);
  325. } else {
  326. $options[$keyValue[0]] = $keyValue[1];
  327. }
  328. }
  329. return $options;
  330. }
  331. /**
  332. * @param $readPreferenceTagString
  333. * @return array
  334. */
  335. private function getReadPreferenceTags($readPreferenceTagString)
  336. {
  337. $tagSets = [];
  338. foreach (explode(',', $readPreferenceTagString) as $index => $tagSet) {
  339. $tags = explode(':', $tagSet);
  340. $tagSets[$tags[0]] = $tags[1];
  341. }
  342. return $tagSets;
  343. }
  344. /**
  345. * @param string $server
  346. * @param array $options
  347. */
  348. private function applyConnectionOptions($server, array $options)
  349. {
  350. $urlOptions = $this->extractUrlOptions($server);
  351. if (isset($urlOptions['wTimeout'])) {
  352. $urlOptions['wTimeoutMS'] = $urlOptions['wTimeout'];
  353. unset($urlOptions['wTimeout']);
  354. }
  355. if (isset($options['wTimeout'])) {
  356. $options['wTimeoutMS'] = $options['wTimeout'];
  357. unset($options['wTimeout']);
  358. }
  359. if (isset($options['readPreferenceTags'])) {
  360. $options['readPreferenceTags'] = [$this->getReadPreferenceTags($options['readPreferenceTags'])];
  361. // Special handling for readPreferenceTags which are merged
  362. if (isset($urlOptions['readPreferenceTags'])) {
  363. $options['readPreferenceTags'] = array_merge($urlOptions['readPreferenceTags'], $options['readPreferenceTags']);
  364. }
  365. }
  366. $urlOptions = array_merge($urlOptions, $options);
  367. if (isset($urlOptions['slaveOkay'])) {
  368. $this->setReadPreferenceFromSlaveOkay($urlOptions['slaveOkay']);
  369. } elseif (isset($urlOptions['readPreference']) || isset($urlOptions['readPreferenceTags'])) {
  370. $readPreference = isset($urlOptions['readPreference']) ? $urlOptions['readPreference'] : null;
  371. $tags = isset($urlOptions['readPreferenceTags']) ? $urlOptions['readPreferenceTags'] : null;
  372. $this->setReadPreferenceFromParameters($readPreference, $tags);
  373. }
  374. if (isset($urlOptions['w']) || isset($urlOptions['wTimeoutMs'])) {
  375. $writeConcern = (isset($urlOptions['w'])) ? $urlOptions['w'] : 1;
  376. $wTimeout = (isset($urlOptions['wTimeoutMs'])) ? $urlOptions['wTimeoutMs'] : null;
  377. $this->setWriteConcern($writeConcern, $wTimeout);
  378. }
  379. }
  380. }