PageRenderTime 89ms CodeModel.GetById 36ms RepoModel.GetById 1ms app.codeStats 0ms

/libs/adodb/adodb-active-record.inc.php

https://github.com/Fusion/lenses
PHP | 1087 lines | 842 code | 135 blank | 110 comment | 179 complexity | 99d4088062c629bddb8ba7c2ef282f33 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. /*
  3. @version V5.06 29 Sept 2008 (c) 2000-2008 John Lim (jlim#natsoft.com). All rights reserved.
  4. Latest version is available at http://adodb.sourceforge.net
  5. Released under both BSD license and Lesser GPL library license.
  6. Whenever there is any discrepancy between the two licenses,
  7. the BSD license will take precedence.
  8. Active Record implementation. Superset of Zend Framework's.
  9. Version 0.08
  10. See http://www-128.ibm.com/developerworks/java/library/j-cb03076/?ca=dgr-lnxw01ActiveRecord
  11. for info on Ruby on Rails Active Record implementation
  12. */
  13. global $_ADODB_ACTIVE_DBS;
  14. global $ADODB_ACTIVE_CACHESECS; // set to true to enable caching of metadata such as field info
  15. global $ACTIVE_RECORD_SAFETY; // set to false to disable safety checks
  16. global $ADODB_ACTIVE_DEFVALS; // use default values of table definition when creating new active record.
  17. // array of ADODB_Active_DB's, indexed by ADODB_Active_Record->_dbat
  18. $_ADODB_ACTIVE_DBS = array();
  19. $ACTIVE_RECORD_SAFETY = true; // CFR: disabled while playing with relations
  20. $ADODB_ACTIVE_DEFVALS = false;
  21. class ADODB_Active_DB {
  22. var $db; // ADOConnection
  23. var $tables; // assoc array of ADODB_Active_Table objects, indexed by tablename
  24. }
  25. class ADODB_Active_Table {
  26. var $name; // table name
  27. var $flds; // assoc array of adofieldobjs, indexed by fieldname
  28. var $keys; // assoc array of primary keys, indexed by fieldname
  29. var $_created; // only used when stored as a cached file
  30. var $_belongsTo = array();
  31. var $_hasMany = array();
  32. var $_colsCount; // total columns count, including relations
  33. function updateColsCount()
  34. {
  35. $this->_colsCount = sizeof($this->flds);
  36. foreach($this->_belongsTo as $foreignTable)
  37. $this->_colsCount += sizeof($foreignTable->TableInfo()->flds);
  38. foreach($this->_hasMany as $foreignTable)
  39. $this->_colsCount += sizeof($foreignTable->TableInfo()->flds);
  40. }
  41. }
  42. // returns index into $_ADODB_ACTIVE_DBS
  43. function ADODB_SetDatabaseAdapter(&$db)
  44. {
  45. global $_ADODB_ACTIVE_DBS;
  46. foreach($_ADODB_ACTIVE_DBS as $k => $d) {
  47. if (PHP_VERSION >= 5) {
  48. if ($d->db === $db) return $k;
  49. } else {
  50. if ($d->db->_connectionID === $db->_connectionID && $db->database == $d->db->database)
  51. return $k;
  52. }
  53. }
  54. $obj = new ADODB_Active_DB();
  55. $obj->db = $db;
  56. $obj->tables = array();
  57. $_ADODB_ACTIVE_DBS[] = $obj;
  58. return sizeof($_ADODB_ACTIVE_DBS)-1;
  59. }
  60. class ADODB_Active_Record {
  61. static $_changeNames = true; // dynamically pluralize table names
  62. static $_foreignSuffix = '_id'; //
  63. var $_dbat; // associative index pointing to ADODB_Active_DB eg. $ADODB_Active_DBS[_dbat]
  64. var $_table; // tablename, if set in class definition then use it as table name
  65. var $_sTable; // singularized table name
  66. var $_pTable; // pluralized table name
  67. var $_tableat; // associative index pointing to ADODB_Active_Table, eg $ADODB_Active_DBS[_dbat]->tables[$this->_tableat]
  68. var $_where; // where clause set in Load()
  69. var $_saved = false; // indicates whether data is already inserted.
  70. var $_lasterr = false; // last error message
  71. var $_original = false; // the original values loaded or inserted, refreshed on update
  72. var $foreignName; // CFR: class name when in a relationship
  73. static function UseDefaultValues($bool=null)
  74. {
  75. global $ADODB_ACTIVE_DEFVALS;
  76. if (isset($bool)) $ADODB_ACTIVE_DEFVALS = $bool;
  77. return $ADODB_ACTIVE_DEFVALS;
  78. }
  79. // should be static
  80. static function SetDatabaseAdapter(&$db)
  81. {
  82. return ADODB_SetDatabaseAdapter($db);
  83. }
  84. public function __set($name, $value)
  85. {
  86. $name = str_replace(' ', '_', $name);
  87. $this->$name = $value;
  88. }
  89. // php5 constructor
  90. // Note: if $table is defined, then we will use it as our table name
  91. // Otherwise we will use our classname...
  92. // In our database, table names are pluralized (because there can be
  93. // more than one row!)
  94. // Similarly, if $table is defined here, it has to be plural form.
  95. //
  96. // $options is an array that allows us to tweak the constructor's behaviour
  97. // if $options['refresh'] is true, we re-scan our metadata information
  98. // if $options['new'] is true, we forget all relations
  99. function __construct($table = false, $pkeyarr=false, $db=false, $options=array())
  100. {
  101. global $ADODB_ASSOC_CASE,$_ADODB_ACTIVE_DBS;
  102. if ($db == false && is_object($pkeyarr)) {
  103. $db = $pkeyarr;
  104. $pkeyarr = false;
  105. }
  106. if($table)
  107. {
  108. // table argument exists. It is expected to be
  109. // already plural form.
  110. $this->_pTable = $table;
  111. $this->_sTable = $this->_singularize($this->_pTable);
  112. }
  113. else
  114. {
  115. // We will use current classname as table name.
  116. // We need to pluralize it for the real table name.
  117. $this->_sTable = strtolower(get_class($this));
  118. $this->_pTable = $this->_pluralize($this->_sTable);
  119. }
  120. $this->_table = &$this->_pTable;
  121. $this->foreignName = $this->_sTable; // CFR: default foreign name (singular)
  122. if ($db) {
  123. $this->_dbat = ADODB_Active_Record::SetDatabaseAdapter($db);
  124. } else
  125. $this->_dbat = sizeof($_ADODB_ACTIVE_DBS)-1;
  126. if ($this->_dbat < 0) $this->Error("No database connection set; use ADOdb_Active_Record::SetDatabaseAdapter(\$db)",'ADODB_Active_Record::__constructor');
  127. $this->_tableat = $this->_table; # reserved for setting the assoc value to a non-table name, eg. the sql string in future
  128. // CFR: Just added this option because UpdateActiveTable() can refresh its information
  129. // but there was no way to ask it to do that.
  130. $forceUpdate = (isset($options['refresh']) && true === $options['refresh']);
  131. $this->UpdateActiveTable($pkeyarr, $forceUpdate);
  132. if(isset($options['new']) && true === $options['new'])
  133. {
  134. $table =& $this->TableInfo();
  135. unset($table->_hasMany);
  136. unset($table->_belongsTo);
  137. $table->_hasMany = array();
  138. $table->_belongsTo = array();
  139. }
  140. }
  141. function __wakeup()
  142. {
  143. $class = get_class($this);
  144. new $class;
  145. }
  146. // CFR: Constants found in Rails
  147. static $IrregularP = array(
  148. 'PERSON' => 'people',
  149. 'MAN' => 'men',
  150. 'WOMAN' => 'women',
  151. 'CHILD' => 'children',
  152. 'COW' => 'kine',
  153. );
  154. static $IrregularS = array(
  155. 'PEOPLE' => 'PERSON',
  156. 'MEN' => 'man',
  157. 'WOMEN' => 'woman',
  158. 'CHILDREN' => 'child',
  159. 'KINE' => 'cow',
  160. );
  161. static $WeIsI = array(
  162. 'EQUIPMENT' => true,
  163. 'INFORMATION' => true,
  164. 'RICE' => true,
  165. 'MONEY' => true,
  166. 'SPECIES' => true,
  167. 'SERIES' => true,
  168. 'FISH' => true,
  169. 'SHEEP' => true,
  170. );
  171. function _pluralize($table)
  172. {
  173. if (!ADODB_Active_Record::$_changeNames) return $table;
  174. $ut = strtoupper($table);
  175. if(isset(self::$WeIsI[$ut]))
  176. {
  177. return $table;
  178. }
  179. if(isset(self::$IrregularP[$ut]))
  180. {
  181. return self::$IrregularP[$ut];
  182. }
  183. $len = strlen($table);
  184. $lastc = $ut[$len-1];
  185. $lastc2 = substr($ut,$len-2);
  186. switch ($lastc) {
  187. case 'S':
  188. return $table.'es';
  189. case 'Y':
  190. return substr($table,0,$len-1).'ies';
  191. case 'X':
  192. return $table.'es';
  193. case 'H':
  194. if ($lastc2 == 'CH' || $lastc2 == 'SH')
  195. return $table.'es';
  196. default:
  197. return $table.'s';
  198. }
  199. }
  200. // CFR Lamest singular inflector ever - @todo Make it real!
  201. // Note: There is an assumption here...and it is that the argument's length >= 4
  202. function _singularize($table)
  203. {
  204. if (!ADODB_Active_Record::$_changeNames) return $table;
  205. $ut = strtoupper($table);
  206. if(isset(self::$WeIsI[$ut]))
  207. {
  208. return $table;
  209. }
  210. if(isset(self::$IrregularS[$ut]))
  211. {
  212. return self::$IrregularS[$ut];
  213. }
  214. $len = strlen($table);
  215. if($ut[$len-1] != 'S')
  216. return $table; // I know...forget oxen
  217. if($ut[$len-2] != 'E')
  218. return substr($table, 0, $len-1);
  219. switch($ut[$len-3])
  220. {
  221. case 'S':
  222. case 'X':
  223. return substr($table, 0, $len-2);
  224. case 'I':
  225. return substr($table, 0, $len-3) . 'y';
  226. case 'H';
  227. if($ut[$len-4] == 'C' || $ut[$len-4] == 'S')
  228. return substr($table, 0, $len-2);
  229. default:
  230. return substr($table, 0, $len-1); // ?
  231. }
  232. }
  233. /*
  234. * ar->foreignName will contain the name of the tables associated with this table because
  235. * these other tables' rows may also be referenced by this table using theirname_id or the provided
  236. * foreign keys (this index name is stored in ar->foreignKey)
  237. *
  238. * this-table.id = other-table-#1.this-table_id
  239. * = other-table-#2.this-table_id
  240. */
  241. function hasMany($foreignRef,$foreignKey=false)
  242. {
  243. $ar = new ADODB_Active_Record($foreignRef);
  244. $ar->foreignName = $foreignRef;
  245. $ar->UpdateActiveTable();
  246. $ar->foreignKey = ($foreignKey) ? $foreignKey : strtolower(get_class($this)) . self::$_foreignSuffix;
  247. $table =& $this->TableInfo();
  248. if(!isset($table->_hasMany[$foreignRef]))
  249. {
  250. $table->_hasMany[$foreignRef] = $ar;
  251. $table->updateColsCount();
  252. }
  253. # @todo Can I make this guy be lazy?
  254. $this->$foreignRef = $table->_hasMany[$foreignRef]; // WATCHME Removed assignment by ref. to please __get()
  255. }
  256. /**
  257. * ar->foreignName will contain the name of the tables associated with this table because
  258. * this table's rows may also be referenced by those tables using thistable_id or the provided
  259. * foreign keys (this index name is stored in ar->foreignKey)
  260. *
  261. * this-table.other-table_id = other-table.id
  262. */
  263. function belongsTo($foreignRef,$foreignKey=false)
  264. {
  265. global $inflector;
  266. $ar = new ADODB_Active_Record($this->_pluralize($foreignRef));
  267. $ar->foreignName = $foreignRef;
  268. $ar->UpdateActiveTable();
  269. $ar->foreignKey = ($foreignKey) ? $foreignKey : $ar->foreignName . self::$_foreignSuffix;
  270. $table =& $this->TableInfo();
  271. if(!isset($table->_belongsTo[$foreignRef]))
  272. {
  273. $table->_belongsTo[$foreignRef] = $ar;
  274. $table->updateColsCount();
  275. }
  276. $this->$foreignRef = $table->_belongsTo[$foreignRef];
  277. }
  278. /**
  279. * __get Access properties - used for lazy loading
  280. *
  281. * @param mixed $name
  282. * @access protected
  283. * @return void
  284. */
  285. function __get($name)
  286. {
  287. return $this->LoadRelations($name, '', -1. -1);
  288. }
  289. function LoadRelations($name, $whereOrderBy, $offset=-1, $limit=-1)
  290. {
  291. $extras = array();
  292. if($offset >= 0) $extras['offset'] = $offset;
  293. if($limit >= 0) $extras['limit'] = $limit;
  294. $table =& $this->TableInfo();
  295. if(!empty($table->_belongsTo[$name]))
  296. {
  297. $obj = $table->_belongsTo[$name];
  298. $columnName = $obj->foreignKey;
  299. if(empty($this->$columnName))
  300. $this->$name = null;
  301. else
  302. {
  303. if(($k = reset($obj->TableInfo()->keys)))
  304. $belongsToId = $k;
  305. else
  306. $belongsToId = 'id';
  307. $arrayOfOne =
  308. $obj->Find(
  309. $belongsToId.'='.$this->$columnName.' '.$whereOrderBy, false, false, $extras);
  310. $this->$name = $arrayOfOne[0];
  311. }
  312. return $this->$name;
  313. }
  314. if(!empty($table->_hasMany[$name]))
  315. {
  316. $obj = $table->_hasMany[$name];
  317. if(($k = reset($table->keys)))
  318. $hasManyId = $k;
  319. else
  320. $hasManyId = 'id';
  321. $this->$name =
  322. $obj->Find(
  323. $obj->foreignKey.'='.$this->$hasManyId.' '.$whereOrderBy, false, false, $extras);
  324. return $this->$name;
  325. }
  326. }
  327. //////////////////////////////////
  328. // update metadata
  329. function UpdateActiveTable($pkeys=false,$forceUpdate=false)
  330. {
  331. global $ADODB_ASSOC_CASE,$_ADODB_ACTIVE_DBS , $ADODB_CACHE_DIR, $ADODB_ACTIVE_CACHESECS;
  332. global $ADODB_ACTIVE_DEFVALS;
  333. $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat];
  334. $table = $this->_table;
  335. $tables = $activedb->tables;
  336. $tableat = $this->_tableat;
  337. if (!$forceUpdate && !empty($tables[$tableat])) {
  338. $tobj = $tables[$tableat];
  339. foreach($tobj->flds as $name => $fld) {
  340. if ($ADODB_ACTIVE_DEFVALS && isset($fld->default_value))
  341. $this->$name = $fld->default_value;
  342. else
  343. $this->$name = null;
  344. }
  345. return;
  346. }
  347. $db = $activedb->db;
  348. $fname = $ADODB_CACHE_DIR . '/adodb_' . $db->databaseType . '_active_'. $table . '.cache';
  349. if (!$forceUpdate && $ADODB_ACTIVE_CACHESECS && $ADODB_CACHE_DIR && file_exists($fname)) {
  350. $fp = fopen($fname,'r');
  351. @flock($fp, LOCK_SH);
  352. $acttab = unserialize(fread($fp,100000));
  353. fclose($fp);
  354. if ($acttab->_created + $ADODB_ACTIVE_CACHESECS - (abs(rand()) % 16) > time()) {
  355. // abs(rand()) randomizes deletion, reducing contention to delete/refresh file
  356. // ideally, you should cache at least 32 secs
  357. $activedb->tables[$table] = $acttab;
  358. //if ($db->debug) ADOConnection::outp("Reading cached active record file: $fname");
  359. return;
  360. } else if ($db->debug) {
  361. ADOConnection::outp("Refreshing cached active record file: $fname");
  362. }
  363. }
  364. $activetab = new ADODB_Active_Table();
  365. $activetab->name = $table;
  366. $cols = $db->MetaColumns($table);
  367. if (!$cols) {
  368. $this->Error("Invalid table name: $table",'UpdateActiveTable');
  369. return false;
  370. }
  371. $fld = reset($cols);
  372. if (!$pkeys) {
  373. if (isset($fld->primary_key)) {
  374. $pkeys = array();
  375. foreach($cols as $name => $fld) {
  376. if (!empty($fld->primary_key)) $pkeys[] = $name;
  377. }
  378. } else
  379. $pkeys = $this->GetPrimaryKeys($db, $table);
  380. }
  381. if (empty($pkeys)) {
  382. $this->Error("No primary key found for table $table",'UpdateActiveTable');
  383. return false;
  384. }
  385. $attr = array();
  386. $keys = array();
  387. switch($ADODB_ASSOC_CASE) {
  388. case 0:
  389. foreach($cols as $name => $fldobj) {
  390. $name = strtolower($name);
  391. if ($ADODB_ACTIVE_DEFVALS && isset($fldobj->default_value))
  392. $this->$name = $fldobj->default_value;
  393. else
  394. $this->$name = null;
  395. $attr[$name] = $fldobj;
  396. }
  397. foreach($pkeys as $k => $name) {
  398. $keys[strtolower($name)] = strtolower($name);
  399. }
  400. break;
  401. case 1:
  402. foreach($cols as $name => $fldobj) {
  403. $name = strtoupper($name);
  404. if ($ADODB_ACTIVE_DEFVALS && isset($fldobj->default_value))
  405. $this->$name = $fldobj->default_value;
  406. else
  407. $this->$name = null;
  408. $attr[$name] = $fldobj;
  409. }
  410. foreach($pkeys as $k => $name) {
  411. $keys[strtoupper($name)] = strtoupper($name);
  412. }
  413. break;
  414. default:
  415. foreach($cols as $name => $fldobj) {
  416. $name = ($fldobj->name);
  417. if ($ADODB_ACTIVE_DEFVALS && isset($fldobj->default_value))
  418. $this->$name = $fldobj->default_value;
  419. else
  420. $this->$name = null;
  421. $attr[$name] = $fldobj;
  422. }
  423. foreach($pkeys as $k => $name) {
  424. $keys[$name] = $cols[$name]->name;
  425. }
  426. break;
  427. }
  428. $activetab->keys = $keys;
  429. $activetab->flds = $attr;
  430. $activetab->updateColsCount();
  431. if ($ADODB_ACTIVE_CACHESECS && $ADODB_CACHE_DIR) {
  432. $activetab->_created = time();
  433. $s = serialize($activetab);
  434. if (!function_exists('adodb_write_file')) include(ADODB_DIR.'/adodb-csvlib.inc.php');
  435. adodb_write_file($fname,$s);
  436. }
  437. if (isset($activedb->tables[$table])) {
  438. $oldtab = $activedb->tables[$table];
  439. if ($oldtab) $activetab->_belongsTo = $oldtab->_belongsTo;
  440. if ($oldtab) $activetab->_hasMany = $oldtab->_hasMany;
  441. }
  442. $activedb->tables[$table] = $activetab;
  443. }
  444. function GetPrimaryKeys(&$db, $table)
  445. {
  446. return $db->MetaPrimaryKeys($table);
  447. }
  448. // error handler for both PHP4+5.
  449. function Error($err,$fn)
  450. {
  451. global $_ADODB_ACTIVE_DBS;
  452. $fn = get_class($this).'::'.$fn;
  453. $this->_lasterr = $fn.': '.$err;
  454. if ($this->_dbat < 0) $db = false;
  455. else {
  456. $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat];
  457. $db = $activedb->db;
  458. }
  459. if (function_exists('adodb_throw')) {
  460. if (!$db) adodb_throw('ADOdb_Active_Record', $fn, -1, $err, 0, 0, false);
  461. else adodb_throw($db->databaseType, $fn, -1, $err, 0, 0, $db);
  462. } else
  463. if (!$db || $db->debug) ADOConnection::outp($this->_lasterr);
  464. }
  465. // return last error message
  466. function ErrorMsg()
  467. {
  468. if (!function_exists('adodb_throw')) {
  469. if ($this->_dbat < 0) $db = false;
  470. else $db = $this->DB();
  471. // last error could be database error too
  472. if ($db && $db->ErrorMsg()) return $db->ErrorMsg();
  473. }
  474. return $this->_lasterr;
  475. }
  476. function ErrorNo()
  477. {
  478. if ($this->_dbat < 0) return -9999; // no database connection...
  479. $db = $this->DB();
  480. return (int) $db->ErrorNo();
  481. }
  482. // retrieve ADOConnection from _ADODB_Active_DBs
  483. function DB()
  484. {
  485. global $_ADODB_ACTIVE_DBS;
  486. if ($this->_dbat < 0) {
  487. $false = false;
  488. $this->Error("No database connection set: use ADOdb_Active_Record::SetDatabaseAdaptor(\$db)", "DB");
  489. return $false;
  490. }
  491. $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat];
  492. $db = $activedb->db;
  493. return $db;
  494. }
  495. // retrieve ADODB_Active_Table
  496. function &TableInfo()
  497. {
  498. global $_ADODB_ACTIVE_DBS;
  499. $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat];
  500. $table = $activedb->tables[$this->_tableat];
  501. return $table;
  502. }
  503. // I have an ON INSERT trigger on a table that sets other columns in the table.
  504. // So, I find that for myTable, I want to reload an active record after saving it. -- Malcolm Cook
  505. function Reload()
  506. {
  507. $db =& $this->DB(); if (!$db) return false;
  508. $table =& $this->TableInfo();
  509. $where = $this->GenWhere($db, $table);
  510. return($this->Load($where));
  511. }
  512. // set a numeric array (using natural table field ordering) as object properties
  513. function Set(&$row)
  514. {
  515. global $ACTIVE_RECORD_SAFETY;
  516. $db = $this->DB();
  517. if (!$row) {
  518. $this->_saved = false;
  519. return false;
  520. }
  521. $this->_saved = true;
  522. $table = $this->TableInfo();
  523. $sizeofFlds = sizeof($table->flds);
  524. $sizeofRow = sizeof($row);
  525. if ($ACTIVE_RECORD_SAFETY && $table->_colsCount != $sizeofRow && $sizeofFlds != $sizeofRow) {
  526. # <AP>
  527. $bad_size = TRUE;
  528. if($sizeofRow == 2 * $table->_colsCount || $sizeofRow == 2 * $sizeofFlds) {
  529. // Only keep string keys
  530. $keys = array_filter(array_keys($row), 'is_string');
  531. if (sizeof($keys) == sizeof($table->flds))
  532. $bad_size = FALSE;
  533. }
  534. if ($bad_size) {
  535. $this->Error("Table structure of $this->_table has changed","Load");
  536. return false;
  537. }
  538. # </AP>
  539. }
  540. else
  541. $keys = array_keys($row);
  542. # <AP>
  543. reset($keys);
  544. $this->_original = array();
  545. foreach($table->flds as $name=>$fld)
  546. {
  547. $value = $row[current($keys)];
  548. $this->$name = $value;
  549. $this->_original[] = $value;
  550. if(!next($keys)) break;
  551. }
  552. $table =& $this->TableInfo();
  553. foreach($table->_belongsTo as $foreignTable)
  554. {
  555. $ft = $foreignTable->TableInfo();
  556. $propertyName = $ft->name;
  557. foreach($ft->flds as $name=>$fld)
  558. {
  559. $value = $row[current($keys)];
  560. $foreignTable->$name = $value;
  561. $foreignTable->_original[] = $value;
  562. if(!next($keys)) break;
  563. }
  564. }
  565. foreach($table->_hasMany as $foreignTable)
  566. {
  567. $ft = $foreignTable->TableInfo();
  568. foreach($ft->flds as $name=>$fld)
  569. {
  570. $value = $row[current($keys)];
  571. $foreignTable->$name = $value;
  572. $foreignTable->_original[] = $value;
  573. if(!next($keys)) break;
  574. }
  575. }
  576. # </AP>
  577. return true;
  578. }
  579. // get last inserted id for INSERT
  580. function LastInsertID(&$db,$fieldname)
  581. {
  582. if ($db->hasInsertID)
  583. $val = $db->Insert_ID($this->_table,$fieldname);
  584. else
  585. $val = false;
  586. if (is_null($val) || $val === false) {
  587. // this might not work reliably in multi-user environment
  588. return $db->GetOne("select max(".$fieldname.") from ".$this->_table);
  589. }
  590. return $val;
  591. }
  592. // quote data in where clause
  593. function doquote(&$db, $val,$t)
  594. {
  595. switch($t) {
  596. case 'D':
  597. case 'T':
  598. if (empty($val)) return 'null';
  599. case 'C':
  600. case 'X':
  601. if (is_null($val)) return 'null';
  602. if (strncmp($val,"'",1) != 0 && substr($val,strlen($val)-1,1) != "'") {
  603. return $db->qstr($val);
  604. break;
  605. }
  606. default:
  607. return $val;
  608. break;
  609. }
  610. }
  611. // generate where clause for an UPDATE/SELECT
  612. function GenWhere(&$db, &$table)
  613. {
  614. $keys = $table->keys;
  615. $parr = array();
  616. foreach($keys as $k) {
  617. $f = $table->flds[$k];
  618. if ($f) {
  619. $parr[] = $k.' = '.$this->doquote($db,$this->$k,$db->MetaType($f->type));
  620. }
  621. }
  622. return implode(' and ', $parr);
  623. }
  624. //------------------------------------------------------------ Public functions below
  625. function Load($where=null,$bindarr=false)
  626. {
  627. $db = $this->DB(); if (!$db) return false;
  628. $this->_where = $where;
  629. $save = $db->SetFetchMode(ADODB_FETCH_NUM);
  630. $qry = "select * from ".$this->_table;
  631. $table =& $this->TableInfo();
  632. if(($k = reset($table->keys)))
  633. $hasManyId = $k;
  634. else
  635. $hasManyId = 'id';
  636. foreach($table->_belongsTo as $foreignTable)
  637. {
  638. if(($k = reset($foreignTable->TableInfo()->keys)))
  639. {
  640. $belongsToId = $k;
  641. }
  642. else
  643. {
  644. $belongsToId = 'id';
  645. }
  646. $qry .= ' LEFT JOIN '.$foreignTable->_table.' ON '.
  647. $this->_table.'.'.$foreignTable->foreignKey.'='.
  648. $foreignTable->_table.'.'.$belongsToId;
  649. }
  650. foreach($table->_hasMany as $foreignTable)
  651. {
  652. $qry .= ' LEFT JOIN '.$foreignTable->_table.' ON '.
  653. $this->_table.'.'.$hasManyId.'='.
  654. $foreignTable->_table.'.'.$foreignTable->foreignKey;
  655. }
  656. if($where)
  657. $qry .= ' WHERE '.$where;
  658. // Simple case: no relations. Load row and return.
  659. if((count($table->_hasMany) + count($table->_belongsTo)) < 1)
  660. {
  661. $row = $db->GetRow($qry,$bindarr);
  662. if(!$row)
  663. return false;
  664. $db->SetFetchMode($save);
  665. return $this->Set($row);
  666. }
  667. // More complex case when relations have to be collated
  668. $rows = $db->GetAll($qry,$bindarr);
  669. if(!$rows)
  670. return false;
  671. $db->SetFetchMode($save);
  672. if(count($rows) < 1)
  673. return false;
  674. $class = get_class($this);
  675. $isFirstRow = true;
  676. if(($k = reset($this->TableInfo()->keys)))
  677. $myId = $k;
  678. else
  679. $myId = 'id';
  680. $index = 0; $found = false;
  681. /** @todo Improve by storing once and for all in table metadata */
  682. /** @todo Also re-use info for hasManyId */
  683. foreach($this->TableInfo()->flds as $fld)
  684. {
  685. if($fld->name == $myId)
  686. {
  687. $found = true;
  688. break;
  689. }
  690. $index++;
  691. }
  692. if(!$found)
  693. $this->outp_throw("Unable to locate key $myId for $class in Load()",'Load');
  694. foreach($rows as $row)
  695. {
  696. $rowId = intval($row[$index]);
  697. if($rowId > 0)
  698. {
  699. if($isFirstRow)
  700. {
  701. $isFirstRow = false;
  702. if(!$this->Set($row))
  703. return false;
  704. }
  705. $obj = new $class($table,false,db);
  706. $obj->Set($row);
  707. // TODO Copy/paste code below: bad!
  708. if(count($table->_hasMany) > 0)
  709. {
  710. foreach($table->_hasMany as $foreignTable)
  711. {
  712. $foreignName = $foreignTable->foreignName;
  713. if(!empty($obj->$foreignName))
  714. {
  715. if(!is_array($this->$foreignName))
  716. {
  717. $foreignObj = $this->$foreignName;
  718. $this->$foreignName = array(clone($foreignObj));
  719. }
  720. else
  721. {
  722. $foreignObj = $obj->$foreignName;
  723. array_push($this->$foreignName, clone($foreignObj));
  724. }
  725. }
  726. }
  727. }
  728. if(count($table->_belongsTo) > 0)
  729. {
  730. foreach($table->_belongsTo as $foreignTable)
  731. {
  732. $foreignName = $foreignTable->foreignName;
  733. if(!empty($obj->$foreignName))
  734. {
  735. if(!is_array($this->$foreignName))
  736. {
  737. $foreignObj = $this->$foreignName;
  738. $this->$foreignName = array(clone($foreignObj));
  739. }
  740. else
  741. {
  742. $foreignObj = $obj->$foreignName;
  743. array_push($this->$foreignName, clone($foreignObj));
  744. }
  745. }
  746. }
  747. }
  748. }
  749. }
  750. return true;
  751. }
  752. // false on error
  753. function Save()
  754. {
  755. if ($this->_saved) $ok = $this->Update();
  756. else $ok = $this->Insert();
  757. return $ok;
  758. }
  759. // CFR: Sometimes we may wish to consider that an object is not to be replaced but inserted.
  760. // Sample use case: an 'undo' command object (after a delete())
  761. function Dirty()
  762. {
  763. $this->_saved = false;
  764. }
  765. // false on error
  766. function Insert()
  767. {
  768. $db = $this->DB(); if (!$db) return false;
  769. $cnt = 0;
  770. $table = $this->TableInfo();
  771. $valarr = array();
  772. $names = array();
  773. $valstr = array();
  774. foreach($table->flds as $name=>$fld) {
  775. $val = $this->$name;
  776. if(!is_null($val) || !array_key_exists($name, $table->keys)) {
  777. $valarr[] = $val;
  778. $names[] = $name;
  779. $valstr[] = $db->Param($cnt);
  780. $cnt += 1;
  781. }
  782. }
  783. if (empty($names)){
  784. foreach($table->flds as $name=>$fld) {
  785. $valarr[] = null;
  786. $names[] = $name;
  787. $valstr[] = $db->Param($cnt);
  788. $cnt += 1;
  789. }
  790. }
  791. $sql = 'INSERT INTO '.$this->_table."(".implode(',',$names).') VALUES ('.implode(',',$valstr).')';
  792. $ok = $db->Execute($sql,$valarr);
  793. if ($ok) {
  794. $this->_saved = true;
  795. $autoinc = false;
  796. foreach($table->keys as $k) {
  797. if (is_null($this->$k)) {
  798. $autoinc = true;
  799. break;
  800. }
  801. }
  802. if ($autoinc && sizeof($table->keys) == 1) {
  803. $k = reset($table->keys);
  804. $this->$k = $this->LastInsertID($db,$k);
  805. }
  806. }
  807. $this->_original = $valarr;
  808. return !empty($ok);
  809. }
  810. function Delete()
  811. {
  812. $db = $this->DB(); if (!$db) return false;
  813. $table = $this->TableInfo();
  814. $where = $this->GenWhere($db,$table);
  815. $sql = 'DELETE FROM '.$this->_table.' WHERE '.$where;
  816. $ok = $db->Execute($sql);
  817. return $ok ? true : false;
  818. }
  819. // returns an array of active record objects
  820. function Find($whereOrderBy,$bindarr=false,$pkeysArr=false,$extra=array())
  821. {
  822. $db = $this->DB(); if (!$db || empty($this->_table)) return false;
  823. $table =& $this->TableInfo();
  824. $arr = $db->GetActiveRecordsClass(get_class($this),$this, $whereOrderBy,$bindarr,$pkeysArr,$extra,
  825. array('foreignName'=>$this->foreignName, 'belongsTo'=>$table->_belongsTo, 'hasMany'=>$table->_hasMany));
  826. return $arr;
  827. }
  828. // CFR: In introduced this method to ensure that inner workings are not disturbed by
  829. // subclasses...for instance when GetActiveRecordsClass invokes Find()
  830. // Why am I not invoking parent::Find?
  831. // Shockingly because I want to preserve PHP4 compatibility.
  832. function packageFind($whereOrderBy,$bindarr=false,$pkeysArr=false,$extra=array())
  833. {
  834. $db = $this->DB(); if (!$db || empty($this->_table)) return false;
  835. $table =& $this->TableInfo();
  836. $arr = $db->GetActiveRecordsClass(get_class($this),$this, $whereOrderBy,$bindarr,$pkeysArr,$extra,
  837. array('foreignName'=>$this->foreignName, 'belongsTo'=>$table->_belongsTo, 'hasMany'=>$table->_hasMany));
  838. return $arr;
  839. }
  840. // returns 0 on error, 1 on update, 2 on insert
  841. function Replace()
  842. {
  843. global $ADODB_ASSOC_CASE;
  844. $db = $this->DB(); if (!$db) return false;
  845. $table = $this->TableInfo();
  846. $pkey = $table->keys;
  847. foreach($table->flds as $name=>$fld) {
  848. $val = $this->$name;
  849. /*
  850. if (is_null($val)) {
  851. if (isset($fld->not_null) && $fld->not_null) {
  852. if (isset($fld->default_value) && strlen($fld->default_value)) continue;
  853. else {
  854. $this->Error("Cannot update null into $name","Replace");
  855. return false;
  856. }
  857. }
  858. }*/
  859. if (is_null($val) && !empty($fld->auto_increment)) {
  860. continue;
  861. }
  862. $t = $db->MetaType($fld->type);
  863. $arr[$name] = $this->doquote($db,$val,$t);
  864. $valarr[] = $val;
  865. }
  866. if (!is_array($pkey)) $pkey = array($pkey);
  867. if ($ADODB_ASSOC_CASE == 0)
  868. foreach($pkey as $k => $v)
  869. $pkey[$k] = strtolower($v);
  870. elseif ($ADODB_ASSOC_CASE == 1)
  871. foreach($pkey as $k => $v)
  872. $pkey[$k] = strtoupper($v);
  873. $ok = $db->Replace($this->_table,$arr,$pkey);
  874. if ($ok) {
  875. $this->_saved = true; // 1= update 2=insert
  876. if ($ok == 2) {
  877. $autoinc = false;
  878. foreach($table->keys as $k) {
  879. if (is_null($this->$k)) {
  880. $autoinc = true;
  881. break;
  882. }
  883. }
  884. if ($autoinc && sizeof($table->keys) == 1) {
  885. $k = reset($table->keys);
  886. $this->$k = $this->LastInsertID($db,$k);
  887. }
  888. }
  889. $this->_original = $valarr;
  890. }
  891. return $ok;
  892. }
  893. // returns 0 on error, 1 on update, -1 if no change in data (no update)
  894. function Update()
  895. {
  896. $db = $this->DB(); if (!$db) return false;
  897. $table = $this->TableInfo();
  898. $where = $this->GenWhere($db, $table);
  899. if (!$where) {
  900. $this->error("Where missing for table $table", "Update");
  901. return false;
  902. }
  903. $valarr = array();
  904. $neworig = array();
  905. $pairs = array();
  906. $i = -1;
  907. $cnt = 0;
  908. foreach($table->flds as $name=>$fld) {
  909. $i += 1;
  910. $val = $this->$name;
  911. $neworig[] = $val;
  912. if (isset($table->keys[$name])) {
  913. continue;
  914. }
  915. if (is_null($val)) {
  916. if (isset($fld->not_null) && $fld->not_null) {
  917. if (isset($fld->default_value) && strlen($fld->default_value)) continue;
  918. else {
  919. $this->Error("Cannot set field $name to NULL","Update");
  920. return false;
  921. }
  922. }
  923. }
  924. if (isset($this->_original[$i]) && $val == $this->_original[$i]) {
  925. continue;
  926. }
  927. $valarr[] = $val;
  928. $pairs[] = $name.'='.$db->Param($cnt);
  929. $cnt += 1;
  930. }
  931. if (!$cnt) return -1;
  932. $sql = 'UPDATE '.$this->_table." SET ".implode(",",$pairs)." WHERE ".$where;
  933. $ok = $db->Execute($sql,$valarr);
  934. if ($ok) {
  935. $this->_original = $neworig;
  936. return 1;
  937. }
  938. return 0;
  939. }
  940. function GetAttributeNames()
  941. {
  942. $table = $this->TableInfo();
  943. if (!$table) return false;
  944. return array_keys($table->flds);
  945. }
  946. };
  947. ?>