Laravel المتقدم

بنية التطبيقات متعددة المستأجرين

20 دقيقة الدرس 26 من 40

بنية التطبيقات متعددة المستأجرين

التطبيقات متعددة المستأجرين (Multi-tenancy) هي نمط معماري حيث يخدم تطبيق واحد عدة عملاء (مستأجرين)، كل منهم له بيانات وإعدادات معزولة. هذا النهج شائع في تطبيقات SaaS، مما يسمح بمشاركة فعالة للموارد مع الحفاظ على فصل البيانات.

حالات الاستخدام الشائعة: منصات SaaS، أنظمة إدارة العقارات، برامج إدارة المدارس، منصات التجارة الإلكترونية مع متاجر متعددة، وأي تطبيق يخدم مؤسسات متعددة.

نهج قاعدة بيانات واحدة مقابل قواعد بيانات متعددة

هناك ثلاث استراتيجيات رئيسية للتطبيقات متعددة المستأجرين في Laravel:

1. قاعدة بيانات واحدة مع عمود المستأجر:

<?php // Migration مع tenant_id Schema::create('posts', function (Blueprint $table) { $table->id(); $table->foreignId('tenant_id')->constrained('tenants'); $table->string('title'); $table->text('content'); $table->timestamps(); // فهرس للأداء $table->index('tenant_id'); }); // Model مع global scope class Post extends Model { protected static function booted() { static::addGlobalScope('tenant', function (Builder $query) { if (auth()->check()) { $query->where('tenant_id', auth()->user()->tenant_id); } }); static::creating(function ($model) { if (auth()->check()) { $model->tenant_id = auth()->user()->tenant_id; } }); } }

2. قاعدة بيانات منفصلة لكل مستأجر:

<?php // config/database.php 'tenant' => [ 'driver' => 'mysql', 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '3306'), 'database' => null, // يتم تعيينها ديناميكيًا 'username' => env('DB_USERNAME', 'forge'), 'password' => env('DB_PASSWORD', ''), ]; // نموذج المستأجر class Tenant extends Model { public function configure() { config([ 'database.connections.tenant.database' => $this->database_name, ]); DB::purge('tenant'); DB::reconnect('tenant'); } public function use() { $this->configure(); app()->instance('tenant', $this); } } // الاستخدام $tenant = Tenant::find(1); $tenant->use(); // الآن جميع الاستعلامات تستخدم قاعدة بيانات المستأجر $users = User::on('tenant')->get();

3. النهج الهجين (قاعدة بيانات مركزية + قواعد بيانات المستأجرين):

<?php // قاعدة بيانات مركزية للمستأجرين والاشتراكات class Tenant extends Model { protected $connection = 'central'; public function switchToTenantDatabase() { DB::setDefaultConnection('tenant'); config(['database.connections.tenant.database' => $this->database_name]); DB::reconnect('tenant'); } } // النماذج الخاصة بالمستأجر تستخدم اتصال المستأجر class Product extends Model { protected $connection = 'tenant'; }
اختيار النهج: قاعدة البيانات الواحدة أبسط وأكثر فعالية من حيث التكلفة للعديد من المستأجرين مع بيانات معتدلة. قواعد البيانات المنفصلة توفر عزلًا وأداءً أفضل للمستأجرين الكبار، لكنها تزيد من تعقيد البنية التحتية.

استراتيجيات تحديد هوية المستأجر

1. التحديد المستند إلى النطاق الفرعي:

<?php // Middleware: app/Http/Middleware/IdentifyTenant.php class IdentifyTenant { public function handle(Request $request, Closure $next) { $subdomain = $this->getSubdomain($request); if (!$subdomain) { abort(404, 'Tenant not found'); } $tenant = Tenant::where('subdomain', $subdomain)->firstOrFail(); $tenant->use(); return $next($request); } protected function getSubdomain(Request $request): ?string { $host = $request->getHost(); $parts = explode('.', $host); // tenant.app.com -> tenant if (count($parts) > 2) { return $parts[0]; } return null; } } // routes/web.php Route::domain('{tenant}.example.com')->middleware('tenant')->group(function () { Route::get('/', [DashboardController::class, 'index']); Route::resource('products', ProductController::class); });

