PDOStatement::bindParam()の注意点
このページは、PHPの PDOStatement::bindParam() の注意点をまとめたページです。
目次
補足
bindParam()
は、特に必要がない限り PDOStatement::bindValue() を使用するか、PDOStatement::execute() のパラメータを渡す形式を使用したほうが問題が少ないです。
$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(); // 実行
$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()
は変数のバインド用のメソッドのため、リテラル (そのままの値) などをバインドできません。
$stmt = $pdo->prepare('SELECT * FROM emp WHERE name LIKE :name');
$stmt->bindParam('name', '%a%');
$stmt = $pdo->prepare('SELECT * FROM emp WHERE name LIKE :name');
$stmt->bindParam('name', get_some_value());
$stmt = $pdo->prepare('SELECT * FROM emp WHERE name LIKE :name');
$name = '%a%';
$stmt->bindParam('name', $name);
execute()実行時に値が評価されるため意図しない動作になることがある
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()
の場合は問題ありません。
$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();
$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内部の処理によって型が変更されます)
$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()
の場合は問題ありません。
$id = 1;
$stmt = $pdo->prepare('SELECT * FROM emp WHERE id = :id');
$stmt->bindParam('id', $id);
$stmt->execute();
var_dump($id); // int(1)
$id = 1;
$stmt = $pdo->prepare('SELECT * FROM emp WHERE id = :id');
$stmt->execute(['id' => $id]);
var_dump($id); // int(1)
- 補足
- MySQLでは
PDO::PARAM_INPUT_OUTPUT
の出力側 (値の受け取り) がサポートされていないため、bindParam
を使用しないといけない状況がほとんどありません。 - SQL Serverでは出力パラメータを使用することができます。(4番目の引数に長さの指定が必要)
- MySQLでは