msgbartop
PHP语言, PHP扩展, Zend引擎相关的研究,技术,新闻分享 – 左手代码 右手诗
msgbarbottom

16 Oct 12 PDOStatement::bindParam的一个陷阱

废话不多说, 直接看代码:

<?php
$dbh = new PDO('mysql:host=localhost;dbname=test', "test");

$query = <<<QUERY
  INSERT INTO `user` (`username`, `password`) VALUES (:username, :password);
QUERY;
$statement = $dbh->prepare($query);

$bind_params = array(':username' => "laruence", ':password' => "weibo");
foreach( $bind_params as $key => $value ){
    $statement->bindParam($key, $value);
}
$statement->execute();

请问, 最终执行的SQL语句是什么, 上面的代码是否有什么问题?

Okey, 我想大部分同学会认为, 最终执行的SQL是:

INSERT INTO `user` (`username`, `password`) VALUES ("laruence", "weibo");

但是, 可惜的是, 你错了, 最终执行的SQL是:

INSERT INTO `user` (`username`, `password`) VALUES ("weibo", "weibo");

是不是很大的一个坑呢?

—— 如果你想自己找到原因, 那么就不要继续往下读了———

这个问题, 来自今天的一个Bug报告: #63281

究其原因, 也就是bindParam和bindValue的不同之处, bindParam要求第二个参数是一个引用变量(reference).

让我们把上面的代码的foreach拆开, 也就是这个foreach:

<?php
foreach( $bind_params as $key => $value ){
    $statement->bindParam($key, $value);
}

相当于:

<?php
//第一次循环
$value = $bind_params[":username"];
$statement->bindParam(":username", &$value); //此时, :username是对$value变量的引用

//第二次循环
$value = $bind_params[":password"]; //oops! $value被覆盖成了:password的值
$statement->bindParam(":password", &$value);

所以, 在使用bindParam的时候, 尤其要注意和foreach联合使用的这个陷阱. 那么正确的作法呢?

1. 不要使用foreach, 而是手动赋值

<?php
$statement->bindParam(":username", $bind_params[":username"]); //$value是引用变量了
$statement->bindParam(":password", $bind_params[":password"]);

2. 使用bindValue代替bindParam, 或者直接在execute中传递整个参数数组.

3. 使用foreach和reference(不推荐, 原因参看:微博)

<?php
foreach( $bind_params as $key => &$value ) { //注意这里
    $statement->bindParam($key, $value);
}

最后, 展开了说, 对于要求参数是引用, 并且有滞后处理的函数, 都要在使用foreach的时候, 谨慎!


分享到:



Related Posts:

Tags: , , , ,

