มินิโปรเจ็ค - 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 // ไฟล์ JavaScript7.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'); // ป้องกัน CSRF5. Input Validation
// ตรวจสอบและทำความสะอาดข้อมูล
$id = intval($_GET['id']); // แปลงเป็น integer
$title = sanitize($_POST['title']); // ลบ HTML tags และ escape7.8 การทดสอบและ Debug
การทดสอบฟีเจอร์
-
Create (เพิ่ม)
- ทดสอบเพิ่มบทความปกติ
- ทดสอบเพิ่มโดยไม่กรอกข้อมูลบังคับ
- ทดสอบเพิ่มข้อมูลที่มี HTML tags (ต้องถูก escape)
-
Read (อ่าน)
- ทดสอบแสดงรายการบทความ
- ทดสอบค้นหาบทความ
- ทดสอบกรองตามหมวดหมู่
-
Update (แก้ไข)
- ทดสอบแก้ไขบทความปกติ
- ทดสอบแก้ไขโดยไม่กรอกข้อมูลบังคับ
- ทดสอบแก้ไข ID ที่ไม่มีอยู่
-
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 การปรับปรุงและพัฒนาต่อ
ฟีเจอร์เพิ่มเติมที่แนะนำ
-
ระบบ Login/Authentication
// ตัวอย่างการตรวจสอบ Login if (!isset($_SESSION['user_id'])) { redirect('login.php'); } -
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(); -
การอัปโหลดรูปภาพ
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); } } -
Rich Text Editor
- ใช้ TinyMCE หรือ CKEditor
- เพิ่มการจัดรูปแบบข้อความ
- แทรกรูปภาพและลิงก์
-
การแสดงความคิดเห็น
- สร้างตาราง comments
- เชื่อมโยงกับ articles
- มีระบบ moderation
Performance Optimization
-
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); -
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); } -
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
ขั้นตอนถัดไป
ระดับกลาง:
- เพิ่มระบบ Login และ Authorization
- เพิ่ม Pagination และ Sorting
- เพิ่มการอัปโหลดรูปภาพ
- ใช้ AJAX สำหรับ Dynamic Content
- เพิ่ม Rich Text Editor
ระดับสูง:
- สร้าง RESTful API
- ใช้ Framework (Laravel, Symfony)
- เพิ่ม Unit Testing
- Deploy ขึ้น Production Server
- ใช้ Docker สำหรับ Development Environment
แหล่งเรียนรู้เพิ่มเติม
- PHP Manual - เอกสาร PHP อย่างเป็นทางการ
- PDO Tutorial - คู่มือ PDO ที่ดี
- OWASP - ความปลอดภัยเว็บแอปพลิเคชัน
- Laravel - PHP Framework ยอดนิยม
แบบฝึกหัดท้ายบท
ระดับพื้นฐาน:
- ติดตั้งและรันโปรเจกต์ Mini CMS บนเครื่องของคุณ
- เพิ่มบทความ 5 บทความในหมวดหมู่ต่างๆ
- ทดสอบฟีเจอร์ค้นหาและกรอง
- ปรับแต่ง CSS ให้เป็นสไตล์ของคุณ
ระดับกลาง:
- เพิ่มฟิลด์
tagsในตาราง articles - สร้างหน้าแสดงบทความตาม tag
- เพิ่มการนับจำนวนการดู (view count)
- สร้างหน้า Dashboard แสดงสถิติ
ระดับสูง:
- เพิ่มระบบ Login ด้วย password hashing
- เพิ่มระบบ Role-Based Access Control
- สร้าง RESTful API สำหรับ CRUD
- เพิ่ม Rate Limiting สำหรับป้องกัน Brute Force
ขอแสดงความยินดี! คุณได้สร้างระบบจัดการบทความที่สมบูรณ์และปลอดภัยแล้ว 🎉
💡 เคล็ดลับ: ความปลอดภัยเป็นสิ่งสำคัญที่สุด อย่าลืมใช้ PDO Prepared Statements, CSRF Token, และ htmlspecialchars() เสมอ!