注意PDO-bindParam中的一个陷阱(不能在foreach循环中使用)
案例代码
$dbh = new PDO('mysql:host=localhost;dbname=test', "test");
$sql = "INSERT INTO `user` (`username`, `password`) VALUES (:username, :password)";
$statement = $dbh->prepare($query);
$bind_params = array(
':username' => "wubin",
':password' => "work"
);
foreach( $bind_params as $key => $value ){
$statement->bindParam($key, $value);
}
$statement->execute();
请问, 最终执行的SQL语句是什么, 上面的代码是否有什么问题?
案例分析
我想大部分同学会认为, 最终执行的SQL是:
INSERT INTO `user` (`username`, `password`) VALUES ("wubin", "work"); // 错的
但是, 可惜的是, 你错了, 最终执行的SQL是:
INSERT INTO `user` (`username`, `password`) VALUES ("work", "work"); // 正确的最终结果
是不是很大的一个坑呢?这个问题, 来自今天的一个Bug报告:#63281
究其原因, 也就是bindParam和bindValue的不同之处, bindParam要求第二个参数是一个引用变量(reference)。
让我们把上面的代码的foreach拆开, 也就是这个foreach:
foreach( $bind_params as $key => $value ){
$statement->bindParam($key, $value);
}
相当于:
# 第一次循环
$value = $bind_params[":username"];
//此时, :username是对$value变量的引用
$statement->bindParam(":username", &$value);
# 第二次循环
// $value被覆盖成了:password的值
$value = $bind_params[":password"];
$statement->bindParam(":password", &$value);
所以, 在使用bindParam的时候, 尤其要注意和foreach联合使用的这个陷阱. 那么正确的作法呢?
正确的做法
将bingParam替换为bindValue
使用bindValue代替bindParam(推荐), 或者直接在execute中传递整个参数数组
foreach( $bind_params as $key => $value ){
$statement->bindValue($key, $value);
}
直接在execute中传递整个参数数组
$statement = $dbh->prepare($sql);
$statement->execute([ ':username' => 'wubin, ':password' => 'work ]);
或者$statement->execute($bind_param);
bindValue更多操作请见下方《四》
不要使用foreach, 而是手动赋值
$statement->bindParam(":username", $bind_params[":username"]);
$statement->bindParam(":password", $bind_params[":password"]);
同理,也可以在循环中:
foreach( $bind_params as $key => $value ){
$statement->bindParam($key, $bind_params[$key]);
}
使用foreach和reference(不推荐)
foreach( $bind_params as $key => &$value ) { //注意这里需要加&
$statement->bindParam($key, $value);
}
最后, 展开了说, 对于要求参数是引用, 并且有滞后处理的函数, 都要在使用foreach的时候, 谨慎!
bindValue使用
语法
bool PDOStatement::bindValue(mixed $parameter,mixed $value[,int$data_type=PDO::PARAM_STR])
绑定一个值到用作预处理的 SQL 语句中的对应命名占位符或问号占位符。
参数
parameter参数标识符。对于使用命名占位符的预处理语句,应是类似 :name 形式的参数名。对于使用问号占位符的预处理语句,应是以1开始索引的参数位置。
value绑定到参数的值
data_type使用 PDO::PARAM_* 常量明确地指定参数的类型。常用的有:
- PDO::PARAM_STR:字符串类型
- PDO::PARAM_INT:整型
- PDO::PARAM_BOOL: 布尔类型
- PDO::PARAM_NULL :null数据类型
返回值
案例
执行一条使用命名占位符的预处理语句:
// 通过绑定的 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();
执行一条使用问号占位符的预处理语句
// 通过绑定的 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();
第一个参数比如(1,2)就代表?在sql语句中的偏移量了,代表sql语句中的第几个参数。
其中data-type(PDO::PARAM_INT)等是可以省略的。