บทเรียนการสร้างเว็บไซต์พื้นฐาน

มินิโปรเจ็ค - Mini CMS ระบบจัดการบทความ

ในส่วนนี้ เราจะนำความรู้ทั้งหมดที่ได้เรียนรู้มาตั้งแต่ HTML, CSS, JavaScript และ PHP/MySQL มารวมกันเพื่อสร้างโปรเจ็คจริงที่ใช้งานได้ทันที โดยเน้นการใช้ PDO และ การรักษาความปลอดภัย


7.1 Mini CMS - ระบบจัดการบทความ

ภาพรวมโปรเจ็ค

Mini CMS เป็นระบบจัดการบทความแบบง่ายที่มีฟังก์ชัน CRUD (Create, Read, Update, Delete) ครบถ้วน เหมาะสำหรับเริ่มต้นสร้างเว็บไซต์บล็อกหรือระบบข่าวสาร

ฟีเจอร์หลัก:

  • ✅ แสดงรายการบทความทั้งหมด
  • ✅ เพิ่มบทความใหม่
  • ✅ แก้ไขบทความที่มีอยู่
  • ✅ ลบบทความ (พร้อม CSRF Protection)
  • ✅ ค้นหาและกรองบทความ
  • ✅ Responsive Design
  • ✅ ความปลอดภัยระดับสูง (PDO, CSRF Token, XSS Protection)

เทคโนโลยีที่ใช้: HTML, CSS, JavaScript, PHP (PDO), MySQL

🚀 ดูตัวอย่างที่พร้อมใช้งาน | 📥 ดาวน์โหลดโค้ด

การออกแบบฐานข้อมูล

เราจะสร้างฐานข้อมูลชื่อ mini_cms_db และตารางหลักชื่อ articles

Column Data Type Description
id INT(11) Primary Key, Auto Increment
title VARCHAR(255) ชื่อบทความ
content TEXT เนื้อหาบทความ
author VARCHAR(100) ชื่อผู้เขียน
category VARCHAR(50) หมวดหมู่บทความ
created_at DATETIME วันที่สร้างบทความ
updated_at DATETIME วันที่แก้ไขล่าสุด

SQL สำหรับสร้างฐานข้อมูลและตาราง:

-- สร้างฐานข้อมูล
CREATE DATABASE IF NOT EXISTS mini_cms_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE mini_cms_db;

