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/
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.
13 Active Record implementation. Superset of Zend Framework's.
15 This is "Active Record eXtended" to support JOIN, WORK and LAZY mode by Chris Ravenscroft chris#voilaweb.com
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
24 // CFR: Active Records Definitions
25 define('ADODB_JOIN_AR', 0x01);
26 define('ADODB_WORK_AR', 0x02);
27 define('ADODB_LAZY_AR', 0x03);
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.
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;
40 class ADODB_Active_DB
{
41 var $db; // ADOConnection
42 var $tables; // assoc array of ADODB_Active_Table objects, indexed by tablename
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
54 function updateColsCount()
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
);
64 // returns index into $_ADODB_ACTIVE_DBS
65 function ADODB_SetDatabaseAdapter(&$db)
67 global $_ADODB_ACTIVE_DBS;
69 foreach($_ADODB_ACTIVE_DBS as $k => $d) {
75 $obj = new ADODB_Active_DB();
77 $obj->tables
= array();
79 $_ADODB_ACTIVE_DBS[] = $obj;
81 return sizeof($_ADODB_ACTIVE_DBS)-1;
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
98 var $foreignName; // CFR: class name when in a relationship
100 static function UseDefaultValues($bool=null)
102 global $ADODB_ACTIVE_DEFVALS;
104 $ADODB_ACTIVE_DEFVALS = $bool;
106 return $ADODB_ACTIVE_DEFVALS;
110 static function SetDatabaseAdapter(&$db)
112 return ADODB_SetDatabaseAdapter($db);
116 public function __set($name, $value)
118 $name = str_replace(' ', '_', $name);
119 $this->$name = $value;
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.
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())
134 global $_ADODB_ACTIVE_DBS;
136 if ($db == false && is_object($pkeyarr)) {
142 // table argument exists. It is expected to be
143 // already plural form.
144 $this->_pTable
= $table;
145 $this->_sTable
= $this->_singularize($this->_pTable
);
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
);
153 $this->_table
= &$this->_pTable
;
155 $this->foreignName
= $this->_sTable
; // CFR: default foreign name (singular)
158 $this->_dbat
= ADODB_Active_Record
::SetDatabaseAdapter($db);
160 $this->_dbat
= sizeof($_ADODB_ACTIVE_DBS)-1;
163 if ($this->_dbat
< 0) {
165 "No database connection set; use ADOdb_Active_Record::SetDatabaseAdapter(\$db)",
166 'ADODB_Active_Record::__constructor'
170 $this->_tableat
= $this->_table
; # reserved for setting the assoc value to a non-table name, eg. the sql string in future
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();
187 $class = get_class($this);
191 // CFR: Constants found in Rails
192 static $IrregularP = array(
193 'PERSON' => 'people',
196 'CHILD' => 'children',
200 static $IrregularS = array(
201 'PEOPLE' => 'PERSON',
204 'CHILDREN' => 'child',
208 static $WeIsI = array(
210 'INFORMATION' => true,
219 function _pluralize($table)
221 if (!ADODB_Active_Record
::$_changeNames) {
224 $ut = strtoupper($table);
225 if(isset(self
::$WeIsI[$ut])) {
228 if(isset(self
::$IrregularP[$ut])) {
229 return self
::$IrregularP[$ut];
231 $len = strlen($table);
232 $lastc = $ut[$len-1];
233 $lastc2 = substr($ut,$len-2);
238 return substr($table,0,$len-1).'ies';
242 if ($lastc2 == 'CH' ||
$lastc2 == 'SH') {
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)
255 if (!ADODB_Active_Record
::$_changeNames) {
258 $ut = strtoupper($table);
259 if(isset(self
::$WeIsI[$ut])) {
262 if(isset(self
::$IrregularS[$ut])) {
263 return self
::$IrregularS[$ut];
265 $len = strlen($table);
266 if($ut[$len-1] != 'S') {
267 return $table; // I know...forget oxen
269 if($ut[$len-2] != 'E') {
270 return substr($table, 0, $len-1);
272 switch($ut[$len-3]) {
275 return substr($table, 0, $len-2);
277 return substr($table, 0, $len-3) . 'y';
279 if($ut[$len-4] == 'C' ||
$ut[$len-4] == 'S') {
280 return substr($table, 0, $len-2);
283 return substr($table, 0, $len-1); // ?
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)
292 * this-table.id = other-table-#1.this-table_id
293 * = other-table-#2.this-table_id
295 function hasMany($foreignRef,$foreignKey=false)
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;
302 $table =& $this->TableInfo();
303 if(!isset($table->_hasMany
[$foreignRef])) {
304 $table->_hasMany
[$foreignRef] = $ar;
305 $table->updateColsCount();
307 # @todo Can I make this guy be lazy?
308 $this->$foreignRef = $table->_hasMany
[$foreignRef]; // WATCHME Removed assignment by ref. to please __get()
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)
316 * this-table.other-table_id = other-table.id
318 function belongsTo($foreignRef,$foreignKey=false)
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;
327 $table =& $this->TableInfo();
328 if(!isset($table->_belongsTo
[$foreignRef])) {
329 $table->_belongsTo
[$foreignRef] = $ar;
330 $table->updateColsCount();
332 $this->$foreignRef = $table->_belongsTo
[$foreignRef];
336 * __get Access properties - used for lazy loading
342 function __get($name)
344 return $this->LoadRelations($name, '', -1. -1);
347 function LoadRelations($name, $whereOrderBy, $offset=-1, $limit=-1)
351 $extras['offset'] = $offset;
354 $extras['limit'] = $limit;
356 $table =& $this->TableInfo();
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;
366 if(!empty($table->_belongsTo
[$name])) {
367 $obj = $table->_belongsTo
[$name];
368 $columnName = $obj->foreignKey
;
369 if(empty($this->$columnName)) {
373 if(($k = reset($obj->TableInfo()->keys
))) {
382 $belongsToId.'='.$this->$columnName.' '.$whereOrderBy, false, false, $extras);
383 $this->$name = $arrayOfOne[0];
387 if(!empty($table->_hasMany
[$name])) {
388 $obj = $table->_hasMany
[$name];
389 if(($k = reset($table->keys
))) {
398 $obj->foreignKey
.'='.$this->$hasManyId.' '.$whereOrderBy, false, false, $extras);
402 //////////////////////////////////
405 function UpdateActiveTable($pkeys=false,$forceUpdate=false)
407 global $_ADODB_ACTIVE_DBS , $ADODB_CACHE_DIR, $ADODB_ACTIVE_CACHESECS;
408 global $ADODB_ACTIVE_DEFVALS, $ADODB_FETCH_MODE;
410 $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat
];
412 $table = $this->_table
;
413 $tables = $activedb->tables
;
414 $tableat = $this->_tableat
;
415 if (!$forceUpdate && !empty($tables[$tableat])) {
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
;
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));
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;
441 //if ($db->debug) ADOConnection::outp("Reading cached active record file: $fname");
443 } else if ($db->debug
) {
444 ADOConnection
::outp("Refreshing cached active record file: $fname");
447 $activetab = new ADODB_Active_Table();
448 $activetab->name
= $table;
450 $save = $ADODB_FETCH_MODE;
451 $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC
;
452 if ($db->fetchMode
!== false) {
453 $savem = $db->SetFetchMode(false);
456 $cols = $db->MetaColumns($table);
459 $db->SetFetchMode($savem);
461 $ADODB_FETCH_MODE = $save;
464 $this->Error("Invalid table name: $table",'UpdateActiveTable');
469 if (isset($fld->primary_key
)) {
471 foreach($cols as $name => $fld) {
472 if (!empty($fld->primary_key
)) {
477 $pkeys = $this->GetPrimaryKeys($db, $table);
481 $this->Error("No primary key found for table $table",'UpdateActiveTable');
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
;
498 $attr[$name] = $fldobj;
500 foreach($pkeys as $k => $name) {
501 $keys[strtolower($name)] = strtolower($name);
505 case ADODB_ASSOC_CASE_UPPER
:
506 foreach($cols as $name => $fldobj) {
507 $name = strtoupper($name);
509 if ($ADODB_ACTIVE_DEFVALS && isset($fldobj->default_value
)) {
510 $this->$name = $fldobj->default_value
;
515 $attr[$name] = $fldobj;
518 foreach($pkeys as $k => $name) {
519 $keys[strtoupper($name)] = strtoupper($name);
523 foreach($cols as $name => $fldobj) {
524 $name = ($fldobj->name
);
526 if ($ADODB_ACTIVE_DEFVALS && isset($fldobj->default_value
)) {
527 $this->$name = $fldobj->default_value
;
532 $attr[$name] = $fldobj;
534 foreach($pkeys as $k => $name) {
535 $keys[$name] = $cols[$name]->name
;
540 $activetab->keys
= $keys;
541 $activetab->flds
= $attr;
542 $activetab->updateColsCount();
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');
550 adodb_write_file($fname,$s);
552 if (isset($activedb->tables
[$table])) {
553 $oldtab = $activedb->tables
[$table];
556 $activetab->_belongsTo
= $oldtab->_belongsTo
;
557 $activetab->_hasMany
= $oldtab->_hasMany
;
560 $activedb->tables
[$table] = $activetab;
563 function GetPrimaryKeys(&$db, $table)
565 return $db->MetaPrimaryKeys($table);
568 // error handler for both PHP4+5.
569 function Error($err,$fn)
571 global $_ADODB_ACTIVE_DBS;
573 $fn = get_class($this).'::'.$fn;
574 $this->_lasterr
= $fn.': '.$err;
576 if ($this->_dbat
< 0) {
580 $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat
];
584 if (function_exists('adodb_throw')) {
586 adodb_throw('ADOdb_Active_Record', $fn, -1, $err, 0, 0, false);
589 adodb_throw($db->databaseType
, $fn, -1, $err, 0, 0, $db);
592 if (!$db ||
$db->debug
) {
593 ADOConnection
::outp($this->_lasterr
);
599 // return last error message
602 if (!function_exists('adodb_throw')) {
603 if ($this->_dbat
< 0) {
610 // last error could be database error too
611 if ($db && $db->ErrorMsg()) {
612 return $db->ErrorMsg();
615 return $this->_lasterr
;
620 if ($this->_dbat
< 0) {
621 return -9999; // no database connection...
625 return (int) $db->ErrorNo();
629 // retrieve ADOConnection from _ADODB_Active_DBs
632 global $_ADODB_ACTIVE_DBS;
634 if ($this->_dbat
< 0) {
636 $this->Error("No database connection set: use ADOdb_Active_Record::SetDatabaseAdaptor(\$db)", "DB");
639 $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat
];
644 // retrieve ADODB_Active_Table
645 function &TableInfo()
647 global $_ADODB_ACTIVE_DBS;
649 $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat
];
650 $table = $activedb->tables
[$this->_tableat
];
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
663 $table =& $this->TableInfo();
664 $where = $this->GenWhere($db, $table);
665 return($this->Load($where));
669 // set a numeric array (using natural table field ordering) as object properties
672 global $ACTIVE_RECORD_SAFETY;
677 $this->_saved
= false;
681 $this->_saved
= true;
683 $table = $this->TableInfo();
684 $sizeofFlds = sizeof($table->flds
);
685 $sizeofRow = sizeof($row);
686 if ($ACTIVE_RECORD_SAFETY && $table->_colsCount
!= $sizeofRow && $sizeofFlds != $sizeofRow) {
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
)) {
697 $this->Error("Table structure of $this->_table has changed","Load");
703 $keys = array_keys($row);
708 $this->_original
= array();
709 foreach($table->flds
as $name=>$fld) {
710 $value = $row[current($keys)];
711 $this->$name = $value;
712 $this->_original
[] = $value;
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;
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;
746 // get last inserted id for INSERT
747 function LastInsertID(&$db,$fieldname)
749 if ($db->hasInsertID
) {
750 $val = $db->Insert_ID($this->_table
,$fieldname);
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
);
763 // quote data in where clause
764 function doquote(&$db, $val,$t)
777 if (strlen($val)>0 &&
778 (strncmp($val,"'",1) != 0 ||
substr($val,strlen($val)-1,1) != "'")
780 return $db->qstr($val);
789 // generate where clause for an UPDATE/SELECT
790 function GenWhere(&$db, &$table)
792 $keys = $table->keys
;
795 foreach($keys as $k) {
796 $f = $table->flds
[$k];
798 $parr[] = $k.' = '.$this->doquote($db,$this->$k,$db->MetaType($f->type
));
801 return implode(' and ', $parr);
805 //------------------------------------------------------------ Public functions below
807 function Load($where=null,$bindarr=false)
813 $this->_where
= $where;
815 $save = $db->SetFetchMode(ADODB_FETCH_NUM
);
816 $qry = "select * from ".$this->_table
;
817 $table =& $this->TableInfo();
819 if(($k = reset($table->keys
))) {
826 foreach($table->_belongsTo
as $foreignTable) {
827 if(($k = reset($foreignTable->TableInfo()->keys
))) {
833 $qry .= ' LEFT JOIN '.$foreignTable->_table
.' ON '.
834 $this->_table
.'.'.$foreignTable->foreignKey
.'='.
835 $foreignTable->_table
.'.'.$belongsToId;
837 foreach($table->_hasMany
as $foreignTable)
839 $qry .= ' LEFT JOIN '.$foreignTable->_table
.' ON '.
840 $this->_table
.'.'.$hasManyId.'='.
841 $foreignTable->_table
.'.'.$foreignTable->foreignKey
;
844 $qry .= ' WHERE '.$where;
847 // Simple case: no relations. Load row and return.
848 if((count($table->_hasMany
) +
count($table->_belongsTo
)) < 1) {
849 $row = $db->GetRow($qry,$bindarr);
853 $db->SetFetchMode($save);
854 return $this->Set($row);
857 // More complex case when relations have to be collated
858 $rows = $db->GetAll($qry,$bindarr);
862 $db->SetFetchMode($save);
863 if(count($rows) < 1) {
866 $class = get_class($this);
869 if(($k = reset($this->TableInfo()->keys
))) {
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) {
886 $this->outp_throw("Unable to locate key $myId for $class in Load()",'Load');
889 foreach($rows as $row) {
890 $rowId = intval($row[$index]);
894 if(!$this->Set($row)) {
898 $obj = new $class($table,false,$db);
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));
910 $foreignObj = $obj->$foreignName;
911 array_push($this->$foreignName, clone($foreignObj));
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));
925 $foreignObj = $obj->$foreignName;
926 array_push($this->$foreignName, clone($foreignObj));
940 $ok = $this->Update();
943 $ok = $this->Insert();
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())
953 $this->_saved
= false;
964 $table = $this->TableInfo();
970 foreach($table->flds
as $name=>$fld) {
972 if(!is_null($val) ||
!array_key_exists($name, $table->keys
)) {
975 $valstr[] = $db->Param($cnt);
981 foreach($table->flds
as $name=>$fld) {
984 $valstr[] = $db->Param($cnt);
988 $sql = 'INSERT INTO '.$this->_table
."(".implode(',',$names).') VALUES ('.implode(',',$valstr).')';
989 $ok = $db->Execute($sql,$valarr);
992 $this->_saved
= true;
994 foreach($table->keys
as $k) {
995 if (is_null($this->$k)) {
1000 if ($autoinc && sizeof($table->keys
) == 1) {
1001 $k = reset($table->keys
);
1002 $this->$k = $this->LastInsertID($db,$k);
1006 $this->_original
= $valarr;
1016 $table = $this->TableInfo();
1018 $where = $this->GenWhere($db,$table);
1019 $sql = 'DELETE FROM '.$this->_table
.' WHERE '.$where;
1020 $ok = $db->Execute($sql);
1022 return $ok ?
true : false;
1025 // returns an array of active record objects
1026 function Find($whereOrderBy,$bindarr=false,$pkeysArr=false,$extra=array())
1029 if (!$db ||
empty($this->_table
)) {
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
));
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())
1045 if (!$db ||
empty($this->_table
)) {
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
));
1054 // returns 0 on error, 1 on update, 2 on insert
1061 $table = $this->TableInfo();
1063 $pkey = $table->keys
;
1065 foreach($table->flds
as $name=>$fld) {
1066 $val = $this->$name;
1068 if (is_null($val)) {
1069 if (isset($fld->not_null) && $fld->not_null) {
1070 if (isset($fld->default_value) && strlen($fld->default_value)) {
1074 $this->Error("Cannot update null into $name","Replace");
1079 if (is_null($val) && !empty($fld->auto_increment
)) {
1082 $t = $db->MetaType($fld->type
);
1083 $arr[$name] = $this->doquote($db,$val,$t);
1087 if (!is_array($pkey)) {
1088 $pkey = array($pkey);
1092 switch (ADODB_ASSOC_CASE
) {
1093 case ADODB_ASSOC_CASE_LOWER
:
1094 foreach($pkey as $k => $v) {
1095 $pkey[$k] = strtolower($v);
1098 case ADODB_ASSOC_CASE_UPPER
:
1099 foreach($pkey as $k => $v) {
1100 $pkey[$k] = strtoupper($v);
1105 $ok = $db->Replace($this->_table
,$arr,$pkey);
1107 $this->_saved
= true; // 1= update 2=insert
1110 foreach($table->keys
as $k) {
1111 if (is_null($this->$k)) {
1116 if ($autoinc && sizeof($table->keys
) == 1) {
1117 $k = reset($table->keys
);
1118 $this->$k = $this->LastInsertID($db,$k);
1122 $this->_original
= $valarr;
1127 // returns 0 on error, 1 on update, -1 if no change in data (no update)
1134 $table = $this->TableInfo();
1136 $where = $this->GenWhere($db, $table);
1139 $this->error("Where missing for table $table", "Update");
1147 foreach($table->flds
as $name=>$fld) {
1149 $val = $this->$name;
1152 if (isset($table->keys
[$name])) {
1156 if (is_null($val)) {
1157 if (isset($fld->not_null
) && $fld->not_null
) {
1158 if (isset($fld->default_value
) && strlen($fld->default_value
)) {
1162 $this->Error("Cannot set field $name to NULL","Update");
1168 if (isset($this->_original
[$i]) && $val === $this->_original
[$i]) {
1172 $pairs[] = $name.'='.$db->Param($cnt);
1180 $sql = 'UPDATE '.$this->_table
." SET ".implode(",",$pairs)." WHERE ".$where;
1181 $ok = $db->Execute($sql,$valarr);
1183 $this->_original
= $neworig;
1189 function GetAttributeNames()
1191 $table = $this->TableInfo();
1195 return array_keys($table->flds
);
1200 function adodb_GetActiveRecordsClass(&$db, $class, $tableObj,$whereOrderBy,$bindarr, $primkeyArr,
1203 global $_ADODB_ACTIVE_DBS;
1205 if (empty($extra['loading'])) {
1206 $extra['loading'] = ADODB_LAZY_AR
;
1208 $save = $db->SetFetchMode(ADODB_FETCH_NUM
);
1209 $table = &$tableObj->_table
;
1210 $tableInfo =& $tableObj->TableInfo();
1211 if(($k = reset($tableInfo->keys
))) {
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)
1222 if($fld->name
== $myId) {
1229 $db->outp_throw("Unable to locate key $myId for $class in GetActiveRecordsClass()",'GetActiveRecordsClass');
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
))) {
1240 $belongsToId = 'id';
1243 $qry .= ' LEFT JOIN '.$foreignTable->_table
.' ON '.
1244 $table.'.'.$foreignTable->foreignKey
.'='.
1245 $foreignTable->_table
.'.'.$belongsToId;
1248 if(!empty($relations['hasMany'])) {
1249 if(empty($relations['foreignName'])) {
1250 $db->outp_throw("Missing foreignName is relation specification in GetActiveRecordsClass()",'GetActiveRecordsClass');
1252 if(($k = reset($tableInfo->keys
))) {
1259 foreach($relations['hasMany'] as $foreignTable) {
1260 $qry .= ' LEFT JOIN '.$foreignTable->_table
.' ON '.
1261 $table.'.'.$hasManyId.'='.
1262 $foreignTable->_table
.'.'.$foreignTable->foreignKey
;
1266 if (!empty($whereOrderBy)) {
1267 $qry .= ' WHERE '.$whereOrderBy;
1269 if(isset($extra['limit'])) {
1271 if(isset($extra['offset'])) {
1272 $rs = $db->SelectLimit($qry, $extra['limit'], $extra['offset']);
1274 $rs = $db->SelectLimit($qry, $extra['limit']);
1278 $rows[] = $rs->fields
;
1283 $rows = $db->GetAll($qry,$bindarr);
1285 $db->SetFetchMode($save);
1289 if ($rows === false) {
1294 if (!isset($_ADODB_ACTIVE_DBS)) {
1295 include_once(ADODB_DIR
.'/adodb-active-record.inc.php');
1297 if (!class_exists($class)) {
1298 $db->outp_throw("Unknown class $class in GetActiveRecordsClass()",'GetActiveRecordsClass');
1301 $uniqArr = array(); // CFR Keep track of records for relations
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.
1308 $bTos = array(); // Will store belongTo's indices if any
1309 foreach($rows as $row) {
1311 $obj = new $class($table,$primkeyArr,$db);
1312 if ($obj->ErrorNo()){
1313 $db->_errorMsg
= $obj->ErrorMsg();
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.
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]);
1329 if(ADODB_WORK_AR
== $extra['loading']) {
1330 $arrRef[$rowId] = $obj;
1331 $arr[] = &$arrRef[$rowId];
1332 if(!isset($indices)) {
1336 $indices .= ','.$rowId;
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();
1345 // Second array: list of ids found
1346 if(empty($obj->$foreignTableRef)) {
1349 if(empty($bTos[$foreignTableRef][$obj->$foreignTableRef])) {
1350 $bTos[$foreignTableRef][$obj->$foreignTableRef] = array();
1352 $bTos[$foreignTableRef][$obj->$foreignTableRef][] = $obj;
1359 if(ADODB_JOIN_AR
== $extra['loading']) {
1360 $isNewObj = !isset($uniqArr['_'.$row[0]]);
1362 $uniqArr['_'.$row[0]] = $obj;
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)) {
1374 $foreignObj = $masterObj->$foreignName;
1375 $masterObj->$foreignName = array(clone($foreignObj));
1379 $foreignObj = $obj->$foreignName;
1380 array_push($masterObj->$foreignName, clone($foreignObj));
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)) {
1393 $foreignObj = $masterObj->$foreignName;
1394 $masterObj->$foreignName = array(clone($foreignObj));
1398 $foreignObj = $obj->$foreignName;
1399 array_push($masterObj->$foreignName, clone($foreignObj));
1405 unset($obj); // We do not need this object itself anymore and do not want it re-added to the main array
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);
1422 if(!empty($relations['belongsTo'])) {
1423 foreach($relations['belongsTo'] as $foreignTable) {
1424 $foreignName = $foreignTable->foreignName
;
1425 if(!empty($obj->$foreignName)) {
1426 unset($obj->$foreignName);
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();
1454 array_push($arrRef[$obj->$dbClassRef]->$foreignName, $obj);
1459 if(!empty($relations['belongsTo'])) {
1460 foreach($relations['belongsTo'] as $foreignTable) {
1461 $foreignTableRef = $foreignTable->foreignKey
;
1462 if(empty($bTos[$foreignTableRef])) {
1465 if(($k = reset($foreignTable->TableInfo()->keys
))) {
1469 $belongsToId = 'id';
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)
1479 foreach($origObjsArr[$obj->$belongsToId] as $idx=>$origObj)
1481 $origObj->$foreignName = $obj;