4 @version V4.94 23 Jan 2007 (c) 2000-2007 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
23 // array of ADODB_Active_DB's, indexed by ADODB_Active_Record->_dbat
24 $_ADODB_ACTIVE_DBS = array();
25 $ACTIVE_RECORD_SAFETY = true;
27 class ADODB_Active_DB
{
28 var $db; // ADOConnection
29 var $tables; // assoc array of ADODB_Active_Table objects, indexed by tablename
32 class ADODB_Active_Table
{
33 var $name; // table name
34 var $flds; // assoc array of adofieldobjs, indexed by fieldname
35 var $keys; // assoc array of primary keys, indexed by fieldname
36 var $_created; // only used when stored as a cached file
39 // returns index into $_ADODB_ACTIVE_DBS
40 function ADODB_SetDatabaseAdapter(&$db)
42 global $_ADODB_ACTIVE_DBS;
44 foreach($_ADODB_ACTIVE_DBS as $k => $d) {
45 if (PHP_VERSION
>= 5) {
46 if ($d->db
=== $db) return $k;
48 if ($d->db
->_connectionID
=== $db->_connectionID
&& $db->database
== $d->db
->database
)
53 $obj = new ADODB_Active_DB();
55 $obj->tables
= array();
57 $_ADODB_ACTIVE_DBS[] = $obj;
59 return sizeof($_ADODB_ACTIVE_DBS)-1;
63 class ADODB_Active_Record
{
64 var $_dbat; // associative index pointing to ADODB_Active_DB eg. $ADODB_Active_DBS[_dbat]
65 var $_table; // tablename, if set in class definition then use it as table name
66 var $_tableat; // associative index pointing to ADODB_Active_Table, eg $ADODB_Active_DBS[_dbat]->tables[$this->_tableat]
67 var $_where; // where clause set in Load()
68 var $_saved = false; // indicates whether data is already inserted.
69 var $_lasterr = false; // last error message
70 var $_original = false; // the original values loaded or inserted, refreshed on update
73 function SetDatabaseAdapter(&$db)
75 return ADODB_SetDatabaseAdapter($db);
79 function ADODB_Active_Record($table = false, $pkeyarr=false, $db=false)
81 ADODB_Active_Record
::__construct($table,$pkeyarr,$db);
85 function __construct($table = false, $pkeyarr=false, $db=false)
87 global $ADODB_ASSOC_CASE,$_ADODB_ACTIVE_DBS;
89 if ($db == false && is_object($pkeyarr)) {
95 if (!empty($this->_table
)) $table = $this->_table
;
96 else $table = $this->_pluralize(get_class($this));
99 $this->_dbat
= ADODB_Active_Record
::SetDatabaseAdapter($db);
101 $this->_dbat
= sizeof($_ADODB_ACTIVE_DBS)-1;
104 if ($this->_dbat
< 0) $this->Error("No database connection set; use ADOdb_Active_Record::SetDatabaseAdapter(\$db)",'ADODB_Active_Record::__constructor');
106 $this->_table
= $table;
107 $this->_tableat
= $table; # reserved for setting the assoc value to a non-table name, eg. the sql string in future
108 $this->UpdateActiveTable($pkeyarr);
113 $class = get_class($this);
117 function _pluralize($table)
119 $ut = strtoupper($table);
120 $len = strlen($table);
121 $lastc = $ut[$len-1];
122 $lastc2 = substr($ut,$len-2);
127 return substr($table,0,$len-1).'ies';
131 if ($lastc2 == 'CH' ||
$lastc2 == 'SH')
138 //////////////////////////////////
141 function UpdateActiveTable($pkeys=false,$forceUpdate=false)
143 global $ADODB_ASSOC_CASE,$_ADODB_ACTIVE_DBS , $ADODB_CACHE_DIR, $ADODB_ACTIVE_CACHESECS;
145 $activedb =& $_ADODB_ACTIVE_DBS[$this->_dbat
];
147 $table = $this->_table
;
148 $tables = $activedb->tables
;
149 $tableat = $this->_tableat
;
150 if (!$forceUpdate && !empty($tables[$tableat])) {
151 $tobj =& $tables[$tableat];
152 foreach($tobj->flds
as $name => $fld)
157 $db =& $activedb->db
;
158 $fname = $ADODB_CACHE_DIR . '/adodb_' . $db->databaseType
. '_active_'. $table . '.cache';
159 if (!$forceUpdate && $ADODB_ACTIVE_CACHESECS && $ADODB_CACHE_DIR && file_exists($fname)) {
160 $fp = fopen($fname,'r');
161 @flock
($fp, LOCK_SH
);
162 $acttab = unserialize(fread($fp,100000));
164 if ($acttab->_created +
$ADODB_ACTIVE_CACHESECS - (abs(rand()) %
16) > time()) {
165 // abs(rand()) randomizes deletion, reducing contention to delete/refresh file
166 // ideally, you should cache at least 32 secs
167 $activedb->tables
[$table] = $acttab;
169 //if ($db->debug) ADOConnection::outp("Reading cached active record file: $fname");
171 } else if ($db->debug
) {
172 ADOConnection
::outp("Refreshing cached active record file: $fname");
175 $activetab = new ADODB_Active_Table();
176 $activetab->name
= $table;
179 $cols = $db->MetaColumns($table);
181 $this->Error("Invalid table name: $table",'UpdateActiveTable');
186 if (isset($fld->primary_key
)) {
188 foreach($cols as $name => $fld) {
189 if (!empty($fld->primary_key
)) $pkeys[] = $name;
192 $pkeys = $this->GetPrimaryKeys($db, $table);
195 $this->Error("No primary key found for table $table",'UpdateActiveTable');
202 switch($ADODB_ASSOC_CASE) {
204 foreach($cols as $name => $fldobj) {
205 $name = strtolower($name);
207 $attr[$name] = $fldobj;
209 foreach($pkeys as $k => $name) {
210 $keys[strtolower($name)] = strtolower($name);
215 foreach($cols as $name => $fldobj) {
216 $name = strtoupper($name);
218 $attr[$name] = $fldobj;
221 foreach($pkeys as $k => $name) {
222 $keys[strtoupper($name)] = strtoupper($name);
226 foreach($cols as $name => $fldobj) {
227 $name = ($fldobj->name
);
229 $attr[$name] = $fldobj;
231 foreach($pkeys as $k => $name) {
232 $keys[$name] = $cols[$name]->name
;
237 $activetab->keys
= $keys;
238 $activetab->flds
= $attr;
240 if ($ADODB_ACTIVE_CACHESECS && $ADODB_CACHE_DIR) {
241 $activetab->_created
= time();
242 $s = serialize($activetab);
243 if (!function_exists('adodb_write_file')) include(ADODB_DIR
.'/adodb-csvlib.inc.php');
244 adodb_write_file($fname,$s);
246 $activedb->tables
[$table] = $activetab;
249 function GetPrimaryKeys(&$db, $table)
251 return $db->MetaPrimaryKeys($table);
254 // error handler for both PHP4+5.
255 function Error($err,$fn)
257 global $_ADODB_ACTIVE_DBS;
259 $fn = get_class($this).'::'.$fn;
260 $this->_lasterr
= $fn.': '.$err;
262 if ($this->_dbat
< 0) $db = false;
264 $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat
];
265 $db =& $activedb->db
;
268 if (function_exists('adodb_throw')) {
269 if (!$db) adodb_throw('ADOdb_Active_Record', $fn, -1, $err, 0, 0, false);
270 else adodb_throw($db->databaseType
, $fn, -1, $err, 0, 0, $db);
272 if (!$db ||
$db->debug
) ADOConnection
::outp($this->_lasterr
);
276 // return last error message
279 if (!function_exists('adodb_throw')) {
280 if ($this->_dbat
< 0) $db = false;
281 else $db = $this->DB();
283 // last error could be database error too
284 if ($db && $db->ErrorMsg()) return $db->ErrorMsg();
286 return $this->_lasterr
;
291 if ($this->_dbat
< 0) return -9999; // no database connection...
294 return (int) $db->ErrorNo();
298 // retrieve ADOConnection from _ADODB_Active_DBs
301 global $_ADODB_ACTIVE_DBS;
303 if ($this->_dbat
< 0) {
305 $this->Error("No database connection set: use ADOdb_Active_Record::SetDatabaseAdaptor(\$db)", "DB");
308 $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat
];
309 $db =& $activedb->db
;
313 // retrieve ADODB_Active_Table
314 function &TableInfo()
316 global $_ADODB_ACTIVE_DBS;
318 $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat
];
319 $table =& $activedb->tables
[$this->_tableat
];
323 // set a numeric array (using natural table field ordering) as object properties
326 global $ACTIVE_RECORD_SAFETY;
331 $this->_saved
= false;
335 $this->_saved
= true;
337 $table =& $this->TableInfo();
338 if ($ACTIVE_RECORD_SAFETY && sizeof($table->flds
) != sizeof($row)) {
339 $this->Error("Table structure of $this->_table has changed","Load");
344 foreach($table->flds
as $name=>$fld) {
345 $this->$name = $row[$cnt];
348 $this->_original
= $row;
352 // get last inserted id for INSERT
353 function LastInsertID(&$db,$fieldname)
355 if ($db->hasInsertID
)
356 $val = $db->Insert_ID($this->_table
,$fieldname);
360 if (is_null($val) ||
$val === false) {
361 // this might not work reliably in multi-user environment
362 return $db->GetOne("select max(".$fieldname.") from ".$this->_table
);
367 // quote data in where clause
368 function doquote(&$db, $val,$t)
373 if (empty($val)) return 'null';
377 if (is_null($val)) return 'null';
379 if (strncmp($val,"'",1) != 0 && substr($val,strlen($val)-1,1) != "'") {
380 return $db->qstr($val);
389 // generate where clause for an UPDATE/SELECT
390 function GenWhere(&$db, &$table)
392 $keys = $table->keys
;
395 foreach($keys as $k) {
396 $f = $table->flds
[$k];
398 $parr[] = $k.' = '.$this->doquote($db,$this->$k,$db->MetaType($f->type
));
401 return implode(' and ', $parr);
405 //------------------------------------------------------------ Public functions below
407 function Load($where,$bindarr=false)
409 $db =& $this->DB(); if (!$db) return false;
410 $this->_where
= $where;
412 $save = $db->SetFetchMode(ADODB_FETCH_NUM
);
413 $row = $db->GetRow("select * from ".$this->_table
.' WHERE '.$where,$bindarr);
414 $db->SetFetchMode($save);
416 return $this->Set($row);
422 if ($this->_saved
) $ok = $this->Update();
423 else $ok = $this->Insert();
431 $db =& $this->DB(); if (!$db) return false;
433 $table =& $this->TableInfo();
439 foreach($table->flds
as $name=>$fld) {
441 if(!is_null($val) ||
!array_key_exists($name, $table->keys
)) {
444 $valstr[] = $db->Param($cnt);
450 foreach($table->flds
as $name=>$fld) {
453 $valstr[] = $db->Param($cnt);
457 $sql = 'INSERT INTO '.$this->_table
."(".implode(',',$names).') VALUES ('.implode(',',$valstr).')';
458 $ok = $db->Execute($sql,$valarr);
461 $this->_saved
= true;
463 foreach($table->keys
as $k) {
464 if (is_null($this->$k)) {
469 if ($autoinc && sizeof($table->keys
) == 1) {
470 $k = reset($table->keys
);
471 $this->$k = $this->LastInsertID($db,$k);
475 $this->_original
= $valarr;
481 $db =& $this->DB(); if (!$db) return false;
482 $table =& $this->TableInfo();
484 $where = $this->GenWhere($db,$table);
485 $sql = 'DELETE FROM '.$this->_table
.' WHERE '.$where;
486 $ok = $db->Execute($sql);
488 return $ok ?
true : false;
491 // returns an array of active record objects
492 function &Find($whereOrderBy,$bindarr=false,$pkeysArr=false)
494 $db =& $this->DB(); if (!$db ||
empty($this->_table
)) return false;
495 $arr =& $db->GetActiveRecordsClass(get_class($this),$this->_table
, $whereOrderBy,$bindarr,$pkeysArr);
499 // returns 0 on error, 1 on update, 2 on insert
502 global $ADODB_ASSOC_CASE;
504 $db =& $this->DB(); if (!$db) return false;
505 $table =& $this->TableInfo();
507 $pkey = $table->keys
;
509 foreach($table->flds
as $name=>$fld) {
513 if (isset($fld->not_null) && $fld->not_null) {
514 if (isset($fld->default_value) && strlen($fld->default_value)) continue;
516 $this->Error("Cannot update null into $name","Replace");
521 if (is_null($val) && !empty($fld->auto_increment
)) {
524 $t = $db->MetaType($fld->type
);
525 $arr[$name] = $this->doquote($db,$val,$t);
529 if (!is_array($pkey)) $pkey = array($pkey);
532 if ($ADODB_ASSOC_CASE == 0)
533 foreach($pkey as $k => $v)
534 $pkey[$k] = strtolower($v);
535 elseif ($ADODB_ASSOC_CASE == 0)
536 foreach($pkey as $k => $v)
537 $pkey[$k] = strtoupper($v);
539 $ok = $db->Replace($this->_table
,$arr,$pkey);
541 $this->_saved
= true; // 1= update 2=insert
544 foreach($table->keys
as $k) {
545 if (is_null($this->$k)) {
550 if ($autoinc && sizeof($table->keys
) == 1) {
551 $k = reset($table->keys
);
552 $this->$k = $this->LastInsertID($db,$k);
556 $this->_original
=& $valarr;
561 // returns 0 on error, 1 on update, -1 if no change in data (no update)
564 $db =& $this->DB(); if (!$db) return false;
565 $table =& $this->TableInfo();
567 $where = $this->GenWhere($db, $table);
570 $this->error("Where missing for table $table", "Update");
578 foreach($table->flds
as $name=>$fld) {
583 if (isset($table->keys
[$name])) {
588 if (isset($fld->not_null
) && $fld->not_null
) {
589 if (isset($fld->default_value
) && strlen($fld->default_value
)) continue;
591 $this->Error("Cannot set field $name to NULL","Update");
597 if (isset($this->_original
[$i]) && $val == $this->_original
[$i]) {
601 $pairs[] = $name.'='.$db->Param($cnt);
606 if (!$cnt) return -1;
607 $sql = 'UPDATE '.$this->_table
." SET ".implode(",",$pairs)." WHERE ".$where;
608 $ok = $db->Execute($sql,$valarr);
610 $this->_original
=& $neworig;
616 function GetAttributeNames()
618 $table =& $this->TableInfo();
619 if (!$table) return false;
620 return array_keys($table->flds
);