-- สร้างตาราง articles
CREATE TABLE articles (
    id INT(11) PRIMARY KEY AUTO_INCREMENT,
    title VARCHAR(255) NOT NULL,
    content TEXT NOT NULL,
    author VARCHAR(100) DEFAULT 'ไม่ระบุชื่อ',
    category VARCHAR(50) DEFAULT 'ทั่วไป',
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_category (category),
    INDEX idx_created_at (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- เพิ่มข้อมูลตัวอย่าง
INSERT INTO articles (title, content, author, category) VALUES
('การเริ่มต้นเรียนรู้ HTML', 'HTML (HyperText Markup Language) เป็นภาษามาร์กอัปที่ใช้สำหรับสร้างโครงสร้างเว็บเพจ เป็นพื้นฐานสำคัญที่ทุกคนที่ต้องการสร้างเว็บไซต์ควรเรียนรู้\n\nHTML ประกอบด้วย Elements ต่างๆ ที่ใช้กำหนดโครงสร้างเนื้อหา เช่น หัวข้อ ย่อหน้า รูปภาพ ลิงก์ และอื่นๆ อีกมากมาย', 'สมชาย ใจดี', 'HTML'),

('CSS: ทำให้เว็บไซต์สวยงาม', 'CSS (Cascading Style Sheets) เป็นภาษาที่ใช้ในการตกแต่งและจัดรูปแบบหน้าเว็บ ทำให้เว็บไซต์ดูสวยงามและน่าสนใจ\n\nด้วย CSS เราสามารถกำหนดสี ฟอนต์ ระยะห่าง การจัดวาง และเอฟเฟกต์ต่างๆ ได้อย่างอิสระ รวมถึงการทำ Responsive Design เพื่อรองรับหน้าจอขนาดต่างๆ', 'สมหญิง รักเรียน', 'CSS'),

('JavaScript เบื้องต้น', 'JavaScript เป็นภาษาโปรแกรมที่ทำให้เว็บไซต์มีความโต้ตอบและมีชีวิตชีวา สามารถสร้างฟีเจอร์ต่างๆ เช่น การตรวจสอบฟอร์ม การแสดงผลแบบไดนามิก และการเชื่อมต่อกับ API\n\nJavaScript ทำงานบนฝั่ง Client (เบราว์เซอร์) และสามารถจัดการ DOM (Document Object Model) เพื่อเปลี่ยนแปลงเนื้อหาหน้าเว็บแบบ Real-time', 'วิชัย โค้ดดี', 'JavaScript'),

('PHP และ MySQL: Backend Development', 'PHP เป็นภาษาโปรแกรมฝั่งเซิร์ฟเวอร์ที่นิยมใช้ในการพัฒนาเว็บไซต์ ทำงานร่วมกับ MySQL ซึ่งเป็นระบบจัดการฐานข้อมูล\n\nด้วย PHP และ MySQL เราสามารถสร้างระบบที่มีการจัดเก็บข้อมูล เช่น ระบบสมาชิก ระบบบทความ ระบบร้านค้าออนไลน์ และอื่นๆ อีกมากมาย', 'ประยุทธ์ เว็บมาสเตอร์', 'PHP'),

('Responsive Web Design', 'Responsive Web Design คือการออกแบบเว็บไซต์ให้สามารถปรับตัวแสดงผลได้ดีบนอุปกรณ์ทุกขนาด ไม่ว่าจะเป็นมือถือ แท็บเล็ต หรือคอมพิวเตอร์\n\nเทคนิคสำคัญ ได้แก่ การใช้ Media Queries, Flexible Grid Layout, และ Flexible Images เพื่อให้เว็บไซต์มีประสบการณ์การใช้งานที่ดีบนทุกอุปกรณ์', 'สุดา ดีไซน์เนอร์', 'CSS'),

('เทคนิคการเขียน SQL ที่มีประสิทธิภาพ', 'SQL (Structured Query Language) เป็นภาษาที่ใช้ในการจัดการฐานข้อมูล การเขียน SQL ที่ดีจะช่วยให้ระบบทำงานได้เร็วและมีประสิทธิภาพ\n\nเทคนิคสำคัญ เช่น การใช้ Index, การหลีกเลี่ยง SELECT *, การใช้ JOIN อย่างถูกต้อง และการเขียน Query ที่เหมาะสมกับโครงสร้างฐานข้อมูล', 'ชัยวัฒน์ ดาต้าเบส', 'PHP');

โครงสร้างไฟล์โปรเจกต์

mini-cms/
├── index.php         // หน้าแรก: แสดงรายการบทความ (Read)
├── create.php        // หน้าเพิ่มบทความ (Create)
├── edit.php          // หน้าแก้ไขบทความ (Update)
├── delete.php        // สคริปต์สำหรับลบบทความ (Delete)
├── view.php          // หน้าแสดงบทความแบบเต็ม
├── config.php        // ไฟล์ตั้งค่าฐานข้อมูล (PDO + Security)
├── database.sql      // SQL สำหรับสร้างฐานข้อมูล
├── styles/
│   └── main.css      // ไฟล์ CSS
└── scripts/
    └── main.js       // ไฟล์ JavaScript

7.2 การตั้งค่าฐานข้อมูลด้วย PDO (config.php)

ไฟล์ config.php เป็นหัวใจสำคัญที่จัดการการเชื่อมต่อฐานข้อมูลและฟังก์ชันความปลอดภัย

<?php
// การตั้งค่าฐานข้อมูล
$host = "localhost";
$db_user = "root";
$db_pass = ""; // เปลี่ยนเป็นรหัสผ่านของคุณ
$db_name = "mini_cms_db";

try {
    // สร้างการเชื่อมต่อด้วย PDO
    $pdo = new PDO("mysql:host=$host;dbname=$db_name;charset=utf8mb4", $db_user, $db_pass);

    // ตั้งค่า Error Mode เป็น Exception
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    // ตั้งค่าให้ fetch ข้อมูลเป็น Associative Array
    $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);

} catch(PDOException $e) {
    die("การเชื่อมต่อล้มเหลว: " . $e->getMessage());
}

// ตั้งค่า Session ที่ปลอดภัย
ini_set('session.cookie_httponly', 1);
ini_set('session.cookie_secure', 0); // เปลี่ยนเป็น 1 ถ้าใช้ HTTPS
ini_set('session.cookie_samesite', 'Strict');
ini_set('session.use_strict_mode', 1);

session_start();

// ฟังก์ชันช่วยเหลือ
function sanitize($data) {
    return htmlspecialchars(strip_tags(trim($data)), ENT_QUOTES, 'UTF-8');
}

function redirect($url) {
    header("Location: $url");
    exit();
}

// ฟังก์ชันสร้าง CSRF Token
function generateCsrfToken() {
    if (empty($_SESSION['csrf_token'])) {
        $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
    }
    return $_SESSION['csrf_token'];
}

// ฟังก์ชันตรวจสอบ CSRF Token
function verifyCsrfToken($token) {
    return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token);
}

