前回の「追加機能」に続き、今回は 更新(編集)機能 を実装します。CRUD の U(Update) にあたる部分です。
- 一覧の各行に「編集」リンクを置く
- リンクを押すと、その学生の現在の値が入った編集フォームが出る
- 更新すると一覧画面に戻り、変更が反映される
主キー(学籍番号)をどう扱うか、既存値をフォームにどう反映するか、といった更新ならではのポイントを押さえます。
前提
Docker編 + 追加機能まで実装済みの状態から始めます。
myportal/
├── docker-compose.yml
├── Dockerfile
├── init.sql
├── db.php
├── manage_students.php # 学生一覧
├── add_student.php # 追加
└── style.css
ステップ1: 一覧に「編集」リンクを置く
manage_students.php のテーブルに「操作」列を足し、各行に編集画面へのリンクを出します。学籍番号をクエリパラメータで渡すのがポイント。
<tr><th>学籍番号</th><th>氏名</th><th>学科</th><th>セメスター</th><th>メール</th><th>操作</th></tr>
<?php
$result = mysqli_query($con, "SELECT * FROM students ORDER BY s_id");
while ($row = mysqli_fetch_assoc($result)) {
$editUrl = "edit_student.php?s_id=" . urlencode($row['s_id']);
echo "<tr>";
echo "<td>".htmlspecialchars($row['s_id'])."</td>";
echo "<td>".htmlspecialchars($row['name'])."</td>";
echo "<td>".htmlspecialchars($row['department'])."</td>";
echo "<td>".htmlspecialchars($row['semester'])."</td>";
echo "<td>".htmlspecialchars($row['email'])."</td>";
echo "<td><a class='btn btn-sm' href='".htmlspecialchars($editUrl)."'>編集</a></td>";
echo "</tr>";
}
?>
urlencode() で学籍番号をURLに安全に埋め込み、リンクテキストは htmlspecialchars() でエスケープします。
ステップ2: 編集画面 edit_student.php
1ファイルで「既存データの表示(GET)」と「更新処理(POST)」を兼ねます。
<?php
include 'db.php';
$error = "";
// 対象の学籍番号(主キー)。GET/POST どちらからでも受ける
$s_id = trim($_GET['s_id'] ?? $_POST['s_id'] ?? '');
if ($s_id === '') {
header("Location: manage_students.php");
exit;
}
// 更新処理
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$name = trim($_POST['name'] ?? '');
$department = trim($_POST['department'] ?? '');
$semester = trim($_POST['semester'] ?? '');
$email = trim($_POST['email'] ?? '');
if ($name === '') {
$error = "氏名は必須です。";
} else {
// SQLインジェクション対策のプリペアドステートメント
$stmt = $con->prepare(
"UPDATE students SET name=?, department=?, semester=?, email=? WHERE s_id=?"
);
$sem = ($semester === '') ? 0 : (int)$semester;
$stmt->bind_param("ssiss", $name, $department, $sem, $email, $s_id);
$stmt->execute();
// 更新成功 → 一覧画面に戻る(PRGパターン)
header("Location: manage_students.php");
exit;
}
// エラー時はPOST値を表示に使う
$student = [
's_id' => $s_id, 'name' => $name, 'department' => $department,
'semester' => $semester, 'email' => $email,
];
} else {
// 初回表示:既存データを取得してフォームに反映
$stmt = $con->prepare("SELECT * FROM students WHERE s_id=?");
$stmt->bind_param("s", $s_id);
$stmt->execute();
$student = $stmt->get_result()->fetch_assoc();
if (!$student) {
// 存在しない学籍番号
header("Location: manage_students.php");
exit;
}
}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>学生を編集</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>学生を編集</h1>
<?php if ($error !== ''): ?>
<p class="error"><?= $error ?></p>
<?php endif; ?>
<form method="post" action="edit_student.php" class="form">
<input type="hidden" name="s_id" value="<?= htmlspecialchars($student['s_id']) ?>">
<label>学籍番号(変更不可)
<input type="text" value="<?= htmlspecialchars($student['s_id']) ?>" disabled>
</label>
<label>氏名 <span class="req">*</span>
<input type="text" name="name" value="<?= htmlspecialchars($student['name']) ?>" required>
</label>
<label>学科
<input type="text" name="department" value="<?= htmlspecialchars($student['department']) ?>">
</label>
<label>セメスター
<input type="number" name="semester" min="1" max="12" value="<?= htmlspecialchars($student['semester']) ?>">
</label>
<label>メール
<input type="email" name="email" value="<?= htmlspecialchars($student['email']) ?>">
</label>
<div class="actions">
<button type="submit" class="btn">更新</button>
<a class="btn btn-cancel" href="manage_students.php">キャンセル</a>
</div>
</form>
</body>
</html>
ここが重要なポイント
-
主キー(学籍番号)は変更させない
学籍番号はレコードを一意に識別するキー。これを編集対象にすると参照関係が壊れやすい。フォームではdisabledの入力欄で見せつつ、実際の値はhiddenで保持してWHERE s_id=?の識別に使う。 -
GETで既存値を読み込み、フォームに反映
初回表示時にSELECT ... WHERE s_id=?で取得し、各value="..."に流し込む。これにより「現在の値が入った状態」で編集を始められる。 -
UPDATE もプリペアドステートメント
追加(INSERT)と同様、値はすべてバインドしてSQLインジェクションを防ぐ。"ssiss"は name, department, semester(整数), email, s_id の型指定。 -
PRG(Post/Redirect/Get)パターン
更新後にheader("Location: ...")でリダイレクト。再読み込みによる二重送信を防ぐ。 -
不正・存在しないIDの防御
s_idが空、または該当レコードがなければ一覧へリダイレクトして安全に処理を打ち切る。
ステップ3: CSS(小さい編集ボタン)
style.css に一覧内で使う小さめのボタンを追記。
.btn-sm { padding:4px 10px; font-size:13px; }
ステップ4: 動作確認
ファイルはボリュームマウントで即時反映。
http://localhost:8081/manage_students.php
- 一覧の「編集」を押す → 現在の値が入ったフォームが出る
- 値を変えて「更新」
- 一覧に戻り、変更が反映されている
CLIでも確認できる。
curl -i -X POST http://localhost:8081/edit_student.php \
--data "s_id=ktu1&name=Jack%20Updated&department=MECH&semester=9&email=jack2@example.com"
# → HTTP/1.1 302 Found / Location: manage_students.php
まとめ
CRUDの「U(Update)」を実装しました。要点は3つ。
- 主キーは編集させず識別キーとして固定する
- GETで既存値を読み込み、フォームに反映する
- UPDATEもプリペアドステートメント+PRGパターンで安全に
残るは削除(Delete)。これを付ければ基本的なCRUDが完成します。