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 = "sqlite:eldb.sqlite3";
    $username = null;
    $passwd = null;
    $pdo = new PDO($dsn, $username, $passwd);
    $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を再利用する場合はプリペアドステートメントを用いることで処理を高速化できる