4 @version V4.92a 29 Aug 2006 (c) 2000-2006 John Lim (jlim#natsoft.com.my). All rights reserved.
5 Latest version is available at http://adodb.sourceforge.net
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.
11 Active Record implementation. Superset of Zend Framework's.
15 See http://www-128.ibm.com/developerworks/java/library/j-cb03076/?ca=dgr-lnxw01ActiveRecord
16 for info on Ruby on Rails Active Record implementation
19 global $_ADODB_ACTIVE_DBS;
20 global $ADODB_ACTIVE_CACHESECS; // set to true to enable caching of metadata such as field info
22 // array of ADODB_Active_DB's, indexed by ADODB_Active_Record->_dbat
23 $_ADODB_ACTIVE_DBS = array();
26 class ADODB_Active_DB
{
27 var $db; // ADOConnection
28 var $tables; // assoc array of ADODB_Active_Table objects, indexed by tablename
31 class ADODB_Active_Table
{
32 var $name; // table name
33 var $flds; // assoc array of adofieldobjs, indexed by fieldname
34 var $keys; // assoc array of primary keys, indexed by fieldname
35 var $_created; // only used when stored as a cached file
38 // returns index into $_ADODB_ACTIVE_DBS
39 function ADODB_SetDatabaseAdapter(&$db)
41 global $_ADODB_ACTIVE_DBS;
43 foreach($_ADODB_ACTIVE_DBS as $k => $d) {
44 if ($d->db
== $db) return $k;
47 $obj = new ADODB_Active_DB();
49 $obj->tables
= array();
51 $_ADODB_ACTIVE_DBS[] = $obj;
53 return sizeof($_ADODB_ACTIVE_DBS)-1;
57 class ADODB_Active_Record
{
58 var $_dbat; // associative index pointing to ADODB_Active_DB eg. $ADODB_Active_DBS[_dbat]
59 var $_table; // tablename, if set in class definition then use it as table name
60 var $_tableat; // associative index pointing to ADODB_Active_Table, eg $ADODB_Active_DBS[_dbat]->tables[$this->_tableat]
61 var $_where; // where clause set in Load()
62 var $_saved = false; // indicates whether data is already inserted.
63 var $_lasterr = false; // last error message
64 var $_original = false; // the original values loaded or inserted, refreshed on update
67 function SetDatabaseAdapter(&$db)
69 return ADODB_SetDatabaseAdapter($db);
73 function ADODB_Active_Record($table = false, $pkeyarr=false, $db=false)
75 ADODB_Active_Record
::__construct($table,$pkeyarr,$db);
79 function __construct($table = false, $pkeyarr=false, $db=false)
81 global $ADODB_ASSOC_CASE,$_ADODB_ACTIVE_DBS;
83 if ($db == false && is_object($pkeyarr)) {
89 if (!empty($this->_table
)) $table = $this->_table
;
90 else $table = $this->_pluralize(get_class($this));
93 $this->_dbat
= ADODB_Active_Record
::SetDatabaseAdapter($db);
95 $this->_dbat
= sizeof($_ADODB_ACTIVE_DBS)-1;
98 if ($this->_dbat
< 0) $this->Error("No database connection set; use ADOdb_Active_Record::SetDatabaseAdapter(\$db)",'ADODB_Active_Record::__constructor');
100 $this->_table
= $table;
101 $this->_tableat
= $table; # reserved for setting the assoc value to a non-table name, eg. the sql string in future
102 $this->UpdateActiveTable($pkeyarr);
107 $class = get_class($this);
111 function _pluralize($table)
113 $ut = strtoupper($table);
114 $len = strlen($table);
115 $lastc = $ut[$len-1];
116 $lastc2 = substr($ut,$len-2);
121 return substr($table,0,$len-1).'ies';
125 if ($lastc2 == 'CH' ||
$lastc2 == 'SH')
132 //////////////////////////////////
135 function UpdateActiveTable($pkeys=false,$forceUpdate=false)
137 global $ADODB_ASSOC_CASE,$_ADODB_ACTIVE_DBS , $ADODB_CACHE_DIR, $ADODB_ACTIVE_CACHESECS;
139 $activedb =& $_ADODB_ACTIVE_DBS[$this->_dbat
];
141 $table = $this->_table
;
142 $tables = $activedb->tables
;
143 $tableat = $this->_tableat
;
144 if (!$forceUpdate && !empty($tables[$tableat])) {
145 $tobj =& $tables[$tableat];
146 foreach($tobj->flds
as $name => $fld)
151 $db =& $activedb->db
;
152 $fname = $ADODB_CACHE_DIR . '/adodb_' . $db->databaseType
. '_active_'. $table . '.cache';
153 if (!$forceUpdate && $ADODB_ACTIVE_CACHESECS && $ADODB_CACHE_DIR && file_exists($fname)) {
154 $fp = fopen($fname,'r');
155 @flock
($fp, LOCK_SH
);
156 $acttab = unserialize(fread($fp,100000));
158 if ($acttab->_created +
$ADODB_ACTIVE_CACHESECS - (abs(rand()) %
16) > time()) {
159 // abs(rand()) randomizes deletion, reducing contention to delete/refresh file
160 // ideally, you should cache at least 32 secs
161 $activedb->tables
[$table] = $acttab;
163 //if ($db->debug) ADOConnection::outp("Reading cached active record file: $fname");
165 } else if ($db->debug
) {
166 ADOConnection
::outp("Refreshing cached active record file: $fname");
169 $activetab = new ADODB_Active_Table();
170 $activetab->name
= $table;
173 $cols = $db->MetaColumns($table);
175 $this->Error("Invalid table name: $table",'UpdateActiveTable');
180 if (isset($fld->primary_key
)) {
182 foreach($cols as $name => $fld) {
183 if (!empty($fld->primary_key
)) $pkeys[] = $name;
186 $pkeys = $this->GetPrimaryKeys($db, $table);
189 $this->Error("No primary key found for table $table",'UpdateActiveTable');
196 switch($ADODB_ASSOC_CASE) {
198 foreach($cols as $name => $fldobj) {
199 $name = strtolower($name);
201 $attr[$name] = $fldobj;
203 foreach($pkeys as $k => $name) {
204 $keys[strtolower($name)] = strtolower($name);
209 foreach($cols as $name => $fldobj) {
210 $name = strtoupper($name);
212 $attr[$name] = $fldobj;
215 foreach($pkeys as $k => $name) {
216 $keys[strtoupper($name)] = strtoupper($name);
220 foreach($cols as $name => $fldobj) {
223 $attr[$name] = $fldobj;
225 foreach($pkeys as $k => $name) {
226 $keys[$name] = ($name);
231 $activetab->keys
= $keys;
232 $activetab->flds
= $attr;
234 if ($ADODB_ACTIVE_CACHESECS && $ADODB_CACHE_DIR) {
235 $activetab->_created
= time();
236 $s = serialize($activetab);
237 if (!function_exists('adodb_write_file')) include(ADODB_DIR
.'/adodb-csvlib.inc.php');
238 adodb_write_file($fname,$s);
240 $activedb->tables
[$table] = $activetab;
243 function GetPrimaryKeys(&$db, $table)
245 return $db->MetaPrimaryKeys($table);
248 // error handler for both PHP4+5.
249 function Error($err,$fn)
251 global $_ADODB_ACTIVE_DBS;
253 $fn = get_class($this).'::'.$fn;
254 $this->_lasterr
= $fn.': '.$err;
256 if ($this->_dbat
< 0) $db = false;
258 $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat
];
259 $db =& $activedb->db
;
262 if (function_exists('adodb_throw')) {
263 if (!$db) adodb_throw('ADOdb_Active_Record', $fn, -1, $err, 0, 0, false);
264 else adodb_throw($db->databaseType
, $fn, -1, $err, 0, 0, $db);
266 if (!$db ||
$db->debug
) ADOConnection
::outp($this->_lasterr
);
270 // return last error message
273 if (!function_exists('adodb_throw')) {
274 if ($this->_dbat
< 0) $db = false;
275 else $db = $this->DB();
277 // last error could be database error too
278 if ($db && $db->ErrorMsg()) return $db->ErrorMsg();
280 return $this->_lasterr
;
283 // retrieve ADOConnection from _ADODB_Active_DBs
286 global $_ADODB_ACTIVE_DBS;
288 if ($this->_dbat
< 0) {
290 $this->Error("No database connection set: use ADOdb_Active_Record::SetDatabaseAdaptor(\$db)", "DB");
293 $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat
];
294 $db =& $activedb->db
;
298 // retrieve ADODB_Active_Table
299 function &TableInfo()
301 global $_ADODB_ACTIVE_DBS;
303 $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat
];
304 $table =& $activedb->tables
[$this->_tableat
];
308 // set a numeric array (using natural table field ordering) as object properties
314 $this->_saved
= false;
318 $this->_saved
= true;
320 $table =& $this->TableInfo();
321 if (sizeof($table->flds
) != sizeof($row)) {
322 $this->Error("Table structure of $this->_table has changed","Load");
327 foreach($table->flds
as $name=>$fld) {
328 $this->$name = $row[$cnt];
331 $this->_original
= $row;
335 // get last inserted id for INSERT
336 function LastInsertID(&$db,$fieldname)
338 if ($db->hasInsertID
)
339 $val = $db->Insert_ID($this->_table
,$fieldname);
343 if (is_null($val) ||
$val === false) {
344 // this might not work reliably in multi-user environment
345 return $db->GetOne("select max(".$fieldname.") from ".$this->_table
);
350 // quote data in where clause
351 function doquote(&$db, $val,$t)
356 if (empty($val)) return 'null';
360 if (is_null($val)) return 'null';
362 if (strncmp($val,"'",1) != 0 && substr($val,strlen($val)-1,1) != "'") {
363 return $db->qstr($val);
372 // generate where clause for an UPDATE/SELECT
373 function GenWhere(&$db, &$table)
375 $keys = $table->keys
;
378 foreach($keys as $k) {
379 $f = $table->flds
[$k];
381 $parr[] = $k.' = '.$this->doquote($db,$this->$k,$db->MetaType($f->type
));
384 return implode(' and ', $parr);
388 //------------------------------------------------------------ Public functions below
390 function Load($where,$bindarr=false)
392 $db =& $this->DB(); if (!$db) return false;
393 $this->_where
= $where;
395 $save = $db->SetFetchMode(ADODB_FETCH_NUM
);
396 $row = $db->GetRow("select * from ".$this->_table
.' WHERE '.$where,$bindarr);
397 $db->SetFetchMode($save);
399 return $this->Set($row);
405 if ($this->_saved
) $ok = $this->Update();
406 else $ok = $this->Insert();
414 $db =& $this->DB(); if (!$db) return false;
416 $table =& $this->TableInfo();
422 foreach($table->flds
as $name=>$fld) {
424 if(!is_null($val) ||
!array_key_exists($name, $table->keys
)) {
427 $valstr[] = $db->Param($cnt);
433 foreach($table->flds
as $name=>$fld) {
436 $valstr[] = $db->Param($cnt);
440 $sql = 'INSERT INTO '.$this->_table
."(".implode(',',$names).') VALUES ('.implode(',',$valstr).')';
441 $ok = $db->Execute($sql,$valarr);
444 $this->_saved
= true;
446 foreach($table->keys
as $k) {
447 if (is_null($this->$k)) {
452 if ($autoinc && sizeof($table->keys
) == 1) {
453 $k = reset($table->keys
);
454 $this->$k = $this->LastInsertID($db,$k);
458 $this->_original
= $valarr;
464 $db =& $this->DB(); if (!$db) return false;
465 $table =& $this->TableInfo();
467 $where = $this->GenWhere($db,$table);
468 $sql = 'DELETE FROM '.$this->_table
.' WHERE '.$where;
469 $ok = $db->Execute($sql);
471 return $ok ?
true : false;
474 // returns an array of active record objects
475 function &Find($whereOrderBy,$bindarr=false,$pkeysArr=false)
477 $db =& $this->DB(); if (!$db ||
empty($this->_table
)) return false;
478 $arr =& $db->GetActiveRecordsClass(get_class($this),$this->_table
, $whereOrderBy,$bindarr,$pkeysArr);
482 // returns 0 on error, 1 on update, 2 on insert
485 global $ADODB_ASSOC_CASE;
487 $db =& $this->DB(); if (!$db) return false;
488 $table =& $this->TableInfo();
490 $pkey = $table->keys
;
492 foreach($table->flds
as $name=>$fld) {
496 if (isset($fld->not_null) && $fld->not_null) {
497 if (isset($fld->default_value) && strlen($fld->default_value)) continue;
499 $this->Error("Cannot update null into $name","Replace");
504 if (is_null($val) && !empty($fld->auto_increment
)) {
507 $t = $db->MetaType($fld->type
);
508 $arr[$name] = $this->doquote($db,$val,$t);
512 if (!is_array($pkey)) $pkey = array($pkey);
515 if ($ADODB_ASSOC_CASE == 0)
516 foreach($pkey as $k => $v)
517 $pkey[$k] = strtolower($v);
518 elseif ($ADODB_ASSOC_CASE == 0)
519 foreach($pkey as $k => $v)
520 $pkey[$k] = strtoupper($v);
522 $ok = $db->Replace($this->_table
,$arr,$pkey);
524 $this->_saved
= true; // 1= update 2=insert
527 foreach($table->keys
as $k) {
528 if (is_null($this->$k)) {
533 if ($autoinc && sizeof($table->keys
) == 1) {
534 $k = reset($table->keys
);
535 $this->$k = $this->LastInsertID($db,$k);
539 $this->_original
=& $valarr;
544 // returns 0 on error, 1 on update, -1 if no change in data (no update)
547 $db =& $this->DB(); if (!$db) return false;
548 $table =& $this->TableInfo();
550 $where = $this->GenWhere($db, $table);
553 $this->error("Where missing for table $table", "Update");
561 foreach($table->flds
as $name=>$fld) {
566 if (isset($table->keys
[$name])) {
571 if (isset($fld->not_null
) && $fld->not_null
) {
572 if (isset($fld->default_value
) && strlen($fld->default_value
)) continue;
574 $this->Error("Cannot set field $name to NULL","Update");
580 if (isset($this->_original
[$i]) && $val == $this->_original
[$i]) {
584 $pairs[] = $name.'='.$db->Param($cnt);
589 if (!$cnt) return -1;
590 $sql = 'UPDATE '.$this->_table
." SET ".implode(",",$pairs)." WHERE ".$where;
591 $ok = $db->Execute($sql,$valarr);
593 $this->_original
=& $neworig;
599 function GetAttributeNames()
601 $table =& $this->TableInfo();
602 if (!$table) return false;
603 return array_keys($table->flds
);