PHPとデータベースで表示・追加・更新・削除を1ページで!

フォルダとファイル名

php-db フォルダに db08.php を作成します。
このページでは、これまでに学んだ 追加(INSERT)・更新(UPDATE)・削除(DELETE)・検索(SELECT) をすべて1つのページで実装します。

完成イメージ

基本コード

  1. ユーザーの一覧を表示
  2. ユーザーの表示(SELECT)
  3. ユーザーの追加(INSERT)
  4. ID指定での更新(UPDATE)
  5. ID指定での削除(DELETE)
<?php
$dsn = 'mysql:host=localhost;dbname=testdb;charset=utf8mb4';
$user = 'root';
$password = '';

// HTMLエスケープ用
function h($str)
{
    return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
}

try {
    $pdo = new PDO($dsn, $user, $password);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    $msg = '';
    $searchKeyword = '';
    $rows = [];
    $errors = [];

    // -----------------------------
    // 表示処理
    // -----------------------------

    // 通常の一覧表示
    $stmt = $pdo->query("SELECT * FROM users ORDER BY id ASC");
    $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

    // -----------------------------
    // 追加処理(POST)
    // -----------------------------
    if (isset($_POST['action']) && $_POST['action'] === 'add') {
        $name = trim($_POST['name'] ?? '');
        $email = trim($_POST['email'] ?? '');
        if ($name === '') {
            $errors[] = '名前は必須です。';
        }
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            $errors[] = 'メールアドレスの形式が正しくありません。';
        }
        if (count($errors) === 0) {
            $stmt = $pdo->prepare("INSERT INTO users (name, email) VALUES (?, ?)");
            $stmt->execute([$name, $email]);
            header("Location: " . $_SERVER['PHP_SELF'] . "?msg=" . urlencode("ユーザー「{$name}」を追加しました。"));
            exit;
        }
    }


    // -----------------------------
    // 更新処理(POST)
    // -----------------------------
    if (isset($_POST['action']) && $_POST['action'] === 'update') {
        $id = (int)($_POST['id'] ?? 0);
        $name = trim($_POST['name'] ?? '');
        $email = trim($_POST['email'] ?? '');

        if ($id <= 0) {
            $errors[] = 'IDが正しくありません。';
        }
        if ($name === '') {
            $errors[] = '名前は必須です。';
        }
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            $errors[] = 'メールアドレスの形式が正しくありません。';
        }


        if (count($errors) === 0) {
            $stmt = $pdo->prepare("UPDATE users SET name = ?, email = ? WHERE id = ?");
            $stmt->execute([$name, $email, $id]);
            header("Location: " . $_SERVER['PHP_SELF'] . "?msg=" . urlencode("ID {$id} のユーザーを更新しました。"));
            exit;
        }
    }

    // -----------------------------
    // 削除処理(POST)
    // -----------------------------
    if (isset($_POST['action']) && $_POST['action'] === 'delete') {
        $id = (int)($_POST['id'] ?? 0);
        if ($id <= 0) {
            $errors[] = 'IDが正しくありません。';
        }

        if (count($errors) === 0) {
            $stmt = $pdo->prepare("DELETE FROM users WHERE id = ?");
            $stmt->execute([$id]);
            $count = $stmt->rowCount();
            $msg = ($count > 0) ? "ID {$id} を削除しました。" : "該当IDが見つかりませんでした。";
            header("Location: " . $_SERVER['PHP_SELF'] . "?msg=" . urlencode($message));
            exit;
        }
    }
} catch (PDOException $e) {
    // 本番ではエラーメッセージは表示せずログに記録する
    echo "エラーが発生しました(詳細はログ参照)";
    // error_log($e->getMessage());
    exit;
}
?>
<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <title>ユーザー管理</title>
    <link rel="stylesheet" href="style.css">
</head>

<body>
    <header>
        <h1>ユーザー管理</h1>
        <div class="success">
            <?php if ($msg): ?>
                <p><strong><?= h($msg) ?></strong></p>
            <?php endif; ?>
        </div>
        <div class="error">
            <?php if ($errors): ?>
                <ul style="color:red;">
                    <?php foreach ($errors as $error): ?>
                        <li><?= h($error) ?></li>
                    <?php endforeach; ?>
                </ul>
            <?php endif; ?>
        </div>

    </header>
    <div class="content">
        <main>
            <!--  一覧表示 -->
            <table>
                <tr>
                    <th>ID</th>
                    <th>名前</th>
                    <th>メール</th>
                    <th>登録日時</th>
                </tr>
                <?php foreach ($rows as $row): ?>
                    <tr>
                        <td><?= h($row['id']) ?></td>
                        <td><?= h($row['name']) ?></td>
                        <td><?= h($row['email']) ?></td>
                        <td><?= h($row['created_at']) ?></td>
                    </tr>
                <?php endforeach; ?>
            </table>
        </main>
        <aside>
            <section>
                <!-- 追加フォーム -->
                <h2>追加</h2>
                <form method="post">
                    <input type="hidden" name="action" value="add">
                    <p>
                        <label for="name">名前</label>
                        <input id="name" type="text" name="name" required>
                    </p>
                    <p>
                        <label for="email">メール</label>
                        <input id="email" type="email" name="email" required>
                    </p>
                    <p>
                        <button type="submit">追加する</button>
                    </p>
                </form>
            </section>
            <section>
                <!-- 更新フォーム -->
                <h2>更新</h2>
                <form method="post">
                    <input type="hidden" name="action" value="update">
                    <p>
                        <label for="update-id">ID</label>
                        <input id="update-id" type="number" name="id" required>

                    </p>
                    <p>
                        <label for="update-name">名前</label>
                        <input id="update-name" type="text" name="name" required>
                    </p>
                    <p>
                        <label for="update-email">メール</label>
                        <input id="update-email" type="email" name="email" required>
                    </p>
                    <button type="submit">更新する</button>
                </form>
            </section>
            <section>
                <!--  削除フォーム -->
                <h2>削除</h2>
                <form method="post">
                    <input type="hidden" name="action" value="delete">
                    <p>
                        <label for="delete-id">ID</label>
                        <input id="delete-id" type="number" name="id" required>

                    </p>
                    <button type="submit">削除する</button>
                </form>
            </section>
        </aside>
    </div>
