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

PHP 5 數(shù)據(jù)對象 (PDO) 抽象層與 Oracle

[摘要]一名新 PHP 數(shù)據(jù)對象 (PDO) 數(shù)據(jù)抽象層的原始開發(fā)人員為您簡要介紹該抽象層,重點講述與 Oracle 一起運行的情況。 需要 PHP:5.0需要其他:Oracle 8 或更高版本客戶端庫下載用于 Oracle 的 PDO (Windows):php_pdo.dll, php_pdo_oci...

一名新 PHP 數(shù)據(jù)對象 (PDO) 數(shù)據(jù)抽象層的原始開發(fā)人員為您簡要介紹該抽象層,重點講述與 Oracle 一起運行的情況。

需要 PHP:5.0
需要其他:Oracle 8 或更高版本客戶端庫
下載用于 Oracle 的 PDO (Windows):php_pdo.dll, php_pdo_oci.dll
下載用于 Oracle 的 PDO (Unix):pdo, pdo_oci


PDO 簡介

PHP 主要是由志愿者完成的項目;盡管有少數(shù)一些固定的“核心”開發(fā)人員,但是我們沒有一個人在全職受薪的開發(fā) PHP。除此之外,我們分別位于世界不同地方,您可以想象長期開發(fā)的協(xié)調(diào)工作是何等困難。因此,PHP 主要是基于突發(fā)奇想的個人短期需求來發(fā)展的,其原因也多種多樣,有的是試驗,有的則是因為“明天有活要交”。盡管這樣通常每一步都會改善 PHP,但從長遠(yuǎn)來看則是缺乏完整性 - 數(shù)據(jù)庫擴(kuò)展就是一個重要的例子。

在各種不同的數(shù)據(jù)擴(kuò)展(oci、mysql、postgresql、mssql 等)之間根本沒有真正的一致性,甚至在某些情況下,在這些擴(kuò)展內(nèi)部也沒有真正的一致性。幾乎所有這些擴(kuò)展都在使用與基礎(chǔ)數(shù)據(jù)庫 API 緊密相連的不同代碼完成著相同種類的任務(wù)。而且因為我們(PHP 核心開發(fā)人員和擴(kuò)展開發(fā)人員)的人手非常有限,因此這就造成了代碼更加難以維護(hù),從而為 PHP 帶來了很大的問題。

由于 PHP 越來越受歡迎并不斷成功,因此主要 PHP 數(shù)據(jù)庫擴(kuò)展的維護(hù)者們參加了在德國舉行的 LinuxTag 2003 大會,在會上我們交換了對 PHP 前景的看法。在討論 PHP 發(fā)展的隨機(jī)性時,我們確定了在 PHP 中進(jìn)行數(shù)據(jù)庫訪問的一些目標(biāo):

·提供一種輕型、清晰、方便的 API
·統(tǒng)一各種不同 RDBMS 庫的共有特性,但不排除更高級的特性。
·通過 PHP 腳本提供可選的較大程度的抽象/兼容性。

我們之所以提出了這種 PHP 數(shù)據(jù)對象 (PDO) 的概念,是因為我們希望通過采用 Zend Engine 2(PHP 5 的核心)先進(jìn)的面向?qū)ο筇匦垣@得該 API 的一些更優(yōu)秀的性能。

PHP 中的數(shù)據(jù)抽象層概念一點都算不上新;在 Google 中查詢“PHP database abstraction”會找到大約 83,200 個匹配項。它幾乎是許多 PHP 開發(fā)人員夢寐以求的,而其產(chǎn)生則部分歸因于我們不完整的 API。如果您曾經(jīng)嘗試過使用第三方抽象層來完成任何真正重要的工作,通常會發(fā)現(xiàn)這些抽象層對于手頭的工作來說設(shè)計的功能過于強(qiáng)大了 - 或者表現(xiàn)為在使用前需要進(jìn)行大量學(xué)習(xí),或者表現(xiàn)為接口速度緩慢,參數(shù)需要經(jīng)過多層腳本函數(shù)調(diào)用才能到達(dá)數(shù)據(jù)庫自有的 API;通常是存在上述兩種表象。

為什么這些抽象層會存在這種問題?這些抽象層總是在試圖完成太多的任務(wù),甚至可能是不可能的任務(wù)。我們決定以實用為目標(biāo),僅將一些最常見的數(shù)據(jù)庫 API 特性作為我們的基礎(chǔ),并使得 PDO 驅(qū)動程序能夠?qū)⑺鼈兲囟ㄓ诋a(chǎn)品的特性暴露為常規(guī)擴(kuò)展函數(shù)。


為什么使用 PDO?


