bindValue() 的文件說明如果沒有非常仔細地閱讀,很容易忽略 bindParam() 是以傳址 (byref) 方式傳遞給 PDO,而 bindValue() 則不是。
因此,使用 bindValue() 時,您可以執行類似 `$stmt->bindValue(":something", "bind this");` 的操作;但使用 bindParam() 時,它會失敗,因為例如您無法以傳址方式傳遞字串。
(PHP 5 >= 5.1.0, PHP 7, PHP 8, PECL pdo >= 1.0.0)
PDOStatement::bindValue — 將值繫結至參數
將一個值繫結到用於準備語句的 SQL 語句中相應的命名或問號佔位符。
param
參數識別符。對於使用命名佔位符的已準備語句,這將是 :name 形式的參數名稱。對於使用問號佔位符的已準備語句,這將是參數的 1 基索引位置。
value
要繫結到參數的值。
type
使用 PDO::PARAM_*
常數 的參數的顯式資料類型。
如果屬性 PDO::ATTR_ERRMODE
設定為 PDO::ERRMODE_WARNING
,則發出層級為 E_WARNING
的錯誤。
如果屬性 PDO::ATTR_ERRMODE
設定為 PDO::ERRMODE_EXCEPTION
,則拋出 PDOException。
範例 #1 執行具有命名佔位符的已準備語句
<?php
/* 執行一個預備語句,並繫結 PHP 變數 */
$calories = 150;
$colour = 'red';
$sth = $dbh->prepare('SELECT name, colour, calories
FROM fruit
WHERE calories < :calories AND colour = :colour');
/* 使用名稱設定參數值 */
$sth->bindValue('calories', $calories, PDO::PARAM_INT);
/* 或者,參數名稱也可以加上冒號 ":" 前綴 */
$sth->bindValue(':colour', $colour, PDO::PARAM_STR);
$sth->execute();
?>
範例 #2 使用問號佔位符執行預備語句
<?php
/* 執行一個預備語句,並繫結 PHP 變數 */
$calories = 150;
$colour = 'red';
$sth = $dbh->prepare('SELECT name, colour, calories
FROM fruit
WHERE calories < ? AND colour = ?');
$sth->bindValue(1, $calories, PDO::PARAM_INT);
$sth->bindValue(2, $colour, PDO::PARAM_STR);
$sth->execute();
?>
bindValue() 的文件說明如果沒有非常仔細地閱讀,很容易忽略 bindParam() 是以傳址 (byref) 方式傳遞給 PDO,而 bindValue() 則不是。
因此,使用 bindValue() 時,您可以執行類似 `$stmt->bindValue(":something", "bind this");` 的操作;但使用 bindParam() 時,它會失敗,因為例如您無法以傳址方式傳遞字串。
當綁定參數時,顯然您不能兩次使用同一個佔位符(例如 "select * from mails where sender=:me or recipient=:me"),您必須給它們不同的名稱,否則您的查詢將返回空值(但不會失敗,很遺憾)。如果您正在為類似的事情苦惱,請注意這一點。
嘗試使用 PDO::PARAM_INT 驗證時要小心。
請考慮以下範例
<?php
/* php --version
* PHP 5.6.25 (cli) (built: Aug 24 2016 09:50:46)
* Copyright (c) 1997-2016 The PHP Group
* Zend Engine v2.6.0, Copyright (c) 1998-2016 Zend Technologies
*/
$id = '1a';
$stm = $pdo->prepare('select * from author where id = :id');
$bind = $stm->bindValue(':id', $id, PDO::PARAM_INT);
$stm->execute();
$authors = $stm->fetchAll();
var_dump($id); // string(2)
var_dump($bind); // true
var_dump((int)$id); // int(1)
var_dump(is_int($id)); // false
var_dump($authors); // 作者 id=1 的資料 =(
// 記住
var_dump(1 == '1'); // true
var_dump(1 === '1'); // false
var_dump(1 === '1a'); // false
var_dump(1 == '1a'); // true
?>
我的看法:bindValue() 應該在內部先測試 is_int()。
這是一個錯誤嗎?我不確定。
雖然 bindValue() 會跳脫引號,但它不會跳脫 "%" 和 "_",因此在使用 LIKE 時要小心。如果沒有自行跳脫參數,一個充滿 %%% 的惡意參數可能會傾印整個資料庫。PDO 沒有提供任何其他跳脫方法來處理它。
請注意,在大多數情況下,第三個參數($data_type)並不會將值轉換成其他類型以供查詢使用,也不會在類型與提供的值不匹配時拋出任何錯誤。這個參數基本上沒有任何作用,除非設定了它且它不是浮點數時會拋出錯誤,所以不要認為它為查詢增加了任何額外的安全性。
執行類型轉換的兩個例外情況
- 如果您使用 PDO::PDO_PARAM_INT 並提供一個布林值,它將被轉換為長整數 (long)
- 如果您使用 PDO::PDO_PARAM_BOOL 並提供一個長整數 (long),它將被轉換為布林值
<?php
$query = 'SELECT * FROM `users` WHERE username = :username AND `password` = ENCRYPT( :password, `crypt_password`)';
$sth= $dbh->prepare($query);
// 首先嘗試傳遞一個隨機數值作為第三個參數
var_dump($sth->bindValue(':username','bob', 12345.67)); // bool(true)
// 接著嘗試使用布林類型傳遞一個字串
var_dump($sth->bindValue(':password','topsecret_pw', PDO::PARAM_BOOL)); // bool(true)
$sth->execute(); // 成功執行查詢
$result = $sth->fetchAll(); // 返回查詢結果
?>
此函數可用於綁定陣列中的值。您可以使用 $typeArray 預先指定值的類型。
<?php
/**
* @param string $req : 查詢字串,用於綁定值的連結
* @param array $array : 關聯式陣列,包含要綁定的值
* @param array $typeArray : 關聯式陣列,指定 $array 中每個鍵對應的值的資料類型
* */
function bindArrayValue($req, $array, $typeArray = false)
{
if(is_object($req) && ($req instanceof PDOStatement))
{
foreach($array as $key => $value)
{
if($typeArray)
$req->bindValue(":$key",$value,$typeArray[$key]);
else
{
if(is_int($value))
$param = PDO::PARAM_INT;
elseif(is_bool($value))
$param = PDO::PARAM_BOOL;
elseif(is_null($value))
$param = PDO::PARAM_NULL;
elseif(is_string($value))
$param = PDO::PARAM_STR;
else
$param = FALSE;
if($param)
$req->bindValue(":$key",$value,$param);
}
}
}
}
/**
* ## 範例 ##
* $array = array('language' => 'php','lines' => 254, 'publish' => true);
* $typeArray = array('language' => PDO::PARAM_STR,'lines' => PDO::PARAM_INT,'publish' => PDO::PARAM_BOOL);
* $req = 'SELECT * FROM code WHERE language = :language AND lines = :lines AND publish = :publish';
* 你可以像這樣綁定 $array :
* bindArrayValue($array,$req,$typeArray);
* 這個函式在使用 limit 子句時特別有用,因為 limit 需要整數。
* */
?>
<?php
/**
* 綁定位元值。
*/
$sql = 'SELECT * FROM myTable WHERE level & ?';
$sth = \App::pdo()->prepare($sql);
$sth->bindValue(1, 0b0101, \PDO::PARAM_INT);
$sth->execute();
$result = $sth->fetchAll(\PDO::FETCH_ASSOC);
我們無法在呼叫 bindValue() 之後才定義值變數的原因是,它會立即將值綁定到預備語句,而不是等到 execute() 發生。
以下程式碼會發出一個通知並阻止查詢執行
<?php
$st = $db->prepare ("SELECT * FROM posts WHERE id= :val ");
$st->bindValue(':val',$val);
$val = '2';
$st->execute();
?>
輸出結果
注意:未定義的變數:val。
而在 bindParam 的情況下,值的評估要等到呼叫 execute() 時才會執行。這是為了利用傳參考的優點。
<?php
$st = $db->prepare ("SELECT * FROM posts WHERE id = :val ");
$st->bindParam(':val',$val);
$val = '2';
//
// 一些程式碼
//
$val = '3'; // 重新賦值給變數
$st->execute();
?>
可以正常運作。
這實際上可以在 MySQL 的整數欄位上綁定 NULL 值
$stm->bindValue(':param', null, PDO::PARAM_INT);
在邊緣情況要小心!
使用 MySQL 原生預備語句時,在某些機器上,您的整數可能會被截斷。
<?php
$x = 2147483648;
var_dump($x); // 顯示:int(2147483648)
$s = $db->prepare('SELECT :int AS I, :str AS S;');
$s->bindValue(':int', $x, PDO::PARAM_INT);
$s->bindValue(':str', $x, PDO::PARAM_STR);
$s->execute();
var_dump( $s->fetchAll(PDO::FETCH_ASSOC) );
/* 顯示:array(2) {
["I"]=>
string(11) "-2147483648"
["S"]=>
string(10) "2147483648"
} */
?>
此外,嘗試在 MySQL 中使用原生預備語句綁定 PDO::PARAM_BOOL 可能會導致查詢無聲無息地失敗並返回空集合。
在這種情況下,模擬預備語句的工作更穩定,因為它們會將所有內容轉換為字串,並決定是否要將參數加上引號。
bindValue 的 data_type 取決於參數名稱
<?php
$db = new PDO (...);
$db -> setAttribute (PDO::ATTR_STATEMENT_CLASS, array ('MY_PDOStatement ', array ($db)));
class MY_PDOStatement extends PDOStatement {
public function execute ($input = array ()) {
foreach ($input as $param => $value) {
if (preg_match ('/_id$/', $param))
$this -> bindValue ($param, $value, PDO::PARAM_INT);
else
$this -> bindValue ($param, $value, PDO::PARAM_STR);
}
return parent::execute ();
}
}
?>