Tiếp Phần 2 (đã có CSDL). Hôm nay dựng "bộ khung" cho toàn bộ website: kết nối PDO, các hàm dùng chung, và giao diện header/footer. Mọi file .php ở các phần sau đều sẽ include lại các file này, nên làm đúng ngay từ đầu là quan trọng nhất. ============================== 1. FILE config/database.php ============================== <?php // Thong tin ket noi CSDL - sua lai theo may cua ban $db_host = '127.0.0.1'; $db_port = 3306; $db_name = 'shopvn'; $db_user = 'root'; $db_pass = ''; try { $pdo = new PDO( "mysql:host={$db_host};port={$db_port};dbname={$db_name};charset=utf8mb4", $db_user, $db_pass, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, ] ); } catch (PDOException $e) { die('Khong ket noi duoc co so du lieu: ' . $e->getMessage()); } Giải thích 2 dòng ATTR quan trọng: - PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION: mọi lỗi SQL sẽ ném exception thay vì âm thầm bỏ qua, giúp phát hiện lỗi ngay khi code sai. - PDO::ATTR_EMULATE_PREPARES => false: bắt PHP dùng prepared statement THẬT của MySQL (không giả lập), đây là lớp bảo vệ chống SQL Injection hiệu quả nhất. ============================== 2. FILE includes/functions.php ============================== <?php if (session_status() === PHP_SESSION_NONE) { session_start(); } require_once __DIR__ . '/../config/database.php'; // Chong XSS: luon dung ham nay khi in du lieu tu CSDL/nguoi dung ra HTML function e(?string $value): string { return htmlspecialchars($value ?? '', ENT_QUOTES, 'UTF-8'); } // Dinh dang so tien kieu Viet Nam: 129000 -> "129.000 d" function format_price(int|float $price): string { return number_format((float) $price, 0, ',', '.') . ' d'; } // Chuyen chuoi tieng Viet co dau thanh slug an toan cho URL function slugify(string $text): string { $text = trim($text); $map = [ 'à'=>'a','á'=>'a','ạ'=>'a','ả'=>'a','ã'=>'a','â'=>'a','ầ'=>'a','ấ'=>'a','ậ'=>'a','ẩ'=>'a','ẫ'=>'a','ă'=>'a','ằ'=>'a','ắ'=>'a','ặ'=>'a','ẳ'=>'a','ẵ'=>'a', 'è'=>'e','é'=>'e','ẹ'=>'e','ẻ'=>'e','ẽ'=>'e','ê'=>'e','ề'=>'e','ế'=>'e','ệ'=>'e','ể'=>'e','ễ'=>'e', 'ì'=>'i','í'=>'i','ị'=>'i','ỉ'=>'i','ĩ'=>'i', 'ò'=>'o','ó'=>'o','ọ'=>'o','ỏ'=>'o','õ'=>'o','ô'=>'o','ồ'=>'o','ố'=>'o','ộ'=>'o','ổ'=>'o','ỗ'=>'o','ơ'=>'o','ờ'=>'o','ớ'=>'o','ợ'=>'o','ở'=>'o','ỡ'=>'o', 'ù'=>'u','ú'=>'u','ụ'=>'u','ủ'=>'u','ũ'=>'u','ư'=>'u','ừ'=>'u','ứ'=>'u','ự'=>'u','ử'=>'u','ữ'=>'u', 'ỳ'=>'y','ý'=>'y','ỵ'=>'y','ỷ'=>'y','ỹ'=>'y', 'đ'=>'d', ]; $lower = mb_strtolower($text, 'UTF-8'); $ascii = strtr($lower, $map); $ascii = preg_replace('/[^a-z0-9]+/u', '-', $ascii); $ascii = trim($ascii, '-'); return $ascii === '' ? 'san-pham' : $ascii; } function redirect(string $path): void { header('Location: ' . $path); exit; } function is_logged_in(): bool { return isset($_SESSION['user_id']); } function is_admin(): bool { return is_logged_in() && ($_SESSION['user_role'] ?? '') === 'admin'; } function require_login(): void { if (!is_logged_in()) { redirect('login.php'); } } function require_admin(): void { if (!is_admin()) { // Ham nay chi duoc goi tu cac file trong thu muc admin/, nen duong dan // tuong doi "login.php" se tro dung ve admin/login.php (Phan 8) redirect('login.php'); } } // So luong san pham dang co trong gio hang (session) function cart_count(): int { $count = 0; foreach ($_SESSION['cart'] ?? [] as $qty) { $count += (int) $qty; } return $count; } // ----- CSRF: chong gia mao form tu trang khac ----- function csrf_token(): string { if (empty($_SESSION['csrf_token'])) { $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); } return $_SESSION['csrf_token']; } function csrf_field(): string { return '<input type="hidden" name="csrf_token" value="' . csrf_token() . '">'; } function csrf_verify(): void { $token = $_POST['csrf_token'] ?? ''; if (!hash_equals($_SESSION['csrf_token'] ?? '', $token)) { http_response_code(400); die('Phien lam viec khong hop le, vui long tai lai trang va thu lai.'); } } Từ Phần 4 trở đi, MỌI file .php trong dự án sẽ bắt đầu bằng dòng: require_once __DIR__ . '/includes/functions.php'; (hoặc '../includes/functions.php' đối với file nằm trong thư mục admin/) - dòng này tự động include luôn config/database.php nên có sẵn biến $pdo để dùng. ============================== 3. FILE includes/header.php ============================== <?php // File nay duoc include boi cac trang o thu muc goc, nen duong dan anh/css la truc tiep ?> <!DOCTYPE html> <html lang="vi"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title><?= isset($page_title) ? e($page_title) . ' - ShopVN' : 'ShopVN - Mua sam truc tuyen' ?></title> <link rel="stylesheet" href="assets/css/style.css"> </head> <body> <header class="site-header"> <div class="container header-inner"> <a href="index.php" class="logo">Shop<span>VN</span></a> <form action="search.php" method="get" class="search-form"> <input type="text" name="q" placeholder="Tim san pham..." value="<?= e($_GET['q'] ?? '') ?>"> <button type="submit">Tim</button> </form> <nav class="header-nav"> <a href="cart.php" class="cart-link">Gio hang (<?= cart_count() ?>)</a> <?php if (is_logged_in()): ?> <a href="account.php"><?= e($_SESSION['user_name']) ?></a> <a href="logout.php">Dang xuat</a> <?php else: ?> <a href="login.php">Dang nhap</a> <a href="register.php">Dang ky</a> <?php endif; ?> </nav> </div> </header> <main class="container"> ============================== 4. FILE includes/footer.php ============================== </main> <footer class="site-footer"> <div class="container"> <p>&copy; <?= date('Y') ?> ShopVN - Website demo hoc lap trinh PHP + MySQL.</p> </div> </footer> </body> </html> ============================== 5. FILE assets/css/style.css (giao diện cơ bản, responsive) ============================== * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: Arial, Helvetica, sans-serif; background: #f5f6fa; color: #222; line-height: 1.6; } .container { max-width: 1140px; margin: 0 auto; padding: 0 16px; } a { text-decoration: none; color: inherit; } .site-header { background: #fff; box-shadow: 0 1px 4px rgba(0,0,0,.08); position: sticky; top: 0; z-index: 10; } .header-inner { display: flex; align-items: center; gap: 20px; padding: 14px 16px; flex-wrap: wrap; } .logo { font-size: 22px; font-weight: bold; color: #e53935; } .logo span { color: #222; } .search-form { flex: 1; display: flex; min-width: 200px; } .search-form input { flex: 1; padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px 0 0 4px; } .search-form button { padding: 8px 16px; background: #e53935; color: #fff; border: none; border-radius: 0 4px 4px 0; cursor: pointer; } .header-nav { display: flex; gap: 14px; align-items: center; font-size: 14px; } .header-nav a { padding: 6px 8px; } .header-nav a:hover { color: #e53935; } main.container { padding: 24px 16px; min-height: 60vh; } .page-title { font-size: 22px; margin-bottom: 16px; } .category-bar { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 20px; } .category-bar a { background: #fff; padding: 8px 16px; border-radius: 20px; border: 1px solid #ddd; font-size: 14px; } .category-bar a.active, .category-bar a:hover { background: #e53935; color: #fff; border-color: #e53935; } .product-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 18px; } .product-card { background: #fff; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 4px rgba(0,0,0,.08); transition: transform .15s; } .product-card:hover { transform: translateY(-3px); box-shadow: 0 4px 12px rgba(0,0,0,.12); } .product-card img { width: 100%; height: 180px; object-fit: cover; } .product-card .info { padding: 12px; } .product-card .name { font-size: 14px; font-weight: 600; min-height: 40px; } .price { color: #e53935; font-weight: bold; } .price-old { color: #999; text-decoration: line-through; font-size: 13px; margin-left: 6px; } .btn { display: inline-block; padding: 10px 20px; background: #e53935; color: #fff; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; } .btn:hover { background: #c62828; } .btn-outline { background: #fff; color: #e53935; border: 1px solid #e53935; } .form-box { background: #fff; max-width: 420px; margin: 0 auto; padding: 24px; border-radius: 8px; box-shadow: 0 1px 4px rgba(0,0,0,.08); } .form-group { margin-bottom: 14px; } .form-group label { display: block; margin-bottom: 6px; font-size: 14px; font-weight: 600; } .form-group input, .form-group textarea, .form-group select { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; } .alert { padding: 10px 14px; border-radius: 4px; margin-bottom: 14px; font-size: 14px; } .alert-error { background: #fdecea; color: #c62828; } .alert-success { background: #e8f5e9; color: #2e7d32; } table.data-table { width: 100%; border-collapse: collapse; background: #fff; } table.data-table th, table.data-table td { padding: 10px 12px; border-bottom: 1px solid #eee; text-align: left; font-size: 14px; } table.data-table th { background: #fafafa; } .site-footer { text-align: center; padding: 20px; color: #777; font-size: 13px; } @media (max-width: 640px) { .header-inner { flex-direction: column; align-items: stretch; } } Vậy là xong bộ khung. Từ Phần 4, mọi trang chỉ cần include header/footer là có ngay giao diện đồng bộ, không phải lặp lại HTML. Phần 4 sẽ code trang chủ (index.php), trang danh mục (category.php) và trang chi tiết sản phẩm (product.php) - bắt đầu hiển thị dữ liệu thật từ CSDL lên giao diện. Hẹn gặp lại! --- Hết Phần 3 --- ---------------------------------------- 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/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 Phần sau: 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