明輝手游網(wǎng)中心:是一個免費提供流行視頻軟件教程、在線學(xué)習(xí)分享的學(xué)習(xí)平臺!

PEAR MDB 數(shù)據(jù)庫抽象層 —— 一次編寫—隨處運行

[摘要]Write once - run anywhere一次編寫——隨處運行這是Java的一句行銷口號,但是它同時也是PHP的關(guān)鍵特性之一。許多商業(yè)模型依賴于操作系統(tǒng)無關(guān)性來保證產(chǎn)品能夠銷售給廣泛的客戶群...
Write once - run anywhere
一次編寫——隨處運行

這是Java的一句行銷口號,但是它同時也是PHP的關(guān)鍵特性之一。許多商業(yè)模型依賴于操作系統(tǒng)無關(guān)性來保證產(chǎn)品能夠銷售給廣泛的客戶群體。因而,為什么要把你自己綁在某種數(shù)據(jù)庫廠商的身上呢?數(shù)據(jù)庫抽象層使得你能夠與數(shù)據(jù)庫獨立的開發(fā)你的應(yīng)用程序。但是,通常情況下它們對性能的影響超過了你所希望的,要么他們并不足夠抽象以消除所有和特定數(shù)據(jù)庫相關(guān)的代碼。

這篇文章將教給我什么?

這篇文章將對數(shù)據(jù)庫抽象包 PEAR MDB 有一個很好的介紹。文章的焦點將是對 MDB 超越類似包所提供的更先進的特性,例如數(shù)據(jù)類型抽象和基于 XML 的 schema 管理。對 PHP 和 SQL 的基本理解是推薦的。

為什么另外再要一個數(shù)據(jù)庫類?

通常, web 工程在客戶已經(jīng)確定了要使用那種 RDBMS (關(guān)系型數(shù)據(jù)庫管理系統(tǒng))之后被添加給已經(jīng)存在的 IT 基礎(chǔ)結(jié)構(gòu)。即使那并不是因為不同的預(yù)算可能影響的你選擇何種數(shù)據(jù)用于部署的情況。最終,你作為開發(fā)者可能簡單的偏好于不把自己綁在某個廠商身上。自此,意味著給每個支持的數(shù)據(jù)保持版本或者犧牲更多性能但是獲得多于必須的易用性:走入 PEAR MDB 吧。

MDB 是著眼于使得編寫 RDBMS 無關(guān)的 PHP 程序成為簡單的過程的數(shù)據(jù)庫抽象層。大部分其他的 PHP 的所謂數(shù)據(jù)庫抽象層緊緊給所有支持的數(shù)據(jù)庫提供了一個公用 API 以及非常有限的抽象(大部分只是針對序列的)。MDB 另一方面能夠用來抽象所有數(shù)據(jù)庫發(fā)送和接收的數(shù)據(jù)。甚至數(shù)據(jù)庫 schema 都能被定義為 RDBMS 無關(guān)的格式。但是它提供這些功能的同時仍然保持了很高的性能以及簡單易用。這是通過深入觀察兩個流行的數(shù)據(jù)庫抽象層,PEAR DB 和 Metabase, 之后并且對它們進行了融合后獲得的。而且在融合過程中,趁著這個機會清理了它們?nèi)诤虾蟮?API 以及任何影響性能的設(shè)計。

MDB 是怎樣出現(xiàn)的?

早在 2001 年的秋天,我就在尋找一種可能能夠讓我公司的程序框架與 RDBMS 獨立的數(shù)據(jù)庫抽象包。這個目標(biāo)是把特定數(shù)據(jù)庫相關(guān)的代碼數(shù)量減少到零。我發(fā)現(xiàn)提供這樣的功能的唯一的一個包是 Metabase。但是 Metabase有一些部分是因為為了和 PHP3 兼容的讓人不舒服的 API。盡管如此,我們決定 Metabase 是我們唯一的選擇。但是即使是在給 Metabase 增加了一個性能改進的補丁之后,我們?nèi)匀桓械轿覀兎艞壛颂嗟男阅堋N覀冊?2001 年的 PHP 國際會議上碰到了 Metabase 的作者,并且我們談?wù)摿俗屜?Metabase 這樣的東西成為 PEAR 工程一部分的好處。后來不久,在 PEAR 郵件列表上就 PEAR DB 和 Metabase 融合的可能的好處又開始了一場討論。在我們公司進行了許多討論之后,我們決定承擔(dān)這個任務(wù)。數(shù)個月的艱辛工作之后,我們現(xiàn)在有了 MDB 的第一個穩(wěn)定的 release。

