3流プログラマのメモ書き

元開発職→社内SE→派遣で営業支援→開発戻り浦島太郎状態の三流プログラマのIT技術メモ書き。 このメモが忘れっぽい自分とググってきた技術者の役に立ってくれれば幸いです。

(PHP)PDOでforeachを使ってbindParamでパラメータを設定してた時の注意点

PDO経由でSQliteを使おうと思ってます。

Insert文で使う予定なんですが、汎用性を出すために、列名、値それぞれを配列に格納し、名前無ププレースホルダSQLを生成して、それにbindParamメソッドでパラメータをセットしていきます。

それで当初下記のようにしていました。

/* DBにデータを追加するSQL

* $tbl:テーブル名

* $clm_ary:列名の配列

* $value_ary:値の配列($clm_aryの要素Noにあったデータを入れること)

*/

function Add($tbl,$clm_ary,$value_ary){

try {

//sql文組立て

$sql_part1 = '';

for ($i = 0; $i < count($clm_ary); $i++) {

$sql_part1 = $sql_part1 . ' ? ';

if( $i < count($clm_ary)-1) {

$sql_part1 = $sql_part1 . ",";

}

}

$sql_part2 ='';

for ($i = 0; $i < count($value_ary); $i++) {

$sql_part2 = $sql_part2 . ' ? ';

if( $i < count($value_ary)-1) {

$sql_part2 = $sql_part2 . ",";

}

}

$sql = 'INSERT INTO ?( ' . $sql_part1 . ') VALUES (' . $sql_part2 . ')';

// DBに接続する

$db = new PDO( 'sqlite2:./test.sqlite', '', '' );

//SQL警告出すようにする

$db->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );

$stmt = $db->prepare( $sql );

//パラメータ指定(列名)

$i = 1;

$stmt->bindParam($i, $tbl);

//列名のパラメータを設定

foreach($clm_ary as $key => $clm) {

$i++;

$stmt->bindParam($i, $clm);

}

//値のパラメータを設定

foreach($value_ary as $key => $value) {

$i++;

$stmt->bindParam($i, $value);

}

//クエリ実行

$res = $stmt->execute();

$db=null;

}catch( PDOException $ex ) {

// DBアクセスができなかったとき

print 'DBにデータ追加失敗。 : ' . $ex->getMessage();

unset( $db );

die();

}

}

x

しかし、これだと下記のエラーとなってしまいました。

DBにデータ追加失敗。 : SQLSTATE[HY000]: General error: 1 SQL logic error or missing database

かなり悩んだんですが、PDOStatement->bindParamの罠で解決の糸口が見えてきました。

たしかに、PDOStatement->bindParaを見てみると、変数は参照としてバインドされ、PDOStatement::execute() がコールされたときのみ評価されます。とあります。

で、foreach 文は、foreachphp の foreach で参照渡しをするにはにあるように、配列のコピーに対して値渡しで処理をするようです。

なので下記のように、&を付けて参照渡しをすることで、ちゃんとSQLが実行できるようになりました。

//列名のパラメータを設定

foreach($clm_ary as $key => &$clm) {

$i++;

$stmt->bindParam($i, $clm);

}

unset($clm);

//値のパラメータを設定

foreach($value_ary as $key => &$value) {

$i++;

$stmt->bindParam($i, $value);

}

unset($value);

しかし、foreachで参照渡しにしたときは、foreachの$valueを参照で受けると思わぬバグを引き起こすにあるような罠があるので、使い終わったら参照を解除してるべきのようです。

(ちなみに、foreach で配列内の値を変更するときも参照渡しにしてやらないといけないようですね。。。記憶が全く飛んでました。)

これ解決するのに相当時間がかかってしまいました。。。