一、什麼是DB類 我們首先簡單地了解一下DB類。DB類是PEAR中進行數據操作的幾個類的集合,它的主要目的是提供一個統一的,抽象的數據接口,這個接口與後端的 數據庫 是無關的。因此,如果你的應用程序使用這個通用的接口來進行數據庫的操作,那麼就能夠平滑地
一、什麼是DB類 我們首先簡單地了解一下DB類。DB類是PEAR中進行數據操作的幾個類的集合,它的主要目的是提供一個統一的,抽象的數據接口,這個接口與後端的
數據庫是無關的。因此,如果你的應用程序使用這個通用的接口來進行數據庫的操作,那麼就能夠平滑地切換到不同的數據庫下面,如MYSQL,SQL,SYBASE等等。實際上,DB類希望能夠起到簡單的類似ODBC或者是PERL中的DBI的作用。說到這裡,不得不提一下
PHP中的另一個優秀的庫:ADODB。ADODB也和DB一樣,提供了一個抽象的中間層,而且ADODB所支持的後端數據庫要比DB多(至少目前如此),不過ADODB沒有直接使用PEAR的一些特性,只是吸取了PEAR的許多思想,包括DB,因此二者的使用方法有許多相似的地方。我不想評論二者孰優孰劣,大家可以根據個人的喜好來使用。
二、為什麼要設計抽象的中間數據層 在詳細討論DB的使用之前,我們先討論一下為什麼要設計中間的數據層,因為這意味著你需要作出一些犧牲和讓步,比如,你需要多寫一些代碼,有的局限於特定數據庫的特性將無法直接使用。
我們回憶一下我們過去的做法,如何連接到MYSQL數據庫?這的確是個小兒科的問題,下面的代碼你一定很熟悉:
/**
* 連接到MYSQL數據庫
*/
$host = "localhost";
$user = "root";
$passwd = "";
$persistent = 1;
if ($persisternt){
$conn = mysql_connect($host,$user,$passwd);
}else {
$conn = mysql_pconnect($host,$user,$passwd);
}
?>
好了,現在建立了數據庫連接,我們可以使用它來進行數據庫的操作,我們可能使用類似的代碼:
function sql_exec($sql) {
global $db_Name;
$result = mysql_db_query($db_dbName,$sql);
if (!$result) {
echo mysql_errno(). ": ".mysql_error(). "
$sql
";
exit();
}
return $result;
}
$db_Name = "test";
$sql = "select * from users";
$result = sql_exec($sql);
while( $row = mysql_fetch_row($result) ){
echo "姓名:$row[0] 性別:$row[1] 年齡 $row[2]
";
}
mysql_free_result($result);
?>
看起來很不錯,是嗎?你可能在你的代碼裡使用很多類似的代碼片段。但是,不要太高興,問題來了。假如,突然,你的數據庫需要從MYSQL遷移到別的數據庫平台,比如ORACLE,SYBASE。遷移的原因很多,也許是你的老板突發奇想,認為這樣能賣個好價錢,或者是你的數據猛增,導致MYSQL的
性能下降,總之,遷移是事在必行了。你怎麼做,你也許會想,呵呵,這簡單,把相關的函數替換一下不就行了。
聽起來簡單,但是……首先,連接數據庫的函數要改,需要把mysql_connect和mysql_pconnect替換成OCILogon和OCIPLogon。mysql_errno和mysql_error()當然不能使用,你需要從OCIError()返回的數組中提取響應的信息。
這還不是太糟,最糟的是相關的mysql_fetch_row,mysql_fetch_array等語句遍布於你的許多代碼函數和過程中,你需要逐一查找,分析,然後重新替換或者編寫相應的ORACLE的版本。如果,你的數據庫操作是集中在一個某一個模塊或類中,這項工作還可以接受,否則,等於你重新閱讀和修改了絕大部分的代碼。即使這個不幸的人不是你,那麼他也會暗地裡詛咒你的;=)
以上,我們回憶了我們以前的做法,以及可能帶來的不幸。那麼,如果使用DB類來做類似的操作,應該是什麼樣的呢?下面是相應的DB版本代碼:
include_once "DB.php";
/*
* 連接到數據庫
*/
$db_host = "localhost";
$db_user = "root";
$db_passwd = "";
$db_dbName = "test";
$PersistentConnection = 1 ;
$db_type ="mysql";
$db_proto ="";
$db = DB::connect("$db_type://$db_user@$db_passwd:$db_host/$db_dbName",$db_options);
if( DB::isError($db) ){
die "無法連接數據庫,錯誤原因:".DB::errorMessage($db);
}
function sql_exec($sql) {
global $db;
$result = $db->query($sql);
if (DB::isError($result)){
echo "發生數據庫錯誤:".DB::errorMessage($result);
exit();
}
return $result;
}
/////////////////////////////////////////////
$sql = "select * from users";
$result = sql_exec($sql);
while( $row = $result->fetchRow() ){
echo "姓名:$row[0] 性別:$row[1] 年齡 $row[2]
";
}
?>
除了連接數據庫部分,其他的看起來只是有一些微小的變化,出錯處理使用的是PEAR類似的方式(isError),實際上也是從PEAR繼承來的。同樣的情況,如果你要把數據庫從mysql遷移到別的形式,這次假如說是PostegreSQL,一個
LINUX中很優秀的數據庫,你所做的只是改變一行代碼:
$db_type ="mysql";
變成:
$db_type ="pgsql";
其他的,不用變動。怎麼樣,升級的感覺是不是很清爽呢,你可以用剩下的時間好好研讀其余的代碼,或者和我繼續往下討論DB的使用方法。
三、 DB的使用入門 DB類由3部分組成:
DB.php 這是前端接口,在DB類裡提供了許多"靜態"的公用方法,我們一般只需要INCLUDE_ONCE這個文件就可以了。
DB/common.php 這是後端數據庫的通用抽象類,不同的數據庫的後端類需要繼承並實現這個類中定義的公用方法和屬性,如果你的數據庫不被支持,你可以自己編寫一個支持類,這樣,你的應用程序就可以遷移過來了。
DB/storage.php 這是一個輔助的工具,它可以把SQL查詢做為對象返回,同時能夠維護這些對象,在對象改變的時候,相應地更新數據庫。
DB/ifx.php
mssql.php Ms
SQL Server支持類
oci8.php Orcale 8i支持類
pgsql.php PostegreSQL支持類
sybase.php Sybase支持類
ibase.php ibase支持類
msql.php mSQL 支持類
mysql.php mysql支持類
odbc.php odbc 支持類
這些是相應後端數據庫的支持類了。相應具體的數據庫的操作是由這些支持類來實現的。
下面,我們首先詳細介紹DB.PHP中的一些"靜態"方法:
connect()方法
這個方法是最重要的靜態方法了,我們通過得到一個DB_COMMON對象,並且連接到相應的數據庫。這個方法的原型如下:
function &connect($dsn, $options = false)
$dsn是數據源名稱(data source name)的縮寫,可以是字符串,或者是特定的數組形式。
一般來說,$dsn是一個字符串,它的格式如下:
phptype(dbsyntax)://username:password@protocol+hostspec/database
* phptype: php後端數據庫的類型名稱(如mysql, odbc 等等.)
* dbsyntax: 數據庫所使用的SQL語法標准,一般不用。
* protocol: 使用的通訊協議。(如tcp, unix 等等.)
* hostspec: 數據庫所在的主機的描述。(形式是:主機名[:端口號])
* database: 數據庫的名稱。
* username: 登陸的用戶名。
* password: 登陸的密碼。
對於DSN,常用的形式如下:
* phptype://username:password@protocol+hostspec:110//usr/db_file.db
* phptype://username:password@hostspec/database_name
* phptype://username:password@hostspec
* phptype://username@hostspec
* phptype://hostspec/database
* phptype://hostspec
* phptype(dbsyntax)
* phptype
對於省略的部分,將使用缺省值。
當然,$dsn也可以是一個數組,數組的形式如下:
$dsn = array(
'phptype' => 'mysql',
'dbsyntax' => ',
'protocol' => ',
'hostspec' => 'localhost',
'database' => 'test',
'username' => 'root',
'password' => '
)
$options 是數據庫的選項,混合型。如果是布爾型,那麼一般來說,這個參數指明是否使用持久性連接(persistent connect),如果後端數據庫支持,當$options是TRUE的時候,將使用持久性連接。如果是數組,那麼表示這是特定的後端數據庫的選項,這些選項將傳遞到DB_common類中的set_option方法中,後端數據庫通過實現或重載這個方法,可以自己決定如何使用這些選項。
isError($value)
這個方法用來判斷DB的一些方法返回的結果是否是一個錯誤對象,你可以使用這個方法來判定某個操作的結果是否是拋出了異常。
當然,如果你的應用程序從是PEAR繼承的,也可以直接使用PEAR的isError來判斷,尤其是當你的程序中拋出的異常的可能是數據庫以外的異常的時候,這個方法只能判斷是否是DB_Errro對象,其他的PEAR_Error對象是無法識別出的。
function isWarning($value)
這個方法是判斷DB方法的返回結果是否是一個警告。和錯誤不同的是,警告不是致命的,所以仍可以繼續執行下去。
function errorMessage($value)
一旦確定出現了錯誤,那麼可以使用這個方法來取得相應的錯誤信息,下面是DB中的預定義的錯誤信息:
DB_ERROR => "unknown error",
DB_ERROR_A
LREADY_EXISTS => "a
lready exists",
DB_ERROR_CANNOT_CREATE => "can not create",
DB_ERROR_CANNOT_DELETE => "can not delete",
DB_ERROR_CANNOT_DROP => "can not drop",
DB_ERROR_CONSTRAINT => "constraint violation",
DB_ERROR_DIVZERO => "division by zero",
DB_ERROR_INVALID => "invalid",
DB_ERROR_INVALID_DATE => "invalid date or time",
DB_ERROR_INVALID_NUMBER => "invalid number",
DB_ERROR_
MISMATCH => "mismatch",
DB_ERROR_NODBSELECTED => "no database selected",
DB_ERROR_NOSUCHFIELD => "no such field",
DB_ERROR_NOSUCHTABLE => "no such table",
DB_ERROR_NOT_CAPABLE => "DB backend not capable",
DB_ERROR_NOT_FOUND => "not found",
DB_ERROR_NOT_LOCKED => "not locked",
DB_ERROR_SYNTAX => "syntax error",
DB_ERROR_UNSUPPORTED => "not supported",
DB_ERROR_VALUE_COUNT_ON_ROW => "value count on row",
DB_OK => "no error",
DB_WARNING => "unknown warning",
DB_WARNING_READ_ONLY => "read only"
assertExtension($name)
動態載入PHP的數據庫擴展。$name是你的PHP擴展的名稱,不包含擴展名(如.dll,.so)。
當你需要讓PHP載入某個數據庫的擴展,但是你沒有權限修改php.ini的時候,可以使用這個函數。
當然,DB內部也是自動調用這個方法來載入所需的PHP數據庫的擴展。
比如:你如果需要加載oracle的擴展,那麼可以這樣使用:
include_once "DB.php";
if ( DB::assertExtension("php_oci8") ){
echo "oracle 8擴展加載成功!";
}else {
die "無法加載oracle 8擴展";
}
?>
以上是在DB類中定義的"靜態"方法,所謂靜態方法,是指你可以不需要構建對象,就可以直接使用,並且只要你指明DB::,你可以在任何地方直接調用這些方法。
在DB.php中,除了DB外,也有幾個非常重要的類,在後端數據庫中也使用了這些類:
DB_Error
這個類是從PEAR_Error繼承來的,在數據庫操作中,對於出現的致命的錯誤,一般會拋出這個錯誤。
構建函數:
function DB_Error($code = DB_ERROR, $mode = PEAR_ERROR_RETURN, $level = E_USER_NOTICE, $de
buginfo = null)
$code是DB錯誤代碼,$mode決定錯誤處理的模式,缺省是返回,$level 錯誤級別,
$debuginfo是附加的調式信息,如剛剛執行的SQL語句等等。
DB_Warning
類似DB_Error。
DB_result
這是非常重要的類。
當執行相應的SQL查詢後,後端的數據庫類將返回一個DB_result的對象實例,你可以使用這個類的方法來獲得查詢結果的數據。這個類實際上是後端數據庫結果集的包裝,這裡說的方法,在實際運行中是直接調用了後端數據庫的同名的方法,不過,對於我們來說,使用這個包裝類會感覺更自然一些。
function fetchRow($fetchmode = DB_FETCHMODE_DEFAULT)
取得一行數據,$fetchmod是獲取數據的模式,如果沒有指定,那麼使用缺省方式。其余的方式我們在後面討論DB_Common接口的時候會詳細討論。
function fetchInto(&$arr, $fetchmode = DB_FETCHMODE_DEFAULT)
取得一行數據,同時追加到給定的$att數組中。$att是要追加到的數組,它是按照引用方式傳遞的。
function numCols()
取得結果集的列數。如果出錯,返回DB_Error對象。
function numRows()
取得結果集的行數。如果出錯,返回DB_Error對象。
function free()
釋放結果集所占用的資源。
至此,我們已經學習了DB基本的使用方法,下面我們簡單復習一下,對於其中沒有見到的方法,我們在後面會詳細介紹:
連接數據庫:
$db = DB::connect("$db_type://$db_user@$db_passwd:$db_host/$db_dbName",$db_options);
if (DB::isError($db)){
echo "數據庫連接錯誤:".DB::errorMessage($db)."
";
exit();
}
/* 查詢1 */
$sql = "select * from user_log";
$result = $db->query($sql);
if (DB::isError($result) ){
echo "數據庫錯誤:".DB::errorMessage($result);
exit();
}
$colCount = $result->numCols();
echo "共找到".$result->numRows()."位用戶";
while($row = $result->fetch()){
echo "
";
for($i=0;$i<$colCount;$i++){
echo "".$row[$]."
";
}
echo "
";
}
$result->free();
$db->disconnect();
?>
四、 DB_Common 使用參考 DB_Common類是一個通用的接口,DB跨數據庫平台的能力是通過實現這個接口來做到的。如果你的數據庫不在DB支持之列,你可以自己編寫一個類繼承DB_Common類,實現這些接口的函數。
function toString()
返回當前類的字符串描述,格式是類名:(phptype="",dbsyntax="")[connected],一般是類似於:
DB_mysql:(phptype=mysql,dbsyntax=)[connected]
function quoteString($string)
圈引一個字符串,使之在查詢中能夠
安全地放在單引號的分界符之間。一般對於字符型的字段,我們查詢的時候使用'作為分隔符,因此如果字符串中有'則會出錯,這個函數把字符串中的'替換成\'.
function provides($feature)
指明當前DB的後端程序是否實現了給定的特性,返回布爾值。$feature是功能的明稱,一般是:"prepare","pconnect","transactions".在後端程序的構建函數中應該設置這些值。例子:
if($db->provider("transactions")){
//支持事務
}else {
//不支持事務
}
function errorCode($nativecode)
用於將後端數據庫產生的錯誤代碼映射到DB的通用錯誤代碼中去。內部調用。後端數據庫在構建函數中應該初始化$errorcode_map屬性。例子(mysql):
//在DB_mysql的構建函數中
$this->errorcode_map = array(
1004 => DB_ERROR_CANNOT_CREATE,
1005 => DB_ERROR_CANNOT_CREATE,
1006 => DB_ERROR_CANNOT_CREATE,
1007 => DB_ERROR_ALREADY_EXISTS,
1008 => DB_ERROR_CANNOT_DROP,
1046 => DB_ERROR_NODBSELECTED,
1050 => DB_ERROR_ALREADY_EXISTS,
1051 => DB_ERROR_NOSUCHTABLE,
1054 => DB_ERROR_NOSUCHFIELD,
1062 => DB_ERROR_ALREADY_EXISTS,
1064 => DB_ERROR_SYNTAX,
1100 => DB_ERROR_NOT_LOCKED,
1136 => DB_ERROR_VALUE_COUNT_ON_ROW,
1146 => DB_ERROR_NOSUCHTABLE,
);
function errorMessage($dbcode)
返回DB錯誤文本信息。
function &raiseError($code = DB_ERROR, $mode = false, $level = false,$debuginfo = false, $nativecode = false)
拋出一個錯誤。這個函數由DB來調用。
function setFetchMode($fetchmode)
設置缺省的fetch模式。
fetchmod有以下幾種:
DB_FETCHMODE_DEFAULT :使用缺省的模式
DB_FETCHMODE_ORDERED :每條記錄的數據列使用數字索引,從0開始
DB_FETCHMODE_ASSOC :每條記錄的數據列使用字段名索引,同查詢中的字段名一致
DB_FETCHMODE_FLIPPED:如果結果集是多維的,多條記錄多個字段,一般來說返回一個2維數組,第一維是記錄號,標明是第幾條記錄,第2維的數組則使用字段名或數字索引。DB_FETCHMODE_FLIPPED則會交換這個順序,也就是說,第一層是字段名,第2維才是記錄號.
function setOption($option, $value)
設置後端數據庫選項。$options,$value分別是選項名和相應的值。
一般不用直接調用,在DB_Common及其子類的構建函數中會調用這個函數。
function getOption($option)
取得某個option的值
function prepare($query)
為execute()准備編譯一個查詢。對於某些後端數據庫,這是通過仿真來實現的。返回編譯後的查詢句柄,出錯則返回一個DB_Error對象。
function execute($stmt, $data = false)
執行編譯後的查詢。$stmt是由prepare返回的句柄。$data是參數數據,如果你使用的是參數查詢,$data將要包含每個?參數的值
例子: /**
* 下面是執行一個刪除查詢,從文章表中將指定用戶的文章記錄刪除
*/
$sql = "delete from artilce where article.userid =$userid";
$qh = $db->prepare($sql);
if(DB::isError($qh)){
return $qh;
}
$result = $db->execute($qh);
return $result;
?>
function executeEmulateQuery($stmt, $data = false)
返回一個實際的查詢字符串,供仿真prepare,execute的時候使用,內部函數。如果出錯,則返回DB_Error對象
function executeMultiple( $stmt, &$data )
在同一個查詢句柄上執行多個查詢。$data必需是一個從0開始的數組,這個函數將依次使用數組中的每一行數據來調用execute。這個函數一般用於參數查詢。你可執行一次查詢的編譯,然後將不同的參數值放入$data數組,然後就可一次同時執行查詢了。需要注意,如果中間某個查詢出錯,剩余的查詢不會繼續進行而是返回錯誤。
function modifyQuery($query)
內部函數,用於後端數據庫修正查詢,後端數據庫實現這個函數,然後返回進行優化和修正後查詢串。例子:這是DB_mysql的實現,
function modifyQuery($query)
{
if ($this->options['optimize'] == 'portability') {
// "DELETE FROM table" gives 0 affected rows in
MySQL.
// This little hack lets you know how many rows were deleted.
if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) {
$query = preg_replace('/^\s*DELETE\s+FROM\s+(\S+)\s*$/',
'DELETE FROM \1 WHERE 1=1', $query);
}
}
return $query;
}
?>
function &query($query)
執行一個查詢,查詢成功,如果是有結果集的查詢,則回一個DB_Result對象,如果沒有結果集的查詢則返回DB_OK。
出錯則返回DB_Error對象。
function &getOne($query, $params = array())
執行查詢,並返回結果集中第一行第一列的數據。$params是參數值,如果後端數據庫支持,將調用prepare/execute來使用這些參數。
例子:
$sql = "select id,date from article where userid= $userid orderby date";
$last = $db->getOne($sql);
if(DB::isError($last)){
echo "出錯:".DB::errorMessage($last);
}
echo "id:".$last;
?>
function &getRow($query, $fetchmode = DB_FETCHMODE_DEFAULT, $params = array())
執行查詢,請返回結果集的第一條記錄。
$fetchmod 指定fetch模式,如果省略,使用缺省模式。
例子:
$sql = "select * from articles order by date desc";
$row = $db->getRow($sql);
if(DB::isError($row)){
echo "出錯:".DB::errorMessage($row);
}
for($i=0;$i
echo "$row[$i]";
}
?>
function &getCol($query, $col = 0, $params = array())
執行查詢,並返回包含結果集中指定字段列的值的數組。
$col是要返回的列的索引,可以是整數,或者是關鍵字段名。
例子:
$sql = "select id,userid,date from articles order by date desc limit 100";
//只返回用戶列表
$row = $db->getCol($sql,1);
if(DB::isError($row)){
echo "出錯:".DB::errorMessage($row);
}
for($i=0;$i echo "$row[$i]";
}
?>
function &getAssoc($query, $force_array = false, $params = array())
執行查詢,並返回一個關聯數組。
$force_arry 強制返回數組。如果true,那麼即使你的結果集裡只有2個字段,那麼關鍵字段對應的值也是一個只有一個元素的
數組。如果false,那麼關鍵字段對應的值是一個標量了。
注意,這個關聯數組有些特別:
如果你查詢的是"select userid,name,reg_date from user",記錄集是:
userid name reg_date
test testor 2001-07-05
test2 teest2 2001-07-06
那麼返回的數組是這樣的:
array( 'test' => array('testor','2001-0705'),
'test2'=> array('teest2','2001-07-06'
)
例子:
$sql = "select userid,name,reg_date,last_login from users limit 100";
$userinfo = $db->getAssoc($sql);
if(DB::isError($userinfo)){
die "錯誤!".DB::errorMessage($userinfo);
}
if(empty($userinfo)){
echo "warning:NO users!";
return;
}
/*現在,userinfo裡面保存有用戶的信息*/
function getUserInfo($user_id='){
$info = $userinfo[$user_id];
if (empty($info){
echo "沒有這個用戶信息!";
}
return $info;
}
?>
function &getAll($query, $fetchmode = DB_FETCHMODE_DEFAULT, $params = array())
返回包含結果集中全部記錄的數組。注意,如果你的結果集很大,不要使用這個函數。
例子:
$sql ="select * from users limit 1000";
$list = $db->getAll($sql);
if(DB::isError($list)){
die "數據庫錯誤:".DB::errorMessage($list);
}
for ($i=0;$i $user = $list[$i];
echo "";
for($j=0;$j echo "".$user[$j]."";
}
echo "";
}
?>
function autoCommit($onoff=false)
指定是否自動提交事務。有的後端數據庫不支持。
function commit()
提交當前事務
function rollback()
回滾當前事務
function numRows($result)
返回結果集中的記錄數
function affectedRows()
返回上一次查詢操作的記錄數
function nextId($seq_name, $ondemand = true)
返回指定的sequence的下一個值
function createSequence($seq_name)
創建一個Sequence
function dropSequence($seq_name)
刪除一個Sequence
五、更進一步,創建你自己的中間數據庫應用層
到此,我們對於DB類的功能已經有了更深的了解。我們可以基於DB類來創建你自己的數據庫應用層了。也許你會問,我為什麼還要創建新的類,直接在我的應用程序中使用DB不好麼?答案是,當然可以,但是我不推薦。
首先,雖然DB的功能很強大,但是仍然是過於底層的,你的類應該是面向應用的。你的這個數據庫層應該屏蔽不需要使用的功能和函數,同時,也要提供一些更'高級'的方法,比如,你的應用程序不應該去聯接數據庫,釋放資源,這些應該是透明的。那麼這些工作就要由你的這個類來完成了。
其次,DB仍有一些缺陷,一旦找到比它更好的,你可以迅速地升級。你所要改的只是這個類的方法如何實現,你的應用程序的其他模塊不會為此受到影響。
在你設計自己的類的時候,希望能夠一些准則:
提供基本的自由的查詢接口。包括query,execute.分別對應有結果集和無結果集的查詢。
不要使用特定數據庫的某些特性,即使因為放棄使用這些特征會給你增加不少的代碼量。
盡量屏蔽一些數據庫的操作細節,比如連接數據庫,釋放資源等等。
六、 DB的不足
上面我們討論了DB的優點和一些使用的方法與技巧。但是,任何事物都不是十全十美的,DB類更是如此,由於DB乃至PEAR的開發時間並不長,因此DB並沒有最終全部完成,其中也或多或少地存在一些BUG或者設計上的問題,需要我們在使用中去發現和修正。
MYSQL的問題
問題1:前段我在開發一個項目中,發現DB的MYSQL類有一個問題,那就是當你connect的時候,MYSQL類自動將當前數據庫設置為$DSN中的數據庫名。以後使用query的時候,都是使用當前數據庫。下面是原來connect的代碼:
if ($dsninfo["database"]) {
if (!mysql_select_db($dsninfo["database"], $conn)) {
return $this->raiseError(); // XXX ERRORMSG
}
}
這導致了一個後來令我費解的問題:
我的項目需要我連接2個數據,假設分別是user和test。test是我的主要數據庫,但是我要從第一個數據庫中user中取得用戶信息,同時test中保存用戶的權限信息。我為此做了一個中間的類CV_DB,用來實現我的數據庫層。在CV_DB的創建函數中,我連接到指定的數據庫,從而我可以這樣使用:
$db1 = new CV("user");
$db2 = new CV("test");
後面當我執行,查詢某個用戶的信息的時候,出現了"該表不存在"的DB錯誤。但實際上這個表是存在的。最後,我發現原來是DB/mysql.php的問題,因為它的查詢使用的是,mysql_query,而不是mysql_db_query,當我連接到第2個數據庫的時候,MYSQL的當前數據庫變成了第2個,這時我再執行針對第一個數據庫的查詢,當然會出錯。
解決方式有2個,在創建CV的時候不連接到數據庫,查詢的時候連接,查詢完畢後斷開。或者,修改DB/Mysql.php。我選擇後者,我將上面幾行注釋,然後將SimpleQuery中的mysql_query 替換成mysql_db_query.
問題2:mysql沒有execute,因此它繼承使用了DB_common中模擬方式,但是不幸地是,這帶來了新的問題,在一些更新查詢中,所要更新的數據有? &這2個特殊字符的時候,會出現問題,因為prepare認為這是參數查詢的字符,將進行分析,導致無法更新數據。
解決方式也有2個:替換?和&,但是這樣要考慮的事情很多。或者:直接使用simpleQuery或者query。
我選擇後者,由於我的其他類只和CV――我這個中間數據庫應用類打交道,於是,我在CV的execute方法中做了判斷,如果是後端數據庫是mysql,那麼我直接調用simpleQuery,否則,調用後端數據庫的prepare和execute。這樣,實際的後端數據庫對於我項目中的其他應用類是透明的,我可以簡單地做相應的調整,這也是我設計數據庫應用層的初衷。
DB的開發狀態
DB目前仍在不斷地開發當中,在DB/下面有一個文件STATUS,它描述了DB類的功能和各個後端數據庫的實現情況,下面是PHP4.0.6這個發布中的開發情況:
"x" - 已經實現,但尚未測試implemented, but without tests
"t" - 已經實現,但是一個或多個測試沒有通過implemented, but one or more tests fail
"T" - 實現並通過全部測試implemented, passing all tests
"e" - 仿真實現,沒有測試emulated, without tests
"E" - 仿真實現,通過全部測試emulated, passing all tests
"n" - 返回 "not capable",沒有這個能力提供該項功能
"-" - 沒有實現
fbsql ifx mssql oci8 pgsql
FEATURE | ibase | msql | mysql | odbc | sybase
simpleQuery x x x x x T T x T x
numCols x x x x x T T x T x
numRows x n n x x T E n T n
errorNative x n x n n T x x T n
prepare/execute e x e e e E T e E e
sequences e n n n n E T n T n
affectedRows x n x n n T T n T n
fetch modes x x x x x T T x T x
fetch absolute rows x n x x x x n x x x
transactions x x n n n n x x x n
auto-commit x x n n n n x x x n
error mapping x - e - - T T x E -
tableInfo x n n n n T n n n n