Ferreteria/archive/data.php: Difference between revisions
(12/24 code update) |
(3/26 version) |
||
Line 2: | Line 2: | ||
/* =========================== | /* =========================== | ||
*** DATA UTILITY CLASSES *** | *** DATA UTILITY CLASSES *** | ||
HISTORY: | |||
2007-05-20 Wzl These classes have been designed to be db-engine agnostic, but I wasn't able | |||
to test against anything other than MySQL nor was I able to implement the usage of | to test against anything other than MySQL nor was I able to implement the usage of | ||
the dbx_ functions, as the system that I was using didn't have them installed. | the dbx_ functions, as the system that I was using didn't have them installed. | ||
2007-08-30 Wzl posting this version at http://htyp.org/User:Woozle/data.php | |||
2007-12-24 Wzl Some changes seem to have been made as recently as 12/17, so posting updated version | |||
2008-02-06 Wzl Modified to use either mysqli or (standard) mysql library depending on flag; the latter isn't working yet | |||
2009-03-10 Wzl adding some static functions to gradually get rid of the need for object factories | |||
2009-03-18 Wzl debug constants now have defaults | |||
2009-03-26 Wzl clsDataSet.Query() no longer fetches first row; this will require some rewriting | |||
NextRow() now returns TRUE if data was fetched; use if (data->NextRow()) {..} to loop through data. | |||
*/ | */ | ||
// Select which DB library to use -- | |||
// exactly one of the following must be true: | |||
define('KF_USE_MYSQL',TRUE); // in progress | |||
define('KF_USE_MYSQLI',FALSE); // complete & tested | |||
define('KF_USE_DBX',false); // not completely written; stalled | |||
if (!defined('KDO_DEBUG')) { define('KDO_DEBUG',FALSE); } | |||
if (!defined('KDO_DEBUG_STACK')) { define('KDO_DEBUG_STACK',FALSE); } | |||
if (!defined('KDO_DEBUG_IMMED')) { define('KDO_DEBUG_IMMED',FALSE); } | |||
if (!defined('KS_DEBUG_HTML')) { define('KS_DEBUG_HTML',FALSE); } | |||
if (!defined('KDO_DEBUG_DARK')) { define('KDO_DEBUG_DARK',FALSE); } | |||
class clsDatabase { | class clsDatabase { | ||
/* ============= | |||
| STATIC SECTION | |||
*/ | |||
// nothing yet | |||
/* ============== | |||
| DYNAMIC SECTION | |||
*/ | |||
private $cntOpen; // count of requests to keep db open | private $cntOpen; // count of requests to keep db open | ||
private $strType; // type of db (MySQL etc.) | private $strType; // type of db (MySQL etc.) | ||
Line 24: | Line 51: | ||
public function __construct($iConn) { | public function __construct($iConn) { | ||
$this->Init($iConn); | $this->Init($iConn); | ||
} | } | ||
public function Init($iConn) { | public function Init($iConn) { | ||
// $iConn format: type:user:pass@server/dbname | // $iConn format: type:user:pass@server/dbname | ||
$this->cntOpen = 0; | $this->cntOpen = 0; | ||
list($part1,$part2) = split('@',$iConn); | list($part1,$part2) = split('@',$iConn); | ||
Line 36: | Line 60: | ||
list($this->strHost,$this->strName) = explode('/',$part2); | list($this->strHost,$this->strName) = explode('/',$part2); | ||
$this->strType = strtolower($this->strType); // make sure it is lowercased, for comparison | $this->strType = strtolower($this->strType); // make sure it is lowercased, for comparison | ||
} | } | ||
public function Open() { | public function Open() { | ||
CallEnter('clsDatabase.Open()'); | CallEnter($this,__LINE__,'clsDatabase.Open()'); | ||
if ($this->cntOpen == 0) { | if ($this->cntOpen == 0) { | ||
// then actually open the db | // then actually open the db | ||
if (KF_USE_MYSQL) { | |||
$this->Conn = mysql_connect( $this->strHost, $this->strUser, $this->strPass, false ); | |||
assert('is_resource($this->Conn)'); | |||
$ok = mysql_select_db($this->strName, $this->Conn); | |||
if (!$ok) { | |||
$this->getError(); | |||
} | |||
} | |||
if (KF_USE_MYSQLI) { | |||
$this->Conn = new mysqli($this->strHost,$this->strUser,$this->strPass,$this->strName); | |||
} | |||
if (KF_USE_DBX) { | |||
$this->Conn = dbx_connect($this->strType,$this->strHost,$this->strName,$this->strUser,$this->strPass); | |||
} | |||
} | |||
if (!$this->isOk()) { | |||
$this->getError(); | |||
} | } | ||
$this->cntOpen++; | $this->cntOpen++; | ||
Line 52: | Line 87: | ||
} | } | ||
public function Shut() { | public function Shut() { | ||
CallEnter('clsDatabase.Shut()'); | CallEnter($this,__LINE__,'clsDatabase.Shut()'); | ||
$this->cntOpen--; | $this->cntOpen--; | ||
if ($this->cntOpen == 0) { | if ($this->cntOpen == 0) { | ||
if (KF_USE_MYSQL) { | |||
mysql_close($this->Conn); | |||
} | |||
if (KF_USE_MYSQLI) { | |||
$this->Conn->close(); | |||
} | |||
if (KF_USE_DBX) { | |||
dbx_close($this->Conn); | |||
} | |||
} | } | ||
CallExit('clsDatabase.Shut() - '.$this->cntOpen.' lock'.Pluralize($this->cntOpen)); | CallExit('clsDatabase.Shut() - '.$this->cntOpen.' lock'.Pluralize($this->cntOpen)); | ||
} | } | ||
public function | public function GetHost() { | ||
/ | return $this->strHost; | ||
$this-> | } | ||
public function GetUser() { | |||
return $this->strUser; | |||
} | |||
} | public function isOpened() { | ||
/* | |||
PURPOSE: For debugging, mainly | |||
RETURNS: TRUE if database connection is supposed to be open | |||
*/ | |||
return ($this->cntOpen > 0); | |||
} | |||
public function isOk() { | |||
/* | |||
PURPOSE: For debugging, mainly | |||
RETURNS: TRUE if database connection was actually opened successfully | |||
*/ | |||
if ($this->strErr) { | |||
return FALSE; | |||
} elseif ($this->Conn == FALSE) { | |||
return FALSE; | |||
} else { | |||
return TRUE; | |||
} | |||
} | |||
public function getError() { | |||
if (KF_USE_MYSQL) { | |||
$this->strErr = mysql_error(); | |||
} | |||
if (KF_USE_MYSQLI) { | |||
$this->strErr = $this->Conn->error; | |||
} | } | ||
return $this->strErr; | |||
} | } | ||
public function Exec($iSQL) { | public function Exec($iSQL) { | ||
CallEnter($this,__LINE__,__CLASS__.'.'.__METHOD__.'('.$iSQL.')'); | |||
CallEnter(' | |||
$this->sql = $iSQL; | $this->sql = $iSQL; | ||
$objQry = $this->Conn->prepare($iSQL); | if (KF_USE_MYSQL) { | ||
$ok = mysql_query($iSQL); | |||
} | |||
if (KF_USE_MYSQLI) { | |||
$objQry = $this->Conn->prepare($iSQL); | |||
if (is_object($objQry)) { | |||
$ok = $objQry->execute(); | |||
} else { | |||
$ok = false; | |||
echo '<br>SQL error: '.$iSQL.'<br>'; | |||
} | |||
} | |||
if (!$ok) { | |||
$this->getError(); | |||
} | |||
if (KF_USE_MYSQL) { | |||
// no need to do anything; no resource allocated as long as query SQL was non-data-fetching | |||
} | |||
if (KF_USE_MYSQLI) { | |||
$objQry->close(); | |||
} | } | ||
CallExit(__CLASS__.'.'.__METHOD__.'()'); | |||
CallExit(' | |||
return $ok; | return $ok; | ||
} | } | ||
public function NewID() { | public function NewID() { | ||
return $this->Conn->insert_id; | if (KF_USE_MYSQL) { | ||
mysql_insert_id($this->Conn); | |||
} | |||
if (KF_USE_MYSQLI) { | |||
return $this->Conn->insert_id; | |||
} | |||
} | } | ||
public function SafeParam($iString) { | public function SafeParam($iString) { | ||
CallEnter($this,__LINE__,__CLASS__.'.SafeParam("'.$iString.'")'); | |||
if (KF_USE_MYSQL) { | |||
if (is_resource($this->Conn)) { | |||
$out = mysql_real_escape_string($iString,$this->Conn); | |||
} else { | |||
$out = '<br>'.get_class($this).'.SafeParam("'.$iString.'") has no connection.'; | |||
} | |||
} | |||
if (KF_USE_MYSQLI) { | |||
$out = $this->Conn->escape_string($iString); | |||
} | |||
CallExit('SafeParam("'.$iString.'")'); | |||
return $out; | |||
} | } | ||
public function ErrorText() { | public function ErrorText() { | ||
return $this-> | if ($this->strErr == '') { | ||
$this->_api_getError(); | |||
} | |||
return $this->strErr; | |||
} | |||
/* === API WRAPPER FUNCTIONS === */ | |||
// some of these could be static, but for now it seems simpler to keep them all together here | |||
public function _api_query($iSQL) { | |||
global $dbgSQL; | |||
$this->sql = $iSQL; | |||
$dbgSQL = $iSQL; | |||
if (KF_USE_MYSQL) { | |||
return mysql_query($iSQL); | |||
} | |||
if (KF_USE_MYSQLI) { | |||
$this->Conn->real_query($iSQL); | |||
return $this->Conn->store_result(); | |||
} | |||
if (KF_USE_DBX) { | |||
return dbx_query($this->Conn,$iSQL,DBX_RESULT_ASSOC); | |||
} | |||
} | |||
public function _api_rows_rewind($iRes) { | |||
if (KF_USE_MYSQL) { | |||
mysql_data_seek($iRes, 0); | |||
} | |||
} | |||
public function _api_fetch_row($iRes) { | |||
// ACTION: Fetch the first/next row of data from a result set | |||
if (KF_USE_MYSQL) { | |||
assert('is_resource($iRes)'); | |||
return mysql_fetch_assoc($iRes); | |||
} | |||
if (KF_USE_MYSQLI) { | |||
return $iRes->fetch_assoc(); | |||
} | |||
} | |||
public function _api_count_rows($iRes) { | |||
// ACTION: Return the number of rows in the result set | |||
if (KF_USE_MYSQL) { | |||
if ($iRes === FALSE) { | |||
return FALSE; | |||
} else { | |||
assert('is_resource($iRes)'); | |||
return mysql_num_rows($iRes); | |||
} | |||
} | |||
if (KF_USE_MYSQLI) { | |||
return $iRes->num_rows; | |||
} | |||
} | |||
public function _api_row_filled($iRow) { | |||
if (KF_USE_MYSQL) { | |||
return ($iRow !== FALSE) ; | |||
} | |||
} | |||
/* === OBJECT FACTORY === */ | |||
public function DataSet($iSQL = NULL,$iClass = NULL) { | |||
CallEnter($this,__LINE__,__CLASS__.'.DataSet("'.$iSQL.'","'.$iClass.'")'); | |||
if (is_string($iClass)) { | |||
$objData = new $iClass($this); | |||
assert('is_object($objData)'); | |||
if (!($objData instanceof clsDataSet)) { | |||
LogError($iClass.' is not a clsDataSet subclass.'); | |||
} | |||
} else { | |||
$objData = new clsDataSet($this); | |||
assert('is_object($objData)'); | |||
} | |||
if (!is_null($iSQL)) { | |||
if (is_object($objData)) { | |||
$objData->Query($iSQL); | |||
} | |||
} | |||
CallExit(__CLASS__.'.DataSet()'); | |||
return $objData; | |||
} | |||
} | |||
class clsDataSet { | |||
protected $objDB; | |||
public $Res; // native result set | |||
public $Row; // data from the active row | |||
public function __construct(clsDatabase $iDB=NULL, $iRes=NULL, array $iRow=NULL) { | |||
CallEnter($this,__LINE__,__CLASS__.'.'.__FUNCTION__.'(['.get_class($iDB).'])'); | |||
$this->objDB = $iDB; | |||
$this->Res = $iRes; | |||
$this->Row = $iRow; | |||
CallExit(__CLASS__.'.'.__FUNCTION__.'()'); | |||
} | |||
// -- loading and navigating through a data set | |||
public function Query($iSQL) { | |||
global $sql; | |||
CallEnter($this,__LINE__,__CLASS__.'.'.__FUNCTION__.'('.$iSQL.')'); | |||
$sql = $iSQL; | |||
$this->Res = $this->objDB->_api_query($iSQL); | |||
//$sqlEsc = $this->objDB->SafeParam($sql); | |||
assert('is_resource($this->Res)'); // && ('$sqlEsc' != '')"); | |||
// $this->NextRow(); // load the first row without wasting time rewinding | |||
CallExit(__CLASS__.'.'.__FUNCTION__.'()'); | |||
} | |||
public function hasRows() { | |||
// RETURNS: # of rows iff result has rows, otherwise FALSE | |||
$rows = $this->objDB->_api_count_rows($this->Res); | |||
if ($rows === FALSE) { | |||
return FALSE; | |||
} elseif ($rows == 0) { | |||
return FALSE; | |||
} else { | |||
return $rows; | |||
} | |||
} | |||
public function hasRow() { | |||
return $this->objDB->_api_row_filled($this->Row); | |||
} | |||
public function RowCount() { | |||
return $this->objDB->_api_count_rows($this->Res); | |||
} | |||
public function FirstRow() { | |||
if ($this->hasRows()) { | |||
$this->objDB->_api_rows_rewind($this->Res); | |||
$this->NextRow(); // get the first row of data | |||
} | |||
} | |||
public function NextRow() { | |||
/* ACTION: Fetch the next row of data into $this->Row. | |||
If no data has been fetched yet, then fetch the first row. | |||
RETURN: TRUE if row was fetched; FALSE if there were no more rows | |||
or the row could not be fetched. | |||
*/ | |||
$this->Row = $this->objDB->_api_fetch_row($this->Res); | |||
return $this->hasRow(); | |||
} | |||
// -- accessing individual fields | |||
public function __set($iName, $iValue) { | |||
$this->Row[$iName] = $iValue; | |||
} | |||
public function __get($iName) { | |||
return $this->Row[$iName]; | |||
} | } | ||
} | } | ||
/* | |||
class clsDataLine extends clsDataSet { | |||
} | |||
*/ | |||
/*============= | |||
| NAME: clsTable | |||
| PURPOSE: objects for operating on particular tables | |||
*/ | |||
class clsTable { | |||
protected $objDB; | |||
protected $vTblName; | |||
protected $vKeyName; | |||
protected $vSngClass; // name of singular class | |||
public function __construct($iDB,$iTblName,$iKeyName,$iSngClass=NULL) { | |||
$this->objDB = $iDB; | |||
$this->vTblName = $iTblName; | |||
$this->vKeyName = $iKeyName; | |||
$this->vSngClass = $iSngClass; | |||
} | |||
public function Name() { | |||
return $this->vTblName; | |||
} | |||
public function SingularName(string $iName=NULL) { | |||
if (!is_null($iName)) { | |||
$this->vSngClass = $iName; | |||
} | |||
return $this->vSngClass; | |||
} | |||
public function GetItem($iID,$iClass=NULL) { | |||
/* $sql = 'SELECT * FROM `'.$this->vTblName.'` WHERE '.$this->vKeyName.'='.$iID; | |||
if (is_null($iClass)) { | |||
$strClass = $this->vSngClass; | |||
} else { | |||
$strClass = $iClass; | |||
} | |||
return $this->objDB->DataSet($sql,$strClass); | |||
*/ | |||
$objItem = $this->GetData($this->vKeyName.'='.$iID,$iClass); | |||
$objItem->NextRow(); | |||
return $objItem; | |||
} | |||
public function GetData($iWhere,$iClass=NULL,$iSort=NULL) { | |||
global $sql; // for debugging | |||
CallEnter($this,__LINE__,__CLASS__.'.'.__METHOD__.'("'.$iWhere.'","'.$iClass.'")'); | |||
$sql = 'SELECT * FROM `'.$this->vTblName.'` WHERE '.$iWhere; | |||
if (!is_null($iSort)) { | |||
$sql .= ' ORDER BY '.$iSort; | |||
} | |||
if (is_null($iClass)) { | |||
$strClass = $this->vSngClass; | |||
} else { | |||
$strClass = $iClass; | |||
} | |||
CallExit('GetData() - SQL: '.$sql); | |||
return $this->objDB->DataSet($sql,$strClass); | |||
} | |||
} | |||
// OLDER CLASSES -- DEPRECATED | |||
/* | |||
class clsData { | class clsData { | ||
protected $objDB; // clsDatabase | protected $objDB; // clsDatabase | ||
Line 120: | Line 419: | ||
public function __construct($iDB,$iSQL) { | public function __construct($iDB,$iSQL) { | ||
CallEnter('clsDataQuery('.get_class($iDB).','.$iSQL.')'); | CallEnter($this,__LINE__,'clsDataQuery('.get_class($iDB).','.$iSQL.')'); | ||
parent::__construct($iDB); | parent::__construct($iDB); | ||
$this->sqlSelect = $iSQL; | $this->sqlSelect = $iSQL; | ||
Line 141: | Line 440: | ||
protected $strName; | protected $strName; | ||
public function __construct($iDB,$iName) { | public function __construct($iDB,$iName) { | ||
CallEnter('clsDataTable_noID('.get_class($iDB).','.$iName.')'); | CallEnter($this,__LINE__,'clsDataTable_noID('.get_class($iDB).','.$iName.')'); | ||
parent::__construct($iDB); | parent::__construct($iDB); | ||
$this->strName = $iName; | $this->strName = $iName; | ||
Line 150: | Line 449: | ||
} | } | ||
protected function _newItem() { | protected function _newItem() { | ||
CallStep('('.get_class($this).') | CallStep('('.get_class($this).')'.__CLASS__.'.'.__FUNCTION__.'()'); | ||
return new clsDataItem_noID($this); | return new clsDataItem_noID($this); | ||
} | } | ||
Line 157: | Line 456: | ||
} | } | ||
public function GetData($iFilt='',$iSort='') { | public function GetData($iFilt='',$iSort='') { | ||
CallEnter('clsDataTable.GetData(filt="'.$iFilt.'",sort="'.$iSort.'")'); | CallEnter($this,__LINE__,'clsDataTable.GetData(filt="'.$iFilt.'",sort="'.$iSort.'")'); | ||
$sql = 'SELECT * FROM '.$this->strName; | $sql = 'SELECT * FROM '.$this->strName; | ||
if ($iFilt != '') { | if ($iFilt != '') { | ||
Line 165: | Line 464: | ||
$sql .= ' ORDER BY '.$iSort; | $sql .= ' ORDER BY '.$iSort; | ||
} | } | ||
CallStep('SQL = ['.$sql.']'); | CallStep('line '.__LINE__.' ['.get_class($this).'] SQL = ['.$sql.']'); | ||
$objItem = $this->_newItem(); | $objItem = $this->_newItem(); | ||
$objItem->Query($sql); | $objItem->Query($sql); | ||
Line 179: | Line 478: | ||
public function __construct($iDB,$iName,$iIDname='ID') { | public function __construct($iDB,$iName,$iIDname='ID') { | ||
CallEnter('clsDataTable('.get_class($iDB).','.$iName.')'); | CallEnter($this,__LINE__,'clsDataTable('.get_class($iDB).','.$iName.')'); | ||
parent::__construct($iDB,$iName); | parent::__construct($iDB,$iName); | ||
$this->strIDname = $iIDname; | $this->strIDname = $iIDname; | ||
Line 187: | Line 486: | ||
return $this->strIDname; | return $this->strIDname; | ||
} | } | ||
public function IDName() { | |||
return $this->strIDname; | |||
} | |||
protected function _newItem() { | protected function _newItem() { | ||
CallStep('('.get_class($this).')clsDataTable._newItem()'); | CallStep('('.get_class($this).')clsDataTable._newItem()'); | ||
Line 192: | Line 496: | ||
} | } | ||
public function GetItem($iID) { | public function GetItem($iID) { | ||
CallEnter | CallEnter($this,__LINE__,'clsDataTable.GetItem('.$iID.')'); | ||
$sql = 'SELECT * FROM '.$this->strName.' WHERE '.$this->strIDname.'="'.$iID.'"'; | $sql = 'SELECT * FROM '.$this->strName.' WHERE '.$this->strIDname.'="'.$iID.'"'; | ||
CallStep('SQL = ['.$sql.']'); | CallStep('SQL = ['.$sql.']'); | ||
Line 209: | Line 513: | ||
public $Table; // clsDataTable object | public $Table; // clsDataTable object | ||
public function __construct($iTable=NULL) { | public function __construct($iTable=NULL) { | ||
CallEnter('clsDataItem_noID('.get_class($iTable).')'); | CallEnter($this,__LINE__,'clsDataItem_noID('.get_class($iTable).')'); | ||
$this->Init($iTable); | $this->Init($iTable); | ||
CallExit('clsDataItem_noID()'); | CallExit('clsDataItem_noID()'); | ||
} | } | ||
public function Init($iTable=NULL) { | public function Init($iTable=NULL) { | ||
CallEnter | CallEnter($this,__LINE__,'clsDataItem_noID.Init('.get_class($iTable).')'); | ||
$this->Table = $iTable; | $this->Table = $iTable; | ||
// this works for mysql only: | // this works for mysql only: | ||
Line 221: | Line 525: | ||
CallExit('clsDataItem_noID.Init()'); | CallExit('clsDataItem_noID.Init()'); | ||
} | } | ||
public function | public function isResOk() { | ||
if (KF_USE_MYSQL) { | |||
return $this->Res !== FALSE; | |||
} | |||
} | } | ||
protected function LoadResults() { | protected function LoadResults() { | ||
Line 244: | Line 536: | ||
public function GetValue($iName) { | public function GetValue($iName) { | ||
// this works for mysql only | // this works for mysql only | ||
CallEnter('clsDataTable_noID.GetValue('.$iName.')'); | CallEnter($this,__LINE__,'clsDataTable_noID.GetValue('.$iName.')'); | ||
// DumpArray($this->Row); | // DumpArray($this->Row); | ||
$val = $this->Row[$iName]; | $val = $this->Row[$iName]; | ||
Line 251: | Line 543: | ||
} | } | ||
public function RowCount() { | public function RowCount() { | ||
if (KF_USE_MYSQL) { | |||
if (is_resource($this->Res)) { | |||
$cntRows = mysql_num_rows($this->Res); | |||
} else { | |||
echo '<br>BAD RESOURCE in '.get_class($this).'.RowCount()'; | |||
} | |||
} | |||
if (KF_USE_MYSQLI) { | |||
$cntRows = $this->Res->num_rows; | $cntRows = $this->Res->num_rows; | ||
} | |||
CallStep('('.get_class($this).')clsDataItem_noID.RowCount() -> '.$cntRows); | CallStep('('.get_class($this).')clsDataItem_noID.RowCount() -> '.$cntRows); | ||
return $cntRows; | return $cntRows; | ||
Line 260: | Line 561: | ||
public function HasRows($iMin=1) { | public function HasRows($iMin=1) { | ||
if ($this->HasData()) { | if ($this->HasData()) { | ||
assert('is_resource($this->Res)'); | |||
return ($this->RowCount() >= $iMin); | return ($this->RowCount() >= $iMin); | ||
} else { | } else { | ||
Line 266: | Line 568: | ||
} | } | ||
private function FirstRow() { | private function FirstRow() { | ||
CallEnter | CallEnter($this,__LINE__,'clsDataItem_noID.FirstRow()'); | ||
if ( | if (is_resource($this->Res)) { | ||
if ($this->RowCount()) { | if ($this->RowCount()) { | ||
$this->NextRow(); // get the first row of data | $this->NextRow(); // get the first row of data | ||
Line 277: | Line 579: | ||
// this works for mysql only: | // this works for mysql only: | ||
// fetch the NEXT row of the results as an associative array: | // fetch the NEXT row of the results as an associative array: | ||
if (KF_USE_MYSQL) { | |||
$this->Row = mysql_fetch_assoc($this->Res); | |||
} | |||
if (KF_USE_MYSQLI) { | |||
$this->Row = $this->Res->fetch_assoc(); | $this->Row = $this->Res->fetch_assoc(); | ||
} | |||
if (is_array($this->Row)) { | if (is_array($this->Row)) { | ||
$this->LoadResults(); | $this->LoadResults(); | ||
Line 295: | Line 602: | ||
protected function LoadResults() { | protected function LoadResults() { | ||
// USAGE: Descendants do not have to call this function | // USAGE: Descendants do not have to call this function | ||
CallEnter | CallEnter($this,__LINE__,'clsDataItem.LoadResults()'); | ||
assert($this->Table); | assert($this->Table); | ||
$strID = $this->Table->NameOfID(); | |||
$this->ID | assert(($strID != '') && ($strID != FALSE)); | ||
$this->ID = $this->GetValue($strID); | |||
assert($this->ID); | assert($this->ID); | ||
if (KDO_DEBUG) { | |||
if (!isset($this->ID)) { | if (!isset($this->ID)) { | ||
echo '<br>TABLE: ['.$this->Table->Name().'] ID name: ['.$ | echo '<br>TABLE: ['.$this->Table->Name().'] ID name: ['.$strID.']<br>'; | ||
} | } | ||
} | |||
CallExit('clsDataItem.LoadResults()'); | CallExit('clsDataItem.LoadResults()'); | ||
} | } | ||
} | } | ||
*/ | |||
/* ======================== | /* ======================== | ||
*** UTILITY FUNCTIONS *** | *** UTILITY FUNCTIONS *** | ||
Line 323: | Line 633: | ||
// these could later be expanded to create a call-path for errors, etc. | // these could later be expanded to create a call-path for errors, etc. | ||
function CallEnter($iName) { | function CallEnter($iObj,$iLine,$iName) { | ||
global $intCallDepth, $debug; | global $intCallDepth, $debug; | ||
if (KDO_DEBUG_STACK) { | if (KDO_DEBUG_STACK) { | ||
$strDescr = ' line '.$iLine.' ('.get_class($iObj).')'.$iName; | |||
_debugLine('enter','>',$strDescr); | |||
$intCallDepth++; | $intCallDepth++; | ||
_debugDump(); | |||
} | } | ||
} | } | ||
Line 342: | Line 646: | ||
if (KDO_DEBUG_STACK) { | if (KDO_DEBUG_STACK) { | ||
$intCallDepth--; | $intCallDepth--; | ||
_debugLine('exit','<',$iName); | |||
_debugDump(); | |||
} | } | ||
} | |||
function CallStep($iDescr) { | |||
global $intCallDepth, $debug; | |||
if (KDO_DEBUG_STACK) { | |||
_debugLine('step',':',$iDescr); | |||
_debugDump(); | |||
} | } | ||
} | } | ||
function | function LogError($iDescr) { | ||
global $intCallDepth, $debug; | global $intCallDepth, $debug; | ||
if (KDO_DEBUG_STACK) { | if (KDO_DEBUG_STACK) { | ||
_debugLine('error',':',$iDescr); | |||
_debugDump(); | |||
} | |||
} | |||
function _debugLine($iType,$iSfx,$iText) { | |||
global $intCallDepth, $debug; | |||
if (KDO_DEBUG_HTML) { | if (KDO_DEBUG_HTML) { | ||
$debug .= ' | $debug .= '<span class="debug-'.$iType.'"><b>'.str_repeat('—',$intCallDepth).$iSfx.'</b> '.$iText.'</span><br>'; | ||
} else { | } else { | ||
$debug .= | $debug .= str_repeat('*',$intCallDepth).'++ '.$iText."\n"; | ||
} | } | ||
} | |||
function _debugDump() { | |||
global $debug; | |||
if (KDO_DEBUG_IMMED) { | if (KDO_DEBUG_IMMED) { | ||
DoDebugStyle(); | |||
echo $debug; | |||
$debug = ''; | |||
} | } | ||
} | } | ||
function DumpArray($iArr) { | function DumpArray($iArr) { | ||
Line 413: | Line 727: | ||
echo '<style type="text/css"><!--'; | echo '<style type="text/css"><!--'; | ||
if (KDO_DEBUG_DARK) { | if (KDO_DEBUG_DARK) { | ||
echo '.debug-enter { background: # | echo '.debug-enter { background: #666600; }'; // dark highlight | ||
} else { | } else { | ||
echo '.debug-enter { background: # | echo '.debug-enter { background: #ffff00; }'; // regular yellow highlight | ||
} | } | ||
echo '--></style>'; | echo '--></style>'; | ||
Line 421: | Line 735: | ||
} | } | ||
} | } | ||
</php> | |||
Revision as of 12:04, 5 April 2009
<php><?php /* ===========================
*** DATA UTILITY CLASSES ***
HISTORY:
2007-05-20 Wzl These classes have been designed to be db-engine agnostic, but I wasn't able
to test against anything other than MySQL nor was I able to implement the usage of the dbx_ functions, as the system that I was using didn't have them installed.
2007-08-30 Wzl posting this version at http://htyp.org/User:Woozle/data.php 2007-12-24 Wzl Some changes seem to have been made as recently as 12/17, so posting updated version 2008-02-06 Wzl Modified to use either mysqli or (standard) mysql library depending on flag; the latter isn't working yet 2009-03-10 Wzl adding some static functions to gradually get rid of the need for object factories 2009-03-18 Wzl debug constants now have defaults 2009-03-26 Wzl clsDataSet.Query() no longer fetches first row; this will require some rewriting NextRow() now returns TRUE if data was fetched; use if (data->NextRow()) {..} to loop through data.
- /
// Select which DB library to use -- // exactly one of the following must be true: define('KF_USE_MYSQL',TRUE); // in progress define('KF_USE_MYSQLI',FALSE); // complete & tested define('KF_USE_DBX',false); // not completely written; stalled
if (!defined('KDO_DEBUG')) { define('KDO_DEBUG',FALSE); } if (!defined('KDO_DEBUG_STACK')) { define('KDO_DEBUG_STACK',FALSE); } if (!defined('KDO_DEBUG_IMMED')) { define('KDO_DEBUG_IMMED',FALSE); } if (!defined('KS_DEBUG_HTML')) { define('KS_DEBUG_HTML',FALSE); } if (!defined('KDO_DEBUG_DARK')) { define('KDO_DEBUG_DARK',FALSE); }
class clsDatabase {
/* ============= | STATIC SECTION
- /
// nothing yet
/* ============== | DYNAMIC SECTION
- /
private $cntOpen; // count of requests to keep db open private $strType; // type of db (MySQL etc.) private $strUser; // database user private $strPass; // password private $strHost; // host (database server domain-name or IP address) private $strName; // database (schema) name
private $Conn; // connection object
// status
private $strErr; // latest error message public $sql; // last SQL executed (or attempted)
public function __construct($iConn) { $this->Init($iConn); } public function Init($iConn) {
// $iConn format: type:user:pass@server/dbname
$this->cntOpen = 0; list($part1,$part2) = split('@',$iConn); list($this->strType,$this->strUser,$this->strPass) = split(':',$part1); list($this->strHost,$this->strName) = explode('/',$part2); $this->strType = strtolower($this->strType); // make sure it is lowercased, for comparison } public function Open() { CallEnter($this,__LINE__,'clsDatabase.Open()'); if ($this->cntOpen == 0) {
// then actually open the db
if (KF_USE_MYSQL) {
$this->Conn = mysql_connect( $this->strHost, $this->strUser, $this->strPass, false ); assert('is_resource($this->Conn)'); $ok = mysql_select_db($this->strName, $this->Conn); if (!$ok) { $this->getError(); }
} if (KF_USE_MYSQLI) {
$this->Conn = new mysqli($this->strHost,$this->strUser,$this->strPass,$this->strName);
} if (KF_USE_DBX) {
$this->Conn = dbx_connect($this->strType,$this->strHost,$this->strName,$this->strUser,$this->strPass);
} } if (!$this->isOk()) { $this->getError(); } $this->cntOpen++; CallExit('clsDatabase.Open() - '.$this->cntOpen.' lock'.Pluralize($this->cntOpen)); } public function Shut() { CallEnter($this,__LINE__,'clsDatabase.Shut()'); $this->cntOpen--; if ($this->cntOpen == 0) { if (KF_USE_MYSQL) {
mysql_close($this->Conn);
} if (KF_USE_MYSQLI) {
$this->Conn->close();
} if (KF_USE_DBX) {
dbx_close($this->Conn);
} } CallExit('clsDatabase.Shut() - '.$this->cntOpen.' lock'.Pluralize($this->cntOpen)); } public function GetHost() {
return $this->strHost;
} public function GetUser() {
return $this->strUser;
} public function isOpened() {
/*
PURPOSE: For debugging, mainly RETURNS: TRUE if database connection is supposed to be open
- /
return ($this->cntOpen > 0); } public function isOk() {
/*
PURPOSE: For debugging, mainly RETURNS: TRUE if database connection was actually opened successfully
- /
if ($this->strErr) { return FALSE; } elseif ($this->Conn == FALSE) { return FALSE; } else { return TRUE; }
} public function getError() { if (KF_USE_MYSQL) {
$this->strErr = mysql_error();
} if (KF_USE_MYSQLI) {
$this->strErr = $this->Conn->error;
} return $this->strErr; } public function Exec($iSQL) { CallEnter($this,__LINE__,__CLASS__.'.'.__METHOD__.'('.$iSQL.')'); $this->sql = $iSQL; if (KF_USE_MYSQL) {
$ok = mysql_query($iSQL);
} if (KF_USE_MYSQLI) {
$objQry = $this->Conn->prepare($iSQL);
if (is_object($objQry)) {
$ok = $objQry->execute();
} else {
$ok = false;
echo '
SQL error: '.$iSQL.'
';
}
} if (!$ok) { $this->getError(); } if (KF_USE_MYSQL) { // no need to do anything; no resource allocated as long as query SQL was non-data-fetching } if (KF_USE_MYSQLI) {
$objQry->close();
} CallExit(__CLASS__.'.'.__METHOD__.'()'); return $ok; } public function NewID() { if (KF_USE_MYSQL) {
mysql_insert_id($this->Conn);
} if (KF_USE_MYSQLI) {
return $this->Conn->insert_id;
} } public function SafeParam($iString) { CallEnter($this,__LINE__,__CLASS__.'.SafeParam("'.$iString.'")'); if (KF_USE_MYSQL) {
if (is_resource($this->Conn)) {
$out = mysql_real_escape_string($iString,$this->Conn);
} else {
$out = '
'.get_class($this).'.SafeParam("'.$iString.'") has no connection.';
}
} if (KF_USE_MYSQLI) {
$out = $this->Conn->escape_string($iString);
} CallExit('SafeParam("'.$iString.'")'); return $out; } public function ErrorText() { if ($this->strErr == ) { $this->_api_getError(); } return $this->strErr; }
/* === API WRAPPER FUNCTIONS === */ // some of these could be static, but for now it seems simpler to keep them all together here
public function _api_query($iSQL) { global $dbgSQL;
$this->sql = $iSQL; $dbgSQL = $iSQL; if (KF_USE_MYSQL) {
return mysql_query($iSQL);
} if (KF_USE_MYSQLI) {
$this->Conn->real_query($iSQL); return $this->Conn->store_result();
} if (KF_USE_DBX) {
return dbx_query($this->Conn,$iSQL,DBX_RESULT_ASSOC);
}
} public function _api_rows_rewind($iRes) { if (KF_USE_MYSQL) {
mysql_data_seek($iRes, 0);
} } public function _api_fetch_row($iRes) { // ACTION: Fetch the first/next row of data from a result set if (KF_USE_MYSQL) {
assert('is_resource($iRes)'); return mysql_fetch_assoc($iRes);
} if (KF_USE_MYSQLI) {
return $iRes->fetch_assoc();
} } public function _api_count_rows($iRes) { // ACTION: Return the number of rows in the result set if (KF_USE_MYSQL) {
if ($iRes === FALSE) { return FALSE; } else { assert('is_resource($iRes)'); return mysql_num_rows($iRes); }
} if (KF_USE_MYSQLI) {
return $iRes->num_rows;
} } public function _api_row_filled($iRow) { if (KF_USE_MYSQL) {
return ($iRow !== FALSE) ;
} }
/* === OBJECT FACTORY === */
public function DataSet($iSQL = NULL,$iClass = NULL) { CallEnter($this,__LINE__,__CLASS__.'.DataSet("'.$iSQL.'","'.$iClass.'")'); if (is_string($iClass)) {
$objData = new $iClass($this); assert('is_object($objData)'); if (!($objData instanceof clsDataSet)) { LogError($iClass.' is not a clsDataSet subclass.'); }
} else {
$objData = new clsDataSet($this); assert('is_object($objData)');
} if (!is_null($iSQL)) { if (is_object($objData)) {
$objData->Query($iSQL);
} } CallExit(__CLASS__.'.DataSet()'); return $objData; }
}
class clsDataSet {
protected $objDB; public $Res; // native result set public $Row; // data from the active row
public function __construct(clsDatabase $iDB=NULL, $iRes=NULL, array $iRow=NULL) { CallEnter($this,__LINE__,__CLASS__.'.'.__FUNCTION__.'(['.get_class($iDB).'])'); $this->objDB = $iDB; $this->Res = $iRes; $this->Row = $iRow; CallExit(__CLASS__.'.'.__FUNCTION__.'()'); }
// -- loading and navigating through a data set
public function Query($iSQL) { global $sql;
CallEnter($this,__LINE__,__CLASS__.'.'.__FUNCTION__.'('.$iSQL.')'); $sql = $iSQL; $this->Res = $this->objDB->_api_query($iSQL); //$sqlEsc = $this->objDB->SafeParam($sql); assert('is_resource($this->Res)'); // && ('$sqlEsc' != )");
// $this->NextRow(); // load the first row without wasting time rewinding
CallExit(__CLASS__.'.'.__FUNCTION__.'()'); } public function hasRows() {
// RETURNS: # of rows iff result has rows, otherwise FALSE
$rows = $this->objDB->_api_count_rows($this->Res); if ($rows === FALSE) {
return FALSE;
} elseif ($rows == 0) {
return FALSE;
} else {
return $rows;
} } public function hasRow() { return $this->objDB->_api_row_filled($this->Row); } public function RowCount() { return $this->objDB->_api_count_rows($this->Res); } public function FirstRow() { if ($this->hasRows()) {
$this->objDB->_api_rows_rewind($this->Res);
$this->NextRow(); // get the first row of data } } public function NextRow() {
/* ACTION: Fetch the next row of data into $this->Row.
If no data has been fetched yet, then fetch the first row. RETURN: TRUE if row was fetched; FALSE if there were no more rows or the row could not be fetched.
- /
$this->Row = $this->objDB->_api_fetch_row($this->Res); return $this->hasRow(); }
// -- accessing individual fields
public function __set($iName, $iValue) { $this->Row[$iName] = $iValue; } public function __get($iName) { return $this->Row[$iName]; }
}
/* class clsDataLine extends clsDataSet { }
- /
/*============= | NAME: clsTable | PURPOSE: objects for operating on particular tables
- /
class clsTable {
protected $objDB; protected $vTblName; protected $vKeyName; protected $vSngClass; // name of singular class
public function __construct($iDB,$iTblName,$iKeyName,$iSngClass=NULL) {
$this->objDB = $iDB; $this->vTblName = $iTblName; $this->vKeyName = $iKeyName; $this->vSngClass = $iSngClass;
} public function Name() {
return $this->vTblName;
} public function SingularName(string $iName=NULL) {
if (!is_null($iName)) { $this->vSngClass = $iName; } return $this->vSngClass;
} public function GetItem($iID,$iClass=NULL) {
/* $sql = 'SELECT * FROM `'.$this->vTblName.'` WHERE '.$this->vKeyName.'='.$iID; if (is_null($iClass)) { $strClass = $this->vSngClass; } else { $strClass = $iClass; } return $this->objDB->DataSet($sql,$strClass);
- /
$objItem = $this->GetData($this->vKeyName.'='.$iID,$iClass); $objItem->NextRow(); return $objItem;
} public function GetData($iWhere,$iClass=NULL,$iSort=NULL) {
global $sql; // for debugging
CallEnter($this,__LINE__,__CLASS__.'.'.__METHOD__.'("'.$iWhere.'","'.$iClass.'")'); $sql = 'SELECT * FROM `'.$this->vTblName.'` WHERE '.$iWhere; if (!is_null($iSort)) { $sql .= ' ORDER BY '.$iSort; } if (is_null($iClass)) { $strClass = $this->vSngClass; } else { $strClass = $iClass; } CallExit('GetData() - SQL: '.$sql); return $this->objDB->DataSet($sql,$strClass);
}
}
// OLDER CLASSES -- DEPRECATED /* class clsData {
protected $objDB; // clsDatabase public function __construct($iDB) { $this->objDB = $iDB; $this->objDB->Open(); assert (isset($this->objDB)); } public function DB() { CallStep('('.get_class($this).')clsDataTable.DB()'); return $this->objDB; }
}
class clsDataQuery extends clsData {
protected $sqlSelect;
public function __construct($iDB,$iSQL) { CallEnter($this,__LINE__,'clsDataQuery('.get_class($iDB).','.$iSQL.')'); parent::__construct($iDB); $this->sqlSelect = $iSQL; CallExit('clsDataQuery()'); } public function __destruct() { $this->objDB->Shut(); } protected function _newItem() { return new clsDataItem_noID($this); } public function GetData() { $objItem = $this->_newItem(); $objItem->Query($this->sqlSelect); return $objItem; }
}
class clsDataTable_noID extends clsData {
protected $strName; public function __construct($iDB,$iName) { CallEnter($this,__LINE__,'clsDataTable_noID('.get_class($iDB).','.$iName.')'); parent::__construct($iDB); $this->strName = $iName; CallExit('clsDataTable_noID()'); } public function __destruct() { $this->objDB->Shut(); } protected function _newItem() { CallStep('('.get_class($this).')'.__CLASS__.'.'.__FUNCTION__.'()'); return new clsDataItem_noID($this); } public function Name() { return $this->strName; } public function GetData($iFilt=,$iSort=) { CallEnter($this,__LINE__,'clsDataTable.GetData(filt="'.$iFilt.'",sort="'.$iSort.'")'); $sql = 'SELECT * FROM '.$this->strName; if ($iFilt != ) { $sql .= ' WHERE ('.$iFilt.')'; } if ($iSort != ) { $sql .= ' ORDER BY '.$iSort; } CallStep('line '.__LINE__.' ['.get_class($this).'] SQL = ['.$sql.']'); $objItem = $this->_newItem(); $objItem->Query($sql); CallExit('clsDataTable.GetData() -> '.get_class($objItem)); return $objItem; }
} // // clsDataTable_noID with functions based on autonumbered ID field // class clsDataTable extends clsDataTable_noID {
protected $strIDname; // name of unique ID field
public function __construct($iDB,$iName,$iIDname='ID') { CallEnter($this,__LINE__,'clsDataTable('.get_class($iDB).','.$iName.')'); parent::__construct($iDB,$iName); $this->strIDname = $iIDname; CallExit('clsDataTable()'); } public function NameOfID() { return $this->strIDname; }
public function IDName() {
return $this->strIDname;
}
protected function _newItem() { CallStep('('.get_class($this).')clsDataTable._newItem()'); return new clsDataItem($this); } public function GetItem($iID) { CallEnter($this,__LINE__,'clsDataTable.GetItem('.$iID.')'); $sql = 'SELECT * FROM '.$this->strName.' WHERE '.$this->strIDname.'="'.$iID.'"'; CallStep('SQL = ['.$sql.']');
// $objQry = dbx_query($this->objDB,$sql,DBX_RESULT_ASSOC);
$objItem = $this->_newItem(); $objItem->Query($sql);
// $objItem->Eat($objQry);
CallExit('clsDataTable.GetItem('.$iID.') -> '.get_class($objItem)); return $objItem; }
}
class clsDataItem_noID {
public $Res; // result set public $Row; // first (presumably the *only*) row data public $Table; // clsDataTable object public function __construct($iTable=NULL) { CallEnter($this,__LINE__,'clsDataItem_noID('.get_class($iTable).')'); $this->Init($iTable); CallExit('clsDataItem_noID()'); } public function Init($iTable=NULL) { CallEnter($this,__LINE__,'clsDataItem_noID.Init('.get_class($iTable).')'); $this->Table = $iTable;
// this works for mysql only: // fetch the first row of the results as an associative array: // $this->Row = $iResults->fetch_assoc();
CallExit('clsDataItem_noID.Init()'); } public function isResOk() {
if (KF_USE_MYSQL) { return $this->Res !== FALSE; }
} protected function LoadResults() {
// USAGE: Abstract
CallStep('('.get_class($this).')clsDataItem_noID.LoadResults()'); } public function GetValue($iName) {
// this works for mysql only
CallEnter($this,__LINE__,'clsDataTable_noID.GetValue('.$iName.')');
// DumpArray($this->Row);
$val = $this->Row[$iName]; CallExit('clsDataTable_noID.GetValue('.$iName.') -> ['.$val.']'); return $val; } public function RowCount() {
if (KF_USE_MYSQL) {
if (is_resource($this->Res)) { $cntRows = mysql_num_rows($this->Res); } else { echo '
BAD RESOURCE in '.get_class($this).'.RowCount()'; }
} if (KF_USE_MYSQLI) {
$cntRows = $this->Res->num_rows;
}
CallStep('('.get_class($this).')clsDataItem_noID.RowCount() -> '.$cntRows); return $cntRows; } public function HasData() { return (is_array($this->Row)); } public function HasRows($iMin=1) { if ($this->HasData()) { assert('is_resource($this->Res)'); return ($this->RowCount() >= $iMin); } else { return 0; } } private function FirstRow() { CallEnter($this,__LINE__,'clsDataItem_noID.FirstRow()'); if (is_resource($this->Res)) { if ($this->RowCount()) { $this->NextRow(); // get the first row of data } } CallExit('('.get_class($this).')clsDataItem_noID.FirstRow()'); } public function NextRow() {
// this works for mysql only: // fetch the NEXT row of the results as an associative array: if (KF_USE_MYSQL) {
$this->Row = mysql_fetch_assoc($this->Res);
} if (KF_USE_MYSQLI) {
$this->Row = $this->Res->fetch_assoc();
}
if (is_array($this->Row)) { $this->LoadResults(); } }
} // // clsDataItem with functions based on autonumbered ID field // class clsDataItem extends clsDataItem_noID {
public $ID; protected $strIDname; // name of unique ID field
//TO DO: set $strIDname at construct time; use it instead of hard-coded "ID" // or maybe clsTitleExt should descend from clsDataItem_noID? Or need clsTitleIttyp class instead?
protected function LoadResults() {
// USAGE: Descendants do not have to call this function
CallEnter($this,__LINE__,'clsDataItem.LoadResults()'); assert($this->Table); $strID = $this->Table->NameOfID(); assert(($strID != ) && ($strID != FALSE)); $this->ID = $this->GetValue($strID); assert($this->ID);
if (KDO_DEBUG) {
if (!isset($this->ID)) { echo '
TABLE: ['.$this->Table->Name().'] ID name: ['.$strID.']
'; }
}
CallExit('clsDataItem.LoadResults()'); }
}
- /
/* ========================
*** UTILITY FUNCTIONS ***
- /
function Pluralize($iQty,$iSingular=,$iPlural='s') { if ($iQty == 1) { return $iSingular; } else { return $iPlural; } } /* ========================
*** DEBUGGING FUNCTIONS ***
- /
// these could later be expanded to create a call-path for errors, etc.
function CallEnter($iObj,$iLine,$iName) {
global $intCallDepth, $debug; if (KDO_DEBUG_STACK) { $strDescr = ' line '.$iLine.' ('.get_class($iObj).')'.$iName; _debugLine('enter','>',$strDescr); $intCallDepth++; _debugDump(); }
} function CallExit($iName) {
global $intCallDepth, $debug; if (KDO_DEBUG_STACK) { $intCallDepth--; _debugLine('exit','<',$iName); _debugDump(); }
} function CallStep($iDescr) {
global $intCallDepth, $debug; if (KDO_DEBUG_STACK) { _debugLine('step',':',$iDescr); _debugDump(); }
} function LogError($iDescr) {
global $intCallDepth, $debug;
if (KDO_DEBUG_STACK) { _debugLine('error',':',$iDescr); _debugDump(); }
} function _debugLine($iType,$iSfx,$iText) {
global $intCallDepth, $debug;
if (KDO_DEBUG_HTML) { $debug .= ''.str_repeat('—',$intCallDepth).$iSfx.' '.$iText.'
'; } else { $debug .= str_repeat('*',$intCallDepth).'++ '.$iText."\n"; }
} function _debugDump() {
global $debug;
if (KDO_DEBUG_IMMED) {
DoDebugStyle(); echo $debug; $debug = ;
}
} function DumpArray($iArr) {
global $intCallDepth, $debug;
if (KDO_DEBUG) { while (list($key, $val) = each($iArr)) { if (KS_DEBUG_HTML) { $debug .= '
'.str_repeat('-- ',$intCallDepth+1).''; $debug .= " $key => $val"; $debug .= ''; } else { $debug .= "/ $key => $val /"; } if (KDO_DEBUG_IMMED) { DoDebugStyle(); echo $debug; $debug = ; } } }
} function DumpValue($iName,$iVal) {
global $intCallDepth, $debug;
if (KDO_DEBUG) { if (KS_DEBUG_HTML) { $debug .= '
'.str_repeat('-- ',$intCallDepth+1); $debug .= " $iName: [$iVal]"; $debug .= ''; } else { $debug .= "/ $iName => $iVal /"; } if (KDO_DEBUG_IMMED) { DoDebugStyle(); echo $debug; $debug = ; } }
} function DoDebugStyle() {
static $isStyleDone = false;
if (!$isStyleDone) { echo '<style type="text/css"></style>'; $isStyleDone = true; }
} </php>