معظم الأنظمة تبدأ بفكرة بسيطة جدًا للصلاحيات: مستخدم عادي ومدير. في البداية يكفي شرط واحد داخل الكود:
if ($user->is_admin) {
// allow
}
لكن هذا النوع من الحلول ينهار بسرعة مع توسع النظام. فجأة تظهر أسئلة مثل:
- هل يمكن للمستخدم أن يكون لديه أكثر من دور؟
- هل الصلاحيات مرتبطة بالدور أم بالمستخدم مباشرة؟
- كيف يمكن تغيير الصلاحيات بدون تعديل الكود؟
- كيف يمكن إدارة الصلاحيات من لوحة التحكم؟
هنا تظهر الحاجة إلى نظام صلاحيات مرن. مكتبة spatie/laravel-permission أصبحت من أكثر الحلول استخدامًا في Laravel لأنها تقدم طبقة جاهزة لإدارة الأدوار والصلاحيات دون إعادة اختراع النظام من الصفر.
الفكرة الأساسية للنظام
المكتبة مبنية على مفهومين رئيسيين:
- Role
- Permission
الصلاحية تمثل فعلًا محددًا في النظام مثل:
- create-post
- edit-post
- delete-user
أما الدور فهو مجموعة من الصلاحيات.
مثلًا:
- admin
- editor
- author
الفكرة أن المستخدم يمكن أن يمتلك:
- دورًا
- أو عدة أدوار
- أو صلاحيات مباشرة بدون دور
هيكل قاعدة البيانات الذي تنشئه المكتبة
عند تثبيت المكتبة يتم إنشاء عدة جداول.
أهمها:
- roles
- permissions
- model_has_roles
- model_has_permissions
- role_has_permissions
هذا التصميم يسمح بعلاقات متعددة بين المستخدمين والأدوار والصلاحيات.
بنية العلاقات تقريبًا تكون كالتالي:
- المستخدم يمكن أن يمتلك عدة أدوار
- الدور يمكن أن يمتلك عدة صلاحيات
- المستخدم يمكن أن يمتلك صلاحيات مباشرة
وهذا يعني أن النظام يدعم:
- Many to Many relations
إضافة Trait إلى نموذج المستخدم
لكي يعمل النظام يجب إضافة trait إلى نموذج المستخدم:
use Spatie\Permission\Traits\HasRoles;
class User extends Authenticatable
{
use HasRoles;
}
هذا الـ Trait يضيف عدة وظائف للنموذج مثل:
- assignRole
- givePermissionTo
- hasRole
- hasPermissionTo
ويوفر أيضًا علاقات Eloquent جاهزة.
إنشاء الأدوار والصلاحيات
يمكن إنشاء الصلاحيات مباشرة عبر الكود:
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;
Permission::create(['name' => 'edit posts']);
Role::create(['name' => 'admin']);
بعد ذلك يمكن ربط الصلاحيات بالدور:
$role = Role::findByName('admin');
$role->givePermissionTo('edit posts');
أو إعطاء صلاحية مباشرة للمستخدم:
$user->givePermissionTo('edit posts');
إسناد الأدوار للمستخدم
ربط الدور بالمستخدم يتم ببساطة:
$user->assignRole('admin');
يمكن أيضًا إسناد عدة أدوار:
$user->assignRole(['admin','editor']);
المكتبة تتكفل بإدارة العلاقة داخل جدول:
model_has_roles
التحقق من الصلاحيات
التحقق يمكن أن يتم بعدة طرق.
التحقق من الدور
if ($user->hasRole('admin')) {
//
}
التحقق من الصلاحية
if ($user->can('edit posts')) {
//
}
المكتبة تتكامل مباشرة مع نظام authorization في Laravel.
استخدام Middleware
يمكن حماية المسارات بسهولة.
Route::get('/admin', function () {
//
})->middleware('role:admin');
أو باستخدام الصلاحيات:
Route::get('/posts', function () {
//
})->middleware('permission:edit posts');
بهذا الشكل يصبح نظام الحماية جزءًا من طبقة التوجيه.
التخزين المؤقت للصلاحيات
أحد التفاصيل المهمة في المكتبة هو استخدام cache للصلاحيات.
السبب بسيط: التحقق من الصلاحيات يحدث كثيرًا أثناء الطلب.
لذلك تقوم المكتبة بتخزين الصلاحيات في cache لتقليل الاستعلامات.
عند تغيير الصلاحيات يجب مسح الكاش:
php artisan permission:cache-reset
المشاكل الشائعة عند استخدام المكتبة
الخلط بين الدور والصلاحية
الدور ليس بديلاً للصلاحية.
الدور مجرد مجموعة صلاحيات.
إنشاء صلاحيات كثيرة جدًا
بعض الأنظمة تنشئ صلاحية لكل زر داخل التطبيق.
هذا يجعل النظام معقدًا جدًا في الإدارة.
عدم التفكير في الصلاحيات أثناء تصميم النظام
إضافة نظام الصلاحيات بعد بناء النظام غالبًا يؤدي إلى مشاكل.
الأفضل التفكير في:
- الأدوار الأساسية
- الصلاحيات الرئيسية
- علاقة الصلاحيات بالموارد
متى لا تكون هذه المكتبة كافية
في الأنظمة الكبيرة قد تحتاج إلى ما يسمى:
- ACL
- Attribute Based Access Control
حيث تعتمد الصلاحية على:
- المستخدم
- المورد
- السياق
مثل:
- المستخدم يمكنه تعديل مقالاته فقط
في هذه الحالة يتم دمج المكتبة مع Policies الخاصة بـ Laravel.