// ฟังก์ชัน Security Headers
function setSecurityHeaders() {
    header('X-Frame-Options: DENY');
    header('X-Content-Type-Options: nosniff');
    header('X-XSS-Protection: 1; mode=block');
    header('Referrer-Policy: strict-origin-when-cross-origin');
}

setSecurityHeaders();
?>

จุดสำคัญ:

  • ✅ ใช้ PDO แทน mysqli เพื่อความยืดหยุ่นและปลอดภัย
  • ✅ ตั้งค่า Error Mode เป็น Exception เพื่อจัดการ error ได้ดีกว่า
  • ✅ ตั้งค่า Session ให้ปลอดภัย (httponly, samesite)
  • ✅ มีฟังก์ชัน CSRF Token เพื่อป้องกัน CSRF Attack
  • ✅ มีฟังก์ชัน sanitize() เพื่อป้องกัน XSS
  • ✅ ตั้งค่า Security Headers อัตโนมัติ

7.3 การสร้างหน้าแสดงผล (Read) - index.php

หน้าแรกที่แสดงรายการบทความทั้งหมด พร้อมฟีเจอร์ค้นหาและกรอง

<?php
include 'config.php';

// รับค่าการค้นหาและหมวดหมู่
$search = isset($_GET['search']) ? sanitize($_GET['search']) : '';
$category = isset($_GET['category']) ? sanitize($_GET['category']) : '';

// สร้าง SQL Query ด้วย Prepared Statement
$sql = "SELECT id, title, content, author, category, created_at FROM articles WHERE 1=1";
$params = [];

if (!empty($search)) {
    $sql .= " AND (title LIKE :search OR content LIKE :search)";
    $params['search'] = "%$search%";
}

if (!empty($category)) {
    $sql .= " AND category = :category";
    $params['category'] = $category;
}

$sql .= " ORDER BY created_at DESC";

try {
    $stmt = $pdo->prepare($sql);
    $stmt->execute($params);
    $articles = $stmt->fetchAll();

    // ดึงหมวดหมู่ทั้งหมด
    $categories_stmt = $pdo->query("SELECT DISTINCT category FROM articles ORDER BY category");
    $categories = $categories_stmt->fetchAll();

} catch(PDOException $e) {
    die("Error: " . $e->getMessage());
}
?>
<!DOCTYPE html>
<html lang="th">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Mini CMS - ระบบจัดการบทความ</title>
    <link rel="stylesheet" href="styles/main.css">
</head>
<body>
    <div class="container">
        <header class="site-header">
            <h1>📝 Mini CMS</h1>
            <p class="tagline">ระบบจัดการบทความแบบง่าย</p>
        </header>

        <div class="toolbar">
            <a href="create.php" class="button-primary">+ เพิ่มบทความใหม่</a>
        </div>

        <!-- ฟอร์มค้นหาและกรอง -->
        <div class="search-filter">
            <form method="GET" action="index.php" class="search-form">
                <input type="text" name="search" placeholder="ค้นหาบทความ..."
                       value="<?php echo htmlspecialchars($search); ?>" class="search-input">
                <select name="category" class="category-select">
                    <option value="">ทุกหมวดหมู่</option>
                    <?php foreach($categories as $cat): ?>
                        <option value="<?php echo htmlspecialchars($cat['category']); ?>"
                                <?php echo ($category == $cat['category']) ? 'selected' : ''; ?>>
                            <?php echo htmlspecialchars($cat['category']); ?>
                        </option>
                    <?php endforeach; ?>
                </select>
                <button type="submit" class="button-search">🔍 ค้นหา</button>
                <?php if (!empty($search) || !empty($category)): ?>
                    <a href="index.php" class="button-clear">✕ ล้าง</a>
                <?php endif; ?>
            </form>
        </div>

        <!-- รายการบทความ -->
        <div class="articles-container">
            <?php
            if (count($articles) > 0) {
                foreach($articles as $row) {
                    $excerpt = mb_substr(strip_tags($row["content"]), 0, 150);
                    ?>
                    <article class="article-card">
                        <div class="article-header">
                            <h2 class="article-title">
                                <a href="view.php?id=<?php echo $row["id"]; ?>">
                                    <?php echo htmlspecialchars($row["title"]); ?>
                                </a>
                            </h2>
                            <span class="category-badge"><?php echo htmlspecialchars($row["category"]); ?></span>
                        </div>
                        <div class="article-meta">
                            <span class="author">👤 <?php echo htmlspecialchars($row["author"]); ?></span>
                            <span class="date">📅 <?php echo date('d/m/Y H:i', strtotime($row["created_at"])); ?></span>
                        </div>
                        <p class="article-excerpt"><?php echo htmlspecialchars($excerpt); ?>...</p>
                        <div class="article-actions">
                            <a href="view.php?id=<?php echo $row["id"]; ?>" class="button-view">อ่านต่อ</a>
                            <a href="edit.php?id=<?php echo $row["id"]; ?>" class="button-edit">แก้ไข</a>
                            <a href="delete.php?id=<?php echo $row["id"]; ?>&csrf_token=<?php echo generateCsrfToken(); ?>"
                               class="button-delete"
                               onclick="return confirm('แน่ใจหรือไม่ที่จะลบบทความนี้?')">ลบ</a>
                        </div>
                    </article>
                    <?php
                }
            } else {
                echo '<div class="no-articles">
                        <div class="no-articles-icon">📭</div>
                        <h3>ไม่พบบทความ</h3>
                        <p>ลองค้นหาด้วยคำอื่น หรือ <a href="create.php">เพิ่มบทความใหม่</a></p>
                      </div>';
            }
            ?>
        </div>
    </div>

    <script src="scripts/main.js"></script>
