4 @version V4.98 13 Feb 2008 (c) 2000-2008 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
21 global $ACTIVE_RECORD_SAFETY; // set to false to disable safety checks
22 global $ADODB_ACTIVE_DEFVALS; // use default values of table definition when creating new active record.
24 // array of ADODB_Active_DB's, indexed by ADODB_Active_Record->_dbat
25 $_ADODB_ACTIVE_DBS = array();
26 $ACTIVE_RECORD_SAFETY = true;
27 $ADODB_ACTIVE_DEFVALS = false;
29 class ADODB_Active_DB
{
30 var $db; // ADOConnection
31 var $tables; // assoc array of ADODB_Active_Table objects, indexed by tablename
34 class ADODB_Active_Table
{
35 var $name; // table name
36 var $flds; // assoc array of adofieldobjs, indexed by fieldname
37 var $keys; // assoc array of primary keys, indexed by fieldname
38 var $_created; // only used when stored as a cached file
41 // returns index into $_ADODB_ACTIVE_DBS
42 function ADODB_SetDatabaseAdapter(&$db)
44 global $_ADODB_ACTIVE_DBS;
46 foreach($_ADODB_ACTIVE_DBS as $k => $d) {
47 if (PHP_VERSION
>= 5) {
48 if ($d->db
=== $db) return $k;
50 if ($d->db
->_connectionID
=== $db->_connectionID
&& $db->database
== $d->db
->database
)
55 $obj = new ADODB_Active_DB();
57 $obj->tables
= array();
59 $_ADODB_ACTIVE_DBS[] = $obj;
61 return sizeof($_ADODB_ACTIVE_DBS)-1;
65 class ADODB_Active_Record
{
66 var $_dbat; // associative index pointing to ADODB_Active_DB eg. $ADODB_Active_DBS[_dbat]
67 var $_table; // tablename, if set in class definition then use it as table name
68 var $_tableat; // associative index pointing to ADODB_Active_Table, eg $ADODB_Active_DBS[_dbat]->tables[$this->_tableat]
69 var $_where; // where clause set in Load()
70 var $_saved = false; // indicates whether data is already inserted.
71 var $_lasterr = false; // last error message
72 var $_original = false; // the original values loaded or inserted, refreshed on update
75 function UseDefaultValues($bool=null)
77 global $ADODB_ACTIVE_DEFVALS;
78 if (isset($bool)) $ADODB_ACTIVE_DEFVALS = $bool;
79 return $ADODB_ACTIVE_DEFVALS;
83 function SetDatabaseAdapter(&$db)
85 return ADODB_SetDatabaseAdapter($db);
89 function ADODB_Active_Record($table = false, $pkeyarr=false, $db=false)
91 ADODB_Active_Record
::__construct($table,$pkeyarr,$db);
95 function __construct($table = false, $pkeyarr=false, $db=false)
97 global $ADODB_ASSOC_CASE,$_ADODB_ACTIVE_DBS;
99 if ($db == false && is_object($pkeyarr)) {
105 if (!empty($this->_table
)) $table = $this->_table
;
106 else $table = $this->_pluralize(get_class($this));
109 $this->_dbat
= ADODB_Active_Record
::SetDatabaseAdapter($db);
111 $this->_dbat
= sizeof($_ADODB_ACTIVE_DBS)-1;
114 if ($this->_dbat
< 0) $this->Error("No database connection set; use ADOdb_Active_Record::SetDatabaseAdapter(\$db)",'ADODB_Active_Record::__constructor');
116 $this->_table
= $table;
117 $this->_tableat
= $table; # reserved for setting the assoc value to a non-table name, eg. the sql string in future
118 $this->UpdateActiveTable($pkeyarr);
123 $class = get_class($this);
127 function _pluralize($table)
129 $ut = strtoupper($table);
130 $len = strlen($table);
131 $lastc = $ut[$len-1];
132 $lastc2 = substr($ut,$len-2);
137 return substr($table,0,$len-1).'ies';
141 if ($lastc2 == 'CH' ||
$lastc2 == 'SH')
148 //////////////////////////////////
151 function UpdateActiveTable($pkeys=false,$forceUpdate=false)
153 global $ADODB_ASSOC_CASE,$_ADODB_ACTIVE_DBS , $ADODB_CACHE_DIR, $ADODB_ACTIVE_CACHESECS;
154 global $ADODB_ACTIVE_DEFVALS;
156 $activedb =& $_ADODB_ACTIVE_DBS[$this->_dbat
];
158 $table = $this->_table
;
159 $tables = $activedb->tables
;
160 $tableat = $this->_tableat
;
161 if (!$forceUpdate && !empty($tables[$tableat])) {
162 $tobj =& $tables[$tableat];
163 foreach($tobj->flds
as $name => $fld) {
164 if ($ADODB_ACTIVE_DEFVALS && isset($fld->default_value
))
165 $this->$name = $fld->default_value
;
172 $db =& $activedb->db
;
173 $fname = $ADODB_CACHE_DIR . '/adodb_' . $db->databaseType
. '_active_'. $table . '.cache';
174 if (!$forceUpdate && $ADODB_ACTIVE_CACHESECS && $ADODB_CACHE_DIR && file_exists($fname)) {
175 $fp = fopen($fname,'r');
176 @flock
($fp, LOCK_SH
);
177 $acttab = unserialize(fread($fp,100000));
179 if ($acttab->_created +
$ADODB_ACTIVE_CACHESECS - (abs(rand()) %
16) > time()) {
180 // abs(rand()) randomizes deletion, reducing contention to delete/refresh file
181 // ideally, you should cache at least 32 secs
182 $activedb->tables
[$table] = $acttab;
184 //if ($db->debug) ADOConnection::outp("Reading cached active record file: $fname");
186 } else if ($db->debug
) {
187 ADOConnection
::outp("Refreshing cached active record file: $fname");
190 $activetab = new ADODB_Active_Table();
191 $activetab->name
= $table;
194 $cols = $db->MetaColumns($table);
196 $this->Error("Invalid table name: $table",'UpdateActiveTable');
201 if (isset($fld->primary_key
)) {
203 foreach($cols as $name => $fld) {
204 if (!empty($fld->primary_key
)) $pkeys[] = $name;
207 $pkeys = $this->GetPrimaryKeys($db, $table);
210 $this->Error("No primary key found for table $table",'UpdateActiveTable');
217 switch($ADODB_ASSOC_CASE) {
219 foreach($cols as $name => $fldobj) {
220 $name = strtolower($name);
221 if ($ADODB_ACTIVE_DEFVALS && isset($fldobj->default_value
))
222 $this->$name = $fldobj->default_value
;
225 $attr[$name] = $fldobj;
227 foreach($pkeys as $k => $name) {
228 $keys[strtolower($name)] = strtolower($name);
233 foreach($cols as $name => $fldobj) {
234 $name = strtoupper($name);
236 if ($ADODB_ACTIVE_DEFVALS && isset($fldobj->default_value
))
237 $this->$name = $fldobj->default_value
;
240 $attr[$name] = $fldobj;
243 foreach($pkeys as $k => $name) {
244 $keys[strtoupper($name)] = strtoupper($name);
248 foreach($cols as $name => $fldobj) {
249 $name = ($fldobj->name
);
251 if ($ADODB_ACTIVE_DEFVALS && isset($fldobj->default_value
))
252 $this->$name = $fldobj->default_value
;
255 $attr[$name] = $fldobj;
257 foreach($pkeys as $k => $name) {
258 $keys[$name] = $cols[$name]->name
;
263 $activetab->keys
= $keys;
264 $activetab->flds
= $attr;
266 if ($ADODB_ACTIVE_CACHESECS && $ADODB_CACHE_DIR) {
267 $activetab->_created
= time();
268 $s = serialize($activetab);
269 if (!function_exists('adodb_write_file')) include(ADODB_DIR
.'/adodb-csvlib.inc.php');
270 adodb_write_file($fname,$s);
272 $activedb->tables
[$table] = $activetab;
275 function GetPrimaryKeys(&$db, $table)
277 return $db->MetaPrimaryKeys($table);
280 // error handler for both PHP4+5.
281 function Error($err,$fn)
283 global $_ADODB_ACTIVE_DBS;
285 $fn = get_class($this).'::'.$fn;
286 $this->_lasterr
= $fn.': '.$err;
288 if ($this->_dbat
< 0) $db = false;
290 $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat
];
291 $db =& $activedb->db
;
294 if (function_exists('adodb_throw')) {
295 if (!$db) adodb_throw('ADOdb_Active_Record', $fn, -1, $err, 0, 0, false);
296 else adodb_throw($db->databaseType
, $fn, -1, $err, 0, 0, $db);
298 if (!$db ||
$db->debug
) ADOConnection
::outp($this->_lasterr
);
302 // return last error message
305 if (!function_exists('adodb_throw')) {
306 if ($this->_dbat
< 0) $db = false;
307 else $db = $this->DB();
309 // last error could be database error too
310 if ($db && $db->ErrorMsg()) return $db->ErrorMsg();
312 return $this->_lasterr
;
317 if ($this->_dbat
< 0) return -9999; // no database connection...
320 return (int) $db->ErrorNo();
324 // retrieve ADOConnection from _ADODB_Active_DBs
327 global $_ADODB_ACTIVE_DBS;
329 if ($this->_dbat
< 0) {
331 $this->Error("No database connection set: use ADOdb_Active_Record::SetDatabaseAdaptor(\$db)", "DB");
334 $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat
];
335 $db =& $activedb->db
;
339 // retrieve ADODB_Active_Table
340 function &TableInfo()
342 global $_ADODB_ACTIVE_DBS;
344 $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat
];
345 $table =& $activedb->tables
[$this->_tableat
];
349 // set a numeric array (using natural table field ordering) as object properties
352 global $ACTIVE_RECORD_SAFETY;
357 $this->_saved
= false;
361 $this->_saved
= true;
363 $table =& $this->TableInfo();
364 if ($ACTIVE_RECORD_SAFETY && sizeof($table->flds
) != sizeof($row)) {
366 if (sizeof($row) == 2 * sizeof($table->flds
)) {
367 // Only keep string keys
368 $keys = array_filter(array_keys($row), 'is_string');
369 if (sizeof($keys) == sizeof($table->flds
))
373 $this->Error("Table structure of $this->_table has changed","Load");
378 $keys = array_keys($row);
381 $this->_original
= array();
382 foreach($table->flds
as $name=>$fld) {
383 $value = $row[current($keys)];
384 $this->$name = $value;
385 $this->_original
[] = $value;
392 // get last inserted id for INSERT
393 function LastInsertID(&$db,$fieldname)
395 if ($db->hasInsertID
)
396 $val = $db->Insert_ID($this->_table
,$fieldname);
400 if (is_null($val) ||
$val === false) {
401 // this might not work reliably in multi-user environment
402 return $db->GetOne("select max(".$fieldname.") from ".$this->_table
);
407 // quote data in where clause
408 function doquote(&$db, $val,$t)
413 if (empty($val)) return 'null';
417 if (is_null($val)) return 'null';
419 if (strncmp($val,"'",1) != 0 && substr($val,strlen($val)-1,1) != "'") {
420 return $db->qstr($val);
429 // generate where clause for an UPDATE/SELECT
430 function GenWhere(&$db, &$table)
432 $keys = $table->keys
;
435 foreach($keys as $k) {
436 $f = $table->flds
[$k];
438 $parr[] = $k.' = '.$this->doquote($db,$this->$k,$db->MetaType($f->type
));
441 return implode(' and ', $parr);
445 //------------------------------------------------------------ Public functions below
447 function Load($where,$bindarr=false)
449 $db =& $this->DB(); if (!$db) return false;
450 $this->_where
= $where;
452 $save = $db->SetFetchMode(ADODB_FETCH_NUM
);
453 $row = $db->GetRow("select * from ".$this->_table
.' WHERE '.$where,$bindarr);
454 $db->SetFetchMode($save);
456 return $this->Set($row);
462 if ($this->_saved
) $ok = $this->Update();
463 else $ok = $this->Insert();
471 $db =& $this->DB(); if (!$db) return false;
473 $table =& $this->TableInfo();
479 foreach($table->flds
as $name=>$fld) {
481 if(!is_null($val) ||
!array_key_exists($name, $table->keys
)) {
484 $valstr[] = $db->Param($cnt);
490 foreach($table->flds
as $name=>$fld) {
493 $valstr[] = $db->Param($cnt);
497 $sql = 'INSERT INTO '.$this->_table
."(".implode(',',$names).') VALUES ('.implode(',',$valstr).')';
498 $ok = $db->Execute($sql,$valarr);
501 $this->_saved
= true;
503 foreach($table->keys
as $k) {
504 if (is_null($this->$k)) {
509 if ($autoinc && sizeof($table->keys
) == 1) {
510 $k = reset($table->keys
);
511 $this->$k = $this->LastInsertID($db,$k);
515 $this->_original
= $valarr;
521 $db =& $this->DB(); if (!$db) return false;
522 $table =& $this->TableInfo();
524 $where = $this->GenWhere($db,$table);
525 $sql = 'DELETE FROM '.$this->_table
.' WHERE '.$where;
526 $ok = $db->Execute($sql);
528 return $ok ?
true : false;
531 // returns an array of active record objects
532 function &Find($whereOrderBy,$bindarr=false,$pkeysArr=false)
534 $db =& $this->DB(); if (!$db ||
empty($this->_table
)) return false;
535 $arr =& $db->GetActiveRecordsClass(get_class($this),$this->_table
, $whereOrderBy,$bindarr,$pkeysArr);
539 // returns 0 on error, 1 on update, 2 on insert
542 global $ADODB_ASSOC_CASE;
544 $db =& $this->DB(); if (!$db) return false;
545 $table =& $this->TableInfo();
547 $pkey = $table->keys
;
549 foreach($table->flds
as $name=>$fld) {
553 if (isset($fld->not_null) && $fld->not_null) {
554 if (isset($fld->default_value) && strlen($fld->default_value)) continue;
556 $this->Error("Cannot update null into $name","Replace");
561 if (is_null($val) && !empty($fld->auto_increment
)) {
564 $t = $db->MetaType($fld->type
);
565 $arr[$name] = $this->doquote($db,$val,$t);
569 if (!is_array($pkey)) $pkey = array($pkey);
572 if ($ADODB_ASSOC_CASE == 0)
573 foreach($pkey as $k => $v)
574 $pkey[$k] = strtolower($v);
575 elseif ($ADODB_ASSOC_CASE == 1)
576 foreach($pkey as $k => $v)
577 $pkey[$k] = strtoupper($v);
579 $ok = $db->Replace($this->_table
,$arr,$pkey);
581 $this->_saved
= true; // 1= update 2=insert
584 foreach($table->keys
as $k) {
585 if (is_null($this->$k)) {
590 if ($autoinc && sizeof($table->keys
) == 1) {
591 $k = reset($table->keys
);
592 $this->$k = $this->LastInsertID($db,$k);
596 $this->_original
=& $valarr;
601 // returns 0 on error, 1 on update, -1 if no change in data (no update)
604 $db =& $this->DB(); if (!$db) return false;
605 $table =& $this->TableInfo();
607 $where = $this->GenWhere($db, $table);
610 $this->error("Where missing for table $table", "Update");
618 foreach($table->flds
as $name=>$fld) {
623 if (isset($table->keys
[$name])) {
628 if (isset($fld->not_null
) && $fld->not_null
) {
629 if (isset($fld->default_value
) && strlen($fld->default_value
)) continue;
631 $this->Error("Cannot set field $name to NULL","Update");
637 if (isset($this->_original
[$i]) && $val == $this->_original
[$i]) {
641 $pairs[] = $name.'='.$db->Param($cnt);
646 if (!$cnt) return -1;
647 $sql = 'UPDATE '.$this->_table
." SET ".implode(",",$pairs)." WHERE ".$where;
648 $ok = $db->Execute($sql,$valarr);
650 $this->_original
=& $neworig;
656 function GetAttributeNames()
658 $table =& $this->TableInfo();
659 if (!$table) return false;
660 return array_keys($table->flds
);