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