Scope در Eloquent — local scope، global scope و الگوهای کاربردی
سومین باری که where('status', 'published') را در پنج Controller کپی میکنید، وقت scope است. Scope در Eloquent همان DRY برای query است — readable، testable و در مدل متمرکز. در این پورتفولیو Post::published() در repository استفاده میشود؛ الگوی استاندارد Laravel.
Local Scope
متد با پیشوند scope — فراخوانی بدون پیشوند:
// Post.php
public function scopePublished(Builder $query): void
{
$query->where('status', PostStatus::Published)
->where('published_at', '<=', now());
}
public function scopeFeatured(Builder $query): void
{
$query->where('is_featured', true);
}
// استفاده
Post::published()->featured()->latest('published_at')->paginate(9);
Scope با پارامتر
public function scopeInCategory(Builder $query, string $slug): void
{
$query->whereHas('category', fn ($q) => $q->where('slug', $slug));
}
Post::published()->inCategory('laravel')->get();
ترکیب scopeها
chain مثل fluent interface — ترتیب معمولاً مهم نیست مگر join.
Global Scope
روی هر query اعمال میشود — مثلاً فقط tenant_id فعلی:
#[ScopedBy([ActiveScope::class])]
class Product extends Model {}
// یا booted
protected static function booted(): void
{
static::addGlobalScope('active', function (Builder $builder) {
$builder->where('is_active', true);
});
}
حذف موقت:
Product::withoutGlobalScope('active')->find($id);
تفاوت Scope و Repository
Scope شرط دیتابیس؛ Repository ترکیب query + caching + pagination. در معماری لایهای هر دو جای خود را دارند.
Scope vs Accessor
Scope قبل از fetch فیلتر میکند؛ accessor بعد از load روی attribute. اشتباه: فیلتر published در PHP collection.
تست scope
it('filters published posts', function () {
Post::factory()->published()->create();
Post::factory()->draft()->create();
expect(Post::published()->count())->toBe(1);
});
اشتباهات
- global scope مخفی که admin panel را میشکند
- scope با side effect (ارسال event)
- نام گیجکننده scopeOrderBy — فقط فیلتر باشد
جمعبندی
هر where تکراری → local scope. شرط همیشگی همه queryها → global با احتیاط. با with() ترکیب کنید.