この内容は古いバージョンです。最新バージョンを表示するには、戻るボタンを押してください。
バージョン:4
ページ更新者:guest
更新日時:2026-06-11 07:07:02

タイトル: PDO(PHP Data Objects)
SEOタイトル: PHP PDO 完全ガイド (Prepared Statement)

この記事の要点
  • PDO (PHP Data Objects) は PHP 標準のDB 抽象化レイヤー。MySQL / PostgreSQL / SQLite / Oracle / SQL Server に統一インターフェースでアクセス
  • 接続: new PDO("mysql:host=...;dbname=...", $user, $pass, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION])
  • Prepared Statement: $stmt = $pdo->prepare("SELECT ... WHERE id = ?"); $stmt->execute([$id]) で SQL Injection 防御
  • 結果取得: fetch(PDO::FETCH_ASSOC) / fetchAll(PDO::FETCH_ASSOC)連想配列モードがほぼ定番
  • トランザクション: beginTransaction() / commit() / rollBack()。例外捕捉で確実に rollBack

PDO とは

PHP のDB アクセス用標準拡張。MySQLi が MySQL 専用なのに対し、PDO は複数 DB を統一 API で扱えるのが特長です。Prepared Statement のサポートによりSQL Injection 対策が容易で、現代的な PHP プロジェクトでは PDO が標準です(Laravel 内部も PDO)。

接続

try {
    $pdo = new PDO(
        'mysql:host=localhost;dbname=mydb;charset=utf8mb4',
        'username',
        'password',
        [
            PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,  // ★ 必須
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,         // 連想配列
            PDO::ATTR_EMULATE_PREPARES   => false,                    // ★ ネイティブ Prepared
        ]
    );
} catch (PDOException $e) {
    error_log('DB connection failed: ' . $e->getMessage());
    http_response_code(500);
    exit('DB connection failed');
}

DB 別の DSN 文字列

DBDSN 例
MySQL / MariaDBmysql:host=localhost;dbname=mydb;charset=utf8mb4
PostgreSQLpgsql:host=localhost;port=5432;dbname=mydb
SQLitesqlite:/path/to/db.sqlite
SQL Serversqlsrv:Server=localhost;Database=mydb
Oracleoci:dbname=//localhost:1521/XE

SELECT - 結果を取得する

// 1 件取得
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = ?');
$stmt->execute([$id]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
// $user = ['id' => 1, 'name' => 'Taro', 'email' => 'taro@test.com']

// 全件取得
$stmt = $pdo->prepare('SELECT * FROM users WHERE active = ?');
$stmt->execute([1]);
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);

// ループ取得(メモリ節約)
$stmt = $pdo->prepare('SELECT * FROM logs WHERE date > ?');
$stmt->execute(['2025-01-01']);
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
    process($row);
}

// 1 カラムだけ
$stmt = $pdo->prepare('SELECT COUNT(*) FROM users');
$stmt->execute();
$count = $stmt->fetchColumn();

// 1 カラム全件
$stmt = $pdo->prepare('SELECT email FROM users');
$stmt->execute();
$emails = $stmt->fetchAll(PDO::FETCH_COLUMN);

Fetch モード

定数結果形式
PDO::FETCH_ASSOC連想配列 ['name' => 'Taro'] ★ 推奨
PDO::FETCH_NUM数値添字 [0 => 1, 1 => 'Taro']
PDO::FETCH_BOTH両方(既定)
PDO::FETCH_OBJstdClass $row->name
PDO::FETCH_CLASS指定クラス
PDO::FETCH_COLUMN指定カラムだけ
PDO::FETCH_KEY_PAIR2 カラムをキー/値の連想配列に

INSERT / UPDATE / DELETE

// INSERT
$stmt = $pdo->prepare(
    'INSERT INTO users (name, email, created_at) VALUES (?, ?, NOW())'
);
$stmt->execute(['Taro', 'taro@test.com']);
$newId = $pdo->lastInsertId();

