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