內容: 一、 什么是DB類 二、 為什么要設計抽象的中間數據層 三、 DB的使用入門 四、 DB_Common 使用參考 五、 更進一步,創建你自己的中間數據庫應用層 六、 DB的不足 七、參考資源 關于作者 相關內容: 1、用PEAR來寫你的下一個php程序 2、常用模塊 3、使用PHPDoc輕松建立你的PEAR文檔 潘凡 (nightsailer@hotmail.com) 北京賽迪網信息技術有限公司 2001 年 8 月
對于PHP的應用程序來說,90%以上需要和數據庫來打交道。那么,你是如何操縱數據庫的?當你的后端數據庫升級或變遷后,你的這些程序是否能夠隨之平滑地升級和掛接呢?如果你正在考慮這個問題,那么不妨和我來討論一下,如何使用PEAR中的DB類來創建與數據庫無關的數據庫應用層。 一、 什么是DB類 我們首先簡單地了解一下DB類。DB類是PEAR中進行數據操作的幾個類的集合,它的主要目的是提供一個統一的,抽象的數據接口,這個接口與后端的數據庫是無關的。因此,如果你的應用程序使用這個通用的接口來進行數據庫的操作,那么就能夠平滑地切換到不同的數據庫下面,如MYSQL,SQL,SYBASE等等。實際上,DB類希望能夠起到簡單的類似ODBC或者是PERL中的DBI的作用。說到這里,不得不提一下PHP中的另一個優秀的庫:ADODB。ADODB也和DB一樣,提供了一個抽象的中間層,而且ADODB所支持的后端數據庫要比DB多(至少目前如此),不過ADODB沒有直接使用PEAR的一些特性,只是吸取了PEAR的許多思想,包括DB,因此二者的使用方法有許多相似的地方。我不想評論二者孰優孰劣,大家可以根據個人的喜好來使用。
二、 為什么要設計抽象的中間數據層 在詳細討論DB的使用之前,我們先討論一下為什么要設計中間的數據層,因為這意味著你需要作出一些犧牲和讓步,比如,你需要多寫一些代碼,有的局限于特定數據庫的特性將無法直接使用。
我們回憶一下我們過去的做法,如何連接到MYSQL數據庫?這的確是個小兒科的問題,下面的代碼你一定很熟悉: <?php /** * 連接到MYSQL數據庫 */ $host = "localhost";
$user = "root"; $passwd = ""; $persistent = 1;
if ($persisternt){ $conn = mysql_connect($host,$user,$passwd); }else { $conn = mysql_pconnect($host,$user,$passwd); } ?> 好了,現在建立了數據庫連接,我們可以使用它來進行數據庫的操作,我們可能使用類似的代碼: <?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); ?>
看起來很不錯,是嗎?你可能在你的代碼里使用很多類似的代碼片段。但是,不要太高興,問題來了。假如,突然,你的數據庫需要從MYSQL遷移到別的數據庫平臺,比如ORACLE,SYBASE。遷移的原因很多,也許是你的老板突發奇想,認為這樣能賣個好價錢,或者是你的數據猛增,導致MYSQL的性能下降,總之,遷移是事在必行了。你怎么做,你也許會想,呵呵,這簡單,把相關的函數替換一下不就行了。
聽起來簡單,但是……首先,連接數據庫的函數要改,需要把mysql_connect和mysql_pconnect替換成OCILogon和OCIPLogon。mysql_errno和mysql_error()當然不能使用,你需要從OCIError()返回的數組中提取響應的信息。
這還不是太糟,最糟的是相關的mysql_fetch_row,mysql_fetch_array等語句遍布于你的許多代碼函數和過程中,你需要逐一查找,分析,然后重新替換或者編寫相應的ORACLE的版本。如果,你的數據庫操作是集中在一個某一個模塊或類中,這項工作還可以接受,否則,等于你重新閱讀和修改了絕大部分的代碼。即使這個不幸的人不是你,那么他也會暗地里詛咒你的;=)
以上,我們回憶了我們以前的做法,以及可能帶來的不幸。那么,如果使用DB類來做類似的操作,應該是什么樣的呢?下面是相應的DB版本代碼: <?php 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]<br>"; } ?>
除了連接數據庫部分,其他的看起來只是有一些微小的變化,出錯處理使用的是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.phpMs SQL Server支持類 oci8.php Orcale 8i支持類 pgsql.phpPostegreSQL支持類 sybase.php Sybase支持類 ibase.phpibase支持類 msql.php mSQL 支持類 mysql.phpmysql支持類 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_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) 動態載入PHP的數據庫擴展。$name是你的PHP擴展的名稱,不包含擴展名(如.dll,.so)。
當你需要讓PHP載入某個數據庫的擴展,但是你沒有權限修改php.ini的時候,可以使用這個函數。
當然,DB內部也是自動調用這個方法來載入所需的PHP數據庫的擴展。
比如:你如果需要加載oracle的擴展,那么可以這樣使用: <?php 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, $debuginfo = 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基本的使用方法,下面我們簡單復習一下,對于其中沒有見到的方法,我們在后面會詳細介紹: 連接數據庫: <?php $db = DB::connect("$db_type://$db_user@$db_passwd:$db_host/$db_dbName",$db_options);
if (DB::isError($db)){ echo "數據庫連接錯誤:".DB::errorMessage($db)."<br>"; exit(); } /* 查詢1 */ $sql = "select * from user_log"; $result = $db->query($sql); if (DB::isError($result) ){ echo "數據庫錯誤:".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跨數據庫平臺的能力是通過實現這個接口來做到的。如果你的數據庫不在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, );
|