PDOによるデータベース接続

このページには,PDOの基本説明に加えて,CRUDシステムのPDO版を追記しています。

このページの内容

PDOとは

PDO(PHP Data Objects)は,PHPからデータベースを操作するための標準的なクラスライブラリです。MySQL専用のmysqliとは異なり,PDOはMySQL,PostgreSQL,SQLiteなど複数のデータベースに対して,ほぼ同じ書き方で接続・問い合わせを行えるように設計されています。

本教材では第4章でmysqliを使ってPHPとMySQLの接続を説明してきましたが,第5章のWebアプリケーション制作では,SQLインジェクション対策を組み込みやすいPDOのprepareexecute,プレースホルダを使う形に統一します。

PDOでMySQLに接続する

PDOでは,接続先データベースの種類,ホスト名,データベース名,文字コードをDSN(Data Source Name)として指定します。MySQLのtest_dbに接続する最小例は次の通りです。

<?php
$dsn = 'mysql:host=localhost;dbname=test_db;charset=utf8mb4';
$user = 'root';
$password = '';

try {
    $dbh = new PDO($dsn, $user, $password, [
        PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::ATTR_EMULATE_PREPARES   => false,
    ]);
} catch (PDOException $e) {
    exit('データベース接続に失敗しました。');
}
?>

PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTIONを指定しておくと,SQL文や接続に問題があった場合に例外として検出できます。PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOCは,検索結果を連想配列として取り出すための指定です。

プリペアドステートメントとプレースホルダ

PDOを使う大きな利点の一つは,SQL文に外部から入力された値を直接連結せず,プレースホルダを使って安全に渡せる点です。例えば,フォームから受け取った$idを使って検索する場合は,次のように書きます。

<?php
$sql = 'SELECT * FROM animal WHERE id = :id';
$stmt = $dbh->prepare($sql);
$stmt->execute([':id' => $id]);
$animal = $stmt->fetch();
?>

:idがプレースホルダです。SQL文の中に変数を直接埋め込まないため,SQLインジェクションを避けやすくなります。

INSERT文の実行

データを追加するときも同じく,prepareexecuteを使います。

<?php
$sql = 'INSERT INTO animal (id, name, size)
        VALUES (:id, :name, :size)';
$stmt = $dbh->prepare($sql);
$stmt->execute([
    ':id'   => 4,
    ':name' => 'クジラ',
    ':size' => 2000,
]);
?>

SELECT文の実行

複数行の検索結果を取り出す場合は,fetchAllまたはwhile文とfetchを使います。

<?php
$sql = 'SELECT * FROM animal ORDER BY id ASC';
$stmt = $dbh->query($sql);
$animals = $stmt->fetchAll();

foreach ($animals as $animal) {
    echo htmlspecialchars($animal['name'], ENT_QUOTES, 'UTF-8');
    echo '<br>';
}
?>

SQL文に外部入力が含まれない単純な検索ではqueryも使えます。一方,フォーム入力やURLパラメータなど外部から受け取った値を使う場合は,原則としてprepareexecuteを使います。

UPDATE文とDELETE文

更新や削除では,対象レコードを指定するidなどに外部入力が入ることが多いため,必ずプレースホルダを使うようにします。

<?php
// 更新
$sql = 'UPDATE animal SET name = :name, size = :size WHERE id = :id';
$stmt = $dbh->prepare($sql);
$stmt->execute([
    ':name' => 'シロナガスクジラ',
    ':size' => 2500,
    ':id'   => 4,
]);

// 削除
$sql = 'DELETE FROM animal WHERE id = :id';
$stmt = $dbh->prepare($sql);
$stmt->execute([':id' => 4]);
?>

CRUDシステムのPDO版

ここまでに作成したCRUDシステムは,従来の教材の流れではmysqliを使っています。PDO版では,SQL文にフォームの値を直接連結せず,prepare()execute()で値を渡します。以下に,PDO版CRUDシステムの完成例を示します。

