الگوی Query Class برای کوئریهای Eloquent
خلاصهٔ کاملتر
به گفتهٔ نویسنده، Eloquent خودش لایهٔ دیتابیس خیلی خوبیه و نوشتن مستقیم کوئری تو کنترلر یا جاب یا کامند کاملاً قابلقبوله. ولی وقتی پروژه بزرگ میشه، بعضی کوئریها دیگه یه جزئیات پیادهسازی ساده نیستن و بخشی از منطق کسبوکار میشن. همون فیلترها تو داشبورد، اکسپورت، ریپورت و API تکرار میشن و اونجاست که به قول نویسنده، کوئری لیاقت یه اسم رو پیدا میکنه.
اینجا الگوی Eloquent Query Class کمک میکنه: یه آبجکت کوچیک که یه کوئری یا عملیات دیتابیسی مشخص و بانام رو نمایندگی میکنه. نویسنده میگه بهترین راه فکرکردن بهش اینه که الگوی اکشنکلاس رو رو کوئریها اعمال کنی؛ یعنی یه نقطهٔ ورود عمومی به اسم handle() که ورودی میگیره، کوئری Eloquent رو میسازه یا اجرا میکنه و نتیجه (کالکشن، پجینیتور، مدل، عدد، یا حتی یه Builder) رو برمیگردونه.
final readonly class PendingOrdersQuery
{
public function handle(?int $merchantId = null): LengthAwarePaginator
{
return Order::query()
->whereIn('status', [OrderStatus::Pending, OrderStatus::PaymentFailed])
->when($merchantId, fn ($q) => $q->where('merchant_id', $merchantId))
->latest()
->paginate(50);
}
}با این کار کنترلر کوچیک میشه: هنوز میدونه که سفارشهای در انتظار رو میخواد، ولی دیگه تعریف کاملِ «سفارشهایی که نیاز به رسیدگی دارن» رو نمیدونه؛ اون تعریف حالا یه اسم داره. نویسنده تأکید میکنه که این با ریپازیتوری فرق داره: کوئری کلاس یه CRUD جنریک نیست، وانمود نمیکنه دیتابیس وجود نداره و قصد نداره بعداً MySQL رو با یه API بیرونی عوض کنه؛ فقط رو یه چیز تمرکز داره، متمرکزکردن کوئریهای مهم.
دربارهٔ اسکوپها هم نویسنده میگه local scope برای محدودیتهای کوچیک و قابلاستفادهٔ مجدد که طبیعتاً به مدل تعلق دارن، معمولاً اولین ابزاریه که سراغش میره. ولی وقتی کوئری چند اسکوپ رو هماهنگ میکنه، چند فیلتر اختیاری داره، ایگر لود یا aggregate یا صفحهبندی و مرتبسازی و سرچ داره، یا باید جدا تست بشه، اونوقت کوئری کلاس انتخاب بهتریه. این دوتا با هم هم کار میکنن؛ یه کوئری کلاس میتونه از همون اسکوپها استفاده کنه.
نتیجه اینکه هدف نویسنده معماریبازی نیست، بلکه جلوگیری از پخششدن منطق مهم دیتابیس تو کل کدبیسه. کوئری کلاس اونجا نیست چون Eloquent بده؛ اونجاست چون بعضی کوئریها اونقدر مهم هستن که ارزش یه اسم و یه تست مستقل رو دارن.
نکات کلیدی:
- کوئری کلاس یه کوئری مهم و بانام رو با یه متد ورودی handle() جمع میکنه
- مثل الگوی اکشنکلاس عمل میکنه و Eloquent رو قایم نمیکنه
- با ریپازیتوری فرق داره؛ CRUD جنریک نیست و یه هدف مشخص داره
- local scope برای محدودیتهای کوچیک بهتره؛ کوئری کلاس برای یوزکیسهای پیچیده و پرتکرار
- اسکوپ و کوئری کلاس میتونن کنار هم استفاده بشن




