[PHP]PDO bindParamで同じ変数名を使うと内容も同じになる

プレースホルダに bindParam() を使って値をセットする時、
同じ変数名を使うと参照渡しが行われてしまい、最後にセットした値が
全てのプレースホルダにセットされてしまいます。

<?php
$host = 'localhost';
$name = 'test';
$user = 'user';
$pass = 'password';

try {
	$dsn = "mysql:host={$host};dbname={$name};charset=utf-8";
	$db = new PDO($dsn, $user, $pass);
} catch (PDOExceprion $e){
	echo $e->getMessage();
}

$sql = "SELECT * FROM sample WHERE (name = ? OR name = ?)";

$stmt = $db->prepare($sql);

$value = "foo";
$stmt->bindParam(1, $value);

$value = "bar";
$stmt->bindParam(2, $value);

$stmt->execute();

上の例では、
「SELECT * FROM sample WHERE (name = ? OR name = ?)」
という SQL 文の疑問符部分に対してそれぞれ「foo」と「bar」をバインドしています。

「name = ‘foo’ OR name = ‘bar’」となるつもりが、実際の結果は
「name = ‘bar’ OR name = ‘bar’」となってしまいました。

どちらも value と言う変数名を使っているため、後から代入した値によって
他の値が上書きされています。
$value1, $value2 のように名前を分ければこれは発生しません。
あるいは一度使った $value を unset() しておけば同じ名前であっても大丈夫です。

$value = "foo";
$stmt->bindParam(1, $value);

unset($value);

$value = "bar";
$stmt->bindParam(2, $value);

execute() を行うまでは $value に代入した時点で上書きが行われるので、
bindParam() を使ったかどうかは実際には関係ありません。
次のようにすると全てのパラメータは「hello」に上書きされます。

$value = "foo";
$stmt->bindParam(1, $value);

$value = "bar";
$stmt->bindParam(2, $value);

$value = "hello";

【結果】

SELECT * FROM sample WHERE (name = 'hello' OR name = 'hello')

ループ内で使う場合は、参照渡し「&$value」を使って bindParam() を行います。
後から問題を起こさないように使った変数は unset() で片付けておきます。

$i=1;foreach($arr as &$value){
  $stmt->bindParam($i, $value);
  $i++;
}
unset($value);

参照渡しをしない bindValue() を使えばこの問題が発生しないので、bindParam にこだわりがなければ
for や foreach などのループ内では bindValue() を使ったほうが無駄な混乱が起こりにくくなると思います。