Press "Enter" to skip to content

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的时候, 谨慎!

80 Comments

  1. 始めて利用させてもらったのですが、対応がとても好感を持てました。支払い方法で、こちらに不備があったのですが、すぐに電話を頂いて商品の到着も指定日の指定時間ちょうどに届きました!!商品も新品Sランクでしたが、本当に美品でここで購入してよかったと満足してます。
    スーパーコピー ヴィヴィアン 財布 https://www.88kopi.com

  2. 张鹏
    张鹏 July 7, 2019

    绑定可不可以这样写
    foreach($param as $k => $v){
    $statement->bindParam($k , &$v);
    }

    • wj008
      wj008 July 7, 2020

      这样 还是最后一个 $v 啊!试试就知道了。

  3. megidmegid
    megidmegid August 30, 2017

    这个问题,用foreach绑定的时候,写成 foreach( $bind_params as $key => $value ) { //注意这里
    $stmt->bindParam($key, $bind_params[$key]);
    } 这样貌似就不会出现这个bug了。

  4. dingling
    dingling December 27, 2016

    $statement->bindParam($key, $value);
    修改成
    $statement->bindParam($key, $bind_params[$key]);

  5. yangakw
    yangakw December 1, 2016

    如果插入的都是字符串
    这样也可以
    foreach( $bind_params as $key => $value ) {
    $statement->bindParam($key, trim($value) );
    }
    使用函数处理可以分配新的地址

  6. liuyafeng
    liuyafeng June 21, 2016

    正好对这两个函数不解,太及时了,谢谢!

  7. array
    array June 16, 2016

    其实这也不算bug,bindParam的初衷就是变量绑定,bindParam()都绑定的一个$value变量可不取值都一样嘛。
    在foreach($arr as $k=>$value)里这样写就没问题了
    bindParam(’:abc’,$arr[$k])

  8. Regan Steinman
    Regan Steinman April 13, 2016

    Si estás comenzando a aprender a tocar guitarra electrica, bien tienes una guitarra acustica, estás en el sitio indicado!

  9. Jonelle Colleano
    Jonelle Colleano April 13, 2016

    Una gran forma de evaluar que el progreso es para grabar y video a ti mismo a tocar la guitarra.

  10. Jane
    Jane February 12, 2016

    I’ll immediately snatch your rss as I can’t find your email subscription hyperlink or e-newsletter service.
    Do you’ve any? Please let me know in order that I may just subscribe.
    Thanks.

  11. katespade
    katespade December 12, 2015

    For the most part, life is one big trial and error. The only way you’re going to grow is to try new things and the only way to get yourself to try new things is to accept that fact that you’re going to mess up every now and then.
    katespade http://www.katespade-outletonline.net/

  12. 小小菜鸟
    小小菜鸟 October 20, 2015

    我今天也遇到这个坑了,查了好久才知道,感谢鸟哥~

  13. clarence
    clarence February 6, 2015

    写foreach来绑定SQL参数的时候,肯定要换bindValue
    ps: 窃以为php引用类型的参数时不需要加’&’,总是会很坑,真搞不懂为啥引用类型的参数为什么加’&’会被废弃

  14. xiao
    xiao January 8, 2015

    确实是个大坑,前几天我也遇到了这种情况,我还循环打印值来看都是true,最后没办法只能在excute中用数组了

  15. reatang
    reatang October 24, 2014

    我一般绑定的话,直接就把关联数组传给execute,也没用给键名加上: 就能操作。这样使用的的安全性没问题么?

  16. get adult toys online
    get adult toys online September 13, 2014

    Sexy lingerie when sold at wholesale rates is a very lucrative offer for any consumer.
    You would undoubtedly enjoy an enhanced charm with its lovely pink
    bowknot. The apparel industry in Australia is doing well internationally and the
    Aussie made fashion attire is now getting popular
    among international fashion freaks.
    Feel free to visit my blog post; get adult toys online

  17. gao
    gao September 1, 2014

    非常感谢分享,解决了我的一个大问题

  18. kaola
    kaola May 27, 2014

    以前确实遇到过这个坑..

  19. nikbobo
    nikbobo March 13, 2014

    原来如此。。好吧,MySQLi 不支持此方法。。。。

  20. 找名网
    找名网 December 30, 2013

    学习了,不错!

  21. 瞬间的永恒
    瞬间的永恒 December 10, 2013

    以前随便看了一下,最近在工作中遇到类似问题,重新好好看了一下,总算理解了原因啦

  22. terry
    terry October 9, 2013

    设计为引用目的是为了提高效率,非引用的传值相当于复制变量,即内存中会多出一份数据。一般来说,在使用bindParam()绑定变量的时候,可以直接使用execute(array(‘:name’=>’jack’))把数组直接传进去的方式,简单方便。效果一样!

  23. studentUser
    studentUser August 28, 2013

    用foreach 没有问题的!
    Will actually end up being double-quoted and can cause problems.

  24. 123
    123 June 18, 2013

    123123213

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

  26. waczx
    waczx March 11, 2013

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

  27. 渊岳
    渊岳 March 2, 2013

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

  28. cq
    cq February 18, 2013

    真没这么用过。。

  29. netinus
    netinus February 5, 2013

    之前遇到过,并且当时没理解和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

  30. 冰山一点红
    冰山一点红 January 29, 2013

    学习了

  31. hellodifa
    hellodifa January 19, 2013

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

  32. 袖之欢
    袖之欢 January 14, 2013

    我一直使用
    $stmt=$pdo->prepare($sql)
    $stmt->execute($params);
    这个有什么弱点吗
    而且把 $params 和 execute 放在一起感觉更方便一些

  33. unspace
    unspace January 10, 2013

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

  34. 一路向南
    一路向南 January 4, 2013

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

  35. 一场下在深秋的雨
    一场下在深秋的雨 January 3, 2013

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

  36. 梦康
    梦康 December 29, 2012

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

  37. 夏韵
    夏韵 December 21, 2012

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

  38. 二话不说
    二话不说 December 13, 2012

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

  39. HouRay
    HouRay November 30, 2012

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

  40. 刘悦
    刘悦 November 16, 2012

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

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

  42. mqg
    mqg November 3, 2012

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

  43. 轩脉刃
    轩脉刃 November 2, 2012

    这个坑真不知道,大坑

  44. taylortai
    taylortai October 31, 2012

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

  45. www.cnfnc.com
    www.cnfnc.com October 26, 2012

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

  46. lxylxy888666
    lxylxy888666 October 24, 2012

    没遇到过,看手册有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);
    }
    ?>

  47. jmeye
    jmeye October 23, 2012

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

  48. 芒果互联
    芒果互联 October 23, 2012

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

  49. sleeping fire
    sleeping fire October 23, 2012

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

  50. 花生
    花生 October 18, 2012

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

  51. fifsky
    fifsky October 17, 2012

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

  52. JackalHu
    JackalHu October 17, 2012

    prepare($sql);
    $limit = 10;
    $statement->bindParam(1, $limit);
    对LIMIT子句中的占位符,绑定时就需要指定第三个参数为PDO::PARAM_INT。这时直接在execute()中传递整个参数数组,或者foreach就都不好用了

  53. 神仙
    神仙 October 17, 2012

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

  54. darasion
    darasion October 16, 2012

    sql 应该必须用单引号吧?

  55. staryang
    staryang October 16, 2012

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

  56. huangchuan
    huangchuan October 16, 2012

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

  57. walu
    walu October 16, 2012

    我去。。。这有点离谱

  58. 破月亮
    破月亮 October 16, 2012

    学习了

Comments are closed.