PHPファイルへ直接リンクして実行させるのではなく,このページ上で主要ソースをHighlight.jsにより表示します。実行用ファイル一式はZIPとして配布します。

配布ファイル

構成

minicrud_pdo/
  index.html
  dbconnect.php
  select.php
  insert.php
  select_delete.php
  delete.php
  select_update.php
  update.php
  create_animal.sql
  README.txt

重要な考え方

dbconnect.php

<?php
// Chapter4 CRUDシステム PDO版 共通接続ファイル
// 環境に合わせて,データベース名・ユーザー名・パスワードを変更してください。
$dsn = 'mysql:host=localhost;dbname=sample;charset=utf8mb4';
$user = 'root';
$password = '';

try {
    $dbh = new PDO($dsn, $user, $password, [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::ATTR_EMULATE_PREPARES => false,
    ]);
} catch (PDOException $e) {
    exit('データベース接続に失敗しました。');
}

function h(?string $value): string
{
    return htmlspecialchars($value ?? '', ENT_QUOTES, 'UTF-8');
}

insert.php

<?php
require_once __DIR__ . '/dbconnect.php';

$message = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $name = trim($_POST['name'] ?? '');
    $size = filter_input(INPUT_POST, 'size', FILTER_VALIDATE_INT);
    $memo = trim($_POST['memo'] ?? '');

    if ($name === '' || $size === false || $size === null) {
        $message = '名前とサイズを正しく入力してください。';
    } else {
        $sql = 'INSERT INTO animal (name, size, memo) VALUES (:name, :size, :memo)';
        $stmt = $dbh->prepare($sql);
        $stmt->execute([
            ':name' => $name,
            ':size' => $size,
            ':memo' => $memo,
        ]);
        $message = 'データを追加しました。';
    }
}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>データ追加 PDO版</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container my-4">
    <h1>データ追加</h1>
    <p><a href="index.html">トップページへ戻る</a></p>
    <?php if ($message !== ''): ?>
        <div class="alert alert-info"><?= h($message) ?></div>
    <?php endif; ?>
    <form method="post" action="insert.php">
        <div class="mb-3">
            <label for="name" class="form-label">name</label>
            <input type="text" name="name" id="name" class="form-control" required>
        </div>
        <div class="mb-3">
            <label for="size" class="form-label">size</label>
            <input type="number" name="size" id="size" class="form-control" required>
        </div>
        <div class="mb-3">
            <label for="memo" class="form-label">memo</label>
            <textarea name="memo" id="memo" class="form-control" rows="3"></textarea>
        </div>
        <button type="submit" class="btn btn-primary">追加</button>
    </form>
</div>
</body>
</html>

select_delete.php

<?php
require_once __DIR__ . '/dbconnect.php';

$sql = 'SELECT id, name, size, memo FROM animal ORDER BY id';
$stmt = $dbh->query($sql);
$animals = $stmt->fetchAll();
?>
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>削除データ選択 PDO版</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container my-4">
    <h1>削除データ選択</h1>
    <p><a href="index.html">トップページへ戻る</a></p>
    <table class="table table-bordered table-striped">
        <thead>
        <tr><th>操作</th><th>id</th><th>name</th><th>size</th><th>memo</th></tr>
        </thead>
        <tbody>
        <?php foreach ($animals as $animal): ?>
            <tr>
                <td>
                    <form method="post" action="delete.php" onsubmit="return confirm('本当に削除しますか?');">
                        <input type="hidden" name="id" value="<?= h((string)$animal['id']) ?>">
                        <button type="submit" class="btn btn-danger btn-sm">削除</button>
                    </form>
                </td>
                <td><?= h((string)$animal['id']) ?></td>
                <td><?= h($animal['name']) ?></td>
                <td><?= h((string)$animal['size']) ?></td>
                <td><?= h($animal['memo']) ?></td>
            </tr>
        <?php endforeach; ?>
        </tbody>
    </table>
</div>
</body>
</html>

delete.php

<?php
require_once __DIR__ . '/dbconnect.php';