</body>

</html>
        * {
            margin: 0;
            padding: 0;
        }

        body {
            font-family: sans-serif;
            padding: 1em;
        }

        header,
        .content {
            max-width: 960px;
            margin: auto;
        }

        .content {
            display: grid;
            column-gap: 3rem;
            grid-template-columns: 3fr 1fr;
        }

        .error ul {
            color: red;
            margin-bottom: 1rem;
        }

        .success p {
            color: green;
        }


        table {
            border-collapse: collapse;
            margin-bottom: 2em;
            width: 100%;
        }

        th,
        td {
            border: 1px solid #ccc;
            padding: .5em 1em;
        }

        form {
            margin-bottom: 2em;
        }

        form input[type=text],
        form input[type=email] {
            width: 100%;
            padding: 0.25rem;
            box-sizing: border-box;
        }

        form button[type=submit] {
            background-color: #ccc;
            padding: 0.25rem 1rem;
            border: none;
            margin-top: 0.5rem;
            border-radius: 0.2rem;
        }

        label {
            display: block;
        }

        section {
            border: 1px solid #ddd;
            padding: 0.5rem;
            margin-bottom: 1rem;
            border-radius: 0.5rem;
        }

処理の順番が大事な理由

このページでは、以下のような4つの処理を順番に書いています

// ① 表示処理(SELECT)
$stmt = $pdo->query("SELECT * FROM users ORDER BY id ASC");
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

// ② 追加処理(POST)
if (isset($_POST['action']) && $_POST['action'] === 'add') { ... }

// ③ 更新処理(POST)
if (isset($_POST['action']) && $_POST['action'] === 'update') { ... }

// ④ 削除処理(POST)
if (isset($_POST['action']) && $_POST['action'] === 'delete') { ... }

正しい順番の理由

処理なぜこの順番が必要か
① 表示処理(SELECT)まず一覧を取得して $rows に入れておくと、このあとどの処理が走っても最終的に画面に反映できます。
② 追加処理(POST)新しいデータを登録したら、次のリロード時に追加後の一覧が見えるようにします。
③ 更新処理(POST)既存データを上書きする処理なので、追加の後に実行するのが自然です。
④ 削除処理(POST)一番最後に実行すると、追加や更新が終わったあとで安全に消せます。

よくある間違いとその影響

もし削除や更新の処理を「表示処理の後」に書いてしまうと、こうなります:

  1. フォームから POST でデータを削除
  2. でも先に $rows に一覧を取得してしまっている
  3. 削除前のデータが表示される!

つまり、画面に反映されない状態になります。
データを変更する処理は一覧表示の前に書くか、処理後にリダイレクト(PRGパターン)して一覧を再取得する必要があります。

処理順のまとめ

優先順位処理内容理由
1表示処理(一覧の取得)$rows に一覧を入れるため先頭で実行
2POST(追加)登録内容を先に処理し、次の画面で反映
3POST(更新)更新内容を上書きして次の表示に反映
4POST(削除)最後に安全に削除して、次の表示で消えている状態にする

課題:キーワード検索をつけてみよう

現在のページでは、すべてのユーザーを一覧表示しています。
ここに 「名前」や「メールアドレス」を部分一致で検索できる機能 を追加してみましょう。

ヒント

1. フォームを作成する

一覧の上部に検索フォームを追加します。

<form method="get">
    <input type="text" name="keyword" placeholder="名前やメールを検索">
    <button type="submit">検索</button>
</form>

2.PHPでキーワードを取得する

PHPの上の方で、$_GET からキーワードを受け取ります。

$searchKeyword = trim($_GET['keyword'] ?? '');

3.SQLに条件を追加する

キーワードがあれば WHERE name LIKE ? OR email LIKE ? を使って検索します。

if ($searchKeyword !== '') {
    $stmt = $pdo->prepare(
        "SELECT * FROM users WHERE name LIKE ? OR email LIKE ? ORDER BY id ASC"
    );
    $like = '%' . $searchKeyword . '%';
    $stmt->execute([$like, $like]);
    $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
} else {
    $stmt = $pdo->query("SELECT * FROM users ORDER BY id ASC");
    $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
}