</body>
</html>

จุดสำคัญ:

  • ✅ ใช้ PDO Prepared Statements สำหรับ query ที่มี parameter
  • ✅ ใช้ htmlspecialchars() ทุกที่ที่แสดงข้อมูล (ป้องกัน XSS)
  • ✅ เพิ่ม CSRF Token ในลิงก์ลบ
  • ✅ รองรับการค้นหาและกรองข้อมูล
  • ✅ Responsive Design

7.4 การสร้างหน้าเพิ่มบทความ (Create) - create.php

หน้าสำหรับเพิ่มบทความใหม่ พร้อม CSRF Protection และ Input Validation

<?php
include 'config.php';

$message = '';
$message_type = '';
$title = $content = $author = $category = '';

if ($_SERVER["REQUEST_METHOD"] == "POST") {
    // ตรวจสอบ CSRF Token
    if (!verifyCsrfToken($_POST['csrf_token'] ?? '')) {
        die("CSRF token validation failed");
    }

    $title = sanitize($_POST['title']);
    $content = $_POST['content']; // เก็บ content แบบเต็ม
    $author = sanitize($_POST['author']);
    $category = sanitize($_POST['category']);

    // ตรวจสอบข้อมูล
    if (empty($title) || empty($content)) {
        $message = "กรุณากรอกชื่อบทความและเนื้อหา";
        $message_type = "error";
    } else {
        try {
            // ใช้ PDO Prepared Statement เพื่อความปลอดภัย
            $stmt = $pdo->prepare("INSERT INTO articles (title, content, author, category) VALUES (:title, :content, :author, :category)");

            $stmt->execute([
                'title' => $title,
                'content' => $content,
                'author' => $author,
                'category' => $category
            ]);

            $message = "เพิ่มบทความสำเร็จ!";
            $message_type = "success";
            // ล้างค่าฟอร์ม
            $title = $content = $author = $category = '';

        } catch(PDOException $e) {
            $message = "เกิดข้อผิดพลาด: " . $e->getMessage();
            $message_type = "error";
        }
    }
}
?>
<!DOCTYPE html>
<html lang="th">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>เพิ่มบทความใหม่ - Mini CMS</title>
    <link rel="stylesheet" href="styles/main.css">
