最大的網站源碼資源下載站,
一名新 php 數據對象 (pdo) 數據抽象層的原始開發人員為您簡要介紹該抽象層,重點講述與 oracle 一起運行的情況。
需要 php:5.0
需要其他:oracle 8 或更高版本客戶端庫
下載用于 oracle 的 pdo (windows):php_pdo.dll, php_pdo_oci.dll
下載用于 oracle 的 pdo (unix):pdo, pdo_oci
pdo 簡介
php 主要是由志愿者完成的項目;盡管有少數一些固定的“核心”開發人員,但是我們沒有一個人在全職受薪的開發 php。除此之外,我們分別位于世界不同地方,您可以想象長期開發的協調工作是何等困難。因此,php 主要是基于突發奇想的個人短期需求來發展的,其原因也多種多樣,有的是試驗,有的則是因為“明天有活要交”。盡管這樣通常每一步都會改善 php,但從長遠來看則是缺乏完整性 - 數據庫擴展就是一個重要的例子。
在各種不同的數據擴展(oci、mysql、postgresql、mssql 等)之間根本沒有真正的一致性,甚至在某些情況下,在這些擴展內部也沒有真正的一致性。幾乎所有這些擴展都在使用與基礎數據庫 api 緊密相連的不同代碼完成著相同種類的任務。而且因為我們(php 核心開發人員和擴展開發人員)的人手非常有限,因此這就造成了代碼更加難以維護,從而為 php 帶來了很大的問題。
由于 php 越來越受歡迎并不斷成功,因此主要 php 數據庫擴展的維護者們參加了在德國舉行的 linuxtag 2003 大會,在會上我們交換了對 php 前景的看法。在討論 php 發展的隨機性時,我們確定了在 php 中進行數據庫訪問的一些目標:
·提供一種輕型、清晰、方便的 api
·統一各種不同 rdbms 庫的共有特性,但不排除更高級的特性。
·通過 php 腳本提供可選的較大程度的抽象/兼容性。
我們之所以提出了這種 php 數據對象 (pdo) 的概念,是因為我們希望通過采用 zend engine 2(php 5 的核心)先進的面向對象特性獲得該 api 的一些更優秀的性能。
php 中的數據抽象層概念一點都算不上新;在 google 中查詢“php database abstraction”會找到大約 83,200 個匹配項。它幾乎是許多 php 開發人員夢寐以求的,而其產生則部分歸因于我們不完整的 api。如果您曾經嘗試過使用第三方抽象層來完成任何真正重要的工作,通常會發現這些抽象層對于手頭的工作來說設計的功能過于強大了 - 或者表現為在使用前需要進行大量學習,或者表現為接口速度緩慢,參數需要經過多層腳本函數調用才能到達數據庫自有的 api;通常是存在上述兩種表象。
為什么這些抽象層會存在這種問題?這些抽象層總是在試圖完成太多的任務,甚至可能是不可能的任務。我們決定以實用為目標,僅將一些最常見的數據庫 api 特性作為我們的基礎,并使得 pdo 驅動程序能夠將它們特定于產品的特性暴露為常規擴展函數。
為什么使用 pdo?
聽過有關數據庫抽象擴展謠傳的大多數人會立刻對 pdo 的擴展方面產生疑惑 - 我們是否要分析 sql,將其轉換為相應的后端方言呢?我們如何處理特性 x 或特性 y,等等。因此,當您聽說我們在 pdo 中根本不用為此而擔憂時可能會大吃一驚;我們不希望使所有內容都完全統一,因為要使得這種統一成為可能,只能是將自己限制在最低的通用標準。
如果 pdo 不是一個整體的抽象層,那還有什么別的原因值得您考慮使用它嗎?
·性能。pdo 從一開始就吸取了現有數據庫擴展成功和失敗的經驗教訓。因為 pdo 的代碼是全新的,所以我們有機會重新開始設計性能,以利用 php 5 的最新特性。
·能力。pdo 旨在將常見的數據庫功能作為基礎提供,同時提供對于 rdbms 獨特功能的方便訪問。
·簡單。pdo 旨在使您能夠輕松使用數據庫。api 不會強行介入您的代碼,同時會清楚地表明每個函數調用的過程。
·運行時可擴展。pdo 擴展是模塊化的,使您能夠在運行時為您的數據庫后端加載驅動程序,而不必重新編譯或重新安裝整個 php 程序。例如,pdo_oci 擴展會替代 pdo 擴展實現 oracle 數據庫 api。還有一些用于 mysql、postgresql、odbc 和 firebird 的驅動程序,更多的驅動程序尚在開發。
您可能想了解 pdo 與其他常用的抽象層的對比情況,例如 pear db 或 adodb。無論在 api 方面還是在性能方面,pdo 都比其他常見抽象層要輕型,但是涉及到在各個數據庫后端之間提供統一性方面,則不如那些抽象層,例如用于處理大量可移植性問題的 pear mdb 2 抽象層。
在哪里可以獲得 pdo?
pdo 是通過 pecl(發音為“pee-kle”,歐洲語言風格),即 php 擴展庫提供的。如果您在運行 linux 計算機,請按照下面的說明進行設置;稍后是在 windows 上安裝的詳細信息。
請注意,pdo 及其驅動程序當前處于“alpha”狀態;這就意味著我們會合理保證沒有重大缺陷,但是該程序包功能并不完善 - 我們還要添加很多功能。雖然我們鼓勵您測試該程序包,但是實在不推薦在現階段將其用于生產。
unix/linux 安裝
如果您以前尚未嘗試過 php 5,則請花一點時間來通讀一下“新聞”和各種聲明。在 unix 計算機上,您可能要安裝或升級 libxml2;如果沒有 libxml2,“pear”程序包管理工具就無法運行,您安裝 pdo 時就會遇到很多困難。獲取 php 5,并將其編譯和安裝。確保指定的前綴不是 /usr/local/,這樣它就不會與 php 4 安裝發生沖突了:
% ./configure --prefix=/usr/local/php5 --with-zlib [此處指定其他選項]
% make install
現在您就可以使用“pear”工具獲取并安裝 pdo 以及用于 pdo 的 oracle 驅動程序了。因為 pdo 當前標記為 alpha,所以默認情況下 pear 工具不會下載該程序包。在該程序包名稱后面添加后綴“-alpha”,通知該 pear 工具可以安裝 alpha 版本: % path="/usr/local/php5/bin:$path"
% pear install pdo-alpha
您需要告知 php 從專用于 php 5 的 php.ini 文件加載 pdo 驅動程序。如果您使用的前綴與我使用的一樣,php 則會在 /usr/local/php5/lib/php.ini 中查找 php.ini 文件。向該文件中添加以下行:
extension=pdo.so
現在您需要獲取數據庫特定的驅動程序;對于 oracle,此特定程序稱為 pdo_oci。在 shell 中,鍵入:
% pear install pdo_oci-alpha
此驅動程序也需要從 php.ini 文件加載;將下行添加到前面添加的那行之后:
extension=pdo_oci.so
現在檢查一下,確保它能夠運行:
% php -m
在模塊列表中,您應該會看到 pdo 和 pdo_oci。
防火墻礙事了?
如果您位于防火墻的后面,則在使用 pear 安裝程序獲取程序包時可能會遇到一些問題。如果發生這種情況,則可以按照下列說明手動下載并安裝這些程序包:
% wget http://pecl.php.net/get/pdo
% pear install pdo-0.1.1.tgz
[ 將 extension=pdo.so 添加到 php.ini ]
% wget http://pecl.php.net/get/pdo_oci
% pear install pdo_oci-0.1.tgz
[ 將 extension=pdo_oci.so 添加到 php.ini ]
在上述兩種情況下,都需要首先調用“pear install”(后跟下載的真正程序包);上述示例中的版本號在本文編寫之時是最新的,但隨著開發的繼續進行會發生變化。
windows 安裝
如果您正在運行 windows,則請按照下列說明執行:
·從 http://www.php.net/downloads.php#v5 獲取 php 5,將其解壓縮到 c:/php5。
·從 http://snaps.php.net/win32/pecl_5_0/php_pdo.dll 和
http://snaps.php.net/win32/pecl_5_0/php_pdo_oci.dll 分別獲取 pdo
和 pdo_oci,將其放入 c:/php5/ext。或者,您可以從 php 5 下載頁上列出的
“用于 php 5.0.0 的 pecl 模塊集合”zip 文件中找到所有這些 pdo 驅動程序,
以及所有 pecl 程序包的所有 windows 版本。
·編輯 c:/php5/php.ini 文件,并添加下列內容:
extension=php_pdo.dll
extension=php_pdo_oci.dll
編輯 php.ini 文件時,有一點很重要,即要在任何其他 pdo 驅動程序之前先加載 pdo 擴展,否則就不能正確初始化(在這種情況下會出錯)。
如果在 windows 目錄中有一個 php 4 的全局 php.ini 文件,則可能會遇到問題。最好的解決方法是,移動該 php.ini 文件,使其與 php 4 sapi 位于相同的文件夾中,以隔離 php 4 安裝;例如,將其移動到與 php4apache.dll 相同的文件夾中。請注意,php 5 程序中并非所有文檔都是最新的;推薦的安裝過程如上面所述 - 如 install.txt 文件所聲明的,請勿將任何 dll 復制到 windows 文件夾或 system 文件夾中 - 任何內容都是自包含的。如果您運行的是 apache,并且遇到無法加載 dll 的錯誤,則檢查一下是否將 c:/php5 添加到了 path 中。另外,還要注意 php 5 的 cgi 版本現在的名稱為 php-cgi.exe。
連接 pdo
首先創建 pdo 類的一個實例,將其用作數據庫句柄。使用哪個基礎驅動程序并不重要;您總要使用 pdo 類名。構造函數的第一個參數為數據源名稱 (dsn),第二個參數為用戶名,第三個參數為該用戶名的口令。dsn 的 pdo 命名慣例為 pdo 驅動程序的名稱,后面一個冒號,再后面是可選的驅動程序特定的信息。在我們的示例中,會加載 oci 驅動程序但不指定任何其他信息;這樣會使用默認的數據庫。對于其他驅動程序,如 odbc 驅動程序,第一個冒號后面的所有內容都將被用作 odbc dsn。mysql 驅動程序會同樣以不同的方式解釋它的 dsn。
如果無法加載該驅動程序,或者發生了連接失敗,則會拋出一個 pdoexception,以便您可以決定如何最好地處理該故障。
<?php
try {
$dbh = new pdo("oci:", "scott", "tiger");
} catch (pdoexception $e) {
echo "failed to obtain database handle " .$e->getmessage();
}
?>
在連接字符串中,您可以指定兩個可選參數;第一個是數據庫名稱,第二個是字符集;這些參數與可選的第三個和第四個參數相對應,后兩個參數您可能在 oci8 擴展函數 ociconnect() 或 ociplogon() 中使用過。要使用特定的字符集連接一個特定的數據庫,則可以執行下列操作:
<?php
try {
$dbh = new pdo("oci:dbname=accounts;charset=utf-8", "scott", "tiger");
} catch (pdoexception $e) {
echo "failed to obtain database handle " .$e->getmessage();
}
?>
省略 try..catch 控制結構并無裨益。如果在應用程序的較高級別沒有定義異常處理,則在無法建立數據庫連接的情況下,該腳本會終止。
連接管理
目前,pdo 完全沒有執行自己的任何連接管理,因此每個“新 pdo”調用都會建立一個新的數據庫連接。該連接在 $dbh 變量越界時,或者當您為其指定 null 值時會被釋放。
<?php
try {
$dbh = new pdo("oci:dbname=accounts;charset=utf-8", "scott", "tiger");
} catch (pdoexception $e) {
echo "failed to obtain database handle " .$e->getmessage();
exit;
}
// 在此處對數據庫執行一些操作
// ...
// 現在完成,釋放該連接
$dbh = null;
?>
計劃在不久的將來為 pdo 增加連接緩存功能;就當前的 oci8 擴展而言,會重用與現有服務器的連接,并且在這些連接中,還會重用閑置的登錄。當在緩存連接模式中運行時,如上面的代碼段所示釋放 $dbh 時會將該登錄標記為可由其他連接重用。
如果您使用 odbc 驅動程序訪問 oracle,則可能會很高興地注意到,默認情況下 pdo_odbc 驅動程序支持 odbc 連接池。
使用 pdo
了解一個編程 api 的最好方式就是使用它,因此我們來看一下附帶的這個演示,以了解如何進行批次更新(代碼如下)。
<?php
// create a pdo database handle object
// the 'oci:' string specifies that the oci driver should be used
// you could use 'oci:dbname=name' to specify the database name.
// the second and third parameters are the username and password respectively
$dbh = new pdo('oci:', 'scott', 'tiger');
// create a test table to hold the data from credits.csv
$dbh->exec("
create table credits (
extension varchar(255),
name varchar(255)
)");
// start a transaction
$dbh->begintransaction();
// prepare to insert a large quantitiy of data
$stmt = $dbh->prepare("insert into credits (extension, name) values (:extension, :name)");
// bind the inputs to php variables; specify that the data will be strings
// with a maximum length of 64 characters
$stmt->bindparam(':extension', $extension, pdo_param_str, 64);
$stmt->bindparam(':name', $name, pdo_param_str, 64);
// open the .csv file for import
$fp = fopen('credits.csv', 'r');
while (!feof($fp)) {
list($extension, $name) = fgetcsv($fp, 1024);
$stmt->execute();
}
fclose($fp);
// commit the changes
$dbh->commit();
?>
既然我們已經成功連接到了 oracle,那么現在就可以創建一個表來保存一些數據了。對于此示例,我們使用一些 php 擴展及其作者,并將這些內容輸入一個數據庫中。數據庫句柄對象的 exec() 方法可用來發出不會返回結果集的快速一次性查詢,因此我們在這里使用該方法來發出 create table 查詢。
為了使得示例更自然,我從 php 源代碼中抽取了擴展及其作者的信息,并將其存儲到了一個 csv 文件中(請參見“相關附件:credits.csv”)。這就代表一個常見情形:從 csv 文件批次導入數據。在我們的示例中,我們充分利用了 oracle 的預處理語句和綁定參數,以獲得一個高效的數據導入腳本。在講述該示例之前,有必要了解一下 pdo 處理事務的方式。
pdo 中的事務處理
oracle 具有一個敏感的默認操作模式:當您進行連接時,將會位于一個隱式事務處理中,在提交事務之前其中的更改不會完全生效。除了事務處理的標準優點(原子性、一致性、隔離性、可持久性 - acid)之外,數據庫服務器在執行每次更新之后還不需要重新構建索引和其他內部結構;它可以延遲到提交之后進行。這樣會加速代碼的執行。oracle 這點確實很好。
但不幸的是,并非每個數據庫供應商都支持事務處理,并且因為 pdo 旨在以一種相對可移植的方式支持這些事務處理,所以它默認情況下以自動提交模式運行。啟用自動提交模式后,數據庫驅動程序會隱式提交每個成功的更新。當您調用 $dbh->begintransaction() 時,就會請求關閉自動提交,直到調用 $dbh->commit() 或者 $dbh->rollback() 才會重新啟用,具體取決于您的代碼是怎樣編寫的。如果基礎驅動程序不支持事務處理,則會拋出一個 pdoexception。
如果發生了問題并且 php 出錯,您的腳本將退出并且事務處于待批狀態;或者您關閉數據庫句柄時,pdo 會自動針對任何待批的事務調用 $dbh->rollback()。此行為會減少向數據庫中提交可能未定義或者已損壞數據的可能性,這是用于處理已放棄事務的標準語義。
預處理語句、存儲過程
pdo 支持使用 oracle 樣式命名的占位符語法將變量幫定到 sql 中的預處理語句(與 oci8 擴展中的 ocibindbyname() 類似)。pdo 還為其他數據庫(如 odbc)提供了命名占位符模擬,甚至可以為生來就不支持該概念的數據庫(如 mysql)模擬預處理語句和綁定參數。這是 php 向前邁進的積極一步,因為這樣可以使開發人員能夠用 php 編寫“企業級”的數據庫應用程序,而不必特別關注數據庫平臺的能力。
使用 pdo 預處理語句非常簡單,調用數據庫句柄的 prepare() 方法即可。它會返回一個語句句柄對象,然后您可以使用該對象來綁定參數和執行語句。在此示例中,我們將要定義兩個命名占位符,“:extension”和“:name”,這兩個占位符分別與 .csv 文件中的 php 擴展名稱和其中一個作者的姓名相對應。
$stmt = $dbh->prepare("insert into credits (extension, name) values (:extension, :name)");
預處理了語句之后,我們使用 bindparam() 方法來將這些命名參數分別與 php 變量名稱“$extension”和“$name”相關聯(這與 ocibindbyname() 類似)。我們還會通知 oracle,這些數據將要格式化為字符串,最大長度為 64 個字符。
$stmt->bindparam(':extension', $extension, pdo_param_str, 64);
$stmt->bindparam(':name', $name, pdo_param_str, 64);
我們現在即準備好插入數據了 - 我們只需要打開該 csv 文件,并從中獲取數據即可。通過使用 fopen() 和 fgetcsv() 函數可以相當簡單地完成此操作。然后,我們可以使用 php list() 構造函數直接將 csv 的列指定給變量“$extension”和“$name”。因為這些變量已經綁定到了語句中,所以我們現在要做的只是調用該語句對象的 execute() 方法使其執行插入。這種方式既方便又快捷 - 在事務處理時每個迭代循環只有兩行。到達文件尾時,我們就可以立即使用數據庫句柄的 commit() 方法來提交這些更改了。
如果您只是要傳遞輸入參數,并且有許多這樣的參數要傳遞,那么您會覺得下面所示的快捷方式語法非常有幫助;此語法使您能夠省去對 $stmt->bindparam() 的調用。
$stmt = $dbh->prepare("insert into credits (extension, name) values (:extension, :name)");
$stmt->execute(array(':extension' => $extension, ':name' => $name));
您還可以使用 bindparam 來為存儲過程設置輸入/輸出參數;語法是完全相同的,只是查詢有所不同。下面的代碼演示如何調用一個名為“sp_add_item”的存儲過程;其目的是要針對輸入設置 $item_name,然后該存儲過程將在返回時更新 $error_code。
$stmt = $dbh->prepare("begin sp_add_item(:item_name, :error_code); end");
$stmt->bindparam(':item_name', $item_name, pdo_param_str, 12);
$stmt->bindparam(':error_code', $error_code, pdo_param_str, 12);
$stmt->execute();
抓取數據
使用 pdo 抓取數據與進行插入或更新相似,只是您執行完查詢之后,將要重復調用 fetch() 方法來獲取結果集的下一行。進行獲取的最簡單情況如下所示,值得注意的一點是,您還可以將參數綁定到查詢,以控制如 where 子句這樣的內容;執行此操作的語法與我們已經看到的 bindparam() 代碼完全相同。
$stmt = $dbh->prepare("select extension, name from credits");
if ($stmt->execute()) {
while ($row = stmt->fetch()) {
print_r($row);
}
}
pdo 支持一些不同的抓取策略,這些策略在方便性和性能方面有所差別;通過將下列選項之一指定為 fetch() 方法的參數,您可以更改其返回值以適應您的語法:
·pdo_fetch_num - 每個行抓取返回一個按照列位置索引的數組,并且以 0 為基數(第一列是第 0 個元素)。
while ($row = $stmt->fetch(pdo_fetch_num)) {
printf("extension %s, by %s<br>", $row[0], $row[1]);
}
·pdo_fetch_assoc - 每個行抓取根據行集中的列名,返回一個按列名索引的數組。
while ($row = $stmt->fetch(pdo_fetch_assoc)) {
echo "extension $row[extension] by $row[name]<br>";
}
·pdo_fetch_both - 每個行抓取返回一個既按照列位置又按照列名索引的數組。也就是上述兩種情況的直接組合。如果沒有指定抓取模式,則該模式為默認模式。 ·pdo_fetch_obj - 每個行抓取返回一個匿名對象,其屬性名與列名對應。
while ($row = $stmt->fetch(pdo_fetch_assoc)) {
echo "extension {$row->extension} by {$row->name}<br>";
}
·pdo_fetch_lazy - 每個行抓取返回一個引用語句對象的重載對象。這“看起來”好像是 pdo_fetch_obj 和 pdo_fetch_both 的組合,只是只有當您在腳本中訪問 php 變量時才創建這些變量。
·pdo_fetch_bound - 抓取每行,返回 true。在使用綁定輸出列時這種方式非常有用,它可以避免創建不需要的任何數組或對象。(請參見下面的示例)。
無論您使用哪種抓取策略,當沒有其他行可抓取時,fetch() 方法將會返回 false。
現在我要講述一些技巧,如果您需要最后再調整一下腳本性能的話,這些技巧可能會對您有所幫助。但先給你一個忠告:要像躲避瘟疫一樣避免不成熟的優化。您應該總是首選最清晰、可維護性最好的解決方案。請記住,在一個典型的 web 應用程序中,您不能衡量各種抓取模式間的區別,除非腳本要處理很多行。我再重復一遍:抓取模式間的性能區別非常小 - 請使用最適合您代碼的模式。
請記住,使用 pdo_fetch_num 的花銷最小,因為訪問列數據只是一個簡單的數值查詢。pdo_fetch_obj 使您能夠使用 oo 語法將數據集的列作為對象的屬性來訪問,但是每個屬性訪問都涉及一個附加的散列查詢,使得使用它的花銷基本上與 pdo_fetch_assoc 相同。每個這樣的模式都會復制整行,從而占用稍多的內存。
很多數據庫驅動程序都會代表您預先抓取并緩存一定數量的行。php 每次訪問其中一個這樣行中的列時,它都需要將其復制到自己的專用內存區域中。如果您的查詢涉及很多行,而只需要基于某種復雜的邏輯訪問給定行的特定列,則您會發現 pdo_fetch_lazy 是一種避免使用很多內存的有用方法,因為它只有在您訪問給定列時才復制該列。使用此方式時要注意,從某個給定語句為每個 fetch() 抓取的“惰性對象”是每次迭代時使用的同一對象(以減少每次創建/銷毀它的開銷)。這就暗示著您不能只是簡單地存儲該對象用于以后的比較,因為它仍然會引用該語句的當前行 - 您需要手動復制所需要的部分。
最后一種模式為 pdo_fetch_bound,該模式會告知 pdo 您已經將所有列綁定到了 php 變量,并且除了要它在到達行集的末尾時通知您外不需要它執行別的任何操作。綁定輸出列在概念上與綁定輸入參數相似,只是綁定輸出列可以用于所有數據庫驅動程序。您可以將 php 變量綁定到命名列,pdo 將在每次調用 execute() 時對其進行更新。此技術可用來剃去結果集中每列、每行的一些虛擬機器操作碼(這種代碼速度比原生碼要慢)。這種技術的缺點在于,可能會使您的代碼難以跟蹤(也稱為 wtf 系數較高),您使用變量名稱時需要倍加小心。下面的代碼說明了綁定輸出列的使用。請注意,您不必指定 pdo_fetch_bound 即可使用 $stmt->bindcolumn();pdo_fetch_bound 只是一個對于您了解只能使用綁定值的情況的一種優化。
$stmt = $dbh->prepare("select extension, name from credits");
if ($stmt->execute()) {
$stmt->bindcolumn('extension', $extension);
$stmt->bindcolumn('name', $name);
while ($stmt->fetch(pdo_fetch_bound)) {
echo "extension:$extension, author:$name/n";
}
}
可移植性
區分大小寫的列
pdo 旨在令使用可移植 sql 的腳本運行良好、可移植。本文中提及的所有查詢(調用存儲過程除外)在使用任何 pdo 驅動程序時其運行性能應該相同 - 包括所有綁定輸入變量和綁定輸出列。
但有一個轉換問題 - 當您使用 pdo_fetch_assoc 抓取數據時,不同的驅動程序會以不同的方式返回列名 - 某些會將列名轉化為大寫,某些轉換為小寫,某些則會使其呈查詢中指定的樣式。這對于 php 腳本來說是一個潛在的問題,因為數組鍵區分大小寫。pdo 提供了一個兼容性屬性來幫助規范腳本的結果。下面的小代碼段是上面 pdo_fetch_bound 示例的可移植版本,因為 setattribute() 方法調用會指導 pdo 將抓取返回的列名全部轉換為大寫:
$dbh = new pdo('oci:', 'scott', 'tiger');
$dbh->setattribute(pdo_attr_case, pdo_case_upper);
stmt = $dbh->prepare("select extension, name from credits");
if ($stmt->execute()) {
$stmt->bindcolumn('extension', $extension);
$stmt->bindcolumn('name', $name);
while ($stmt->fetch(pdo_fetch_bound)) {
echo "extension:$extension, author:$name/n";
}
}
除了 pdo_case_upper 之外,還有 pdo_case_lower(它會將列名轉換為小寫)和 pdo_case_natural(它是默認選項:使列保持數據庫驅動程序返回的形式)。
錯誤和錯誤處理
可移植腳本的另一個難題是處理從各種數據庫處理程序返回的各種不同的錯誤消息;某些數據庫對于程序化處理錯誤的支持能力很差,而其他一些數據庫則具有非常豐富的錯誤代碼。只要可行,pdo 將為您的腳本提供一個統一的錯誤代碼,從而使您不必為應對可移植性的這個方面所累。當然,pdo 還會為驅動程序提供原生錯誤代碼和錯誤消息,以防您需要用它來進行診斷,或者錯誤代碼映射不完整。
另一個困擾 php 數據庫擴展的一致性問題是錯誤處理策略的一致性:某些擴展會返回的錯誤代碼需要您手動抓取錯誤字符串,而其他一些擴展則只是發出 php 警告。pdo 允許您從下列三種不同的錯誤處理策略中選擇一種:
·pdo_errmode_silent
這是默認模式;它只是使用語句和數據庫句柄對象的 errorcode() 和 errorinfo() 方法為您設置要檢查的錯誤代碼。
if (!$dbh->exec($sql)) {
echo $dbh->errorcode() ."<br>";
$info = $dbh->errorinfo();
// $info[0] == $dbh->errorcode() 統一的錯誤代碼
// $info[1] 是驅動程序特定的錯誤代碼
// $info[2] 是驅動程序特定的錯誤字符串
}
·pdo_errmode_warning
除了設置錯誤代碼之外,pdo 還會發出 php 警告,您可以使用常規的 php 錯誤處理程序捕獲該警告,并集中應用您準備好用于應用程序的任何錯誤處理/記錄策略,或者只是使該錯誤顯示在瀏覽器中(在內部測試過程中非常有用)。
·pdo_errmode_exception
除了設置錯誤代碼之外,pdo 還會拋出一個 pdoexception,并將其屬性設置為包含該錯誤代碼和信息。然后,您可以在代碼的較高級別捕獲該異常,使用全局異常處理程序捕獲該異常,或者不對其進行處理而終止腳本(此時將回滾任何未決的事務)。
try {
$dbh->exec($sql);
} catch (pdoexception $e) {
// 顯示警告消息
print $e->getmessage();
$info = $e->errorinfo;
// $info[0] == $e->code; unified error code
// $info[1] 是驅動程序特定的錯誤代碼
// $info[2] 是驅動程序特定的錯誤字符串
}
請注意,與警告或異常相比,靜默模式針對運行時錯誤使用的資源最少,但是為了獲得該速度,您犧牲了一些簡單性,而變得有一點復雜。
統一錯誤代碼表當前包括下列常量: pdo_err_none、pdo_err_cant_map、pdo_err_syntax、pdo_err_constraint、pdo_err_not_found、pdo_err_already_exists、pdo_err_not_implemented、pdo_err_mismatch、pdo_err_truncated、pdo_err_disconnected。
這些常量所代表的意思字面即可推知,但是 pdo_err_cant_map 代碼除外;這是一個 pdo 特定的代碼,也就是說它無法將驅動程序特定的代碼映射到統一的錯誤代碼,因此您應該查詢 errorinfo() 方法返回的驅動程序特定代碼來獲得更多信息。
數據類型
pdo 在某種程度上類型不可知,因此它喜歡將數據表示為字符串,而不是將其轉換為整數或雙精度類型。此時您可能對此有些迷惑,但是原因非常簡單:字符串類型是最精確的類型,在 php 中具有最廣泛的應用范圍;過早地將數據轉換為整數或者雙精度類型可能會導致截斷或舍入錯誤。通過將數據以字符串抽出,pdo 為您提供了一些腳本控制,您可以使用普通的 php 類型轉換工具(如數學運算過程中的轉換和隱式)來控制如何進行轉換以及何時進行轉換。
null
如果結果集中的某列包含一個 null 值,pdo 則會將其映射為 php null 值。oracle 在將數據返回 pdo 時會將空字符串轉換為 null,但是 php 支持的任何其他數據庫都不會這樣處理,從而導致了可移植性問題。pdo 提供了一個驅動程序級屬性 pdo_attr_oracle_nulls,該屬性會為其他數據驅動程序模擬此行為:
$dbh = new pdo('oci:', 'scott', 'tiger'); $dbh->setattribute(pdo_attr_oracle_nulls, true); // 現在從此 $dbh 打開的任何語句中的 // 空字符串都將被轉換為 null pod 的現狀和未來
pdo 現在仍相當不成熟,但是會快速成熟起來。在編寫本文之時,我在本文中提到的任何內容都能夠通過 pdo_oci 驅動程序適用于 oracle 8 或更高版本(在 oracle 8.0 和 9.2 上測試過)。
已經計劃增加以下主要特性,在不久將可以使用:
1.使用 php 流的 lob 支持。 使用綁定參數,您能夠將任何流資源(如文件、套接字、http 資源、壓縮/篩選的流)作為輸入或輸出參數傳遞到在 lob 上運行的查詢中。與之相似,類型為 lob 的輸出參數將表現為 php 流,因此您可以使用 fread()、fwrite()、fseek() 和其他流函數來訪問這些參數。此時,在 pdo 中根本沒有 lob 支持。
2.持久性連接和緩存的預處理語句。 持久性連接使您能夠避免在每個頁面命中時打開和關閉數據庫服務器連接。緩存的預處理語句又前進了一步,它使您能夠持久保持查詢的預處理版本以及數據庫句柄。
3.游標。 目前,pdo 只提供前向只讀游標,但是將來會提供可滾動游標(需要基礎驅動程序支持)、ref-cursor、使用游標進行定位更新,以及可更新滾動游標。
我們希望在 php 5.1 中默認啟用 php 擴展(距此目標尚遠),但是在此之前,我們希望能讓 pdo 在 php 5.0 發布時穩定運行,但是我們日常工作中的壓力稍稍拖延了這些工作。同時,通過 pecl 發布 pdo 使我們能夠在收到問題報告時做出回應,并根據不同于 php 5.0 發布時間表的時間表發布修復版本,因此您在 php 5.1 發布前即可使用 pdo。
我們需要您的反饋
如果您試用了 pdo,并且發現了問題,請務必使用我們的錯誤跟蹤軟件將其報告給我們。如果您使用的是 oracle 驅動程序,則請使用此頁:
http://pecl.php.net/bugs/report.php?package=pdo_oci
如果您使用的是其他驅動程序,則請用其名稱替換該 url 中 pdo_oci。
如果您使用 pdo 時遇到問題,或者針對某些特性存在疑問,或者具有特性請求,請聯系 [email protected]。如果您愿意,當然還可以直接聯系我 ([email protected]),但是請注意,我每天都會收到大量有關 php 的電子郵件;您可能會發現如果首先與前面的郵件列表聯系會更快得到答復。
關于作者
wez furlong 是 brain room ltd. 的技術總監,他在該公司不但使用 php 用于 web 開發,還將其用作 linux 和 windows 應用程序和系統的嵌入式腳本引擎。wez 是 php 的核心開發人員,經常向 sqlite、com/.net、activephp、mailparse 和 streams api 等投稿,他是 pecl 即 php 擴展社區庫的“頭兒”。他的咨詢公司的網頁為 http://www.thebrainroom.net。
資源
1.有關 pdo 程序包的更多信息
2.報告新錯誤? - 請提供可能有助于修復該錯誤的任何信息。
3.用于 oracle jdeveloper 的 php 擴展 - 該 php 擴展簡化了 oracle jdeveloper 10g 中 php 腳本的創建、編輯和運行
4.php 漫游者指南 - 了解關于 php 的一切,從啟動您的第一個應用程序到這種語言將擁有怎樣的未來。
新聞熱點
疑難解答