MDB 給你提供了什么?

MDB 結(jié)合了 PEAR DB 和 Metabase 的大部分特性。實際上,PEAR DB 的特性中唯一不再存在的是作為結(jié)果集返回一個對象。我們放棄了這個特性是因為這個特性不常用而且對于性能的損失是非常明顯的。許多開發(fā)上的時間用在了使得 API 盡可能的好用。最終,MDB 非常高地提供了這些功能至少和 PEAR DB 一樣快而且比 Metabase 快很多。這些最重要地特性的列表:

OO 風(fēng)格的 API
預(yù)準(zhǔn)備的查詢模擬
給所有傳遞進來以及從數(shù)據(jù)庫中取出的數(shù)據(jù)的完全的數(shù)據(jù)類型抽象(包括 LOB 支持)
事務(wù)支持
數(shù)據(jù)庫/表/索引/序列創(chuàng)建/拋棄/改變
RDBMS 無關(guān)的數(shù)據(jù)庫 schema 管理
繼承進 PEAR 框架(PEAR 安裝程序,PEAR 錯誤處理等)

那么它如何使用呢?

MDB 提供了一些非常先進的抽象特性。記住這些特性只是供選擇的是很重要的。但是在編寫 RDBMS 無關(guān)的 PHP 程序時使用它們是非常重要的。一個展示使用 MDB 是多么簡單的例子在文章的結(jié)尾的 "鏈接和文獻" 部分。如前面所說,文章的焦點是介紹使得 MDB 與其他 PHP 數(shù)據(jù)庫抽象層不同的那些特性。你可以在隨本期文章一同包裝的 CD 中找到所有這些例子腳本的代碼。

但是,首先我們需要把 MDB 安裝上去。使用 PEAR 安裝程序這其實非常容易。我不能在這篇文章中完整的講述 PEAR 安裝程序但是我聽說下一期將非常詳細的討論 PEAR 框架的里里外外。讓安裝程序運行于 Windows 的工作在進行當(dāng)作但是支持仍然有一點古怪。對于 *nix 系統(tǒng)你需要 PHP 的 CGI 版本安裝在了你的系統(tǒng)并且簡單地運行下面地命令:

lynx -source go-pear.org php


在安裝完成之后你只需要再輸入一行命令那么就全部搞定了。

pear install MDB


如果前面的過程對你來說不管用,總是有從 PEAR MDB 主頁中直接獲得包的選項。URL 列于文章的最后。

利用數(shù)據(jù)類型抽象

因為大部分數(shù)據(jù)庫傾向于有一些個性或者怪癖,對于MDB來說把這些不同之處給開發(fā)者隱藏起來非常重要。MDB 通過定義自己的內(nèi)部數(shù)據(jù)類型來達到這點:text,boolean,integer,decimal,float,date,time,time stamp,large objects(文件)。所有傳遞給數(shù)據(jù)庫和從數(shù)據(jù)庫獲取的數(shù)據(jù)都能轉(zhuǎn)換成 MDB 的內(nèi)部格式或者從數(shù)據(jù)庫的內(nèi)部格式轉(zhuǎn)化回來。本節(jié)相關(guān)的例子腳本能夠再 datatype 目錄中找到。讓我們看看下面的查詢:

$session = '098f6bcd4621d373cade4e832627b4f6';
// set time out to 30 minutes
$timeout = time()+60*30;
// SELECT query showing how the datatype conversion works
$query = 'SELECT createtime, user_id FROM sessions';
$query .= ' WHERE session = '.$session;
$query .= ' AND lastaccess < '.$timeout;


這個查詢?nèi)绻l(fā)送給數(shù)據(jù)庫的話八成要失敗。原因是存儲在 $name 中的值需要轉(zhuǎn)換為正確的字符串格式。這也許意味著 $name 的內(nèi)容可能有特殊的轉(zhuǎn)義字符或者被引號包圍。PEAR DB 為此提供了方法 DB:.quote()。在 MDB 中這個方法叫 MDB::getTextValue()。不同之處是 MDB 給每種前面所列的數(shù)據(jù)類型都提供了這樣的函數(shù)。因而我們也能夠把 $timeout 轉(zhuǎn)換為正確的格式。