聽過有關(guān)數(shù)據(jù)庫抽象擴(kuò)展謠傳的大多數(shù)人會立刻對 PDO 的擴(kuò)展方面產(chǎn)生疑惑 - 我們是否要分析 SQL,將其轉(zhuǎn)換為相應(yīng)的后端方言呢?我們?nèi)绾翁幚硖匦?X 或特性 Y,等等。因此,當(dāng)您聽說我們在 PDO 中根本不用為此而擔(dān)憂時可能會大吃一驚;我們不希望使所有內(nèi)容都完全統(tǒng)一,因為要使得這種統(tǒng)一成為可能,只能是將自己限制在最低的通用標(biāo)準(zhǔn)。

如果 PDO 不是一個整體的抽象層,那還有什么別的原因值得您考慮使用它嗎?

·性能。PDO 從一開始就吸取了現(xiàn)有數(shù)據(jù)庫擴(kuò)展成功和失敗的經(jīng)驗教訓(xùn)。因為 PDO 的代碼是全新的,所以我們有機(jī)會重新開始設(shè)計性能,以利用 PHP 5 的最新特性。
·能力。PDO 旨在將常見的數(shù)據(jù)庫功能作為基礎(chǔ)提供,同時提供對于 RDBMS 獨特功能的方便訪問。
·簡單。PDO 旨在使您能夠輕松使用數(shù)據(jù)庫。API 不會強(qiáng)行介入您的代碼,同時會清楚地表明每個函數(shù)調(diào)用的過程。
·運行時可擴(kuò)展。PDO 擴(kuò)展是模塊化的,使您能夠在運行時為您的數(shù)據(jù)庫后端加載驅(qū)動程序,而不必重新編譯或重新安裝整個 PHP 程序。例如,PDO_OCI 擴(kuò)展會替代 PDO 擴(kuò)展實現(xiàn) Oracle 數(shù)據(jù)庫 API。還有一些用于 MySQL、PostgreSQL、ODBC 和 Firebird 的驅(qū)動程序,更多的驅(qū)動程序尚在開發(fā)。

您可能想了解 PDO 與其他常用的抽象層的對比情況,例如 PEAR DB 或 ADODB。無論在 API 方面還是在性能方面,PDO 都比其他常見抽象層要輕型,但是涉及到在各個數(shù)據(jù)庫后端之間提供統(tǒng)一性方面,則不如那些抽象層,例如用于處理大量可移植性問題的 PEAR MDB 2 抽象層。


在哪里可以獲得 PDO?


PDO 是通過 PECL(發(fā)音為“pee-kle”,歐洲語言風(fēng)格),即 PHP 擴(kuò)展庫提供的。如果您在運行 Linux 計算機(jī),請按照下面的說明進(jìn)行設(shè)置;稍后是在 Windows 上安裝的詳細(xì)信息。

請注意,PDO 及其驅(qū)動程序當(dāng)前處于“alpha”狀態(tài);這就意味著我們會合理保證沒有重大缺陷,但是該程序包功能并不完善 - 我們還要添加很多功能。雖然我們鼓勵您測試該程序包,但是實在不推薦在現(xiàn)階段將其用于生產(chǎn)。


Unix/Linux 安裝


如果您以前尚未嘗試過 PHP 5,則請花一點時間來通讀一下“新聞”和各種聲明。在 UNIX 計算機(jī)上,您可能要安裝或升級 libxml2;如果沒有 libxml2,“pear”程序包管理工具就無法運行,您安裝 PDO 時就會遇到很多困難。獲取 PHP 5,并將其編譯和安裝。確保指定的前綴不是 /usr/local/,這樣它就不會與 PHP 4 安裝發(fā)生沖突了:


% ./configure --prefix=/usr/local/php5 --with-zlib [此處指定其他選項]
% make install

 

現(xiàn)在您就可以使用“pear”工具獲取并安裝 PDO 以及用于 PDO 的 Oracle 驅(qū)動程序了。因為 PDO 當(dāng)前標(biāo)記為 alpha,所以默認(rèn)情況下 pear 工具不會下載該程序包。在該程序包名稱后面添加后綴“-alpha”,通知該 pear 工具可以安裝 alpha 版本:


% PATH="/usr/local/php5/bin:$PATH"
% pear install PDO-alpha

 

您需要告知 PHP 從專用于 PHP 5 的 php.ini 文件加載 PDO 驅(qū)動程序。如果您使用的前綴與我使用的一樣,PHP 則會在 /usr/local/php5/lib/php.ini 中查找 php.ini 文件。向該文件中添加以下行:

extension=pdo.so

現(xiàn)在您需要獲取數(shù)據(jù)庫特定的驅(qū)動程序;對于 Oracle,此特定程序稱為 PDO_OCI。在 shell 中,鍵入:

% pear install PDO_OCI-alpha

