はじめに
mpyw さんの PHPでデータベースに接続するときのまとめ - Qiita という記事をいつも愛読しているのですが、初心者向けにはハードルが高い気がするので、接続に必要な最小限の内容にまとめてみました。
最終的には**【php のヤッカイなクセ】**も含めて理解する必要がありますが、学習当初は以下のテンプレートを使用することで、最低限守らなければならないルールは満たせます。
テンプレート
コードは、mpyw さんの記事を元に微調整しています。
try {
$pdo = new PDO(
'mysql:dbname=testdb;host=localhost;charset=utf8',//or charset=utf8mb4
'user',
'password',
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
// PDO::MYSQL_ATTR_MULTI_STATEMENTS => false,
]
);
$stmt = $pdo->prepare('SELECT * FROM users WHERE age = :age AND gender = :gender');
$stmt->bindValue(':age', (int)$age, PDO::PARAM_INT);
$stmt->bindValue(':gender', $gender);
$stmt->execute();
$result = $stmt->fetchAll();
} catch (PDOException $e) {
header('Content-Type: text/plain; charset=UTF-8', true, 500);
exit($e->getMessage());
}
var_dump($result);
記述の前提
接続には PDO を使用します。
また、PDO::ATTR_EMULATE_PREPARES
は false
、使用するcharset
はutf8
を想定しています。
(utf8mb4
を利用するケースも多いです)
定石
PDO::ATTR_ERRMODE
SQL実行でエラーが起こった際、例外を投げる PDO::ERRMODE_EXCEPTION
を選択します。このモードでは、エラーが発生した時点で スクリプトの実行を停止させ、catch
で、問題発生箇所特定のための情報を表示します。
*実用時は、catch
の内部はもっと考慮して記述する必要があります。
PDO::ATTR_DEFAULT_FETCH_MODE
結果セットにカラム名で添字を付けた配列を返す PDO::FETCH_ASSOC
を選択します。他のモードも便利なので、理解が進めば試してください。
参考:PDOフェッチパターン大全
PDO::ATTR_EMULATE_PREPARES
エミュレートを false
にするとは、いわゆる静的プレースホルダを使用することを意味します。true
にすると動的プレースホルダです。
現行の php ではtrue
が default ですが、過去のバージョンではfalse
が default だった時期があります。挙動に差があるので明記しておくのが適切です。
動的プレースホルダはクセがあるので、当面静的プレースホルダ(false
)を使用してください。
動的プレースホルダを使用する場合、複文を利用できないようにMYSQL_ATTR_MULTI_STATEMENTS => false
しておきましょう。
(使用できるバージョンが限定されるので、一応コメントにしています。7 系ならコメントを外してしまって良いです。)
参考1:PDOに複文実行を禁止するオプションが追加されていた-徳丸浩の日記
また、PDO::ATTR_EMULATE_PREPARES
の【現時点での】セキュリティ的な意味合いに関して質問したところ、徳丸さんに回答いただけたので紹介しておきます。
参考2:PDO::ATTR_EMULATE_PREPARES => false は必要か?
プリペアドステートメントを使おう
SQL 文に、直接変数を埋め込むのは、SQL インジェクションを引き起こす原因となります。
$pdo->query('SELECT * FROM users WHERE age = ' . $age . ' AND gender = ' . $gender);
PDO::prepare()
で、プリペアドステートメントを用意し、変数をバインドする方式を覚えてください。
バインドされる値に対して、引用符は自動付加されるので、PDO::prepare()
内に引用符は記述しないことに注意してください。
bindValue を使おう
プレースホルダに値をバインドするメソッドには、PDOStatement::bindValue
とPDOStatement::bindParam
がありますが、bindParam
は SQL 実行時に値がバインドされるため、直感的に分かりにくいです。(あと、副作用があったり^^;)慣れないうちは、bindValue
を使用しましょう。
#おまけ
PDO::PARAM_INT は罠 1
PDO::PARAM_INT は、以下のような実装になっているようです。
PDOでの数値列の扱いにはワナがいっぱい(2)
PDO::PARAM_INTはboolをintに変換(他は何もしない)
つまり、(string)'15' は、15 にならないのです。
この場合、'15' を渡すのはそもそもケシカラン!って考え方もありますが、テンプレートでは (int) でキャストする方法を記述しています。
LIKE も気をつけよう
MySQL であれば、LIKE はワイルドカードとして_%
が使用されます。
(ワイルドカード使用時にエスケープに使う文字のデフォルトは、データベースエンジンによって違うので注意!エスケープのための文字の指定もできるので興味があれば調べてみてください。)
単純にバインドすると100%
等の文字列の検索が正しく行われません。
ワイルドカードをエスケープしてやる必要があります。
$stmt = $pdo->prepare('SELECT * FROM users WHERE name LIKE ?');
$stmt->bindValue(1, '%' . addcslashes($name, '\_%') . '%', PDO::PARAM_STR);
まとめ
慣れれば大した事ではないし、そのうちフレームワークの機能で DB を扱うようになるので、あまり役に立つ情報ではないのですが、初心者が躓くポイントなのでまとめました。
最初に書いたとおり「最小限」の記述なので、最終的には元記事をよく読んで理解すると良いです。
-
PHP 7.2 以降のエミュレーション ON の場合、int へキャストされます。詳しくはあの PDO::PARAM_INT の挙動が変更になってる!を参照してください。 ↩