Module 1 - 網路應用程式四大元件

探討網頁架構、伺服器機制及前端與伺服器端的運作對比

四大元件運作原理

費曼簡化比喻:網頁應用的「餐廳理論」

把整個網頁架構想成去餐廳吃飯:
1. Web Browser (瀏覽器/客戶端):就是「點餐的客人」,發送請求並享用最後上桌的菜餚。
2. Web Server (網頁伺服器,如 Apache):就是「服務生」,負責接收訂單、傳遞需求並送菜。
3. Database Server (資料庫,如 MySQL):就是「儲藏室」,存放著所有新鮮的食材原料。
4. Server-side Language (後端語言,如 PHP):就是後廚的「主廚」,根據訂單從儲藏室撈食材,加工煮成佳餚(HTML),再交給服務生端給客人。

  • Client-Side 職責: 擅長驗證使用者輸入、響應瀏覽器端的即時事件(如 JavaScript 彈出視窗),完全不涉及伺服器硬碟。
  • Server-Side 職責: 擅長存取伺服器上的資料庫進行安全讀寫,控制不同狀態(如登入/管理員權限)下的動態網頁生成。

歷屆常見考點與考題陷阱

常見致命錯誤觀念

陷阱: 考試常問「客戶端程式碼(JavaScript)可以直接安全地讀寫伺服器上的 MySQL 資料庫嗎?」
正解: 絕對不行!客戶端程式執行在客人的瀏覽器中,根本無法也不應擁有伺服器資料庫的連線權限。必須透過伺服器端的 PHP 發送 SQL 查詢才可以讀寫資料庫。

核心滿分知識點摘要

  • Apache 特性: 開源 HTTP 伺服器,多工作業,硬體資源消耗低、快速且具備高可擴展性。預設文件根目錄(Document Root)常為 htdocswww
  • MySQL 特性: 高效能關聯式資料庫管理系統(RDBMS),支援多使用者並符合 SQL 標準。

PHP 解析生命週期

費曼邏輯推演:PHP 程式碼的生命週期

當瀏覽器請求一個 .php 文件時,伺服器不是直接把代碼送回去,而是經歷四步:
1. 請求: 瀏覽器發送 `.php` 網頁文件請求。
2. 轉交: Web Server(Apache)收到後發現副檔名是 `.php`,立刻轉交給 PHP 解析器 (PHP Parser)
3. 執行: Parser 執行 PHP 標籤 <?php ... ?> 內的所有邏輯與運算。
4. 回傳: Parser 把執行結果轉化為**純 HTML/CSS** 文字,交還給 Apache 傳回瀏覽器渲染。所以客人在瀏覽器「檢視原始碼」時,絕對看不見任何一行 PHP 代碼!

考題趨勢與輸出差異

核心考點:echo 與 print() 的本質區別

考試極常考填空或選擇兩者的差異:
1. echo: 輸出一個或多個字串,速度較快,且**沒有任何回傳值**。
2. print(): 輸出字串,類似 JavaScript 的 document.write(),但它是一個函式,**成功執行時會固定回傳 1**,因此可以用在更複雜的條件表達式中。

基礎語法滿分模板

<?php
  // 標準宣告與變數解析
  $course = "ITP4523M";
  
  echo "單引號與雙引號的重大差異:<br>";
  echo "雙引號會解析變數:This is $course<br>"; // 輸出: This is ITP4523M
  echo '單引號不解析變數:This is $course<br>'; // 輸出: This is $course
?>

表單資料動態防呆驗證

在實務網頁開發或期末大題中,驗證使用者傳來的表單數據(漏填、未選取)是核心安全關卡。利用關聯式陣列對所有輸入欄位進行遍歷與防呆,是最乾淨且不容易出錯的架構。

考題趨勢:文字框與單選框的驗證陷阱

考點陷阱:strlen() 與 isset() 的適配對象