此驅(qū)動程序也需要從 php.ini 文件加載;將下行添加到前面添加的那行之后:

extension=pdo_oci.so

現(xiàn)在檢查一下,確保它能夠運行:

% php -m

在模塊列表中,您應(yīng)該會看到 PDO 和 PDO_OCI。


防火墻礙事了?


如果您位于防火墻的后面,則在使用 pear 安裝程序獲取程序包時可能會遇到一些問題。如果發(fā)生這種情況,則可以按照下列說明手動下載并安裝這些程序包:


% 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 ]

在上述兩種情況下,都需要首先調(diào)用“pear install”(后跟下載的真正程序包);上述示例中的版本號在本文編寫之時是最新的,但隨著開發(fā)的繼續(xù)進(jìn)行會發(fā)生變化。


Windows 安裝


如果您正在運行 Windows,則請按照下列說明執(zhí)行:

·從 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 驅(qū)動程序,以及所有 PECL 程序包的所有 Windows 版本。
·編輯 C:\php5\php.ini 文件,并添加下列內(nèi)容:

extension=php_pdo.dll
extension=php_pdo_oci.dll

編輯 php.ini 文件時,有一點很重要,即要在任何其他 PDO 驅(qū)動程序之前先加載 PDO 擴(kuò)展,否則就不能正確初始化(在這種情況下會出錯)。

如果在 Windows 目錄中有一個 PHP 4 的全局 php.ini 文件,則可能會遇到問題。最好的解決方法是,移動該 php.ini 文件,使其與 PHP 4 SAPI 位于相同的文件夾中,以隔離 PHP 4 安裝;例如,將其移動到與 php4apache.dll 相同的文件夾中。請注意,PHP 5 程序中并非所有文檔都是最新的;推薦的安裝過程如上面所述 - 如 install.txt 文件所聲明的,請勿將任何 DLL 復(fù)制到 windows 文件夾或 system 文件夾中 - 任何內(nèi)容都是自包含的。如果您運行的是 apache,并且遇到無法加載 DLL 的錯誤,則檢查一下是否將 C:\php5 添加到了 PATH 中。另外,還要注意 PHP 5 的 CGI 版本現(xiàn)在的名稱為 php-cgi.exe。


連接 PDO


首先創(chuàng)建 PDO 類的一個實例,將其用作數(shù)據(jù)庫句柄。使用哪個基礎(chǔ)驅(qū)動程序并不重要;您總要使用 PDO 類名。構(gòu)造函數(shù)的第一個參數(shù)為數(shù)據(jù)源名稱 (DSN),第二個參數(shù)為用戶名,第三個參數(shù)為該用戶名的口令。DSN 的 PDO 命名慣例為 PDO 驅(qū)動程序的名稱,后面一個冒號,再后面是可選的驅(qū)動程序特定的信息。在我們的示例中,會加載 OCI 驅(qū)動程序但不指定任何其他信息;這樣會使用默認(rèn)的數(shù)據(jù)庫。對于其他驅(qū)動程序,如 ODBC 驅(qū)動程序,第一個冒號后面的所有內(nèi)容都將被用作 ODBC DSN。MySQL 驅(qū)動程序會同樣以不同的方式解釋它的 DSN。

如果無法加載該驅(qū)動程序,或者發(fā)生了連接失敗,則會拋出一個 PDOException,以便您可以決定如何最好地處理該故障。


<?php
try {
$dbh = new PDO("OCI:", "scott", "tiger");
} catch (PDOException $e) {
echo "Failed to obtain database handle " .$e->getMessage();
    }
?>


在連接字符串中,您可以指定兩個可選參數(shù);第一個是數(shù)據(jù)庫名稱,第二個是字符集;這些參數(shù)與可選的第三個和第四個參數(shù)相對應(yīng),后兩個參數(shù)您可能在 oci8 擴(kuò)展函數(shù) ociconnect() 或 ociplogon() 中使用過。要使用特定的字符集連接一個特定的數(shù)據(jù)庫,則可以執(zhí)行下列操作:


<?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 控制結(jié)構(gòu)并無裨益。如果在應(yīng)用程序的較高級別沒有定義異常處理,則在無法建立數(shù)據(jù)庫連接的情況下,該腳本會終止。


連接管理


目前,PDO 完全沒有執(zhí)行自己的任何連接管理,因此每個“新 PDO”調(diào)用都會建立一個新的數(shù)據(jù)庫連接。該連接在 $dbh 變量越界時,或者當(dāng)您為其指定 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;
    }
// 在此處對數(shù)據(jù)庫執(zhí)行一些操作
    // ...
   
// 現(xiàn)在完成,釋放該連接
$dbh = null;
?>


