【PHP】Preparedステートメントの書き方

PHP

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 'エラーが発生しました。';
  }
}

PHP

Posted by devsakaso