// convert $timeout to the MDB timestamp format
$timeout = MDB_date::unix2Mdbstamp($timeout);
// SELECT query showing how the datatype conversion works
$query = 'SELECT createtime, user_id FROM sessions';
$query .= ' WHERE session = '.$mdb->getTextValue($session);
$query .= ' AND lastaccess < '.$mdb->getTimestampValue($timeout);


為了作個演示,讓我們假定我僅僅想要獲取第一行。MDB::queryRow() 獲得第一行,它釋放結(jié)果集并且返回其內(nèi)容,因而它正是我們所要的。

$result = $mdb->queryRow($query);


但是不同的 RDBMS 返回像日期這樣的數(shù)據(jù)時用的格式是不同的。因此,如果我們?nèi)缓笠獙σ恍⿺?shù)據(jù)進行計算,不管選擇的 RDBMS 是什么,把數(shù)據(jù)以相同的格式返回是重要的。這個可以由 MDB 半自動地完成。你所有需要做的是告訴你的結(jié)果列將是什么樣的類型,MDB將處理轉(zhuǎn)換的工作。最簡單的辦法是把這樣的信息傳遞給查詢函數(shù)。

$types = array('timestamp', 'integer');
$result = $mdb->queryRow($query, $types);


這告訴 MDB 結(jié)果集的第一列類型是 'timestamp' 以及第二列是'integer'。所有查詢函數(shù)能夠接受這樣的元信息作為可選的參數(shù)。數(shù)據(jù)還能事后用 MDB::setResultTypes() 來設(shè)置。取決于數(shù)據(jù)獲取于的數(shù)據(jù)庫,它然后將被相應(yīng)的轉(zhuǎn)換返回的數(shù)據(jù)。MDB 內(nèi)部的 timestamps 的數(shù)據(jù)格式是遵循 ISO 8601 標(biāo)準(zhǔn)的。其他像 PEAR::Date 這樣的包能夠處理這種格式。MDB 還在 MDB_Date 類中提供了一些數(shù)據(jù)格式轉(zhuǎn)換函數(shù),它們能夠被可選的包含。

因為相當(dāng)多的 RDBMS 以相同的方法返回整數(shù)數(shù)據(jù),沒有必要轉(zhuǎn)換整數(shù)數(shù)據(jù)。因而,為了獲得稍許的性能改進你能夠這么做:

$types = array('timestamp');
$result = $mdb->queryRow($query, $types);


這樣只有結(jié)果集的第一列會被轉(zhuǎn)換。當(dāng)然,如果 MDB 用于返回整數(shù)不同的數(shù)據(jù)庫,這可能成為一個問題。然而,稍許的性能改善可能并不值得冒這個風(fēng)險。但是再一次的,它顯示了這些特性的使用僅僅是供選擇的。

Listing 1 展示了一個使用預(yù)準(zhǔn)備的查詢的例子。如果你必須運行大量查詢而唯一的差別是數(shù)據(jù)傳遞給數(shù)據(jù)庫,但是查詢的結(jié)構(gòu)還是一樣的,這些能夠相當(dāng)?shù)姆奖。高級的?shù)據(jù)庫能夠在內(nèi)存中儲存解析好的查詢來加速性能。

Listing 1

$alldata = array(
array(1, 'one', 'un'),
array(2, 'two', 'deux'),
array(3, 'three', 'trois'),
array(4, 'four', 'quatre')
);

$p_query = $mdb->prepareQuery('INSERT INTO numbers VALUES (?,?,?)');
$param_types = array('integer', 'text', 'text');

foreach ($alldata as $row) {
$mdb->execute($p_query, NULL, $row, $param_types);
}


在 $alldata 中儲存的所有四個數(shù)組將用于 execute 語句。數(shù)據(jù)將自動被轉(zhuǎn)換為正確的格式。因為這是一個插入語句,MDB::execute() 的第二個參數(shù)被設(shè)置為 NULL 因為我們將沒有任何結(jié)果列需要我們設(shè)置數(shù)據(jù)類型。