$id = filter_input(INPUT_POST, 'id', FILTER_VALIDATE_INT);
$message = '削除対象のidが正しくありません。';

if ($id !== false && $id !== null) {
    $sql = 'DELETE FROM animal WHERE id = :id';
    $stmt = $dbh->prepare($sql);
    $stmt->execute([':id' => $id]);
    $message = 'データを削除しました。';
}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>削除結果 PDO版</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container my-4">
    <h1>削除結果</h1>
    <div class="alert alert-info"><?= h($message) ?></div>
    <p><a href="select_delete.php">削除画面へ戻る</a></p>
    <p><a href="index.html">トップページへ戻る</a></p>
</div>
</body>
</html>

select_update.php

<?php
require_once __DIR__ . '/dbconnect.php';

$sql = 'SELECT id, name, size, memo FROM animal ORDER BY id';
$stmt = $dbh->query($sql);
$animals = $stmt->fetchAll();
?>
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>更新データ選択 PDO版</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container my-4">
    <h1>更新データ選択</h1>
    <p><a href="index.html">トップページへ戻る</a></p>
    <table class="table table-bordered table-striped align-middle">
        <thead>
        <tr><th>id</th><th>name</th><th>size</th><th>memo</th><th>操作</th></tr>
        </thead>
        <tbody>
        <?php foreach ($animals as $animal): ?>
            <tr>
                <form method="post" action="update.php">
                    <td>
                        <?= h((string)$animal['id']) ?>
                        <input type="hidden" name="id" value="<?= h((string)$animal['id']) ?>">
                    </td>
                    <td><input type="text" name="name" class="form-control" value="<?= h($animal['name']) ?>" required></td>
                    <td><input type="number" name="size" class="form-control" value="<?= h((string)$animal['size']) ?>" required></td>
                    <td><input type="text" name="memo" class="form-control" value="<?= h($animal['memo']) ?>"></td>
                    <td><button type="submit" class="btn btn-primary btn-sm">更新</button></td>
                </form>
            </tr>
        <?php endforeach; ?>
        </tbody>
    </table>
</div>
</body>
</html>

update.php

<?php
require_once __DIR__ . '/dbconnect.php';

$id = filter_input(INPUT_POST, 'id', FILTER_VALIDATE_INT);
$name = trim($_POST['name'] ?? '');
$size = filter_input(INPUT_POST, 'size', FILTER_VALIDATE_INT);
$memo = trim($_POST['memo'] ?? '');
$message = '入力内容が正しくありません。';

if ($id !== false && $id !== null && $name !== '' && $size !== false && $size !== null) {
    $sql = 'UPDATE animal SET name = :name, size = :size, memo = :memo WHERE id = :id';
    $stmt = $dbh->prepare($sql);
    $stmt->execute([
        ':name' => $name,
        ':size' => $size,
        ':memo' => $memo,
        ':id' => $id,
    ]);
    $message = 'データを更新しました。';
}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>更新結果 PDO版</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container my-4">
    <h1>更新結果</h1>
    <div class="alert alert-info"><?= h($message) ?></div>
    <p><a href="select_update.php">更新画面へ戻る</a></p>
    <p><a href="index.html">トップページへ戻る</a></p>
</div>
</body>
</html>

第5章での方針

第5章の講義支援システムでは,最初は第4章と同様にdbconnect.phpでPDO接続オブジェクト$dbhを作成し,各PHPスクリプトから読み込む形で作成します。その後,「共通する機能をまとめる」ところでcommon/common.phpを作成し,データベース接続,ログイン確認,HTMLエスケープなどの共通処理をまとめます。完成版では各PHPスクリプトからrequire_once __DIR__ . '/common/common.php';で共通処理を読み込み,$dbh->prepare()$stmt->execute()$stmt->fetch()を使ってデータベース操作を行います。


Copyright (c) 2014-2026 幸谷研究室 @ 追手門学院大学 All rights reserved.
Copyright (c) 2014-2026 T.Kouya Laboratory @ Otemon Gakuin University. All rights reserved.