bindValue() 文档没有解释清楚的地方(除非非常仔细地阅读)是 bindParam() 是通过引用传递给 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() 是通过引用传递给 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); // the author 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()。
这是个bug吗?我不确定。
虽然 bindValue() 转义了引号,但它没有转义 "%" 和 "_",因此在使用 LIKE 时要小心。如果自己不转义参数,则充满 %%% 的恶意参数可能会转储整个数据库。PDO 没有提供任何其他转义方法来处理它。
请注意,在大多数情况下,第三个参数 ($data_type) 不会将值类型转换为其他任何内容以用于查询,如果类型与提供的值不匹配,它也不会引发任何错误。此参数实际上没有任何效果,除非它已设置且不是浮点数时会引发错误,因此不要认为它为查询增加了任何额外的安全级别。
执行类型转换的两个例外情况:
- 如果你使用 PDO::PDO_PARAM_INT 并提供一个布尔值,它将被转换为长整型。
- 如果你使用 PDO::PDO_PARAM_BOOL 并提供一个长整型,它将被转换为布尔值。
<?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 子句时,此函数更有用,因为它们需要整数。
* */
?>
<?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();
?>
输出
Notice: Undefined variable: val.
而在 bindParam 的情况下,直到调用 execute() 才会执行对参数的值求值。这是为了获得引用传递的益处。
<?php
$st = $db->prepare ("SELECT * FROM posts WHERE id = :val ");
$st->bindParam(':val',$val);
$val = '2';
//
// 一些代码
//
$val = '3'; // 重新赋值变量
$st->execute();
?>
运行正常。
注意极端情况!
使用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 的数据类型取决于参数名称
<?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 ();
}
}
?>