1. **文字框 (Text / Textarea)**:即使用戶沒填,POST 傳過來的依然是個空字串 "",它的變數是**存在**的!所以不能用 isset() 檢查,必須使用 strlen($_POST['field']) > 0empty()
2. **單選框/核取框 (Radio / Checkbox)**:如果用戶完全沒點選,POST 陣列裡**根本不會有這個 key**!此時直接讀取會觸發 Undefined Index 錯誤,因此**必須使用 isset($_POST['radio_name'])** 進行檢查!

全欄位動態防呆遍歷滿分語法模板

<?php
// 滿分結構:定義表單所有欄位屬性與顯示文字
$fields = array(
  'myname' => array('text', 'Name'),
  'gYear' => array('text', 'Graduate Year'),
  'isAlumni' => array('radio', 'Alumni')
);

$dataOK = true;
$errMsg = "";

foreach ($fields as $fieldname => $info) {
  $type = $info[0];
  $label = $info[1];
  
  if ($type == 'text') {
    if (strlen(trim($_POST[$fieldname])) == 0) {
      $errMsg .= "$label 欄位不可空白!<br>";
      $dataOK = false;
    }
  } else if ($type == 'radio') {
    if (!isset($_POST[$fieldname])) {
      $errMsg .= "請選取 $label 選項!<br>";
      $dataOK = false;
    }
  }
}
?>

檔案上傳設定與 $_FILES 陣列

檔案上傳必須在 HTML 表單加上 enctype="multipart/form-data"。而在伺服器端,最大上傳限制受到 php.ini 的設定檔控制。我們可以利用 ini_get('post_max_size') 查詢本機目前的限制上限。

歷屆上傳大題核心必考點

5個核心檔案屬性與搬移函數

上傳檔案的資料會被存放在 $_FILES 二維陣列中,考試必考五大屬性填空:
1. $_FILES['file']['name']: 原始檔案名稱。
2. $_FILES['file']['type']: 檔案的 Mime Type (如 image/jpeg)。
3. $_FILES['file']['size']: 檔案大小 (以 Bytes 為單位)。
4. $_FILES['file']['tmp_name']: 檔案上傳後在伺服器端的**暫存路徑**。
5. $_FILES['file']['error']: 錯誤代碼 (0 代表成功)。

核心函數: 必須使用 move_uploaded_file(暫存路徑, 目標路徑) 將檔案從暫存區安全移至網頁資料夾中!

安全檔案上傳防呆處理模板

<?php
$target_folder = "upload/";
$target_file = $target_folder . basename($_FILES["fileToUpload"]["name"]);
$uploadOk = 1;

// 1. 防重複檢查
if (file_exists($target_file)) {
  echo "Sorry, file already exists.<br>";
  $uploadOk = 0;
}

// 2. 限制檔案大小 (500KB 限額)
if ($_FILES["fileToUpload"]["size"] > 500000) {
  echo "檔案太大!<br>";
  $uploadOk = 0;
}

// 3. 執行安全搬移
if ($uploadOk == 1) {
  if (move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $target_file)) {
    echo "上傳成功!";
  } else {
    echo "搬移過程發生非預期錯誤。";
  }
}
?>

資料庫連接與 CRUD 控制流程

網頁應用程式的核心在於處理動態資料。在 PHP 中,我們會使用 mysqli_connect 連接 MySQL,執行 SQL 指令(SELECT, INSERT, UPDATE, DELETE),並利用 mysqli_fetch_assoc() 撈取資料庫記錄為關聯式陣列。

考題趨勢:防重複與重導向控制

一頁式寫入流程控制重點

在實作新增記錄(INSERT)時,考卷常要求「防止資料重複寫入」以及「執行完畢後重定向」:
1. **防重複:** 先執行 SELECT 查詢該 ID 是否已存在。接著使用 mysqli_num_rows($rs) > 0 判斷,若大於 0 則代表重複,應立刻中斷流程。
2. **重定向跳轉:** 使用 header("location: 頁面.php?msg=訊息") 將網頁導回列表頁。注意:呼叫 header() 之前,網頁前方不能有任何的 HTML 或 echo 輸出!

防重複一頁式寫入滿分代碼模板