// 名前付きバインド
$stmt = $pdo->prepare(
    'INSERT INTO users (name, email) VALUES (:name, :email)'
);
$stmt->execute([
    ':name'  => 'Hanako',
    ':email' => 'hanako@test.com',
]);

// UPDATE
$stmt = $pdo->prepare(
    'UPDATE users SET name = ? WHERE id = ?'
);
$stmt->execute(['Taro Yamada', $id]);
$affectedRows = $stmt->rowCount();

// DELETE
$stmt = $pdo->prepare('DELETE FROM users WHERE id = ?');
$stmt->execute([$id]);

トランザクション

複数 SQL の「全部成功するか、全部失敗するか」を保証:

$pdo->beginTransaction();
try {
    $stmt = $pdo->prepare('UPDATE accounts SET balance = balance - ? WHERE id = ?');
    $stmt->execute([100, $fromId]);

    $stmt = $pdo->prepare('UPDATE accounts SET balance = balance + ? WHERE id = ?');
    $stmt->execute([100, $toId]);

    $stmt = $pdo->prepare('INSERT INTO transfers (from_id, to_id, amount) VALUES (?, ?, ?)');
    $stmt->execute([$fromId, $toId, 100]);

    $pdo->commit();   // ★ 全部成功したらコミット
} catch (\Throwable $e) {
    $pdo->rollBack();  // ★ どれか失敗したらロールバック
    throw $e;
}

エラーモード

定数動作
PDO::ERRMODE_SILENTエラーを返すだけ(旧デフォルト、危険)
PDO::ERRMODE_WARNINGWarning 発行
PDO::ERRMODE_EXCEPTION★ PDOException 投げる(PHP 8.0+ で既定)

PDO::ATTR_EMULATE_PREPARES

Prepared Statement の挙動を制御する重要設定:

// true (デフォルト): PHP 側で SQL 文字列を組み立ててサーバに送る
// → DB がネイティブ Prepared に対応していなくても動く
// → 1 回の往復で済む(速い)
// → ★ ただし型推論が文字列扱いになりやすい

// false: DB のネイティブ Prepared Statement を使う
// → 1. プレースホルダ込み SQL を送る(パース)
// → 2. パラメータを送る(実行)
// → 2 往復だが安全 + キャッシュが効く

// ★ 推奨設定
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

// 型を厳密に指定
$stmt->bindValue(':age', $age, PDO::PARAM_INT);
$stmt->bindValue(':name', $name, PDO::PARAM_STR);

MySQLi との比較

項目PDOMySQLi
対応 DB12 種類MySQL のみ
APIOOP のみOOP + 手続き型
名前付きバインドOK (:name)NG (位置のみ)
非同期NGOK
ストアドプロシージャOKOK
推奨★ 汎用プロジェクトMySQL 専用 + 高度な機能必要時

Laravel の DB Facade との関係

use Illuminate\Support\Facades\DB;

// Laravel の DB は内部で PDO を使っている
$users = DB::select('SELECT * FROM users WHERE active = ?', [1]);

// Query Builder
$users = DB::table('users')->where('active', 1)->get();

// Eloquent
$users = User::where('active', 1)->get();

// 生 PDO が必要なら
$pdo = DB::connection()->getPdo();

FAQ

Q: query()prepare() + execute() の違いは?
A: query()固定 SQL 専用(ユーザー入力を含めるなら危険)。動的値を含めるなら必ず prepare() + execute()

Q: charset=utf8mb4 が必須?
A: 絵文字や 4 バイト UTF-8 を扱うなら必須。utf8 は実は最大 3 バイトで絵文字が化ける。

Q: 接続を永続化したい
A: PDO::ATTR_PERSISTENT => true。ただしトランザクション残存リスクがあるため Web アプリでは慎重に。

Q: バインドの数値 ID が文字列扱いされて遅い
A: PDO::ATTR_EMULATE_PREPARES => false に設定 + bindValue(... , PDO::PARAM_INT) で型明示。