iT邦幫忙

2026 iThome 鐵人賽

DAY 14
0

今天要完成圖書管理系統中的修改書籍 edit_book.php 與刪除書籍 delete_book.php,並在首頁 index.php 新增操作欄位來串接這些功能。。

步驟一:修改書籍

編輯功能分為兩階段:

  1. GET 請求:根據網址列帶入的 id(例如 edit_book.php?id=1),去資料庫撈出該書籍目前的資料並填入表單。
  2. POST 請求:使用者修改完資料按下送出後,進行欄位驗證並執行 UPDATE 語法更新資料庫。

給 Claude Code 的 Prompt:

請幫我在 ~/library-system/edit_book.php 建立編輯書籍頁面:
1. 引進 config/database.php 並取得 PDO 連線。
2. 接收 GET 參數 id,若 id 不存在或資料庫找不到該書籍,自動重新導向至 index.php。
3. GET 請求時,將該書籍目前的資料帶入 HTML 表單的 input 欄位中。
4. POST 請求時,接收表單欄位:title(書名)、author(作者)、isbn(ISBN)、category(類別)、quantity(數量)。
5. 進行基本資料驗證(必填檢查、數量必須為 >= 0 的整數)。
6. 使用 Prepared Statement 執行 UPDATE 語法更新 books 資料表,注意可借數量 (available) 需依據總數量 (total) 的增減調整,或保持合理性。
7. 成功更新後重新導向至 index.php。
8. 捕捉 ISBN 重複例外(SQLSTATE 23000)並給予友善提示。
9. 前端 HTML5 表單樣式需與 add_book.php 保持一致。

edit_book.php程式碼:

<?php

require __DIR__ . '/config/database.php';

$pdo = Database::getConnection();

// 從網址參數取得要編輯的書籍 id,格式不合法就導回列表頁
$id = $_GET['id'] ?? null;

if ($id === null || !ctype_digit((string) $id) || (int) $id < 1) {
    header('Location: index.php');
    exit;
}

$id = (int) $id;

// 查出這本書目前的資料,作為表單預設值
$stmt = $pdo->prepare("SELECT id, title, author, isbn, category, total, available FROM books WHERE id = :id");
$stmt->execute([':id' => $id]);
$book = $stmt->fetch();

// 查無此書(id 不存在)也導回列表頁
if ($book === false) {
    header('Location: index.php');
    exit;
}

$errors = [];
$title = $book['title'];
$author = $book['author'];
$isbn = $book['isbn'];
$category = $book['category'];
$quantity = $book['total'];

// 只有表單送出(POST)時才驗證與更新資料庫,GET 只是顯示帶預設值的表單
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $title = trim($_POST['title'] ?? '');
    $author = trim($_POST['author'] ?? '');
    $isbn = trim($_POST['isbn'] ?? '');
    $category = trim($_POST['category'] ?? '');
    $quantity = trim($_POST['quantity'] ?? '');

    if ($title === '') {
        $errors[] = '請輸入書名';
    }
    if ($author === '') {
        $errors[] = '請輸入作者';
    }
    if ($isbn === '') {
        $errors[] = '請輸入 ISBN';
    }
    if ($quantity === '' || !ctype_digit($quantity) || (int) $quantity < 0) {
        $errors[] = '數量請輸入 0 以上的整數';
    }
    
    if (empty($errors)) {
        $quantity = (int) $quantity;

        // 借出中的本數不會變,新總量必須至少能涵蓋這些借出中的書籍
        $borrowed = $book['total'] - $book['available'];

        if ($quantity < $borrowed) {
            $errors[] = "數量不能小於目前已借出的 {$borrowed} 本";
        } else {
            $available = $quantity - $borrowed;

            try {
                $sql = "UPDATE books
                        SET title = :title, author = :author, isbn = :isbn,
                            category = :category, total = :total, available = :available
                        WHERE id = :id";
                $stmt = $pdo->prepare($sql);
                $stmt->execute([
                    ':title' => $title,
                    ':author' => $author,
                    ':isbn' => $isbn,
                    ':category' => $category !== '' ? $category : null,
                    ':total' => $quantity,
                    ':available' => $available,
                    ':id' => $id,
                ]);
                
                // 更新成功導回列表頁
                header('Location: index.php');
                exit;
            } catch (PDOException $e) {
                // 23000 為唯一鍵衝突(ISBN 重複),其餘視為一般更新錯誤
                if ($e->getCode() === '23000') {
                    $errors[] = '這個 ISBN 已經存在,請確認後再試一次';
                } else {
                    $errors[] = '更新資料庫失敗,請稍後再試';
                }
            }
        }
    }
}

