Để 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:

/project-root
    /app
        /Controllers
        /Models
        /Views
        /Middlewares
        /Helpers
        /Libraries
        /Logs
        /Cache
    /config
        config.php
        database.php
        routes.php
    /public
        index.php
    /resources
        /lang
    /storage
    .htaccess
    composer.json

2. Config Database và Routes

Config Database (/config/database.php)

Bạn cần cấu hình kết nối cơ sở dữ liệu trong file này:

return [
    'host' => 'localhost',
    'dbname' => 'your_database_name',
    'username' => 'your_username',
    'password' => 'your_password',
    'charset' => 'utf8mb4',
];

Config Routes (/config/routes.php)

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

return [
    '/' => 'HomeController@index',
    '/posts' => 'PostController@index',
    '/post/{id}' => 'PostController@show',
    '/category/{id}' => 'CategoryController@show',
];

Trong ví dụ này, {id} là placeholder cho tham số sẽ được truyền vào phương thức của controller.

2.1 index.php để xử lý tham số từ URL

Dưới đây là phiên bản cập nhật của file index.php để hỗ trợ truyền tham số vào các phương thức của controller:

<?php

// Bật báo cáo lỗi trong quá trình phát triển
ini_set('display_errors', 1);
error_reporting(E_ALL);

session_start();

// Autoload các class sử dụng PSR-4 autoloading
spl_autoload_register(function ($class) {
    $prefix = 'App\\';
    $baseDir = __DIR__ . '/../app/';

    $len = strlen($prefix);
    // có vai trò kiểm tra xem tên lớp ($class) có bắt đầu với tiền tố ($prefix) được xác định hay không
    if (strncmp($prefix, $class, $len) !== 0) {
        return;
    }

    $relativeClass = substr($class, $len);
    $file = $baseDir . str_replace('\\', '/', $relativeClass) . '.php';

    if (file_exists($file)) {
        require $file;
    }
});

// Nạp các tệp cấu hình
$routes = include __DIR__ . '/../config/routes.php';

// Lấy URL hiện tại và xử lý nó
$requestUri = trim($_SERVER['REQUEST_URI'], '/');
$requestUri = parse_url($requestUri, PHP_URL_PATH);

$found = false;

// Duyệt qua các route để tìm khớp
foreach ($routes as $route => $target) {
    // Chuyển đổi route thành regex
    $routePattern = preg_replace('/\{([a-zA-Z0-9_]+)\}/', '([a-zA-Z0-9_]+)', $route);
    $routePattern = str_replace('/', '\/', $routePattern);

    // Kiểm tra nếu URL khớp với route pattern
    if (preg_match('/^' . $routePattern . '$/', $requestUri, $matches)) {
        array_shift($matches); // Bỏ phần tử đầu tiên vì đó là toàn bộ khớp

        // Tách controller và method
        $targetParts = explode('@', $target);
        $controllerName = $targetParts[0];
        $methodName = $targetParts[1];

        $controllerClass = "App\\Controllers\\$controllerName";

        if (class_exists($controllerClass)) {
            $controller = new $controllerClass();

            if (method_exists($controller, $methodName)) {
                // Gọi phương thức của controller với các tham số
                call_user_func_array([$controller, $methodName], $matches);
                $found = true;
                break;
            } else {
                http_response_code(404);
                echo "Method $methodName not found in controller $controllerName.";
                $found = true;
                break;
            }
        } else {
            http_response_code(404);
            echo "Controller $controllerName not found.";
            $found = true;
            break;
        }
    }
}

if (!$found) {
    http_response_code(404);
    echo "Route not found.";
}

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

namespace App\Core;

class Router {
    private $routes;

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

    public function direct($requestUri) {
        foreach ($this->routes as $route => $target) {
            $routePattern = preg_replace('/\{([a-zA-Z0-9_]+)\}/', '([a-zA-Z0-9_]+)', $route);
            $routePattern = str_replace('/', '\/', $routePattern);

            if (preg_match('/^' . $routePattern . '$/', $requestUri, $matches)) {
                array_shift($matches);

                list($controller, $method) = explode('@', $target);
                $controllerClass = "App\\Controllers\\$controller";

                if (class_exists($controllerClass)) {
                    $controllerObject = new $controllerClass();
                    if (method_exists($controllerObject, $method)) {
                        return call_user_func_array([$controllerObject, $method], $matches);
                    }
                }
            }
        }

        http_response_code(404);
        echo "Route not found.";
    }
}

