db.php 28 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178
  1. <?php
  2. /**
  3. * Part of the Fuel framework.
  4. *
  5. * @package Fuel
  6. * @version 1.5
  7. * @author Fuel Development Team
  8. * @license MIT License
  9. * @copyright 2010 - 2013 Fuel Development Team
  10. * @link http://fuelphp.com
  11. */
  12. namespace Fuel\Core;
  13. /**
  14. * This code is based on Redisent, a Redis interface for the modest.
  15. *
  16. * It has been modified to work with Fuel and to improve the code slightly.
  17. *
  18. * @author Justin Poliey <[email protected]>
  19. * @copyright 2009 Justin Poliey <[email protected]>
  20. * @modified Alex Bilbie
  21. * @modified Phil Sturgeon
  22. * @license http://www.opensource.org/licenses/mit-license.php The MIT License
  23. */
  24. class Mongo_DbException extends \FuelException {}
  25. class Mongo_Db
  26. {
  27. /**
  28. * Holds the current Mongo connection object
  29. *
  30. * @var Mongo
  31. */
  32. protected $connection = false;
  33. /**
  34. * Holds the current DB reference on the connection object
  35. *
  36. * @var Object
  37. */
  38. protected $db;
  39. /**
  40. * Whether to use a persistent connection
  41. *
  42. * @var bool
  43. */
  44. protected $persist = false;
  45. /**
  46. * Whether to use the profiler
  47. *
  48. * @var bool
  49. */
  50. protected $profiling = false;
  51. /**
  52. * Holds all the select options
  53. *
  54. * @var array
  55. */
  56. protected $selects = array();
  57. /**
  58. * Holds all the where options.
  59. *
  60. * @var array
  61. */
  62. public $wheres = array();
  63. /**
  64. * Holds the sorting options
  65. *
  66. * @var array
  67. */
  68. protected $sorts = array();
  69. /**
  70. * Holds the limit of the number of results to return
  71. *
  72. * @var int
  73. */
  74. protected $limit = 999999;
  75. /**
  76. * The offset to start from.
  77. *
  78. * @var int
  79. */
  80. protected $offset = 0;
  81. /**
  82. * All the Mongo_Db instances
  83. *
  84. * @var array
  85. */
  86. protected static $instances = array();
  87. /**
  88. * Acts as a Multiton. Will return the requested instance, or will create
  89. * a new one if it does not exist.
  90. *
  91. * @param string $name The instance name
  92. * @return Mongo_Db
  93. */
  94. public static function instance($name = 'default')
  95. {
  96. if (\array_key_exists($name, static::$instances))
  97. {
  98. return static::$instances[$name];
  99. }
  100. if (empty(static::$instances))
  101. {
  102. \Config::load('db', true);
  103. }
  104. if ( ! ($config = \Config::get('db.mongo.'.$name)))
  105. {
  106. throw new \Mongo_DbException('Invalid instance name given.');
  107. }
  108. static::$instances[$name] = new static($config);
  109. return static::$instances[$name];
  110. }
  111. /**
  112. * The class constructor
  113. * Automatically check if the Mongo PECL extension has been installed/enabled.
  114. * Generate the connection string and establish a connection to the MongoDB.
  115. *
  116. * @param array $config an array of config values
  117. */
  118. public function __construct(array $config = array())
  119. {
  120. if ( ! class_exists('Mongo'))
  121. {
  122. throw new \Mongo_DbException("The MongoDB PECL extension has not been installed or enabled");
  123. }
  124. // Build up a connect options array for mongo
  125. $options = array("connect" => true);
  126. if ( ! empty($config['persistent']))
  127. {
  128. $options['persist'] = 'fuel_mongo_persist';
  129. }
  130. if ( ! empty($config['replicaset']))
  131. {
  132. $options['replicaSet'] = $config['replicaset'];
  133. }
  134. $connection_string = "mongodb://";
  135. if (empty($config['hostname']))
  136. {
  137. throw new \Mongo_DbException("The host must be set to connect to MongoDB");
  138. }
  139. if (empty($config['database']))
  140. {
  141. throw new \Mongo_DbException("The database must be set to connect to MongoDB");
  142. }
  143. if ( ! empty($config['username']) and ! empty($config['password']))
  144. {
  145. $connection_string .= "{$config['username']}:{$config['password']}@";
  146. }
  147. if (isset($config['port']) and ! empty($config['port']))
  148. {
  149. $connection_string .= "{$config['hostname']}:{$config['port']}";
  150. }
  151. else
  152. {
  153. $connection_string .= "{$config['hostname']}";
  154. }
  155. if (\Arr::get($config, 'profiling') === true)
  156. {
  157. $this->profiling = true;
  158. }
  159. $connection_string .= "/{$config['database']}";
  160. // Let's give this a go
  161. try
  162. {
  163. $this->connection = new \Mongo(trim($connection_string), $options);
  164. $this->db = $this->connection->{$config['database']};
  165. return $this;
  166. }
  167. catch (\MongoConnectionException $e)
  168. {
  169. throw new \Mongo_DbException("Unable to connect to MongoDB: {$e->getMessage()}");
  170. }
  171. }
  172. /**
  173. * Drop a Mongo database
  174. *
  175. * @param string $database the database name
  176. * @usage $mongodb->drop_db("foobar");
  177. */
  178. public static function drop_db($database = null)
  179. {
  180. if (empty($database))
  181. {
  182. throw new \Mongo_DbException('Failed to drop MongoDB database because name is empty');
  183. }
  184. else
  185. {
  186. try
  187. {
  188. static::instance()->connection->{$database}->drop();
  189. return true;
  190. }
  191. catch (\Exception $e)
  192. {
  193. throw new \Mongo_DbException("Unable to drop Mongo database `{$database}`: {$e->getMessage()}");
  194. }
  195. }
  196. }
  197. /**
  198. * Drop a Mongo collection
  199. *
  200. * @param string $db the database name
  201. * @param string $col the collection name
  202. * @usage $mongodb->drop_collection('foo', 'bar');
  203. */
  204. public static function drop_collection($db = '', $col = '')
  205. {
  206. if (empty($db))
  207. {
  208. throw new \Mongo_DbException('Failed to drop MongoDB collection because database name is empty');
  209. }
  210. if (empty($col))
  211. {
  212. throw new \Mongo_DbException('Failed to drop MongoDB collection because collection name is empty');
  213. }
  214. else
  215. {
  216. try
  217. {
  218. static::instance()->connection->{$db}->{$col}->drop();
  219. return true;
  220. }
  221. catch (\Exception $e)
  222. {
  223. throw new \Mongo_DbException("Unable to drop Mongo collection `{$col}`: {$e->getMessage()}");
  224. }
  225. }
  226. }
  227. /**
  228. * Determine which fields to include OR which to exclude during the query process.
  229. * Currently, including and excluding at the same time is not available, so the
  230. * $includes array will take precedence over the $excludes array. If you want to
  231. * only choose fields to exclude, leave $includes an empty array().
  232. *
  233. * @param array $includes which fields to include
  234. * @param array $excludes which fields to exclude
  235. * @usage $mongodb->select(array('foo', 'bar'))->get('foobar');
  236. */
  237. public function select($includes = array(), $excludes = array())
  238. {
  239. if ( ! is_array($includes))
  240. {
  241. $includes = array($includes);
  242. }
  243. if ( ! is_array($excludes))
  244. {
  245. $excludes = array($excludes);
  246. }
  247. if ( ! empty($includes))
  248. {
  249. foreach ($includes as $col)
  250. {
  251. $this->selects[$col] = 1;
  252. }
  253. }
  254. else
  255. {
  256. foreach ($excludes as $col)
  257. {
  258. $this->selects[$col] = 0;
  259. }
  260. }
  261. return $this;
  262. }
  263. /**
  264. * Get the documents based on these search parameters. The $wheres array should
  265. * be an associative array with the field as the key and the value as the search
  266. * criteria.
  267. *
  268. * @param array $wheres an associative array with conditions, array(field => value)
  269. * @usage $mongodb->where(array('foo' => 'bar'))->get('foobar');
  270. */
  271. public function where($wheres = array())
  272. {
  273. foreach ($wheres as $wh => $val)
  274. {
  275. $this->wheres[$wh] = $val;
  276. }
  277. return $this;
  278. }
  279. /**
  280. * Get the documents where the value of a $field may be something else
  281. *
  282. * @param array $wheres an associative array with conditions, array(field => value)
  283. * @usage $mongodb->or_where(array( array('foo'=>'bar', 'bar'=>'foo' ))->get('foobar');
  284. */
  285. public function or_where($wheres = array())
  286. {
  287. if (count($wheres) > 0)
  288. {
  289. if ( ! isset($this->wheres['$or']) or ! is_array($this->wheres['$or']))
  290. {
  291. $this->wheres['$or'] = array();
  292. }
  293. foreach ($wheres as $wh => $val)
  294. {
  295. $this->wheres['$or'][] = array($wh => $val);
  296. }
  297. }
  298. return $this;
  299. }
  300. /**
  301. * Get the documents where the value of a $field is in a given $in array().
  302. *
  303. * @param string $field the field name
  304. * @param array $in an array of values to compare to
  305. * @usage $mongodb->where_in('foo', array('bar', 'zoo', 'blah'))->get('foobar');
  306. */
  307. public function where_in($field = '', $in = array())
  308. {
  309. $this->_where_init($field);
  310. $this->wheres[$field]['$in'] = $in;
  311. return $this;
  312. }
  313. /**
  314. * Get the documents where the value of a $field is in all of a given $in array().
  315. *
  316. * @param string $field the field name
  317. * @param array $in an array of values to compare to
  318. * @usage $mongodb->where_in('foo', array('bar', 'zoo', 'blah'))->get('foobar');
  319. */
  320. public function where_in_all($field = '', $in = array())
  321. {
  322. $this->_where_init($field);
  323. $this->wheres[$field]['$all'] = $in;
  324. return $this;
  325. }
  326. /**
  327. * Get the documents where the value of a $field is not in a given $in array().
  328. *
  329. * @param string $field the field name
  330. * @param array $in an array of values to compare to
  331. * @usage $mongodb->where_not_in('foo', array('bar', 'zoo', 'blah'))->get('foobar');
  332. */
  333. public function where_not_in($field = '', $in = array())
  334. {
  335. $this->_where_init($field);
  336. $this->wheres[$field]['$nin'] = $in;
  337. return $this;
  338. }
  339. /**
  340. * Get the documents where the value of a $field is greater than $x
  341. *
  342. * @param string $field the field name
  343. * @param mixed $x the value to compare to
  344. * @usage $mongodb->where_gt('foo', 20);
  345. */
  346. public function where_gt($field = '', $x)
  347. {
  348. $this->_where_init($field);
  349. $this->wheres[$field]['$gt'] = $x;
  350. return $this;
  351. }
  352. /**
  353. * Get the documents where the value of a $field is greater than or equal to $x
  354. *
  355. * @param string $field the field name
  356. * @param mixed $x the value to compare to
  357. * @usage $mongodb->where_gte('foo', 20);
  358. */
  359. public function where_gte($field = '', $x)
  360. {
  361. $this->_where_init($field);
  362. $this->wheres[$field]['$gte'] = $x;
  363. return($this);
  364. }
  365. /**
  366. * Get the documents where the value of a $field is less than $x
  367. *
  368. * @param string $field the field name
  369. * @param mixed $x the value to compare to
  370. * @usage $mongodb->where_lt('foo', 20);
  371. */
  372. public function where_lt($field = '', $x)
  373. {
  374. $this->_where_init($field);
  375. $this->wheres[$field]['$lt'] = $x;
  376. return($this);
  377. }
  378. /**
  379. * Get the documents where the value of a $field is less than or equal to $x
  380. *
  381. * @param string $field the field name
  382. * @param mixed $x the value to compare to
  383. * @usage $mongodb->where_lte('foo', 20);
  384. */
  385. public function where_lte($field = '', $x)
  386. {
  387. $this->_where_init($field);
  388. $this->wheres[$field]['$lte'] = $x;
  389. return $this;
  390. }
  391. /**
  392. * Get the documents where the value of a $field is between $x and $y
  393. *
  394. * @param string $field the field name
  395. * @param mixed $x the value to compare to
  396. * @param mixed $y the high value to compare to
  397. * @usage $mongodb->where_between('foo', 20, 30);
  398. */
  399. public function where_between($field = '', $x, $y)
  400. {
  401. $this->_where_init($field);
  402. $this->wheres[$field]['$gte'] = $x;
  403. $this->wheres[$field]['$lte'] = $y;
  404. return $this;
  405. }
  406. /**
  407. * Get the documents where the value of a $field is between but not equal to $x and $y
  408. *
  409. * @param string $field the field name
  410. * @param mixed $x the low value to compare to
  411. * @param mixed $y the high value to compare to
  412. * @usage $mongodb->where_between_ne('foo', 20, 30);
  413. */
  414. public function where_between_ne($field = '', $x, $y)
  415. {
  416. $this->_where_init($field);
  417. $this->wheres[$field]['$gt'] = $x;
  418. $this->wheres[$field]['$lt'] = $y;
  419. return $this;
  420. }
  421. /**
  422. * Get the documents where the value of a $field is not equal to $x
  423. *
  424. * @param string $field the field name
  425. * @param mixed $x the value to compare to
  426. * @usage $mongodb->where_not_equal('foo', 1)->get('foobar');
  427. */
  428. public function where_ne($field = '', $x)
  429. {
  430. $this->_where_init($field);
  431. $this->wheres[$field]['$ne'] = $x;
  432. return $this;
  433. }
  434. /**
  435. * Get the documents nearest to an array of coordinates (your collection must have a geospatial index)
  436. *
  437. * @param string $field the field name
  438. * @param array $co array of 2 coordinates
  439. * @usage $mongodb->where_near('foo', array('50','50'))->get('foobar');
  440. */
  441. public function where_near($field = '', $co = array())
  442. {
  443. $this->_where_init($field);
  444. $this->where[$field]['$near'] = $co;
  445. return $this;
  446. }
  447. /**
  448. * --------------------------------------------------------------------------------
  449. * LIKE PARAMETERS
  450. * --------------------------------------------------------------------------------
  451. *
  452. * Get the documents where the (string) value of a $field is like a value. The defaults
  453. * allow for a case-insensitive search.
  454. *
  455. * @param $flags
  456. * Allows for the typical regular expression flags:
  457. * i = case insensitive
  458. * m = multiline
  459. * x = can contain comments
  460. * l = locale
  461. * s = dotall, "." matches everything, including newlines
  462. * u = match unicode
  463. *
  464. * @param $enable_start_wildcard
  465. * If set to anything other than TRUE, a starting line character "^" will be prepended
  466. * to the search value, representing only searching for a value at the start of
  467. * a new line.
  468. *
  469. * @param $enable_end_wildcard
  470. * If set to anything other than TRUE, an ending line character "$" will be appended
  471. * to the search value, representing only searching for a value at the end of
  472. * a line.
  473. *
  474. * @usage $mongodb->like('foo', 'bar', 'im', false, TRUE);
  475. */
  476. public function like($field = '', $value = '', $flags = 'i', $enable_start_wildcard = TRUE, $enable_end_wildcard = TRUE)
  477. {
  478. $field = (string) trim($field);
  479. $this->_where_init($field);
  480. $value = (string) trim($value);
  481. $value = quotemeta($value);
  482. if ($enable_start_wildcard !== TRUE)
  483. {
  484. $value = '^' . $value;
  485. }
  486. if ($enable_end_wildcard !== TRUE)
  487. {
  488. $value .= '$';
  489. }
  490. $regex = "/$value/$flags";
  491. $this->wheres[$field] = new \MongoRegex($regex);
  492. return $this;
  493. }
  494. /**
  495. * Sort the documents based on the parameters passed. To set values to descending order,
  496. * you must pass values of either -1, false, 'desc', or 'DESC', else they will be
  497. * set to 1 (ASC).
  498. *
  499. * @param array $fields an associative array, array(field => direction)
  500. * @usage $mongodb->where_between('foo', 20, 30);
  501. */
  502. public function order_by($fields = array())
  503. {
  504. foreach ($fields as $col => $val)
  505. {
  506. if ($val == -1 or $val === false or strtolower($val) == 'desc')
  507. {
  508. $this->sorts[$col] = -1;
  509. }
  510. else
  511. {
  512. $this->sorts[$col] = 1;
  513. }
  514. }
  515. return $this;
  516. }
  517. /**
  518. * Limit the result set to $x number of documents
  519. *
  520. * @param number $x the max amount of documents to fetch
  521. * @usage $mongodb->limit($x);
  522. */
  523. public function limit($x = 99999)
  524. {
  525. if ($x !== null and is_numeric($x) and $x >= 1)
  526. {
  527. $this->limit = (int) $x;
  528. }
  529. return $this;
  530. }
  531. /**
  532. * --------------------------------------------------------------------------------
  533. * OFFSET DOCUMENTS
  534. * --------------------------------------------------------------------------------
  535. *
  536. * Offset the result set to skip $x number of documents
  537. *
  538. * @param number $x the number of documents to skip
  539. * @usage $mongodb->offset($x);
  540. */
  541. public function offset($x = 0)
  542. {
  543. if ($x !== null and is_numeric($x) and $x >= 1)
  544. {
  545. $this->offset = (int) $x;
  546. }
  547. return $this;
  548. }
  549. /**
  550. * Get the documents based upon the passed parameters
  551. *
  552. * @param string $collection the collection name
  553. * @param array $where an array of conditions, array(field => value)
  554. * @param number $limit the max amount of documents to fetch
  555. * @usage $mongodb->get_where('foo', array('bar' => 'something'));
  556. */
  557. public function get_where($collection = '', $where = array(), $limit = 99999)
  558. {
  559. return ($this->where($where)->limit($limit)->get($collection));
  560. }
  561. /**
  562. * Get the document cursor from mongodb based upon the passed parameters
  563. *
  564. * @param string $collection the collection name
  565. * @usage $mongodb->get_cursor('foo', array('bar' => 'something'));
  566. */
  567. public function get_cursor($collection = "")
  568. {
  569. if (empty($collection))
  570. {
  571. throw new \Mongo_DbException("In order to retrieve documents from MongoDB you must provide a collection name.");
  572. }
  573. $documents = $this->db->{$collection}->find($this->wheres, $this->selects)->limit((int) $this->limit)->skip((int) $this->offset)->sort($this->sorts);
  574. $this->_clear();
  575. return $documents;
  576. }
  577. /**
  578. * Get the documents based upon the passed parameters
  579. *
  580. * @param string $collection the collection name
  581. * @usage $mongodb->get('foo', array('bar' => 'something'));
  582. */
  583. public function get($collection = "")
  584. {
  585. if ($this->profiling)
  586. {
  587. $query = json_encode(array(
  588. 'type' => 'find',
  589. 'collection' => $collection,
  590. 'select' => $this->selects,
  591. 'where' => $this->wheres,
  592. 'limit' => $this->limit,
  593. 'offset' => $this->offset,
  594. 'sort' => $this->sorts,
  595. ));
  596. $benchmark = \Profiler::start("Database {$this->db}", $query);
  597. }
  598. $documents = $this->get_cursor($collection);
  599. if (isset($benchmark))
  600. {
  601. \Profiler::stop($benchmark);
  602. }
  603. $returns = array();
  604. if ($documents and ! empty($documents))
  605. {
  606. foreach ($documents as $doc)
  607. {
  608. $returns[] = $doc;
  609. }
  610. }
  611. return $returns;
  612. }
  613. /**
  614. * Get one document based upon the passed parameters
  615. *
  616. * @param string $collection the collection name
  617. * @usage $mongodb->get_one('foo');
  618. */
  619. public function get_one($collection = "")
  620. {
  621. if (empty($collection))
  622. {
  623. throw new \Mongo_DbException("In order to retrieve documents from MongoDB");
  624. }
  625. if ($this->profiling)
  626. {
  627. $query = json_encode(array(
  628. 'type' => 'findOne',
  629. 'collection' => $collection,
  630. 'select' => $this->selects,
  631. 'where' => $this->wheres,
  632. ));
  633. $benchmark = \Profiler::start("Database {$this->db}", $query);
  634. }
  635. $returns = $this->db->{$collection}->findOne($this->wheres, $this->selects);
  636. if (isset($benchmark))
  637. {
  638. \Profiler::stop($benchmark);
  639. }
  640. $this->_clear();
  641. return $returns;
  642. }
  643. /**
  644. * Count the documents based upon the passed parameters
  645. *
  646. * @param string $collection the collection name
  647. * @param boolean $foundonly send cursor limit and skip information to the count function, if applicable.
  648. * @usage $mongodb->count('foo');
  649. */
  650. public function count($collection = '', $foundonly = false)
  651. {
  652. if (empty($collection))
  653. {
  654. throw new \Mongo_DbException("In order to retrieve a count of documents from MongoDB");
  655. }
  656. if ($this->profiling)
  657. {
  658. $query = json_encode(array(
  659. 'type' => 'count',
  660. 'collection' => $collection,
  661. 'where' => $this->wheres,
  662. 'limit' => $this->limit,
  663. 'offset' => $this->offset,
  664. ));
  665. $benchmark = \Profiler::start("Database {$this->db}", $query);
  666. }
  667. $count = $this->db->{$collection}->find($this->wheres)->limit((int) $this->limit)->skip((int) $this->offset)->count($foundonly);
  668. if (isset($benchmark))
  669. {
  670. \Profiler::stop($benchmark);
  671. }
  672. $this->_clear();
  673. return ($count);
  674. }
  675. /**
  676. * --------------------------------------------------------------------------------
  677. * INSERT
  678. * --------------------------------------------------------------------------------
  679. *
  680. * Insert a new document into the passed collection
  681. *
  682. * @param string $collection the collection name
  683. * @param array $insert an array of values to insert, array(field => value)
  684. * @usage $mongodb->insert('foo', $data = array());
  685. */
  686. public function insert($collection = '', $insert = array())
  687. {
  688. if (empty($collection))
  689. {
  690. throw new \Mongo_DbException("No Mongo collection selected to insert into");
  691. }
  692. if (empty($insert) or ! is_array($insert))
  693. {
  694. throw new \Mongo_DbException("Nothing to insert into Mongo collection or insert is not an array");
  695. }
  696. try
  697. {
  698. if ($this->profiling)
  699. {
  700. $query = json_encode(array(
  701. 'type' => 'insert',
  702. 'collection' => $collection,
  703. 'payload' => $insert,
  704. ));
  705. $benchmark = \Profiler::start("Database {$this->db}", $query);
  706. }
  707. $this->db->{$collection}->insert($insert, array('fsync' => true));
  708. if (isset($benchmark))
  709. {
  710. \Profiler::stop($benchmark);
  711. }
  712. if (isset($insert['_id']))
  713. {
  714. return $insert['_id'];
  715. }
  716. else
  717. {
  718. return false;
  719. }
  720. }
  721. catch (\MongoCursorException $e)
  722. {
  723. throw new \Mongo_DbException("Insert of data into MongoDB failed: {$e->getMessage()}");
  724. }
  725. }
  726. /**
  727. * Updates a single document
  728. *
  729. * @param string $collection the collection name
  730. * @param array $data an associative array of values, array(field => value)
  731. * @param array $options an associative array of options
  732. * @usage $mongodb->update('foo', $data = array());
  733. */
  734. public function update($collection = '', $data = array(), $options = array(), $literal = false)
  735. {
  736. if (empty($collection))
  737. {
  738. throw new \Mongo_DbException("No Mongo collection selected to update");
  739. }
  740. if (empty($data) or ! is_array($data))
  741. {
  742. throw new \Mongo_DbException("Nothing to update in Mongo collection or update is not an array");
  743. }
  744. try
  745. {
  746. $options = array_merge($options, array('fsync' => true, 'multiple' => false));
  747. if ($this->profiling)
  748. {
  749. $query = json_encode(array(
  750. 'type' => 'update',
  751. 'collection' => $collection,
  752. 'where' => $this->wheres,
  753. 'payload' => $data,
  754. 'options' => $options,
  755. ));
  756. $benchmark = \Profiler::start("Database {$this->db}", $query);
  757. }
  758. $this->db->{$collection}->update($this->wheres, (($literal) ? $data : array('$set' => $data)), $options);
  759. if (isset($benchmark))
  760. {
  761. \Profiler::stop($benchmark);
  762. }
  763. $this->_clear();
  764. return true;
  765. }
  766. catch (\MongoCursorException $e)
  767. {
  768. throw new \Mongo_DbException("Update of data into MongoDB failed: {$e->getMessage()}");
  769. }
  770. }
  771. /**
  772. * Updates a collection of documents
  773. *
  774. * @param string $collection the collection name
  775. * @param array $data an associative array of values, array(field => value)
  776. * @usage $mongodb->update_all('foo', $data = array());
  777. */
  778. public function update_all($collection = "", $data = array(), $literal = false)
  779. {
  780. if (empty($collection))
  781. {
  782. throw new \Mongo_DbException("No Mongo collection selected to update");
  783. }
  784. if (empty($data) or ! is_array($data))
  785. {
  786. throw new \Mongo_DbException("Nothing to update in Mongo collection or update is not an array");
  787. }
  788. try
  789. {
  790. if ($this->profiling)
  791. {
  792. $query = json_encode(array(
  793. 'type' => 'updateAll',
  794. 'collection' => $collection,
  795. 'where' => $this->wheres,
  796. 'payload' => $data,
  797. 'literal' => $literal,
  798. ));
  799. $benchmark = \Profiler::start("Database {$this->db}", $query);
  800. }
  801. $this->db->{$collection}->update($this->wheres, (($literal) ? $data : array('$set' => $data)), array('fsync' => true, 'multiple' => true));
  802. if (isset($benchmark))
  803. {
  804. \Profiler::stop($benchmark);
  805. }
  806. $this->_clear();
  807. return true;
  808. }
  809. catch (\MongoCursorException $e)
  810. {
  811. throw new \Mongo_DbException("Update of data into MongoDB failed: {$e->getMessage()}");
  812. }
  813. }
  814. /**
  815. * Delete a document from the passed collection based upon certain criteria
  816. *
  817. * @param string $collection the collection name
  818. * @usage $mongodb->delete('foo');
  819. */
  820. public function delete($collection = '')
  821. {
  822. if (empty($collection))
  823. {
  824. throw new \Mongo_DbException("No Mongo collection selected to delete from");
  825. }
  826. try
  827. {
  828. if ($this->profiling)
  829. {
  830. $query = json_encode(array(
  831. 'type' => 'delete',
  832. 'collection' => $collection,
  833. 'where' => $this->wheres,
  834. ));
  835. $benchmark = \Profiler::start("Database {$this->db}", $query);
  836. }
  837. $this->db->{$collection}->remove($this->wheres, array('fsync' => true, 'justOne' => true));
  838. if (isset($benchmark))
  839. {
  840. \Profiler::stop($benchmark);
  841. }
  842. $this->_clear();
  843. return true;
  844. }
  845. catch (\MongoCursorException $e)
  846. {
  847. throw new \Mongo_DbException("Delete of data into MongoDB failed: {$e->getMessage()}");
  848. }
  849. }
  850. /**
  851. * Delete all documents from the passed collection based upon certain criteria.
  852. *
  853. * @param string $collection the collection name
  854. * @usage $mongodb->delete_all('foo');
  855. */
  856. public function delete_all($collection = '')
  857. {
  858. if (empty($collection))
  859. {
  860. throw new \Mongo_DbException("No Mongo collection selected to delete from");
  861. }
  862. try
  863. {
  864. if ($this->profiling)
  865. {
  866. $query = json_encode(array(
  867. 'type' => 'deleteAll',
  868. 'collection' => $collection,
  869. 'where' => $this->wheres,
  870. ));
  871. $benchmark = \Profiler::start("Database {$this->db}", $query);
  872. }
  873. $this->db->{$collection}->remove($this->wheres, array('fsync' => true, 'justOne' => false));
  874. if (isset($benchmark))
  875. {
  876. \Profiler::stop($benchmark);
  877. }
  878. $this->_clear();
  879. return true;
  880. }
  881. catch (\MongoCursorException $e)
  882. {
  883. throw new \Mongo_DbException("Delete of data into MongoDB failed: {$e->getMessage()}");
  884. }
  885. }
  886. /**
  887. * Runs a MongoDB command (such as GeoNear). See the MongoDB documentation for more usage scenarios:
  888. * http://dochub.mongodb.org/core/commands
  889. *
  890. * @param array $query a query array
  891. * @usage $mongodb->command(array('geoNear'=>'buildings', 'near'=>array(53.228482, -0.547847), 'num' => 10, 'nearSphere'=>TRUE));
  892. */
  893. public function command($query = array())
  894. {
  895. try
  896. {
  897. $run = $this->db->command($query);
  898. return $run;
  899. }
  900. catch (\MongoCursorException $e)
  901. {
  902. throw new \Mongo_DbException("MongoDB command failed to execute: {$e->getMessage()}");
  903. }
  904. }
  905. /**
  906. * Ensure an index of the keys in a collection with optional parameters. To set values to descending order,
  907. * you must pass values of either -1, false, 'desc', or 'DESC', else they will be
  908. * set to 1 (ASC).
  909. *
  910. * @param string $collection the collection name
  911. * @param array $keys an associative array of keys, array(field => direction)
  912. * @param array $options an associative array of options
  913. * @usage $mongodb->add_index($collection, array('first_name' => 'ASC', 'last_name' => -1), array('unique' => TRUE));
  914. */
  915. public function add_index($collection = '', $keys = array(), $options = array())
  916. {
  917. if (empty($collection))
  918. {
  919. throw new \Mongo_DbException("No Mongo collection specified to add index to");
  920. }
  921. if (empty($keys) or ! is_array($keys))
  922. {
  923. throw new \Mongo_DbException("Index could not be created to MongoDB Collection because no keys were specified");
  924. }
  925. foreach ($keys as $col => $val)
  926. {
  927. if($val == -1 or $val === false or strtolower($val) == 'desc')
  928. {
  929. $keys[$col] = -1;
  930. }
  931. else
  932. {
  933. $keys[$col] = 1;
  934. }
  935. }
  936. if ($this->db->{$collection}->ensureIndex($keys, $options) == true)
  937. {
  938. $this->_clear();
  939. return $this;
  940. }
  941. else
  942. {
  943. throw new \Mongo_DbException("An error occured when trying to add an index to MongoDB Collection");
  944. }
  945. }
  946. /**
  947. * Remove an index of the keys in a collection. To set values to descending order,
  948. * you must pass values of either -1, false, 'desc', or 'DESC', else they will be
  949. * set to 1 (ASC).
  950. *
  951. * @param string $collection the collection name
  952. * @param array $keys an associative array of keys, array(field => direction)
  953. * @usage $mongodb->remove_index($collection, array('first_name' => 'ASC', 'last_name' => -1));
  954. */
  955. public function remove_index($collection = '', $keys = array())
  956. {
  957. if (empty($collection))
  958. {
  959. throw new \Mongo_DbException("No Mongo collection specified to remove index from");
  960. }
  961. if (empty($keys) or ! is_array($keys))
  962. {
  963. throw new \Mongo_DbException("Index could not be removed from MongoDB Collection because no keys were specified");
  964. }
  965. if ($this->db->{$collection}->deleteIndex($keys) == true)
  966. {
  967. $this->_clear();
  968. return $this;
  969. }
  970. else
  971. {
  972. throw new \Mongo_DbException("An error occured when trying to remove an index from MongoDB Collection");
  973. }
  974. }
  975. /**
  976. * Remove all indexes from a collection.
  977. *
  978. * @param string $collection the collection name
  979. * @usage $mongodb->remove_all_index($collection);
  980. */
  981. public function remove_all_indexes($collection = '')
  982. {
  983. if (empty($collection))
  984. {
  985. throw new \Mongo_DbException("No Mongo collection specified to remove all indexes from");
  986. }
  987. $this->db->{$collection}->deleteIndexes();
  988. $this->_clear();
  989. return $this;
  990. }
  991. /**
  992. * Lists all indexes in a collection.
  993. *
  994. * @param string $collection the collection name
  995. * @usage $mongodb->list_indexes($collection);
  996. */
  997. public function list_indexes($collection = '')
  998. {
  999. if (empty($collection))
  1000. {
  1001. throw new \Mongo_DbException("No Mongo collection specified to remove all indexes from");
  1002. }
  1003. return ($this->db->{$collection}->getIndexInfo());
  1004. }
  1005. /**
  1006. * Returns a collection object so you can perform advanced queries, upserts, pushes and addtosets
  1007. *
  1008. * @param string $collection the collection name
  1009. * @usage $collection_name = $mongodb->get_collection('collection_name');
  1010. */
  1011. public function get_collection($collection)
  1012. {
  1013. return ($this->db->{$collection});
  1014. }
  1015. /**
  1016. * Resets the class variables to default settings
  1017. */
  1018. protected function _clear()
  1019. {
  1020. $this->selects = array();
  1021. $this->wheres = array();
  1022. $this->limit = 999999;
  1023. $this->offset = 0;
  1024. $this->sorts = array();
  1025. }
  1026. /**
  1027. * Prepares parameters for insertion in $wheres array().
  1028. *
  1029. * @param string $param the field name
  1030. */
  1031. protected function _where_init($param)
  1032. {
  1033. if ( ! isset($this->wheres[$param]))
  1034. {
  1035. $this->wheres[ $param ] = array();
  1036. }
  1037. }
  1038. }