選修單元 B:基本網站製作(EBCH4)

本章以教學用的 users 資料表作示範,講解如何以 PHP 使用 MySQLi 連接數據庫並查詢資料:由連線、執行查詢、逐行取出結果集,到輸出成 HTML。 你亦會學習把用戶輸入帶入結構化查詢語言(SQL)時的安全寫法、SQL 注入風險,以及兩種常見防護方式:mysqli_real_escape_string()Prepared Statement(參數化查詢)。 最後補上 CRUD 完整流程、錯誤處理與安全性要點。

1 示範數據庫與資料表:先看清「有甚麼資料」

重點

  • 能指出數據庫數據庫管理系統(DBMS)資料表之間的關係。
  • 能說明結構化查詢語言(SQL)的用途:用於查詢與管理數據庫內的資料。
  • 在寫 SQL 之前,先理解「要查的是哪一張資料表、哪一些欄、甚麼條件」。

示範資料表:test_dbusers

在本頁所有示範與練習中,我們都以此資料表作為查詢目標。請先看清楚「欄」與「例子資料」,再開始寫 SQL。

資料表結構(Schema)

欄名(field) 資料型態(示意) 用途 例子
id INTEGER(主鍵;自動遞增) 記錄編號,用於精確定位一筆記錄 1、2、3…
username TEXT / VARCHAR 用戶名稱(示範用) admin、student、chan_it
password TEXT / VARCHAR 密碼(教學示範:明文) password123、teacherpass
role TEXT / VARCHAR 角色/身分(示範用) admin、user、teacher

例子資料(節錄 8 行)

id username password role
1adminpassword123admin
2studentpassword123user
3teacher1teacherpassteacher
4teacher2teacherpassteacher
5teacher3teacherpassteacher
6teacher4teacherpassteacher
7teacher5teacherpassteacher
8chan_itpw_chanuser

模擬數據庫:即時檢視與重置

本頁的模擬器會以「模擬數據庫」方式執行 SQL。當你在 CRUD 練習中新增/更新/刪除記錄後, 可用下方工具即時查看目前 test_db 內的資料表與資料,並在需要時把示範數據庫還原至預設狀態。

SQL 是「向資料表提問」

寫 SQL 時,你其實是在回答三個核心問題: (1)查哪一張資料表(2)取哪些欄(3)有甚麼條件/排序/限制。 例如:

2 MySQLi:連線與查詢流程

重點

  • 能寫出 MySQLi 的基本流程:連線 → 查詢 → 取資料 → 輸出
  • 能分辨四個常用函式的用途、輸入與回傳:mysqli_connect()mysqli_query()mysqli_fetch_assoc()mysqli_num_rows()
  • 能在程式中處理「連線失敗/查詢失敗/結果為 0」等情況。

函式速查表:輸入與回傳

函式 需要輸入甚麼? 回傳甚麼? 常見用途
mysqli_connect(host, user, pass, db) 主機、帳戶、密碼、數據庫名稱 連線物件/false 建立連線,供後續查詢使用
mysqli_query(conn, sql) 連線物件、SQL 字串 結果集/truefalse 執行 SELECT/INSERT/UPDATE/DELETE 等指令
mysqli_fetch_assoc(result) 結果集 關聯式陣列/null 逐行讀取結果,常與 while 迴圈配合
mysqli_num_rows(result) 結果集 整數(列數) 顯示「找到 X 筆資料」或判斷 0 筆結果

一行示範:常用 MySQLi 呼叫(每行一個示範)

以下每一行都是一個常用動作;實務上你會把它們串成「連線 → 查詢 → 取資料 → 輸出」。

$conn = mysqli_connect("localhost", "root", "", "test_db");
$result = mysqli_query($conn, "SELECT * FROM users");
$row = mysqli_fetch_assoc($result); // 取得下一行(關聯式陣列)
$count = mysqli_num_rows($result); // 結果集列數

代碼小練習:填寫 mysqli_connect() 的 4 個參數

練習目標:根據題目提供的數據庫資料,補齊連線參數,並在成功時輸出指定文字。

即時示範(模擬器):連線與查詢

示範 1:列出用戶表(前 5 筆)

示範 2:WHERE 過濾 + mysqli_num_rows

Check Point 1:MySQLi 連線與查詢流程(四選一;≥40 題)

題目以 MySQLi 函式的輸入/回傳/用途為主,並包含程式片段理解題。

3 逐行解釋示範程式:查詢 users 並輸出表格

重點

  • 能逐行解釋程式在做甚麼:變量、連線、查詢、迴圈、輸出。
  • 能指出「結果集」需要用 mysqli_fetch_assoc() 逐行讀取。
  • 能說明為何輸出時要使用 htmlspecialchars()(避免把資料當成 HTML 解讀)。

示範(可試玩):逐行註解已寫在代碼內

說明已用 // 註解 方式直接寫入程式碼。你可以在模擬器內捲動查看註解,並按「執行」觀察輸出結果。

4 把用戶輸入帶入 SQL:安全寫法

重點

  • 能分辨 GETPOST 的用途,並在合適情境選用。
  • 能在伺服器端對輸入進行有效性檢驗(例如:空值、長度、允許清單)。
  • 在把輸入帶入 SQL 前,先「限制」輸入範圍;不要直接相信用戶傳入的字串。

即時示範(模擬器):GET + 白名單驗證 + 查詢

Check Point 2:用戶輸入帶入 SQL(四選一;≥40 題)

題目以「填空/找出正確寫法」為主:補齊 $_GET[...]、補齊查詢條件、補齊有效性檢驗邏輯等。

5.1 SQL 注入防護:mysqli_real_escape_string()