2.5 Sử dụng Router trong index.php

<?php

ini_set('display_errors', 1);
error_reporting(E_ALL);

session_start();

spl_autoload_register(function ($class) {
    $prefix = 'App\\';
    $baseDir = __DIR__ . '/../app/';

    $len = strlen($prefix);
    if (strncmp($prefix, $class, $len) !== 0) {
        return;
    }

    $relativeClass = substr($class, $len);
    $file = $baseDir . str_replace('\\', '/', $relativeClass) . '.php';

    if (file_exists($file)) {
        require $file;
    }
});

$routes = include __DIR__ . '/../config/routes.php';
$requestUri = trim($_SERVER['REQUEST_URI'], '/');
$requestUri = parse_url($requestUri, PHP_URL_PATH);

$router = new \App\Core\Router($routes);
$router->direct($requestUri);

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:

namespace App\Controllers;

class BaseController {
    public function render($view, $data = []) {
        extract($data);
        include __DIR__ . '/../Views/' . $view . '.php';
    }
}

PostController (/app/Controllers/PostController.php)

Controller quản lý bài viết:

namespace App\Controllers;

use App\Models\PostModel;

class PostController extends BaseController {
    private $postModel;

    public function __construct() {
        $this->postModel = new PostModel();
    }

    public function index() {
        $posts = $this->postModel->getAllPosts();
        $this->render('posts/index', ['posts' => $posts]);
    }

    public function show($id) {
        $post = $this->postModel->getPostById($id);
        $this->render('posts/show', ['post' => $post]);
    }
}

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:

namespace App\Models;

use PDO;

class BaseModel {
    protected $db;

    public function __construct() {
        $config = include __DIR__ . '/../../config/database.php';
        $dsn = "mysql:host={$config['host']};dbname={$config['dbname']};charset={$config['charset']}";
        $this->db = new PDO($dsn, $config['username'], $config['password']);
    }
}

PostModel (/app/Models/PostModel.php)

Model quản lý dữ liệu bài viết:

namespace App\Models;

use PDO;

class BaseModel {
    protected $db;

    public function __construct() {
        $config = include __DIR__ . '/../../config/database.php';
        $dsn = "mysql:host={$config['host']};dbname={$config['dbname']};charset={$config['charset']}";
        $this->db = new PDO($dsn, $config['username'], $config['password']);
    }
}

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

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

<!DOCTYPE html>
<html>
<head>
    <title>Posts</title>
</head>
<body>
    <h1>All Posts</h1>
    <ul>
        <?php foreach($posts as $post): ?>
            <li>
                <a href="/post/<?= $post['id'] ?>"><?= $post['title'] ?></a>
            </li>
        <?php endforeach; ?>
    </ul>
</body>
</html>

6. Middleware (/app/Middlewares/CheckAuth.php)

Middleware là một lớp hoặc thành phần trong ứng dụng PHP MVC, được sử dụng để xử lý logic trước khi yêu cầu HTTP được gửi đến controller hoặc sau khi controller đã xử lý yêu cầu. Middleware rất hữu ích để xử lý các tác vụ như xác thực (authentication), phân quyền (authorization), log request, hoặc thực hiện các thao tác tiền xử lý khác.

Dưới đây là chi tiết về cách tạo và sử dụng middleware, cụ thể là middleware kiểm tra xác thực người dùng (CheckAuth), và cách nó được nạp và xử lý trong ứng dụng.

6.1 Tạo Middleware CheckAuth

Đầu tiên, chúng ta sẽ tạo một middleware có tên CheckAuth để kiểm tra xem người dùng đã đăng nhập hay chưa. Nếu người dùng chưa đăng nhập, middleware này sẽ chuyển hướng họ đến trang đăng nhập.

Cấu trúc thư mục

Middleware thường được đặt trong thư mục /app/Middlewares/ để tổ chức mã nguồn rõ ràng. Dưới đây là cấu trúc thư mục:

/app
  /Controllers
  /Models
  /Views
  /Middlewares
    - CheckAuth.php
  /Core
  ...
/public
  - index.php
/config
  - routes.php
  - config.php

Nội dung CheckAuth.php

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

<?php

namespace App\Middlewares;

class CheckAuth {
    public function handle() {
        // Kiểm tra xem người dùng đã đăng nhập hay chưa
        if (!isset($_SESSION['user'])) {
            // Nếu chưa đăng nhập, chuyển hướng đến trang đăng nhập
            header('Location: /login');
            exit;
        }
        // Nếu đã đăng nhập, middleware này không làm gì cả và cho phép tiếp tục xử lý
    }
}

