1. 12:00 06/08/2026
Tiếp Phần 4 (đã hiển thị được sản phẩm). Hôm nay làm giỏ hàng - phần này KHÔNG cần tạo bảng CSDL riêng, chỉ cần lưu trong session của PHP là đủ cho website vừa và nhỏ. Cấu trúc giỏ hàng mình chọn:
$_SESSION['cart'] = [
product_id_1 => so_luong,
product_id_2 => so_luong,
...
];
Đơn giản, dễ hiểu: chỉ lưu ID sản phẩm và số lượng, còn tên/giá/ảnh thì lúc hiển thị mới JOIN sang bảng products để lấy dữ liệu MỚI NHẤT (đảm bảo nếu admin vừa đổi giá thì giỏ hàng cũng cập nhật đúng giá, không bị lưu giá cũ).
==============================
1. FILE cart_add.php (thêm vào giỏ)
==============================
<?php
require_once __DIR__ . '/includes/functions.php';
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
redirect('index.php');
}
csrf_verify();
$product_id = (int) ($_POST['product_id'] ?? 0);
$quantity = max(1, (int) ($_POST['quantity'] ?? 1));
$stmt = $pdo->prepare('SELECT id, stock FROM products WHERE id = ? AND is_active = 1');
$stmt->execute([$product_id]);
$product = $stmt->fetch();
if (!$product) {
die('San pham khong ton tai.');
}
if (!isset($_SESSION['cart'])) {
$_SESSION['cart'] = [];
}
$current_qty = $_SESSION['cart'][$product_id] ?? 0;
$new_qty = min($current_qty + $quantity, (int) $product['stock']);
$_SESSION['cart'][$product_id] = $new_qty;
redirect('cart.php');
==============================
2. FILE cart.php (xem giỏ hàng)
==============================
<?php
require_once __DIR__ . '/includes/functions.php';
$page_title = 'Gio hang';
$cart_items = [];
$total = 0;
if (!empty($_SESSION['cart'])) {
$ids = array_keys($_SESSION['cart']);
$placeholders = implode(',', array_fill(0, count($ids), '?'));
$stmt = $pdo->prepare(
"SELECT id, name, slug, price, sale_price, image, stock FROM products WHERE id IN ($placeholders)"
);
$stmt->execute($ids);
$products = $stmt->fetchAll();
foreach ($products as $p) {
$qty = (int) $_SESSION['cart'][$p['id']];
$price = $p['sale_price'] ?: $p['price'];
$subtotal = $price * $qty;
$total += $subtotal;
$cart_items[] = ['product' => $p, 'qty' => $qty, 'price' => $price, 'subtotal' => $subtotal];
}
}
require __DIR__ . '/includes/header.php';
?>
<h1 class="page-title">Gio hang cua ban</h1>
<?php if (empty($cart_items)): ?>
<p>Gio hang trong. <a href="index.php">Tiep tuc mua sam</a></p>
<?php else: ?>
<form action="cart_update.php" method="post">
<?= csrf_field() ?>
<table class="data-table">
<thead>
<tr>
<th>San pham</th>
<th>Don gia</th>
<th>So luong</th>
<th>Thanh tien</th>
<th></th>
</tr>
</thead>
<tbody>
<?php foreach ($cart_items as $item): $p = $item['product']; ?>
<tr>
<td>
<a href="product.php?slug=<?= e($p['slug']) ?>"><?= e($p['name']) ?></a>
</td>
<td><?= format_price($item['price']) ?></td>
<td>
<input type="number" name="qty[<?= (int) $p['id'] ?>]" value="<?= $item['qty'] ?>" min="1" max="<?= (int) $p['stock'] ?>" style="width:70px; padding:6px;">
</td>
<td><?= format_price($item['subtotal']) ?></td>
<td><a href="cart_remove.php?product_id=<?= (int) $p['id'] ?>&csrf_token=<?= e(csrf_token()) ?>" style="color:#e53935;">Xoa</a></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<div style="text-align:right; margin-top:16px;">
<p style="font-size:18px; margin-bottom:12px;">Tong cong: <strong class="price"><?= format_price($total) ?></strong></p>
<button type="submit" class="btn btn-outline">Cap nhat gio hang</button>
<a href="checkout.php" class="btn">Tien hanh dat hang</a>
</div>
</form>
<?php endif; ?>
<?php require __DIR__ . '/includes/footer.php'; ?>
==============================
3. FILE cart_update.php (cập nhật số lượng)
==============================
<?php
require_once __DIR__ . '/includes/functions.php';
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
redirect('cart.php');
}
csrf_verify();
$qtys = $_POST['qty'] ?? [];
foreach ($qtys as $product_id => $qty) {
$product_id = (int) $product_id;
$qty = (int) $qty;
if (!isset($_SESSION['cart'][$product_id])) {
continue;
}
if ($qty <= 0) {
unset($_SESSION['cart'][$product_id]);
continue;
}
// Gioi han lai theo ton kho thuc te, tranh khach sua tay so luong vuot ton kho
$stmt = $pdo->prepare('SELECT stock FROM products WHERE id = ?');
$stmt->execute([$product_id]);
$stock = (int) $stmt->fetchColumn();
$_SESSION['cart'][$product_id] = min($qty, max($stock, 0));
}
redirect('cart.php');
==============================
4. FILE cart_remove.php (xoá 1 sản phẩm khỏi giỏ)
==============================
<?php
require_once __DIR__ . '/includes/functions.php';
$token = $_GET['csrf_token'] ?? '';
if (!hash_equals($_SESSION['csrf_token'] ?? '', $token)) {
die('Yeu cau khong hop le.');
}
$product_id = (int) ($_GET['product_id'] ?? 0);
unset($_SESSION['cart'][$product_id]);
redirect('cart.php');
==============================
GIẢI THÍCH
==============================
- cart_add.php giới hạn số lượng thêm vào giỏ không vượt quá stock hiện tại (min(...)) - tránh khách đặt nhiều hơn số hàng còn trong kho.
- cart_remove.php dùng GET (bấm link) nhưng vẫn kiểm tra csrf_token qua query string - đây là cách đơn giản hoá cho 1 hành động ít nhạy cảm (chỉ xoá khỏi giỏ hàng CỦA CHÍNH MÌNH, không phải hành động phá hoại nghiêm trọng), nếu muốn chuẩn hơn nữa các bạn có thể chuyển thành 1 form POST nhỏ có nút "Xoá" thay vì thẻ a.
- Toàn bộ số lượng luôn được ép kiểu (int) và so sánh lại với stock trong CSDL ở MỌI bước (thêm, sửa, và sau này ở Phần 7 khi tạo đơn hàng) - không bao giờ tin tưởng số liệu gửi lên từ form một cách tuyệt đối.
Phần 6 tiếp theo: đăng ký, đăng nhập, đăng xuất thành viên - để khách có tài khoản trước khi đặt hàng ở Phần 7. Hẹn gặp lại!
--- Hết Phần 5 ---
----------------------------------------
MỤC LỤC LOẠT BÀI:
1. [Series PHP+MySQL] Làm Website Bán Hàng Từ A-Z - Phần 1: Tổng Quan Dự Án, Yêu Cầu Môi Trường & Cấu Trúc Thư Mục - https://diendan.anyvlogs.info/chu-de/457-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-1-tong-quan-du-an-yeu-cau-moi-truong-cau-truc-thu-muc
2. [Series PHP+MySQL] Làm Website Bán Hàng Từ A-Z - Phần 2: Thiết Kế Cơ Sở Dữ Liệu (Database) Đầy Đủ Cho Website Bán Hàng - https://diendan.anyvlogs.info/chu-de/458-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-2-thiet-ke-co-so-du-lieu-database-day-du-cho-website-ban-hang
3. [Series PHP+MySQL] Làm Website Bán Hàng Từ A-Z - Phần 3: Dựng Khung Ứng Dụng - Kết Nối PDO, Hàm Dùng Chung, Giao Diện Chung - https://diendan.anyvlogs.info/chu-de/459-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-3-dung-khung-ung-dung-ket-noi-pdo-ham-dung-chung-giao-dien-chung
4. [Series PHP+MySQL] Làm Website Bán Hàng Từ A-Z - Phần 4: Trang Chủ, Danh Mục Sản Phẩm & Chi Tiết Sản Phẩm - https://diendan.anyvlogs.info/chu-de/460-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-4-trang-chu-danh-muc-san-pham-chi-tiet-san-pham
>> 5. [Series PHP+MySQL] Làm Website Bán Hàng Từ A-Z - Phần 5: Xây Dựng Giỏ Hàng (Cart) Bằng Session - https://diendan.anyvlogs.info/chu-de/461-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-5-xay-dung-gio-hang-cart-bang-session
6. [Series PHP+MySQL] Làm Website Bán Hàng Từ A-Z - Phần 6: Đăng Ký, Đăng Nhập, Đăng Xuất Thành Viên - https://diendan.anyvlogs.info/chu-de/462-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-6-dang-ky-dang-nhap-dang-xuat-thanh-vien
7. [Series PHP+MySQL] Làm Website Bán Hàng Từ A-Z - Phần 7: Đặt Hàng, Thanh Toán COD & Lịch Sử Đơn Hàng - https://diendan.anyvlogs.info/chu-de/463-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-7-dat-hang-thanh-toan-cod-lich-su-don-hang
8. [Series PHP+MySQL] Làm Website Bán Hàng Từ A-Z - Phần 8: Trang Quản Trị (Admin) - Quản Lý Danh Mục, Sản Phẩm, Đơn Hàng - https://diendan.anyvlogs.info/chu-de/464-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-8-trang-quan-tri-admin-quan-ly-danh-muc-san-pham-don-hang
9. [Series PHP+MySQL] Làm Website Bán Hàng Từ A-Z - Phần 9: Bảo Mật Website (CSRF, XSS, SQL Injection, Upload An Toàn) - https://diendan.anyvlogs.info/chu-de/465-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-9-bao-mat-website-csrf-xss-sql-injection-upload-an-toan
10. [Series PHP+MySQL] Làm Website Bán Hàng Từ A-Z - Phần 10 (Kết Thúc): Tối Ưu Hiệu Năng & Triển Khai Lên Hosting Thật - https://diendan.anyvlogs.info/chu-de/466-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-10-ket-thuc-toi-uu-hieu-nang-trien-khai-len-hosting-that
Phần trước: https://diendan.anyvlogs.info/chu-de/460-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-4-trang-chu-danh-muc-san-pham-chi-tiet-san-pham
Phần sau: https://diendan.anyvlogs.info/chu-de/462-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-6-dang-ky-dang-nhap-dang-xuat-thanh-vien