Module hóa dự án với laravel theo mô hình HMVC

Đăng bởi:

Nguyễn Viết Hiếu

Đăng ngày:

Dec 23, 2020

Đăng ở:

Lập trình website

Lập trình viên như chúng ta thì hầu như không ai là không biết về mô hình MVC (Model - View - Controller). Đây cũng là một mô hình được áp dụng trong hầu hết các ứng dụng web. Các framework PHP phổ biến hiện nay đều đang áp dụng mô hình này và Laravel cũng vậy.

Mô hình MVC của Laravel hỗ trợ rất tốt cho các dự án trung bình và nhỏ, tuy nhiên, với các dự án lớn hoặc cần mở rộng với nhiều người phát triển thì mô hình MVC lại bộc lộ một vài khuyết điểm, đáng kể nhất chính là khó quản lý số số lượng code khi chúng được tăng lên không ngừng.

Để giải quyết vấn đề này, Laravel đã cung cấp cho chúng ta khả năng Module hóa sử dụng mô hình HMVC (Hierarchy - Model - View - Controller). HMVC giúp chúng ta chia từng tính tăng thành các nhóm với cấu trúc MVC và Routing riêng biệt từ đó giúp chúng ta dễ dang quản lý code cũng như khả năng mở rộng trong tương lai.

Để tạo một Module cơ bản theo mô hình HMVC thì chúng ta cần làm những bước sau:

1. Khai báo ban đầu và khởi tạo ServiceProvider

1.1 Khai báo Folder chứa Modules

Chúng ta sẽ đặt Folder chứa Modules tại thư mục root của ứng dụng và đặt tên là "modules", lưu ý là Folder này không nhất thiết phải đặt tại thư mục root và cũng không nhất thiết có tên là "modules".

Khi tạo xong chúng ta sẽ khai báo trong mục "autoload/psr-4" ở file "composer.json"

"autoload": {
    "psr-4": {
        "App\\": "app/",
        "modules\\": "modules/"
    },
    "classmap": [
        "database/seeds",
        "database/factories"
    ]
},

Cuối cùng chạy "composer dump-autoload" để hoàn tất khai báo.

1.2 Khởi tạo và khai báo ServiceProvider

Chúng ta sẽ tạo file "ModuleServiceProvider.php" đặt tại thư mục "modules" vừa tạo và nó có nội dung như sau:

<?php

namespace modules;
use Illuminate\Support\ServiceProvider;
use File;

class ModuleServiceProvider extends ServiceProvider
{
    
    public function register() {}

    public function boot(){}

}

Để hoàn tất quá trình khai báo thì chúng ta cần một bước cuối cùng nữa đó là khai báo trong "config/app.php" tại mục "providers":

'providers' => [
    ...
    /*
    * Custom Service Providers...
    */
    'App\Modules\ServiceProvider',
]

2. Tạo Module cùng với khai báo ServiceProvider tương ứng

2.1 Cấu trúc Module cơ bản

Một module cơ bản sẽ gồm có cấu trúc thư mục như sau:

modules
├── Demo
│   ├── configs
│   │   └── demo.php
│   ├── migrations
│   ├── resources
│   │   └── lang
│   │   └── views
│   ├── routes
│   │   └── routes.php
│   ├── src
│   │   └── Commands
│   │   └── Http
│   │   |   └── Controllers
│   │   |   └── Middlewares
│   │   └── Models
├── Demo2
│   ├── ...
└── ModuleServiceProvider.php

PS: Các thư mục trên không nhất thiết cần phải có mà nó còn tùy theo modules đó có dùng hay không.

2.2 Khai báo ServiceProvider để có thể load được toàn bộ modules

Chúng ta sẽ ghi chú lại khai báo cho toàn bộ các thành phần có trong module theo đoạn code dưới đây:

<?php

namespace modules;
use Illuminate\Support\ServiceProvider;
use File;

class ModuleServiceProvider extends ServiceProvider
{
    
    public function register() {}

    public function boot(){
        // Đăng ký modules theo cấu trúc thư mục
        $directories = array_map('basename', File::directories(__DIR__));
        foreach ($directories as $moduleName) {
            $this->registerModule($moduleName);
        }
    }

    // Khai báo đăng ký cho từng modules
    private function registerModule($moduleName) {
        $modulePath = __DIR__ . "/$moduleName/";
        // Khai báo thành phần ở đây
    }

}

