前回作った「学生一覧画面」(Docker編)に、学生の追加機能を付けます。
- 一覧に「学生を追加」ボタンを置く
- ボタンを押すと登録フォーム画面が出る
- 登録すると一覧画面に戻り、追加した学生が表示される
という、CRUD の C(Create) にあたる部分です。SQLインジェクション対策(プリペアドステートメント)や、登録後の PRG(Post/Redirect/Get)パターン といった実務でも重要なポイントを押さえます。
前提
Docker編で作った以下の構成が動いている状態から始めます。
myportal/
├── docker-compose.yml
├── Dockerfile
├── init.sql
├── db.php # 接続先 "db"
├── manage_students.php # 学生一覧画面
└── style.css
students テーブルは次の通り。
CREATE TABLE students (
s_id VARCHAR(20) PRIMARY KEY,
name VARCHAR(100) NOT NULL,
department VARCHAR(50),
semester INT,
email VARCHAR(100)
);
ステップ1: 一覧に「追加」ボタンを置く
manage_students.php の見出しの下に、登録画面へのリンクを追加します。
<h1>学生一覧</h1>
<p><a class="btn" href="add_student.php">+ 学生を追加</a></p>
<table>
ステップ2: 登録画面 add_student.php
1つのファイルで「フォーム表示」と「登録処理」の両方を担当します。POSTで来たら登録、それ以外(GET)ならフォームを表示、という典型パターンです。
<?php
include 'db.php';
$error = "";
// フォームが送信されたら登録処理
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$s_id = trim($_POST['s_id'] ?? '');
$name = trim($_POST['name'] ?? '');
$department = trim($_POST['department'] ?? '');
$semester = trim($_POST['semester'] ?? '');
$email = trim($_POST['email'] ?? '');
if ($s_id === '' || $name === '') {
$error = "学籍番号と氏名は必須です。";
} else {
// SQLインジェクション対策のプリペアドステートメント
$stmt = $con->prepare(
"INSERT INTO students (s_id, name, department, semester, email) VALUES (?, ?, ?, ?, ?)"
);
$sem = ($semester === '') ? 0 : (int)$semester;
$stmt->bind_param("sssis", $s_id, $name, $department, $sem, $email);
try {
$stmt->execute();
// 登録成功 → 一覧画面に戻る(PRGパターン)
header("Location: manage_students.php");
exit;
} catch (mysqli_sql_exception $e) {
if ($e->getCode() == 1062) { // 主キー重複
$error = "その学籍番号は既に登録されています。";
} else {
$error = "登録に失敗しました: " . htmlspecialchars($e->getMessage());
}
}
}
}
?>
<!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="add_student.php" class="form">
<label>学籍番号 <span class="req">*</span>
<input type="text" name="s_id" value="<?= htmlspecialchars($_POST['s_id'] ?? '') ?>" required>
</label>
<label>氏名 <span class="req">*</span>
<input type="text" name="name" value="<?= htmlspecialchars($_POST['name'] ?? '') ?>" required>
</label>
<label>学科
<input type="text" name="department" value="<?= htmlspecialchars($_POST['department'] ?? '') ?>">
</label>
<label>セメスター
<input type="number" name="semester" min="1" max="12" value="<?= htmlspecialchars($_POST['semester'] ?? '') ?>">
</label>
<label>メール
<input type="email" name="email" value="<?= htmlspecialchars($_POST['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>
ここが重要なポイント
-
プリペアドステートメント
$con->prepare(...)+bind_param(...)で値を埋め込む。文字列連結でSQLを組み立てないので、SQLインジェクションを防げる。"sssis"は各プレースホルダの型(s=文字列, i=整数)。 -
PRG(Post/Redirect/Get)パターン
登録成功後にheader("Location: ...")でリダイレクトする。これをしないと、登録完了画面でブラウザを再読み込みしたときにフォームが再送信され二重登録になる。リダイレクトを挟むことで防げる。 -
重複エラーのハンドリング
学籍番号は主キー。同じIDを登録するとMySQLがエラーコード1062を返すので、それを捕まえて分かりやすいメッセージを出す。 -
入力値の保持と表示
エラー時もフォームに入力値を残す(value="<?= htmlspecialchars(...) ?>")。表示は必ずhtmlspecialchars()を通してXSSを防ぐ。
ステップ3: CSS(ボタン・フォーム・エラー)
style.css に追記。
/* ボタン */
.btn {
display:inline-block; padding:8px 16px; background:#4a90d9; color:#fff;
text-decoration:none; border:none; border-radius:4px; cursor:pointer; font-size:14px;
}
.btn:hover { background:#3a7bc0; }
.btn-cancel { background:#999; }
.btn-cancel:hover { background:#777; }
/* フォーム */
.form {
max-width:400px; background:#fff; padding:24px; border-radius:6px;
box-shadow:0 1px 4px rgba(0,0,0,.1);
}
.form label { display:block; margin-bottom:14px; color:#333; font-size:14px; }
.form input {
width:100%; padding:8px; margin-top:4px; box-sizing:border-box;
border:1px solid #ccc; border-radius:4px;
}
.form .req { color:#e00; }
.actions { margin-top:8px; display:flex; gap:10px; }
/* エラー */
.error {
color:#c0392b; background:#fdecea; padding:10px 14px;
border-radius:4px; max-width:400px;
}
ステップ4: 動作確認
ファイルはボリュームマウントで即時反映されるので、再起動は不要。
http://localhost:8081/manage_students.php
- 「+ 学生を追加」ボタンを押す → 登録フォームが出る
- 学籍番号・氏名などを入れて「登録」
- 一覧画面に戻り、追加した学生が表示される
CLIでも確認できる。
curl -i -X POST http://localhost:8081/add_student.php \
--data "s_id=ktu99&name=Tanaka&department=CS&semester=5&email=tanaka@example.com"
# → HTTP/1.1 302 Found / Location: manage_students.php が返ればOK
まとめ
CRUDの「C(Create)」を実装しました。要点は3つ。
- プリペアドステートメント でSQLインジェクションを防ぐ
- PRGパターン で二重登録を防ぐ
- 主キー重複 をエラーハンドリングして親切なメッセージを出す
次は編集(Update)・削除(Delete)を付ければ、基本的なCRUDが揃います。