آنچه در این مقاله میخوانید [پنهانسازی]
تفاوت Gate و Policy در Laravel به طور خلاصه این است: Gate برای مجوزهای ساده و سراسری (بر اساس یک نام توانایی) استفاده میشود، و Policy برای قوانین سطح مدل با متدهای ساختیافته. اگر مجوز شما به یک مدل خاص وابسته است یا احتمال رشد و پیچیدگی دارد، Policy انتخاب بهتری است؛ اگر قانون سبک، نقش محور یا سراسری است (مثل مشاهده داشبورد ادمین یا یک Feature toggle)، Gate کفایت میکند.
تفاوت Gate و Policy در لاراول چیست؟
Gate مجموعهای از قوانین سبک و مبتنی بر نام توانایی (ability) است که معمولا با کلوزر تعریف میشوند؛ مثلا view-admin یا access-report. Policy یک کلاس اختصاصی برای هر مدل دامنه (مثل PostPolicy برای Post) است که متدهای استانداردی مانند view، create، update و delete دارد. نتیجه این تفاوت:
- اگر سناریو به یک مدل گره خورده است (مالکیت رکورد، وضعیت انتشار، چند-سازمانی)، Policy ساختار تمیزتر و تستپذیرتری میدهد.
- اگر سناریو مدلمحور نیست (نقشهای کلی، دسترسی به بخشهای سراسری، قفل موقت یک قابلیت)، Gate سریعتر و سرراستتر است.
معیار انتخاب سریع
- قانون به یک مدل وابسته است؟ Policy.
- CRUD روی یک مدل (view/update/delete و…) دارید؟ Policy.
- قانون سراسری یا نقشمحور است (بدون مدل خاص)؟ Gate.
- میخواهید قوانین را سازماندهی و مستند کنید؟ Policy.
- قانون موقتی/کوچک است یا فقط یک «پرچم ویژگی» دارید؟ Gate.
- میخواهید ادمین ارشد همه چیز را دور بزند؟ Gate::before مناسب است، چه با Gate چه با Policy.
Gate در عمل: تعریف، استفاده و نکات
هدف: تعریف یک دسترسی ساده برای مشاهده پنل ادمین و یک دورزن عمومی برای ادمین ارشد.
<?php
namespace App\Providers;
use App\Models\User;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
class AuthServiceProvider extends ServiceProvider
{
public function boot(): void
{
// توانایی سراسری
Gate::define('view-admin', function (User $user): bool {
return (bool) $user->is_admin;
});
// دورزن جهانی؛ اگر true برگردد، سایر قوانین بررسی نمیشوند
Gate::before(function (User $user, string $ability) {
return $user->is_super_admin ? true : null;
});
}
}
استفاده در کنترلر:
<?php
use Illuminate\Support\Facades\Gate;
public function adminDashboard()
{
Gate::authorize('view-admin'); // در صورت عدم مجوز، 403 پرتاب میکند
// ...
}
استفاده در Blade:
@can('view-admin')
<!-- محتوای مخصوص ادمین -->
@endcan
نکتهها:
- برای بررسی دستی میتوانید از
Gate::allowsوGate::deniesاستفاده کنید. Gate::beforeوGate::afterبرای قوانین فراگیر مفیدند؛ اما مراقب باشید ناخواسته همه چیز را باز نکنید.
Policy در عمل: ساخت، ثبت و فراخوانی
هدف: فقط نویسنده یا ادمین بتواند یک پست را ویرایش کند؛ نمایش پست عمومی برای همه آزاد باشد.
# ساخت Policy (نمونه دستور Artisan)
php artisan make:policy PostPolicy --model=Post
نمونه کلاس Policy:
<?php
namespace App\Policies;
use App\Models\Post;
use App\Models\User;
class PostPolicy
{
// مهم: اگر میخواهید مهمان هم بررسی شود، از ?User استفاده کنید
public function view(?User $user, Post $post): bool
{
return $post->is_public || ($user && $user->id === $post->user_id);
}
public function update(User $user, Post $post): bool
{
return $user->id === $post->user_id || (bool) $user->is_admin;
}
public function delete(User $user, Post $post): bool
{
return $user->id === $post->user_id;
}
}
ثبت Policy (اگر کشف خودکار فعال نیست):
<?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(): void
{
// ...
}
}
استفاده در کنترلر:
<?php
public function update(Post $post)
{
$this->authorize('update', $post); // 403 در صورت عدم مجوز
// ...
}
استفاده در Route Middleware:
<?php
use Illuminate\Support\Facades\Route;
Route::patch('/posts/{post}', [PostController::class, 'update'])
->middleware('can:update,post');
استفاده در Blade:
@can('update', $post)
<button>ویرایش</button>
@endcan
مقایسه بر اساس ابعاد کاربردی
| معیار | Gate | Policy |
|---|---|---|
| دامنه | سراسری/ویژگیمحور | مدلمحور (Post, Order, …) |
| ساختار | کلوزرهای پراکنده با نام توانایی | کلاسهای منسجم با متدهای استاندارد |
| مقیاسپذیری | برای قوانین کم و ساده | برای قوانین زیاد و در حال رشد |
| استفاده در Middleware | can:ability | can:ability,model |
| تستپذیری | خوب، ولی پراکندگی بیشتر | بسیار خوب، منطبق با مدل |
| دورزن جهانی | Gate::before/after | قابل استفاده همراه با Policy |
سناریوهای واقعی و انتخاب مناسب
- نمایش منوی ادمین یا ورود به داشبورد: Gate با توانایی
view-admin. - ویرایش پست توسط صاحب پست یا ادمین: Policy روی مدل Post با متد
update. - قفلکردن یک قابلیت در بازه زمانی نگهداری: Gate (مثلا
feature-enabled). - سیستم چندسازمانی (tenant) که هر کاربر فقط دادههای سازمان خودش را ببیند: Policy روی همه مدلهای مرتبط تا منطق به هر مدل نزدیک بماند.
اشتباهات رایج و نکات امنیتی
- ثبتنکردن Policy در AuthServiceProvider (وقتی کشف خودکار فعال نیست) که باعث بیاثر شدن آن میشود.
- ناهماهنگی نام تواناییها: در Policy از نامهای استاندارد (
view،update…) استفاده کنید تا باauthorizeوcanسازگار بماند. - اتکا به فرانتاند: دکمه را در Blade پنهان میکنید ولی بررسی سمت سرور در کنترلر را فراموش میکنید. همیشه در سرور هم authorize کنید.
- استفاده نادرست از
Gate::before: اگر بیمحاباtrueبرگردانید، عملا همه چیز باز میشود. فقط برای نقشهای مشخص و مطمئن استفاده کنید. - عدم توجه به کاربر مهمان: اگر دسترسی برای مهمان هم باید ارزیابی شود، امضای متد Policy را
?Userبگذارید و null را مدیریت کنید. - اجرا پس از بازیابی داده حساس: در عملیات مهم، قبل از کار با داده،
authorizeرا صدا بزنید تا از افشای ناخواسته جلوگیری شود. - نادیدهگرفتن پاسخهای قابل توضیح: اگر نیاز به پیام خطای دقیق دارید، به جای bool از
\Illuminate\Auth\Access\Responseدر Policy برگردانید.
روش بررسی نتیجه و تست
دو مسیر برای اطمینان از صحت پیادهسازی وجود دارد: تست رفتار برنامه و تست مستقیم مجوز.
تست رفتار با HTTP تستها
<?php
public function test_non_owner_cannot_update_post(): void
{
$user = \App\Models\User::factory()->create();
$post = \App\Models\Post::factory()->create(); // owner متفاوت است
$this->actingAs($user)
->patch(route('posts.update', $post), ['title' => 'X'])
->assertForbidden();
}
public function test_owner_can_update_post(): void
{
$owner = \App\Models\User::factory()->create();
$post = \App\Models\Post::factory()->for($owner)->create();
$this->actingAs($owner)
->patch(route('posts.update', $post), ['title' => 'New'])
->assertOk();
}
تست مستقیم Gate/Policy
<?php
use Illuminate\Support\Facades\Gate;
public function test_gate_view_admin_is_blocked_for_regular_user(): void
{
$user = \App\Models\User::factory()->create(['is_admin' => false]);
$this->assertFalse(Gate::forUser($user)->allows('view-admin'));
}
کدام را کجا استفاده کنیم؟ نتیجه نهایی
برای هر چیزی که به یک مدل مربوط است، با Policy شروع کنید؛ نامگذاری استاندارد و ساختار کلاسمحور، نگهداری و تست را ساده میکند. برای قوانین سراسری، نقشهای کلی و پرچمهای ویژگی، Gate انتخاب بهینه است. در پروژههای واقعی ترکیبی از هر دو لازم میشود: نقشهای پایه با Gate، و مجوزهای جزئی CRUD با Policy. مستند کنید که هر توانایی کجا تعریف شده و قبل از هر اقدام حساس، authorize را فراخوانی کنید.







