購物車是電商系統核心模組。本文解析一個經典的 PHP 購物車類別:使用 Session + Cookie 識別用戶,MySQL 持久化商品明細,提供增刪改查、金額計算、內容顯示等完整功能。
scid Cookie 存放 Session ID(MD5 唯一值),有效期 4 小時。cart (id, session, product, quantity) + inventory (product, price, description)。<?php
// Session/Cookie 初始化(放在入口檔案或共用 include)
if (!isset($_COOKIE['scid']) && !isset($_SESSION['scid'])) {
$session = md5(uniqid(rand(), true));
setcookie('scid', $session, time() + 14400); // 4 小時
$_SESSION['scid'] = $session;
} else {
$session = $_COOKIE['scid'] ?? $_SESSION['scid'];
}
class Cart {
private $table = 'cart';
// 檢查商品是否已在購物車
public function check_item($session, $product) {
$sql = "SELECT quantity FROM {$this->table} WHERE session=? AND product=?";
$stmt = $GLOBALS['db']->prepare($sql);
$stmt->execute([$session, $product]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
return $row ? (int)$row['quantity'] : 0;
}
// 新增或增加數量
public function add_item($session, $product, $quantity) {
$qty = $this->check_item($session, $product);
if ($qty == 0) {
$sql = "INSERT INTO {$this->table} (session, product, quantity) VALUES (?, ?, ?)";
$GLOBALS['db']->execute($sql, [$session, $product, $quantity]);
} else {
$quantity += $qty;
$sql = "UPDATE {$this->table} SET quantity=? WHERE session=? AND product=?";
$GLOBALS['db']->execute($sql, [$quantity, $session, $product]);
}
}
// 刪除商品
public function delete_item($session, $product) {
$sql = "DELETE FROM {$this->table} WHERE session=? AND product=?";
$GLOBALS['db']->execute($sql, [$session, $product]);
}
// 修改數量
public function modify_quantity($session, $product, $quantity) {
$sql = "UPDATE {$this->table} SET quantity=? WHERE session=? AND product=?";
$GLOBALS['db']->execute($sql, [$quantity, $session, $product]);
}
// 清空購物車
public function clear_cart($session) {
$sql = "DELETE FROM {$this->table} WHERE session=?";
$GLOBALS['db']->execute($sql, [$session]);
}
// 計算總金額
public function cart_total($session) {
$sql = "SELECT c.product, c.quantity, i.price
FROM {$this->table} c
JOIN inventory i ON c.product = i.product
WHERE c.session=?";
$stmt = $GLOBALS['db']->prepare($sql);
$stmt->execute([$session]);
$total = 0;
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$total += $row['price'] * $row['quantity'];
}
return $total;
}
// 取得詳細明細(含商品資訊)
public function display_contents($session) {
$sql = "SELECT c.product, c.quantity, i.price, i.description
FROM {$this->table} c
JOIN inventory i ON c.product = i.product
WHERE c.session=?
ORDER BY c.id";
$stmt = $GLOBALS['db']->prepare($sql);
$stmt->execute([$session]);
$contents = ['product'=>[], 'price'=>[], 'quantity'=>[], 'total'=>[], 'description'=>[]];
$count = 0;
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$contents['product'][$count] = $row['product'];
$contents['price'][$count] = $row['price'];
$contents['quantity'][$count] = $row['quantity'];
$contents['total'][$count] = $row['price'] * $row['quantity'];
$contents['description'][$count] = $row['description'];
$count++;
}
$contents['final'] = $this->cart_total($session);
return $contents;
}
// 商品種類數
public function num_items($session) {
$sql = "SELECT COUNT(*) FROM {$this->table} WHERE session=?";
$stmt = $GLOBALS['db']->prepare($sql);
$stmt->execute([$session]);
return $stmt->fetchColumn();
}
// 總數量
public function quant_items($session) {
$sql = "SELECT SUM(quantity) FROM {$this->table} WHERE session=?";
$stmt = $GLOBALS['db']->prepare($sql);
$stmt->execute([$session]);
return (int)$stmt->fetchColumn();
}
}
?>
<?php
require 'Cart.php';
$cart = new Cart();
$session = $_COOKIE['scid'] ?? session_id();
// 加入商品
if (isset($_POST['add_product'])) {
$cart->add_item($session, $_POST['product_id'], $_POST['qty'] ?? 1);
header('Location: cart.php');
exit;
}
// 顯示購物車
$items = $cart->display_contents($session);
?>
<table>
<tr><th>商品</th><th>單價</th><th>數量</th><th>小計</th><th>操作</th></tr>
<?php for ($i=0; $i
| 原始寫法 | 問題 | 改進 |
|---|---|---|
| 字串拼接 SQL | SQL 注入風險極高 | PDO 預處理語句 + 參數綁定 |
| mysql_* 函式 | PHP 7+ 已移除 | PDO / MySQLi |
| eval() 執行後端 JS | 極度危險、難維護 | 前端 AJAX + JSON 回傳 |
| 單一 $GLOBALS['db'] | 強耦合、難測試 | 建構子注入 PDO 實例 |
| Session ID 產生邏輯 | rand() 不夠隨機 | random_bytes() + uniqid() |
class Cart { public function __construct(PDO $db, string $table) {...} }CartInterface,方便替換實作(Redis、Session、資料庫)。CartItemAdded 事件,解耦庫存扣減、優惠計算等副作用。此購物車類別展示了「Session 識別 + 資料庫持久化 + 核心 CRUD」的經典模式。理解其邏輯後,應立即套用參數綁定、依賴注入、前後端分離等現代化實踐,構建安全、可維護、可擴展的電商核心模組。