6.2 Sử dụng Middleware trong ứng dụng

Để tổ chức và quản lý middleware một cách hiệu quả hơn, bạn có thể cấu hình middleware trong file routes.php và xử lý chúng trong lớp Router. Dưới đây là hướng dẫn chi tiết cách làm:

Cấu hình Middleware trong routes.php

Trong file routes.php, bạn có thể cấu hình middleware cho từng route hoặc nhóm route cụ thể. Ví dụ:

return [
    '/' => [
        'uses' => 'HomeController@index',
    ],
    '/dashboard' => [
        'uses' => 'DashboardController@index',
        'middleware' => ['auth'], // Middleware kiểm tra xác thực
    ],
    '/profile' => [
        'uses' => 'ProfileController@show',
        'middleware' => ['auth'], // Middleware kiểm tra xác thực
    ],
    '/posts/create' => [
        'uses' => 'PostController@create',
        'middleware' => ['auth'], // Middleware kiểm tra xác thực
    ],
    '/post/{id}' => [
        'uses' => 'PostController@show',
    ],
    '/login' => [
        'uses' => 'AuthController@login',
    ],
    // Thêm các route khác ở đây
];

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:

namespace App\Core;

use App\Middlewares\CheckAuth;

class Router {
    private $routes;

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

    public function direct($requestUri) {
        foreach ($this->routes as $route => $options) {
            $routePattern = preg_replace('/\{([a-zA-Z0-9_]+)\}/', '([a-zA-Z0-9_]+)', $route);
            $routePattern = str_replace('/', '\/', $routePattern);

            if (preg_match('/^' . $routePattern . '$/', $requestUri, $matches)) {
                array_shift($matches);

                // Lấy controller và phương thức từ cấu hình
                $target = $options['uses'];
                list($controller, $method) = explode('@', $target);
                $controllerClass = "App\\Controllers\\$controller";

                // Xử lý middleware nếu có
                if (isset($options['middleware'])) {
                    foreach ($options['middleware'] as $middleware) {
                        $this->handleMiddleware($middleware);
                    }
                }

                if (class_exists($controllerClass)) {
                    $controllerObject = new $controllerClass();
                    if (method_exists($controllerObject, $method)) {
                        return call_user_func_array([$controllerObject, $method], $matches);
                    }
                }
            }
        }

        http_response_code(404);
        echo "Route not found.";
    }

    private function handleMiddleware($middleware) {
        switch ($middleware) {
            case 'auth':
                $authMiddleware = new CheckAuth();
                $authMiddleware->handle();
                break;
            // Bạn có thể thêm các middleware khác tại đây
            default:
                throw new \Exception("Middleware $middleware not found.");
        }
    }
}

6.4 Cách hoạt động của Middleware trong Router

  1. Cấu hình Middleware trong routes.php: Bạn thêm phần tử 'middleware' trong cấu hình route để chỉ định middleware cần chạy trước khi truy cập vào controller. Ví dụ, 'middleware' => ['auth'] chỉ định rằng route này cần phải chạy middleware CheckAuth.
  2. Xử lý Middleware trong Router:
    • Xác định route: Khi một yêu cầu đến, Router sẽ xác định route phù hợp bằng cách so khớp với URL.
    • Chạy Middleware: Trước khi gọi controller, Router kiểm tra xem route có middleware hay không. Nếu có, nó sẽ gọi hàm handleMiddleware để xử lý các middleware đã được cấu hình.
    • Gọi Controller: Sau khi middleware đã được xử lý, Router tiếp tục gọi controller và phương thức tương ứng.
  3. Hàm handleMiddleware:
    • Hàm này nhận tên middleware từ cấu hình route và thực hiện nó. Trong ví dụ trên, middleware auth được ánh xạ đến lớp CheckAuth.
    • Bạn có thể mở rộng hàm này để hỗ trợ nhiều middleware khác nhau bằng cách thêm các case mới vào switch.

Middleware là một công cụ mạnh mẽ giúp bạn kiểm soát luồng yêu cầu trong ứng dụng PHP MVC. Bằng cách sử dụng middleware, bạn có thể tách biệt rõ ràng logic xử lý phụ trợ (như kiểm tra xác thực) khỏi các controller, giúp mã nguồn dễ bảo trì và mở rộng hơn. Việc nạp và xử lý middleware có thể thực hiện trực tiếp trong index.php, nhưng việc tổ chức tốt hơn là thông qua một lớp Router để quản lý các middleware một cách linh hoạt hơn.