?>
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<title>編輯書籍 - 圖書管理系統</title>
<style>
    body {
        font-family: "Microsoft JhengHei", Arial, sans-serif;
        background-color: #f5f5f5;
        color: #333;
        margin: 0;
        padding: 2rem;
    }
    h1 {
        text-align: center;
        margin-bottom: 1.5rem;
    }
    form {
        width: 100%;
        max-width: 480px;
        margin: 0 auto;
        background-color: #fff;
        box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
        padding: 1.5rem 2rem;
        box-sizing: border-box;
    }
    .field {
        margin-bottom: 1rem;
    }
    label {
        display: block;
        margin-bottom: 0.35rem;
        font-weight: bold;
    }
    input[type="text"],
    input[type="number"] {
        width: 100%;
        padding: 0.5rem;
        border: 1px solid #ccc;
        border-radius: 4px;
        box-sizing: border-box;
        font-size: 1rem;
    }
    button {
        width: 100%;
        padding: 0.75rem;
        background-color: #2c3e50;
        color: #fff;
        border: none;
        border-radius: 4px;
        font-size: 1rem;
        cursor: pointer;
    }
    button:hover {
        background-color: #1a252f;
    }
    .errors {
        max-width: 480px;
        margin: 0 auto 1rem;
        background-color: #fdecea;
        color: #b71c1c;
        border: 1px solid #f5c6cb;
        border-radius: 4px;
        padding: 0.75rem 1rem;
    }
    .errors ul {
        margin: 0;
        padding-left: 1.25rem;
    }
    .back-link {
        display: block;
        max-width: 480px;
        margin: 1rem auto 0;
        text-align: center;
    }
</style>
</head>
<body>
    <h1>編輯書籍</h1>
    
<?php // 有驗證或更新錯誤時,在表單上方列出所有錯誤訊息 ?>
<?php if (!empty($errors)): ?>
    <div class="errors">
        <ul>
<?php foreach ($errors as $error): ?>
            <li><?= htmlspecialchars($error) ?></li>
<?php endforeach; ?>
        </ul>
    </div>
<?php endif; ?>
    
    <form method="POST" action="edit_book.php?id=<?= (int) $id ?>">
        <div class="field">
            <label for="title">書名</label>
            <input type="text" id="title" name="title" value="<?= htmlspecialchars($title) ?>" required>
        </div>
        <div class="field">
            <label for="author">作者</label>
            <input type="text" id="author" name="author" value="<?= htmlspecialchars($author) ?>" required>
        </div>
        <div class="field">
            <label for="isbn">ISBN</label>
            <input type="text" id="isbn" name="isbn" value="<?= htmlspecialchars($isbn) ?>" required>
        </div>
        <div class="field">
            <label for="category">類別</label>
            <input type="text" id="category" name="category" value="<?= htmlspecialchars($category ?? '') ?>">
        </div>
        <div class="field">
            <label for="quantity">數量</label>
            <input type="number" id="quantity" name="quantity" value="<?= htmlspecialchars($quantity) ?>" min="0" required>
        </div>
        <button type="submit">儲存變更</button>
    </form>
    
     <a class="back-link" href="index.php">返回書籍列表</a>
</body>
</html>

步驟二:刪除書籍功能

資安非常強調一點:絕對不能使用 GET 請求執行刪除操作(例如 delete_book.php?id=1),否則可能字串拼接或是被搜尋引擎爬蟲點擊造成資料意外刪除。因此,使用 POST 請求來處理刪除操作。