計劃在不久的將來為 PDO 增加連接緩存功能;就當(dāng)前的 oci8 擴(kuò)展而言,會重用與現(xiàn)有服務(wù)器的連接,并且在這些連接中,還會重用閑置的登錄。當(dāng)在緩存連接模式中運行時,如上面的代碼段所示釋放 $dbh 時會將該登錄標(biāo)記為可由其他連接重用。

如果您使用 ODBC 驅(qū)動程序訪問 Oracle,則可能會很高興地注意到,默認(rèn)情況下 PDO_ODBC 驅(qū)動程序支持 ODBC 連接池。


使用 PDO


了解一個編程 API 的最好方式就是使用它,因此我們來看一下附帶的這個演示,以了解如何進(jìn)行批次更新(代碼如下)。


<?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();

?>


既然我們已經(jīng)成功連接到了 Oracle,那么現(xiàn)在就可以創(chuàng)建一個表來保存一些數(shù)據(jù)了。對于此示例,我們使用一些 PHP 擴(kuò)展及其作者,并將這些內(nèi)容輸入一個數(shù)據(jù)庫中。數(shù)據(jù)庫句柄對象的 exec() 方法可用來發(fā)出不會返回結(jié)果集的快速一次性查詢,因此我們在這里使用該方法來發(fā)出 CREATE TABLE 查詢。

為了使得示例更自然,我從 PHP 源代碼中抽取了擴(kuò)展及其作者的信息,并將其存儲到了一個 CSV 文件中(請參見“相關(guān)附件:credits.csv”)。這就代表一個常見情形:從 CSV 文件批次導(dǎo)入數(shù)據(jù)。在我們的示例中,我們充分利用了 Oracle 的預(yù)處理語句和綁定參數(shù),以獲得一個高效的數(shù)據(jù)導(dǎo)入腳本。在講述該示例之前,有必要了解一下 PDO 處理事務(wù)的方式。


PDO 中的事務(wù)處理


Oracle 具有一個敏感的默認(rèn)操作模式:當(dāng)您進(jìn)行連接時,將會位于一個隱式事務(wù)處理中,在提交事務(wù)之前其中的更改不會完全生效。除了事務(wù)處理的標(biāo)準(zhǔn)優(yōu)點(原子性、一致性、隔離性、可持久性 - ACID)之外,數(shù)據(jù)庫服務(wù)器在執(zhí)行每次更新之后還不需要重新構(gòu)建索引和其他內(nèi)部結(jié)構(gòu);它可以延遲到提交之后進(jìn)行。這樣會加速代碼的執(zhí)行。Oracle 這點確實很好。

但不幸的是,并非每個數(shù)據(jù)庫供應(yīng)商都支持事務(wù)處理,并且因為 PDO 旨在以一種相對可移植的方式支持這些事務(wù)處理,所以它默認(rèn)情況下以自動提交模式運行。啟用自動提交模式后,數(shù)據(jù)庫驅(qū)動程序會隱式提交每個成功的更新。當(dāng)您調(diào)用 $dbh->beginTransaction() 時,就會請求關(guān)閉自動提交,直到調(diào)用 $dbh->commit() 或者 $dbh->rollBack() 才會重新啟用,具體取決于您的代碼是怎樣編寫的。如果基礎(chǔ)驅(qū)動程序不支持事務(wù)處理,則會拋出一個 PDOException。

如果發(fā)生了問題并且 PHP 出錯,您的腳本將退出并且事務(wù)處于待批狀態(tài);或者您關(guān)閉數(shù)據(jù)庫句柄時,PDO 會自動針對任何待批的事務(wù)調(diào)用 $dbh->rollBack()。此行為會減少向數(shù)據(jù)庫中提交可能未定義或者已損壞數(shù)據(jù)的可能性,這是用于處理已放棄事務(wù)的標(biāo)準(zhǔn)語義。

 

預(yù)處理語句、存儲過程


PDO 支持使用 Oracle 樣式命名的占位符語法將變量幫定到 SQL 中的預(yù)處理語句(與 oci8 擴(kuò)展中的 ocibindbyname() 類似)。PDO 還為其他數(shù)據(jù)庫(如 ODBC)提供了命名占位符模擬,甚至可以為生來就不支持該概念的數(shù)據(jù)庫(如 MySQL)模擬預(yù)處理語句和綁定參數(shù)。這是 PHP 向前邁進(jìn)的積極一步,因為這樣可以使開發(fā)人員能夠用 PHP 編寫“企業(yè)級”的數(shù)據(jù)庫應(yīng)用程序,而不必特別關(guān)注數(shù)據(jù)庫平臺的能力。

