[PHP] mysqliのbind paramに可変する引数を渡す

2019-03-16PHPSQL

プリペアドステートメントの「?」の数を動的に変化させる方法。

例えば、画面で検索条件が複数指定できて、「検索」的なボタンを押すたびに、 SQL の WHERE 句に指定する条件を変えたい場合。または、テーブルの各行にチェックボックスあって、チェックされた数だけ、 WHERE 句の条件が増える時に使う。

WHERE orderid IN (?,?) を可変にする

SELECT * FROM table WHERE id IN (?,?,?,....)
こんな SQL があって、 IN のあとの '?' を可変にしたい場合

「… による引数のアンパック」を使う

$stmt->bind_param($paramType, ...$stmtArray);
... つければ、配列を bind_param の引数に指定できる

PHP 5.6 以上から使える
PHP マニュアル 「… による引数のアンパック」

  // チェックボックスの値が配列で送られてくるとか
  $ids = filter_input(INPUT_POST, 'id', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY);
  $sql = 'SELECT * FROM table WHERE id IN ('.implode(',', array_fill(0, count($ids), '?')).')';
  $stmt = $mysqli->prepare($sql);
  // idが文字列だったら's'、数値だったら'i'
  $stmt->bind_param(str_repeat('s', count($ids)), ...$ids);
  $stmt->execute();
  $res = $stmt->get_result();

クエリ( $sql のとこ) にはバインドさせたいパラメータの数だけ '?' を出力しなければならないので、 array_fill(初めの要素のインデックス番号, 要素の数, 値)'?' の値をもつ配列を作り、 implode(連結文字 , 連結したい配列) で、カンマ区切りの '?' 連結文字列を出力する。

bind_param の第一引数は、バインド変数の数だけ型 ( s = string, i = integer, d = double, b = blob) を指定するので、 str_repeat(出力する文字列, 反復回数) で必要な個数分出力する。

call_user_func_array を使う

PHP 5.6 以下使ってる場合は、上記のアンパックは使えないのでこっち

call_user_func_arrayとは
(引数を持つ)関数の引数に配列を渡し、その関数を実行させることができる
PHP マニュアル call_user_func_array

// チェックボックスの値が配列で送られてくるとか
$ids = filter_input(INPUT_POST, 'id', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY);
$sql = 'SELECT * FROM table WHERE id IN ('.implode(',', array_fill(0, count($ids), '?')).')';
//IN句の組み立て
$sqlParams = [];
$sqlParams[0] = "";
foreach($ids as $key => $val) {
  $sqlParams[0] .= "s";
  $sqlParams[] = $val;
}
// bind_paramに渡す引数を参照渡しにする
$params = [];
foreach ($sqlParams as $key => $value) {
    $params[$key] = &$sqlParams[$key];
}
// プリペアドステートメント
if ($stmt = $mysqli->prepare($sql)) {
// 変数のバインド
call_user_func_array(array($stmt, 'bind_param'), $params);
$stmt->execute();

bind_param に渡す引数は参照渡しでなければならない
& をつけると、参照渡しになる

WHERE 句の検索条件が可変する

WHERE name=? and id=? とか WHERE id=? とか、 WHERE 句の条件が可変で組み立てが必要な時

「… による引数のアンパック」版

// POSTで送られてきた
$chk = filter_input(INPUT_POST, 'chk', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY);
$name = filter_input(INPUT_POST, 'name');
$sql='select * from tableA';
$sqlWhere = [];
// WHERE句の組み立て
if (!empty($chk) ) {
  $sqlWhere[] = 'id IN ('.implode(',', array_fill(0, count($chk), '?')).')';
}
if (!empty($name)) {
  $sqlWhere[] = "name = ?";
  $chk[] = $name;
}
if (count($sqlWhere) > 0) {
  $sql .= ' WHERE ' . implode(" and ", $sqlWhere);
}
$stmt = $mysqli->prepare($sql);
$stmt->bind_param(str_repeat( 's', count($chk)), ...$chk);
$stmt->execute();
$res = $stmt->get_result();

count($chk) するため、 $name$chk[] に追加している。


以上

Posted by Agopeanuts