PHP PDO 物件
PDO 連線方式
PDO 是使用物件導向的方式進行使用,好處是 PDO 除了 MySQL 也支援其他資料庫類型,如果今天專案的資料庫變成其他品牌了,PDO 只要調整宣告的代碼即可異動範圍較小。
-
連線方式
<?php $pdo = new PDO("mysql:host=127.0.0.1;dbname=class; charset=utf8", "root", "1qaz@wsx"); ?>- 使用
PDO必須先新宣告成一個變數(名稱自訂),需完整提供SQL資訊,包含("SQL 類型類型:host=位置;dbname=資料庫名稱;charset=編碼","帳號","密碼") - 之後每次要透過
PDO進行SQL操作,都能用$pdo來進行執行PDO函數。
- 使用
-
執行指令
query()
PDO因為是物件導向,要利用->做一個導向執行。下列為透過PHP去執行新增資料表的動作。$sql = " CREATE TABLE `students` ( `cID` tinyint(2) UNSIGNED ZEROFILL NOT NULL AUTO_INCREMENT, `cName` varchar(20) COLLATE utf8_unicode_ci NOT NULL, `cSex` enum('F','M') COLLATE utf8_unicode_ci NOT NULL DEFAULT 'F', `cBirthday` date NOT NULL, `cEmail` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL, `cPhone` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL, `cAddr` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, `cHeight` tinyint(3) UNSIGNED DEFAULT NULL, `cWeight` tinyint(3) UNSIGNED DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; "; $result = $pdo->query($sql);- 先將
SQL語法當作字串放到變數 - 再來執行
PDO物件並導向到裡面的query()函數。讓PDO進行SQL連接並且執行query()。 - 每次
PDO連線結束後會return資料給我們,我們可以用個變數(名稱自訂)存起來。
- 先將
-
檢查錯誤訊息
errorInfo()若
PDO異常發生問題,PDO會自動儲存錯誤訊息。需要透過沒有如期的得到你要的結果,需要errorInfo()函數。下列為檢查錯誤的示範$result = $pdo->query($sql); if(!$result) print_r($pdo->errorInfo());如果回傳是空的(無結果),陣列印出
$pdo->errorInfo()這個結果
singleton
singleton 中文名稱為 單例模式,是一種建構類別的設計模式(一套編寫類別的規範、技巧,將它作成公版來給大家使用;設計模式有許多種,例如:工廠模式、簡單工廠模式、抽象工廠模式、複雜模式、單例模式、高抽象模式..等)。其目的是為了在全域中獲取這個類別的物件時 ==總是能獲取到唯一的物件==,而不是每次創建實體時都創建出新的物件的一種類別結構。特別的在 DB 操作中,DB 連接這種物件就必須是通過 單例模式 來實現的。
-
說明
類別的主要目的在建構物件,所以每次創建類別都會獲得一個實體,這是正常操作現象,但是為了達到在全局開發中,對一個類別,不管創建幾次都只會得到一個物件,所以有了這個類別的設計模式:單例模式。- 在
JS中寫一個類別01單例模式.html - 在
PHP中寫一個資料庫連線類別01單例模式.php
- 在
-
練習: 設計一單例模式,類別名稱:
richestMan,裡面有一單例方法:findRichestMan(),用來在全局中獲取唯一的單例對象 (richestMan)。解答:
單例模式-練習.php
輸出:==================創建單例類別 ================= richestMan Object ( [pname] => 最富有的人 ) richestMan Object ( [pname] => 最富有的人 ) -
結論
為何需要單例模式:- 例一:購物車
網站中在任一頁面開啟時,購物車應該都只有一個,依過去類別的概念來作,打開一個頁面實例一個對象一個新的購物車對象,這時就須用單例的結構。 - 例二:地圖
全局中只希望一張地圖,否則一經修改又須一張地圖,將耗時耗力。
- 例一:購物車
PDO 與 DB
-
為何需使用
PDOmysqli_xxx()一系列的方法,都是針對mysql資料庫的操作。 -
問題
公司成立剛開始人數約莫只有 5人,客戶幾百人,使用 mysql 就足夠應付,但公司發展到一規模階段後,比如員工人數有 5000人,用戶從幾千人變成千萬人時,這時
mysql就不夠足以處理業務上的需求,資料庫需變大,這時如把mysql DB更換成容量更大,體積更大,處理能力更強的資料庫,比如升級成oracle, 但問題是原先的程式都是透過mysql處理,所以一旦要變更資料庫,資料庫操作的指令也要改變,如果有多個php mysql程式頁面要轉換(例如: 轉成oracl),將造成一大維護成本問題產生,所以php提供了一訪問資料庫通用的方法。
通過
PHP操作PDO,PDO會將這些操作自動轉換成想要連接的資料庫操作的方法 -
描述
PDO即PHP資料物件 (PHP Data Object)。PDO可被視為是一個工具,而這個工具為PHP訪問資料庫定義了一個羽量級的一致介面。- 實現
PDO介面的每個資料庫驅動可以公開具體資料庫的特性作為標準擴展功能。
-
語法
==$pdo=new PDO("DB名稱:host=主機名稱;dbname=DB名","DB帳號","DB密碼");== -
注意
- 利用
PDO擴展自身並不能實現任何資料庫功能,必須使用一個具體資料庫的PDO驅動來訪問資料庫服務。 - ==
PDO提供了一個【資料訪問】抽象層,這意味著不管使用哪種資料庫,都可以用相同的函數(方法)來查詢和獲取資料。== PDO不提供資料庫抽象層,它不會重寫SQL,也不會模擬缺失的特性。
如果需要的話,應該使用一個成熟的抽象層。- 從
PHP 5.1開始附帶了PDO,在PHP 5.0中是作為一個PECL擴展使用。
PDO需要PHP 5核心的新特性,因此不能在較早版本的PHP上運行。
- 利用
-
總結
PDO就像是一把槍,而使用哪種資料庫就好比是選擇不同的子彈。
不管子彈有怎樣的特性,擊發的方法總沒有偏差,都是開槍而已。補充:在連接 DB 的時候,並不是每一次的連接都能保證一定完成。因此我們必須設置一個"保險"來説明我們監測連接的情況,這個東西就是
==try…catch==機制。
-
範例
connDB.phpheader("content-type:text/html;charset=utf-8"); try{ $pdo = new PDO("mysql:host=localhost;dbname=class","root","1qaz@wsx"); $pdo->query("set names utf8"); }catch(PDOException $e){ echo "錯誤"; echo $e->getMessage(); }在整個
try…catch結構中,try部分是可能會出現異常的代碼。
而當代碼執行的過程當中一旦try部分的代碼真的發生了異常,那麼會立即將這個異常拋出,並執行catch部分的代碼。
catch部分的形參$e就是用來接收拋出的異常的。可以這樣認為:上述結構是獲取
PDO時的一個固定結構!02pdo對象與mysql數據庫連接.php
singleton 獲取 PDO
-
目的: 透過單例模式來確保唯一的
$PDO對象去操作資料庫。 -
說明:
操作資料庫時,後台的php頁面 與mysql DB是兩個獨立的東西,資料庫如同一表格,而php如同一文件,若要操作資料庫需要借助像是document類似的對象,這裡就是之前建立的$PDO對象。
所以要操作資料庫就得透過$PDO對象,但如果操作:PHP要插入資料到資料庫時時,先找到一個$PDO對象。PHP要刪除資料到資料庫時時,再先找到一個$PDO對象。-
假設此刻資料庫為空,若有兩個對象
$PDO來操作:
case1: 一個$PDO先+1, 另一個$PDO再-1, 不會有問題發生。
case2: 但順序若相反,先做-1, 但資料庫此刻並無數據(一開始為空),所以操作會失敗。意味著若使用兩個
$PDO對象來操作,結果可能會不相同;因為沒辦法確認哪一個$PDO對象會先執行(程序是多執行緒性的)。
所以解決的唯一辦法,就是指定同一個$PDO對象來做操作。
-
範例
03單例模式獲取pdo對像.php每次使用
DB要連上DB都要寫一次,太麻煩,所以將其寫成一獨立檔案,改成:singletonPDO.php然後再來引用,寫成:
03_1單例模式獲取pdo對像.php
PDO 實現 CRUD 操作
在上說明中說過,pdo 是一種【資料訪問】層的抽象。
因此本質上來講在面對同一種 DB 進行操作的時候,pdo 的操作和php 本身直接操作沒有區別。
-
INSERT、UPDATE、DELETE
練習: 跟著做以下指令,每寫完一行執行看看並觀看MySQL變化,接著註解再執行下行避免干擾//新增 //$sql = "INSERT INTO `students` (`cName`, `cSex`, `cBirthday`, `cEmail`, `cPhone`, `cAddr`, `cHeight`, `cWeight`) VALUES ('王子建', 'F', '1987-04-04', 'wan@gmail.com', '0988265123', '台北市三重市', 176, 65)"; //$result = $pdo->query($sql); //修改 //$sql = "UPDATE `students` SET `cWeight`=70 WHERE `cName`='王子建'"; //$result = $pdo->query($sql); //刪除 $sql = "DELETE FROM `students` WHERE `cId`=1"; $result = $pdo->query(); if(!$result) print_r($dblink->errorInfo($sql)); // 找錯誤問題的方法 -
SELECT
接下來,請先塞個大概四筆學生資料。 示範如何查詢,並將資料列印到網頁上。(你可以用PHP或是phpmyadmin完成。$sql = "INSERT INTO `students`(`cName`,`cSex`,`cBirthday`,`cEmail`,`cPhone`,`cAddr`,`cHeight`, `cWeight`) VALUES ('簡奉君','F','1987-04-04','elven@superstar.com','0922988876','台北市濟洲北路12號',160,49), ('黃靖輪','M','1987-07-01','jinglun@superstar.com','0918181111','台北市敦化南路93號5樓',175,72), ('潘四敬','M','1987-08-11','sugie@superstar.com','0914530768','台北市中央路201號7樓',162,65), ('賴勝恩','M','1984-06-20','shane@superstar.com','0946820035','台北市建國路177號6樓',178,72)"; $result = $pdo->query();接下來有兩種做法分別是
fetch跟fetchAll,差別在於一個抓(回傳字串)跟全部抓(回傳二維陣列):-
fetch: 通常你並不知道資料結果會有幾筆,所以你需要用while的方式去做。//select by fetch $result = $pdo->query("SELECT * FROM `students` WHERE 1"); //不為空 if (!$result) print_r($pdo->errorInfo()); // 找錯誤問題的方法 else{ while ($row = $result->fetch()) { print_r($row); echo "<br>"; } } -
fetchAll: 會一次全部回傳(用陣列包住回傳),所以這裡會到二維陣列去解讀。//select $result = $pdo->query("SELECT * FROM `students` WHERE 1"); if (!$result) print_r($pdo->errorInfo()); // 找錯誤問題的方法 // by fetchAll $row = $result->fetchAll(); print_r($row);fetchAll取得的會是二維陣列,跟fetch取得的不同。fetchAll透過一次全吐出來,優點是快直接處理資料,缺點是你暫存會隨資料多而吃重。- 要注意不管是
fetch或fetchAll,被讀取出來後就會被清掉。 fetchAll()可以直接物件去直接串,一行寫完
$result = $pdo->query("SELECT * FROM ch5_animal WHERE 1")->fetchAll(); print_r($result);
-
-
範例
04pdo實現mysql增刪改操作.php本例子,示範如何使用
POSTMAN來測試04pdo實現mysql增刪改操作.php。新增、刪除、修改執行上沒問題,但查詢的結果是一個不可見的值,而且這不可見的值,不知如何處理,這裡需借用
PDO預處理prepare和交易處理transaction
SQL 注入攻擊
SQL 注入 (SQL injection),也稱 SQL 隱碼或 SQL 注碼,為駭客利用超全域變數的表單提交時,透過變數欄位進行 SQL 語言變化導致,使得在伺服器端進行不預期的 SQL 資料指令。
-
攻擊範例: 帳號登入
-
登入資料表如下:
nano login.phpCREATE DATABASE IF NOT EXISTS `login` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE `login`; CREATE TABLE `users` ( `id` int(11) NOT NULL, `username` varchar(10) NOT NULL DEFAULT '', `password` varchar(6) NOT NULL DEFAULT '', `email` varchar(50) NOT NULL DEFAULT '' ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; INSERT INTO `users` (`id`, `username`, `password`, `email`) VALUES (1, 'admin', '1234', 'admin@gmail.com'), (2, 'test', 'test', 'test@gmail.com'); ALTER TABLE `users` ADD PRIMARY KEY (`id`); -
伺服器之帳密檢查方式,如下:
<?php : function select($table, $cond){ global $pdo; return $pdo->query("SELECT * FROM {$table} WHERE {$cond}")->fetchAll(); } $result=select("login","username= { $_POST['username'] and password= {$_POST['password']} "); if($result->rowCount()){ //有找到此帳號密碼 $_SESSION['admin']=$_POST['username']; header("location: admin.php"); } else{ echo "<script>alert('帳號或密碼錯誤');</script>"; } : ?>完整範例:
login.php -
此時輸入提交驗證
帳號:admin, 密碼:1111' OR 1=1;/*結果變成了:
select * from users where username='admin' and password='1111' OR 1=1;/' -
進而獲得登入之許可
另外或是當駭客或工程師在已知你的資料結構時,也能夾帶
SQL帳號: admin 密碼: 1111' ; DROP TABLE users;/*結果變成了:
select * from users where username='admin' and password='1111';DROP TABLE users;/*'
-
-
防範方式:
-
替換符號
最大的問題是透過'或"開頭所影響,透過preg_replace()將變數內(含陣列)任何出現的單雙引號都強迫拿掉。//下面會把所有字元 ' 與 " 過濾掉 $_POST = preg_replace("/['"]+/", '', $_POST); $result=select("users","username='".$_POST['username']."' and password='".$_POST['password']."'"); //過濾為: select * from users where username='admin' and password='1111;DROP TABLE users;/*'相對來說每次跑
SQL之前都需要替換檢查過。完整範例:
login_sol1.php -
PDO的prepare
透過prepare的方式,對SQL進行準備告知我有這行指令要執行。
execute()等於執行,與query()不同的是execute都會伴隨prepare(預告)來使用。- 將原本的
sql指令分解為那些不先寫好,使用?來代替。 -
?這些真正的內容,用一維陣列即可,順序根據?出現順序為準。
由於是透過自訂函式代替SELECT,所以步驟2也是要提交FUNCTION//for SQL Injection $data=array($_POST['username'], $_POST['password']); $result = selectV2("users", "username=? and password=?", $data); if($result){//有找到此帳號密碼 $_SESSION['admin']=$_POST['username']; plo("admin.php"); } else echo "<script>alert('帳號或密碼錯誤');".jlo("login.php")."</script>"; - 關於自訂函式,
prepare預告後還要用個變數將物件存著。 ?將你的?轉換陣列丟給execute()做替換執行。-
最後做
fetchAll作業(無法簡化為1行寫完)並回傳結果//selet SQL v2 for SQL injection function selectV2($table, $where, $data){ global $pdo; $sql = "select * from " . $table . " where " . $where; $stmt=$pdo->prepare($sql); $stmt->execute($data); return $stmt->fetchAll(); } - 透過
prepare由於sql代碼會並固定住,駭客就無法將原語意誘騙為不同的內容。
完整範例:
login_sol2.php - 將原本的
- 結論
嚴格上PDO的方式會比較好一些,SQL不會受到任何暴力的SQL注入類型所干擾。而resplace只是將符號替換,有可能部分資料輸入就剛好需此符號。
-
XSS(Cross-site scripting) 跨網站指令碼-攻擊
是一種網站應用程式的安全漏洞攻擊,是代碼注入的一種。它允許惡意使用者將程式碼注入到網頁上,其他使用者在觀看網頁時就會受到影響。這類攻擊通常包含了 HTML 以及使用者端手稿語言。
-
範例: 跨網站指令碼-攻擊
-
使用表單方式產生資料
xss.php請在標題輸入框分別輸入底下內容試試(XSS攻擊),若是無法順利寫入,那就是有問題:<?php // 告訴瀏覽器這頁面是 UTF-8 編碼 header("content-type:text/html; charset=utf-8"); date_default_timezone_set("Asia/Taipei"); if (isset($_POST["action"]) && $_POST["action"] == "add") { // require_once "connDB.php"; $user = "root"; $password = "1qaz@wsx"; $host = "localhost:3306"; $db = "blog"; $port = "3306"; $conn = mysqli_connect($host, $user, $password); if ($conn) { //選擇資料庫 mysqli_select_db($conn, $db); //設置資料庫編碼方式 mysqli_query($conn, 'set names utf8'); mysqli_set_charset($conn, "utf8"); } else { echo "資料庫連線失敗"; } $sql_query = "INSERT INTO `articles`(`title`,`content`) VALUES("; $sql_query .= "'" . $_POST["title"] . "',"; $sql_query .= "'" . $_POST["content"] . "')"; // echo $sql_query; // exit; mysqli_query($conn, $sql_query); mysqli_close($conn); //關閉資料庫連接 // header("location: xss.html"); } ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <center> <h2>XSS 示範</h2> <form action="xss_create.php" method="post"> <p> <label for="title"> 文章標題 </label> <input type="text" name="title" placeholder="請輸入文章標題" value="" /> </p> <p> <label for="content"> 文章內容 </label> <textarea name="content" placeholder="請輸入文章內容" rows="10"></textarea> </p> <p> <input type="hidden" name="action" value="add"> <button type="submit">送出</button> </p> </form> </center> </body> </html><script>alert('這就是XSS攻擊')</script><img src="https://source.unsplash.com/rndom/320x200">
-
改用
xss_pdo.php以PDO的prepare()方式寫資料到資料庫<?php // 告訴瀏覽器這頁面是 UTF-8 編碼 header("content-type:text/html; charset=utf-8"); date_default_timezone_set("Asia/Taipei"); $dbhost = 'localhost'; //一般是 localhost 或 127.0.0.1 $dbuser = 'root'; //一般是 root $dbpasswd = '1qaz@wsx'; $dbname = 'blog'; $dbcharacter = 'utf8mb4'; //一般是 utf8 if (isset($_POST["action"]) && $_POST["action"] == "add") { try { $db = new PDO("mysql:host={$dbhost};dbname={$dbname};charset={$dbcharacter} ", $dbuser, $dbpasswd); $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); //禁用prepared statements的模擬效果 $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); //讓資料庫顯示錯 誤原因 // echo "連線成功"; } catch (PDOException $e) { die("無法連上資料庫:" . $e->getMessage()); } //$title = $_POST["title"]; //過濾顯示變數 //$title = filter_var($news['title'], FILTER_SANITIZE_SPECIAL_CHARS); //$content = $_POST["content"]; //過濾顯示變數 //$content = filter_var($news['content'], FILTER_SANITIZE_SPECIAL_CHARS); // 寫入資料庫 $sql = "INSERT INTO `articles` (`title`, `content`) VALUES(?, ?)"; $sth = $db->prepare($sql); $values = [ $_POST['title'], $_POST['content'], ]; $sth->execute($values); } ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <center> <h2>XSS 示範</h2> <form action="xss_pdo.php" method="post"> <p> <label for="title"> 文章標題 </label> <input type="text" name="title" placeholder="請輸入文章標題" value="" /> </p> <p> <label for="content"> 文章內容 </label> <textarea name="content" placeholder="請輸入文章內容" rows="10"></textarea> </p> <p> <input type="hidden" name="action" value="add"> <button type="submit">送出</button> </p> </form> </center> </body> </html>

- 在去以
xss_read.php或xss_read_pdo.php讀資料庫內容
-
-
過濾顯示變數
- 讀出的資料,要考量是否允許使用
HTML - 若是允許使用
HTML,那可能要用專業的工具來做,如:http://htmlpurifier.org/ - 若是不允許使用
HTML,可以直接使用內建的htmlspecialchars($string)來過濾 -
但內建的
htmlspecialchars($string)預設只轉化雙引號,不對單引號做轉義,所以,這樣用htmlspecialchars($string,ENT_QUOTES)更好:$title = htmlspecialchars($news['title'], ENT_QUOTES);另一個和
htmlspecialchars很像的htmlentities函數並不適用中文,因為會連同中文字一起轉義。 -
用
PHP的filter_var過濾器亦有同樣效果(還更彈性一些):$title = filter_var($news['title'], FILTER_SANITIZE_SPECIAL_CHARS);
- 讀出的資料,要考量是否允許使用
-
PHP的filter_var過濾器- 可利用
PHP內建的filter_var()函數來過濾變數。 -
幾種常用過濾方法,完整過濾器可由此查看:
名稱 功用 FILTER_CALLBACK option可以讓開發者用自訂的function處理 FILTER_SANITIZE_STRING 去除標籤或特殊字元(html標籤會直接被消除) FILTER_SANITIZE_ENCODED 與urlencode()相同,過濾特殊字串 FILTER_SANITIZE_MAGIC_QUOTES 過濾針對SQL injection做過濾(例如單、雙引號) FILTER_SANITIZE_SPECIAL_CHARS 針對HTML做encoding,例如<會轉成< FILTER_SANITIZE_EMAIL 過濾e-mail,刪除e-mail格式不該出現的字元(除了$-_. +!*'{} ^~[]`#%/?@&=和數字),例如a(b)@gmail.com會被過濾成ab@gmail.com FILTER_SANITIZE_URL 過濾URL,刪除URL格式不該出現的字元 FILTER_SANITIZE_NUMBER_INT 刪除所有字元,只留下數字與+-符號 FILTER_SANITIZE_NUMBER_FLOAT 刪除所有字元,只留下數字和+-.,eE FILTER_VALIDATE_INT 判斷數字是否有在範圍內 FILTER_VALIDATE_BOOLEAN 判斷布林值,1、true、on、yes都會判斷成true,反之為 false,若是這些以外的值會回傳NULL FILTER_VALIDATE_FLOAT 判斷是否為浮點數 FILTER_VALIDATE_REGEXP 利用regexp做驗證 FILTER_VALIDATE_URL URL驗證 FILTER_VALIDATE_EMAIL e-mail驗證 FILTER_VALIDATE_IP IP驗證
- 可利用
-
過濾數字
- 一般過濾數字用
(int) $xxx或intval($xxx)即可,會強制把輸入的內容變成數字。 -
亦可用
filter_var($num, FILTER_SANITIZE_NUMBER_INT)來過濾,但和intval不太一樣,例如:$num = "123其他文字456"; echo filter_var($num, FILTER_SANITIZE_NUMBER_INT) . "<br>"; echo (int) $num . "<br>"; echo intval($num) . "<br>"; - 用
(int) $xxx或intval($xxx)會得到123,但用filter_var()會得到123456
- 一般過濾數字用
-
將欲呈現變數過濾
例如:
while ($row = $sth->fetch(PDO::FETCH_ASSOC)) { echo (int) $news['id'] . "<br>"; echo filter_var($news['title'], FILTER_SANITIZE_SPECIAL_CHARS) . "<br>"; echo filter_var($news['content'], FILTER_SANITIZE_SPECIAL_CHARS) . "<br>"; } - 改寫
xss_read.php與xss_read_pdo.php加入過濾xss_read_filter_var.php
5. PDO 異常處理 Exception
Exception 指的都是在執行 db 操作的時候發生異常,例如 SQL 語句異常或語法錯誤。而如果是 DB
連接發生的錯誤則不會走本異常處理,而是直接由 pdo 輸出連接失敗。
異常處理 Exception 是指在 try…catch 時發生異常時的處理手段,通常異常處理都是直接拋出提醒即可。而設置提醒的手段有三種設置方式:
- 預設模式
主要依賴於系統提供的errorCode和errorInfo屬性實現 - 警報模式:
為pdo設置setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_WARNING); -
中斷模式:
為pdo設置setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
需要說明的是異常處理並不是三種模式必須有一種顯式的去表現出來,哪怕一種都不主動寫出也不會認為是違法。只不過主動實現異常處理能夠在異常發生的時候給予我們更好的提示,因此推薦如果允許的情況下盡可能的添加異常處理模組代碼。
-
範例:
05pdo異常捕獲.php注意: 即使 SQL 語句執行成功,但沒更新資料(例:無須改變),仍會回傳 false。
6. PDO 預處理 prepare
預處理語句 prepare是 pdo 提供的一種 db 操作方式。其語言邏輯與正常的 pdo 訪問相同。但區別在於 prepare 語句允許用戶在【設置sql語句】與【執行sql語句】之間部分進行參數的注入與提取操作,而不是像正常的pdo訪問一樣直接將參數寫死。
-
正常
pdo直接訪問程序
【設置sql語句】→【執行sql語句】$sql = "SQL 語句"; $pdo->exec($sql); -
預處理訪問程序
【設置sql語句】→ 【執行 sql 語句】 → 【處理sql中參數】 → 【執行sql語句】$sql = "SQL 語句"; $pdo->prepare();prepare是另一種像是$PDO提供資料庫操作exec的另一種方法,只是部分功能,得到的是一個PDOStatement物件,這個物件是可定義與執行參數化的SQL命令 。
預先處理得到一種結果,再用這結果執行另一新方法,這新方法可處理sql中所需參數,然後最後再執行$pdo->exec($sql)。 -
區別
在於再處理參數時,預處理可以將當初用$pdo->exec();處理上,看不到,拿不到,獲取不到的內容,通過處理參數後可取得。處理分三部分
- 關鍵核心:
prepare()預處理sql語句 和execute()處理sql中參數 兩種方法, bindValue()bindColumn()
- 關鍵核心:
6.1 prepare()方法和execute()方法
-
描述:
-
prepare()方法為預處理sql語句的方法,能夠讓pdo預先處理【半成品的】sql 語句。
並生成一個PDOStatementObject類型的結果。 execute()方法是提供給PDOStatementObject類型物件去執行的【成品】sql語句的方法並生成一個PDOStatementObject類型的結果。
-
-
說明:
-
交由
pdo去prepare預處理的【半成品】sql語句,使用?問號作為預留位置,表示待傳參的參數 -
prepare預處理必須只能處理【半成品】sql語句,如果是完整則需要使用exec方法執行 -
execute()方法允許一個陣列作為參數,將參數帶入到預處理的sql語句中,並且會將結果存放到PDOStatementObject對象中。 PDOStatementObject物件在預處理的不同階段有著不同的含義!!不可混淆,必須根據上下文判斷。
-
-
語法:
require_once "singletonPDO.php"; $sql = "INSERT INTO students (`cName`,`cSex`,`cBirthday`,`cEmail`,`cPhone`,`cAddr`,`cHeight`,`cWeight`)values(?,?,?,?,?,?,?,?)"; $stmt = $pdo->prepare($sql); echo $stmt->execute(array('allen','m','1990-01-02','test@gmail.com','098876543','台北市','180', '80'));輸出: 1。 如果新增成功
上面寫法等同:
require_once "singletonPDO.php"; $pdo->prepare("INSERT INTO students (`cName`,`cSex`,`cBirthday`,`cEmail`,`cPhone`,`cAddr`,`cHeight`,`cWeight`) values(?,?,?,?,?,?,?,?)")->execute(array('allen', 'm', '1990-01-02', 'test@gmail.com', '098876543', '台北市', '180', '80'));
6.2 bindColumn()方法
-
描述:
bindColumn()方法允許將執行結果的一列資料綁定到一個指定物件上,本方法需要在execute()方法執行結束後在執行。 -
語法:
$pdoStatementObject->bindColumn(index,指定變數); -
說明:
- 第一個參數表示給
sql語句中第幾個參數傳值。第一個就寫1,以此類推。 - 第二個參數表示給
sql語句中的對應參數傳的具體的值。 bindColumn()一次綁定一個參數,如需綁定多個,則執行多次即可。
- 第一個參數表示給
-
範例
06pdo預處理語句.php -
練習: 修改原
06pdo預處理語句.php的SQL插入操作,變成查詢06pdo預處理語句_查詢.php:06pdo預處理語句_查詢.php
3. bindValue()方法
-
前言:
之前的方式,SQL查詢中,使用prepare()做SQL語句預處理,得到的半成品,再利用execute()傳遞所需參數,同時轉換得到成品,但讀不到內容,將結果中的內容透過bindColumn()來綁定資料在指定變數上。若不想使用
execute()來傳遞參數,可以改用bindValue()來給半成品的SQL語句進行傳值。 -
描述:
bindValue()方法是提供給pdo預處理之後得到的PdoStatementObject物件使用的方法,用來給【半成品】的sql語句進行傳值。 -
語法:
$pdoStatementObject->bindValue(index,value); -
說明:
- 第一個參數表示給
sql語句中第幾個參數傳值。第一個就寫1,以此類推。 - 第二個參數表示給
sql語句中的對應參數傳的具體的值。 bindValue()一次綁定一個參數,如果有多個則需要調用多次。
- 第一個參數表示給
-
例子:
$sql = "INSERT INTO students values(?,?,?,?,?,?,?,?,?)"; $pdoStatementObject = $pdo->prepare($sql); $halfPro->bindValue(1, 12); $halfPro->bindValue(2, 'allen'); $halfPro->bindValue(3, 'm'); $halfPro->bindValue(4, '2019-08-01'); $halfPro->bindValue(5, 'test@gmail.com'); $halfPro->bindValue(6, '098876543'); $halfPro->bindValue(7, '台北市'); $halfPro->bindValue(8, '170'); $halfPro->bindValue(9, '68'); echo $pdoStatementObject->execute(); -
完整案例:
07pdo補充bindValue方法.php這種方式好處,是傳遞的值可以寫成一變數,提供了一種更靈活的方式,來編輯
sql語句。
7. PDO 交易處理 transaction
- 交易:多個事件組成的結構。
- 事件:事件實際上就是預處理語句執行的
execute語句。
即多個
execute子句 -
注意:
-
整個交易操作必須放到
try...catch中,這是因為我們並不能保證執行的事件一定成功。
而對於整個交易而言,任何一個事件的失敗都會導致catch的觸發。
而catch觸發就意味著必須將之前做出的所有的操作都必須還原回滾操作:$pdo->rollBack() -
動作陳述式(CRUW)必須在交易開啟之後執行,在交易提交之前停止.
開啟交易:$pdo->beginTransaction();
關閉交易:$pdo->commit(); -
中文處理方案(避免亂碼):
讀取:$pdo->query("set names utf8");
插入:$pdo->exec('set names utf8'); -
銀行轉帳:
- 提錢 -> 轉多少 -> 輸入密碼 -> 成功 -> 轉帳成功
- 提錢 -> 轉多少 -> 輸入密碼 -> 錯誤 -> 轉帳失敗
只有成功,才允許轉錢出去。
-
-
交易目的:保證只有在交易成功才會有影響發生,否則不會有任何異動
require_once "singletonPDO.php"; $pdo = singletonPDO::getpdo(); $pdo->query('set names utf8'); $pdo->exec('set names utf8'); try{ $pdo->beginTransaction(); $sql = "update students set cHeight=?, cWeight=? where cID=?"; $pdoso = $pdo->prepare($sql); //成功 $pdoso->execute(array('170', '60', 1)); //失敗,參數不符 $pdoso->execute(array('180', '60')); echo $pdo->commit(); }catch (PDOException $e){ $pdo -> rollBack(); echo "失敗"; } -
範例:
08pdo交易處理transaction方法.php執行第二條
execute語句(第二個事件),沒給cID, 不足數據,所以會觸發Exception,本次交易發生異常。此錯誤為操作錯誤,屬於第一類異常,執行到這裏有問題,就該中斷停止繼續往下執行,異常需設中斷模式,在單例模式產生
$PDO就要加入$pdo ->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);,參考singletonPDO.php。
留言