PHP-DB - 12. プリペアドステートメント

続いてSQLの実行方法について学習します。ここではプリペアドステートメントという手法を使って、SQLを実行する方法を取り上げます。プリペアドステートメントとは「SQLの構文解析」と「解析済みSQLの実行」を分離する実行方式です。同様のSQLを繰り返し実行する場合、プリペアドステートメントを使用することで、SQLの実行速度を改善できます。

プリペアドステートメントの副次的な効果として、SQLインジェクションへの対策としての効果も期待できます。セキュリティについては後述します。

プリペアドステートメントの仕組み

プリペアドステートメントの名前に含まれれる "Prepared" とは "準備された" という意味です。SQLは実行時に、「SQLの構文解析」と「解析済みSQLの実行」という2つのフェーズで実行されます。プリペアドステートメントは「SQLの構文解析」フェーズだけを予め先に済ませておく(準備しておく)ことで、SQLの実行時には「解析済みSQLの実行」フェーズだけで処理が完了します。そのため同じ構文のSQLを再利用する場合に処理時間が短くなります。

またプリペアドステートメントを使う場合、SQLにプレースホルダ(パラメータ)を利用できます。

"insert into categories (id, title) values (?, ?)";

上記のSQLでは values 句に指定するデータの部分を ? としています。この ? のことをプレースホルダやパラメータなどと呼びます。プリペアドステートメントは「SQLの構文解析」フェーズにおいては、このようにデータ部分にプレースホルダを利用できます。また「解析済みSQLの実行」フェーズではプレースホルダに実際のデータを投入してSQLを実行するようにします。

それでは実際にプリペアドステートメントを利用するプログラム( pdo10.php )を作成してみましょう。

<?php
try {
    $dsn = "mysql:host=localhost;dbname=eldb;charset=utf8mb4";
    $username = "root";
    $password = "admin";
    $pdo = new PDO($dsn, $username, $password);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    $sql = "insert into categories (id, title) values (?, ?)";
    $ps = $pdo->prepare($sql);

    $id = 4;
    $title = "Photo";
    $ps->bindValue(1, $id, PDO::PARAM_INT);
    $ps->bindValue(2, $title, PDO::PARAM_STR);

    $ps->execute();
    $count = $ps->rowCount();
    echo "Count: $count" . PHP_EOL;
} catch (PDOException $e) {
    echo $e->getMessage() . PHP_EOL;
}

PHPでプリペアドステートメントを利用するには PDO クラスの prepare メソッドを利用します。

    $sql = "insert into categories (id, title) values (?, ?)";
    $ps = $pdo->prepare($sql);

prepare メソッドは引数にSQLを受け取り、解析済みのSQLを保持した PDOStatement インスタンスを戻り値に返します。

PDOStatement クラスには、 bindValue メソッドや、 execute メソッド、 rowCount メソッドといったプリペアドステートメントを操作するためのメソッドが用意されています。

このプログラムではまず bindValue メソッドを使ってプレースホルダにデータをバインドしています。

    $id = 4;
    $title = "Photo";
    $ps->bindValue(1, $id, PDO::PARAM_INT);
    $ps->bindValue(2, $title, PDO::PARAM_STR);

bindValue メソッドの名前に含まれるバインド( "bind" )とは「結びつける」という意味です。 bindValue メソッドによって ? (プレースホルダ)と実データを結びつけます。 bindValue メソッドは第1引数に、プレースホルダの番号(前から何個目の ? か)を指定し、第2引数にバインドする値、第3引数にデータ型を定数で指定します。

ここまでで解析済みのSQLとプレースホルダへの実データのバインドが完了しているので、 PDOStatement クラスの execute メソッドを呼び出すことで、解析済みのSQLを実行できます。

    $ps->execute();

またSQLによる更新件数(登録件数)を取得するには PDOStatement クラスの rowCount メソッドを呼び出します。

    $count = $ps->rowCount();

PDOStatement クラスの bindValue メソッドや execute は戻り値に論理値を返す点に注意してください。これらは正常に操作できた場合に true 、失敗した場合に false を返します。エラーレポートの設定を変更する( $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); )ことで異常時に例外( PDOException )をスローできます。

それでは実際にターミナルからプログラムを実行してみましょう。

$ php pdo10.php
Count: 1

実行結果から insert 文によって1件のレコードが追加されたことがわかります。また insert 文以外の update 文や delete 文においても、同様の方法でプリペアドステートメントを利用できます。 select 文を実行する方法については次節で取り上げます。

まとめ

  • プリペアドステートメントとは「SQLの構文解析」と「解析済みSQLの実行」を分離するSQLの実行方式
  • PDO インスタンスの prepare メソッドによってプリペアドステートメントを使用できる
  • 同様のSQLを再利用する場合はプリペアドステートメントを用いることで処理を高速化できる