pdo的使用操作1

很多程序員都學習過如何使用 MySQL 或 MySQLi 擴展訪問數據庫。在 PHP 5.1 中,有一個更好的方法。 PHP Data Objects (PDO) 提供了很多預處理語句的方法,且使用對象將使你的工作更有成效!

PDO 介紹

“PDO – PHP Data Objects – 是一個對多種數據庫提供統一操作方法的數據庫訪問層。”

它並不具備數據庫特有的語法,但它將使切換數據庫和平臺更加容易,多數情況下,只需要簡單修改鏈接字符串。

PDO - db abstraction layer

這並非一篇完整教導如何使用SQL的教程。它重要爲那些現今仍在使用 mysql 或 mysqli 擴展的人,幫助他們躍至更具可移植性和強力的 PDO。

數據庫支持

此擴展可以使用 PDO 驅動編寫過的所有數據庫。在本文書寫時,下面的數據庫支持已經實現:

  • PDO_DBLIB ( FreeTDS / Microsoft SQL Server / Sybase )
  • PDO_FIREBIRD ( Firebird/Interbase 6 )
  • PDO_IBM ( IBM DB2 )
  • PDO_INFORMIX ( IBM Informix Dynamic Server )
  • PDO_MYSQL ( MySQL 3.x/4.x/5.x )
  • PDO_OCI ( Oracle Call Interface )
  • PDO_ODBC ( ODBC v3 (IBM DB2, unixODBC and win32 ODBC) )
  • PDO_PGSQL ( PostgreSQL )
  • PDO_SQLITE ( SQLite 3 and SQLite 2 )
  • PDO_4D ( 4D )

你的系統不會也不必支持所有上面的驅動;下面是一個快速檢查所支持數據庫的方法:

1 print_r(PDO::getAvailableDrivers());

連接

不同數據庫的連接方法可能稍有不同,下面是一些較爲流行的數據庫連接方法。你將注意到,雖然數據庫類型不同,前三種數據庫的連接方式是相同的——而 SQLite 使用自己的語法。

Connection String
01 try { 
02   # MS SQL Server andSybase with PDO_DBLIB 
03   $DBH = newPDO("mssql:host=$host;dbname=$dbname, $user, $pass"); 
04   $DBH = newPDO("sybase:host=$host;dbname=$dbname, $user, $pass"); 
05  
06   # MySQL with PDO_MYSQL 
07   $DBH = newPDO("mysql:host=$host;dbname=$dbname", $user, $pass); 
08  
09   # SQLite Database 
10   $DBH = newPDO("sqlite:my/database/path/database.db"); 
11
12 catch(PDOException $e) { 
13     echo$e->getMessage(); 
14 }

注意 try/catch 塊——你應該總是使用 try/catch 包裝你的 PDO 操作,並使用異常機制——這裏只是簡單的示例。通常,你只需要一個連接——有很多可以教你語法的列表。 $DBH 代表“數據庫句柄”,這將貫穿全文。

通過將句柄設置爲 NULL,你可以關閉任一連接。

1 # close the connection 
2 $DBH = null;

你可以在PHP.net找到更多數據庫特定選項和/或其它數據庫連接字符串的信息。

異常與 PDO

PDO 可以使用異常處理錯誤,這意味着你的所有 PDO 操作都應當包裝在一個 try/catch 塊中。你可以通過設定錯誤模式屬性強制 PDO 在新建的句柄中使用三種錯誤模式中的某一個。下面是語法:

1 $DBH->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT ); 
2 $DBH->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING ); 
3 $DBH->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );

無論你設定哪個錯誤模式,一個錯誤的連接總會產生一個異常,因此創建連接應該總是包裝在 try/catch 塊中。

PDO::ERRMODE_SILENT

這是默認的錯誤模式。如果你使用這個模式,你將得使用同 mysql 或 mysqli 擴展一樣的方法差錯。其它兩種模式更適合 DRY 編程。

PDO::ERRMODE_WARNING

此方法將會發出一個標準PHP警告,並允許程序繼續運行。這對調試很有幫助。

