PHP

【PHP】変数を使ったbindParamをすると値が反映されない!?

PHP-アイキャッチ

こんにちは!

今月も早折り返しです。

日々の過ぎるスピードの速さについていけないゆーたろうです。

さて、今回は、PHPでPDOを使ったバインド時に分かったことの紹介です。

直面した問題:ループ変数付きでbindParamをすると、なぜか最後の値だけ反映される

下記のプログラムをご覧ください。


$pref = array("北海道", "東京", "沖縄");

$sql = "SELECT * FROM table WHERE ";

//配列の数だけOR文を追加
for($i = 0; $i < count($pref); $i++)
{
	$sql .= " pref=:pref" . $i . " OR ";
	$i++;
}

//末尾のORが不要なので削除
$sql = str_replace("OR @", "", ($sql .= "@"));

$stmt = $this->pdo->prepare($sql);

$i = 0;
foreach($pref as $todouhuken)
{
	$stmt->bindValue(':pref'.$i      , $todouhuken , PDO::PARAM_STR);
	$i++;
}


このSQL文を展開すると以下のようになります。

SELECT * FROM table WHERE pref=:pref OR pref=:pref1 OR pref=:pref2

つまり、ここでしたいことは、配列としてわたってきた都道府県名に引っかかるレコードを全て引っ張ってきたいのです。

しかし、結果として取ってこれたのは「沖縄」に関するレコードだけ。

バインド前のSQL文を吐き出してみましたが、ちゃんと上記のようになっていました。

調査

いつものごとく、原因調査開始です。

調査1:fetchAll()になっていない?

よくあるミスとして、

  • $stmt->fetchAll()
  • $stmt->fetch()

の呼び先が違っていることが挙げられます。

  • 前者は、該当するデータ全て引っ張ってくる命令
  • 後者は、1番最初のレコードだけを引っ張ってくる命令

です。

しかし、今回はちゃんと前者の「$stmt->fetchAll()」になっていました。

疑って思ったのですが、そもそも「$stmt->fetch()」だった場合、先頭のレコードしか取得されません。

そのため、もし今回のバグが呼び先関数の違いだったら、「北海道」のレコードが引っ張られてくるはずです。

なので、「$stmt->fetchAll()」「$stmt->fetch()」違いではないと気づきました。

調査2:引数にわたってくる配列の中身がおかしい?

この処理は関数化しています。

先ほどのプログラム例では直書きしていますが、$prefは引数から渡ってきた配列です。

そのため、子の関数に渡ってくる途中で最後の沖縄文字列しか来ないのかな?と思い、こんな感じで調べてみました。


var_dump($pref);
prefFunction($pref)
{
	var_dump($pref);
	:
	ループによるバインド処理
}

しかし、ちゃんと、どちらも都道府県名が入っていることが分かりました。

うーむ?

調査3:そういえば、PHPにおけるバインド処理をする関数は2種類あることを思い出す!

はい、そうなんです。

PHPでバインド処理を行える関数は2種類あるのです。

  • bindParam()
  • bindValue()

このことは以前から知っていたのですが、今まではbindParamの方を慣習的に使っていたのです。

が、それが今回アダとなったようでした。

bindParam()とbindValue()の違い

この技術記事のように、bindParamは全て終わってから変数が評価されます。

一方、bindValueは都度、変数が評価されるのです。

つまり、今回私がやろうとしていた、ループを使ったバインド処理では、後者のbindValue()の方を使うべきだったのです!!

そして、bindParam箇所をbindValue()に置き換えたところ、無事に北海道のレコード、東京のレコード、沖縄のレコードをDBから取得することができました。

めでたしめでたし。

まとめ

やはり、「慣習」的にやるのはだめですね汗

いつか必ずボロが出ます。

私自身、今回でいえば、両者の存在は知ってはいましたが、実際どんな処理の変化になるのかは動作確認していなかったので、理解はしてなかったんだと、気づくことができました。

それでも、今、知ることができたのは大きな成長です。

点と点が線で結ばれた瞬間でもあるので、収穫でした。

今日、両者の違いを身をもって知れたので、今後の開発の際は意識していこうっと!