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