44 Responses to “PDOStatement::bindParam的一个陷阱”

  1. 123 |

    123123213

  2. PDOStatement::bindParam的一个陷阱 | 午后小憩 |

    [...] 本文地址: http://www.laruence.com/2012/10/16/2831.html [...]

  3. PDOStatement::bindParam的参数分析 | 朱智超的博客 |

    [...] 大家可以注意到,第二个参数前面有个“&”,这个是引用符号,通过这个方法穿进去的值会是引用的。通常我们会使用foreach进行数组遍历绑定,一不小心就错了,详情就参见 PDOStatement::bindParam的一个陷阱这篇文章。 [...]

  4. waczx |

    尊敬的鸟哥您好:您的友情链接博客,有几个都是死链,不是个人博客!

  5. 51爱健康 |

    算是找到原因了,

  6. PHP PDOStatement:bindParam的一个陷阱 | 小谈博客 |

    [...] (转自风雪之隅:http://www.laruence.com/2012/10/16/2831.html) [...]

  7. 渊岳 |

    从知乎来到这里,好像在新浪微博行关注过你,呵呵。

  8. cq |

    真没这么用过。。

  9. netinus |

    之前遇到过,并且当时没理解和bindValue的区别。
    同时其实php官方文档已经有人指出此问题:
    comment from php.net
    This works ($val by reference):
    &$val) {
    $sth->bindParam($key, $val);
    }
    ?>

    This will fail ($val by value, because bindParam needs &$variable):
    $val) {
    $sth->bindParam($key, $val);
    }
    ?>
    http://www.php.net/manual/en/pdostatement.bindparam.php

  10. 冰山一点红 |

    学习了

  11. hellodifa |

    引用和foreach,想说爱你不容易啊……

  12. 袖之欢 |

    我一直使用

    $stmt=$pdo->prepare($sql)
    $stmt->execute($params);

    这个有什么弱点吗

    而且把 $params 和 execute 放在一起感觉更方便一些

  13. unspace |

    一直使用 execute($params) 单个的时候也是单个bind,还真没遇到过

  14. 一路向南 |

    为什么要用
    $query = <<<QUERY
    INSERT INTO `user` (`username`, `password`) VALUES (:username, :password);
    QUERY;
    这样的写法呢?有啥好处?

  15. 一场下在深秋的雨 |

    一场秋雨,在窗外无声地下着,若不是提醒,它或许就会在不知不觉中成为过去。才打开手中的伞,一阵冷意便袭击着全身。雨点不是很大,也不是很紧密,落在伞上的声音,轻微,有一种柔柔的感觉。雨…

  16. PHP教程:PDOStatement::bindParam的一个陷阱 | PHP爱好者 |

    [...] 转自:http://www.laruence.com/2012/10/16/2831.html [...]

  17. 梦康 |

    先收藏了,不是经常用,一般都是手动赋值。

  18. 夏韵 |

    花色断尽,无韵千年,只为一生,贪恋一季。月晕涣散,一世流年,水榭楼台,风雨苍白。夏,绿生的季节,富美的季节。花蕾将葳蕤带给整座森林,岚岫将氤氲带给整片大地,盛夏将繁阜带给整个世界,…

  19. PDOStatement::bindParam的一个陷阱 | 5iphp |

    [...] 2012/10/16, 破月亮 writes: 学习了 [...]

  20. 二话不说 |

    手册已经有此问题的描述!

  21. HouRay |

    记得当年也踏入过这个陷阱。
    这里的bindParam参数是引用方式传递的。
    这么做的后果就是,绑定的所有值都和foreach里最后一个value的值相等了。
    还有在foreach($arr AS $k => &$v)这种用法也是有相当大陷阱的。

  22. 刘悦 |

    很久没有看到鸟哥的新文章了…很期待啊…

  23. PHP PDO prepared statement 一则 | 指间世界 |

    [...] 自从用了PDO,代码果然小清新多了,腰也不疼,腿也不酸了,写代码也更有劲了。需要注意的是以上示例里的bindParam如果使用不当即为巨坑,详情见PDOStatement::bindParam的一个陷阱 ,前车之鉴后事之师! This entry was posted in php by love. Bookmark the permalink. [...]

  24. Anonymous |

    0

  25. mqg |

    我们是把值放execute里,这样会有什么问题么

  26. 轩脉刃 |

    这个坑真不知道,大坑

  27. taylortai |

    平时很少用这个功能,一般都是几个基本功能

  28. PDOStatement::bindParam的一个陷阱 | 奇言妙事-文学奇谈小小说阅读xlinblog.sinaapp.com |

    [...] 本文地址: http://www.laruence.com/2012/10/16/2831.html [...]

  29. www.cnfnc.com |

    让萨达姆被捕时被发现他随身携带的除了一把手枪之外还有的就是它的复印件的好帖532170236owpun

  30. lxylxy888666 |

    没遇到过,看手册有demo说明这个问题.
    This works ($val by reference):
    &$val) {
    $sth->bindParam($key, $val);
    }
    ?>

    This will fail ($val by value, because bindParam needs &$variable):
    $val) {
    $sth->bindParam($key, $val);
    }
    ?>

  31. jmeye |

    我之前也发现了这个问题,改成这样就好了。
    foreach( $bind_params as $key => $value ){ $statement->bindParam($key, $bind_params[$key]);
    }

  32. 芒果互联 |

    稳定放心的在线服务!互访把 亲

  33. 万维网黑客联盟 |

    http://bbs.w3hacker.com nodejs教程 underscore教程 seajs教程 backbone教程

  34. sleeping fire |

    的确是个天坑,循环还是好点,用unset可以解决

  35. 花生 |

    学习了,这个果真是个天坑啊

  36. fifsky |

    额,我封装的PDO操作类一直都是
    foreach ($params as $k => &$param)
    {
    $stmt->bindParam($k, $param, PDO::PARAM_STR, strlen($param));
    }
    这样的,目前还没有发现过问题,看来是个陷阱

  37. JackalHu |

    prepare($sql);
    $limit = 10;
    $statement->bindParam(1, $limit);

    对LIMIT子句中的占位符,绑定时就需要指定第三个参数为PDO::PARAM_INT。这时直接在execute()中传递整个参数数组,或者foreach就都不好用了

  38. 神仙 |

    设计成是引用型参数是怎么考虑的呢

  39. PDOStatement::bindParam的一个陷阱树林/咖啡 成都专业php网站制作 | 树林/咖啡 成都专业php网站制作 |

    [...] 风雪之隅 » PHP应用 Posted in: php / Tagged: PDOStatementbindParam的一个陷阱 [...]

  40. darasion |

    sql 应该必须用单引号吧?

  41. staryang |

    foreach结束之前, 加上unset($value)就可以了…

  42. huangchuan |

    这个问题真的遇到过。
    尤其是想强制指定某参数是整形时,采用foreach,只有第一个sql语句插入的是整形,之后插入的所有sql类型都是字符型

  43. walu |

    我去。。。这有点离谱

  44. 破月亮 |

    学习了

Leave a Reply