4 @version v5.20.16 12-Jan-2020
5 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
6 @copyright (c) 2014 Damien Regad, Mark Newnham and the ADOdb community
7 Latest version is available at http://adodb.org/
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) {
70 if (PHP_VERSION
>= 5) {
75 if ($d->db
->_connectionID
=== $db->_connectionID
&& $db->database
== $d->db
->database
) {
81 $obj = new ADODB_Active_DB();
83 $obj->tables
= array();
85 $_ADODB_ACTIVE_DBS[] = $obj;
87 return sizeof($_ADODB_ACTIVE_DBS)-1;
91 class ADODB_Active_Record
{
92 static $_changeNames = true; // dynamically pluralize table names
93 static $_foreignSuffix = '_id'; //
94 var $_dbat; // associative index pointing to ADODB_Active_DB eg. $ADODB_Active_DBS[_dbat]
95 var $_table; // tablename, if set in class definition then use it as table name
96 var $_sTable; // singularized table name
97 var $_pTable; // pluralized table name
98 var $_tableat; // associative index pointing to ADODB_Active_Table, eg $ADODB_Active_DBS[_dbat]->tables[$this->_tableat]
99 var $_where; // where clause set in Load()
100 var $_saved = false; // indicates whether data is already inserted.
101 var $_lasterr = false; // last error message
102 var $_original = false; // the original values loaded or inserted, refreshed on update
104 var $foreignName; // CFR: class name when in a relationship
106 static function UseDefaultValues($bool=null)
108 global $ADODB_ACTIVE_DEFVALS;
110 $ADODB_ACTIVE_DEFVALS = $bool;
112 return $ADODB_ACTIVE_DEFVALS;
116 static function SetDatabaseAdapter(&$db)
118 return ADODB_SetDatabaseAdapter($db);
122 public function __set($name, $value)
124 $name = str_replace(' ', '_', $name);
125 $this->$name = $value;
129 // Note: if $table is defined, then we will use it as our table name
130 // Otherwise we will use our classname...
131 // In our database, table names are pluralized (because there can be
132 // more than one row!)
133 // Similarly, if $table is defined here, it has to be plural form.
135 // $options is an array that allows us to tweak the constructor's behaviour
136 // if $options['refresh'] is true, we re-scan our metadata information
137 // if $options['new'] is true, we forget all relations
138 function __construct($table = false, $pkeyarr=false, $db=false, $options=array())
140 global $_ADODB_ACTIVE_DBS;
142 if ($db == false && is_object($pkeyarr)) {
148 // table argument exists. It is expected to be
149 // already plural form.
150 $this->_pTable
= $table;
151 $this->_sTable
= $this->_singularize($this->_pTable
);
154 // We will use current classname as table name.
155 // We need to pluralize it for the real table name.
156 $this->_sTable
= strtolower(get_class($this));
157 $this->_pTable
= $this->_pluralize($this->_sTable
);
159 $this->_table
= &$this->_pTable
;
161 $this->foreignName
= $this->_sTable
; // CFR: default foreign name (singular)
164 $this->_dbat
= ADODB_Active_Record
::SetDatabaseAdapter($db);
166 $this->_dbat
= sizeof($_ADODB_ACTIVE_DBS)-1;
169 if ($this->_dbat
< 0) {
171 "No database connection set; use ADOdb_Active_Record::SetDatabaseAdapter(\$db)",
172 'ADODB_Active_Record::__constructor'
176 $this->_tableat
= $this->_table
; # reserved for setting the assoc value to a non-table name, eg. the sql string in future
178 // CFR: Just added this option because UpdateActiveTable() can refresh its information
179 // but there was no way to ask it to do that.
180 $forceUpdate = (isset($options['refresh']) && true === $options['refresh']);
181 $this->UpdateActiveTable($pkeyarr, $forceUpdate);
182 if(isset($options['new']) && true === $options['new']) {
183 $table =& $this->TableInfo();
184 unset($table->_hasMany
);
185 unset($table->_belongsTo
);
186 $table->_hasMany
= array();
187 $table->_belongsTo
= array();
193 $class = get_class($this);
197 // CFR: Constants found in Rails
198 static $IrregularP = array(
199 'PERSON' => 'people',
202 'CHILD' => 'children',
206 static $IrregularS = array(
207 'PEOPLE' => 'PERSON',
210 'CHILDREN' => 'child',
214 static $WeIsI = array(
216 'INFORMATION' => true,
225 function _pluralize($table)
227 if (!ADODB_Active_Record
::$_changeNames) {
230 $ut = strtoupper($table);
231 if(isset(self
::$WeIsI[$ut])) {
234 if(isset(self
::$IrregularP[$ut])) {
235 return self
::$IrregularP[$ut];
237 $len = strlen($table);
238 $lastc = $ut[$len-1];
239 $lastc2 = substr($ut,$len-2);
244 return substr($table,0,$len-1).'ies';
248 if ($lastc2 == 'CH' ||
$lastc2 == 'SH') {
256 // CFR Lamest singular inflector ever - @todo Make it real!
257 // Note: There is an assumption here...and it is that the argument's length >= 4
258 function _singularize($table)
261 if (!ADODB_Active_Record
::$_changeNames) {
264 $ut = strtoupper($table);
265 if(isset(self
::$WeIsI[$ut])) {
268 if(isset(self
::$IrregularS[$ut])) {
269 return self
::$IrregularS[$ut];
271 $len = strlen($table);
272 if($ut[$len-1] != 'S') {
273 return $table; // I know...forget oxen
275 if($ut[$len-2] != 'E') {
276 return substr($table, 0, $len-1);
278 switch($ut[$len-3]) {
281 return substr($table, 0, $len-2);
283 return substr($table, 0, $len-3) . 'y';
285 if($ut[$len-4] == 'C' ||
$ut[$len-4] == 'S') {
286 return substr($table, 0, $len-2);
289 return substr($table, 0, $len-1); // ?
294 * ar->foreignName will contain the name of the tables associated with this table because
295 * these other tables' rows may also be referenced by this table using theirname_id or the provided
296 * foreign keys (this index name is stored in ar->foreignKey)
298 * this-table.id = other-table-#1.this-table_id
299 * = other-table-#2.this-table_id
301 function hasMany($foreignRef,$foreignKey=false)
303 $ar = new ADODB_Active_Record($foreignRef);
304 $ar->foreignName
= $foreignRef;
305 $ar->UpdateActiveTable();
306 $ar->foreignKey
= ($foreignKey) ?
$foreignKey : strtolower(get_class($this)) . self
::$_foreignSuffix;
308 $table =& $this->TableInfo();
309 if(!isset($table->_hasMany
[$foreignRef])) {
310 $table->_hasMany
[$foreignRef] = $ar;
311 $table->updateColsCount();
313 # @todo Can I make this guy be lazy?
314 $this->$foreignRef = $table->_hasMany
[$foreignRef]; // WATCHME Removed assignment by ref. to please __get()
318 * ar->foreignName will contain the name of the tables associated with this table because
319 * this table's rows may also be referenced by those tables using thistable_id or the provided
320 * foreign keys (this index name is stored in ar->foreignKey)
322 * this-table.other-table_id = other-table.id
324 function belongsTo($foreignRef,$foreignKey=false)
328 $ar = new ADODB_Active_Record($this->_pluralize($foreignRef));
329 $ar->foreignName
= $foreignRef;
330 $ar->UpdateActiveTable();
331 $ar->foreignKey
= ($foreignKey) ?
$foreignKey : $ar->foreignName
. self
::$_foreignSuffix;
333 $table =& $this->TableInfo();
334 if(!isset($table->_belongsTo
[$foreignRef])) {
335 $table->_belongsTo
[$foreignRef] = $ar;
336 $table->updateColsCount();
338 $this->$foreignRef = $table->_belongsTo
[$foreignRef];
342 * __get Access properties - used for lazy loading
348 function __get($name)
350 return $this->LoadRelations($name, '', -1. -1);
353 function LoadRelations($name, $whereOrderBy, $offset=-1, $limit=-1)
357 $extras['offset'] = $offset;
360 $extras['limit'] = $limit;
362 $table =& $this->TableInfo();
364 if (strlen($whereOrderBy)) {
365 if (!preg_match('/^[ \n\r]*AND/i',$whereOrderBy)) {
366 if (!preg_match('/^[ \n\r]*ORDER[ \n\r]/i',$whereOrderBy)) {
367 $whereOrderBy = 'AND '.$whereOrderBy;
372 if(!empty($table->_belongsTo
[$name])) {
373 $obj = $table->_belongsTo
[$name];
374 $columnName = $obj->foreignKey
;
375 if(empty($this->$columnName)) {
379 if(($k = reset($obj->TableInfo()->keys
))) {
388 $belongsToId.'='.$this->$columnName.' '.$whereOrderBy, false, false, $extras);
389 $this->$name = $arrayOfOne[0];
393 if(!empty($table->_hasMany
[$name])) {
394 $obj = $table->_hasMany
[$name];
395 if(($k = reset($table->keys
))) {
404 $obj->foreignKey
.'='.$this->$hasManyId.' '.$whereOrderBy, false, false, $extras);
408 //////////////////////////////////
411 function UpdateActiveTable($pkeys=false,$forceUpdate=false)
413 global $_ADODB_ACTIVE_DBS , $ADODB_CACHE_DIR, $ADODB_ACTIVE_CACHESECS;
414 global $ADODB_ACTIVE_DEFVALS, $ADODB_FETCH_MODE;
416 $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat
];
418 $table = $this->_table
;
419 $tables = $activedb->tables
;
420 $tableat = $this->_tableat
;
421 if (!$forceUpdate && !empty($tables[$tableat])) {
423 $tobj = $tables[$tableat];
424 foreach($tobj->flds
as $name => $fld) {
425 if ($ADODB_ACTIVE_DEFVALS && isset($fld->default_value
)) {
426 $this->$name = $fld->default_value
;
436 $fname = $ADODB_CACHE_DIR . '/adodb_' . $db->databaseType
. '_active_'. $table . '.cache';
437 if (!$forceUpdate && $ADODB_ACTIVE_CACHESECS && $ADODB_CACHE_DIR && file_exists($fname)) {
438 $fp = fopen($fname,'r');
439 @flock
($fp, LOCK_SH
);
440 $acttab = unserialize(fread($fp,100000));
442 if ($acttab->_created +
$ADODB_ACTIVE_CACHESECS - (abs(rand()) %
16) > time()) {
443 // abs(rand()) randomizes deletion, reducing contention to delete/refresh file
444 // ideally, you should cache at least 32 secs
445 $activedb->tables
[$table] = $acttab;
447 //if ($db->debug) ADOConnection::outp("Reading cached active record file: $fname");
449 } else if ($db->debug
) {
450 ADOConnection
::outp("Refreshing cached active record file: $fname");
453 $activetab = new ADODB_Active_Table();
454 $activetab->name
= $table;
456 $save = $ADODB_FETCH_MODE;
457 $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC
;
458 if ($db->fetchMode
!== false) {
459 $savem = $db->SetFetchMode(false);
462 $cols = $db->MetaColumns($table);
465 $db->SetFetchMode($savem);
467 $ADODB_FETCH_MODE = $save;
470 $this->Error("Invalid table name: $table",'UpdateActiveTable');
475 if (isset($fld->primary_key
)) {
477 foreach($cols as $name => $fld) {
478 if (!empty($fld->primary_key
)) {
483 $pkeys = $this->GetPrimaryKeys($db, $table);
487 $this->Error("No primary key found for table $table",'UpdateActiveTable');
494 switch (ADODB_ASSOC_CASE
) {
495 case ADODB_ASSOC_CASE_LOWER
:
496 foreach($cols as $name => $fldobj) {
497 $name = strtolower($name);
498 if ($ADODB_ACTIVE_DEFVALS && isset($fldobj->default_value
)) {
499 $this->$name = $fldobj->default_value
;
504 $attr[$name] = $fldobj;
506 foreach($pkeys as $k => $name) {
507 $keys[strtolower($name)] = strtolower($name);
511 case ADODB_ASSOC_CASE_UPPER
:
512 foreach($cols as $name => $fldobj) {
513 $name = strtoupper($name);
515 if ($ADODB_ACTIVE_DEFVALS && isset($fldobj->default_value
)) {
516 $this->$name = $fldobj->default_value
;
521 $attr[$name] = $fldobj;
524 foreach($pkeys as $k => $name) {
525 $keys[strtoupper($name)] = strtoupper($name);
529 foreach($cols as $name => $fldobj) {
530 $name = ($fldobj->name
);
532 if ($ADODB_ACTIVE_DEFVALS && isset($fldobj->default_value
)) {
533 $this->$name = $fldobj->default_value
;
538 $attr[$name] = $fldobj;
540 foreach($pkeys as $k => $name) {
541 $keys[$name] = $cols[$name]->name
;
546 $activetab->keys
= $keys;
547 $activetab->flds
= $attr;
548 $activetab->updateColsCount();
550 if ($ADODB_ACTIVE_CACHESECS && $ADODB_CACHE_DIR) {
551 $activetab->_created
= time();
552 $s = serialize($activetab);
553 if (!function_exists('adodb_write_file')) {
554 include(ADODB_DIR
.'/adodb-csvlib.inc.php');
556 adodb_write_file($fname,$s);
558 if (isset($activedb->tables
[$table])) {
559 $oldtab = $activedb->tables
[$table];
562 $activetab->_belongsTo
= $oldtab->_belongsTo
;
563 $activetab->_hasMany
= $oldtab->_hasMany
;
566 $activedb->tables
[$table] = $activetab;
569 function GetPrimaryKeys(&$db, $table)
571 return $db->MetaPrimaryKeys($table);
574 // error handler for both PHP4+5.
575 function Error($err,$fn)
577 global $_ADODB_ACTIVE_DBS;
579 $fn = get_class($this).'::'.$fn;
580 $this->_lasterr
= $fn.': '.$err;
582 if ($this->_dbat
< 0) {
586 $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat
];
590 if (function_exists('adodb_throw')) {
592 adodb_throw('ADOdb_Active_Record', $fn, -1, $err, 0, 0, false);
595 adodb_throw($db->databaseType
, $fn, -1, $err, 0, 0, $db);
598 if (!$db ||
$db->debug
) {
599 ADOConnection
::outp($this->_lasterr
);
605 // return last error message
608 if (!function_exists('adodb_throw')) {
609 if ($this->_dbat
< 0) {
616 // last error could be database error too
617 if ($db && $db->ErrorMsg()) {
618 return $db->ErrorMsg();
621 return $this->_lasterr
;
626 if ($this->_dbat
< 0) {
627 return -9999; // no database connection...
631 return (int) $db->ErrorNo();
635 // retrieve ADOConnection from _ADODB_Active_DBs
638 global $_ADODB_ACTIVE_DBS;
640 if ($this->_dbat
< 0) {
642 $this->Error("No database connection set: use ADOdb_Active_Record::SetDatabaseAdaptor(\$db)", "DB");
645 $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat
];
650 // retrieve ADODB_Active_Table
651 function &TableInfo()
653 global $_ADODB_ACTIVE_DBS;
655 $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat
];
656 $table = $activedb->tables
[$this->_tableat
];
661 // I have an ON INSERT trigger on a table that sets other columns in the table.
662 // So, I find that for myTable, I want to reload an active record after saving it. -- Malcolm Cook
669 $table =& $this->TableInfo();
670 $where = $this->GenWhere($db, $table);
671 return($this->Load($where));
675 // set a numeric array (using natural table field ordering) as object properties
678 global $ACTIVE_RECORD_SAFETY;
683 $this->_saved
= false;
687 $this->_saved
= true;
689 $table = $this->TableInfo();
690 $sizeofFlds = sizeof($table->flds
);
691 $sizeofRow = sizeof($row);
692 if ($ACTIVE_RECORD_SAFETY && $table->_colsCount
!= $sizeofRow && $sizeofFlds != $sizeofRow) {
695 if($sizeofRow == 2 * $table->_colsCount ||
$sizeofRow == 2 * $sizeofFlds) {
696 // Only keep string keys
697 $keys = array_filter(array_keys($row), 'is_string');
698 if (sizeof($keys) == sizeof($table->flds
)) {
703 $this->Error("Table structure of $this->_table has changed","Load");
709 $keys = array_keys($row);
714 $this->_original
= array();
715 foreach($table->flds
as $name=>$fld) {
716 $value = $row[current($keys)];
717 $this->$name = $value;
718 $this->_original
[] = $value;
723 $table =& $this->TableInfo();
724 foreach($table->_belongsTo
as $foreignTable) {
725 $ft = $foreignTable->TableInfo();
726 $propertyName = $ft->name
;
727 foreach($ft->flds
as $name=>$fld) {
728 $value = $row[current($keys)];
729 $foreignTable->$name = $value;
730 $foreignTable->_original
[] = $value;
736 foreach($table->_hasMany
as $foreignTable) {
737 $ft = $foreignTable->TableInfo();
738 foreach($ft->flds
as $name=>$fld) {
739 $value = $row[current($keys)];
740 $foreignTable->$name = $value;
741 $foreignTable->_original
[] = $value;
752 // get last inserted id for INSERT
753 function LastInsertID(&$db,$fieldname)
755 if ($db->hasInsertID
) {
756 $val = $db->Insert_ID($this->_table
,$fieldname);
762 if (is_null($val) ||
$val === false) {
763 // this might not work reliably in multi-user environment
764 return $db->GetOne("select max(".$fieldname.") from ".$this->_table
);
769 // quote data in where clause
770 function doquote(&$db, $val,$t)
783 if (strlen($val)>0 &&
784 (strncmp($val,"'",1) != 0 ||
substr($val,strlen($val)-1,1) != "'")
786 return $db->qstr($val);
795 // generate where clause for an UPDATE/SELECT
796 function GenWhere(&$db, &$table)
798 $keys = $table->keys
;
801 foreach($keys as $k) {
802 $f = $table->flds
[$k];
804 $parr[] = $k.' = '.$this->doquote($db,$this->$k,$db->MetaType($f->type
));
807 return implode(' and ', $parr);
811 //------------------------------------------------------------ Public functions below
813 function Load($where=null,$bindarr=false)
819 $this->_where
= $where;
821 $save = $db->SetFetchMode(ADODB_FETCH_NUM
);
822 $qry = "select * from ".$this->_table
;
823 $table =& $this->TableInfo();
825 if(($k = reset($table->keys
))) {
832 foreach($table->_belongsTo
as $foreignTable) {
833 if(($k = reset($foreignTable->TableInfo()->keys
))) {
839 $qry .= ' LEFT JOIN '.$foreignTable->_table
.' ON '.
840 $this->_table
.'.'.$foreignTable->foreignKey
.'='.
841 $foreignTable->_table
.'.'.$belongsToId;
843 foreach($table->_hasMany
as $foreignTable)
845 $qry .= ' LEFT JOIN '.$foreignTable->_table
.' ON '.
846 $this->_table
.'.'.$hasManyId.'='.
847 $foreignTable->_table
.'.'.$foreignTable->foreignKey
;
850 $qry .= ' WHERE '.$where;
853 // Simple case: no relations. Load row and return.
854 if((count($table->_hasMany
) +
count($table->_belongsTo
)) < 1) {
855 $row = $db->GetRow($qry,$bindarr);
859 $db->SetFetchMode($save);
860 return $this->Set($row);
863 // More complex case when relations have to be collated
864 $rows = $db->GetAll($qry,$bindarr);
868 $db->SetFetchMode($save);
869 if(count($rows) < 1) {
872 $class = get_class($this);
875 if(($k = reset($this->TableInfo()->keys
))) {
881 $index = 0; $found = false;
882 /** @todo Improve by storing once and for all in table metadata */
883 /** @todo Also re-use info for hasManyId */
884 foreach($this->TableInfo()->flds
as $fld) {
885 if($fld->name
== $myId) {
892 $this->outp_throw("Unable to locate key $myId for $class in Load()",'Load');
895 foreach($rows as $row) {
896 $rowId = intval($row[$index]);
900 if(!$this->Set($row)) {
904 $obj = new $class($table,false,$db);
906 // TODO Copy/paste code below: bad!
907 if(count($table->_hasMany
) > 0) {
908 foreach($table->_hasMany
as $foreignTable) {
909 $foreignName = $foreignTable->foreignName
;
910 if(!empty($obj->$foreignName)) {
911 if(!is_array($this->$foreignName)) {
912 $foreignObj = $this->$foreignName;
913 $this->$foreignName = array(clone($foreignObj));
916 $foreignObj = $obj->$foreignName;
917 array_push($this->$foreignName, clone($foreignObj));
922 if(count($table->_belongsTo
) > 0) {
923 foreach($table->_belongsTo
as $foreignTable) {
924 $foreignName = $foreignTable->foreignName
;
925 if(!empty($obj->$foreignName)) {
926 if(!is_array($this->$foreignName)) {
927 $foreignObj = $this->$foreignName;
928 $this->$foreignName = array(clone($foreignObj));
931 $foreignObj = $obj->$foreignName;
932 array_push($this->$foreignName, clone($foreignObj));
946 $ok = $this->Update();
949 $ok = $this->Insert();
955 // CFR: Sometimes we may wish to consider that an object is not to be replaced but inserted.
956 // Sample use case: an 'undo' command object (after a delete())
959 $this->_saved
= false;
970 $table = $this->TableInfo();
976 foreach($table->flds
as $name=>$fld) {
978 if(!is_null($val) ||
!array_key_exists($name, $table->keys
)) {
981 $valstr[] = $db->Param($cnt);
987 foreach($table->flds
as $name=>$fld) {
990 $valstr[] = $db->Param($cnt);
994 $sql = 'INSERT INTO '.$this->_table
."(".implode(',',$names).') VALUES ('.implode(',',$valstr).')';
995 $ok = $db->Execute($sql,$valarr);
998 $this->_saved
= true;
1000 foreach($table->keys
as $k) {
1001 if (is_null($this->$k)) {
1006 if ($autoinc && sizeof($table->keys
) == 1) {
1007 $k = reset($table->keys
);
1008 $this->$k = $this->LastInsertID($db,$k);
1012 $this->_original
= $valarr;
1022 $table = $this->TableInfo();
1024 $where = $this->GenWhere($db,$table);
1025 $sql = 'DELETE FROM '.$this->_table
.' WHERE '.$where;
1026 $ok = $db->Execute($sql);
1028 return $ok ?
true : false;
1031 // returns an array of active record objects
1032 function Find($whereOrderBy,$bindarr=false,$pkeysArr=false,$extra=array())
1035 if (!$db ||
empty($this->_table
)) {
1038 $table =& $this->TableInfo();
1039 $arr = $db->GetActiveRecordsClass(get_class($this),$this, $whereOrderBy,$bindarr,$pkeysArr,$extra,
1040 array('foreignName'=>$this->foreignName
, 'belongsTo'=>$table->_belongsTo
, 'hasMany'=>$table->_hasMany
));
1044 // CFR: In introduced this method to ensure that inner workings are not disturbed by
1045 // subclasses...for instance when GetActiveRecordsClass invokes Find()
1046 // Why am I not invoking parent::Find?
1047 // Shockingly because I want to preserve PHP4 compatibility.
1048 function packageFind($whereOrderBy,$bindarr=false,$pkeysArr=false,$extra=array())
1051 if (!$db ||
empty($this->_table
)) {
1054 $table =& $this->TableInfo();
1055 $arr = $db->GetActiveRecordsClass(get_class($this),$this, $whereOrderBy,$bindarr,$pkeysArr,$extra,
1056 array('foreignName'=>$this->foreignName
, 'belongsTo'=>$table->_belongsTo
, 'hasMany'=>$table->_hasMany
));
1060 // returns 0 on error, 1 on update, 2 on insert
1067 $table = $this->TableInfo();
1069 $pkey = $table->keys
;
1071 foreach($table->flds
as $name=>$fld) {
1072 $val = $this->$name;
1074 if (is_null($val)) {
1075 if (isset($fld->not_null) && $fld->not_null) {
1076 if (isset($fld->default_value) && strlen($fld->default_value)) {
1080 $this->Error("Cannot update null into $name","Replace");
1085 if (is_null($val) && !empty($fld->auto_increment
)) {
1088 $t = $db->MetaType($fld->type
);
1089 $arr[$name] = $this->doquote($db,$val,$t);
1093 if (!is_array($pkey)) {
1094 $pkey = array($pkey);
1098 switch (ADODB_ASSOC_CASE
) {
1099 case ADODB_ASSOC_CASE_LOWER
:
1100 foreach($pkey as $k => $v) {
1101 $pkey[$k] = strtolower($v);
1104 case ADODB_ASSOC_CASE_UPPER
:
1105 foreach($pkey as $k => $v) {
1106 $pkey[$k] = strtoupper($v);
1111 $ok = $db->Replace($this->_table
,$arr,$pkey);
1113 $this->_saved
= true; // 1= update 2=insert
1116 foreach($table->keys
as $k) {
1117 if (is_null($this->$k)) {
1122 if ($autoinc && sizeof($table->keys
) == 1) {
1123 $k = reset($table->keys
);
1124 $this->$k = $this->LastInsertID($db,$k);
1128 $this->_original
= $valarr;
1133 // returns 0 on error, 1 on update, -1 if no change in data (no update)
1140 $table = $this->TableInfo();
1142 $where = $this->GenWhere($db, $table);
1145 $this->error("Where missing for table $table", "Update");
1153 foreach($table->flds
as $name=>$fld) {
1155 $val = $this->$name;
1158 if (isset($table->keys
[$name])) {
1162 if (is_null($val)) {
1163 if (isset($fld->not_null
) && $fld->not_null
) {
1164 if (isset($fld->default_value
) && strlen($fld->default_value
)) {
1168 $this->Error("Cannot set field $name to NULL","Update");
1174 if (isset($this->_original
[$i]) && $val === $this->_original
[$i]) {
1178 $pairs[] = $name.'='.$db->Param($cnt);
1186 $sql = 'UPDATE '.$this->_table
." SET ".implode(",",$pairs)." WHERE ".$where;
1187 $ok = $db->Execute($sql,$valarr);
1189 $this->_original
= $neworig;
1195 function GetAttributeNames()
1197 $table = $this->TableInfo();
1201 return array_keys($table->flds
);
1206 function adodb_GetActiveRecordsClass(&$db, $class, $tableObj,$whereOrderBy,$bindarr, $primkeyArr,
1209 global $_ADODB_ACTIVE_DBS;
1211 if (empty($extra['loading'])) {
1212 $extra['loading'] = ADODB_LAZY_AR
;
1214 $save = $db->SetFetchMode(ADODB_FETCH_NUM
);
1215 $table = &$tableObj->_table
;
1216 $tableInfo =& $tableObj->TableInfo();
1217 if(($k = reset($tableInfo->keys
))) {
1223 $index = 0; $found = false;
1224 /** @todo Improve by storing once and for all in table metadata */
1225 /** @todo Also re-use info for hasManyId */
1226 foreach($tableInfo->flds
as $fld)
1228 if($fld->name
== $myId) {
1235 $db->outp_throw("Unable to locate key $myId for $class in GetActiveRecordsClass()",'GetActiveRecordsClass');
1238 $qry = "select * from ".$table;
1239 if(ADODB_JOIN_AR
== $extra['loading']) {
1240 if(!empty($relations['belongsTo'])) {
1241 foreach($relations['belongsTo'] as $foreignTable) {
1242 if(($k = reset($foreignTable->TableInfo()->keys
))) {
1246 $belongsToId = 'id';
1249 $qry .= ' LEFT JOIN '.$foreignTable->_table
.' ON '.
1250 $table.'.'.$foreignTable->foreignKey
.'='.
1251 $foreignTable->_table
.'.'.$belongsToId;
1254 if(!empty($relations['hasMany'])) {
1255 if(empty($relations['foreignName'])) {
1256 $db->outp_throw("Missing foreignName is relation specification in GetActiveRecordsClass()",'GetActiveRecordsClass');
1258 if(($k = reset($tableInfo->keys
))) {
1265 foreach($relations['hasMany'] as $foreignTable) {
1266 $qry .= ' LEFT JOIN '.$foreignTable->_table
.' ON '.
1267 $table.'.'.$hasManyId.'='.
1268 $foreignTable->_table
.'.'.$foreignTable->foreignKey
;
1272 if (!empty($whereOrderBy)) {
1273 $qry .= ' WHERE '.$whereOrderBy;
1275 if(isset($extra['limit'])) {
1277 if(isset($extra['offset'])) {
1278 $rs = $db->SelectLimit($qry, $extra['limit'], $extra['offset']);
1280 $rs = $db->SelectLimit($qry, $extra['limit']);
1284 $rows[] = $rs->fields
;
1289 $rows = $db->GetAll($qry,$bindarr);
1291 $db->SetFetchMode($save);
1295 if ($rows === false) {
1300 if (!isset($_ADODB_ACTIVE_DBS)) {
1301 include(ADODB_DIR
.'/adodb-active-record.inc.php');
1303 if (!class_exists($class)) {
1304 $db->outp_throw("Unknown class $class in GetActiveRecordsClass()",'GetActiveRecordsClass');
1307 $uniqArr = array(); // CFR Keep track of records for relations
1309 // arrRef will be the structure that knows about our objects.
1310 // It is an associative array.
1311 // We will, however, return arr, preserving regular 0.. order so that
1312 // obj[0] can be used by app developpers.
1314 $bTos = array(); // Will store belongTo's indices if any
1315 foreach($rows as $row) {
1317 $obj = new $class($table,$primkeyArr,$db);
1318 if ($obj->ErrorNo()){
1319 $db->_errorMsg
= $obj->ErrorMsg();
1323 // CFR: FIXME: Insane assumption here:
1324 // If the first column returned is an integer, then it's a 'id' field
1325 // And to make things a bit worse, I use intval() rather than is_int() because, in fact,
1326 // $row[0] is not an integer.
1328 // So, what does this whole block do?
1329 // When relationships are found, we perform JOINs. This is fast. But not accurate:
1330 // instead of returning n objects with their n' associated cousins,
1331 // we get n*n' objects. This code fixes this.
1332 // Note: to-many relationships mess around with the 'limit' parameter
1333 $rowId = intval($row[$index]);
1335 if(ADODB_WORK_AR
== $extra['loading']) {
1336 $arrRef[$rowId] = $obj;
1337 $arr[] = &$arrRef[$rowId];
1338 if(!isset($indices)) {
1342 $indices .= ','.$rowId;
1344 if(!empty($relations['belongsTo'])) {
1345 foreach($relations['belongsTo'] as $foreignTable) {
1346 $foreignTableRef = $foreignTable->foreignKey
;
1347 // First array: list of foreign ids we are looking for
1348 if(empty($bTos[$foreignTableRef])) {
1349 $bTos[$foreignTableRef] = array();
1351 // Second array: list of ids found
1352 if(empty($obj->$foreignTableRef)) {
1355 if(empty($bTos[$foreignTableRef][$obj->$foreignTableRef])) {
1356 $bTos[$foreignTableRef][$obj->$foreignTableRef] = array();
1358 $bTos[$foreignTableRef][$obj->$foreignTableRef][] = $obj;
1365 if(ADODB_JOIN_AR
== $extra['loading']) {
1366 $isNewObj = !isset($uniqArr['_'.$row[0]]);
1368 $uniqArr['_'.$row[0]] = $obj;
1371 // TODO Copy/paste code below: bad!
1372 if(!empty($relations['hasMany'])) {
1373 foreach($relations['hasMany'] as $foreignTable) {
1374 $foreignName = $foreignTable->foreignName
;
1375 if(!empty($obj->$foreignName)) {
1376 $masterObj = &$uniqArr['_'.$row[0]];
1377 // Assumption: this property exists in every object since they are instances of the same class
1378 if(!is_array($masterObj->$foreignName)) {
1380 $foreignObj = $masterObj->$foreignName;
1381 $masterObj->$foreignName = array(clone($foreignObj));
1385 $foreignObj = $obj->$foreignName;
1386 array_push($masterObj->$foreignName, clone($foreignObj));
1391 if(!empty($relations['belongsTo'])) {
1392 foreach($relations['belongsTo'] as $foreignTable) {
1393 $foreignName = $foreignTable->foreignName
;
1394 if(!empty($obj->$foreignName)) {
1395 $masterObj = &$uniqArr['_'.$row[0]];
1396 // Assumption: this property exists in every object since they are instances of the same class
1397 if(!is_array($masterObj->$foreignName)) {
1399 $foreignObj = $masterObj->$foreignName;
1400 $masterObj->$foreignName = array(clone($foreignObj));
1404 $foreignObj = $obj->$foreignName;
1405 array_push($masterObj->$foreignName, clone($foreignObj));
1411 unset($obj); // We do not need this object itself anymore and do not want it re-added to the main array
1414 else if(ADODB_LAZY_AR
== $extra['loading']) {
1415 // Lazy loading: we need to give AdoDb a hint that we have not really loaded
1416 // anything, all the while keeping enough information on what we wish to load.
1417 // Let's do this by keeping the relevant info in our relationship arrays
1418 // but get rid of the actual properties.
1419 // We will then use PHP's __get to load these properties on-demand.
1420 if(!empty($relations['hasMany'])) {
1421 foreach($relations['hasMany'] as $foreignTable) {
1422 $foreignName = $foreignTable->foreignName
;
1423 if(!empty($obj->$foreignName)) {
1424 unset($obj->$foreignName);
1428 if(!empty($relations['belongsTo'])) {
1429 foreach($relations['belongsTo'] as $foreignTable) {
1430 $foreignName = $foreignTable->foreignName
;
1431 if(!empty($obj->$foreignName)) {
1432 unset($obj->$foreignName);
1444 if(ADODB_WORK_AR
== $extra['loading']) {
1445 // The best of both worlds?
1446 // Here, the number of queries is constant: 1 + n*relationship.
1447 // The second query will allow us to perform a good join
1448 // while preserving LIMIT etc.
1449 if(!empty($relations['hasMany'])) {
1450 foreach($relations['hasMany'] as $foreignTable) {
1451 $foreignName = $foreignTable->foreignName
;
1452 $className = ucfirst($foreignTable->_singularize($foreignName));
1453 $obj = new $className();
1454 $dbClassRef = $foreignTable->foreignKey
;
1455 $objs = $obj->packageFind($dbClassRef.' IN ('.$indices.')');
1456 foreach($objs as $obj) {
1457 if(!is_array($arrRef[$obj->$dbClassRef]->$foreignName)) {
1458 $arrRef[$obj->$dbClassRef]->$foreignName = array();
1460 array_push($arrRef[$obj->$dbClassRef]->$foreignName, $obj);
1465 if(!empty($relations['belongsTo'])) {
1466 foreach($relations['belongsTo'] as $foreignTable) {
1467 $foreignTableRef = $foreignTable->foreignKey
;
1468 if(empty($bTos[$foreignTableRef])) {
1471 if(($k = reset($foreignTable->TableInfo()->keys
))) {
1475 $belongsToId = 'id';
1477 $origObjsArr = $bTos[$foreignTableRef];
1478 $bTosString = implode(',', array_keys($bTos[$foreignTableRef]));
1479 $foreignName = $foreignTable->foreignName
;
1480 $className = ucfirst($foreignTable->_singularize($foreignName));
1481 $obj = new $className();
1482 $objs = $obj->packageFind($belongsToId.' IN ('.$bTosString.')');
1483 foreach($objs as $obj)
1485 foreach($origObjsArr[$obj->$belongsToId] as $idx=>$origObj)
1487 $origObj->$foreignName = $obj;