【PHP】Preparedステートメントの書き方
Preparedステートメントを使うことで、SQLインジェクションを防ぐことができます。
Preparedステートメントがどのようにして動いているのか、また書き方を紹介します。
Preparedステートメントの流れ
-- prepare() 事前準備させる
select * from テーブル名 where id = :id;
-- bind パラメータの値をセット
:id => 2
-- execute() クエリを実行
流れは以下のようになります。
- Prepare()でDB上に事前準備
- bindで値を設定
- execute()でクエリを実行
prepare()を使うことで、上のクエリをデータベース上に事前準備させることができます。ここがquery()やexec()との違いになります。
prepare()はデータベースはクエリが正しいかどうか、処理の仕方などを事前に準備しておきます。
あとは実行文とパラメータを渡せばすぐに実行できる状態にします。これをpre-compiledといいます。
そして、bindで値を設定して、execute()で事前準備されたデータベース上のクエリを実行します。
SQLインジェクションを防げる
prepare()を使うことで、構文のチェックは事前に終わっているため、それに沿わないものは実行されません。
たとえば、idにintを設定していれば、数値以外のものは受け付けないので、SQLインジェクションでSQLを流されても受け付けることがありません。
同じ構文の処理が早くなる
事前準備でSQLの構文をデータベース上にあげているため、同じクエリは二度目以降は省略されます。同じクエリがある場合もprepare()を使った方がパフォーマンスが向上します。
Preparedステートメントの書き方
-- コネクション生成
$conn = new PDO($dsn, $user, $pwd);
-- Preparedステートメントに未対応DBに擬似的に実行
$pst = $conn->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
setAttributeでPDOのATTR_EMULATE_PREPARESをfalseに設定することで、Preparedステートメントに未対応のデータベースでも擬似的にPDOの機能を使ってprepare()を実行できるようにします。
データ型を決めるbindValue
-- prepare()でDB上に事前準備
$pst = $conn->prepare("select * from mst_hoge where id = :id;");
-- 型を決める
$pst->bindValue(':id', $hoge_id, PDO::PARAM_INT);
prepare()では、:idなどとすることで、パラメータをセットできます。
そのパラメータのデータ型をbindValue()で決めることができます。
PARAM_INTなら、intです。
PARAM_STRなら、文字列です。
execute()で実行
$pst->execute();
-- bindValueでデータ型を指定しないときは直接指定
$pst->execute([
':id' => $hoge_id
]);
データ型を気にしない場合は、bindValue部分を省略して、値を直接execute()の中引数に対してパラメータを渡すことができます。
このとき、データ型は自動的に文字列になっていると理解するといいです。
Preparedステートメントの例文
html
<form action="<?php $_SERVER['REQUEST_URI']; ?>" method="POST">
Hoge ID: <input type="text" name="hoge_id">
<input type="submit" value="検索">
</form>
php
if (isset($_POST['hoge_id'])) {
try {
$hoge_id = $_POST['hoge_id'];
$user = 'hoge';
$pwd = 'hoge';
$host = 'localhost';
$dbName = 'hoge_db';
$dsn = "mysql:host={$host};port=8889;dbname={$dbName};";
$conn = new PDO($dsn, $user, $pwd);
$conn->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$conn->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$pst = $conn->prepare("select * from hoge_db.mst_hoges where id = :id;");
// $pst->bindValue(':id', $hoge_id, PDO::PARAM_STR);
$pst->execute([
':id' => $hoge_id
]);
$result = $pst->fetch();
// fetch()でデータがないときfalseになるためemptyでチェック
if (!empty($result) && count($result) > 0) {
echo "[{$result['name']}]です。";
} else {
echo "ないです。";
}
} catch (PDOException $e) {
echo 'エラーが発生しました。';
}
}