PlusMagi's Blog By Pitt Phunsanit architecture,Backend,computer science,Design,Programming,Uncategorized PHP: การแยก Logic ออกจาก View (MVC Pattern)

PHP: การแยก Logic ออกจาก View (MVC Pattern)


1. บทนำและแนวคิดสำคัญ

ในโลกของการพัฒนาเว็บแอปพลิเคชันขนาดใหญ่ การที่โค้ดส่วนการแสดงผล (View) และโค้ดส่วนตรรกะทางธุรกิจ (Business Logic) ถูกรวมอยู่ในไฟล์เดียวกันเป็นปัญหาคลาสสิกที่เรียกว่า “Spaghetti Code” ซึ่งทำให้ระบบยากต่อการบำรุงรักษา, ยากต่อการทดสอบ (Testing), และเสี่ยงต่อช่องโหว่ด้านความปลอดภัย

MVC Pattern (Model-View-Controller) คือรูปแบบสถาปัตยกรรมที่ถูกออกแบบมาเพื่อแก้ไขปัญหาดังกล่าว โดยหลักการสำคัญคือ Separation of Concerns (SoC) หรือการแยกส่วนความรับผิดชอบออกจากกันอย่างชัดเจน:

  • Model
    รับผิดชอบในการจัดการข้อมูล (Data) และกฎทางธุรกิจ (Business Rules) ทั้งหมด Model จะเป็นตัวติดต่อกับฐานข้อมูลโดยตรง ไม่รู้ว่าข้อมูลจะถูกแสดงผลที่ไหน
  • View
    รับผิดชอบเฉพาะการนำเสนอข้อมูล (Presentation Layer) คือส่วนที่เราเห็นในเบราว์เซอร์ ประกอบด้วย HTML, CSS, และ PHP เพียงเล็กน้อยเพื่อวนลูปแสดงผลเท่านั้น View จะไม่สามารถเรียกใช้ตรรกะทางธุรกิจได้เอง
  • Controller
    ทำหน้าที่เป็นผู้ประสานงาน (The Coordinator) รับคำขอจากผู้ใช้ (Request) จากนั้นจะตัดสินใจว่าต้องเรียก Model ส่วนใดมาประมวลผล และเมื่อได้ข้อมูลแล้ว ก็ส่งต่อข้อมูลเหล่านั้นไปยัง View ที่เหมาะสมเพื่อแสดงผล

การทำตามหลัก MVC ไม่เพียงแต่ทำให้โค้ดสะอาดขึ้นเท่านั้น แต่ยังช่วยให้ทีมพัฒนาสามารถทำงานพร้อมกันในส่วนที่แตกต่างกันได้อย่างมีประสิทธิภาพ (เช่น นักออกแบบ UI ทำงานกับ View ในขณะที่ Backend Developer ทำงานกับ Model และ Controller) ทำให้ระบบมีความยืดหยุ่นและ Scalable สูงมาก


2. ตัวอย่างการใช้งานและรูปแบบโค้ด (Code Examples)

สมมติว่าเราต้องการสร้างฟังก์ชันง่ายๆ เพื่อแสดงรายชื่อผู้ใช้ทั้งหมดจากฐานข้อมูล เราจะแบ่งโค้ดออกเป็น 3 ส่วนตามหลัก MVC:

  • Model (User.php)
    มีหน้าที่เชื่อมต่อและเรียกข้อมูล
  • Controller (UserController.php)
    รับคำขอและสั่งให้ Model ดึงข้อมูล
  • View (user_list.php)
    รับข้อมูลจาก Controller และนำไปแสดงผลในรูปแบบ HTML
<pre class="wp-block-syntaxhighlighter-code"><code lang="php"><code><?php
// ==================================================
// 1. MODEL (User.php) - จัดการข้อมูลและ Business Logic
// ความรับผิดชอบ: การติดต่อฐานข้อมูลเท่านั้น ไม่รู้ว่าใครจะเรียกใช้ข้อมูลนี้
// ==================================================
class User {
    private $db;

    public function __construct($connection) {
        $this->db = $connection; // รับ Object การเชื่อมต่อ DB มาใช้งาน (Dependency Injection)
    }

    /**
     * ดึงรายชื่อผู้ใช้ทั้งหมดจากฐานข้อมูล
     * @return array รายชื่อผู้ใช้
     */
    public function getAllUsers() {
        // *** Best Practice: ใช้ Prepared Statements เสมอเพื่อป้องกัน SQL Injection ***
        $stmt = $this->db->prepare("SELECT id, username, email FROM users ORDER BY id DESC");
        $stmt->execute();
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
}

// ==================================================
// 2. CONTROLLER (UserController.php) - ผู้ประสานงาน
// ความรับผิดชอบ: รับ Request, เรียก Model, และส่งข้อมูลไปให้ View
// ==================================================
class UserController {
    private $userModel;

    public function __construct(User $userModel) {
        $this->userModel = $userModel;
    }

