天天看點

pdo的使用操作1

很多程式員都學習過如何使用 MySQL 或 MySQLi 擴充通路資料庫。在 PHP 5.1 中,有一個更好的方法。 PHP Data Objects (PDO) 提供了很多預處理語句的方法,且使用對象将使你的工作更有成效!

PDO 介紹

“PDO – PHP Data Objects – 是一個對多種資料庫提供統一操作方法的資料庫通路層。”

它并不具備資料庫特有的文法,但它将使切換資料庫和平台更加容易,多數情況下,隻需要簡單修改連結字元串。

pdo的使用操作1

這并非一篇完整教導如何使用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 使用自己的文法。

pdo的使用操作1
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,這通常需要兩個步驟。本節中所述的所有内容對更新和插入都有效。

pdo的使用操作1

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

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

pdo的使用操作1

資料通過語句句柄的->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 }
PDO