mapper.php 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. <?php
  2. /*
  3. Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
  4. This file is part of the Fat-Free Framework (http://fatfreeframework.com).
  5. This is free software: you can redistribute it and/or modify it under the
  6. terms of the GNU General Public License as published by the Free Software
  7. Foundation, either version 3 of the License, or later.
  8. Fat-Free Framework is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  11. General Public License for more details.
  12. You should have received a copy of the GNU General Public License along
  13. with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. namespace DB\Mongo;
  16. //! MongoDB mapper
  17. class Mapper extends \DB\Cursor {
  18. protected
  19. //! MongoDB wrapper
  20. $db,
  21. //! Legacy flag
  22. $legacy,
  23. //! Mongo collection
  24. $collection,
  25. //! Mongo document
  26. $document=[],
  27. //! Mongo cursor
  28. $cursor,
  29. //! Defined fields
  30. $fields;
  31. /**
  32. * Return database type
  33. * @return string
  34. **/
  35. function dbtype() {
  36. return 'Mongo';
  37. }
  38. /**
  39. * Return TRUE if field is defined
  40. * @return bool
  41. * @param $key string
  42. **/
  43. function exists($key) {
  44. return array_key_exists($key,$this->document);
  45. }
  46. /**
  47. * Assign value to field
  48. * @return scalar|FALSE
  49. * @param $key string
  50. * @param $val scalar
  51. **/
  52. function set($key,$val) {
  53. return $this->document[$key]=$val;
  54. }
  55. /**
  56. * Retrieve value of field
  57. * @return scalar|FALSE
  58. * @param $key string
  59. **/
  60. function &get($key) {
  61. if ($this->exists($key))
  62. return $this->document[$key];
  63. user_error(sprintf(self::E_Field,$key),E_USER_ERROR);
  64. }
  65. /**
  66. * Delete field
  67. * @return NULL
  68. * @param $key string
  69. **/
  70. function clear($key) {
  71. unset($this->document[$key]);
  72. }
  73. /**
  74. * Convert array to mapper object
  75. * @return static
  76. * @param $row array
  77. **/
  78. function factory($row) {
  79. $mapper=clone($this);
  80. $mapper->reset();
  81. foreach ($row as $key=>$val)
  82. $mapper->document[$key]=$val;
  83. $mapper->query=[clone($mapper)];
  84. if (isset($mapper->trigger['load']))
  85. \Base::instance()->call($mapper->trigger['load'],$mapper);
  86. return $mapper;
  87. }
  88. /**
  89. * Return fields of mapper object as an associative array
  90. * @return array
  91. * @param $obj object
  92. **/
  93. function cast($obj=NULL) {
  94. if (!$obj)
  95. $obj=$this;
  96. return $obj->document;
  97. }
  98. /**
  99. * Build query and execute
  100. * @return static[]
  101. * @param $fields string
  102. * @param $filter array
  103. * @param $options array
  104. * @param $ttl int|array
  105. **/
  106. function select($fields=NULL,$filter=NULL,array $options=NULL,$ttl=0) {
  107. if (!$options)
  108. $options=[];
  109. $options+=[
  110. 'group'=>NULL,
  111. 'order'=>NULL,
  112. 'limit'=>0,
  113. 'offset'=>0
  114. ];
  115. $tag='';
  116. if (is_array($ttl))
  117. list($ttl,$tag)=$ttl;
  118. $fw=\Base::instance();
  119. $cache=\Cache::instance();
  120. if (!($cached=$cache->exists($hash=$fw->hash($this->db->dsn().
  121. $fw->stringify([$fields,$filter,$options])).($tag?'.'.$tag:'').'.mongo',
  122. $result)) || !$ttl || $cached[0]+$ttl<microtime(TRUE)) {
  123. if ($options['group']) {
  124. $grp=$this->collection->group(
  125. $options['group']['keys'],
  126. $options['group']['initial'],
  127. $options['group']['reduce'],
  128. [
  129. 'condition'=>$filter,
  130. 'finalize'=>$options['group']['finalize']
  131. ]
  132. );
  133. $tmp=$this->db->selectcollection(
  134. $fw->HOST.'.'.$fw->BASE.'.'.
  135. uniqid(NULL,TRUE).'.tmp'
  136. );
  137. $tmp->batchinsert($grp['retval'],['w'=>1]);
  138. $filter=[];
  139. $collection=$tmp;
  140. }
  141. else {
  142. $filter=$filter?:[];
  143. $collection=$this->collection;
  144. }
  145. if ($this->legacy) {
  146. $this->cursor=$collection->find($filter,$fields?:[]);
  147. if ($options['order'])
  148. $this->cursor=$this->cursor->sort($options['order']);
  149. if ($options['limit'])
  150. $this->cursor=$this->cursor->limit($options['limit']);
  151. if ($options['offset'])
  152. $this->cursor=$this->cursor->skip($options['offset']);
  153. $result=[];
  154. while ($this->cursor->hasnext())
  155. $result[]=$this->cursor->getnext();
  156. }
  157. else {
  158. $this->cursor=$collection->find($filter,[
  159. 'sort'=>$options['order'],
  160. 'limit'=>$options['limit'],
  161. 'skip'=>$options['offset']
  162. ]);
  163. $result=$this->cursor->toarray();
  164. }
  165. if ($options['group'])
  166. $tmp->drop();
  167. if ($fw->CACHE && $ttl)
  168. // Save to cache backend
  169. $cache->set($hash,$result,$ttl);
  170. }
  171. $out=[];
  172. foreach ($result as $doc)
  173. $out[]=$this->factory($doc);
  174. return $out;
  175. }
  176. /**
  177. * Return records that match criteria
  178. * @return static[]
  179. * @param $filter array
  180. * @param $options array
  181. * @param $ttl int|array
  182. **/
  183. function find($filter=NULL,array $options=NULL,$ttl=0) {
  184. if (!$options)
  185. $options=[];
  186. $options+=[
  187. 'group'=>NULL,
  188. 'order'=>NULL,
  189. 'limit'=>0,
  190. 'offset'=>0
  191. ];
  192. return $this->select($this->fields,$filter,$options,$ttl);
  193. }
  194. /**
  195. * Count records that match criteria
  196. * @return int
  197. * @param $filter array
  198. * @param $options array
  199. * @param $ttl int|array
  200. **/
  201. function count($filter=NULL,array $options=NULL,$ttl=0) {
  202. $fw=\Base::instance();
  203. $cache=\Cache::instance();
  204. $tag='';
  205. if (is_array($ttl))
  206. list($ttl,$tag)=$ttl;
  207. if (!($cached=$cache->exists($hash=$fw->hash($fw->stringify(
  208. [$filter])).($tag?'.'.$tag:'').'.mongo',$result)) || !$ttl ||
  209. $cached[0]+$ttl<microtime(TRUE)) {
  210. $result=$this->collection->count($filter?:[]);
  211. if ($fw->CACHE && $ttl)
  212. // Save to cache backend
  213. $cache->set($hash,$result,$ttl);
  214. }
  215. return $result;
  216. }
  217. /**
  218. * Return record at specified offset using criteria of previous
  219. * load() call and make it active
  220. * @return array
  221. * @param $ofs int
  222. **/
  223. function skip($ofs=1) {
  224. $this->document=($out=parent::skip($ofs))?$out->document:[];
  225. if ($this->document && isset($this->trigger['load']))
  226. \Base::instance()->call($this->trigger['load'],$this);
  227. return $out;
  228. }
  229. /**
  230. * Insert new record
  231. * @return array
  232. **/
  233. function insert() {
  234. if (isset($this->document['_id']))
  235. return $this->update();
  236. if (isset($this->trigger['beforeinsert']) &&
  237. \Base::instance()->call($this->trigger['beforeinsert'],
  238. [$this,['_id'=>$this->document['_id']]])===FALSE)
  239. return $this->document;
  240. if ($this->legacy) {
  241. $this->collection->insert($this->document);
  242. $pkey=['_id'=>$this->document['_id']];
  243. }
  244. else {
  245. $result=$this->collection->insertone($this->document);
  246. $pkey=['_id'=>$result->getinsertedid()];
  247. }
  248. if (isset($this->trigger['afterinsert']))
  249. \Base::instance()->call($this->trigger['afterinsert'],
  250. [$this,$pkey]);
  251. $this->load($pkey);
  252. return $this->document;
  253. }
  254. /**
  255. * Update current record
  256. * @return array
  257. **/
  258. function update() {
  259. $pkey=['_id'=>$this->document['_id']];
  260. if (isset($this->trigger['beforeupdate']) &&
  261. \Base::instance()->call($this->trigger['beforeupdate'],
  262. [$this,$pkey])===FALSE)
  263. return $this->document;
  264. $upsert=['upsert'=>TRUE];
  265. if ($this->legacy)
  266. $this->collection->update($pkey,$this->document,$upsert);
  267. else
  268. $this->collection->replaceone($pkey,$this->document,$upsert);
  269. if (isset($this->trigger['afterupdate']))
  270. \Base::instance()->call($this->trigger['afterupdate'],
  271. [$this,$pkey]);
  272. return $this->document;
  273. }
  274. /**
  275. * Delete current record
  276. * @return bool
  277. * @param $quick bool
  278. * @param $filter array
  279. **/
  280. function erase($filter=NULL,$quick=TRUE) {
  281. if ($filter) {
  282. if (!$quick) {
  283. foreach ($this->find($filter) as $mapper)
  284. if (!$mapper->erase())
  285. return FALSE;
  286. return TRUE;
  287. }
  288. return $this->legacy?
  289. $this->collection->remove($filter):
  290. $this->collection->deletemany($filter);
  291. }
  292. $pkey=['_id'=>$this->document['_id']];
  293. if (isset($this->trigger['beforeerase']) &&
  294. \Base::instance()->call($this->trigger['beforeerase'],
  295. [$this,$pkey])===FALSE)
  296. return FALSE;
  297. $result=$this->legacy?
  298. $this->collection->remove(['_id'=>$this->document['_id']]):
  299. $this->collection->deleteone(['_id'=>$this->document['_id']]);
  300. parent::erase();
  301. if (isset($this->trigger['aftererase']))
  302. \Base::instance()->call($this->trigger['aftererase'],
  303. [$this,$pkey]);
  304. return $result;
  305. }
  306. /**
  307. * Reset cursor
  308. * @return NULL
  309. **/
  310. function reset() {
  311. $this->document=[];
  312. parent::reset();
  313. }
  314. /**
  315. * Hydrate mapper object using hive array variable
  316. * @return NULL
  317. * @param $var array|string
  318. * @param $func callback
  319. **/
  320. function copyfrom($var,$func=NULL) {
  321. if (is_string($var))
  322. $var=\Base::instance()->$var;
  323. if ($func)
  324. $var=call_user_func($func,$var);
  325. foreach ($var as $key=>$val)
  326. $this->set($key,$val);
  327. }
  328. /**
  329. * Populate hive array variable with mapper fields
  330. * @return NULL
  331. * @param $key string
  332. **/
  333. function copyto($key) {
  334. $var=&\Base::instance()->ref($key);
  335. foreach ($this->document as $key=>$field)
  336. $var[$key]=$field;
  337. }
  338. /**
  339. * Return field names
  340. * @return array
  341. **/
  342. function fields() {
  343. return array_keys($this->document);
  344. }
  345. /**
  346. * Return the cursor from last query
  347. * @return object|NULL
  348. **/
  349. function cursor() {
  350. return $this->cursor;
  351. }
  352. /**
  353. * Retrieve external iterator for fields
  354. * @return object
  355. **/
  356. function getiterator() {
  357. return new \ArrayIterator($this->cast());
  358. }
  359. /**
  360. * Instantiate class
  361. * @return void
  362. * @param $db object
  363. * @param $collection string
  364. * @param $fields array
  365. **/
  366. function __construct(\DB\Mongo $db,$collection,$fields=NULL) {
  367. $this->db=$db;
  368. $this->legacy=$db->legacy();
  369. $this->collection=$db->selectcollection($collection);
  370. $this->fields=$fields;
  371. $this->reset();
  372. }
  373. }