之前一段時間,開始了php的研究,看了關於PDO的一些資料,發現不錯,整理和總結一下,作為開發筆記,留待日後使用,《PHP開發筆記系列(一)-PDO使用》。
PDO是PHP Data Objects的簡稱,是一種數據庫訪問抽象層。PDO是用於多種數據庫的一致接口。類比的說,PDO做的事情類似於JAVA中的持久層框架(Hibernate、OpenJPA)的功能,為異構數據庫提供一個統一的編程接口,這樣就不必再使用mysql_*、pg_*這樣的函數,也不必再寫自己的"GenericDAO"了。PDO在PHP5.1的時候一起發布,所以我們用的PHP5.2、PHP5.3都已經可以使用。
為了方便,我們使用MySQL5來做演示。
0. 建立實驗環境數據庫及相關表
Sql代碼
- CREATE TABLE `blog` (
- `id` int(10) NOT NULL AUTO_INCREMENT,
- `title` varchar(255) NOT NULL,
- PRIMARY KEY (`id`)
- ) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=latin1
1. 使用PDO訪問數據庫
通過PDO訪問數據庫的步驟是:a)指定dsn、username、password,b)通過#a中的設置構造PDO對象,代碼如下:
Php代碼
- file:pdo-access.php
- url:http://localhost:88/pdo/pdo-access.php
- <?php
- // 設置dsn、username、passwd
- $dsn = 'mysql:host=localhost;dbname=pdotest';
- $username = 'root';
- $passwd = 'password';
-
- // 構造PDO對象
- try {
- $dbh = new PDO($dsn, $username, $passwd);
- echo 'connect to database successfully!';
- } catch (Exception $e) {
- echo 'Fail to connect to database!\n';
- echo $e->getMessage();
- }
- ?>
備注:DSN即Data Source Name-數據源名稱,提供數據庫的連接信息,包括三部分,PDO驅動名稱(MySQL、SQLite、PostgreSQL等)、冒號和驅動特定的語法。但是一般情況下,我們都很難記住這些,可以下載個php manual查,也可以到php的官網查。
2. 使用Query方法查詢數據
在#1的基礎上,連接數據庫成功後,構造SQL語句,調用query方法返回結構數組,通過foreach進行數據結果遍歷,代碼如下:
Php代碼
- file:pdo-query.php
- url:http://localhost:88/pdo/pdo-query.php?title=title1
- <?php
-
- $dsn = 'mysql:host=localhost;dbname=pdotest';
- $username = 'root';
- $passwd = 'password';
-
- try {
- $dbh = new PDO($dsn, $username, $passwd);
- echo 'connect to database successfully!'."\r\n";
-
- $title = 'title1';
- // 構造SQL語句
- $sql = "SELECT * FROM blog WHERE title = '".$title."'";
- // 執行查詢並遍歷結果
- foreach ($dbh->query($sql) as $row){
- print $row['id']."\t";
- print $row['title']."\t";
- }
- } catch (PDOException $e) {
- echo 'Errors occur when query data!\n';
- echo $e->getMessage();
- }
- ?>
備注:一般情況下, 通過構造SQL語句的方法來進行query、update、insert、delete,都會需要指定where條件,因此不可避免的需要防止SQL注入的問題出現。
例如,正常情況下,當用戶輸入“title1”時,我們構造的sql語句會是SELECT * FROM blog WHERE title='title1',但是對SQL比較熟悉的用戶會輸入'OR id LIKE '%,此時我們構造的SQL就會變成SELECT * FROM blog where title='' OR id LIKE '%',這樣整張blog 表中的數據都會被讀取,因此需要避免,所以需要用到quote方法,把所有用戶提供的數據進行轉移,從而防止SQL注入的發生。使用quote方法後的sql為$sql = "SELECT * FROM blog WHERE title = ".$dbh->quote($title),轉移出來後的sql是SELECT * FROM blog WHERE title = '\'OR id LIKE \'%',把所有的單引號(')都轉移了。
3. 使用prepare和execute方法查詢數據
如果我們用到的SQL查詢是使用頻率不高的查詢,那麼使用query或prepare和execute方法來查詢都無太大差別,查詢速度也不會差太遠。兩者不同的是,使用query時,php向數據庫發送的sql,每執行一次都需要編譯一次,而使用prepare和execute方法,則不需要,因此做大並發量的操作時,使用prepare和execute方法的優勢會更加明顯。
使用prepare和execute方法的步驟不多,a)構造SQL,b)將SQL傳入PDO->prepart方法,得到一個PDOStatement對象,3)調用PDOStatement對象的execute方法,4)通過PDOStatement->fetch或PDOStatement->fetchObject遍歷結果集。代碼如下:
Php代碼
- file:pdo-prepare-fetch.php
- url:http://localhost:88/pdo/pdo-prepare-fetch.php?title=title1
- <?php
- $dsn = 'mysql:host=localhost;dbname=pdotest';
- $username = 'root';
- $passwd = 'password';
-
- // 從請求獲取title參數值
- $title = $_GET['title'];
- try {
- $dbh = new PDO($dsn, $username, $passwd);
- echo 'connect to database successfully!'."<br/>";
-
- // 構造SQL語句,使用綁定變量
- $sql = "SELECT * FROM blog WHERE title = :title";
- // 編譯SQL
- $stmt = $dbh->prepare($sql);
- // 為綁定變量賦值
- $stmt->bindParam(":title", $title, PDO::PARAM_STR);
- // 執行SQL
- $stmt->execute();
- // 以聯合數組方式獲取結果,並遍歷結果
- while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
- print $row['id']."\t";
- print $row['title']."\t";
- }
- } catch (PDOException $e) {
- echo 'Errors occur when query data!\n';
- echo $e->getMessage();
- }
- ?>
除了使用上面的PDO::FETCH_ASSOC返回聯合數組外,還可以使用fetchObject方法,返回結果集對象,代碼如下:
Php代碼
- file:pdo-prepare-fetch-object.php
- url:http://localhost:88/pdo/pdo-prepare-fetch-object.php?title=title1
- <?php
- $dsn = 'mysql:host=localhost;dbname=pdotest';
- $username = 'root';
- $passwd = 'password';
-
- $title = $_GET['title'];
- try {
- $dbh = new PDO($dsn, $username, $passwd);
- echo 'connect to database successfully!'."<br/>";
-
- $sql = "SELECT * FROM blog WHERE title = :title";
- $stmt = $dbh->prepare($sql);
- $stmt->bindParam(":title", $title, PDO::PARAM_STR);
- $stmt->execute();
- // 以對象數組方式獲取結果,並遍歷結果
- while ($row = $stmt->fetchObject()) {
- print $row->id."\t";
- print $row->title."\t";
- }
- } catch (Exception $e) {
- echo 'Errors occur when query data!\n';
- echo $e->getMessage();
- }
- ?>
4. 設置PDO的錯誤級別
PDO的錯誤級別分成PDO::ERRMODE_SILENT(默認)、PDO::ERRORMODE_WARNING、PDO::ERRORMODE_EXCEPTION三種。
PDO::ERRMODE_SILENT級別,當出現錯誤時,會自動設置PDOStatement對象的errorCode屬性,但不進行任何其他操作,因此需要我們手工檢查是否出現錯誤(使用empty($stmt->errorCode())),否則程序將繼續走下去。
PDO::ERRORMODE_WARNING級別,基本與PDO::ERRMODE_SILENT一致,都是需要使用empty($stmt->errorCode())手工檢查。
只需要在創建PDO對象後,加入以下代碼即可:$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);或$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
PDO::ERRORMODE_WARNING級別,當出現錯誤時,系統將拋出一個PDOException,並設置errorCode屬性,程序可以通過try{...}catch{...}進行捕捉,否則未catch的exception會導致程序中斷,加入以下代碼即可:$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
Php代碼
- <?php
- ...
- try {
- ...
- } catch (Exception $e) {
- echo 'Errors occur when operation!'."<br/>";
- // 獲取Exception信息
- echo $e->getMessage()."<br/>";
- // 獲取錯誤碼
- echo $e->getCode()."<br/>";
- // 獲取出錯文件名
- echo $e->getFile()."<br/>";
- // 獲取出錯行
- echo $e->getLine()."<br/>";
- // 把異常以字符串返回
- echo $e->getTraceAsString();
- }
- ?>
5. 使用prepare和execute方法插入/更新數據
方法和#3中進行查詢的差不多,只是構造的SQL語句是insert語句或update語句,代碼如下:
Php代碼
- file:pdo-prepare-insert.php
- url:http://localhost:88/pdo/pdo-insert.php?title=title11
- <?php
- $dsn = 'mysql:host=localhost;dbname=pdotest';
-
- $username = 'root';
- $passwd = 'password';
-
- $title = $_GET['title'];
- try {
- $dbh = new PDO($dsn, $username, $passwd);
- $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
- echo 'connect to database successfully!'."<br/>";
-
- // 構造Insert語句
- $sql = "INSERT INTO blog(title) VALUES(:title)";
- $stmt = $dbh->prepare($sql);
- $stmt->bindParam(":title", $title);
- $stmt->execute();
- } catch (Exception $e) {
- echo 'Errors occur when query data!\n';
- echo $e->getMessage();
- }
- ?>
Php代碼
- file:pdo-prepare-update.php
- url:http://localhost:88/pdo/pdo-update.php?id=1&title=title12
- <?php
- $dsn = 'mysql:host=localhost;dbname=pdotest';
-
- $username = 'root';
- $passwd = 'password';
-
- $id = $_GET['id'];
- $title = $_GET['title'];
- try {
- $dbh = new PDO($dsn, $username, $passwd);
- $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
- echo 'connect to database successfully!'."<br/>";
-
- // 構造update語句
- $sql = "UPDATE blog SET title=:title where id=:id";
- $stmt = $dbh->prepare($sql);
- $stmt->bindParam(":id", $id);
- $stmt->bindParam(":title", $title);
- $stmt->execute();
- } catch (Exception $e) {
- echo 'Errors occur when query data!\n';
- echo $e->getMessage();
- }
- ?>
6. 獲取返回的行數
使用#3中的prepare和execute方法,然後將sql語句改成count的,例如SELECT COUNT(id) FROM article ...,代碼如下:
Php代碼
- file:pdo-prepare-fetch-column.php
- url:http://localhost:88/pdo/pdo-prepare-fetch-column.php?id=1&title=title12
- <?php
- $dsn = 'mysql:host=localhost;dbname=pdotest';
- $username = 'root';
- $passwd = 'password';
-
- try {
- $dbh = new PDO($dsn, $username, $passwd);
- $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
- echo 'connect to database successfully!'."<br/>";
-
- // 構造count語句
- $sql = "SELECT COUNT(id) FROM blog";
- $stmt = $dbh->prepare($sql);
- $stmt->execute();
- // 使用fetchColumn獲取0列值
- echo $stmt->fetchColumn()." rows returned!";
- } catch (Exception $e) {
- echo 'Errors occur when query data!\n';
- echo $e->getMessage();
- }
- ?>
7. 獲取受影響的行數
使用#3中的prepare和execute方法,然後將SQL語句改成insert、update、delete語句即可,代碼如下:
Php代碼
- file:pdo-prepare-row-count.php
- url:http://localhost:88/pdo/pdo-prepare-row-count.php?id=1
- <?php
- $dsn = 'mysql:host=localhost;dbname=pdotest';
- $username = 'root';
- $passwd = 'password';
-
- $id = $_GET['id'];
- try {
- $dbh = new PDO($dsn, $username, $passwd);
- $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
- echo 'connect to database successfully!'."<br/>";
-
- $sql = "DELETE FROM blog WHERE id=:id";
- $stmt = $dbh->prepare($sql);
- $stmt->bindParam(":id", $id);
- $stmt->execute();
- // 獲取update、insert、delete操作後影響的行數
- echo $stmt->rowCount()." rows affected!";
- } catch (Exception $e) {
- echo 'Errors occur when data operation!\n';
- echo $e->getMessage();
- }
- ?>
8. 獲得新插入行的ID值
為數據庫表插入新數據行時,我們需要獲得剛剛插入的新行的ID值,此時我們需要使用到PDO的lastInsertId()方法,代碼如下:
Php代碼
- file:pdo-prepare-last-insertid.php
- url:http://localhost:88/pdo/pdo-prepare-last-insertid.php?title=title13
- <?php
- $dsn = 'mysql:host=localhost;dbname=pdotest';
-
- $username = 'root';
- $passwd = 'password';
-
- $title = $_GET['title'];
- try {
- $dbh = new PDO($dsn, $username, $passwd);
- $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
- echo 'connect to database successfully!'."<br/>";
-
- $sql = "INSERT INTO blog(title) VALUES(:title)";
- $stmt = $dbh->prepare($sql);
- $stmt->bindParam(":title", $title);
- $stmt->execute();
- // 獲取上一個之行的insert語句插入的數據的id值
- echo $dbh->lastInsertId();
- } catch (Exception $e) {
- echo 'Errors occur when query data!\n';
- echo $e->getMessage();
- }
- ?>
9. 使用PDO進行事務管理
事務是進行程序開發時,保證數據ACID(可分性、一致性、獨立性、持久性)的工具。要不全部成功,要不全部不成功,這樣才能保證關聯數據的保存能夠達到預期的目的。下面使用PDO的Transaction來進行實驗,進行多比數據插入,開啟事務,第一句sql是可以正常插入,第二句sql插入出錯,檢查是否rollback。
Php代碼
- file:pdo-prepare-transaction.php
- url:http://localhost:88/pdo/pdo-prepare-transaction.php
- <?php
- $dsn = 'mysql:host=localhost;dbname=pdotest';
-
- $username = 'root';
- $passwd = 'password';
-
- try {
- $dbh = new PDO($dsn, $username, $passwd);
- $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
- echo 'connect to database successfully!'."<br/>";
- // 開啟事務
- $dbh->beginTransaction();
- $sql = "INSERT INTO blog(title) VALUES(:title)";
- $stmt = $dbh->prepare($sql);
- $stmt->execute(array(':title'=>'insert title1'));
- $stmt->execute(array(':title'=>NULL));
- // 提交事務
- $dbh->commit();
- } catch (Exception $e) {
- echo 'Errors occur when data operation!\n';
- echo $e->getMessage();
- // 回滾事務
- $dbh->rollBack();
- }
- ?>
10. 使用PDO進行數據庫備份
使用system函數,將我們構造的mysqldump命令傳入即可。下面為了演示,只做了簡單的調用。
Php代碼
- file:pdo-backup.php
- url:http://localhost:88/pdo/pdo-backup.php
- <?php
- $username="root";
- $passwd="password";
- $dbname="pdotest";
- $file='d:/'.$dbname.'.sql';
- // 構造備份命令
- $cmd = "mysqldump -u".$username." -p".$passwd." ".$dbname." >".$file;
- // 執行備份命令
- system($cmd,$error);
- if($error){
- trigger_error("backup failed".$error);
- }
- ?>
采用工廠模式:
Php代碼
- file:AbstractMySQLDump.php
- <?php
- require_once 'MySQLDump_Win.php';
-
- abstract class AbstractMySQLDump {
- protected $cmd;
-
- abstract function __construct($username, $passwd, $dbname, $file);
-
- // 依據操作系統類型,使用工廠方法構造備份類
- public static function factory($username, $passwd, $dbname, $file){
- if(strtoupper(substr(PHP_OS, 0, 3))==='WIN'){
- return new MySQLDump_Win($username, $passwd, $dbname, $file);
- }else{
- // implement MySQLDump_NIX($username, $passwd, $dbname, $file);
- }
- }
-
- // 備份邏輯
- public function backup(){
- system($this->cmd, $error);
- // 判斷是否出錯及出錯邏輯
- if($error){
- trigger_error("backup failure! command:".$this->cmd." Error:".$error);
- }
- }
- }
- ?>
Php代碼
- file:MySQLDump_Win.php
- <?php
- class MySQLDump_Win extends AbstractMySQLDump {
-
- // 覆蓋父類的構造方法
- public function __construct($username, $passwd, $dbname, $file){
- $this->cmd = "mysqldump -u".$username." -p".$passwd." ".$dbname." > ".$file;
- }
- }
- ?>
Php代碼
- file:MySQLDumpTest.php
- url:http://localhost:88/pdo/MySQLDumpTest.php
- <?php
- require_once 'AbstractMySQLDump.php';
-
- $username = "root";
- $passwd = "password";
- $dbname = "pdotest";
- $file = "d:/".$dbname.".sql";
-
- // 使用工廠方法生成備份類
- $dump = AbstractMySQLDump::factory($username, $passwd, $dbname, $file);
- // 執行備份類的backup方法
- $dump->backup();
- ?>