Để xây dựng một ứng dụng theo mô hình MVC (Model-View-Controller) trong PHP thuần, bạn cần nắm rõ cách các thành phần chính tương tác với nhau. Tôi sẽ hướng dẫn bạn triển khai các chức năng như bài viết, chuyên mục bài viết, trang chủ theo mô hình MVC

1. Cấu trúc thư mục

Trước hết, chúng ta cần tổ chức cấu trúc thư mục cho dự án:

/config/database.php

Config Routes (/config/routes.php)

Định nghĩa các tuyến đường (routes) trong ứng dụng:

{id}

Giải thích

  • Route với tham số: Trong routes.php, các route có thể chứa tham số dưới dạng {id}, {slug}, v.v. Ví dụ /post/{id}.
  • Chuyển đổi route thành regex: Để khớp các route với URL yêu cầu, chúng ta chuyển đổi các phần tử {id}, {slug} thành các regex có thể khớp với các phần URL tương ứng. Cụ thể, {id} trở thành ([a-zA-Z0-9_]+).
  • Khớp URL với route: Nếu URL khớp với route, các tham số bắt được từ URL sẽ được lưu vào $matches.
  • Gọi phương thức với tham số: Sử dụng call_user_func_array, chúng ta truyền các tham số từ $matches vào phương thức của controller.

Ví dụ thực tế

Với cấu hình trên, nếu bạn truy cập vào URL /post/1, hệ thống sẽ:

  1. Khớp URL với route /post/{id}.
  2. Bắt được tham số 1 từ URL và truyền nó vào phương thức show của PostController.

Phương thức show trong PostController sẽ nhận tham số $id và có thể sử dụng nó như sau:

Việc truyền tham số vào phương thức của controller có thể được thực hiện trực tiếp trong index.php, như tôi đã trình bày ở trên. Tuy nhiên, nếu ứng dụng của bạn phức tạp hoặc bạn muốn tổ chức mã nguồn một cách rõ ràng và dễ bảo trì hơn, bạn có thể tách việc xử lý logic đó ra một hàm hoặc lớp riêng biệt. Dưới đây, tôi sẽ so sánh hai cách tiếp cận này để bạn có thể lựa chọn giải pháp phù hợp nhất.

2.2 Truyền tham số trực tiếp trong index.php

Ưu điểm:

  • Đơn giản: Đối với các ứng dụng nhỏ hoặc đơn giản, việc xử lý trực tiếp trong index.php giúp giảm thiểu số lượng tệp tin và lớp cần phải quản lý.
  • Nhanh chóng: Không cần phải tạo thêm các lớp hoặc hàm phụ trợ, bạn có thể nhanh chóng triển khai các tính năng cơ bản.

Nhược điểm:

  • Khó bảo trì: Khi ứng dụng phát triển, index.php có thể trở nên phức tạp và khó bảo trì nếu chứa quá nhiều logic.
  • Khó mở rộng: Nếu bạn muốn bổ sung thêm các tính năng như tiền xử lý (preprocessing), kiểm tra quyền hạn (authorization), hoặc ghi log cho từng route, mã nguồn sẽ trở nên rối rắm.

2.3 Tách logic xử lý tham số vào hàm hoặc lớp riêng biệt

Ưu điểm:

  • Dễ bảo trì và mở rộng: Bằng cách tách logic xử lý routing và truyền tham số vào một hàm hoặc lớp riêng, mã nguồn sẽ dễ bảo trì hơn. Bạn có thể dễ dàng bổ sung hoặc thay đổi chức năng mà không cần chỉnh sửa quá nhiều phần khác.
  • Tái sử dụng: Nếu bạn cần thực hiện cùng một logic trên nhiều phần của ứng dụng, việc tách thành hàm hoặc lớp riêng cho phép tái sử dụng mã.
  • Tổ chức mã nguồn tốt hơn: Ứng dụng của bạn sẽ có cấu trúc rõ ràng hơn, dễ đọc và dễ hiểu hơn, đặc biệt là với các đội ngũ làm việc trên cùng một dự án.

Nhược điểm:

  • Phức tạp hơn đối với ứng dụng nhỏ: Nếu ứng dụng của bạn rất nhỏ và đơn giản, việc tách logic có thể tạo thêm một chút phức tạp không cần thiết.

2.4 Ví dụ sử dụng lớp Router riêng biệt

Dưới đây là một cách tiếp cận sử dụng lớp Router riêng để xử lý route và truyền tham số:

Tạo lớp Router

index.php

Dự án nhỏ hoặc đơn giản: Nếu bạn đang phát triển một ứng dụng nhỏ hoặc đơn giản, việc xử lý trực tiếp trong index.php có thể là đủ.

