參考 CodeIgniter 經典購物車庫,實現一個輕量、Session 持久化、支援選項 (options) 的購物車類。功能涵蓋:加入/更新/刪除商品、統計總金額/總種類/總數量、單項小計、清空購物車、資料驗證與錯誤日誌。
class Cart {
private $productIdRules = 'a-z0-9_\-.';
private $productNameRules = 'a-z0-9_\-.:';
private $debug = true;
private $_cart = [];
public function __construct() {
session_start();
if (isset($_SESSION['cart_contents'])) {
$this->_cart = $_SESSION['cart_contents'];
} else {
$this->_cart = ['cart_total' => 0, 'total_items' => 0];
}
}
/** 加入商品 (支援單筆或批量陣列) */
public function insert($items) {
if (!is_array($items) || empty($items)) return false;
$saved = false;
$items = isset($items['id']) ? [$items] : $items; // 統一為二維
foreach ($items as $item) {
if ($this->_insert($item)) $saved = true;
}
if ($saved) { $this->_save(); return true; }
return false;
}
/** 更新數量 (依 rowid) */
public function update($items) {
if (!is_array($items) || empty($items)) return false;
$saved = false;
$items = isset($items['rowid']) ? [$items] : $items;
foreach ($items as $item) {
if ($this->_update($item)) $saved = true;
}
if ($saved) { $this->_save(); return true; }
return false;
}
/** 取得購物車內容 */
public function contents() { return $this->_cart; }
public function total() { return $this->_cart['cart_total']; }
public function totalItems() { return $this->_cart['total_items']; }
/** 清空購物車 */
public function destroy() {
$this->_cart = ['cart_total' => 0, 'total_items' => 0];
unset($_SESSION['cart_contents']);
}
// --- 私有方法 ---
private function _insert($item) {
// 驗證必要欄位
foreach (['id','qty','price','name'] as $f) {
if (!isset($item[$f])) return $this->_log("缺少欄位: $f");
}
// 數量清理
$item['qty'] = (int)preg_replace('/[^0-9]/', '', $item['qty']);
if ($item['qty'] <= 0) return $this->_log("數量無效");
// ID/名稱格式驗證
if (!preg_match("/^[$this->productIdRules]+$/i", $item['id'])) return $this->_log("ID格式錯誤");
if (!preg_match("/^[$this->productNameRules]+$/i", $item['name'])) return $this->_log("名稱格式錯誤");
// 價格清理
$item['price'] = (float)preg_replace('/[^0-9.]/', '', $item['price']);
if ($item['price'] < 0) return $this->_log("價格無效");
// 生成 rowid (含 options 區分)
$rowid = isset($item['options']) && $item['options']
? md5($item['id'] . serialize($item['options']))
: md5($item['id']);
unset($this->_cart[$rowid]);
$this->_cart[$rowid] = ['rowid' => $rowid] + $item;
return true;
}
private function _update($item) {
if (!isset($item['rowid'], $item['qty']) || !isset($this->_cart[$item['rowid']])) return false;
$item['qty'] = (int)preg_replace('/[^0-9]/', '', $item['qty']);
if (!is_numeric($item['qty'])) return false;
if ($this->_cart[$item['rowid']]['qty'] == $item['qty']) return false; // 無變化
if ($item['qty'] <= 0) {
unset($this->_cart[$item['rowid']]);
} else {
$this->_cart[$item['rowid']]['qty'] = $item['qty'];
}
return true;
}
private function _save() {
unset($this->_cart['total_items'], $this->_cart['cart_total']);
$total = 0;
foreach ($this->_cart as $key => $val) {
if (!is_array($val) || !isset($val['price'], $val['qty'])) continue;
$subtotal = $val['price'] * $val['qty'];
$this->_cart[$key]['subtotal'] = $subtotal;
$total += $subtotal;
}
$this->_cart['total_items'] = count($this->_cart);
$this->_cart['cart_total'] = $total;
if (count($this->_cart) <= 2) { // 只有統計欄位
unset($_SESSION['cart_contents']);
} else {
$_SESSION['cart_contents'] = $this->_cart;
}
}
private function _log($msg) {
if ($this->debug) file_put_contents('cart_error.log', date('Y-m-d H:i:s')." $msg\n", FILE_APPEND);
return false;
}
}
session_start();
require 'Cart.php';
$cart = new Cart();
// 加入商品
$cart->insert([
['id' => 'SKU001', 'qty' => 2, 'price' => 199.00, 'name' => 'Wireless Mouse', 'options' => ['color' => 'black']],
['id' => 'SKU002', 'qty' => 1, 'price' => 899.00, 'name' => 'Mechanical Keyboard'],
]);
// 更新數量 (需先取得 rowid)
$items = $cart->contents();
foreach ($items as $rowid => $item) {
if ($item['id'] === 'SKU001') {
$cart->update(['rowid' => $rowid, 'qty' => 3]);
break;
}
}
// 顯示購物車
echo "總金額: {$cart->total()} 元\n";
echo "商品種類: {$cart->totalItems()}\n";
foreach ($cart->contents() as $rowid => $item) {
if (!is_array($item) || !isset($item['price'])) continue;
echo "- {$item['name']} x{$item['qty']} = {$item['subtotal']} 元\n";
}
// 清空
// $cart->destroy();
_save() 計算總價前套用折扣邏輯。insert/update 時呼叫庫存服務驗證。