Kotchasan Framework Documentation

Kotchasan Framework Documentation

Kotchasan MVC Architecture Guide

EN 07 Feb 2026 02:20

Kotchasan MVC Architecture Guide

Introduction

The Kotchasan Framework uses the MVC (Model-View-Controller) architectural pattern that helps separate application functionality into 3 main parts, making code well-organized, easy to maintain, and scalable.

Table of Contents

  1. MVC Pattern Overview
  2. Directory Structure
  3. Model Layer
  4. View Layer
  5. Controller Layer
  6. MVC Collaboration
  7. Module Creation Examples
  8. Best Practices

MVC Pattern Overview

Model

  • Manages data and business logic
  • Connects to the database
  • Validates data integrity
  • Processes data before sending to Controller

View

  • Manages presentation and UI
  • Creates HTML, JSON, or other output formats
  • Receives data from Controller and renders output
  • Contains no business logic

Controller

  • Manages HTTP Request and Response
  • Connects Model and View
  • Decides which Model and View to use
  • Handles routing and authentication

Directory Structure

modules/
└── mymodule/
    ├── controllers/
    │   ├── index.php          # Main 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          # Main View (namespace MyModule\Index)
    │   ├── user.php           # User View (namespace MyModule\User)
    │   └── admin.php          # Admin View (namespace MyModule\Admin)
    ├── template/
    │   └── email.html         # Email template
    ├── script.js              # JavaScript
    └── style.css              # CSS

Model Layer

Creating a Model

namespace MyModule\User;

use Kotchasan\Model;
use Kotchasan\Database;

/**
 * User Model
 * File: modules/mymodule/models/user.php
 */
class Model extends \Kotchasan\Model
{
    /**
     * Table name
     */
    protected $table = 'users';

    /**
     * Kotchasan\Database connection (defaults to 'default' if not specified)
     */
    protected $conn = 'default';

    /**
     * Get all users
     */
    public function getAllUsers()
    {
        return $this->db->select()
            ->from($this->table)
            ->where('status', '=', 'active')
            ->orderBy('created_at', 'DESC')
            ->fetchAll();
    }

    /**
     * Get user by ID
     */
    public function getUserById($id)
    {
        return $this->db->select()
            ->from($this->table)
            ->where('id', '=', $id)
            ->first();
    }

    /**
     * Create new user
     */
    public function createUser($data)
    {
        // Validate data
        $this->validateUserData($data);

        // Hash password
        $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();
    }

    /**
     * Update user
     */
    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();
    }

    /**
     * Delete user
     */
    public function deleteUser($id)
    {
        return $this->db->delete($this->table)
            ->where('id', '=', $id)
            ->execute();
    }

    /**
     * Validate user data
     */
    private function validateUserData($data)
    {
        if (empty($data['name'])) {
            throw new \Exception('Name is required');
        }

        if (empty($data['email']) || !filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
            throw new \Exception('Valid email is required');
        }

        // Check for duplicate email
        $existing = $this->db->select('id')
            ->from($this->table)
            ->where('email', '=', $data['email'])
            ->first();

        if ($existing) {
            throw new \Exception('Email already exists');
        }
    }
}

Advanced Model Usage

namespace MyModule\Post;

use Kotchasan\Model;

class Model extends \Kotchasan\Model
{
    /**
     * Get posts with author information
     */
    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();
    }

    /**
     * Search posts
     */
    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();
    }

    /**
     * Get post statistics
     */
    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

Creating a 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
{
    /**
     * Display user list
     */
    public function render(Request $request)
    {
        // Get data from Model
        $model = new \MyModule\User\Model();
        $users = $model->getAllUsers();

        // Create 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', [], 'Name');
        $tr->add('th', [], 'Email');
        $tr->add('th', [], 'Status');
        $tr->add('th', [], 'Actions');

        // 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']);

            // Action buttons
            $actions = $tr->add('td');
            $actions->add('a', [
                'href' => 'index.php?module=mymodule&page=user&action=edit&id=' . $user['id'],
                'class' => 'button green'
            ], 'Edit');
            $actions->add('a', [
                'href' => 'index.php?module=mymodule&page=user&action=delete&id=' . $user['id'],
                'class' => 'button red',
                'onclick' => 'return confirm("Are you sure you want to delete?")'
            ], 'Delete');
        }

        return $table->render();
    }

    /**
     * Add/Edit user form
     */
    public function form(Request $request)
    {
        $id = $request->request('id')->toInt();
        $user = [];

        if ($id > 0) {
            $model = new \MyModule\User\Model();
            $user = $model->getUserById($id);
        }

        // Create form
        $form = Html::create('form', [
            'id' => 'user_form',
            'class' => 'setup_frm',
            'action' => 'index.php/mymodule/model/user/save',
            'onsubmit' => 'doFormSubmit',
            'ajax' => true,
            'token' => true
        ]);

        // Hidden ID field
        if ($id > 0) {
            $form->add('input', [
                'type' => 'hidden',
                'name' => 'id',
                'value' => $id
            ]);
        }

        // Name field
        $fieldset = $form->add('fieldset');
        $fieldset->add('legend', [], 'User Information');

        $groups = $fieldset->add('div', ['class' => 'item']);
        $groups->add('label', [], 'Name');
        $groups->add('input', [
            'type' => 'text',
            'name' => 'name',
            'value' => isset($user['name']) ? $user['name'] : '',
            'required' => true
        ]);

        // Email field
        $groups = $fieldset->add('div', ['class' => 'item']);
        $groups->add('label', [], 'Email');
        $groups->add('input', [
            'type' => 'email',
            'name' => 'email',
            'value' => isset($user['email']) ? $user['email'] : '',
            'required' => true
        ]);

        // Password field (only for new users)
        if ($id == 0) {
            $groups = $fieldset->add('div', ['class' => 'item']);
            $groups->add('label', [], 'Password');
            $groups->add('input', [
                'type' => 'password',
                'name' => 'password',
                'required' => true
            ]);
        }

        // Save button
        $fieldset = $form->add('fieldset');
        $fieldset->add('input', [
            'class' => 'button save large',
            'type' => 'submit',
            'value' => 'Save'
        ]);

        return $form->render();
    }
}