    /**
     * จัดการ Logic เมื่อมีการเรียกดูรายชื่อผู้ใช้ (Index Action)
     * @return array ข้อมูลที่พร้อมส่งให้ View
     */
    public function index() {
        try {
            // 1. สั่ง Model ให้ดึงข้อมูลมา
            $users = $this->userModel->getAllUsers();

            // 2. ส่งข้อมูลไปยัง View (ในโลกจริงอาจเป็นการเรียก include หรือใช้ Template Engine)
            return [
                'success' => true,
                'data' => $users
            ];
        } catch (Exception $e) {
            // จัดการข้อผิดพลาดที่เกิดขึ้นระหว่าง Model และ Controller
            error_log("Error fetching users: " . $e->getMessage());
            return [
                'success' => false,
                'message' => 'เกิดข้อผิดพลาดในการดึงข้อมูลผู้ใช้ กรุณาลองใหม่อีกครั้ง.'
            ];
        }
    }
}

// ==================================================
// 3. VIEW (user_list.php) - การนำเสนอผลลัพธ์
// ความรับผิดชอบ: แสดง HTML เท่านั้น ไม่มีการเรียก DB หรือ Logic ทางธุรกิจ
// ==================================================

// --- ส่วนจำลองการทำงานของระบบ (Simulation/Bootstrap) ---
try {
    // 1. จำลองการเชื่อมต่อฐานข้อมูล (PDO Connection)
    $pdo = new PDO("mysql:host=localhost;dbname=testdb", "user", "pass");
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    // 2. สร้าง Model และ Controller
    $userModel = new User($pdo);
    $controller = new UserController($userModel);

    // 3. Controller ทำงานและส่งข้อมูลกลับมา (เรียกใช้ Logic)
    $result = $controller->index();

} catch (Exception $e) {
    $result = ['success' => false, 'message' => 'System Initialization Error: ' . $e->getMessage()];
}


// ------------------------------------------------------
// ส่วนนี้คือ View ที่รับข้อมูลจาก Controller มาแสดงผล
// ------------------------------------------------------
if ($result['success']) { ?>
    <h1 style="font-size: 2em;">รายชื่อผู้ใช้งานระบบ (View Layer)</h1>
    <table border="1" width="80%">
        <thead>
            <tr>
                <th>ID</th>
                <th>Username</th>
                <th>Email</th>
            </tr>
        </thead>
        <tbody>
            <?php foreach ($result['data'] as $user): ?>
            <tr>
                <td><?= htmlspecialchars($user['id']) ?></td>
                <td><?= htmlspecialchars($user['username']) ?></td>
                <td><?= htmlspecialchars($user['email']) ?></td>
            </tr>
            <?php endforeach; ?>
        </tbody>
    </table>
<?php else: ?>
    <div style="color: red;">⚠️ <?= htmlspecialchars($result['message']) ?></div>
<?php endif; ?></code></pre>

3. ข้อควรระวัง Security และ Best Practices

  • คำนึงถึงความปลอดภัย (Security)
    การใช้ Prepared Statements (เช่น PDO) ใน Model ทุกครั้งที่ต้องมีการรับ Input จากผู้ใช้หรือการ Query ฐานข้อมูล เพื่อป้องกัน SQL Injection และควรใช้ `htmlspecialchars()` เสมอใน View ก่อนแสดงผล Output ใดๆ ที่มาจากแหล่งภายนอกเพื่อป้องกัน XSS Attack
  • Performance & Testability (Dependency Injection)
    พิจารณาใช้ Dependency Injection (DI) Container หรือ Service Locator ในการจัดการ Object ต่างๆ แทนการสร้าง Object ด้วย `new` โดยตรง วิธีนี้ช่วยให้สามารถ Mock Object สำหรับการทดสอบ Unit Test ได้ง่าย และยังทำให้โค้ดมีความยืดหยุ่นสูง
  • Error Handling
    ต้องมีการใช้ `try…catch` block อย่างครอบคลุมใน Controller เพื่อดักจับ Exception ที่อาจเกิดขึ้นจาก Model (เช่น การเชื่อมต่อฐานข้อมูลล้มเหลว) และส่งข้อความที่เหมาะสมกลับไปให้ View แสดงผล โดยไม่เปิดเผยรายละเอียดทางเทคนิคของระบบ

4. สรุปและการนำไปประยุกต์ใช้งาน

การทำความเข้าใจและปฏิบัติตาม MVC Pattern ไม่ใช่แค่ทางเลือก แต่เป็นมาตรฐานที่จำเป็นสำหรับการพัฒนาแอปพลิเคชันระดับ Production ที่มีความซับซ้อนสูง หากคุณกำลังสร้างระบบที่มีฟีเจอร์หลากหลาย, มีผู้ใช้งานจำนวนมาก, หรือต้องมีการขยายตัวในอนาคต การยึดหลักการแยกส่วนความรับผิดชอบนี้จะช่วยให้ทีมของคุณสามารถทำงานได้อย่างเป็นระเบียบและลดภาระในการแก้ไขบั๊กที่อาจเกิดจากการปะปนกันของโค้ด

  • Model = “สมอง” (คิดเรื่อง Business Logic และจัดการข้อมูล)
  • View = “ปาก/ตา” (แสดงผลเท่านั้น ไม่มีการเรียก DB หรือ Logic ทางธุรกิจ)
  • Controller = “ผู้จัดการ” (รับคำขอ สั่งการให้ Model และส่งต่อข้อมูลไป View)

เมื่อคุณเริ่มใช้ Frameworks ยอดนิยมอย่าง Laravel, Symfony หรือ CodeIgniter คุณกำลังใช้งาน MVC Pattern อยู่โดยอัตโนมัติ การเรียนรู้หลักการนี้จะช่วยให้คุณเข้าใจว่าเบื้องหลังของ Framework เหล่านั้นทำงานอย่างไร ทำให้คุณสามารถพัฒนาโค้ดที่ได้มาตรฐานระดับ Senior Developer ได้อย่างแท้จริง