Thiết kế cấu trúc folder HMVC cho Laravel

Ở bài viết này mình sẽ hướng dẫn bạn bắt đầu xây dựng một ứng dụng HMVC với Laravel, và tận dụng sức mạnh của Composer khi quản lí modules.
Thiết kế cấu trúc folder HMVC cho Laravel
Thiết kế cấu trúc folder HMVC cho Laravel

Tạo mới một dự án Laravel

Khởi tạo một dự án Laravel mới bằng dòng lệnh sau

$ composer create-project --prefer-dist laravel/laravel hmvc-project
$ cd hmvc-project

Lúc này cấu trúc thư mục của chúng ta sẽ có dạng như thế này

|- app/
    |- Console/
    |- Exceptions/
    |- Http/
    |- Providers/
    User.php
|- bootstrap/
|- config/
|- database/
|- public/
|- resources
|- routes/
|- storages/
|- tests/
|- vendor/
...

 

Khởi tạo thư mục chứa các modules HMVC

Chúng ta sẽ cố gắng không sử dụng cấu trúc thư mục mặc định để tránh tối đa sự xung đột hoặc khó khăn lúc nâng cấp lên các phiên bản cao hơn khi mà Laravel định kỳ release 6 tháng một lần. Điều này cũng giúp bạn đạt được sự thuận tiện tối đa khi tích hợp vào một dự án sẵn có.

Từ thư mục Laravel gốc, bạn tạo các thư mục tương ứng qua các dòng lệnh sau:

$ mkdir platform
$ mkdir platform/core
$ mkdir platform/plugins
$ mkdir platform/themes

Lúc này chúng ta sẽ có cấu trúc thư mục như sau

Tất cả source code của chúng ta sẽ được gói gọn bên trong folder platform.

  • core: chứa tất cả các modules không thể thiếu để có thể khởi chạy ứng dụng.
  • plugins: chứa các modules được thực thi dưới dạng trình cắm. Khi chúng ta xóa bỏ hoặc vô hiệu hóa chúng ra khỏi composer.json thì chúng sẽ không được khởi chạy, nhưng cũng không ảnh hưởng đến hệ thống hiện tại.
  • themes: chứa danh sách các themes của hệ thống. Một thời điểm chỉ nên có một theme được kích hoạt. Theme sẽ là phần sử dụng các modules core và plugins để hiển thị dữ liệu và thao tác với người dùng.

 

Chỉnh sửa file composer.json

Thêm đoạn khai báo sau vào trong file composer.json từ thư mục gốc của dự án. Đoạn khai báo này giúp composer biết được nơi tìm kiếm các custom module của bạn.

"repositories": [
    {
        "type": "path",
        "url": "./platform/core/*"
    },
    {
        "type": "path",
        "url": "./platform/plugins/*"
    },
    {
        "type": "path",
        "url": "./platform/themes/*"
    }
]

Cấu trúc một module trong dự án sẽ tương tự một Laravel pakage. Bạn có thể tham khảo thêm ở liên kết https://laravel.com/docs/7.x/packages.

Từ thư mục platform/plugins, bạn cần tạo ra cấu trúc thư mục như sau:

Cấu trúc package

Việc tạo ra một cấu trúc như vầy khá là tốn công sức, nên mình có viết một Laravel package giúp các bạn thực hiện việc này nhanh chóng ^_^

Bạn mở cửa sổ terminal từ thư mục gốc dự án và thực thi dòng lệnh:

$ composer require duyphan2502/hmvc-tools

Mình có hỗ trợ một số lệnh như:

  • Tạo một module mới
  • Tạo các tập tin thường hay sử dụng, ví dụ như controllerrequestmiddlewaremigrationfacadeview composer...

Bạn có thể xem tài liệu hướng dẫn kỹ hơn ở liên kết sau https://github.com/duyphan2502/hmvc-tools.

Để tạo ra module mới, từ cửa sổ terminal bạn thực thi câu lệnh:

$ php artisan module:create test-module

Hệ thống sẽ gợi ý bạn chỉ định loại module mà bạn muốn khởi tạo. Ở đây bạn sẽ chọn plugins.

Create Module

Khi mở tập tin composer.json trong thư mục platform/plugins/test-module, bạn sẽ thấy đoạn khai báo sau:

{
    "name": "plugins/test-module",
    "require": {},
    "require-dev": {},
    "autoload": {
        "psr-4": {
            "TestModule\\": "src/"
        }
    },
    "extra": [],
    "minimum-stability": "dev",
    "description": "Test HMVC module"
}

Bạn cũng đừng quên đăng ký provider TestModule\Providers\ModuleServiceProvider:class vào config/app.php trong thư mục gốc.

Nếu bạn không muốn đăng ký một cách thủ công như vậy, bạn có thể đăng ký auto register cho Laravel bằng cách cập nhật lại tập tin platform/plugins/test-module/composer.json.

{
    ...,
    "extra": {
        "laravel": {
            "providers": [
                "TestModule\\Providers\\ModuleServiceProvider"
            ]
        }
    },
}

Tiếp theo, chúng ta sẽ khai báo module này vào composer.json của dự án bằng dòng lệnh:

$ composer require plugins/test-module:*

Khởi tạo một controller mới cho module của chúng ta:

$ php artisan module:make:controller test-module Test --resource

Mở tập tin platform/plugins/test-module/routes/web.php và cập nhật thông tin route:

use Illuminate\Routing\Router;
use Illuminate\Support\Facades\Route;

$moduleRoute = 'test-module';

Route::group(['prefix' => $moduleRoute], function (Router $router) {
    $router->resource('', 'TestController');
});

Tiếp theo, tạo ra một tập tin platform/plugins/test-module/resources/views/index.blade.php và cập nhật nội dung cho nó như sau

Đến đây, mọi thứ cơ bản đã hoàn thành. Chúng ta bắt đầu kiểm tra xem module mới tạo đã hoạt động đúng chưa.

Từ terminal, thực thi câu lệnh:

$ php artisan serve
Laravel development server started: http://127.0.0.1:8000

Mở trình duyệt của bạn lên và truy cập theo đường dẫn http://127.0.0.1:8000/test-module.

Các bạn có thể tải xuống mã nguồn của bản demo này theo link sau https://github.com/duyphan2502/demo-hmvc.

 

Tổng kết

Bạn có thể thấy rằng, bản chất của module chính là một ứng dụng Laravel thu nhỏ, với đầy đủ các thành phần như ControllerModelProvider,... 

Phía trên chỉ là một ví dụ khá đơn giản, khi mà chỉ có một plugin duy nhất.

Bên dưới là cấu trúc mà CMS mà mình đang xây dựng thực tế (Webed CMS).

Đây cũng là CMS mà blog này đang sử dụng.

Tất cả các core modules sẽ được require bởi module core/basecomposer.json ở thư mục gốc chỉ cần require core/base là đủ. Bạn cũng cần đảm bảo việc các plugins được load sau core modules bằng cách require core/base ở các composer.json của plugins.

Do kiến trúc đã được định nghĩa ngay từ đầu, nên khi có lỗi ở đâu đó xảy ra, mình và đồng nghiệp rất dễ dàng debug và tìm ra root cause. Khi dự án lớn dần thì cũng dễ dàng kiểm soát và mở rộng hơn. Việc merge code giữa các team hoặc thành viên trong nhóm cũng trở nên đơn giản nhiều.

Ví dụ như mình cần một bạn trong nhóm phát triển tính năng tích hợp hệ thống với GHN thì bạn đó sẽ làm việc trên một module riêng biệt, không tác động tới những phần khác. Thậm chí việc loại bỏ một module cũng dễ dàng khi chỉ cần xóa nó ra khỏi composer.json.

 

Kết thúc chương trình truyền hình đến đây là hết :D hẹn gặp lại các bạn trong các series chọc phá laravel sắp tới. Nếu các bạn có ý kiến hay đóng góp về bài viết thì để lại bình luận bên dưới nhé.

Cám ơn các bạn đã theo dõi.

 

Comments

Bài viết nổi bật

