PHP 5.1 發(fā)布時將附帶 PDO,但是也可以通過 PECL 這個 PHP 擴展庫(PHP Extension Repository)來結(jié)合使用 PDO 和 PHP 5.0.3 及以上版本。如果您使用的是 Windows,那么您會欣喜地發(fā)現(xiàn)安裝過程要簡單得多。
我將假設(shè)您已經(jīng)擁有配置 PHP 5 使之使用您選擇的 Web 服務(wù)器的經(jīng)驗,只有在此假設(shè)下,我才能集中精力關(guān)注更相關(guān)的細節(jié)。同樣,我還將假設(shè)您使用的是一個 DB2 Universal Database 服務(wù)器或網(wǎng)絡(luò)服務(wù)器模式下的 IBM Cloudscape 數(shù)據(jù)庫,并且接受了用戶為 db2inst1、密碼為 ibmdb2 的默認安裝選項。如果您自己編譯驅(qū)動程序,那么在進行編譯的機器上,應(yīng)該安裝有 DB2 客戶機,并且存在應(yīng)用程序開發(fā) header,否則編譯將遭到失敗。
至此我們已經(jīng)掌握了數(shù)據(jù)庫及 PHP 的背景知識,現(xiàn)在正好可以提及 PDO 背后的一些設(shè)計目標:
為大多數(shù)數(shù)據(jù)庫 API 中的常見特性提供一致的 API。
具有可擴展性,以使數(shù)據(jù)庫供應(yīng)商 X 仍然可以暴露特性 Y 并保持 PDO 的兼容性。
提供大量基本的兼容性技巧,以便能夠更方便地創(chuàng)建跨數(shù)據(jù)庫兼容的應(yīng)用程序。
不為給定數(shù)據(jù)庫 API 中本來沒有的特性(例如序列)提供完全抽象或仿真。PDO 類意圖為您提供對數(shù)據(jù)庫本地特性的一致性訪問,并減少干擾。
通過將與 PHP 內(nèi)部打交道的代碼(這是最難于編寫的部分)集中起來,簡化 PHP 數(shù)據(jù)庫驅(qū)動程序的創(chuàng)建。
最后一點非常重要。PDO 是模塊化結(jié)構(gòu),它被分成一個公共核心以及一個或多個驅(qū)動程序擴展,公共核心提供了在腳本(PDO 本身)中使用的 API,驅(qū)動程序擴展則為 PDO 和本地 RDBMS 客戶機 API 庫架起一座橋梁。DB2 用戶將會希望使用 PDO_ODBC 驅(qū)動程序,據(jù)稱它可以提供以下特性:
它經(jīng)過重新編寫,能支持遵從 ODBC V3 的驅(qū)動程序和驅(qū)動程序管理器。它還考慮了對 DB2 特定特性和優(yōu)化的支持,這成為設(shè)計過程中的一部分 -- 不是后來補充的。
它支持經(jīng)過試驗和測試的存儲過程和大型對象。它不僅能夠工作,而且非常好用。
對于取 10,000 行記錄這樣的 DB2 訪問操作,使用 PDO_ODBC 驅(qū)動程序時的性能比使用傳統(tǒng)的 PHP Unified ODBC 擴展要快大約 10 倍。之所以有這么大的差異,是因為在 PDO 中默認的游標是輕量級的只能向前移動的游標。
隨著擁有更成熟 OO 語法的 PHP 5 的發(fā)布,PHP 越來越多地受到越來越大的機構(gòu)的關(guān)注,對于 PHP 來說,提供更加一致的和可訪問的數(shù)據(jù)訪問 API 變得越來越重要。
PHP 與流行的開放源代碼關(guān)系數(shù)據(jù)庫管理系統(tǒng)(RDBMS)MySQL 之間總是很有默契。這對拍檔的成功很大程度上是由于它們免費可用,而且進入的門檻也比較低,這兩種產(chǎn)品的合作使它們各自都取得了廣受推崇的地位。
很多 PHP 應(yīng)用程序開發(fā)人員都習(xí)慣于 PHP-MySQL 這對組合,以致 PHP 對其他數(shù)據(jù)庫的支持常常模仿 MySQL 客戶機庫 API。然而,并不是所有的數(shù)據(jù)庫客戶機 API 都是一樣的,也不是所有的數(shù)據(jù)庫都提供相同的特性。雖然存在模仿,但不同的 PHP 數(shù)據(jù)庫擴展都有它們各自的怪僻和不同之處,所以從一種數(shù)據(jù)庫遷移到另一種數(shù)據(jù)庫時會有一些困難。雖然這不是創(chuàng)建 PDO 的直接原因,但是在設(shè)計過程中還是有一定影響的。
如果您是帶著想結(jié)合使用 PHP 和 DB2 的目的閱讀本文,那么您很可能屬于以下類型中的一種:
您從一家小公司開始,在 MySQL(舉個例子)上運行 PHP,由于業(yè)務(wù)增長,您需要 DB2 所提供的可伸縮性/可靠性/支持或其他特性。您希望移植代碼,以使用 DB2,但由于 API 的變化,您需要編寫或?qū)崿F(xiàn)一個抽象層,以便在 DB2 上測試應(yīng)用程序的同時可以繼續(xù)在舊的數(shù)據(jù)庫上運行。不僅如此,您還希望能有自己的選擇,并保留支持其他 RDBMS 的可能性,因為您清楚,有些客戶機可能已經(jīng)和其他平臺栓在一起了。
您用 PHP 在 MySQL之上構(gòu)建了一個小型的部門應(yīng)用程序(同樣,這只是舉個例子,我并不是要跟 MySQL 過不去)。事實證明這個應(yīng)用程序本身很有用,現(xiàn)在已經(jīng)在這個部門之外使用,并且闖入了 CIO/CTO 的法眼 -- 現(xiàn)在需要遵從托管的標準數(shù)據(jù)庫。(是的,這是第一點的一個變種。) 在其他某些復(fù)雜的企業(yè)級應(yīng)用程序的后臺,您已經(jīng)有一個 DB2 實例,您希望利用 PHP 的快速應(yīng)用程序開發(fā)和原型設(shè)計來生成動態(tài)報告。
在Unix環(huán)境下PHP5.1以上版本中:
如果你正在使用PHP5.1版本,PDO和PDO SQLITE已經(jīng)包含在了此發(fā)行版中;當你運行configure時它將自動啟用。推薦你將PDO作為共享擴展構(gòu)建,這樣可以使你獲得通過PECL升級的好處。推薦的構(gòu)建支持PDO的PHP的configure line應(yīng)該也要啟用zlib。你也應(yīng)該啟用你選擇的數(shù)據(jù)庫的PDO驅(qū)動 ;關(guān)于這個的更多信息請查看database-specific PDO drivers ,但要注意如果你將PDO作為一個共享擴展構(gòu)建,你必須也要將PDO驅(qū)動構(gòu)建為共享擴展。SQLite擴展依賴于PDO,所以如果PDO作為共享擴展構(gòu)建,SQLite也應(yīng)當這樣構(gòu)建
./configure --with-zlib --enable-pdo=shared --with-pdo-sqlite=shared --with-sqlite=shared
將PDO安裝為一個共享模塊后,你必須編輯php.ini文件使得在PHP運行時自動載入PDO擴展。你同樣需要啟用那兒的特定數(shù)據(jù)庫驅(qū)動;確保他們列出在 pdo. so 行之后,因為PDO必須在特定數(shù)據(jù)庫驅(qū)動載入之前初始化。如果你是以靜態(tài)方式構(gòu)建的PDO和特定數(shù)據(jù)庫驅(qū)動擴展,你可以跳過這一步。
extension=pdo. so
讓PDO作為一個共享的模塊將使你可以在新版PDO發(fā)布時運行 pecl upgrade pdo 命令升級,而不用強制你重新構(gòu)建整個PHP。注意如果你是這樣做的,你也需要同時升級你的特定數(shù)據(jù)庫驅(qū)動。
在windows環(huán)境下PHP5.1以上版本中:
PDO和主要數(shù)據(jù)庫的驅(qū)動同PHP一起作為擴展發(fā)布,要激活它們只需簡單的編輯php.ini文件:
extension=php_pdo.dll
然后,選擇針對特定數(shù)據(jù)庫的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文件應(yīng)當存在于系統(tǒng)的 extension_dir 目錄里。
注意 PDO_INFORMIX 只能作為一個PECL擴展使用。
========================================================================================
PHP 5.1 發(fā)布時將附帶一個全新的數(shù)據(jù)庫連接層,即 PHP Data Objects (PDO)。雖然 PHP 一直都擁有很好的數(shù)據(jù)庫連接,但 PDO 讓 PHP 達到一個新的高度。學(xué)習(xí)如何獲得、安裝和使用 PDO,以連接到 IBM? DB2? Universal Database? 和 IBM Cloudscape? 數(shù)據(jù)庫,插入和檢索數(shù)據(jù),并探索更多高級特性,例如預(yù)處理語句(prepared statements)、綁定參數(shù)(bound parameters)、可滾動游標(scrollable cursors)、定位更新(positioned updates)以及 LOB。
并不能使用PDO擴展本身執(zhí)行任何數(shù)據(jù)庫操作,必須使用一個database-specific PDO driver(針對特定數(shù)據(jù)庫的PDO驅(qū)動)訪問數(shù)據(jù)庫服務(wù)器。
PDO并不提供數(shù)據(jù)庫抽象,它并不會重寫SQL或提供數(shù)據(jù)庫本身缺失的功能,如果你需要這種功能,你需要使用一個更加成熟的抽象層。
PDO需要PHP5核心OO特性的支持,所以它無法運行于之前的PHP版本。
連接是通過創(chuàng)建 PDO 基類的實例而建立的。不管您想要使用哪種驅(qū)動程序,您總是使用 PDO 類名。構(gòu)造函數(shù)接受用于指定數(shù)據(jù)源(即 DSN)的參數(shù),可能還包括用戶名和密碼參數(shù)(如果有的話)。最后一個參數(shù)用于傳遞附加的調(diào)優(yōu)參數(shù)到 PDO 或底層驅(qū)動程序 -- 后面很快會有更詳細的論述。下面是一個簡短的連接到 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 它應(yīng)該使用 ODBC 驅(qū)動程序,并且應(yīng)該使用 "SAMPLE" 數(shù)據(jù)庫。如果使用一個驅(qū)動程序管理器,那么可以用一個 ODBC 級數(shù)據(jù)源名稱替代 SAMPLE。實際上,在冒號字符之后可以指定任何有效的 ODBC 數(shù)據(jù)源連接字符串。
如果連接成功,您將看到消息 "Connected",否則,PDO 將拋出一個 PDOException,解釋為什么連接失敗??赡艿脑虬o效的參數(shù),不正確的用戶/密碼,甚至是您忘了裝載驅(qū)動程序。
值得注意的是,除非您捕捉從構(gòu)造函數(shù)拋出的異常,否則,如果 PHP 腳本未能連接到數(shù)據(jù)庫,它將終止。這與傳統(tǒng)的 PHP 數(shù)據(jù)庫擴展有很大的不同。對于不喜歡異常的人來說,只有兩個"硬故障(hard-failure)"點可能拋出異常,這是其中一個點(另一個地點是,當您試圖使用事務(wù)時缺乏對事務(wù)的支持)。對于所有其他錯誤,PDO 將使用您選擇的 錯誤處理設(shè)置。
連接將保持開放狀態(tài),直到所有對它的引用被釋放。如果在主腳本的頂端打開連接,并將其句柄存儲在一個全局變量中,那么該連接將一直處于開放狀態(tài),直到腳本結(jié)束,或者直到 $dbh 變量被設(shè)為 null。如果在一個函數(shù)中打開連接,并且只將句柄存儲在一個本地變量中,那么當函數(shù)返回時,連接將被關(guān)閉。這些語義對于 PHP 中的任何對象都是一樣的,沒有什么特別的地方。
ODBC 連接池如果您使用的是 Windows,或者如果您選擇在 UNIX 型平臺上使用一個 ODBC 驅(qū)動程序管理器,那么值得注意的是,PDO_ODBC 將自動嘗試使用該驅(qū)動程序管理器的 ODBC 連接池特性。這個特性類似于 PHP 級連接緩存,不要求專門請求一個持久的連接。此外,緩存是在 ODBC 級進行的,這意味著在同一個進程中運行的其他組件(例如在 IIS 下運行的 ASP/.Net 腳本)也能利用相同的連接池。 |
對于流量較大的站點,讓 PHP 在不同請求的間隙中緩存打開的連接,使得每個進程(每個惟一的連接參數(shù)集)只需花費一次建立連接的成本,這樣做常常很有益處。雖然這聽起來像是一個不錯的想法,但您應(yīng)該仔細評估這樣做對系統(tǒng)的影響,因為當大量緩存的連接空閑在那里的時候,就會適得其反。
要建立一個緩存的連接(如果您更熟悉傳統(tǒng)的數(shù)據(jù)庫擴展的話,也可以說是 *pconnect()),需要在實例化數(shù)據(jù)庫連接時傳遞一個屬性:
清單 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(); } |
至此,您已經(jīng)通過 PDO 連接到了 DB2,在發(fā)出查詢之前,您應(yīng)該理解 PDO 是如何管理事務(wù)的。如果之前沒有接觸過事務(wù),那么首先要知道事務(wù)的 4 個特征:原子性(Atomicity)、一致性(Consistency)、獨立性(Isolation)和持久性(Durability),即 ACID。用外行人的話說,對于在一個事務(wù)中執(zhí)行的任何工作,即使它是分階段執(zhí)行的,也一定可以保證該工作會安全地應(yīng)用于數(shù)據(jù)庫,并且在工作被提交時,不會受到來自其他連接的影響。事務(wù)性工作可以根據(jù)請求自動撤銷(假設(shè)您還沒有提交它),這使得腳本中的錯誤處理變得更加容易。
事務(wù)通常是通過把一批更改積蓄起來、使之同時生效而實現(xiàn)的。這樣做的好處是可以大大提高這些更新的效率。換句話說,事務(wù)可以使腳本更快,而且可能更健壯(不過需要正確地使用事務(wù)才能獲得這樣的好處)。
警告只有在通過 PDO::beginTransaction() 啟動事務(wù)的情況下,才會發(fā)生自動回滾。如果手動地發(fā)出開始一個事務(wù)的查詢,那么 PDO 就無法知道該事務(wù),從而不能在必要時進行回滾。 |
不幸的是,并不是每種數(shù)據(jù)庫都支持事務(wù),所以當?shù)谝淮未蜷_連接時,PDO 需要在所謂的"自動提交(auto-commit)"模式下運行。自動提交模式意味著,如果數(shù)據(jù)庫支持事務(wù),那么您所運行的每一個查詢都有它自己的隱式事務(wù),如果數(shù)據(jù)庫不支持事務(wù),每個查詢就沒有這樣的事務(wù)。如果您需要一個事務(wù),那么必須使用 PDO::beginTransaction() 方法來啟動一個事務(wù)。如果底層驅(qū)動程序不支持事務(wù),那么將會拋出一個 PDOException(無論錯誤處理設(shè)置是怎樣的:這總是一個嚴重錯誤狀態(tài))。在一個事務(wù)中,可以使用 PDO::commit() 或 PDO::rollBack() 來結(jié)束該事務(wù),這取決于事務(wù)中運行的代碼是否成功。
DB2 特性雖然我認為事務(wù)通常要更快一些,但您還是應(yīng)該自己評估事務(wù)是否真的可以加快代碼。例如,在高并發(fā)環(huán)境中您可能會發(fā)現(xiàn),過度使用事務(wù)會增加鎖開銷。如果在應(yīng)用程序中出現(xiàn)這種情況,那么建議的補救辦法是在一般情況下使用自動提交,而對于真正需要全部 ACID 特征的代碼部分則仍然使用事務(wù)。 |
當腳本結(jié)束時,或者當一個連接即將被關(guān)閉時,如果有一個未完成的事務(wù),那么 PDO 將自動回滾該事務(wù)。這是一種安全措施,有助于避免在腳本非正常結(jié)束時出現(xiàn)不一致的情況 -- 如果沒有顯式地提交事務(wù),那么假設(shè)有某個地方會出現(xiàn)不一致,所以要執(zhí)行回滾,以保證數(shù)據(jù)的安全性。
清單 4. 在事務(wù)中執(zhí)行批處理
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(); } |
在上面的示例中,假設(shè)我們?yōu)橐粋€新雇員創(chuàng)建一組條目,這個雇員有一個 ID 號,即 23。除了輸入這個人的基本數(shù)據(jù)外,我們還需要記錄雇員的薪水。兩個更新分別完成起來很簡單,但通過將這兩個更新包括在 beginTransaction() 和 commit() 調(diào)用中,就可以保證在更改完成之前,其他人無法看到更改。如果發(fā)生了錯誤,catch 塊可以回滾事務(wù)開始以來發(fā)生的所有更改,并打印出一條錯誤消息。
并不是一定要在事務(wù)中作出更新。您也可以發(fā)出復(fù)雜的查詢來提取數(shù)據(jù),還可以使用那種信息構(gòu)建更多的更新和查詢。當事務(wù)在活動時,可以保證其他人在工作進行當中無法作出更改。事實上,這不是 100% 的正確,但如果您之前沒有聽說過事務(wù)的話,這樣介紹也未嘗不可。
關(guān)于 PHP 應(yīng)用程序中安全性的說明
PHP Security Consortium 雖然本文表明在使用 PDO 時不再需要引用輸入,但這不是說您應(yīng)該盲目地使數(shù)據(jù)通過數(shù)據(jù)庫。XSS 攻擊是很實際的危險。您應(yīng)該總是確保對傳入應(yīng)用程序的不受信任的數(shù)據(jù)應(yīng)用適當?shù)倪^濾器,并采取措施避免讓不受信任的數(shù)據(jù)在站點上發(fā)出 HTML 或 javascript。 請訪問 The PHP Security Consortium 以了解關(guān)于這些危險的更多知識,以及應(yīng)該如何避免這些危險。 |
很多 PHP 腳本中一個常見的缺陷是缺乏輸入檢驗。這種缺陷可以被利用,從而招致 XSS(Cross Site Scripting)以及 SQL 入侵攻擊。在 SQL 入侵中,不受信任的數(shù)據(jù)(例如發(fā)給 Web 網(wǎng)頁的反饋)和其他文本被銜接在一起,構(gòu)成一個查詢。攻擊者可以蓄意地安排他們的輸入,使之溢出引號之外,并在您想運行的真正查詢后面鏈接上任意一個查詢。這種攻擊使攻擊者可以更新、插入或刪除數(shù)據(jù),甚至可能可以看到數(shù)據(jù)庫中的任意信息。
XSS 也是一個類似的問題。不過這一次不受信任的數(shù)據(jù)瞄準的是瀏覽站點的人們,而不是應(yīng)用程序本身。通過提交包含 HTML 或 javascript 組合的文本,攻擊者期望您之后會將那種數(shù)據(jù)直接輸出到其他訪問站點的人那里,從而使惡意代碼可以在站點訪問者的瀏覽器上運行。
在編寫應(yīng)用程序時,需要同時考慮這兩種攻擊。如果小心地檢驗和過濾輸入,這兩種攻擊都是可以防止的。對 XSS 的處理很有技巧性,所以在這里我不便多講(不過可以從側(cè)欄找到有用的參考資料)。相比之下,SQL 入侵更容易對付。您只需在構(gòu)造查詢之前,適當?shù)嘏懦繅K不受信任的數(shù)據(jù)。這種事情有點煩雜,特別是當您有大量的字段要處理時,很容易忘記做這件事。
雖然這是有用的(并且也是重要的)信息,但是您可能想知道,為什么我要花時間提到這一點,本文的重點不是結(jié)合使用 PDO 和 DB2 嗎?原因是這樣的:PHP 現(xiàn)在得到很廣泛的部署,自然地,大量流行的基于 PHP 的應(yīng)用程序也得到了廣泛的部署。每當某一種這樣的應(yīng)用程序(和 PHP 本身沒有聯(lián)系)被發(fā)現(xiàn)存在漏洞時,PHP 常常被誤認為是不安全的,可被利用的或者有缺陷的。為了避免將來出現(xiàn)這樣的情況,我們可以采取的一個措施是鼓勵應(yīng)用程序開發(fā)人員多考慮安全問題,從而減少由誠實的錯誤導(dǎo)致的損害。扯遠了,下面繼續(xù)介紹其他關(guān)鍵概念。
很多更成熟的數(shù)據(jù)庫都支持預(yù)處理語句的概念。什么是預(yù)處理語句?您可以把預(yù)處理語句看作您想要運行的 SQL 的一種編譯過的模板,它可以使用變量參數(shù)進行定制。預(yù)處理語句可以帶來兩大好處:
查詢只需解析(或準備)一次,但是可以用相同或不同的參數(shù)執(zhí)行多次。當查詢準備好后,數(shù)據(jù)庫將分析、編譯和優(yōu)化執(zhí)行該查詢的計劃。對于復(fù)雜的查詢,這個過程要花比較長的時間,如果您需要以不同參數(shù)多次重復(fù)相同的查詢,那么該過程將大大降低應(yīng)用程序的速度。通過使用預(yù)處理語句,可以避免重復(fù)分析/編譯/優(yōu)化周期。簡言之,預(yù)處理語句使用更少的資源,因而運行得更快。 提供給預(yù)處理語句的參數(shù)不需要用引號括起來,驅(qū)動程序會處理這些。如果應(yīng)用程序獨占地使用預(yù)處理語句,那么可以確保沒有 SQL 入侵發(fā)生。(然而,如果您仍然將查詢的其他部分建立在不受信任的輸入之上,那么就仍然存在風(fēng)險)。 預(yù)處理語句是如此有用,以致 PDO 實際上打破了在目標 4 中設(shè)下的規(guī)則:如果驅(qū)動程序不支持預(yù)處理語句,那么 PDO 將仿真預(yù)處理語句。
下面是使用預(yù)處理語句的兩個例子。第一個例子 通過替換指定占位符的 name 和 value,執(zhí)行一次插入。而 第二個例子 使用問號占位符執(zhí)行一條 select 語句。
清單 4. 使用預(yù)處理語句的重復(fù)插入
$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. 使用預(yù)處理語句取數(shù)據(jù)
$stmt = $dbh->prepare("SELECT * FROM REGISTRY where name = ?"); if ($stmt->execute(array('one'))) { while ($row = $stmt->fetch()) { print_r($row); }} |
如果數(shù)據(jù)庫驅(qū)動程序支持,您還可以綁定輸出和輸入?yún)?shù)。輸出參數(shù)通常用于從存儲過程獲取值。輸出參數(shù)使用起來比輸入?yún)?shù)要復(fù)雜一些,當綁定一個給定的輸出參數(shù)時,必須知道該參數(shù)的長度。如果為參數(shù)綁定的值大于您建議的長度,那么就會產(chǎn)生錯誤。
清單 6. 帶輸出參數(shù)調(diào)用存儲過程
$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"; |
您還可以指定同時具有輸入和輸出值的參數(shù),其語法類似于輸出參數(shù)。在接下來的例子中,字符串 'hello' 被傳遞給存儲過程,當存儲過程返回時,hello 被替換為該存儲過程返回的值。
清單 7. 帶輸入/輸出參數(shù)調(diào)用存儲過程
$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"; |
PDO 提供了 3 種不同的錯誤處理模式,以滿足不同風(fēng)格的編程:
PDO_ERRMODE_SILENT 這是默認模式。PDO 將只設(shè)置錯誤代碼,以通過 errorCode() 和 errorInfo() 方法對語句和數(shù)據(jù)庫對象進行檢查。如果錯誤是由于對語句對象的調(diào)用而產(chǎn)生的,那么可以在那個對象上調(diào)用 errorCode() 或 errorInfo() 方法。如果錯誤是由于調(diào)用數(shù)據(jù)庫對象而產(chǎn)生的,那么可以在那個數(shù)據(jù)庫對象上調(diào)用上述兩個方法。 PDO_ERRMODE_WARNING 除了設(shè)置錯誤代碼以外,PDO 還將發(fā)出一條傳統(tǒng)的 E_WARNING 消息。如果您只是想看看發(fā)生了什么問題,而無意中斷應(yīng)用程序的流程,那么在調(diào)試/測試當中這種設(shè)置很有用。 PDO_ERRMODE_EXCEPTION 除了設(shè)置錯誤代碼以外,PDO 還將拋出一個 PDOException,并設(shè)置其屬性,以反映錯誤代碼和錯誤信息。這種設(shè)置在調(diào)試當中也很有用,因為它會放大腳本中產(chǎn)生錯誤的地方,從而可以非??焖俚刂赋龃a中有問題的潛在區(qū)域(記住,如果異常導(dǎo)致腳本終止,則事務(wù)將自動回滾)。 異常模式另一個有用的地方是,與傳統(tǒng)的 PHP 風(fēng)格的警告相比,您可以更清晰地構(gòu)造自己的錯誤處理,而且,比起以靜寂方式以及顯式地檢查每個數(shù)據(jù)庫調(diào)用的返回值,異常模式需要的代碼/嵌套也更少。 PDO 定制了使用 SQL-92 SQLSTATE 錯誤代碼字符串的標準;不同 PDO 驅(qū)動程序負責(zé)將它們本地代碼映射為適當?shù)?SQLSTATE 代碼。例如,SQLSTATE 是用于 DB2(以及通常的 ODBC)的本地錯誤代碼格式,這是多么方便啊!errorCode() 方法返回一個 SQLSTATE 代碼。如果您需要關(guān)于一個錯誤的更多特定的信息,PDO 還提供了一個 errorInfo() 方法,該方法將返回一個數(shù)組,其中包含 SQLSTATE 代碼、特定于驅(qū)動程序的錯誤代碼以及特定于驅(qū)動程序的錯誤字符串。
分頁數(shù)據(jù)、滾動游標和定位更新
在 Web 應(yīng)用程序中,一種常見的范例是對查詢結(jié)果進行分頁。如果您使用一個 Internet 搜索引擎,那么很可能每天都會做這樣的事。您輸入搜索詞,然后得到前 10-20 個匹配項。如果您想看到更多搜索結(jié)果,可以單擊 "next page" 鏈接。如果想回頭看前面看過的結(jié)果,可以單擊 "previous page" 鏈接。記得在幾年前,當我第一次在 Web 上使用這樣的東西時,我對自己說:"為什么我不能通過滾動查看所有數(shù)據(jù)呢?" 問題的答案說簡單也簡單,說復(fù)雜也復(fù)雜 -- 我只想說,HTTP 不會智能地使數(shù)據(jù)庫上的可滾動游標一直處于開放狀態(tài),即便如此,需要大量傳輸?shù)?Web 應(yīng)用程序也會很快地消耗掉大量開放的可滾動游標。因此,最簡單的解決方案是為用戶顯示所有的匹配項 -- 但是用戶很容易迷失在大量的結(jié)果當中。比較符合邏輯的措施是人工地將數(shù)據(jù)格式化到多個頁面上,使用戶可以每次查看一部分可以管理的數(shù)據(jù)。
所以人們編寫可以取所有數(shù)據(jù)的 PHP 應(yīng)用程序,然后只顯示前 10 行。根據(jù)下一次請求,應(yīng)用程序又顯示 11-20 行,依此類推。這對于只返回少量數(shù)據(jù)的查詢來說很不錯,但是,如果有很多匹配項(比如多于 100),那么先取全部數(shù)據(jù)然后丟棄其中的 90%,這種做法很浪費。PHP 的創(chuàng)始人 Rasmus Lerdorf 就這種情形特地為 MySQL 發(fā)明了一個特殊的 "LIMIT, OFFSET" 子句。它允許您通知數(shù)據(jù)庫,您只對一小部分行感興趣,這樣它就不會取其他不需要的行了。其語法(或非常類似的東西)已經(jīng)被其他流行的開放源代碼數(shù)據(jù)庫采納,但并不是所有數(shù)據(jù)庫都提供了相同的語法。 Troels Arvin 收集了一些非常有用的信息,對不同 RDBMS 所支持的語法進行了比較。
如果您想在以 DB2 為后臺數(shù)據(jù)庫的 PHP 應(yīng)用程序中實現(xiàn)分頁結(jié)果,那么可以(也應(yīng)該)使用下面示例中的語法。這里我們假設(shè)有一個 books 表,表中包含書名和作者,我們現(xiàn)在想要每次在一頁中顯示 10 個以上結(jié)果:
清單 8. 使用 SQL Standard "Window Functions" 實現(xiàn)數(shù)據(jù)分頁
$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 實現(xiàn)中還不支持 ROW_NUMBER(),所以需要使用可滾動游標。 |
現(xiàn)在,如果您要編寫一個更通用的應(yīng)用程序,并希望實現(xiàn)分頁的結(jié)果集,但是不想專門編寫很多的代碼,并且也不想使用更重量級的抽象層,Troels Arvin 的非常有幫助的 RDBMS 信息建議,您可以使用游標作為更輕便(稍微慢一點)的方案。碰巧的是,PDO 具有這方面的 API 級的支持。下面將談到如何使用這種支持來達到與上面示例相同的效果:
清單 9. 使用滾動游標實現(xiàn)數(shù)據(jù)分頁
$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); } } |
需要強調(diào)的是,雖然滾動游標對于更冗長的 window 函數(shù)方案來說是一個很方便的替代方案,但這種方案要慢很多。如果在一個傳輸量比較少的環(huán)境中進行測試,您可能發(fā)現(xiàn)不了速度上的差異,但當規(guī)模擴大時,您就會開始發(fā)現(xiàn)速度降慢帶來的痛苦。
定位更新
可滾動游標的另一個用途是,基于 SQL 中無法表達的重大標準驅(qū)動更新。如果您有一個 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)); }} |
大型對象
在應(yīng)用程序中的某個地方,您可能發(fā)現(xiàn)需要在數(shù)據(jù)庫中存儲"大型(large)"數(shù)據(jù)。大型通常意味著"大約 4kb 或 4kb 以上",盡管在沒有"大型"數(shù)據(jù)之前 DB2 最大可以處理 32kb 的數(shù)據(jù)。 大型對象可以是文本的,也可以是二進制的。PDO 允許在 bindParam() 或 bindColumn() 調(diào)用中通過使用 PDO_PARAM_LOB 類型代碼來使用大型數(shù)據(jù)類型。PDO_PARAM_LOB 告訴 PDO 將數(shù)據(jù)映射為流,所以可以使用 PHP Streams API 來操縱這樣的數(shù)據(jù)。下面是一個示例:
清單 11. 從數(shù)據(jù)庫取一副圖像
$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); |
上面的介紹很簡明扼要。現(xiàn)在讓我們試試另一面,將上傳的圖像插入到一個數(shù)據(jù)庫中:
清單 12. 將圖像插入數(shù)據(jù)庫中
$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(); |
這兩個例子都是宏觀層次的。請記住,被取的大型對象是一個流,可以通過所有常規(guī)的流函數(shù)來使用它,例如 fgets()、fread()、fgetcsv() 和 stream_get_contents()。
關(guān)于全球化、NLS 和字符集的簡要說明
在越來越多的 PHP 應(yīng)用程序中,越來越重要的一點是讓應(yīng)用程序能夠在全球范圍內(nèi)使用。從實踐角度來講,這意味著應(yīng)用程序需要能夠正確地處理多種語言(例如英語和日語)中的數(shù)據(jù),并且其功能性不變。這是一個很大的專題,做起來很有技巧性。實現(xiàn)全球化要走的第一步是采用一種適合所有數(shù)據(jù)的全球編碼,例如 UTF-8。這是一種 ASCII 兼容的編碼,它可以使用特殊字符序列為整個 unicode 字符集編碼。UTF-8 也是一種多字節(jié)編碼。
與常規(guī) ASCII 字符串相比,多字節(jié)編碼的字符串處理起來要棘手一點,因為一個或多個字符對應(yīng)于一個給定的字母 -- 例如,UTF-8 允許最多 6 個字符的序列映射到字符串中的一個字母。ASCII 字符在 UTF-8 中仍具有相同的表示,因此,如果只是處理不帶任何特殊音調(diào)的純英文文本,則 UTF-8 看上去就像是 ASCII。這意味著類似的字符串函數(shù)(作用于字節(jié)而不是字符位置),例如 strlen() 和 substr(),可能得不到預(yù)期的效果,這取決于 UTF-8 字符串中的內(nèi)容。幸運的是,PHP iconv 擴展為這些函數(shù)提供了一些編碼感知的替代函數(shù)。例如,您可以使用 iconv_strlen() 來得出字符串中的字符數(shù),而不是使用 strlen()。類似地,您可以不用 strpos() 或 substr(),而使用 iconv_strpos() 和 iconv_substr()。
iconv 擴展為您提供了在處理多種編碼下的數(shù)據(jù)時所需的基本工具。應(yīng)用程序應(yīng)該盡量確保所有數(shù)據(jù)都是 UTF-8 編碼的。如果用適當?shù)?Content-Type 標記 Web 頁面,那么大多數(shù)瀏覽器將發(fā)送 UTF-8 編碼的數(shù)據(jù),可以確信,一定有一個編碼類型屬性可應(yīng)用于 HTML FORM 標簽。
接下來的一步是設(shè)置 DB2 實例,使它在您與之交互時使用 UTF-8。這很容易辦到,只需在 DB2 的命令行提示符中運行以下命令:
清單 14. 設(shè)置 DB2 實例,使之使用 UTF-8
$ db2set DB2CODEPAGE=1208 |
完成這樣的更改后,從 DB2 實例取到的所有文本數(shù)據(jù)都是 UTF-8 編碼的。同樣,DB2 期望您輸入的所有文本也是 UTF-8 編碼的。當應(yīng)用程序的每個部分都使用 UTF-8 時,應(yīng)用程序就可以全球使用了,并且能夠顯示任何語言的文本,只要這種語言的文本可以用 UTF-8 編碼。前面我已經(jīng)暗示過,這只是通往國際化大道的第一步。還有很多其他的事情需要考慮,例如本地化(采用給定用戶的地區(qū)設(shè)置來顯示日期、時間、重量和度量,將通用文本翻譯成用戶本地的語言)、從右到左或雙向(bi-di)文本布局,等等。
值得注意的是,PDO 不對該數(shù)據(jù)做任何特殊的事情。有些驅(qū)動程序允許更改為一個連接使用的編碼,但是在 PDO 級沒有處理這種事情的特殊邏輯。其原因是,PHP 內(nèi)部完全不知道 unicode,所以在這里試圖使 PDO 知道 unicode 是沒有意義的。如果您對這方面的專題感興趣,那么您會欣喜地得知,PHP 的 unicode 支持很快就要出現(xiàn),不過我也不知道它初次露面的確切日期。
1,3-丙二醇簡稱PDO
中文名稱: 1,3-丙二醇
英文名稱: 1,3-propanediol
英文名稱2: 1,3-dihydroxypropane
分子式: C3H8O2
結(jié)構(gòu): 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): 無資料
溶解性: 與水混溶,可混溶于乙醇、乙醚。
主要用途: 用作溶劑, 用于有機合成。
格式:pdf
大?。?span id="c7g77re" class="single-tag-height">267KB
頁數(shù): 1頁
評分: 4.5
軟件名稱:谷歌瀏覽器Google Chrome 軟件屬性:主頁瀏覽 運行平臺:Windows2000/2005/xP/Vista/7/8軟件大?。?00KB 軟件語言:簡體中文
格式:pdf
大?。?span id="d7els9u" class="single-tag-height">267KB
頁數(shù): 4頁
評分: 4.6
1 信息技術(shù)教學(xué)設(shè)計 年級 八年級 主備 審核 編號 課題 圖片的獲取 課型 新授課 備課時間 上課時間 教材分析 : 圖片是多媒體中常見的素材之一,也是人們傳遞與表達信息的重要形式。本節(jié)是本單元 的起點,只有了解圖片的基礎(chǔ)知識,掌握獲取圖片的各種方法,學(xué)會根據(jù)主題需求獲取所需 的圖片,才能為圖片的加工與創(chuàng)作提供必要的條件,進而為多媒體創(chuàng)作提供更多的素材。因 此,本節(jié)在培養(yǎng)學(xué)生信息獲取能力方面有著重要的作用。 學(xué)情分析 : 本節(jié)內(nèi)容緊扣時下流行元素例如數(shù)碼相機、掃描儀、屏幕截圖等等,對初一的同學(xué)來說 很新鮮,有吸引力,會激起他們的學(xué)習(xí)興趣。 教學(xué)目標 : 知識與技能:了解圖片的特點,掌握獲取圖片的幾種方法。 過程與方法:能夠掌握獲取圖片的一般方法,選擇合理的方法獲取圖片。 情感、態(tài)度與價值觀:學(xué)生交流合作,養(yǎng)成正確的審美觀,通過完成操作體驗成功的喜 悅。 教學(xué)重點 : 掌握獲取圖片的常用
產(chǎn)品名稱(中文)LAP Dorado 激光定位系統(tǒng)
產(chǎn)品名稱(英文)LAP Dorado Laser Positioning Systems
注冊號國食藥監(jiān)械(進)字2005第2240139號
產(chǎn)品性能結(jié)構(gòu)及組成結(jié)構(gòu):固定激光燈, 可移動激光軌,手控盒。性能:可移動激光軌移動范圍600mm;激光移動定位精度±0.25mm。
產(chǎn)品適用范圍該定位系統(tǒng)與CT掃描設(shè)備安裝在一起,在病人皮膚上投射出用作標記的位置參考點, 供對病人腫瘤放射治療時定位之用。
注冊代理德國LAP激光應(yīng)用有限公司上海代表處
售后服務(wù)機構(gòu)德國LAP激光應(yīng)用有限公司上海代表處
批準日期2005.01.17
有效期截止日2009.01.16
備注LAP GmbH Laser Application,
生產(chǎn)廠商名稱(英文)LAP GmbH Laser Application
生產(chǎn)廠地址(中文)Zeppelinstr. 23 D-21337 Luneburg, Germany
生產(chǎn)場所Zeppelinstr. 23 D-21337 Luneburg, Germany
生產(chǎn)國(中文)德國
規(guī)格型號Dorado CT-1-1, Dorado CT-1-3, Dorado CT-1-4、
產(chǎn)品標準YZB/GEM 1738-24-2004 《LAP Dorado 激光定位系統(tǒng)》
單品 產(chǎn)品名稱 產(chǎn)地/廠商 地域 價格 (元/噸) 漲跌 報價日期 DOP DOP 東莞盛和 全國 12800 0 12-08-07 DOP DOP 浙江慶安 全國 12500 0 12-08-07 DOP DOP 鎮(zhèn)江聯(lián)成 全國 12500 0 12-08-07 DOP DOP 山東宏信 全國 12400 100 12-08-07 DOP DOP 愛敬寧波 全國 12600 100 12-08-07 DOP DOP 齊魯增塑劑 全國 12500 100 12-08-07 DOP DOP 寧波聯(lián)泰 全國 12500 0 12-08-07 DOP DOP 寧波東來 全國 12500 0 12-08-07 DOP DOP 金陵石化 全國 12500 0 12-08-07 DOP DOP 浙江偉博 全國 12500 0 12-08-07
單品 產(chǎn)品名稱 產(chǎn)地/廠商 地域 價格 (元/噸) 漲跌 報價日期 DOP DOP 東莞盛和 全國 12800 -100 12-08-06 DOP DOP 浙江慶安 全國 12500 0 12-08-06 DOP DOP 鎮(zhèn)江聯(lián)成 全國 12500 0 12-08-06 DOP DOP 山東宏信 全國 12300 0 12-08-06 DOP DOP 愛敬寧波 全國 12500 0 12-08-06 DOP DOP 齊魯增塑劑 全國 12400 0 12-08-06 DOP DOP 寧波聯(lián)泰 全國 12500 0 12-08-06 DOP DOP 寧波東來 全國 12500 0 12-08-06 DOP DOP 金陵石化 全國 12500 0 12-08-06 DOP DOP 浙江偉博 全國 12500 0 12-08-06