7. Đa ngôn ngữ (/resources/lang/en.php, vn.php)

Để triển khai chức năng đa ngôn ngữ trong ứng dụng PHP MVC, bạn có thể sử dụng các tệp ngôn ngữ để lưu trữ các chuỗi văn bản và nạp chúng dựa trên ngôn ngữ mà người dùng đã chọn. Dưới đây là hướng dẫn chi tiết về cách nạp và xử lý đa ngôn ngữ trong ứng dụng của bạn.

7.1 Cấu trúc thư mục và tệp tin ngôn ngữ

Bạn sẽ cần tạo thư mục resources/lang/ và đặt các tệp ngôn ngữ tương ứng cho từng ngôn ngữ mà bạn muốn hỗ trợ. Ví dụ:

/resources
  /lang
    - en.php
    - vn.php
/app
  /Controllers
  /Models
  /Views
  ...
/public
  - index.php
/config
  - config.php

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

<?php

return [
    'welcome' => 'Welcome to our website!',
    'login' => 'Login',
    'register' => 'Register',
    'dashboard' => 'Dashboard',
    // Thêm các chuỗi văn bản khác
];

Tệp vn.php

<?php

return [
    'welcome' => 'Chào mừng bạn đến với trang web của chúng tôi!',
    'login' => 'Đăng nhập',
    'register' => 'Đăng ký',
    'dashboard' => 'Bảng điều khiển',
    // Thêm các chuỗi văn bản khác
];

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:

<?php

namespace App\Helpers;

class Localization {
    private $language;
    private $translations = [];

    public function __construct($language = 'en') {
        $this->language = $language;
        $this->loadTranslations();
    }

    private function loadTranslations() {
        $file = __DIR__ . '/../../resources/lang/' . $this->language . '.php';
        if (file_exists($file)) {
            $this->translations = include $file;
        }
    }

    public function get($key) {
        return $this->translations[$key] ?? $key;
    }

    public static function translate($key, $language = 'en') {
        $localization = new self($language);
        return $localization->get($key);
    }
}

7.3 Nạp và sử dụng Helper trong index.php

Trong file index.php, bạn có thể nạp Helper và thiết lập ngôn ngữ mặc định hoặc dựa trên lựa chọn của người dùng:

<?php

ini_set('display_errors', 1);
error_reporting(E_ALL);

session_start();

// Nạp autoload các class
spl_autoload_register(function ($class) {
    $prefix = 'App\\';
    $baseDir = __DIR__ . '/../app/';

    $len = strlen($prefix);
    if (strncmp($prefix, $class, $len) !== 0) {
        return;
    }

    $relativeClass = substr($class, $len);
    $file = $baseDir . str_replace('\\', '/', $relativeClass) . '.php';

    if (file_exists($file)) {
        require $file;
    }
});

// Xác định ngôn ngữ dựa trên session hoặc query string
$language = isset($_GET['lang']) ? $_GET['lang'] : (isset($_SESSION['lang']) ? $_SESSION['lang'] : 'en');
$_SESSION['lang'] = $language;

// Khởi tạo Localization Helper
use App\Helpers\Localization;
$localization = new Localization($language);

// Ví dụ: sử dụng helper để dịch chuỗi văn bản
echo $localization->get('welcome');

// Tiếp tục xử lý routing và các phần khác
$routes = include __DIR__ . '/../config/routes.php';
$requestUri = trim($_SERVER['REQUEST_URI'], '/');
$requestUri = parse_url($requestUri, PHP_URL_PATH);

$router = new \App\Core\Router($routes);
$router->direct($requestUri);

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.

<?php

namespace App\Controllers;

use App\Helpers\Localization;

class HomeController {
    public function index() {
        $language = $_SESSION['lang'] ?? 'en';
        $welcomeMessage = Localization::translate('welcome', $language);
        
        // Truyền thông điệp chào mừng tới view
        $this->render('home/index', ['welcomeMessage' => $welcomeMessage]);
    }
}
<?php
echo $welcomeMessage;
?>

