# CẨM NANG ĐÓNG GÓI MODULE LARAVEL THÀNH COMPOSER PACKAGE TÁI SỬ DỤNG
> **Dành cho Lập trình viên & AI Agent**: Hướng dẫn toàn diện từ A-Z về phương pháp trích xuất, đóng gói bất kỳ chức năng nào trong Laravel thành một Composer Package độc lập, có thể import vào mọi hệ thống Laravel mới chỉ với 1 lệnh `composer require`.

---

## 🎯 1. NGUYÊN LÝ CỐT LÕI (CORE CONCEPTS & DIRECTIVE)

Khi muốn "bê" một tính năng (ví dụ: Quản lý Role, Quản lý Học sinh, Báo cáo, Đánh giá...) sang một project Laravel mới mà không muốn copy-paste code thủ công, giải pháp chuẩn nhất là **đóng gói thành Laravel Package**.

Một Laravel Package chuẩn cần đảm bảo **3 nguyên tắc bất di bất dịch**:
1. **Decoupling (Không gắn cứng phụ thuộc)**: Không phụ thuộc cứng vào Layout UI (`layouts.app`), Model `User` cụ thể, hay Helper riêng của project cũ. Tất cả phải cấu hình động qua file `config/`.
2. **Auto-Discovery (Tự động nhận diện)**: Khi cài đặt qua `composer require`, Laravel phải tự động nạp ServiceProvider mà không cần cấu hình thủ công vào `config/app.php` hay `bootstrap/providers.php`.
3. **Self-Contained (Tự chứa đủ)**: Package phải tự mang theo Migration, Route, Controller, View, Model và Service.

---

## 📁 2. CẤU TRÚC THƯ MỤC CHUẨN CỦA MỘT PACKAGE

Mọi package sẽ được khởi tạo trong thư mục `packages/<vendor>/<package-name>/`:

```text
packages/<vendor>/<package-name>/
├── composer.json                         # Khai báo package, PSR-4, Laravel Auto-discovery
├── README.md                             # Hướng dẫn cài đặt và sử dụng
├── config/
│   └── <package>.php                     # Cấu hình Layout, Route, Model, Database Connection
├── database/
│   ├── migrations/                       # Migrations tự động tạo bảng
│   └── seeders/                          # Dữ liệu mẫu (nếu có)
├── resources/
│   ├── views/                            # Blade templates (index, add, edit...)
│   └── assets/                           # CSS/JS riêng (nếu có)
├── routes/
│   └── web.php                           # Route của package (hoặc api.php)
└── src/
    ├── <Package>ServiceProvider.php      # ServiceProvider nạp route, view, migration, config
    ├── Http/
    │   ├── Controllers/                  # Controller xử lý request
    │   └── Requests/                     # Form Request Validation
    ├── Models/                           # Eloquent Models
    │   └── Traits/                       # Trait kết nối DB động
    ├── Services/                         # Business Logic và Connection Resolver
    └── Traits/                           # Trait cung cấp cho Model của Project đích (VD: HasRoles)
```

---

## 🛠️ 3. QUY TRÌNH 7 BƯỚC ĐÓNG GÓI TÍNH NĂNG (7-STEP WORKFLOW)

### BƯỚC 1: Khảo sát & Phân tích tính năng cần đóng gói
Trước khi viết code, Agent/Dev cần quét và thống kê toàn bộ các tài nguyên liên quan đến tính năng:
1. **Controller & Actions**: Các phương thức cần thiết (index, create, store, edit, update, destroy...).
2. **Models & Database**: Bảng dữ liệu nào tham gia (`roles`, `permissions`, quan hệ pivot...).
3. **Views / Blade**: Layout đang kế thừa (`@extends('...')`), các partials, modals, icon, javascript.
4. **Routes**: Các đường dẫn, middleware đang áp dụng (`web`, `auth`, `permission`...).
5. **Helpers / Services ngoài**: Tìm các hàm helper hoặc class bên ngoài đang bị phụ thuộc để chuyển thành Service hoặc Config.

---

### BƯỚC 2: Tạo `composer.json` cho Package
Tạo file `packages/<vendor>/<package-name>/composer.json`:

```json
{
    "name": "<vendor>/<package-name>",
    "description": "Mô tả chức năng package",
    "type": "library",
    "license": "MIT",
    "authors": [
        {
            "name": "Dev Team",
            "email": "dev@company.com"
        }
    ],
    "require": {
        "php": "^8.1|^8.2|^8.3",
        "illuminate/support": "^10.0|^11.0",
        "illuminate/database": "^10.0|^11.0",
        "illuminate/routing": "^10.0|^11.0",
        "illuminate/view": "^10.0|^11.0"
    },
    "autoload": {
        "psr-4": {
            "Vendor\\PackageName\\": "src/"
        }
    },
    "extra": {
        "laravel": {
            "providers": [
                "Vendor\\PackageName\\PackageNameServiceProvider"
            ]
        }
    },
    "minimum-stability": "dev",
    "prefer-stable": true
}
```

> **Điểm mấu chốt**: Mục `extra.laravel.providers` là nơi kích hoạt tính năng **Auto-Discovery** của Laravel!

---

### BƯỚC 3: Tạo File Cấu hình `config/<package>.php`
File cấu hình giúp tách rời các phụ thuộc:

```php
<?php

return [
    // 1. Tùy biến Master Layout của project đích (@extends)
    'layout' => 'layouts.app',

    // 2. Cấu hình Routes
    'routes' => [
        'prefix' => 'admin/my-feature',
        'as' => 'my_feature.',
        'middleware' => ['web', 'auth'],
    ],

    // 3. Mapping Model người dùng của hệ thống đích
    'models' => [
        'user' => \App\Models\User::class,
    ],

    // 4. Custom Connection Resolver (Hỗ trợ Single-DB & Multi-tenant)
    'custom_connection_resolver' => null,
];
```

---

### BƯỚC 4: Tạo `ServiceProvider` (`src/<Package>ServiceProvider.php`)
ServiceProvider là trung tâm điều khiển của Package:

```php
<?php

namespace Vendor\PackageName;

use Illuminate\Support\ServiceProvider;
use Vendor\PackageName\Services\PackageService;

class PackageNameServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        // 1. Hợp nhất cấu hình mặc định
        $this->mergeConfigFrom(__DIR__ . '/../config/package.php', 'package_key');

        // 2. Đăng ký Service Singleton
        $this->app->singleton(PackageService::class, function ($app) {
            return new PackageService();
        });
    }

    public function boot(): void
    {
        // 1. Nạp Routes
        $this->loadRoutesFrom(__DIR__ . '/../routes/web.php');

        // 2. Nạp Views với namespace riêng (gọi qua view('package-namespace::index'))
        $this->loadViewsFrom(__DIR__ . '/../resources/views', 'package-namespace');

        // 3. Nạp Migrations tự động
        $this->loadMigrationsFrom(__DIR__ . '/../database/migrations');

        // 4. Cung cấp lệnh publish khi người dùng muốn tùy biến
        if ($this->app->runningInConsole()) {
            $this->publishes([
                __DIR__ . '/../config/package.php' => config_path('package.php'),
            ], 'package-config');

            $this->publishes([
                __DIR__ . '/../resources/views' => resource_path('views/vendor/package-namespace'),
            ], 'package-views');

            $this->publishes([
                __DIR__ . '/../database/migrations' => database_path('migrations'),
            ], 'package-migrations');
        }
    }
}
```

---

### BƯỚC 5: Xây dựng Database Connection Resolver & Model Base
Để hỗ trợ cả hệ thống chạy 1 Database tiêu chuẩn và hệ thống Multi-tenant:

1. **`src/Services/ConnectionResolver.php`**:
```php
<?php

namespace Vendor\PackageName\Services;

use Illuminate\Support\Facades\DB;

class ConnectionResolver
{
    public static function getConnectionName(): ?string
    {
        $customResolver = config('package_key.custom_connection_resolver');
        if (is_callable($customResolver)) {
            return call_user_func($customResolver);
        }

        // Hỗ trợ dynamic tenant nếu có HelperTenant
        if (class_exists('\App\Helpers\HelperTenant')) {
            $helper = '\App\Helpers\HelperTenant';
            if (method_exists($helper, 'hasSelectedTenant') && $helper::hasSelectedTenant()) {
                return $helper::getCurrentTenantConnection();
            }
        }

        return config('database.default');
    }
}
```

2. **`src/Models/Traits/UsesPackageConnection.php`**:
```php
<?php

namespace Vendor\PackageName\Models\Traits;

use Vendor\PackageName\Services\ConnectionResolver;

trait UsesPackageConnection
{
    public function getConnectionName()
    {
        return $this->connection ?? ConnectionResolver::getConnectionName() ?? config('database.default');
    }

    protected function newRelatedInstance($class)
    {
        return tap(new $class, function ($instance) {
            $instance->setConnection($this->getConnectionName());
        });
    }
}
```

---

### BƯỚC 6: Chuẩn hóa Views, Routes và Controller