2. التحديد المستند إلى المسار:

<?php // Route مع بادئة المستأجر Route::prefix('{tenant}')->middleware('tenant')->group(function () { Route::get('/dashboard', [DashboardController::class, 'index']); }); // Middleware public function handle(Request $request, Closure $next) { $tenantSlug = $request->route('tenant'); $tenant = Tenant::where('slug', $tenantSlug)->firstOrFail(); app()->instance('current_tenant', $tenant); $tenant->use(); return $next($request); } // دالة مساعدة function tenant(): ?Tenant { return app('current_tenant'); }

استخدام حزمة Spatie Laravel Multitenancy

توفر حزمة Spatie حلاً قويًا للتطبيقات متعددة المستأجرين:

<?php // التثبيت // composer require spatie/laravel-multitenancy // config/multitenancy.php return [ 'tenant_model' => App\Models\Tenant::class, 'tenant_finder' => Spatie\Multitenancy\TenantFinder\DomainTenantFinder::class, 'switch_tenant_tasks' => [ Spatie\Multitenancy\Tasks\SwitchTenantDatabase::class, ], ]; // نموذج المستأجر use Spatie\Multitenancy\Models\Tenant as BaseTenant; class Tenant extends BaseTenant { public function makeCurrent(): self { $this->configure()->makeCurrent(); return $this; } public function configure(): self { config([ 'database.connections.tenant.database' => $this->database, ]); return $this; } } // الاستخدام في Controllers use Spatie\Multitenancy\Models\Concerns\UsesTenantConnection; class Product extends Model { use UsesTenantConnection; }
أمان حرج: تحقق دائمًا من وصول المستأجر قبل تنفيذ العمليات. لا تثق أبدًا في معاملات النطاق الفرعي/المسار دون التحقق. نفذ فحوصات تفويض مناسبة لمنع تسرب البيانات عبر المستأجرين.

النطاق المتقدم وإدارة الاستعلامات

<?php // Trait للنطاق التلقائي للمستأجر trait BelongsToTenant { protected static function bootBelongsToTenant() { static::addGlobalScope('tenant', function (Builder $query) { if ($tenant = tenant()) { $query->where($this->qualifyColumn('tenant_id'), $tenant->id); } }); static::creating(function ($model) { if ($tenant = tenant()) { $model->tenant_id = $tenant->id; } }); } public function tenant() { return $this->belongsTo(Tenant::class); } } // استخدام Trait class Invoice extends Model { use BelongsToTenant; } // الاستعلام بدون نطاق المستأجر عند الحاجة Invoice::withoutGlobalScope('tenant')->get(); // الاستعلام عبر جميع المستأجرين (للمسؤول فقط) if (auth()->user()->isSuperAdmin()) { $allInvoices = Invoice::withoutGlobalScope('tenant') ->with('tenant') ->get(); }
تمرين 1: أنشئ نظام تجارة إلكترونية متعدد المستأجرين حيث كل متجر له نطاق فرعي خاص. نفذ middleware لتحديد هوية المستأجر، واستعلامات المنتجات المحددة النطاق، وتأكد من عزل الطلبات لكل مستأجر.
تمرين 2: ابنِ آلية تبديل المستأجر للوحة تحكم المسؤول الرئيسي. أنشئ قائمة منسدلة تسمح للمسؤولين بانتحال شخصية أي مستأجر وعرض بياناتهم. تأكد من فحوصات الأمان المناسبة وتسجيل التدقيق.
تمرين 3: نفذ تدفق تسجيل المستأجر الذي ينشئ قاعدة بيانات جديدة لكل مستأجر، وينفذ migrations، ويملأ البيانات الأولية. قم بتضمين فحص توفر النطاق الفرعي وتعليمات تكوين DNS.
اختبار التطبيقات متعددة المستأجرين: استخدم مساعدي اختبار Laravel لاختبار سيناريوهات المستأجرين المتعددين. أنشئ دوال مساعدة لتبديل المستأجرين في الاختبارات، وتأكد من عزل قاعدة بيانات الاختبار بشكل صحيح بين عمليات الاختبار.