フォルダとファイル名
php-db12 フォルダを作成します。ファイル構成は以下になります。
php-db12/ ├── common.php ├── register.php ├── register_done.php ├── login.php ├── auth.php ├── member.php ├── logout.php ├── bbs.php # 一覧と投稿フォーム(ログインなしでもOK) ├── update.php # 更新フォーム & 更新処理(ログイン必須) ├── delete.php # 削除処理(ログイン必須) └── members.sql
フローチャート
register.php(会員登録フォーム:ユーザー名・パスワード入力) ↓ register_done.php(データベースに保存) ↓ login.php(ログインフォーム) ↓ auth.php(ログイン認証:データベースと照合) → 成功 → member.php(会員専用ページ) ↓ bbs.php(掲示板:ログインしていなくても閲覧・投稿可能) ↓ (ログイン中のみ) ├─ update.php(投稿内容を編集 → bbs.phpに戻る) └─ delete.php(投稿を削除 → bbs.phpに戻る) → 失敗 → login.php に戻る ↓ logout.php(セッション情報の破棄) → bbs.php に戻る
完成イメージ






データベース:更新のためbbsテーブルにカラム追加
ALTER TABLE bbs ADD COLUMN user_id INT NULL AFTER comment;
1.common.php(共通処理)
<?php
session_start();
// DB接続
$dsn = 'mysql:host=localhost;dbname=testdb;charset=utf8mb4';
$user = 'root';
$password = '';
try {
$pdo = new PDO($dsn, $user, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
echo "DB接続エラー:" . $e->getMessage();
exit;
}
// エスケープ関数
function h($str)
{
return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
}
2.register.php(会員登録フォーム)
新規会員登録のフォーム ユーザー名とパスワードを入力
<?php require 'common.php'; ?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>新規会員登録</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<div class="container mt-5">
<div class="col-md-6 mx-auto">
<div class="card shadow-sm">
<div class="card-body">
<h1 class="h4 mb-4 text-center">新規会員登録</h1>
<form action="register_done.php" method="post">
<div class="mb-3">
<label for="username" class="form-label">ユーザー名</label>
<input type="text" name="username" id="username" class="form-control" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">パスワード</label>
<input type="password" name="password" id="password" class="form-control" required>
</div>
<button type="submit" class="btn btn-primary w-100">登録</button>
</form>
<p class="mt-3 text-center">
<a href="login.php">既にアカウントをお持ちの方はこちら</a>
</p>
</div>
</div>
</div>
</div>
</body>
</html>
3.register_done.php
<?php
require 'common.php';
$username = trim($_POST['username'] ?? '');
$password = trim($_POST['password'] ?? '');
// バリデーション
if ($username === '' || $password === '') {
exit('ユーザー名とパスワードは必須です。');
}
// パスワードをハッシュ化
$hashed = password_hash($password, PASSWORD_DEFAULT);
// 登録
try {
$stmt = $pdo->prepare("INSERT INTO members (username, password) VALUES (?, ?)");
$stmt->execute([$username, $hashed]);
echo "登録完了!<a href='login.php'>ログインする</a>";
} catch (PDOException $e) {
if ($e->getCode() === '23000') { // UNIQUE制約エラー
echo "このユーザー名は既に使われています。<a href='register.php'>戻る</a>";
} else {
echo "エラー:" . $e->getMessage();
}
}
4.login.php (ログイン)
<?php require 'common.php'; ?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ログイン</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<div class="container mt-5">
<div class="col-md-6 mx-auto">
<div class="card shadow-sm">
<div class="card-body">
<h1 class="h4 mb-4 text-center">ログイン</h1>
<form action="auth.php" method="post">
<div class="mb-3">
<label for="username" class="form-label">ユーザー名</label>
<input type="text" name="username" id="username" class="form-control" required autofocus>
</div>
<div class="mb-3">
<label for="password" class="form-label">パスワード</label>
<input type="password" name="password" id="password" class="form-control" required>
</div>
<button type="submit" class="btn btn-primary w-100">ログイン</button>
</form>
<p class="mt-3 text-center">
<a href="register.php">新規会員登録はこちら</a>
</p>
<p class="mt-3 text-center">
<a href="bbs.php">掲示板を見る(ゲスト)</a>
</p>
</div>
</div>
</div>
</div>
</body>
</html>
5.auth.php(認証処理)
<?php
require 'common.php';
$username = trim($_POST['username'] ?? '');
$password = trim($_POST['password'] ?? '');
// ユーザー検索
$stmt = $pdo->prepare("SELECT * FROM members WHERE username = ?");
$stmt->execute([$username]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
// 認証判定
if ($user && password_verify($password, $user['password'])) {
// ログイン成功 → セッションに保存
$_SESSION['member'] = $user['username']; // 表示用
$_SESSION['member_id'] = $user['id']; // ★ これを追加!
header('Location: member.php');
exit;
} else {
// ログイン失敗 → シンプルなメッセージ
echo "ユーザー名またはパスワードが違います。<a href='login.php'>戻る</a>";
}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ログイン失敗</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="alert alert-danger text-center" role="alert">
ユーザー名またはパスワードが違います。
</div>
<div class="text-center">
<a href="login.php" class="btn btn-outline-primary">ログイン画面に戻る</a>
</div>
</div>
</div>
</div>
</body>
</html>
6.member.php(会員専用ページ)
<?php
require 'common.php';
if (empty($_SESSION['member'])) {
header('Location: login.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 href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card shadow-sm">
<div class="card-body text-center">
<h1 class="h4 mb-4">ようこそ <span class="text-primary"><?= h($_SESSION['member']) ?></span> さん</h1>
<p class="mb-4">ログインしましたので掲示板で削除・更新が可能です。</p>
<p class="mb-4">
<a href="bbs.php" class="btn btn-outline-success w-100">掲示板に移動する</a>
</p>
<p>
<a href="logout.php" class="btn btn-outline-danger w-100">ログアウト</a>
</p>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
7.logout.php(ログアウト処理)
<?php
require 'common.php';
// セッションの中身を空にする
$_SESSION = [];
// セッションを完全に破棄
if (session_id() !== '' || isset($_COOKIE[session_name()])) {
setcookie(session_name(), '', time() - 42000, '/');
}
session_destroy();
// ログインページへリダイレクト
header('Location: login.php');
exit;
7.bbs.php(掲示板本体)
<?php
require 'common.php';
$errors = [];
$maxComment = 100;
$name = '';
$comment = '';
try {
// 投稿処理
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = trim($_POST['name'] ?? '');
$comment = trim($_POST['comment'] ?? '');
// ログイン中なら名前はセッションから強制
if (!empty($_SESSION['member'])) {
$name = $_SESSION['member'];
}
// バリデーション
if ($name === '') {
$errors[] = '名前を入力してください。';
}
if ($comment === '') {
$errors[] = 'コメントを入力してください。';
} elseif (mb_strlen($comment) > $maxComment) {
$errors[] = "コメントは {$maxComment} 文字以内で入力してください。";
}
if (count($errors) === 0) {
$stmt = $pdo->prepare("INSERT INTO bbs (name, comment, user_id) VALUES (?, ?, ?)");
$stmt->execute([$name, $comment, $_SESSION['member_id'] ?? null]);
$_SESSION['flash_msg'] = '投稿しました!';
header('Location: bbs.php');
exit;
}
}
// 投稿一覧取得
// 投稿一覧取得(JOIN)
$stmt = $pdo->query("
SELECT bbs.*, members.profile_image
FROM bbs
LEFT JOIN members ON bbs.user_id = members.id
ORDER BY bbs.id DESC
");
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
echo "DBエラー:" . $e->getMessage();
exit;
}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Mini掲示板</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<div class="col-md-8 mx-auto mt-4">
<header class="text-center">
<h1 class="mb-4">Mini掲示板</h1>
<?php if (!empty($_SESSION['member'])): ?>
<p class="h5 mb-4">
<span class="text-primary"><?= h($_SESSION['member']) ?></span> さんはログインしています。
<a href="member.php" class="btn btn-primary">会員専用ページに移動</a>
</p>
<?php else: ?>
<p class="h6 text-muted mb-4">
ログインしていません(投稿はできますが、削除・更新はできません)
<a href="login.php">ログインする</a>
</p>
<?php endif; ?>
</header>
<!-- フラッシュメッセージ -->
<section class="col-md-8 mx-auto mt-4">
<?php if (!empty($_SESSION['flash_msg'])): ?>
<div class="alert alert-success">
<?= h($_SESSION['flash_msg']) ?>
</div>
<?php unset($_SESSION['flash_msg']); ?>
<?php endif; ?>
<!-- エラーメッセージ -->
<?php if (count($errors) > 0): ?>
<div class="alert alert-danger">
<ul class="mb-0">
<?php foreach ($errors as $e): ?>
<li><?= h($e) ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
</section>
<!-- 投稿フォーム -->
<section class="card p-3 mb-5 col-md-8 mx-auto">
<h2 class="h5">新規投稿</h2>
<form method="post">
<div class="mb-3">
<label for="name" class="form-label">名前</label>
<input type="text" name="name" id="name" class="form-control"
value="<?= !empty($_SESSION['member']) ? h($_SESSION['member']) : h($name) ?>"
<?= !empty($_SESSION['member']) ? 'readonly' : '' ?>>
</div>
<div class="mb-3">
<label for="comment" class="form-label">コメント(最大<?= $maxComment ?>文字)</label>
<textarea name="comment" id="comment" class="form-control" maxlength="<?= $maxComment ?>"><?= h($comment) ?></textarea>
</div>
<button type="submit" class="btn btn-primary"><i class="bi bi-pencil"></i> 投稿する</button>
</form>
</section>
<!-- 投稿一覧 -->
<section class="col-md-8 mx-auto">
<h2 class="h5">投稿一覧</h2>
<?php if (empty($rows)): ?>
<p>まだ投稿はありません。</p>
<?php else: ?>
<?php foreach ($rows as $row): ?>
<div class="card mb-3">
<div class="card-body row">
<div class="col-lg-11">
<h5 class="card-title d-flex align-items-center">
<?php if (!empty($row['profile_image'])): ?>
<img src="uploads/<?= h($row['profile_image']) ?>" height="40">
<?php else: ?>
<i class="bi bi-person-circle h3"></i>
<?php endif; ?>
<span class="h3">
<?= h($row['name']) ?>
</span>
</h5>
<h6 class="card-subtitle mb-2 text-muted"><?= h($row['created_at']) ?></h6>
<p class="card-text"><?= nl2br(h($row['comment'])) ?></p>
</div>
<div class="col-lg-1 text-end">
<?php if (!empty($_SESSION['member_id']) && $row['user_id'] == $_SESSION['member_id']): ?>
<!-- 自分の投稿だけ編集・削除ボタン表示 -->
<form action="delete.php" method="post" class="mb-1">
<input type="hidden" name="id" value="<?= h($row['id']) ?>">
<button type="submit" class="btn btn-outline-danger btn-sm"
onclick="return confirm('本当に削除しますか?');">
<i class="bi bi-trash h3"></i>
</button>
</form>
<form action=" update.php" method="post">
<input type="hidden" name="update_id" value="<?= h($row['id']) ?>">
<button type="submit" class="btn btn-sm btn-outline-primary">
<i class="bi bi-pencil-fill h3"></i>
</button>
</form>
<?php endif; ?>
</div>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
<p class="mt-4"><a href="logout.php">ログアウトする</a></p>
</section>
</div>
</body>
</html>
7.delete.php
<?php
require 'common.php';
// ログインしていない場合はログインページへ
if (empty($_SESSION['member_id'])) {
header('Location: login.php');
exit;
}
// POST送信以外はbbs.phpに戻す
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: bbs.php');
exit;
}
$id = (int)($_POST['id'] ?? 0);
if ($id > 0) {
// 自分の投稿かどうかを確認して削除
$stmt = $pdo->prepare("DELETE FROM bbs WHERE id = ? AND user_id = ?");
$stmt->execute([$id, $_SESSION['member_id']]);
if ($stmt->rowCount() > 0) {
$_SESSION['flash_msg'] = "削除しました。";
} else {
$_SESSION['flash_msg'] = "削除できません(権限がありません)。";
}
}
header('Location: bbs.php');
exit;
7.update.php
<?php
require 'common.php';
if (empty($_SESSION['member_id'])) {
$_SESSION['flash_msg'] = "ログインしてください。";
header('Location: login.php');
exit;
}
// ----------------------
// id の受け取り(POST専用)
// ----------------------
$id = isset($_POST['update_id']) ? (int)$_POST['update_id'] : 0;
if ($id <= 0) {
header('Location: bbs.php');
exit;
}
// ----------------------
// 投稿取得(自分の投稿のみ)
// ----------------------
$stmt = $pdo->prepare("SELECT * FROM bbs WHERE id = ? AND user_id = ?");
$stmt->execute([$id, $_SESSION['member_id']]);
$post = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$post) {
$_SESSION['flash_msg'] = "権限がありません。";
header('Location: bbs.php');
exit;
}
// ----------------------
// 更新処理
// ----------------------
if (isset($_POST['comment'])) { // 更新ボタンが押されたとき
$comment = trim($_POST['comment']);
if ($comment === '') {
$error = 'コメントを入力してください。';
} else {
$stmt = $pdo->prepare("UPDATE bbs SET comment = ? WHERE id = ? AND user_id = ?");
$stmt->execute([$comment, $id, $_SESSION['member_id']]);
$_SESSION['flash_msg'] = '更新しました!';
header('Location: bbs.php');
exit;
}
}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>投稿を編集</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<div class="col-md-8 mx-auto mt-4">
<h1 class="mb-4">投稿を編集</h1>
<?php if (!empty($error)): ?>
<div class="alert alert-danger"><?= h($error) ?></div>
<?php endif; ?>
<form method="post">
<!-- 編集対象IDを再送信する -->
<input type="hidden" name="update_id" value="<?= h($id) ?>">
<div class="mb-3">
<label for="comment" class="form-label">コメント</label>
<textarea name="comment" id="comment" class="form-control"><?= h($post['comment']) ?></textarea>
</div>
<button type="submit" class="btn btn-primary">更新する</button>
<a href="bbs.php" class="btn btn-secondary">戻る</a>
</form>
</div>
</body>
</html>
課題:検索フォームを追加しよう
<form method="get"> <input type="text" name="keyword" placeholder="キーワードを入力"> <button type="submit">検索</button> </form>
- 検索はget送信
- GETメソッドを使うとURLに
?keyword=◯◯が付く - PHPで
$_GET['keyword']からキーワードを取得できる
if (!empty($_GET['keyword'])) {
$keyword = '%' . $_GET['keyword'] . '%';
$stmt = $pdo->prepare("SELECT * FROM bbs WHERE name LIKE ? OR comment LIKE ? ORDER BY id DESC");
$stmt->execute([$keyword, $keyword]);
} else {
$stmt = $pdo->query("SELECT * FROM bbs ORDER BY id DESC");
}
LIKEで部分一致検索nameとcomment両方を対象に検索- キーワードが空なら全件表示
結果がなかったときの表示
<?php if (empty($rows)): ?>
<p>該当する投稿はありません。</p>
<?php endif; ?>