7.5 Lý do không nên gọi trực tiếp Localization::translate(‘welcome’, ‘en’) trong View:

  1. Tách biệt giữa logic và hiển thị: View nên chỉ tập trung vào việc hiển thị dữ liệu mà không phải lo lắng về cách dữ liệu đó được lấy hoặc xử lý như thế nào. Bằng cách này, view có thể dễ dàng thay đổi mà không ảnh hưởng đến logic của ứng dụng.
  2. Dễ bảo trì: Khi bạn giữ logic xử lý ngôn ngữ trong Controller hoặc lớp Helper, việc thay đổi hoặc mở rộng chức năng sẽ dễ dàng hơn. Nếu logic bị trộn lẫn với view, việc bảo trì và mở rộng mã sẽ trở nên khó khăn hơn.
  3. Tính tái sử dụng: Tách biệt logic ra khỏi view giúp bạn có thể tái sử dụng các phần xử lý logic trong nhiều view khác nhau mà không cần phải sao chép mã.

8. Helper (/app/Helpers/url_helper.php)

Để nạp các Helper trong ứng dụng PHP MVC của bạn thông qua cấu hình mảng, bạn có thể thiết lập một hệ thống quản lý việc nạp Helper một cách tự động dựa trên cấu hình này. Dưới đây là hướng dẫn chi tiết:

8.1 Tạo Cấu Hình Helper

Trước tiên, tạo một file cấu hình để liệt kê các Helper mà bạn muốn nạp tự động. Tạo file config/helpers.php với nội dung như sau:

<?php

return [
    'url_helper',
    'string_helper',
    'array_helper',
    // Thêm các helper khác nếu có
];

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.

<?php

ini_set('display_errors', 1);
error_reporting(E_ALL);

session_start();

// Nạp autoload các class
spl_autoload_register(function ($class) {
    $prefix = 'App\\';
    $baseDir = __DIR__ . '/../app/';

    $len = strlen($prefix);
    if (strncmp($prefix, $class, $len) !== 0) {
        return;
    }

    $relativeClass = substr($class, $len);
    $file = $baseDir . str_replace('\\', '/', $relativeClass) . '.php';

    if (file_exists($file)) {
        require $file;
    }
});

// Nạp các helper từ file cấu hình
$helpers = include __DIR__ . '/../config/helpers.php';

foreach ($helpers as $helper) {
    $helperPath = __DIR__ . '/../app/Helpers/' . $helper . '.php';
    if (file_exists($helperPath)) {
        require_once $helperPath;
    } else {
        throw new Exception("Helper file not found: " . $helperPath);
    }
}

// Tiếp tục với phần xử lý routing và controller
$routes = include __DIR__ . '/../config/routes.php';
$requestUri = trim($_SERVER['REQUEST_URI'], '/');
$requestUri = parse_url($requestUri, PHP_URL_PATH);

$router = new \App\Core\Router($routes);
$router->direct($requestUri);

Giải Thích Hoạt Động

  • File cấu hình config/helpers.php: Liệt kê các helper mà bạn muốn nạp tự động dưới dạng mảng. Bạn có thể thêm hoặc bớt các helper tại đây mà không cần chỉnh sửa logic nạp trong index.php.
  • Nạp Helper: Trong index.php, sau khi lấy danh sách các helper từ file cấu hình, một vòng lặp foreach được sử dụng để nạp từng helper. Điều này giúp bạn dễ dàng quản lý và mở rộng danh sách helper mà không phải nạp từng helper thủ công.

Ví Dụ Cụ Thể

Giả sử bạn có các hàm trong các helper như sau:

url_helper.php

<?php

if (!function_exists('base_url')) {
    function base_url($path = '') {
        $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
        $host = $_SERVER['HTTP_HOST'];
        $baseUrl = $protocol . $host;

        if ($path) {
            return rtrim($baseUrl, '/') . '/' . ltrim($path, '/');
        }

        return $baseUrl;
    }
}

string_helper.php

<?php

if (!function_exists('str_contains')) {
    function str_contains($haystack, $needle) {
        return strpos($haystack, $needle) !== false;
    }
}

array_helper.php

<?php

if (!function_exists('array_get')) {
    function array_get($array, $key, $default = null) {
        return isset($array[$key]) ? $array[$key] : $default;
    }
}

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:

composer require phpmailer/phpmailer

Trong code:

use PHPMailer\PHPMailer\PHPMailer;

$mail = new PHPMailer();

10. Ghi log (/app/Helpers/log_helper.php)

Tạo một helper để ghi log:

function write_log($message) {
    $logFile = __DIR__ . '/../Logs/log.txt';
    file_put_contents($logFile, date('Y-m-d H:i:s') . ": " . $message . "\n", FILE_APPEND);
}

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

Sử dụng file caching:

namespace App\Cache;

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.