Kotchasan Framework Documentation
คู่มือสถาปัตยกรรม MVC ของ Kotchasan
คู่มือสถาปัตยกรรม MVC ของ Kotchasan
บทนำ
Kotchasan Framework ใช้รูปแบบสถาปัตยกรรม MVC (Model-View-Controller) ที่ช่วยแยกการทำงานของแอปพลิเคชันออกเป็น 3 ส่วนหลัก ทำให้โค้ดมีการจัดระเบียบที่ดี ง่ายต่อการบำรุงรักษา และสามารถขยายได้
สารบัญ
- ภาพรวม MVC Pattern
- โครงสร้างไดเรกทอรี
- Model Layer
- View Layer
- Controller Layer
- การทำงานร่วมกันของ MVC
- ตัวอย่างการสร้างโมดูล
- Best Practices
ภาพรวม MVC Pattern
Model (โมเดล)
- จัดการข้อมูลและตรรกะทางธุรกิจ
- เชื่อมต่อกับฐานข้อมูล
- ตรวจสอบความถูกต้องของข้อมูล
- ประมวลผลข้อมูลก่อนส่งให้ Controller
View (วิว)
- จัดการการแสดงผลและ UI
- สร้าง HTML, JSON หรือรูปแบบการแสดงผลอื่นๆ
- รับข้อมูลจาก Controller และแสดงผล
- ไม่มีตรรกะทางธุรกิจ
Controller (คอนโทรลเลอร์)
- จัดการ HTTP Request และ Response
- เชื่อมต่อระหว่าง Model และ View
- ตัดสินใจว่าจะใช้ Model และ View ไหน
- จัดการ routing และ authentication
โครงสร้างไดเรกทอรี
modules/
└── mymodule/
├── controllers/
│ ├── index.php # Controller หลัก (namespace MyModule\Index)
│ ├── api.php # API Controller (namespace MyModule\Api)
│ ├── user.php # User Controller (namespace MyModule\User)
│ └── admin.php # Admin Controller (namespace MyModule\Admin)
├── models/
│ ├── index.php # Index Model (namespace MyModule\Index)
│ ├── user.php # User Model (namespace MyModule\User)
│ └── validation.php # Validation Model (namespace MyModule\Validation)
├── views/
│ ├── index.php # Index View (namespace MyModule\Index)
│ ├── user.php # User View (namespace MyModule\User)
│ └── admin.php # Admin View (namespace MyModule\Admin)
├── template/
│ └── email.html # Template อีเมล
├── script.js # JavaScript
└── style.css # CSSModel Layer
การสร้าง Model
namespace MyModule\User;
use Kotchasan\Model;
use Kotchasan\Database;
/**
* User Model
* File: modules/mymodule/models/user.php
*/
class Model extends \Kotchasan\Model
{
/**
* ตารางที่ใช้งาน
*/
protected $table = 'users';
/**
* การเชื่อมต่อฐานข้อมูล (ถ้าไม่ระบุจะใช้ default)
*/
protected $conn = 'default';
/**
* ดึงข้อมูลผู้ใช้ทั้งหมด
*/
public function getAllUsers()
{
return $this->db->select()
->from($this->table)
->where('status', '=', 'active')
->orderBy('created_at', 'DESC')
->fetchAll();
}
/**
* ดึงข้อมูลผู้ใช้ตาม ID
*/
public function getUserById($id)
{
return $this->db->select()
->from($this->table)
->where('id', '=', $id)
->first();
}
/**
* สร้างผู้ใช้ใหม่
*/
public function createUser($data)
{
// ตรวจสอบข้อมูล
$this->validateUserData($data);
// เข้ารหัสรหัสผ่าน
$data['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
$data['created_at'] = date('Y-m-d H:i:s');
$this->db->insert($this->table)
->values($data)
->execute();
return $this->db->lastInsertId();
}
/**
* อัปเดตข้อมูลผู้ใช้
*/
public function updateUser($id, $data)
{
$data['updated_at'] = date('Y-m-d H:i:s');
return $this->db->update($this->table)
->set($data)
->where('id', '=', $id)
->execute();
}
/**
* ลบผู้ใช้
*/
public function deleteUser($id)
{
return $this->db->delete($this->table)
->where('id', '=', $id)
->execute();
}
/**
* ตรวจสอบความถูกต้องของข้อมูล
*/
private function validateUserData($data)
{
if (empty($data['name'])) {
throw new \Exception('กรุณากรอกชื่อ');
}
if (empty($data['email']) || !filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
throw new \Exception('กรุณากรอกอีเมลที่ถูกต้อง');
}
// ตรวจสอบอีเมลซ้ำ
$existing = $this->db->select('id')
->from($this->table)
->where('email', '=', $data['email'])
->first();
if ($existing) {
throw new \Exception('อีเมลนี้มีผู้ใช้แล้ว');
}
}
}การใช้งาน Model ขั้นสูง
namespace MyModule\Post;
use Kotchasan\Model;
class Model extends \Kotchasan\Model
{
/**
* ดึงโพสต์พร้อมข้อมูลผู้เขียน
*/
public function getPostsWithAuthor($limit = 10)
{
return $this->db->select('p.', 'u.name as author_name', 'u.avatar')
->from('posts p')
->leftJoin('users u', 'u.id', 'p.user_id')
->where('p.status', '=', 'published')
->orderBy('p.created_at', 'DESC')
->limit($limit)
->fetchAll();
}
/**
* ค้นหาโพสต์
*/
public function searchPosts($keyword, $category = null)
{
$query = $this->db->select()
->from('posts')
->where('status', '=', 'published');
if (!empty($keyword)) {
$query->where('title', 'LIKE', "%{$keyword}%")
->orWhere('content', 'LIKE', "%{$keyword}%");
}
if ($category) {
$query->where('category_id', '=', $category);
}
return $query->orderBy('created_at', 'DESC')->fetchAll();
}
/**
* สถิติโพสต์
*/
public function getPostStats()
{
return [
'total' => $this->db->select('COUNT() as count')
->from('posts')
->first()['count'],
'published' => $this->db->select('COUNT() as count')
->from('posts')
->where('status', '=', 'published')
->first()['count'],
'draft' => $this->db->select('COUNT() as count')
->from('posts')
->where('status', '=', 'draft')
->first()['count']
];
}
}View Layer
การสร้าง View
namespace MyModule\User;
use Kotchasan\Html;
use Kotchasan\Http\Request;
use Kotchasan\Language;
/**
* User View
* File: modules/mymodule/views/user.php
*/
class View extends \Gcms\View
{
/**
* แสดงรายการผู้ใช้
*/
public function render(Request $request)
{
// ดึงข้อมูลจาก Model
$model = new \MyModule\User\Model();
$users = $model->getAllUsers();
// สร้าง HTML Table
$table = Html::create('table', [
'class' => 'data fullwidth'
]);
// Header
$thead = $table->add('thead');
$tr = $thead->add('tr');
$tr->add('th', [], 'ID');
$tr->add('th', [], 'ชื่อ');
$tr->add('th', [], 'อีเมล');
$tr->add('th', [], 'สถานะ');
$tr->add('th', [], 'จัดการ');
// Body
$tbody = $table->add('tbody');
foreach ($users as $user) {
$tr = $tbody->add('tr');
$tr->add('td', [], $user['id']);
$tr->add('td', [], $user['name']);
$tr->add('td', [], $user['email']);
$tr->add('td', [], $user['status']);
// ปุ่มจัดการ
$actions = $tr->add('td');
$actions->add('a', [
'href' => 'index.php?module=mymodule&page=user&action=edit&id=' . $user['id'],
'class' => 'button green'
], 'แก้ไข');
$actions->add('a', [
'href' => 'index.php?module=mymodule&page=user&action=delete&id=' . $user['id'],
'class' => 'button red',
'onclick' => 'return confirm("ต้องการลบหรือไม่?")'
], 'ลบ');
}
return $table->render();
}
/**
* ฟอร์มเพิ่ม/แก้ไขผู้ใช้
*/
public function form(Request $request)
{
$id = $request->request('id')->toInt();
$user = [];
if ($id > 0) {
$model = new \MyModule\User\Model();
$user = $model->getUserById($id);
}
// สร้างฟอร์ม
$form = Html::create('form', [
'id' => 'user_form',
'class' => 'setup_frm',
'action' => 'index.php/mymodule/model/user/save',
'onsubmit' => 'doFormSubmit',
'ajax' => true,
'token' => true
]);
// ฟิลด์ซ่อน ID
if ($id > 0) {
$form->add('input', [
'type' => 'hidden',
'name' => 'id',
'value' => $id
]);
}
// ชื่อ
$fieldset = $form->add('fieldset');
$fieldset->add('legend', [], 'ข้อมูลผู้ใช้');
$groups = $fieldset->add('div', ['class' => 'item']);
$groups->add('label', [], 'ชื่อ');
$groups->add('input', [
'type' => 'text',
'name' => 'name',
'value' => isset($user['name']) ? $user['name'] : '',
'required' => true
]);
// อีเมล
$groups = $fieldset->add('div', ['class' => 'item']);
$groups->add('label', [], 'อีเมล');
$groups->add('input', [
'type' => 'email',
'name' => 'email',
'value' => isset($user['email']) ? $user['email'] : '',
'required' => true
]);
// รหัสผ่าน (เฉพาะเพิ่มใหม่)
if ($id == 0) {
$groups = $fieldset->add('div', ['class' => 'item']);
$groups->add('label', [], 'รหัสผ่าน');
$groups->add('input', [
'type' => 'password',
'name' => 'password',
'required' => true
]);
}
// ปุ่มบันทึก
$fieldset = $form->add('fieldset');
$fieldset->add('input', [
'class' => 'button save large',
'type' => 'submit',
'value' => 'บันทึก'
]);
return $form->render();
}
}การสร้าง JSON Response
namespace MyModule\Api;
use Kotchasan\Http\Request;
use Kotchasan\Http\Response;
class View extends \Kotchasan\View
{
/**
* ส่งข้อมูล JSON
*/
public function json(Request $request)
{
$model = new \MyModule\User\Model();
$users = $model->getAllUsers();
// สร้าง Response JSON
$response = new Response();
$response = $response->withHeader('Content-Type', 'application/json');
$data = [
'success' => true,
'data' => $users,
'total' => count($users)
];
$response->getBody()->write(json_encode($data, JSON_UNESCAPED_UNICODE));
return $response;
}
}Controller Layer
การสร้าง Controller
namespace MyModule\User;
use Gcms\Login;
use Kotchasan\Http\Request;
use Kotchasan\Language;
/**
* User Controller
* File: modules/mymodule/controllers/user.php
*/
class Controller extends \Kotchasan\Controller
{
/**
* แสดงรายการผู้ใช้
*/
public function render(Request $request)
{
// ตรวจสอบสิทธิ์
$login = Login::isMember();
if (!$login || !Login::checkPermission($login, 'can_manage_user')) {
return new \Kotchasan\Http\NotFound();
}
// เรียก View
$view = new \MyModule\User\View();
return $view->render($request);
}
/**
* แสดงฟอร์ม
*/
public function form(Request $request)
{
$login = Login::isMember();
if (!$login || !Login::checkPermission($login, 'can_manage_user')) {
return new \Kotchasan\Http\NotFound();
}
$view = new \MyModule\User\View();
return $view->form($request);
}
}API Controller
namespace MyModule\Api;
use Kotchasan\ApiController;
use Kotchasan\Http\Request;
/**
* API Controller
* File: modules/mymodule/controllers/api.php
*/
class Controller extends ApiController
{
/**
* GET /api/users
*/
public function index(Request $request)
{
try {
$model = new \MyModule\User\Model();
$users = $model->getAllUsers();
return $this->successResponse($users, 'ดึงข้อมูลสำเร็จ');
} catch (\Exception $e) {
return $this->errorResponse($e->getMessage(), 500);
}
}
/**
* POST /api/users
*/
public function store(Request $request)
{
try {
$data = [
'name' => $request->post('name')->toString(),
'email' => $request->post('email')->toString(),
'password' => $request->post('password')->toString()
];
$model = new \MyModule\User\Model();
$userId = $model->createUser($data);
return $this->successResponse(['id' => $userId], 'สร้างผู้ใช้สำเร็จ', 201);
} catch (\Exception $e) {
return $this->errorResponse($e->getMessage(), 400);
}
}
/**
* PUT /api/users/{id}
*/
public function update(Request $request)
{
try {
$id = $request->request('id')->toInt();
$data = [
'name' => $request->post('name')->toString(),
'email' => $request->post('email')->toString()
];
$model = new \MyModule\User\Model();
$model->updateUser($id, $data);
return $this->successResponse([], 'อัปเดตสำเร็จ');
} catch (\Exception $e) {
return $this->errorResponse($e->getMessage(), 400);
}
}
/**
* DELETE /api/users/{id}
*/
public function destroy(Request $request)
{
try {
$id = $request->request('id')->toInt();
$model = new \MyModule\User\Model();
$model->deleteUser($id);
return $this->successResponse([], 'ลบสำเร็จ');
} catch (\Exception $e) {
return $this->errorResponse($e->getMessage(), 400);
}
}
}การทำงานร่วมกันของ MVC
Flow การทำงาน
- Request เข้ามาที่ Router
- Router เรียก Controller ที่เหมาะสม
- Controller ตรวจสอบสิทธิ์และเรียก Model
- Model ประมวลผลข้อมูลและส่งกลับ
- Controller ส่งข้อมูลให้ View
- View สร้าง HTML/JSON และส่งกลับ
- Response ถูกส่งกลับไปยัง Client
ตัวอย่างการทำงานแบบสมบูรณ์
// 1. Router (index.php)
$request = \Kotchasan\Http\Request::createFromGlobals();
$router = new \Kotchasan\Http\Router();
$router->get('/users', '\MyModule\User\Controller@render');
$router->post('/users/save', '\MyModule\User\Model@save');
// 2. Controller รับ Request
class Controller extends \Kotchasan\Controller
{
public function render(Request $request)
{
// 3. เรียก Model
$model = new Model();
$users = $model->getAllUsers();
// 4. ส่งข้อมูลให้ View
$view = new View();
return $view->render($users);
}
}
// 5. Model ประมวลผลข้อมูล
class Model extends \Kotchasan\Model
{
public function getAllUsers()
{
return $this->db->select('*')
->from('users')
->where('status', '=', 'active')
->fetchAll();
}
}
// 6. View สร้าง HTML
class View extends \Kotchasan\View
{
public function render($users)
{
$html = '<table>';
foreach ($users as $user) {
$html .= '<tr><td>' . $user['name'] . '</td></tr>';
}
$html .= '</table>';
return $html;
}
}ตัวอย่างการสร้างโมดูล
1. สร้างโครงสร้างไดเรกทอรี
mkdir -p modules/blog/{controllers,models,views,template}2. สร้าง Model
// modules/blog/models/post.php
namespace Blog\Post;
use Kotchasan\Model;
class Model extends \Kotchasan\Model
{
protected $table = 'blog_posts';
/**
* ดึงโพสต์พร้อมข้อมูลผู้เขียน
*/
public function getPostsWithAuthor($limit = 10, $offset = 0)
{
return $this->db->select('p.', 'u.name as author')
->from($this->table . ' p')
->leftJoin('users u', 'u.id', 'p.user_id')
->where('p.status', '=', 'published')
->orderBy('p.created_at', 'DESC')
->limit($limit, $offset)
->fetchAll();
}
/**
* ดึงโพสต์ตาม ID
*/
public function getPostById($id)
{
return $this->db->select()
->from($this->table)
->where('id', '=', $id)
->where('status', '=', 'published')
->first();
}
/**
* สร้างโพสต์ใหม่
*/
public function createPost($data)
{
$data['created_at'] = date('Y-m-d H:i:s');
$data['status'] = 'draft';
$this->db->insert($this->table)
->values($data)
->execute();
return $this->db->lastInsertId();
}
}3. สร้าง View
// modules/blog/views/index.php
namespace Blog\Index;
use Kotchasan\Html;
use Kotchasan\Http\Request;
class View extends \Gcms\View
{
public function render(Request $request)
{
$model = new \Blog\Post\Model();
$posts = $model->getAllPosts();
$container = Html::create('div', ['class' => 'blog-container']);
foreach ($posts as $post) {
$article = $container->add('article', ['class' => 'blog-post']);
$article->add('h2', [], $post['title']);
$article->add('p', ['class' => 'meta'],
'โดย ' . $post['author'] . ' เมื่อ ' . $post['created_at']
);
$article->add('div', ['class' => 'content'],
substr($post['content'], 0, 200) . '...'
);
$article->add('a', [
'href' => 'index.php?module=blog&page=post&id=' . $post['id'],
'class' => 'read-more'
], 'อ่านต่อ');
}
return $container->render();
}
}4. สร้าง Controller
// modules/blog/controllers/index.php
namespace Blog\Index;
use Kotchasan\Http\Request;
class Controller extends \Kotchasan\Controller
{
public function render(Request $request)
{
$view = new View();
return $view->render($request);
}
}Best Practices
1. การตั้งชื่อ
// ดี - ชื่อที่มีความหมาย
class UserController extends \Kotchasan\Controller
{
public function getUserProfile(Request $request) { }
public function updateUserSettings(Request $request) { }
}
// ไม่ดี - ชื่อที่ไม่ชัดเจน
class Controller extends \Kotchasan\Controller
{
public function action1(Request $request) { }
public function doSomething(Request $request) { }
}2. การแยกความรับผิดชอบ
// Model - จัดการข้อมูลเท่านั้น
class UserModel extends \Kotchasan\Model
{
public function findUserByEmail($email)
{
return $this->db->select('*')
->from('users')
->where('email', '=', $email)
->first();
}
}
// Controller - จัดการ logic และ flow
class UserController extends \Kotchasan\Controller
{
public function login(Request $request)
{
$email = $request->post('email')->toString();
$password = $request->post('password')->toString();
$model = new UserModel();
$user = $model->findUserByEmail($email);
if ($user && password_verify($password, $user['password'])) {
// Login สำเร็จ
$view = new DashboardView();
return $view->render($user);
} else {
// Login ไม่สำเร็จ
$view = new LoginView();
return $view->renderWithError('อีเมลหรือรหัสผ่านไม่ถูกต้อง');
}
}
}
// View - แสดงผลเท่านั้น
class LoginView extends \Kotchasan\View
{
public function renderWithError($message)
{
$form = Html::create('form');
$form->add('div', ['class' => 'error'], $message);
// ... สร้างฟอร์ม login
return $form->render();
}
}3. การจัดการ Error
class UserController extends \Kotchasan\Controller
{
public function createUser(Request $request)
{
try {
$model = new UserModel();
$userId = $model->createUser($request->getParsedBody());
return $this->successResponse(['id' => $userId]);
} catch (\InvalidArgumentException $e) {
return $this->errorResponse($e->getMessage(), 400);
} catch (\Exception $e) {
error_log($e->getMessage());
return $this->errorResponse('เกิดข้อผิดพลาดในระบบ', 500);
}
}
}4. การใช้ Dependency Injection
class UserController extends \Kotchasan\Controller
{
private $userModel;
private $emailService;
public function __construct(UserModel $userModel, EmailService $emailService)
{
$this->userModel = $userModel;
$this->emailService = $emailService;
}
public function registerUser(Request $request)
{
$userData = $request->getParsedBody();
$userId = $this->userModel->createUser($userData);
// ส่งอีเมลยืนยัน
$this->emailService->sendWelcomeEmail($userData['email']);
return $this->successResponse(['id' => $userId]);
}
}5. การทดสอบ
// การทดสอบ Model
class UserModelTest extends \PHPUnit\Framework\TestCase
{
public function testCreateUser()
{
$model = new UserModel();
$userData = [
'name' => 'Test User',
'email' => 'test@example.com',
'password' => 'password123'
];
$userId = $model->createUser($userData);
$this->assertGreaterThan(0, $userId);
$user = $model->getUserById($userId);
$this->assertEquals('Test User', $user['name']);
}
}
// การทดสอบ Controller
class UserControllerTest extends \PHPUnit\Framework\TestCase
{
public function testLoginSuccess()
{
$request = $this->createMockRequest([
'email' => 'test@example.com',
'password' => 'password123'
]);
$controller = new UserController();
$response = $controller->login($request);
$this->assertEquals(200, $response->getStatusCode());
}
}สรุป
การใช้รูปแบบ MVC ใน Kotchasan Framework ช่วยให้:
- โค้ดมีการจัดระเบียบที่ดี - แยกความรับผิดชอบชัดเจน
- ง่ายต่อการบำรุงรักษา - แก้ไขส่วนใดส่วนหนึ่งโดยไม่กระทบส่วนอื่น
- สามารถขยายได้ - เพิ่มฟีเจอร์ใหม่ได้ง่าย
- ทดสอบได้ - แต่ละส่วนสามารถทดสอบแยกได้
- ทำงานเป็นทีมได้ดี - นักพัฒนาหลายคนทำงานพร้อมกันได้
การปฏิบัติตาม MVC Pattern และ Best Practices จะช่วยให้แอปพลิเคชันมีคุณภาพสูงและพัฒนาได้อย่างยั่งยืน