在支持的數(shù)據(jù)類型中還有 LOB (大對象),它使得我們能夠在數(shù)據(jù)庫中儲存文件。二進制文件儲存在 BLOB (二進制大對象)中而且普通文本文件儲存在 CLOB (字符大對象)中。在 MDB 中你僅僅能夠使用預(yù)準(zhǔn)備的 INSERT 和 UPDATE 查詢儲存 LOB。使用 MDBA::setParamBlob() 或者 MDB::setParamClob() 你能夠設(shè)置預(yù)準(zhǔn)備查詢的 LOB 域的值。兩個函數(shù)都預(yù)期傳遞一個 LOB 對象,而它能夠使用 MDB::createLob() 創(chuàng)建。

$binary_lob = array(
'Type' => 'inputfile',
'FileName' => './myfile.gif'
);
$blob = $mdb->createLob($binary_lob);

$character_lob = array(
'Type' => 'data',
'Data' => 'this would be a very long string container the CLOB data'
);
$clob = $mdb->createLob($character_lob);


如你能看到的,MDB::createLob() 被傳遞一個關(guān)系數(shù)組。Type 鍵的值可能是以下中的一個:data, inputfile 或者 outputfile。前兩個用于你想要把 LOB 寫入數(shù)據(jù)庫的時候。如果你有一個儲存在變量中的 LOB,你應(yīng)當(dāng)在 需要使用 inputfile 時從文件直接讀取 LOB。最后,outpufile 應(yīng)當(dāng)在你想要從數(shù)據(jù)庫中讀取 LOB 時使用。取決于你是否使用數(shù)據(jù)或者 inputfile 你需要給 Filename 鍵或者 Data 鍵指定一個值,像上面的例子那樣,F(xiàn)在,我們將把前面的 LOB 儲存到數(shù)據(jù)庫中去。

$p_query = $mdb->prepareQuery('INSERT INTO files (id, b_data, c_data) VALUES (1, ?, ?)');

$mdb->setParamBlob($p_query, 1 , $blob, 'b_data');
$mdb->setParamClob($p_query, 2 , $clob, 'c_data');

$result = $mdb->executeQuery($p_query);


為了從數(shù)據(jù)庫中獲取上面的文件,我們需要首先從數(shù)據(jù)庫中選擇數(shù)據(jù)并且使用 MDB::createLob() 創(chuàng)建 LOB 對象。這次我們將設(shè)置 'Type' 為 'outputfile'

$mdb->query('SELECT b_data FROM files WHERE id = 1');

$binary_lob = array(
'Type' => 'outputfile',
'Result' => $result,
'Row' => 0,
'Field' => 'b_data',
'Binary' => 1,
'FileName' => './myfile2.gif'
);
$blob = $mdb->createLob($binary_lob);


現(xiàn)在我們能夠使用 MDB::readLob() 從結(jié)果集中讀取 LOB。傳遞長度 0 給 MDB::readLob() 意味著整個 LOB 被讀取和儲存在我們前面指定的文件中。一旦任務(wù)完成了,我們可以把資源釋放了。你也可以設(shè)置任何大于零的長度并且使用一個 while 循環(huán)檢查 MDB::endofLob() 來讀取 LOB。

$mdb->readLob($blob, $data, 0);


注意你不要把這個獲取函數(shù)和 bulk 獲取函數(shù)像 MDB::fetchAll()搞混了,因為這將在大部分 PHP 數(shù)據(jù)庫擴展中導(dǎo)致問題。在一些時候,MDB 可能能夠使用 bulk 獲取函數(shù)獲得 LOB。

如我們在這節(jié)所見,MDB 特性本身的原生數(shù)據(jù)類型集自動映射于數(shù)據(jù)庫中的原生數(shù)據(jù)類型。這保證了無論我們發(fā)送和從數(shù)據(jù)庫接收什么樣的數(shù)據(jù),它都能與使用的 RDBMS 無關(guān)的使用相同的格式。如我在本節(jié)開篇已經(jīng)提到的,這明顯需要數(shù)據(jù)庫使用的數(shù)據(jù)類型是 MDB 預(yù)期的。這種需要被用于確保映射所耗費的代價很小。下一節(jié)將教給我們 MDB 如何輔助在數(shù)據(jù)庫中使用正確的數(shù)據(jù)類型。


