# Code Example - Multi-Tenant Implementation

## 1. Database Configuration

### config/database.php

```php
<?php

return [
    'default' => env('DB_CONNECTION', 'mysql'),
    
    'connections' => [
        // Connection cho config DB
        'lms_new' => [
            'driver' => 'mysql',
            'host' => env('DB_HOST', '127.0.0.1'),
            'port' => env('DB_PORT', '3306'),
            'database' => env('DB_DATABASE_CONFIG', 'lms_new'),
            'username' => env('DB_USERNAME', 'root'),
            'password' => env('DB_PASSWORD', ''),
            'charset' => 'utf8mb4',
            'collation' => 'utf8mb4_unicode_ci',
        ],
        
        // Connection động cho tenant (sẽ được set runtime)
        'tenant' => [
            'driver' => 'mysql',
            'host' => env('DB_HOST', '127.0.0.1'),
            'port' => env('DB_PORT', '3306'),
            'database' => '', // Sẽ được set động
            'username' => env('DB_USERNAME', 'root'),
            'password' => env('DB_PASSWORD', ''),
            'charset' => 'utf8mb4',
            'collation' => 'utf8mb4_unicode_ci',
        ],
    ],
];
```

## 2. Tenant Model

### app/Models/Tenant.php

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;

class Tenant extends Model
{
    protected $connection = 'lms_new';
    protected $table = 'tenants';
    
    protected $fillable = [
        'name',
        'database_name',
        'status',
    ];
    
    /**
     * Lấy danh sách tenant mà user có quyền
     */
    public static function getTenantsForUser($userId)
    {
        return DB::connection('lms_new')
            ->table('user_tenant_permissions')
            ->join('tenants', 'user_tenant_permissions.tenant_id', '=', 'tenants.id')
            ->where('user_tenant_permissions.user_id', $userId)
            ->where('tenants.status', 'active')
            ->select('tenants.*', 'user_tenant_permissions.role_in_tenant')
            ->get();
    }
    
    /**
     * Tìm tenant theo name
     */
    public static function findByName($name)
    {
        return self::where('name', $name)
            ->where('status', 'active')
            ->first();
    }
}
```

## 3. Tenant Connection Manager

### app/Services/TenantConnectionService.php

```php
<?php

namespace App\Services;

use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Config;
use App\Models\Tenant;
use Illuminate\Support\Facades\Log;

class TenantConnectionService
{
    /**
     * Switch database connection đến tenant
     */
    public static function switch($tenantName)
    {
        $tenant = Tenant::findByName($tenantName);
        
        if (!$tenant) {
            throw new \Exception("Tenant not found: {$tenantName}");
        }
        
        // Update config
        Config::set('database.connections.tenant.database', $tenant->database_name);
        
        // Purge và reconnect
        DB::purge('tenant');
        DB::reconnect('tenant');
        
        // Set default connection
        DB::setDefaultConnection('tenant');
        
        Log::info("Switched to tenant: {$tenantName} (DB: {$tenant->database_name})");
        
        return $tenant;
    }
    
    /**
     * Switch về config DB
     */
    public static function switchToConfig()
    {
        DB::setDefaultConnection('lms_new');
    }
    
    /**
     * Lấy tenant hiện tại từ session
     */
    public static function getCurrentTenant()
    {
        return session('current_tenant');
    }
    
    /**
     * Set tenant hiện tại vào session
     */
    public static function setCurrentTenant($tenantName)
    {
        session(['current_tenant' => $tenantName]);
    }
}
```

## 4. Middleware cho Tenant Detection

### app/Http/Middleware/TenantMiddleware.php

```php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use App\Services\TenantConnectionService;
use App\Models\Tenant;

class TenantMiddleware
{
    /**
     * Handle an incoming request.
     */
    public function handle(Request $request, Closure $next)
    {
        // Nếu là admin route, tenant sẽ được set từ session sau khi login
        if ($request->is('admin/*') || $request->is('api/admin/*')) {
            $tenantName = session('current_tenant');
            
            if ($tenantName) {
                TenantConnectionService::switch($tenantName);
            } else {
                // Chưa chọn tenant, có thể redirect về tenant selection
                // Hoặc để route handler xử lý
            }
        } else {
            // Student route - detect từ subdomain hoặc path
            $tenantName = $this->detectTenantFromRequest($request);
            
            if ($tenantName) {
                TenantConnectionService::switch($tenantName);
            } else {
                return response()->json(['error' => 'Tenant not found'], 404);
            }
        }
        
        return $next($request);
    }
    
