【PHP】プリペアドステートメントのバインド用配列には気を付けよう

PHP

やらかしてしまったので、繰り返さないよう戒めとして記しておきます。当然のこととは思いますが、PHPでSQLのプリペアドステートメントに値をバインドするために配列を渡す際には注意が必要です。

▼関係ないけど、筆者の好きなやらかし動画

前提条件

  • PHP8.2以降(筆者の環境は8.4.12)
  • PDOを使っています
  • PDOでDB接続可能な状態(筆者の環境はMySQL9.3.0)
  • CLIで操作していきます

名前つきプレースホルダの場合

このケースではやらかさなかったのですが、一応書いておきます。

プレースホルダの名前を配列キーとする連想配列を送り込みます。

例えば次のようなプリペアドステートメントを作ったとして、

SELECT * FROM users WHERE email = :email AND name = :name

バインドする値の配列は次のようにします。

$conditions = [
    "name" => "hoge001",
    "email" => "hoge001@example.com",
];

コードの例

            $dsn = sprintf(
                "%s:host=%s;dbname=%s;port=%d",
                "mysql",    // PDO driver: https://www.php.net/manual/ja/pdo.drivers.php
                "mysql",    // host
                "myapp",  // database
                3306        // port
            );
            $dbh = new PDO(
                $dsn,
                "user", // db user
                "pass"  // db password
            );
            $dbh->exec("SET NAMES utf8");
            $sql = "SELECT * FROM users WHERE email = :email AND name = :name";
            $conditions = [
                "name" => "hoge001",
                "email" => "hoge001@example.com",
            ];
            $statement = $dbh->prepare($sql);
            $statement->execute($conditions);
            $users = $statement->fetchAll();

名前なしプレースホルダの場合

必ず添え字が「連番」の配列を送り込む。

途中の添え字の番号が抜けていたりするとExceptionとなるので注意!

そうです。今回はこれでやらかしましたorz。

「array_unique()」や「array_filter()」のように返り値の配列の添え字が歯抜けになるような場合は、バインドする際には必ず「array_values()」で連番添え字にして渡しましょう。

例えば次のようなプリペアドステートメントを作ったとします。

SELECT * FROM users WHERE id IN (?, ?, ?)

値をバインドする際に渡す配列は例えば

$ids = [9, 1, 4];  // [0 => 9, 1 => 1, 2 => 4] と同値
// または
$ids = array_values([0 => 4, 3 => 9, 5 => 1]);

のように配列の添え字が連番のものを渡します。

次のように、添え字が歯抜けだと

$ids = [0 => 4, 3 => 9, 5 => 1];

次のようにExceptionスローされます。

PHP Fatal error:  Uncaught PDOException: SQLSTATE[HY093]: Invalid parameter number: parameter was not defined in ...

というわけで、バインドする際にメソッドの引数で渡す箇所で必ず「array_values()」を仕込んでおけば確実でしょう。

コードの例

            $dsn = sprintf(
                "%s:host=%s;dbname=%s;port=%d",
                "mysql",    // PDO driver: https://www.php.net/manual/ja/pdo.drivers.php
                "mysql",    // host
                "myapp",  // database
                3306        // port
            );
            $dbh = new PDO(
                $dsn,
                "user", // db user
                "pass"  // db password
            );
            $dbh->exec("SET NAMES utf8");
            $sql = "SELECT * FROM users WHERE id IN (?, ?, ?)";
            $ids = [0 => 4, 3 => 9, 5 => 1];
            $statement = $dbh->prepare($sql);
            $statement->execute(array_values($ids));
            $users = $statement->fetchAll();

まとめ

プリペアドステートメントに値をバインドする際には、

▼名前付きプレースホルダの場合 ⇒ プレースホルダ名をキーとする連想配列を渡す。

▼名前なしプレースホルダ ⇒ array_values()で添え字を連番にした配列を渡す。

▼やらかすと税金消えるかも(いや、ないだろ)

以上です。

  • 0
  • 0
  • 0
  • 0

コメント

タイトルとURLをコピーしました