PageRenderTime 48ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/m2.php

https://gitlab.com/f3/f3-skeleton
PHP | 541 lines | 290 code | 32 blank | 219 comment | 39 complexity | 5d7b54b208eb2b46d47b288a76974558 MD5 | raw file
Possible License(s): GPL-3.0
  1. <?php
  2. /**
  3. MongoDB Mapper for the PHP Fat-Free Framework
  4. The contents of this file are subject to the terms of the GNU General
  5. Public License Version 3.0. You may not use this file except in
  6. compliance with the license. Any of the license terms and conditions
  7. can be waived if you get permission from the copyright holder.
  8. Copyright (c) 2009-2011 F3::Factory
  9. Bong Cosca <bong.cosca@yahoo.com>
  10. @package M2
  11. @version 2.0.5
  12. **/
  13. //! MongoDB Mapper
  14. class M2 extends Base {
  15. //@{ Locale-specific error/exception messages
  16. const
  17. TEXT_M2Connect='Undefined database',
  18. TEXT_M2Empty='M2 is empty',
  19. TEXT_M2Collection='Collection %s does not exist';
  20. //@}
  21. //@{
  22. //! M2 properties
  23. private
  24. $db,$collection,$object,$cond,$seq,$ofs;
  25. //@}
  26. /**
  27. M2 factory
  28. @return object
  29. @param $doc array
  30. @public
  31. **/
  32. function factory($doc) {
  33. $self=get_class($this);
  34. $m2=new $self($this->collection,$this->db);
  35. foreach ($doc as $key=>$val)
  36. $m2->object[$key]=$val;
  37. return $m2;
  38. }
  39. /**
  40. Retrieve from cache; or save query results to cache if not
  41. previously executed
  42. @param $query array
  43. @param $ttl int
  44. @private
  45. **/
  46. private function cache(array $query,$ttl) {
  47. $cmd=json_encode($query,TRUE);
  48. $hash='mdb.'.self::hash($cmd);
  49. $cached=Cache::cached($hash);
  50. $db=(string)$this->db;
  51. $stats=&self::ref('STATS');
  52. if ($ttl && $cached && $_SERVER['REQUEST_TIME']-$cached<$ttl) {
  53. // Gather cached queries for profiler
  54. if (!isset($stats[$db]['cache'][$cmd]))
  55. $stats[$db]['cache'][$cmd]=0;
  56. $stats[$db]['cache'][$cmd]++;
  57. // Retrieve from cache
  58. return Cache::get($hash);
  59. }
  60. else {
  61. $result=$this->exec($query);
  62. if ($ttl)
  63. Cache::set($hash,$result,$ttl);
  64. // Gather real queries for profiler
  65. if (!isset($stats[$db]['queries'][$cmd]))
  66. $stats[$db]['queries'][$cmd]=0;
  67. $stats[$db]['queries'][$cmd]++;
  68. return $result;
  69. }
  70. }
  71. /**
  72. Execute MongoDB query
  73. @return mixed
  74. @param $query array
  75. @private
  76. **/
  77. private function exec(array $query) {
  78. $cmd=json_encode($query,TRUE);
  79. $hash='mdb.'.self::hash($cmd);
  80. // Except for save method, collection must exist
  81. $list=$this->db->listCollections();
  82. foreach ($list as &$coll)
  83. $coll=$coll->getName();
  84. if ($query['method']!='save' && !in_array($this->collection,$list)) {
  85. trigger_error(sprintf(self::TEXT_M2Collection,$this->collection));
  86. return;
  87. }
  88. if (isset($query['map'])) {
  89. // Create temporary collection
  90. $ref=$this->db->selectCollection($hash);
  91. $ref->batchInsert(iterator_to_array($out,FALSE));
  92. $map=$query['map'];
  93. $func='function() {}';
  94. // Map-reduce
  95. $tmp=$this->db->command(
  96. array(
  97. 'mapreduce'=>$ref->getName(),
  98. 'map'=>isset($map['map'])?
  99. $map['map']:$func,
  100. 'reduce'=>isset($map['reduce'])?
  101. $map['reduce']:$func,
  102. 'finalize'=>isset($map['finalize'])?
  103. $map['finalize']:$func
  104. )
  105. );
  106. if (!$tmp['ok']) {
  107. trigger_error($tmp['errmsg']);
  108. return FALSE;
  109. }
  110. $ref->remove();
  111. // Aggregate the result
  112. foreach (iterator_to_array($this->db->
  113. selectCollection($tmp['result'])->find(),FALSE) as $agg)
  114. $ref->insert($agg['_id']);
  115. $out=$ref->find();
  116. $ref->drop();
  117. }
  118. elseif (preg_match('/find/',$query['method'])) {
  119. // find and findOne methods allow selection of fields
  120. $out=call_user_func(
  121. array(
  122. $this->db->selectCollection($this->collection),
  123. $query['method']
  124. ),
  125. isset($query['cond'])?$query['cond']:array(),
  126. isset($query['fields'])?$query['fields']:array()
  127. );
  128. if ($query['method']=='find') {
  129. if (isset($query['seq']))
  130. // Sort results
  131. $out=$out->sort($query['seq']);
  132. if (isset($query['ofs']))
  133. // Skip to record ofs
  134. $out=$out->skip($query['ofs']);
  135. if (isset($query['limit']) && $query['limit'])
  136. // Limit number of results
  137. $out=$out->limit($query['limit']);
  138. // Convert cursor to PHP array
  139. $out=iterator_to_array($out,FALSE);
  140. if ($query['m2'])
  141. foreach ($out as &$obj)
  142. $obj=$this->factory($obj);
  143. }
  144. }
  145. else
  146. $out=preg_match('/count|remove/',$query['method'])?
  147. // count() and remove() methods can specify cond
  148. call_user_func(
  149. array(
  150. $this->db->selectCollection($this->collection),
  151. $query['method']
  152. ),
  153. isset($query['cond'])?$query['cond']:array()
  154. ):
  155. // All other queries
  156. call_user_func(
  157. array(
  158. $this->db->selectCollection($this->collection),
  159. $query['method']
  160. ),
  161. $this->object
  162. );
  163. return $out;
  164. }
  165. /**
  166. Return current object contents as an array
  167. @return array
  168. @public
  169. **/
  170. function cast() {
  171. return $this->object;
  172. }
  173. /**
  174. Similar to M2->find method but provides more fine-grained control
  175. over specific fields and map-reduced results
  176. @return array
  177. @param $fields array
  178. @param $cond mixed
  179. @param $map mixed
  180. @param $seq mixed
  181. @param $limit mixed
  182. @param $ofs mixed
  183. @param $ttl int
  184. @param $m2 bool
  185. @public
  186. **/
  187. function lookup(
  188. array $fields,
  189. $cond=NULL,
  190. $map=NULL,
  191. $seq=NULL,
  192. $limit=0,
  193. $ofs=0,
  194. $ttl=0,
  195. $m2=TRUE) {
  196. $query=array(
  197. 'method'=>'find',
  198. 'fields'=>$fields,
  199. 'cond'=>$cond,
  200. 'map'=>$map,
  201. 'seq'=>$seq,
  202. 'limit'=>$limit,
  203. 'ofs'=>$ofs,
  204. 'm2'=>$m2
  205. );
  206. return $ttl?$this->cache($query,$ttl):$this->exec($query);
  207. }
  208. /**
  209. Alias of the lookup method
  210. @public
  211. **/
  212. function select() {
  213. // PHP doesn't allow direct use as function argument
  214. $args=func_get_args();
  215. return call_user_func_array(array($this,'lookup'),$args);
  216. }
  217. /**
  218. Return an array of collection objects matching cond
  219. @return array
  220. @param $cond mixed
  221. @param $seq mixed
  222. @param $limit mixed
  223. @param $ofs mixed
  224. @param $ttl int
  225. @param $m2 bool
  226. @public
  227. **/
  228. function find($cond=NULL,$seq=NULL,$limit=NULL,$ofs=0,$ttl=0,$m2=TRUE) {
  229. $query=array(
  230. 'method'=>'find',
  231. 'cond'=>$cond,
  232. 'seq'=>$seq,
  233. 'limit'=>$limit,
  234. 'ofs'=>$ofs,
  235. 'm2'=>$m2
  236. );
  237. return $ttl?$this->cache($query,$ttl):$this->exec($query);
  238. }
  239. /**
  240. Return an array of associative arrays matching cond
  241. @return array
  242. @param $cond mixed
  243. @param $seq mixed
  244. @param $limit mixed
  245. @param $ofs int
  246. @param $ttl int
  247. @public
  248. **/
  249. function afind($cond=NULL,$seq=NULL,$limit=NULL,$ofs=0,$ttl=0) {
  250. return $this->find($cond,$seq,$limit,$ofs,$ttl,FALSE);
  251. }
  252. /**
  253. Return the first object that matches the specified cond
  254. @return array
  255. @param $cond mixed
  256. @param $seq mixed
  257. @param $ofs int
  258. @param $ttl int
  259. @public
  260. **/
  261. function findone($cond=NULL,$seq=NULL,$ofs=0,$ttl=0) {
  262. list($result)=$this->find($cond,$seq,1,$ofs,$ttl)?:array(NULL);
  263. return $result;
  264. }
  265. /**
  266. Return the array equivalent of the object matching criteria
  267. @return array
  268. @param $cond mixed
  269. @param $seq mixed
  270. @param $ofs int
  271. @public
  272. **/
  273. function afindone($cond=NULL,$seq=NULL,$ofs=0) {
  274. list($result)=$this->afind($cond,$seq,1,$ofs)?:array(NULL);
  275. return $result;
  276. }
  277. /**
  278. Count objects that match condition
  279. @return int
  280. @param $cond mixed
  281. @public
  282. **/
  283. function found($cond=NULL) {
  284. $result=$this->exec(
  285. array(
  286. 'method'=>'count',
  287. 'cond'=>$cond
  288. )
  289. );
  290. return $result;
  291. }
  292. /**
  293. Hydrate M2 with elements from framework array variable, keys of
  294. which will be identical to field names in collection object
  295. @param $name string
  296. @public
  297. **/
  298. function copyFrom($name) {
  299. if (is_array($ref=self::ref($name)))
  300. foreach ($ref as $key=>$val)
  301. $this->object[$key]=$val;
  302. }
  303. /**
  304. Populate framework array variable with M2 properties, keys of
  305. which will have names identical to fields in collection object
  306. @param $name string
  307. @param $fields string
  308. @public
  309. **/
  310. function copyTo($name,$fields=NULL) {
  311. if ($this->dry()) {
  312. trigger_error(self::TEXT_M2Empty);
  313. return FALSE;
  314. }
  315. if (is_string($fields))
  316. $list=preg_split('/[\|;,]/',$fields,0,PREG_SPLIT_NO_EMPTY);
  317. foreach (array_keys($this->object) as $field)
  318. if (!isset($list) || in_array($field,$list)) {
  319. $var=&self::ref($name);
  320. $var[$field]=$this->object[$field];
  321. }
  322. }
  323. /**
  324. Dehydrate M2
  325. @public
  326. **/
  327. function reset() {
  328. // Dehydrate
  329. $this->object=NULL;
  330. $this->cond=NULL;
  331. $this->seq=NULL;
  332. $this->ofs=NULL;
  333. }
  334. /**
  335. Retrieve first collection object that satisfies cond
  336. @return mixed
  337. @param $cond mixed
  338. @param $seq mixed
  339. @param $ofs int
  340. @public
  341. **/
  342. function load($cond=NULL,$seq=NULL,$ofs=0) {
  343. if ($ofs>-1) {
  344. $this->ofs=0;
  345. if ($m2=$this->findOne($cond,$seq,$ofs)) {
  346. if (method_exists($this,'beforeLoad') &&
  347. $this->beforeLoad()===FALSE)
  348. return;
  349. // Hydrate M2
  350. foreach ($m2->object as $key=>$val)
  351. $this->object[$key]=$val;
  352. list($this->cond,$this->seq,$this->ofs)=
  353. array($cond,$seq,$ofs);
  354. if (method_exists($this,'afterLoad'))
  355. $this->afterLoad();
  356. return $this;
  357. }
  358. }
  359. $this->reset();
  360. return FALSE;
  361. }
  362. /**
  363. Retrieve N-th object relative to current using the same cond
  364. that hydrated M2
  365. @return mixed
  366. @param $count int
  367. @public
  368. **/
  369. function skip($count=1) {
  370. if ($this->dry()) {
  371. trigger_error(self::TEXT_M2Empty);
  372. return FALSE;
  373. }
  374. return $this->load($this->cond,$this->seq,$this->ofs+$count);
  375. }
  376. /**
  377. Return next record
  378. @return array
  379. @public
  380. **/
  381. function next() {
  382. return $this->skip();
  383. }
  384. /**
  385. Return previous record
  386. @return array
  387. @public
  388. **/
  389. function prev() {
  390. return $this->skip(-1);
  391. }
  392. /**
  393. Insert/update collection object
  394. @public
  395. **/
  396. function save() {
  397. if ($this->dry() ||
  398. method_exists($this,'beforeSave') &&
  399. $this->beforeSave()===FALSE)
  400. return;
  401. // Let the MongoDB driver decide how to persist the
  402. // collection object in the database
  403. $obj=$this->object;
  404. $this->exec(array('method'=>'save'));
  405. if (!isset($obj['_id']))
  406. // Reload to retrieve MongoID of inserted object
  407. $this->object=
  408. $this->exec(array('method'=>'findOne','cond'=>$obj));
  409. if (method_exists($this,'afterSave'))
  410. $this->afterSave();
  411. }
  412. /**
  413. Delete collection object and reset M2
  414. @public
  415. **/
  416. function erase() {
  417. if (method_exists($this,'beforeErase') &&
  418. $this->beforeErase()===FALSE)
  419. return;
  420. $this->exec(array('method'=>'remove','cond'=>$this->cond));
  421. $this->reset();
  422. if (method_exists($this,'afterErase'))
  423. $this->afterErase();
  424. }
  425. /**
  426. Return TRUE if M2 is NULL
  427. @return bool
  428. @public
  429. **/
  430. function dry() {
  431. return is_null($this->object);
  432. }
  433. /**
  434. Synchronize M2 and MongoDB collection
  435. @param $coll string
  436. @param $db object
  437. @public
  438. **/
  439. function sync($coll,$db=NULL) {
  440. if (!in_array('mongo',get_loaded_extensions())) {
  441. // MongoDB extension not activated
  442. trigger_error(sprintf(self::TEXT_PHPExt,'mongo'));
  443. return;
  444. }
  445. if (!$db) {
  446. if (isset(self::$vars['DB']) && is_a(self::$vars['DB'],'MongoDB'))
  447. $db=self::$vars['DB'];
  448. else {
  449. trigger_error(self::TEXT_M2Connect);
  450. return;
  451. }
  452. }
  453. if (method_exists($this,'beforeSync') &&
  454. $this->beforeSync()===FALSE)
  455. return;
  456. // Initialize M2
  457. list($this->db,$this->collection)=array($db,$coll);
  458. if (method_exists($this,'afterSync'))
  459. $this->afterSync();
  460. }
  461. /**
  462. Return value of M2-mapped field
  463. @return bool
  464. @param $name string
  465. @public
  466. **/
  467. function &__get($name) {
  468. return $this->object[$name];
  469. }
  470. /**
  471. Assign value to M2-mapped field
  472. @return bool
  473. @param $name string
  474. @param $val mixed
  475. @public
  476. **/
  477. function __set($name,$val) {
  478. $this->object[$name]=$val;
  479. }
  480. /**
  481. Clear value of M2-mapped field
  482. @return bool
  483. @param $name string
  484. @public
  485. **/
  486. function __unset($name) {
  487. unset($this->object[$name]);
  488. }
  489. /**
  490. Return TRUE if M2-mapped field exists
  491. @return bool
  492. @param $name string
  493. @public
  494. **/
  495. function __isset($name) {
  496. return array_key_exists($name,$this->object);
  497. }
  498. /**
  499. Class constructor
  500. @public
  501. **/
  502. function __construct() {
  503. // Execute mandatory sync method
  504. call_user_func_array(array($this,'sync'),func_get_args());
  505. }
  506. }