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