PDOStatement::bindParam()の注意点

このページは、PHPの PDOStatement::bindParam() の注意点をまとめたページです。

目次

補足

bindValue()での値のバインド
$stmt = $pdo->prepare('SELECT * FROM emp WHERE name LIKE :name LIMIT :limit');
$stmt->bindValue('name', '%a%'); // 文字列 PDO::PARAM_STR としてのバインド
$stmt->bindValue('name', '%a%', PDO::PARAM_STR); // ↑と同じ
$stmt->bindValue('limit', 1, PDO::PARAM_INT); // 数値 PDO::PARAM_INT としてのバインド
$stmt->execute(); // 実行
execute()での値のバインド (文字列 (PDO::PARAM_STR) としてバインドするため LIMIT や OFFSET などには使用できない)
$stmt = $pdo->prepare('SELECT * FROM emp WHERE name LIKE :name');
$stmt->execute(['name' => '%a%']); // バインドと実行
  • このページの結果は下記の環境で検証したものです。別のバージョンや組み合わせでは違う動作になるかもしれません。
    (基本的にはここで検証した範囲のbindParam()の動作はPHP 5から変わっていません)
    • PHP 8.1 + MySQL 8.0.31
  • bindParam(), bindValue(), execute() ともプレースホルダに疑問符形式 (「?」) と名前形式 (「:name」) の両方が使用できますが、ここでは名前形式を使用しています。(結果は変わりません)
  • bindParam(), bindValue(), execute() ともパラメータ名にコロン (「:」) があってもなくても動作するためここではコロンを除いていますが、コロンがある場合でも動作は同じです。(SQL側にはコロンが必要です)

PDOStatement::bindParam() の注意点

変数しかバインドできない

bindParam() は変数のバインド用のメソッドのため、リテラル (そのままの値) などをバインドできません。

エラー (Fatal error: Uncaught Error: PDOStatement::bindParam(): Argument #2 ($var) cannot be passed by reference)
$stmt = $pdo->prepare('SELECT * FROM emp WHERE name LIKE :name');
$stmt->bindParam('name', '%a%');
エラー (Notice: Only variables should be passed by reference。実行は可能)
$stmt = $pdo->prepare('SELECT * FROM emp WHERE name LIKE :name');
$stmt->bindParam('name', get_some_value());
OK
$stmt = $pdo->prepare('SELECT * FROM emp WHERE name LIKE :name');
$name = '%a%';
$stmt->bindParam('name', $name);

execute()実行時に値が評価されるため意図しない動作になることがある

bindParam() は変数の参照をバインドしているため、反復処理中などで値が変わる変数に使用すると意図しない動作になることがあります。

bindParam()
$params = ['name0' => 'emp1', 'name1' => 'emp2'];
$ph = ':' . implode(',:', array_keys($params)); // :name0, :name1
$stmt = $pdo->prepare('SELECT * FROM emp WHERE name IN (' . $ph . ')');
foreach ($params as $k => $v) {
    $stmt->bindParam($k, $v);
}
$stmt->execute(); // この時点で$vが評価されるため、:name1 = 'emp2', :name2 = 'emp2' として評価される (この時点での$vの値)

bindValue(), execute() の場合は問題ありません。

bindValue()
$params = ['name0' => 'emp1', 'name1' => 'emp2'];
$ph = ':' . implode(',:', array_keys($params)); // :name0, :name1
$stmt = $pdo->prepare('SELECT * FROM emp WHERE name IN (' . $ph . ')');
foreach ($params as $k => $v) {
    $stmt->bindValue($k, $v);
}
$stmt->execute();
execute()
$params = ['name0' => 'emp1', 'name1' => 'emp2'];
$ph = ':' . implode(',:', array_keys($params)); // :name0, :name1
$stmt = $pdo->prepare('SELECT * FROM emp WHERE name IN (' . $ph . ')');
$stmt->execute($params);

変数の値や型が変更されることがある

bindParam() で変数をバインドすると、実行結果によって値や型が変更されることがあります。
(bindParam() はもとからストアドプロシージャのOUT, INOUTパラメータなどに適用してSQLの実行結果の値を受け取ることが想定されたメソッドですが、そのようなSQLでなくてもPDO内部の処理によって型が変更されます)

bindParam()
$id = 1;
$stmt = $pdo->prepare('SELECT * FROM emp WHERE id = :id');
$stmt->bindParam('id', $id);
$stmt->execute();

var_dump($id); // string(1) "1"
// bindParam() のデフォルトの型が PDO::PARAM_STR のため、内部で文字列に変換された結果、値が文字列になります。
// この例では $stmt->bindParam('id', $id, PDO::PARAM_INT); だと変換されません。

// 逆に $id = '1'; $stmt->bindParam('id', $id, PDO::PARAM_INT); のときはintには変換されませんが、
// これはPDO::PARAM_INTが値を変換しているのはboolのみのためです。
// $id = true; $stmt->bindParam('id', $id, PDO::PARAM_INT); だと1になります。
// ※ 該当ソース: https://github.com/php/php-src/blob/PHP-8.1.14/ext/pdo/pdo_stmt.c#L282

bindValue(), execute() の場合は問題ありません。

bindValue()
$id = 1;
$stmt = $pdo->prepare('SELECT * FROM emp WHERE id = :id');
$stmt->bindParam('id', $id);
$stmt->execute();

var_dump($id); // int(1)
execute()
$id = 1;
$stmt = $pdo->prepare('SELECT * FROM emp WHERE id = :id');
$stmt->execute(['id' => $id]);

var_dump($id); // int(1)