使用 PDO 預(yù)處理語句非常簡單,調(diào)用數(shù)據(jù)庫句柄的 prepare() 方法即可。它會返回一個語句句柄對象,然后您可以使用該對象來綁定參數(shù)和執(zhí)行語句。在此示例中,我們將要定義兩個命名占位符,“:extension”和“:name”,這兩個占位符分別與 .CSV 文件中的 PHP 擴(kuò)展名稱和其中一個作者的姓名相對應(yīng)。


$stmt = $dbh->prepare("INSERT INTO CREDITS (extension, name) VALUES (:extension, :name)");


預(yù)處理了語句之后,我們使用 bindParam() 方法來將這些命名參數(shù)分別與 PHP 變量名稱“$extension”和“$name”相關(guān)聯(lián)(這與 ocibindbyname() 類似)。我們還會通知 Oracle,這些數(shù)據(jù)將要格式化為字符串,最大長度為 64 個字符。


$stmt->bindParam(':extension', $extension, PDO_PARAM_STR, 64);
$stmt->bindParam(':name',      $name,      PDO_PARAM_STR, 64);


我們現(xiàn)在即準(zhǔn)備好插入數(shù)據(jù)了 - 我們只需要打開該 CSV 文件,并從中獲取數(shù)據(jù)即可。通過使用 fopen() 和 fgetcsv() 函數(shù)可以相當(dāng)簡單地完成此操作。然后,我們可以使用 PHP list() 構(gòu)造函數(shù)直接將 CSV 的列指定給變量“$extension”和“$name”。因為這些變量已經(jīng)綁定到了語句中,所以我們現(xiàn)在要做的只是調(diào)用該語句對象的 execute() 方法使其執(zhí)行插入。這種方式既方便又快捷 - 在事務(wù)處理時每個迭代循環(huán)只有兩行。到達(dá)文件尾時,我們就可以立即使用數(shù)據(jù)庫句柄的 commit() 方法來提交這些更改了。

如果您只是要傳遞輸入?yún)?shù),并且有許多這樣的參數(shù)要傳遞,那么您會覺得下面所示的快捷方式語法非常有幫助;此語法使您能夠省去對 $stmt->bindParam() 的調(diào)用。


$stmt = $dbh->prepare("INSERT INTO CREDITS (extension, name) VALUES (:extension, :name)");
$stmt->execute(array(':extension' => $extension, ':name' => $name));


您還可以使用 bindParam 來為存儲過程設(shè)置輸入/輸出參數(shù);語法是完全相同的,只是查詢有所不同。下面的代碼演示如何調(diào)用一個名為“sp_add_item”的存儲過程;其目的是要針對輸入設(shè)置 $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();

 


抓取數(shù)據(jù)


使用 PDO 抓取數(shù)據(jù)與進(jìn)行插入或更新相似,只是您執(zhí)行完查詢之后,將要重復(fù)調(diào)用 fetch() 方法來獲取結(jié)果集的下一行。進(jìn)行獲取的最簡單情況如下所示,值得注意的一點是,您還可以將參數(shù)綁定到查詢,以控制如 WHERE 子句這樣的內(nèi)容;執(zhí)行此操作的語法與我們已經(jīng)看到的 bindParam() 代碼完全相同。

 

$stmt = $dbh->prepare("SELECT extension, name from CREDITS");
if ($stmt->execute()) {
while ($row = stmt->fetch()) {
print_r($row);
    }
}


PDO 支持一些不同的抓取策略,這些策略在方便性和性能方面有所差別;通過將下列選項之一指定為 fetch() 方法的參數(shù),您可以更改其返回值以適應(yīng)您的語法:


·PDO_FETCH_NUM - 每個行抓取返回一個按照列位置索引的數(shù)組,并且以 0 為基數(shù)(第一列是第 0 個元素)。

while ($row = $stmt->fetch(PDO_FETCH_NUM)) {
 printf("Extension %s, by %s<br>", $row[0], $row[1]);
}


·PDO_FETCH_ASSOC - 每個行抓取根據(jù)行集中的列名,返回一個按列名索引的數(shù)組。

while ($row = $stmt->fetch(PDO_FETCH_ASSOC)) {
 echo "Extension $row[EXTENSION] by $row[NAME]<br>";
}

·PDO_FETCH_BOTH - 每個行抓取返回一個既按照列位置又按照列名索引的數(shù)組。也就是上述兩種情況的直接組合。如果沒有指定抓取模式,則該模式為默認(rèn)模式。
·PDO_FETCH_OBJ - 每個行抓取返回一個匿名對象,其屬性名與列名對應(yīng)。

while ($row = $stmt->fetch(PDO_FETCH_ASSOC)) {
 echo "Extension {$row->EXTENSION} by {$row->NAME}<br>";
}