<?php
if (isset($_POST['tfID'])) {
  require_once("conn.php");
  
  // 步驟 1:防重複查詢
  $check_sql = "SELECT * FROM Customers WHERE custID = '" . $_POST['tfID'] . "'";
  $rs = mysqli_query($conn, $check_sql);
  
  if (mysqli_num_rows($rs) > 0) {
    // 已存在,跳轉並帶入錯誤訊息
    header("location:" . $_SERVER['PHP_SELF'] . "?msg=" . urlencode("Record already exist!"));
  } else {
    // 步驟 2:安全寫入
    $insert_sql = "INSERT INTO Customers (custID, custName) VALUES ('".$_POST['tfID']."', '".$_POST['tfName']."')";
    mysqli_query($conn, $insert_sql);
    
    if (mysqli_affected_rows($conn) > 0) {
      header("location:" . $_SERVER['PHP_SELF'] . "?msg=" . urlencode("Added successfully"));
    }
  }
}
?>

資料庫連接異常攔截

當資料庫伺服器宕機、密碼錯誤、或是 SQL 語法拼錯時,程式如果沒有進行處理,會直接崩潰或暴露出敏感的伺服器資訊。搭配 or die() 可以在出錯的當下攔截並印出乾淨的偵錯內容。

考試常見填空函數對比

核心辨析:mysqli_connect_error() vs mysqli_error()

這兩個錯誤處理函數極容易在填空題中混淆:
1. mysqli_connect_error(): **不需要任何參數**。專門用在 mysqli_connect() 失敗時,抓取「資料庫連線失敗」的理由。
2. mysqli_error($conn): **必須傳入資料庫連線變數 `$conn`**。用在 mysqli_query() 失敗時,抓取「SQL 語法寫錯、資料表不存在」等查詢層面的錯誤訊息。

標準安全連接與偵錯範例

<?php
// 1. 連線層面錯誤處理 (不用放參數)
$conn = mysqli_connect("127.0.0.1", "root", "", "labdb") or die(mysqli_connect_error());

// 2. 查詢層面錯誤處理 (必須放 $conn 變數)
$sql = "SELECT * FROM invalid_table_name";
$rs = mysqli_query($conn, $sql) or die(mysqli_error($conn));
?>

狀態管理與無狀態協議

HTTP 是一個無狀態協議 (Stateless protocol)。這意味著瀏覽器上一次發送的請求與下一次發送的請求,在伺服器眼中是完全獨立、毫無關聯的(伺服器沒有記憶能力)。為了讓網頁記得使用者(例如保持登入狀態、購物車),必須使用狀態管理技術:Cookie 與 Session。

Cookie 與 Session 世紀大對比

費曼比喻:俱樂部置物櫃理論

1. Cookie (客戶端暫存):就像你把會員資料卡**放在自己口袋裡**。每次去俱樂部都掏出來給櫃檯看。因為在你自己口袋,你隨時可以把它丟掉,或者是別人也可能偷看,安全度較低。
2. Session (伺服器端儲存):就像你把貴重物品安全地**鎖在俱樂部的置物櫃中**。櫃檯只交給你一把「置物櫃鑰匙」(這把鑰匙就是 Session ID,會透過 Cookie 存在你的瀏覽器中)。每次你交出鑰匙,伺服器就去後台對應的櫃子拿資料。安全度極高!

比較維度CookieSession
儲存位置客戶端瀏覽器 (Client-side)網頁伺服器記憶體/檔案 (Server-side)
生命週期可自訂過期時間 (即便瀏覽器關閉依然存在)預設在**關閉瀏覽器**時即刻消失
安全性較低 (容易遭篡改)極高 (使用者無法直接摸到真實資料)

狀態宣告核心基礎

<?php
// 建立一個名字為 user, 值為 Stanley, 期限為 1 小時的 Cookie
setcookie("user", "Stanley", time() + 3600);

// 使用 Session 之前,必須在網頁最頂端呼叫啟動方法
session_start();
$_SESSION['role'] = "admin";
?>

會話生命週期管理與安全銷毀

