PEAR:創(chuàng)建中間的數(shù)據(jù)庫應(yīng)用層1
發(fā)表時間:2024-06-19 來源:明輝站整理相關(guān)軟件相關(guān)文章人氣:
[摘要]內(nèi)容: 一、 什么是DB類 二、 為什么要設(shè)計抽象的中間數(shù)據(jù)層 三、 DB的使用入門 四、 DB_Common 使用參考 五、 更進一步,創(chuàng)建你自己的中間數(shù)據(jù)庫應(yīng)用層 六、 DB的不足 七、參考資源 關(guān)于作者 相關(guān)內(nèi)容: 1、用PEAR來寫你的下一個php程序 2、常用模塊 3、使用PHP...
內(nèi)容:
一、 什么是DB類
二、 為什么要設(shè)計抽象的中間數(shù)據(jù)層
三、 DB的使用入門
四、 DB_Common 使用參考
五、 更進一步,創(chuàng)建你自己的中間數(shù)據(jù)庫應(yīng)用層
六、 DB的不足
七、參考資源
關(guān)于作者
相關(guān)內(nèi)容:
1、用PEAR來寫你的下一個php程序
2、常用模塊
3、使用PHPDoc輕松建立你的PEAR文檔
潘凡 (
[email protected])
北京賽迪網(wǎng)信息技術(shù)有限公司
2001 年 8 月
對于PHP的應(yīng)用程序來說,90%以上需要和數(shù)據(jù)庫來打交道。那么,你是如何操縱數(shù)據(jù)庫的?當你的后端數(shù)據(jù)庫升級或變遷后,你的這些程序是否能夠隨之平滑地升級和掛接呢?如果你正在考慮這個問題,那么不妨和我來討論一下,如何使用PEAR中的DB類來創(chuàng)建與數(shù)據(jù)庫無關(guān)的數(shù)據(jù)庫應(yīng)用層。
一、 什么是DB類
我們首先簡單地了解一下DB類。DB類是PEAR中進行數(shù)據(jù)操作的幾個類的集合,它的主要目的是提供一個統(tǒng)一的,抽象的數(shù)據(jù)接口,這個接口與后端的數(shù)據(jù)庫是無關(guān)的。因此,如果你的應(yīng)用程序使用這個通用的接口來進行數(shù)據(jù)庫的操作,那么就能夠平滑地切換到不同的數(shù)據(jù)庫下面,如MYSQL,SQL,SYBASE等等。實際上,DB類希望能夠起到簡單的類似ODBC或者是PERL中的DBI的作用。說到這里,不得不提一下PHP中的另一個優(yōu)秀的庫:ADODB。ADODB也和DB一樣,提供了一個抽象的中間層,而且ADODB所支持的后端數(shù)據(jù)庫要比DB多(至少目前如此),不過ADODB沒有直接使用PEAR的一些特性,只是吸取了PEAR的許多思想,包括DB,因此二者的使用方法有許多相似的地方。我不想評論二者孰優(yōu)孰劣,大家可以根據(jù)個人的喜好來使用。
二、 為什么要設(shè)計抽象的中間數(shù)據(jù)層
在詳細討論DB的使用之前,我們先討論一下為什么要設(shè)計中間的數(shù)據(jù)層,因為這意味著你需要作出一些犧牲和讓步,比如,你需要多寫一些代碼,有的局限于特定數(shù)據(jù)庫的特性將無法直接使用。
我們回憶一下我們過去的做法,如何連接到MYSQL數(shù)據(jù)庫?這的確是個小兒科的問題,下面的代碼你一定很熟悉:
<?php
/**
* 連接到MYSQL數(shù)據(jù)庫
*/
$host = "localhost";
$user = "root";
$passwd = "";
$persistent = 1;
if ($persisternt){
$conn = mysql_connect($host,$user,$passwd);
}else {
$conn = mysql_pconnect($host,$user,$passwd);
}
?>
好了,現(xiàn)在建立了數(shù)據(jù)庫連接,我們可以使用它來進行數(shù)據(jù)庫的操作,我們可能使用類似的代碼:
<?php
function sql_exec($sql) {
global $db_Name;
$result = mysql_db_query($db_dbName,$sql);
if (!$result) {
echo mysql_errno(). ": ".mysql_error(). "<br>$sql<br>";
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]<br>";
}
mysql_free_result($result);
?>
看起來很不錯,是嗎?你可能在你的代碼里使用很多類似的代碼片段。但是,不要太高興,問題來了。假如,突然,你的數(shù)據(jù)庫需要從MYSQL遷移到別的數(shù)據(jù)庫平臺,比如ORACLE,SYBASE。遷移的原因很多,也許是你的老板突發(fā)奇想,認為這樣能賣個好價錢,或者是你的數(shù)據(jù)猛增,導致MYSQL的性能下降,總之,遷移是事在必行了。你怎么做,你也許會想,呵呵,這簡單,把相關(guān)的函數(shù)替換一下不就行了。
聽起來簡單,但是……首先,連接數(shù)據(jù)庫的函數(shù)要改,需要把mysql_connect和mysql_pconnect替換成OCILogon和OCIPLogon。mysql_errno和mysql_error()當然不能使用,你需要從OCIError()返回的數(shù)組中提取響應(yīng)的信息。
這還不是太糟,最糟的是相關(guān)的mysql_fetch_row,mysql_fetch_array等語句遍布于你的許多代碼函數(shù)和過程中,你需要逐一查找,分析,然后重新替換或者編寫相應(yīng)的ORACLE的版本。如果,你的數(shù)據(jù)庫操作是集中在一個某一個模塊或類中,這項工作還可以接受,否則,等于你重新閱讀和修改了絕大部分的代碼。即使這個不幸的人不是你,那么他也會暗地里詛咒你的;=)
以上,我們回憶了我們以前的做法,以及可能帶來的不幸。那么,如果使用DB類來做類似的操作,應(yīng)該是什么樣的呢?下面是相應(yīng)的DB版本代碼:
<?php
include_once "DB.php";
/*
* 連接到數(shù)據(jù)庫
*/
$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 "無法連接數(shù)據(jù)庫,錯誤原因:".DB::errorMessage($db);
}
function sql_exec($sql) {
global $db;
$result = $db->query($sql);
if (DB::isError($result)){
echo "發(fā)生數(shù)據(jù)庫錯誤:".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]<br>";
}
?>
除了連接數(shù)據(jù)庫部分,其他的看起來只是有一些微小的變化,出錯處理使用的是PEAR類似的方式(isError),實際上也是從PEAR繼承來的。同樣的情況,如果你要把數(shù)據(jù)庫從mysql遷移到別的形式,這次假如說是PostegreSQL,一個LINUX中很優(yōu)秀的數(shù)據(jù)庫,你所做的只是改變一行代碼:
$db_type ="mysql";
變成:
$db_type ="pgsql";
其他的,不用變動。怎么樣,升級的感覺是不是很清爽呢,你可以用剩下的時間好好研讀其余的代碼,或者和我繼續(xù)往下討論DB的使用方法。
三、 DB的使用入門
DB類由3部分組成:
DB.php 這是前端接口,在DB類里提供了許多"靜態(tài)"的公用方法,我們一般只需要INCLUDE_ONCE這個文件就可以了。
DB/common.php 這是后端數(shù)據(jù)庫的通用抽象類,不同的數(shù)據(jù)庫的后端類需要繼承并實現(xiàn)這個類中定義的公用方法和屬性,如果你的數(shù)據(jù)庫不被支持,你可以自己編寫一個支持類,這樣,你的應(yīng)用程序就可以遷移過來了。
DB/storage.php 這是一個輔助的工具,它可以把SQL查詢做為對象返回,同時能夠維護這些對象,在對象改變的時候,相應(yīng)地更新數(shù)據(jù)庫。
DB/ifx.php
mssql.phpMs SQL Server支持類
oci8.php Orcale 8i支持類
pgsql.phpPostegreSQL支持類
sybase.php Sybase支持類
ibase.phpibase支持類
msql.php mSQL 支持類
mysql.phpmysql支持類
odbc.php odbc 支持類
這些是相應(yīng)后端數(shù)據(jù)庫的支持類了。相應(yīng)具體的數(shù)據(jù)庫的操作是由這些支持類來實現(xiàn)的。
下面,我們首先詳細介紹DB.PHP中的一些"靜態(tài)"方法:
connect()方法
這個方法是最重要的靜態(tài)方法了,我們通過得到一個DB_COMMON對象,并且連接到相應(yīng)的數(shù)據(jù)庫。這個方法的原型如下:
function &connect($dsn, $options = false)
$dsn是數(shù)據(jù)源名稱(data source name)的縮寫,可以是字符串,或者是特定的數(shù)組形式。
一般來說,$dsn是一個字符串,它的格式如下:
phptype(dbsyntax)://username:password@protocol+hostspec/database
*phptype:php后端數(shù)據(jù)庫的類型名稱(如mysql, odbc 等等.)
*dbsyntax: 數(shù)據(jù)庫所使用的SQL語法標準,一般不用。
*protocol: 使用的通訊協(xié)議。(如tcp, unix 等等.)
*hostspec: 數(shù)據(jù)庫所在的主機的描述。(形式是:主機名[:端口號])
*database: 數(shù)據(jù)庫的名稱。
*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也可以是一個數(shù)組,數(shù)組的形式如下:
$dsn = array(
'phptype'=> 'mysql',
'dbsyntax' => '',
'protocol' => '',
'hostspec' => 'localhost',
'database' => 'test',
'username' => 'root',
'password' => ''
)
$options 是數(shù)據(jù)庫的選項,混合型。如果是布爾型,那么一般來說,這個參數(shù)指明是否使用持久性連接(persistent connect),如果后端數(shù)據(jù)庫支持,當$options是TRUE的時候,將使用持久性連接。如果是數(shù)組,那么表示這是特定的后端數(shù)據(jù)庫的選項,這些選項將傳遞到DB_common類中的set_option方法中,后端數(shù)據(jù)庫通過實現(xiàn)或重載這個方法,可以自己決定如何使用這些選項。
isError($value)
這個方法用來判斷DB的一些方法返回的結(jié)果是否是一個錯誤對象,你可以使用這個方法來判定某個操作的結(jié)果是否是拋出了異常。
當然,如果你的應(yīng)用程序從是PEAR繼承的,也可以直接使用PEAR的isError來判斷,尤其是當你的程序中拋出的異常的可能是數(shù)據(jù)庫以外的異常的時候,這個方法只能判斷是否是DB_Errro對象,其他的PEAR_Error對象是無法識別出的。
function isWarning($value)
這個方法是判斷DB方法的返回結(jié)果是否是一個警告。和錯誤不同的是,警告不是致命的,所以仍可以繼續(xù)執(zhí)行下去。
function errorMessage($value)
一旦確定出現(xiàn)了錯誤,那么可以使用這個方法來取得相應(yīng)的錯誤信息,下面是DB中的預(yù)定義的錯誤信息:
DB_ERROR=> "unknown error",
DB_ERROR_ALREADY_EXISTS => "already 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)
動態(tài)載入PHP的數(shù)據(jù)庫擴展。$name是你的PHP擴展的名稱,不包含擴展名(如.dll,.so)。
當你需要讓PHP載入某個數(shù)據(jù)庫的擴展,但是你沒有權(quán)限修改php.ini的時候,可以使用這個函數(shù)。
當然,DB內(nèi)部也是自動調(diào)用這個方法來載入所需的PHP數(shù)據(jù)庫的擴展。
比如:你如果需要加載oracle的擴展,那么可以這樣使用:
<?php
include_once "DB.php";
if ( DB::assertExtension("php_oci8") ){
echo "oracle 8擴展加載成功!";
}else {
die "無法加載oracle 8擴展";
}
?>
以上是在DB類中定義的"靜態(tài)"方法,所謂靜態(tài)方法,是指你可以不需要構(gòu)建對象,就可以直接使用,并且只要你指明DB::,你可以在任何地方直接調(diào)用這些方法。
在DB.php中,除了DB外,也有幾個非常重要的類,在后端數(shù)據(jù)庫中也使用了這些類:
DB_Error
這個類是從PEAR_Error繼承來的,在數(shù)據(jù)庫操作中,對于出現(xiàn)的致命的錯誤,一般會拋出這個錯誤。
構(gòu)建函數(shù):
function DB_Error($code = DB_ERROR, $mode = PEAR_ERROR_RETURN, $level = E_USER_NOTICE, $debuginfo = null)
$code是DB錯誤代碼,$mode決定錯誤處理的模式,缺省是返回,$level 錯誤級別,
$debuginfo是附加的調(diào)式信息,如剛剛執(zhí)行的SQL語句等等。
DB_Warning
類似DB_Error。
DB_result
這是非常重要的類。
當執(zhí)行相應(yīng)的SQL查詢后,后端的數(shù)據(jù)庫類將返回一個DB_result的對象實例,你可以使用這個類的方法來獲得查詢結(jié)果的數(shù)據(jù)。這個類實際上是后端數(shù)據(jù)庫結(jié)果集的包裝,這里說的方法,在實際運行中是直接調(diào)用了后端數(shù)據(jù)庫的同名的方法,不過,對于我們來說,使用這個包裝類會感覺更自然一些。
function fetchRow($fetchmode = DB_FETCHMODE_DEFAULT)
取得一行數(shù)據(jù),$fetchmod是獲取數(shù)據(jù)的模式,如果沒有指定,那么使用缺省方式。其余的方式我們在后面討論DB_Common接口的時候會詳細討論。
function fetchInto(&$arr, $fetchmode = DB_FETCHMODE_DEFAULT)
取得一行數(shù)據(jù),同時追加到給定的$att數(shù)組中。$att是要追加到的數(shù)組,它是按照引用方式傳遞的。
function numCols()
取得結(jié)果集的列數(shù)。如果出錯,返回DB_Error對象。
function numRows()
取得結(jié)果集的行數(shù)。如果出錯,返回DB_Error對象。
function free()
釋放結(jié)果集所占用的資源。
至此,我們已經(jīng)學習了DB基本的使用方法,下面我們簡單復習一下,對于其中沒有見到的方法,我們在后面會詳細介紹:
連接數(shù)據(jù)庫:
<?php
$db = DB::connect("$db_type://$db_user@$db_passwd:$db_host/$db_dbName",$db_options);
if (DB::isError($db)){
echo "數(shù)據(jù)庫連接錯誤:".DB::errorMessage($db)."<br>";
exit();
}
/* 查詢1 */
$sql = "select * from user_log";
$result = $db->query($sql);
if (DB::isError($result) ){
echo "數(shù)據(jù)庫錯誤:".DB::errorMessage($result);
exit();
}
$colCount = $result->numCols();
echo "<tr><td colspan=\"".$colCount."\">共找到".$result->numRows()."位用戶</td></tr>";
while($row = $result->fetch()){
echo "<tr><br />";
for($i=0;$i<$colCount;$i++){
echo "<td>".$row[$]."</td><br />";
}
echo "</tr><br />";
}
$result->free();
$db->disconnect();
?>
四、 DB_Common 使用參考
DB_Common類是一個通用的接口,DB跨數(shù)據(jù)庫平臺的能力是通過實現(xiàn)這個接口來做到的。如果你的數(shù)據(jù)庫不在DB支持之列,你可以自己編寫一個類繼承DB_Common類,實現(xiàn)這些接口的函數(shù)。
function toString()
返回當前類的字符串描述,格式是類名:(phptype="",dbsyntax="")[connected],一般是類似于:
DB_mysql:(phptype=mysql,dbsyntax=)[connected]
function quoteString($string)
圈引一個字符串,使之在查詢中能夠安全地放在單引號的分界符之間。一般對于字符型的字段,我們查詢的時候使用''作為分隔符,因此如果字符串中有'則會出錯,這個函數(shù)把字符串中的'替換成\'.
function provides($feature)
指明當前DB的后端程序是否實現(xiàn)了給定的特性,返回布爾值。$feature是功能的明稱,一般是:"prepare","pconnect","transactions".在后端程序的構(gòu)建函數(shù)中應(yīng)該設(shè)置這些值。例子:
if($db->provider("transactions")){
//支持事務(wù)
}else {
//不支持事務(wù)
}
function errorCode($nativecode)
用于將后端數(shù)據(jù)庫產(chǎn)生的錯誤代碼映射到DB的通用錯誤代碼中去。內(nèi)部調(diào)用。后端數(shù)據(jù)庫在構(gòu)建函數(shù)中應(yīng)該初始化$errorcode_map屬性。例子(mysql):
//在DB_mysql的構(gòu)建函數(shù)中
$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,
);