Laravel المتقدم

التفويض: الغوص العميق في السياسات والبوابات

18 دقيقة الدرس 8 من 40

التفويض: الغوص العميق في السياسات والبوابات

أتقن نظام التفويض في Laravel بأنماط السياسات المتقدمة وتعريفات البوابات وعمليات الاستدعاء قبل/بعد وتفويض الموارد وتفويض المستخدمين الضيوف.

فهم البوابات مقابل السياسات

البوابات والسياسات هما آليتا التفويض في Laravel:

// البوابات: إغلاقات بسيطة لفحوصات التفويض السريعة Gate::define('update-post', function (User $user, Post $post) { return $user->id === $post->user_id; }); // السياسات: فئات تنظم منطق التفويض حول النماذج class PostPolicy { public function update(User $user, Post $post) { return $user->id === $post->user_id; } }
نصيحة احترافية: استخدم البوابات للفحوصات البسيطة المستقلة عن النموذج. استخدم السياسات للتفويض الخاص بالنموذج الذي يجمع المنطق ذي الصلة معاً.

أنماط السياسات المتقدمة

إنشاء سياسات شاملة بأنماط متقدمة:

// إنشاء السياسة php artisan make:policy PostPolicy --model=Post // app/Policies/PostPolicy.php namespace App\Policies; use App\Models\User; use App\Models\Post; class PostPolicy { // يتم تنفيذه قبل جميع الطرق الأخرى public function before(User $user, string $ability) { // المسؤولون الفائقون يمكنهم فعل أي شيء if ($user->isSuperAdmin()) { return true; } // إرجاع null للاستمرار إلى الطرق الأخرى return null; } // عرض أي منشورات (الفهرس) public function viewAny(User $user) { return $user->hasPermission('view-posts'); } // عرض منشور واحد public function view(?User $user, Post $post) { // المستخدمون الضيوف يمكنهم عرض المنشورات المنشورة if ($post->is_published) { return true; } // المستخدمون المصادق عليهم يمكنهم عرض مسوداتهم الخاصة return $user && $user->id === $post->user_id; } // إنشاء منشور جديد public function create(User $user) { return $user->hasPermission('create-posts') && !$user->isBanned(); } // تحديث منشور موجود public function update(User $user, Post $post) { // المالك يمكنه التحديث if ($user->id === $post->user_id) { return true; } // المحررون يمكنهم تحديث أي منشور if ($user->hasRole('editor')) { return true; } return false; } // حذف منشور public function delete(User $user, Post $post) { // المالك فقط يمكنه الحذف if ($user->id === $post->user_id) { return true; } return false; } // الحذف القسري (دائم) public function forceDelete(User $user, Post $post) { return $user->hasRole('admin'); } // استعادة منشور محذوف بشكل ناعم public function restore(User $user, Post $post) { return $user->hasRole(['admin', 'editor']); } // القدرة المخصصة: نشر المنشور public function publish(User $user, Post $post) { return $user->hasRole(['admin', 'editor']) || ($user->id === $post->user_id && $user->isVerified()); } }

تسجيل السياسات

سجل السياسات في AuthServiceProvider:

// app/Providers/AuthServiceProvider.php namespace App\Providers; use App\Models\Post; use App\Policies\PostPolicy; use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; class AuthServiceProvider extends ServiceProvider { protected $policies = [ Post::class => PostPolicy::class, ]; public function boot() { $this->registerPolicies(); // الاكتشاف التلقائي للسياسات Gate::guessPolicyNamesUsing(function ($modelClass) { return 'App\\Policies\\' . class_basename($modelClass) . 'Policy'; }); } }

استخدام التفويض في وحدات التحكم

تطبيق فحوصات التفويض بطرق مختلفة:

