購物車是電商系統核心模組之一。對於中小型項目或無服務端 Session 存儲條件的場景,基於 Cookie 的客戶端存儲方案具有部署簡單、無需資料庫、天然支援分佈式等優勢。本文提供一個功能完整、結構清晰、生產可用的 CartApi 類,支援商品增刪改查、多規格價格、數量統計、自動序列化持久化等特性。
| 決策點 | 方案 | 理由 |
|---|---|---|
| 存儲介質 | Cookie (序列化陣列) | 無服務端狀態,天然水平擴展 |
| 數據結構 | 二維關聯陣列 [字段][商品ID] | 單次遍歷即可完成多種統計,記憶體效率高 |
| 序列化格式 | PHP serialize() |
保留類型信息,支援物件,比 JSON 更緊湊 |
| 過期策略 | 可配置 TTL,預設 86400 秒 (1天) | 平衡用戶體驗與 Cookie 體積限制 (4KB) |
| 併發處理 | 整體覆寫模式 | Cookie 無鎖機制,接受極低概率的覆寫丟失 |
<?php
/**
* Cookie 購物車核心類
*
* 特性:
* - 支援多規格價格 (price1/price2/price3)
* - 自動合併同款商品數量
* - 靈活的修改標誌 (增/減/設定/移除)
* - 價格統計與數量統計
* - 可配置過期時間
*/
class CartApi
{
// 內部數據結構:
// [0] => [id => id]
// [1] => [id => name]
// [2] => [id => price1]
// [3] => [id => price2]
// [4] => [id => price3]
// [5] => [id => count]
// [6] => [id => image]
private $cartArray = [];
private $cartCount = 0;
public $expires = 86400; // 秒,0 表示瀏覽器關閉即失效
public function __construct(
$id = "", $name = "", $price1 = "", $price2 = "", $price3 = "",
$count = "", $image = "", $expires = 86400
) {
if ($id !== "" && is_numeric($id)) {
$this->expires = $expires;
$this->addCart($id, $name, $price1, $price2, $price3, $count, $image);
}
}
/**
* 添加商品到購物車
* @return bool false=商品已存在(已合併數量) true=新增成功
*/
public function addCart($id, $name, $price1, $price2, $price3, $count, $image)
{
$this->cartArray = $this->cartView();
if ($this->checkItem($id)) {
$this->modifyCart($id, $count, 0); // 0 = 增加數量
return false;
}
$this->cartArray[0][$id] = $id;
$this->cartArray[1][$id] = $name;
$this->cartArray[2][$id] = $price1;
$this->cartArray[3][$id] = $price2;
$this->cartArray[4][$id] = $price3;
$this->cartArray[5][$id] = (int)$count;
$this->cartArray[6][$id] = $image;
$this->save();
return true;
}
/**
* 修改購物車商品
* @param int $flag 0:增加 1:減少 2:設定數量(0=移除) 3:移除
* @return bool
*/
public function modifyCart($id, $count, $flag = 0)
{
$this->cartArray = $this->cartView();
$arr = &$this->cartArray; // 參考操作
if (!is_array($arr[0]) || $id < 1) return false;
foreach ($arr[0] as $itemId) {
if ($itemId === $id) {
switch ($flag) {
case 0: // 增加
$arr[5][$id] += $count;
break;
case 1: // 減少
$arr[5][$id] -= $count;
if ($arr[5][$id] <= 0) $flag = 3; // 數量歸零自動移除
break;
case 2: // 設定具體數量
if ($count == 0) {
$flag = 3;
} else {
$arr[5][$id] = $count;
}
break;
case 3: // 移除/清空
default:
unset($arr[0][$id], $arr[1][$id], $arr[2][$id],
$arr[3][$id], $arr[4][$id], $arr[5][$id], $arr[6][$id]);
break;
}
$this->save();
return true;
}
}
return false;
}
/** 清空整個購物車 */
public function removeAll()
{
$this->cartArray = [];
$this->save();
}
/** 獲取完整購物車數據 (二維陣列) */
public function cartView()
{
$cookie = stripslashes($_COOKIE['cartapi'] ?? '');
if (!$cookie) return false;
return @unserialize($cookie) ?: false;
}
/** 檢查購物車是否有商品 */
public function checkCart()
{
$arr = $this->cartView();
return is_array($arr[0] ?? null) && count($arr[0]) > 0;
}
/**
* 價格統計
* @return array [0=>price1總計, 1=>price2總計, 2=>price3總計, 3=>總數量]
*/
public function countPrice()
{
$arr = $this->cartArray = $this->cartView();
$out = [0, 0, 0, 0];
if (is_array($arr[0])) {
foreach ($arr[0] as $key => $val) {
$qty = (int)$arr[5][$key];
$out[0] += (float)$arr[2][$key] * $qty;
$out[1] += (float)$arr[3][$key] * $qty;
$out[2] += (float)$arr[4][$key] * $qty;
$out[3] += $qty;
}
}
return $out;
}
/** 獲取商品種類數 */
public function cartCount()
{
$arr = $this->cartView();
$this->cartCount = is_array($arr[0]) ? count($arr[0]) : 0;
return $this->cartCount;
}
/** 持久化到 Cookie */
public function save()
{
$serialized = serialize($this->cartArray);
// 檢查 Cookie 大小限制 (約 4KB)
if (strlen($serialized) > 3800) {
trigger_error('購物車數據超過 Cookie 大小限制', E_USER_WARNING);
}
setcookie("cartapi", $serialized, time() + $this->expires, '/', '', false, true);
}
/** 內部檢查商品是否存在 */
private function checkItem($id)
{
$arr = $this->cartArray;
if (!is_array($arr[0])) return false;
return in_array($id, $arr[0], true);
}
}
<?php
require_once 'CartApi.php';
// 實例化即可添加 (構造函數自動調用 addCart)
$cart = new CartApi(
$_POST['goods_id'], // id
$_POST['goods_name'], // name
$_POST['price1'], // 價格1 (如會員價)
$_POST['price2'], // 價格2 (如批發價)
$_POST['price3'], // 價格3 (如零售價)
$_POST['quantity'] ?? 1, // 數量
$_POST['image_url'], // 圖片
86400 * 7 // 保存 7 天
);
// 重定向防止表單重複提交
header('Location: cart.php');
exit;
<?php
require_once 'CartApi.php';
$cart = new CartApi();
if (!$cart->checkCart()) {
echo '購物車是空的
';
exit;
}
$items = $cart->cartView();
$totals = $cart->countPrice(); // [price1總價, price2總價, price3總價, 總數量]
?>
商品 單價 數量 小計 操作
$val): ?>
= htmlspecialchars($items[1][$id]) ?>
¥= number_format($items[3][$id], 2) ?>
¥= number_format($items[3][$id] * $items[5][$id], 2) ?>
移除
合計
= $totals[3] ?> 件
¥= number_format($totals[1], 2) ?>
去結算
<?php
// cart_action.php
require_once 'CartApi.php';
$cart = new CartApi();
$action = $_GET['action'] ?? $_POST['action'] ?? '';
$id = (int)($_GET['id'] ?? $_POST['id'] ?? 0);
switch ($action) {
case 'update':
$count = (int)($_POST['count'] ?? 0);
$cart->modifyCart($id, $count, 2); // 2 = 設定具體數量
break;
case 'remove':
$cart->modifyCart($id, 0, 3); // 3 = 移除
break;
case 'clear':
$cart->removeAll();
break;
}
header('Location: ' . $_SERVER['HTTP_REFERER'] ?? 'cart.php');
exit;
gzcompress(serialize($data)) 存儲,讀取時 unserialize(gzuncompress($data))cart_1、cart_2 多個 Cookie// 簽名驗證防篡改
const SECRET = 'your-secret-key';
public function save() {
$data = serialize($this->cartArray);
$sig = hash_hmac('sha256', $data, SECRET);
setcookie("cartapi", $data, ...);
setcookie("cartapi_sig", $sig, ...);
}
public function cartView() {
$data = $_COOKIE['cartapi'] ?? '';
$sig = $_COOKIE['cartapi_sig'] ?? '';
if (!hash_equals(hash_hmac('sha256', $data, SECRET), $sig)) {
return false; // 數據被篡改,拋棄
}
return @unserialize($data);
}
當前以 id 為唯一鍵。若需支援「同款不同規格」(如顏色、尺碼),建議鍵值改為 "{$id}:{$spec_signature}",$spec_signature 為規格屬性有序序列化的哈希。
CartApi 類提供了開箱即用的 Cookie 購物車解決方案,代碼約 150 行,無第三方依賴,涵蓋電商核心流程所需的全部操作介面。通過本文的進階優化建議(體積控制、簽名驗證、規格擴展),可平滑支撐日均萬單級別的中小型電商業務。對於大型系統,建議採用「Cookie 暫存 + 登入後同步至 Redis/資料庫」的混合架構,兼顧匿名用戶體驗與數據一致性。