PDO の bindValue と bindParam の違い
PDO でプレースホルダに値をバインドするメソッドは bindValue と bindParam の 2 つがある。名前が似ているため混同しやすいが、値を渡すタイミングが根本的に異なる。
bindValue は「値」をバインドする
bindValue は呼び出した時点の値をプレースホルダに固定する。その後に変数の中身が変わっても、バインドされた値は変わらない。
$name = 'Alice';
$stmt = $pdo->prepare( 'SELECT * FROM users WHERE name = :name' );
$stmt->bindValue( ':name', $name, PDO::PARAM_STR );
$name = 'Bob'; // ここで変えても影響なし
$stmt->execute(); // name = 'Alice' で検索されるbindValue は値のコピーを渡す。execute 時には bindValue を呼んだ時点のスナップショットが使われる。
bindParam は「変数への参照」をバインドする
bindParam は変数そのものへの参照をプレースホルダに紐づける。execute が呼ばれた時点の変数の中身がバインドされる。
$name = 'Alice';
$stmt = $pdo->prepare( 'SELECT * FROM users WHERE name = :name' );
$stmt->bindParam( ':name', $name, PDO::PARAM_STR );
$name = 'Bob'; // execute 時にはこの値が使われる
$stmt->execute(); // name = 'Bob' で検索されるbindParam は変数の参照を渡すため、execute の直前に変数を書き換えればその値が反映される。
違いのまとめ
呼び出し時点の値を固定する。リテラルや定数も直接渡せる。値が変わらないことが保証されるので安全性が高い。
変数への参照を渡す。execute 時点の値が使われる。ループ内で同じステートメントを繰り返し実行する場合に便利だが、意図しない値が入るリスクもある。
bindParam が活きるケース
bindParam の参照渡しが効果を発揮するのは、同じ SQL を異なる値で繰り返し実行する場面だ。
$stmt = $pdo->prepare(
'INSERT INTO users ( name, age ) VALUES ( :name, :age )'
);
$stmt->bindParam( ':name', $name, PDO::PARAM_STR );
$stmt->bindParam( ':age', $age, PDO::PARAM_INT );
$users = [
[ 'Alice', 30 ],
[ 'Bob', 25 ],
[ 'Charlie', 35 ],
];
$pdo->beginTransaction();
foreach ( $users as $user )
{
$name = $user[0];
$age = $user[1];
$stmt->execute();
}
$pdo->commit();prepare は 1 回だけ呼び、ループ内では変数を書き換えて execute するだけでよい。bindParam が参照を保持しているため、再度 bindParam を呼ぶ必要がない。
bindValue で同じことをする場合
bindValue の場合は、ループのたびにバインドし直す必要がある。
$stmt = $pdo->prepare(
'INSERT INTO users ( name, age ) VALUES ( :name, :age )'
);
$pdo->beginTransaction();
foreach ( $users as $user )
{
$stmt->bindValue( ':name', $user[0], PDO::PARAM_STR );
$stmt->bindValue( ':age', $user[1], PDO::PARAM_INT );
$stmt->execute();
}
$pdo->commit();こちらのほうがコードの意図は明確だ。毎回値を渡しているため、参照の挙動を気にする必要がない。
リテラルを渡すときの違い
bindValue はリテラルを直接渡せるが、bindParam はできない。
// bindValue は OK
$stmt->bindValue( ':status', 'active', PDO::PARAM_STR );
// bindParam はエラー(参照を渡す必要があるため)
$stmt->bindParam( ':status', 'active', PDO::PARAM_STR ); // エラーbindParam の第 2 引数は変数でなければならない。リテラルや関数の戻り値を直接渡すことはできないため、一時変数に入れる手間が発生する。
どちらを使うべきか
迷ったら bindValue を使うのが無難だ。値が固定されるため予測しやすく、バグの原因になりにくい。bindParam は参照の仕組みを理解したうえで、ループ処理の最適化など明確な理由があるときに使えばよい。
execute に配列を渡す方法も含めると、バインドの方法は 3 通りある。単純なクエリなら execute の配列渡しが最も簡潔で、型の指定が必要なら bindValue、ループ最適化が必要なら bindParam という使い分けが実務的な判断基準になる。