adodb-active-recordx.inc.php 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495
  1. <?php
  2. /*
  3. @version v5.20.17 31-Mar-2020
  4. @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
  5. @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
  6. Latest version is available at http://adodb.org/
  7. Released under both BSD license and Lesser GPL library license.
  8. Whenever there is any discrepancy between the two licenses,
  9. the BSD license will take precedence.
  10. Active Record implementation. Superset of Zend Framework's.
  11. This is "Active Record eXtended" to support JOIN, WORK and LAZY mode by Chris Ravenscroft chris#voilaweb.com
  12. Version 0.9
  13. See http://www-128.ibm.com/developerworks/java/library/j-cb03076/?ca=dgr-lnxw01ActiveRecord
  14. for info on Ruby on Rails Active Record implementation
  15. */
  16. // CFR: Active Records Definitions
  17. define('ADODB_JOIN_AR', 0x01);
  18. define('ADODB_WORK_AR', 0x02);
  19. define('ADODB_LAZY_AR', 0x03);
  20. global $_ADODB_ACTIVE_DBS;
  21. global $ADODB_ACTIVE_CACHESECS; // set to true to enable caching of metadata such as field info
  22. global $ACTIVE_RECORD_SAFETY; // set to false to disable safety checks
  23. global $ADODB_ACTIVE_DEFVALS; // use default values of table definition when creating new active record.
  24. // array of ADODB_Active_DB's, indexed by ADODB_Active_Record->_dbat
  25. $_ADODB_ACTIVE_DBS = array();
  26. $ACTIVE_RECORD_SAFETY = true; // CFR: disabled while playing with relations
  27. $ADODB_ACTIVE_DEFVALS = false;
  28. class ADODB_Active_DB {
  29. var $db; // ADOConnection
  30. var $tables; // assoc array of ADODB_Active_Table objects, indexed by tablename
  31. }
  32. class ADODB_Active_Table {
  33. var $name; // table name
  34. var $flds; // assoc array of adofieldobjs, indexed by fieldname
  35. var $keys; // assoc array of primary keys, indexed by fieldname
  36. var $_created; // only used when stored as a cached file
  37. var $_belongsTo = array();
  38. var $_hasMany = array();
  39. var $_colsCount; // total columns count, including relations
  40. function updateColsCount()
  41. {
  42. $this->_colsCount = sizeof($this->flds);
  43. foreach($this->_belongsTo as $foreignTable)
  44. $this->_colsCount += sizeof($foreignTable->TableInfo()->flds);
  45. foreach($this->_hasMany as $foreignTable)
  46. $this->_colsCount += sizeof($foreignTable->TableInfo()->flds);
  47. }
  48. }
  49. // returns index into $_ADODB_ACTIVE_DBS
  50. function ADODB_SetDatabaseAdapter(&$db)
  51. {
  52. global $_ADODB_ACTIVE_DBS;
  53. foreach($_ADODB_ACTIVE_DBS as $k => $d) {
  54. if (PHP_VERSION >= 5) {
  55. if ($d->db === $db) {
  56. return $k;
  57. }
  58. } else {
  59. if ($d->db->_connectionID === $db->_connectionID && $db->database == $d->db->database) {
  60. return $k;
  61. }
  62. }
  63. }
  64. $obj = new ADODB_Active_DB();
  65. $obj->db = $db;
  66. $obj->tables = array();
  67. $_ADODB_ACTIVE_DBS[] = $obj;
  68. return sizeof($_ADODB_ACTIVE_DBS)-1;
  69. }
  70. class ADODB_Active_Record {
  71. static $_changeNames = true; // dynamically pluralize table names
  72. static $_foreignSuffix = '_id'; //
  73. var $_dbat; // associative index pointing to ADODB_Active_DB eg. $ADODB_Active_DBS[_dbat]
  74. var $_table; // tablename, if set in class definition then use it as table name
  75. var $_sTable; // singularized table name
  76. var $_pTable; // pluralized table name
  77. var $_tableat; // associative index pointing to ADODB_Active_Table, eg $ADODB_Active_DBS[_dbat]->tables[$this->_tableat]
  78. var $_where; // where clause set in Load()
  79. var $_saved = false; // indicates whether data is already inserted.
  80. var $_lasterr = false; // last error message
  81. var $_original = false; // the original values loaded or inserted, refreshed on update
  82. var $foreignName; // CFR: class name when in a relationship
  83. static function UseDefaultValues($bool=null)
  84. {
  85. global $ADODB_ACTIVE_DEFVALS;
  86. if (isset($bool)) {
  87. $ADODB_ACTIVE_DEFVALS = $bool;
  88. }
  89. return $ADODB_ACTIVE_DEFVALS;
  90. }
  91. // should be static
  92. static function SetDatabaseAdapter(&$db)
  93. {
  94. return ADODB_SetDatabaseAdapter($db);
  95. }
  96. public function __set($name, $value)
  97. {
  98. $name = str_replace(' ', '_', $name);
  99. $this->$name = $value;
  100. }
  101. // php5 constructor
  102. // Note: if $table is defined, then we will use it as our table name
  103. // Otherwise we will use our classname...
  104. // In our database, table names are pluralized (because there can be
  105. // more than one row!)
  106. // Similarly, if $table is defined here, it has to be plural form.
  107. //
  108. // $options is an array that allows us to tweak the constructor's behaviour
  109. // if $options['refresh'] is true, we re-scan our metadata information
  110. // if $options['new'] is true, we forget all relations
  111. function __construct($table = false, $pkeyarr=false, $db=false, $options=array())
  112. {
  113. global $_ADODB_ACTIVE_DBS;
  114. if ($db == false && is_object($pkeyarr)) {
  115. $db = $pkeyarr;
  116. $pkeyarr = false;
  117. }
  118. if($table) {
  119. // table argument exists. It is expected to be
  120. // already plural form.
  121. $this->_pTable = $table;
  122. $this->_sTable = $this->_singularize($this->_pTable);
  123. }
  124. else {
  125. // We will use current classname as table name.
  126. // We need to pluralize it for the real table name.
  127. $this->_sTable = strtolower(get_class($this));
  128. $this->_pTable = $this->_pluralize($this->_sTable);
  129. }
  130. $this->_table = &$this->_pTable;
  131. $this->foreignName = $this->_sTable; // CFR: default foreign name (singular)
  132. if ($db) {
  133. $this->_dbat = ADODB_Active_Record::SetDatabaseAdapter($db);
  134. } else
  135. $this->_dbat = sizeof($_ADODB_ACTIVE_DBS)-1;
  136. if ($this->_dbat < 0) {
  137. $this->Error(
  138. "No database connection set; use ADOdb_Active_Record::SetDatabaseAdapter(\$db)",
  139. 'ADODB_Active_Record::__constructor'
  140. );
  141. }
  142. $this->_tableat = $this->_table; # reserved for setting the assoc value to a non-table name, eg. the sql string in future
  143. // CFR: Just added this option because UpdateActiveTable() can refresh its information
  144. // but there was no way to ask it to do that.
  145. $forceUpdate = (isset($options['refresh']) && true === $options['refresh']);
  146. $this->UpdateActiveTable($pkeyarr, $forceUpdate);
  147. if(isset($options['new']) && true === $options['new']) {
  148. $table =& $this->TableInfo();
  149. unset($table->_hasMany);
  150. unset($table->_belongsTo);
  151. $table->_hasMany = array();
  152. $table->_belongsTo = array();
  153. }
  154. }
  155. function __wakeup()
  156. {
  157. $class = get_class($this);
  158. new $class;
  159. }
  160. // CFR: Constants found in Rails
  161. static $IrregularP = array(
  162. 'PERSON' => 'people',
  163. 'MAN' => 'men',
  164. 'WOMAN' => 'women',
  165. 'CHILD' => 'children',
  166. 'COW' => 'kine',
  167. );
  168. static $IrregularS = array(
  169. 'PEOPLE' => 'PERSON',
  170. 'MEN' => 'man',
  171. 'WOMEN' => 'woman',
  172. 'CHILDREN' => 'child',
  173. 'KINE' => 'cow',
  174. );
  175. static $WeIsI = array(
  176. 'EQUIPMENT' => true,
  177. 'INFORMATION' => true,
  178. 'RICE' => true,
  179. 'MONEY' => true,
  180. 'SPECIES' => true,
  181. 'SERIES' => true,
  182. 'FISH' => true,
  183. 'SHEEP' => true,
  184. );
  185. function _pluralize($table)
  186. {
  187. if (!ADODB_Active_Record::$_changeNames) {
  188. return $table;
  189. }
  190. $ut = strtoupper($table);
  191. if(isset(self::$WeIsI[$ut])) {
  192. return $table;
  193. }
  194. if(isset(self::$IrregularP[$ut])) {
  195. return self::$IrregularP[$ut];
  196. }
  197. $len = strlen($table);
  198. $lastc = $ut[$len-1];
  199. $lastc2 = substr($ut,$len-2);
  200. switch ($lastc) {
  201. case 'S':
  202. return $table.'es';
  203. case 'Y':
  204. return substr($table,0,$len-1).'ies';
  205. case 'X':
  206. return $table.'es';
  207. case 'H':
  208. if ($lastc2 == 'CH' || $lastc2 == 'SH') {
  209. return $table.'es';
  210. }
  211. default:
  212. return $table.'s';
  213. }
  214. }
  215. // CFR Lamest singular inflector ever - @todo Make it real!
  216. // Note: There is an assumption here...and it is that the argument's length >= 4
  217. function _singularize($table)
  218. {
  219. if (!ADODB_Active_Record::$_changeNames) {
  220. return $table;
  221. }
  222. $ut = strtoupper($table);
  223. if(isset(self::$WeIsI[$ut])) {
  224. return $table;
  225. }
  226. if(isset(self::$IrregularS[$ut])) {
  227. return self::$IrregularS[$ut];
  228. }
  229. $len = strlen($table);
  230. if($ut[$len-1] != 'S') {
  231. return $table; // I know...forget oxen
  232. }
  233. if($ut[$len-2] != 'E') {
  234. return substr($table, 0, $len-1);
  235. }
  236. switch($ut[$len-3]) {
  237. case 'S':
  238. case 'X':
  239. return substr($table, 0, $len-2);
  240. case 'I':
  241. return substr($table, 0, $len-3) . 'y';
  242. case 'H';
  243. if($ut[$len-4] == 'C' || $ut[$len-4] == 'S') {
  244. return substr($table, 0, $len-2);
  245. }
  246. default:
  247. return substr($table, 0, $len-1); // ?
  248. }
  249. }
  250. /*
  251. * ar->foreignName will contain the name of the tables associated with this table because
  252. * these other tables' rows may also be referenced by this table using theirname_id or the provided
  253. * foreign keys (this index name is stored in ar->foreignKey)
  254. *
  255. * this-table.id = other-table-#1.this-table_id
  256. * = other-table-#2.this-table_id
  257. */
  258. function hasMany($foreignRef,$foreignKey=false)
  259. {
  260. $ar = new ADODB_Active_Record($foreignRef);
  261. $ar->foreignName = $foreignRef;
  262. $ar->UpdateActiveTable();
  263. $ar->foreignKey = ($foreignKey) ? $foreignKey : strtolower(get_class($this)) . self::$_foreignSuffix;
  264. $table =& $this->TableInfo();
  265. if(!isset($table->_hasMany[$foreignRef])) {
  266. $table->_hasMany[$foreignRef] = $ar;
  267. $table->updateColsCount();
  268. }
  269. # @todo Can I make this guy be lazy?
  270. $this->$foreignRef = $table->_hasMany[$foreignRef]; // WATCHME Removed assignment by ref. to please __get()
  271. }
  272. /**
  273. * ar->foreignName will contain the name of the tables associated with this table because
  274. * this table's rows may also be referenced by those tables using thistable_id or the provided
  275. * foreign keys (this index name is stored in ar->foreignKey)
  276. *
  277. * this-table.other-table_id = other-table.id
  278. */
  279. function belongsTo($foreignRef,$foreignKey=false)
  280. {
  281. global $inflector;
  282. $ar = new ADODB_Active_Record($this->_pluralize($foreignRef));
  283. $ar->foreignName = $foreignRef;
  284. $ar->UpdateActiveTable();
  285. $ar->foreignKey = ($foreignKey) ? $foreignKey : $ar->foreignName . self::$_foreignSuffix;
  286. $table =& $this->TableInfo();
  287. if(!isset($table->_belongsTo[$foreignRef])) {
  288. $table->_belongsTo[$foreignRef] = $ar;
  289. $table->updateColsCount();
  290. }
  291. $this->$foreignRef = $table->_belongsTo[$foreignRef];
  292. }
  293. /**
  294. * __get Access properties - used for lazy loading
  295. *
  296. * @param mixed $name
  297. * @access protected
  298. * @return void
  299. */
  300. function __get($name)
  301. {
  302. return $this->LoadRelations($name, '', -1. -1);
  303. }
  304. function LoadRelations($name, $whereOrderBy, $offset=-1, $limit=-1)
  305. {
  306. $extras = array();
  307. if($offset >= 0) {
  308. $extras['offset'] = $offset;
  309. }
  310. if($limit >= 0) {
  311. $extras['limit'] = $limit;
  312. }
  313. $table =& $this->TableInfo();
  314. if (strlen($whereOrderBy)) {
  315. if (!preg_match('/^[ \n\r]*AND/i',$whereOrderBy)) {
  316. if (!preg_match('/^[ \n\r]*ORDER[ \n\r]/i',$whereOrderBy)) {
  317. $whereOrderBy = 'AND '.$whereOrderBy;
  318. }
  319. }
  320. }
  321. if(!empty($table->_belongsTo[$name])) {
  322. $obj = $table->_belongsTo[$name];
  323. $columnName = $obj->foreignKey;
  324. if(empty($this->$columnName)) {
  325. $this->$name = null;
  326. }
  327. else {
  328. if(($k = reset($obj->TableInfo()->keys))) {
  329. $belongsToId = $k;
  330. }
  331. else {
  332. $belongsToId = 'id';
  333. }
  334. $arrayOfOne =
  335. $obj->Find(
  336. $belongsToId.'='.$this->$columnName.' '.$whereOrderBy, false, false, $extras);
  337. $this->$name = $arrayOfOne[0];
  338. }
  339. return $this->$name;
  340. }
  341. if(!empty($table->_hasMany[$name])) {
  342. $obj = $table->_hasMany[$name];
  343. if(($k = reset($table->keys))) {
  344. $hasManyId = $k;
  345. }
  346. else {
  347. $hasManyId = 'id';
  348. }
  349. $this->$name =
  350. $obj->Find(
  351. $obj->foreignKey.'='.$this->$hasManyId.' '.$whereOrderBy, false, false, $extras);
  352. return $this->$name;
  353. }
  354. }
  355. //////////////////////////////////
  356. // update metadata
  357. function UpdateActiveTable($pkeys=false,$forceUpdate=false)
  358. {
  359. global $_ADODB_ACTIVE_DBS , $ADODB_CACHE_DIR, $ADODB_ACTIVE_CACHESECS;
  360. global $ADODB_ACTIVE_DEFVALS, $ADODB_FETCH_MODE;
  361. $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat];
  362. $table = $this->_table;
  363. $tables = $activedb->tables;
  364. $tableat = $this->_tableat;
  365. if (!$forceUpdate && !empty($tables[$tableat])) {
  366. $tobj = $tables[$tableat];
  367. foreach($tobj->flds as $name => $fld) {
  368. if ($ADODB_ACTIVE_DEFVALS && isset($fld->default_value)) {
  369. $this->$name = $fld->default_value;
  370. }
  371. else {
  372. $this->$name = null;
  373. }
  374. }
  375. return;
  376. }
  377. $db = $activedb->db;
  378. $fname = $ADODB_CACHE_DIR . '/adodb_' . $db->databaseType . '_active_'. $table . '.cache';
  379. if (!$forceUpdate && $ADODB_ACTIVE_CACHESECS && $ADODB_CACHE_DIR && file_exists($fname)) {
  380. $fp = fopen($fname,'r');
  381. @flock($fp, LOCK_SH);
  382. $acttab = unserialize(fread($fp,100000));
  383. fclose($fp);
  384. if ($acttab->_created + $ADODB_ACTIVE_CACHESECS - (abs(rand()) % 16) > time()) {
  385. // abs(rand()) randomizes deletion, reducing contention to delete/refresh file
  386. // ideally, you should cache at least 32 secs
  387. $activedb->tables[$table] = $acttab;
  388. //if ($db->debug) ADOConnection::outp("Reading cached active record file: $fname");
  389. return;
  390. } else if ($db->debug) {
  391. ADOConnection::outp("Refreshing cached active record file: $fname");
  392. }
  393. }
  394. $activetab = new ADODB_Active_Table();
  395. $activetab->name = $table;
  396. $save = $ADODB_FETCH_MODE;
  397. $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
  398. if ($db->fetchMode !== false) {
  399. $savem = $db->SetFetchMode(false);
  400. }
  401. $cols = $db->MetaColumns($table);
  402. if (isset($savem)) {
  403. $db->SetFetchMode($savem);
  404. }
  405. $ADODB_FETCH_MODE = $save;
  406. if (!$cols) {
  407. $this->Error("Invalid table name: $table",'UpdateActiveTable');
  408. return false;
  409. }
  410. $fld = reset($cols);
  411. if (!$pkeys) {
  412. if (isset($fld->primary_key)) {
  413. $pkeys = array();
  414. foreach($cols as $name => $fld) {
  415. if (!empty($fld->primary_key)) {
  416. $pkeys[] = $name;
  417. }
  418. }
  419. } else {
  420. $pkeys = $this->GetPrimaryKeys($db, $table);
  421. }
  422. }
  423. if (empty($pkeys)) {
  424. $this->Error("No primary key found for table $table",'UpdateActiveTable');
  425. return false;
  426. }
  427. $attr = array();
  428. $keys = array();
  429. switch (ADODB_ASSOC_CASE) {
  430. case ADODB_ASSOC_CASE_LOWER:
  431. foreach($cols as $name => $fldobj) {
  432. $name = strtolower($name);
  433. if ($ADODB_ACTIVE_DEFVALS && isset($fldobj->default_value)) {
  434. $this->$name = $fldobj->default_value;
  435. }
  436. else {
  437. $this->$name = null;
  438. }
  439. $attr[$name] = $fldobj;
  440. }
  441. foreach($pkeys as $k => $name) {
  442. $keys[strtolower($name)] = strtolower($name);
  443. }
  444. break;
  445. case ADODB_ASSOC_CASE_UPPER:
  446. foreach($cols as $name => $fldobj) {
  447. $name = strtoupper($name);
  448. if ($ADODB_ACTIVE_DEFVALS && isset($fldobj->default_value)) {
  449. $this->$name = $fldobj->default_value;
  450. }
  451. else {
  452. $this->$name = null;
  453. }
  454. $attr[$name] = $fldobj;
  455. }
  456. foreach($pkeys as $k => $name) {
  457. $keys[strtoupper($name)] = strtoupper($name);
  458. }
  459. break;
  460. default:
  461. foreach($cols as $name => $fldobj) {
  462. $name = ($fldobj->name);
  463. if ($ADODB_ACTIVE_DEFVALS && isset($fldobj->default_value)) {
  464. $this->$name = $fldobj->default_value;
  465. }
  466. else {
  467. $this->$name = null;
  468. }
  469. $attr[$name] = $fldobj;
  470. }
  471. foreach($pkeys as $k => $name) {
  472. $keys[$name] = $cols[$name]->name;
  473. }
  474. break;
  475. }
  476. $activetab->keys = $keys;
  477. $activetab->flds = $attr;
  478. $activetab->updateColsCount();
  479. if ($ADODB_ACTIVE_CACHESECS && $ADODB_CACHE_DIR) {
  480. $activetab->_created = time();
  481. $s = serialize($activetab);
  482. if (!function_exists('adodb_write_file')) {
  483. include(ADODB_DIR.'/adodb-csvlib.inc.php');
  484. }
  485. adodb_write_file($fname,$s);
  486. }
  487. if (isset($activedb->tables[$table])) {
  488. $oldtab = $activedb->tables[$table];
  489. if ($oldtab) {
  490. $activetab->_belongsTo = $oldtab->_belongsTo;
  491. $activetab->_hasMany = $oldtab->_hasMany;
  492. }
  493. }
  494. $activedb->tables[$table] = $activetab;
  495. }
  496. function GetPrimaryKeys(&$db, $table)
  497. {
  498. return $db->MetaPrimaryKeys($table);
  499. }
  500. // error handler for both PHP4+5.
  501. function Error($err,$fn)
  502. {
  503. global $_ADODB_ACTIVE_DBS;
  504. $fn = get_class($this).'::'.$fn;
  505. $this->_lasterr = $fn.': '.$err;
  506. if ($this->_dbat < 0) {
  507. $db = false;
  508. }
  509. else {
  510. $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat];
  511. $db = $activedb->db;
  512. }
  513. if (function_exists('adodb_throw')) {
  514. if (!$db) {
  515. adodb_throw('ADOdb_Active_Record', $fn, -1, $err, 0, 0, false);
  516. }
  517. else {
  518. adodb_throw($db->databaseType, $fn, -1, $err, 0, 0, $db);
  519. }
  520. } else {
  521. if (!$db || $db->debug) {
  522. ADOConnection::outp($this->_lasterr);
  523. }
  524. }
  525. }
  526. // return last error message
  527. function ErrorMsg()
  528. {
  529. if (!function_exists('adodb_throw')) {
  530. if ($this->_dbat < 0) {
  531. $db = false;
  532. }
  533. else {
  534. $db = $this->DB();
  535. }
  536. // last error could be database error too
  537. if ($db && $db->ErrorMsg()) {
  538. return $db->ErrorMsg();
  539. }
  540. }
  541. return $this->_lasterr;
  542. }
  543. function ErrorNo()
  544. {
  545. if ($this->_dbat < 0) {
  546. return -9999; // no database connection...
  547. }
  548. $db = $this->DB();
  549. return (int) $db->ErrorNo();
  550. }
  551. // retrieve ADOConnection from _ADODB_Active_DBs
  552. function DB()
  553. {
  554. global $_ADODB_ACTIVE_DBS;
  555. if ($this->_dbat < 0) {
  556. $false = false;
  557. $this->Error("No database connection set: use ADOdb_Active_Record::SetDatabaseAdaptor(\$db)", "DB");
  558. return $false;
  559. }
  560. $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat];
  561. $db = $activedb->db;
  562. return $db;
  563. }
  564. // retrieve ADODB_Active_Table
  565. function &TableInfo()
  566. {
  567. global $_ADODB_ACTIVE_DBS;
  568. $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat];
  569. $table = $activedb->tables[$this->_tableat];
  570. return $table;
  571. }
  572. // I have an ON INSERT trigger on a table that sets other columns in the table.
  573. // So, I find that for myTable, I want to reload an active record after saving it. -- Malcolm Cook
  574. function Reload()
  575. {
  576. $db =& $this->DB();
  577. if (!$db) {
  578. return false;
  579. }
  580. $table =& $this->TableInfo();
  581. $where = $this->GenWhere($db, $table);
  582. return($this->Load($where));
  583. }
  584. // set a numeric array (using natural table field ordering) as object properties
  585. function Set(&$row)
  586. {
  587. global $ACTIVE_RECORD_SAFETY;
  588. $db = $this->DB();
  589. if (!$row) {
  590. $this->_saved = false;
  591. return false;
  592. }
  593. $this->_saved = true;
  594. $table = $this->TableInfo();
  595. $sizeofFlds = sizeof($table->flds);
  596. $sizeofRow = sizeof($row);
  597. if ($ACTIVE_RECORD_SAFETY && $table->_colsCount != $sizeofRow && $sizeofFlds != $sizeofRow) {
  598. # <AP>
  599. $bad_size = TRUE;
  600. if($sizeofRow == 2 * $table->_colsCount || $sizeofRow == 2 * $sizeofFlds) {
  601. // Only keep string keys
  602. $keys = array_filter(array_keys($row), 'is_string');
  603. if (sizeof($keys) == sizeof($table->flds)) {
  604. $bad_size = FALSE;
  605. }
  606. }
  607. if ($bad_size) {
  608. $this->Error("Table structure of $this->_table has changed","Load");
  609. return false;
  610. }
  611. # </AP>
  612. }
  613. else {
  614. $keys = array_keys($row);
  615. }
  616. # <AP>
  617. reset($keys);
  618. $this->_original = array();
  619. foreach($table->flds as $name=>$fld) {
  620. $value = $row[current($keys)];
  621. $this->$name = $value;
  622. $this->_original[] = $value;
  623. if(!next($keys)) {
  624. break;
  625. }
  626. }
  627. $table =& $this->TableInfo();
  628. foreach($table->_belongsTo as $foreignTable) {
  629. $ft = $foreignTable->TableInfo();
  630. $propertyName = $ft->name;
  631. foreach($ft->flds as $name=>$fld) {
  632. $value = $row[current($keys)];
  633. $foreignTable->$name = $value;
  634. $foreignTable->_original[] = $value;
  635. if(!next($keys)) {
  636. break;
  637. }
  638. }
  639. }
  640. foreach($table->_hasMany as $foreignTable) {
  641. $ft = $foreignTable->TableInfo();
  642. foreach($ft->flds as $name=>$fld) {
  643. $value = $row[current($keys)];
  644. $foreignTable->$name = $value;
  645. $foreignTable->_original[] = $value;
  646. if(!next($keys)) {
  647. break;
  648. }
  649. }
  650. }
  651. # </AP>
  652. return true;
  653. }
  654. // get last inserted id for INSERT
  655. function LastInsertID(&$db,$fieldname)
  656. {
  657. if ($db->hasInsertID) {
  658. $val = $db->Insert_ID($this->_table,$fieldname);
  659. }
  660. else {
  661. $val = false;
  662. }
  663. if (is_null($val) || $val === false) {
  664. // this might not work reliably in multi-user environment
  665. return $db->GetOne("select max(".$fieldname.") from ".$this->_table);
  666. }
  667. return $val;
  668. }
  669. // quote data in where clause
  670. function doquote(&$db, $val,$t)
  671. {
  672. switch($t) {
  673. case 'D':
  674. case 'T':
  675. if (empty($val)) {
  676. return 'null';
  677. }
  678. case 'C':
  679. case 'X':
  680. if (is_null($val)) {
  681. return 'null';
  682. }
  683. if (strlen($val)>0 &&
  684. (strncmp($val,"'",1) != 0 || substr($val,strlen($val)-1,1) != "'")
  685. ) {
  686. return $db->qstr($val);
  687. break;
  688. }
  689. default:
  690. return $val;
  691. break;
  692. }
  693. }
  694. // generate where clause for an UPDATE/SELECT
  695. function GenWhere(&$db, &$table)
  696. {
  697. $keys = $table->keys;
  698. $parr = array();
  699. foreach($keys as $k) {
  700. $f = $table->flds[$k];
  701. if ($f) {
  702. $parr[] = $k.' = '.$this->doquote($db,$this->$k,$db->MetaType($f->type));
  703. }
  704. }
  705. return implode(' and ', $parr);
  706. }
  707. //------------------------------------------------------------ Public functions below
  708. function Load($where=null,$bindarr=false)
  709. {
  710. $db = $this->DB();
  711. if (!$db) {
  712. return false;
  713. }
  714. $this->_where = $where;
  715. $save = $db->SetFetchMode(ADODB_FETCH_NUM);
  716. $qry = "select * from ".$this->_table;
  717. $table =& $this->TableInfo();
  718. if(($k = reset($table->keys))) {
  719. $hasManyId = $k;
  720. }
  721. else {
  722. $hasManyId = 'id';
  723. }
  724. foreach($table->_belongsTo as $foreignTable) {
  725. if(($k = reset($foreignTable->TableInfo()->keys))) {
  726. $belongsToId = $k;
  727. }
  728. else {
  729. $belongsToId = 'id';
  730. }
  731. $qry .= ' LEFT JOIN '.$foreignTable->_table.' ON '.
  732. $this->_table.'.'.$foreignTable->foreignKey.'='.
  733. $foreignTable->_table.'.'.$belongsToId;
  734. }
  735. foreach($table->_hasMany as $foreignTable)
  736. {
  737. $qry .= ' LEFT JOIN '.$foreignTable->_table.' ON '.
  738. $this->_table.'.'.$hasManyId.'='.
  739. $foreignTable->_table.'.'.$foreignTable->foreignKey;
  740. }
  741. if($where) {
  742. $qry .= ' WHERE '.$where;
  743. }
  744. // Simple case: no relations. Load row and return.
  745. if((count($table->_hasMany) + count($table->_belongsTo)) < 1) {
  746. $row = $db->GetRow($qry,$bindarr);
  747. if(!$row) {
  748. return false;
  749. }
  750. $db->SetFetchMode($save);
  751. return $this->Set($row);
  752. }
  753. // More complex case when relations have to be collated
  754. $rows = $db->GetAll($qry,$bindarr);
  755. if(!$rows) {
  756. return false;
  757. }
  758. $db->SetFetchMode($save);
  759. if(count($rows) < 1) {
  760. return false;
  761. }
  762. $class = get_class($this);
  763. $isFirstRow = true;
  764. if(($k = reset($this->TableInfo()->keys))) {
  765. $myId = $k;
  766. }
  767. else {
  768. $myId = 'id';
  769. }
  770. $index = 0; $found = false;
  771. /** @todo Improve by storing once and for all in table metadata */
  772. /** @todo Also re-use info for hasManyId */
  773. foreach($this->TableInfo()->flds as $fld) {
  774. if($fld->name == $myId) {
  775. $found = true;
  776. break;
  777. }
  778. $index++;
  779. }
  780. if(!$found) {
  781. $this->outp_throw("Unable to locate key $myId for $class in Load()",'Load');
  782. }
  783. foreach($rows as $row) {
  784. $rowId = intval($row[$index]);
  785. if($rowId > 0) {
  786. if($isFirstRow) {
  787. $isFirstRow = false;
  788. if(!$this->Set($row)) {
  789. return false;
  790. }
  791. }
  792. $obj = new $class($table,false,$db);
  793. $obj->Set($row);
  794. // TODO Copy/paste code below: bad!
  795. if(count($table->_hasMany) > 0) {
  796. foreach($table->_hasMany as $foreignTable) {
  797. $foreignName = $foreignTable->foreignName;
  798. if(!empty($obj->$foreignName)) {
  799. if(!is_array($this->$foreignName)) {
  800. $foreignObj = $this->$foreignName;
  801. $this->$foreignName = array(clone($foreignObj));
  802. }
  803. else {
  804. $foreignObj = $obj->$foreignName;
  805. array_push($this->$foreignName, clone($foreignObj));
  806. }
  807. }
  808. }
  809. }
  810. if(count($table->_belongsTo) > 0) {
  811. foreach($table->_belongsTo as $foreignTable) {
  812. $foreignName = $foreignTable->foreignName;
  813. if(!empty($obj->$foreignName)) {
  814. if(!is_array($this->$foreignName)) {
  815. $foreignObj = $this->$foreignName;
  816. $this->$foreignName = array(clone($foreignObj));
  817. }
  818. else {
  819. $foreignObj = $obj->$foreignName;
  820. array_push($this->$foreignName, clone($foreignObj));
  821. }
  822. }
  823. }
  824. }
  825. }
  826. }
  827. return true;
  828. }
  829. // false on error
  830. function Save()
  831. {
  832. if ($this->_saved) {
  833. $ok = $this->Update();
  834. }
  835. else {
  836. $ok = $this->Insert();
  837. }
  838. return $ok;
  839. }
  840. // CFR: Sometimes we may wish to consider that an object is not to be replaced but inserted.
  841. // Sample use case: an 'undo' command object (after a delete())
  842. function Dirty()
  843. {
  844. $this->_saved = false;
  845. }
  846. // false on error
  847. function Insert()
  848. {
  849. $db = $this->DB();
  850. if (!$db) {
  851. return false;
  852. }
  853. $cnt = 0;
  854. $table = $this->TableInfo();
  855. $valarr = array();
  856. $names = array();
  857. $valstr = array();
  858. foreach($table->flds as $name=>$fld) {
  859. $val = $this->$name;
  860. if(!is_null($val) || !array_key_exists($name, $table->keys)) {
  861. $valarr[] = $val;
  862. $names[] = $name;
  863. $valstr[] = $db->Param($cnt);
  864. $cnt += 1;
  865. }
  866. }
  867. if (empty($names)){
  868. foreach($table->flds as $name=>$fld) {
  869. $valarr[] = null;
  870. $names[] = $name;
  871. $valstr[] = $db->Param($cnt);
  872. $cnt += 1;
  873. }
  874. }
  875. $sql = 'INSERT INTO '.$this->_table."(".implode(',',$names).') VALUES ('.implode(',',$valstr).')';
  876. $ok = $db->Execute($sql,$valarr);
  877. if ($ok) {
  878. $this->_saved = true;
  879. $autoinc = false;
  880. foreach($table->keys as $k) {
  881. if (is_null($this->$k)) {
  882. $autoinc = true;
  883. break;
  884. }
  885. }
  886. if ($autoinc && sizeof($table->keys) == 1) {
  887. $k = reset($table->keys);
  888. $this->$k = $this->LastInsertID($db,$k);
  889. }
  890. }
  891. $this->_original = $valarr;
  892. return !empty($ok);
  893. }
  894. function Delete()
  895. {
  896. $db = $this->DB();
  897. if (!$db) {
  898. return false;
  899. }
  900. $table = $this->TableInfo();
  901. $where = $this->GenWhere($db,$table);
  902. $sql = 'DELETE FROM '.$this->_table.' WHERE '.$where;
  903. $ok = $db->Execute($sql);
  904. return $ok ? true : false;
  905. }
  906. // returns an array of active record objects
  907. function Find($whereOrderBy,$bindarr=false,$pkeysArr=false,$extra=array())
  908. {
  909. $db = $this->DB();
  910. if (!$db || empty($this->_table)) {
  911. return false;
  912. }
  913. $table =& $this->TableInfo();
  914. $arr = $db->GetActiveRecordsClass(get_class($this),$this, $whereOrderBy,$bindarr,$pkeysArr,$extra,
  915. array('foreignName'=>$this->foreignName, 'belongsTo'=>$table->_belongsTo, 'hasMany'=>$table->_hasMany));
  916. return $arr;
  917. }
  918. // CFR: In introduced this method to ensure that inner workings are not disturbed by
  919. // subclasses...for instance when GetActiveRecordsClass invokes Find()
  920. // Why am I not invoking parent::Find?
  921. // Shockingly because I want to preserve PHP4 compatibility.
  922. function packageFind($whereOrderBy,$bindarr=false,$pkeysArr=false,$extra=array())
  923. {
  924. $db = $this->DB();
  925. if (!$db || empty($this->_table)) {
  926. return false;
  927. }
  928. $table =& $this->TableInfo();
  929. $arr = $db->GetActiveRecordsClass(get_class($this),$this, $whereOrderBy,$bindarr,$pkeysArr,$extra,
  930. array('foreignName'=>$this->foreignName, 'belongsTo'=>$table->_belongsTo, 'hasMany'=>$table->_hasMany));
  931. return $arr;
  932. }
  933. // returns 0 on error, 1 on update, 2 on insert
  934. function Replace()
  935. {
  936. $db = $this->DB();
  937. if (!$db) {
  938. return false;
  939. }
  940. $table = $this->TableInfo();
  941. $pkey = $table->keys;
  942. foreach($table->flds as $name=>$fld) {
  943. $val = $this->$name;
  944. /*
  945. if (is_null($val)) {
  946. if (isset($fld->not_null) && $fld->not_null) {
  947. if (isset($fld->default_value) && strlen($fld->default_value)) {
  948. continue;
  949. }
  950. else {
  951. $this->Error("Cannot update null into $name","Replace");
  952. return false;
  953. }
  954. }
  955. }*/
  956. if (is_null($val) && !empty($fld->auto_increment)) {
  957. continue;
  958. }
  959. $t = $db->MetaType($fld->type);
  960. $arr[$name] = $this->doquote($db,$val,$t);
  961. $valarr[] = $val;
  962. }
  963. if (!is_array($pkey)) {
  964. $pkey = array($pkey);
  965. }
  966. switch (ADODB_ASSOC_CASE) {
  967. case ADODB_ASSOC_CASE_LOWER:
  968. foreach($pkey as $k => $v) {
  969. $pkey[$k] = strtolower($v);
  970. }
  971. break;
  972. case ADODB_ASSOC_CASE_UPPER:
  973. foreach($pkey as $k => $v) {
  974. $pkey[$k] = strtoupper($v);
  975. }
  976. break;
  977. }
  978. $ok = $db->Replace($this->_table,$arr,$pkey);
  979. if ($ok) {
  980. $this->_saved = true; // 1= update 2=insert
  981. if ($ok == 2) {
  982. $autoinc = false;
  983. foreach($table->keys as $k) {
  984. if (is_null($this->$k)) {
  985. $autoinc = true;
  986. break;
  987. }
  988. }
  989. if ($autoinc && sizeof($table->keys) == 1) {
  990. $k = reset($table->keys);
  991. $this->$k = $this->LastInsertID($db,$k);
  992. }
  993. }
  994. $this->_original = $valarr;
  995. }
  996. return $ok;
  997. }
  998. // returns 0 on error, 1 on update, -1 if no change in data (no update)
  999. function Update()
  1000. {
  1001. $db = $this->DB();
  1002. if (!$db) {
  1003. return false;
  1004. }
  1005. $table = $this->TableInfo();
  1006. $where = $this->GenWhere($db, $table);
  1007. if (!$where) {
  1008. $this->error("Where missing for table $table", "Update");
  1009. return false;
  1010. }
  1011. $valarr = array();
  1012. $neworig = array();
  1013. $pairs = array();
  1014. $i = -1;
  1015. $cnt = 0;
  1016. foreach($table->flds as $name=>$fld) {
  1017. $i += 1;
  1018. $val = $this->$name;
  1019. $neworig[] = $val;
  1020. if (isset($table->keys[$name])) {
  1021. continue;
  1022. }
  1023. if (is_null($val)) {
  1024. if (isset($fld->not_null) && $fld->not_null) {
  1025. if (isset($fld->default_value) && strlen($fld->default_value)) {
  1026. continue;
  1027. }
  1028. else {
  1029. $this->Error("Cannot set field $name to NULL","Update");
  1030. return false;
  1031. }
  1032. }
  1033. }
  1034. if (isset($this->_original[$i]) && $val === $this->_original[$i]) {
  1035. continue;
  1036. }
  1037. $valarr[] = $val;
  1038. $pairs[] = $name.'='.$db->Param($cnt);
  1039. $cnt += 1;
  1040. }
  1041. if (!$cnt) {
  1042. return -1;
  1043. }
  1044. $sql = 'UPDATE '.$this->_table." SET ".implode(",",$pairs)." WHERE ".$where;
  1045. $ok = $db->Execute($sql,$valarr);
  1046. if ($ok) {
  1047. $this->_original = $neworig;
  1048. return 1;
  1049. }
  1050. return 0;
  1051. }
  1052. function GetAttributeNames()
  1053. {
  1054. $table = $this->TableInfo();
  1055. if (!$table) {
  1056. return false;
  1057. }
  1058. return array_keys($table->flds);
  1059. }
  1060. };
  1061. function adodb_GetActiveRecordsClass(&$db, $class, $tableObj,$whereOrderBy,$bindarr, $primkeyArr,
  1062. $extra, $relations)
  1063. {
  1064. global $_ADODB_ACTIVE_DBS;
  1065. if (empty($extra['loading'])) {
  1066. $extra['loading'] = ADODB_LAZY_AR;
  1067. }
  1068. $save = $db->SetFetchMode(ADODB_FETCH_NUM);
  1069. $table = &$tableObj->_table;
  1070. $tableInfo =& $tableObj->TableInfo();
  1071. if(($k = reset($tableInfo->keys))) {
  1072. $myId = $k;
  1073. }
  1074. else {
  1075. $myId = 'id';
  1076. }
  1077. $index = 0; $found = false;
  1078. /** @todo Improve by storing once and for all in table metadata */
  1079. /** @todo Also re-use info for hasManyId */
  1080. foreach($tableInfo->flds as $fld)
  1081. {
  1082. if($fld->name == $myId) {
  1083. $found = true;
  1084. break;
  1085. }
  1086. $index++;
  1087. }
  1088. if(!$found) {
  1089. $db->outp_throw("Unable to locate key $myId for $class in GetActiveRecordsClass()",'GetActiveRecordsClass');
  1090. }
  1091. $qry = "select * from ".$table;
  1092. if(ADODB_JOIN_AR == $extra['loading']) {
  1093. if(!empty($relations['belongsTo'])) {
  1094. foreach($relations['belongsTo'] as $foreignTable) {
  1095. if(($k = reset($foreignTable->TableInfo()->keys))) {
  1096. $belongsToId = $k;
  1097. }
  1098. else {
  1099. $belongsToId = 'id';
  1100. }
  1101. $qry .= ' LEFT JOIN '.$foreignTable->_table.' ON '.
  1102. $table.'.'.$foreignTable->foreignKey.'='.
  1103. $foreignTable->_table.'.'.$belongsToId;
  1104. }
  1105. }
  1106. if(!empty($relations['hasMany'])) {
  1107. if(empty($relations['foreignName'])) {
  1108. $db->outp_throw("Missing foreignName is relation specification in GetActiveRecordsClass()",'GetActiveRecordsClass');
  1109. }
  1110. if(($k = reset($tableInfo->keys))) {
  1111. $hasManyId = $k;
  1112. }
  1113. else {
  1114. $hasManyId = 'id';
  1115. }
  1116. foreach($relations['hasMany'] as $foreignTable) {
  1117. $qry .= ' LEFT JOIN '.$foreignTable->_table.' ON '.
  1118. $table.'.'.$hasManyId.'='.
  1119. $foreignTable->_table.'.'.$foreignTable->foreignKey;
  1120. }
  1121. }
  1122. }
  1123. if (!empty($whereOrderBy)) {
  1124. $qry .= ' WHERE '.$whereOrderBy;
  1125. }
  1126. if(isset($extra['limit'])) {
  1127. $rows = false;
  1128. if(isset($extra['offset'])) {
  1129. $rs = $db->SelectLimit($qry, $extra['limit'], $extra['offset']);
  1130. } else {
  1131. $rs = $db->SelectLimit($qry, $extra['limit']);
  1132. }
  1133. if ($rs) {
  1134. while (!$rs->EOF) {
  1135. $rows[] = $rs->fields;
  1136. $rs->MoveNext();
  1137. }
  1138. }
  1139. } else
  1140. $rows = $db->GetAll($qry,$bindarr);
  1141. $db->SetFetchMode($save);
  1142. $false = false;
  1143. if ($rows === false) {
  1144. return $false;
  1145. }
  1146. if (!isset($_ADODB_ACTIVE_DBS)) {
  1147. include(ADODB_DIR.'/adodb-active-record.inc.php');
  1148. }
  1149. if (!class_exists($class)) {
  1150. $db->outp_throw("Unknown class $class in GetActiveRecordsClass()",'GetActiveRecordsClass');
  1151. return $false;
  1152. }
  1153. $uniqArr = array(); // CFR Keep track of records for relations
  1154. $arr = array();
  1155. // arrRef will be the structure that knows about our objects.
  1156. // It is an associative array.
  1157. // We will, however, return arr, preserving regular 0.. order so that
  1158. // obj[0] can be used by app developpers.
  1159. $arrRef = array();
  1160. $bTos = array(); // Will store belongTo's indices if any
  1161. foreach($rows as $row) {
  1162. $obj = new $class($table,$primkeyArr,$db);
  1163. if ($obj->ErrorNo()){
  1164. $db->_errorMsg = $obj->ErrorMsg();
  1165. return $false;
  1166. }
  1167. $obj->Set($row);
  1168. // CFR: FIXME: Insane assumption here:
  1169. // If the first column returned is an integer, then it's a 'id' field
  1170. // And to make things a bit worse, I use intval() rather than is_int() because, in fact,
  1171. // $row[0] is not an integer.
  1172. //
  1173. // So, what does this whole block do?
  1174. // When relationships are found, we perform JOINs. This is fast. But not accurate:
  1175. // instead of returning n objects with their n' associated cousins,
  1176. // we get n*n' objects. This code fixes this.
  1177. // Note: to-many relationships mess around with the 'limit' parameter
  1178. $rowId = intval($row[$index]);
  1179. if(ADODB_WORK_AR == $extra['loading']) {
  1180. $arrRef[$rowId] = $obj;
  1181. $arr[] = &$arrRef[$rowId];
  1182. if(!isset($indices)) {
  1183. $indices = $rowId;
  1184. }
  1185. else {
  1186. $indices .= ','.$rowId;
  1187. }
  1188. if(!empty($relations['belongsTo'])) {
  1189. foreach($relations['belongsTo'] as $foreignTable) {
  1190. $foreignTableRef = $foreignTable->foreignKey;
  1191. // First array: list of foreign ids we are looking for
  1192. if(empty($bTos[$foreignTableRef])) {
  1193. $bTos[$foreignTableRef] = array();
  1194. }
  1195. // Second array: list of ids found
  1196. if(empty($obj->$foreignTableRef)) {
  1197. continue;
  1198. }
  1199. if(empty($bTos[$foreignTableRef][$obj->$foreignTableRef])) {
  1200. $bTos[$foreignTableRef][$obj->$foreignTableRef] = array();
  1201. }
  1202. $bTos[$foreignTableRef][$obj->$foreignTableRef][] = $obj;
  1203. }
  1204. }
  1205. continue;
  1206. }
  1207. if($rowId>0) {
  1208. if(ADODB_JOIN_AR == $extra['loading']) {
  1209. $isNewObj = !isset($uniqArr['_'.$row[0]]);
  1210. if($isNewObj) {
  1211. $uniqArr['_'.$row[0]] = $obj;
  1212. }
  1213. // TODO Copy/paste code below: bad!
  1214. if(!empty($relations['hasMany'])) {
  1215. foreach($relations['hasMany'] as $foreignTable) {
  1216. $foreignName = $foreignTable->foreignName;
  1217. if(!empty($obj->$foreignName)) {
  1218. $masterObj = &$uniqArr['_'.$row[0]];
  1219. // Assumption: this property exists in every object since they are instances of the same class
  1220. if(!is_array($masterObj->$foreignName)) {
  1221. // Pluck!
  1222. $foreignObj = $masterObj->$foreignName;
  1223. $masterObj->$foreignName = array(clone($foreignObj));
  1224. }
  1225. else {
  1226. // Pluck pluck!
  1227. $foreignObj = $obj->$foreignName;
  1228. array_push($masterObj->$foreignName, clone($foreignObj));
  1229. }
  1230. }
  1231. }
  1232. }
  1233. if(!empty($relations['belongsTo'])) {
  1234. foreach($relations['belongsTo'] as $foreignTable) {
  1235. $foreignName = $foreignTable->foreignName;
  1236. if(!empty($obj->$foreignName)) {
  1237. $masterObj = &$uniqArr['_'.$row[0]];
  1238. // Assumption: this property exists in every object since they are instances of the same class
  1239. if(!is_array($masterObj->$foreignName)) {
  1240. // Pluck!
  1241. $foreignObj = $masterObj->$foreignName;
  1242. $masterObj->$foreignName = array(clone($foreignObj));
  1243. }
  1244. else {
  1245. // Pluck pluck!
  1246. $foreignObj = $obj->$foreignName;
  1247. array_push($masterObj->$foreignName, clone($foreignObj));
  1248. }
  1249. }
  1250. }
  1251. }
  1252. if(!$isNewObj) {
  1253. unset($obj); // We do not need this object itself anymore and do not want it re-added to the main array
  1254. }
  1255. }
  1256. else if(ADODB_LAZY_AR == $extra['loading']) {
  1257. // Lazy loading: we need to give AdoDb a hint that we have not really loaded
  1258. // anything, all the while keeping enough information on what we wish to load.
  1259. // Let's do this by keeping the relevant info in our relationship arrays
  1260. // but get rid of the actual properties.
  1261. // We will then use PHP's __get to load these properties on-demand.
  1262. if(!empty($relations['hasMany'])) {
  1263. foreach($relations['hasMany'] as $foreignTable) {
  1264. $foreignName = $foreignTable->foreignName;
  1265. if(!empty($obj->$foreignName)) {
  1266. unset($obj->$foreignName);
  1267. }
  1268. }
  1269. }
  1270. if(!empty($relations['belongsTo'])) {
  1271. foreach($relations['belongsTo'] as $foreignTable) {
  1272. $foreignName = $foreignTable->foreignName;
  1273. if(!empty($obj->$foreignName)) {
  1274. unset($obj->$foreignName);
  1275. }
  1276. }
  1277. }
  1278. }
  1279. }
  1280. if(isset($obj)) {
  1281. $arr[] = $obj;
  1282. }
  1283. }
  1284. if(ADODB_WORK_AR == $extra['loading']) {
  1285. // The best of both worlds?
  1286. // Here, the number of queries is constant: 1 + n*relationship.
  1287. // The second query will allow us to perform a good join
  1288. // while preserving LIMIT etc.
  1289. if(!empty($relations['hasMany'])) {
  1290. foreach($relations['hasMany'] as $foreignTable) {
  1291. $foreignName = $foreignTable->foreignName;
  1292. $className = ucfirst($foreignTable->_singularize($foreignName));
  1293. $obj = new $className();
  1294. $dbClassRef = $foreignTable->foreignKey;
  1295. $objs = $obj->packageFind($dbClassRef.' IN ('.$indices.')');
  1296. foreach($objs as $obj) {
  1297. if(!is_array($arrRef[$obj->$dbClassRef]->$foreignName)) {
  1298. $arrRef[$obj->$dbClassRef]->$foreignName = array();
  1299. }
  1300. array_push($arrRef[$obj->$dbClassRef]->$foreignName, $obj);
  1301. }
  1302. }
  1303. }
  1304. if(!empty($relations['belongsTo'])) {
  1305. foreach($relations['belongsTo'] as $foreignTable) {
  1306. $foreignTableRef = $foreignTable->foreignKey;
  1307. if(empty($bTos[$foreignTableRef])) {
  1308. continue;
  1309. }
  1310. if(($k = reset($foreignTable->TableInfo()->keys))) {
  1311. $belongsToId = $k;
  1312. }
  1313. else {
  1314. $belongsToId = 'id';
  1315. }
  1316. $origObjsArr = $bTos[$foreignTableRef];
  1317. $bTosString = implode(',', array_keys($bTos[$foreignTableRef]));
  1318. $foreignName = $foreignTable->foreignName;
  1319. $className = ucfirst($foreignTable->_singularize($foreignName));
  1320. $obj = new $className();
  1321. $objs = $obj->packageFind($belongsToId.' IN ('.$bTosString.')');
  1322. foreach($objs as $obj)
  1323. {
  1324. foreach($origObjsArr[$obj->$belongsToId] as $idx=>$origObj)
  1325. {
  1326. $origObj->$foreignName = $obj;
  1327. }
  1328. }
  1329. }
  1330. }
  1331. }
  1332. return $arr;
  1333. }