topic: db (PDO / SQL / マルチDB)
PDO 経由で RDB を操作するトピック。SQLite を既定 に、DOJO_DB_DRIVER=mysql|pgsql で MySQL / PostgreSQL にも接続できる構造で組んである。
このトピックで身につくこと
- PDO で DB に接続して SELECT / INSERT / UPDATE / DELETE を実行できる
- プレースホルダ (
?/:name) で値を差し込み、SQL インジェクションを防げる fetch/fetchAll/fetchColumnのモードを使い分けられる- トランザクション (
beginTransaction/commit/rollBack) で複数 SQL をまとめられる - SQLite / MySQL / PostgreSQL の DSN の違い と 方言の差 を読み分けられる
前提知識の要点
連想配列 (['key' => 値]) が読めれば start できる。クラス・例外は必須ではない (new PDO(...) と try/catch だけ書ければよい)。
// PDO の最小コード
$pdo = new PDO('sqlite:' . getenv('DOJO_DB_PATH'));
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $pdo->prepare('SELECT name FROM users WHERE age >= ?');
$stmt->execute([20]);
foreach ($stmt as $row) {
echo $row['name'], "\n";
}new PDO(<DSN>)で接続。DSN は 「どの DB のどこに繋ぐか」 を表す文字列setAttribute(ERRMODE_EXCEPTION)を付けて例外で気付く形にする (これ無しは事故の元)- 値を SQL に埋めるときは 必ず
prepare+execute。"WHERE name = '$name'"と連結すると SQL インジェクションになる
chapter 一覧
| # | chapter | 内容 |
|---|---|---|
| 1 | ch01-what-is-db/ |
DB / テーブル / 行 / 列 の概念 (ドリルなし) |
| 2 | ch02-pdo-connect/ |
PDO で DB に接続する / DSN |
| 3 | ch03-select-basic/ |
SELECT で行を取り出す |
| 4 | ch04-select-where/ |
WHERE / プレースホルダ / prepare + execute |
| 5 | ch05-insert/ |
INSERT で 1 件追加する |
| 6 | ch06-update-delete/ |
UPDATE と DELETE / rowCount |
| 7 | ch07-fetch-modes/ |
FETCH_ASSOC / FETCH_NUM / fetchColumn |
| 8 | ch08-transactions/ |
トランザクション (beginTransaction / commit / rollBack) |
| 9 | ch09-sql-injection/ |
プリペアド vs 連結 / 安全な書き方 |
| 10 | ch10-multi-db/ |
SQLite / MySQL / PostgreSQL の DSN 比較 |
| 11 | ch11-mysql-specific/ |
MySQL 固有の文法と運用 |
| 12 | ch12-postgres-specific/ |
PostgreSQL 固有の文法と機能 |
合計 12 chapter / 25 drill / 所要 3〜4 時間。
採点ランナーと DB の関係
採点ランナーは 既定で SQLite を一時ファイルに作って tests/setup.sql を流し込み、そのパスを環境変数 DOJO_DB_PATH として受講生コードに渡す。
# 既定 (SQLite)
php scripts/grade.php topics/db/ch03-select-basic/drill/01-select-all/
# MySQL コンテナ側で採点
DOJO_DB_DRIVER=mysql php scripts/grade.php topics/db/ch11-mysql-specific/drill/01-...
# PostgreSQL コンテナ側で採点
DOJO_DB_DRIVER=pgsql php scripts/grade.php topics/db/ch12-postgres-specific/drill/01-...
ch11 / ch12 は その DB 専用の SQL (例: MySQL の AUTO_INCREMENT、PostgreSQL の RETURNING) を扱うので、setup.mysql.sql / setup.pgsql.sql で方言差を吸収している。Docker は docker/compose.yml を docker compose up -d で立てる (詳細は docker/README.md)。
進め方
- 各 chapter の
slide.mdを読む (4〜5 分) drill/配下の問題を順番に解く (ch01 は読むだけ)- 採点:
php scripts/grade.php topics/db/<chapter>/drill/<drill>/ - MySQL / PostgreSQL を試したい時だけ Docker を立てて
DOJO_DB_DRIVER=...を付ける
つまづきポイント
| 症状 | 多くの原因 |
|---|---|
SQLSTATE[HY000] で何が起きたか分からない |
setAttribute(ERRMODE_EXCEPTION) を付け忘れている。例外で原因を出させる |
prepare した SQL が動かない |
プレースホルダ ? の数と execute([...]) の配列要素数がズレている |
| 値が文字列リテラルとして埋まらない | プレースホルダはクォート不要。"WHERE name = ?" に対して execute(['太郎']) |
LIMIT ? が SQLSTATE エラー |
一部 PDO + MySQL で型推論が効かない。bindValue($p, $v, PDO::PARAM_INT) で明示 |
last_insert_id が 0 / RETURNING が無い |
MySQL は lastInsertId()、PostgreSQL は INSERT ... RETURNING id。DB ごとに違う |
setup.sql の DDL が SQLite で通らない |
AUTO_INCREMENT (MySQL) / SERIAL (PostgreSQL) は SQLite では別表記。INTEGER PRIMARY KEY AUTOINCREMENT を使う |
関連トピック
| トピック | 関係 |
|---|---|
| array-assoc | fetch(PDO::FETCH_ASSOC) の戻り値が連想配列 |
| exception | PDO は例外モード前提で書く |
| web | Web 経由で DB に書き込む実践 |
| session-cookie | ログイン情報を DB に置き、Session で参照 |
トピックを並列で参照する全体地図は TOPICS_INDEX.md にある。
案件 (dojo_map.tsv) での参照
topic_slug chapter_dir
db topics/db/ch02-pdo-connect
db topics/db/ch04-select-where
db topics/db/ch08-transactions
db topics/db/ch09-sql-injection
...
slug db で参照可。11-db / db どちらの path でもアクセスできる (シンボリックリンク)。
このレッスンの章
- ch01 ch01 — DB とは / テーブル・行・列
- ch02 ch02 — PDO で SQLite 接続 / DSN 比較
- ch03 ch03 — SELECT 基本 / fetchAll / ループ
- ch04 ch04 — WHERE / プレースホルダ / prepare + execute
- ch05 ch05 — INSERT で 1 件追加する
- ch06 ch06 — UPDATE と DELETE
- ch07 ch07 — fetch のモード
- ch08 ch08 — トランザクション
- ch09 ch09 — SQL インジェクション / プリペアド vs 連結
- ch10 ch10 — DSN 比較 / 複数 DB 接続
- ch11 ch11 — MySQL 固有の文法と運用
- ch12 ch12 — PostgreSQL 固有の文法と機能