😃

プリペアドステートメントの出力(ログ,デバッグ用)

2023/12/02に公開

悩み

ログ用に出すのもそうだし、SQLでエラーが出た際に
実際にDBに発行されているSQLの内容を調べる方法がわからなかった。
変数がバインドされた後に実際に投げられているSQLはどうやって調べるの、という疑問があった。

結論

私はこのように出しました。
私の環境(PHP8.2.9 mysql 5.7.36)では動きました。

    //バインドされた後のプリペアドステートメントを出力(ログ用)
    public function getPreparedStmtAfterBind($pdo_stmt) {
        
        ob_start();
        $pdo_stmt->debugDumpParams();
        $content = ob_get_contents();
        ob_end_clean();
        
        preg_match('/Sent\sSQL:\s\[\d+\]\s(.*?)\sParams/s', $content, $matches);

        return $matches[1];
    }
    
    使用方法
    引数にはPDOに対してprepare($sql)をして、executeした時の戻り値である変数を指定
    $stmt = $pdo->prepare($sql);
    $result = $stmt->execute($params);
    echo getPreparedStmtAfterBind($result);
    とすれば帰ってくるはずです。
    

やっていること

  1. ob_start()で出力バッファリング
  2. PDOStatementインスタンス(上の例だと$result)に対してdebugDumpParamsメソッドを実行
  3. 2の実行結果を変数contentに格納
  4. ob_end_clean()でバッファをクリアして終了
  5. contentの中からpreg_matchでほしいところだけ抽出
  6. その結果を出力

出力バッファリング周りについてはこの方を参考にしました。
https://servercan.net/blog/2021/03/pdoで実行したsql文をdebugdumpparamsから抽出表示する/

経緯

よくPHPでSQLを投げる際、
$stmt = $pdo->prepare($sql); した後に
$stmt->execute($params);
とかするのですが、
たまにエラーになることがよくあります。

SQLSTATE[HY093]: Invalid parameter number: number of bound variables does not match number of tokens

与えた変数の数と、バインド変数の数があっていませんよ、というエラーです。

たいてい、

いやそんなことはない。あってるはずだ。

と思いながらデバッグを始めるんですけど。

実際にDBに発行されているSQL文って、ぱっとは出せないんですよね。
prepareメソッドを実行する際に引数として与える sql文を 出力して
埋め込む変数を1つずつechoなり var_dumpすれば見えなくもないですが、

見ずらい、めんどくさい

そこでなんとかして、出せないかといろいろ探していたところ
https://www.php.net/manual/ja/pdostatement.debugdumpparams.php
という求めていたものを見つけた!!
と思ったのですが、出力がまぁかゆいところに手が届かない、出力でした。

SQL: [322] UPDATE 
                    user 
                SET 
                    name = :name,
                    email = :email,
                    sex_type = :sex_type,
                    age_type = :age_type,
                    update_at = now()
                , icon_image_file = :icon_image_file WHERE id = :id
Sent SQL: [358] UPDATE 
                    user 
                SET 
                    name = 'あたし',
                    email = '**************************',
                    sex_type = '0',
                    age_type = '4',
                    update_at = now()
                , icon_image_file = '*************************' WHERE id = '**'
Params:  6
Key: Name: [5] :name
paramno=-1
name=[5] ":name"
is_param=1
param_type=2
Key: Name: [6] :email
paramno=-1
name=[6] ":email"
is_param=1
param_type=2
Key: Name: [9] :sex_type
paramno=-1
name=[9] ":sex_type"
is_param=1
param_type=2
Key: Name: [9] :age_type
paramno=-1
name=[9] ":age_type"
is_param=1
param_type=2
Key: Name: [16] :icon_image_file
paramno=-1
name=[16] ":icon_image_file"
is_param=1
param_type=2
Key: Name: [3] :id
paramno=-1
name=[3] ":id"
is_param=1
param_type=2

欲しいのは

Sent SQL: [358] UPDATE 
                    user 
                SET 
                    name = 'あたし',
                    email = '**************************',
                    sex_type = '0',
                    age_type = '4',
                    update_at = now()
                , icon_image_file = '*************************' WHERE id = '**'

この中の UPDATE から PARAMSの間なんですよね。
だから、preg_match()で抜き出しました。
正直、参考にした方の記事が無かったら思いつきませんでしたね。
ありがとうございます。

結果はこんな感じです。

UPDATE user SET name = 'あたし',email = '**************************', sex_type = '0',age_type = '4',update_at = now(), icon_image_file = '*************************' WHERE id = '**'

ちなみに、php自体は初心者なのでおかしな箇所があるかもしれません。
その時はご指摘ください。

Discussion