重點

  • mysqli_real_escape_string() 用於逃脫字串中的特殊字符,降低以字串拼接造成的 SQL 注入風險。
  • 它主要針對「把用戶輸入放進引號字串」的情況;對於 SQL 結構(例如:欄名、ORDER BY)不適用。
  • 此方法仍屬「較脆弱」的防線;一般建議以 Prepared Statement 作主力。

字串與跳脫字符

當你把用戶輸入放入 SQL 字串時,必須處理引號、反斜線等特殊字符,否則輸入可能被解讀為 SQL 結構的一部分。mysqli_real_escape_string() 會按照目前連線的規則,把輸入中的特殊字符轉成較安全的形式,從而降低注入風險。

注意:逃脫字串並非萬能;較穩健的做法是使用 Prepared Statement(參數化查詢),詳見下一節。

即時示範(模擬器):逃脫字串後再拼接 SQL

小練習(建議)

  • 嘗試輸入:' OR '1'='1,觀察 escape 後的查詢結果是否仍會「回傳大量資料」。
  • 再到下一節用 Prepared Statement 以相同輸入測試,對照兩種做法的差異。

5.2 SQL 注入防護:Prepared Statement(參數化查詢)

重點

  • Prepared Statement 以「佔位符 + 綁定參數」方式執行查詢,避免把用戶輸入直接拼接進 SQL。
  • 參數化查詢把「SQL 結構」與「資料」分開處理,因此能有效降低 SQL 注入風險。
  • 常見流程:mysqli_prepare()bind_param()execute()get_result()

即時示範(模擬器):Prepared Statement 精確查詢

6 CRUD 完整流程、錯誤處理與安全性

重點

  • 能指出 CRUD 的四種操作與對應 SQL:Create/Read/Update/Delete。
  • 能用同一張資料表設計完整流程:輸入 → 有效性檢驗 →(Prepared Statement)→ 執行 → 處理結果 → 輸出。
  • 能處理常見例外:連線失敗、查詢失敗、結果為 0、輸入無效。
  • 理解基本安全原則:伺服器端有效性檢驗、最小權限、避免回傳敏感錯誤訊息。

完整流程範例(同一張 users 資料表)

1) Create:新增(INSERT)

<?php $conn = mysqli_connect("localhost","root","","test_db"); if(!$conn){ echo "系統繁忙"; exit; } $username = trim($_POST["username"] ?? ""); if($username === "" || strlen($username) > 30){ echo "輸入無效"; exit; } $stmt = mysqli_prepare($conn, "INSERT INTO users (username, password, role) VALUES (?, ?, ?)"); $pw = "pw_temp"; $role = "user"; mysqli_stmt_bind_param($stmt, "sss", $username, $pw, $role); mysqli_stmt_execute($stmt); echo "新增完成"; ?>

2) Read:讀取單筆(SELECT WHERE id=?)

<?php $id = intval($_GET["id"] ?? 0); if($id <= 0){ echo "輸入無效"; exit; } $stmt = mysqli_prepare($conn, "SELECT id, username, role FROM users WHERE id = ?"); mysqli_stmt_bind_param($stmt, "i", $id); mysqli_stmt_execute($stmt); $result = mysqli_stmt_get_result($stmt); if(mysqli_num_rows($result) === 0){ echo "沒有符合資料"; }else{ $row = mysqli_fetch_assoc($result); echo htmlspecialchars($row["username"]); } ?>

3) Update:更新(UPDATE)

<?php $id = intval($_POST["id"] ?? 0); $role = trim($_POST["role"] ?? ""); $allowedRole = ["admin","user","teacher"]; if($id <= 0 || !in_array($role, $allowedRole, true)){ echo "輸入無效"; exit; } $stmt = mysqli_prepare($conn, "UPDATE users SET role = ? WHERE id = ?"); mysqli_stmt_bind_param($stmt, "si", $role, $id); mysqli_stmt_execute($stmt); echo "更新完成"; ?>

4) Delete:刪除(DELETE)

<?php $id = intval($_POST["id"] ?? 0); if($id <= 0){ echo "輸入無效"; exit; } $stmt = mysqli_prepare($conn, "DELETE FROM users WHERE id = ?"); mysqli_stmt_bind_param($stmt, "i", $id); mysqli_stmt_execute($stmt); // 顯示剩餘筆數(較穩健做法:COUNT(*)) $r2 = mysqli_query($conn, "SELECT COUNT(*) AS c FROM users"); $row = mysqli_fetch_assoc($r2); echo "剩餘 " . intval($row["c"]) . " 筆"; ?>

Check Point 3:CRUD、錯誤處理與安全性(四選一;≥40 題)

題目涵蓋 CRUD、常見錯誤處理策略、最小權限與避免洩漏敏感錯誤訊息等。

Coding 代碼題(20 題;一行一題)

每題均提供:提示/核對答案/顯示參考答案。請按題目要求輸出指定文字或結構,以便「核對答案」自動評分。

代碼題 1:連線
代碼題 2:SELECT *
代碼題 3:指定欄
代碼題 4:WHERE(固定值)
代碼題 5:mysqli_num_rows
代碼題 6:LIKE 搜尋
代碼題 7:輸入有效性檢驗
代碼題 8:escape(示範)
代碼題 9:Prepared(示範)
代碼題 10:INSERT
代碼題 11:SELECT 單筆(prepared)
代碼題 12:UPDATE
代碼題 13:DELETE
代碼題 14:0 筆結果提示
代碼題 15:一般化錯誤訊息
代碼題 16:卡片式輸出
代碼題 17:排序(ORDER BY)
代碼題 18:LIMIT 分頁
代碼題 19:綜合(搜尋→驗證→prepared→表格→筆數)
代碼題 20:Prepared INSERT + 顯示新 id