PageRenderTime 52ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/Comparison_Shopping_Engine/provider/langs/php/lib/filedb.php

https://github.com/vinai-drive-by-commits/Sample-capabilities
PHP | 729 lines | 444 code | 43 blank | 242 comment | 56 complexity | 63554082c8e7968321046b51f23969ec MD5 | raw file
Possible License(s): GPL-3.0
  1. <?php
  2. /**
  3. Simple Flat-file ORM 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 FileDB
  11. @version 2.0.9
  12. **/
  13. //! Flat-file data access layer
  14. class FileDB extends Base {
  15. //@{ Storage formats
  16. const
  17. FORMAT_Plain=0,
  18. FORMAT_Serialized=1,
  19. FORMAT_JSON=2,
  20. FORMAT_GZip=3;
  21. //@}
  22. //@{ Locale-specific error/exception messages
  23. const
  24. TEXT_Criteria='Invalid criteria: %s';
  25. //@}
  26. public
  27. //! Exposed properties
  28. $path,$result;
  29. private
  30. //! Storage settings
  31. $format,
  32. //! Journal identifier
  33. $journal;
  34. /**
  35. Begin transaction
  36. @public
  37. **/
  38. function begin() {
  39. $this->journal=base_convert(microtime(TRUE)*100,10,16);
  40. }
  41. /**
  42. Rollback transaction
  43. @public
  44. **/
  45. function rollback() {
  46. if ($glob=glob($this->path.'*.jnl.'.$this->journal))
  47. foreach ($glob as $temp) {
  48. self::mutex(
  49. function() use($temp) {
  50. unlink($temp);
  51. },
  52. $temp
  53. );
  54. break;
  55. }
  56. $this->journal=NULL;
  57. }
  58. /**
  59. Commit transaction
  60. @public
  61. **/
  62. function commit() {
  63. if ($glob=glob($this->path.'*.jnl.'.$this->journal))
  64. foreach ($glob as $temp) {
  65. $file=preg_replace('/\.jnl\.'.$this->journal.'$/','',$temp);
  66. self::mutex(
  67. function() use($temp,$file) {
  68. @rename($temp,$file);
  69. },
  70. $temp,$file
  71. );
  72. break;
  73. }
  74. $this->journal=NULL;
  75. }
  76. /**
  77. Retrieve contents of flat-file
  78. @return mixed
  79. @param $file string
  80. @public
  81. **/
  82. function read($file) {
  83. $file=$this->path.$file;
  84. if (!is_file($file))
  85. return array();
  86. $text=self::getfile($file);
  87. switch ($this->format) {
  88. case self::FORMAT_GZip:
  89. $text=gzinflate($text);
  90. case self::FORMAT_Plain:
  91. if (ini_get('allow_url_fopen') &&
  92. ini_get('allow_url_include'))
  93. // Stream wrap
  94. $file='data:text/plain,'.urlencode($text);
  95. else {
  96. $file=self::$vars['TEMP'].$_SERVER['SERVER_NAME'].'.'.
  97. 'php.'.self::hash($file);
  98. self::putfile($file,$text);
  99. }
  100. $instance=new F3instance;
  101. $out=$instance->sandbox($file);
  102. break;
  103. case self::FORMAT_Serialized:
  104. $out=unserialize($text);
  105. break;
  106. case self::FORMAT_JSON:
  107. $out=json_decode($text,TRUE);
  108. }
  109. return $out;
  110. }
  111. /**
  112. Store PHP expression in flat-file
  113. @param $file string
  114. @param $expr mixed
  115. @public
  116. **/
  117. function write($file,$expr) {
  118. $file=$this->path.$file;
  119. $auto=FALSE;
  120. if (!$this->journal) {
  121. $auto=TRUE;
  122. $this->begin();
  123. if (is_file($file))
  124. copy($file,$file.'.jnl.'.$this->journal);
  125. }
  126. $file.='.jnl.'.$this->journal;
  127. if (!$expr)
  128. $expr=array();
  129. $out='<?php'."\n\n".'return '.self::stringify($expr).';'."\n";
  130. switch ($this->format) {
  131. case self::FORMAT_GZip:
  132. $out=gzdeflate($out);
  133. break;
  134. case self::FORMAT_Serialized:
  135. $out=serialize($expr);
  136. break;
  137. case self::FORMAT_JSON:
  138. $out=json_encode($expr);
  139. }
  140. if (self::putfile($file,$out)===FALSE)
  141. $this->rollback();
  142. elseif ($auto)
  143. $this->commit();
  144. }
  145. /**
  146. Convert database to another format
  147. @return bool
  148. @param $fmt int
  149. @public
  150. **/
  151. function convert($fmt) {
  152. $glob=glob($this->path.'*');
  153. if ($glob) {
  154. foreach ($glob as $file) {
  155. $file=str_replace($this->path,'',$file);
  156. $out=$this->read($file);
  157. switch ($fmt) {
  158. case self::FORMAT_GZip:
  159. $out=gzdeflate($out);
  160. break;
  161. case self::FORMAT_Serialized:
  162. $out=serialize($out);
  163. break;
  164. case self::FORMAT_JSON;
  165. $out=json_encode($out);
  166. }
  167. $this->format=$fmt;
  168. $this->write($file,$out);
  169. }
  170. return TRUE;
  171. }
  172. return FALSE;
  173. }
  174. /**
  175. Custom session handler
  176. @param $table string
  177. @public
  178. **/
  179. function session($table='sessions') {
  180. session_set_save_handler(
  181. // Open
  182. function($path,$name) {
  183. register_shutdown_function('session_commit');
  184. return TRUE;
  185. },
  186. // Close
  187. function() {
  188. return TRUE;
  189. },
  190. // Read
  191. function($id) use($table) {
  192. $jig=new Jig($table);
  193. $jig->load(array('id'=>$id));
  194. return $jig->dry()?FALSE:$jig->data;
  195. },
  196. // Write
  197. function($id,$data) use($table) {
  198. $jig=new Jig($table);
  199. $jig->load(array('id'=>$id));
  200. $jig->id=$id;
  201. $jig->data=$data;
  202. $jig->stamp=time();
  203. $jig->save();
  204. return TRUE;
  205. },
  206. // Delete
  207. function($id) use($table) {
  208. $jig=new Jig($table);
  209. $jig->erase(array('id'=>$id));
  210. return TRUE;
  211. },
  212. // Cleanup
  213. function($max) use($table) {
  214. $jig=new Jig($table);
  215. $jig->erase(
  216. array(
  217. '_PHP_'=>
  218. array(
  219. 'stamp'=>
  220. function($stamp) use($max) {
  221. return $stamp+$max<time();
  222. }
  223. )
  224. )
  225. );
  226. return TRUE;
  227. }
  228. );
  229. }
  230. /**
  231. Class constructor
  232. @param $path string
  233. @param $fmt int
  234. @public
  235. **/
  236. function __construct($path=NULL,$fmt=self::FORMAT_Plain) {
  237. $path=self::fixslashes(realpath(self::resolve($path)).'/');
  238. if (!is_dir($path))
  239. self::mkdir($path);
  240. list($this->path,$this->format)=array($path,$fmt);
  241. if (!isset(self::$vars['DB']))
  242. self::$vars['DB']=$this;
  243. }
  244. }
  245. //! Flat-file ORM
  246. class Jig extends Base {
  247. //@{ Locale-specific error/exception messages
  248. const
  249. TEXT_JigConnect='Undefined database',
  250. TEXT_JigEmpty='Jig is empty',
  251. TEXT_JigTable='Table %s does not exist',
  252. TEXT_JigField='Field %s does not exist';
  253. //@}
  254. //@{
  255. //! Jig properties
  256. public
  257. $_id;
  258. private
  259. $db,$table,$object,$mod,$cond,$seq,$ofs;
  260. //@}
  261. /**
  262. Jig factory
  263. @return object
  264. @param $obj array
  265. @public
  266. **/
  267. function factory($obj) {
  268. $self=get_class($this);
  269. $jig=new $self($this->table,$this->db);
  270. $jig->_id=$obj['_id'];
  271. unset($obj['_id']);
  272. foreach ($obj as $key=>$val)
  273. $jig->object[$key]=$val;
  274. return $jig;
  275. }
  276. /**
  277. Evaluate query criteria
  278. @return boolean
  279. @param $expr array
  280. @param $obj array
  281. @private
  282. **/
  283. private function check(array $expr,array $obj) {
  284. if (is_null($expr))
  285. return TRUE;
  286. if (is_array($expr)) {
  287. $result=TRUE;
  288. foreach ($expr as $field=>$cond) {
  289. if ($field=='_OR_') {
  290. if (!is_array($cond)) {
  291. trigger_error(
  292. sprintf(
  293. self::TEXT_Criteria,
  294. $this->stringify($cond)
  295. )
  296. );
  297. return FALSE;
  298. }
  299. foreach ($cond as $val)
  300. // Short circuit
  301. if ($this->check($val,$obj))
  302. return TRUE;
  303. return FALSE;
  304. }
  305. elseif ($field=='_PHP_') {
  306. list($key,$val)=array(key($cond),current($cond));
  307. if (!is_array($cond) || !is_callable($val)) {
  308. trigger_error(
  309. sprintf(
  310. self::TEXT_Callback,
  311. $this->stringify($val)
  312. )
  313. );
  314. return FALSE;
  315. }
  316. return isset($obj[$key])?
  317. call_user_func($val,$obj[$key]):TRUE;
  318. }
  319. elseif (!isset($obj[$field]))
  320. $result=FALSE;
  321. elseif (is_array($cond)) {
  322. $map=array(
  323. '='=>'==',
  324. 'eq'=>'==',
  325. 'gt'=>'>',
  326. 'lt'=>'<',
  327. 'gte'=>'>=',
  328. 'lte'=>'<=',
  329. '<>'=>'!=',
  330. 'ne'=>'!='
  331. );
  332. foreach ($cond as $op=>$val)
  333. $result=($op=='_OR_' || $op=='_PHP_')?
  334. $this->check($val,$obj):
  335. ($op=='regex'?
  336. preg_match('/'.$val.'/s',$obj[$field]):
  337. eval(
  338. 'return $obj[$field]'.
  339. (isset($map[$op])?$map[$op]:$op).
  340. '(is_string($val)?'.
  341. 'self::resolve($val):$val);'));
  342. }
  343. else
  344. $result=($obj[$field]==(is_string($cond)?
  345. self::resolve($cond):$cond));
  346. if (!$result)
  347. break;
  348. }
  349. return $result;
  350. }
  351. return (bool)$expr;
  352. }
  353. /**
  354. Return current object contents as an array
  355. @return array
  356. @public
  357. **/
  358. function cast() {
  359. return array_merge(array('_id'=>$this->_id),$this->object);
  360. }
  361. /**
  362. Return an array of objects matching criteria
  363. @return array
  364. @param $cond array
  365. @param $seq array
  366. @param $limit mixed
  367. @param $ofs int
  368. @param $jig boolean
  369. @public
  370. **/
  371. function find(
  372. array $cond=NULL,array $seq=NULL,$limit=0,$ofs=0,$jig=TRUE) {
  373. $table=$this->db->read($this->table);
  374. $result=array();
  375. if ($table) {
  376. if (is_array($seq))
  377. foreach (array_reverse($seq,TRUE) as $key=>$sort)
  378. Matrix::sort($table,$key,$sort);
  379. foreach ($table as $key=>$obj) {
  380. $obj['_id']=$key;
  381. if (is_null($cond) || $this->check($cond,$obj))
  382. $result[]=$jig?$this->factory($obj):$obj;
  383. }
  384. $result=array_slice($result,$ofs,$limit?:NULL);
  385. }
  386. $this->db->result=$result;
  387. return $result;
  388. }
  389. /**
  390. Return an array of associative arrays matching criteria
  391. @return array
  392. @param $cond array
  393. @param $seq array
  394. @param $limit mixed
  395. @param $ofs int
  396. @public
  397. **/
  398. function afind(array $cond=NULL,array $seq=NULL,$limit=0,$ofs=0) {
  399. return $this->find($cond,$seq,$limit,$ofs,FALSE);
  400. }
  401. /**
  402. Return the first object that matches the specified criteria
  403. @return array
  404. @param $cond array
  405. @param $seq array
  406. @param $ofs int
  407. @public
  408. **/
  409. function findone(array $cond=NULL,array $seq=NULL,$ofs=0) {
  410. list($result)=$this->find($cond,$seq,1,$ofs)?:array(NULL);
  411. return $result;
  412. }
  413. /**
  414. Return the array equivalent of the object matching criteria
  415. @return array
  416. @param $cond array
  417. @param $seq array
  418. @param $ofs int
  419. @public
  420. **/
  421. function afindone(array $cond=NULL,array $seq=NULL,$ofs=0) {
  422. list($result)=$this->afind($cond,$seq,1,$ofs)?:array(NULL);
  423. return $result;
  424. }
  425. /**
  426. Count objects that match condition
  427. @return int
  428. @param $cond array
  429. @public
  430. **/
  431. function found(array $cond=NULL) {
  432. return count($this->find($cond));
  433. }
  434. /**
  435. Hydrate Jig with elements from framework array variable, keys of
  436. which will be identical to object properties
  437. @param $name string
  438. @public
  439. **/
  440. function copyFrom($name) {
  441. if (is_array($ref=self::ref($name))) {
  442. foreach ($ref as $key=>$val)
  443. $this->object[$key]=$val;
  444. $this->mod=TRUE;
  445. }
  446. }
  447. /**
  448. Populate framework array variable with Jig properties, keys of
  449. which will have names identical to object properties
  450. @param $name string
  451. @param $fields string
  452. @public
  453. **/
  454. function copyTo($name,$fields=NULL) {
  455. if ($this->dry()) {
  456. trigger_error(self::TEXT_JigEmpty);
  457. return FALSE;
  458. }
  459. if (is_string($fields))
  460. $list=preg_split('/[\|;,]/',$fields,0,PREG_SPLIT_NO_EMPTY);
  461. foreach (array_keys($this->object) as $field)
  462. if (!isset($list) || in_array($field,$list)) {
  463. $var=&self::ref($name);
  464. $var[$field]=$this->object[$field];
  465. }
  466. }
  467. /**
  468. Dehydrate Jig
  469. @public
  470. **/
  471. function reset() {
  472. // Dehydrate
  473. $this->_id=NULL;
  474. $this->object=NULL;
  475. $this->mod=NULL;
  476. $this->cond=NULL;
  477. $this->seq=NULL;
  478. $this->ofs=0;
  479. }
  480. /**
  481. Retrieve first object that satisfies criteria
  482. @return mixed
  483. @param $cond array
  484. @param $seq array
  485. @param $ofs int
  486. @public
  487. **/
  488. function load(array $cond=NULL,array $seq=NULL,$ofs=0) {
  489. if ($ofs>-1) {
  490. $this->ofs=0;
  491. if ($jig=$this->findone($cond,$seq,$ofs)) {
  492. if (method_exists($this,'beforeLoad') &&
  493. $this->beforeLoad()===FALSE)
  494. return;
  495. // Hydrate Jig
  496. $this->_id=$jig->_id;
  497. foreach ($jig->object as $key=>$val)
  498. $this->object[$key]=$val;
  499. list($this->cond,$this->seq,$this->ofs)=
  500. array($cond,$seq,$ofs);
  501. if (method_exists($this,'afterLoad'))
  502. $this->afterLoad();
  503. $this->mod=NULL;
  504. return $this;
  505. }
  506. }
  507. $this->reset();
  508. return FALSE;
  509. }
  510. /**
  511. Retrieve N-th object relative to current using the same criteria
  512. that hydrated Jig
  513. @return mixed
  514. @param $count int
  515. @public
  516. **/
  517. function skip($count=1) {
  518. if ($this->dry()) {
  519. trigger_error(self::TEXT_JigEmpty);
  520. return FALSE;
  521. }
  522. return $this->load($this->cond,$this->seq,$this->ofs+$count);
  523. }
  524. /**
  525. Return next record
  526. @return array
  527. @public
  528. **/
  529. function next() {
  530. return $this->skip();
  531. }
  532. /**
  533. Return previous record
  534. @return array
  535. @public
  536. **/
  537. function prev() {
  538. return $this->skip(-1);
  539. }
  540. /**
  541. Insert/update object
  542. @public
  543. **/
  544. function save() {
  545. if ($this->dry() ||
  546. method_exists($this,'beforeSave') &&
  547. $this->beforeSave()===FALSE)
  548. return;
  549. if ($this->mod) {
  550. // Object modified
  551. $table=$this->db->read($this->table);
  552. $obj=$this->object;
  553. if (!is_null($this->_id))
  554. // Update
  555. $id=$this->_id;
  556. else {
  557. // Insert with concurrency control
  558. while (($id=base_convert(microtime(TRUE)*100,10,16)) &&
  559. isset($table[$id])) {
  560. usleep(mt_rand(0,100));
  561. // Reload table
  562. $table=$this->db->read($this->table);
  563. }
  564. $this->_id=$id;
  565. }
  566. $table[$id]=$obj;
  567. // Save to file
  568. $this->db->write($this->table,$table);
  569. }
  570. if (method_exists($this,'afterSave'))
  571. $this->afterSave();
  572. }
  573. /**
  574. Delete object/s and reset Jig
  575. @param $cond array
  576. @param $force boolean
  577. @public
  578. **/
  579. function erase(array $cond=NULL,$force=FALSE) {
  580. if (method_exists($this,'beforeErase') &&
  581. $this->beforeErase()===FALSE)
  582. return;
  583. if (!$cond)
  584. $cond=$this->cond;
  585. if ($force || $cond) {
  586. $table=$this->db->read($this->table);
  587. foreach ($this->find($cond) as $found)
  588. unset($table[$found->_id]);
  589. // Save to file
  590. $this->db->write($this->table,$table);
  591. }
  592. $this->reset();
  593. if (method_exists($this,'afterErase'))
  594. $this->afterErase();
  595. }
  596. /**
  597. Return TRUE if Jig is NULL
  598. @return boolean
  599. @public
  600. **/
  601. function dry() {
  602. return is_null($this->object);
  603. }
  604. /**
  605. Synchronize Jig and underlying file
  606. @param $table string
  607. @param $db object
  608. @public
  609. **/
  610. function sync($table,$db=NULL) {
  611. if (!$db) {
  612. if (isset(self::$vars['DB']) &&
  613. is_a(self::$vars['DB'],'FileDB'))
  614. $db=self::$vars['DB'];
  615. else {
  616. trigger_error(self::TEXT_JigConnect);
  617. return;
  618. }
  619. }
  620. if (method_exists($this,'beforeSync') &&
  621. $this->beforeSync()===FALSE)
  622. return;
  623. // Initialize Jig
  624. list($this->db,$this->table)=array($db,$table);
  625. if (method_exists($this,'afterSync'))
  626. $this->afterSync();
  627. }
  628. /**
  629. Return value of Jig-mapped property
  630. @return boolean
  631. @param $name string
  632. @public
  633. **/
  634. function &__get($name) {
  635. return $this->object[$name];
  636. }
  637. /**
  638. Assign value to Jig-mapped property
  639. @return boolean
  640. @param $name string
  641. @param $val mixed
  642. @public
  643. **/
  644. function __set($name,$val) {
  645. if (!isset($this->object[$name]) || $this->object[$name]!=$val)
  646. $this->mod=TRUE;
  647. $this->object[$name]=$val;
  648. }
  649. /**
  650. Clear value of Jig-mapped property
  651. @return boolean
  652. @param $name string
  653. @public
  654. **/
  655. function __unset($name) {
  656. unset($this->object[$name]);
  657. $this->mod=TRUE;
  658. }
  659. /**
  660. Return TRUE if Jig-mapped property exists
  661. @return boolean
  662. @param $name string
  663. @public
  664. **/
  665. function __isset($name) {
  666. return array_key_exists($name,$this->object);
  667. }
  668. /**
  669. Display class name if conversion to string is attempted
  670. @public
  671. **/
  672. function __toString() {
  673. return get_class($this);
  674. }
  675. /**
  676. Class constructor
  677. @public
  678. **/
  679. function __construct() {
  680. // Execute mandatory sync method
  681. call_user_func_array(array($this,'sync'),func_get_args());
  682. }
  683. }