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