搜尋
首頁後端開發PHP問題三分鐘帶你了解PHP中的預處理類別及綁定數據

本篇文章為大家介紹一下PHP中的預處理類別及綁定資料。有一定的參考價值,有需要的朋友可以參考一下,希望對大家有幫助。

三分鐘帶你了解PHP中的預處理類別及綁定數據

PHP中的PDO操作學習(三)預處理類別及綁定資料

要說PDO 中最強大的功能,除了為不同的資料庫提供了統一的介面之外,更重要的就是它的預處理能力,也就是PDOStatement 所提供的功能。因為它的存在,才讓我們可以安心地去使用而不用操心 SQL 語句的拼接不好所帶來的安全風險問題。當然,預處理也為我們提升了語句的執行效率,可以說是 PDO 的另一個大殺器。

PDOStatement 類別

PDOStatement 類別其實就是代表一條預處理語句,並且在該語句被執行後代表一個相關的結果集。它提供一些方法,讓我們能夠對這條預處理語句進行操作。

$dns = 'mysql:host=localhost;dbname=blog_test;port=3306;charset=utf8';
$pdo = new PDO($dns, 'root', '', [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);

$stmt = $pdo->prepare("select * from zyblog_test_user");

// PDOStatement 对象的内容
var_dump($stmt);
// object(PDOStatement)#2 (1) {
//     ["queryString"]=>
//     string(57) "select * from zyblog_test_user where username = :username"
//   }

PDOStatement 物件是透過 PDO 物件的 prepare() 方法傳回的物件。它沒有建構函數,也就是說我們不能直接實例化一個 PDOStatement 物件。它包含一個唯讀屬性,也就是我們要執行的 SQL 語句,保存在 queryString 中。

PDOStatement 錯誤處理

接下來我們先看看 PDOStatement 的兩個錯誤訊息方法。

// 没有指定异常处理状态下的错误信息函数 
$pdo_no_exception = new PDO($dns, 'root', '');
$errStmt = $pdo_no_exception->prepare("select * from errtable");
$errStmt->execute();
var_dump($errStmt->errorCode()); // string(5) "42S02"
var_dump($errStmt->errorInfo());
// array(3) {
//     [0]=>
//     string(5) "42S02"
//     [1]=>
//     int(1146)
//     [2]=>
//     string(40) "Table 'blog_test.errtable' doesn't exist"
//   }

在先前的文章中,我們學習過,如果不給 PDO 物件指定錯誤處理格式的話。它會使用傳回錯誤碼和錯誤訊息的方式來處理錯誤。在這種情況下,如果預處理的語句有問題,我們就可以透過 errorCode() 和 errorInfo() 方法來獲得錯誤的程式碼和錯誤的詳細資訊。不過,還是更建議指定 PDO 的錯誤處理方式為拋出異常,就像最上面我們定義的 PDO 物件。這樣我們就可以透過 try...catch 來處理錯誤異常了。

PDOStatement FETCH_MODE 指定

// 为语句设置默认的获取模式。
$stmt->setFetchMode(PDO::FETCH_ASSOC);
$stmt->execute();
while($row = $stmt->fetch(PDO::FETCH_ASSOC)){
    var_dump($row);
}
// array(4) {
//     ["id"]=>
//     string(1) "1"
//     ["username"]=>
//     string(3) "aaa"
//     ["password"]=>
//     string(3) "aaa"
//     ["salt"]=>
//     string(3) "aaa"
//   }
// ……

為查詢結構指定 FETCH_MODE 是透過 setFetchMode() 方法來實現的。我們之前也講過,透過 PDO 物件的屬性可以指定預設的查詢結果集模式,不過在 PDOStatement 中,也可以透過這個方法來為目前的這次預處理語句的查詢指定 FETCH_MODE 。

PDOStatement 取得列數及欄位資訊

// 返回结果集列数、返回结果集中一列的元数据
$stmt = $pdo->prepare("select * from zyblog_test_user");
$stmt->execute();
var_dump($stmt->columnCount()); // int(4)
var_dump($stmt->getColumnMeta(0)); 
// array(7) {
//     ["native_type"]=>
//     string(4) "LONG"
//     ["pdo_type"]=>
//     int(2)
//     ["flags"]=>
//     array(2) {
//       [0]=>
//       string(8) "not_null"
//       [1]=>
//       string(11) "primary_key"
//     }
//     ["table"]=>
//     string(16) "zyblog_test_user"
//     ["name"]=>
//     string(2) "id"
//     ["len"]=>
//     int(11)
//     ["precision"]=>
//     int(0)
//   }

columnCount() 可以傳回我們目前查詢結果集中的列的數量。關於行的數量獲得的方法我們將在下篇文章中再介紹。

getColumnMeta() 方法則是取得結果集中一列的元數據,它的參數是列的序號,從1 開始的序號,在這裡我們獲取的是第一列,也就是id 列的信息。從列印的結果,可以看到這個欄位的名稱、精確度(precisiion)、長度、型別、所屬的表名、屬性(主鍵、非空)這些資訊。是不是感覺非常有用。不過這個方法是實驗性質的,有可能在未來的 PHP 版本中進行修改,不是正式的固定方法。而且並不是所有資料庫連線驅動都支援這個方法。

PDOStatement 列印出一條預處理語句所包含的資訊

$stmt = $pdo->prepare("select * from zyblog_test_user where username=? and salt = ?");
$username = 'aaa';
$stmt->bindParam(1, $username, PDO::PARAM_STR);
$stmt->bindValue(2, 'aaa', PDO::PARAM_STR);
$stmt->execute();
var_dump($stmt->debugDumpParams()); 
// SQL: [60] select * from zyblog_test_user where username=? and salt = ?
// Sent SQL: [68] select * from zyblog_test_user where username='aaa' and salt = 'aaa'
// Params:  2
// Key: Position #0:
// paramno=0
// name=[0] ""
// is_param=1
// param_type=2
// Key: Position #1:
// paramno=1
// name=[0] ""
// is_param=1
// param_type=2

debugDumpParams() 也是很好玩的一個方法,它直接列印出目前執行的SQL 語句的訊息,注意,它和var_dump () 、 php_info() 這類函數一樣,是直接列印的,不是將結果傳回一個變數。還記得我們怎麼將這種函數的內容保存到變數中嗎? [還沒搞不懂PHP中的輸出緩衝控制? ]()。

從列印的結果來看,它能傳回真實執行的 SQL 語句以及相關的一些參數資訊。對於日常的開發調試來說絕對是神器啊。許多小夥伴都會受困於 PDO 預處理的語句如果取得到真實的執行語句。而這個方法只需要我們簡單的封裝一下,就可以從裡面提取出真實的執行語句了哦!

兩個MySQL 擴充功能不支援的屬性

// MySQL 驱动不支持 setAttribute
$stmt->setAttribute(PDO::ATTR_CURSOR, PDO::CURSOR_FWDONLY);
// Fatal error: Uncaught PDOException: SQLSTATE[IM001]: Driver does not support this function: This driver doesn't support setting attributes

// MySQL 驱动不支持 getAttribute
var_dump($stmt->getAttribute(PDO::ATTR_AUTOCOMMIT));
// Fatal error: Uncaught PDOException: SQLSTATE[IM001]: Driver does not support this function: This driver doesn't support getting attributes

這兩個方法對於MySQL 擴充驅動程式來說是不支援的,但是有其它的資料庫是支援的,筆者沒有測試過它資料庫,大家可以自行測試一下。

綁定欄位

接下來就是重點內容了,在預處理語句中,我們可以使用佔位符來綁定變量,從而達到安全處理查詢語句的作用。透過佔位符,我們就不用去自己拼裝處理帶有單引號的欄位內容了,從而避免了 SQL 注入的發生。請注意,這裡並不是可以處理所有的 SQL 注入問題,例如字元集問題的 寬位元組 注入 。

佔位符包含兩種形式,一種是使用 :xxx 這種形式的名稱佔位符,: 後面的內容可以是自己定義的一個名稱。另一種形式就是使用問號佔位符,當使用問號佔位符的時候,我們綁定的是字段的下標,下標是從 1 開始的,這點是需要注意的地方。我們直接透過範例來看看。

bindParam

$stmt = $pdo->prepare("insert into zyblog_test_user(username, password, salt) values(:username, :pass, :salt)");

$username = 'ccc';
$passwrod = '333';
$salt = 'c3';

$stmt->bindParam(':username', $username);
$stmt->bindParam(':pass', $password);
$stmt->bindParam(':salt', $salt);

$stmt->execute();

// bindParam 问号占位符
$stmt = $pdo->prepare("insert into zyblog_test_user(username, password, salt) values(?, ?, ?)");

$username = 'ccc';
$passwrod = '333';
$salt = 'c3';

$stmt->bindParam(1, $username);
$stmt->bindParam(2, $password);
$stmt->bindParam(3, $salt);

$stmt->execute();

在这段代码中,我们分别使用了两种形式的占位符来实现了数据的插入。当然,预处理语句和占位符是任何操作语句都可以使用的。它的作用就是用绑定的值来替换语句中的占位符所在位置的内容。不过它只是使用在 values 、 set 、 where 、 order by 、 group by 、 having 这些条件及对字段的操作中,有兴趣的同学可以试试用占位符来表示一个表名会是什么结果。

bindParam() 方法是绑定一个参数到指定的变量名。在这个方法中,绑定的变量是作为引用被绑定,并且只能是一个变量,不能直接给一个常量。这点我们在后面讲和 bindValue() 的区别时再详细讲解。一些驱动支持调用存储过程的输入/输出操作,也可以使用这个方法来绑定,我们将在后面的文章中讲解。

bindValue

$stmt = $pdo->prepare("insert into zyblog_test_user(username, password, salt) values(:username, :pass, :salt)");

$username = 'ddd';
$passwrod = '444';
$salt = 'd4';

$stmt->bindValue(':username', $username);
$stmt->bindValue(':pass', $password);
$stmt->bindValue(':salt', $salt);

$stmt->execute();

咦?它的用法和 bindParam() 一样呀?没错,它们的作用也是一样的,绑定一个参数到值。注意,这里是绑定到值,而 bindParam() 是绑定到变量。在正常情况下,你可以将它们看作是一样的操作,但是,其实它们有很大的不同,我们直接就来看它们的区别。

bindParam 和 bindValue 的区别

首先,bindValue() 是可以绑定常量的。

$stmt = $pdo->prepare("select * from zyblog_test_user where username = :username");

//$stmt->bindParam(':username', 'ccc');
// Fatal error: Uncaught Error: Cannot pass parameter 2 by reference

$stmt->bindValue(':username', 'ccc');

$stmt->execute();
while($row = $stmt->fetch(PDO::FETCH_ASSOC)){
    var_dump($row);
}
// array(4) {
//     ["id"]=>
//     string(2) "19"
//     ["username"]=>
//     string(3) "ccc"
//     ["password"]=>
//     string(3) "bbb"
//     ["salt"]=>
//     string(2) "c3"
//   }
// ……

如果我们使用 bindParam() 来指定第二个参数值为常量的话,它会直接报错。bindParam() 的第二个参数是作为引用类型的变量,不能指定为一个常量。

其次,因为bindParam() 是以引用方式绑定,它的变量内容是可变的,所以在任何位置定义绑定的变量都不影响它的预处理,而 bindValue() 是定义后就立即将参数进行绑定的,所以下面的代码使用 bindValue() 是无法获得结果的($username 在 bindValue() 之后才赋值)。

$stmt = $pdo->prepare("select * from zyblog_test_user where username = :username");

$stmt->bindValue(':username', $username);
$username = 'ccc';

$stmt->execute();
while($row = $stmt->fetch(PDO::FETCH_ASSOC)){
    var_dump($row);
}
//

必须要保证变量在 bindValue() 之前被赋值。

$username = 'ccc';
$stmt->bindValue(':username', $username);

当然,bindParam() 就不存在这样的问题了,我们可以在 bindParam() 之后再给它指定的变量赋值。

$stmt = $pdo->prepare("select * from zyblog_test_user where username = :username");

$stmt->bindParam(':username', $username);
$username = 'ddd';

$stmt->execute();
while($row = $stmt->fetch(PDO::FETCH_ASSOC)){
    var_dump($row);
}
// array(4) {
//     ["id"]=>
//     string(1) "8"
//     ["username"]=>
//     string(3) "ddd"
//     ["password"]=>
//     string(3) "bbb"
//     ["salt"]=>
//     string(2) "d4"
//   }
//   ……

这下对 bindParam() 和 bindValue() 的区别就非常清楚了吧?总结一下:

  • bindParam() 必须绑定变量,变量是引用形式的参数,只要在 execute() 之前完成绑定都可以

  • bindValue() 可以绑定常量,如果是绑定的变量,那么变量赋值要在 bindValue() 语句执行之前完成,否则绑定的就是一个空的数据

bindColumn

这个方法是用于绑定查询结果集的内容的。我们可以将查询结果集中指定的列绑定到一个特定的变量中,这样就可以在 fetch() 或 fetchAll() 遍历结果集时通过变量来得到列的值。

这个方法在实际应用中用到的比较少,所以很多小伙伴可能是只闻其名不见其身。我们还是通过代码来看看。

$stmt = $pdo->prepare("select * from zyblog_test_user");

$stmt->execute();

$stmt->bindColumn(1, $id);
$stmt->bindColumn(2, $username, PDO::PARAM_STR);
$stmt->bindColumn("password", $password);
$stmt->bindColumn("salt", $salt, PDO::PARAM_INT); // 指定类型强转成了 INT 类型

// 不存在的字段
// $stmt->bindColumn(5, $t); 
//Fatal error: Uncaught PDOException: SQLSTATE[HY000]: General error: Invalid column index

while($row = $stmt->fetch(PDO::FETCH_BOUND)){
    $data = [
        'id'=>$id,
        'username'=>$username,
        'password'=>$password,
        'salt'=>$salt,
    ];
    var_dump($data);
}
// array(4) {
//     ["id"]=>
//     string(1) "1"
//     ["username"]=>
//     string(3) "aaa"
//     ["password"]=>
//     string(3) "aaa"
//     ["salt"]=>
//     int(0)
//   }
//   array(4) {
//     ["id"]=>
//     string(1) "2"
//     ["username"]=>
//     string(3) "bbb"
//     ["password"]=>
//     string(3) "bbb"
//     ["salt"]=>
//     int(123)
//   }
// ……

// 外部获取变量就是最后一条数据的信息
$data = [
    'id'=>$id,
    'username'=>$username,
    'password'=>$password,
    'salt'=>$salt,
];
print_r($data);
// Array
// (
//     [id] => 2
//     [username] => bbb
//     [password] => bbb
//     [salt] => bbb
// )

在代码中,我们使用的是 * 来获得的查询结果集。然后就可以通过问号占位符或者列名来将列绑定到变量中。接着在 fetch() 的遍历过程中,就可以通过变量直接获取每一条数据的相关列的值。需要注意的是,为变量赋值的作用域仅限于在执行 fetch() 方法之后。从代码的结构中我们就可以看出,bindColumn() 方法对于变量也是作为引用的方式绑定到 PDOStatement 对象内部的,所以 fetch() 在处理的时候就直接为这些变量赋上了值。

bindCloumn() 方法后面的参数是可选的字段类型,这个参数在 bindParam() 和 bindValue() 中都是存在的,也都是可选的。如果获取的类型和我们绑定时定义的类型不同,那么 PDOStatement 就会强转为绑定时指定的类型。例如上面例子中我们将本身为 varchar 类型的 salt 字段强转为 int 类型之后就输出的都是 int 类型了。除了这个参数之外,还有一些其它可选的参数,大家可以自行查阅相关的文档。

fetch() 循环结束后,变量中依然保留着最后一行结果集的内容。所以在使用的时候要注意如果外部有其它地方使用这些变量的话,是否需要重新赋值或者清理掉它们。

execute 直接传递参数

最后,如果我们不想这么麻烦地去绑定字段或者变量,也可以直接在 execute() 方法中直接传递参数,它是类似于 bindValue() 的形式进行字段绑定的。

$stmt = $pdo->prepare("insert into zyblog_test_user(username, password, salt) values(:username, :pass, :salt)");
$stmt->execute([
    ':username'=>'jjj',
    ':pass'=>'888',
    ':salt'=>'j8'
]);
// 使用问号占位符的话是按从0开始的下标
$stmt = $pdo->prepare("insert into zyblog_test_user(username, password, salt) values(?, ?, ?)");
$stmt->execute(['jjjj','8888','j8']);

execute() 的这个绑定参数是一个数组,在使用问号占位符的时候需要注意,在这里,按数组的下标来说,它们是从 0 开始算位置的。

另外需要注意的是,PDOStatement 对象的操作都是使用 execute() 方法来进行语句执行的。这个方法只会返回一个布尔值,也就是成功或者失败。不像 PDO 对象的 exec() 方法返回的是受影响的条数。如果是查询类的语句,我们需要在 execute() 之后调用 fetch() 之类的方法遍历结果集。而增、删、改之类的操作,则需要通过 rowCount() 来获得返回的执行结果条数。相关的内容我们也将在之后的文章一起详细讲解。

总结

划重点的时刻又到咯!今天我们学习的主要是 PDOStatement 对象的一些不太常用但很好玩的方法,另外就是占位符绑定的问题。其中最主要的就是 bindParam() 和 bindValue() 的区别。下篇文章我们主要就是要学习 PDOStatement 中的查询相关的操作,这个可不能丢呀,大家一定不要迟到!

测试代码:

https://github.com/zhangyue0503/dev-blog/blob/master/php/202009/source/PHP%E4%B8%AD%E7%9A%84PDO%E6%93%8D%E4%BD%9C%E5%AD%A6%E4%B9%A0%EF%BC%88%E4%B8%89%EF%BC%89%E9%A2%84%E5%A4%84%E7%90%86%E7%B1%BB%E5%8F%8A%E7%BB%91%E5%AE%9A%E6%95%B0%E6%8D%AE.php

推荐学习:php视频教程

以上是三分鐘帶你了解PHP中的預處理類別及綁定數據的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文轉載於:segmentfault。如有侵權,請聯絡admin@php.cn刪除
酸與基本數據庫:差異和何時使用。酸與基本數據庫:差異和何時使用。Mar 26, 2025 pm 04:19 PM

本文比較了酸和基本數據庫模型,詳細介紹了它們的特徵和適當的用例。酸優先確定數據完整性和一致性,適合財務和電子商務應用程序,而基礎則側重於可用性和

PHP安全文件上傳:防止與文件相關的漏洞。PHP安全文件上傳:防止與文件相關的漏洞。Mar 26, 2025 pm 04:18 PM

本文討論了確保PHP文件上傳的確保,以防止諸如代碼注入之類的漏洞。它專注於文件類型驗證,安全存儲和錯誤處理以增強應用程序安全性。

PHP輸入驗證:最佳實踐。PHP輸入驗證:最佳實踐。Mar 26, 2025 pm 04:17 PM

文章討論了PHP輸入驗證以增強安全性的最佳實踐,重點是使用內置功能,白名單方法和服務器端驗證等技術。

PHP API率限制:實施策略。PHP API率限制:實施策略。Mar 26, 2025 pm 04:16 PM

本文討論了在PHP中實施API速率限制的策略,包括諸如令牌桶和漏水桶等算法,以及使用Symfony/Rate-limimiter之類的庫。它還涵蓋監視,動態調整速率限制和手

php密碼哈希:password_hash和password_verify。php密碼哈希:password_hash和password_verify。Mar 26, 2025 pm 04:15 PM

本文討論了使用password_hash和pyspasswify在PHP中使用密碼的好處。主要論點是,這些功能通過自動鹽,強大的哈希算法和SECH來增強密碼保護

OWASP前10 php:描述並減輕常見漏洞。OWASP前10 php:描述並減輕常見漏洞。Mar 26, 2025 pm 04:13 PM

本文討論了OWASP在PHP和緩解策略中的十大漏洞。關鍵問題包括注射,驗證損壞和XSS,並提供用於監視和保護PHP應用程序的推薦工具。

PHP XSS預防:如何預防XSS。PHP XSS預防:如何預防XSS。Mar 26, 2025 pm 04:12 PM

本文討論了防止PHP中XSS攻擊的策略,專注於輸入消毒,輸出編碼以及使用安全增強的庫和框架。

PHP接口與抽像類:何時使用。PHP接口與抽像類:何時使用。Mar 26, 2025 pm 04:11 PM

本文討論了PHP中接口和抽像類的使用,重點是何時使用。界面定義了無實施的合同,適用於無關類和多重繼承。摘要類提供常見功能

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

Atom編輯器mac版下載

Atom編輯器mac版下載

最受歡迎的的開源編輯器

SecLists

SecLists

SecLists是最終安全測試人員的伙伴。它是一個包含各種類型清單的集合,這些清單在安全評估過程中經常使用,而且都在一個地方。 SecLists透過方便地提供安全測試人員可能需要的所有列表,幫助提高安全測試的效率和生產力。清單類型包括使用者名稱、密碼、URL、模糊測試有效載荷、敏感資料模式、Web shell等等。測試人員只需將此儲存庫拉到新的測試機上,他就可以存取所需的每種類型的清單。

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)