數據對象
並不能使用PDO擴展本身執行任何資料庫操作,必須使用一個database-specific PDO driver(針對特定資料庫的PDO驅動)訪問
資料庫伺服器。
PDO並不提供資料庫抽象,它並不會重寫SQL或提供資料庫本身缺失的功能,如果你需要這種功能,你需要使用一個更加成熟的抽象層。
PDO需要PHP5核心OO特性的支持,所以它無法運行於之前的PHP版本。
運行環境
在Unix環境下PHP5.1以上版本中:
如果你正在使用PHP5.1版本,PDO和PDO SQLITE已經包含在了此發行版中;當你運行configure時它將自動啟用。推薦你將PDO作為共享擴展構建,這樣可以使你獲得通過PECL升級的好處。推薦的構建支持PDO的PHP的configure line應該也要啟用zlib。你也應該啟用你選擇的資料庫的PDO驅動 ;關於這個的更多信息請查看database-specific PDO drivers ,但要注意如果你將PDO作為一個共享擴展構建,你必須也要將PDO驅動構建為共享擴展。SQLite擴展依賴於PDO,所以如果PDO作為共享擴展構建,SQLite也應當這樣構建
./configure --with-zlib --enable-pdo=shared --with-pdo-sqlite=shared --with-sqlite=shared
將PDO安裝為一個共享模組後,你必須編輯php.ini檔案使得在PHP運行時自動載入PDO擴展。你同樣需要啟用那兒的特定資料庫驅動;確保他們列出在 pdo. so 行之後,因為PDO必須在特定資料庫驅動載入之前初始化。如果你是以靜態方式構建的PDO和特定資料庫驅動擴展,你可以跳過這一步。
extension=pdo. so
讓PDO作為一個共享的模組將使你可以在新版PDO發布時運行 pecl upgrade pdo 命令升級,而不用強制你重新構建整個PHP。注意如果你是這樣做的,你也需要同時升級你的特定資料庫驅動。
在windows環境下PHP5.1以上版本中:
PDO和主要資料庫的驅動同PHP一起作為擴展發布,要激活它們只需簡單的編輯php.ini檔案:
extension=php_pdo.dll
然後,選擇針對特定資料庫的DLL檔案使用 dl() 在運行時載入,或者在php.ini檔案中 php_pdo.dll 行後啟用它們,如:
extension=php_pdo.dll
extension=php_pdo_firebird.dll
extension=php_pdo_informix.dll
extension=php_pdo_mssql.dll
extension=php_pdo_mysql.dll
extension=php_pdo_oci.dll
extension=php_pdo_oci8.dll
extension=php_pdo_odbc.dll
extension=php_pdo_pgsql.dll
extension=php_pdo_sqlite.dll
這些DLL檔案應當存在於系統的 extension_dir 目錄里。
注意 PDO_INFORMIX 只能作為一個PECL擴展使用。
========================================================================================
PHP 5.1 發布時將附帶一個全新的資料庫連線層,即 PHP Data Objects (PDO)。雖然 PHP 一直都擁有很好的資料庫連線,但 PDO 讓 PHP 達到一個新的高度。學習如何獲得、安裝和使用 PDO,以連線到 IBM? DB2? Universal Database? 和 IBM Cloudscape? 資料庫,插入和檢索數據,並探索更多高級特性,例如預處理語句(prepared statements)、綁定參數(bound parameters)、可滾動游標(scrollable cursors)、
定位更新(positioned updates)以及 LOB。
背景
隨著擁有更成熟 OO 語法的 PHP 5 的發布,PHP 越來越多地受到越來越大的機構的關注,對於 PHP 來說,提供更加一致的和可訪問的數據訪問 API 變得越來越重要。
PHP 與流行的
開放原始碼關係資料庫管理系統(RDBMS)MySQL 之間總是很有默契。這對拍檔的成功很大程度上是由於它們免費可用,而且進入的門檻也比較低,這兩種產品的合作使它們各自都取得了廣受推崇的地位。
很多 PHP 應用程式開發人員都習慣於 PHP-MySQL 這對組合,以致 PHP 對其他資料庫的支持常常模仿 MySQL 客戶機庫 API。然而,並不是所有的資料庫客戶機 API 都是一樣的,也不是所有的資料庫都提供相同的特性。雖然存在模仿,但不同的 PHP 資料庫擴展都有它們各自的怪僻和不同之處,所以從一種資料庫遷移到另一種資料庫時會有一些困難。雖然這不是創建 PDO 的直接原因,但是在設計過程中還是有一定影響的。
如果您是帶著想結合使用 PHP 和 DB2 的目的閱讀本文,那么您很可能屬於以下類型中的一種:
您從一家小公司開始,在 MySQL(舉個例子)上運行 PHP,由於業務增長,您需要 DB2 所提供的可伸縮性/可靠性/支持或其他特性。您希望移植代碼,以使用 DB2,但由於 API 的變化,您需要編寫或實現一個抽象層,以便在 DB2 上測試應用程式的同時可以繼續在舊的資料庫上運行。不僅如此,您還希望能有自己的選擇,並保留支持其他 RDBMS 的可能性,因為您清楚,有些客戶機可能已經和其他平台栓在一起了。
您用 PHP 在 MySQL之上構建了一個小型的部門應用程式(同樣,這只是舉個例子,我並不是要跟 MySQL 過不去)。事實證明這個應用程式本身很有用,已經在這個部門之外使用,並且闖入了 CIO/CTO 的法眼 —— 需要遵從託管的標準資料庫。(是的,這是第一點的一個變種。) 在其他某些複雜的企業級應用程式的後台,您已經有一個 DB2 實例,您希望利用 PHP 的快速應用程式開發和
原型設計來生成動態報告。
目標
至此我們已經掌握了資料庫及 PHP 的背景知識,正好可以提及 PDO 背後的一些設計目標:
為大多數資料庫 API 中的常見特性提供一致的 API。
具有可擴展性,以使資料庫供應商 X 仍然可以暴露特性 Y 並保持 PDO 的兼容性。
提供大量基本的兼容性技巧,以便能夠更方便地創建跨資料庫兼容的應用程式。
不為給定資料庫 API 中本來沒有的特性(例如序列)提供完全抽象或仿真。PDO 類意圖為您提供對資料庫本地特性的一致性訪問,並減少干擾。
通過將與 PHP 內部打交道的代碼(這是最難於編寫的部分)集中起來,簡化 PHP 資料庫驅動程式的創建。
最後一點非常重要。PDO 是模組化結構,它被分成一個公共核心以及一個或多個驅動程式擴展,公共核心提供了在腳本(PDO 本身)中使用的 API,驅動程式擴展則為 PDO 和本地 RDBMS 客戶機 API 庫架起一座橋樑。DB2 用戶將會希望使用 PDO_ODBC 驅動程式,據稱它可以提供以下特性:
它經過重新編寫,能支持遵從 ODBC V3 的驅動程式和驅動程式管理器。它還考慮了對 DB2 特定特性和最佳化的支持,這成為設計過程中的一部分 —— 不是後來補充的。
它支持經過試驗和測試的
存儲過程和大型對象。它不僅能夠工作,而且非常好用。
對於取 10,000 行記錄這樣的 DB2 訪問操作,使用 PDO_ODBC 驅動程式時的性能比使用傳統的 PHP Unified ODBC 擴展要快大約 10 倍。之所以有這么大的差異,是因為在 PDO 中默認的
游標是輕量級的只能向前移動的游標。
獲取
PHP 5.1 發布時將附帶 PDO,但是也可以通過 PECL 這個 PHP 擴展庫(PHP Extension Repository)來結合使用 PDO 和 PHP 5.0.3 及以上版本。如果您使用的是 Windows,那么您會欣喜地發現安裝過程要簡單得多。
我將假設您已經擁有配置 PHP 5 使之使用您選擇的 Web 伺服器的經驗,只有在此假設下,我才能集中精力關注更相關的細節。同樣,我還將假設您使用的是一個 DB2 Universal Database 伺服器或
網路伺服器模式下的 IBM Cloudscape 資料庫,並且接受了用戶為
db2inst1、密碼為
ibmdb2 的默認安裝選項。如果您自己編譯驅動程式,那么在進行編譯的機器上,應該安裝有 DB2 客戶機,並且存在應用程式開發 header,否則編譯將遭到失敗。
安裝
在 PHP 5.0.3 及以上版本上通過 PECL 進行安裝
默認情況下,PHP 將安裝 "PEAR" 包管理系統。您選擇的 OS 發行版很可能已經創建了一個包含 PEAR 的組件的包,很可能您已經安裝了這個包,並準備運行它。讓我們試驗一下。
如果它不能工作如果您沒能看到類似於左側文本的輸出,那么很可能您沒有安裝需要的所有包。這時應查閱您選擇的 OS 發行版的文檔,看看接下來應該做什麼。或者,您可以自己編譯 PHP。
清單 1. 列出已安裝的 PEAR 包
$ pear listInstalled packages:=================== Package Version StateArchive_Tar 1.1 stable Console_Getopt 1.2 stablePEAR 1.3.4 stable XML_RPC 1.1.0 stable
這個包列表表明,我已經安裝了 PEAR 1.3.4。很可能您也會安裝那個版本。為了成功地安裝 PDO,需要升級到 PEAR 1.3.5;這個過程很快,很順利:
可以放心安裝 PDO 了:
您已經安裝了 PDO 核心,為了使之生效,需要在 php.ini 檔案中啟用它。您需要添加以下一行:
安裝用於 PDO 的 ODBC 驅動程式,如果您需要連線到 DB2、Cloudscape 或 Apache Derby,就需要這個驅動程式:
$ sudo pear install PDO_ODBC |
您將看到這樣的提示:flavour,dir ? (just leave blank for help)。這是一個稍微有點隱蔽的提示,它詢問需要配置哪種類型的 ODBC 驅動程式,以及將它安裝在哪裡。如果在安裝 DB2 時選擇了默認安裝選項,那么可以輸入 ibm-db2,這相當於 ibm-db2,/home/db2inst1/sqllib。如果您選擇了不同的安裝位置,那么應該用它置替換 /home/db2inst1/sqllib。輸入了正確的細節後,按下 enter 鍵,這樣驅動程式就會構建和安裝。
您需要將驅動程式添加到 php.ini 檔案中,從而激活驅動程式。確保將下面這一行添加在之前所添加的 pdo. so 這一行之後,否則 PHP 不能正確地初始化。
PHP 5.1 及以上版本上的安裝
PHP 5.1 發布時附帶了 PDO。為獲得 DB2 支持,只需將下面的開關添加到配置行。您顯然希望添加更多的配置選項,以滿足您自己的 Web 伺服器。關於這方面的詳細內容,可以查看 PHP 文檔。我將假設您使用的是一個最近的 Linux? 發行版,並運行 Apache 2:
$ tar xjf php-5.1.0.tar.bz2$ cd php-5.1.0 $ ./configure --with-pdo-odbc=ibm-db2,/home/db2inst1/sqllib \ --with-apxs2=/usr/sbin/apxs$ make $ sudo make install
Windows 上的安裝
Windows 上的安裝比起 UNIX? 上的安裝來要簡單一些。如果您下載了 PHP 5.1,那么就已經擁有了這個包中相關的 DLL。否則,您需要從 PHP 快照站點(請參閱本文後面的“下載”小節)下載這些 DLL。
為了激活 PDO,將下面兩行添加到 php.ini 檔案:extension=php_pdo.dll extension=php_pdo_odbc.dll
重新啟動 Web 伺服器
安裝完畢後,應該完全重新啟動 Web 伺服器,以確保 PHP 裝載新的擴展,這樣就可以開始使用 PDO 了。如果您使用的是 UNIX 平台,那么需要獲得 DB2 客戶機的 DB2 實例環境,以便正確地初始化。如果您使用的是 bourne shell 型的 shell,那么可以通過
運行命令 . /home/db2inst1/sqllib/db2profile 來獲得。(注意: 假定開頭部分的句號已經在那裡!)您需要作出安排,使之在 Web 伺服器啟動腳本中自動發生。
關鍵概念
1、連線管理
連線是通過創建 PDO 基類的實例而建立的。不管您想要使用哪種驅動程式,您總是使用 PDO 類名。
構造函式接受用於指定
數據源(即 DSN)的參數,可能還包括用戶名和密碼參數(如果有的話)。最後一個參數用於傳遞附加的調優參數到 PDO 或底層驅動程式 —— 後面很快會有更詳細的論述。下面是一個簡短的連線到 DB2 的示例腳本:
清單 2. 如何使用 PDO 連線到 DB2
try { $dbh = new PDO('odbc:SAMPLE', 'db2inst1', 'ibmdb2'); echo "Connected\n";} catch (Exception $e) { echo "Failed: " . $e->getMessage();}
odbc:SAMPLE 告訴 PDO 它應該使用 ODBC 驅動程式,並且應該使用 "SAMPLE" 資料庫。如果使用一個驅動程式管理器,那么可以用一個 ODBC 級
數據源名稱替代 SAMPLE。實際上,在冒號
字元之後可以指定任何有效的 ODBC 數據源連線字元串。
如果連線成功,您將看到訊息 "Connected",否則,PDO 將拋出一個 PDOException,解釋為什麼連線失敗。可能的原因包括無效的參數,不正確的用戶/密碼,甚至是您忘了裝載驅動程式。
值得注意的是,除非您捕捉從
構造函式拋出的異常,否則,如果 PHP 腳本未能連線到資料庫,它將終止。這與傳統的 PHP 資料庫擴展有很大的不同。對於不喜歡異常的人來說,只有兩個“硬故障(hard-failure)”點可能
拋出異常,這是其中一個點(另一個地點是,當您試圖使用
事務時缺乏對事務的支持)。對於所有其他錯誤,PDO 將使用您選擇的 錯誤處理設定。
連線將保持開放狀態,直到所有對它的引用被釋放。如果在主腳本的頂端打開連線,並將其句柄存儲在一個
全局變數中,那么該連線將一直處於開放狀態,直到腳本結束,或者直到 $dbh 變數被設為 null。如果在一個函式中打開連線,並且只將句柄存儲在一個本地變數中,那么當函式返回時,連線將被關閉。這些語義對於 PHP 中的任何對象都是一樣的,沒有什麼特別的地方。
ODBC 連線池如果您使用的是 Windows,或者如果您選擇在 UNIX 型平台上使用一個 ODBC 驅動程式管理器,那么值得注意的是,PDO_ODBC 將自動嘗試使用該驅動程式管理器的 ODBC 連線池特性。這個特性類似於 PHP 級連線快取,不要求專門請求一個持久的連線。此外,快取是在 ODBC 級進行的,這意味著在同一個進程中運行的其他組件(例如在 IIS 下運行的 ASP/.Net 腳本)也能利用相同的連線池。
對於流量較大的站點,讓 PHP 在不同請求的間隙中快取打開的連線,使得每個進程(每個惟一的連線參數集)只需花費一次建立連線的成本,這樣做常常很有益處。雖然這聽起來像是一個不錯的想法,但您應該仔細評估這樣做對系統的影響,因為當大量快取的連線空閒在那裡的時候,就會適得其反。
要建立一個快取的連線(如果您更熟悉傳統的資料庫擴展的話,也可以說是 *pconnect()),需要在實例化資料庫連線時傳遞一個屬性:
清單 3. 如何用 PDO 連線到 DB2,使用持久(快取)連線
try { $dbh = new PDO('odbc:SAMPLE', 'db2inst1', 'ibmdb2', array(PDO_ATTR_PERSISTENT => true)); echo "Connected\n"; } catch (Exception $e) { echo "Failed: " . $e->getMessage(); }
2、事務提交
至此,您已經通過 PDO 連線到了 DB2,在發出查詢之前,您應該理解 PDO 是如何管理
事務的。如果之前沒有接觸過事務,那么首先要知道事務的 4 個特徵:原子性(Atomicity)、一致性(Consistency)、獨立性(Isolation)和持久性(Durability),即 ACID。用外行人的話說,對於在一個事務中執行的任何工作,即使它是分階段執行的,也一定可以保證該工作會安全地套用於資料庫,並且在工作被提交時,不會受到來自其他連線的影響。事務性工作可以根據請求自動撤銷(假設您還沒有提交它),這使得腳本中的錯誤處理變得更加容易。
事務通常是通過把一批更改積蓄起來、使之同時生效而實現的。這樣做的好處是可以大大提高這些更新的效率。換句話說,
事務可以使腳本更快,而且可能更健壯(不過需要正確地使用事務才能獲得這樣的好處)。
警告只有在通過 PDO::beginTransaction() 啟動事務的情況下,才會發生自動回滾。如果手動地發出開始一個事務的查詢,那么 PDO 就無法知道該事務,從而不能在必要時進行回滾。
不幸的是,並不是每種資料庫都支持事務,所以當第一次打開連線時,PDO 需要在所謂的“自動提交(auto-commit)”模式下運行。自動提交模式意味著,如果資料庫支持
事務,那么您所運行的每一個查詢都有它自己的隱式事務,如果資料庫不支持事務,每個查詢就沒有這樣的事務。如果您需要一個事務,那么必須使用 PDO::beginTransaction() 方法來啟動一個事務。如果底層驅動程式不支持事務,那么將會拋出一個 PDOException(無論錯誤處理設定是怎樣的:這總是一個嚴重錯誤狀態)。在一個事務中,可以使用 PDO::commit() 或 PDO::rollBack() 來結束該事務,這取決於事務中運行的代碼是否成功。
DB2 特性雖然我認為事務通常要更快一些,但您還是應該自己評估事務是否真的可以加快代碼。例如,在高並發環境中您可能會發現,過度使用事務會增加鎖開銷。如果在應用程式中出現這種情況,那么建議的補救辦法是在一般情況下使用自動提交,而對於真正需要全部 ACID 特徵的代碼部分則仍然使用事務。
當腳本結束時,或者當一個連線即將被關閉時,如果有一個未完成的事務,那么 PDO 將自動
回滾該事務。這是一種安全措施,有助於避免在腳本非正常結束時出現不一致的情況 —— 如果沒有顯式地提交事務,那么假設有
某個地方會出現不一致,所以要執行回滾,以保證數據的安全性。
清單 4. 在事務中執行批處理
try { $dbh = new PDO('odbc:SAMPLE', 'db2inst1', 'ibmdb2', array(PDO_ATTR_PERSISTENT => true)); echo "Connected\n"; $dbh->setAttribute(PDO_ATTR_ERRMODE, PDO_ERRMODE_EXCEPTION); $dbh->beginTransaction(); $dbh->exec("insert into staff (id, first, last) values (23, 'Joe', 'Bloggs')"); $dbh->exec("insert into salarychange (id, amount, changedate) values (23, 50000, NOW())"); $dbh->commit(); } catch (Exception $e) { $dbh->rollBack(); echo "Failed: " . $e->getMessage(); }
在上面的示例中,假設我們為一個新雇員創建一組條目,這個雇員有一個 ID 號,即 23。除了輸入這個人的基本數據外,我們還需要記錄雇員的薪水。兩個更新分別完成起來很簡單,但通過將這兩個更新包括在 beginTransaction() 和 commit() 調用中,就可以保證在更改完成之前,其他人無法看到更改。如果發生了錯誤,catch 塊可以
回滾事務開始以來發生的所有更改,並列印出一條錯誤訊息。
並不是一定要在事務中作出更新。您也可以發出複雜的查詢來提取數據,還可以使用那種信息構建更多的更新和查詢。當事務在活動時,可以保證其他人在工作進行當中無法作出更改。事實上,這不是 100% 的正確,但如果您之前沒有聽說過事務的話,這樣介紹也未嘗不可。
關於 PHP 應用程式中安全性的說明
PHP Security Consortium 雖然本文表明在使用 PDO 時不再需要引用輸入,但這不是說您應該盲目地使數據通過資料庫。XSS 攻擊是很實際的危險。您應該總是確保對傳入應用程式的不受信任的數據套用適當的過濾器,並採取措施避免讓不受信任的數據在站點上發出 HTML 或 javascript。 請訪問 The PHP Security Consortium 以了解關於這些危險的更多知識,以及應該如何避免這些危險。
很多 PHP 腳本中一個常見的缺陷是缺乏輸入檢驗。這種缺陷可以被利用,從而招致 XSS(Cross Site Scripting)以及 SQL 入侵攻擊。在 SQL 入侵中,不受信任的數據(例如發給 Web 網頁的反饋)和其他文本被銜接在一起,構成一個查詢。攻擊者可以蓄意地安排他們的輸入,使之溢出引號之外,並在您想運行的真正查詢後面連結上任意一個查詢。這種攻擊使攻擊者可以更新、插入或刪除數據,甚至可能可以看到資料庫中的任意信息。
XSS 也是一個類似的問題。不過這一次不受信任的數據瞄準的是瀏覽站點的人們,而不是應用程式本身。通過提交包含 HTML 或 javascript 組合的文本,攻擊者期望您之後會將那種數據直接輸出到其他訪問站點的人那裡,從而使
惡意代碼可以在站點訪問者的瀏覽器上運行。
在編寫應用程式時,需要同時考慮這兩種攻擊。如果小心地檢驗和過濾輸入,這兩種攻擊都是可以防止的。對 XSS 的處理很有技巧性,所以在這裡我不便多講(不過可以從側欄找到有用的參考資料)。相比之下,SQL 入侵更容易對付。您只需在構造查詢之前,適當地排除每塊不受信任的數據。這種事情有點煩雜,特別是當您有大量的欄位要處理時,很容易忘記做這件事。
雖然這是有用的(並且也是重要的)信息,但是您可能想知道,為什麼我要花時間提到這一點,本文的重點不是結合使用 PDO 和 DB2 嗎?原因是這樣的:PHP 得到很廣泛的部署,自然地,大量流行的基於 PHP 的應用程式也得到了廣泛的部署。每當某一種這樣的應用程式(和 PHP 本身沒有聯繫)被發現存在漏洞時,PHP 常常被誤認為是不安全的,可被利用的或者有缺陷的。為了避免將來出現這樣的情況,我們可以採取的一個措施是鼓勵應用程式開發人員多考慮安全問題,從而減少由誠實的錯誤導致的損害。扯遠了,下面繼續介紹其他關鍵概念。
3、預處理過程
很多更成熟的資料庫都支持預處理語句的概念。什麼是預處理語句?您可以把預處理語句看作您想要運行的 SQL 的一種編譯過的模板,它可以使用變數參數進行定製。預處理語句可以帶來兩大好處:
查詢只需解析(或準備)一次,但是可以用相同或不同的參數執行多次。當查詢準備好後,資料庫將分析、編譯和最佳化執行該查詢的計畫。對於複雜的查詢,這個過程要花比較長的時間,如果您需要以不同參數多次重複相同的查詢,那么該過程將大大降低應用程式的速度。通過使用預處理語句,可以避免重複分析/編譯/最佳化周期。簡言之,預處理語句使用更少的資源,因而運行得更快。 提供給預處理語句的參數不需要用引號括起來,驅動程式會處理這些。如果應用程式獨占地使用預處理語句,那么可以確保沒有 SQL 入侵發生。(然而,如果您仍然將查詢的其他部分建立在不受信任的輸入之上,那么就仍然存在風險)。 預處理語句是如此有用,以致 PDO 實際上打破了在目標 4 中設下的規則:如果驅動程式不支持預處理語句,那么 PDO 將仿真預處理語句。
下面是使用預處理語句的兩個例子。第一個例子 通過替換指定
占位符的 name 和 value,執行一次插入。而 第二個例子 使用問號占位符執行一條 select 語句。
清單 4. 使用預處理語句的重複插入
$stmt = $dbh->prepare("INSERT INTO REGISTRY (name, value) VALUES (:name, :value)"); $stmt->bindParam(':name', $name);$stmt->bindParam(':value', $value); // insert one row$name = 'one';$value = 1;$stmt->execute(); // insert another row with different values$name = 'two';$value = 2; $stmt->execute();
清單 5. 使用預處理語句取數據
$stmt = $dbh->prepare("SELECT * FROM REGISTRY where name = ?"); if ($stmt->execute(array('one'))) { while ($row = $stmt->fetch()) { print_r($row); }}
如果資料庫驅動程式支持,您還可以綁定輸出和輸入參數。輸出參數通常用於從
存儲過程獲取值。輸出參數使用起來比輸入參數要複雜一些,當綁定一個給定的輸出參數時,必須知道該參數的長度。如果為參數綁定的值大於您建議的長度,那么就會產生錯誤。
清單 6. 帶輸出參數調用存儲過程
$stmt = $dbh->prepare("CALL sp_returns_string(?)"); $stmt->bindParam(1, $return_value, PDO_PARAM_STR, 4000); // call the stored procedure$stmt->execute(); print "procedure returned $return_value\n";
您還可以指定同時具有輸入和輸出值的參數,其語法類似於輸出參數。在接下來的例子中,字元串 'hello' 被傳遞給
存儲過程,當存儲過程返回時,hello 被替換為該存儲過程返回的值。
清單 7. 帶輸入/輸出參數調用存儲過程
$stmt = $dbh->prepare("CALL sp_takes_string_returns_string(?)"); $value = 'hello'; $stmt->bindParam(1, $value, PDO_PARAM_STR|PDO_PARAM_INPUT_OUTPUT, 4000); // call the stored procedure$stmt->execute(); print "procedure returned $value\n";
4、錯誤處理
PDO 提供了 3 種不同的錯誤處理模式,以滿足不同風格的編程:
PDO_ERRMODE_SILENT 這是默認模式。PDO 將只設定
錯誤代碼,以通過 errorCode() 和 errorInfo() 方法對語句和
資料庫對象進行檢查。如果錯誤是由於對語句對象的調用而產生的,那么可以在那個對象上調用 errorCode() 或 errorInfo() 方法。如果錯誤是由於調用資料庫對象而產生的,那么可以在那個資料庫對象上調用上述兩個方法。 PDO_ERRMODE_WARNING 除了設定錯誤代碼以外,PDO 還將發出一條傳統的 E_WARNING 訊息。如果您只是想看看發生了什麼問題,而無意中斷應用程式的流程,那么在調試/測試當中這種設定很有用。 PDO_ERRMODE_EXCEPTION 除了設定
錯誤代碼以外,PDO 還將拋出一個 PDOException,並設定其屬性,以反映錯誤代碼和錯誤信息。這種設定在調試當中也很有用,因為它會放大腳本中產生錯誤的地方,從而可以非常快速地指出代碼中有問題的潛在區域(記住,如果異常導致腳本終止,則
事務將自動
回滾)。 異常模式另一個有用的地方是,與傳統的 PHP 風格的警告相比,您可以更清晰地構造自己的錯誤處理,而且,比起以靜寂方式以及顯式地檢查每個資料庫調用的返回值,異常模式需要的代碼/嵌套也更少。 PDO 定製了使用 SQL-92 SQLSTATE
錯誤代碼字元串的標準;不同 PDO 驅動程式負責將它們
本地代碼映射為適當的 SQLSTATE 代碼。例如,SQLSTATE 是用於 DB2(以及通常的 ODBC)的本地錯誤代碼格式,這是多么方便啊!errorCode() 方法返回一個 SQLSTATE 代碼。如果您需要關於一個錯誤的更多特定的信息,PDO 還提供了一個 errorInfo() 方法,該方法將返回一個
數組,其中包含 SQLSTATE 代碼、特定於驅動程式的錯誤代碼以及特定於驅動程式的錯誤字元串。
分頁數據、滾動游標和定位更新
在 Web 應用程式中,一種常見的範例是對查詢結果進行
分頁。如果您使用一個 Internet 搜尋引擎,那么很可能每天都會做這樣的事。您輸入
搜尋詞,然後得到前 10-20 個匹配項。如果您想看到更多搜尋結果,可以單擊 "next page" 連結。如果想回頭看前面看過的結果,可以單擊 "previous page" 連結。記得在幾年前,當我第一次在 Web 上使用這樣的東西時,我對自己說:“為什麼我不能通過滾動查看所有數據呢?” 問題的答案說簡單也簡單,說複雜也複雜 —— 我只想說,HTTP 不會智慧型地使資料庫上的可滾動
游標一直處於開放狀態,即便如此,需要大量傳輸的 Web 應用程式也會很快地消耗掉大量開放的可滾動游標。因此,最簡單的解決方案是為用戶顯示所有的匹配項 —— 但是用戶很容易迷失在大量的結果當中。比較符合邏輯的措施是人工地將數據格式化到多個頁面上,使用戶可以每次查看一部分可以管理的數據。
所以人們編寫可以取所有數據的 PHP 應用程式,然後只顯示前 10 行。根據下一次請求,應用程式又顯示 11-20 行,依此類推。這對於只返回少量數據的查詢來說很不錯,但是,如果有很多匹配項(比如多於 100),那么先取全部數據然後丟棄其中的 90%,這種做法很浪費。PHP 的創始人 Rasmus Lerdorf 就這種情形特地為 MySQL 發明了一個特殊的 "LIMIT, OFFSET" 子句。它允許您通知資料庫,您只對一小部分行感興趣,這樣它就不會取其他不需要的行了。其語法(或非常類似的東西)已經被其他流行的開放原始碼資料庫採納,但並不是所有資料庫都提供了相同的語法。 Troels Arvin 收集了一些非常有用的信息,對不同 RDBMS 所支持的語法進行了比較。
如果您想在以 DB2 為
後台資料庫的 PHP 應用程式中實現分頁結果,那么可以(也應該)使用下面示例中的語法。這裡我們假設有一個 books 表,表中包含書名和作者,我們想要每次在一頁中顯示 10 個以上結果:
清單 8. 使用 SQL Standard "Window Functions" 實現數據分頁
$db = new PDO('odbc:SAMPLE', 'db2inst1', 'ibmdb2'); // the offset is passed in from the user when they click on a link // this cast to integer ensures that no SQL injection can occur $offset = (int)$_GET['offset'];$stmt = $db->prepare("select * from ( select ROW_NUMBER() OVER (ORDER BY author) as rownum, * from books ) as books_windowWHERE rownum > $offset AND rownum <= (10 + $offset)"); if ($stmt->execute()) { while (($row = $stmt->fetch()) !== false) { print_r($row); }}
Cloudscape 說明在撰寫本文之際,Cloudscape 在其 SQL 實現中還不支持 ROW_NUMBER(),所以需要使用可滾動游標。
如果您要編寫一個更通用的應用程式,並希望實現
分頁的
結果集,但是不想專門編寫很多的代碼,並且也不想使用更重量級的抽象層,Troels Arvin 的非常有幫助的 RDBMS 信息建議,您可以使用
游標作為更輕便(稍微慢一點)的方案。碰巧的是,PDO 具有這方面的 API 級的支持。下面將談到如何使用這種支持來達到與上面示例相同的效果:
清單 9. 使用滾動游標實現數據分頁
$db = new PDO('odbc:SAMPLE', 'db2inst1', 'ibmdb2'); $stmt = $db->prepare("select * from books order by author", array( PDO_ATTR_CURSOR => PDO_CURSOR_SCROLL)); // the offset is passed in from the user when they click on a link // this cast to integer ensures that no SQL injection can occur $offset = (int)$_GET['offset'];if ($stmt->execute()) { // moves the cursor to the requested offset and fetches the first for ($tofetch = 10, $row = $stmt->fetch(PDO_FETCH_ASSOC, PDO_FETCH_ORI_REL, $offset); $row !== false && $tofetch-- > 0; $row = $stmt->fetch(PDO_FETCH_ASSOC)) { print_r($row); } }
需要強調的是,雖然滾動
游標對於更冗長的 window 函式方案來說是一個很方便的替代方案,但這種方案要慢很多。如果在一個傳輸量比較少的環境中進行測試,您可能發現不了速度上的差異,但當規模擴大時,您就會開始發現速度降慢帶來的痛苦。
定位更新
可滾動游標的另一個用途是,基於 SQL 中無法表達的重大標準驅動更新。如果您有一個 Web 頁面連結的表,並且需要在每晚的批處理過程中更新那個表,以反映 Web 頁面當前大小,那么可以編寫如下代碼:
清單 10. 使用滾動游標作出定位更新
$db = new PDO('odbc:SAMPLE', 'db2inst1', 'ibmdb2'); // create a named, scrolling, updateable cursor $stmt = $db->prepare("select url, size from links FOR UPDATE OF size", array( PDO_ATTR_CURSOR => PDO_CURSOR_SCROLL, PDO_ATTR_CURSOR_NAME => 'link_pos'));if ($stmt->execute()) { // a statement for applying our updates. // Notice the WHERE CURRENT OF clause mentions "link_pos", // which is the name of the cursor we're using to select the data $upd = $db->prepare("UPDATE links set size = ? WHERE CURRENT OF link_pos"); // grab each row while (($row = $stmt->fetch()) !== false) { // There are much more efficient ways to do this; // this is a brief example only: grab all the content // from the URL $content = file_get_conents($row['url']); // and measure its length $size = strlen($content) // and pass that as a parameter to our update statement $upd->execute(array($size)); }}
大型對象
在應用程式中的
某個地方,您可能發現需要在資料庫中存儲“大型(large)”數據。大型通常意味著“大約 4kb 或 4kb 以上”,儘管在沒有“大型”數據之前 DB2 最大可以處理 32kb 的數據。 大型對象可以是文本的,也可以是二進制的。PDO 允許在 bindParam() 或 bindColumn() 調用中通過使用 PDO_PARAM_LOB 類型代碼來使用大型數據類型。PDO_PARAM_LOB 告訴 PDO 將數據映射為流,所以可以使用 PHP Streams API 來操縱這樣的數據。下面是一個示例:
5、程式功能
清單 11. 從資料庫取一副圖像
$db = new PDO('odbc:SAMPLE', 'db2inst1', 'ibmdb2'); $stmt = $db->prepare("select contenttype, imagedata from images where id=?"); $stmt->execute(array($_GET['id']));list($type, $lob) = $stmt->fetch(); header("Content-Type: $type");fpassthru($lob);
上面的介紹很簡明扼要。讓我們試試另一面,將上傳的圖像插入到一個資料庫中:
清單 12. 將圖像插入資料庫中
$db = new PDO('odbc:SAMPLE', 'db2inst1', 'ibmdb2'); $stmt = $db->prepare("insert into images (id, contenttype, imagedata) values (?, ?, ?)"); $id = get_new_id(); // some function to allocate a new ID // assume that we are running as part of a file upload form // You can find more information in the PHP documentation $fp = fopen($_FILES['file']['tmp_name'], 'rb');$stmt->bindParam(1, $id); $stmt->bindParam(2, $_FILES['file']['type']); $stmt->bindParam(3, $fp, PDO_PARAM_LOB); $stmt->execute();
這兩個例子都是巨觀層次的。請記住,被取的大型對象是一個流,可以通過所有常規的流函式來使用它,例如 fgets()、fread()、fgetcsv() 和 stream_get_contents()。
關於全球化、NLS 和字元集的簡要說明
在越來越多的 PHP 應用程式中,越來越重要的一點是讓應用程式能夠在全球範圍內使用。從實踐角度來講,這意味著應用程式需要能夠正確地處理多種語言(例如英語和日語)中的數據,並且其功能性不變。這是一個很大的專題,做起來很有技巧性。實現全球化要走的第一步是採用一種適合所有數據的全球編碼,例如 UTF-8。這是一種 ASCII 兼容的編碼,它可以使用特殊字元序列為整個 unicode
字元集編碼。UTF-8 也是一種多
位元組編碼。
與常規 ASCII
字元串相比,多位元組編碼的字元串處理起來要棘手一點,因為一個或多個字元對應於一個給定的字母 —— 例如,UTF-8 允許最多 6 個字元的序列映射到字元串中的一個字母。ASCII 字元在 UTF-8 中仍具有相同的表示,因此,如果只是處理不帶任何特殊音調的純英文文本,則 UTF-8 看上去就像是 ASCII。這意味著類似的
字元串函式(作用於位元組而不是字元位置),例如 strlen() 和 substr(),可能得不到預期的效果,這取決於 UTF-8 字元串中的內容。幸運的是,PHP iconv 擴展為這些函式提供了一些編碼感知的替代函式。例如,您可以使用 iconv_strlen() 來得出
字元串中的字元數,而不是使用 strlen()。類似地,您可以不用 strpos() 或 substr(),而使用 iconv_strpos() 和 iconv_substr()。
iconv 擴展為您提供了在處理多種編碼下的數據時所需的基本工具。應用程式應該儘量確保所有數據都是 UTF-8 編碼的。如果用適當的 Content-Type 標記 Web 頁面,那么大多數瀏覽器將傳送 UTF-8 編碼的數據,可以確信,一定有一個編碼類型屬性可套用於 HTML FORM 標籤。
接下來的一步是設定 DB2 實例,使它在您與之互動時使用 UTF-8。這很容易辦到,只需在 DB2 的命令行提示符中運行以下命令:
清單 14. 設定 DB2 實例,使之使用 UTF-8
$ db2set DB2CODEPAGE=1208
完成這樣的更改後,從 DB2 實例取到的所有文本數據都是 UTF-8 編碼的。同樣,DB2 期望您輸入的所有文本也是 UTF-8 編碼的。當應用程式的每個部分都使用 UTF-8 時,應用程式就可以全球使用了,並且能夠顯示任何語言的文本,只要這種語言的文本可以用 UTF-8 編碼。前面我已經暗示過,這只是通往國際化大道的第一步。還有很多其他的事情需要考慮,例如本地化(採用給定用戶的地區設定來顯示日期、時間、重量和度量,將通用文本翻譯成用戶本地的語言)、從右到左或雙向(bi-di)文本布局,等等。
值得注意的是,PDO 不對該數據做任何特殊的事情。有些驅動程式允許更改為一個連線使用的編碼,但是在 PDO 級沒有處理這種事情的特殊邏輯。其原因是,PHP 內部完全不知道 unicode,所以在這裡試圖使 PDO 知道 unicode 是沒有意義的。如果您對這方面的專題感興趣,那么您會欣喜地得知,PHP 的 unicode 支持很快就要出現,不過我也不知道它初次露面的確切日期。
丙二醇
中文名稱: 1,3-丙二醇
英文名稱: 1,3-propanediol
英文名稱2: 1,3-dihydroxypropane
分子式: C3H8O2
結構: HOCH2CH2CH2OH
CAS No.: 504-63-2
分子量: 76.10
外觀與性狀: 無色、無臭,具鹹味、吸濕性的粘稠液體。(純品)
熔點(℃): -27
沸點(℃): 210-211
相對密度(水=1): 1.05(25℃)
相對蒸氣密度(空氣=1): 2.6
飽和蒸氣壓(kPa): 0.13(60℃)
閃點(℃): 79
引燃溫度(℃): 400
爆炸上限%(V/V): 無資料
爆炸下限%(V/V): 無資料
溶解性: 與水混溶,可混溶於乙醇、乙醚。
主要用途: 用作溶劑, 用於有機合成。
石油公司
petroleum development of Oman.阿曼石油開發公司
進程數據對象
工程上,PDO(Process data object)是CANopen 專有名詞。
進程數據對象(PDO)協定可用來在許多節點之間交換即時的資料。可透過一個PDO,傳送最多8位元組(64位元)資料給一設備,或由一設備接收最多8位元組(64位元)的資料。一個PDO可以由對象字典中幾個不同索引的資料組成,規劃方式則是透過對象字典中對應PDO mapping及PDO參數的索引。
PDO分為兩種:傳送用的TPDO及接收用的RPDO。一個節點的TPDO是將資料由此節點傳輸到其他節點,而RPDO則是接收由其他節點傳輸的資料。一個節點分別有4個TPDO及4個RPDO。
PDO可以用同步或異步的方式傳送:同步的PDO是由SYNC訊息觸發,而異步的PDO是由節點內部的條件或其他外部條件觸發。例如若一個節點規劃為允許接受其他節點產生的TPDO請求,則可以由其他節點送出一個沒有資料但有設定RTR位元的TPDO(TPDO請求),使該節點送出需求的資料。
藉由RPDO也可以使兩個或兩個以上的設備同時啟動。只要將其RPDO對應到相同的TPDO即可。
太平洋十年濤動
Pacific Decade Oscillation
PDO是指中緯度太平洋盆地地區,海洋與大氣氣候變化的循環模式。由北緯20°的太平洋表面的溫度所得。