Dự án lớn hoặc cần bảo trì lâu dài: Nếu dự án của bạn có khả năng mở rộng hoặc cần bảo trì trong thời gian dài, tách logic xử lý routing và truyền tham số vào một lớp hoặc hàm riêng biệt sẽ là lựa chọn tốt hơn. Điều này giúp tổ chức mã nguồn tốt hơn, dễ bảo trì và mở rộng trong tương lai.

3. BaseController và Controller

BaseController (/app/Controllers/BaseController.php)

BaseController là lớp cha mà tất cả các Controller khác sẽ kế thừa:

/app/Controllers/PostController.php

4. BaseModel và Model

BaseModel (/app/Models/BaseModel.php)

Lớp BaseModel quản lý kết nối cơ sở dữ liệu và các chức năng chung:

/app/Models/PostModel.php

5. Views (/app/Views/posts/index.php)

Views sẽ hiển thị dữ liệu từ Controller:

/app/Middlewares/CheckAuth.php

Nội dung CheckAuth.php

Dưới đây là ví dụ về middleware CheckAuth:

routes.php

6.3 Cập nhật lớp Router để xử lý Middleware

Lớp Router sẽ được cập nhật để xử lý middleware trước khi gọi controller. Dưới đây là cách bạn có thể triển khai:

Router

Nội dung của tệp ngôn ngữ

Mỗi tệp ngôn ngữ sẽ chứa một mảng các chuỗi văn bản được sử dụng trong ứng dụng.

Tệp en.php

vn.php

7.2 Tạo Helper để xử lý ngôn ngữ

Bạn cần một helper để nạp các tệp ngôn ngữ và lấy chuỗi văn bản tương ứng. Tạo một helper trong app/Helpers/Localization.php:

index.php

7.4 Sử dụng trong Views và Controllers

Bạn có thể sử dụng Helper để dịch các chuỗi văn bản trong Views và Controllers.

/app/Helpers/url_helper.php

8.2 Tạo Các Helper

Giả sử bạn đã có các helper tương ứng trong thư mục /app/Helpers/ như url_helper.php, string_helper.php, và array_helper.php. Ví dụ:

  • url_helper.php
  • string_helper.php
  • array_helper.php

8.3 Cập Nhật index.php để Nạp Helper

Trong file index.php, bạn sẽ cần thêm logic để tự động nạp các Helper được liệt kê trong file cấu hình.

config/helpers.php

string_helper.php

array_helper.php

Khi bạn đã cấu hình và nạp các helper, bạn có thể sử dụng các hàm như base_url(), str_contains(), và array_get() ở bất kỳ đâu trong ứng dụng mà không cần phải nạp lại chúng.

Bằng cách sử dụng một cấu hình mảng để quản lý các helper, bạn tạo ra một hệ thống linh hoạt và dễ bảo trì. Khi bạn cần thêm helper mới, bạn chỉ cần cập nhật file cấu hình config/helpers.php, giúp mã nguồn của bạn dễ dàng mở rộng và sạch sẽ hơn.

9. Library tích hợp bên thứ 3

Bạn có thể sử dụng Composer để cài đặt các thư viện bên thứ 3:

/app/Helpers/log_helper.php

11. Cache dữ liệu (/app/Cache/Cache.php)

Sử dụng file caching:

namespace AppCache;

class Cache {
    public static function set($key, $value, $expiration = 3600) {
        $file = __DIR__ . "/$key.cache";
        $data = ['expiration' => time() + $expiration, 'value' => $value];
        file_put_contents($file, serialize($data));
    }

    public static function get($key) {
        $file = __DIR__ . "/$key.cache";
        if (file_exists($file)) {
            $data = unserialize(file_get_contents($file));
            if ($data['expiration'] >= time()) {
                return $data['value'];
            }
            unlink($file); // Xóa cache nếu đã hết hạn
        }
        return null;
    }
}

12. Hooks trong MVC

Hooks có thể được sử dụng để thực hiện một số hành động trước hoặc sau khi các sự kiện nhất định xảy ra. Ví dụ, bạn có thể sử dụng hooks trước khi một controller xử lý:

class BaseController {
    protected function before() {
        // Code chạy trước khi phương thức controller thực thi
    }

    protected function after() {
        // Code chạy sau khi phương thức controller thực thi
    }

    public function __call($name, $arguments) {
        $this->before();
        call_user_func_array([$this, $name], $arguments);
        $this->after();
    }
}

Trong ví dụ này, các phương thức beforeafter sẽ tự động được gọi trước và sau khi controller thực thi các phương thức khác.

Kết luận

Trên đây là hướng dẫn chi tiết cách thiết kế một ứng dụng PHP theo mô hình MVC với các chức năng yêu cầu. Bạn có thể tùy chỉnh và mở rộng theo nhu cầu thực tế của dự án.