使用 XML schema 文件

利用在上個段落中描述的特性,你能編寫真正的數(shù)據(jù)庫獨立的程序。但是 MDB 嘗試向前更加邁出一步:它允許你用 XML 定義你的 schema。一個管理器把這種 schema 轉(zhuǎn)換為給每種 RDBMS 的必要的 SQL 語句。這意味著你能對所有支持的 RDBMS 使用相同的 schema。本節(jié)的例子能夠在 xml_schema 目錄中找到。

我們現(xiàn)在將從頭編寫一個 XML schema 文件。首先,我們必須定義一個 XML 文檔。數(shù)據(jù)庫定義是包含在一個 database 標(biāo)簽之中的。數(shù)據(jù)庫的名字是使用 name 標(biāo)簽定義的。create 標(biāo)簽告訴管理器數(shù)據(jù)庫是否需要在它不存在的時候被創(chuàng)建。如果你把你的 schema 文件分割成好幾個文件你你首先提交給管理器的那個文件中把 create 設(shè)置為 1。

<?xml version="1.0" encoding="ISO-8859-1" ?>
<database>
<name>auth</name>
<create>1</create>
</database>


可能你已經(jīng)從數(shù)據(jù)庫名 auth 猜出了這個數(shù)據(jù)庫的目的是用于儲存簡單的驗證程序的用戶數(shù)據(jù)。Listing 2 定義了在其中我們能儲存用戶數(shù)據(jù)的表。

Listing 2

<table>
<name>users</name>
<declaration>
<field>
<name>user_id</name>
<type>integer</type>
<notnull>1</notnull>
<unsigned>1</unsigned>
<default>0</default>
</field>
<field>
<name>handle</name>
<type>text</type>
<length>20</length>
<notnull>1</notnull>
<default></default>
</field>
<field>
<name>is_active</name>
<type>boolean</type>
<notnull>1</notnull>
<default>N</default>
</field>
</declaration>



如你能看到的,如使用 XML 時可以預(yù)期的,東西變得有一些冗長。不用擔(dān)心:我們有一個基于瀏覽器的工具稱為 MDB_frontend 使得這個過程更加簡單。我將在這篇文章的后面談?wù)撨@個工程?赡苓@極其詳細地表格描述的優(yōu)點是非常明顯。前面例子中的表格被稱為 users 并且我們定義了 3 個域:類型為整數(shù)的 user_id,類型為文本的 handle 和類型為邏輯型的 is_active。記住如果你如前一節(jié)那樣傳遞了必要的元數(shù)據(jù) MDB 為你處理類型抽象。你還不需要 MDB 把這些類型映射為你的 RDBMS 中的什么。在每個域聲明中還能使用的其他標(biāo)簽是可選的:length,notnull,unsigned 和 default。

下一件我們現(xiàn)在需要做的事情是通過在 user_id 域放置恰當(dāng)?shù)乃饕_保 user_id 是唯一的。索引定義就在聲明標(biāo)簽之內(nèi)(Listing 3)。

Listing 3:

<table>
<name>users</name>
<declaration>
<index>
<unique>1</unique>
<name>user_id_index</name>
<field>
<name>user_id</name>
<sorting>ascending</sorting>
</field>
</index>
</declaration>



在 listing 3 中的定義在域 user_id 中創(chuàng)建一個唯一的上升排序的名為 user_id_index 的索引。當(dāng)然,我們可以簡單地添加另外一個域標(biāo)簽在索引定義中指定多于一個的域。我們現(xiàn)在仍然沒有提到的是為我們產(chǎn)生唯一的用戶 id 的序列。

<sequence>
<name>users_user_id</name>
<start>1</start>
<on>
<table>users
<field>user_id</field>
</on>
</sequence>


上一個例子非常的繞彎。一行行看過來,我們看到首先打開一個 sequence 標(biāo)簽,跟著一個指定序列名字的 name 標(biāo)簽。這之后跟著一個定義序列初始值的 start 標(biāo)簽。現(xiàn)在,我們打開一個可選的 on標(biāo)簽。這兒我們需要設(shè)置一個表中的指定域。這個信息是管理器用來把序列的值設(shè)置為 users 表的 user_id 域的最大值。如果 users 表是空的,作為替代使用的是 start 標(biāo)簽中指定的值。請注意在 start 標(biāo)簽中指定的值是我們調(diào)用 MDB::nextId() 返回的第一個值。

