購物車數據模型
CREATE TABLE `cart` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`session_id` VARCHAR(64) NOT NULL, -- 關聯用戶會話
`product_id` INT NOT NULL, -- 商品 ID
`quantity` INT UNSIGNED NOT NULL DEFAULT 1,
`added_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY `uk_session_product` (`session_id`, `product_id`),
INDEX `idx_session` (`session_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
現代 PDO 版購物車類
<?php
class Cart {
private PDO $pdo;
private string $sessionId;
private string $table = 'cart';
public function __construct(PDO $pdo, string $sessionId) {
$this->pdo = $pdo;
$this->sessionId = $sessionId;
}
/** 獲取購物車當前項目 */
public function getItems(): array {
$stmt = $this->pdo->prepare("
SELECT c.*, p.name, p.price, p.image, p.stock
FROM {$this->table} c
JOIN products p ON c.product_id = p.id
WHERE c.session_id = ?
ORDER BY c.added_at
");
$stmt->execute([$this->sessionId]);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
/** 添加商品(存在則增加數量) */
public function add(int $productId, int $quantity = 1): bool {
// 檢查庫存
$stmt = $this->pdo->prepare("SELECT stock FROM products WHERE id = ?");
$stmt->execute([$productId]);
$stock = $stmt->fetchColumn();
if ($stock === false || $stock < $quantity) return false;
$this->pdo->beginTransaction();
try {
// 嘗試更新
$stmt = $this->pdo->prepare("
UPDATE {$this->table} SET quantity = quantity + ?
WHERE session_id = ? AND product_id = ?
");
$stmt->execute([$quantity, $this->sessionId, $productId]);
if ($stmt->rowCount() === 0) {
// 不存在則插入
$stmt = $this->pdo->prepare("
INSERT INTO {$this->table} (session_id, product_id, quantity) VALUES (?, ?, ?)
");
$stmt->execute([$this->sessionId, $productId, $quantity]);
}
$this->pdo->commit();
return true;
} catch (Exception $e) {
$this->pdo->rollBack();
return false;
}
}
/** 更新數量(0 表示刪除) */
public function updateQuantity(int $productId, int $quantity): bool {
if ($quantity <= 0) return $this->remove($productId);
// 檢查庫存
$stmt = $this->pdo->prepare("SELECT stock FROM products WHERE id = ?");
$stmt->execute([$productId]);
if ($stmt->fetchColumn() < $quantity) return false;
$stmt = $this->pdo->prepare("
UPDATE {$this->table} SET quantity = ?
WHERE session_id = ? AND product_id = ?
");
return $stmt->execute([$quantity, $this->sessionId, $productId]);
}
/** 刪除單項 */
public function remove(int $productId): bool {
$stmt = $this->pdo->prepare("DELETE FROM {$this->table} WHERE session_id = ? AND product_id = ?");
return $stmt->execute([$this->sessionId, $productId]);
}
/** 清空購物車 */
public function clear(): bool {
$stmt = $this->pdo->prepare("DELETE FROM {$this->table} WHERE session_id = ?");
return $stmt->execute([$this->sessionId]);
}
/** 獲取總價 */
public function getTotal(): float {
$stmt = $this->pdo->prepare("
SELECT SUM(c.quantity * p.price) as total
FROM {$this->table} c
JOIN products p ON c.product_id = p.id
WHERE c.session_id = ?
");
$stmt->execute([$this->sessionId]);
return (float)($stmt->fetchColumn() ?: 0);
}
/** 獲取商品種類數 */
public function getItemCount(): int {
$stmt = $this->pdo->prepare("SELECT COUNT(*) FROM {$this->table} WHERE session_id = ?");
$stmt->execute([$this->sessionId]);
return (int)$stmt->fetchColumn();
}
/** 獲取商品總件數 */
public function getTotalQuantity(): int {
$stmt = $this->pdo->prepare("SELECT SUM(quantity) FROM {$this->table} WHERE session_id = ?");
$stmt->execute([$this->sessionId]);
return (int)($stmt->fetchColumn() ?: 0);
}
}
控制器使用示例
<?php
// cart.php
session_start();
require_once 'Cart.php';
require_once 'pdo_config.php';
$cart = new Cart($pdo, session_id());
$action = $_GET['action'] ?? 'view';
switch ($action) {
case 'add':
$pid = (int)($_POST['product_id'] ?? 0);
$qty = (int)($_POST['quantity'] ?? 1);
if ($pid && $cart->add($pid, $qty)) {
header('Location: cart.php?msg=added');
} else {
header('Location: cart.php?err=stock');
}
exit;
case 'update':
$pid = (int)($_POST['product_id'] ?? 0);
$qty = (int)($_POST['quantity'] ?? 0);
if ($pid && $cart->updateQuantity($pid, $qty)) {
header('Location: cart.php?msg=updated');
} else {
header('Location: cart.php?err=update');
}
exit;
case 'remove':
$pid = (int)($_GET['product_id'] ?? 0);
if ($pid && $cart->remove($pid)) {
header('Location: cart.php?msg=removed');
}
exit;
case 'clear':
$cart->clear();
header('Location: cart.php?msg=cleared');
exit;
default: // view
$items = $cart->getItems();
$total = $cart->getTotal();
include 'cart_view.php';
}
視圖模板 (cart_view.php)
<form method="post" action="cart.php?action=update">
<table class="cart-table">
<thead>
<tr><th>商品</th><th>單價</th><th>數量</th><th>小計</th><th>操作</th></tr>
</thead>
<tbody>
<?php foreach ($items as $item): ?>
<tr>
<td><img src="<?= $item['image'] ?>" width="50"> <?= htmlspecialchars($item['name']) ?></td>
<td><?= number_format($item['price'], 2) ?></td>
<td>
<input type="number" name="quantity[<?= $item['product_id'] ?>]"
value="<?= $item['quantity'] ?>" min="0" max="<?= $item['stock'] ?>" style="width:60px">
</td>
<td><?= number_format($item['price'] * $item['quantity'], 2) ?></td>
<td>
<a href="cart.php?action=remove&product_id=<?= $item['product_id'] ?>"
onclick="return confirm('確定刪除?')>刪除</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<div class="cart-actions">
<button type="submit">更新購物車</button>
<a href="cart.php?action=clear" onclick="return confirm('清空購物車?')>清空</a>
<a href="checkout.php">去結算</a>
</div>
</form>
<div class="cart-summary">總計:<strong><?= number_format($total, 2) ?></strong> 元</div>
Session 型購物車(無需數據庫,適合簡單場景)
<?php
class SessionCart {
private const KEY = 'cart_items';
public static function get(): array {
return $_SESSION[self::KEY] ?? [];
}
public static function add(int $productId, int $qty = 1): void {
$cart = self::get();
$cart[$productId] = ($cart[$productId] ?? 0) + $qty;
$_SESSION[self::KEY] = $cart;
}
public static function update(int $productId, int $qty): void {
$cart = self::get();
if ($qty <= 0) unset($cart[$productId]);
else $cart[$productId] = $qty;
$_SESSION[self::KEY] = $cart;
}
public static function remove(int $productId): void {
$cart = self::get();
unset($cart[$productId]);
$_SESSION[self::KEY] = $cart;
}
public static function clear(): void {
unset($_SESSION[self::KEY]);
}
public static function getTotal(PDO $pdo): float {
$cart = self::get();
if (!$cart) return 0;
$ids = implode(',', array_keys($cart));
$stmt = $pdo->query("SELECT id, price FROM products WHERE id IN ($ids)");
$prices = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
$total = 0;
foreach ($cart as $pid => $qty) {
$total += ($prices[$pid] ?? 0) * $qty;
}
return $total;
}
}
選型建議
| 場景 | 推薦方案 |
| 用戶登錄制、多端同步、持久化需求 | 數據庫型(上方 Cart 類) |
| 遊客瀏覽、單會話、簡單商品 | Session 型 |
| 高併發、分佈式 | Redis 哈希結構 HSET cart:{session} {pid} {qty} |