PDO::bindParamの挙動を知る

PDOを使って、よくあるコードを書いていた。

<?php
/* 前略 */
    public function getData($data_id, $user_id = -1)
    {
        // $pdo : PDO object
        $sql = "SELECT * FROM data" .
               "  WHERE data_id = :data_id" .
               // ↓テストのためにコメントアウトしてみる
//             "    AND user_id = :user_id" .
               "    AND valid   = true" ;
        $pdoStatement = $pdo->prepare($sql) ;
        $pdoStatement->bindParam(":data_id", $data_id, PDO::PARAM_INT) ;
        $pdoStatement->bindParam(":user_id", $user_id, PDO::PARAM_INT) ;
        if ($pdoStatement->execute())
        {
            return $pdoStatement->fetchAll() ;
        }
        return false ;
    }
/* 後略 */
?>

コメントの通りuser_idをチェックする条件を外してみた。つまり、プレイスホルダがないステートメントにパラメータをバインドしようとしている、という状態。bind先がないなら無視してくれてもよさそうなものだが…

Red Hat EL5 / PHP 5.2.5 / PostgreSQL 8.3.0
>この環境では問題なく動作する。

WindowsXP / PHP 5.2.5(XAMPP 1.6.6a) / PostgreSQL 8.3.1
>この環境ではWarningが発生し、クエリは失敗する。

Warning: PDOStatement::execute()
  [function.PDOStatement-execute]: SQLSTATE[HY093]:
  Invalid parameter number:
  number of bound variables does not match number of tokens in
  C:\path\to\appdir\modelclass.php on line ***

つまり、Windows環境でPDOを使う場合クエリのプレイスホルダとバインドするパラメータはきちんと数を合わせる必要がある、ということ。ただ、こちらの記事では逆で、Windows上では数があってなくてもOK / Fedoraに持ってくとNG、という挙動をしてる模様。どちらにしても数をあわせておけば問題ないので、そのようなコードを書くようにしよう。

逆を確認するためにこんなコードに変更してみる。プレイスホルダに対してバインドするパラメータが不足なので、当然エラーが出るはずなのだが…

<?php
/* 前略 */
        $sql = "SELECT * FROM data" .
               "  WHERE data_id = :data_id" .
               "    AND user_id = :user_id" .
               "    AND valid   = true" ;
        $stmt = $pdo->prepare($sql) ;
        $stmt->bindParam(":data_id", $data_id, PDO::PARAM_INT) ;
//      $stmt->bindParam(":user_id", $user_id, PDO::PARAM_INT) ;
/* 後略 */
?>

Red Hat EL5 / PHP 5.2.5 / PostgreSQL 8.3.0
>クエリは失敗する。エラー情報をダンプしてみると…

<?php
var_dump($stmt->errorInfo()) ;
/*
array(3) {
  [0]=>
  string(5) "08P01"
  [1]=>
  int(7)
  [2]=>
  string(103) "ERROR:  bind message supplies 1 parameters, but prepared statement "pdo_pgsql_stmt_03d03950" requires 2"
}
*/
?>

WindowsXP / PHP 5.2.5(XAMPP 1.6.6a) / PostgreSQL 8.3.1
>先ほどと同じWarningが発生し、クエリは失敗する。

RedHat上で同僚が開発していたものをWindows上に持ってきたらこのような挙動が判明した。

さらに。

プレイスホルダを使わないクエリを実行するコードはこんな感じになるのだが…

<?php
/* 前略 */
        $sql = "SELECT * FROM data" .
               "  WHERE valid   = true" .
               "  LIMIT 10" ;
        // プリペアドステートメントは不要なので「query」
        $stmt = $pdo->query($sql) ;
        if ($stmt->execute())
        {
            return $stmt->fetchAll() ;
        }
        return false ;
/* 後略 */
?>

WindowsXP / PHP 5.2.5(XAMPP 1.6.6a) / PostgreSQL 8.3.1
PDOStatement#executeが失敗する。
PDO#queryPDO#prepare に変更すると成功する。

前代フレームワーク開発当時の環境はXAMPPではなく単品でインストールしたPHP(5.2.2)で、当然ながら問題なく動作していた。「PHP5.2.6(for Windows)に注意する」に続き「注意しよう」でまとめることになってしまうが、些細な環境の違いだと思っても主力級の命令の挙動が変わってしまうことがあるので注意しよう。

3 Comments

  1. 匿名 8月 1, 2008 6:55 pm  返信

    なぜ query の後に execute を実行するのか?
    成功の可否は errorCode でチェックすべきでは?

  2. kwappa.856 8月 1, 2008 7:54 pm  返信

    ご指摘ありがとうございます。
    気になったので調べた結果、前代フレームワークの実装時に勘違いをして、
    そのままイディオムとして使っているコードでした。
     # queryもprepare同様ステートメントの準備をするだけだと思っていた
    あまりの恥ずかしさにエントリごと削りたい気分ですが、
    近日調査して修正しようと思います。
    どちらかというとWindows環境のほうが正しい挙動なのですね。

Leave a comment

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です