</head>
<body>
    <div class="container">
        <header class="site-header">
            <h1>📝 เพิ่มบทความใหม่</h1>
        </header>

        <div class="toolbar">
            <a href="index.php" class="button-secondary">← กลับหน้าแรก</a>
        </div>

        <?php if (!empty($message)): ?>
            <div class="message <?php echo $message_type; ?>">
                <?php echo $message; ?>
                <?php if ($message_type == 'success'): ?>
                    <a href="index.php" class="message-link">ดูรายการบทความ</a>
                <?php endif; ?>
            </div>
        <?php endif; ?>

        <div class="form-container">
            <form action="create.php" method="POST" class="article-form">
                <input type="hidden" name="csrf_token" value="<?php echo generateCsrfToken(); ?>">

                <div class="form-group">
                    <label for="title">ชื่อบทความ <span class="required">*</span></label>
                    <input type="text"
                           id="title"
                           name="title"
                           required
                           maxlength="255"
                           value="<?php echo htmlspecialchars($title); ?>"
                           placeholder="กรอกชื่อบทความ"
                           class="form-input">
                </div>

                <div class="form-row">
                    <div class="form-group">
                        <label for="author">ชื่อผู้เขียน</label>
                        <input type="text"
                               id="author"
                               name="author"
                               maxlength="100"
                               value="<?php echo htmlspecialchars($author); ?>"
                               placeholder="ไม่ระบุชื่อ"
                               class="form-input">
                    </div>

                    <div class="form-group">
                        <label for="category">หมวดหมู่</label>
                        <select id="category" name="category" class="form-select">
                            <option value="ทั่วไป" <?php echo ($category == 'ทั่วไป') ? 'selected' : ''; ?>>ทั่วไป</option>
                            <option value="HTML" <?php echo ($category == 'HTML') ? 'selected' : ''; ?>>HTML</option>
                            <option value="CSS" <?php echo ($category == 'CSS') ? 'selected' : ''; ?>>CSS</option>
                            <option value="JavaScript" <?php echo ($category == 'JavaScript') ? 'selected' : ''; ?>>JavaScript</option>
                            <option value="PHP" <?php echo ($category == 'PHP') ? 'selected' : ''; ?>>PHP</option>
                            <option value="MySQL" <?php echo ($category == 'MySQL') ? 'selected' : ''; ?>>MySQL</option>
                        </select>
                    </div>
                </div>

                <div class="form-group">
                    <label for="content">เนื้อหาบทความ <span class="required">*</span></label>
                    <textarea id="content"
                              name="content"
                              rows="15"
                              required
                              placeholder="เขียนเนื้อหาบทความที่นี่..."
                              class="form-textarea"><?php echo htmlspecialchars($content); ?></textarea>
                    <div class="form-hint">รองรับการขึ้นบรรทัดใหม่และย่อหน้า</div>
                </div>

                <div class="form-actions">
                    <button type="submit" class="button-primary">💾 บันทึกบทความ</button>
                    <button type="reset" class="button-secondary">🔄 ล้างฟอร์ม</button>
                </div>
            </form>
        </div>
    </div>

    <script src="scripts/main.js"></script>
</body>
</html>

จุดสำคัญ:

  • ✅ มี CSRF Token ในฟอร์ม (hidden input)
  • ✅ ตรวจสอบ CSRF Token ก่อนประมวลผล
  • ✅ ใช้ PDO Prepared Statements กับ Named Parameters
  • ✅ มี maxlength ใน input fields
  • ✅ แสดงข้อความ success/error
  • ✅ ล้างฟอร์มหลังบันทึกสำเร็จ

7.5 การสร้างหน้าแก้ไข (Update) - edit.php

หน้าสำหรับแก้ไขบทความที่มีอยู่

<?php
include 'config.php';

$message = '';
$message_type = '';
$id = isset($_GET['id']) ? intval($_GET['id']) : 0;

if ($id <= 0) {
    redirect('index.php');
}

try {
    // ดึงข้อมูลเดิมมาแสดง
    $stmt = $pdo->prepare("SELECT title, content, author, category FROM articles WHERE id = :id");
    $stmt->execute(['id' => $id]);
    $article = $stmt->fetch();

    if (!$article) {
        redirect('index.php');
    }

    // ประมวลผลเมื่อมีการส่งฟอร์มแก้ไข
    if ($_SERVER["REQUEST_METHOD"] == "POST") {
        // ตรวจสอบ CSRF Token
        if (!verifyCsrfToken($_POST['csrf_token'] ?? '')) {
            die("CSRF token validation failed");
        }

        $title = sanitize($_POST['title']);
        $content = $_POST['content'];
        $author = sanitize($_POST['author']);
        $category = sanitize($_POST['category']);

        if (empty($title) || empty($content)) {
            $message = "กรุณากรอกชื่อบทความและเนื้อหา";
            $message_type = "error";
        } else {
            $stmt_update = $pdo->prepare("UPDATE articles SET title = :title, content = :content, author = :author, category = :category WHERE id = :id");

            $stmt_update->execute([
                'title' => $title,
                'content' => $content,
                'author' => $author,
                'category' => $category,
                'id' => $id
            ]);

            $message = "แก้ไขบทความสำเร็จ!";
            $message_type = "success";
            // อัปเดตค่าในตัวแปร $article
            $article['title'] = $title;
            $article['content'] = $content;
            $article['author'] = $author;
            $article['category'] = $category;
        }
    }
} catch(PDOException $e) {
    die("Error: " . $e->getMessage());
}
?>
<!DOCTYPE html>
<html lang="th">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>แก้ไขบทความ - Mini CMS</title>
    <link rel="stylesheet" href="styles/main.css">