PDO::ERRMODE_EXCEPTION

這是多數情況下你所希望的方式。它生成異常,允許你更容易的處理錯誤,隱藏可能導致它人瞭解你係統的信息。下面是一個充分利用異常的示例:

01 # connect to the database 
02 try { 
03   $DBH = newPDO("mysql:host=$host;dbname=$dbname", $user, $pass); 
04   $DBH->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); 
05  
06   # UH-OH! Typed DELECT instead of SELECT! 
07   $DBH->prepare('DELECT name FROM people'); 
08
09 catch(PDOException $e) { 
10     echo"I'm sorry, Dave. I'm afraid I can't do that."; 
11     file_put_contents('PDOErrors.txt', $e->getMessage(), FILE_APPEND); 
12 }

在 select 語句中有一個故意留下的錯誤;這將導致一個異常。異常錯誤細節保存至一個 log 文件,並生成一段友好的(或不怎麼友好的)信息於用戶。

插入和更新

插入新數據,更新已存數據是一種非常常見的數據庫操作。使用 PDO,這通常需要兩個步驟。本節中所述的所有內容對更新和插入都有效。

2 to 3 step insert and update

這裏有一個最基本的插入示例:

1 # STH means "Statement Handle"
2 $STH = $DBH->prepare("INSERT INTO folks ( first_name ) values ( 'Cathy' )");
3 $STH->execute();

你也可以使用 exec() 完成相同的操作,這將減少調用。多數情況下,你會使用調用多的方法,以充分利用語句預處理的優勢。即使你只用它一次,使用語句預處理,幫助你保護你的 SQL 免於注入攻擊。

預處理語句

使用語句預處理將幫助你免於SQL注入攻擊。

一條預處理語句是一條預編譯的 SQL 語句,它可以使用多次,每次只需將數據傳至服務器。其額外優勢在於可以對使用佔位符的數據進行安全處理,防止SQL注入攻擊。

你通過在 SQL 語句中使用佔位符的方法使用預處理語句。下面是三個例子:一個沒有佔位符,一個使用無名佔位符,一個使用命名佔位符。