Dạo gần đây đi đâu cũng nghe nói về microservices, người người nhà nhà rục rịch chuyển dịch hệ thống sang microservices. Trước khi đưa ra sự so sánh, mình sẽ khái quát một chút về Monolith Application và MicroServices một chút cho các bạn chưa biết nắm rõ hơn nhé.
PHP là ngôn ngữ được sử dụng rộng rãi nhất trên thế giới trong lập trình web. Nó cũng bị ghét nhất. Nhưng tại sao nhiều developer lại ghét nó đến vậy? Hôm nay chúng ta hãy cùng tìm hiểu lý do xem chúng có thuyết phục không nhé ^_^
Lúc trước mình hay sử dụng cách này trên laptop phụ của mình, giờ mua license luôn rồi. Hôm nay mình xin chia sẻ cho bạn nào cần nhé.
JWT Tokens là một cách thức lưu trữ thông tin xác thực hiệu quả, nhưng làm cách nào để chúng ta có thể giúp chúng an toàn hơn? Có 2 cách thường dùng để lưu trữ JWT Tokens là LocalStorage và Cookies. Bây giờ chúng ta sẽ bắt đầu "mổ xẻ" các ưu - nhược điểm của mỗi loại nhé.
Có khá nhiều bạn đã yêu cầu mình một bài viết về Repository Design Pattern. Vậy mục đích của nó là gì? Nó có thực sự cần thiết cho ứng dụng của bạn hay không? Những điểm mạnh, điểm yếu của nó là gì? Chúng ta cùng đi sâu tìm hiểu qua bài viết này nhé.

Mục lục

Related posts

Triển khai Saga Pattern trong microservices với NodeJS và Choreography-Based Saga
Mô hình Saga đưa ra một giải pháp có cấu trúc để giải quyết thách thức này. Nó cung cấp một phương pháp có hệ thống để quản lý transaction qua nhiều microservices. Điều này giải quyết những phức tạp của các transaction phân tán và hoàn toàn tương thích với các nguyên tắc của kiến trúc microservices, được đặc trưng bởi sự kết nối lỏng lẻo và khả năng triển khai độc lập của các service.
Một API cho phép giao tiếp hai chiều giữa các ứng dụng phần mềm thông qua các requests. Một Webhook là một API nhẹ, hỗ trợ chia sẻ dữ liệu một chiều được kích hoạt bởi các events.
Một trong những câu hỏi được đặt thường xuyên nhất về TypeScript là liệu chúng ta nên sử dụng interface hay type. Câu trả lời cho câu hỏi này, giống như nhiều câu hỏi lập trình khác, là nó phụ thuộc vào tình hình cụ thể. Trong một số trường hợp, một cái có lợi thế rõ rệt hơn cái kia, nhưng trong nhiều trường hợp, chúng có thể thay thế cho nhau.
Đây là các types cơ bản nhưng cũng phổ biến nhất trong Typescript. Một số types khác phức tạp hơn cũng được xây dựng dựa trên những types cơ bản này.
Trong thế giới lập trình, trách nhiệm lớn nhất của chúng ta không phải chỉ làm cho code chạy được, mà còn phải đảm bảo rằng các đoạn code mà chúng ta viết có thể dễ dàng kiểm tra và bảo trì trong một khoảng thời gian dài.
Phân trang - một thành phần không thể thiếu trong các ứng dụng có lượng dữ liệu lớn. Tuy nhiên, bạn hiểu được bao nhiêu về nó?
Javascript là một thành phần không thể thiếu đối với frontend developers. Tuy nhiên, ngay từ lúc ra đời, nó đã tồn tại khá nhiều vấn đề cần khắc phục. Đó là lý do tại sao từ 2015 (ES6) tới 2021 (ES12) ra đời nhằm giúp Javascript trở nên tốt hơn.
Dạo này mình làm việc với mấy bạn trên github, thấy hay xài mấy từ viết tắt mà mình không hiểu lắm. Thôi thì tổng hợp lại một list các từ viết tắt hay dùng trong github luôn cho ai cần :D
Dạo gần đây đi đâu cũng nghe nói về microservices, người người nhà nhà rục rịch chuyển dịch hệ thống sang microservices. Trước khi đưa ra sự so sánh, mình sẽ khái quát một chút về Monolith Application và MicroServices một chút cho các bạn chưa biết nắm rõ hơn nhé.
Cách bỏ qua câu lệnh --set-upstream quen thuộc cho các con lười
Mình sẽ giới thiệu 2 cách để xóa một property trong Javascript Object. Một cách sử dụng mutable - toán tử delete, một cách còn lại là immutable - tính năng Object Restructuring.

Tin mới nhất