    /**
     * Detect tenant từ request
     */
    private function detectTenantFromRequest(Request $request)
    {
        // Method 1: Từ subdomain
        $host = $request->getHost();
        $parts = explode('.', $host);
        
        if (count($parts) >= 3) {
            // subdomain.domain.com
            $subdomain = $parts[0];
            $tenant = Tenant::findByName($subdomain);
            if ($tenant) {
                return $subdomain;
            }
        }
        
        // Method 2: Từ path parameter
        $tenantFromPath = $request->segment(1);
        $tenant = Tenant::findByName($tenantFromPath);
        if ($tenant) {
            return $tenantFromPath;
        }
        
        // Method 3: Từ header (nếu có)
        $tenantFromHeader = $request->header('X-Tenant');
        if ($tenantFromHeader) {
            $tenant = Tenant::findByName($tenantFromHeader);
            if ($tenant) {
                return $tenantFromHeader;
            }
        }
        
        return null;
    }
}
```

## 5. Base Model cho Tenant Models

### app/Models/TenantModel.php

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class TenantModel extends Model
{
    /**
     * Tất cả model của tenant sẽ dùng connection 'tenant'
     */
    protected $connection = 'tenant';
    
    /**
     * Không cần tenant_id vì mỗi tenant có DB riêng
     */
}
```

## 6. Student Model (Example)

### app/Models/Student.php

```php
<?php

namespace App\Models;

use App\Models\TenantModel;

class Student extends TenantModel
{
    protected $table = 'students';
    
    protected $fillable = [
        'name',
        'email',
        'phone',
    ];
    
    /**
     * Relationship với exam_histories
     */
    public function examHistories()
    {
        return $this->hasMany(ExamHistory::class);
    }
    
    /**
     * Relationship với enrollments
     */
    public function enrollments()
    {
        return $this->hasMany(Enrollment::class);
    }
}
```

## 7. ExamHistory Model (Example)

### app/Models/ExamHistory.php

```php
<?php

namespace App\Models;

use App\Models\TenantModel;

class ExamHistory extends TenantModel
{
    protected $table = 'exam_histories';
    
    protected $fillable = [
        'student_id',
        'exam_id',
        'score',
        'completed_at',
    ];
    
    protected $casts = [
        'completed_at' => 'datetime',
        'score' => 'float',
    ];
    
    /**
     * Relationship với student
     */
    public function student()
    {
        return $this->belongsTo(Student::class);
    }
}
```

## 8. Admin Authentication Controller

### app/Http/Controllers/Admin/AuthController.php

```php
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use App\Models\Tenant;
use App\Services\TenantConnectionService;

class AuthController extends Controller
{
    /**
     * Login
     */
    public function login(Request $request)
    {
        $credentials = $request->validate([
            'email' => 'required|email',
            'password' => 'required',
        ]);
        
        // Authenticate với config DB
        TenantConnectionService::switchToConfig();
        
        // Custom authentication logic
        $user = DB::connection('lms_new')
            ->table('users')
            ->where('email', $credentials['email'])
            ->first();
        
        if (!$user || !\Hash::check($credentials['password'], $user->password)) {
            return response()->json(['error' => 'Invalid credentials'], 401);
        }
        
        // Lấy danh sách tenant user có quyền
        $tenants = Tenant::getTenantsForUser($user->id);
        
        if ($tenants->isEmpty()) {
            return response()->json(['error' => 'No tenant access'], 403);
        }
        
        // Lưu user vào session
        session(['admin_user' => $user]);
        session(['available_tenants' => $tenants]);
        
        // Nếu chỉ có 1 tenant, auto select
        if ($tenants->count() === 1) {
            $tenant = $tenants->first();
            TenantConnectionService::setCurrentTenant($tenant->name);
            TenantConnectionService::switch($tenant->name);
            
            return response()->json([
                'message' => 'Login successful',
                'tenant_selected' => true,
                'tenant' => $tenant,
            ]);
        }
        
        // Nếu nhiều tenant, trả về danh sách để chọn
        return response()->json([
            'message' => 'Login successful',
            'tenant_selected' => false,
            'tenants' => $tenants,
        ]);
    }
    
    /**
     * Select tenant sau khi login
     */
    public function selectTenant(Request $request)
    {
        $tenantName = $request->input('tenant_name');
        
        $tenants = session('available_tenants', []);
        $tenant = collect($tenants)->firstWhere('name', $tenantName);
        
        if (!$tenant) {
            return response()->json(['error' => 'Tenant not accessible'], 403);
        }
        
        // Switch đến tenant
        TenantConnectionService::setCurrentTenant($tenantName);
        TenantConnectionService::switch($tenantName);
        
        return response()->json([
            'message' => 'Tenant selected',
            'tenant' => $tenant,
        ]);
    }
    
    /**
     * Logout
     */
    public function logout()
    {
        session()->forget('admin_user');
        session()->forget('available_tenants');
        session()->forget('current_tenant');
        
        TenantConnectionService::switchToConfig();
        
        return response()->json(['message' => 'Logged out']);
    }
}
```

## 9. Student Controller (Example)

### app/Http/Controllers/StudentController.php

```php
<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Models\Student;
use App\Models\ExamHistory;
use Illuminate\Http\Request;

class StudentController extends Controller
{
    /**
     * Lấy thông tin student
     * Middleware đã switch DB connection rồi
     */
    public function getProfile($studentId)
    {
        $student = Student::with(['examHistories', 'enrollments'])
            ->findOrFail($studentId);
        
        return response()->json($student);
    }
    
    /**
     * Lấy lịch sử thi
     */
    public function getExamHistories($studentId)
    {
        $histories = ExamHistory::where('student_id', $studentId)
            ->orderBy('completed_at', 'desc')
            ->get();
        
        return response()->json($histories);
    }
}
```