</head>
<body>
    <div class="container">
        <header class="site-header">
            <h1>✏️ แก้ไขบทความ</h1>
        </header>

        <div class="toolbar">
            <a href="index.php" class="button-secondary">← กลับหน้าแรก</a>
            <a href="view.php?id=<?php echo $id; ?>" class="button-secondary">👁️ ดูบทความ</a>
        </div>

        <?php if (!empty($message)): ?>
            <div class="message <?php echo $message_type; ?>">
                <?php echo $message; ?>
                <?php if ($message_type == 'success'): ?>
                    <a href="view.php?id=<?php echo $id; ?>" class="message-link">ดูบทความ</a>
                <?php endif; ?>
            </div>
        <?php endif; ?>

        <div class="form-container">
            <form action="edit.php?id=<?php echo $id; ?>" method="POST" class="article-form">
                <input type="hidden" name="csrf_token" value="<?php echo generateCsrfToken(); ?>">

                <div class="form-group">
                    <label for="title">ชื่อบทความ <span class="required">*</span></label>
                    <input type="text"
                           id="title"
                           name="title"
                           required
                           maxlength="255"
                           value="<?php echo htmlspecialchars($article['title']); ?>"
                           class="form-input">
                </div>

                <div class="form-row">
                    <div class="form-group">
                        <label for="author">ชื่อผู้เขียน</label>
                        <input type="text"
                               id="author"
                               name="author"
                               maxlength="100"
                               value="<?php echo htmlspecialchars($article['author']); ?>"
                               class="form-input">
                    </div>

                    <div class="form-group">
                        <label for="category">หมวดหมู่</label>
                        <select id="category" name="category" class="form-select">
                            <option value="ทั่วไป" <?php echo ($article['category'] == 'ทั่วไป') ? 'selected' : ''; ?>>ทั่วไป</option>
                            <option value="HTML" <?php echo ($article['category'] == 'HTML') ? 'selected' : ''; ?>>HTML</option>
                            <option value="CSS" <?php echo ($article['category'] == 'CSS') ? 'selected' : ''; ?>>CSS</option>
                            <option value="JavaScript" <?php echo ($article['category'] == 'JavaScript') ? 'selected' : ''; ?>>JavaScript</option>
                            <option value="PHP" <?php echo ($article['category'] == 'PHP') ? 'selected' : ''; ?>>PHP</option>
                            <option value="MySQL" <?php echo ($article['category'] == 'MySQL') ? 'selected' : ''; ?>>MySQL</option>
                        </select>
                    </div>
                </div>

                <div class="form-group">
                    <label for="content">เนื้อหาบทความ <span class="required">*</span></label>
                    <textarea id="content"
                              name="content"
                              rows="15"
                              required
                              class="form-textarea"><?php echo htmlspecialchars($article['content']); ?></textarea>
                </div>

                <div class="form-actions">
                    <button type="submit" class="button-primary">💾 บันทึกการแก้ไข</button>
                    <a href="view.php?id=<?php echo $id; ?>" class="button-secondary">❌ ยกเลิก</a>
                </div>
            </form>
        </div>
    </div>

    <script src="scripts/main.js"></script>
</body>
</html>

จุดสำคัญ:

  • ✅ ตรวจสอบ ID ก่อนดึงข้อมูล
  • ✅ ใช้ PDO Prepared Statements ทั้งการดึงและอัปเดต
  • ✅ มี CSRF Token ในฟอร์ม
  • ✅ แสดงข้อมูลเดิมในฟอร์ม
  • ✅ อัปเดตข้อมูลอย่างปลอดภัย

7.6 การสร้างหน้าลบ (Delete) - delete.php

สคริปต์สำหรับลบบทความพร้อม CSRF Protection

<?php
include 'config.php';

// ตรวจสอบ ID และ CSRF Token
$id = isset($_GET['id']) ? intval($_GET['id']) : 0;
$csrf_token = $_GET['csrf_token'] ?? '';

if ($id <= 0) {
    redirect('index.php');
}

// ตรวจสอบ CSRF Token
if (!verifyCsrfToken($csrf_token)) {
    die("CSRF token validation failed");
}

