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を再利用する場合はプリペアドステートメントを用いることで処理を高速化できる