1 # no placeholders - ripe for SQL Injection!
2 $STH = $DBH->("INSERT INTO folks (name, addr, city) values ($name, $addr, $city)");
3  
4 # unnamed placeholders
5 $STH = $DBH->("INSERT INTO folks (name, addr, city) values (?, ?, ?);
6  
7 # named placeholders
8 $STH = $DBH->("INSERT INTO folks (name, addr, city) value (:name, :addr, :city)");

你希望避免第一種方法。選擇命名我無名佔位符將會對你對語句中數據的設置產生影響。

無名佔位符

01 # assign variables to each place holder, indexed 1-3
02 $STH->bindParam(1, $name);
03 $STH->bindParam(2, $addr);
04 $STH->bindParam(3, $city);
05  
06 # insert one row
07 $name = "Daniel"
08 $addr = "1 Wicked Way";
09 $city = "Arlington Heights";
10 $STH->execute();
11  
12 # insert another row with different values
13 $name = "Steve"
14 $addr = "5 Circle Drive";
15 $city = "Schaumburg";
16 $STH->execute();

這裏有兩步。首先,我們對各個佔位符指定變量(2-4行)。然後,我們對各個佔位符指定數據,並執行語句。要發送另一組數據,只需改變這些變量的值並再次執行語句。

這種方法看上去對擁有很多參數的語句很笨拙吧?的確。然而,當數據保存於數組中時,這非常容易簡略:

1 # the data we want to insert
2 $data = array('Cathy', '9 Dark and Twisty Road', 'Cardiff');
3  
4 $STH = $DBH->("INSERT INTO folks (name, addr, city) values (?, ?, ?);
5 $STH->execute($data);

容易吧!

數組中的數據按順序填入佔位符中。 $data[0]是第一個,$data[1]是第二個,依次。不過,要是數組中數據的次序不正確,這將不能正常運行,你需要先對數組排序。

命名佔位符

你可能已經開始猜測語法了,不過下面就是示例:

1 # the first argument is the named placeholder name - notice named
2 # placeholders always start with a colon.
3 $STH->bindParam(':name', $name);

你可以看使用快捷方式,但它需使用關聯數組。下面是示例:

1 # the data we want to insert
2 $data = array( 'name' => 'Cathy', 'addr' => '9 Dark and Twisty', 'city' => 'Cardiff' );
3  
4 # the shortcut!
5 $STH = $DBH->("INSERT INTO folks (name, addr, city) value (:name, :addr, :city)");
6 $STH->execute($data);

數組中的鍵不需要以冒號開頭,但其它部分需要同佔位符匹配。如果你有一個二維數組,你只需遍歷它,並對遍歷的每個數組執行語句。

命名佔位符的另一個好的功能是直接將對象插入到你的數據庫中,只要屬性同命名字段匹配。下面是一個示例對象,以及如何將它插入到數據庫中的示例:

01 # a simple object
02 class person {
03     public $name;
04     public $addr;
05     public $city;
06  
07     function __construct($n,$a,$c) {
08         $this->name = $n;
09         $this->addr = $a;
10         $this->city = $c;
11     }
12     # etc ...
13 }
14  
15 $cathy = new person('Cathy','9 Dark and Twisty','Cardiff');
16  
17 # here's the fun part:
18 $STH = $DBH->("INSERT INTO folks (name, addr, city) value (:name, :addr, :city)");
19 $STH->execute((array)$cathy);

通過在執行時將對象轉換爲數組,輸將將會同數組的鍵一樣對待。

Selecting Data

Fetch data into arrays or objects

數據通過語句句柄的->fetch() 方法獲取。在調用 fetch 之前,最好通知 PDO 你所希望獲取數據的方式。你有如下選項:

  • PDO::FETCH_ASSOC:返回一個通過字段名稱索引的數組。
  • PDO::FETCH_BOTH (default):返回一個數組,同時通過序號和名稱索引。
  • PDO::FETCH_BOUND:通過->bindColumn() 方法綁定變量獲取返回值
  • PDO::FETCH_CLASS: 將返回值分配給一個命名類。如果類匹配屬性不存在,則將創建相應的屬性。
  • PDO::FETCH_INTO: 更新一個命名類現有的實例化對象。
  • PDO::FETCH_LAZY: 結合 PDO::FETCH_BOTH/PDO::FETCH_OBJ, 同它們各自方式一樣創建對象的變量名稱。
  • PDO::FETCH_NUM:返回一個按列順序數字索引的數組
  • PDO::FETCH_OBJ:返回一個匿名對象,屬性名稱對應列名。

在實際應用中,三個就能涵蓋大多數情況:FETCH_ASSOC、FETCH_CLASS 和 FETCH_OBJ。要設定 fetch 方法,使用如下語法:

1 $STH->setFetchMode(PDO::FETCH_ASSOC);

你也可以在調用 ->fetch() 方法時直接設定。

FETCH_ASSOC

這個 fetch 創建一個關聯數組,通過列的名稱索引。這對使用過 mysql/mysqli 擴展的人應該相當熟悉。下面是通過此方法獲取數據的示例:

01 # using the shortcut ->query() method here since there are no variable
02 # values in the select statement.
03 $STH = $DBH->query('SELECT name, addr, city from folks');
04  
05 # setting the fetch mode
06 $STH->setFetchMode(PDO::FETCH_ASSOC);
07  
08 while($row = $STH->fetch()) {
09     echo $row['name'] . "\n";
10     echo $row['addr'] . "\n";
11     echo $row['city'] . "\n";
12 }

while 循環將繼續逐行遍歷結果集,直到遍歷完畢。

FETCH_OBJ

此 fetch 將爲返回數據的每一行創建一個標準對象。示例如下:

01 # creating the statement
02 $STH = $DBH->query('SELECT name, addr, city from folks');
03  
04 # setting the fetch mode
05 $STH->setFetchMode(PDO::FETCH_OBJ);
06  
07 # showing the results
08 while($row = $STH->fetch()) {
09     echo $row->name . "\n";
10     echo $row->addr . "\n";
11     echo $row->city . "\n";
12 }

FETCH_CLASS

對象的屬性將在構造函數被調用之前完成設置,這點非常重要。

此 fetch 方法允許你將獲取結果直接填入你選擇的類中。當使用 FETCH_CLASS 時,對象的屬性將在構造函數被調用之前完成設置。再讀一遍,這點相當哪個重要。如果匹配列名稱的屬性不存在,這些屬相將被創建(以 public 方式)。

這意味着,如果你的數據在從數據庫中讀取後需要轉化處理,它可以在每個對象創建時由對象自動處理。

例如,假如每條記錄的地址都需要掩蓋一部分。我們可以在構造函數中操作這個屬性。示例如下:

01 class secret_person {
02     public $name;
03     public $addr;
04     public $city;
05     public $other_data;
06  
07     function __construct($other = '') {
08         $this->address = preg_replace('/[a-z]/', 'x', $this->address);
09         $this->other_data = $other;
10     }
11 }

當數據被獲取到類中時,地址的所有小寫字母 a-z 都被 x 替換。現在,使用類和完成數據轉化是完全透明的。

1 $STH = $DBH->query('SELECT name, addr, city from folks');
2 $STH->setFetchMode(PDO::FETCH_CLASS, 'secret_person');
3  
4 while($obj = $STH->fetch()) {
5     echo $obj->addr;
6 }

如果地址是 ’5 Rosebud,’,你將看到 ’5 Rxxxxxx’ 這樣的輸出。當然,有時你希望構造函數在數據設置之前被調用。PDO 也考慮到這種情形。

1 $STH->setFetchMode(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'secret_person');

現在,當你使用這個 fetch 模式(PDO::FETCH_PROPS_LATE)重複前一個示例代碼時,地址不會被掩蓋,因爲構造函數在屬性分配之前就被調用了。

最後,如果你真的需要,你可以在使用 PDO 獲取數據到對象中時傳值給構造函數:

1 $STH->setFetchMode(PDO::FETCH_CLASS, 'secret_person', array('stuff'));

如果你對每個對象的構造函數傳遞的數據不同,你可以在 fetch 方法中設置 fetch 模式。

1 $i = 0;
2 while($rowObj =  $STH->fetch(PDO::FETCH_CLASS, 'secret_person', array($i))) {
3     // do stuff
4     $i++
5 }

其它一些有用的方法

儘管並不是說 PDO 就涵蓋了一切(它並非一個龐大的擴展!)。它依然有一些其它的方法,在使用 PDO 做一些基礎工作時會用到。

1 $DBH->lastInsertId();

->lastInsertId() 方法永遠在數據庫句柄上被調用,而非語句句柄,並將返回該連接插入的最後一條插入行的自增長id值。

1 $DBH->exec('DELETE FROM folks WHERE 1');
2 $DBH->exec("SET time_zone = '-8:00'");

->exec() 方法被用來執行那些無返回值或影響行數據的的命令。上面就是兩條使用exec 方法的例子。

1 $safe = $DBH->quote($unsafe);

->quote() 方法將過濾字符串引號,這樣你就可以在查詢語句中安全的使用了。此返回函數適應於不適用預處理語句的情形。

1 $rows_affected = $STH->rowCount();

->rowCount() 方法返回一個操作影響數據行的數量整數。在某個已知PDO版本中,根據 [this bug report](http://bugs.php.net/40822) 該方法對 select 語句無效。如果你遭遇此問題,請更新PHP,你也可以使用下面的代碼獲取行數:

01 $sql = "SELECT COUNT(*) FROM folks";
02 if ($STH = $DBH->query($sql)) {
03     # check the row count
04     if ($STH->fetchColumn() > 0) {
05  
06     # issue a real select here, because there's data!
07     }
08     else {
09         echo "No rows matched the query.";
10     }
11 }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章