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