·PDO_FETCH_LAZY - 每個行抓取返回一個引用語句對象的重載對象。這“看起來”好像是 PDO_FETCH_OBJ 和 PDO_FETCH_BOTH 的組合,只是只有當(dāng)您在腳本中訪問 PHP 變量時才創(chuàng)建這些變量。
·PDO_FETCH_BOUND - 抓取每行,返回 TRUE。在使用綁定輸出列時這種方式非常有用,它可以避免創(chuàng)建不需要的任何數(shù)組或?qū)ο。(請參見下面的示例)?


無論您使用哪種抓取策略,當(dāng)沒有其他行可抓取時,fetch() 方法將會返回 FALSE。

現(xiàn)在我要講述一些技巧,如果您需要最后再調(diào)整一下腳本性能的話,這些技巧可能會對您有所幫助。但先給你一個忠告:要像躲避瘟疫一樣避免不成熟的優(yōu)化。您應(yīng)該總是首選最清晰、可維護(hù)性最好的解決方案。請記住,在一個典型的 Web 應(yīng)用程序中,您不能衡量各種抓取模式間的區(qū)別,除非腳本要處理很多行。我再重復(fù)一遍:抓取模式間的性能區(qū)別非常小 - 請使用最適合您代碼的模式。

請記住,使用 PDO_FETCH_NUM 的花銷最小,因為訪問列數(shù)據(jù)只是一個簡單的數(shù)值查詢。PDO_FETCH_OBJ 使您能夠使用 OO 語法將數(shù)據(jù)集的列作為對象的屬性來訪問,但是每個屬性訪問都涉及一個附加的散列查詢,使得使用它的花銷基本上與 PDO_FETCH_ASSOC 相同。每個這樣的模式都會復(fù)制整行,從而占用稍多的內(nèi)存。

很多數(shù)據(jù)庫驅(qū)動程序都會代表您預(yù)先抓取并緩存一定數(shù)量的行。PHP 每次訪問其中一個這樣行中的列時,它都需要將其復(fù)制到自己的專用內(nèi)存區(qū)域中。如果您的查詢涉及很多行,而只需要基于某種復(fù)雜的邏輯訪問給定行的特定列,則您會發(fā)現(xiàn) PDO_FETCH_LAZY 是一種避免使用很多內(nèi)存的有用方法,因為它只有在您訪問給定列時才復(fù)制該列。使用此方式時要注意,從某個給定語句為每個 fetch() 抓取的“惰性對象”是每次迭代時使用的同一對象(以減少每次創(chuàng)建/銷毀它的開銷)。這就暗示著您不能只是簡單地存儲該對象用于以后的比較,因為它仍然會引用該語句的當(dāng)前行 - 您需要手動復(fù)制所需要的部分。

最后一種模式為 PDO_FETCH_BOUND,該模式會告知 PDO 您已經(jīng)將所有列綁定到了 PHP 變量,并且除了要它在到達(dá)行集的末尾時通知您外不需要它執(zhí)行別的任何操作。綁定輸出列在概念上與綁定輸入?yún)?shù)相似,只是綁定輸出列可以用于所有數(shù)據(jù)庫驅(qū)動程序。您可以將 PHP 變量綁定到命名列,PDO 將在每次調(diào)用 execute() 時對其進(jìn)行更新。此技術(shù)可用來剃去結(jié)果集中每列、每行的一些虛擬機(jī)器操作碼(這種代碼速度比原生碼要慢)。這種技術(shù)的缺點在于,可能會使您的代碼難以跟蹤(也稱為 WTF 系數(shù)較高),您使用變量名稱時需要倍加小心。下面的代碼說明了綁定輸出列的使用。請注意,您不必指定 PDO_FETCH_BOUND 即可使用 $stmt->bindColumn();PDO_FETCH_BOUND 只是一個對于您了解只能使用綁定值的情況的一種優(yōu)化。


$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";
    }
}

 


可移植性


區(qū)分大小寫的列

PDO 旨在令使用可移植 SQL 的腳本運行良好、可移植。本文中提及的所有查詢(調(diào)用存儲過程除外)在使用任何 PDO 驅(qū)動程序時其運行性能應(yīng)該相同 - 包括所有綁定輸入變量和綁定輸出列。

但有一個轉(zhuǎn)換問題 - 當(dāng)您使用 PDO_FETCH_ASSOC 抓取數(shù)據(jù)時,不同的驅(qū)動程序會以不同的方式返回列名 - 某些會將列名轉(zhuǎn)化為大寫,某些轉(zhuǎn)換為小寫,某些則會使其呈查詢中指定的樣式。這對于 PHP 腳本來說是一個潛在的問題,因為數(shù)組鍵區(qū)分大小寫。PDO 提供了一個兼容性屬性來幫助規(guī)范腳本的結(jié)果。下面的小代碼段是上面 PDO_FETCH_BOUND 示例的可移植版本,因為 setAttribute() 方法調(diào)用會指導(dǎo) PDO 將抓取返回的列名全部轉(zhuǎn)換為大寫:


$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(它會將列名轉(zhuǎn)換為小寫)和 PDO_CASE_NATURAL(它是默認(rèn)選項:使列保持?jǐn)?shù)據(jù)庫驅(qū)動程序返回的形式)。

錯誤和錯誤處理

可移植腳本的另一個難題是處理從各種數(shù)據(jù)庫處理程序返回的各種不同的錯誤消息;某些數(shù)據(jù)庫對于程序化處理錯誤的支持能力很差,而其他一些數(shù)據(jù)庫則具有非常豐富的錯誤代碼。只要可行,PDO 將為您的腳本提供一個統(tǒng)一的錯誤代碼,從而使您不必為應(yīng)對可移植性的這個方面所累。當(dāng)然,PDO 還會為驅(qū)動程序提供原生錯誤代碼和錯誤消息,以防您需要用它來進(jìn)行診斷,或者錯誤代碼映射不完整。

另一個困擾 PHP 數(shù)據(jù)庫擴(kuò)展的一致性問題是錯誤處理策略的一致性:某些擴(kuò)展會返回的錯誤代碼需要您手動抓取錯誤字符串,而其他一些擴(kuò)展則只是發(fā)出 PHP 警告。PDO 允許您從下列三種不同的錯誤處理策略中選擇一種:

·PDO_ERRMODE_SILENT
這是默認(rèn)模式;它只是使用語句和數(shù)據(jù)庫句柄對象的 errorCode() 和 errorInfo() 方法為您設(shè)置要檢查的錯誤代碼。


if (!$dbh->exec($sql)) {
 echo $dbh->errorCode() ."<BR>";
 $info = $dbh->errorInfo();
 // $info[0] == $dbh->errorCode() 統(tǒng)一的錯誤代碼
 // $info[1] 是驅(qū)動程序特定的錯誤代碼
 // $info[2] 是驅(qū)動程序特定的錯誤字符串
}

·
PDO_ERRMODE_WARNING
除了設(shè)置錯誤代碼之外,PDO 還會發(fā)出 PHP 警告,您可以使用常規(guī)的 PHP 錯誤處理程序捕獲該警告,并集中應(yīng)用您準(zhǔn)備好用于應(yīng)用程序的任何錯誤處理/記錄策略,或者只是使該錯誤顯示在瀏覽器中(在內(nèi)部測試過程中非常有用)。
·
PDO_ERRMODE_EXCEPTION
除了設(shè)置錯誤代碼之外,PDO 還會拋出一個 PDOException,并將其屬性設(shè)置為包含該錯誤代碼和信息。然后,您可以在代碼的較高級別捕獲該異常,使用全局異常處理程序捕獲該異常,或者不對其進(jìn)行處理而終止腳本(此時將回滾任何未決的事務(wù))。

try {
 $dbh->exec($sql);
} catch (PDOException $e) {
 // 顯示警告消息
 print $e->getMessage();
 $info = $e->errorInfo;
 // $info[0] == $e->code; unified error code
 // $info[1] 是驅(qū)動程序特定的錯誤代碼
 // $info[2] 是驅(qū)動程序特定的錯誤字符串
}


請注意,與警告或異常相比,靜默模式針對運行時錯誤使用的資源最少,但是為了獲得該速度,您犧牲了一些簡單性,而變得有一點復(fù)雜。

統(tǒng)一錯誤代碼表當(dāng)前包括下列常量: 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 特定的代碼,也就是說它無法將驅(qū)動程序特定的代碼映射到統(tǒng)一的錯誤代碼,因此您應(yīng)該查詢 errorInfo() 方法返回的驅(qū)動程序特定代碼來獲得更多信息。

數(shù)據(jù)類型

PDO 在某種程度上類型不可知,因此它喜歡將數(shù)據(jù)表示為字符串,而不是將其轉(zhuǎn)換為整數(shù)或雙精度類型。此時您可能對此有些迷惑,但是原因非常簡單:字符串類型是最精確的類型,在 PHP 中具有最廣泛的應(yīng)用范圍;過早地將數(shù)據(jù)轉(zhuǎn)換為整數(shù)或者雙精度類型可能會導(dǎo)致截斷或舍入錯誤。通過將數(shù)據(jù)以字符串抽出,PDO 為您提供了一些腳本控制,您可以使用普通的 PHP 類型轉(zhuǎn)換工具(如數(shù)學(xué)運算過程中的轉(zhuǎn)換和隱式)來控制如何進(jìn)行轉(zhuǎn)換以及何時進(jìn)行轉(zhuǎn)換。


NULL