1. **Trong file Blade View (`resources/views/index.blade.php`)**:
   - Sử dụng `@extends(config('package_key.layout', 'layouts.app'))` thay vì fix cứng `@extends('layouts.app')`.
   - Sử dụng `route(config('package_key.routes.as') . 'add')` để gọi route linh hoạt.
   - Bọc session message an toàn: `@json(session('success'))` hoặc `{{ Js::from(session('success')) }}`.

2. **Trong file Routes (`routes/web.php`)**:
```php
<?php

use Illuminate\Support\Facades\Route;
use Vendor\PackageName\Http\Controllers\FeatureController;

$prefix = config('package_key.routes.prefix', 'my-feature');
$as = config('package_key.routes.as', 'my_feature.');
$middleware = config('package_key.routes.middleware', ['web', 'auth']);

Route::group(['prefix' => $prefix, 'as' => $as, 'middleware' => $middleware], function () {
    Route::get('/index', [FeatureController::class, 'index'])->name('index');
    Route::get('/add', [FeatureController::class, 'create'])->name('add');
    Route::post('/store', [FeatureController::class, 'store'])->name('store');
    Route::get('/edit/{id}', [FeatureController::class, 'edit'])->name('edit');
    Route::post('/update/{id}', [FeatureController::class, 'update'])->name('update');
    Route::post('/delete', [FeatureController::class, 'destroy'])->name('delete');
});
```

3. **Trong Controller (`src/Http/Controllers/FeatureController.php`)**:
   - Trả về view theo namespace của package: `return view('package-namespace::index', compact(...));`
   - Điều hướng về route có cấu hình: `return redirect()->route(config('package_key.routes.as') . 'index');`

4. **Trong Migrations (`database/migrations/xxxx_xx_xx_create_table.php`)**:
   - Luôn bọc điều kiện kiểm tra tồn tại:
     ```php
     if (!Schema::hasTable('table_name')) {
         Schema::create('table_name', function (Blueprint $table) {
             $table->id();
             $table->string('name');
             $table->timestamps();
             $table->softDeletes();
         });
     }
     ```

---

### BƯỚC 7: Kiểm thử & Phân phối Package

#### 1. Kiểm thử cục bộ (Local Testing):
Trong file `composer.json` của project cha hiện tại, khai báo namespace trong `autoload.psr-4`:
```json
"autoload": {
    "psr-4": {
        "Vendor\\PackageName\\": "packages/<vendor>/<package-name>/src/"
    }
}
```
Và chạy:
```bash
composer dump-autoload
php artisan route:list
php artisan vendor:publish --tag=package-config
```

#### 2. Phân phối sang hệ thống Laravel mới qua Git riêng:
1. Đẩy thư mục `packages/<vendor>/<package-name>` lên repository Git riêng (ví dụ `git@gitlab.com:company/my-package.git`).
2. Tại bất kỳ dự án Laravel mới nào, mở `composer.json` và thêm:
```json
"repositories": [
    {
        "type": "vcs",
        "url": "git@gitlab.com:company/my-package.git"
    }
]
```
3. Chạy 2 lệnh:
```bash
composer require vendor/package-name
php artisan migrate
```
🎉 **Hoàn thành! Toàn bộ tính năng đã sẵn sàng hoạt động tại dự án mới.**

---

## ✅ 4. AGENT VERIFICATION CHECKLIST

Khi một AI Agent thực hiện đóng gói package, Agent phải tự kiểm tra danh sách tiêu chí sau trước khi hoàn thành:

- [ ] File `composer.json` của package có đầy đủ `name`, `psr-4 autoload`, và `extra.laravel.providers`.
- [ ] File `config/<package>.php` có cấu hình `layout`, `routes`, `models`.
- [ ] ServiceProvider đã đăng ký đủ: `mergeConfigFrom`, `loadRoutesFrom`, `loadViewsFrom`, `loadMigrationsFrom`, và các khối `publishes`.
- [ ] Tất cả file Blade dùng `@extends(config('...'))` và gọi route qua prefix config.
- [ ] Không còn code nào tham chiếu cứng (hard-coded) đến Model hay Helper nội bộ của project cũ mà không qua Config/Interface.
- [ ] Lệnh kiểm tra cú pháp PHP (`find packages/... -name "*.php" -exec php -l {} \;`) đạt 0 lỗi.
- [ ] Lệnh `php artisan route:list` hiển thị đúng danh sách routes của package.
- [ ] Có sẵn file `README.md` hướng dẫn sử dụng và cài đặt.