namespace App\Http\Controllers; class PostController extends Controller { // الطريقة 1: مساعد authorize() public function update(Request $request, Post $post) { $this->authorize('update', $post); // إذا وصلنا إلى هنا، المستخدم مصرح له $post->update($request->validated()); return redirect()->route('posts.show', $post); } // الطريقة 2: واجهة Gate public function destroy(Post $post) { if (Gate::denies('delete', $post)) { abort(403, 'لا يمكنك حذف هذا المنشور.'); } $post->delete(); return redirect()->route('posts.index'); } // الطريقة 3: طريقة can() للمستخدم public function publish(Post $post) { if (!auth()->user()->can('publish', $post)) { return back()->with('error', 'لا يمكنك نشر هذا المنشور.'); } $post->update(['is_published' => true]); return back()->with('success', 'تم نشر المنشور!'); } // الطريقة 4: الوسيطة public function __construct() { $this->middleware('can:create,App\Models\Post')->only(['create', 'store']); $this->middleware('can:update,post')->only(['edit', 'update']); $this->middleware('can:delete,post')->only(['destroy']); } // الطريقة 5: authorizeResource (تلقائي) public function __construct() { $this->authorizeResource(Post::class, 'post'); } }
ملاحظة: طريقة authorizeResource() تربط تلقائياً طرق وحدة التحكم بطرق السياسة، مما يقلل من كود التفويض النموذجي.

تعريفات البوابات المتقدمة

تعريف بوابات معقدة بمعاملات ومنطق:

// app/Providers/AuthServiceProvider.php public function boot() { $this->registerPolicies(); // بوابة بسيطة Gate::define('view-dashboard', function (User $user) { return $user->hasRole(['admin', 'manager']); }); // بوابة مع مورد Gate::define('assign-role', function (User $user, User $target, string $role) { // لا يمكن تعيين دور لنفسك if ($user->id === $target->id) { return false; } // يمكن فقط تعيين أدوار أقل من دورك return $user->role_level > Role::where('name', $role)->first()->level; }); // بوابة مع موارد متعددة Gate::define('transfer-post', function (User $user, Post $post, User $newOwner) { return $user->id === $post->user_id && $newOwner->hasPermission('own-posts'); }); // بوابة تسمح للمستخدمين الضيوف Gate::define('read-post', function (?User $user, Post $post) { if ($post->is_public) { return true; } return $user && $user->id === $post->user_id; }); // اعتراض فحوصات التفويض Gate::before(function (User $user, string $ability) { // المسؤولون الفائقون يتجاوزون جميع الفحوصات if ($user->hasRole('super-admin')) { return true; } // إرجاع null لمواصلة التفويض العادي return null; }); Gate::after(function (User $user, string $ability, bool|null $result) { // تسجيل إخفاقات التفويض if ($result === false) { Log::info("تم رفض التفويض: {$ability}", [ 'user_id' => $user->id, 'ability' => $ability, ]); } // إرجاع null للحفاظ على النتيجة الأصلية return null; }); }

استجابات السياسة مع الرسائل

إرجاع استجابات مفصلة من السياسات:

use Illuminate\Auth\Access\Response; class PostPolicy { public function update(User $user, Post $post) { if ($user->id === $post->user_id) { return Response::allow(); } if ($post->is_locked) { return Response::deny('هذا المنشور مقفل ولا يمكن تعديله.'); } if ($user->isBanned()) { return Response::deny('تم حظر حسابك.', 403); } return Response::deny('ليس لديك إذن لتعديل هذا المنشور.'); } public function delete(User $user, Post $post) { if ($post->hasComments()) { return Response::denyWithStatus( 422, 'لا يمكن حذف منشور بتعليقات. قم بأرشفته بدلاً من ذلك.' ); } return $user->id === $post->user_id ? Response::allow() : Response::deny('المؤلف فقط يمكنه حذف هذا المنشور.'); } } // المعالجة في وحدة التحكم public function update(Request $request, Post $post) { $response = Gate::inspect('update', $post); if ($response->denied()) { return back()->with('error', $response->message()); } // متابعة التحديث }
تحذير: تحقق دائماً من التفويض قبل تنفيذ العمليات الحساسة، حتى لو كانت واجهة المستخدم تخفي الإجراءات غير المصرح بها. القيود من جانب العميل ليست تدابير أمنية.

التفويض في قوالب Blade

التحكم في عناصر واجهة المستخدم بناءً على التفويض:

{{-- التحقق من القدرة الواحدة --}} @can('update', $post) <a href="{{ route('posts.edit', $post) }}">تعديل</a> @endcan {{-- التحقق مع else --}} @can('delete', $post) <button type="submit">حذف</button> @else <span class="text-muted">لا يمكن الحذف</span> @endcan {{-- التحقق من عدم القدرة --}} @cannot('create', App\Models\Post::class) <p>ليس لديك إذن لإنشاء منشورات.</p> @endcannot {{-- التحقق من قدرات متعددة (OR) --}} @canany(['update', 'delete'], $post) <div class="post-actions"> @can('update', $post) <a href="{{ route('posts.edit', $post) }}">تعديل</a> @endcan @can('delete', $post) <button>حذف</button> @endcan </div> @endcanany {{-- التحقق من البوابة بدون مورد --}} @can('view-dashboard') <a href="{{ route('dashboard') }}">لوحة التحكم</a> @endcan {{-- تفويض الضيف --}} @guest @can('read-post', $post) <p>{{ $post->content }}</p> @endcan @endguest

التفويض لموارد API

تطبيق التفويض في سياقات API:

namespace App\Http\Controllers\Api; class PostController extends Controller { public function index(Request $request) { $this->authorize('viewAny', Post::class); return PostResource::collection( Post::paginate() ); } public function store(Request $request) { $this->authorize('create', Post::class); $post = Post::create($request->validated()); return new PostResource($post); } public function update(Request $request, Post $post) { $response = Gate::inspect('update', $post); if ($response->denied()) { return response()->json([ 'message' => $response->message(), ], $response->status() ?? 403); } $post->update($request->validated()); return new PostResource($post); } // التفويض الجماعي public function bulkDelete(Request $request) { $postIds = $request->input('post_ids'); $posts = Post::findMany($postIds); // التحقق من التفويض لكل منشور foreach ($posts as $post) { if (Gate::denies('delete', $post)) { return response()->json([ 'message' => "لا يمكن حذف المنشور {$post->id}", ], 403); } } // الكل مصرح به، متابعة الحذف Post::destroy($postIds); return response()->json([ 'message' => 'تم حذف المنشورات بنجاح' ]); } }

تسجيل السياسة الديناميكي

تسجيل السياسات برمجياً بناءً على حالة التطبيق:

// app/Providers/AuthServiceProvider.php public function boot() { $this->registerPolicies(); // تسجيل السياسات من قاعدة البيانات $customPolicies = PolicyConfiguration::all(); foreach ($customPolicies as $config) { Gate::define($config->name, function (User $user) use ($config) { return $user->hasPermission($config->required_permission); }); } // تسجيل السياسات من الإضافات $plugins = app(PluginManager::class)->getActivePlugins(); foreach ($plugins as $plugin) { foreach ($plugin->getPolicies() as $modelClass => $policyClass) { Gate::policy($modelClass, $policyClass); } } }

اختبار التفويض

كتابة اختبارات لمنطق التفويض:

// tests/Feature/PostAuthorizationTest.php namespace Tests\Feature; use Tests\TestCase; use App\Models\User; use App\Models\Post; class PostAuthorizationTest extends TestCase { public function test_user_can_update_own_post() { $user = User::factory()->create(); $post = Post::factory()->create(['user_id' => $user->id]); $this->assertTrue($user->can('update', $post)); } public function test_user_cannot_update_others_post() { $user = User::factory()->create(); $post = Post::factory()->create(); $this->assertFalse($user->can('update', $post)); } public function test_admin_can_delete_any_post() { $admin = User::factory()->admin()->create(); $post = Post::factory()->create(); $this->assertTrue($admin->can('delete', $post)); } public function test_unauthorized_update_returns_403() { $user = User::factory()->create(); $post = Post::factory()->create(); $response = $this->actingAs($user) ->put(route('posts.update', $post), [ 'title' => 'عنوان جديد' ]); $response->assertForbidden(); } }
تمرين 1: أنشئ نظام تفويض شامل لمدونة بالقواعد التالية:
1. يمكن للمستخدمين عرض جميع المنشورات المنشورة، ولكن فقط مسوداتهم الخاصة
2. يمكن للمستخدمين تعديل منشوراتهم الخاصة خلال 24 ساعة من الإنشاء
3. يمكن للمحررين تعديل أي منشور، ولكن لا يحذفونها
4. المسؤولون يمكنهم فعل كل شيء
5. المستخدمون المحظورون لا يمكنهم إنشاء أو تعديل المنشورات
نفذ بالسياسات، اختبر جميع السيناريوهات، وأضف توجيهات Blade.
تمرين 2: ابن نظام مشاركة مستندات حيث:
1. مالكو المستندات يمكنهم العرض والتعديل والحذف والمشاركة
2. المتعاونون (دور المحرر) يمكنهم العرض والتعديل
3. المشاهدون يمكنهم العرض فقط
4. نفذ ميزة "نقل الملكية" بتفويض مناسب
5. أضف أذونات على مستوى المجلد تنتقل إلى المستندات
استخدم السياسات بقدرات مخصصة ورسائل استجابة.
تمرين 3: نفذ نظام موافقة متعدد المستويات:
1. المستخدمون العاديون يقدمون الطلبات
2. المديرون يمكنهم الموافقة/الرفض حتى 10,000 دولار
3. المديرون يمكنهم الموافقة/الرفض حتى 50,000 دولار
4. التنفيذيون يمكنهم الموافقة على أي مبلغ
5. المستخدمون يمكنهم عرض طلباتهم الخاصة، المديرون يرون طلبات الفريق
استخدم البوابات بمعاملات متعددة وخطافات قبل/بعد مخصصة لتسجيل جميع فحوصات التفويض.