try {
    // ลบบทความ
    $stmt = $pdo->prepare("DELETE FROM articles WHERE id = :id");
    $stmt->execute(['id' => $id]);

    // Redirect กลับไปหน้าแรก
    $_SESSION['message'] = "ลบบทความสำเร็จ";
    $_SESSION['message_type'] = "success";
    redirect('index.php');

} catch(PDOException $e) {
    $_SESSION['message'] = "เกิดข้อผิดพลาด: " . $e->getMessage();
    $_SESSION['message_type'] = "error";
    redirect('index.php');
}
?>

จุดสำคัญ:

  • ✅ ตรวจสอบ CSRF Token ก่อนลบ (ป้องกัน CSRF Attack)
  • ✅ ใช้ PDO Prepared Statement
  • ✅ Redirect พร้อม message ใน Session
  • ✅ ไม่มี HTML output (เป็น script เท่านั้น)

7.7 สรุปความปลอดภัยที่ใช้ในโปรเจกต์

1. SQL Injection Protection

// ❌ อันตราย
$sql = "SELECT * FROM articles WHERE id = " . $_GET['id'];

// ✅ ปลอดภัย - ใช้ PDO Prepared Statements
$stmt = $pdo->prepare("SELECT * FROM articles WHERE id = :id");
$stmt->execute(['id' => $_GET['id']]);

2. XSS Protection

// ❌ อันตราย
echo $user_input;

// ✅ ปลอดภัย - ใช้ htmlspecialchars()
echo htmlspecialchars($user_input, ENT_QUOTES, 'UTF-8');

3. CSRF Protection

// สร้าง Token
<input type="hidden" name="csrf_token" value="<?php echo generateCsrfToken(); ?>">

// ตรวจสอบ Token
if (!verifyCsrfToken($_POST['csrf_token'] ?? '')) {
    die("CSRF token validation failed");
}

4. Session Security

// ตั้งค่า Session ให้ปลอดภัย
ini_set('session.cookie_httponly', 1);  // ป้องกัน JavaScript เข้าถึง
ini_set('session.cookie_secure', 0);    // ใช้ HTTPS (เปลี่ยนเป็น 1 ใน production)
ini_set('session.cookie_samesite', 'Strict');  // ป้องกัน CSRF

5. Input Validation

// ตรวจสอบและทำความสะอาดข้อมูล
$id = intval($_GET['id']);  // แปลงเป็น integer
$title = sanitize($_POST['title']);  // ลบ HTML tags และ escape

7.8 การทดสอบและ Debug

การทดสอบฟีเจอร์

  1. Create (เพิ่ม)

    • ทดสอบเพิ่มบทความปกติ
    • ทดสอบเพิ่มโดยไม่กรอกข้อมูลบังคับ
    • ทดสอบเพิ่มข้อมูลที่มี HTML tags (ต้องถูก escape)
  2. Read (อ่าน)

    • ทดสอบแสดงรายการบทความ
    • ทดสอบค้นหาบทความ
    • ทดสอบกรองตามหมวดหมู่
  3. Update (แก้ไข)

    • ทดสอบแก้ไขบทความปกติ
    • ทดสอบแก้ไขโดยไม่กรอกข้อมูลบังคับ
    • ทดสอบแก้ไข ID ที่ไม่มีอยู่
  4. Delete (ลบ)

    • ทดสอบลบบทความปกติ
    • ทดสอบลบโดยไม่มี CSRF Token (ต้องถูกปฏิเสธ)
    • ทดสอบลบ ID ที่ไม่มีอยู่

การ Debug

// เปิด Error Reporting ในระหว่างพัฒนา
error_reporting(E_ALL);
ini_set('display_errors', 1);

// ดู SQL Query
try {
    $stmt = $pdo->prepare($sql);
    $stmt->execute($params);
    echo "SQL: " . $stmt->queryString;  // แสดง SQL
} catch(PDOException $e) {
    echo "Error: " . $e->getMessage();
}

// ดูค่าตัวแปร
var_dump($variable);
print_r($array);

7.9 การปรับปรุงและพัฒนาต่อ