對 Claude Code 下的 Prompt:

請幫我在 ~/library-system/delete_book.php 建立刪除書籍功能:
1. 僅允許 POST 請求方式。若為 GET 請求則直接重導向至 index.php。
2. 接收 POST 參數 id,驗證是否為有效整數。
3. 使用 Prepared Statement 執行 DELETE FROM books WHERE id = :id。
4. 刪除完成後,重新導向至 index.php。

產生的 delete_book.php

<?php

require __DIR__ . '/config/database.php';

// 只接受 POST 請求,避免用網址直接觸發刪除
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    header('Location: index.php');
    exit;
}

// 驗證要刪除的 id 格式,不合法就導回列表頁
$id = $_POST['id'] ?? null;

if ($id === null || !ctype_digit((string) $id) || (int) $id < 1) {
    header('Location: index.php');
    exit;
}

$id = (int) $id;

$pdo = Database::getConnection();

try {
    $stmt = $pdo->prepare("DELETE FROM books WHERE id = :id");
    $stmt->execute([':id' => $id]);
} catch (PDOException $e) {
    // 刪除失敗仍導回列表頁,不中斷使用者流程
}

// 不論成功或失敗都導回列表頁
header('Location: index.php');
exit;

步驟三:更新 index.php 表格(新增操作欄位)

有了編輯與刪除頁面後,還需要更新首頁的表格,讓使用者可以直接在列表中點擊操作。

對 Claude Code 下 Prompt:

請幫我修改 ~/library-system/index.php 頁面的表格,新增一個「操作」欄位以串接編輯與刪除功能:

1. 表格標頭:在 table 的 thead 最右側新增 th 標題「操作」。
2. 資料欄位:在 tbody 的 foreach 迴圈最右側新增 td 欄位,內含:
   - 「編輯」連結:指向 edit_book.php?id=【書籍ID】。
   - 「刪除」表單:使用 POST 方式傳送至 delete_book.php,內含隱藏欄位 input (name="id") 帶入書籍 ID。
   - 刪除二次確認:在刪除表單加上 onsubmit,跳出 JavaScript 確認框,顯示「確定要刪除《【書名】》嗎?」(書名需做 htmlspecialchars 轉義)。
3. 樣式與排版優化:
   - 讓「編輯」連結與「刪除」表單按鈕在同一個 td 內保持水平並排 (inline / flex)。
   - 刪除按鈕樣式改為紅色文字連結風格,並加上 cursor: pointer。
   - 保持現有的表格整體樣式風格統一。

更新後的 index.php:

<?php

require __DIR__ . '/config/database.php';

$pdo = Database::getConnection();

// 查出所有書籍,依 id 由新到舊排序,供下方表格顯示
$sql = "SELECT id, title, author, category, available, created_at FROM books ORDER BY id DESC";
$stmt = $pdo->query($sql);
$books = $stmt->fetchAll();
?>
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<title>書籍列表 - 圖書管理系統</title>
<style>
    body {
        font-family: "Microsoft JhengHei", Arial, sans-serif;
        background-color: #f5f5f5;
        color: #333;
        margin: 0;
        padding: 2rem;
    }
    h1 {
        text-align: center;
        margin-bottom: 1.5rem;
    }
    table {
        width: 100%;
        max-width: 960px;
        margin: 0 auto;
        border-collapse: collapse;
        background-color: #fff;
        box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
    }
    th, td {
        padding: 0.75rem 1rem;
        text-align: left;
        border-bottom: 1px solid #ddd;
    }
    th {
        background-color: #2c3e50;
        color: #fff;
    }
    tr:hover {
        background-color: #f1f1f1;
    }
    .empty {
        text-align: center;
        padding: 2rem;
        color: #777;
    }
    .actions {
        display: flex;
        align-items: center;
        gap: 0.75rem;
    }
    .edit-link {
        color: #2c3e50;
        text-decoration: none;
    }
    .edit-link:hover {
        text-decoration: underline;
    }
    .delete-form {
        display: inline;
        margin: 0;
    }
    .delete-btn {
        background: none;
        border: none;
        padding: 0;
        font: inherit;
        color: #c0392b;
        cursor: pointer;
        text-decoration: underline;
    }
    .delete-btn:hover {
        color: #922b21;
    }
