PageRenderTime 60ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/m2.php

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