## 10. Routes

### routes/web.php hoặc routes/api.php

```php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Admin\AuthController;
use App\Http\Controllers\StudentController;

// Admin routes
Route::prefix('admin')->group(function () {
    Route::post('/login', [AuthController::class, 'login']);
    Route::post('/select-tenant', [AuthController::class, 'selectTenant'])
        ->middleware('auth:admin');
    Route::post('/logout', [AuthController::class, 'logout'])
        ->middleware('auth:admin');
});

// Student routes (tenant sẽ được detect từ middleware)
Route::middleware(['tenant'])->group(function () {
    Route::get('/students/{id}', [StudentController::class, 'getProfile']);
    Route::get('/students/{id}/exams', [StudentController::class, 'getExamHistories']);
});
```

## 11. Register Middleware

### app/Http/Kernel.php

```php
protected $middlewareGroups = [
    'web' => [
        // ... existing middleware
        \App\Http\Middleware\TenantMiddleware::class,
    ],
    
    'api' => [
        // ... existing middleware
        \App\Http\Middleware\TenantMiddleware::class,
    ],
];
```

## 12. Migration Example

### database/migrations/xxxx_create_tenants_table.php

```php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;

class CreateTenantsTable extends Migration
{
    /**
     * Run the migrations.
     */
    public function up()
    {
        // Chạy trên config DB
        DB::connection('lms_new')->statement('
            CREATE TABLE IF NOT EXISTS tenants (
                id INT AUTO_INCREMENT PRIMARY KEY,
                name VARCHAR(255) UNIQUE NOT NULL,
                database_name VARCHAR(255) NOT NULL,
                status ENUM("active", "inactive") DEFAULT "active",
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
        ');
    }
    
    public function down()
    {
        DB::connection('lms_new')->statement('DROP TABLE IF EXISTS tenants');
    }
}
```

## 13. Migration Script cho Tất Cả Tenant

### scripts/migrate_all_tenants.php

```php
<?php

require __DIR__ . '/../vendor/autoload.php';

$app = require_once __DIR__ . '/../bootstrap/app.php';
$app->make('Illuminate\Contracts\Console\Kernel')->bootstrap();

use App\Models\Tenant;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\DB;

// Lấy tất cả tenant
$tenants = Tenant::all();

foreach ($tenants as $tenant) {
    echo "Migrating tenant: {$tenant->name} (DB: {$tenant->database_name})\n";
    
    // Set database connection
    config(['database.connections.tenant.database' => $tenant->database_name]);
    DB::purge('tenant');
    DB::reconnect('tenant');
    
    // Chạy migration
    Artisan::call('migrate', [
        '--database' => 'tenant',
        '--path' => 'database/migrations/tenant',
    ]);
    
    echo "✓ Completed: {$tenant->name}\n\n";
}

echo "All tenants migrated!\n";
```

## 14. Testing Example

### tests/Feature/TenantTest.php

```php
<?php

namespace Tests\Feature;

use Tests\TestCase;
use App\Models\Tenant;
use App\Services\TenantConnectionService;
use Illuminate\Foundation\Testing\RefreshDatabase;

class TenantTest extends TestCase
{
    public function test_switch_tenant_connection()
    {
        $tenant = Tenant::findByName('tenant_icc');
        
        TenantConnectionService::switch($tenant->name);
        
        // Test query trên tenant DB
        $students = \DB::connection('tenant')->table('students')->count();
        
        $this->assertIsInt($students);
    }
    
    public function test_admin_login_and_select_tenant()
    {
        $response = $this->postJson('/admin/login', [
            'email' => 'admin@example.com',
            'password' => 'password',
        ]);
        
        $response->assertStatus(200);
        $response->assertJsonStructure(['tenants']);
        
        // Select tenant
        $response = $this->postJson('/admin/select-tenant', [
            'tenant_name' => 'tenant_icc',
        ]);
        
        $response->assertStatus(200);
    }
}
```

## 15. Best Practices

### 15.1. Connection Pooling

```php
// config/database.php
'tenant' => [
    // ... other config
    'pool' => [
        'min' => 5,
        'max' => 20,
    ],
],
```

### 15.2. Caching Tenant Info

```php
use Illuminate\Support\Facades\Cache;

public static function switch($tenantName)
{
    $tenant = Cache::remember("tenant:{$tenantName}", 3600, function () use ($tenantName) {
        return Tenant::findByName($tenantName);
    });
    
    // ... rest of switch logic
}
```

### 15.3. Error Handling

```php
try {
    TenantConnectionService::switch($tenantName);
} catch (\Exception $e) {
    Log::error("Failed to switch tenant: {$tenantName}", [
        'error' => $e->getMessage(),
    ]);
    
    return response()->json(['error' => 'Tenant connection failed'], 500);
}
```

