PageRenderTime 29ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/Model/Datasource/MongodbSource.php

http://github.com/ichikaway/cakephp-mongodb
PHP | 1615 lines | 945 code | 162 blank | 508 comment | 221 complexity | d5463096ad4134013dd25b8eb8b879c3 MD5 | raw file
  1. <?php
  2. /**
  3. * A CakePHP datasource for the mongoDB (http://www.mongodb.org/) document-oriented database.
  4. *
  5. * This datasource uses Pecl Mongo (http://php.net/mongo)
  6. * and is thus dependent on PHP 5.0 and greater.
  7. *
  8. * Original implementation by ichikaway(Yasushi Ichikawa) http://github.com/ichikaway/
  9. *
  10. * Reference:
  11. * Nate Abele's lithium mongoDB datasource (http://li3.rad-dev.org/)
  12. * Joél Perras' divan(http://github.com/jperras/divan/)
  13. *
  14. * Copyright 2010, Yasushi Ichikawa http://github.com/ichikaway/
  15. *
  16. * Contributors: Predominant, Jrbasso, tkyk, AD7six
  17. *
  18. * Licensed under The MIT License
  19. * Redistributions of files must retain the above copyright notice.
  20. *
  21. * @copyright Copyright 2010, Yasushi Ichikawa http://github.com/ichikaway/
  22. * @package mongodb
  23. * @subpackage mongodb.models.datasources
  24. * @license http://www.opensource.org/licenses/mit-license.php The MIT License
  25. */
  26. App::uses('DboSource', 'Model/Datasource');
  27. App::uses('SchemalessBehavior', 'Mongodb.Model/Behavior');
  28. /**
  29. * MongoDB Source
  30. *
  31. * @package mongodb
  32. * @subpackage mongodb.models.datasources
  33. */
  34. class MongodbSource extends DboSource {
  35. /**
  36. * Are we connected to the DataSource?
  37. *
  38. * true - yes
  39. * null - haven't tried yet
  40. * false - nope, and we can't connect
  41. *
  42. * @var boolean
  43. * @access public
  44. */
  45. public $connected = null;
  46. /**
  47. * Database Instance
  48. *
  49. * @var resource
  50. * @access protected
  51. */
  52. protected $_db = null;
  53. /**
  54. * Mongo Driver Version
  55. *
  56. * @var string
  57. * @access protected
  58. */
  59. protected $_driverVersion = Mongo::VERSION;
  60. /**
  61. * startTime property
  62. *
  63. * If debugging is enabled, stores the (micro)time the current query started
  64. *
  65. * @var mixed null
  66. * @access protected
  67. */
  68. protected $_startTime = null;
  69. /**
  70. * Direct connection with database, isn't the
  71. * same of DboSource::_connection
  72. *
  73. * @var mixed null | Mongo
  74. * @access private
  75. */
  76. public $connection = null;
  77. /**
  78. * Base Config
  79. *
  80. * set_string_id:
  81. * true: In read() method, convert MongoId object to string and set it to array 'id'.
  82. * false: not convert and set.
  83. *
  84. * @var array
  85. * @access public
  86. *
  87. */
  88. public $_baseConfig = array(
  89. 'set_string_id' => true,
  90. 'persistent' => true,
  91. 'host' => 'localhost',
  92. 'database' => '',
  93. 'port' => '27017',
  94. 'login' => '',
  95. 'password' => '',
  96. 'replicaset' => '',
  97. );
  98. /**
  99. * column definition
  100. *
  101. * @var array
  102. */
  103. public $columns = array(
  104. 'boolean' => array('name' => 'boolean'),
  105. 'string' => array('name' => 'varchar'),
  106. 'text' => array('name' => 'text'),
  107. 'integer' => array('name' => 'integer', 'format' => null, 'formatter' => 'intval'),
  108. 'float' => array('name' => 'float', 'format' => null, 'formatter' => 'floatval'),
  109. 'datetime' => array('name' => 'datetime', 'format' => null, 'formatter' => 'MongodbDateFormatter'),
  110. 'timestamp' => array('name' => 'timestamp', 'format' => null, 'formatter' => 'MongodbDateFormatter'),
  111. 'time' => array('name' => 'time', 'format' => null, 'formatter' => 'MongodbDateFormatter'),
  112. 'date' => array('name' => 'date', 'format' => null, 'formatter' => 'MongodbDateFormatter'),
  113. );
  114. /**
  115. * Default schema for the mongo models
  116. *
  117. * @var array
  118. * @access protected
  119. */
  120. protected $_defaultSchema = array(
  121. '_id' => array('type' => 'string', 'length' => 24, 'key' => 'primary'),
  122. 'created' => array('type' => 'datetime', 'default' => null),
  123. 'modified' => array('type' => 'datetime', 'default' => null)
  124. );
  125. /**
  126. * construct method
  127. *
  128. * By default don't try to connect until you need to
  129. *
  130. * @param array $config Configuration array
  131. * @param bool $autoConnect false
  132. * @return void
  133. * @access public
  134. */
  135. function __construct($config = array(), $autoConnect = false) {
  136. return parent::__construct($config, $autoConnect);
  137. }
  138. /**
  139. * Destruct
  140. *
  141. * @access public
  142. */
  143. public function __destruct() {
  144. if ($this->connected) {
  145. $this->disconnect();
  146. }
  147. }
  148. /**
  149. * commit method
  150. *
  151. * MongoDB doesn't support transactions
  152. *
  153. * @return void
  154. * @access public
  155. */
  156. public function commit() {
  157. return false;
  158. }
  159. /**
  160. * Connect to the database
  161. *
  162. * If using 1.0.2 or above use the mongodb:// format to connect
  163. * The connect syntax changed in version 1.0.2 - so check for that too
  164. *
  165. * If authentication information in present then authenticate the connection
  166. *
  167. * @return boolean Connected
  168. * @access public
  169. */
  170. public function connect() {
  171. $this->connected = false;
  172. try{
  173. $host = $this->createConnectionName($this->config, $this->_driverVersion);
  174. $class = 'MongoClient';
  175. if(!class_exists($class)){
  176. $class = 'Mongo';
  177. }
  178. if (isset($this->config['replicaset']) && count($this->config['replicaset']) === 2) {
  179. $this->connection = new $class($this->config['replicaset']['host'], $this->config['replicaset']['options']);
  180. } else if ($this->_driverVersion >= '1.3.0') {
  181. $this->connection = new $class($host);
  182. } else if ($this->_driverVersion >= '1.2.0') {
  183. $this->connection = new $class($host, array("persist" => $this->config['persistent']));
  184. } else {
  185. $this->connection = new $class($host, true, $this->config['persistent']);
  186. }
  187. if (isset($this->config['slaveok'])) {
  188. if (method_exists($this->connection, 'setSlaveOkay')) {
  189. $this->connection->setSlaveOkay($this->config['slaveok']);
  190. } else {
  191. $this->connection->setReadPreference($this->config['slaveok']
  192. ? $class::RP_SECONDARY_PREFERRED : $class::RP_PRIMARY);
  193. }
  194. }
  195. if ($this->_db = $this->connection->selectDB($this->config['database'])) {
  196. if (!empty($this->config['login']) && $this->_driverVersion < '1.2.0') {
  197. $return = $this->_db->authenticate($this->config['login'], $this->config['password']);
  198. if (!$return || !$return['ok']) {
  199. trigger_error('MongodbSource::connect ' . $return['errmsg']);
  200. return false;
  201. }
  202. }
  203. $this->connected = true;
  204. }
  205. } catch(MongoException $e) {
  206. $this->error = $e->getMessage();
  207. trigger_error($this->error);
  208. }
  209. return $this->connected;
  210. }
  211. /**
  212. * create connection name.
  213. *
  214. * @param array $config
  215. * @param string $version version of MongoDriver
  216. */
  217. public function createConnectionName($config, $version) {
  218. $host = null;
  219. if ($version >= '1.0.2') {
  220. $host = "mongodb://";
  221. } else {
  222. $host = '';
  223. }
  224. $hostname = $config['host'] . ':' . $config['port'];
  225. if(!empty($config['login'])){
  226. $host .= $config['login'] .':'. $config['password'] . '@' . $hostname . '/'. $config['database'];
  227. } else {
  228. $host .= $hostname;
  229. }
  230. return $host;
  231. }
  232. /**
  233. * Inserts multiple values into a table
  234. *
  235. * @param string $table
  236. * @param string $fields
  237. * @param array $values
  238. * @access public
  239. */
  240. public function insertMulti($table, $fields, $values) {
  241. $table = $this->fullTableName($table);
  242. if (!is_array($fields) || !is_array($values)) {
  243. return false;
  244. }
  245. $inUse = array_search('id', $fields);
  246. $default = array_search('_id', $fields);
  247. if ($inUse !== false && $default === false) {
  248. $fields[$inUse] = '_id';
  249. }
  250. $values = $this->normalizeValues($table, $fields, $values);
  251. $data = array();
  252. foreach ($values as $row) {
  253. if (is_string($row)) {
  254. $row = explode(', ', substr($row, 1, -1));
  255. }
  256. $data[] = array_combine($fields, $row);
  257. }
  258. $this->_prepareLogQuery($table); // just sets a timer
  259. try{
  260. $return = $this->_db
  261. ->selectCollection($table)
  262. ->batchInsert($data, array('w' => 1));
  263. } catch (MongoException $e) {
  264. $this->error = $e->getMessage();
  265. trigger_error($this->error);
  266. }
  267. if ($this->fullDebug) {
  268. $this->logQuery("db.{$table}.insertMulti( :data , array('w' => 1))", compact('data'));
  269. }
  270. }
  271. public function normalizeValues($table, $fields, $values) {
  272. $Model = ClassRegistry::init(Inflector::classify($table));
  273. foreach ($values as $key => $value) {
  274. foreach ($value as $k => $v) {
  275. switch($Model->mongoSchema[$fields[$k]]['type']) {
  276. case 'datetime':
  277. case 'timestamp':
  278. case 'date':
  279. case 'time':
  280. if (is_string($values[$key][$k])) {
  281. $values[$key][$k] = new MongoDate(strtotime($v));
  282. }
  283. break;
  284. default:
  285. break;
  286. }
  287. }
  288. }
  289. return $values;
  290. }
  291. /**
  292. * check connection to the database
  293. *
  294. * @return boolean Connected
  295. * @access public
  296. */
  297. public function isConnected() {
  298. if ($this->connected === false) {
  299. return false;
  300. }
  301. return $this->connect();
  302. }
  303. /**
  304. * get MongoDB Object
  305. *
  306. * @return mixed MongoDB Object
  307. * @access public
  308. */
  309. public function getMongoDb() {
  310. if ($this->connected === false) {
  311. return false;
  312. }
  313. return $this->_db;
  314. }
  315. /**
  316. * get MongoDB Collection Object
  317. *
  318. * @return mixed MongoDB Collection Object
  319. * @access public
  320. */
  321. public function getMongoCollection(&$Model) {
  322. if ($this->connected === false) {
  323. return false;
  324. }
  325. $table = $this->fullTableName($Model);
  326. $collection = $this->_db
  327. ->selectCollection($table);
  328. return $collection;
  329. }
  330. /**
  331. * isInterfaceSupported method
  332. *
  333. * listSources is infact supported, however: cake expects it to return a complete list of all
  334. * possible sources in the selected db - the possible list of collections is infinte, so it's
  335. * faster and simpler to tell cake that the interface is /not/ supported so it assumes that
  336. * <insert name of your table here> exist
  337. *
  338. * @param mixed $interface
  339. * @return void
  340. * @access public
  341. */
  342. public function isInterfaceSupported($interface) {
  343. if ($interface === 'listSources') {
  344. return false;
  345. }
  346. return parent::isInterfaceSupported($interface);
  347. }
  348. /**
  349. * Close database connection
  350. *
  351. * @return boolean Connected
  352. * @access public
  353. */
  354. public function close() {
  355. return $this->disconnect();
  356. }
  357. /**
  358. * Disconnect from the database
  359. *
  360. * @return boolean Connected
  361. * @access public
  362. */
  363. public function disconnect() {
  364. if ($this->connected) {
  365. $this->connected = !$this->connection->close();
  366. unset($this->_db, $this->connection);
  367. return !$this->connected;
  368. }
  369. return true;
  370. }
  371. /**
  372. * Get list of available Collections
  373. *
  374. * @param array $data
  375. * @return array Collections
  376. * @access public
  377. */
  378. public function listSources($data = null) {
  379. if (!$this->isConnected()) {
  380. return false;
  381. }
  382. return true;
  383. }
  384. /**
  385. * Describe
  386. *
  387. * Automatically bind the schemaless behavior if there is no explicit mongo schema.
  388. * When called, if there is model data it will be used to derive a schema. a row is plucked
  389. * out of the db and the data obtained used to derive the schema.
  390. *
  391. * @param Model $Model
  392. * @return array if model instance has mongoSchema, return it.
  393. * @access public
  394. */
  395. public function describe($Model) {
  396. if(empty($Model->primaryKey)) {
  397. $Model->primaryKey = '_id';
  398. }
  399. $schema = array();
  400. $table = $this->fullTableName($Model);
  401. if (!empty($Model->mongoSchema) && is_array($Model->mongoSchema)) {
  402. $schema = $Model->mongoSchema;
  403. return $schema + array($Model->primaryKey => $this->_defaultSchema['_id']);
  404. } elseif ($this->isConnected() && is_a($Model, 'Model') && !empty($Model->Behaviors)) {
  405. $Model->Behaviors->attach('Mongodb.Schemaless');
  406. if (!$Model->data) {
  407. if ($this->_db->selectCollection($table)->count()) {
  408. return $this->deriveSchemaFromData($Model, $this->_db->selectCollection($table)->findOne());
  409. }
  410. }
  411. }
  412. return $this->deriveSchemaFromData($Model);
  413. }
  414. /**
  415. * begin method
  416. *
  417. * Mongo doesn't support transactions
  418. *
  419. * @return void
  420. * @access public
  421. */
  422. public function begin() {
  423. return false;
  424. }
  425. /**
  426. * Calculate
  427. *
  428. * @param Model $Model
  429. * @return array
  430. * @access public
  431. */
  432. public function calculate(Model $Model, $func, $params = array()) {
  433. return array('count' => true);
  434. }
  435. /**
  436. * Quotes identifiers.
  437. *
  438. * MongoDb does not need identifiers quoted, so this method simply returns the identifier.
  439. *
  440. * @param string $name The identifier to quote.
  441. * @return string The quoted identifier.
  442. */
  443. public function name($name) {
  444. return $name;
  445. }
  446. /**
  447. * Create Data
  448. *
  449. * @param Model $Model Model Instance
  450. * @param array $fields Field data
  451. * @param array $values Save data
  452. * @return boolean Insert result
  453. * @access public
  454. */
  455. public function create(Model $Model, $fields = null, $values = null) {
  456. if (!$this->isConnected()) {
  457. return false;
  458. }
  459. if ($fields !== null && $values !== null) {
  460. $data = array_combine($fields, $values);
  461. } else {
  462. $data = $Model->data;
  463. }
  464. if($Model->primaryKey !== '_id' && isset($data[$Model->primaryKey]) && !empty($data[$Model->primaryKey])) {
  465. $data['_id'] = $data[$Model->primaryKey];
  466. unset($data[$Model->primaryKey]);
  467. }
  468. if (!empty($data['_id'])) {
  469. $this->_convertId($data['_id']);
  470. }
  471. $this->_prepareLogQuery($Model); // just sets a timer
  472. $table = $this->fullTableName($Model);
  473. try{
  474. if ($this->_driverVersion >= '1.3.0') {
  475. $return = $this->_db
  476. ->selectCollection($table)
  477. ->insert($data, array('safe' => true));
  478. } else {
  479. $return = $this->_db
  480. ->selectCollection($table)
  481. ->insert($data, true);
  482. }
  483. } catch (MongoException $e) {
  484. $this->error = $e->getMessage();
  485. trigger_error($this->error);
  486. }
  487. if ($this->fullDebug) {
  488. $this->logQuery("db.{$table}.insert( :data , true)", compact('data'));
  489. }
  490. if (!empty($return) && $return['ok']) {
  491. $id = $data['_id'];
  492. if($this->config['set_string_id'] && is_object($data['_id'])) {
  493. $id = $data['_id']->__toString();
  494. }
  495. $Model->setInsertID($id);
  496. $Model->id = $id;
  497. return true;
  498. }
  499. return false;
  500. }
  501. /**
  502. * createSchema method
  503. *
  504. * Mongo no care for creating schema. Mongo work with no schema.
  505. *
  506. * @param mixed $schema
  507. * @param mixed $tableName null
  508. * @return void
  509. * @access public
  510. */
  511. public function createSchema($schema, $tableName = null) {
  512. return true;
  513. }
  514. /**
  515. * dropSchema method
  516. *
  517. * Return a command to drop each table
  518. *
  519. * @param mixed $schema
  520. * @param mixed $tableName null
  521. * @return void
  522. * @access public
  523. */
  524. public function dropSchema(CakeSchema $schema, $tableName = null) {
  525. if (!$this->isConnected()) {
  526. return false;
  527. }
  528. if (!is_a($schema, 'CakeSchema')) {
  529. trigger_error(__('Invalid schema object', true), E_USER_WARNING);
  530. return null;
  531. }
  532. if ($tableName) {
  533. return "db.{$tableName}.drop();";
  534. }
  535. $toDrop = array();
  536. foreach ($schema->tables as $curTable => $columns) {
  537. if ($tableName === $curTable) {
  538. $toDrop[] = $curTable;
  539. }
  540. }
  541. if (count($toDrop) === 1) {
  542. return "db.{$toDrop[0]}.drop();";
  543. }
  544. $return = "toDrop = :tables;\nfor( i = 0; i < toDrop.length; i++ ) {\n\tdb[toDrop[i]].drop();\n}";
  545. $tables = '["' . implode($toDrop, '", "') . '"]';
  546. return String::insert($return, compact('tables'));
  547. }
  548. /**
  549. * distinct method
  550. *
  551. * @param mixed $Model
  552. * @param array $keys array()
  553. * @param array $params array()
  554. * @return void
  555. * @access public
  556. */
  557. public function distinct(&$Model, $keys = array(), $params = array()) {
  558. if (!$this->isConnected()) {
  559. return false;
  560. }
  561. $this->_prepareLogQuery($Model); // just sets a timer
  562. if (array_key_exists('conditions', $params)) {
  563. $params = $params['conditions'];
  564. }
  565. $table = $this->fullTableName($Model);
  566. try{
  567. $return = $this->_db
  568. ->selectCollection($table)
  569. ->distinct($keys, $params);
  570. } catch (MongoException $e) {
  571. $this->error = $e->getMessage();
  572. trigger_error($this->error);
  573. }
  574. if ($this->fullDebug) {
  575. $this->logQuery("db.{$table}.distinct( :keys, :params )", compact('keys', 'params'));
  576. }
  577. return $return;
  578. }
  579. /**
  580. * group method
  581. *
  582. * @param array $params array()
  583. * Set params same as MongoCollection::group()
  584. * key,initial, reduce, options(conditions, finalize)
  585. *
  586. * Ex. $params = array(
  587. * 'key' => array('field' => true),
  588. * 'initial' => array('csum' => 0),
  589. * 'reduce' => 'function(obj, prev){prev.csum += 1;}',
  590. * 'options' => array(
  591. * 'condition' => array('age' => array('$gt' => 20)),
  592. * 'finalize' => array(),
  593. * ),
  594. * );
  595. * @param mixed $Model
  596. * @return void
  597. * @access public
  598. */
  599. public function group($params, Model $Model = null) {
  600. if (!$this->isConnected() || count($params) === 0 || $Model === null) {
  601. return false;
  602. }
  603. $this->_prepareLogQuery($Model); // just sets a timer
  604. $key = (empty($params['key'])) ? array() : $params['key'];
  605. $initial = (empty($params['initial'])) ? array() : $params['initial'];
  606. $reduce = (empty($params['reduce'])) ? array() : $params['reduce'];
  607. $options = (empty($params['options'])) ? array() : $params['options'];
  608. $table = $this->fullTableName($Model);
  609. try{
  610. $return = $this->_db
  611. ->selectCollection($table)
  612. ->group($key, $initial, $reduce, $options);
  613. } catch (MongoException $e) {
  614. $this->error = $e->getMessage();
  615. trigger_error($this->error);
  616. }
  617. if ($this->fullDebug) {
  618. $this->logQuery("db.{$table}.group( :key, :initial, :reduce, :options )", $params);
  619. }
  620. return $return;
  621. }
  622. /**
  623. * ensureIndex method
  624. *
  625. * @param mixed $Model
  626. * @param array $keys array()
  627. * @param array $params array()
  628. * @return void
  629. * @access public
  630. */
  631. public function ensureIndex(&$Model, $keys = array(), $params = array()) {
  632. if (!$this->isConnected()) {
  633. return false;
  634. }
  635. $this->_prepareLogQuery($Model); // just sets a timer
  636. $table = $this->fullTableName($Model);
  637. try{
  638. $return = $this->_db
  639. ->selectCollection($table)
  640. ->ensureIndex($keys, $params);
  641. } catch (MongoException $e) {
  642. $this->error = $e->getMessage();
  643. trigger_error($this->error);
  644. }
  645. if ($this->fullDebug) {
  646. $this->logQuery("db.{$table}.ensureIndex( :keys, :params )", compact('keys', 'params'));
  647. }
  648. return $return;
  649. }
  650. /**
  651. * Update Data
  652. *
  653. * This method uses $set operator automatically with MongoCollection::update().
  654. * If you don't want to use $set operator, you can chose any one as follw.
  655. * 1. Set TRUE in Model::mongoNoSetOperator property.
  656. * 2. Set a mongodb operator in a key of save data as follow.
  657. * Model->save(array('_id' => $id, '$inc' => array('count' => 1)));
  658. * Don't use Model::mongoSchema property,
  659. * CakePHP delete '$inc' data in Model::Save().
  660. * 3. Set a Mongo operator in Model::mongoNoSetOperator property.
  661. * Model->mongoNoSetOperator = '$inc';
  662. * Model->save(array('_id' => $id, array('count' => 1)));
  663. *
  664. * @param Model $Model Model Instance
  665. * @param array $fields Field data
  666. * @param array $values Save data
  667. * @return boolean Update result
  668. * @access public
  669. */
  670. public function update(Model $Model, $fields = null, $values = null, $conditions = null) {
  671. if (!$this->isConnected()) {
  672. return false;
  673. }
  674. if ($fields !== null && $values !== null) {
  675. $data = array_combine($fields, $values);
  676. } elseif($fields !== null && $conditions !== null) {
  677. return $this->updateAll($Model, $fields, $conditions);
  678. } else{
  679. $data = $Model->data;
  680. }
  681. if($Model->primaryKey !== '_id' && isset($data[$Model->primaryKey]) && !empty($data[$Model->primaryKey])) {
  682. $data['_id'] = $data[$Model->primaryKey];
  683. unset($data[$Model->primaryKey]);
  684. }
  685. if (empty($data['_id'])) {
  686. $data['_id'] = $Model->id;
  687. }
  688. $this->_convertId($data['_id']);
  689. $table = $this->fullTableName($Model);
  690. try{
  691. $mongoCollectionObj = $this->_db
  692. ->selectCollection($table);
  693. } catch (MongoException $e) {
  694. $this->error = $e->getMessage();
  695. trigger_error($this->error);
  696. return false;
  697. }
  698. $this->_prepareLogQuery($Model); // just sets a timer
  699. if (!empty($data['_id'])) {
  700. $this->_convertId($data['_id']);
  701. $cond = array('_id' => $data['_id']);
  702. unset($data['_id']);
  703. $data = $this->setMongoUpdateOperator($Model, $data);
  704. try{
  705. if ($this->_driverVersion >= '1.3.0') {
  706. $return = $mongoCollectionObj->update($cond, $data, array("multiple" => false, 'safe' => true));
  707. } else {
  708. $return = $mongoCollectionObj->update($cond, $data, array("multiple" => false));
  709. }
  710. } catch (MongoException $e) {
  711. $this->error = $e->getMessage();
  712. trigger_error($this->error);
  713. }
  714. if ($this->fullDebug) {
  715. $this->logQuery("db.{$table}.update( :conditions, :data, :params )",
  716. array('conditions' => $cond, 'data' => $data, 'params' => array("multiple" => false))
  717. );
  718. }
  719. } else {
  720. try{
  721. if ($this->_driverVersion >= '1.3.0') {
  722. $return = $mongoCollectionObj->save($data, array('safe' => true));
  723. } else {
  724. $return = $mongoCollectionObj->save($data);
  725. }
  726. } catch (MongoException $e) {
  727. $this->error = $e->getMessage();
  728. trigger_error($this->error);
  729. }
  730. if ($this->fullDebug) {
  731. $this->logQuery("db.{$table}.save( :data )", compact('data'));
  732. }
  733. }
  734. return $return;
  735. }
  736. /**
  737. * setMongoUpdateOperator
  738. *
  739. * Set Mongo update operator following saving data.
  740. * This method is for update() and updateAll.
  741. *
  742. * @param Model $Model Model Instance
  743. * @param array $values Save data
  744. * @return array $data
  745. * @access public
  746. */
  747. public function setMongoUpdateOperator(&$Model, $data) {
  748. if(isset($data['updated'])) {
  749. $updateField = 'updated';
  750. } else {
  751. $updateField = 'modified';
  752. }
  753. //setting Mongo operator
  754. if(empty($Model->mongoNoSetOperator)) {
  755. if(!preg_grep('/^\$/', array_keys($data))) {
  756. $data = array('$set' => $data);
  757. } else {
  758. if(!empty($data[$updateField])) {
  759. $modified = $data[$updateField];
  760. unset($data[$updateField]);
  761. $data['$set'] = array($updateField => $modified);
  762. }
  763. }
  764. } elseif(substr($Model->mongoNoSetOperator,0,1) === '$') {
  765. if(!empty($data[$updateField])) {
  766. $modified = $data[$updateField];
  767. unset($data[$updateField]);
  768. $data = array($Model->mongoNoSetOperator => $data, '$set' => array($updateField => $modified));
  769. } else {
  770. $data = array($Model->mongoNoSetOperator => $data);
  771. }
  772. }
  773. return $data;
  774. }
  775. /**
  776. * Update multiple Record
  777. *
  778. * @param Model $Model Model Instance
  779. * @param array $fields Field data
  780. * @param array $conditions
  781. * @return boolean Update result
  782. * @access public
  783. */
  784. public function updateAll(&$Model, $fields = null, $conditions = null) {
  785. if (!$this->isConnected()) {
  786. return false;
  787. }
  788. $this->_stripAlias($conditions, $Model->alias);
  789. $this->_stripAlias($fields, $Model->alias, false, 'value');
  790. $fields = $this->setMongoUpdateOperator($Model, $fields);
  791. $this->_prepareLogQuery($Model); // just sets a timer
  792. $table = $this->fullTableName($Model);
  793. try{
  794. if ($this->_driverVersion >= '1.3.0') {
  795. // not use 'upsert'
  796. $return = $this->_db
  797. ->selectCollection($table)
  798. ->update($conditions, $fields, array("multiple" => true, 'safe' => true));
  799. if (isset($return['updatedExisting'])) {
  800. $return = $return['updatedExisting'];
  801. }
  802. } else {
  803. $return = $this->_db
  804. ->selectCollection($table)
  805. ->update($conditions, $fields, array("multiple" => true));
  806. }
  807. } catch (MongoException $e) {
  808. $this->error = $e->getMessage();
  809. trigger_error($this->error);
  810. }
  811. if ($this->fullDebug) {
  812. $this->logQuery("db.{$table}.update( :conditions, :fields, :params )",
  813. array('conditions' => $conditions, 'fields' => $fields, 'params' => array("multiple" => true))
  814. );
  815. }
  816. return $return;
  817. }
  818. /**
  819. * deriveSchemaFromData method
  820. *
  821. * @param mixed $Model
  822. * @param array $data array()
  823. * @return void
  824. * @access public
  825. */
  826. public function deriveSchemaFromData($Model, $data = array()) {
  827. if (!$data) {
  828. $data = $Model->data;
  829. if ($data && array_key_exists($Model->alias, $data)) {
  830. $data = $data[$Model->alias];
  831. }
  832. }
  833. $return = $this->_defaultSchema;
  834. if ($data) {
  835. $fields = array_keys($data);
  836. foreach($fields as $field) {
  837. if (in_array($field, array('created', 'modified', 'updated'))) {
  838. $return[$field] = array('type' => 'datetime', 'null' => true);
  839. } else {
  840. $return[$field] = array('type' => 'string', 'length' => 2000);
  841. }
  842. }
  843. }
  844. return $return;
  845. }
  846. /**
  847. * Delete Data
  848. *
  849. * For deleteAll(true, false) calls - conditions will arrive here as true - account for that and
  850. * convert to an empty array
  851. * For deleteAll(array('some conditions')) calls - conditions will arrive here as:
  852. * array(
  853. * Alias._id => array(1, 2, 3, ...)
  854. * )
  855. *
  856. * This format won't be understood by mongodb, it'll find 0 rows. convert to:
  857. *
  858. * array(
  859. * Alias._id => array('$in' => array(1, 2, 3, ...))
  860. * )
  861. *
  862. * @TODO bench remove() v drop. if it's faster to drop - just drop the collection taking into
  863. * account existing indexes (recreate just the indexes)
  864. * @param Model $Model Model Instance
  865. * @param array $conditions
  866. * @return boolean Update result
  867. * @access public
  868. */
  869. public function delete(Model $Model, $conditions = null) {
  870. if (!$this->isConnected()) {
  871. return false;
  872. }
  873. $id = null;
  874. $this->_stripAlias($conditions, $Model->alias);
  875. if ($conditions === true) {
  876. $conditions = array();
  877. } elseif (empty($conditions)) {
  878. $id = $Model->id;
  879. } elseif (!empty($conditions) && !is_array($conditions)) {
  880. $id = $conditions;
  881. $conditions = array();
  882. } elseif (!empty($conditions['id'])) { //for cakephp2.0
  883. $id = $conditions['id'];
  884. unset($conditions['id']);
  885. }
  886. $table = $this->fullTableName($Model);
  887. $mongoCollectionObj = $this->_db
  888. ->selectCollection($table);
  889. $this->_stripAlias($conditions, $Model->alias);
  890. if (!empty($id)) {
  891. $conditions['_id'] = $id;
  892. }
  893. if (!empty($conditions['_id'])) {
  894. $this->_convertId($conditions['_id'], true);
  895. }
  896. $return = false;
  897. $r = false;
  898. try{
  899. $this->_prepareLogQuery($Model); // just sets a timer
  900. $return = $mongoCollectionObj->remove($conditions);
  901. if ($this->fullDebug) {
  902. $this->logQuery("db.{$table}.remove( :conditions )",
  903. compact('conditions')
  904. );
  905. }
  906. $return = true;
  907. } catch (MongoException $e) {
  908. $this->error = $e->getMessage();
  909. trigger_error($this->error);
  910. }
  911. return $return;
  912. }
  913. /**
  914. * Read Data
  915. *
  916. * For deleteAll(true) calls - the conditions will arrive here as true - account for that and switch to an empty array
  917. *
  918. * @param Model $Model Model Instance
  919. * @param array $query Query data
  920. * @param mixed $recursive
  921. * @return array Results
  922. * @access public
  923. */
  924. public function read(Model $Model, $query = array(), $recursive = null) {
  925. if (!$this->isConnected()) {
  926. return false;
  927. }
  928. $this->_setEmptyValues($query);
  929. extract($query);
  930. if (!empty($order[0])) {
  931. $order = array_shift($order);
  932. }
  933. $this->_stripAlias($conditions, $Model->alias);
  934. $this->_stripAlias($fields, $Model->alias, false, 'value');
  935. $this->_stripAlias($order, $Model->alias, false, 'both');
  936. if(!empty($conditions['id']) && empty($conditions['_id'])) {
  937. $conditions['_id'] = $conditions['id'];
  938. unset($conditions['id']);
  939. }
  940. if (!empty($conditions['_id'])) {
  941. $this->_convertId($conditions['_id']);
  942. }
  943. $fields = (is_array($fields)) ? $fields : array($fields => 1);
  944. if ($conditions === true) {
  945. $conditions = array();
  946. } elseif (!is_array($conditions)) {
  947. $conditions = array($conditions);
  948. }
  949. $order = (is_array($order)) ? $order : array($order);
  950. if (is_array($order)) {
  951. foreach($order as $field => &$dir) {
  952. if (is_numeric($field) || is_null($dir)) {
  953. unset ($order[$field]);
  954. continue;
  955. }
  956. if ($dir && strtoupper($dir) === 'ASC') {
  957. $dir = 1;
  958. continue;
  959. } elseif (!$dir || strtoupper($dir) === 'DESC') {
  960. $dir = -1;
  961. continue;
  962. }
  963. $dir = (int)$dir;
  964. }
  965. }
  966. if (empty($offset) && $page && $limit) {
  967. $offset = ($page - 1) * $limit;
  968. }
  969. $return = array();
  970. $this->_prepareLogQuery($Model); // just sets a timer
  971. $table = $this->fullTableName($Model);
  972. if (empty($modify)) {
  973. if ($Model->findQueryType === 'count' && $fields == array('count' => true)) {
  974. $cursor = $this->_db
  975. ->selectCollection($table)
  976. ->find($conditions, array('_id' => true));
  977. if (!empty($hint)) {
  978. $cursor->hint($hint);
  979. }
  980. $count = $cursor->count();
  981. if ($this->fullDebug) {
  982. if (empty($hint)) {
  983. $hint = array();
  984. }
  985. $this->logQuery("db.{$table}.find( :conditions ).hint( :hint ).count()",
  986. compact('conditions', 'count', 'hint')
  987. );
  988. }
  989. return array(array($Model->alias => array('count' => $count)));
  990. }
  991. $return = $this->_db
  992. ->selectCollection($table)
  993. ->find($conditions, $fields)
  994. ->sort($order)
  995. ->limit($limit)
  996. ->skip($offset);
  997. if (!empty($hint)) {
  998. $return->hint($hint);
  999. }
  1000. if ($this->fullDebug) {
  1001. $count = $return->count(true);
  1002. if (empty($hint)) {
  1003. $hint = array();
  1004. }
  1005. $this->logQuery("db.{$table}.find( :conditions, :fields ).sort( :order ).limit( :limit ).skip( :offset ).hint( :hint )",
  1006. compact('conditions', 'fields', 'order', 'limit', 'offset', 'count', 'hint')
  1007. );
  1008. }
  1009. } else {
  1010. $options = array_filter(array(
  1011. 'findandmodify' => $table,
  1012. 'query' => $conditions,
  1013. 'sort' => $order,
  1014. 'remove' => !empty($remove),
  1015. 'update' => $this->setMongoUpdateOperator($Model, $modify),
  1016. 'new' => !empty($new),
  1017. 'fields' => $fields,
  1018. 'upsert' => !empty($upsert)
  1019. ));
  1020. $return = $this->_db
  1021. ->command($options);
  1022. if ($this->fullDebug) {
  1023. if ($return['ok']) {
  1024. $count = 1;
  1025. if ($this->config['set_string_id'] && !empty($return['value']['_id']) && is_object($return['value']['_id'])) {
  1026. $return['value']['_id'] = $return['value']['_id']->__toString();
  1027. }
  1028. $return[][$Model->alias] = $return['value'];
  1029. } else {
  1030. $count = 0;
  1031. }
  1032. $this->logQuery("db.runCommand( :options )",
  1033. array('options' => array_filter($options), 'count' => $count)
  1034. );
  1035. }
  1036. }
  1037. if ($Model->findQueryType === 'count') {
  1038. return array(array($Model->alias => array('count' => $return->count())));
  1039. }
  1040. if (is_object($return)) {
  1041. $_return = array();
  1042. while ($return->hasNext()) {
  1043. $mongodata = $return->getNext();
  1044. if ($this->config['set_string_id'] && !empty($mongodata['_id']) && is_object($mongodata['_id'])) {
  1045. $mongodata['_id'] = $mongodata['_id']->__toString();
  1046. }
  1047. if ($Model->primaryKey !== '_id') {
  1048. $mongodata[$Model->primaryKey] = $mongodata['_id'];
  1049. unset($mongodata['_id']);
  1050. }
  1051. $_return[][$Model->alias] = $mongodata;
  1052. }
  1053. return $_return;
  1054. }
  1055. return $return;
  1056. }
  1057. /**
  1058. * rollback method
  1059. *
  1060. * MongoDB doesn't support transactions
  1061. *
  1062. * @return void
  1063. * @access public
  1064. */
  1065. public function rollback() {
  1066. return false;
  1067. }
  1068. /**
  1069. * Deletes all the records in a table
  1070. *
  1071. * @param mixed $table A string or model class representing the table to be truncated
  1072. * @return boolean
  1073. * @access public
  1074. */
  1075. public function truncate($table) {
  1076. if (!$this->isConnected()) {
  1077. return false;
  1078. }
  1079. $fullTableName = $this->fullTableName($table);
  1080. $return = false;
  1081. try{
  1082. $return = $this->getMongoDb()->selectCollection($fullTableName)->remove(array());
  1083. if ($this->fullDebug) {
  1084. $this->logQuery("db.{$fullTableName}.remove({})");
  1085. }
  1086. $return = true;
  1087. } catch (MongoException $e) {
  1088. $this->error = $e->getMessage();
  1089. trigger_error($this->error);
  1090. }
  1091. return $return;
  1092. }
  1093. /**
  1094. * query method
  1095. * If call getMongoDb() from model, this method call getMongoDb().
  1096. *
  1097. * @param mixed $query
  1098. * @param array $params array()
  1099. * @return void
  1100. * @access public
  1101. */
  1102. public function query() {
  1103. $args = func_get_args();
  1104. $query = $args[0];
  1105. $params = array();
  1106. if(count($args) > 1) {
  1107. $params = $args[1];
  1108. }
  1109. if (!$this->isConnected()) {
  1110. return false;
  1111. }
  1112. if($query === 'getMongoDb') {
  1113. return $this->getMongoDb();
  1114. }
  1115. if (count($args) > 1 && (strpos($args[0], 'findBy') === 0 || strpos($args[0], 'findAllBy') === 0)) {
  1116. $params = $args[1];
  1117. if (substr($args[0], 0, 6) === 'findBy') {
  1118. $field = Inflector::underscore(substr($args[0], 6));
  1119. return $args[2]->find('first', array('conditions' => array($field => $args[1][0])));
  1120. } else{
  1121. $field = Inflector::underscore(substr($args[0], 9));
  1122. return $args[2]->find('all', array('conditions' => array($field => $args[1][0])));
  1123. }
  1124. }
  1125. if(isset($args[2]) && is_a($args[2], 'Model')) {
  1126. $this->_prepareLogQuery($args[2]);
  1127. }
  1128. $return = $this->_db
  1129. ->command($query);
  1130. if ($this->fullDebug) {
  1131. $this->logQuery("db.runCommand( :query )", compact('query'));
  1132. }
  1133. return $return;
  1134. }
  1135. /**
  1136. * mapReduce
  1137. *
  1138. * @param mixed $query
  1139. * @param integer $timeout (milli second)
  1140. * @return mixed false or array
  1141. * @access public
  1142. */
  1143. public function mapReduce($query, $timeout = null) {
  1144. //above MongoDB1.8, query must object.
  1145. if(isset($query['query']) && !is_object($query['query'])) {
  1146. $query['query'] = (object)$query['query'];
  1147. }
  1148. $result = $this->query($query);
  1149. if($result['ok']) {
  1150. if (isset($query['out']['inline']) && $query['out']['inline'] === 1) {
  1151. if (is_array($result['results'])) {
  1152. $data = $result['results'];
  1153. }else{
  1154. $data = false;
  1155. }
  1156. }else {
  1157. $data = $this->_db->selectCollection($result['result'])->find();
  1158. if(!empty($timeout)) {
  1159. $data->timeout($timeout);
  1160. }
  1161. }
  1162. return $data;
  1163. }
  1164. return false;
  1165. }
  1166. /**
  1167. * Prepares a value, or an array of values for database queries by quoting and escaping them.
  1168. *
  1169. * @param mixed $data A value or an array of values to prepare.
  1170. * @param string $column The column into which this data will be inserted
  1171. * @return mixed Prepared value or array of values.
  1172. * @access public
  1173. */
  1174. public function value($data, $column = null) {
  1175. if (is_array($data) && !empty($data)) {
  1176. return array_map(
  1177. array(&$this, 'value'),
  1178. $data, array_fill(0, count($data), $column)
  1179. );
  1180. } elseif (is_object($data) && isset($data->type, $data->value)) {
  1181. if ($data->type == 'identifier') {
  1182. return $this->name($data->value);
  1183. } elseif ($data->type == 'expression') {
  1184. return $data->value;
  1185. }
  1186. } elseif (in_array($data, array('{$__cakeID__$}', '{$__cakeForeignKey__$}'), true)) {
  1187. return $data;
  1188. }
  1189. if ($data === null || (is_array($data) && empty($data))) {
  1190. return 'NULL';
  1191. }
  1192. if (empty($column)) {
  1193. $column = $this->introspectType($data);
  1194. }
  1195. switch ($column) {
  1196. case 'binary':
  1197. case 'string':
  1198. case 'text':
  1199. return $data;
  1200. case 'boolean':
  1201. return !empty($data);
  1202. default:
  1203. if ($data === '') {
  1204. return 'NULL';
  1205. }
  1206. if (is_float($data)) {
  1207. return str_replace(',', '.', strval($data));
  1208. }
  1209. return $data;
  1210. }
  1211. }
  1212. /**
  1213. * execute method
  1214. *
  1215. * If there is no query or the query is true, execute has probably been called as part of a
  1216. * db-agnostic process which does not have a mongo equivalent, don't do anything.
  1217. *
  1218. * @param mixed $query
  1219. * @param array $options
  1220. * @param array $params array()
  1221. * @return void
  1222. * @access public
  1223. */
  1224. public function execute($query, $options = array(), $params = array()) {
  1225. if (!$this->isConnected()) {
  1226. return false;
  1227. }
  1228. if (!$query || $query === true) {
  1229. return;
  1230. }
  1231. $this->_prepareLogQuery($Model); // just sets a timer
  1232. $return = $this->_db
  1233. ->execute($query, $params);
  1234. if ($this->fullDebug) {
  1235. if ($params) {
  1236. $this->logQuery(":query, :params",
  1237. compact('query', 'params')
  1238. );
  1239. } else {
  1240. $this->logQuery($query);
  1241. }
  1242. }
  1243. if ($return['ok']) {
  1244. return $return['retval'];
  1245. }
  1246. return $return;
  1247. }
  1248. /**
  1249. * Set empty values, arrays or integers, for the variables Mongo uses
  1250. *
  1251. * @param mixed $data
  1252. * @param array $integers array('limit', 'offset')
  1253. * @return void
  1254. * @access protected
  1255. */
  1256. protected function _setEmptyValues(&$data, $integers = array('limit', 'offset')) {
  1257. if (!is_array($data)) {
  1258. return;
  1259. }
  1260. foreach($data as $key => $value) {
  1261. if (empty($value)) {
  1262. if (in_array($key, $integers)) {
  1263. $data[$key] = 0;
  1264. } else {
  1265. $data[$key] = array();
  1266. }
  1267. }
  1268. }
  1269. }
  1270. /**
  1271. * prepareLogQuery method
  1272. *
  1273. * Any prep work to log a query
  1274. *
  1275. * @param mixed $Model
  1276. * @return void
  1277. * @access protected
  1278. */
  1279. protected function _prepareLogQuery(&$Model) {
  1280. if (!$this->fullDebug) {
  1281. return false;
  1282. }
  1283. $this->_startTime = microtime(true);
  1284. $this->took = null;
  1285. $this->affected = null;
  1286. $this->error = null;
  1287. $this->numRows = null;
  1288. return true;
  1289. }
  1290. /**
  1291. * setTimeout Method
  1292. *
  1293. * Sets the MongoCursor timeout so long queries (like map / reduce) can run at will.
  1294. * Expressed in milliseconds, for an infinite timeout, set to -1
  1295. *
  1296. * @param int $ms
  1297. * @return boolean
  1298. * @access public
  1299. */
  1300. public function setTimeout($ms){
  1301. MongoCursor::$timeout = $ms;
  1302. return true;
  1303. }
  1304. /**
  1305. * logQuery method
  1306. *
  1307. * Set timers, errors and refer to the parent
  1308. * If there are arguments passed - inject them into the query
  1309. * Show MongoIds in a copy-and-paste-into-mongo format
  1310. *
  1311. *
  1312. * @param mixed $query
  1313. * @param array $args array()
  1314. * @return void
  1315. * @access public
  1316. */
  1317. public function logQuery($query, $args = array()) {
  1318. if ($args) {
  1319. $this->_stringify($args);
  1320. $query = String::insert($query, $args);
  1321. }
  1322. $this->took = round((microtime(true) - $this->_startTime) * 1000, 0);
  1323. $this->affected = null;
  1324. if (empty($this->error['err'])) {
  1325. $this->error = $this->_db->lastError();
  1326. if (!is_scalar($this->error)) {
  1327. $this->error = json_encode($this->error);
  1328. }
  1329. }
  1330. $this->numRows = !empty($args['count'])?$args['count']:null;
  1331. $query = preg_replace('@"ObjectId\((.*?)\)"@', 'ObjectId ("\1")', $query);
  1332. return parent::logQuery($query);
  1333. }
  1334. /**
  1335. * convertId method
  1336. *
  1337. * $conditions is used to determine if it should try to auto correct _id => array() queries
  1338. * it only appies to conditions, hence the param name
  1339. *
  1340. * @param mixed $mixed
  1341. * @param bool $conditions false
  1342. * @return void
  1343. * @access protected
  1344. */
  1345. protected function _convertId(&$mixed, $conditions = false) {
  1346. if (is_int($mixed) || ctype_digit($mixed)) {
  1347. return;
  1348. }
  1349. if (is_string($mixed)) {
  1350. if (strlen($mixed) !== 24) {
  1351. return;
  1352. }
  1353. $mixed = new MongoId($mixed);
  1354. }
  1355. if (is_array($mixed)) {
  1356. foreach($mixed as &$row) {
  1357. $this->_convertId($row, false);
  1358. }
  1359. if (!empty($mixed[0]) && $conditions) {
  1360. $mixed = array('$in' => $mixed);
  1361. }
  1362. }
  1363. }
  1364. /**
  1365. * stringify method
  1366. *
  1367. * Takes an array of args as an input and returns an array of json-encoded strings. Takes care of
  1368. * any objects the arrays might be holding (MongoID);
  1369. *
  1370. * @param array $args array()
  1371. * @param int $level 0 internal recursion counter
  1372. * @return array
  1373. * @access protected
  1374. */
  1375. protected function _stringify(&$args = array(), $level = 0) {
  1376. foreach($args as &$arg) {
  1377. if (is_array($arg)) {
  1378. $this->_stringify($arg, $level + 1);
  1379. } elseif (is_object($arg) && is_callable(array($arg, '__toString'))) {
  1380. $class = get_class($arg);
  1381. if ($class === 'MongoId') {
  1382. $arg = 'ObjectId(' . $arg->__toString() . ')';
  1383. } elseif ($class === 'MongoRegex') {
  1384. $arg = '_regexstart_' . $arg->__toString() . '_regexend_';
  1385. } else {
  1386. $arg = $class . '(' . $arg->__toString() . ')';
  1387. }
  1388. }
  1389. if ($level === 0) {
  1390. $arg = json_encode($arg);
  1391. if (strpos($arg, '_regexstart_')) {
  1392. preg_match_all('@"_regexstart_(.*?)_regexend_"@', $arg, $matches);
  1393. foreach($matches[0] as $i => $whole) {
  1394. $replace = stripslashes($matches[1][$i]);
  1395. $arg = str_replace($whole, $replace, $arg);
  1396. }
  1397. }
  1398. }
  1399. }
  1400. }
  1401. /**
  1402. * Convert automatically array('Model.field' => 'foo') to array('field' => 'foo')
  1403. *
  1404. * This introduces the limitation that you can't have a (nested) field with the same name as the model
  1405. * But it's a small price to pay to be able to use other behaviors/functionality with mongoDB
  1406. *
  1407. * @param array $args array()
  1408. * @param string $alias 'Model'
  1409. * @param bool $recurse true
  1410. * @param string $check 'key', 'value' or 'both'
  1411. * @return void
  1412. * @access protected
  1413. */
  1414. protected function _stripAlias(&$args = array(), $alias = 'Model', $recurse = true, $check = 'key') {
  1415. if (!is_array($args)) {
  1416. return;
  1417. }
  1418. $checkKey = ($check === 'key' || $check === 'both');
  1419. $checkValue = ($check === 'value' || $check === 'both');
  1420. foreach($args as $key => &$val) {
  1421. if ($checkKey) {
  1422. if (strpos($key, $alias . '.') === 0) {
  1423. unset($args[$key]);
  1424. $key = substr($key, strlen($alias) + 1);
  1425. $args[$key] = $val;
  1426. }
  1427. }
  1428. if ($checkValue) {
  1429. if (is_string($val) && strpos($val, $alias . '.') === 0) {
  1430. $val = substr($val, strlen($alias) + 1);
  1431. }
  1432. }
  1433. if ($recurse && is_array($val)) {
  1434. $this->_stripAlias($val, $alias, true, $check);
  1435. }
  1436. }
  1437. }
  1438. }
  1439. /**
  1440. * MongoDbDateFormatter method
  1441. *
  1442. * This function cannot be in the class because of the way model save is written
  1443. *
  1444. * @param mixed $date null
  1445. * @return void
  1446. * @access public
  1447. */
  1448. function MongoDbDateFormatter($date = null) {
  1449. if ($date) {
  1450. return new MongoDate($date);
  1451. }
  1452. return new MongoDate();
  1453. }