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 の直前に変数を書き換えればその値が反映される。

違いのまとめ

bindValue

呼び出し時点の値を固定する。リテラルや定数も直接渡せる。値が変わらないことが保証されるので安全性が高い。

bindParam

変数への参照を渡す。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 という使い分けが実務的な判断基準になる。