mapper.php 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  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. protected 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
  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. $fw=\Base::instance();
  116. $cache=\Cache::instance();
  117. if (!($cached=$cache->exists($hash=$fw->hash($this->db->dsn().
  118. $fw->stringify([$fields,$filter,$options])).'.mongo',
  119. $result)) || !$ttl || $cached[0]+$ttl<microtime(TRUE)) {
  120. if ($options['group']) {
  121. $grp=$this->collection->group(
  122. $options['group']['keys'],
  123. $options['group']['initial'],
  124. $options['group']['reduce'],
  125. [
  126. 'condition'=>$filter,
  127. 'finalize'=>$options['group']['finalize']
  128. ]
  129. );
  130. $tmp=$this->db->selectcollection(
  131. $fw->HOST.'.'.$fw->BASE.'.'.
  132. uniqid(NULL,TRUE).'.tmp'
  133. );
  134. $tmp->batchinsert($grp['retval'],['w'=>1]);
  135. $filter=[];
  136. $collection=$tmp;
  137. }
  138. else {
  139. $filter=$filter?:[];
  140. $collection=$this->collection;
  141. }
  142. if ($this->legacy) {
  143. $this->cursor=$collection->find($filter,$fields?:[]);
  144. if ($options['order'])
  145. $this->cursor=$this->cursor->sort($options['order']);
  146. if ($options['limit'])
  147. $this->cursor=$this->cursor->limit($options['limit']);
  148. if ($options['offset'])
  149. $this->cursor=$this->cursor->skip($options['offset']);
  150. $result=[];
  151. while ($this->cursor->hasnext())
  152. $result[]=$this->cursor->getnext();
  153. }
  154. else {
  155. $this->cursor=$collection->find($filter,[
  156. 'sort'=>$options['order'],
  157. 'limit'=>$options['limit'],
  158. 'skip'=>$options['offset']
  159. ]);
  160. $result=$this->cursor->toarray();
  161. }
  162. if ($options['group'])
  163. $tmp->drop();
  164. if ($fw->CACHE && $ttl)
  165. // Save to cache backend
  166. $cache->set($hash,$result,$ttl);
  167. }
  168. $out=[];
  169. foreach ($result as $doc)
  170. $out[]=$this->factory($doc);
  171. return $out;
  172. }
  173. /**
  174. * Return records that match criteria
  175. * @return static[]
  176. * @param $filter array
  177. * @param $options array
  178. * @param $ttl int
  179. **/
  180. function find($filter=NULL,array $options=NULL,$ttl=0) {
  181. if (!$options)
  182. $options=[];
  183. $options+=[
  184. 'group'=>NULL,
  185. 'order'=>NULL,
  186. 'limit'=>0,
  187. 'offset'=>0
  188. ];
  189. return $this->select($this->fields,$filter,$options,$ttl);
  190. }
  191. /**
  192. * Count records that match criteria
  193. * @return int
  194. * @param $filter array
  195. * @param $options array
  196. * @param $ttl int
  197. **/
  198. function count($filter=NULL,array $options=NULL,$ttl=0) {
  199. $fw=\Base::instance();
  200. $cache=\Cache::instance();
  201. if (!($cached=$cache->exists($hash=$fw->hash($fw->stringify(
  202. [$filter])).'.mongo',$result)) || !$ttl ||
  203. $cached[0]+$ttl<microtime(TRUE)) {
  204. $result=$this->collection->count($filter?:[]);
  205. if ($fw->CACHE && $ttl)
  206. // Save to cache backend
  207. $cache->set($hash,$result,$ttl);
  208. }
  209. return $result;
  210. }
  211. /**
  212. * Return record at specified offset using criteria of previous
  213. * load() call and make it active
  214. * @return array
  215. * @param $ofs int
  216. **/
  217. function skip($ofs=1) {
  218. $this->document=($out=parent::skip($ofs))?$out->document:[];
  219. if ($this->document && isset($this->trigger['load']))
  220. \Base::instance()->call($this->trigger['load'],$this);
  221. return $out;
  222. }
  223. /**
  224. * Insert new record
  225. * @return array
  226. **/
  227. function insert() {
  228. if (isset($this->document['_id']))
  229. return $this->update();
  230. if (isset($this->trigger['beforeinsert']) &&
  231. \Base::instance()->call($this->trigger['beforeinsert'],
  232. [$this,['_id'=>$this->document['_id']]])===FALSE)
  233. return $this->document;
  234. if ($this->legacy) {
  235. $this->collection->insert($this->document);
  236. $pkey=['_id'=>$this->document['_id']];
  237. }
  238. else {
  239. $result=$this->collection->insertone($this->document);
  240. $pkey=['_id'=>$result->getinsertedid()];
  241. }
  242. if (isset($this->trigger['afterinsert']))
  243. \Base::instance()->call($this->trigger['afterinsert'],
  244. [$this,$pkey]);
  245. $this->load($pkey);
  246. return $this->document;
  247. }
  248. /**
  249. * Update current record
  250. * @return array
  251. **/
  252. function update() {
  253. $pkey=['_id'=>$this->document['_id']];
  254. if (isset($this->trigger['beforeupdate']) &&
  255. \Base::instance()->call($this->trigger['beforeupdate'],
  256. [$this,$pkey])===FALSE)
  257. return $this->document;
  258. $upsert=['upsert'=>TRUE];
  259. if ($this->legacy)
  260. $this->collection->update($pkey,$this->document,$upsert);
  261. else
  262. $this->collection->replaceone($pkey,$this->document,$upsert);
  263. if (isset($this->trigger['afterupdate']))
  264. \Base::instance()->call($this->trigger['afterupdate'],
  265. [$this,$pkey]);
  266. return $this->document;
  267. }
  268. /**
  269. * Delete current record
  270. * @return bool
  271. * @param $quick bool
  272. * @param $filter array
  273. **/
  274. function erase($filter=NULL,$quick=TRUE) {
  275. if ($filter) {
  276. if (!$quick) {
  277. foreach ($this->find($filter) as $mapper)
  278. if (!$mapper->erase())
  279. return FALSE;
  280. return TRUE;
  281. }
  282. return $this->legacy?
  283. $this->collection->remove($filter):
  284. $this->collection->deletemany($filter);
  285. }
  286. $pkey=['_id'=>$this->document['_id']];
  287. if (isset($this->trigger['beforeerase']) &&
  288. \Base::instance()->call($this->trigger['beforeerase'],
  289. [$this,$pkey])===FALSE)
  290. return FALSE;
  291. $result=$this->legacy?
  292. $this->collection->remove(['_id'=>$this->document['_id']]):
  293. $this->collection->deleteone(['_id'=>$this->document['_id']]);
  294. parent::erase();
  295. if (isset($this->trigger['aftererase']))
  296. \Base::instance()->call($this->trigger['aftererase'],
  297. [$this,$pkey]);
  298. return $result;
  299. }
  300. /**
  301. * Reset cursor
  302. * @return NULL
  303. **/
  304. function reset() {
  305. $this->document=[];
  306. parent::reset();
  307. }
  308. /**
  309. * Hydrate mapper object using hive array variable
  310. * @return NULL
  311. * @param $var array|string
  312. * @param $func callback
  313. **/
  314. function copyfrom($var,$func=NULL) {
  315. if (is_string($var))
  316. $var=\Base::instance()->$var;
  317. if ($func)
  318. $var=call_user_func($func,$var);
  319. foreach ($var as $key=>$val)
  320. $this->set($key,$val);
  321. }
  322. /**
  323. * Populate hive array variable with mapper fields
  324. * @return NULL
  325. * @param $key string
  326. **/
  327. function copyto($key) {
  328. $var=&\Base::instance()->ref($key);
  329. foreach ($this->document as $key=>$field)
  330. $var[$key]=$field;
  331. }
  332. /**
  333. * Return field names
  334. * @return array
  335. **/
  336. function fields() {
  337. return array_keys($this->document);
  338. }
  339. /**
  340. * Return the cursor from last query
  341. * @return object|NULL
  342. **/
  343. function cursor() {
  344. return $this->cursor;
  345. }
  346. /**
  347. * Retrieve external iterator for fields
  348. * @return object
  349. **/
  350. function getiterator() {
  351. return new \ArrayIterator($this->cast());
  352. }
  353. /**
  354. * Instantiate class
  355. * @return void
  356. * @param $db object
  357. * @param $collection string
  358. * @param $fields array
  359. **/
  360. function __construct(\DB\Mongo $db,$collection,$fields=NULL) {
  361. $this->db=$db;
  362. $this->legacy=$db->legacy();
  363. $this->collection=$db->selectcollection($collection);
  364. $this->fields=$fields;
  365. $this->reset();
  366. }
  367. }