ฟีเจอร์เพิ่มเติมที่แนะนำ

  1. ระบบ Login/Authentication

    // ตัวอย่างการตรวจสอบ Login
    if (!isset($_SESSION['user_id'])) {
       redirect('login.php');
    }
  2. Pagination (แบ่งหน้า)

    $page = isset($_GET['page']) ? intval($_GET['page']) : 1;
    $per_page = 10;
    $offset = ($page - 1) * $per_page;
    
    $sql = "SELECT * FROM articles LIMIT :limit OFFSET :offset";
    $stmt = $pdo->prepare($sql);
    $stmt->bindValue(':limit', $per_page, PDO::PARAM_INT);
    $stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
    $stmt->execute();
  3. การอัปโหลดรูปภาพ

    if (isset($_FILES['image'])) {
       $allowed = ['jpg', 'jpeg', 'png', 'gif'];
       $filename = $_FILES['image']['name'];
       $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
    
       if (in_array($ext, $allowed)) {
           $new_name = uniqid() . '.' . $ext;
           move_uploaded_file($_FILES['image']['tmp_name'], 'uploads/' . $new_name);
       }
    }
  4. Rich Text Editor

    • ใช้ TinyMCE หรือ CKEditor
    • เพิ่มการจัดรูปแบบข้อความ
    • แทรกรูปภาพและลิงก์
  5. การแสดงความคิดเห็น

    • สร้างตาราง comments
    • เชื่อมโยงกับ articles
    • มีระบบ moderation

Performance Optimization

  1. Database Indexing

    CREATE INDEX idx_title ON articles(title);
    CREATE INDEX idx_category ON articles(category);
    CREATE INDEX idx_created_at ON articles(created_at);
  2. Caching

    // ใช้ APCu หรือ Redis
    if (apcu_exists('articles_list')) {
       $articles = apcu_fetch('articles_list');
    } else {
       $articles = $pdo->query("SELECT * FROM articles")->fetchAll();
       apcu_store('articles_list', $articles, 3600);
    }
  3. Lazy Loading

    • โหลดรูปภาพเมื่อเลื่อนถึง
    • ใช้ Intersection Observer API

7.10 สรุปและแนวทางต่อไป

สิ่งที่ได้เรียนรู้

✅ การสร้างระบบ CRUD ด้วย PHP และ MySQL
✅ การใช้ PDO สำหรับความปลอดภัยและความยืดหยุ่น
✅ การป้องกัน SQL Injection ด้วย Prepared Statements
✅ การป้องกัน XSS ด้วย htmlspecialchars()
✅ การป้องกัน CSRF ด้วย CSRF Token
✅ การตั้งค่า Session และ Cookie ที่ปลอดภัย
✅ การสร้าง Responsive Web Design
✅ การจัดการ Error และ Exception

ขั้นตอนถัดไป

ระดับกลาง:

  1. เพิ่มระบบ Login และ Authorization
  2. เพิ่ม Pagination และ Sorting
  3. เพิ่มการอัปโหลดรูปภาพ
  4. ใช้ AJAX สำหรับ Dynamic Content
  5. เพิ่ม Rich Text Editor

ระดับสูง:

  1. สร้าง RESTful API
  2. ใช้ Framework (Laravel, Symfony)
  3. เพิ่ม Unit Testing
  4. Deploy ขึ้น Production Server
  5. ใช้ Docker สำหรับ Development Environment

แหล่งเรียนรู้เพิ่มเติม

  • PHP Manual - เอกสาร PHP อย่างเป็นทางการ
  • PDO Tutorial - คู่มือ PDO ที่ดี
  • OWASP - ความปลอดภัยเว็บแอปพลิเคชัน
  • Laravel - PHP Framework ยอดนิยม

แบบฝึกหัดท้ายบท

ระดับพื้นฐาน:

  1. ติดตั้งและรันโปรเจกต์ Mini CMS บนเครื่องของคุณ
  2. เพิ่มบทความ 5 บทความในหมวดหมู่ต่างๆ
  3. ทดสอบฟีเจอร์ค้นหาและกรอง
  4. ปรับแต่ง CSS ให้เป็นสไตล์ของคุณ

ระดับกลาง:

  1. เพิ่มฟิลด์ tags ในตาราง articles
  2. สร้างหน้าแสดงบทความตาม tag
  3. เพิ่มการนับจำนวนการดู (view count)
  4. สร้างหน้า Dashboard แสดงสถิติ

ระดับสูง:

  1. เพิ่มระบบ Login ด้วย password hashing
  2. เพิ่มระบบ Role-Based Access Control
  3. สร้าง RESTful API สำหรับ CRUD
  4. เพิ่ม Rate Limiting สำหรับป้องกัน Brute Force

ขอแสดงความยินดี! คุณได้สร้างระบบจัดการบทความที่สมบูรณ์และปลอดภัยแล้ว 🎉

💡 เคล็ดลับ: ความปลอดภัยเป็นสิ่งสำคัญที่สุด อย่าลืมใช้ PDO Prepared Statements, CSRF Token, และ htmlspecialchars() เสมอ!