A trap in PDOStatement::bindParam

Summary

Developers using PDOStatement::bindParam within a foreach loop can encounter a subtle trap where all bound parameters unexpectedly resolve to the final value from the loop. This occurs because bindParam requires its second argument to be a reference, and a standard foreach loop reassigns the $value variable in each iteration, causing all parameters to point to the same memory location by the loop's end. Consequently, the executed SQL query might use identical values for different placeholders, leading to incorrect data. To mitigate this, developers should either manually bind parameters, use PDOStatement::bindValue, or pass the parameters directly to the execute method. While iterating with a reference in the foreach loop is technically possible, it is generally not the recommended approach.

First, let's check out below codes:

<?php
$dbh = new PDO('mysql:host=localhost;dbname=test', "test");
 
$query = <<prepare($query);
 
$bind_params = array(':username' => "laruence", ':password' => "weibo");
foreach( $bind_params as $key => $value ){
    $statement->bindParam($key, $value);
}
$statement->execute();

What is the SQL executed finally? Is there any problem with above codes?

Many people may think the query executed is :

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

But the query actually gets executed is :

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

Is it a big trap? This issue is drafted in a bug report : #63281.

The reason comes from the difference between bindParam and bindValue, the second parameter in bindParam should be a reference.

Let's take a look at the foreach statement above:

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

It's the same as:

<?php
//The first iteration
$value = $bind_params[":username"];
$statement->bindParam(":username", &$value); //Now username is the reference to the $value

//The second iteration
$value = $bind_params[":password"]; //oops! $value is overrided with value of password
$statement->bindParam(":password", &$value);

So we should pay attention to this trap when using bindParam together with foreach. What's the right way to achieve what we expected?

1. Don't use foreach, manually assign values instead

<?php
$statement->bindParam(":username", $bind_params[":username"]); 
$statement->bindParam(":password", $bind_params[":password"]);

2. Replace bindParam with bindValue, or directly pass the parameters in the execute method

3. Use foreach and reference(Don't recommend this method)

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

At last, you should pay attention to the use of foreach when need to pass reference to a function call.

Source : http://www.laruence.com/2012/10/16/2831.html

PHP TRAP BINDPARAM

  RELATED

  COMMENT

1
Rodrigo
Jul 21, 2014 at 12:39 pm

Hey! Thanks a lot man!!
This saved my life.

I had the same problem. After use the reference in my foreach, worked like a charm.