如果結(jié)果集中的某列包含一個 NULL 值,PDO 則會將其映射為 PHP null 值。Oracle 在將數(shù)據(jù)返回 PDO 時會將空字符串轉(zhuǎn)換為 NULL,但是 PHP 支持的任何其他數(shù)據(jù)庫都不會這樣處理,從而導(dǎo)致了可移植性問題。PDO 提供了一個驅(qū)動程序級屬性 PDO_ATTR_ORACLE_NULLS,該屬性會為其他數(shù)據(jù)驅(qū)動程序模擬此行為:


$dbh = new PDO('OCI:', 'scott', 'tiger');
$dbh->setAttribute(PDO_ATTR_ORACLE_NULLS, true);
// 現(xiàn)在從此 $dbh 打開的任何語句中的
// 空字符串都將被轉(zhuǎn)換為 NULL

 

POD 的現(xiàn)狀和未來


PDO 現(xiàn)在仍相當(dāng)不成熟,但是會快速成熟起來。在編寫本文之時,我在本文中提到的任何內(nèi)容都能夠通過 PDO_OCI 驅(qū)動程序適用于 Oracle 8 或更高版本(在 Oracle 8.0 和 9.2 上測試過)。

已經(jīng)計劃增加以下主要特性,在不久將可以使用:

1.使用 PHP 流的 LOB 支持。 使用綁定參數(shù),您能夠?qū)⑷魏瘟髻Y源(如文件、套接字、HTTP 資源、壓縮/篩選的流)作為輸入或輸出參數(shù)傳遞到在 LOB 上運行的查詢中。與之相似,類型為 LOB 的輸出參數(shù)將表現(xiàn)為 PHP 流,因此您可以使用 fread()、fwrite()、fseek() 和其他流函數(shù)來訪問這些參數(shù)。此時,在 PDO 中根本沒有 LOB 支持。
2.持久性連接和緩存的預(yù)處理語句。 持久性連接使您能夠避免在每個頁面命中時打開和關(guān)閉數(shù)據(jù)庫服務(wù)器連接。緩存的預(yù)處理語句又前進(jìn)了一步,它使您能夠持久保持查詢的預(yù)處理版本以及數(shù)據(jù)庫句柄。
3.游標(biāo)。 目前,PDO 只提供前向只讀游標(biāo),但是將來會提供可滾動游標(biāo)(需要基礎(chǔ)驅(qū)動程序支持)、REF-CURSOR、使用游標(biāo)進(jìn)行定位更新,以及可更新滾動游標(biāo)。

我們希望在 PHP 5.1 中默認(rèn)啟用 PHP 擴(kuò)展(距此目標(biāo)尚遠(yuǎn)),但是在此之前,我們希望能讓 PDO 在 PHP 5.0 發(fā)布時穩(wěn)定運行,但是我們?nèi)粘9ぷ髦械膲毫ι陨酝涎恿诉@些工作。同時,通過 PECL 發(fā)布 PDO 使我們能夠在收到問題報告時做出回應(yīng),并根據(jù)不同于 PHP 5.0 發(fā)布時間表的時間表發(fā)布修復(fù)版本,因此您在 PHP 5.1 發(fā)布前即可使用 PDO。

我們需要您的反饋

如果您試用了 PDO,并且發(fā)現(xiàn)了問題,請務(wù)必使用我們的錯誤跟蹤軟件將其報告給我們。如果您使用的是 Oracle 驅(qū)動程序,則請使用此頁:

http://pecl.php.net/bugs/report.php?package=PDO_OCI

如果您使用的是其他驅(qū)動程序,則請用其名稱替換該 URL 中 PDO_OCI。

如果您使用 PDO 時遇到問題,或者針對某些特性存在疑問,或者具有特性請求,請聯(lián)系 pecl-dev@lists.php.net。如果您愿意,當(dāng)然還可以直接聯(lián)系我 (wez@php.net),但是請注意,我每天都會收到大量有關(guān) PHP 的電子郵件;您可能會發(fā)現(xiàn)如果首先與前面的郵件列表聯(lián)系會更快得到答復(fù)。

-----------
關(guān)于作者
Wez Furlong 是 Brain Room Ltd. 的技術(shù)總監(jiān),他在該公司不但使用 PHP 用于 Web 開發(fā),還將其用作 Linux 和 Windows 應(yīng)用程序和系統(tǒng)的嵌入式腳本引擎。Wez 是 PHP 的核心開發(fā)人員,經(jīng)常向 SQLite、COM/.Net、ActivePHP、mailparse 和 Streams API 等投稿,他是 PECL 即 PHP 擴(kuò)展社區(qū)庫的“頭兒”。他的咨詢公司的網(wǎng)頁為 http://www.thebrainroom.net。