مقاله

بازاستفاده از شرط‌های query با scope در Laravel: published، active، برایAdmin و global scope برای soft delete منطقی.

Scope در Eloquent — local scope، global scope و الگوهای کاربردی

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() ترکیب کنید.

refactor query