Creating JSON Response

namespace MyModule\Api;

use Kotchasan\Http\Request;
use Kotchasan\Http\Response;

class View extends \Kotchasan\View
{
    /**
     * Send JSON data
     */
    public function json(Request $request)
    {
        $model = new \MyModule\User\Model();
        $users = $model->getAllUsers();

        // Create JSON Response
        $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

Creating a 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
{
    /**
     * Display user list
     */
    public function render(Request $request)
    {
        // Check permissions
        $login = Login::isMember();
        if (!$login || !Login::checkPermission($login, 'can_manage_user')) {
            return new \Kotchasan\Http\NotFound();
        }

        // Call View
        $view = new \MyModule\User\View();
        return $view->render($request);
    }

    /**
     * Display form
     */
    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, 'Data retrieved successfully');

        } 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], 'User created successfully', 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([], 'Updated successfully');

        } 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([], 'Deleted successfully');

        } catch (\Exception $e) {
            return $this->errorResponse($e->getMessage(), 400);
        }
    }
}

MVC Collaboration

Workflow

  1. Request comes to Router
  2. Router calls appropriate Controller
  3. Controller checks permissions and calls Model
  4. Model processes data and returns results
  5. Controller sends data to View
  6. View creates HTML/JSON and returns
  7. Response is sent back to Client

Complete Workflow Example

// 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 receives Request
class Controller extends \Kotchasan\Controller
{
    public function render(Request $request)
    {
        // 3. Call Model
        $model = new Model();
        $users = $model->getAllUsers();

        // 4. Send data to View
        $view = new View();
        return $view->render($users);
    }
}

// 5. Model processes data
class Model extends \Kotchasan\Model
{
    public function getAllUsers()
    {
        return $this->db->select('*')
            ->from('users')
            ->where('status', '=', 'active')
            ->fetchAll();
    }
}

// 6. View creates 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;
    }
}

Module Creation Examples

1. Create Directory Structure

mkdir -p modules/blog/{controllers,models,views,template}

2. Create Model

// modules/blog/models/post.php
namespace Blog\Post;

use Kotchasan\Model;

class Model extends \Kotchasan\Model
{
    protected $table = 'blog_posts';

    /**
     * Retrieve posts with author information
     */
    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();
    }

    /**
     * Retrieve post by ID
     */
    public function getPostById($id)
    {
        return $this->db->select()
            ->from($this->table)
            ->where('id', '=', $id)
            ->where('status', '=', 'published')
            ->first();
    }

    /**
     * Create new post
     */
    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. Create 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'],
                'By ' . $post['author'] . ' on ' . $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'
            ], 'Read More');
        }

        return $container->render();
    }
}

4. Create 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. Naming Conventions

// Good - meaningful names
class UserController extends \Kotchasan\Controller
{
    public function getUserProfile(Request $request) { }
    public function updateUserSettings(Request $request) { }
}

// Bad - unclear names
class Controller extends \Kotchasan\Controller
{
    public function action1(Request $request) { }
    public function doSomething(Request $request) { }
}

2. Separation of Concerns

// Model - handles data only
class UserModel extends \Kotchasan\Model
{
    public function findUserByEmail($email)
    {
        return $this->db->select('*')
            ->from('users')
            ->where('email', '=', $email)
            ->first();
    }
}

// Controller - handles logic and 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 successful
            $view = new DashboardView();
            return $view->render($user);
        } else {
            // Login failed
            $view = new LoginView();
            return $view->renderWithError('Invalid email or password');
        }
    }
}

// View - handles presentation only
class LoginView extends \Kotchasan\View
{
    public function renderWithError($message)
    {
        $form = Html::create('form');
        $form->add('div', ['class' => 'error'], $message);
        // ... create login form
        return $form->render();
    }
}

3. Error Handling

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('System error occurred', 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);

        // Send welcome email
        $this->emailService->sendWelcomeEmail($userData['email']);

        return $this->successResponse(['id' => $userId]);
    }
}

5. Testing

// Model testing
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 testing
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());
    }
}

Summary

Using the MVC pattern in Kotchasan Framework provides:

  1. Well-organized code - Clear separation of concerns
  2. Easy maintenance - Modify one part without affecting others
  3. Scalability - Easy to add new features
  4. Testability - Each part can be tested separately
  5. Team collaboration - Multiple developers can work simultaneously

Following MVC Pattern and Best Practices will help create high-quality, sustainable applications.