當(dāng)然,你也能使用任何值初始化表。例如你可能想要用你總是想要包含在你的程序中的管理用戶來初始化前面的表格。為了這么做,我們需要把一個 initialization 標(biāo)簽添加給 table 標(biāo)簽。Listing 4 定義了一在另外一用 insert 標(biāo)簽包括的行之后的行。

Listing 4

<table>
<name>users</name>
<initialization>
<insert>
<field>
<name>user_id</name>
<value>1</value>
</field>
<field>
<name>handle</name>
<value>default</value>
</field>
<field>
<name>is_active</name>
<value>Y</value>
</field>
</insert>
</initialization>



如你從上個例子中能看到的那樣,所有我們需要做的就是給表的每個域設(shè)定值。我們現(xiàn)在已經(jīng)知道了必要的基礎(chǔ)知識來創(chuàng)建一個 MDB 的 XML schema。下一步是把這個 schema 文件傳遞給 MDB 管理器。

$manager = new MDB_Manager;
$input_file = 'auth.schema';
// we do not have to connect to a specify a specific database at this time
$dsn = "mysql://$user:$pass@$host";
$manager->connect($dsn);
$manager->updateDatabase($input_file, $input_file. '.before');


我們現(xiàn)在有了一個新的名字叫 auth 的數(shù)據(jù)庫,它有一個表叫 users。在域 user_id 有一個索引。而且在表中還有一行。我們還有一個序列稱為 users_user_id,它將被初始化為 1。因此序列中的下一個值就是 2。最后,schema 的一個拷貝以名字auth.schema.before 被創(chuàng)建。這是因為我們給 MDB_Manger::updateDatabase() 傳遞了可選的第二個參數(shù)。在下一節(jié)我們將看到為什么要創(chuàng)建這個拷貝。

所有這些都非常令人驚奇但是它變得更好。許多情況下程序需要在某些地方作出改變。例如我們可能決定需要把表的名字從 users 變成 people。我們可能還需要增加一個域 pwd 來儲存密碼域(請檢查 textbox 的保留字)。


保留字

我們沒有稱那個域為 password 的原因是那是 Interbase 中一個域名的保留字。因為我們需要 RDBMS 獨立,MDB 管理器要么給出一個警告要么在 fail_on_invalid_names 選項被設(shè)置為真的時候(這是缺省值)失敗。

在過去的時候,你可能現(xiàn)在正處于把你所有已經(jīng)有的東西變成這種新的 schema 的痛苦之中。但是由于 MDB 這些工作能夠自動完成。在 listing 5 中是我們對我們的表格定義進行的修改:

Listing 5

<table>
<name>people</name>
<was>users</was>
<declaration>
<field>
<name>pwd</name>
<type>text</type>
<length>32</length>
<notnull>1</notnull>
<default></default>
</field>
</declaration>



現(xiàn)在我們想要管理器來作出必要的改變,但是在此之前我像提一下可能的陷阱。因為我們把表從 users 更名為 people,我們還需要把所有對原來名字的引用進行更改,比如我們建立的序列。在 on 標(biāo)簽中的索引需要更改為指向 people 表。為了達到這個目的,我們把 shcema 的新舊版本傳遞給管理器。這酒是為什么我們在第一次調(diào)用 MDB_Manager::updateDatabase() 時我們創(chuàng)建一個 .before 文件的原因。這確保了我們有一個舊版本的 shcema 來與新的版本進行比照。

$input_file = 'auth.schema';
$manager->updateDatabase($input_file, $input_file.'.before');


所有的就是這樣!users 表現(xiàn)在稱為 people 并且我們也有了一個 pwd 域。