管理 Session 的狀態包含:確認變數是否存在、更新內容(例如累加存取次數),以及在使用者點擊「登出」時,安全徹底地清除置物櫃裡所有的變數與包裏,確保帳號安全。

考題趨勢:三種銷毀 Session 函數辨析

核心語法填空:unset、session_unset 與 session_destroy

考試常在登出系統的大題中,要求填入正確的銷毀函數。它們的威力與範圍完全不同:
1. unset($_SESSION['key']);: 僅僅清除、釋放**單一個**會話變數(例如只拿掉購物車,但保留登入狀態)。
2. session_unset();: 清空當前 Session 箱子裡**所有的會話變數**,釋放所有記憶體,但箱子本身(Session ID 鑰匙)還在。
3. session_destroy();: 徹底**銷毀、瓦解整個 Session 會話**,把伺服器上的檔案徹底刪除,鑰匙直接作廢。

完整網頁存取計數器與登出銷毀模板

網頁累加計數器實作:

<?php
session_start();
if (!isset($_SESSION['access_count'])) {
  $_SESSION['access_count'] = 1;
  echo "這是您第一次造訪本網頁!";
} else {
  $_SESSION['access_count'] += 1;
  echo "您已經重新載入造訪了 " . $_SESSION['access_count'] . " 次!";
}
?>

標準安全登出銷毀流程:

<?php
session_start();
// 1. 清空所有變數值
session_unset();
// 2. 徹底銷毀會話箱子
session_destroy();
// 3. 跳轉回登入首頁
header("location: login.php");
?>

陣列操作與字串處理函數

精通 PHP 的內建函數能大幅提高程式碼撰寫效率。在考試卷中,最常與 Heredoc 字串大題或關聯陣列混合考核。

核心滿分函數考點記憶庫

  • 陣列尾端自動追加: 使用空方括號語法 $array[] = "新元素"; 可以不需指定索引,自動將值推入陣列最後方。
  • 移除特定索引項目: 使用 unset($serviceBookings['booking1']); 可以將指定鍵值的項目從記憶體中完全移除。
  • 字串長度與單字量計算: strlen($str) 用於計算字串的總字元長度;str_word_count($str) 用於統計字串內含的英文單字個數。

陣列與字串綜合實戰代碼

<?php
// 1. 陣列追加與刪除
$petCategories = ["Dog", "Cat"];
$petCategories[] = "Fish"; // 動態追加
unset($petCategories[0]); // 移除 Dog

// 2. Heredoc 字串處理與函數計算
$campaign = <<<EOD
this is a biggest pet exhibition in HK
Come and join us to explore the wonderful world of pets!
EOD;

echo "字元長度:" . strlen($campaign) . "<br>"; // 輸出: 101
echo "單字個數:" . str_word_count($campaign); // 輸出: 19
?>

Advanced PHP - 物件導向程式設計 (OOP)

從 PHP 5 開始,系統全面支援物件導向開發樣式。我們可以宣告類別 (Class),將屬性變數與成員函式封裝在一起,並透過 new 關鍵字將其實例化為實體物件。

物件導向核心必考語法細節

語法細節:成員呼叫符號與構造函數

考試若考出 OOP 題型,有兩個地方是失分高發期:
1. **成員呼叫符號:** 在 JavaScript 中我們用點符號(`object.method()`),但在 PHP 中**必須使用瘦箭頭符號($object->method())**!
2. **建構子魔術方法:** 在類別內部宣告建構子(當物件被 new 出來時會立刻自動執行的初始化函式),其名稱必須嚴格固定寫為 **function __construct()**(前方是兩個連續的底線!)。

標準類別宣告與實例化模板

<?php
class Member {
  var $uName;
  
  // 建構子魔術方法 (雙底線)
  function __construct($name) {
    $this->uName = $name;
  }
  
  function showWelcome() {
    echo "Welcome, " . $this->uName;
  }
}

// 實例化物件並調用成員方法 (使用 -> 符號)
$user1 = new Member("Lai Tuk Sui");
$user1->showWelcome(); // 輸出: Welcome, Lai Tuk Sui
?>