Triển khai Saga Pattern trong microservices với NodeJS và Choreography-Based Saga
Mô hình Saga đưa ra một giải pháp có cấu trúc để giải quyết thách thức này. Nó cung cấp một phương pháp có hệ thống để quản lý transaction qua nhiều microservices. Điều này giải quyết những phức tạp của các transaction phân tán và hoàn toàn tương thích với các nguyên tắc của kiến trúc microservices, được đặc trưng bởi sự kết nối lỏng lẻo và khả năng triển khai độc lập của các service.
Một API cho phép giao tiếp hai chiều giữa các ứng dụng phần mềm thông qua các requests. Một Webhook là một API nhẹ, hỗ trợ chia sẻ dữ liệu một chiều được kích hoạt bởi các events.
Một trong những câu hỏi được đặt thường xuyên nhất về TypeScript là liệu chúng ta nên sử dụng interface hay type. Câu trả lời cho câu hỏi này, giống như nhiều câu hỏi lập trình khác, là nó phụ thuộc vào tình hình cụ thể. Trong một số trường hợp, một cái có lợi thế rõ rệt hơn cái kia, nhưng trong nhiều trường hợp, chúng có thể thay thế cho nhau.
Trong phần này, chúng ta sẽ tìm hiểu một số khái niệm cơ bản nhất về AWS là gì và một số lợi ích khi sử dụng AWS.
Trở thành một software developer hiệu suất cao không phải là điều dễ dàng. Điều này đòi hỏi bạn phải có kỹ năng và kiến thức về lập trình, cũng như cách tiếp cận và giải quyết các vấn đề phức tạp. Tuy nhiên, nếu bạn có chút kiên nhẫn và sự nỗ lực, bạn hoàn toàn có thể trở thành một developer tài năng và thành công.
Đây là các types cơ bản nhưng cũng phổ biến nhất trong Typescript. Một số types khác phức tạp hơn cũng được xây dựng dựa trên những types cơ bản này.
Trong thế giới lập trình, trách nhiệm lớn nhất của chúng ta không phải chỉ làm cho code chạy được, mà còn phải đảm bảo rằng các đoạn code mà chúng ta viết có thể dễ dàng kiểm tra và bảo trì trong một khoảng thời gian dài.
Thông tin được định nghĩa dưới dạng dữ liệu, kiến thức về thông tin, và trí tuệ về tri thức.
Phân trang - một thành phần không thể thiếu trong các ứng dụng có lượng dữ liệu lớn. Tuy nhiên, bạn hiểu được bao nhiêu về nó?
Javascript là một thành phần không thể thiếu đối với frontend developers. Tuy nhiên, ngay từ lúc ra đời, nó đã tồn tại khá nhiều vấn đề cần khắc phục. Đó là lý do tại sao từ 2015 (ES6) tới 2021 (ES12) ra đời nhằm giúp Javascript trở nên tốt hơn.
Dạo này mình làm việc với mấy bạn trên github, thấy hay xài mấy từ viết tắt mà mình không hiểu lắm. Thôi thì tổng hợp lại một list các từ viết tắt hay dùng trong github luôn cho ai cần :D
Triển khai Saga Pattern trong microservices với NodeJS và Choreography-Based Saga
Mô hình Saga đưa ra một giải pháp có cấu trúc để giải quyết thách thức này. Nó cung cấp một phương pháp có hệ thống để quản lý transaction qua nhiều microservices. Điều này giải quyết những phức tạp của các transaction phân tán và hoàn toàn tương thích với các nguyên tắc của kiến trúc microservices, được đặc trưng bởi sự kết nối lỏng lẻo và khả năng triển khai độc lập của các service.
Một API cho phép giao tiếp hai chiều giữa các ứng dụng phần mềm thông qua các requests. Một Webhook là một API nhẹ, hỗ trợ chia sẻ dữ liệu một chiều được kích hoạt bởi các events.
Một trong những câu hỏi được đặt thường xuyên nhất về TypeScript là liệu chúng ta nên sử dụng interface hay type. Câu trả lời cho câu hỏi này, giống như nhiều câu hỏi lập trình khác, là nó phụ thuộc vào tình hình cụ thể. Trong một số trường hợp, một cái có lợi thế rõ rệt hơn cái kia, nhưng trong nhiều trường hợp, chúng có thể thay thế cho nhau.
Đây là các types cơ bản nhưng cũng phổ biến nhất trong Typescript. Một số types khác phức tạp hơn cũng được xây dựng dựa trên những types cơ bản này.