我現(xiàn)在要看看 XML schema 格式的最后一個特性。如果你想要編程性的使用管理器,這個特性尤其重要。假設(shè)你有好幾個有相同驗證程序運行在你的數(shù)據(jù)庫服務(wù)器的客戶。 每個客戶有一個服務(wù)器運行在這個服務(wù)器有相同的 schema 只有微小的區(qū)別:數(shù)據(jù)庫的名字?赡転槊總客戶單獨保存 schema 文件是可行的因為更新周期可能不是一樣的,這不是我們例子驗證程序的情況。這兒所有的客戶同時更新。XML schema 文件允許我們?yōu)榇丝梢允褂米兞俊?br>
<?xml version="1.0" encoding="ISO-8859-1" ?>
<database>
<name><variable>name</variable></name>
</database>


我們現(xiàn)在在運行時設(shè)置變量為任意我們需要的東西。

foreach($clients as $name) {
$variables = array('name' => $name)
$manager->updateDatabase($input_file, $input_file.'.before', $variables);
}


XML schema 管理是 MDB 提供的數(shù)據(jù)庫抽象概念的另外一個非常重要的部分。它使得我們保持我們的 schema 定義與特定的 RDBMS 無關(guān)。但是使用這個格式還確保了使用正確的原生數(shù)據(jù)類型因而 MDB 能夠正確地映射它的原生數(shù)據(jù)類型。最后,因為數(shù)據(jù)是基于 XML 的,編寫產(chǎn)生或者讀取 XML schema 文件的工具要容易一些。


聽起來不錯但是我的應(yīng)用程序已經(jīng)使用了……

大部分讀者可能發(fā)現(xiàn)它們處于這樣的境地——他們已經(jīng)有了大量運行于其他數(shù)據(jù)庫抽象層的程序。由于 MDB 的出身,大部分 PEAR DB 的用戶應(yīng)當(dāng)發(fā)現(xiàn) MDB 感覺上非常類似,因為 MDB 的 API 是基于 PEAR DB 的。Metabase 用戶應(yīng)當(dāng)發(fā)現(xiàn)他們所有偏愛的功能都在 MDB 中有對應(yīng)的東西。XML schema 格式和 Metabase 中的是一摸一樣的。一個完全的指導(dǎo)來引導(dǎo)你把已經(jīng)寫好的程序移植到 MDB 中超出了本文的范圍,但是我將利用這個機會給一些提示。如果你有任何具體的問題,放心的發(fā)信來詢問我。

為了把你的 PEAR DB 程序移植到 MDB,最好的起點是 PEAR wrapper。你能使用 PEAR wrapper 來運行你的程序。wrapper 當(dāng)然增加了一些額外負擔(dān),因而你可能有些想要移植到原生的接口。那么第一步是列出所有你程序當(dāng)前使用的 PEAR DB 函數(shù)。然后看看 wrapper 從中找出任何 API 上的區(qū)別。有兩個你要注意的關(guān)鍵區(qū)別:結(jié)果集不再是對象而且所有的允許你傳遞結(jié)果集的數(shù)據(jù)類型的查詢方法將導(dǎo)致參數(shù)順序上的少許改變。第一個區(qū)別意味著不能再結(jié)果對象上調(diào)用獲取函數(shù)。

$result = $db->query($sql);
$row = $result->fetchRow();


你現(xiàn)在必須調(diào)用 MDB 對象來進行獲取:

$result = $mdb->query($sql);
$row = $mdb->fetchRow($result);


第二個區(qū)別通過觀察 wrapper 可以輕易的被解決。如你再 wrapper 中能看到的,你可以再 MDB 期望得到結(jié)果集的數(shù)據(jù)類型的地方簡單地傳遞 NULL。現(xiàn)在,你地程序應(yīng)當(dāng)能夠使用 MDB。當(dāng)然,你現(xiàn)在沒有真正得到了 MDB 地高級特性優(yōu)點的益處。這最有可能的是需要對你當(dāng)前的數(shù)據(jù)庫 schema 進行一些改動。管理器能夠嘗試反向地從已經(jīng)存在的數(shù)據(jù)庫中獲取 XML schema 文件。一個非常簡單的前端可以在 MDB 包中找到:reverse_engineer_xml_schema.php 腳本。極有可能你將需要手動修正產(chǎn)生的 XML schema 恩見,但是它將給你一個很好的開始。