</style>
</head>
<body>
    <h1>書籍列表</h1>
    <table>
        <thead>
            <tr>
                <th>ID</th>
                <th>書名</th>
                <th>作者</th>
                <th>類別</th>
                <th>可借數量</th>
                <th>建立時間</th>
                <th>操作</th>
            </tr>
        </thead>
        <tbody>
<?php if (empty($books)): ?>
            <!-- 沒有書籍資料時顯示提示列 -->
            <tr>
                <td class="empty" colspan="7">目前尚無書籍資料</td>
            </tr>
<?php else: ?>
<?php foreach ($books as $book): ?>
<?php // 先跳脫書名,供刪除按鈕的 confirm 對話框安全嵌入 JS 字串使用 ?>
<?php $confirmTitle = htmlspecialchars(addslashes($book['title']), ENT_QUOTES, 'UTF-8'); ?>
            <tr>
                <td><?= htmlspecialchars($book['id']) ?></td>
                <td><?= htmlspecialchars($book['title']) ?></td>
                <td><?= htmlspecialchars($book['author']) ?></td>
                <td><?= htmlspecialchars($book['category']) ?></td>
                <td><?= htmlspecialchars($book['available']) ?></td>
                <td><?= htmlspecialchars($book['created_at']) ?></td>
                <td>
                    <div class="actions">
                        <a class="edit-link" href="edit_book.php?id=<?= htmlspecialchars($book['id']) ?>">編輯</a>
                        <form class="delete-form" method="POST" action="delete_book.php" onsubmit="return confirm('確定要刪除《<?= $confirmTitle ?>》嗎?')">
                            <input type="hidden" name="id" value="<?= htmlspecialchars($book['id']) ?>">
                            <button type="submit" class="delete-btn">刪除</button>
                        </form>
                    </div>
                </td>
            </tr>
<?php endforeach; ?>
<?php endif; ?>
        </tbody>
    </table>
</body>
</html>                                                                  

更新後的結果:
更新書籍列表

步驟四:執行與瀏覽器驗證

輸入網址 http://localhost:8000/index.php,進行以下四種情境驗證:

  1. 編輯功能測試:點擊某本書的「編輯」,確認原欄位資料正確帶入。修改書名或數量後點擊「儲存變更」,確認返回列表後資料成功更新。
    更改數量:圖片描述
  2. 無效 ID 網址測試(安全防護):在網址列輸入不存在的 ID(例如 edit_book.php?id=99999 或 edit_book.php?id=abc),確認系統自動重導向回 index.php。
  3. 重複 ISBN 測試(例外攔截):將書籍 A 的 ISBN 改成與書籍 B 一模一樣,確認頁面捕捉到例外並跳出提示:「這個 ISBN 已經存在,請確認後再試一次」。
    重複 ISBN 測試
  4. 刪除功能與二次確認(POST 刪除):點擊「刪除」,確認跳出瀏覽器「確定要刪除這本書嗎?」彈窗,按下確定後該書籍順利從列表中移除。
    二次確認
    刪除功能

今天完成了書籍資料的編輯、刪除與前端介面串接,已經完整建立了 books 資料表的 CRUD 基礎功能。

不過目前的程式碼並未檢查「操作者的權限」。現階段專注在建置 CRUD 的核心邏輯;但在真實環境中,若缺乏身份驗證與角色權限控管,任何人都能透過 API 工具直接對 delete_book.php 發送請求來清空資料。因此在正式上線前,後端必須導入管理者 Session 驗證機制,才能有效防範越權存取的資安風險。


上一篇
Day 13 新增書籍
下一篇
Day 15 會員註冊與 password_hash()
系列文
從零打造圖書管理系統:WSL2 × MySQL × Claude Code 的整合實作 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言