PageRenderTime 77ms CodeModel.GetById 28ms RepoModel.GetById 1ms app.codeStats 0ms

/autoload/axon.php

https://github.com/seyyah/f3kulmysql
PHP | 549 lines | 324 code | 31 blank | 194 comment | 44 complexity | 05d57039deb0350e938b0572a56dc975 MD5 | raw file
  1. <?php
  2. /**
  3. Axon 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-2010 F3 Factory
  9. Bong Cosca <bong.cosca@yahoo.com>
  10. @package Auth
  11. @version 1.3.21
  12. **/
  13. //! Axon Object Relational Mapper
  14. class Axon {
  15. //! Minimum framework version required to run
  16. const F3_Minimum='1.3.21';
  17. //@{
  18. //! Locale-specific error/exception messages
  19. const
  20. TEXT_AxonTable='Unable to map table {@CONTEXT} to Axon',
  21. TEXT_AxonEmpty='Axon is empty',
  22. TEXT_AxonNotMapped='The field {@CONTEXT} does not exist',
  23. TEXT_AxonCantUndef='Cannot undefine an Axon-mapped field',
  24. TEXT_AxonCantUnset='Cannot unset an Axon-mapped field',
  25. TEXT_AxonConflict='Name conflict with Axon-mapped field',
  26. TEXT_AxonInvalid='Invalid virtual field expression',
  27. TEXT_AxonReadOnly='Virtual fields are read-only',
  28. TEXT_AxonEngine='Database engine is not supported';
  29. //@}
  30. //@{
  31. //! Axon properties
  32. private $db=NULL;
  33. private $table=NULL;
  34. private $keys=array();
  35. private $criteria=NULL;
  36. private $order=NULL;
  37. private $offset=NULL;
  38. private $fields=array();
  39. private $virtual=array();
  40. private $empty=TRUE;
  41. //@}
  42. /**
  43. Similar to Axon->find method but provides more fine-grained control
  44. over specific fields and grouping of results
  45. @param $_fields string
  46. @param $_criteria mixed
  47. @param $_grouping mixed
  48. @param $_order mixed
  49. @param $_limit mixed
  50. @param $_ttl integer
  51. @public
  52. **/
  53. public function lookup(
  54. $_fields,
  55. $_criteria=NULL,
  56. $_grouping=NULL,
  57. $_order=NULL,
  58. $_limit=NULL,
  59. $_ttl=0) {
  60. return F3::sql(
  61. 'SELECT '.$_fields.' FROM '.$this->table.
  62. (is_null($_criteria)?'':(' WHERE '.$_criteria)).
  63. (is_null($_grouping)?'':(' GROUP BY '.$_grouping)).
  64. (is_null($_order)?'':(' ORDER BY '.$_order)).
  65. (is_null($_limit)?'':(' LIMIT '.$_limit)).';',
  66. $this->db,
  67. $_ttl
  68. );
  69. }
  70. /**
  71. Alias of the lookup method
  72. @public
  73. **/
  74. public function select() {
  75. // PHP doesn't allow direct use as function argument
  76. $_args=func_get_args();
  77. return call_user_func_array(array($this,'lookup'),$_args);
  78. }
  79. /**
  80. Return an array of DB records matching criteria
  81. @return array
  82. @param $_criteria mixed
  83. @param $_order mixed
  84. @param $_limit mixed
  85. @param $_ttl integer
  86. @public
  87. **/
  88. public function find(
  89. $_criteria=NULL,
  90. $_order=NULL,
  91. $_limit=NULL,
  92. $_ttl=0) {
  93. return $this->lookup('*',$_criteria,NULL,$_order,$_limit,$_ttl);
  94. }
  95. /**
  96. Return number of DB records that match criteria
  97. @return integer
  98. @param $_criteria mixed
  99. @public
  100. **/
  101. public function found($_criteria=NULL) {
  102. $_result=$this->lookup('COUNT(*) AS found',$_criteria);
  103. return $_result[0]['found'];
  104. }
  105. /**
  106. Hydrate Axon with elements from framework array variable, keys of
  107. which must be identical to field names in DB record
  108. @param $_name string
  109. @public
  110. **/
  111. public function copyFrom($_name) {
  112. foreach (array_keys($this->fields) as $_field)
  113. if (is_array(F3::get($_name)) &&
  114. array_key_exists($_field,F3::get($_name)))
  115. $this->fields[$_field]=F3::get($_name.'.'.$_field);
  116. $this->empty=FALSE;
  117. }
  118. /**
  119. Populate framework array variable with Axon properties, keys of
  120. which will have names identical to fields in DB record
  121. @param $_name string
  122. @param $_fields string
  123. @public
  124. **/
  125. public function copyTo($_name,$_fields=NULL) {
  126. if (is_string($_fields))
  127. $_list=explode('|',$_fields);
  128. foreach (array_keys($this->fields) as $_field)
  129. if (!isset($_list) || in_array($_field,$_list))
  130. F3::set($_name.'.'.$_field,$this->fields[$_field]);
  131. }
  132. /**
  133. Dehydrate Axon
  134. @public
  135. **/
  136. public function reset() {
  137. // Null out fields
  138. foreach (array_keys($this->fields) as $_field)
  139. $this->fields[$_field]=NULL;
  140. if ($this->keys)
  141. // Null out primary keys
  142. foreach (array_keys($this->keys) as $_field)
  143. $this->keys[$_field]=NULL;
  144. if ($this->virtual)
  145. // Null out primary keys
  146. foreach (array_keys($this->virtual) as $_field)
  147. unset($this->virtual[$_field]['value']);
  148. // Dehydrate Axon
  149. $this->empty=TRUE;
  150. $this->criteria=NULL;
  151. $this->order=NULL;
  152. $this->offset=NULL;
  153. }
  154. /**
  155. Retrieve first DB record that satisfies criteria
  156. @param $_criteria mixed
  157. @param $_order mixed
  158. @param $_offset integer
  159. @public
  160. **/
  161. public function load($_criteria=NULL,$_order=NULL,$_offset=0) {
  162. if (method_exists($this,'beforeLoad'))
  163. // Execute beforeLoad event
  164. $this->beforeLoad();
  165. if (!is_null($_offset) && $_offset>-1) {
  166. $_virtual='';
  167. foreach ($this->virtual as $_field=>$_value)
  168. $_virtual.=',('.$_value['expr'].') AS '.$_field;
  169. // Retrieve record
  170. $_result=$this->lookup(
  171. '*'.$_virtual,$_criteria,NULL,$_order,'1 OFFSET '.$_offset
  172. );
  173. $this->offset=NULL;
  174. if ($_result) {
  175. // Hydrate Axon
  176. foreach ($_result[0] as $_field=>$_value) {
  177. if (array_key_exists($_field,$this->fields)) {
  178. $this->fields[$_field]=$_value;
  179. if (array_key_exists($_field,$this->keys))
  180. $this->keys[$_field]=$_value;
  181. }
  182. else
  183. $this->virtual[$_field]['value']=$_value;
  184. }
  185. $this->empty=FALSE;
  186. $this->criteria=$_criteria;
  187. $this->order=$_order;
  188. $this->offset=$_offset;
  189. }
  190. else
  191. $this->reset();
  192. }
  193. else
  194. $this->reset();
  195. if (method_exists($this,'afterLoad'))
  196. // Execute afterLoad event
  197. $this->afterLoad();
  198. }
  199. /**
  200. Retrieve N-th record relative to current using the same criteria
  201. that hydrated the Axon
  202. @param $_count integer
  203. @public
  204. **/
  205. public function skip($_count=1) {
  206. if ($this->dry()) {
  207. trigger_error(self::TEXT_AxonEmpty);
  208. return;
  209. }
  210. $this->load($this->criteria,$this->order,$this->offset+$_count);
  211. }
  212. /**
  213. Insert/update DB record
  214. @public
  215. **/
  216. public function save() {
  217. if ($this->empty) {
  218. // Axon is empty
  219. trigger_error(self::TEXT_AxonEmpty);
  220. return;
  221. }
  222. if (method_exists($this,'beforeSave'))
  223. // Execute beforeSave event
  224. $this->beforeSave();
  225. $_new=TRUE;
  226. if ($this->keys)
  227. // If ALL primary keys are NULL, this is a new record
  228. foreach ($this->keys as $_value)
  229. if (!is_null($_value)) {
  230. $_new=FALSE;
  231. break;
  232. }
  233. if ($_new) {
  234. // Insert new record
  235. $_fields='';
  236. $_values='';
  237. foreach ($this->fields as $_field=>$_value) {
  238. $_fields.=($_fields?',':'').$_field;
  239. $_values.=($_values?',':'').':'.$_field;
  240. $_bind[':'.$_field]=array($_value,SQLdb::type($_value));
  241. }
  242. F3::sqlBind(
  243. 'INSERT INTO '.$this->table.' ('.$_fields.') '.
  244. 'VALUES ('.$_values.');',
  245. $_bind,$this->db
  246. );
  247. }
  248. else {
  249. // Update record
  250. $_set='';
  251. foreach ($this->fields as $_field=>$_value) {
  252. $_set.=($_set?',':'').($_field.'=:'.$_field);
  253. $_bind[':'.$_field]=array($_value,SQLdb::type($_value));
  254. }
  255. // Use prior primary key values (if changed) to find record
  256. $_cond='';
  257. foreach ($this->keys as $_key=>$_value) {
  258. $_cond.=($_cond?' AND ':'').($_key.'=:c_'.$_key);
  259. $_bind[':c_'.$_key]=array($_value,SQLdb::type($_value));
  260. }
  261. F3::sqlBind(
  262. 'UPDATE '.$this->table.' SET '.$_set.
  263. (is_null($_cond)?'':(' WHERE '.$_cond)).';',
  264. $_bind,$this->db
  265. );
  266. }
  267. if ($this->keys) {
  268. // Update primary keys with new values
  269. foreach (array_keys($this->keys) as $_field)
  270. $this->keys[$_field]=$this->fields[$_field];
  271. }
  272. if (method_exists($this,'afterSave'))
  273. // Execute afterSave event
  274. $this->afterSave();
  275. }
  276. /**
  277. Delete DB record and reset Axon
  278. @public
  279. **/
  280. public function erase() {
  281. if ($this->empty) {
  282. trigger_error(self::TEXT_AxonEmpty);
  283. return;
  284. }
  285. if (method_exists($this,'beforeErase'))
  286. // Execute beforeErase event
  287. $this->beforeErase();
  288. $_cond=$this->criteria;
  289. F3::sql(
  290. 'DELETE FROM '.$this->table.
  291. (is_null($_cond)?'':(' WHERE '.$_cond)).';',
  292. $this->db
  293. );
  294. $this->reset();
  295. if (method_exists($this,'afterErase'))
  296. // Execute afterErase event
  297. $this->afterErase();
  298. }
  299. /**
  300. Return TRUE if Axon is devoid of values in its properties
  301. @return boolean
  302. @public
  303. **/
  304. public function dry() {
  305. return $this->empty;
  306. }
  307. /**
  308. Synchronize Axon and table structure
  309. @param $_table string
  310. @param $_id string
  311. @public
  312. **/
  313. public function sync($_table,$_id='DB') {
  314. $_db=&F3::$global[$_id];
  315. // Can't proceed until DSN is set
  316. if (!$_db || !$_db['dsn']) {
  317. trigger_error(SQLdb::TEXT_DBConnect);
  318. return;
  319. }
  320. // MySQL schema
  321. if (preg_match('/^mysql\:/',$_db['dsn'])) {
  322. $_cmd='SHOW columns FROM '.$_table.';';
  323. $_fields=array('Field','Key','PRI');
  324. }
  325. // SQLite schema
  326. elseif (preg_match('/^sqlite[2]*\:/',$_db['dsn'])) {
  327. $_cmd='PRAGMA table_info('.$_table.');';
  328. $_fields=array('name','pk',1);
  329. }
  330. // SQL Server/Sybase/DBLib/ProgreSQL schema
  331. elseif (preg_match('/^(mssql|sybase|dblib|pgsql)\:/',$_db['dsn'])) {
  332. $_cmd='SELECT C.column_name AS field,T.constraint_type AS key '.
  333. 'FROM information_schema.columns C '.
  334. 'LEFT OUTER JOIN information_schema.key_column_usage K '.
  335. 'ON C.table_name=K.table_name AND '.
  336. 'C.column_name=K.column_name '.
  337. 'LEFT OUTER JOIN information_schema.table_constraints T '.
  338. 'ON K.table_name=T.table_name AND '.
  339. 'K.constraint_name=T.constraint_name '.
  340. 'WHERE C.table_name="'.$_table.'";';
  341. $_fields=array('field','key','PRIMARY KEY');
  342. }
  343. // Unsupported DB engine
  344. else {
  345. trigger_error(self::TEXT_AxonEngine);
  346. return;
  347. }
  348. if (method_exists($this,'beforeSync'))
  349. // Execute beforeSync event
  350. $this->beforeSync();
  351. $_result=F3::sql($_cmd,$_id,F3::$global['SYNC']);
  352. if (!$_result) {
  353. F3::$global['CONTEXT']=$_table;
  354. trigger_error(self::TEXT_AxonTable);
  355. return;
  356. }
  357. // Initialize Axon
  358. $this->db=$_id;
  359. $this->table=$_table;
  360. foreach ($_result as $_col) {
  361. // Populate properties
  362. $this->fields[$_col[$_fields[0]]]=NULL;
  363. if ($_col[$_fields[1]]==$_fields[2])
  364. // Save primary key
  365. $this->keys[$_col[$_fields[0]]]=NULL;
  366. }
  367. $this->empty=TRUE;
  368. if (method_exists($this,'afterSync'))
  369. // Execute afterSync event
  370. $this->afterSync();
  371. }
  372. /**
  373. Create a virtual field
  374. @param $_name string
  375. @param $_expr string
  376. @public
  377. **/
  378. public function def($_name,$_expr) {
  379. if (array_key_exists($_name,$this->fields)) {
  380. trigger_error(self::TEXT_AxonConflict);
  381. return;
  382. }
  383. if (!is_string($_expr) || !strlen($_expr)) {
  384. trigger_error(self::TEXT_AxonInvalid);
  385. return;
  386. }
  387. $this->virtual[$_name]['expr']=F3::resolve($_expr);
  388. }
  389. /**
  390. Destroy a virtual field
  391. @param $_name string
  392. @public
  393. **/
  394. public function undef($_name) {
  395. if (array_key_exists($_name,$this->fields)) {
  396. trigger_error(self::TEXT_AxonCantUndef);
  397. return;
  398. }
  399. if (!array_key_exists($_name,$this->virtual)) {
  400. F3::$global['CONTEXT']=$_name;
  401. trigger_error(self::TEXT_AxonNotMapped);
  402. return;
  403. }
  404. unset($this->virtual[$_name]);
  405. }
  406. /**
  407. Return TRUE if virtual field exists
  408. @param $_name
  409. @public
  410. **/
  411. public function isdef($_name) {
  412. return array_key_exists($_name,$this->virtual);
  413. }
  414. /**
  415. Return value of Axon-mapped/virtual field
  416. @return boolean
  417. @param $_name string
  418. @public
  419. **/
  420. public function __get($_name) {
  421. if (array_key_exists($_name,$this->fields))
  422. return $this->fields[$_name];
  423. if (array_key_exists($_name,$this->virtual))
  424. return $this->virtual[$_name]['value'];
  425. F3::$global['CONTEXT']=$_name;
  426. trigger_error(self::TEXT_AxonNotMapped);
  427. }
  428. /**
  429. Assign value to Axon-mapped field
  430. @return boolean
  431. @param $_name string
  432. @param $_value mixed
  433. @public
  434. **/
  435. public function __set($_name,$_value) {
  436. if (array_key_exists($_name,$this->fields)) {
  437. $this->fields[$_name]=is_string($_value)?
  438. F3::resolve($_value):$_value;
  439. if (!is_null($_value))
  440. // Axon is now hydrated
  441. $this->empty=FALSE;
  442. return;
  443. }
  444. if (array_key_exists($_name,$this->virtual)) {
  445. trigger_error(self::TEXT_AxonReadOnly);
  446. return;
  447. }
  448. F3::$global['CONTEXT']=$_name;
  449. trigger_error(self::TEXT_AxonNotMapped);
  450. }
  451. /**
  452. Clear value of Axon-mapped field
  453. @return boolean
  454. @param $_name string
  455. @public
  456. **/
  457. public function __unset($_name) {
  458. if (array_key_exists($_name,$this->fields)) {
  459. trigger_error(self::TEXT_AxonCantUnset);
  460. return;
  461. }
  462. F3::$global['CONTEXT']=$_name;
  463. trigger_error(self::TEXT_AxonNotMapped);
  464. }
  465. /**
  466. Return TRUE if Axon-mapped/virtual field exists
  467. @return boolean
  468. @param $_name string
  469. @public
  470. **/
  471. public function __isset($_name) {
  472. return array_key_exists(
  473. $_name,array_merge($this->fields,$this->virtual)
  474. );
  475. }
  476. /**
  477. Display class name if conversion to string is attempted
  478. @public
  479. **/
  480. public function __toString() {
  481. return get_class($this);
  482. }
  483. /**
  484. Mapper constructor
  485. @public
  486. **/
  487. public function __construct() {
  488. // Execute mandatory sync method of child class
  489. call_user_func_array(
  490. array(get_called_class(),'sync'),func_get_args()
  491. );
  492. }
  493. /**
  494. Intercept calls to undefined object methods
  495. @param $_func string
  496. @param $_args array
  497. @public
  498. **/
  499. public function __call($_func,array $_args) {
  500. F3::$global['CONTEXT']=$_func;
  501. trigger_error(F3::TEXT_Method);
  502. }
  503. /**
  504. Intercept calls to undefined static methods
  505. @return mixed
  506. @param $_func string
  507. @param $_args array
  508. @public
  509. **/
  510. public static function __callStatic($_func,array $_args) {
  511. F3::$global['CONTEXT']=__CLASS__.'::'.$_func;
  512. trigger_error(F3::TEXT_Method);
  513. }
  514. }
  515. ?>