如果你想要把你已經(jīng)存在的程序從 Metabase 移植到 MDB 你將必須改動所有的函數(shù)調(diào)用。查看 Metabase wrapper 需要改動什么將變得非常明顯。如果你知道正則表達式你可能能夠完成大部分這樣的替換工作。無論如何,你應(yīng)當(dāng)向前并且運行你原來喜愛的高級抽象特性但是現(xiàn)在用的是 MDB。你可能注意得到的是函數(shù)名變得更加簡短了。如果你作一些性能測試,你也將看到可觀的性能改善。


那么 MDB 將來會是什么樣子呢?

本文發(fā)表時 MDB 可能已經(jīng)不再是原來的 1.0 release 了。在原來的 MySQL 和 PostGreSQL 驅(qū)動之后,MDB還將有一個 ODBC 驅(qū)動以及可能的更多的驅(qū)動。這是 MDB 開發(fā)過程中關(guān)注的關(guān)鍵區(qū)域之一。一旦 MDB 在驅(qū)動方面跟上了 PEAR DB,它很有可能成為 PEAR 框架中標(biāo)準(zhǔn)的數(shù)據(jù)庫抽象層。

但是還有另外一個開發(fā)中的關(guān)鍵領(lǐng)域:MDB_frontend 工程。MDB_frontend將成為基于 MDB 和 MDB 管理器的 phpMyadmin。有了這個工具,你將能夠瀏覽儲存在 MDB 支持的 RDBMS 中的數(shù)據(jù)庫。MDB_frontend 將同時顯示原生和 MDB 數(shù)據(jù)類型。模擬的特性比如 MySQL 中的序列將被隱藏。用戶將僅僅看到一個序列列表而不是一個儲存序列指的表,而在 MySQL 中這就是序列是如何被模擬的。而且 MDB_frontend 將幫助移植已經(jīng)存在的數(shù)據(jù)庫來符合 MDB 預(yù)期使用的原生數(shù)據(jù)類型。它還將幫助創(chuàng)建和更新 XML schema 文件。一些初期的工作已經(jīng)完成了但是很多工作需要在公開發(fā)布之前被添加。

驅(qū)動和 MDB_frontend 是當(dāng)前開發(fā)的所有焦點,在 MDB 中還有許多用戶可能需要的:像 bulk 獲取 LOB 域的集成,其他人可能需要外部和主鍵支持。如一直以來的那樣如果你參與測試和實現(xiàn),開源的東西將加快很多。但是我也很感謝像特性需求合陽的反饋。


一些文后的思考

在數(shù)月的艱辛工作之后,MDB 正在當(dāng)前的 PEAR DB 和 Metabase 用戶中獲得認可。我還希望當(dāng)前還沒有被其他數(shù)據(jù)庫抽象層說服的用戶意識到 MDB 給他們的好處。當(dāng)然,還是有許多程序需要對 RDBMS 進行特殊剪裁,對于這種情況像 MDB 這樣的工具僅僅是增加了不必要的額外負擔(dān)和限制?偟膩碚f,我非常高興我們在我們的公司中作出領(lǐng)導(dǎo) MDB 開發(fā)的決定。在起初,我對嘗試同時取悅 PEAR DB 和 Metabase 的用戶但是結(jié)果可能到處不討好多少有些擔(dān)心。另外一個關(guān)心的來源是 PHP 社區(qū)是否將幫助其開發(fā)。我非常高興 PHP 社區(qū)來了并且?guī)椭珜戲?qū)動以及 MDB 的核心。因而我們認為這個項目是一個極大的成功。我們還一并相信 MDB 將得到更大的改進。而且我們對幫助 PHP 變得更好感到高興。

關(guān)于作者
Lukas Smith 是 PEAR DB 的主要作者。它積極地給多個 PHP 開遠項目進行貢獻并且是專注于 PHP 開發(fā)的 BackendMeida 公司的建立者。


鏈接 和 文獻

PEAR MDB homepage: http://pear.php.net/package-info.php?package=MDB
PEAR MDB documentation: http://www.backendmedia.com/MDB/docs/
PEAR MDB sample script: http://cvs.php.net/co.php/pear/MDB/MDB_test.php
PEAR DB homepage: http://pear.php.net/package-info.php?package=DB
Metabase homepage: http://www.phpclasses.org/mirrors.html?page=%2Fbrowse.html%2Fpackage%2F20.html
Simple benchmark: http://freshmeat.net/screenshots/30313/