3. Khai báo các thành phần có tại cấu trúc thư mục

3.1 Khai báo routes, migrations, langs, views, helpers

Khai báo các thành phần của modules tại "modules/ModuleServiceProvider.php" và nó được đặt tại hàm "registerModule".

private function registerModule($moduleName) {
    $modulePath = __DIR__ . "/$moduleName/";
    
    // Khai báo route
    if (File::exists($modulePath . "routes/routes.php")) {
        $this->loadRoutesFrom($modulePath . "routes/routes.php");
    }
    
    // Khai báo migration
    // Toàn bộ file migration của modules sẽ tự động được load
    if (File::exists($modulePath . "migrations")) {
        $this->loadMigrationsFrom($modulePath . "migrations");
    }

    // Khai báo languages
    if (File::exists($modulePath . "resources/lang")) {
        // Đa ngôn ngữ theo file php
        // Dùng đa ngôn ngữ tại file php resources/lang/en/general.php : @lang('Demo::general.hello')
        $this->loadTranslationsFrom($modulePath . "resources/lang", $moduleName);
        // Đa ngôn ngữ theo file json
        $this->loadJSONTranslationsFrom($modulePath . 'resources/lang');
    }

    // Khai báo views
    // Gọi view thì ta sử dụng: view('Demo::index'), @extends('Demo::index'), @include('Demo::index')
    if (File::exists($modulePath . "resources/views")) {
        $this->loadViewsFrom($modulePath . "resources/views", $moduleName);
    }

    // Khai báo helpers
    if (File::exists($modulePath . "helpers")) {
        // Tất cả files có tại thư mục helpers
        $helper_dir = File::allFiles($modulePath . "helpers");
        // khai báo helpers
        foreach ($helper_dir as $key => $value) {
            $file = $value->getPathName();
            require $file;
        }
    }
}

2.2 configs

Để có thể sử dụng configs thì chúng ta cần khai báo tại function "register"

public function register() {
    ...
    // Khai báo configs
    $configFile = [
        'demo' => __DIR__.'/Demo/configs/demo.php',
    ];
    foreach ($configFile as $alias => $path) {
        $this->mergeConfigFrom($path, $alias);
    }
    ...
}

Khi khai báo như trên thì chúng ta có thể gọi config tại module như các config bình thường khác, VD: config('demo')

2.3 Middlewares

Để có thể sử dụng Middlewares thì chúng ta cần khai báo tại function "register"

public function register() {
    ...
    // Khai báo middleare
    $middleare = [
        // 'key' => 'namespace của middleare'
        'demo' => '\modules\Demo\src\Http\Controllers\Middlewares\DemoMiddleware',
    ];
    foreach ($middleare as $key => $value) {
        $this->app['router']->pushMiddlewareToGroup($key, $value);
    }
    ...
}

2.4 Commands

Để có thể sử dụng commands thì chúng ta cần khai báo tại function "register"

public function register() {
    ...
    // Khai báo commands
    $this->commands([
        // namespace của commands đặt tại đây
        '\modules\Demo\src\Http\Commands\DemoCommand'
    ]);
    ...
}

Tổng kết

Như các bạn có thể thấy các module được thiết kế theo mô hình HMVC như là một ứng dụng Laravel thu nhỏ với đầy đủ các thành phần. Do cấu trúc thư mục đã được xây dựng và định nghĩa ngay từ đầu nên khi có lỗi xảy ra chúng ta cũng dễ dàng tìm ra lỗi và fix chúng. Khi các dự án lớn dần chúng ta cũng dễ dàng kiểm soát và mở rộng.

Bài hướng dẫn chỉ là một demo đơn giản nên có thể sẽ thiếu một vài thành phần nhưng mình vẫn mong nó giúp ích cho các bạn trong quá trình phát triển ứng dụng web bằng laravel.

Nguyễn Viết Hiếu

Bình luận

name
Nguyen Huy 18.05.2021 lúc 09:26

Thiếu load model, controller??

name
Dev cùi 23.12.2020 lúc 11:56

Bài viết hay và dễ hiểu. Cám ơn Hiếu :))

name
Code Dởm 23.12.2020 lúc 11:51

Hay quá bạn ơi. Đang cần tìm

Để lại bình luận

Email và số điện thoại sẽ không được công khai. Những trường bắt buộc được đánh dấu *

Repository deleted Your repository has remove
Loading