PHP
MySQL

PDOのプレースホルダーの有効性の検証

前提条件

  1. PHP内部の文字コードはUTF-8
  2. MySQLのversioinが5.1
  3. ストレージエンジンがMyISAM
  4. 今回はVARCHAR、INT、BLOBで検証

テーブルがこんな感じです

mysql
CREATE TABLE IF NOT EXISTS TEST (
    TESTID      INT(11)       PRIMARY KEY NOT NULL,
    TESTNAME    VARCHAR(255)  DEFAULT NULL,
    TESTNUMBER  INT(11)       DEFAULT 0,
    TESTBLOB    BLOB          DEFAULT NULL,
    DELFLG      INT(1)        DEFAULT 0     NOT NULL,
    UPDDATE     DATETIME      NOT NULL,
    INPDATE     TIMESTAMP     DEFAULT CURRENT_TIMESTAMP NOT NULL
);

とりあえずいろんな記号を入れてみる

日本語、半角カナ、記号、機種依存文字それぞれ入れてみました。
まずはインサート

insert.php
    $sql = <<<SQL
        INSERT INTO TEST (
            TESTID,
            TESTNAME,
            TESTNUMBER,
            TESTBLOB,
            DELFLG, UPDDATE, INPDATE
        ) SELECT
            (SELECT IFNULL(MAX(TESTID), 0)+1 FROM TEST),
            :TESTNAME,
            :TESTNUMBER,
            :TESTBLOB,
            0, NOW(), NOW()
        FROM DUAL;

SQL;

    $TESTNAME   = "てすとテスト!\"#$%&'()=~|`{+*}<>?_-^\@[;:],./①";
    $TESTNUMBER = "てすとテスト!\"#$%&'()=~|`{+*}<>?_-^\@[;:],./①";
    $TESTBLOB   = "てすとテスト!\"#$%&'()=~|`{+*}<>?_-^\@[;:],./①";

    $stmt = $GLOBALS['MYDB']->prepare($sql);

    $stmt->bindParam(':TESTNAME',    $TESTNAME,    PDO::PARAM_STR);
    $stmt->bindParam(':TESTNUMBER',  $TESTNUMBER,  PDO::PARAM_INT);
    $stmt->bindParam(':TESTBLOB',    $TESTBLOB,    PDO::PARAM_LOB);
    $stmt->execute();

selectしてデータを確認してみます。

    $sql = <<<SQL
        SELECT 
        TESTID, TESTNAME, TESTNUMBER,TESTBLOB
        FROM TEST 
        ORDER BY TESTID DESC LIMIT 1;
SQL;

    $stmt = $GLOBALS['MYDB']->prepare($sql);
    $stmt->execute();
    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
        $DB_TESTNAME    = $row['TESTNAME'];
        $DB_TESTNUMBER  = $row['TESTNUMBER'];
        $DB_TESTBLOB    = $row['TESTBLOB'];
    }

    $finfo = new finfo(FILEINFO_MIME_TYPE);
    $type  = $finfo->buffer($DB_TESTBLOB);

    $txt = <<<TXT
<pre>
DB_TESTNAME:{$DB_TESTNAME}
DB_TESTNUMBER:{$DB_TESTNUMBER}
</pre>
TXT;
    echo $txt;
    echo '<img src="data:'. $type .';base64, ' . base64_encode($DB_TESTBLOB) . '" >';

結果がこちら

DB_TESTNAME:てすとテスト!"#$%&'()=~|`{+*}<>?_-^\@[;:],./①
DB_TESTNUMBER:0   
<img src="data:text/plain;base64, 44Gm44GZ44Go776D7729776EISIjJCUmJygpPX58YHsrKn08Pj9fLV5cQFs7Ol0sLi/ikaA=">

INTとBLOBに無理やり突っ込みましたが、エラーを吐かずに処理してくれました。
画像はURLで開くと文字化けしたものが出てきました。
VARCHARに関しては機種依存文字でもきちんと文字としてinsertしてくれました。

制御文字コードを入れてみる

制御文字コードの間に日本語を入れて確認してみます。

\0い\aう\bえ\tお\nか\vき\fく\rけ\eこ\r\nさ

結果がこちら

水平タブ、改行コードは有効で、null文字はきちんと削除されました。
画像はURLを開くとダウンロードが始まり、中身を見ると上記で表示されたような文字になっていました。

文字コード 名前/意味 結果
\0 Null文字 削除
\a ベル そのまま
\b 後退 そのまま
\t 水平タブ 有効
\n 改行 有効
\v 垂直タブ 削除
\f 書式送り 削除
\r 復帰 有効
\e エスケープ そのまま
\r\n 改行 有効

文字化けを入れてみる

    $str = mb_convert_encoding("あいうえお", 'SJIS', 'UTF-8');

    echo $str;    //����������

    $TESTNAME   = $str;
    $TESTNUMBER = $str;
    $TESTBLOB   = $str;
    $stmt = $GLOBALS['MYDB']->prepare($sql);

    $stmt->bindParam(':TESTNAME',     $TESTNAME,     PDO::PARAM_STR);
    $stmt->bindParam(':TESTNUMBER',   $TESTNUMBER,     PDO::PARAM_INT);
    $stmt->bindParam(':TESTBLOB',     $TESTBLOB,     PDO::PARAM_LOB);
    $stmt->execute();

結果がこちら

DB_TESTNAME:
DB_TESTNUMBER:0
<img src="data:text/plain;base64, gqCCooKkgqaCqA==" >

文字化けは一文字も入らずに、画像を開くと全く別の記号になっていました。

bindParamの第三引数(data_type)を指定せずに入れてみる

      $stmt = $GLOBALS['MYDB']->prepare($sql);

      $stmt->bindParam(':TESTNAME',   $TESTNAME);
      $stmt->bindParam(':TESTNUMBER', $TESTNUMBER);
      $stmt->bindParam(':TESTBLOB',   $TESTBLOB);

結論としては先ほどの2パターンの文字や記号、制御コードを入れた場合と結果は変わりませんでした。

まとめ

今回の検証で、プレースホルダはかなり有効な事が分かりました。
PHPでDBを扱う場合は必ず使ってください。
ちなみにヒアドキュメント等で記述した文字列の改行も1文字に含まれるので気を付けてください。
bindParamは参照渡し、bindValueは値渡しで基本的にはbindValueを使ってください。
以前bindParamを使ってupdateしたら、すべて同じ値で上書きされたことがありましたので、ご注意ください。